Kenpali JSON Specification
Literals
A literal expression has the form {"type": "literal", "value": <value>}. The value can be null, true, false, or any valid JSON string or number.
Examples:
Names
Names are defined using a block expression, which has the form {"type": "block", "defs": <definitions>, "result": <result>}. The <definitions> node must be an array of pairs, where the first element of each pair is a name pattern and the second element is any expression. The <result> node can be any expression. The value of the block expression is the value of the <result> node.
The simplest name pattern has the form {"type": "name", "name": <name>}, where the <name> is a string. This defines a single name, which is bound to the value of the expression in the second element of the pair. See Arrays and Objects for more complex types of name patterns.
A name expression evaluates to the value previously assigned to the specified name. It also has the form {"type": "name", "name": <name>}, where the <name> is a string.
Examples:
Another kind of name pattern has the form {"type": "ignore"}. This creates an expression statement, which evaluates the expression (usually for its side effects) and then discards the result.
Arrays
An array expression has the form {"type": "array", "elements": <elements>}. The <elements> node is a JSON array containing expressions, which are evaluated to obtain the array’s elements.
Instead of an expression, any of the array’s elements can be a spread instead. A spread has the form {"type": "spread", "value": <value>}, where <value> is any expression. The <value> expression is expected to evaluate to a sequence, whose elements are added to the array.
When defining names, an array pattern can be used to extract individual elements from an array and assign them to names. An array pattern has the form {"type": "arrayPattern", "names": <names>}, where <names> is an array of name patterns.
Wrapping one of the names in an optional pattern allows the assignment to work even if there aren’t enough elements in the array. An optional pattern has the form {"type": "optional", "name": <name>, "defaultValue": <defaultValue>}, where <name> is a name pattern and <defaultValue> is an expression to evaludate if the array doesn’t have enough elements to provide a value for the name.
One of the elements of an array pattern can be a rest pattern, of the form {"type": "rest", "name": <name>}. The <name> is bound to an array containing whatever elements are left over after the other name patterns have taken theirs.
Objects
An object expression has the form {"type": "object", "entries": <entries>}. The <entries> node is a JSON array containing name-value pairs. In each pair, the first element must be an expression evaluating to a string, while the second element can be any expression.
Instead of an expression, a key can be {"type": "spread"} instead. The corresponding <value> expression is expected to evaluate to an object, whose entries are added to the containing object.
When defining names, an object pattern can be used to extract property values from an object and assign them to names. An object pattern has the form {"type": "objectPattern", "entries": <entries>}. Each entry is a pair whose first element is an expression yielding the property name to extract, and whose second element is a name pattern to bind the property value to.
One of the entries of an object pattern can have {"type": "rest"} as its key. The corresponding name pattern is bound to an object containing whatever properties aren’t mentioned explicitly in the object pattern.
Defining and Calling Functions
Functions are defined using a function expression, which has the form {"type": "function", "posParams": <pos-param-spec>, "namedParams": <named-param-spec>, "body": <body>}. Both <pos-param-spec> and <named-param-spec> are optional. The <pos-param-spec> indicates the positional parameters the function accepts, with the same format as the names in an array pattern; the <named-param-spec> indicates the named parameters the function accepts, with the same format as the entries in an object pattern. The <body> can be any expression, and it defines what the function returns. It can reference the parameters as if they were names defined in the function’s scope.
Functions are called using a call expression, which has the form {"type": "call", "callee": <callee>, "posArgs": <pos-arg-spec>, "namedArgs": <named-arg-spec>}. Both posArgs and namedArgs are optional. The <callee> must be an expression, and its result is the function to call. The <pos-arg-spec> indicates the positional arguments to pass to the function, with the same format as the value in an array expression; the <named-arg-spec> indicates the named arguments to pass to the function, with the same format as the value in an object expression.
Indexing
An index expression extracts the value at a specific index from a collection. It has the form {"type": "index", "collection": <collection>, "index": <index>}, where both <collection> and <index> are expressions.
All numeric indexes are one-based.