ucode Language Notes

ucode reads like JavaScript, and that is exactly what makes it worth writing these notes down: the places where the resemblance stops are the places where a module breaks. What follows are the differences that come up while writing modules for this collection. It is not a tutorial - the ucode documentation is, and it is worth keeping open.

Functions

Parameters have no default values. Writing function f(x = 1) does not compile, so test for null inside the function and fill the default in yourself.

Rest parameters work: function f(a, ...rest) collects the remaining arguments into an array, empty when none were passed, and it has to be the last parameter. Spreading works too, in calls and in array and object literals:

push(cmd, 'add', ...packages);
let merged = { ...defaults, ...overrides };

There are no classes. Where you would reach for one, write a function that builds an object and returns it, with the state captured in the closure - which is what AnsibleModule() is.

Values and collections

Arrays and objects are manipulated with global functions rather than methods: push(), length(), sort(), map(), split(), join(), substr(), replace(), match(). There is no array.push() and no string.length.

for (let item in array) iterates over the values of an array, not over its indices. Over an object, it iterates over the keys. Use a counting for loop when you need an index.

There is no undefined: a key that is not there reads as null, so value == null is the test for “not set”. Strings cannot be indexed with [] - take one character with substr(s, i, 1).

type() names the type of a value, and the names are not always the ones you expect: a string is string, a floating point number is double, an array is array and a dictionary is object.

Numbers

+ concatenates as soon as either side is a string, so "1.5" + 0.0 is the string "1.50" and not the number 1.5. Nothing complains: you get a string where you expected a number, and only notice further down. Convert with unary plus instead - +"1.5" is the double 1.5 - and note that it reads a whole number as an int, so add + 0.0 when you need a double. Unary plus does not read a leading dot either: +".5" is NaN, not 0.5.

Dividing two integers yields an integer: 7 / 2 is 3, not 3.5. Make one side a double when you want the fraction.

Errors

Errors are raised with die(), not throw, and caught with try/catch as usual:

let meta;
try {
    meta = json(dump.stdout);
} catch (e) {
    meta = null;
}

Standard library corners

A few behaviors are worth knowing before they cost you an afternoon:

  • printf() and sprintf() take %J to format a value as JSON, and json() parses a JSON string.

  • Template literals in backticks interpolate with ${...}, as in JavaScript.

  • Regular expressions are written as literals - match(value, /^[0-9]+$/) - and replace() takes the g flag for a global replacement.

  • math.rand() seeds itself from the clock with millisecond resolution, so two processes starting together produce the same sequence. Do not use it to make something unique.

  • fs.mkstemp() unlinks the file it creates and hands back an open handle, so there is no path to give to another program. fs.mkdtemp() returns a directory path, and open(path, "x") creates a file only if it does not exist yet.

  • There is no getpid(). Reading the link /proc/self gives the process id as a string.