Skip to content

hymkor/gmnlisp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gmnlisp

Go Reference Go Test

Gmnlisp is an ISLisp interpreter written in Go, designed for embedding into Go applications to enable scripting and customization in ISLisp.

Example image

Features

  • 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)
  • 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

Usage and Integration Guide

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. Calling gmnlisp.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 for map[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))

Type assertions

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.

User-defined functions

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 function f with the signature: func(context.Context, *gmnlisp.World, gmnlisp.Node) (gmnlisp.Node, error) Accepts one evaluated argument.

  • gmnlisp.Function2(f) For a function f 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 function f 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 expects C arguments.
    • If Min and Max 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.

  • 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.

Supported Types

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) 
)

(load) and (eval)

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.

(quit) and (abort)

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 code N.

    • N must be a non-negative integer (0255).
    • 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 code 1. In the interactive REPL, (abort) does not terminate the REPL itself; it only stops the current evaluation. Internally, (abort) raises an exit.AbortError instance (ErrAbort).

Both (quit) and (abort) are only available when the exit package is imported. In the gmnlisp executable, they are imported by default.

Projects Using gmnlisp

The following open-source applications embed gmnlisp to provide ISLisp-based customization and scripting:

  • lispect:
    A text-terminal automation tool similar to expect(1), powered by a subset of ISLisp.

  • smake:
    A build automation tool where Makefiles are written in S-expressions.

References

Documents (English)

Documents (Japanese)

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

Author

hymkor (HAYAMA Kaoru)

License

MIT Licence

About

The interpreter of ISLisp written in Go

Topics

Resources

License

Stars

Watchers

Forks

Languages