diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 7aeb56b..31fd331 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -799,6 +799,9 @@ pub enum Statement { // Assert statement Assert(Token), + + // Identify an Expression for output. + Output(Token, Expression), } #[cfg(test)] diff --git a/src/build/mod.rs b/src/build/mod.rs index 5a2160d..26e08e2 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -293,7 +293,7 @@ pub struct AssertCollector { pub failures: String, } -/// Builder handles building ucg code. +/// Builder handles building ucg code for a single file.. pub struct Builder { root: PathBuf, validate_mode: bool, @@ -306,10 +306,12 @@ pub struct Builder { assets: ValueMap, // List of file paths we have already parsed. files: HashSet, - /// out is our built output. - out: ValueMap, + /// build_output is our built output. + build_output: ValueMap, /// last is the result of the last statement. pub last: Option>, + // FIXME(jwall): This should be a per file mapping. + out_lock: Option<(String, Rc)>, } macro_rules! eval_binary_expr { @@ -330,7 +332,7 @@ macro_rules! eval_binary_expr { } impl Builder { - // FIXME(jwall): This needs some unit tests. + // TOOD(jwall): This needs some unit tests. fn tuple_to_val(&self, fields: &Vec<(Token, Expression)>) -> Result, Box> { let mut new_fields = Vec::<(Positioned, Rc)>::new(); for &(ref name, ref expr) in fields.iter() { @@ -403,7 +405,8 @@ impl Builder { env: env, assets: HashMap::new(), files: HashSet::new(), - out: scope, + build_output: scope, + out_lock: None, last: None, } } @@ -473,9 +476,10 @@ impl Builder { if !self.files.contains(&key) { // Only parse the file once on import. if self.assets.get(&positioned_sym).is_none() { + // FIXME(jwall): We should be sharing our assets collection. let mut b = Self::new(normalized); try!(b.build_file(&def.path.fragment)); - let fields: Vec<(Positioned, Rc)> = b.out.drain().collect(); + let fields: Vec<(Positioned, Rc)> = b.build_output.drain().collect(); let result = Rc::new(Val::Tuple(fields)); self.assets.entry(positioned_sym).or_insert(result.clone()); self.files.insert(def.path.fragment.clone()); @@ -504,7 +508,7 @@ impl Builder { fn build_let(&mut self, def: &LetDef) -> Result, Box> { let val = try!(self.eval_expr(&def.value)); let name = &def.name; - match self.out.entry(name.into()) { + match self.build_output.entry(name.into()) { Entry::Occupied(e) => { return Err(Box::new(error::Error::new( format!( @@ -531,6 +535,21 @@ impl Builder { &Statement::Let(ref def) => self.build_let(def), &Statement::Import(ref def) => self.build_import(def), &Statement::Expression(ref expr) => self.eval_expr(expr), + // FIXME(jwall): Stash this into an output slot. + // Only one output can be used per file. + &Statement::Output(ref typ, ref expr) => { + if let None = self.out_lock { + let val = try!(self.eval_expr(expr)); + self.out_lock = Some((typ.fragment.to_string(), val.clone())); + Ok(val) + } else { + Err(Box::new(error::Error::new( + format!("You can only have one output per file."), + error::ErrorType::DuplicateBinding, + typ.pos.clone(), + ))) + } + } } } @@ -538,8 +557,8 @@ impl Builder { if &sym.val == "env" { return Some(self.env.clone()); } - if self.out.contains_key(sym) { - return Some(self.out[sym].clone()); + if self.build_output.contains_key(sym) { + return Some(self.build_output[sym].clone()); } if self.assets.contains_key(sym) { return Some(self.assets[sym].clone()); @@ -1138,7 +1157,6 @@ impl Builder { // we are not in validate_mode then build_asserts are noops. return Ok(Rc::new(Val::Empty)); } - // FIXME(jwall): We need to append a semicolon to the expr. let mut expr_as_stmt = String::new(); let expr = &tok.fragment; expr_as_stmt.push_str(expr); diff --git a/src/build/test.rs b/src/build/test.rs index 7c79cb0..9af089e 100644 --- a/src/build/test.rs +++ b/src/build/test.rs @@ -369,7 +369,7 @@ fn test_eval_simple_expr() { #[test] fn test_eval_simple_lookup_expr() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("var1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Int(1))); test_expr_to_val( @@ -384,7 +384,7 @@ fn test_eval_simple_lookup_expr() { #[test] fn test_eval_simple_lookup_error() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("var1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Int(1))); let expr = Expression::Simple(Value::Symbol(value_node!("var".to_string(), 1, 1))); @@ -394,7 +394,7 @@ fn test_eval_simple_lookup_error() { #[test] fn test_eval_selector_expr() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("var1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Tuple(vec![( value_node!("lvl1".to_string(), 1, 0), @@ -403,10 +403,10 @@ fn test_eval_selector_expr() { Rc::new(Val::Int(3)), )])), )]))); - b.out + b.build_output .entry(value_node!("var2".to_string(), 1, 0)) .or_insert(Rc::new(Val::Int(2))); - b.out + b.build_output .entry(value_node!("var3".to_string(), 1, 0)) .or_insert(Rc::new(Val::Tuple(vec![( value_node!("lvl1".to_string(), 1, 0), @@ -458,7 +458,7 @@ fn test_eval_selector_expr() { #[test] fn test_eval_selector_list_expr() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("var1".to_string(), 1, 1)) .or_insert(Rc::new(Val::List(vec![ Rc::new(Val::Str("val1".to_string())), @@ -502,7 +502,7 @@ fn test_expr_copy_no_such_tuple() { #[should_panic(expected = "Expected Tuple got Int(1)")] fn test_expr_copy_not_a_tuple() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("tpl1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Int(1))); test_expr_to_val( @@ -522,7 +522,7 @@ fn test_expr_copy_not_a_tuple() { #[should_panic(expected = "Expected type Integer for field fld1 but got String")] fn test_expr_copy_field_type_error() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("tpl1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Tuple(vec![( value_node!("fld1".to_string(), 1, 0), @@ -550,7 +550,7 @@ fn test_expr_copy_field_type_error() { #[test] fn test_expr_copy() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("tpl1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Tuple(vec![( value_node!("fld1".to_string(), 1, 0), @@ -621,7 +621,7 @@ fn test_expr_copy() { #[test] fn test_macro_call() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("tstmac".to_string(), 1, 0)) .or_insert(Rc::new(Val::Macro(MacroDef { argdefs: vec![value_node!("arg1".to_string(), 1, 0)], @@ -655,10 +655,10 @@ fn test_macro_call() { #[should_panic(expected = "Unable to find arg1")] fn test_macro_hermetic() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("arg1".to_string(), 1, 0)) .or_insert(Rc::new(Val::Str("bar".to_string()))); - b.out + b.build_output .entry(value_node!("tstmac".to_string(), 1, 0)) .or_insert(Rc::new(Val::Macro(MacroDef { argdefs: vec![value_node!("arg2".to_string(), 1, 0)], @@ -691,10 +691,10 @@ fn test_macro_hermetic() { #[test] fn test_select_expr() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("foo".to_string(), 1, 0)) .or_insert(Rc::new(Val::Str("bar".to_string()))); - b.out + b.build_output .entry(value_node!("baz".to_string(), 1, 0)) .or_insert(Rc::new(Val::Str("boo".to_string()))); test_expr_to_val( @@ -753,7 +753,7 @@ fn test_select_expr() { #[should_panic(expected = "Expected String but got Integer in Select expression")] fn test_select_expr_not_a_string() { let mut b = Builder::new(std::env::current_dir().unwrap()); - b.out + b.build_output .entry(value_node!("foo".to_string(), 1, 0)) .or_insert(Rc::new(Val::Int(4))); test_expr_to_val( @@ -805,7 +805,7 @@ fn test_build_file_string() { let mut b = Builder::new(std::env::current_dir().unwrap()); b.eval_string("let foo = 1;").unwrap(); let key = value_node!("foo".to_string(), 1, 0); - assert!(b.out.contains_key(&key)); + assert!(b.build_output.contains_key(&key)); } #[test] diff --git a/src/parse/mod.rs b/src/parse/mod.rs index c3f9c4e..93bc70f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -842,12 +842,29 @@ named!(assert_statement, ) ); +named!(out_statement, + do_parse!( + word!("out") >> + pos: pos >> + typ: match_type!(BAREWORD) >> + expr: add_return_error!( + nom::ErrorKind::Custom( + error::Error::new( + "Invalid syntax for assert", + error::ErrorType::ParseError, pos)), + expression) >> + punct!(";") >> + (Statement::Output(typ.clone(), expr.clone())) + ) +); + //trace_macros!(true); fn statement(i: TokenIter) -> nom::IResult { return alt_peek!(i, word!("assert") => trace_nom!(assert_statement) | word!("import") => trace_nom!(import_statement) | word!("let") => trace_nom!(let_statement) | + word!("out") => trace_nom!(out_statement) | trace_nom!(expression_statement) ); } diff --git a/src/parse/precedence.rs b/src/parse/precedence.rs index 0c427a6..31a79fa 100644 --- a/src/parse/precedence.rs +++ b/src/parse/precedence.rs @@ -253,7 +253,6 @@ named!(compare_expression, map_res!( do_parse!( left: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >> - // FIXME(jwall): Wrong type of combinator typ: parse_compare_operator >> right: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >> (typ, left, right) diff --git a/src/parse/test.rs b/src/parse/test.rs index 497c18c..358fa90 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -291,6 +291,24 @@ fn test_let_statement_parse() { ); } +#[test] +fn test_out_statement_parse() { + assert_error!(out_statement("out")); + assert_error!(out_statement("out json")); + assert_error!(out_statement("out json foo")); + assert_parse!( + out_statement("out json 1.0;"), + Statement::Output( + Token { + pos: Position { line: 1, column: 5 }, + fragment: "json".to_string(), + typ: TokenType::BAREWORD + }, + Expression::Simple(Value::Float(value_node!(1.0, 1, 10))) + ) + ); +} + #[test] fn test_expression_statement_parse() { assert_error!(expression_statement("foo")); diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 5b2b9a7..ad4b4bb 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -253,6 +253,10 @@ named!(asserttok( Span ) -> Token, do_tag_tok!(TokenType::BAREWORD, "assert", WS) ); +named!(outtok( Span ) -> Token, + do_tag_tok!(TokenType::BAREWORD, "out", WS) +); + named!(astok( Span ) -> Token, do_tag_tok!(TokenType::BAREWORD, "as", WS) ); @@ -359,6 +363,7 @@ named!(token( Span ) -> Token, rightsquarebracket | booleantok | lettok | + outtok | selecttok | asserttok | macrotok | diff --git a/src/tokenizer/test.rs b/src/tokenizer/test.rs index 540fdfa..be85938 100644 --- a/src/tokenizer/test.rs +++ b/src/tokenizer/test.rs @@ -22,6 +22,16 @@ fn test_assert_token() { } } +#[test] +fn test_out_token() { + let result = outtok(LocatedSpan::new("out ")); + assert!(result.is_done(), format!("result {:?} is not done", result)); + if let nom::IResult::Done(_, tok) = result { + assert_eq!(tok.fragment, "out"); + assert_eq!(tok.typ, TokenType::BAREWORD); + } +} + #[test] fn test_escape_quoted() { let result = escapequoted(LocatedSpan::new("foo \\\"bar\"")); @@ -103,7 +113,7 @@ fn test_lteqtok() { #[test] fn test_tokenize_one_of_each() { let result = tokenize(LocatedSpan::new( - "map filter assert let import macro select as => [ ] { } ; = % / * \ + "map out filter assert let import macro select as => [ ] { } ; = % / * \ + - . ( ) , 1 . foo \"bar\" // comment\n ; true false == < > <= >= !=", )); assert!(result.is_ok(), format!("result {:?} is not ok", result)); @@ -111,8 +121,8 @@ fn test_tokenize_one_of_each() { for (i, t) in v.iter().enumerate() { println!("{}: {:?}", i, t); } - assert_eq!(v.len(), 38); - assert_eq!(v[37].typ, TokenType::END); + assert_eq!(v.len(), 39); + assert_eq!(v[38].typ, TokenType::END); } #[test]