// The list of base types for a UCG value. let base_types = [ "int", "float", "str", "bool", "null", "tuple", "list", "func", "module", ]; // Computes the base type of a value. let base_type_of = func (val) => reduce(func (acc, f) => select ((acc.val is f), f) => { true = acc{typ = f}, false = acc, }, {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 = func (m, msg) => select (m, fail msg) => { true = m, }; // All does a boolean match against all of 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. // // This module does partial matches for the shapes. let all = module { // The source value to check. val=NULL, // The set of allowed type shapes it can be. types=[], } => (result) { let schema = mod.pkg(); let reducer = func (acc, t) => acc{ ok = acc.ok && (schema.shaped{val=acc.val, shape=t, partial=true}), }; let any = func (val, types) => reduce(reducer, {ok=true, val=val}, types); let result = any(mod.val, mod.types).ok; }; // 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. // // This module does not do partial matches for the shapes by default. let any = module { // The source value to check. val=NULL, // The set of allowed type shapes it can be. types=[], // Whether to do partial matches for the shapes partial=false } => (result) { let schema = mod.pkg(); let reducer = func (acc, t) => acc{ ok = acc.ok || (schema.shaped{val=acc.val, shape=t, partial=mod.partial}), }; let any = func (val, types) => reduce(reducer, {ok=false, val=val}, types); let result = any(mod.val, mod.types).ok; }; // 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 must contain types from the list shape they are compared against. // and empty list shape means the list val can have any types it wants inside. // // We do not check that functions 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, } => (result) { let schema = mod.pkg(); let this = mod.this; let simple_handler = func (val, shape) => val is schema.base_type_of(shape); let tuple_handler = func (acc, name, value) => acc{ ok = select ((name) in acc.shape, mod.partial) => { true = this{val=value, shape=acc.shape.(name), partial=mod.partial}, }, }; let shape_tuple_handler = func (acc, field_name, value) => acc{ ok = select ((field_name) in acc.val, false) => { true = this{val=value, shape=acc.val.(field_name), partial=false}, }, }; let match_shape_fields = func(val, shape) => reduce( shape_tuple_handler, {val=val, ok=false}, shape).ok; let match_tuple_fields = func(val, shape) => reduce(tuple_handler, {shape=shape, ok=false}, val).ok; let list_handler = func(acc, value) => acc{ ok = acc.ok && schema.any{val=value, types=acc.shape}, }; 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 = (mod.shape is "tuple") && match_shape_fields(mod.val, mod.shape) && match_tuple_fields(mod.val, mod.shape), list = (mod.shape is "list") && select (mod.shape == [], true) => { false = reduce(list_handler, {shape=mod.shape, ok=true}, mod.val).ok, }, func = simple_handler(mod.val, mod.shape), module = simple_handler(mod.val, mod.shape), }; };