From e975dea201563be45d1f0e4b8e0a647817a3878b Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 15 Nov 2017 22:41:55 -0600 Subject: [PATCH] cleanup: fix some selector vs symbol parsing issues Add test coverage of selector parsing. --- examples/test.ucg | 4 +- src/ast.rs | 13 +++-- src/build.rs | 28 +++++++-- src/lib.rs | 1 + src/main.rs | 54 +++++++++++++++-- src/parse.rs | 143 ++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 204 insertions(+), 39 deletions(-) diff --git a/examples/test.ucg b/examples/test.ucg index 4c72f43..47d6a85 100644 --- a/examples/test.ucg +++ b/examples/test.ucg @@ -2,13 +2,13 @@ let dbhost = "localhost"; let dbname = "testdb"; let mk_db_conn = macro (host, port, db) => { - conn_string = "@:@/@" % (host, port, db) host = host, port = port, db = db, + conn_string = "@:@/@" % (host, port, db) }; -let db_conn = mk_db_conn(host, 3306, dbame); +let db_conn = mk_db_conn(dbhost, 3306, dbname); let server_config = { dbconn = db_conn.conn_string, diff --git a/src/ast.rs b/src/ast.rs index cb74ae4..3e291e7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -241,24 +241,29 @@ impl MacroDef { -> HashSet { let mut bad_symbols = HashSet::new(); if let &Value::Symbol(ref name) = val { - let mut ok = true; + let mut ok = false; for arg in self.argdefs.iter() { - ok &= arg.val == name.val + if arg.val == name.val { + ok = true; + } } if !ok { bad_symbols.insert(name.val.clone()); } } else if let &Value::Selector(ref sel_node) = val { let list = &sel_node.val; - let mut ok = true; + let mut ok = false; if list.len() > 0 { // We only look to see if the first selector item exists. // This is because only the first one is a symbol all of the // rest of the items in the selector are fields in a tuple. // But we don't know at this time of the value passed into // this macro is a tuple since this isn't a callsite. + println!("checking selector head {}", list[0].fragment); for arg in self.argdefs.iter() { - ok &= arg.val == list[0].fragment + if arg.val == list[0].fragment { + ok = true; + } } if !ok { bad_symbols.insert(list[0].fragment.to_string()); diff --git a/src/build.rs b/src/build.rs index 6feb2ea..cd5679a 100644 --- a/src/build.rs +++ b/src/build.rs @@ -231,7 +231,7 @@ pub struct Builder { /// out is our built output. out: ValueMap, /// last is the result of the last statement. - last: Option>, + pub last: Option>, } macro_rules! eval_binary_expr { @@ -294,6 +294,17 @@ impl Builder { } } + pub fn get_out_by_name(&self, name: &str) -> Option> { + let key = Positioned { + pos: Position { + line: 0, + column: 0, + }, + val: name.to_string(), + }; + self.lookup_sym(&key) + } + pub fn build(&mut self, ast: &Vec) -> BuildResult { for stmt in ast.iter() { try!(self.build_stmt(stmt)); @@ -303,7 +314,7 @@ impl Builder { pub fn build_file_string(&mut self, name: &str, input: String) -> BuildResult { match parse(Span::new(&input)) { - nom::IResult::Done(_, stmts) => { + nom::IResult::Done(_span, stmts) => { for stmt in stmts.iter() { try!(self.build_stmt(stmt)); } @@ -324,6 +335,7 @@ impl Builder { // TODO(jwall): It would be nice to be able to do this with 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 { @@ -923,8 +935,16 @@ mod test { })) .or_insert(Rc::new(Val::Int(2))); b.out - .entry(Positioned::new("var3".to_string(), Position{line: 1, column: 0})) - .or_insert(Rc::new(Val::Tuple(vec![(Positioned::new("lvl1".to_string(), Position{line: 1, column: 0}), + .entry(Positioned::new("var3".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::Int(4)))]))); test_expr_to_val(vec![ (Expression::Simple(Value::Selector(make_value_node(vec![Token::new("var1", Position{line: 1, column: 1})], 1, 1))), Val::Tuple( diff --git a/src/lib.rs b/src/lib.rs index 4bfed0f..fd27875 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub mod ast; pub mod tokenizer; pub mod parse; pub mod build; +pub mod convert; mod format; pub use ast::Value; diff --git a/src/main.rs b/src/main.rs index 5b5b000..d970464 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,11 +13,19 @@ // limitations under the License. #[macro_use] extern crate clap; - extern crate ucglib; -use ucglib::build; +use std::fs::File; +use std::rc::Rc; +use std::io; +use std::process; +use ucglib::build::Val; +use ucglib::build; +//use ucglib::convert::get_converter; +use ucglib::convert::ConverterRunner; + +// TODO(jwall): List the target output types automatically. fn do_flags<'a>() -> clap::ArgMatches<'a> { clap_app!( ucg => @@ -26,6 +34,9 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> { (about: "Universal Configuration Grammar compiler.") (@subcommand build => (about: "Compile a specific ucg file.") + (@arg sym: --sym +takes_value "Specify a specific let binding in the ucg file to output.") + (@arg target: --target -t +required +takes_value "Target output type.") + (@arg out: --out -o +takes_value "Output file to write to.") (@arg INPUT: +required "Input ucg file to build.") ) (@subcommand validate => @@ -36,18 +47,53 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> { .get_matches() } +fn run_converter(c: ConverterRunner, v: Rc, f: &str) -> io::Result<()> { + let file = File::create(f); + c.convert(v, Box::new(file.unwrap())) +} + fn main() { // TODO(jwall): Read and build an actual file. let app = do_flags(); if let Some(matches) = app.subcommand_matches("build") { let file = matches.value_of("INPUT").unwrap(); + let out = matches.value_of("out").unwrap(); + let sym = matches.value_of("sym"); + let target = matches.value_of("target").unwrap(); let mut builder = build::Builder::new(); - builder.build_file(file).unwrap(); - println!("Build successful"); + match ConverterRunner::new(target) { + Ok(converter) => { + let result = builder.build_file(file); + if !result.is_ok() { + eprintln!("{:?}", result.err()); + process::exit(1); + } + let val = match sym { + Some(sym_name) => builder.get_out_by_name(sym_name), + None => builder.last, + }; + match val { + Some(value) => { + run_converter(converter, value, out).unwrap(); + println!("Build successful"); + process::exit(0); + } + None => { + eprintln!("Build results in no value."); + process::exit(1); + } + } + } + Err(msg) => { + eprintln!("{}", msg); + process::exit(1); + } + } } else if let Some(matches) = app.subcommand_matches("validate") { let file = matches.value_of("INPUT").unwrap(); let mut builder = build::Builder::new(); builder.build_file(file).unwrap(); println!("File Validates"); + process::exit(0); } } diff --git a/src/parse.rs b/src/parse.rs index ae59059..8eb0cac 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -15,6 +15,9 @@ use std::str::FromStr; use std::error::Error; use std::borrow::Borrow; +use nom::IResult; +use nom::InputLength; + use ast::*; use tokenizer::*; @@ -163,7 +166,27 @@ named!( ) ); -named!(value( Span ) -> Value, alt!(number | quoted_value | symbol | tuple)); +pub fn selector_or_symbol(input: Span) -> IResult { + let sym = do_parse!(input, + sym: symbol >> + not!(dottok) >> + (sym) + ); + match sym { + IResult::Incomplete(i) => { + return IResult::Incomplete(i); + }, + IResult::Error(_) => { + // TODO(jwall): Maybe be smarter about the error reporting here? + return ws!(input, selector_value); + }, + IResult::Done(rest, val) => { + return IResult::Done(rest, val); + } + } +} + +named!(value( Span ) -> Value, alt!(number | quoted_value | tuple | selector_or_symbol )); fn value_to_expression(v: Value) -> ParseResult { Ok(Expression::Simple(v)) @@ -504,19 +527,90 @@ named!(statement( Span ) -> Statement, ) ); -named!(pub parse( Span ) -> Vec, many1!(ws!(statement))); +pub fn parse(input: Span) -> IResult> { + let mut out = Vec::new(); + let mut i = input; + loop { + match ws!(i, statement) { + IResult::Error(e) => { + return IResult::Error(e); + }, + IResult::Incomplete(i) => { + return IResult::Incomplete(i); + }, + IResult::Done(rest, stmt) => { + out.push(stmt); + i = rest; + if i.input_len() == 0 { + break; + } + } + } + } + return IResult::Done(i, out); +} + +//named!(pub parse( Span ) -> Vec, many1!()); #[cfg(test)] mod test { use super::{Statement, Expression, Value, MacroDef, SelectDef, CallDef}; - use super::{number, symbol, parse, field_value, tuple, grouped_expression}; + use super::{number, symbol, parse, field_value, selector_value, selector_or_symbol, tuple, grouped_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}; use ast::*; use nom_locate::LocatedSpan; - use nom::IResult; + use nom::{Needed, IResult}; + + #[test] + fn test_symbol_parsing() { + assert_eq!(symbol(LocatedSpan::new("foo")), + IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1}, + Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) ); + assert_eq!(symbol(LocatedSpan::new("foo-bar")), + IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1}, + Value::Symbol(value_node!("foo-bar".to_string(), Position{line: 1, column: 1}))) ); + assert_eq!(symbol(LocatedSpan::new("foo_bar")), + IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1}, + Value::Symbol(value_node!("foo_bar".to_string(), Position{line: 1, column: 1}))) ); + } + + #[test] + fn test_selector_parsing() { + assert_eq!(selector_value(LocatedSpan::new("foo.")), + IResult::Incomplete(Needed::Unknown) + ); + assert_eq!(selector_value(LocatedSpan::new("foo.bar ")), + IResult::Done(LocatedSpan{fragment: "", offset: 8, line: 1}, + Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}}, + Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}], + Position{line: 1, column: 0}))) + ); + assert_eq!(selector_value(LocatedSpan::new("foo.bar;")), + IResult::Done(LocatedSpan{fragment: ";", offset: 7, line: 1}, + Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}}, + Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}], + Position{line: 1, column: 0}))) + ); + } + + #[test] + fn test_selector_or_symbol_parsing() { + assert_eq!(selector_or_symbol(LocatedSpan::new("foo.")), + IResult::Incomplete(Needed::Unknown) + ); + assert_eq!(selector_or_symbol(LocatedSpan::new("foo")), + IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1}, + Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) ); + assert_eq!(selector_or_symbol(LocatedSpan::new("foo.bar ")), + IResult::Done(LocatedSpan{fragment: "", offset: 8, line: 1}, + Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}}, + Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}], + Position{line: 1, column: 0}))) + ); + } #[test] fn test_statement_parse() { @@ -744,19 +838,27 @@ mod test { #[test] fn test_expression_parse() { assert_eq!(expression(LocatedSpan::new("1")), - IResult::Done(LocatedSpan { - fragment: "", - offset: 1, - line: 1, - }, - Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 1}))))); + IResult::Done(LocatedSpan { + fragment: "", + offset: 1, + line: 1, + }, + Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 1}))))); assert_eq!(expression(LocatedSpan::new("foo")), + IResult::Done(LocatedSpan { + fragment: "", + offset: 3, + line: 1, + }, + Expression::Simple(Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))))); + assert_eq!(expression(LocatedSpan::new("foo.bar ")), IResult::Done(LocatedSpan { fragment: "", - offset: 3, + offset: 8, line: 1, }, - Expression::Simple(Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))))); + Expression::Simple(Value::Selector(make_value_node(vec![Token::new("foo", Position{line: 1, column: 1}), + Token::new("bar", Position{line: 1, column: 5})], 1, 0))))); assert_eq!(expression(LocatedSpan::new("1 + 1")), IResult::Done(LocatedSpan { fragment: "", @@ -1237,6 +1339,10 @@ mod test { IResult::Done(LocatedSpan { offset: 10, line: 1, fragment: "" }, (Token::new("foo", Position{line: 1, column: 1}), Expression::Simple(Value::Symbol(value_node!("bar".to_string(), Position{line: 1, column: 7}))))) ); + assert_eq!(field_value(LocatedSpan::new("foo = bar.baz ")), + IResult::Done(LocatedSpan { offset: 14, line: 1, fragment: "" }, + (Token::new("foo", Position{line: 1, column: 1}), + Expression::Simple(Value::Selector(make_value_node(vec![Token::new("bar", Position{line: 1, column: 7}), Token::new("baz", Position{line: 1, column: 11})], 1, 6)))))); } #[test] @@ -1257,19 +1363,6 @@ mod test { Value::Float(value_node!(0.1, Position{line: 1, column: 1}))) ); } - #[test] - fn test_symbol_parsing() { - assert_eq!(symbol(LocatedSpan::new("foo")), - IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1}, - Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) ); - assert_eq!(symbol(LocatedSpan::new("foo-bar")), - IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1}, - Value::Symbol(value_node!("foo-bar".to_string(), Position{line: 1, column: 1}))) ); - assert_eq!(symbol(LocatedSpan::new("foo_bar")), - IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1}, - Value::Symbol(value_node!("foo_bar".to_string(), Position{line: 1, column: 1}))) ); - } - #[test] fn test_parse() { let bad_input = LocatedSpan::new("import mylib as lib;");