// 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, }; // 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. let any = 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=false}), }; 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 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 = schema.shaped{val=value, shape=acc.shape.(name)}, }, }; let list_handler = func(acc, value) => acc{ ok = false || 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") && reduce(tuple_handler, {shape=mod.shape, ok=false}, mod.val).ok, list = select mod.shape == [], true, { false = reduce(list_handler, {shape=mod.shape, ok=false}, mod.val).ok, }, func = simple_handler(mod.val, mod.shape), module = simple_handler(mod.val, mod.shape), }; };