FEATURE: Parsing support for assert statements.

This commit is contained in:
Jeremy Wall 2018-05-30 23:00:50 -05:00
parent 5c0df5b538
commit 9674d6006f
7 changed files with 76 additions and 12 deletions

View File

@ -792,6 +792,9 @@ pub enum Statement {
// Import a file.
Import(ImportDef),
// Assert statement
Assert(Expression),
}
#[cfg(test)]

View File

@ -505,6 +505,7 @@ impl Builder {
fn build_stmt(&mut self, stmt: &Statement) -> Result<Rc<Val>, Box<Error>> {
match stmt {
&Statement::Assert(ref expr) => self.build_assert(expr),
&Statement::Let(ref def) => self.build_let(def),
&Statement::Import(ref def) => self.build_import(def),
&Statement::Expression(ref expr) => self.eval_expr(expr),
@ -1110,6 +1111,21 @@ impl Builder {
)));
}
fn build_assert(&self, expr: &Expression) -> Result<Rc<Val>, Box<Error>> {
let ok = try!(self.eval_expr(expr));
if let &Val::Boolean(b) = ok.as_ref() {
// record the assertion result.
if b {
// success!
} else {
// failure!
}
} else {
// record an assertion type-failure result.
}
Ok(ok)
}
// Evals a single Expression in the context of a running Builder.
// It does not mutate the builders collected state at all.
pub fn eval_expr(&self, expr: &Expression) -> Result<Rc<Val>, Box<Error>> {

View File

@ -48,6 +48,7 @@
//!
//! The following words are reserved in ucg and can't be used as named bindings.
//!
//! * assert
//! * true
//! * false
//! * let
@ -371,7 +372,7 @@
//!
//! * Let statements
//!
//! The let expression binds the result of any valid expression to a name. It starts with the `let` keyword and is followed by
//! The let statement binds the result of any valid expression to a name. It starts with the `let` keyword and is followed by
//! the name of the binding, an `=`, and a valid ucg expression.
//!
//! ```ucg
@ -389,6 +390,20 @@
//!
//! let mysqlconf = dbconfigs.mysql;
//! ```
//!
//! * Assert statement
//!
//! 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 testting for your configurations. It starts with the assert keyword followed by a valid boolean ucg expression.
//!
//! ```ucg
//! assert host == "www.example.com";
//! assert select qa, 443, {
//! qa = 80,
//! prod = 443,
//! } == 443;
//! ```
// The following is necessary to allow the macros in tokenizer and parse modules
// to succeed.

View File

@ -896,9 +896,24 @@ named!(import_statement<TokenIter, Statement, error::Error>,
)
);
named!(assert_statement<TokenIter, Statement, error::Error>,
do_parse!(
word!("assert") >>
pos: pos >>
expr: add_return_error!(
nom::ErrorKind::Custom(
error::Error::new(
"Invalid syntax for assert",
error::ErrorType::ParseError, pos)),
expression) >>
(Statement::Assert(expr))
)
);
//trace_macros!(true);
fn statement(i: TokenIter) -> nom::IResult<TokenIter, Statement, error::Error> {
return alt_peek!(i,
word!("assert") => assert_statement |
word!("import") => import_statement |
word!("let") => let_statement |
expression_statement);

View File

@ -42,7 +42,7 @@ macro_rules! assert_error {
#[test]
fn test_null_parsing() {
assert_parse!(empty_value("NULL"), Value::Empty(Position::new(1, 1)));
assert_parse!(empty_value("NULL "), Value::Empty(Position::new(1, 1)));
}
#[test]
@ -337,7 +337,7 @@ fn test_expression_statement_parse() {
#[test]
fn test_expression_parse() {
assert_parse!(
expression("NULL"),
expression("NULL "),
Expression::Simple(Value::Empty(Position::new(1, 1)))
);
assert_parse!(

View File

@ -142,7 +142,7 @@ macro_rules! do_tag_tok {
}
named!(emptytok( Span ) -> Token,
do_tag_tok!(TokenType::EMPTY, "NULL")
do_tag_tok!(TokenType::EMPTY, "NULL", WS)
);
named!(commatok( Span ) -> Token,
@ -233,10 +233,6 @@ named!(fatcommatok( Span ) -> Token,
do_tag_tok!(TokenType::PUNCT, "=>")
);
named!(lettok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "let", WS)
);
named!(selecttok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "select", WS)
);
@ -245,10 +241,18 @@ named!(macrotok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "macro", WS)
);
named!(lettok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "let", WS)
);
named!(importtok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "import", WS)
);
named!(asserttok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "assert", WS)
);
named!(astok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "as", WS)
);
@ -356,6 +360,7 @@ named!(token( Span ) -> Token,
booleantok |
lettok |
selecttok |
asserttok |
macrotok |
importtok |
astok |

View File

@ -4,7 +4,7 @@ use nom_locate::LocatedSpan;
#[test]
fn test_empty_token() {
let result = emptytok(LocatedSpan::new("NULL"));
let result = emptytok(LocatedSpan::new("NULL "));
assert!(result.is_done(), format!("result {:?} is not done", result));
if let nom::IResult::Done(_, tok) = result {
assert_eq!(tok.fragment, "NULL");
@ -12,6 +12,16 @@ fn test_empty_token() {
}
}
#[test]
fn test_assert_token() {
let result = asserttok(LocatedSpan::new("assert "));
assert!(result.is_done(), format!("result {:?} is not done", result));
if let nom::IResult::Done(_, tok) = result {
assert_eq!(tok.fragment, "assert");
assert_eq!(tok.typ, TokenType::BAREWORD);
}
}
#[test]
fn test_escape_quoted() {
let result = escapequoted(LocatedSpan::new("foo \\\"bar\""));
@ -93,7 +103,7 @@ fn test_lteqtok() {
#[test]
fn test_tokenize_one_of_each() {
let result = tokenize(LocatedSpan::new(
"let import macro select as => [ ] { } ; = % / * \
"map filter assert let import macro select as => [ ] { } ; = % / * \
+ - . ( ) , 1 . foo \"bar\" // comment\n ; true false == < > <= >= !=",
));
assert!(result.is_ok(), format!("result {:?} is not ok", result));
@ -101,8 +111,8 @@ fn test_tokenize_one_of_each() {
for (i, t) in v.iter().enumerate() {
println!("{}: {:?}", i, t);
}
assert_eq!(v.len(), 35);
assert_eq!(v[34].typ, TokenType::END);
assert_eq!(v.len(), 38);
assert_eq!(v[37].typ, TokenType::END);
}
#[test]