diff --git a/docsite/site/content/stdlib/schema.md b/docsite/site/content/stdlib/schema.md new file mode 100644 index 0000000..ad2423d --- /dev/null +++ b/docsite/site/content/stdlib/schema.md @@ -0,0 +1,65 @@ ++++ +title = "UCG Schema Modules" +weight = 4 +sort_by = "weight" +in_search_index = true ++++ + +UCG's std lib has a selection of functions and modules to help assert a base type and shape for a value. They are located in the `std/schema.ucg` import. + +## Schema shapes + +The primary tool in this import is the shaped module. This module allows you to match a value against a stereotype value. If the provided value is the same base type and has the same shape of that base type as the provided stereotype then the +result field of the computed tuple will be true. + +``` +let fits = schema.shaped{ + val={foo="bar", inner=[1, 2]}, + shape={foo="", inner=[]} +}.result; + +fits == true; +``` + +By default the matching allows the shape to specify a partial or minimum shape. But you can specify exact matching if desired by setting the partial paramater to false.. + +``` +let exact_fit = schema.shaped{ + partial=false, + val={foo="bar", count=1}, + shape={foo=""}, + }.result; + + exact_fit == false; +``` + +The shape algorithm does not enforce a length or set of contained types for lists at this time. + +Besides the shaped module there are also some useful utility modules and functions to assist in some addtional type checking logic. +## any + +The `any` module tests a value against a list of possible shapes. If the value +fits any of the candidate shapes then it stores true in the result field. If it does not then it returns false in that result field. + +``` +let fits_one_of = any{val={foo="bar"}, types=[1, {foo=""}]}.result; +fits_one_of == true; +``` + +## base_type_of function. + +The `base_type_of` function computes the base type of a value. + +``` +let foo = 1; +base_type_of(foo) == "int"; +``` + +## must + +The `must` function turns any schema check module failure into a compile failure. It composes well with the modules in the `std/schema.ucg` import and automatically checks the result field they produce. If that field is true then it returns that true value. If the field is false they produce a compile failure. + +``` +// results in a compile failure "Must be a string" +must(shaped{val="foo", shape=1}, "Must be a string"); +``` \ No newline at end of file diff --git a/std/lists.ucg b/std/lists.ucg index 1f39c7c..8fa813e 100644 --- a/std/lists.ucg +++ b/std/lists.ucg @@ -1,7 +1,14 @@ +// Joins a list into a string with an optional provided separator. +// The string will be in the result field of the generated tuple +// from the module. let str_join = module{ + // The default separator is a single space. You can override + // this when you call the module if desired. sep=" ", + // The list is a required parameter. list=NULL, } => { + // The function used by reduce to join the list into a string. let joiner = func (acc, item) => select (acc.out == ""), NULL, { true = acc{ out="@@" % (acc.out,item), @@ -11,16 +18,27 @@ let str_join = module{ }, }; + // The resulting joined string. Reference the result field from the + // tuple the module produces to get the joined string. let result = reduce(joiner, {sep=mod.sep, out=""}, (mod.list)).out; }; +// Computes the length of the provided list. let len = func(list) => reduce(func(acc, item) => acc + 1, 0, list); +// Reverses the provided list. let reverse = func(list) => reduce(func (acc, item) => [item] + acc, [], list); +// Enumerates the provided list with optional start and step parameters for the +// enumeration. Prodices a list of pairs with the numeration and the list item. +// +// enumerate{list=["a","b","c"]}.result == [[0, "a"], [1, "b"], [2, "c"]] let enumerate = module{ + // Where to start the enumeration. start = 0, + // The step amount for each enumeration. step = 1, + // The list to enumerate. list = NULL, } => { let reducer = func (acc, item) => acc{ @@ -34,21 +52,26 @@ let enumerate = module{ let result = enumerated.list; }; +// zips two lists together. +// +// zip{list1=[0,2,4],list2=[1,3,5]}.result == [[0, 1], [2, 3], [4, 5]] let zip = module{ list1 = NULL, list2 = NULL, } => { let len = import "std/lists.ucg".len; - + // Compute the length of each list. let len1 = len(mod.list1); let len2 = len(mod.list2); + // Compute the min range of the two lists. let rng = select (len1 >= len2), NULL, { true = 0:(len1 - 1), false = 0:(len2 - 1), }; + // The reducer function for the zip operation. let reducer = func (acc, item) => acc{ result = acc.result + [[acc.list1.(item), acc.list2.(item)]], idxs = acc.idxs + [item] @@ -61,5 +84,6 @@ let zip = module{ idxs = [], }; + // The resulting zipped list. let result = reduce(reducer, acc, rng).result; }; \ No newline at end of file diff --git a/std/tuples.ucg b/std/tuples.ucg index a8d7cfa..e946610 100644 --- a/std/tuples.ucg +++ b/std/tuples.ucg @@ -1,8 +1,9 @@ +// Assert that a value is a tuple. Generate a compile failure if it is not. let assert_tuple = func (tpl) => select tpl is "tuple", NULL, { false = fail "@ is not a tuple" % (tpl), }; -// return a list of the fields in a tuple. +// Return a list of the fields in a tuple. let fields = module{ tpl = NULL, } => { @@ -13,7 +14,7 @@ let fields = module{ let result = reduce(func (acc, field, value) => acc + [field], [], (mod.tpl)); }; -// return a list of the values in a tuple. +// Return a list of the values in a tuple. let values = module{ tpl = NULL, } => { @@ -24,6 +25,7 @@ let values = module{ let result = reduce(func (acc, field, value) => acc + [value], [], (mod.tpl)); }; +// Return a list of the key value pairs in the tuple. let iter = module{ tpl = NULL, } => { @@ -34,6 +36,7 @@ let iter = module{ let result = reduce(func (acc, field, value) => acc + [[field, value]], [], (mod.tpl)); }; +// Strip all the null fields from a tuple. let strip_nulls = module{ tpl = NULL, } => { @@ -44,6 +47,7 @@ let strip_nulls = module{ let result = filter(func (name, value) => value != NULL, (mod.tpl)); }; +// Check if a tuple has all the fields in a given list. let has_fields = module{ tpl = NULL, fields = [], @@ -57,6 +61,7 @@ let has_fields = module{ let result = reduce(reducer = func (acc, f) => acc && (f in fs), true, (mod.fields)); }; +// Check if a field in a tuple is a given type. let field_type = module{ tpl = NULL, field = NULL, @@ -76,12 +81,15 @@ let field_type = module{ false = fail "@ is not a string" % (mod.type), }; + // Get the list of field value pairs. let it = lib.iter{tpl=mod.tpl}.result; - + + // The reducer function used to check the field's types if it exists. let reducer = func (acc, l) => acc && (select l.0 == mod.field, NULL, { true = l.1 is mod.type, // if this is the field then we propagate if it's the right type. false = true, // if this isn't the field then we propagate true }); + // The computed answer true or false. let result = lib.has_fields{tpl=mod.tpl, fields=[mod.field]}.result && reduce(reducer, true, it); }; \ No newline at end of file