GLISP is a dialect of LISP 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.
Run ./run_test.sh for the complete test suite and aggregate statement coverage
across the interpreter, extensions, REPL, and CLI. The profile is written to
tests/c.out; ./run_test.sh -c opens its HTML report, and -v shows individual
tests. Test support code in tests/ is not part of the production coverage total.
The suite includes calling-convention comparisons, nested expression generation,
closure and tail-call lifetimes, error recovery, and local HTTP and file fixtures.
Run concurrency checks with go test -race ./.... To explore additional parser
and JSON combinations:
go test . -run '^$' -fuzz '^FuzzParseSource$' -fuzztime=30s
go test ./extensions -run '^$' -fuzz '^FuzzJSONRoundTrip$' -fuzztime=30s- Rich Data Types: Float, Int, Char, String, Bytes, Symbol, List, Array, and Hash.
- Comprehensive Operators:
- Arithmetic:
+,-,*,/,mod - Shift:
sla,sra - Bitwise:
bit-and,bit-or,bit-xor
- Arithmetic:
- Big Integer Support
- Control Flow:
- Comparison:
<,>,<=,>=,=,not= - Short-circuit booleans:
and,or - Conditionals:
cond
- Comparison:
- Core LISP Functionality:
- REPL (Read-Eval-Print Loop)
- Lambdas (
fn) and Bindings (def,defn,let) - Tail-call optimization
- Powerful Macro System with syntax quoting (backticks)
- Go Integration:
- Seamless Go API
- Concurrency support with Channels and Goroutines
To evaluate the performance of GLisp, we have created a separate benchmark project: glisp-benchmark.
This project benchmarks GLisp against other popular embedded scripting languages for Go, including:
The benchmarks cover various scenarios such as Fibonacci calculation, function calls, closures, and concurrency. The results show that GLisp demonstrates competitive performance across many test cases, making it a solid choice for performance-sensitive applications.
We encourage you to check out the benchmark project for detailed performance data and testing methodologies.
GLISP supports seven atomic types: ints, floats, strings, chars, bools, bytes, and symbols.
; Numbers
3 ; an int
-21 ; a negative int
0x41 ; hexadecimal int
0o755 ; octal int
0b1110 ; binary int
4.1 ; a float
1.3e20 ; float in scientific notation
; Characters
#c ; the character 'c'
#\n ; the newline character
; Strings
"hello world" ; a string
#`raw string` ; a raw string literal
; Other Atoms
asdf ; a symbol
true ; boolean true
false ; boolean false
0B676c... ; byte stream (hex encoded)Semicolons (;) are used for single-line comments.
Lists: Standard cons-cell lists, delimited by parentheses.
(a-function arg1 (another-function arg2))
'(1 2 3) ; A quoted list
(1 . 2) ; A cons pairArrays: Correspond to Go slices, delimited by square brackets.
[1 2 3 4]Hashes: Mutable hashmaps (Go maps internally), delimited by curly braces.
{'a 3 'b 2} ; Maps symbol 'a' to 3 and 'b' to 2The quote symbol (') prevents evaluation, treating the following expression as data.
'(+ 1 2) ; results in the list (+ 1 2), not the number 3
'a-symbol ; results in the symbol a-symbolDefine anonymous functions with fn and named functions with defn.
; Anonymous function
(fn [a b] (+ a b))
; Named function
(defn add-three [a] (+ a 3))
; Shorthand lambda syntax
#(+ 6 %) ; Equivalent to: (fn [x] (+ 6 x))
#(+ %1 %2) ; Equivalent to: (fn [x y] (+ x y))def: Creates a binding in the current scope.let/let*: Creates a new scope with local bindings.let*allows later bindings to refer to earlier ones.set!: Modifies an existing binding, searching up the scope stack if necessary. Use with care.
(def a 3)
(let [a 3 b 4] (* a b)) ; returns 12
(let* [a 2 b (+ a 1)] (+ a b)) ; returns 5cond is the primary conditional form. and and or provide short-circuit evaluation.
In GLISP, false and nil ('()) are "falsy". All other values are "truthy".
(cond
(< x 0) "negative"
(> x 0) "positive"
"zero") ; default case
(and truthy-val falsy-val) ; returns falsy-val
(or falsy-val truthy-val) ; returns truthy-valMacros enable syntactic extension by transforming code at compile time. They are defined with defmac.
`(syntax-quote): Creates a template for code expansion.~(unquote): Evaluates an expression within a syntax-quoted template.~@(splicing-unquote): Splices a list's elements into the template.
(defmac when [predicate & body]
`(cond ~predicate
(begin
~@body)
'()))GLISP provides a rich set of built-in functions. Here is a partial list.
- Type Introspection:
list?,array?,number?,string?,symbol?,type, etc. - Generic Collections:
len,append,concat,slice,exist?. - List Operations:
cons,car,cdr,list. - Array Operations:
make-array,aget,aset!. - Hashmap Operations:
hget,hset!,hdel!. - Higher-Order Functions:
map,flatmap,filter,foldl,compose. - Symbolic:
gensym,symbol,str. - Printing:
println,print,printf. - Sequencing:
begin.
GLISP's functionality can be extended with modules.
- Core: Arithmetic (
+,-,*,/), comparisons (<,=,>), metaprogramming (alias,currying,override), threading macros (->,->>). - Math: Bitwise operations (
bit-and,sla), float functions (round,floor). - JSON:
json/parse,json/stringify, and powerful querying withjson/query. - Strings:
str/split,str/contains?,str/replace,str/trim-space, and more. - Time:
time/now,time/parse,time/format, and time arithmetic. - OS:
os/read-file,os/write-file,os/exec. - HTTP:
http/get,http/post,http/curlfor making HTTP requests. - And more: Base64, Random, Regexp, etc.
To embed GLISP in your Go application, start by creating a glisp.Environment.
package main
import (
"fmt"
"github.com/qjpcpu/glisp"
ext "github.com/qjpcpu/glisp/extensions"
)
func main() {
// 1. Create a new interpreter environment
env := glisp.New()
ext.ImportCoreUtils(env) // Load standard library
// 2. Load and compile GLISP code
err := env.LoadString(`(defn add [a b] (+ a b))`)
if err != nil {
panic(err)
}
// 3. Run the code
_, err = env.Run()
if err != nil {
panic(err)
}
// 4. Call a LISP function from Go
addObject, found := env.FindObject("add")
if !found {
panic("add function not found")
}
addFunc := addObject.(*glisp.SexpFunction)
args := glisp.MakeArgs(glisp.NewSexpInt(10), glisp.NewSexpInt(20))
result, err := env.Apply(addFunc, args)
if err != nil {
panic(err)
}
fmt.Printf("Result of (add 10 20) is: %s", result.SexpString())
}GLISP values are represented by the Sexp interface. Strings and booleans map directly to Go types. Integers use SexpInt, constructed with NewSexpInt or NewSexpInt64; arithmetic keeps arbitrary precision when values exceed int64. Lists and hashes use SexpPair and SexpHash.
You can expose Go functions to your GLISP environment. A Go function must have the following signature:
func MyGoFunction(env *glisp.Environment, args glisp.Args) (glisp.Sexp, error) {
if args.Len() != 2 {
return glisp.SexpNull, glisp.WrongNargs
}
// ... your logic here ...
return glisp.NewSexpInt(42), nil
}Register it in the environment:
env.AddFunction("my-go-function", MyGoFunction)If Run() or Apply() returns an error, the environment's state is compromised. You can get a stack trace with GetStackTrace() and must call Clear() to reset the VM before running more code. Clear() releases active call frames and local scopes while preserving global bindings and registered functions.
expr, err := env.Run()
if err != nil {
stackTrace := env.GetStackTrace(err)
fmt.Println(stackTrace)
env.Clear() // Reset the environment
}To start the interactive REPL:
cd ./repl/glisp
go build && ./glispInside the REPL, you can use (doc function-name) to get documentation for any function.
glisp> (doc map)
Usage: (map f coll)
Returns a sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the
set of second items in each coll, until any one of the colls is
exhausted. coll can be array or list.