diff --git a/TODO.md b/TODO.md index 0f37a31..7c6fc49 100644 --- a/TODO.md +++ b/TODO.md @@ -37,4 +37,12 @@ Some options here could be: * Better error messages. * Flags should allow different seperators for prefixed flags. * YAML export -* HCL export \ No newline at end of file +* HCL export + +# Release Checklist + +* Cargo test +* Cargo fmt +* Update Cargo.toml version. +* Tag git commit with version tag. +* Cargo publish \ No newline at end of file diff --git a/src/build/compile_test.rs b/src/build/compile_test.rs index 2947d54..1c4da16 100644 --- a/src/build/compile_test.rs +++ b/src/build/compile_test.rs @@ -41,6 +41,18 @@ fn test_comparisons() { ); } +#[test] +fn test_empty_value() { + assert_build( + "let empty = NULL; + let tpl = { + foo = NULL, + }; + assert \"tpl.foo == empty\"; + ", + ); +} + #[test] fn test_deep_comparison() { assert_build( @@ -48,7 +60,7 @@ fn test_deep_comparison() { foo = \"bar\", lst = [1, 2, 3], inner = { - fld = \"value\" + fld = \"value\", } }; let copy = tpl1; diff --git a/src/build/mod.rs b/src/build/mod.rs index 2685de3..4c7ec85 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -134,31 +134,32 @@ impl Val { (&Val::Float(ref f), &Val::Float(ref ff)) => Ok(f == ff), (&Val::Boolean(ref b), &Val::Boolean(ref bb)) => Ok(b == bb), (&Val::String(ref s), &Val::String(ref ss)) => Ok(s == ss), - (&Val::List(ref ldef), &Val::List(ref lldef)) => { - if ldef.len() != lldef.len() { + (&Val::List(ref ldef), &Val::List(ref rdef)) => { + if ldef.len() != rdef.len() { Ok(false) } else { - for (i, v) in ldef.iter().enumerate() { - try!(v.equal(lldef[i].as_ref(), file_name, pos.clone())); + for (i, lv) in ldef.iter().enumerate() { + try!(lv.equal(rdef[i].as_ref(), file_name, pos.clone())); } Ok(true) } } - (&Val::Tuple(ref ldef), &Val::Tuple(ref lldef)) => { - if ldef.len() != lldef.len() { + (&Val::Tuple(ref ldef), &Val::Tuple(ref rdef)) => { + if ldef.len() != rdef.len() { Ok(false) } else { - for (i, v) in ldef.iter().enumerate() { - let field_target = &lldef[i]; - if v.0.val != field_target.0.val { + for (i, lv) in ldef.iter().enumerate() { + let field_target = &rdef[i]; + if lv.0.val != field_target.0.val { // field name equality return Ok(false); } else { // field value equality. - if !try!( - v.1 - .equal(field_target.1.as_ref(), file_name, v.0.pos.clone()) - ) { + if !try!(lv.1.equal( + field_target.1.as_ref(), + file_name, + lv.0.pos.clone() + )) { return Ok(false); } } @@ -328,6 +329,7 @@ macro_rules! eval_binary_expr { } impl Builder { + // FIXME(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() { @@ -433,6 +435,7 @@ impl Builder { pub fn eval_string(&mut self, input: &str) -> Result, Box> { match parse(Span::new(input)) { Ok(stmts) => { + //panic!("Successfully parsed {}", input); let mut out: Option> = None; for stmt in stmts.iter() { out = Some(try!(self.build_stmt(stmt))); diff --git a/src/build/test.rs b/src/build/test.rs index f5f3c38..ce034b0 100644 --- a/src/build/test.rs +++ b/src/build/test.rs @@ -237,6 +237,87 @@ fn test_eval_add_expr_fail() { ); } +#[test] +fn test_eval_nested_tuple() { + test_expr_to_val( + vec![ + ( + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("foo", TokenType::BAREWORD, 1, 1), + Expression::Simple(Value::Tuple(value_node!(Vec::new(), 1, 1))), + )], + 1, + 1 + ))), + Val::Tuple(vec![( + Positioned::new("foo".to_string(), 1, 1), + Rc::new(Val::Tuple(Vec::new())), + )]), + ), + ( + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("foo", TokenType::BAREWORD, 1, 1), + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("bar".to_string(), TokenType::BAREWORD, 1, 5), + Expression::Simple(Value::Tuple(value_node!(vec![], 1, 10))), + )], + 1, + 1 + ))), + )], + 1, + 1 + ))), + Val::Tuple(vec![( + Positioned::new("foo".to_string(), 1, 1), + Rc::new(Val::Tuple(vec![( + Positioned::new("bar".to_string(), 1, 10), + Rc::new(Val::Tuple(vec![])), + )])), + )]), + ), + ( + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("foo", TokenType::BAREWORD, 1, 1), + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("bar".to_string(), TokenType::BAREWORD, 1, 5), + Expression::Simple(Value::Tuple(value_node!( + vec![( + Token::new("quux".to_string(), TokenType::BAREWORD, 1, 1), + Expression::Simple(Value::Int(value_node!(3, 1, 1))), + )], + 1, + 10 + ))), + )], + 1, + 1 + ))), + )], + 1, + 1 + ))), + Val::Tuple(vec![( + Positioned::new("foo".to_string(), 1, 1), + Rc::new(Val::Tuple(vec![( + Positioned::new("bar".to_string(), 1, 10), + Rc::new(Val::Tuple(vec![( + Positioned::new("quux".to_string(), 1, 1), + Rc::new(Val::Int(3)), + )])), + )])), + )]), + ), + ], + Builder::new(std::env::current_dir().unwrap()), + ); +} + #[test] fn test_eval_simple_expr() { test_expr_to_val( @@ -385,6 +466,8 @@ fn test_eval_selector_list_expr() { ); } +// TODO(jwall): Eval for tuple and list. +// Include nested for each. #[test] #[should_panic(expected = "Unable to find tpl1")] fn test_expr_copy_no_such_tuple() { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 099bf27..c1dfb94 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -129,6 +129,31 @@ macro_rules! alt_peek { } ); + // These are our no fallback termination cases. + (__inner $i:expr, $peekrule:ident!( $($peekargs:tt)* ) => $parserule:ident, __end ) => ( + alt_peek!(__inner $i, $peekrule!($($peekargs)*) => call!($parserule), __end ) + ); + + (__inner $i:expr, $peekrule:ident!( $($peekargs:tt)* ) => $parserule:ident!( $($parseargs:tt)* ), __end ) => ( + { + let _i = $i.clone(); + let pre_res = peek!(_i, $peekrule!($($peekargs)*)); + match pre_res { + // if the peek was incomplete then it might still match so return incomplete. + nom::IResult::Incomplete(i) => nom::IResult::Incomplete(i), + // If the peek was in error then try the next peek => parse pair. + nom::IResult::Error(_) => { + alt_peek!(__inner $i, __end) + }, + // If the peek was successful then return the result of the parserule + // regardless of it's result. + nom::IResult::Done(_i, _) => { + $parserule!(_i, $($parseargs)*) + }, + } + } + ); + // These are our fallback termination cases. (__inner $i:expr, $fallback:ident, __end) => ( { @@ -149,7 +174,7 @@ macro_rules! alt_peek { // If there is no fallback then we return an Error. (__inner $i:expr, __end) => { // FIXME(jwall): We should do a better custom error here. - nom::IResult::Error(error_position!($crate::ErrorKind::Alt,$i)) + nom::IResult::Error(error_position!(nom::ErrorKind::Alt,$i)) }; // alt_peek entry_point. @@ -322,52 +347,53 @@ fn tuple_to_binary_expression( /// are passed in as lowerrule parsers. We default to grouped_expression and simple_expression as /// the most tightly bound expressions. macro_rules! do_binary_expr { - ($i:expr, $oprule:ident!( $($args:tt)* ), $typ:expr) => { - do_binary_expr!($i, $oprule!($($args)*), $typ, non_op_expression) + ($i:expr, $oprule:ident!( $($args:tt)* )) => { + do_binary_expr!($i, $oprule!($($args)*), non_op_expression) }; - ($i:expr, $oprule:ident!( $($args:tt)* ), $typ:expr, $lowerrule:ident) => { - do_binary_expr!($i, $oprule!($($args)*), $typ, call!($lowerrule)) + ($i:expr, $oprule:ident!( $($args:tt)* ), $lowerrule:ident) => { + do_binary_expr!($i, $oprule!($($args)*), call!($lowerrule)) }; - ($i:expr, $oprule:ident!( $($args:tt)* ), $typ:expr, $lowerrule:ident!( $($lowerargs:tt)* )) => { + ($i:expr, $oprule:ident!( $($args:tt)* ), $lowerrule:ident!( $($lowerargs:tt)* )) => { map_res!($i, do_parse!( pos: pos >> left: $lowerrule!($($lowerargs)*) >> - $oprule!($($args)*) >> + typ: $oprule!($($args)*) >> right: $lowerrule!($($lowerargs)*) >> - (pos, $typ, left, right) + (pos, typ, left, right) ), tuple_to_binary_expression ) }; } +// Matches an operator token to a BinaryExprType +named!(math_op_type, + alt!( + do_parse!(punct!("+") >> (BinaryExprType::Add)) | + do_parse!(punct!("-") >> (BinaryExprType::Sub)) | + do_parse!(punct!("*") >> (BinaryExprType::Mul)) | + do_parse!(punct!("/") >> (BinaryExprType::Div)) + ) +); + // trace_macros!(true); -named!(add_expression, - do_binary_expr!(punct!("+"), BinaryExprType::Add, alt!(product_expression | simple_expression | grouped_expression)) -); -// trace_macros!(false); - -named!(sub_expression, - do_binary_expr!(punct!("-"), BinaryExprType::Sub, alt!(product_expression | simple_expression | grouped_expression)) -); - named!(sum_expression, - alt!(add_expression | sub_expression) -); - -named!(mul_expression, - do_binary_expr!(punct!("*"), BinaryExprType::Mul) -); - -named!(div_expression, - do_binary_expr!(punct!("/"), BinaryExprType::Div) + do_binary_expr!( + alt_peek!( + punct!("+") => math_op_type | + punct!("-") => math_op_type), + alt!(product_expression | simple_expression | grouped_expression)) ); named!(product_expression, - alt!(mul_expression | div_expression) + do_binary_expr!( + alt_peek!( + punct!("*") => math_op_type | + punct!("/") => math_op_type) + ) ); named!(math_expression, @@ -386,57 +412,31 @@ fn tuple_to_compare_expression( })) } -// This macro is much simpler than the math binary expressions since they are the -// bottom of the precendence tree and we can hard code the precedence in here. -macro_rules! do_compare_expr { - ($i:expr, $subrule:ident!( $($args:tt)* ), $typ:expr) => { - map_res!($i, - do_parse!( - pos: pos >> - left: alt!(math_expression | non_op_expression) >> - $subrule!($($args)*) >> - right: alt!(math_expression | non_op_expression) >> - (pos, $typ, left, right) - ), - tuple_to_compare_expression - ) - }; -} - -named!(eqeq_expression, - do_compare_expr!(punct!("=="), CompareType::Equal) -); - -named!(not_eqeq_expression, - do_compare_expr!(punct!("!="), CompareType::NotEqual) -); - -named!(lt_eqeq_expression, - do_compare_expr!(punct!("<="), CompareType::LTEqual) -); - -named!(gt_eqeq_expression, - do_compare_expr!(punct!(">="), CompareType::GTEqual) -); - -named!(gt_expression, - do_compare_expr!(punct!(">"), CompareType::GT) -); - -named!(lt_expression, - do_compare_expr!(punct!("<"), CompareType::LT) +named!(compare_op_type, + alt!( + do_parse!(punct!("==") >> (CompareType::Equal)) | + do_parse!(punct!("!=") >> (CompareType::NotEqual)) | + do_parse!(punct!("<=") >> (CompareType::LTEqual)) | + do_parse!(punct!(">=") >> (CompareType::GTEqual)) | + do_parse!(punct!("<") >> (CompareType::LT)) | + do_parse!(punct!(">") >> (CompareType::GT)) + ) ); named!(compare_expression, - alt!( - eqeq_expression | - not_eqeq_expression | - lt_eqeq_expression | - gt_eqeq_expression | - lt_expression | - gt_expression) + map_res!( + do_parse!( + pos: pos >> + left: alt!(math_expression | non_op_expression) >> + typ: compare_op_type >> + right: alt!(math_expression | non_op_expression) >> + (pos, typ, left, right) + ), + tuple_to_compare_expression + ) ); +// FIXME(jwall): This is really *really* slow. named!(op_expression, alt!(math_expression | compare_expression) ); @@ -817,7 +817,7 @@ named!(non_op_expression, ); named!(expression, - alt!(complete!(op_expression) | complete!(non_op_expression)) + alt_complete!(op_expression | non_op_expression) ); fn expression_to_statement(v: Expression) -> ParseResult { diff --git a/src/parse/test.rs b/src/parse/test.rs index 2a67564..5a6f6ac 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -43,6 +43,15 @@ macro_rules! assert_error { #[test] fn test_null_parsing() { assert_parse!(empty_value("NULL "), Value::Empty(Position::new(1, 1))); + assert_parse!(value("NULL "), Value::Empty(Position::new(1, 1))); + assert_parse!( + simple_expression("NULL "), + Expression::Simple(Value::Empty(Position::new(1, 1))) + ); + assert_parse!( + expression("NULL,"), + Expression::Simple(Value::Empty(Position::new(1, 1))) + ); } #[test] @@ -412,7 +421,7 @@ fn test_expression_parse() { }) ); assert_parse!( - mul_expression("1 * 1"), + product_expression("1 * 1"), Expression::Binary(BinaryOpDef { kind: BinaryExprType::Mul, left: Box::new(Expression::Simple(Value::Int(value_node!(1, 1, 1)))), @@ -1013,6 +1022,34 @@ fn test_tuple_parse() { 1 )) ); + + assert_parse!( + expression("{ foo = 1, lst = [1, 2, 3], }"), + Expression::Simple(Value::Tuple(value_node!( + vec![ + ( + make_tok!("foo", 1, 3), + Expression::Simple(Value::Int(value_node!(1, Position::new(1, 9)))), + ), + ( + make_tok!("lst", 1, 12), + Expression::Simple(Value::List(ListDef { + elems: vec![ + Expression::Simple(Value::Int(value_node!(1, Position::new(1, 19)))), + Expression::Simple(Value::Int(value_node!(2, Position::new(1, 22)))), + Expression::Simple(Value::Int(value_node!(3, Position::new(1, 25)))), + ], + pos: Position { + line: 1, + column: 18, + }, + })), + ), + ], + 1, + 1 + ))) + ); } #[test] @@ -1043,6 +1080,23 @@ fn test_field_list_parse() { (make_tok!("quux", 3, 1), make_expr!(2 => int, 3, 8)), ] ); + f_list = "foo = 1,\nquux = [1, 2],"; + assert_parse!( + field_list(f_list), + vec![ + (make_tok!("foo", 1, 1), make_expr!(1 => int, 1, 7)), + ( + make_tok!("quux", 2, 1), + Expression::Simple(Value::List(ListDef { + elems: vec![ + Expression::Simple(Value::Int(value_node!(1, Position::new(2, 9)))), + Expression::Simple(Value::Int(value_node!(2, Position::new(2, 12)))), + ], + pos: Position::new(2, 8), + })), + ), + ] + ); } #[test] @@ -1106,6 +1160,19 @@ fn test_field_value_parse() { )) ) ); + assert_parse!( + field_value("foo = [1,2], "), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::List(ListDef { + elems: vec![ + Expression::Simple(Value::Int(value_node!(1, Position::new(1, 8)))), + Expression::Simple(Value::Int(value_node!(2, Position::new(1, 10)))), + ], + pos: Position::new(1, 7), + })) + ) + ); } #[test] diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 664783c..6cd68e9 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -142,7 +142,7 @@ macro_rules! do_tag_tok { } named!(emptytok( Span ) -> Token, - do_tag_tok!(TokenType::EMPTY, "NULL", WS) + do_tag_tok!(TokenType::EMPTY, "NULL") ); named!(commatok( Span ) -> Token,