Kenpali Code Specification

Tokens

Kenpali code is first split into tokens.

The following regular expressions are used for number and string literals:

NUMBER = -?(0|[1-9](\d*))(.\d+)?([Ee][+-]?\d+)?

STRING = "(\\"|[^"])*"

RAW_STRING = `[^`]*`

The following regular expression is used for names:

NAME = [A-Za-z][A-Za-z0-9]*

The keywords null, false, and true are treated as literal tokens even though they match the name pattern.

The following character sequences are tokens:

( ) [ ] { } , ; : => = |. | @ . ! $ ** *

All spaces, tabs, carriage returns, and linefeeds are considered whitespace and discarded.

Comments consist of the characters // and all following text until the end of the line. They are also discarded when parsing.

Any characters not matching the above token patterns cause parsing to fail.

Literals

literal ::= "null" | "false" | "true" | NUMBER | STRING | RAW_STRING

A literal parses to a literal expression.

Kenpali supports “raw string” syntax, delimited using backticks instead of quotes. Raw strings treat all backslashes as literal backslashes, rather than creating escape sequences, which can make backslash-heavy strings (e.g. regexes) easier to write and read. Raw strings parse to ordinary literal expressions.

Comments

Names

name ::= [NAME "/"] NAME

A name normally parses to a name expression, though some other syntactic structures use names for other purposes.

The form with a slash indicates that the name is found in a module. The module name is added as the from property of the name expression.

Kenpali uses camelCase for names by convention.

Arrays

array ::= "[" [array_element ("," array_element)* [","]] "]"

array_element ::= assignable | array_spread

array_spread ::= "*" assignable

An array parses to an array expression.

Objects

object ::= "{" [object_element ("," object_element)* [","]] "}"

object_element ::= assignable ":" assignable | name ":" | object_spread

object_spread ::= "**" assignable

An object parses to an object expression.

If a key is a valid Kenpali name, the quotes can be omitted.

If the key is meant to actually reference a name from the surrounding scope, enclose it in parentheses.

If the value is omitted, it defaults to reading the property name from the surrounding scope: {foo:} is equivalent to {foo: foo}.

Groups

group ::= "(" expression ")"

Any expression can be enclosed in parentheses to force it to be parsed first, overriding precedence rules and special processing rules. A group parses to the same JSON as the expression it contains would if parsed on its own.

Scopes

scope ::= statement* assignable

statement ::= [defining_pattern "="] assignable ";"

defining_pattern ::= name | array_pattern | object_pattern

array_pattern ::= "[" [array_pattern_element ("," array_pattern_element)* [","]] "]"

array_pattern_element ::= defining_pattern ["=" assignable] | "*" defining_pattern

object_pattern ::= "{" [object_pattern_element ("," object_pattern_element)* [","]] "}"

object_pattern_element ::= object_pattern_simple ["=" assignable] | "**" defining_pattern

object_pattern_simple ::= assignable ":" defining_pattern | name ":"

A scope parses to a defining expression

Property Access

tight_pipeline ::= atomic ("." name)*

atomic ::= group | array | object | literal | name

A single property can be extracted from an object by putting the property name after a dot. This parses to an indexing expression.

Property access can be chained, and associates left to right.

Function Definitions

arrow_function ::= parameter_list "=>" assignable

parameter_list ::= "(" [parameter ("," parameter)* [","]] ")"

parameter ::= array_pattern_element | object_pattern_element

A function definition parses to a given expression.

Pipelines

Most of Kenpali’s operators are pipeline operators. All pipeline operators have the same precedence, and associate left to right.

assignable ::= arrow_function | pipeline | point_free_pipeline | constant_function

pipeline ::= tight_pipeline pipeline_step*

pipeline_step ::= call | pipe | pipe_call | pipe_dot | at | "!"

call ::= argument_list

pipe ::= "|" tight_pipeline

pipe_call ::= "|" tight_pipeline argument_list

pipe_dot ::= "|." name

at ::= "@" tight_pipeline

argument_list ::= "(" [argument ("," argument)* [","]] ")"

argument ::= positional_argument | named_argument | array_spread | object_spread

positional_argument ::= assignable

named_argument ::= name ":" [assignable]

Function Calls

Function call steps parse to calling expressions.

Pipes and Pipe-Calls

Pipe and pipe-call steps are transformed into ordinary function calls, producting calling expressions.

Error Catching

Error catching steps parse to catching expressions.

Indexing

Indexing steps parse to indexing expressions.

Function Definition Shorthand

Kenpali has two kinds of shorthand syntax for compactly defining common function types.

A constant function ignores any arguments passed to it. The shorthand syntax is a $ followed by an expression for the function’s return value.

constant_function ::= "$" assignable

A point-free pipeline is written as a pipeline missing the initial value. It parses to a function with one positional parameter, which becomes the missing initial value.

point_free_pipeline ::= pipeline_step*