mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
FEATURE: Better error reporting.
Slight change to how assert works to support this. We no longer automatically add a semicolon to the expressions we require the user to right them. This updates the docs to illustrate that and reformats our integration test suite for this and readability.
This commit is contained in:
parent
d254ff3f94
commit
d2f0ea9f24
0
example_errors/bad_assert.ucg
Normal file
0
example_errors/bad_assert.ucg
Normal file
1
example_errors/bad_import.ucg
Normal file
1
example_errors/bad_import.ucg
Normal file
@ -0,0 +1 @@
|
||||
import
|
@ -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 |=|;
|
||||
assert |=|;
|
||||
|
||||
assert |let |;
|
||||
assert |let foo |;
|
||||
assert |let foo =|;
|
||||
//assert |import |;
|
||||
//assert |import foo |;
|
||||
//assert |out |;
|
||||
//assert |out "|;
|
||||
//assert |out json|;
|
||||
|
@ -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|;
|
||||
assert |
|
||||
select want, 1, { foo=2, } == 2;
|
||||
|;
|
@ -1,2 +1,6 @@
|
||||
assert |"hello " + "world" == "hello world"|;
|
||||
assert |[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]|;
|
||||
assert |
|
||||
"hello " + "world" == "hello world";
|
||||
|;
|
||||
assert |
|
||||
[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6];
|
||||
|;
|
@ -2,4 +2,6 @@ let empty = NULL;
|
||||
let tpl = {
|
||||
foo = NULL,
|
||||
};
|
||||
assert |tpl.foo == empty|;
|
||||
assert |
|
||||
tpl.foo == empty;
|
||||
|;
|
@ -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"|;
|
||||
assert |
|
||||
"hello @" % ("world") == "hello world";
|
||||
|;
|
||||
assert |
|
||||
"1 @ @" % (2, 3) == "1 2 3";
|
||||
|;
|
||||
assert |
|
||||
"@ or @" % (true, false) == "true or false";
|
||||
|;
|
||||
assert |
|
||||
"@" % (NULL) == "NULL";
|
||||
|;
|
@ -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]|;
|
||||
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];
|
||||
|;
|
@ -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|;
|
||||
assert |
|
||||
cplxresult.field1 == 2;
|
||||
|;
|
||||
assert |
|
||||
cplxresult.field2 == "We are here";
|
||||
|;
|
||||
assert |
|
||||
cplxresult.field3 == 2.0;
|
||||
|;
|
||||
assert |
|
||||
cplxresult.boolfield == true;
|
||||
|;
|
@ -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|;
|
||||
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;
|
||||
|;
|
@ -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}|;
|
||||
assert |
|
||||
condmacro("opt1") == {output = "yay"};
|
||||
|;
|
||||
assert |
|
||||
condmacro("opt2") == {output = "boo"};
|
||||
|;
|
||||
assert |
|
||||
condmacro("invalid") == {output = NULL};
|
||||
|;
|
@ -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"|;
|
||||
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";
|
||||
|;
|
@ -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|;
|
||||
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;
|
||||
|;
|
@ -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<Rc<Val>, Box<Error>> {
|
||||
fn eval_input(&mut self, input: OffsetStrIter) -> Result<Rc<Val>, Box<Error>> {
|
||||
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<Rc<Val>, Box<Error>> {
|
||||
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<Rc<Val>, Box<Error>> {
|
||||
@ -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!
|
||||
|
18
src/lib.rs
18
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
|
||||
|
@ -843,9 +843,10 @@ fn tuple_to_let(tok: Token, expr: Expression) -> Statement {
|
||||
make_fn!(
|
||||
let_stmt_body<SliceIter<Token>, 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<SliceIter<Token>, 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<SliceIter<Token>, 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()))
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user