diff --git a/integration_tests/comparisons_test.ucg b/integration_tests/comparisons_test.ucg index 84ebbe8..f7456de 100644 --- a/integration_tests/comparisons_test.ucg +++ b/integration_tests/comparisons_test.ucg @@ -219,4 +219,19 @@ let name = "foo"; assert { ok = (name) in {foo="bar"}, desc = "(name) in {foo=\"bar\"} works", +}; + +assert { + ok = not false, + desc = "not false is true", +}; + +assert { + ok = not true == false, + desc = "not true is false", +}; + +assert { + ok = not not true, + desc = "double negatives are positive", }; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a713832..bc9819f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -601,11 +601,18 @@ pub struct FailDef { pub message: Box, } +#[derive(Debug, PartialEq, Clone)] +pub struct NotDef { + pub pos: Position, + pub expr: Box, +} + /// Encodes a ucg expression. Expressions compute a value from. #[derive(Debug, PartialEq, Clone)] pub enum Expression { // Base Expression Simple(Value), + Not(NotDef), // Binary expressions Binary(BinaryOpDef), @@ -646,6 +653,7 @@ impl Expression { &Expression::Include(ref def) => &def.pos, &Expression::Import(ref def) => &def.pos, &Expression::Fail(ref def) => &def.pos, + &Expression::Not(ref def) => &def.pos, } } } @@ -695,6 +703,9 @@ impl fmt::Display for Expression { &Expression::Fail(_) => { write!(w, "")?; } + &Expression::Not(ref def) => { + write!(w, "!{}", def.expr)?; + } } Ok(()) } diff --git a/src/ast/walk.rs b/src/ast/walk.rs index d59ef66..5dce074 100644 --- a/src/ast/walk.rs +++ b/src/ast/walk.rs @@ -114,6 +114,9 @@ impl<'a> AstWalker<'a> { Expression::Import(_) | Expression::Include(_) | Expression::Fail(_) => { //noop } + Expression::Not(ref mut def) => { + self.walk_expression(def.expr.as_mut()); + } } } diff --git a/src/build/mod.rs b/src/build/mod.rs index ae79ed4..7f5a837 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1786,7 +1786,7 @@ impl<'a> FileBuilder<'a> { } else { Err(Box::new(error::BuildError::new( format!( - "Expected string form message but got {}", + "Expected string for message but got {}", def.message.as_ref() ), error::ErrorType::TypeFail, @@ -1794,6 +1794,21 @@ impl<'a> FileBuilder<'a> { ))) }; } + &Expression::Not(ref def) => { + let val = self.eval_expr(&def.expr, scope)?; + return if let Val::Boolean(b) = val.as_ref() { + Ok(Rc::new(Val::Boolean(!b))) + } else { + Err(Box::new(error::BuildError::new( + format!( + "Expected boolean for expression but got {}", + def.expr.as_ref() + ), + error::ErrorType::TypeFail, + def.expr.pos().clone(), + ))) + }; + } } } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 08bc57d..e2bd776 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -667,6 +667,19 @@ make_fn!( ) ); +make_fn!( + not_expression, Expression>, + do_each!( + pos => pos, + _ => word!("not"), + expr => must!(wrap_err!(expression, "Expected failure message")), + (Expression::Not(NotDef{ + pos: pos, + expr: Box::new(expr), + })) + ) +); + fn unprefixed_expression(input: SliceIter) -> ParseResult { let _input = input.clone(); either!( @@ -685,6 +698,7 @@ make_fn!( trace_parse!(func_op_expression), trace_parse!(macro_expression), trace_parse!(import_expression), + trace_parse!(not_expression), trace_parse!(fail_expression), trace_parse!(module_expression), trace_parse!(select_expression), diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 7ea5329..8c6ae22 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -278,6 +278,10 @@ make_fn!(istok, do_text_token_tok!(TokenType::BAREWORD, "is", WS) ); +make_fn!(nottok, + do_text_token_tok!(TokenType::BAREWORD, "not", WS) +); + make_fn!(failtok, do_text_token_tok!(TokenType::BAREWORD, "fail", WS) ); @@ -417,6 +421,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result, Token> { booleantok, intok, istok, + nottok, lettok, outtok, selecttok, diff --git a/std/testing.ucg b/std/testing.ucg index 7c6208b..32d9000 100644 --- a/std/testing.ucg +++ b/std/testing.ucg @@ -30,7 +30,7 @@ let asserts = module{ // descriptive message to use in output. desc=mod.default_description, } => { - let ok = mod.test != true; + let ok = not mod.test; let desc = "@" % (mod.desc);