ucg 0.8.0

A configuration generation grammar.
Documentation
// 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,
} => (result) {
    // 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),
        },
        false = acc{
            out="@@@" % (acc.out, acc.sep, item),
        },
    };

    // 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);

// head returns the first item in a list as a list of one item.
// This function is safe for empty list inputs but is not safe
// for NULL inputs.
let head = func(list) => select (len(list) > 0, []) => {
    true = [list.0],
};

// tail returns the tail of a list without the head.
// This function is safe for empty lists but is not safe
// for NULL inputs.
let tail = func(list) => reduce(
    func (acc, item) => select (acc.count > 0, acc{count=1, tail=[]}) => {
        true = acc{count = acc.count + 1, tail = acc.tail + [item]},
    },
    {count=0},
    list,
).tail;

// Enumerates the provided list with optional start and step parameters for the
// enumeration. Produces a list of pairs with the enumeration and the list item.
//
// enumerate{list=["a","b","c"]} == [[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,
} => (result) {
    let reducer = func (acc, item) =>  acc{
        count = acc.count + acc.step,
        list=acc.list + [[acc.count, item]],
    };

    let result = reduce(
        reducer,
        {count=mod.start, list=[], step=mod.step},
        (mod.list)).list;
};

// slice returns a slice of a list starting at index start up to and 
// including index end, inclusive.
let slice = module {
    start = 0,
    end = NULL,
    list = NULL,
} => (result) {
    let list = mod.pkg();

    let list_len = list.len(mod.list);

    let end = select (mod.end is "null", mod.end) => {
        true = list_len - 1,
    };
    let start = mod.start;

    // ensure some invariants
    (start >= 0) || fail "Slice must be positive";
    (start <= list_len) || fail "Slice start cannot be larger than the list len of @" % (list_len);
    (end <= list_len) || fail "Slice end cannot be larger than list len of @" % (list_len);

    let reducer = func(acc, item) => acc + [mod.list.(item)];

    let result = reduce(
        reducer,
        [],
        start:end);
};

// zips two lists together.
// The result list is only as long as the shortest list.
//
// zip{list1=[0,2,4],list2=[1,3,5]}.result == [[0, 1], [2, 3], [4, 5]]
let zip = module{
    list1 = NULL,
    list2 = NULL,
} => (result) {
    let len = mod.pkg().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]
    };

    let acc = {
        list1 = mod.list1,
        list2 = mod.list2,
        result = [],
        idxs = [],
    };

    // The resulting zipped list.
    let result = reduce(reducer, acc, rng).result;
};

// Wraps a list and provides a number of helpful operations.
//
// * len - property the length of the wrapped list.
//
// * str_join - function joins list into string with provided separator `sep`.
//
// * slice - function returns a slice of the list from `start` to `end`.
//
// * enumerate - function returns a list of pairs of [index, value] from the list.
//               The returned list is wrapped in the ops module.
//
// * head - function returns the head of the list as list of one item.
//
// * tail - function returns the tail of the list.
//          The returned list is wrapped in the ops module.
//
// * reverse - function returns the list reversed.
//             The returned list is wrapped in the ops module.
let ops = module{
    list=NULL,
} => ({len=len,
       str_join=str_join,
       slice=slice,
       enumerate=enumerate,
       tail=tail,
       head=head,
       reverse=reverse,
       list=list,
      }) {
    let pkg = mod.pkg();
    let list = mod.list;
    let len = pkg.len(mod.list);
    let str_join = func(sep) => pkg.str_join{list=mod.list, sep=sep};
    
    let slice = func(start, end) => pkg.slice{start=start, end=end, list=mod.list};
    let enumerate = func() => pkg.ops{list=pkg.enumerate{start=0, step=1, list=mod.list}};
    let tail = func() => pkg.ops{list=pkg.tail(mod.list)};
    let head = func() => pkg.head(mod.list);
    let reverse = func() => mod.this{list=pkg.reverse(mod.list)};
};