Skip to content
Jason E. Aten edited this page Feb 23, 2016 · 57 revisions

zygomys is a dialect of LISP. It is designed as an embedded extension language for Go. It is implemented in pure Go as a bytecode interpreter. As a result, the interpreter can be compiled or cross-compiled for any platform Go runs on.

The zygo library REPL can easily be used either standalone or embedded as a library in your Go application. This facilitates creating an interactive command-line for your application.

Sandboxing the repl

When you don't want script to read/write the filesystem or issue system calls, you can sandbox it.

Start the repl with zygo -sandbox. (See also the -quiet flag).

When embedding use the NewGlispSandbox() method in place of NewGlisp().

Differences from traditional LISP syntax

  • Using = for assignment and multiple assignment

The = sign represents assignment. To support Go-like multiple assignment, the = assignment operator supports multiple values on either side of the = sign. After the following assignment,

(a b c = 1 "two" 3)

a will be 1, b will be "two", and c will be 3.

Note that commas act like whitespace; use them for style or ease of reading. The following is equivalent to the above:

(a, b, c = 1, "two", 3)
  • dot-symbols

Dot-symbols are one of the most innovative, modern features of zygomys. Dot-symbols avoid the need for macros in many cases, provide object-oriented style compact notation, and allow assignment with = to be expressed naturally.

Dot-symbols are simply symbols that start with dot . For example, these are dot-symbols:

.hello
.hugs!
.welcome.home

In contrast, regular symbols are not allowed to start with a dot.

Regular symbols should avoid using any dots, so they can be referenced by their corresponding dot-symbols. (Dots are not illegal after the first position because file paths are occassionally useful as regular symbols.)

What is special about dot-symbols is that they typically evaluate just to themselves. For example, at the repl, if we ask for the value of a dot symbol, we get back itself:

zygo> .a
.a
zygo> (defn f [d] d)
zygo> (f .a)
.a
zygo> 

Notice how the dot-symbol .a passed through the function call completely unchanged.

However things change when dot-symbols are called like a function.

zygo> (.a)  ;; refers now to the value of `a`, but it's not yet been defined
error in __main:36: symbol `a` not found
zygo> (.a = 10) ;; bind `a` to 10. Equivalent to (def a 10).
10
zygo> a
10
zygo> (.a)  ;; no 2nd argument, so just gets the value of `a`
10
zygo> 

As the example above shows, when dot-symbols are used as the function name (first-position) in a function call, then they evaluate to their underlying reference.

  • Using dot-symbols for nested member selection

Dot-symbols that have multiple dots act like path descriptors for referencing nested objects.

Like Go/C++/Java, dot-symbols let one easily pick out members of nested objects:

zygo> ;; make an nested object
zygo> (def sn (snoopy asst: (hornet forecast: (weather desc:"sunny"))))
 (snoopy asst: (hornet forecast: (weather desc:"sunny")))
zygo> (.sn.asst)
 (hornet forecast: (weather desc:"sunny"))
zygo> (.sn.asst.forecast)
 (weather desc:"sunny")
zygo> (.sn.asst.forecast.desc)
`sunny`
zygo> 

  • Using dot-symbols for method calls

A object-oriented-style call like (.baby cry) will call the method cry with the de-referenced value of baby as its first argument. This works for (.nursery.baby cry arg1 arg2) as well. Here we assume that cry is a function defined in zygomys. If it refers to a bulitin Go extension function, then the method will get the dot-symbol instead of the dereference. This allows builtin functions to easily act like macros when need be.

Notice that (.baby) alone, without any arguments, is just the value of baby.

zygo> (def baby 12)
12
zygo> (.baby)
12
zygo> ;; but note it must refer to a function if given arguments
zygo> (.baby "hi")
error in __main:1: dot-symbol '.baby' was followed by non-function '"hi"'.
zygo> 

Let's illustrate object-oriented style method invocation. Notice that currently methods can be associated with any type of object. This may evolve in the future. (We are considering adding an optional type discipline.)

zygo> (defn myMethod [self arg1] (concat "self:" (str self) " arg1:" (str arg1)))
zygo> 
zygo> (def obj "I-am-an-object")
`I-am-an-object`
zygo> 
zygo> (obj myMethod "first-param")  ;; no dot-symbol, no call of myMethod
error in __main:14: obj is not a function
zygo> 
zygo> (.obj myMethod "first-param")
`self:"I-am-an-object" arg1:"first-param"`
zygo> 
  • Minor syntax improvement: ^ replaces `

In macros, templates (syntax-quotes) are introduced by the caret ^ instead of the backtick. This allows the convenient use of the backtick to delimit verbatim, newline and double quote containing strings, just as in Go.

So instead of this:

(defmac require [sympath]
    `(source (sym2str (quote ~sympath)))) ; not supported

Do this instead:

(defmac require [sympath]
    ^(source (sym2str (quote ~sympath)))) ; way better!
  • Raw string literals using a pair of backticks

Just as in Go, you may submit strings like this:

> (def a `hello
 readable
 "quotes"`)
> a
"hello\n readable\n\ "quotes\""
>
  • defmap to create records.

The defmap macro defines a new record type that is a hash table with a new name. Field definition order is always preserved upon display.

> (defmap ranch)
> (def lazy8 (ranch cowboy:"Jim" cowgirl:"Jane"))
> lazy8
(ranch cowboy:"Jim" cowgirl:"Jane")
>
  • clojure like field access with the ':' colon operator

Instead of writing (hget acre100 :friend), instead there is the briefer (:friend acre100)

> (defmap wood)
> (def acre100 (wood tractor:"Bessie" combine:"Steve" friend:"Eeyore"))
> (assert (== (:friend acre100) "Eeyore"))
>
> ;; or using the new dot-symbol syntax:
> (.acre100.friend)
`Eeyore`
> 
  • clojure like threading with (-> hash field: field2: ...) for picking out nested fields. Note a difference from clojure however: the colon is always on the trailing (right) side of the symbol. Note that a space after the -> operator is required. The newer dot-symbol syntax now offers the same functionality with even more compact notation. Both are retained as one may be easier to generate programmatically.
> (defmap ranch)
> (def hogwild (ranch cowboy:"Harry" cowgirl:"Hermonie"))
> (defmap bunkhouse)
> (hset! hogwild bunk1:(bunkhouse bed1:"Luciuos" bed2: "Dumbledore"))
> (defmap closet)
> (hset! (:bunk1 hogwild) closet1:(closet broom:"Nimbuz2"))
> (assert (== (-> hogwild bunk1: closet1: broom:) "Nimbuz2"))
> (assert (== (-> hogwild bunk1:closet1:broom:) "Nimbuz2"))
> hogwild
 (ranch cowboy:"Harry" cowgirl:"Hermonie" bunk1: (bunkhouse bed1:"Luciuos" bed2:"Dumbledore" closet1: (closet broom:"Nimbuz2")))
> 
  • == instead of = for equality checking.

Notice this in the repl snippet above. Its just less confusing to eyes accustomed to more common Go, C, etc. And we may wish to use '=' later for useful purposes.

  • Symbols can be invoked directly

Like scheme, in a function call, we evaluate the (1st) position just like all the other positions. The function position isn't special. This means if a symbol refers to a function's name, you can use that symbol in the first (function name) position, without fuss. No (funcall sym) needed as in lisp. There is a single namespace for all symbols.

> (def myprintsymbol 'printf)
> (myprintsymbol "in hex: %x\n" 32) ;; invokes printf
in hex: 20
>

See also this reference.

Clone this wiki locally