diff --git a/docsite/site/content/reference/expressions.md b/docsite/site/content/reference/expressions.md index cb58a73..02b6f75 100644 --- a/docsite/site/content/reference/expressions.md +++ b/docsite/site/content/reference/expressions.md @@ -164,9 +164,19 @@ Type test expressions --------------------- ucg has the `is` operator for testing that something is of a given base type. +The type must be a string literal matching one of: + +* `"null"` +* `"str"` +* `"int"` +* `"float"` +* `"tuple"` +* `"list"` +* `"macro"` +* `"module"` ``` -("foo" is str) == true; +("foo" is "str") == true; ``` Copy Expressions diff --git a/integration_tests/types_test.ucg b/integration_tests/types_test.ucg index 58ebe21..c9d18ae 100644 --- a/integration_tests/types_test.ucg +++ b/integration_tests/types_test.ucg @@ -1,51 +1,63 @@ let t = import "std/testing.ucg".asserts{}; assert t.ok{ - test = "foo" is str, + test = "foo" is "str", desc = "foo is a str", }; assert t.not_ok{ - test = "foo" is int, + test = "foo" is "int", desc = "foo is not an int", }; assert t.ok{ - test = {foo="bar"} is tuple, + test = {foo="bar"} is "tuple", desc = "found a tuple", }; assert t.not_ok{ - test = {foo="bar"} is str, + test = {foo="bar"} is "str", desc = "a tuple is not a str", }; assert t.ok{ - test = [1, 2] is list, + test = [1, 2] is "list", desc = "found a list", }; assert t.not_ok{ - test = [1, 2] is tuple, + test = [1, 2] is "tuple", desc = "list is not a tuple", }; assert t.ok{ - test = (macro(arg) => arg) is macro, + test = (macro(arg) => arg) is "macro", desc = "found a macro", }; assert t.not_ok{ - test = (macro(arg) => arg) is list, + test = (macro(arg) => arg) is "list", desc = "a macro is not a list", }; assert t.ok{ - test = (module{} => {}) is module, + test = (module{} => {}) is "module", desc = "found a module", }; assert t.not_ok{ - test = module{} => {} is macro, + test = module{} => {} is "macro", desc = "a module is not a macro", +}; + +let foo_check = macro(val) => (foo in val) && (val.foo is "str"); + +assert t.ok{ + test = foo_check({foo="bar"}), + desc = "we can check for foo string fields", +}; + +assert t.not_ok{ + test = foo_check({bar="foo"}), + desc = "we can check for absent foo string fields", }; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 28d5bd4..050c1f2 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -405,6 +405,7 @@ pub enum BinaryExprType { REMatch, NotREMatch, IN, + IS, // Selector operator DOT, } @@ -424,18 +425,19 @@ impl BinaryExprType { BinaryExprType::LT => 1, BinaryExprType::REMatch => 1, BinaryExprType::NotREMatch => 1, - BinaryExprType::IN => 1, + BinaryExprType::IN => 2, + BinaryExprType::IS => 2, // Sum operators are next least tightly bound - BinaryExprType::Add => 2, - BinaryExprType::Sub => 2, + BinaryExprType::Add => 3, + BinaryExprType::Sub => 3, // Product operators are next tightly bound - BinaryExprType::Mul => 3, - BinaryExprType::Div => 3, + BinaryExprType::Mul => 4, + BinaryExprType::Div => 4, // Boolean operators bind tighter than math - BinaryExprType::AND => 4, - BinaryExprType::OR => 4, + BinaryExprType::AND => 5, + BinaryExprType::OR => 5, // Dot operators are most tightly bound. - BinaryExprType::DOT => 5, + BinaryExprType::DOT => 6, } } } @@ -602,9 +604,6 @@ pub enum Expression { // Binary expressions Binary(BinaryOpDef), - // Type tests - IS(IsDef), - // Complex Expressions Copy(CopyDef), Range(RangeDef), @@ -637,7 +636,6 @@ impl Expression { &Expression::FuncOp(ref def) => def.pos(), &Expression::Include(ref def) => &def.pos, &Expression::Import(ref def) => &def.pos, - &Expression::IS(ref def) => &def.pos, } } } @@ -681,9 +679,6 @@ impl fmt::Display for Expression { &Expression::Include(_) => { write!(w, "")?; } - &Expression::IS(_) => { - write!(w, "")?; - } &Expression::Import(_) => { write!(w, "")?; } diff --git a/src/ast/walk.rs b/src/ast/walk.rs index 33236af..6a48e82 100644 --- a/src/ast/walk.rs +++ b/src/ast/walk.rs @@ -103,9 +103,6 @@ impl<'a> AstWalker<'a> { self.walk_expression(expr.as_mut()); } } - Expression::IS(ref mut def) => { - self.walk_expression(def.target.as_mut()); - } Expression::Select(ref mut def) => { self.walk_expression(def.default.as_mut()); self.walk_expression(def.val.as_mut()); diff --git a/src/build/mod.rs b/src/build/mod.rs index 425b248..e7c96bb 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -350,7 +350,7 @@ impl<'a> FileBuilder<'a> { fn check_reserved_word(name: &str) -> bool { match name { "self" | "assert" | "true" | "false" | "let" | "import" | "as" | "select" | "macro" - | "module" | "env" | "map" | "filter" | "NULL" | "out" => true, + | "module" | "env" | "map" | "filter" | "NULL" | "out" | "in" | "is" => true, _ => false, } } @@ -952,7 +952,10 @@ impl<'a> FileBuilder<'a> { if let &BinaryExprType::IN = kind { // TODO Should we support this operation on strings too? return self.do_element_check(&def.left, &def.right, scope); - }; + } + if let &BinaryExprType::IS = kind { + return self.eval_is_check(def, scope); + } match kind { // We special case the boolean operators because we want them to short circuit. &BinaryExprType::AND | &BinaryExprType::OR => { @@ -994,6 +997,7 @@ impl<'a> FileBuilder<'a> { self.eval_re_match(left, def.left.pos(), right, def.right.pos(), true) } &BinaryExprType::IN + | &BinaryExprType::IS | &BinaryExprType::DOT | &BinaryExprType::AND | &BinaryExprType::OR => panic!("Unreachable"), @@ -1702,9 +1706,23 @@ impl<'a> FileBuilder<'a> { Ok(Rc::new(Val::List(vec))) } - pub fn eval_is_check(&self, def: &IsDef, scope: &Scope) -> Result, Box> { - let val = self.eval_expr(def.target.as_ref(), scope)?; - let result = match def.typ.fragment.as_str() { + pub fn eval_is_check( + &self, + def: &BinaryOpDef, + scope: &Scope, + ) -> Result, Box> { + let typ = match def.right.as_ref() { + Expression::Simple(Value::Str(ref s)) => s.val.clone(), + _ => { + return Err(Box::new(error::BuildError::new( + format!("Expected string expression but got {}", def.right), + error::ErrorType::TypeFail, + def.right.pos().clone(), + ))); + } + }; + let val = self.eval_expr(def.left.as_ref(), scope)?; + let result = match typ.as_str() { "str" => val.is_str(), "bool" => val.is_bool(), "null" => val.is_empty(), @@ -1718,7 +1736,7 @@ impl<'a> FileBuilder<'a> { return Err(Box::new(error::BuildError::new( format!("Expected valid type name but got {}", other), error::ErrorType::TypeFail, - def.pos.clone(), + def.right.pos().clone(), ))); } }; @@ -1748,7 +1766,6 @@ impl<'a> FileBuilder<'a> { &Expression::FuncOp(ref def) => self.eval_func_op(def, scope), &Expression::Include(ref def) => self.eval_include(def), &Expression::Import(ref def) => self.eval_import(def), - &Expression::IS(ref def) => self.eval_is_check(def, scope), } } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 88ea9fe..1e03432 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -650,21 +650,6 @@ make_fn!( ) ); -make_fn!( - is_type_expression, Expression>, - do_each!( - pos => pos, - expr => non_op_expression, - _ => word!("is"), - typ => must!(match_type!(BAREWORD)), - (Expression::IS(IsDef{ - pos: pos, - target: Box::new(expr), - typ: typ, - })) - ) -); - fn unprefixed_expression(input: SliceIter) -> ParseResult { let _input = input.clone(); either!( @@ -693,7 +678,7 @@ make_fn!( fn expression(input: SliceIter) -> ParseResult { let _input = input.clone(); - match trace_parse!(_input, either!(is_type_expression, op_expression)) { + match trace_parse!(_input, op_expression) { Result::Incomplete(i) => Result::Incomplete(i), Result::Fail(_) => trace_parse!(input, non_op_expression), Result::Abort(e) => Result::Abort(e), diff --git a/src/parse/precedence.rs b/src/parse/precedence.rs index 2aae458..c9611ce 100644 --- a/src/parse/precedence.rs +++ b/src/parse/precedence.rs @@ -20,6 +20,17 @@ use abortable_parser::{Error, Peekable, Result, SliceIter}; use super::{non_op_expression, ParseResult}; use crate::ast::*; +macro_rules! abort_on_end { + ($i:expr) => {{ + if eoi($i.clone()).is_complete() { + return Result::Fail(Error::new( + format!("Expected Expression found End Of Input"), + Box::new($i.clone()), + )); + } + }}; +} + /// Defines the intermediate stages of our bottom up parser for precedence parsing. #[derive(Debug, PartialEq, Clone)] pub enum Element { @@ -66,12 +77,7 @@ make_fn!( fn parse_expression(i: SliceIter) -> Result, Expression> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Abort(Error::new( - "Expected Expression found End Of Input", - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Expr(ref expr)) = el { return Result::Complete(i_.clone(), expr.clone()); @@ -87,12 +93,7 @@ fn parse_expression(i: SliceIter) -> Result, Express fn parse_bool_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Fail(Error::new( - format!("Expected Expression found End Of Input"), - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Op(ref op)) = el { match op { @@ -115,12 +116,7 @@ fn parse_bool_operator(i: SliceIter) -> Result, Bina fn parse_dot_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Fail(Error::new( - format!("Expected Expression found End Of Input"), - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Op(ref op)) = el { match op { @@ -143,12 +139,7 @@ fn parse_dot_operator(i: SliceIter) -> Result, Binar fn parse_sum_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Fail(Error::new( - format!("Expected Expression found End Of Input"), - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Op(ref op)) = el { match op { @@ -174,12 +165,7 @@ fn parse_sum_operator(i: SliceIter) -> Result, Binar fn parse_product_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Fail(Error::new( - format!("Expected Expression found End Of Input"), - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Op(ref op)) = el { match op { @@ -214,18 +200,14 @@ make_fn!( do_each!(_ => punct!(">="), (Element::Op(BinaryExprType::GTEqual))), do_each!(_ => punct!("<"), (Element::Op(BinaryExprType::LT))), do_each!(_ => punct!(">"), (Element::Op(BinaryExprType::GT))), - do_each!(_ => word!("in"), (Element::Op(BinaryExprType::IN))) + do_each!(_ => word!("in"), (Element::Op(BinaryExprType::IN))), + do_each!(_ => word!("is"), (Element::Op(BinaryExprType::IS))) ) ); fn parse_compare_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); - if eoi(i_.clone()).is_complete() { - return Result::Fail(Error::new( - format!("Expected Expression found End Of Input"), - Box::new(i_), - )); - } + abort_on_end!(i_); let el = i_.next(); if let Some(&Element::Op(ref op)) = el { match op { @@ -237,6 +219,7 @@ fn parse_compare_operator(i: SliceIter) -> Result, B | &BinaryExprType::REMatch | &BinaryExprType::NotREMatch | &BinaryExprType::Equal + | &BinaryExprType::IS | &BinaryExprType::IN => { return Result::Complete(i_.clone(), op.clone()); }