Skip to content

Latest commit

 

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GoLisp

Description of image

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.

Usage

As an embedded language (library)

Add as a dependency:

go get github.com/guiyuanju/golisp

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

As a standalone language (executable)

Install binary:

go install github.com/guiyuanju/golisp

Run REPL:

golisp

Run with golisp source file:

golisp main.gl

Syntax

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

Interoperability

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) // => 1

Set global value of GoLisp from Go code:

a, _ := e.SetGlobal("a", 2)

fmt.Println(a) // => 2
(print a) ;; => 2

Macro

; 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 miliseconds

Test

go test -v ./...

About

Embeddable Lisp for Golang.

Resources

Stars

20 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages