The whitespace before or after ! can usually be elided since it cannot occur as part of another expression type. Also, the parser currently allows space between a / prefix and the corresponding identifier name, though this may change in the future.
Gulik is understood as running on an abstract machine which keeps two forms of state: a global stack of values and an environment mapping identifiers to values.
A value is one of:
The value expressions are described by:
This alone is enough to implement some standard stack manipulation commands (like { /x x x } /dup). However, value manipulation requires more specialized primitives. These are provided through special closures which are bound in the default environment, and actually reference code within the interpreter. However, they are functionally identical to closures written in Gulik. Thus, unlike in GML, one must explicitly apply the "builtin" functions as one would explicitly apply one's own functions. The upside is that builtin functions are first-class, can be passed as arguments to higher-order functions, etc.
The closures in the default environment can be browsed here; this includes some "builtin" closures as well as others derived from them in Gulik. The default environment also includes the values true, false, and nil.
Of particular note: the builtin (and thus also the derived) math operators will always produce a double result unless both arguments are integers. Most operators are overloaded across types when sensical.
{ /x x x! } { /x x x! } ! -- result: divergesLambda calculus translates very directly, as illustrated here with the classic non-reducing expression. The major difference is explicit application.
{ 5 mul! } /true 73 true! -- result: 365Since they are just environment members, you can redefine true and false (in true Discordian style).
{ /self /n n iszero! { 1 } { n dec! self self! n mul! } if!! } /factr { factr factr! } /fact 5 fact! -- result: 120Recursion (as in GML) is achievable by passing the closure as an argument to itself. Also note the if!! idiom: to make functional-if act like procedural-if, it is necessary to apply a closure after the if has been reduced.
{ /self /f /x x isempty! { nil } { x car! f! x cdr! f self self! cons! } if!! } /mapr { mapr mapr! } /map 0 1 2 3 4 5 6 list! fact map! -- result: [1,1,2,6,24,120]Higher-order procedures are straightforward to implement, as in this example (from the Prelude) of mapping over a list.