diff --git a/example_errors/bad_assert.ucg b/example_errors/bad_assert.ucg new file mode 100644 index 0000000..e69de29 diff --git a/example_errors/bad_import.ucg b/example_errors/bad_import.ucg new file mode 100644 index 0000000..efca996 --- /dev/null +++ b/example_errors/bad_import.ucg @@ -0,0 +1 @@ +import \ No newline at end of file diff --git a/example_errors/bad_file.ucg b/example_errors/bad_let_test.ucg similarity index 100% rename from example_errors/bad_file.ucg rename to example_errors/bad_let_test.ucg diff --git a/example_errors/errors_test.ucg b/example_errors/errors_test.ucg index 7742c73..4510eb7 100644 --- a/example_errors/errors_test.ucg +++ b/example_errors/errors_test.ucg @@ -2,12 +2,14 @@ assert |macro |; assert |{ foo = hello world}|; assert |{ foo = "hello world"|; assert |{ = }|; -assert |let foo |; -assert |let |; -assert |import |; -assert |import foo |; -assert |out |; -assert |out "|; -assert |out json|; assert |"|; -assert |=|; \ No newline at end of file +assert |=|; + +assert |let |; +assert |let foo |; +assert |let foo =|; +//assert |import |; +//assert |import foo |; +//assert |out |; +//assert |out "|; +//assert |out json|; diff --git a/integration_tests/comparisons_test.ucg b/integration_tests/comparisons_test.ucg index e63f522..b8983af 100644 --- a/integration_tests/comparisons_test.ucg +++ b/integration_tests/comparisons_test.ucg @@ -16,15 +16,33 @@ let list = [1, 2, 3]; let list2 = list; let list3 = [1, 2]; -assert |one == one|; -assert |one == one|; -assert |one >= one|; -assert |two > one|; -assert |two >= two|; -assert |tpl1 == tpl2|; -assert |tpl1 != tpl3|; -assert |list == list2|; -assert |list != list3|; +assert | + one == one; +|; +assert | + one == one; +|; +assert | + one >= one; +|; +assert | + two > one; +|; +assert | + two >= two; +|; +assert | + tpl1 == tpl2; +|; +assert | + tpl1 != tpl3; +|; +assert | + list == list2; +|; +assert | + list != list3; +|; // Deep Comparisons let tpl4 = { @@ -40,17 +58,33 @@ let less = { foo = "bar" }; -assert |tpl4.inner == copy.inner|; -assert |tpl4.inner.fld == copy.inner.fld|; -assert |tpl4.lst == copy.lst|; -assert |tpl4.foo == copy.foo|; -assert |tpl4 == copy|; -assert |tpl4 != extra|; -assert |tpl4 != less|; +assert | + tpl4.inner == copy.inner; +|; +assert | + tpl4.inner.fld == copy.inner.fld; +|; +assert | + tpl4.lst == copy.lst; +|; +assert | + tpl4.foo == copy.foo; +|; +assert | + tpl4 == copy; +|; +assert | + tpl4 != extra; +|; +assert | + tpl4 != less; +|; // Expression comparisons -assert |2 == 1+1|; -assert |(1+1) == 2|; -assert |(1+1) == (1+1)|; +assert |2 == 1+1;|; +assert |(1+1) == 2;|; +assert |(1+1) == (1+1);|; let want = "foo"; -assert |select want, 1, { foo=2, } == 2|; \ No newline at end of file +assert | + select want, 1, { foo=2, } == 2; +|; \ No newline at end of file diff --git a/integration_tests/concatenation_test.ucg b/integration_tests/concatenation_test.ucg index 2bbc58c..a7c6558 100644 --- a/integration_tests/concatenation_test.ucg +++ b/integration_tests/concatenation_test.ucg @@ -1,2 +1,6 @@ -assert |"hello " + "world" == "hello world"|; -assert |[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]|; \ No newline at end of file +assert | + "hello " + "world" == "hello world"; +|; +assert | + [1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]; +|; \ No newline at end of file diff --git a/integration_tests/empty_test.ucg b/integration_tests/empty_test.ucg index d6772ee..9707de3 100644 --- a/integration_tests/empty_test.ucg +++ b/integration_tests/empty_test.ucg @@ -2,4 +2,6 @@ let empty = NULL; let tpl = { foo = NULL, }; -assert |tpl.foo == empty|; \ No newline at end of file +assert | + tpl.foo == empty; +|; \ No newline at end of file diff --git a/integration_tests/format_test.ucg b/integration_tests/format_test.ucg index e2d825f..77016ca 100644 --- a/integration_tests/format_test.ucg +++ b/integration_tests/format_test.ucg @@ -1,4 +1,12 @@ -assert |"hello @" % ("world") == "hello world"|; -assert |"1 @ @" % (2, 3) == "1 2 3"|; -assert |"@ or @" % (true, false) == "true or false"|; -assert |"@" % (NULL) == "NULL"|; \ No newline at end of file +assert | + "hello @" % ("world") == "hello world"; +|; +assert | + "1 @ @" % (2, 3) == "1 2 3"; +|; +assert | + "@ or @" % (true, false) == "true or false"; +|; +assert | + "@" % (NULL) == "NULL"; +|; \ No newline at end of file diff --git a/integration_tests/list_ops_test.ucg b/integration_tests/list_ops_test.ucg index 915effd..b27ca7a 100644 --- a/integration_tests/list_ops_test.ucg +++ b/integration_tests/list_ops_test.ucg @@ -12,11 +12,25 @@ let boolfiltrator = macro(item) => { result = item < 5, }; -assert |map mapper.result list1 == [2, 3, 4, 5]|; -assert |(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]|; -assert |map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5]|; +assert | +map mapper.result list1 == [2, 3, 4, 5]; +|; +assert | + (map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]; +|; +assert | + map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5]; +|; -assert |filter filtrator.result list2 == ["foo", "foo"]|; -assert |(filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"]|; -assert |filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"]|; -assert |filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]|; \ No newline at end of file +assert | + filter filtrator.result list2 == ["foo", "foo"]; +|; +assert | + (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"]; +|; +assert | + filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"]; +|; +assert | + filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]; +|; \ No newline at end of file diff --git a/integration_tests/macros_test.ucg b/integration_tests/macros_test.ucg index 0c63e04..d14dfc7 100644 --- a/integration_tests/macros_test.ucg +++ b/integration_tests/macros_test.ucg @@ -14,11 +14,25 @@ let cplxmacro = macro(argint, argstr, argfloat) => { let simpleresult = simplemacro(1, 2, 3); let cplxresult = cplxmacro(1, "We", 3.0); -assert |simpleresult.field1 == 1|; -assert |simpleresult.field2 == 2|; -assert |simpleresult.field3 == 3|; +assert | + simpleresult.field1 == 1; +|; +assert | + simpleresult.field2 == 2; +|; +assert | + simpleresult.field3 == 3; +|; -assert |cplxresult.field1 == 2|; -assert |cplxresult.field2 == "We are here"|; -assert |cplxresult.field3 == 2.0|; -assert |cplxresult.boolfield == true|; \ No newline at end of file +assert | + cplxresult.field1 == 2; +|; +assert | + cplxresult.field2 == "We are here"; +|; +assert | + cplxresult.field3 == 2.0; +|; +assert | + cplxresult.boolfield == true; +|; \ No newline at end of file diff --git a/integration_tests/operator_precedence_test.ucg b/integration_tests/operator_precedence_test.ucg index d66460b..2eabb9d 100644 --- a/integration_tests/operator_precedence_test.ucg +++ b/integration_tests/operator_precedence_test.ucg @@ -1,9 +1,27 @@ -assert |2 * 2 + 1 == 5|; -assert |2 + 2 * 3 == 8|; -assert |2 * (2 + 1) == 6|; -assert |2 * 2 + 1 > 4|; -assert |2 * 2 + 1 < 6|; -assert |2 * 2 + 1 >= 5|; -assert |2 * 2 + 1 <= 5|; -assert |2 / 2 == 1|; -assert |2 - 1 == 1|; \ No newline at end of file +assert | + 2 * 2 + 1 == 5; +|; +assert | + 2 + 2 * 3 == 8; +|; +assert | + 2 * (2 + 1) == 6; +|; +assert | + 2 * 2 + 1 > 4; +|; +assert | + 2 * 2 + 1 < 6; +|; +assert | + 2 * 2 + 1 >= 5; +|; +assert | + 2 * 2 + 1 <= 5; +|; +assert | + 2 / 2 == 1; +|; +assert | + 2 - 1 == 1; +|; \ No newline at end of file diff --git a/integration_tests/select_expressions_test.ucg b/integration_tests/select_expressions_test.ucg index 0be2573..f49b9cd 100644 --- a/integration_tests/select_expressions_test.ucg +++ b/integration_tests/select_expressions_test.ucg @@ -11,8 +11,12 @@ let defaultgot = select badwant, "OOPS", { door2 = "you lose", }; -assert |got == "grand prize"|; -assert |defaultgot == "OOPS"|; +assert | + got == "grand prize"; +|; +assert | + defaultgot == "OOPS"; +|; // select inside a macro @@ -25,6 +29,12 @@ let condmacro = macro(arg) => { let result = condmacro("opt1"); -assert |condmacro("opt1") == {output = "yay"}|; -assert |condmacro("opt2") == {output = "boo"}|; -assert |condmacro("invalid") == {output = NULL}|; \ No newline at end of file +assert | + condmacro("opt1") == {output = "yay"}; +|; +assert | + condmacro("opt2") == {output = "boo"}; +|; +assert | + condmacro("invalid") == {output = NULL}; +|; \ No newline at end of file diff --git a/integration_tests/selectors_test.ucg b/integration_tests/selectors_test.ucg index 207094d..745e83a 100644 --- a/integration_tests/selectors_test.ucg +++ b/integration_tests/selectors_test.ucg @@ -10,10 +10,24 @@ let testmacro = macro(arg) => { output = arg, }; -assert |list.0 == 1|; -assert |list.1 == 2|; -assert |list.3 == 4|; -assert |tuple.field1 == 1|; -assert |tuple.field2 == 3|; -assert |tuple.deeplist.0 == "foo"|; -assert |tuple.deeplist.1 == "bar"|; \ No newline at end of file +assert | + list.0 == 1; +|; +assert | + list.1 == 2; +|; +assert | + list.3 == 4; +|; +assert | + tuple.field1 == 1; +|; +assert | + tuple.field2 == 3; +|; +assert | + tuple.deeplist.0 == "foo"; +|; +assert | + tuple.deeplist.1 == "bar"; +|; \ No newline at end of file diff --git a/integration_tests/tuple_test.ucg b/integration_tests/tuple_test.ucg index 5b14114..82b00c5 100644 --- a/integration_tests/tuple_test.ucg +++ b/integration_tests/tuple_test.ucg @@ -14,11 +14,27 @@ let nestedtpl = { list = [1, 2, 3, 4], }; -assert |simpletpl.foo == "bar"|; -assert |stringfieldtpl."field 1" == 1|; -assert |nestedtpl.scalar == 1|; -assert |nestedtpl.inner.field == "value"|; -assert |nestedtpl.list.0 == 1|; -assert |nestedtpl.list.1 == 2|; -assert |nestedtpl.list.2 == 3|; -assert |nestedtpl.list.3 == 4|; \ No newline at end of file +assert | + simpletpl.foo == "bar"; +|; +assert | + stringfieldtpl."field 1" == 1; +|; +assert | + nestedtpl.scalar == 1; +|; +assert | + nestedtpl.inner.field == "value"; +|; +assert | + nestedtpl.list.0 == 1; +|; +assert | + nestedtpl.list.1 == 2; +|; +assert | + nestedtpl.list.2 == 3; +|; +assert | + nestedtpl.list.3 == 4; +|; \ No newline at end of file diff --git a/src/build/mod.rs b/src/build/mod.rs index 19d54eb..f2303f2 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -25,6 +25,8 @@ use std::path::PathBuf; use std::rc::Rc; use std::string::ToString; +use simple_error; + use ast::*; use error; use format; @@ -248,7 +250,7 @@ impl<'a> Builder<'a> { Ok(()) } - fn eval_span(&mut self, input: OffsetStrIter) -> Result, Box> { + fn eval_input(&mut self, input: OffsetStrIter) -> Result, Box> { match parse(input.clone()) { Ok(stmts) => { //panic!("Successfully parsed {}", input); @@ -271,7 +273,7 @@ impl<'a> Builder<'a> { /// Evaluate an input string as UCG. pub fn eval_string(&mut self, input: &str) -> Result, Box> { - self.eval_span(OffsetStrIter::new(input)) + self.eval_input(OffsetStrIter::new(input)) } /// Builds a ucg file at the named path. @@ -280,8 +282,19 @@ impl<'a> Builder<'a> { let mut f = try!(File::open(name)); let mut s = String::new(); try!(f.read_to_string(&mut s)); - self.last = Some(try!(self.eval_string(&s))); - Ok(()) + let eval_result = self.eval_string(&s); + match eval_result { + Ok(v) => { + self.last = Some(v); + Ok(()) + } + Err(e) => { + let err = simple_error::SimpleError::new( + format!("Error building file: {}\n{}", name, e.as_ref()).as_ref(), + ); + Err(Box::new(err)) + } + } } fn build_import(&mut self, def: &ImportDef) -> Result, Box> { @@ -993,13 +1006,10 @@ impl<'a> Builder<'a> { // we are not in validate_mode then build_asserts are noops. return Ok(Rc::new(Val::Empty)); } - let mut expr_as_stmt = String::new(); let expr = &tok.fragment; - expr_as_stmt.push_str(expr); - expr_as_stmt.push_str(";"); let assert_input = - OffsetStrIter::new_with_offsets(&expr_as_stmt, tok.pos.line - 1, tok.pos.column - 1); - let ok = match self.eval_span(assert_input) { + OffsetStrIter::new_with_offsets(expr, tok.pos.line - 1, tok.pos.column - 1); + let ok = match self.eval_input(assert_input) { Ok(v) => v, Err(e) => { // failure! diff --git a/src/lib.rs b/src/lib.rs index 7b85b23..a43f562 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -409,15 +409,19 @@ //! //! The assert statement defines an expression that must evaluate to either true or false. Assert statements are noops except //! during a validation compile. They give you a way to assert certains properties about your data and can be used as a form -//! of unit testing for your configurations. It starts with the assert keyword followed by a valid boolean ucg expression -//! delimited by `|` characters. +//! of unit testing for your configurations. It starts with the assert keyword followed by a valid block of ucg statements +//! delimited by `|` characters. The final statement in the in the block must evaluate to a boolean expression. //! //! ```ucg -//! assert host == "www.example.com"; -//! assert |select qa, 443, { -//! qa = 80, -//! prod = 443, -//! } == 443|; +//! assert | +//! host == "www.example.com"; +//! |; +//! assert | +//! select qa, 443, { +//! qa = 80, +//! prod = 443, +//! } == 443; +//! |; //! ``` //! //! When _test.ucg files are run in a validation run then ucg will output a log of all the assertions diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a65bf13..fc0c000 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -843,9 +843,10 @@ fn tuple_to_let(tok: Token, expr: Expression) -> Statement { make_fn!( let_stmt_body, Statement>, do_each!( - name => match_type!(BAREWORD), + name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"), _ => punct!("="), - val => trace_nom!(expression), + // TODO(jwall): Wrap this error with an appropriate abortable_parser::Error + val => wrap_err!(trace_nom!(expression), "Expected Expression"), _ => punct!(";"), (tuple_to_let(name, val)) ) @@ -870,9 +871,9 @@ fn tuple_to_import(tok: Token, tok2: Token) -> Statement { make_fn!( import_stmt_body, Statement>, do_each!( - path => match_type!(STR), + path => wrap_err!(match_type!(STR), "Expected import path"), _ => word!("as"), - name => match_type!(BAREWORD), + name => wrap_err!(match_type!(BAREWORD), "Expected import name"), _ => punct!(";"), (tuple_to_import(path, name)) ) @@ -902,8 +903,8 @@ make_fn!( out_statement, Statement>, do_each!( _ => word!("out"), - typ => must!(match_type!(BAREWORD)), - expr => must!(expression), + typ => wrap_err!(must!(match_type!(BAREWORD)), "Expected converter name"), + expr => wrap_err!(must!(expression), "Expected Expression to export"), _ => must!(punct!(";")), (Statement::Output(typ.clone(), expr.clone())) )