mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
FEATURE: Schema checks for particular shapes.
Progress toward #6 and #32.
This commit is contained in:
parent
6321828006
commit
5bfe38140a
@ -214,3 +214,9 @@ assert {
|
||||
ok = true || true == true,
|
||||
desc = "||: likes truth",
|
||||
};
|
||||
|
||||
let name = "foo";
|
||||
assert {
|
||||
ok = (name) in {foo="bar"},
|
||||
desc = "(name) in {foo=\"bar\"} works",
|
||||
};
|
@ -769,11 +769,11 @@ impl<'a> FileBuilder<'a> {
|
||||
|
||||
fn do_dot_lookup(&self, right: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||
let pos = right.pos().clone();
|
||||
match right {
|
||||
Expression::Copy(_) => return self.eval_expr(right, scope),
|
||||
Expression::Call(_) => return self.eval_expr(right, scope),
|
||||
Expression::Simple(Value::Symbol(ref s)) => {
|
||||
let scope = scope.clone().use_curr_val();
|
||||
match right {
|
||||
Expression::Copy(_) => return self.eval_expr(right, &scope),
|
||||
Expression::Call(_) => return self.eval_expr(right, &scope),
|
||||
Expression::Simple(Value::Symbol(ref s)) => {
|
||||
scope
|
||||
.lookup_sym(s, true)
|
||||
.ok_or(Box::new(error::BuildError::new(
|
||||
@ -783,7 +783,6 @@ impl<'a> FileBuilder<'a> {
|
||||
)))
|
||||
}
|
||||
Expression::Simple(Value::Str(ref s)) => {
|
||||
let scope = scope.clone().use_curr_val();
|
||||
scope
|
||||
.lookup_sym(s, false)
|
||||
.ok_or(Box::new(error::BuildError::new(
|
||||
@ -796,7 +795,7 @@ impl<'a> FileBuilder<'a> {
|
||||
scope.lookup_idx(right.pos(), &Val::Int(i.val))
|
||||
}
|
||||
_ => {
|
||||
let val = self.eval_expr(right, scope)?;
|
||||
let val = self.eval_expr(right, &scope)?;
|
||||
match val.as_ref() {
|
||||
Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)),
|
||||
Val::Str(ref s) => scope
|
||||
@ -968,7 +967,6 @@ impl<'a> FileBuilder<'a> {
|
||||
let left = self.eval_expr(&def.left, scope)?;
|
||||
let mut child_scope = scope.spawn_child();
|
||||
child_scope.set_curr_val(left.clone());
|
||||
child_scope.search_curr_val = true;
|
||||
if let &BinaryExprType::DOT = kind {
|
||||
return self.do_dot_lookup(&def.right, &child_scope);
|
||||
};
|
||||
|
@ -1,15 +1,90 @@
|
||||
let one_of = module {
|
||||
// The list of base types for a UCG value.
|
||||
let base_types = [
|
||||
"int",
|
||||
"float",
|
||||
"str",
|
||||
"bool",
|
||||
"null",
|
||||
"tuple",
|
||||
"list",
|
||||
"macro",
|
||||
"module",
|
||||
];
|
||||
|
||||
let base_type_reducer = macro(acc, f) => select (acc.val is f), f, {
|
||||
true = acc{typ = f},
|
||||
false = acc,
|
||||
};
|
||||
|
||||
// Computes the base type of a value.
|
||||
let base_type_of = macro(val) => (reduce base_type_reducer {val=val, typ="null"}, base_types).typ;
|
||||
|
||||
// Turns any schema check module into a compile failure.
|
||||
// The module must export the computed value as the result field.
|
||||
let must = macro(m, msg) => select m.result, fail msg, {
|
||||
true = m.result,
|
||||
};
|
||||
|
||||
// Any does a boolean match against a set of allowed shapes for a type.
|
||||
// This module uses the shape module below so the same rules for checking
|
||||
// the types against the source value apply for each example in the list.
|
||||
let any = module {
|
||||
// The source value to check.
|
||||
val=NULL,
|
||||
// The set of allowed type shapes it can be.
|
||||
types=[],
|
||||
} => {
|
||||
let schema = import "std/schema.ucg";
|
||||
|
||||
let reducer = macro(acc, t) => acc{
|
||||
ok = acc.ok || (acc.val is t),
|
||||
ok = acc.ok || (schema.shaped{val=acc.val, shape=t}.result),
|
||||
};
|
||||
let any = macro(val, types) => reduce reducer {ok=false, val=val}, types;
|
||||
|
||||
let result = any(mod.val, mod.types).ok;
|
||||
};
|
||||
|
||||
let must = macro(m, msg) => select m.result, fail msg, {
|
||||
true = m.result,
|
||||
// Compares a value against an example schema value. compares the "shape" of the
|
||||
// value to see if it matches. The base type must be the same as the base type
|
||||
// of the shape. For tuples any field in the shape tuple must be present in the
|
||||
// source value and must be of the same base type and shape. This module will
|
||||
// recurse into nested tuples.
|
||||
//
|
||||
// Lists are assumed to be able to contain any type and can be any length.
|
||||
// We do not check that macros or modules have the same argument lengths or types
|
||||
// nor we check that they output the same types.
|
||||
let shaped = module {
|
||||
// The source value to validate
|
||||
val = NULL,
|
||||
|
||||
// The shape to validate it against.
|
||||
shape = NULL,
|
||||
|
||||
// Whether partial matches are accepted.
|
||||
// When set to true then the source value can have
|
||||
// fields in tuples that are not present in the
|
||||
// shape it is compared with.
|
||||
partial = true,
|
||||
} => {
|
||||
let schema = import "std/schema.ucg";
|
||||
|
||||
let simple_handler = macro(val, shape) => val is (schema.base_type_of(shape));
|
||||
|
||||
let tuple_handler = macro(acc, name, value) => acc{
|
||||
ok = select (name) in acc.shape, mod.partial, {
|
||||
true = schema.shaped{val=value, shape=acc.shape.(name)}.result,
|
||||
},
|
||||
};
|
||||
|
||||
let result =select schema.base_type_of(mod.val), false, {
|
||||
str = simple_handler(mod.val, mod.shape),
|
||||
int = simple_handler(mod.val, mod.shape),
|
||||
float = simple_handler(mod.val, mod.shape),
|
||||
bool = simple_handler(mod.val, mod.shape),
|
||||
null = simple_handler(mod.val, mod.shape),
|
||||
tuple = (reduce tuple_handler {shape=mod.shape, ok=false}, (mod.val)).ok,
|
||||
list = simple_handler(mod.val, mod.shape),
|
||||
macro = simple_handler(mod.val, mod.shape),
|
||||
module = simple_handler(mod.val, mod.shape),
|
||||
};
|
||||
};
|
@ -2,11 +2,106 @@ let t = import "std/testing.ucg".asserts{};
|
||||
let schema = import "std/schema.ucg";
|
||||
|
||||
assert t.ok{
|
||||
test = schema.must(schema.one_of{val=1, types=["float", "int"]}, "Must be a float or an int"),
|
||||
test = schema.must(schema.any{val=1, types=[1.0, 1]}, "Must be a float or an int"),
|
||||
desc = "1 is an int",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = schema.one_of{val=1, types=["float", "str"]}.result,
|
||||
test = schema.any{val=1, types=[1.0, ""]}.result,
|
||||
desc = "1 is not a float or string",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of("foo"),
|
||||
right = "str",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(1),
|
||||
right = "int",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(1.0),
|
||||
right = "float",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(1.0),
|
||||
right = "float",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(true),
|
||||
right = "bool",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(NULL),
|
||||
right = "null",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of({}),
|
||||
right = "tuple",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of([]),
|
||||
right = "list",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(macro(arg) => arg),
|
||||
right = "macro",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.base_type_of(schema.any),
|
||||
right = "module",
|
||||
};
|
||||
|
||||
assert t.equal{
|
||||
left = schema.shaped{val="foo", shape=""}.result,
|
||||
right = true,
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = schema.shaped{val="foo", shape=""}.result,
|
||||
desc = "\"foo\" is same shape as \"\"",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = schema.shaped{val="foo", shape=0}.result,
|
||||
desc = "\"foo\" is not the same shape as 0",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = schema.shaped{val={foo="bar"}, shape={foo=""}}.result,
|
||||
desc = "shaped for simple tuples works",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = schema.shaped{val={foo="bar", count=1}, shape={foo=""}}.result,
|
||||
desc = "shaped for partial tuples works",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = schema.shaped{partial=false, val={foo="bar", count=1}, shape={foo=""}}.result,
|
||||
desc = "shaped for non partial tuples also works",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = schema.shaped{val={foo="bar", inner={one=1}}, shape={foo="", inner={one=0}}}.result,
|
||||
desc = "shaped for nested tuples works",
|
||||
};
|
||||
|
||||
assert t.ok{
|
||||
test = schema.shaped{val={foo="bar", inner=[1, 2]}, shape={foo="", inner=[]}}.result,
|
||||
desc = "shaped for nested list in tuple works",
|
||||
};
|
||||
|
||||
assert t.not_ok{
|
||||
test = schema.shaped{val={inner={foo="bar"}}, shape={inner={foo=1}}}.result,
|
||||
desc = "shaped fails when the shape doesn't match",
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user