Kenpali Code Specification

Literals

# Literal null
null
>> {"literal": null}
# Literal false
false
>> {"literal": false}
# Literal true
true
>> {"literal": true}
# Literal integer
1
>> {"literal": 1}
# Literal decimal
-2.5
>> {"literal": -2.5}
# Literal string
"foobar"
>> {"literal": "foobar"}
# Raw literal string
`fooa\r`
>> {"literal": "f\o\o\b\a\r"}

Names

# Name with only letters
foo
>> {"name": "foo"}
# Name with uppercase letters
FOO
>> {"name": "FOO"}
# Name with numbers
f00
>> {"name": "f00"}

Arrays

# Empty array
[]
>> {"array": []}
# Single-element array
[1]
>> {"array": [{"literal": 1}]}
# Array of literals
[1, 2, 3]
>> {"array": [{"literal": 1}, {"literal": 2}, {"literal": 3}]}
# Trailing comma
[1, 2, 3,]
>> {"array": [{"literal": 1}, {"literal": 2}, {"literal": 3}]}
# Array with elements of mixed types
[null, 1, "foo"]
>> {"array": [{"literal": null}, {"literal": 1}, {"literal": "foo"}]}
# Nested arrays
[[1]]
>> {"array": [{"array": [{"literal": 1}]}]}
# Array containing an expression to evaluate
[foo]
>> {"array": [{"name": "foo"}]}
# Arrays with spread
foo = [1, 2, 3];
[42, *foo, 97]
>> {
    "defining": [
        [
            "foo",
            {
                "array": [
                    {"literal": 1},
                    {"literal": 2},
                    {"literal": 3}
                ]
            }
        ]
    ],
    "result": {
        "array": [
            {"literal": 42},
            {"spread": {"name": "foo"}},
            {"literal": 97}
        ]
    }
}

Objects

# Empty object
{}
>> {"object": []}
# Object with literal values
{"foo": "bar", "spam": "eggs"}
>> {
    "object": [
        ["foo", {"literal": "bar"}],
        ["spam", {"literal": "eggs"}]
    ]
}

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

# Object with shorthand keys
{foo: "bar", spam: "eggs"}
>> {
    "object": [
        ["foo", {"literal": "bar"}],
        ["spam", {"literal": "eggs"}]
    ]
}
# Object taking properties from names
foo = "bar";
spam = "eggs";
{foo:, spam:}
>> {
    "defining": [
        ["foo", {"literal": "bar"}],
        ["spam", {"literal": "eggs"}]
    ],
    "result": {
        "object": [
            ["foo", {"name": "foo"}],
            ["spam", {"name": "spam"}]
        ]
    }
}
# Object with values of mixed types
{foo: null, bar: 1, baz: [2]}
>> {
    "object": [
        ["foo", {"literal": null}],
        ["bar", {"literal": 1}],
        ["baz", {"array": [{"literal": 2}]}]
    ]
}
# Nested objects
{foo: {bar: "baz"}}
>> {
    "object": [
        [
            "foo",
            {
                "object": [["bar", {"literal": "baz"}]]
            }
        ]
    ]
}
# Object with expression keys and values
{(key): value}
>> {"object": [[{"name": "key"}, {"name": "value"}]]}
# Objects with spread
foo = {bar: 1, baz: 2};
{answer: 42, **foo, question: 69}
>> {
    "defining": [
        [
            "foo",
            {
                "object": [
                    ["bar", {"literal": 1}],
                    ["baz", {"literal": 2}]
                ]
            }
        ]
    ],
    "result": {
        "object": [
            ["answer", {"literal": 42}],
            {"spread": {"name": "foo"}},
            ["question", {"literal": 69}]
        ]
    }
}

Comments

# A comment on its own line
// A billion-dollar mistake
null
>> {"literal": null}
# A comment at the end of a line
null // A billion-dollar mistake
>> {"literal": null}

Function Calls

# One positional argument
foo(1)
>> {
    "calling": {"name": "foo"},
    "args": [{"literal": 1}]
}
# Two positional arguments
foo(1, 2)
>> {
    "calling": {"name": "foo"},
    "args": [{"literal": 1}, {"literal": 2}]
}
# Spread positional arguments
foo(*bar)
>> {
    "calling": {"name": "foo"},
    "args": [{"spread": {"name": "bar"}}]
}
# Positional and spread positional arguments
foo(1, *bar)
>> {
    "calling": {"name": "foo"},
    "args": [
        {"literal": 1},
        {"spread": {"name": "bar"}}
    ]
}
# One named argument
foo(bar: 1)
>> {
    "calling": {"name": "foo"},
    "namedArgs": [["bar", {"literal": 1}]]
}
# Two named arguments
foo(bar: 1, baz: 2)
>> {
    "calling": {"name": "foo"},
    "namedArgs": [["bar", {"literal": 1}], ["baz", {"literal": 2}]]
}
# Named arguments from names
foo(bar:, baz:)
>> {
    "calling": {"name": "foo"},
    "namedArgs": [["bar", {"name": "bar"}], ["baz", {"name": "baz"}]]
}
# Spread named arguments
foo(**bar)
>> {
    "calling": {"name": "foo"},
    "namedArgs": [{"spread": {"name": "bar"}}]
}
# Positional and named arguments
foo(1, 2, bar: 3, baz: 4)
>> {
    "calling": {"name": "foo"},
    "args": [{"literal": 1}, {"literal": 2}],
    "namedArgs": [["bar", {"literal": 3}], ["baz", {"literal": 4}]]
}
# Calling the result of a function call
foo(x)(y)
>> {
    "calling": {
        "calling": {"name": "foo"},
        "args": [{"name": "x"}]
    },
    "args": [{"name": "y"}]
}

Error Catching

# Error catching
foo !
>> {
    "catching": {"name": "foo"}
}

Function Definitions

# No parameters
() => 42
>> {"given": {}, "result": {"literal": 42}}
# One positional parameter
(x) => plus(x, 3)
>> {
    "given": {
        "params": ["x"]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"literal": 3}]
    }
}
# Optional positional parameter
(x, y = 3) => plus(x, y)
>> {
    "given": {
        "params": [
            "x",
            {"name": "y", "defaultValue": {"literal": 3}}
        ]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"name": "y"}]
    }
}
# Positional rest parameter
(*args) => length(args)
>> {
    "given": {
        "params": [{"rest": "args"}]
    },
    "result": {
        "calling": {"name": "length"},
        "args": [{"name": "args"}]
    }
}
# Named parameter
(x, y:) => plus(x, y)
>> {
    "given": {
        "params": ["x"],
        "namedParams": ["y"]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"name": "y"}]
    }
}
# Optional named parameter
(x, y: = 3) => plus(x, y)
>> {
    "given": {
        "params": ["x"],
        "namedParams": [
            {"name": "y", "defaultValue": {"literal": 3}}
        ]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"name": "y"}]
    }
}
# Named rest parameter
(**namedArgs) => namedArgs
>> {
    "given": {
        "namedParams": [{"rest": "namedArgs"}]
    },
    "result": {"name": "namedArgs"}
}
# Named parameter with alias
(x, y: z) => plus(x, z)
>> {
    "given": {
        "params": ["x"],
        "namedParams": [{"name": "z", "property": "y"}]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"name": "z"}]
    }
}
# Array destructuring in parameters
([foo, bar]) => foo
>> {
    "given": {
        "params": [
            {"arrayPattern": ["foo", "bar"]}
        ]
    },
    "result": {"name": "foo"}
}
# Scope in function body
(x) => (y = plus(x, 3); y)
>> {
    "given": {
        "params": ["x"]
    },
    "result": {
        "defining": [
            [
                "y",
                {
                    "calling": {"name": "plus"},
                    "args": [{"name": "x"}, {"literal": 3}]
                }
            ]
        ],
        "result": {"name": "y"}
    }
}

Forward Pipe

# Forward pipe into a bare name
1 | foo
>> {
    "calling": {"name": "foo"},
    "args": [{"literal": 1}]
}
# Forward pipe injecting a first argument
1 | bar(2)
>> {
    "calling": {"name": "bar"},
    "args": [{"literal": 1}, {"literal": 2}]
}
# Forward pipe injecting alongside a named argument
1 | bar(foo: 2)
>> {
    "calling": {"name": "bar"},
    "args": [{"literal": 1}],
    "namedArgs": [["foo", {"literal": 2}]]
}
# Chaining forward pipes
1 | foo | bar(2)
>> {
    "calling": {"name": "bar"},
    "args": [
        {
            "calling": {"name": "foo"},
            "args": [{"literal": 1}]
        },
        {"literal": 2}
    ]
}
# Blocking first-argument injection
1 | (bar(2))
>> {
    "calling": {
        "calling": {"name": "bar"},
        "args": [{"literal": 2}]
    },
    "args": [{"literal": 1}]
}
# Pipe in an arrow
(x) => x | plus(3)
>> {
    "given": {
        "params": ["x"]
    },
    "result": {
        "calling": {"name": "plus"},
        "args": [{"name": "x"}, {"literal": 3}]
    }
}
# Error catching in pipeline
1 | foo ! | bar
>> {
    "calling": {"name": "bar"},
    "args": [
        {
            "catching": {
                "calling": {"name": "foo"},
                "args": [{"literal": 1}]
            }
        }
    ]
}
# Point-free pipeline starting with |
| foo | bar(2)
>> {
    "given": {"params": ["pipelineArg"]},
    "result": {
        "calling": {"name": "bar"},
        "args": [
            {
                "calling": {"name": "foo"},
                "args": [{"name": "pipelineArg"}]
            },
            {"literal": 2}
        ]
    }
}

Indexing

# Indexing
["foo", "bar"] @ 2
>> {
    "indexing": {"array": [
        {"literal": "foo"},
        {"literal": "bar"}
    ]},
    "at": {"literal": 2}
}
# Correct precedence of @
[x @ 1 | f, x | f @ 1]
>> {
    "array": [
        {
            "calling": {"name": "f"},
            "args": [
                {
                    "indexing": {"name": "x"},
                    "at": {"literal": 1}
                }
            ]
        },
        {
            "indexing": {
                "calling": {"name": "f"},
                "args": [{"name": "x"}]
            },
            "at": {"literal": 1}
        }
    ]
}
# Indexing with a property name
x @ y:
>> {
    "indexing": {"name": "x"},
    "at": {"literal": "y"}
}
# Indexing with an explicit string
x @ "y"
>> {
    "indexing": {"name": "x"},
    "at": {"literal": "y"}
}

Scopes

# Simple declaration
foo = 42; foo
>> {
    "defining": [
        ["foo", {"literal": 42}]
    ],
    "result": {"name": "foo"}
}
# Nested scopes
foo = (bar = 1; bar); foo
>> {
    "defining": [
        [
            "foo",
            {
                "defining": [
                    ["bar", {"literal": 1}]
                ],
                "result": {"name": "bar"}
            }
        ]
    ],
    "result": {"name": "foo"}
}
# Array destructuring declaration
[foo, bar] = [42, 97];
plus(foo, bar)
>> {
    "defining": [
        [
            {"arrayPattern": ["foo", "bar"]},
            {
                "array": [
                    {"literal": 42},
                    {"literal": 97}
                ]
            }
        ]
    ],
    "result": {
        "calling": {"name": "plus"},
        "args": [
            {"name": "foo"},
            {"name": "bar"}
        ]
    }
}
# Nested array destructuring
[foo, [spam, eggs]] = [42, [97, 216]];
plus(foo, spam, eggs)
>> {
    "defining": [
        [
            {
                "arrayPattern": [
                    "foo",
                    {"arrayPattern": ["spam", "eggs"]}
                ]
            },
            {
                "array": [
                    {"literal": 42},
                    {"array": [
                        {"literal": 97},
                        {"literal": 216}
                    ]}
                ]
            }
        ]
    ],
    "result": {
        "calling": {"name": "plus"},
        "args": [
            {"name": "foo"},
            {"name": "spam"},
            {"name": "eggs"}
        ]
    }
}
# Object destructuring declaration
{foo:, bar:} = {foo: 42, bar: 97};
plus(foo, bar)
>> {
    "defining": [
        [
            {"objectPattern": ["foo", "bar"]},
            {
                "object": [
                    ["foo", {"literal": 42}],
                    ["bar", {"literal": 97}]
                ]
            }
        ]
    ],
    "result": {
        "calling": {"name": "plus"},
        "args": [
            {"name": "foo"},
            {"name": "bar"}
        ]
    }
}
# Object destructuring with aliases
{foo: spam, bar: eggs} = {foo: 42, bar: 97};
plus(spam, eggs)
>> {
    "defining": [
        [
            {
                "objectPattern": [
                    {"name": "spam", "property": "foo"},
                    {"name": "eggs", "property": "bar"}
                ]
            },
            {
                "object": [
                    ["foo", {"literal": 42}],
                    ["bar", {"literal": 97}]
                ]
            }
        ]
    ],
    "result": {
        "calling": {"name": "plus"},
        "args": [
            {"name": "spam"},
            {"name": "eggs"}
        ]
    }
}
# Expression statements
frobnicate();
42
>> {
    "defining": [
        [null, {"calling": {"name": "frobnicate"}}]
    ],
    "result": {"literal": 42}
}
# Assignment as the scope result
foo = 42
!! missingStatementSeparator {"line": 1, "column": 9}
# Chained assignment
foo = bar = 42;
foo
!! missingStatementSeparator {"line": 1, "column": 11}

Modules

# Accessing a name in a module
foo.bar
>> {
    "name": "bar",
    "from": "foo"
}