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.Newreturns a new Lisp interpreter instance (*gmnlisp.World).gmnlisp.NewSymbolconstructs a symbol. Callinggmnlisp.NewSymbol("a")always returns the same value, no matter how many times it's called.gmnlisp.Variablesis a symbol map type. It is an alias formap[gmnlisp.Symbol]gmnlisp.Node.
Nodeis the interface that all Lisp objects must implement..Letcreates 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 functionfwith the signature:func(context.Context, *gmnlisp.World, gmnlisp.Node) (gmnlisp.Node, error)Accepts one evaluated argument. -
gmnlisp.Function2(f)For a functionfwith 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 functionfwith the signature:func(context.Context, *gmnlisp.World, []gmnlisp.Node) (gmnlisp.Node, error)Accepts multiple evaluated arguments.- If
Cis non-zero, the function strictly expectsCarguments. - If
MinandMaxare 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
evalpackage 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.Nmust 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
255signals 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.AbortErrorinstance (ErrAbort).
Both
(quit)and(abort)are only available when theexitpackage 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 |
