mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-23 18:29:50 -04:00
FEATURE: Add the is operator
This operator tests that something is of a certain base type.
This commit is contained in:
parent
77075d6c79
commit
25d84a771e
@ -160,6 +160,15 @@ UCG binary operators follow the typical operator precedence for math. `*` and
|
||||
`/` are higher precendence than `+` and `-` which are higher precedence than
|
||||
any of the comparison operators.
|
||||
|
||||
Type test expressions
|
||||
---------------------
|
||||
|
||||
ucg has the `is` operator for testing that something is of a given base type.
|
||||
|
||||
```
|
||||
("foo" is str) == true;
|
||||
```
|
||||
|
||||
Copy Expressions
|
||||
----------------
|
||||
|
||||
|
51
integration_tests/types_test.ucg
Normal file
51
integration_tests/types_test.ucg
Normal file
@ -0,0 +1,51 @@
|
||||
let t = import "std/testing.ucg".asserts{};
|
||||
|
||||
assert t.ok{
|
||||
test = "foo" is str,
|
||||
desc = "foo is a str",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = "foo" is int,
|
||||
desc = "foo is not an int",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = {foo="bar"} is tuple,
|
||||
desc = "found a tuple",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = {foo="bar"} is str,
|
||||
desc = "a tuple is not a str",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = [1, 2] is list,
|
||||
desc = "found a list",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = [1, 2] is tuple,
|
||||
desc = "list is not a tuple",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = (macro(arg) => arg) is macro,
|
||||
desc = "found a macro",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = (macro(arg) => arg) is list,
|
||||
desc = "a macro is not a list",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = (module{} => {}) is module,
|
||||
desc = "found a module",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = module{} => {} is macro,
|
||||
desc = "a module is not a macro",
|
||||
};
|
@ -586,6 +586,13 @@ pub struct ImportDef {
|
||||
pub path: Token,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct IsDef {
|
||||
pub pos: Position,
|
||||
pub target: Box<Expression>,
|
||||
pub typ: Token,
|
||||
}
|
||||
|
||||
/// Encodes a ucg expression. Expressions compute a value from.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expression {
|
||||
@ -595,6 +602,9 @@ pub enum Expression {
|
||||
// Binary expressions
|
||||
Binary(BinaryOpDef),
|
||||
|
||||
// Type tests
|
||||
IS(IsDef),
|
||||
|
||||
// Complex Expressions
|
||||
Copy(CopyDef),
|
||||
Range(RangeDef),
|
||||
@ -627,6 +637,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -670,6 +681,9 @@ impl fmt::Display for Expression {
|
||||
&Expression::Include(_) => {
|
||||
write!(w, "<Include>")?;
|
||||
}
|
||||
&Expression::IS(_) => {
|
||||
write!(w, "<IS>")?;
|
||||
}
|
||||
&Expression::Import(_) => {
|
||||
write!(w, "<Include>")?;
|
||||
}
|
||||
|
@ -103,6 +103,9 @@ 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());
|
||||
|
@ -96,3 +96,8 @@ fn test_concatenation() {
|
||||
fn test_format() {
|
||||
assert_build(include_str!("../../integration_tests/format_test.ucg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_checks() {
|
||||
assert_build(include_str!("../../integration_tests/types_test.ucg"));
|
||||
}
|
||||
|
@ -191,6 +191,19 @@ impl Val {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn is_str(&self) -> bool {
|
||||
if let &Val::Str(_) = self {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn is_module(&self) -> bool {
|
||||
if let &Val::Module(_) = self {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Val {
|
||||
|
@ -518,7 +518,7 @@ impl<'a> FileBuilder<'a> {
|
||||
}
|
||||
Val::Str(ref s) => match right.as_ref() {
|
||||
&Val::Str(ref ss) => {
|
||||
return Ok(Rc::new(Val::Str([s.to_string(), ss.clone()].concat())))
|
||||
return Ok(Rc::new(Val::Str([s.to_string(), ss.clone()].concat())));
|
||||
}
|
||||
val => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
@ -531,7 +531,7 @@ impl<'a> FileBuilder<'a> {
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
},
|
||||
Val::List(ref l) => match right.as_ref() {
|
||||
@ -552,7 +552,7 @@ impl<'a> FileBuilder<'a> {
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
},
|
||||
ref expr => {
|
||||
@ -560,7 +560,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("{} does not support the '+' operation", expr.type_name()),
|
||||
error::ErrorType::Unsupported,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -583,7 +583,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("{} does not support the '-' operation", expr.type_name()),
|
||||
error::ErrorType::Unsupported,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -606,7 +606,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("{} does not support the '*' operation", expr.type_name()),
|
||||
error::ErrorType::Unsupported,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -629,7 +629,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("{} does not support the '*' operation", expr.type_name()),
|
||||
error::ErrorType::Unsupported,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1530,7 +1530,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("Error finding file {} {}", path, e),
|
||||
error::ErrorType::TypeFail,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
let mut f = match File::open(&normalized) {
|
||||
@ -1540,7 +1540,7 @@ impl<'a> FileBuilder<'a> {
|
||||
format!("Error opening file {} {}", normalized.to_string_lossy(), e),
|
||||
error::ErrorType::TypeFail,
|
||||
pos.clone(),
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
let mut contents = String::new();
|
||||
@ -1642,6 +1642,29 @@ 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() {
|
||||
"str" => val.is_str(),
|
||||
"bool" => val.is_bool(),
|
||||
"null" => val.is_empty(),
|
||||
"int" => val.is_int(),
|
||||
"float" => val.is_float(),
|
||||
"tuple" => val.is_tuple(),
|
||||
"list" => val.is_list(),
|
||||
"macro" => val.is_macro(),
|
||||
"module" => val.is_module(),
|
||||
other => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!("Expected valid type name but got {}", other),
|
||||
error::ErrorType::TypeFail,
|
||||
def.pos.clone(),
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(Rc::new(Val::Boolean(result)))
|
||||
}
|
||||
|
||||
// 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, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
@ -1665,6 +1688,7 @@ 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,6 +650,21 @@ 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!(
|
||||
@ -678,7 +693,7 @@ make_fn!(
|
||||
|
||||
fn expression(input: SliceIter<Token>) -> ParseResult<Expression> {
|
||||
let _input = input.clone();
|
||||
match trace_parse!(_input, op_expression) {
|
||||
match trace_parse!(_input, either!(is_type_expression, op_expression)) {
|
||||
Result::Incomplete(i) => Result::Incomplete(i),
|
||||
Result::Fail(_) => trace_parse!(input, non_op_expression),
|
||||
Result::Abort(e) => Result::Abort(e),
|
||||
|
@ -274,6 +274,10 @@ make_fn!(intok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "in", WS)
|
||||
);
|
||||
|
||||
make_fn!(istok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "is", WS)
|
||||
);
|
||||
|
||||
make_fn!(macrotok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
|
||||
);
|
||||
@ -408,6 +412,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
||||
rightsquarebracket,
|
||||
booleantok,
|
||||
intok,
|
||||
istok,
|
||||
lettok,
|
||||
outtok,
|
||||
selecttok,
|
||||
|
Loading…
x
Reference in New Issue
Block a user