mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-25 18:49: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
|
`/` are higher precendence than `+` and `-` which are higher precedence than
|
||||||
any of the comparison operators.
|
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
|
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,
|
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.
|
/// Encodes a ucg expression. Expressions compute a value from.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
@ -595,6 +602,9 @@ 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),
|
||||||
@ -627,6 +637,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,6 +681,9 @@ 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>")?;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,9 @@ 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());
|
||||||
|
@ -96,3 +96,8 @@ fn test_concatenation() {
|
|||||||
fn test_format() {
|
fn test_format() {
|
||||||
assert_build(include_str!("../../integration_tests/format_test.ucg"));
|
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;
|
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 {
|
impl Display for Val {
|
||||||
|
@ -518,7 +518,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
}
|
}
|
||||||
Val::Str(ref s) => match right.as_ref() {
|
Val::Str(ref s) => match right.as_ref() {
|
||||||
&Val::Str(ref ss) => {
|
&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 => {
|
val => {
|
||||||
return Err(Box::new(error::BuildError::new(
|
return Err(Box::new(error::BuildError::new(
|
||||||
@ -531,7 +531,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
),
|
),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Val::List(ref l) => match right.as_ref() {
|
Val::List(ref l) => match right.as_ref() {
|
||||||
@ -552,7 +552,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
),
|
),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ref expr => {
|
ref expr => {
|
||||||
@ -560,7 +560,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("{} does not support the '+' operation", expr.type_name()),
|
format!("{} does not support the '+' operation", expr.type_name()),
|
||||||
error::ErrorType::Unsupported,
|
error::ErrorType::Unsupported,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -583,7 +583,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("{} does not support the '-' operation", expr.type_name()),
|
format!("{} does not support the '-' operation", expr.type_name()),
|
||||||
error::ErrorType::Unsupported,
|
error::ErrorType::Unsupported,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -606,7 +606,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("{} does not support the '*' operation", expr.type_name()),
|
format!("{} does not support the '*' operation", expr.type_name()),
|
||||||
error::ErrorType::Unsupported,
|
error::ErrorType::Unsupported,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -629,7 +629,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("{} does not support the '*' operation", expr.type_name()),
|
format!("{} does not support the '*' operation", expr.type_name()),
|
||||||
error::ErrorType::Unsupported,
|
error::ErrorType::Unsupported,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1530,7 +1530,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("Error finding file {} {}", path, e),
|
format!("Error finding file {} {}", path, e),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut f = match File::open(&normalized) {
|
let mut f = match File::open(&normalized) {
|
||||||
@ -1540,7 +1540,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
format!("Error opening file {} {}", normalized.to_string_lossy(), e),
|
format!("Error opening file {} {}", normalized.to_string_lossy(), e),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
pos.clone(),
|
pos.clone(),
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
@ -1642,6 +1642,29 @@ 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>> {
|
||||||
|
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.
|
// Evals a single Expression in the context of a running Builder.
|
||||||
// It does not mutate the builders collected state at all.
|
// 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>> {
|
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::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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
fn unprefixed_expression(input: SliceIter<Token>) -> ParseResult<Expression> {
|
||||||
let _input = input.clone();
|
let _input = input.clone();
|
||||||
either!(
|
either!(
|
||||||
@ -678,7 +693,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, op_expression) {
|
match trace_parse!(_input, either!(is_type_expression, 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),
|
||||||
|
@ -274,6 +274,10 @@ make_fn!(intok<OffsetStrIter, Token>,
|
|||||||
do_text_token_tok!(TokenType::BAREWORD, "in", WS)
|
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>,
|
make_fn!(macrotok<OffsetStrIter, Token>,
|
||||||
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
|
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
|
||||||
);
|
);
|
||||||
@ -408,6 +412,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
|||||||
rightsquarebracket,
|
rightsquarebracket,
|
||||||
booleantok,
|
booleantok,
|
||||||
intok,
|
intok,
|
||||||
|
istok,
|
||||||
lettok,
|
lettok,
|
||||||
outtok,
|
outtok,
|
||||||
selecttok,
|
selecttok,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user