FIXES: Unit test and fixing performance regressions.

This commit is contained in:
Jeremy Wall 2018-06-10 13:51:19 -05:00
parent 3524a5df73
commit bb198d9d02
7 changed files with 264 additions and 91 deletions

View File

@ -38,3 +38,11 @@ Some options here could be:
* Flags should allow different seperators for prefixed flags.
* YAML export
* HCL export
# Release Checklist
* Cargo test
* Cargo fmt
* Update Cargo.toml version.
* Tag git commit with version tag.
* Cargo publish

View File

@ -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;

View File

@ -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<Rc<Val>, Box<Error>> {
let mut new_fields = Vec::<(Positioned<String>, Rc<Val>)>::new();
for &(ref name, ref expr) in fields.iter() {
@ -433,6 +435,7 @@ impl Builder {
pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<Error>> {
match parse(Span::new(input)) {
Ok(stmts) => {
//panic!("Successfully parsed {}", input);
let mut out: Option<Rc<Val>> = None;
for stmt in stmts.iter() {
out = Some(try!(self.build_stmt(stmt)));

View File

@ -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() {

View File

@ -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<TokenIter, BinaryExprType, error::Error>,
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<TokenIter, Expression, error::Error>,
do_binary_expr!(punct!("+"), BinaryExprType::Add, alt!(product_expression | simple_expression | grouped_expression))
);
// trace_macros!(false);
named!(sub_expression<TokenIter, Expression, error::Error>,
do_binary_expr!(punct!("-"), BinaryExprType::Sub, alt!(product_expression | simple_expression | grouped_expression))
);
named!(sum_expression<TokenIter, Expression, error::Error>,
alt!(add_expression | sub_expression)
);
named!(mul_expression<TokenIter, Expression, error::Error>,
do_binary_expr!(punct!("*"), BinaryExprType::Mul)
);
named!(div_expression<TokenIter, Expression, error::Error>,
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<TokenIter, Expression, error::Error>,
alt!(mul_expression | div_expression)
do_binary_expr!(
alt_peek!(
punct!("*") => math_op_type |
punct!("/") => math_op_type)
)
);
named!(math_expression<TokenIter, Expression, error::Error>,
@ -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<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!("=="), CompareType::Equal)
);
named!(not_eqeq_expression<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!("!="), CompareType::NotEqual)
);
named!(lt_eqeq_expression<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!("<="), CompareType::LTEqual)
);
named!(gt_eqeq_expression<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!(">="), CompareType::GTEqual)
);
named!(gt_expression<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!(">"), CompareType::GT)
);
named!(lt_expression<TokenIter, Expression, error::Error>,
do_compare_expr!(punct!("<"), CompareType::LT)
named!(compare_op_type<TokenIter, CompareType, error::Error>,
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<TokenIter, Expression, error::Error>,
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<TokenIter, Expression, error::Error>,
alt!(math_expression | compare_expression)
);
@ -817,7 +817,7 @@ named!(non_op_expression<TokenIter, Expression, error::Error>,
);
named!(expression<TokenIter, Expression, error::Error>,
alt!(complete!(op_expression) | complete!(non_op_expression))
alt_complete!(op_expression | non_op_expression)
);
fn expression_to_statement(v: Expression) -> ParseResult<Statement> {

View File

@ -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]

View File

@ -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,