-
Notifications
You must be signed in to change notification settings - Fork 81
Go API
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()
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.
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.
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 zygo/time.go file for an example.
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"))
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 zygo/gotypereg.go. TheMakeGoStructFunc
function should follow the examplesevent-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 replaceEvent
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/zygo/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 thezygo
prompt, issue the(defmap snoopy)
command to create a new record type (replacesnoopy
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 theSexpHash.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 theGoShadowStruct
, 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 zygo/functions.go. - profit