Since Keli is adapting Smalltalk's syntactical minimalism, there are only 3 kinds of expressions in Keli, namely literal expressions, lambda expressions and function invocation.
Every expression in Keli can be captured using the following EBNF grammar. However, one should note that the FuncCall
node might be translated into other expression that bears different semantics, as explained in Section 4.
Expr= FuncCall LitExpr Lambda '(' Expr ')'â€‹FuncCall= Expr '.' FuncCallTailâ€‹FuncCallTail= FuncId {FuncId '(' Expr ')'}â€‹LitExpr= NumberLit StringLit ConstLit ArrayLitâ€‹ArrayLit= ('[' ']'  '[' Expr {',' Expr} ']')Lambda= Id '' Expr= '.' FuncCallTail
Abbreviations  Meaning 
lit  literal 
expr  expression 
func  function 
id  identifier 
invo  invocation 
const  constant 
unifunc  function that takes one and only one parameter 
polyfunc  function that takes more than one parameter 
Integer literal are one or more sequences of digits, as described in Section 2. For example,
12345
Float literal are one or more sequences of digits followed by a period and one or more sequences of digits. For example,
3.141592653
String literals are any lexemes enquoted by double quotes. For example,
"Hello world"
A constant literal is any valid identifier of Keli (as described in Section 2.5). For example, the following are valid constant literals:
xpoint1wwwwww
Array are transpiled directly as native JavaScript array. Also, array needs to be homogeneous, i.e., all the element of an array must be of the same type.
Array type can be created using the following grammar:
( [
expr { ,
expr } ]
 []
)
For example,
[1,2,3,4,5][] // empty array
Function invocations is the heart of Keli, because almost everything in Keli can be viewed as function invocations. In brief, there are only 2 kinds of function invocations, namely, unifunc invocations and polyfunc invocations.
A function identifier can be any combination of alphabets and also any combinations of operators (except brackets). For example, the following are valid function identifiers:
+>==
Also, because of polyfunc invocations, functions in Keli are actually identified by one or more function identifiers.
The precedence rule is very simple:
Evaluate any expressions in the most nested parenthesis first.
If no parenthesis is found, evaluate from left to right until the dot operator is encountered.
Unifunc invocations are functions invoked with exactly one parameter. The grammar for unifunc invocations are as follows:
expr
.
funcId
Expr '.' FuncId
For example, in the following code, 123
is the parameter and square
is the function identifier. In other words, it means applying 123
to the function square
.
123.square
Due to the precedence rule as described in Section 3.3.2, the following code snippets are equivalent:
123.negate.square
(123.negate).square
Polyfunc invocations are functions invoked with more than one parameter.
The grammar for polyfunc invocations are as follows:
expr
.
{ funcId(
expr)
}
For example, the following code means applying 1
and x
to the function plus
,
1.plus(x)
â€‹
The following code means applying "Hello world"
, "Hello"
and "Bye"
to the function named replace
with
,
"Hello world".replace("Hello") with("Bye")
â€‹
As mentioned in Section 3.3.1, since operators are also valid identifiers, the following code is valid, and shall mean applying myHashMap
, "key"
and 123
to the function add
>
.
myHashMap.add("key")>(123)
Due to such flexibility, one can easily create custom DSL (domainspecific languages) using Keli. In fact, Keli itself also abuses this flexibility for compilerspecific commands, such as defining tagged union, foreign function interface etc.
Because of the the precedence rule, one can chain function invocations as such:
myList.append(5).reverse.put(5) atIndex(0).++(anotherList).select(x  x.isEven)
Lambda are also known as anonymous functions, which are functions that is nameless.
Lambda can be created using the following grammar:
id [ typeAnnotation ]

expr
For example, the following code means x
is the parameter and x.+(5)
is the lambda body.
x  x.+(5)
Lambda with more than one parameters is not supported, since one can easily emulate that using nested lambdas, as such:
x  y  x.+(y)
To apply argument to a lambda expression, use the magic function .apply
as follows:
f = x  x.square= f.apply(6) // Answer is 36
Since most of the time we will only be using a single parameter lambda, we could use lambda shorthand to reduce typings and improve readability. Lambda shorthand can be created using the following grammar:
.
( funcId  { funcId(
expr)
})
For example,
Shorthand  Expansion 






Lambda shorthand are useful especially when we are using higherorder functions like select
.
For example,
[1, 2, 3, 4, 5].select(.*(2)) // [2 4 6 8 10]
Most of the time, the type of the parameter of a lambda can be inferred by the compiler, however, in certain situation the compiler might not be able to deduce the parameter type.
Suppose we have two negate
functions.
(this Int).negate = undefined(this String).negate = undefined
After, if we create a lambda as follows, we will get a compile error:
f = x  x.negate// Unable to deduce the type of `x`, because `x` might be `String` or `Int`
In such situation, we can provide a type annotation for the parameter to overcome this issue:
f = x Int  x.negate