GoLisp is an embedded Lisp in Go, bringing Lisp's powerful macro to Go, provoding a dynamic and extensible execution layer. Being embedded means you can easily provide existing Go function to GoLisp by defining and register builtin functions.
Typical usage: rule engine, game logic, workflow script, plugin and extension system.
Add as a dependency:
go get github.com/guiyuanju/golispUse GoLisp script to dynamically execute discount strategy: (Check examples folder for more examples)
import (
"fmt"
"github.com/guiyuanju/golisp/evaluator"
)
func main() {
// data is Go side
prices := []float64{12, 100, 80, 120, 200, 40, 30}
// define a Go function to retrieve data, register it for GoLisp to use
evaluator.RegisterBuiltin("get-price-for-order", func(params ...any) (any, error) {
// ignoring all error handling
return prices[int(params[0].(float64))], nil
})
// define script, can be provided dynamically
dynamicDiscountRule := `
;; use Go function to get data
(fn is-discount-applicable (order)
(>= (get-price-for-order order) 100))
(fn apply-percentage-discount (price)
(* 0.8 price))
;; function defined in script can be invoked from Go side
(fn get-discounted-price (order)
(let (price (get-price-for-order order))
(if (is-discount-applicable order)
(apply-percentage-discount price)
price)))
`
// initialize evaluator with standard library
e := evaluator.WithPrelude()
// evaluate script
e.EvalString(dynamicDiscountRule)
// invoke function in script for each order, get discounted price
for order := range prices {
res, _ := e.InvokeFunc("get-discounted-price", order)
fmt.Printf("%v -> %v\n", prices[order], res)
}
}Install binary:
go install github.com/guiyuanju/golispRun REPL:
golispRun with golisp source file:
golisp main.glexpr = int | string | bool | symbol | nil | quote | var | set | if | fn | macro | list
var = "(" "var" symbol expr ")"
set = "(" "set" symbol expr ")"
if = "(" "if" expr expr expr? ")"
fn = "(" "fn" symbol? "[" symbol* "]" expr* ")"
quote = "'" expr
macro = "(" "macro" symbol "[" symbol* "]" expr* ")"
list = "(" expr* ")"
special_form = quote | var | set | if | fn | macro- primitives
- bool:
true,false - number (float64):
1,1.0,-1 - string:
"hello, world" - symbol:
'key - nil:
nil
- bool:
- list:
'(1 2 3) - variable:
(var a 0)(set a 1) - control flow:
(if cond true-brach false-branch) - comparison:
(= 1 1) => true(>= 0 1) => false - logical:
(and true false) => true,(or nil 1) => 1 - closure:
(fn (x) (+ x 1)) - function:
(fn inc (x) (+ x 1))equals(var inc (fn (x) (+ x 1))) - recursive as loop
- quote:
'1 - eval:
(eval 'key) => key - macro:
(macro name [forms] ...),(macroexpand macroname)
Register Go function for GoLisp to use:
// params are values passed from GoLisp, auto unwrapped as Go value
// use type assertion to test and retrieve the underlying typed value
// return an error so that GoLisp will print the error and stop execution
// the value returned will be auto wrapped as a GoLisp value for GoLisp to use
evaluator.RegisterBuiltin("get-price-for-order", func(params ...any) (any, error) {
// ignoring all error handling
return prices[int(params[0].(float64))], nil
})Call Go function from GoLisp:
;; call the registered name
(var price (get-price-for-order 0))
;; value returned by Go function is auto wrapped as GoLisp value
(print price)Invoke GoLisp function from Go code:
// get evaluator
e := evaluator.WithPrelude()
// evaluate the script
e.EvalString(dynamicDiscountRule)
// invoke GoLisp function, return value returned
res, err := e.InvokeFunc("get-discounted-price", order)Get global value of GoLisp from Go code:
(var a 1)a, _ := e.GetGlobal("a")
fmt.Println(a) // => 1Set global value of GoLisp from Go code:
a, _ := e.SetGlobal("a", 2)
fmt.Println(a) // => 2(print a) ;; => 2; function definition
(fn fib (x)
(if (< x 2)
x
(+ (fib (- x 1))
(fib (- x 2)))))
; macro definition
(macro timeit (forms)
(list 'let '(start (time))
(list 'do
forms
'(nano->milisec (- (time) start)))))
; variable definition
(var form '(timeit (fib 30)))
; macro expansion
(print (macroexpand form))
; => (let (start (time)) (do (fib 30) (nano->milisec (- (time) start))))
(print (macroexpand (macroexpand form)))
; => ((fn () (var start (time)) (do (fib 30) (nano->milisec (- (time) start)))))
; eval
(print (eval form) "miliseconds")
; => 8397 milisecondsgo test -v ./...