mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-23 18:29:50 -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 |{ foo = "hello world"|;
|
assert |{ foo = "hello world"|;
|
||||||
assert |{ = }|;
|
assert |{ = }|;
|
||||||
assert |let foo |;
|
|
||||||
assert |let |;
|
|
||||||
assert |import |;
|
|
||||||
assert |import foo |;
|
|
||||||
assert |out |;
|
|
||||||
assert |out "|;
|
|
||||||
assert |out json|;
|
|
||||||
assert |"|;
|
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 list2 = list;
|
||||||
let list3 = [1, 2];
|
let list3 = [1, 2];
|
||||||
|
|
||||||
assert |one == one|;
|
assert |
|
||||||
assert |one == one|;
|
one == one;
|
||||||
assert |one >= one|;
|
|;
|
||||||
assert |two > one|;
|
assert |
|
||||||
assert |two >= two|;
|
one == one;
|
||||||
assert |tpl1 == tpl2|;
|
|;
|
||||||
assert |tpl1 != tpl3|;
|
assert |
|
||||||
assert |list == list2|;
|
one >= one;
|
||||||
assert |list != list3|;
|
|;
|
||||||
|
assert |
|
||||||
|
two > one;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
two >= two;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl1 == tpl2;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl1 != tpl3;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
list == list2;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
list != list3;
|
||||||
|
|;
|
||||||
|
|
||||||
// Deep Comparisons
|
// Deep Comparisons
|
||||||
let tpl4 = {
|
let tpl4 = {
|
||||||
@ -40,17 +58,33 @@ let less = {
|
|||||||
foo = "bar"
|
foo = "bar"
|
||||||
};
|
};
|
||||||
|
|
||||||
assert |tpl4.inner == copy.inner|;
|
assert |
|
||||||
assert |tpl4.inner.fld == copy.inner.fld|;
|
tpl4.inner == copy.inner;
|
||||||
assert |tpl4.lst == copy.lst|;
|
|;
|
||||||
assert |tpl4.foo == copy.foo|;
|
assert |
|
||||||
assert |tpl4 == copy|;
|
tpl4.inner.fld == copy.inner.fld;
|
||||||
assert |tpl4 != extra|;
|
|;
|
||||||
assert |tpl4 != less|;
|
assert |
|
||||||
|
tpl4.lst == copy.lst;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl4.foo == copy.foo;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl4 == copy;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl4 != extra;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
tpl4 != less;
|
||||||
|
|;
|
||||||
|
|
||||||
// Expression comparisons
|
// Expression comparisons
|
||||||
assert |2 == 1+1|;
|
assert |2 == 1+1;|;
|
||||||
assert |(1+1) == 2|;
|
assert |(1+1) == 2;|;
|
||||||
assert |(1+1) == (1+1)|;
|
assert |(1+1) == (1+1);|;
|
||||||
let want = "foo";
|
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 |
|
||||||
assert |[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]|;
|
"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 = {
|
let tpl = {
|
||||||
foo = NULL,
|
foo = NULL,
|
||||||
};
|
};
|
||||||
assert |tpl.foo == empty|;
|
assert |
|
||||||
|
tpl.foo == empty;
|
||||||
|
|;
|
@ -1,4 +1,12 @@
|
|||||||
assert |"hello @" % ("world") == "hello world"|;
|
assert |
|
||||||
assert |"1 @ @" % (2, 3) == "1 2 3"|;
|
"hello @" % ("world") == "hello world";
|
||||||
assert |"@ or @" % (true, false) == "true or false"|;
|
|;
|
||||||
assert |"@" % (NULL) == "NULL"|;
|
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,
|
result = item < 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert |map mapper.result list1 == [2, 3, 4, 5]|;
|
assert |
|
||||||
assert |(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]|;
|
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 [1, 2, 3, 4] == [2, 3, 4, 5];
|
||||||
|
|;
|
||||||
|
|
||||||
assert |filter filtrator.result list2 == ["foo", "foo"]|;
|
assert |
|
||||||
assert |(filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"]|;
|
filter filtrator.result list2 == ["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 ["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 simpleresult = simplemacro(1, 2, 3);
|
||||||
let cplxresult = cplxmacro(1, "We", 3.0);
|
let cplxresult = cplxmacro(1, "We", 3.0);
|
||||||
|
|
||||||
assert |simpleresult.field1 == 1|;
|
assert |
|
||||||
assert |simpleresult.field2 == 2|;
|
simpleresult.field1 == 1;
|
||||||
assert |simpleresult.field3 == 3|;
|
|;
|
||||||
|
assert |
|
||||||
|
simpleresult.field2 == 2;
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
simpleresult.field3 == 3;
|
||||||
|
|;
|
||||||
|
|
||||||
assert |cplxresult.field1 == 2|;
|
assert |
|
||||||
assert |cplxresult.field2 == "We are here"|;
|
cplxresult.field1 == 2;
|
||||||
assert |cplxresult.field3 == 2.0|;
|
|;
|
||||||
assert |cplxresult.boolfield == true|;
|
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 |
|
||||||
assert |2 + 2 * 3 == 8|;
|
2 * 2 + 1 == 5;
|
||||||
assert |2 * (2 + 1) == 6|;
|
|;
|
||||||
assert |2 * 2 + 1 > 4|;
|
assert |
|
||||||
assert |2 * 2 + 1 < 6|;
|
2 + 2 * 3 == 8;
|
||||||
assert |2 * 2 + 1 >= 5|;
|
|;
|
||||||
assert |2 * 2 + 1 <= 5|;
|
assert |
|
||||||
assert |2 / 2 == 1|;
|
2 * (2 + 1) == 6;
|
||||||
assert |2 - 1 == 1|;
|
|;
|
||||||
|
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",
|
door2 = "you lose",
|
||||||
};
|
};
|
||||||
|
|
||||||
assert |got == "grand prize"|;
|
assert |
|
||||||
assert |defaultgot == "OOPS"|;
|
got == "grand prize";
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
defaultgot == "OOPS";
|
||||||
|
|;
|
||||||
|
|
||||||
// select inside a macro
|
// select inside a macro
|
||||||
|
|
||||||
@ -25,6 +29,12 @@ let condmacro = macro(arg) => {
|
|||||||
|
|
||||||
let result = condmacro("opt1");
|
let result = condmacro("opt1");
|
||||||
|
|
||||||
assert |condmacro("opt1") == {output = "yay"}|;
|
assert |
|
||||||
assert |condmacro("opt2") == {output = "boo"}|;
|
condmacro("opt1") == {output = "yay"};
|
||||||
assert |condmacro("invalid") == {output = NULL}|;
|
|;
|
||||||
|
assert |
|
||||||
|
condmacro("opt2") == {output = "boo"};
|
||||||
|
|;
|
||||||
|
assert |
|
||||||
|
condmacro("invalid") == {output = NULL};
|
||||||
|
|;
|
@ -10,10 +10,24 @@ let testmacro = macro(arg) => {
|
|||||||
output = arg,
|
output = arg,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert |list.0 == 1|;
|
assert |
|
||||||
assert |list.1 == 2|;
|
list.0 == 1;
|
||||||
assert |list.3 == 4|;
|
|;
|
||||||
assert |tuple.field1 == 1|;
|
assert |
|
||||||
assert |tuple.field2 == 3|;
|
list.1 == 2;
|
||||||
assert |tuple.deeplist.0 == "foo"|;
|
|;
|
||||||
assert |tuple.deeplist.1 == "bar"|;
|
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],
|
list = [1, 2, 3, 4],
|
||||||
};
|
};
|
||||||
|
|
||||||
assert |simpletpl.foo == "bar"|;
|
assert |
|
||||||
assert |stringfieldtpl."field 1" == 1|;
|
simpletpl.foo == "bar";
|
||||||
assert |nestedtpl.scalar == 1|;
|
|;
|
||||||
assert |nestedtpl.inner.field == "value"|;
|
assert |
|
||||||
assert |nestedtpl.list.0 == 1|;
|
stringfieldtpl."field 1" == 1;
|
||||||
assert |nestedtpl.list.1 == 2|;
|
|;
|
||||||
assert |nestedtpl.list.2 == 3|;
|
assert |
|
||||||
assert |nestedtpl.list.3 == 4|;
|
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::rc::Rc;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
|
use simple_error;
|
||||||
|
|
||||||
use ast::*;
|
use ast::*;
|
||||||
use error;
|
use error;
|
||||||
use format;
|
use format;
|
||||||
@ -248,7 +250,7 @@ impl<'a> Builder<'a> {
|
|||||||
Ok(())
|
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()) {
|
match parse(input.clone()) {
|
||||||
Ok(stmts) => {
|
Ok(stmts) => {
|
||||||
//panic!("Successfully parsed {}", input);
|
//panic!("Successfully parsed {}", input);
|
||||||
@ -271,7 +273,7 @@ impl<'a> Builder<'a> {
|
|||||||
|
|
||||||
/// Evaluate an input string as UCG.
|
/// Evaluate an input string as UCG.
|
||||||
pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<Error>> {
|
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.
|
/// 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 f = try!(File::open(name));
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
try!(f.read_to_string(&mut s));
|
try!(f.read_to_string(&mut s));
|
||||||
self.last = Some(try!(self.eval_string(&s)));
|
let eval_result = self.eval_string(&s);
|
||||||
Ok(())
|
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>> {
|
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.
|
// we are not in validate_mode then build_asserts are noops.
|
||||||
return Ok(Rc::new(Val::Empty));
|
return Ok(Rc::new(Val::Empty));
|
||||||
}
|
}
|
||||||
let mut expr_as_stmt = String::new();
|
|
||||||
let expr = &tok.fragment;
|
let expr = &tok.fragment;
|
||||||
expr_as_stmt.push_str(expr);
|
|
||||||
expr_as_stmt.push_str(";");
|
|
||||||
let assert_input =
|
let assert_input =
|
||||||
OffsetStrIter::new_with_offsets(&expr_as_stmt, tok.pos.line - 1, tok.pos.column - 1);
|
OffsetStrIter::new_with_offsets(expr, tok.pos.line - 1, tok.pos.column - 1);
|
||||||
let ok = match self.eval_span(assert_input) {
|
let ok = match self.eval_input(assert_input) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// failure!
|
// 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
|
//! 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
|
//! 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
|
//! of unit testing for your configurations. It starts with the assert keyword followed by a valid block of ucg statements
|
||||||
//! delimited by `|` characters.
|
//! delimited by `|` characters. The final statement in the in the block must evaluate to a boolean expression.
|
||||||
//!
|
//!
|
||||||
//! ```ucg
|
//! ```ucg
|
||||||
//! assert host == "www.example.com";
|
//! assert |
|
||||||
//! assert |select qa, 443, {
|
//! host == "www.example.com";
|
||||||
//! qa = 80,
|
//! |;
|
||||||
//! prod = 443,
|
//! assert |
|
||||||
//! } == 443|;
|
//! 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
|
//! 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!(
|
make_fn!(
|
||||||
let_stmt_body<SliceIter<Token>, Statement>,
|
let_stmt_body<SliceIter<Token>, Statement>,
|
||||||
do_each!(
|
do_each!(
|
||||||
name => match_type!(BAREWORD),
|
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
|
||||||
_ => punct!("="),
|
_ => 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!(";"),
|
_ => punct!(";"),
|
||||||
(tuple_to_let(name, val))
|
(tuple_to_let(name, val))
|
||||||
)
|
)
|
||||||
@ -870,9 +871,9 @@ fn tuple_to_import(tok: Token, tok2: Token) -> Statement {
|
|||||||
make_fn!(
|
make_fn!(
|
||||||
import_stmt_body<SliceIter<Token>, Statement>,
|
import_stmt_body<SliceIter<Token>, Statement>,
|
||||||
do_each!(
|
do_each!(
|
||||||
path => match_type!(STR),
|
path => wrap_err!(match_type!(STR), "Expected import path"),
|
||||||
_ => word!("as"),
|
_ => word!("as"),
|
||||||
name => match_type!(BAREWORD),
|
name => wrap_err!(match_type!(BAREWORD), "Expected import name"),
|
||||||
_ => punct!(";"),
|
_ => punct!(";"),
|
||||||
(tuple_to_import(path, name))
|
(tuple_to_import(path, name))
|
||||||
)
|
)
|
||||||
@ -902,8 +903,8 @@ make_fn!(
|
|||||||
out_statement<SliceIter<Token>, Statement>,
|
out_statement<SliceIter<Token>, Statement>,
|
||||||
do_each!(
|
do_each!(
|
||||||
_ => word!("out"),
|
_ => word!("out"),
|
||||||
typ => must!(match_type!(BAREWORD)),
|
typ => wrap_err!(must!(match_type!(BAREWORD)), "Expected converter name"),
|
||||||
expr => must!(expression),
|
expr => wrap_err!(must!(expression), "Expected Expression to export"),
|
||||||
_ => must!(punct!(";")),
|
_ => must!(punct!(";")),
|
||||||
(Statement::Output(typ.clone(), expr.clone()))
|
(Statement::Output(typ.clone(), expr.clone()))
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user