mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-21 18:10:42 -04:00
FEATURE: Parsing support for assert statements.
This commit is contained in:
parent
5c0df5b538
commit
9674d6006f
@ -792,6 +792,9 @@ pub enum Statement {
|
||||
|
||||
// Import a file.
|
||||
Import(ImportDef),
|
||||
|
||||
// Assert statement
|
||||
Assert(Expression),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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>> {
|
||||
|
17
src/lib.rs
17
src/lib.rs
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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!(
|
||||
|
@ -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 |
|
||||
|
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user