Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ license = "MIT"

[dependencies]

# Proc macros
macros = { path = "./macros" }

# For colored terminal output
colored = "2"

Expand Down
11 changes: 11 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "macros"
version = "0.0.0"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
quote = "1.0.9"
syn = "1.0.71"
104 changes: 104 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use proc_macro::TokenStream;
use syn::{Data, DeriveInput, Type, spanned::Spanned};
use quote::{quote, quote_spanned, format_ident};

macro_rules! error {
($span:expr, $msg:expr) => {
syn::Error::new($span, $msg).to_compile_error().into()
};
}

enum ImplType {
Iter,
Single
}

// Returns impl type and the inner type
fn kind(ty: &Type) -> Result<(ImplType, &Type), TokenStream> {
Ok(match ty {
Type::Slice(ts) => {
(ImplType::Iter, ts.elem.as_ref())
}
Type::Array(ta) => {
(ImplType::Iter, ta.elem.as_ref())
}
Type::Reference(tr) => {
// Recurse!
kind(tr.elem.as_ref())?
}
Type::Path(_) => {
(ImplType::Single, ty)
}
_ => return Err(error!(ty.span(), "Representation type is not a regular type, array, nor slice"))
})
}

#[proc_macro_derive(Searchable, attributes(representation))]
pub fn searchable_derive(ts: TokenStream) -> TokenStream {
let ast = syn::parse(ts).unwrap();
searchable_impl(&ast)
}

fn searchable_impl(ast: &DeriveInput) -> TokenStream {

// Find tagged field
let field = match &ast.data {
Data::Struct(struc) => {
struc.fields.iter().find(|fields| {
fields.attrs.iter().any(|attr| attr.path.is_ident("representation"))
})
},
_ => return error!(ast.span(), "`Searchable` can only derive structs.")
};

let field = if let Some(field) = field {
field
} else {
return error!(ast.span(), "No `#[representable]` annotated member.")
};

let ty_span = field.ty.span();

// Determine impl type and get inner type of representation
let (kind, ty) = match kind(&field.ty) {
Ok(a) => a,
Err(ts) => return ts
};

let name = &ast.ident;

// Enforce that the representation type has to be `AsRef<str>`
let assert_ident = format_ident!("{}AssertAsRefStr", name);
let assert_as_ref_str = quote_spanned! {ty_span=>
struct #assert_ident where #ty: AsRef<str>;
};

let ident = field.ident.as_ref().unwrap();
let impl_ = match kind {
ImplType::Iter => quote! {
self.#ident
.iter()
.find(|repr| search.to_lowercase().starts_with(&repr.to_lowercase()))
.map(|repr| (self, repr.len()))
},
ImplType::Single => quote! {
if search.starts_with(&self.#ident) {
Some((self, self.#ident.len()))
} else {
None
}
}
};

let gen = quote! {
#[allow(dead_code)]
#assert_as_ref_str
impl Searchable for #name {
fn search<'a>(&'a self, search: &str) -> Option<(&'a Self, usize)> {
#impl_
}
}
};

gen.into()
}
2 changes: 1 addition & 1 deletion src/lib/eval.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{constants::Constant, errors::Error, operators::Operator, tokens::Token};
use super::model::{constants::Constant, errors::Error, operators::Operator, tokens::Token};

pub fn eval(tokens: &[Token]) -> Result<f64, Error> {
// We need a mutable copy of the tokens
Expand Down
14 changes: 5 additions & 9 deletions src/lib/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
pub mod constants;
pub mod errors;
mod eval;
pub mod operators;
mod representable;
mod rpn;
mod tokenize;
pub mod tokens;

pub mod utils;
pub mod variables;

use errors::Error;
pub mod model;

use eval::eval;
use rpn::rpn;
use tokenize::tokenize;
use tokens::Token;
use variables::Variable;

use self::model::{errors::Error, tokens::Token, variables::Variable};

pub fn doeval<'a>(string: &str, vars: &'a [Variable]) -> Result<(f64, Vec<Token<'a>>), Error> {
let tokens = tokenize(string, vars)?;
Expand Down
16 changes: 8 additions & 8 deletions src/lib/constants.rs → src/lib/model/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![allow(clippy::non_ascii_literal)]

use super::representable::{get_by_repr, Representable};
use super::searchable::Searchable;
use macros::Searchable;

use super::searchable::get_by_repr;

#[allow(clippy::upper_case_acronyms)]
#[derive(PartialEq, Clone, Copy, Debug)]
Expand All @@ -10,18 +13,15 @@ pub enum ConstantType {
Tau,
}

#[derive(Searchable)]
pub struct Constant {
pub kind: ConstantType,

#[representation]
pub repr: &'static [&'static str],
pub value: f64,
}

impl Representable for Constant {
fn repr(&self) -> &[&str] {
self.repr
}
}

static CONSTANTS: &[Constant] = &[
Constant {
kind: ConstantType::PI,
Expand All @@ -44,7 +44,7 @@ impl Constant {
pub fn by_type(kind: ConstantType) -> &'static Self {
CONSTANTS.iter().find(|c| c.kind == kind).unwrap()
}
pub fn by_repr(repr: &str) -> Option<(&'static Self, usize)> {
pub fn by_repr(repr: &str) -> Option<(&Self, usize)> {
get_by_repr(repr, CONSTANTS)
}
pub fn is(repr: &str) -> bool {
Expand Down
File renamed without changes.
6 changes: 6 additions & 0 deletions src/lib/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod constants;
pub mod errors;
pub mod operators;
mod searchable;
pub mod tokens;
pub mod variables;
26 changes: 10 additions & 16 deletions src/lib/operators.rs → src/lib/model/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use rand::Rng;

use super::representable::{get_by_repr, Representable};
use super::searchable::Searchable;
use macros::Searchable;

use super::searchable::get_by_repr;

#[derive(PartialEq, Clone, Copy, Debug)]
pub enum OperatorType {
Expand All @@ -27,46 +30,37 @@ pub enum OperatorType {

const UNARY_OPERATORS: &[OperatorType] = &[OperatorType::Positive, OperatorType::Negative];

impl Representable for OperatorType {
fn repr(&self) -> &'static [&'static str] {
Operator::by_type(*self).repr
}
}

#[derive(Clone, PartialEq, Copy)]
pub enum Associativity {
Left,
Right,
}

#[derive(Clone, Copy)]
#[derive(Searchable)]
pub struct Operator {
pub kind: OperatorType,

#[representation]
pub repr: &'static [&'static str],
pub precedence: u8,
pub associativity: Associativity,
pub arity: usize,
pub doit: fn(&[f64]) -> f64,
}

impl Representable for Operator {
fn repr(&self) -> &[&str] {
self.repr
}
}

impl Operator {
pub fn by_type(kind: OperatorType) -> &'static Self {
OPERATORS.iter().find(|op| op.kind == kind).unwrap()
}
pub fn by_repr(repr: &str) -> Option<(&'static Self, usize)> {
pub fn by_repr(repr: &str) -> Option<(&Self, usize)> {
get_by_repr(repr, OPERATORS)
}
pub fn is(repr: &str) -> bool {
Self::by_repr(repr).is_some()
}
pub fn unary(repr: &str) -> Option<(&OperatorType, usize)> {
get_by_repr(repr, UNARY_OPERATORS)
let ops = UNARY_OPERATORS.iter().map(|&uop| Self::by_type(uop));
get_by_repr(repr, ops).map(|(op, idx)| (&op.kind, idx))
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/lib/model/searchable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub trait Searchable {
fn search<'a>(&'a self, search: &str) -> Option<(&'a Self, usize)>;
}

pub(super) fn get_by_repr<'a, T, L>(search: &str, list: L) -> Option<(&'a T, usize)>
where
T: Searchable,
L: IntoIterator<Item = &'a T>,
{
list.into_iter().find_map(|t| t.search(search))
}
2 changes: 1 addition & 1 deletion src/lib/tokens.rs → src/lib/model/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Token<'_> {
ParenType::Right => ')'.to_string(),
},
Self::Constant { kind } => Constant::by_type(*kind).repr[0].to_string(),
Self::Variable { inner } => format!("${}", inner.repr),
Self::Variable { inner } => format!("${}", inner.name),
}
}
}
Loading