Skip to content
Howard Mao edited this page Jul 24, 2014 · 5 revisions

Running GLISP in Go

An instance of the GLISP interpreter is represented by a Glisp struct. You can create one using the NewGlisp function.

env := glisp.NewGlisp()

To add code to the environment, you will need to call any of the three load methods on the environment, which will compile GLISP code into VM instructions and add the instructions to the environment. The methods are named LoadString, LoadFile, and LoadStream. They will take as arguments a Go string, a file pointer, or an io.RuneReader, respectively. If there is a syntax error, an error will be returned by the load function. You will probably want to print or otherwise display this error message, since it will tell you which line the error occurred.

err := env.LoadString("(+ 3 2)")

Once you have loaded in the code you want, you can run the code by calling the Run method on the environment. This will run the code and return the top of the stack at the end or an error.

expr, err := env.Run()

Recovering from Errors

If an error occurs, you must do some cleanup before more code can be loaded and run. If you want to get a stack trace of what went wrong, call the GetStackTrace method, and you will receive the stack trace as a string.

Unfortunately, line-level runtime error information is not available. However, since most of the language uses functions, it should not be difficult to trace down your bug by inspecting the functions in the stack trace.

To reset the environment to a sane state, just call the Clear method. Once you have called Clear, you can proceed to load and run more code.

GLISP Types

The different types of LISP values are represented by the Sexp interface. Most of the subtypes correspond to regular Go values.

  • SexpInt - int
  • SexpFloat - float
  • SexpChar - rune
  • SexpBool - bool
  • SexpStr - string
  • SexpArray - slice

The four which are special are SexpSymbol, SexpPair, SexpHash, and SexpFunction.

A GLISP symbol contains a name and a number. Two symbols created with the same name in the same environment are guaranteed to have the same number. In Go, symbols must be created using the MakeSymbol method of the Glisp struct.

sym1 := env.MakeSymbol("foo")
sym2 := env.MakeSymbol("foo")
sym1.Name() == sym2.Name() // should be true
sym1.Number() == sym2.Number() // should be true

Pairs are represented in Go by a struct containing a head and tail expression. You can create a pair using the Cons function and access their head and tail using the Head and Tail methods.

pair := glisp.Cons(SexpInt(1), SexpInt(2))
pair.Head() == SexpInt(1)
pair.Tail() == SexpInt(2)

If you want a list, you can create one from a slice using the MakeList function.

list := glisp.MakeList([]Sexp{SexpInt(1), SexpInt(2), SexpInt(3)})
list.Head() // should be 1
list.Tail() // should be (2 3)

You can convert a list back into a slice using the ListToArray function. This will return an error if the expression passed in is not really a list.

arr, err := glisp.ListToArray(list)

The SexpHash type is a Go map, but it would be to difficult to actually access the keys using the normal Go syntax. The suggested way is to use the functions HashGet, HashGetDefault, HashSet, and HashDelete. The HashGet and HashGetDefault functions retrieve a value from the hash given a key. If the key is not found, HashGet returns an error, whereas HashGetDefault returns the default value given as the third argument. The HashSet and HashDelete functions correspond to the hset! and hdel! LISP functions.

var hash SexpHash
err := HashSet(hash, SexpStr("foo"), SexpInt(3))
expr, err := HashGet(hash, SexpStr("foo"))
expr, err := HashGetDefault(hash, SexpStr("bar"), SexpInt(0))
err := HashDel(hash, SexpStr("foo"))

The SexpFunction type is an opaque struct. The one thing you can do with it in Go is apply it to some other expressions.

expr, err := glisp.Apply(fun, []Sexp{SexpInt(1)})
// equivalent to (apply fun [1]) in lisp

The Apply function returns the result of the function or an error if something went wrong.

One last type is the SexpSentinel type. You should only ever use the SexpNull constant for this type. It represents the null value.

Adding Go Functions

You can write functions in Go to be called by LISP code. Your function must have a signature like the following.

func Function(env *glisp.Glisp, name string, args []glisp.Sexp) (glisp.Sexp, error) {
    // ...
}

The first argument is the interpreter environment, the second argument is the name by which the function was called, and the third argument is the arguments passed into the function by the LISP code.

The function should return the result LISP expression and nil if everything went alright. If something goes wrong, the function should return SexpNull and an error object.

Once you've defined your function, you can add it into an environment using the AddFunction method.

env.AddFunction(Function)

Adding Global Variables

Sexp objects can be bound to names in the global scope of the environment by calling the AddGlobal method.

// bind "bar" to variable foo in global scope
env.AddGlobal("foo", SexpStr("bar"))

Defining your own types

You can define your own type to be used in GLISP by creating a type implementing the glisp.Sexp interface. The only method in this interface is SexpString, which should return a string representing the given type. You must provide your own functions for dealing with these types, as none of the builtin functions will be able to deal with user-created types.