From afdd2e5be89a965d356d1c0fb931b685a3a45334 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sun, 26 Nov 2017 12:22:58 -0500 Subject: [PATCH] List Parsing and evaluation support. Also some formatting and todo cleanup. --- examples/test.ucg | 10 +++++-- src/ast.rs | 13 +++++++-- src/build.rs | 24 +++++++++++----- src/convert/flags.rs | 12 +++++--- src/parse.rs | 68 +++++++++++++++++++++++++++++++++++++++++++- src/tokenizer.rs | 8 ++++++ 6 files changed, 118 insertions(+), 17 deletions(-) diff --git a/examples/test.ucg b/examples/test.ucg index 47d6a85..b8718ab 100644 --- a/examples/test.ucg +++ b/examples/test.ucg @@ -1,4 +1,5 @@ -let dbhost = "localhost"; +let dbhost1 = "db1.prod.net"; +let dbhost2 = "db2.prod.net"; let dbname = "testdb"; let mk_db_conn = macro (host, port, db) => { @@ -8,9 +9,12 @@ let mk_db_conn = macro (host, port, db) => { conn_string = "@:@/@" % (host, port, db) }; -let db_conn = mk_db_conn(dbhost, 3306, dbname); +let db_conn1 = mk_db_conn(dbhost1, 3306, dbname); +let db_conn2 = mk_db_conn(dbhost2, 3306, dbname); + +let db_conns = [db_conn1.conn_string, db_conn2.conn_string]; let server_config = { - dbconn = db_conn.conn_string, + db_conn = db_conns, tmpldir = "./templates" }; \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 3e291e7..9eb231c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -59,7 +59,7 @@ pub type SelectorList = Vec; // str is expected to always be a symbol. #[derive(Debug,PartialEq,Clone)] pub struct LocatedNode { - // TODO(jwall): Make this non-optional, Should we just use positioned instead? + // TODO(jwall): Should we just use positioned instead? pub pos: Position, pub val: T, } @@ -78,7 +78,6 @@ impl LocatedNode { } -// TODO(jwall): This should take a line and a column as argumentsn now. pub fn make_value_node(v: T, line: usize, column: usize) -> LocatedNode { LocatedNode::new(v, Position { @@ -290,6 +289,9 @@ impl MacroDef { bad_symbols.extend(syms_set.drain()); stack.push(&bexpr.right); } + &Expression::List(ref def) => { + stack.extend(def.elems.iter()); + } &Expression::Grouped(ref expr) => { stack.push(expr); } @@ -366,6 +368,12 @@ pub struct FormatDef { pub pos: Position, } +#[derive(Debug,PartialEq,Clone)] +pub struct ListDef { + pub elems: Vec, + pub pos: Position, +} + /// Expression encodes an expression. Expressions compute a value from operands. #[derive(Debug,PartialEq,Clone)] pub enum Expression { @@ -377,6 +385,7 @@ pub enum Expression { // Complex Expressions Copy(CopyDef), Grouped(Box), + List(ListDef), Format(FormatDef), diff --git a/src/build.rs b/src/build.rs index cd5679a..482faf8 100644 --- a/src/build.rs +++ b/src/build.rs @@ -106,6 +106,7 @@ pub enum Val { Int(i64), Float(f64), String(String), + List(Vec>), Tuple(Vec<(Positioned, Rc)>), Macro(MacroDef), } @@ -116,6 +117,7 @@ impl Val { &Val::Int(_) => "Integer".to_string(), &Val::Float(_) => "Float".to_string(), &Val::String(_) => "String".to_string(), + &Val::List(_) => "List".to_string(), &Val::Tuple(_) => "Tuple".to_string(), &Val::Macro(_) => "Macro".to_string(), } @@ -144,6 +146,13 @@ impl Val { false } } + &Val::List(_) => { + if let &Val::List(_) = target { + true + } else { + false + } + } &Val::Tuple(_) => { if let &Val::Tuple(_) = target { true @@ -332,10 +341,9 @@ impl Builder { pub fn build_file(&mut self, name: &str) -> BuildResult { let mut f = try!(File::open(name)); let mut s = String::new(); - // TODO(jwall): It would be nice to be able to do this with streaming + // TODO(jwall): It would be nice to be able to do this while streaming try!(f.read_to_string(&mut s)); self.build_file_string(name, s) - // TODO(jwall): Call an output converter. } fn build_stmt(&mut self, stmt: &Statement) -> BuildResult { @@ -623,6 +631,13 @@ impl Builder { &Expression::Grouped(ref expr) => { return self.eval_expr(expr); } + &Expression::List(ref def) => { + let mut vals = Vec::new(); + for expr in def.elems.iter() { + vals.push(try!(self.eval_expr(expr))); + } + return Ok(Rc::new(Val::List(vals))); + } &Expression::Format(ref def) => { let tmpl = &def.template; let args = &def.args; @@ -918,7 +933,6 @@ mod test { #[test] fn test_eval_selector_expr() { - // TODO(jwall): Tests for this expression. let mut b = Builder::new(); b.out.entry(Positioned::new("var1".to_string(), Position{line: 1, column: 0})).or_insert(Rc::new(Val::Tuple(vec![ (Positioned::new("lvl1".to_string(), Position{line: 1, column: 0}), Rc::new(Val::Tuple( @@ -1024,11 +1038,8 @@ mod test { ], b); } - // TODO(jwall): What about the duplicate field error? - #[test] fn test_expr_copy() { - // TODO(jwall): Tests for this expression. let mut b = Builder::new(); b.out.entry(Positioned::new("tpl1".to_string(), Position{line: 1, column: 0})).or_insert(Rc::new(Val::Tuple(vec![ (Positioned::new("fld1".to_string(), Position{line: 1, column: 0}), Rc::new(Val::Int(1))), @@ -1248,5 +1259,4 @@ mod test { ], b); } - // TODO(jwall): Unit test for MacroDef Symbol Validation. } diff --git a/src/convert/flags.rs b/src/convert/flags.rs index 999c8be..638ac21 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -32,20 +32,24 @@ impl FlagConverter { match v { &Val::Float(ref f) => { try!(write!(w, "{} ", f)); - }, + } &Val::Int(ref i) => { try!(write!(w, "{} ", i)); - }, + } &Val::String(ref s) => { try!(write!(w, "'{}' ", s)); - }, + } + &Val::List(ref _def) => { + // FIXME(jwall): Fill this in? + eprintln!("Skipping List..."); + } &Val::Tuple(ref flds) => { for &(ref name, ref val) in flds.iter() { try!(write!(w, "--{} ", name.val)); // TODO(jwall): What if the value is a tuple? try!(self.write(&val, w)); } - }, + } &Val::Macro(ref _def) => { // This is ignored eprintln!("Skipping macro..."); diff --git a/src/parse.rs b/src/parse.rs index 8eb0cac..5f7329b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -442,6 +442,26 @@ named!(call_expression( Span ) -> Expression, ) ); +fn tuple_to_list >(t: (Sp, Vec)) -> ParseResult { + return Ok(Expression::List(ListDef{ + elems: t.1, + pos: t.0.into(), + })); +} + +named!(list_expression( Span ) -> Expression, + map_res!( + do_parse!( + pos: position!() >> + leftsquarebracket >> + elements: ws!(separated_list!(ws!(commatok), expression)) >> + rightsquarebracket >> + (pos, elements) + ), + tuple_to_list + ) +); + // NOTE(jwall): HERE THERE BE DRAGONS. The order for these matters // alot. We need to process alternatives in order of decreasing // specificity. Unfortunately this means we are required to go in a @@ -459,6 +479,7 @@ named!(expression( Span ) -> Expression, complete!(mul_expression) | complete!(div_expression) | complete!(grouped_expression) | + complete!(list_expression) | complete!(macro_expression) | complete!(format_expression) | complete!(select_expression) | @@ -555,7 +576,7 @@ pub fn parse(input: Span) -> IResult> { #[cfg(test)] mod test { use super::{Statement, Expression, Value, MacroDef, SelectDef, CallDef}; - use super::{number, symbol, parse, field_value, selector_value, selector_or_symbol, tuple, grouped_expression}; + use super::{number, symbol, parse, field_value, selector_value, selector_or_symbol, tuple, grouped_expression, list_expression}; use super::{copy_expression, macro_expression, select_expression}; use super::{format_expression, call_expression, expression}; use super::{expression_statement, let_statement, import_statement, statement}; @@ -1032,6 +1053,19 @@ mod test { ) ) ); + assert_eq!(expression(LocatedSpan::new("[1, 1]")), + IResult::Done(LocatedSpan{fragment: "", offset: 6, line: 1}, + Expression::List( + ListDef{ + elems: vec![ + Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 2}))), + Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 5}))), + ], + pos: Position{line: 1, column: 1}, + } + ) + ) + ); } #[test] @@ -1256,6 +1290,38 @@ mod test { ); } + #[test] + fn test_list_expression_parse() { + assert!(list_expression(LocatedSpan::new("foo")).is_err() ); + assert!(list_expression(LocatedSpan::new("[foo")).is_incomplete() ); + assert_eq!(list_expression(LocatedSpan::new("[foo]")), + IResult::Done(LocatedSpan{fragment: "", offset: 5, line: 1}, + Expression::List( + ListDef{ + elems: vec![ + Expression::Simple(Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 2}))) + ], + pos: Position{ line: 1, column: 1} + } + ) + ) + ); + + assert_eq!(list_expression(LocatedSpan::new("[1, 1]")), + IResult::Done(LocatedSpan{fragment: "", offset: 6, line: 1}, + Expression::List( + ListDef{ + elems: vec![ + Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 2}))), + Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 5}))), + ], + pos: Position{line: 1, column: 1}, + } + ) + ) + ); + } + #[test] fn test_tuple_parse() { assert!(tuple(LocatedSpan::new("{")).is_incomplete() ); diff --git a/src/tokenizer.rs b/src/tokenizer.rs index e3c0eb2..76b3b42 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -137,6 +137,14 @@ named!(pub semicolontok( Span ) -> Token, do_tag_tok!(";") ); +named!(pub leftsquarebracket( Span ) -> Token, + do_tag_tok!("[") +); + +named!(pub rightsquarebracket( Span ) -> Token, + do_tag_tok!("]") +); + named!(pub fatcommatok( Span ) -> Token, do_tag_tok!("=>") );