Jason E. Aten edited this page Jan 4, 2018 · 25 revisions

Running zygomys in Go

An instance of the zygomys interpreter is represented by a Zlisp struct. You can create one using the NewZlisp function.

env := zygo.NewZlisp()

To add code to the environment, you will need to call any of the three load methods on the environment, which will compile zygomys 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.

zygomys Types

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

  • SexpInt - int64
  • SexpFloat - float64
  • SexpChar - rune
  • SexpBool - bool
  • SexpStr - string
  • SexpArray - slice
  • SexpRaw - []byte

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

A zygomys 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 Zlisp 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 fields.

pair := zygo.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 := zygo.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 := zygo.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(&SexpStr{S: "hello"}, &SexpStr{S: "world"})
expr, err := HashGet(hash, &SexpStr{S:"hello"})
expr, err := HashGetDefault(hash, &SexpStr{S:"bar"}, &SexpInt{})
err := HashDel(hash, &SexpStr{S:"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 := zygo.Apply(fun, []*Sexp{&SexpInt{Val: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 *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.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)

See the repl/time.go file for an example.

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 zygomys by creating a type implementing the zygo.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.

Registering Go structs, creating corresponding records, and automatically configuring a tree of Go structures

The (togo) function, internally implemented in the SexpToGoStructs() function, uses reflection to automagically instantiate a tree of Go structs that matches your s-expressions. Steps to use this:

  • Register an RegistryEntry struct in the init() function of repl/gotypereg.go. The MakeGoStructFunc function should follow the examples event-demo, person-demo, snoopy, etc. Your function will look like this, and need ony return a pointer to a struct of your type. i.e. Copy this example and replace Event with your public struct type, and replace "event-demo" with your type's lisp name (typically the same as its json tag name.)
GostructRegistry["event-demo"] = RegistryEntry{
     Factory: func(env *Zlisp) interface{} {
	return &Event{}
}}

The Factory function will return a pointer to a new instance of your Go struct. Pick a lowercase string to be the glisp shortcut, and use the json:"field" tags on the struct to declare what the s-expression record keys will be called. For example, lets call our struct Snoopy. See the zygomys/repl/demo_go_structs.go code for a few pre-defined structures that are used in the test code and serve as howto examples.

  • Next recompile zygo (type make from the zygomys directory) and in your scripts/at the zygo prompt, issue the (defmap snoopy) command to create a new record type (replace snoopy with your structs lisp name). Multiple types can be inter-related, and be referred to by interfaces, and zygomys will still create the Go shadow tree for you.

  • Given a record associated with your struct (def s (snoopy)), simply calling (togo s) will reify the Go structs (filling them with data from the hash record. The root of the Go tree is stored in the SexpHash.GoShadowStruct field. This should mostly be done automatically for you if; e.g. if you make .method calls or save a struct to gob using the (gob) function. Nonetheless, it's good to know that this is required if you write your own Go add-on function that accesses the GoShadowStruct.

  • Create a simple new extension command, and send your record s to it. Pick out the GoShadowStruct, and call your native Go functions on it.

  • The usual user-defined-extension function signature applies, for all user defined lisp functions:

func YourNewFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {}
  • Lastly, add your user-defined-function's name and implementation to BuiltinFunctions in repl/functions.go.
  • profit
Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.