Documentation ¶
Overview ¶
Package lisp provides a minimal Lisp interpreter focused on embedded work in Go code, as config or as a transmission format. Lisp external libraries are loaded from the Go code, and loading them from Lisp code is not allowed (on purpose).
This interpreter is based on kanaka/mal implementation that is inspired on Clojure. It is still mostly compatible with kanaka/mal except that def!, try*, etc. symbols have been changed to def, try, etc. See ./examples/mal.lisp as a port of mal.mal
Overview of this implementation addition to kanaka/mal:
- simpler embedded use with a simple package API (mostly inherited, just code reorganisation)
- testing based on Go tooling (all python tests scripts substituted by Go tests, see ./run_test.go)
- support of Go constructors to simplify extendability
- slightly faster parsing by swapping regex implementation for a text/scanner one
- support of preamble (AKA "placeholders") to simplify parametrisation of Go functions implemented on Lisp
- easier library development (using reflect)
- simple debugger
- line numbers
Functions and file directories keep the same structure as original MAL, this is way main functions READ, EVAL and PRINT keep its all caps (non Go standard) names.
Example (ConfigInLisp) ¶
package main import ( "fmt" "github.com/jig/lisp" "github.com/jig/lisp/env" "github.com/jig/lisp/lib/core/nscore" "github.com/jig/lisp/types" ) func main() { ns := env.NewEnv() nscore.Load(ns) config, _ := lisp.READ( `{ :sessions 10 }`, types.NewCursorFile("ExampleFunctionInLisp"), ns, ) fmt.Println("sessions:", config.(types.HashMap).Val[types.NewKeyword("sessions")]) }
Output: sessions: 10
Example (FunctionInLisp) ¶
package main import ( "context" "fmt" "github.com/jig/lisp" "github.com/jig/lisp/env" "github.com/jig/lisp/lib/core/nscore" "github.com/jig/lisp/lnotation" "github.com/jig/lisp/types" ) func main() { ns := env.NewEnv() nscore.Load(ns) ast, _ := lisp.READ( `(fn [a] (* 10 a))`, types.NewCursorFile("ExampleFunctionInLisp"), ns, ) res, _ := lisp.EVAL(context.Background(), ast, ns) functionInLisp := func(a int) (int, error) { res, err := lisp.EVAL(context.Background(), lnotation.L(res, a), ns) if err != nil { return 0, err } return res.(int), nil } result, _ := functionInLisp(3) fmt.Println("result:", result) }
Output: result: 30
Index ¶
- Variables
- func AddPreamble(str string, placeholderMap map[string]MalType) (string, error)
- func EVAL(ctx context.Context, ast MalType, env EnvType) (res MalType, e error)
- func PRINT(ast MalType) string
- func READ(sourceCode string, cursor *Position, ns EnvType) (MalType, error)
- func READWithPreamble(str string, cursor *Position, ns EnvType) (MalType, error)
- func REPL(ctx context.Context, env EnvType, sourceCode string, cursor *Position) (MalType, error)
- func REPLWithPreamble(ctx context.Context, env EnvType, sourceCode string, cursor *Position) (MalType, error)
- func ReadEvalWithPreamble(ctx context.Context, env EnvType, sourceCode string, cursor *Position) (MalType, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Stepper func(ast MalType, ns EnvType) debuggertypes.Command
Stepper is called (if not null) to stop at each step of the Lisp interpreter.
It might be used as a debugger. Look at lisp/debugger package for a simple implementation.
Functions ¶
func AddPreamble ¶ added in v0.2.0
AddPreamble combines prefix variables into a preamble to the provided source code.
Source code encoded be readed with READWithPreamble. placeholderMap must contain a map with keys being the variable names on the placeholder and the values the AST assigned to each placeholder. Value ASTs might be generated with READ or EVAL or with the [lnotation] package (most likely). Key names must contain the '$' prefix.
Example ¶
package main import ( "context" "fmt" "log" "github.com/jig/lisp" "github.com/jig/lisp/env" "github.com/jig/lisp/lib/core/nscore" "github.com/jig/lisp/types" ) func main() { // incrementInLisp is sample function implemented in Lisp result, err := incrementInLisp(2) if err != nil { log.Fatalf("eval error: %s", err) } fmt.Printf("result: %d\n", result) } const incrementInLispSourceCode = "(+ 1 $ARG)" func incrementInLisp(arg int) (int, error) { ns := env.NewEnv() nscore.Load(ns) // to load '+' function preamble := map[string]types.MalType{ "$ARG": arg, } sourceCode, err := lisp.AddPreamble( incrementInLispSourceCode, preamble, ) if err != nil { return 0, err } ast, err := lisp.READWithPreamble( sourceCode, types.NewCursorFile("ExampleREAD"), ns, ) if err != nil { return 0, err } result, err := lisp.EVAL( context.Background(), ast, ns, ) if err != nil { return 0, err } return result.(int), nil }
Output: result: 3
func EVAL ¶
EVAL evaluates an Abstract Syntaxt Tree (AST) and returns a result (a reduced AST). It requires a context that might cancel execution, and requires an environment that might be modified. AST usually is generated by READ or READWithPreamble.
Example ¶
ns := env.NewEnv() nscore.Load(ns) // to load '+' function ast := LS("+", 1, 1) result, err := lisp.EVAL( context.Background(), ast, ns, ) if err != nil { log.Fatalf("error: %s", err) } fmt.Printf("result: %d\n", result)
Output: result: 2
func PRINT ¶
func PRINT(ast MalType) string
PRINT converts an AST to a string, suitable for printing AST might be generated by EVAL or by READ or READWithPreamble.
func READ ¶
READ reads Lisp source code and generates an AST that might be evaled by EVAL or printed by PRINT.
cursor and environment might be passed nil and READ will provide correct values for you. It is recommended though that cursor is initialised with a source code file identifier to provide better positioning information in case of encountering an execution error.
EnvType is required in case you expect to parse Go constructors
Example ¶
package main import ( "context" "fmt" "log" "github.com/jig/lisp" "github.com/jig/lisp/env" "github.com/jig/lisp/lib/core/nscore" "github.com/jig/lisp/types" ) func main() { ns := env.NewEnv() nscore.Load(ns) // to load '+' function ast, err := lisp.READ( "(+ 1 1)", types.NewCursorFile("ExampleREAD"), ns, ) if err != nil { log.Fatalf("read error: %s", err) } result, err := lisp.EVAL( context.Background(), ast, ns, ) if err != nil { log.Fatalf("eval error: %s", err) } fmt.Printf("result: %d\n", result) }
Output: result: 2
func READWithPreamble ¶ added in v0.2.0
READWithPreamble reads Lisp source code with preamble placeholders and generates an AST that might be evaled by EVAL or printed by PRINT.
cursor and environment might be passed nil and READ will provide correct values for you. It is recommended though that cursor is initialised with a source code file identifier to provide better positioning information in case of encountering an execution error.
EnvType is required in case you expect to parse Go constructors. ¶
Preamble placeholders are prefix the source code and have the following format:
;; <$-prefixed-var-name> <Lisp readable expression>
For example:
;; $1 {:key "value"} ;; $NUMBER 1984 ;; $EXPR1 (+ 1 1)
will create three values that will fill the placeholders in the source code. Following the example the source code might look like:
...some code... (prn "$NUMBER is" $NUMBER)
note that the actual code to be parsed will be:
(prn "$NUMBER is" 1984)
this simplifies inserting Lisp code in Go packages and passing Go parameters to it.
Look for the "L-notation" to simplify the pass of complex Lisp structures as placeholders.
READWithPreamble is used to read code (actually decode) on transmission. Use AddPreamble when calling from Go code.
func REPL ¶
REPL or READ, EVAL and PRINT loop execute those three functions in sequence. (but the loop "L" actually must be executed by the caller)
func REPLWithPreamble ¶ added in v0.2.0
func REPLWithPreamble(ctx context.Context, env EnvType, sourceCode string, cursor *Position) (MalType, error)
REPLWithPreamble or READ, EVAL and PRINT loop with preamble execute those three functions in sequence. (but the loop "L" actually must be executed by the caller)
Source code might include a preamble with the values for the placeholders. See READWithPreamble
func ReadEvalWithPreamble ¶ added in v0.2.0
func ReadEvalWithPreamble(ctx context.Context, env EnvType, sourceCode string, cursor *Position) (MalType, error)
ReadEvalWithPreamble or READ and EVAL with preamble execute those three functions in sequence. (but the loop "L" actually must be executed by the caller)
Source code might include a preamble with the values for the placeholders. See READWithPreamble ReadEvalWithPreamble returns the result in AST structure.
Example ¶
package main import ( "context" "fmt" "log" "github.com/jig/lisp" "github.com/jig/lisp/env" "github.com/jig/lisp/lib/core/nscore" "github.com/jig/lisp/types" ) func main() { ns := env.NewEnv() nscore.Load(ns) // to load '+' function sourceCode := `;; $ARG 1 (+ 1 $ARG)` result, err := lisp.ReadEvalWithPreamble( context.Background(), ns, sourceCode, types.NewCursorFile("ExampleReadEvalWithPreamble"), ) if err != nil { log.Fatalf("error: %s", err) } fmt.Printf("result: %d\n", result) }
Output: result: 2
Types ¶
This section is empty.