DOCS: Add documentation on our stdlib.

* Adds schema.ucg to our reference site.
* Adds comments to the std libs themselves.
This commit is contained in:
Jeremy Wall 2019-02-04 20:29:04 -06:00
parent cccef4554d
commit b571ead599
3 changed files with 101 additions and 4 deletions

View File

@ -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");
```

View File

@ -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;
};

View File

@ -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);
};