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*