FIX: Treat is as a regular binary operator

do syntax checks post parse. Also to avoid collisions with
keywords require quoting the types.
This commit is contained in:
Jeremy Wall 2019-01-19 10:34:58 -06:00
parent 1dba5f66ce
commit eaa3e84179
7 changed files with 88 additions and 89 deletions

View File

@ -164,9 +164,19 @@ Type test expressions
--------------------- ---------------------
ucg has the `is` operator for testing that something is of a given base type. 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 Copy Expressions

View File

@ -1,51 +1,63 @@
let t = import "std/testing.ucg".asserts{}; let t = import "std/testing.ucg".asserts{};
assert t.ok{ assert t.ok{
test = "foo" is str, test = "foo" is "str",
desc = "foo is a str", desc = "foo is a str",
}; };
assert t.not_ok{ assert t.not_ok{
test = "foo" is int, test = "foo" is "int",
desc = "foo is not an int", desc = "foo is not an int",
}; };
assert t.ok{ assert t.ok{
test = {foo="bar"} is tuple, test = {foo="bar"} is "tuple",
desc = "found a tuple", desc = "found a tuple",
}; };
assert t.not_ok{ assert t.not_ok{
test = {foo="bar"} is str, test = {foo="bar"} is "str",
desc = "a tuple is not a str", desc = "a tuple is not a str",
}; };
assert t.ok{ assert t.ok{
test = [1, 2] is list, test = [1, 2] is "list",
desc = "found a list", desc = "found a list",
}; };
assert t.not_ok{ assert t.not_ok{
test = [1, 2] is tuple, test = [1, 2] is "tuple",
desc = "list is not a tuple", desc = "list is not a tuple",
}; };
assert t.ok{ assert t.ok{
test = (macro(arg) => arg) is macro, test = (macro(arg) => arg) is "macro",
desc = "found a macro", desc = "found a macro",
}; };
assert t.not_ok{ assert t.not_ok{
test = (macro(arg) => arg) is list, test = (macro(arg) => arg) is "list",
desc = "a macro is not a list", desc = "a macro is not a list",
}; };
assert t.ok{ assert t.ok{
test = (module{} => {}) is module, test = (module{} => {}) is "module",
desc = "found a module", desc = "found a module",
}; };
assert t.not_ok{ assert t.not_ok{
test = module{} => {} is macro, test = module{} => {} is "macro",
desc = "a module is not a 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",
};

View File

@ -405,6 +405,7 @@ pub enum BinaryExprType {
REMatch, REMatch,
NotREMatch, NotREMatch,
IN, IN,
IS,
// Selector operator // Selector operator
DOT, DOT,
} }
@ -424,18 +425,19 @@ impl BinaryExprType {
BinaryExprType::LT => 1, BinaryExprType::LT => 1,
BinaryExprType::REMatch => 1, BinaryExprType::REMatch => 1,
BinaryExprType::NotREMatch => 1, BinaryExprType::NotREMatch => 1,
BinaryExprType::IN => 1, BinaryExprType::IN => 2,
BinaryExprType::IS => 2,
// Sum operators are next least tightly bound // Sum operators are next least tightly bound
BinaryExprType::Add => 2, BinaryExprType::Add => 3,
BinaryExprType::Sub => 2, BinaryExprType::Sub => 3,
// Product operators are next tightly bound // Product operators are next tightly bound
BinaryExprType::Mul => 3, BinaryExprType::Mul => 4,
BinaryExprType::Div => 3, BinaryExprType::Div => 4,
// Boolean operators bind tighter than math // Boolean operators bind tighter than math
BinaryExprType::AND => 4, BinaryExprType::AND => 5,
BinaryExprType::OR => 4, BinaryExprType::OR => 5,
// Dot operators are most tightly bound. // Dot operators are most tightly bound.
BinaryExprType::DOT => 5, BinaryExprType::DOT => 6,
} }
} }
} }
@ -602,9 +604,6 @@ pub enum Expression {
// Binary expressions // Binary expressions
Binary(BinaryOpDef), Binary(BinaryOpDef),
// Type tests
IS(IsDef),
// Complex Expressions // Complex Expressions
Copy(CopyDef), Copy(CopyDef),
Range(RangeDef), Range(RangeDef),
@ -637,7 +636,6 @@ impl Expression {
&Expression::FuncOp(ref def) => def.pos(), &Expression::FuncOp(ref def) => def.pos(),
&Expression::Include(ref def) => &def.pos, &Expression::Include(ref def) => &def.pos,
&Expression::Import(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(_) => { &Expression::Include(_) => {
write!(w, "<Include>")?; write!(w, "<Include>")?;
} }
&Expression::IS(_) => {
write!(w, "<IS>")?;
}
&Expression::Import(_) => { &Expression::Import(_) => {
write!(w, "<Include>")?; write!(w, "<Include>")?;
} }

View File

@ -103,9 +103,6 @@ impl<'a> AstWalker<'a> {
self.walk_expression(expr.as_mut()); self.walk_expression(expr.as_mut());
} }
} }
Expression::IS(ref mut def) => {
self.walk_expression(def.target.as_mut());
}
Expression::Select(ref mut def) => { Expression::Select(ref mut def) => {
self.walk_expression(def.default.as_mut()); self.walk_expression(def.default.as_mut());
self.walk_expression(def.val.as_mut()); self.walk_expression(def.val.as_mut());

View File

@ -350,7 +350,7 @@ impl<'a> FileBuilder<'a> {
fn check_reserved_word(name: &str) -> bool { fn check_reserved_word(name: &str) -> bool {
match name { match name {
"self" | "assert" | "true" | "false" | "let" | "import" | "as" | "select" | "macro" "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, _ => false,
} }
} }
@ -952,7 +952,10 @@ impl<'a> FileBuilder<'a> {
if let &BinaryExprType::IN = kind { if let &BinaryExprType::IN = kind {
// TODO Should we support this operation on strings too? // TODO Should we support this operation on strings too?
return self.do_element_check(&def.left, &def.right, scope); return self.do_element_check(&def.left, &def.right, scope);
}; }
if let &BinaryExprType::IS = kind {
return self.eval_is_check(def, scope);
}
match kind { match kind {
// We special case the boolean operators because we want them to short circuit. // We special case the boolean operators because we want them to short circuit.
&BinaryExprType::AND | &BinaryExprType::OR => { &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) self.eval_re_match(left, def.left.pos(), right, def.right.pos(), true)
} }
&BinaryExprType::IN &BinaryExprType::IN
| &BinaryExprType::IS
| &BinaryExprType::DOT | &BinaryExprType::DOT
| &BinaryExprType::AND | &BinaryExprType::AND
| &BinaryExprType::OR => panic!("Unreachable"), | &BinaryExprType::OR => panic!("Unreachable"),
@ -1702,9 +1706,23 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::List(vec))) Ok(Rc::new(Val::List(vec)))
} }
pub fn eval_is_check(&self, def: &IsDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { pub fn eval_is_check(
let val = self.eval_expr(def.target.as_ref(), scope)?; &self,
let result = match def.typ.fragment.as_str() { def: &BinaryOpDef,
scope: &Scope,
) -> Result<Rc<Val>, Box<dyn Error>> {
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(), "str" => val.is_str(),
"bool" => val.is_bool(), "bool" => val.is_bool(),
"null" => val.is_empty(), "null" => val.is_empty(),
@ -1718,7 +1736,7 @@ impl<'a> FileBuilder<'a> {
return Err(Box::new(error::BuildError::new( return Err(Box::new(error::BuildError::new(
format!("Expected valid type name but got {}", other), format!("Expected valid type name but got {}", other),
error::ErrorType::TypeFail, 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::FuncOp(ref def) => self.eval_func_op(def, scope),
&Expression::Include(ref def) => self.eval_include(def), &Expression::Include(ref def) => self.eval_include(def),
&Expression::Import(ref def) => self.eval_import(def), &Expression::Import(ref def) => self.eval_import(def),
&Expression::IS(ref def) => self.eval_is_check(def, scope),
} }
} }
} }

View File

@ -650,21 +650,6 @@ make_fn!(
) )
); );
make_fn!(
is_type_expression<SliceIter<Token>, 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<Token>) -> ParseResult<Expression> { fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
let _input = input.clone(); let _input = input.clone();
either!( either!(
@ -693,7 +678,7 @@ make_fn!(
fn expression(input: SliceIter<Token>) -> ParseResult<Expression> { fn expression(input: SliceIter<Token>) -> ParseResult<Expression> {
let _input = input.clone(); 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::Incomplete(i) => Result::Incomplete(i),
Result::Fail(_) => trace_parse!(input, non_op_expression), Result::Fail(_) => trace_parse!(input, non_op_expression),
Result::Abort(e) => Result::Abort(e), Result::Abort(e) => Result::Abort(e),

View File

@ -20,6 +20,17 @@ use abortable_parser::{Error, Peekable, Result, SliceIter};
use super::{non_op_expression, ParseResult}; use super::{non_op_expression, ParseResult};
use crate::ast::*; 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. /// Defines the intermediate stages of our bottom up parser for precedence parsing.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Element { pub enum Element {
@ -66,12 +77,7 @@ make_fn!(
fn parse_expression(i: SliceIter<Element>) -> Result<SliceIter<Element>, Expression> { fn parse_expression(i: SliceIter<Element>) -> Result<SliceIter<Element>, Expression> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Abort(Error::new(
"Expected Expression found End Of Input",
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Expr(ref expr)) = el { if let Some(&Element::Expr(ref expr)) = el {
return Result::Complete(i_.clone(), expr.clone()); return Result::Complete(i_.clone(), expr.clone());
@ -87,12 +93,7 @@ fn parse_expression(i: SliceIter<Element>) -> Result<SliceIter<Element>, Express
fn parse_bool_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> { fn parse_bool_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Fail(Error::new(
format!("Expected Expression found End Of Input"),
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Op(ref op)) = el { if let Some(&Element::Op(ref op)) = el {
match op { match op {
@ -115,12 +116,7 @@ fn parse_bool_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, Bina
fn parse_dot_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> { fn parse_dot_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Fail(Error::new(
format!("Expected Expression found End Of Input"),
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Op(ref op)) = el { if let Some(&Element::Op(ref op)) = el {
match op { match op {
@ -143,12 +139,7 @@ fn parse_dot_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, Binar
fn parse_sum_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> { fn parse_sum_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Fail(Error::new(
format!("Expected Expression found End Of Input"),
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Op(ref op)) = el { if let Some(&Element::Op(ref op)) = el {
match op { match op {
@ -174,12 +165,7 @@ fn parse_sum_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, Binar
fn parse_product_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> { fn parse_product_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Fail(Error::new(
format!("Expected Expression found End Of Input"),
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Op(ref op)) = el { if let Some(&Element::Op(ref op)) = el {
match op { match op {
@ -214,18 +200,14 @@ make_fn!(
do_each!(_ => punct!(">="), (Element::Op(BinaryExprType::GTEqual))), do_each!(_ => punct!(">="), (Element::Op(BinaryExprType::GTEqual))),
do_each!(_ => punct!("<"), (Element::Op(BinaryExprType::LT))), do_each!(_ => punct!("<"), (Element::Op(BinaryExprType::LT))),
do_each!(_ => punct!(">"), (Element::Op(BinaryExprType::GT))), 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<Element>) -> Result<SliceIter<Element>, BinaryExprType> { fn parse_compare_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, BinaryExprType> {
let mut i_ = i.clone(); let mut i_ = i.clone();
if eoi(i_.clone()).is_complete() { abort_on_end!(i_);
return Result::Fail(Error::new(
format!("Expected Expression found End Of Input"),
Box::new(i_),
));
}
let el = i_.next(); let el = i_.next();
if let Some(&Element::Op(ref op)) = el { if let Some(&Element::Op(ref op)) = el {
match op { match op {
@ -237,6 +219,7 @@ fn parse_compare_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, B
| &BinaryExprType::REMatch | &BinaryExprType::REMatch
| &BinaryExprType::NotREMatch | &BinaryExprType::NotREMatch
| &BinaryExprType::Equal | &BinaryExprType::Equal
| &BinaryExprType::IS
| &BinaryExprType::IN => { | &BinaryExprType::IN => {
return Result::Complete(i_.clone(), op.clone()); return Result::Complete(i_.clone(), op.clone());
} }