SteelSeries GoLisp: Language Reference

Overview

GoLisp is a simple Lisp language and runtime implemented in Google’s Go programming language. It’s main purpose is integration into a Go application to provide runtime extension and scripting. The core of a basic Lisp is provided, but with limited special forms and primitive functions. More of these will be added as required without having to modify the GoLisp core. Also, a REPL is provided and GoLisp can be used as-is to enter and evaluate code.

GoLisp is a lexically scoped Lisp-1:

Lexically scoped:

The scope of definitions are determined by where they are made in the lexical structure of the code. Each function, lambda, let, and do creates a new lexical scope. From [2]:

Here references to the established entity can occur only within certain program portions that are lexically (that is, textually) contained within the establishing construct. Typically the construct will have a part designated the body, and the scope of all entities established will be (or include) the body.

Lisp-1:

A Lisp where functions and variables share a single namespace. This differs from a Lisp-2 in which functions and variables have separate namespaces.

On systems other than Windows, the GoLisp REPL supports readline and so provides history and command editing. History is local to the directory from which you execute GoLisp.

Data types

Cons Cells are the central data type in classic Lisp used both as dotted pairs (a . b) and general lists (a b). For an overview of cons cells and how to use them see [1], [3], or http://cs.gmu.edu/~sean/lisp/cons/.

Symbols are simple identifiers, e.g. function-name. Symbols follow the follow 2 simple rules:

  • starts with a letter, and

  • contains alphanumeric, -, _, ?, !, >, and *.

Typically, - is used to separate words in a symbol, _ is used in special symbols (such as system use) to separate words and as a prefix and suffix. The characters ?, !, and * are typically used as the final character of a function name to denote:

? a predicate, e.g. nil?

! a mutating function (changes the argument rather than returning a modified copy), e.g. set-car!

* a variant of the primary function, e.g. flatten (which does a one level flattening of a list) and flatten* (which is a recursive flatten)

Some builtin function names violate these rules (e.g. arithmetic and relative functions). You can’t create symbols like this without adding support for them to the tokenizer and parser.

Strings are any sequence of characters other than " enclosed by a pair of ", e.g. "string". If you need to have " in a string, use \".

Integers are sixtyfour bit signed integers. Both decimal and hexadecimal formats are supported. E.g. 26, 0x1a, 0x1A.

Floats are Go float32 numbers. Accordingly they are signed. All arithmetic functions with the exception of modulus work as expected for both integers and floats. Numbers are coerced to floats as required, specifically if any arguments are float, all are converted to float and the result will be a float.

Booleans represent true and false. nil, (), and false are all logically false, everything else is logically true. Boolean literals are #t and #f for true and false, respectively.

Functions are user defined procedures. They are covered in detail later.

Objects allow you to encapsulate a Go object (struct) in a Lisp data object. There is no way to do this from Lisp itself, but is useful when writing primitive functions (see below).

Bytearrays are simply objects that encapsulate []byte objects. The difference is that there is syntactic support for them. Use square braces surrounding a list of numbers between 0 and 255, inclusive. For example: [1 2 3 4 5]. That format will parse to an object containing a the Go bytearray (i.e. []byte). Bytearrays evaluate to themselves. There are also functions for doing bytearray manipulation.

Primitives are just as they are in Lisp or Smalltalk: functions written in Go and exposed as functions in Lisp. The combinaion of primitives and objects allow you to integrate with the underlying Go program.

Builtin functions

Arithmetic

(+ number…)

Adds a series of numbers, e.g.

  (+ 4)        ⇒ 4
  (+ 4 2)      ⇒ 6
  (+ 4 2 7)    ⇒ 13
  (+ 1.2 2.3)  ⇒ 3.5
  (+ -1.2 2.3) ⇒ 1.0999999

(- number…)

Sequentially subtracts a sequence of numbers, e.g.

  (- 10 3)     ⇒ 7
  (- 10 3 4)   ⇒ 3
  (- 10 3 4 5) ⇒ -2
  (- 2.3 1.2)  ⇒ 1.0999999
  (- 1.2 2.3)  ⇒ -1.0999999

(* number…)

Multiplies a series of numbers, e.g.

  (* 4)       ⇒ 4
  (* 4 2)     ⇒ 8
  (* 4 2 7)   ⇒ 56
  (* 1.2 2.3) ⇒ 2.76

(/ number…)

(quotient number…)

Sequentially divides a sequence of numbers. E.g.

  (/ 30 2)    ⇒ 15
  (/ 30 2 3 ) ⇒ 5
  (/ 30 4)    ⇒ 7
  (/ 30.0 4)  ⇒ 7.5

(% numerator denominator)

(modulo numerator denominator)

Returns the remainder of division of two numbers. NOTE: modulus only works for integers. E.g.

  (% 4 2) ⇒ 0
  (% 4 3) ⇒ 1

Conversions

(integer number)

Returns the integer value of number. If it is an integer, it is simply returned. However, if it is a float the integer part is returned. Note that this is implemented in go by casting the float32 to a int64 so converting negative floats gives a large integer.

  (integer 5)    ⇒ 5
  (integer 5.2)  ⇒ 5
  (integer 5.8)  ⇒ 5
  (integer -1.0) ⇒ 4294967295

(float number)

Returns the float value of number. If it is a float, it is simply returned. However, if it is an integer the corresponding float is returned.

  (float 5) ⇒ 5.0

Note that converting a float to a string for printing using the format %g to use the minimum number of characters so 5.0 will actually print as 5.

(number->string number [base])

Converts number (first converted to an integer) to a string, in the given base. Allowed bases are 2, 8, 10, and 16. If the base is omitted, 10 is used. No base prefixes (e.g. 0x for base 16) are added.

  (number->string 42)    ⇒ "42"
  (number->string 42 2)  ⇒ "101010"
  (number->string 42 8)  ⇒ "52"
  (number->string 42 10) ⇒ "42"
  (number->string 42 16) ⇒ "2a"
  (number->string 42 15) ⇒ "Unsupported base: 15"

(string->number numeric-string [base])

Converts numeric-string to an integer, in the given base. Allowed bases are 2, 8, 10, and 16. If the base is omitted, 10 is used. No base prefixes (e.g. 0x for base 16) are allowed. Specifying an unsupported base will result in 0.

  (string->number "42")       ⇒ 42
  (string->number "101010" 2) ⇒ 42
  (string->number "52" 8)     ⇒ 42
  (string->number "42" 10)    ⇒ 42
  (string->number "2a" 16)    ⇒ 42
  (string->number "42" 15)    ⇒ 0

Comparisons

All comparison operations work with floating point numbers as well.

(< number number)

Returns whether the first argument is less than the second argument.

  (< 1 2) ⇒ #t
  (< 2 1) ⇒ #f
  (< 2 2) ⇒ #f

(> number number)

Returns whether the first argument is greater than the second argument.

  (> 1 2) ⇒ #f
  (> 2 1) ⇒ #t
  (> 2 2) ⇒ #f

(= number number)

(== number number)

Returns whether the first argument is equal to the second argument.

  (== 1 2) ⇒ #f
  (== 1 1) ⇒ #t

(!= number number)

Returns whether the first argument is not equal to the second argument.

  (!= 1 2) ⇒ #t
  (!= 1 1) ⇒ #f

(<= number number)

Returns whether the first argument is less than or equal to the second argument.

  (<= 1 2) ⇒ #t
  (<= 2 1) ⇒ #f
  (<= 2 2) ⇒ #t

(>= number number)

Returns whether the first argument is greater than or equal to the second argument.

  (>= 1 2) ⇒ #f
  (>= 2 1) ⇒ #t
  (>= 2 2) ⇒ #t

(odd? number)

Returns whether the argument is odd.

  (odd? 2) ⇒ #f
  (odd? 5) ⇒ #t

(even? number)

Returns whether the argument is even.

  (even? 2) ⇒ #t
  (even? 5) ⇒ #f

Logical

(not sexpr)

Returns whether the boolean negation of the argument.

  (! #t) ⇒ #f
  (! #f) ⇒ #t

(and sexpr…)

Computes a logical and of the arguments (which are evaluated first), with shortcircuiting. I.e. each argument is evaluated in order until one evaluates to false or the end of the list is reached. If all evaluate to true the result of the and is true, otherwise it is false. If an argument evaluates to false, subsequent arguments are not evaluated. and with no arguments evaluates to true.

(or sexpr…)

Computes a logical or of the arguments (which are evaluated first), with shortcircuiting. I.e. each argument is evaluated in order until one evaluates to true or the end of the list is reached. If all evaluate to false the result of the or is false, otherwise it is true. If an argument evaluates to true, subsequent arguments are not evaluated. or with no arguments evaluates to false.

Binary

(binary-and int-1 int-2)

Performs a bitwise AND of int-1 and int-2, returning the result.

  (number->string (binary-and 0xaa 0x0f) 16) ⇒ "a"
  (number->string (binary-and 0xaa 0xf0) 16) ⇒ "a0"

(binary-or int-1 int-2)

Performs a bitwise OR of int-1 and int-2, returning the result.

  (number->string (binary-or 0xaa 0x0f) 16) ⇒ "af"
  (number->string (binary-or 0xaa 0xf0) 16) ⇒ "fa"

(binary-not int)

Performs a bitwise NOT of int, returning the result.

  (number->string (binary-not 0x000000aa) 16) ⇒ "ffffff55"

(left-shift int count)

Shifts int left by count bits, returning the result.

  (number->string (left-shift (string->number "10101010" 2) 1) 2) ⇒ "101010100"
  (number->string (left-shift (string->number "10101010" 2) 3) 2) ⇒ "10101010000"

(right-shift int count)

Shifts int right by count bits, returning the result.

  (number->string (right-shift (string->number "10000" 2) 1) 2) ⇒ "1000"
  (number->string (right-shift (string->number "10000" 2) 4) 2) ⇒ "1"

Lists

(list sexpr…)

Creates a list out of a sequence of values.

  (list 1 2 3)                   ⇒ (1 2 3)
  (list (+ 1 1) (+ 2 2) (+ 3 3)) ⇒ (2 4 6)

(append list1 list2)

Append the result of evaluating list2 to list1. This makes a copy of list1, appends to it, and returns the copy (which now contains list2).

(append! list1 list2)

Append the result of evaluating list2 to list1. This appends to list1, and returns it. The side effect (indicated by the !) is that list1 is modified, unlike append. For example:

  (define a '(1 2 3))
  (append a '(4 5 6))  ⇒ (1 2 3 4 5 6)
  a                    ⇒ (1 2 3)

  (append! a '(4 5 6)) ⇒ (1 2 3 4 5 6)
  a                    ⇒ (1 2 3 4 5 6)

(interval lo hi)

Creates a list of numbers from lo to hi, inclusive.

  (interval 1 5) ⇒ (1 2 3 4 5)

(car list)

(head list)

Returns the first item in the list, i.e. the car pointer of the first cell in the chain referenced by list.

  (car nil)                    ⇒ nil
  (car '(a))                   ⇒ a
  (car '(a b c))               ⇒ a
  (car (list (+ 1 1) (+ 2 2))) ⇒ 2

(cdr list)

(rest list)

(tail list)

Returns the rest of the list, i.e. the cdr pointer of the first cell in the chain referenced by list. rest is provided as an alias.

  (cdr nil)                    ⇒ nil
  (cdr '(a))                   ⇒ nil
  (cdr '(a b c))               ⇒ (b c)
  (cdr (list (+ 1 1) (+ 2 2))) ⇒ (4)

The above car and cdr functions are the fundamental list decomposition operations. There are more that are based on these two. First there are a series of functions explicitly using car and cdr such as caddr which is equivalent to (car (cdr (cdr list))). Up to cddddr is supported.

  (caddr '(1 2 3 4 5)) ⇒ 3
  (cdddr '(1 2 3 4 5)) ⇒ (4 5)

(first list)

(second list)

(third list)

(fourth list)

(fifth list)

(sixth list)

(seventh list)

(eighth list)

(ninth list)

(tenth list)

These do exactly what you’d think: return the first, second, etc. item from list.

(nth list n)

Returns the nth element of list, the first being at n = 1.

  (nth '(1 2 3 4) 3) ⇒ 3

(take k list)

Returns a newly allocated list consisting of the first k elements of list. K must not be greater than the length of list.

  (take 3 '(1 2 3 4 5))      ⇒ '(1 2 3)

(drop k list)

Returns the sublist of list obtained by omitting the first k elements. The result, if it is not the empty list, shares structure with list. K must not be greater than the length of list.

  (drop 3 '(1 2 3 4 5))      ⇒ '(4 5)

(memq object list)

Returns the first pair of list whose car is object; the returned pair is always one from which list is composed. If object does not occur in list, #f (n.b.: not the empty list) is returned. memq uses eq? to compare object with the elements of list.

Association lists

Association lists (aka a-lists) are a classic Lisp way to handle associating a value with a key. Implementation is a list of dotted pairs: the car is the key, and the cdr is the value.

(acons key value [a-list])

This returns the result of adding a pair (key . value) to a-list. key, value, and a-list are evaluated. If a-list is omitted, it defaults to nil.

  (acons 'a 1)                    ⇒ ((a . 1))
  (acons 'a 1 '((b . 2) (c . 3))) ⇒ ((a . 1) (b . 2) (c . 3)))

(pairlis keys values [a-list])

Creates an a-list from lists of keys and values. A third argument can be provided which is an exicting a-list that the new pairs will be added to.

  (pairlis '(a b) '(1 2))                ⇒ ((b.2) (a.1)))
  (pairlis '(a b) '(1 2) '((c.3) (d.4))) ⇒ ((b.2) (a.1) (c.3) (d.4))))

Now that we have a way to create a-lists, we have a couple functions to get data out of them.

(assoc key a-list)

Return the pair from a-list whose car is equal to key. The empty pair is returned is key isn’t found.

  (assoc 'a '((a.1) (b.2) (c.3))) ⇒ (a.1)
  (assoc 'c '((a.1) (b.2)))       ⇒ ()

(rassoc value a-list)

Return the pair from a-list whose cdr is equal to value. The empty pair is returned is value isn’t found.

  (rassoc '1 '((a.1) (b.2) (c.3))) ⇒ (a.1)
  (rassoc '3 '((a.1) (b.2)))       ⇒ ()

(dissoc value a-list)

Remove the pair from a-list whose car is equal to value. The resulting alist is returned.

  (dissoc 'a '((a . 1) (b . 2))) ⇒ ((b . 2))
  (dissoc 'b '((a . 1) (b . 2))) ⇒ ((a . 1))

Bytearrays

(list-to-bytearray list of bytes)

The list must be comprised of numbers between 0 and 255, inclusive. The result is an object containing a []byte.

  (list-to-bytearray '(1 2 3 4)) ⇒ [1 2 3 4]

(bytearray-to-list bytearray)

This is the opposite of the previous function. The result is a list containing the numbers in the bytearray.

  (bytearray-to-list [1 2 3 4]) ⇒ (1 2 3 4)

(replace-byte bytearray index value)

Makes a copy of bytearray and replaces the byte at index with value. The new bytearray with the replaced byte is returned. index must be a valid index into the byte array (zero based), and value must be a valid byte value, i.e. between 0 and 255, inclusive.

  (define a [1 2 3 4])    ⇒ [1 2 3 4]
  (replace-byte a 2 100)  ⇒ [1 2 100 4]
  a                       ⇒ [1 2 3 4]

(replace-byte! bytearray index value)

Replaces the byte at index with value. index must be a valid index into the byte array (zero based), and value must be a valid byte value, i.e. between 0 and 255, inclusive. The original byte array is modified and the returned bytearray object is the one that is passed to the function.

  (define a [1 2 3 4])    ⇒ [1 2 3 4]
  (replace-byte! a 2 100) ⇒ [1 2 100 4]
  a                       ⇒ [1 2 100 4]

(extract-byte bytearray index)

Fetch and return the byte at index. index must be a valid index into the byte array (zero based).

  (extract-byte [1 2 3 4] 2) ⇒ 3

(append-bytes bytearray byte…)

(append-bytes bytearray list of bytes)

(append-bytes bytearray bytearray)

Appends the rest of the arguments to a copy of the bytearray that is the first arg. The copy is returned. Things that can be appended are: a single byte, a sequence of bytes (as a sequence of separate arguments), a list of bytes, a bytearray object, and code that evaluates to a byte, list of bytes, or bytearray.

  (append-bytes [1 2 3] 4)            ⇒ [1 2 3 4]
  (append-bytes [1 2 3] 4 5 6)        ⇒ [1 2 3 4 5 6]
  (append-bytes [1 2 3] '(4 5 6))     ⇒ [1 2 3 4 5 6]
  (append-bytes [1 2 3] [4 5 6])      ⇒ [1 2 3 4 5 6]
  (append-bytes [1 2 3] (list 4 5 6)) ⇒ [1 2 3 4 5 6]

(append-bytes! bytearray byte…)

(append-bytes! bytearray list of bytes)

(append-bytes! bytearray bytearray)

As with append-bytes, but modifies and returns the bytearray that is passed in rather than making a copy.

  (define a [1 2 3])  ⇒ [1 2 3]
  (append-bytes a 4)  ⇒ [1 2 3 4]
  a                   ⇒ [1 2 3]
  (append-bytes! a 4) ⇒ [1 2 3 4]
  a                   ⇒ [1 2 3 4]

Utility

(random-byte)

Returns a psuedo-random unsigned integer between 0 and 255, inclusive..

  (random-byte) ⇒ 13
  (random-byte) ⇒ 207

(sleep millis)

Sleep for millis milliseconds.

  (sleep 1000)  ;; resumes execution 1 second later

(write-line sexpr)

Writes the string form of sexpr.

  (write-line "Hello, world.")  ;; Hello, world. is sent to the console.

(str sexpr…)

Creates a string from concatenating the string forms of all the _sexpr_s.

  (str 1 "." 2) ⇒ "1.2"

(copy sexpr)

Make a copy of the result of evaluating sexpr, IFF it’s mutable. This is limited to lists and association lists. All other values are immutable. Copying an immutable item will return the item, whereas copying a list or association list will make a deep copy of the structure, and return it.

Control Structures

(quote sexpr)

Suppresses evaluation of the expression.

   (quote (+ 1 2)) ⇒ (+ 1 2)

There is a shortcut for quote that uses the single quote:

  '(+ 1 2)         ⇒ (+ 1 2)

(cond (predicate sexpr…)…)

Each predicate is evaluated in order until one results in true value. The expressions associated with this predicate are then evaluated in order, and the result of the cond is the result of the last evaluation. If all predicates evaluate to false values, the value of cond in indeterminate. If, however, the final predicate is the symbol else the expressions associated with it are evaluated, and the value of cond is the value of the last evaluation.

  (cond ((> 3 2) 'greater)
        ((< 3 2) 'less)) ⇒ greater

  (cond ((> 3 3) 'greater)
        ((< 3 3) 'less)
        (else 'equal))   ⇒ equal

(case target ((value…) sexpr…)… [(else sexpr…)])

case chooses code to evaluate based on the value of evaluating target. Any number of expressions can be associated with each target value. None of the value extressions are evaluated.

(define (test-func x)
  (case x
    ((0) "zero")
    ((1) "one")
    ((2 3) "some")
    (else "unknown")))

(test-func 0) ⇒ "zero"
(test-func 1) ⇒ "one"
(test-func 2) ⇒ "some"
(test-func 3) ⇒ "some"
(test-func 5) ⇒ "unknown"

(if condition true-clause)

This is a special case of cond that is more familiar to programmers used to Algol style languages. if has two forms, one that conditionally evaluates a single sexpr (see below about begin which provides a way to use multiple sexprs in this context).

Iff the condition evaluates to logically true, true-clause is evaluated and the result of that is the result of the if form, otherwise nil is the result of the if form.

  (if (< 1 2)
      "less") ⇒ "less"
  (if (< 2 2)
      "less") ⇒ nil

(if condition true-clause false-clause)

If the condition evaluates to logically true, true-clause is evaluated and the result of that is the result of the if form, otherwise the false-clause is evaluated and is the result of the if form.

  (if (< 1 2)
      "less"
      "not less") ⇒ "less"
  (if (< 2 2)
      "less"
      "not less") ⇒ "not less"

(define symbol value)

Evaluates the value expression and binds it to the symbol, returning the value.

  (define x (+ 2 3)) ⇒ 5
  x ⇒ 5

(map function list)

Applies function (which has to be of a single argument) to each element in list in order, returning the list of the results.

  (map - '(1 2 3 4))) ⇒ (-1 -2 -3 -4)

  (map (lambda (x)
               (* x x))
       '(1 2 3 4))    ⇒ (1 4 9 16)

(begin sexpr…)

Evaluates the each sexpr in order, returning the result of the last one evaluated. Used in a context that allows a single sexpr but you need multiple. E.g.

  (if (predicate)
      (begin
        (do-something)
        (do-something-else)))

(let ((name value)…) sexpr…)

Create a local scope and bindings for evaluating a body of code. The first argument is a list of bindings. Each binding is a raw symbol (doesn’t get evaluated) that is the name to be bound, and a value (which is evaluated). These bindings are added to a scope that is local to the let. The body is evaluated in this local scope. The value of the let is the last evaluation result. E.g.

  (let ((x 1)
        (y 2))
       (+ x y)) ⇒ 3

Bindings cascade. I.e. each binding’s value is evaluated in the context of the local scope. E.g.

  (let ((x 1)
        (y (+ x 2)))
       (+ x y)) ⇒ 4

As mentioned above, let takes a sequence of sexprs as it’s body. In effect the body of the let is an implicit begin.

  (let ((x 1)
        (y (+ x 2)))
       (do-something)
       (+ x y)) ⇒ 4

(do ((name initial next)…) ((test sexpr…)) sexpr…)

This is a general purpose iteration construct. There are three sections:

bindings This is similar to let in that it defines names that can be used in the remainder of the scope of the do. Like let it is a list of lists, each starting with the binding name followed by the initial value of the binding. The difference is that this is followed by an expression that is evaluated at the beginning of each subsequent pass through the loop, and whose result is used as a new binding of the name.

termination This is a list whose first element is an expression which is evaluated before each pass through the loop (after rebinding the variables). If it evaluates to a truthy value, the remaining expressions are evaluated in turn. Looping ends and the result of the final evaluation is used as the value of the do form.

body This is a sequence of expressions that are evaluated in order each time though the loop. This section can be empty.

Here’s an example of using do to iteratively find the length of a list.

  (do ((l some-list (cdr l))
       (count 0 (+ 1 count)))
      ((nil? l) count))

(-> value sexpr|symbol…)

This creates a function chain. value (evaluated first) is used as the first argument to the first sexpr. The result of each sexpr is used as the first argument of the next, and the result of the final sexpr is the value of the -> form. If a sexpr would take a single argument (which would be provided by the value or the result of the previous sexpr, just the function name can be used.

The form (-> 0 a b c) is equivalent to (c (b (a 0))).

  (-> 1 (+ 3) (- 2))     ⇒ 2     ; (- (+ 1 3) 2)
  (-> 1 (+ 3) (- 2) str) ⇒ "2"   ; (str (- (+ 1 3) 2))

(=> value sexpr|symbol…)

This operates similarly to -> with two differences:

  1. value (evaluated once at the beginning) is used as the initial argument to each function, and they are independent and do not pass results one to another.

  2. value is the result of the form.

The form (=> 1 a b c) is equivalent to (begin (a 1) (b 1) (c 1) 1).

Assignment

(set! name new-value)

The way to assign (i.e. rebind) a symbol. name is the symbol to be rebound. new-value is evaluated to arrive at the new value to be bound to. Use of set! is frowned upon, and should not be used without thought. See Chapter 3 of [1] for a discussion of the pros and cons of assignment.

  (define x 5)
  (set! x 10)
  x ⇒ 10

Assigning takes action in the closest scope in which the symbol is bound. In the following example, that’s the global scope, so when the let exits, x still has the rebound value of 10.

  (define x 5)
  (let ()
    (set! x 10)
    x) ⇒ 10
  x    ⇒ 10

In this example, x is bound by the let, i.e. in the local context of the let, so the rebinding is done in the scope of the let and doesn’t extend beyond it.

  (define x 5)
  (let ((x 1))
    (set! x 10)
    x) ⇒ 10
  x    ⇒ 5

(set-car! cons-cell new-value)

Set the car pointer of cons-cell.

  (define a '(1 2))
  (set-car! a 3)
  a ⇒ (3 2)

(set-cdr! cons-cell new-value)

Set the cdr pointer of cons-cell.

  (define a '(1 2))
  (set-cdr! a 3)
  a ⇒ (1 . 3)

(set-nth! list n new-value)

Set the car pointer of the nth cons cell of list. Numbering starts at 1.

  (define a '(1 2 3 4))
  (set-nth! a 3 0)
  a ⇒ (1 2 0 4)

Functions

(lambda (param…) expr…)

Creates an anonymous function. This can then be used in a function call.

  ((lambda (x)
     (+ x x))
   5) ⇒ 10

lambda creates a local environment at the point of it’s evaluation. This environment is attached to the resulting function, and any binding or symbol lookup starts in this local environment. For example, consider the following code (from [1], §3.2.2):

  (define square (lambda (x)
     (* x x)))

   (define sum-of-squares (lambda (x y)
     (+ (square x) (square y))))

   (define f (lambda (a)
     (sum-of-squares (+ a 1) (* a 2))))

Each of the three functions have their own associated definition environment pointer that points back to the global environment.

When a function is executed, the lambda creates a new environment frame for the evaluation that (in this case) points back to the global environment. This scoping approach is what is described in §3 of [1].

![Fig. 1. Procedure objects in the global frame.](/images/function_declarations.png) ![Fig 2. Environments created by evaluating (f 5) using the procedures in Fig 1.] (images/function_invocations.png)

Functions can also be named (i.e. bound to a symbol) by a special form of define:

(define (symbol param…) body)

symbol specifies the name (how you reference the function)

param>… parameters of the function, these are bound to the respective arguments when the function is called.

body the sequence of expressions that are evaluated in order when the function is called. The final evaluation result becomes the value of evaluation of the function.

  (define (square x)
     (* x x))

   (define (sum-of-squares x y)
     (+ (square x) (square y)))

   (define (f a)
     (sum-of-squares (+ a 1) (* a 2)))

(apply function sexpr…)

Apply the function that results from evaluating function to the argument list resulting from evaluating each sexpr.

Each initial sexpr can evaluate to any type of object, but the final one (and there must be at least one sexpr) must evaluate to a list.

    (apply + 1 2 '(3 4)) ⇒ 10
    (apply + '(1 2 3 4)) ⇒ 10

(eval sexpr)

Evaluate sexpr.

    (eval '(+ 1 2 3 4)) ⇒ 10

Macros

Macros are for making syntactic extensions to Lisp.

(quasiquote sexpr)

This defines a template expression that can be filled in by unquote and unquote-splicing. The backquote character can be used as a shorthand for quasiquote: `sexpr.

(unquote sexpr)

This evaluates sexpr and inserts the result into the template in place of the unquote expression. A comma can be used as a shorthand: ,sexpr.

(unquote-splicing sexpr)

This evaluates sexpr and inserts the result, which is assumed to be a list, into the template in place of the unquote-splicing expression. The list itself is not inserted, rather the sequence of items in the list are. A comma followed by an at can be used as a shorthand: ,@sexpr.

    `(a ,(+ 1 2) ,@(map abs ’(4 -5 6)) b) ⇒ (a 3 4 5 6 b)

Quasiquote forms may be nested. Substitutions are made only for unquoted components appearing at the same nesting level as the outermost backquote. The nesting level increases by one inside each successive quasiquotation, and decreases by one inside each unquotation.

    ‘(a ‘(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f) ⇒ (a ‘(b ,(+ 1 2) ,(foo 4 d) e) f)

    (let ((name1 ’x)
          (name2 ’y))
         ‘(a ‘(b ,,name1 ,’,name2 d) e)) ⇒ (a ‘(b ,x ,’y d) e)

(defmacro (symbol param…) sexpr)

Create a named macro:

symbol specifies the name (how you reference the macro)

param… parameters of the macro, these are bound to the respective arguments when the macro is invoked. NOTE that the arguments to a macro invocation are not evaluated, but are passed as is to the macro to do with as it wishes.

sexpr the template expression that is processed when the macro is invoked. The result of evaluating the processed template expression becomes the value of the macro’s invocation.

    (defmacro (double x)
       `(+ ,x ,x))
    (double 5) ⇒ 10

(expand symbol sexpr…)

Expands the macro named by symbol, passing the evaluated sequence of sexpr as arguments. NOTE: whereas invoking the macro (in the same way you invoke a function) expands and evaluates, expand (as you would expect) only expands the macro, resulting in the expanded template sexpr. This can then be evaluated as desired.

Testing

Golisp has a simple builtin testing framework.

The primary function is describe which takes a name (as a raw symbol) as it’s first argument, followed by a sequence of expressions. The expressions are of the form:

  (== actual expected)

Generally you should create a test file for each feature you are testing. The file is a plain lisp file and can contain any lisp code, including global variable and function definitions.

For example, here is the test file for scoping:

(define a 5)

(define (foo a)
  (lambda (x) (+ a x)))

(describe global-env
          (== a 5))

(describe lambda-env
          (== ((foo 1) 5) 6)
          (== ((foo 2) 5) 7)
          (== ((foo 10) 7) 17))

Running a test results in a stream of status output for each test, followed at the very end by a summary. Running the above results in the following:

Loading tests/scope_test.lsp
global-env
   (== a 5) - ok
lambda-env
   (== ((foo 1) 5) 6) - ok
   (== ((foo 2) 5) 7) - ok
   (== ((foo 10) 7) 17) - ok

Done.

4 Tests
4 Passes, 0 failures, 0 errors

If we introduce a failure, the output would be:

Loading tests/scope_test.lsp
global-env
   (== a 5) - ok
lambda-env
   (== ((foo 1) 5) 6) - ok
   (== ((foo 2) 5) 8) - failed: ((foo 2) 5) is 7
   (== ((foo 10) 7) 17) - ok

Done.
Failures:
  failed: ((foo 2) 5) is 7

4 Tests
3 Passes, 1 failures, 0 errors

You run tests by running the golisp repl in test mode, providing the list of test files to run:

golisp -t tests/scope_test.lsp

Defining primitives

The Go function MakePrimitiveFunction allows you to create primitive functions. This takes three arguments:

  1. The function name. This is the name of a symbol which will be used to reference the function.

  2. The number of expected arguments. Using a -1 for this denotes any number of arguments. In the function definition you can enforce further constraints on argument counts and types.

  3. The Go function which implements the primitive. This function must have the signature func <Name>(*Data, *SymbolTableFrame) (*Data, error)

The implementing function takes two parameters as seen above:

  1. A Lisp list containing the arguments

  2. The environment in which the primitive is being evaluated. This is used when calling Eval or Apply.

An example:

  MakePrimitiveFunction("!", 1, BooleanNot)

  func BooleanNot(args *Data, env *SymbolTableFrame)
                 (result *Data, err error) {
      arg, err := Eval(Car(args), env)
      if err != nil {
          return
      }

      val := BooleanValue(arg)
      return BooleanWithValue(!val), nil
   }

You can extend the goLisp runtime without changing any of it’s code. You simply import the golisp package (typically aliased to . to make the code less noisy) and place calls to MakePrimitiveFunction in your package’s init block.

Data

The core lisp data element is the data type which logically contains a type tag and a value. The type tags are defined by the constants: ConsCellType, NumberType, BooleanType, StringType, SymbolType, FunctionType, PrimitiveType, ObjectType.

The types are described earlier. If you need to check the type of a piece of data you can fetch it’s type using the TypeOf(*Data) int function and then compare it to a type tag constant. Additionally there are predicate functions for the most common types:

StringP(*Data) bool returns whether the data is a string

SymbolP(*Data) bool returns whether the data is a symbol

NumberP(*Data) bool returns whether the data is a number

IntegerP(*Data) bool returns whether the data is an integer

FloatP(*Data) bool returns whether the data is a float

PairP(*Data) bool returns whether the data is a cons cell (aka pair)

ListP(*Data) bool is an alias for PairP

ObjectP(*Data) bool returns whether the data is an encapsulated Go object

FunctionP(*Data) bool returns whether the data is either a user defined or primitive function

Two other very handy functions are:

NilP(*Data) bool returns whether the data is nil

NotNilP(*Data) bool returns whether the data is non-nil

Creating and accessing data

There are various convenience functions that you can use to create data:

Cons(car *Data, cdr *Data) *Data creates a cons cell with the provided values for it’s car and cdr.

IntegerWithValue(n int64) *Data creates a number (integer) with the provided value

FloatWithValue(n float32) *Data creates a number (float) with the provided value

BooleanWithValue(b bool) *Data creates a boolean with the provided value

StringWithValue(s string) *Data creates a string with the provided value

SymbolWithName(s string) *Data creates a symbol with the provided name

ObjectWithTypeAndValue(typeName string, o unsafe.Pointer) *Data creates an encapsulated Go object with the given type tag and value

Similarly there are functions for extracting information from data elements:

IntegerValue(*Data) int64 extracts the primitive integer value

FloatValue(*Data) float32 extracts the primitive float value

StringValue(*Data) string extracts the primitive string value

BooleanValue(*Data) bool extracts the primitive boolean value

TypeOfObject(*Data) string extracts the type tag of the encapsulated Go object

ObjectValue(*Data) unsafe.Pointer extracts the primitive object value

String(*Data) string returns a human readable string representation of the data

Length(*Data) int returns the length of a piece of Lisp data. This will be 0 in all cases except lists, when it will be the number of elements in the list.

IsEqual(*Data, o *Data) bool compares two expressions:

  • if the two pointers point to the same thing, they are equal

  • nil is never equal to anything

  • data of different types are never equal

  • if the data are lists, they are equal if corresponding pairs of elements are equal

  • if values are the same (e.g. strings & numbers) they are equal

Any of the above ...Value functions return the corresponding zero value if the data is not of the appropriate type.

Working with lists

Car(*Data) *Data return the car pointer from a cons cell (nil otherwise)

Cdr(*Data) *Data) return the cdr pointer from a cons cell (nil otherwise)

Caar through Cddddr returns a piece of the list as expected

First(*Data) *Data Return the first item in a list (equivalent to car) … Fifth(*Data) *Data return the fifth item in a list

Nth(*Data, int) *Data return the nth item in a list (starting at 1)

ArrayToList(sexprs []*Data) *Data converts an array of Lisp data objects to a Lisp list.

Evaluation

These functions are the entry points into code evaluation. The main reason to use these, and especially Eval, is to evaluate arguments to primitives as required.

Eval(d *Data, env *SymbolTableFrame) (*Data, error) evaluates the expression in d in the provided environment and returns the result along with any error that occurred during evaluation. If an error is returned, the value of the data result is indeterminate.

Apply(function *Data, args *Data, env *SymbolTableFrame) (*Data, error) takes a function element, either user defined or primitive, and applies it to the provided arguments in the provided environment. This is actually how Eval deals with a list: the evaluation of the car of the list is the function, the cdr is the list of arguments. It’s up to the primitive implementation to evaluate arguments as required.

The implementation of if serves as an example:

  func If(args *Data, env *SymbolTableFrame) (result *Data, err error) {
    if Length(args) < 2 || Length(args) > 3 {
        msg := fmt.Sprintf("IF requires 2 or 3 arguments. Received %d.",
                           Length(args))
        err = errors.New(msg)
        return
    }

    c, err := Eval(Car(args), env)
    if err != nil {
        return
    }
    condition := BooleanValue(c)
    thenClause := Cadr(args)
    elseClause := Caddr(args)

    if condition {
        return Eval(thenClause, env)
    } else {
        return Eval(elseClause, env)
    }
}

Note that Cadr and Caddr return nil if the corresponding element is missing from the argument list. Also, passing a nil to Eval returns nil.

Embedded GoLisp

Now that you know what’s supported in GoLisp, and how to manipulate it’s structures and code in from Go, you need to get code into it. There are two ways to do this.

Single expression strings

If you have a Go string that contains a single sexpr, you can pass it to Parse which returns the list containing the parsed string and an error value. The returned structure can be passed to Eval to evaluate it. The resulting sexpr is returned.

For example, here is the REPL:

prompt := "> "
LoadHistoryFromFile(".golisp_history")
lastInput := ""
for true {
    input := *ReadLine(&prompt)
    if input != "" {
        if input != lastInput {
            AddHistory(input)
        }
        lastInput = input
        code, err := Parse(input)
        if err != nil {
            fmt.Printf("Error: %s\n", err)
        } else {
            d, err := Eval(code, Global)
            if err != nil {
                fmt.Printf("Error in evaluation: %s\n", err)
            } else {
                fmt.Printf("⇒ %s\n", String(d))
            }
        }
    }
}

Loading a file

If you have GoLisp code in a plain text file you can pass the filename to ProcessFile. This will parse and evaluate each sexpr contained in the file. The result of ProcessFile is the value of the evaluation of the final sexpr in the file and an error value.

Generally, you load a file for it’s side effects: defining values and functions.

The REPL

You typically import the golisp package into your Go app and use it. However, you can run the golisp/main/golisp package, which puts you into the REPL where you can enter and evaluate one line at a time. You can use the load function to load/evaluate files of golisp code you have written. If you provide a list of filenames on the command line they will be loaded and evaluated before dropping you into the REPL.

Note that you can easily make the REPL available to the user from within your application.

References

[1] Harold Abelson, Gerald Jay Sussman, and Julie Sussman. Structure and Interpretation of Computer Programs. MIT Press, Cambridge, Mass., 1985.

[2] Richard P. Gabriel and Kent M. Pitman. Endpaper: Technical issues of separation in function cells and value cells. Lisp and Symbolic Computation, 1(1):81–101, June 1988.

[3] Guy L. Steele. COMMON LISP: the language. Digital Press, 12 Crosby Drive, Bedford, MA 01730, USA, 1984. With contributions by Scott E. Fahlman and Richard P. Gabriel and David A. Moon and Daniel L. Weinreb.