The Gulik Programming Language

Introduction

Gulik is a stack-based functional language. It is stack-based in the sense that functions manipulate a single global stack of values; it is functional in the sense that these functions are first-class values and can themselves be stored on the stack.

Syntax

The syntax of Gulik is very simple. Comments start with -- and go to end of line. The program is a (whitespace-separated) sequence of expressions. An expression is one of: The precise formats allowed for numbers, strings, etc. are those of the corresponding parsers in Parsec.Token with haskellStyle.

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.

Semantics

The formal semantics of Gulik are quite similar to those of GML; they will be discussed here in a more informal style.

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:

All expressions in Gulik are either value expressions or operators (never both). A value expression is evaluated to a value, which is then pushed onto the stack. An operator does something else.

The value expressions are described by:

The operators 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.

Examples

{ /x x x! } { /x x x! } !
-- result: diverges
Lambda 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: 365
Since 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: 120
Recursion (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.