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,
|
ok = true || true == true,
|
||||||
desc = "||: likes truth",
|
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>> {
|
fn do_dot_lookup(&self, right: &Expression, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
|
||||||
let pos = right.pos().clone();
|
let pos = right.pos().clone();
|
||||||
|
let scope = scope.clone().use_curr_val();
|
||||||
match right {
|
match right {
|
||||||
Expression::Copy(_) => return self.eval_expr(right, scope),
|
Expression::Copy(_) => return self.eval_expr(right, &scope),
|
||||||
Expression::Call(_) => return self.eval_expr(right, scope),
|
Expression::Call(_) => return self.eval_expr(right, &scope),
|
||||||
Expression::Simple(Value::Symbol(ref s)) => {
|
Expression::Simple(Value::Symbol(ref s)) => {
|
||||||
let scope = scope.clone().use_curr_val();
|
|
||||||
scope
|
scope
|
||||||
.lookup_sym(s, true)
|
.lookup_sym(s, true)
|
||||||
.ok_or(Box::new(error::BuildError::new(
|
.ok_or(Box::new(error::BuildError::new(
|
||||||
@ -783,7 +783,6 @@ impl<'a> FileBuilder<'a> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
Expression::Simple(Value::Str(ref s)) => {
|
Expression::Simple(Value::Str(ref s)) => {
|
||||||
let scope = scope.clone().use_curr_val();
|
|
||||||
scope
|
scope
|
||||||
.lookup_sym(s, false)
|
.lookup_sym(s, false)
|
||||||
.ok_or(Box::new(error::BuildError::new(
|
.ok_or(Box::new(error::BuildError::new(
|
||||||
@ -796,7 +795,7 @@ impl<'a> FileBuilder<'a> {
|
|||||||
scope.lookup_idx(right.pos(), &Val::Int(i.val))
|
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() {
|
match val.as_ref() {
|
||||||
Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)),
|
Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)),
|
||||||
Val::Str(ref s) => scope
|
Val::Str(ref s) => scope
|
||||||
@ -968,7 +967,6 @@ impl<'a> FileBuilder<'a> {
|
|||||||
let left = self.eval_expr(&def.left, scope)?;
|
let left = self.eval_expr(&def.left, scope)?;
|
||||||
let mut child_scope = scope.spawn_child();
|
let mut child_scope = scope.spawn_child();
|
||||||
child_scope.set_curr_val(left.clone());
|
child_scope.set_curr_val(left.clone());
|
||||||
child_scope.search_curr_val = true;
|
|
||||||
if let &BinaryExprType::DOT = kind {
|
if let &BinaryExprType::DOT = kind {
|
||||||
return self.do_dot_lookup(&def.right, &child_scope);
|
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,
|
val=NULL,
|
||||||
|
// The set of allowed type shapes it can be.
|
||||||
types=[],
|
types=[],
|
||||||
} => {
|
} => {
|
||||||
|
let schema = import "std/schema.ucg";
|
||||||
|
|
||||||
let reducer = macro(acc, t) => acc{
|
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 any = macro(val, types) => reduce reducer {ok=false, val=val}, types;
|
||||||
|
|
||||||
let result = any(mod.val, mod.types).ok;
|
let result = any(mod.val, mod.types).ok;
|
||||||
};
|
};
|
||||||
|
|
||||||
let must = macro(m, msg) => select m.result, fail msg, {
|
// Compares a value against an example schema value. compares the "shape" of the
|
||||||
true = m.result,
|
// 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";
|
let schema = import "std/schema.ucg";
|
||||||
|
|
||||||
assert t.ok{
|
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",
|
desc = "1 is an int",
|
||||||
};
|
};
|
||||||
|
|
||||||
assert t.not_ok{
|
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",
|
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