mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
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:
parent
1dba5f66ce
commit
eaa3e84179
@ -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
|
||||
|
@ -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",
|
||||
};
|
@ -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, "<Include>")?;
|
||||
}
|
||||
&Expression::IS(_) => {
|
||||
write!(w, "<IS>")?;
|
||||
}
|
||||
&Expression::Import(_) => {
|
||||
write!(w, "<Include>")?;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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<Rc<Val>, Box<dyn Error>> {
|
||||
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<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(),
|
||||
"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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
let _input = input.clone();
|
||||
either!(
|
||||
@ -693,7 +678,7 @@ make_fn!(
|
||||
|
||||
fn expression(input: SliceIter<Token>) -> ParseResult<Expression> {
|
||||
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),
|
||||
|
@ -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<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, Express
|
||||
|
||||
fn parse_bool_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, Bina
|
||||
|
||||
fn parse_dot_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, Binar
|
||||
|
||||
fn parse_sum_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, Binar
|
||||
|
||||
fn parse_product_operator(i: SliceIter<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, 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<Element>) -> Result<SliceIter<Element>, B
|
||||
| &BinaryExprType::REMatch
|
||||
| &BinaryExprType::NotREMatch
|
||||
| &BinaryExprType::Equal
|
||||
| &BinaryExprType::IS
|
||||
| &BinaryExprType::IN => {
|
||||
return Result::Complete(i_.clone(), op.clone());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user