summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <daniel.silverstone@codethink.co.uk>2018-05-22 17:08:59 +0100
committerDaniel Silverstone <daniel.silverstone@codethink.co.uk>2018-05-22 17:08:59 +0100
commit2dbab9da4669ace4ffe761820373840fbfe84790 (patch)
treefe48435ade31fe812551247f22898ebf7ceaf61a
parenta57e7a2a4d6a84d4fbcca3a3955c93cedc9acdd5 (diff)
downloadcanopied-2dbab9da4669ace4ffe761820373840fbfe84790.tar.bz2
Expression parser and expression handler
-rw-r--r--Cargo.toml6
-rw-r--r--exprparser/Cargo.toml8
-rw-r--r--exprparser/design.mdwn18
-rw-r--r--exprparser/src/lib.rs11
-rw-r--r--exprparser/src/parser.rs243
-rw-r--r--exprparser/src/types.rs62
-rw-r--r--qexpr/Cargo.toml8
-rw-r--r--qexpr/src/lib.rs31
8 files changed, 387 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5350068..bf79fbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,5 +18,11 @@ tokio = "0.1.5"
[dependencies.qvalues]
path = "qvalues"
+[dependencies.exprparser]
+path = "exprparser"
+
+[dependencies.qexpr]
+path = "qexpr"
+
[workspace]
diff --git a/exprparser/Cargo.toml b/exprparser/Cargo.toml
new file mode 100644
index 0000000..df04c1d
--- /dev/null
+++ b/exprparser/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "exprparser"
+version = "0.1.0"
+authors = ["Daniel Silverstone <daniel.silverstone@codethink.co.uk>"]
+
+[dependencies]
+nom = {version = "4.0", features = [ "verbose-errors"]}
+log = "0.4.1"
diff --git a/exprparser/design.mdwn b/exprparser/design.mdwn
new file mode 100644
index 0000000..84ddef3
--- /dev/null
+++ b/exprparser/design.mdwn
@@ -0,0 +1,18 @@
+# Expression parser for Canopied
+
+Expressions are simple (for now):
+
+ expr ::= <term> "+" <expr>
+ | <term> "-" <expr>
+ | <term>
+
+ term ::= <factor> "*" <term>
+ | <factor> "/" <term>
+ | <factor>
+
+ factor ::= "(" <expr> ")"
+ | <const>
+
+ <const> ::= integer | floatynumber
+
+We will use Nom, and construct an expression parse tree as our output.
diff --git a/exprparser/src/lib.rs b/exprparser/src/lib.rs
new file mode 100644
index 0000000..233e095
--- /dev/null
+++ b/exprparser/src/lib.rs
@@ -0,0 +1,11 @@
+#[macro_use]
+extern crate nom;
+
+#[macro_use]
+extern crate log;
+
+mod parser;
+mod types;
+
+pub use parser::*;
+pub use types::*;
diff --git a/exprparser/src/parser.rs b/exprparser/src/parser.rs
new file mode 100644
index 0000000..625a231
--- /dev/null
+++ b/exprparser/src/parser.rs
@@ -0,0 +1,243 @@
+use types::*;
+
+use nom::double_s;
+
+named!(namepart<&str, &str>,
+ recognize!(
+ do_parse!(
+ one_of!("_abcdefghijklmnopqrstuvwxyz")
+ >>
+ many0!(complete!(one_of!("_abcdefghijklmnopqrstuvwxyz0123456789")))
+ >>
+ ()
+ )
+ )
+);
+
+named!(symbol<&str, &str>,
+ recognize!(
+ do_parse!(
+ complete!(namepart) >>
+ many0!(
+ complete!(do_parse!(
+ char!('.') >>
+ namepart >>
+ ()
+ ))
+ ) >>
+ ()
+ )
+ )
+);
+
+named!(factor<&str,Factor>,
+ws!(alt_complete!(
+ symbol => { |sym: &str| Factor::new_symbol(sym.to_owned()) }
+ |
+ delimited!(tag_s!("("), expression, tag_s!(")")) => { |expr| Factor::new_expression(expr) }
+ |
+ double_s => { |val| Factor::new_value(val) }
+)));
+
+named!(term<&str,Term>,
+ alt_complete!(
+ do_parse!(
+ left: factor >>
+ op: alt!(char!('*') | char!('/')) >>
+ right: term >>
+ (if op == '*' {
+ Term::new_mul(left, right)
+ } else {
+ Term::new_div(left, right)
+ })
+ ) |
+ do_parse!(
+ left: factor >>
+ (Term::new_factor(left))
+ )
+ )
+);
+
+named!(expression<&str, Expression>,
+ alt_complete!(
+ do_parse!(
+ left: term >>
+ op: alt!(char!('+') | char!('-')) >>
+ right: expression >>
+ (if op == '+' {
+ Expression::new_add(left, right)
+ } else {
+ Expression::new_sub(left, right)
+ })
+ ) |
+ do_parse!(
+ left: term >>
+ (Expression::new_term(left))
+ )
+ )
+);
+
+pub fn parse_expression(s: &str) -> Option<Expression> {
+ let mut expr = String::from(s);
+ expr.push('\0');
+ let result = expression(&expr);
+ trace!("Result: {:?}", result);
+ if let Ok((rest, expret)) = result {
+ if rest.len() == 1 {
+ Some(expret)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ #[test]
+ fn parse_namepart() {
+ let (left, s) = namepart("foobar\0").expect("Should parse");
+ assert!(left.len() == 1);
+ assert!(s == "foobar");
+ }
+
+ #[test]
+ fn parse_symbol() {
+ let (left, s) = symbol("foobar.baz.meta\0").expect("Should parse");
+ assert!(left.len() == 1);
+ assert!(s == "foobar.baz.meta");
+ }
+
+ #[test]
+ fn parse_symbol_factor() {
+ let (left, s) = factor("foobar\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Factor::Symbol(ref name) => assert!(name == "foobar"),
+ _ => panic!("Wrong kind of Factor."),
+ };
+ }
+
+ #[test]
+ fn parse_number_factor() {
+ let (left, s) = factor("1.5\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of Factor."),
+ };
+ }
+
+ #[test]
+ fn parse_number_term() {
+ let (left, s) = term("1.5\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Term::Factor(bf) => match *bf {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of Factor."),
+ },
+ _ => panic!("Wrong kind of Term."),
+ };
+ }
+
+ #[test]
+ fn parse_symbol_term() {
+ let (left, s) = term("foobar.baz\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Term::Factor(bf) => match *bf {
+ Factor::Symbol(ref name) => assert!(name == "foobar.baz"),
+ _ => panic!("Wrong kind of Factor."),
+ },
+ _ => panic!("Wrong kind of Term."),
+ };
+ }
+
+ #[test]
+ fn parse_product_term() {
+ let (left, s) = term("1.5 * 2.1\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Term::Mul(bf, bt) => {
+ match *bf {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of Factor."),
+ };
+ match *bt {
+ Term::Factor(bf) => match *bf {
+ Factor::Value(v) => assert!(v == 2.1),
+ _ => panic!("Wrong kind of second Factor."),
+ },
+ _ => panic!("Wrong kind of second Term."),
+ };
+ }
+ _ => panic!("Wrong kind of Term."),
+ };
+ }
+
+ #[test]
+ fn parse_division_term() {
+ let (left, s) = term("1.5 / 2.1\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Term::Div(bf, bt) => {
+ match *bf {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of Factor."),
+ };
+ match *bt {
+ Term::Factor(bf) => match *bf {
+ Factor::Value(v) => assert!(v == 2.1),
+ _ => panic!("Wrong kind of second Factor."),
+ },
+ _ => panic!("Wrong kind of second Term."),
+ };
+ }
+ _ => panic!("Wrong kind of Term."),
+ };
+ }
+
+ #[test]
+ fn parse_number_expression() {
+ let (left, s) = expression("1.5\0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Expression::Term(bt) => match *bt {
+ Term::Factor(bf) => match *bf {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of Factor."),
+ },
+ _ => panic!("Wrong kind of Term."),
+ },
+ _ => panic!("Wrong kind of Expression."),
+ };
+ }
+
+ #[test]
+ fn parse_parens_number_expression() {
+ let (left, s) = expression(" ( 1.5 ) \0").expect("Should parse");
+ assert!(left.len() == 1);
+ match s {
+ Expression::Term(bt) => match *bt {
+ Term::Factor(bf) => match *bf {
+ Factor::Expression(be) => match *be {
+ Expression::Term(bbt) => match *bbt {
+ Term::Factor(bbf) => match *bbf {
+ Factor::Value(v) => assert!(v == 1.5),
+ _ => panic!("Wrong kind of inner Factor."),
+ },
+ _ => panic!("Wrong kind of inner Term."),
+ },
+ _ => panic!("Wrong kind of inner Expression."),
+ },
+ _ => panic!("Wrong kind of outer Factor"),
+ },
+ _ => panic!("Wrong kind of outer Term."),
+ },
+ _ => panic!("Wrong kind of outer Expression."),
+ };
+ }
+}
diff --git a/exprparser/src/types.rs b/exprparser/src/types.rs
new file mode 100644
index 0000000..8551661
--- /dev/null
+++ b/exprparser/src/types.rs
@@ -0,0 +1,62 @@
+#[derive(Clone, Debug)]
+pub enum Expression {
+ Add(Box<Term>, Box<Expression>),
+ Sub(Box<Term>, Box<Expression>),
+ Term(Box<Term>),
+}
+
+#[derive(Clone, Debug)]
+pub enum Term {
+ Mul(Box<Factor>, Box<Term>),
+ Div(Box<Factor>, Box<Term>),
+ Factor(Box<Factor>),
+}
+
+#[derive(Clone, Debug)]
+pub enum Factor {
+ Expression(Box<Expression>),
+ Value(f64),
+ Symbol(String),
+}
+
+impl Expression {
+ pub fn new_add(t: Term, e: Expression) -> Expression {
+ Expression::Add(Box::new(t), Box::new(e))
+ }
+
+ pub fn new_sub(t: Term, e: Expression) -> Expression {
+ Expression::Sub(Box::new(t), Box::new(e))
+ }
+
+ pub fn new_term(t: Term) -> Expression {
+ Expression::Term(Box::new(t))
+ }
+}
+
+impl Term {
+ pub fn new_mul(f: Factor, t: Term) -> Term {
+ Term::Mul(Box::new(f), Box::new(t))
+ }
+
+ pub fn new_div(f: Factor, t: Term) -> Term {
+ Term::Div(Box::new(f), Box::new(t))
+ }
+
+ pub fn new_factor(f: Factor) -> Term {
+ Term::Factor(Box::new(f))
+ }
+}
+
+impl Factor {
+ pub fn new_expression(e: Expression) -> Factor {
+ Factor::Expression(Box::new(e))
+ }
+
+ pub fn new_value(v: f64) -> Factor {
+ Factor::Value(v)
+ }
+
+ pub fn new_symbol(s: String) -> Factor {
+ Factor::Symbol(s)
+ }
+}
diff --git a/qexpr/Cargo.toml b/qexpr/Cargo.toml
new file mode 100644
index 0000000..90bbac3
--- /dev/null
+++ b/qexpr/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "qexpr"
+version = "0.1.0"
+authors = ["Daniel Silverstone <daniel.silverstone@codethink.co.uk>"]
+
+[dependencies]
+qvalues = { path = "../qvalues" }
+exprparser = { path = "../exprparser" }
diff --git a/qexpr/src/lib.rs b/qexpr/src/lib.rs
new file mode 100644
index 0000000..fa59c5c
--- /dev/null
+++ b/qexpr/src/lib.rs
@@ -0,0 +1,31 @@
+extern crate exprparser;
+extern crate qvalues;
+
+use exprparser::*;
+use qvalues::*;
+
+use std::collections::HashMap;
+
+fn evaluate_factor(f: &Factor, symbols: &HashMap<String, QValue>) -> Option<QValue> {
+ match f {
+ Factor::Value(v) => Some(QValue::new(*v)),
+ Factor::Symbol(s) => symbols.get(s).map(|v| v.clone()),
+ Factor::Expression(e) => evaluate(e, symbols),
+ }
+}
+
+fn evaluate_term(t: &Term, symbols: &HashMap<String, QValue>) -> Option<QValue> {
+ match t {
+ Term::Mul(bf, bt) => Some(evaluate_factor(bf, symbols)? * evaluate_term(bt, symbols)?),
+ Term::Div(bf, bt) => Some(evaluate_factor(bf, symbols)? / evaluate_term(bt, symbols)?),
+ Term::Factor(bf) => evaluate_factor(bf, symbols),
+ }
+}
+
+pub fn evaluate(e: &Expression, symbols: &HashMap<String, QValue>) -> Option<QValue> {
+ match e {
+ Expression::Add(bt, be) => evaluate_term(bt, symbols)? + evaluate(be, symbols)?,
+ Expression::Sub(bt, be) => evaluate_term(bt, symbols)? + evaluate(be, symbols)?,
+ Expression::Term(bt) => evaluate_term(bt, symbols),
+ }
+}