Gmnlisp is an ISLisp interpreter written in Go, designed for embedding into Go applications to enable scripting and customization in ISLisp.
-
High ISLisp Standard Compliance
- Verified with the ISLisp Verification System:
PASS: 16173 / FAIL: 238 → Pass rate: 98.54% - Remaining failures are mainly due to:
- Class system and generic functions: core features implemented, a few features still missing
- Nearly all standard functions have correct parameter validation; some minor cases remain
- Strings are currently immutable (spec requires mutability)
- Verified with the ISLisp Verification System:
-
Written in Go — Interpreter
- Pure interpreter implementation (no compiler)
- Embeddable directly into Go programs
- Allows mutual function calls between Go and ISLisp
-
Embedding-Oriented API
- Simple registration of variables, functions, and special forms from Go
- Direct access to Lisp objects as Go interfaces (
gmnlisp.Node
) - Works on Windows and Linux
package main
import (
"context"
"fmt"
"os"
"github.com/hymkor/gmnlisp"
)
func sum(ctx context.Context, w *gmnlisp.World, args []gmnlisp.Node) (gmnlisp.Node, error) {
a, err := gmnlisp.ExpectClass[gmnlisp.Integer](ctx, w, args[0])
if err != nil {
return nil, err
}
b, err := gmnlisp.ExpectClass[gmnlisp.Integer](ctx, w, args[1])
if err != nil {
return nil, err
}
return a + b, nil
}
func main() {
lisp := gmnlisp.New()
lisp = lisp.Let(gmnlisp.Variables{
gmnlisp.NewSymbol("a"): gmnlisp.Integer(1),
gmnlisp.NewSymbol("b"): gmnlisp.Integer(2),
})
lisp = lisp.Flet(
gmnlisp.Functions{
gmnlisp.NewSymbol("sum"): &gmnlisp.Function{C: 2, F: sum},
})
value, err := lisp.Interpret(context.TODO(), "(sum a b)")
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
fmt.Println(value.String())
}
$ go run examples/example.go
3
gmnlisp.New
returns a new Lisp interpreter instance (*gmnlisp.World
).gmnlisp.NewSymbol
constructs a symbol. Callinggmnlisp.NewSymbol("a")
always returns the same value, no matter how many times it's called.gmnlisp.Variables
is a symbol map type. It is an alias formap[gmnlisp.Symbol]gmnlisp.Node
.
Node
is the interface that all Lisp objects must implement..Let
creates a new world instance with the given variable bindings (namespace).
lisp.Let(gmnlisp.Variables{
gmnlisp.NewSymbol("a"): gmnlisp.Integer(1),
gmnlisp.NewSymbol("b"): gmnlisp.Integer(2),
}).Interpret(context.Background(), "(c)")
is equivalent to the Lisp code: (let ((a 1) (b 2)) (c))
a, err := gmnlisp.ExpectClass[gmnlisp.Integer](ctx, w, x)
is similar to:
a, ok := x.(gmnlisp.Integer)
However, ExpectClass
invokes the user-defined error handler if x
is not of type Integer
.
You can register user-defined functions to the interpreter using .Flet()
:
lisp = lisp.Flet(
gmnlisp.Functions{
gmnlisp.NewSymbol("sum"): &gmnlisp.Function{C: 2, F: sum},
})
The function definitions are passed as a gmnlisp.Functions
map, where the keys are symbols and the values are Lisp function objects. There are several ways to define the function values:
-
gmnlisp.Function1(f)
For a functionf
with the signature:func(context.Context, *gmnlisp.World, gmnlisp.Node) (gmnlisp.Node, error)
Accepts one evaluated argument. -
gmnlisp.Function2(f)
For a functionf
with the signature:func(context.Context, *gmnlisp.World, gmnlisp.Node, gmnlisp.Node) (gmnlisp.Node, error)
Accepts two evaluated arguments. -
&gmnlisp.Function{ C: n, Min: min, Max: max, F: f }
For a functionf
with the signature:func(context.Context, *gmnlisp.World, []gmnlisp.Node) (gmnlisp.Node, error)
Accepts multiple evaluated arguments.- If
C
is non-zero, the function strictly expectsC
arguments. - If
Min
andMax
are specified instead, the function accepts a range of arguments. - If all are left as zero values, argument count is not validated.
Note: A zero value (0) means "unspecified"; negative values are not used.
- If
-
gmnlisp.SpecialF(f)
For defining special forms (macros, control structures, etc.), where arguments are passed unevaluated:func(context.Context, *gmnlisp.World, gmnlisp.Node) (gmnlisp.Node, error)
All arguments are passed as a Lisp list (e.g.,(list a b c)
becomes&gmnlisp.Cons{Car: ..., Cdr: ...}
).Inside this function, you can evaluate an argument manually with:
result, err := w.Eval(ctx, x)
See the example in the "Usage and Integration Guide" section above for how to define and register a user function.
Lisp values correspond to the following Go types or constructors when embedding gmnlisp in Go applications:
Lisp | Go |
---|---|
t |
gmnlisp.True |
nil |
gmnlisp.Null |
1 |
gmnlisp.Integer(1) |
2.3 |
gmnlisp.Float(2.3) |
"string" |
gmnlisp.String("string") |
Symbol |
gmnlisp.NewSymbol("Symbol") |
(cons 1 2) |
&gmnlisp.Cons{ Car:gmnlisp.Integer(1), Cdr:gmnlisp.Integer(2) } |
#\A |
gmnlisp.Rune('A') |
Unlike other types shown above, gmnlisp.NewSymbol(...)
is a function call, not a type conversion.
It returns a value of type Symbol
(defined as type Symbol int
), which is distinct from int
.
The function guarantees that the same string always maps to the same symbol value.
gmnlisp.Node
is the root interface.
All values that appear in Lisp code must implement this interface.
type Node interface {
Equals(Node, EqlMode) bool
String() string
ClassOf() Class
}
type Class interface {
Node
Name() Symbol
InstanceP(Node) bool
Create() Node
InheritP(Class) bool
}
type EqlMode int
const (
STRICT EqlMode = iota // corresponds to (eql a b)
EQUAL // corresponds to (equal a b)
EQUALP // corresponds to (equalp a b)
)
The functions (load)
and (eval)
are provided by the eval
subpackage.
To make them available, simply import the package:
import _ "github.com/hymkor/gmnlisp/eval"
-
(load "filename")
Reads S-expressions from the specified file and evaluates them sequentially. -
(eval expr)
Evaluates the given S-expression.
Importing the
eval
package is sufficient; the functions are automatically registered when a gmnlisp interpreter instance is created.
The functions (quit)
and (abort)
are provided by the exit
subpackage. To use them,
import:
import "github.com/hymkor/gmnlisp/exit"
-
(quit [N])
Terminates the gmnlisp executable with exit codeN
.N
must be a non-negative integer (0
–255
).- If omitted, the exit code defaults to
0
. - Passing a non-integer or a negative integer signals a
<domain-error>
. - Passing an integer greater than
255
signals a<program-error>
.
-
(abort)
When running a script from the gmnlisp executable, terminates with exit code1
. In the interactive REPL,(abort)
does not terminate the REPL itself; it only stops the current evaluation. Internally,(abort)
raises anexit.AbortError
instance (ErrAbort
).
Both
(quit)
and(abort)
are only available when theexit
package is imported. In the gmnlisp executable, they are imported by default.
The following open-source applications embed gmnlisp to provide ISLisp-based customization and scripting:
-
lispect:
A text-terminal automation tool similar toexpect(1)
, powered by a subset of ISLisp. -
smake:
A build automation tool where Makefiles are written in S-expressions.
- ISLISP - Wikipedia
- ISLisp Home Page
- www.islisp.info: Home
- Programming Language ISLISP Working Draft 23.0
Gmnlisp and other implementations of ISLisp
Implementation | Language | Windows | Linux | Execution Model |
---|---|---|---|---|
OK!ISLisp | C | Supported | Supported | Interpreter/Bytecode compiler |
iris | Go/JavaScript | Supported | Supported | Interpreter |
Easy-ISLisp | C | Supported | Interpreter/Native Compiler | |
gmnlisp | Go | Supported | Supported | Interpreter |