├── .github └── workflows │ └── go.yml ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── builtins.go ├── builtins_test.go ├── env.go ├── examples └── simple │ └── main.go ├── expr.go ├── expr_test.go ├── go.mod ├── go.sum ├── options.go ├── parens.go ├── parens_test.go ├── reader ├── errors.go ├── macros.go ├── options.go ├── reader.go └── reader_test.go ├── repl ├── options.go ├── printer.go └── repl.go ├── special.go ├── utils.go ├── utils_test.go ├── values.go └── values_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.15 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | bin/ 14 | .vscode/ 15 | .idea/ 16 | 17 | 18 | # experimental files + directories. 19 | expt/ 20 | temp.lisp 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## v0.1.0 (2020-09-09) 9 | 10 | ### Added 11 | 12 | * Initial commit with project setup. 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION="`git describe --abbrev=0 --tags`" 2 | COMMIT="`git rev-list -1 --abbrev-commit HEAD`" 3 | 4 | all: clean fmt test 5 | 6 | fmt: 7 | @echo "Formatting..." 8 | @goimports -l -w ./ 9 | 10 | clean: 11 | @echo "Cleaning up..." 12 | @go mod tidy -v 13 | 14 | test: 15 | @echo "Running tests..." 16 | @go test -cover ./... 17 | 18 | test-verbose: 19 | @echo "Running tests..." 20 | @go test -v -cover ./... 21 | 22 | benchmark: 23 | @echo "Running benchmarks..." 24 | @go test -benchmem -run="none" -bench="Benchmark.*" -v ./... 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parens 2 | 3 | [![GoDoc](https://godoc.org/github.com/spy16/parens?status.svg)](https://godoc.org/github.com/spy16/parens) [![Go Report Card](https://goreportcard.com/badge/github.com/spy16/parens)](https://goreportcard.com/report/github.com/spy16/parens) ![Go](https://github.com/spy16/parens/workflows/Go/badge.svg?branch=master) 4 | 5 | 6 | **DEPRECATED**: *This repository is deprecated in favour much better [slurp](github.com/spy16/slurp) project and will be archived/removed soon.* 7 | 8 | Parens is a highly customisable, embeddable LISP toolkit. 9 | 10 | ## Features 11 | 12 | * Highly customizable and powerful reader/parser through a read table (Inspired by Clojure) (See [Reader](#reader)) 13 | * Built-in data types: nil, bool, string, number, character, keyword, symbol, list. 14 | * Multiple number formats supported: decimal, octal, hexadecimal, radix and scientific notations. 15 | * Full unicode support. Symbols can include unicode characters (Example: `find-δ`, `π` etc.) 16 | and `🧠`, `🏃` etc. (yes, smileys too). 17 | * Character Literals with support for: 18 | 1. simple literals (e.g., `\a` for `a`) 19 | 2. special literals (e.g., `\newline`, `\tab` etc.) 20 | 3. unicode literals (e.g., `\u00A5` for `¥` etc.) 21 | * Easy to extend. See [Extending](#extending). 22 | * A macro system. 23 | 24 | > Please note that Parens is _NOT_ an implementation of a particular LISP dialect. It provides 25 | > pieces that can be used to build a LISP dialect or can be used as a scripting layer. 26 | 27 | ## Usage 28 | 29 | What can you use it for? 30 | 31 | 1. Embedded script engine to provide dynamic behavior without requiring re-compilation 32 | of your application. 33 | 2. Business rule engine by exposing very specific & composable rule functions. 34 | 3. To build your own LISP dialect. 35 | 36 | > Parens requires Go 1.14 or higher. 37 | 38 | ## Extending 39 | 40 | ### Reader 41 | 42 | Parens reader is inspired by Clojure reader and uses a _read table_. Reader can be extended 43 | to add new syntactical features by adding _reader macros_ to the _read table_. _Reader Macros_ 44 | are implementations of `reader.Macro` function type. All syntax that reader can read are 45 | implemented using _Reader Macros_. Use `SetMacro()` method of `reader.Reader` to override or 46 | add a custom reader or dispatch macro. 47 | 48 | Reader returned by `reader.New(...)`, is configured to support following forms: 49 | 50 | * Numbers: 51 | * Integers use `int64` Go representation and can be specified using decimal, binary 52 | hexadecimal or radix notations. (e.g., 123, -123, 0b101011, 0xAF, 2r10100, 8r126 etc.) 53 | * Floating point numbers use `float64` Go representation and can be specified using 54 | decimal notation or scientific notation. (e.g.: 3.1412, -1.234, 1e-5, 2e3, 1.5e3 etc.) 55 | * You can override number reader using `WithNumReader()`. 56 | * Characters: Characters use `rune` or `uint8` Go representation and can be written in 3 ways: 57 | * Simple: `\a`, `\λ`, `\β` etc. 58 | * Special: `\newline`, `\tab` etc. 59 | * Unicode: `\u1267` 60 | * Boolean: `true` or `false` are converted to `Bool` type. 61 | * Nil: `nil` is represented as a zero-allocation empty struct in Go. 62 | * Keywords: Keywords represent symbolic data and start with `:`. (e.g., `:foo`) 63 | * Symbols: Symbols can be used to name a value and can contain any Unicode symbol. 64 | * Lists: Lists are zero or more forms contained within parenthesis. (e.g., `(1 2 3)`, `(1 [])`). 65 | 66 | ### Evaluation 67 | 68 | Parens uses an `Env` for evaluating forms. A form is first macro-expanded and then analysed 69 | to produce an `Expr` that can be evaluated. 70 | 71 | * Macro-expansion can be customised by setting a custom `Expander` implementation. See `parens.WithExpander()`. 72 | * Syntax analysis can be customised (For example, to add special forms), by setting a custom 73 | `Analyzer` implementation. See `parens.WithAnalyzer()`. 74 | 75 | ![I've just received word that the Emperor has dissolved the MIT computer science program permanently.](https://imgs.xkcd.com/comics/lisp_cycles.png) 76 | -------------------------------------------------------------------------------- /builtins.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | _ Analyzer = (*BuiltinAnalyzer)(nil) 9 | _ Expander = (*builtinExpander)(nil) 10 | ) 11 | 12 | // BuiltinAnalyzer parses builtin value forms and returns Expr that can 13 | // be evaluated against parens Env. Custom special form parsers can be 14 | // set using WithAnalyzer(). 15 | type BuiltinAnalyzer struct { 16 | SpecialForms map[string]ParseSpecial 17 | } 18 | 19 | // ParseSpecial validates a special form invocation, parse the form and 20 | // returns an expression that can be evaluated for result. 21 | type ParseSpecial func(env *Env, args Seq) (Expr, error) 22 | 23 | // Analyze performs syntactic analysis of given form and returns an Expr 24 | // that can be evaluated for result against an Env. 25 | func (ba BuiltinAnalyzer) Analyze(env *Env, form Any) (Expr, error) { 26 | if IsNil(form) { 27 | return &ConstExpr{Const: Nil{}}, nil 28 | } 29 | 30 | switch f := form.(type) { 31 | case Symbol: 32 | v := env.Resolve(string(f)) 33 | if v == nil { 34 | return nil, Error{ 35 | Cause: ErrNotFound, 36 | Message: string(f), 37 | } 38 | } 39 | return &ConstExpr{Const: v}, nil 40 | 41 | case Seq: 42 | cnt, err := f.Count() 43 | if err != nil { 44 | return nil, err 45 | } else if cnt == 0 { 46 | break 47 | } 48 | 49 | return ba.analyzeSeq(env, f) 50 | } 51 | 52 | return &ConstExpr{Const: form}, nil 53 | } 54 | 55 | func (ba BuiltinAnalyzer) analyzeSeq(env *Env, seq Seq) (Expr, error) { 56 | // Analyze the call target. This is the first item in the sequence. 57 | first, err := seq.First() 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | // The call target may be a special form. In this case, we need to get the 63 | // corresponding parser function, which will take care of parsing/analyzing 64 | // the tail. 65 | if sym, ok := first.(Symbol); ok { 66 | if parse, found := ba.SpecialForms[string(sym)]; found { 67 | next, err := seq.Next() 68 | if err != nil { 69 | return nil, err 70 | } 71 | return parse(env, next) 72 | } 73 | } 74 | 75 | // Call target is not a special form and must be a Invokable. Analyze 76 | // the arguments and create an InvokeExpr. 77 | ie := InvokeExpr{Name: fmt.Sprintf("%s", first)} 78 | err = ForEach(seq, func(item Any) (done bool, err error) { 79 | if ie.Target == nil { 80 | ie.Target, err = ba.Analyze(env, first) 81 | return 82 | } 83 | 84 | var arg Expr 85 | if arg, err = ba.Analyze(env, item); err == nil { 86 | ie.Args = append(ie.Args, arg) 87 | } 88 | return 89 | }) 90 | return &ie, err 91 | } 92 | 93 | type builtinExpander struct{} 94 | 95 | func (be builtinExpander) Expand(_ *Env, _ Any) (Any, error) { 96 | // TODO: implement macro expansion. 97 | return nil, nil 98 | } 99 | -------------------------------------------------------------------------------- /builtins_test.go: -------------------------------------------------------------------------------- 1 | package parens_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/parens" 8 | ) 9 | 10 | func TestBasicAnalyzer_Analyze(t *testing.T) { 11 | t.Parallel() 12 | 13 | table := []struct { 14 | title string 15 | form parens.Any 16 | want parens.Expr 17 | wantErr bool 18 | }{ 19 | { 20 | title: "Nil", 21 | form: nil, 22 | want: &parens.ConstExpr{Const: parens.Nil{}}, 23 | }, 24 | { 25 | title: "Symbol", 26 | form: parens.Symbol("str"), 27 | want: &parens.ConstExpr{Const: parens.String("hello")}, 28 | }, 29 | { 30 | title: "Unknown Symbol", 31 | form: parens.Symbol("unknown"), 32 | wantErr: true, 33 | }, 34 | { 35 | title: "List With One Entry", 36 | form: parens.NewList(parens.Keyword("hello")), 37 | want: &parens.InvokeExpr{ 38 | Name: ":hello", 39 | Target: &parens.ConstExpr{Const: parens.Keyword("hello")}, 40 | }, 41 | }, 42 | { 43 | title: "List With Multi Entry", 44 | form: parens.NewList(parens.Keyword("hello"), parens.Int64(1)), 45 | want: &parens.InvokeExpr{ 46 | Name: ":hello", 47 | Target: &parens.ConstExpr{Const: parens.Keyword("hello")}, 48 | Args: []parens.Expr{ 49 | &parens.ConstExpr{Const: parens.Int64(1)}, 50 | }, 51 | }, 52 | }, 53 | } 54 | 55 | for _, tt := range table { 56 | t.Run(tt.title, func(t *testing.T) { 57 | env := parens.New(parens.WithGlobals(map[string]parens.Any{ 58 | "str": parens.String("hello"), 59 | }, nil)) 60 | 61 | az := &parens.BuiltinAnalyzer{} 62 | got, err := az.Analyze(env, tt.form) 63 | if (err != nil) != tt.wantErr { 64 | t.Errorf("BuiltinAnalyzer.Analyze() error = %#v, wantErr %#v", err, tt.wantErr) 65 | return 66 | } 67 | if !reflect.DeepEqual(got, tt.want) { 68 | t.Errorf("BuiltinAnalyzer.Analyze() got = %#v, want %#v", got, tt.want) 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | var _ ConcurrentMap = (*mutexMap)(nil) 9 | 10 | // Env represents the environment/context in which forms are evaluated 11 | // for result. Env is not safe for concurrent use. Use fork() to get a 12 | // child context for concurrent executions. 13 | type Env struct { 14 | ctx context.Context 15 | analyzer Analyzer 16 | expander Expander 17 | globals ConcurrentMap 18 | stack []stackFrame 19 | maxDepth int 20 | } 21 | 22 | // ConcurrentMap is used by the Env to store variables in the global stack 23 | // frame. 24 | type ConcurrentMap interface { 25 | // Store should store the key-value pair in the map. 26 | Store(key string, val Any) 27 | 28 | // Load should return the value associated with the key if it exists. 29 | // Returns nil, false otherwise. 30 | Load(key string) (Any, bool) 31 | 32 | // Map should return a native Go map of all key-values in the concurrent 33 | // map. This can be used for iteration etc. 34 | Map() map[string]Any 35 | } 36 | 37 | // Eval performs macro-expansion if necessary, converts the expanded form 38 | // to an expression and evaluates the resulting expression. 39 | func (env *Env) Eval(form Any) (Any, error) { 40 | expr, err := env.expandAnalyze(form) 41 | if err != nil { 42 | return nil, err 43 | } else if expr == nil { 44 | return nil, nil 45 | } 46 | 47 | return expr.Eval() 48 | } 49 | 50 | // Resolve a symbol. 51 | func (env Env) Resolve(sym string) Any { 52 | if len(env.stack) > 0 { 53 | // check inside top of the stack for local bindings. 54 | top := env.stack[len(env.stack)-1] 55 | if v, found := top.Vars[sym]; found { 56 | return v 57 | } 58 | } 59 | // return the value from global bindings if found. 60 | v, _ := env.globals.Load(sym) 61 | return v 62 | } 63 | 64 | // Analyze performs syntax checks for special forms etc. and returns an Expr value that 65 | // can be evaluated against the env. 66 | func (env *Env) Analyze(form Any) (Expr, error) { return env.analyzer.Analyze(env, form) } 67 | 68 | func (env *Env) expandAnalyze(form Any) (Expr, error) { 69 | if expr, ok := form.(Expr); ok { 70 | // Already an Expr, nothing to do. 71 | return expr, nil 72 | } 73 | 74 | if expanded, err := env.expander.Expand(env, form); err != nil { 75 | return nil, err 76 | } else if expanded != nil { 77 | // Expansion did happen. Throw away the old form and continue with 78 | // the expanded version. 79 | form = expanded 80 | } 81 | 82 | return env.analyzer.Analyze(env, form) 83 | } 84 | 85 | // Fork creates a child context from Env and returns it. The child context 86 | // can be used as context for an independent thread of execution. 87 | func (env *Env) Fork() *Env { 88 | return &Env{ 89 | ctx: env.ctx, 90 | globals: env.globals, 91 | expander: env.expander, 92 | analyzer: env.analyzer, 93 | maxDepth: env.maxDepth, 94 | } 95 | } 96 | 97 | func (env *Env) push(frame stackFrame) { 98 | env.stack = append(env.stack, frame) 99 | } 100 | 101 | func (env *Env) pop() (frame *stackFrame) { 102 | if len(env.stack) == 0 { 103 | panic("pop from empty stack") 104 | } 105 | frame, env.stack = &env.stack[len(env.stack)-1], env.stack[:len(env.stack)-1] 106 | return frame 107 | } 108 | 109 | func (env *Env) setGlobal(key string, value Any) { 110 | env.globals.Store(key, value) 111 | } 112 | 113 | type stackFrame struct { 114 | Name string 115 | Args []Any 116 | Vars map[string]Any 117 | } 118 | 119 | func newMutexMap() ConcurrentMap { return &mutexMap{} } 120 | 121 | // mutexMap implements a simple ConcurrentMap using sync.RWMutex locks. 122 | // Zero value is ready for use. 123 | type mutexMap struct { 124 | sync.RWMutex 125 | vs map[string]Any 126 | } 127 | 128 | func (m *mutexMap) Load(name string) (v Any, ok bool) { 129 | m.RLock() 130 | defer m.RUnlock() 131 | v, ok = m.vs[name] 132 | return 133 | } 134 | 135 | func (m *mutexMap) Store(name string, v Any) { 136 | m.Lock() 137 | defer m.Unlock() 138 | 139 | if m.vs == nil { 140 | m.vs = map[string]Any{} 141 | } 142 | m.vs[name] = v 143 | } 144 | 145 | func (m *mutexMap) Map() map[string]Any { 146 | m.RLock() 147 | defer m.RUnlock() 148 | 149 | native := make(map[string]Any, len(m.vs)) 150 | for k, v := range m.vs { 151 | native[k] = v 152 | } 153 | 154 | return native 155 | } 156 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/spy16/parens" 8 | "github.com/spy16/parens/repl" 9 | ) 10 | 11 | var globals = map[string]parens.Any{ 12 | "nil": parens.Nil{}, 13 | "true": parens.Bool(true), 14 | "false": parens.Bool(false), 15 | "*version*": parens.String("1.0"), 16 | } 17 | 18 | func main() { 19 | env := parens.New(parens.WithGlobals(globals, nil)) 20 | 21 | r := repl.New(env, 22 | repl.WithBanner("Welcome to Parens!"), 23 | repl.WithPrompts(">>", " |"), 24 | ) 25 | 26 | if err := r.Loop(context.Background()); err != nil { 27 | log.Fatal(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /expr.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | _ Expr = (*ConstExpr)(nil) 11 | _ Expr = (*DefExpr)(nil) 12 | _ Expr = (*QuoteExpr)(nil) 13 | _ Expr = (*InvokeExpr)(nil) 14 | _ Expr = (*IfExpr)(nil) 15 | _ Expr = (*DoExpr)(nil) 16 | ) 17 | 18 | // ConstExpr returns the Const value wrapped inside when evaluated. It has 19 | // no side-effect on the VM. 20 | type ConstExpr struct{ Const Any } 21 | 22 | // Eval returns the constant value unmodified. 23 | func (ce ConstExpr) Eval() (Any, error) { return ce.Const, nil } 24 | 25 | // QuoteExpr expression represents a quoted form and 26 | type QuoteExpr struct{ Form Any } 27 | 28 | // Eval returns the quoted form unmodified. 29 | func (qe QuoteExpr) Eval() (Any, error) { 30 | // TODO: re-use this for syntax-quote and unquote? 31 | return qe.Form, nil 32 | } 33 | 34 | // DefExpr creates a global binding with the Name when evaluated. 35 | type DefExpr struct { 36 | Env *Env 37 | Name string 38 | Value Expr 39 | } 40 | 41 | // Eval creates a symbol binding in the global (root) stack frame. 42 | func (de DefExpr) Eval() (Any, error) { 43 | de.Name = strings.TrimSpace(de.Name) 44 | if de.Name == "" { 45 | return nil, fmt.Errorf("%w: '%s'", ErrInvalidBindName, de.Name) 46 | } 47 | 48 | val, err := de.Value.Eval() 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | de.Env.setGlobal(de.Name, val) 54 | return Symbol(de.Name), nil 55 | } 56 | 57 | // IfExpr represents the if-then-else form. 58 | type IfExpr struct{ Test, Then, Else Expr } 59 | 60 | // Eval the expression 61 | func (ife IfExpr) Eval() (Any, error) { 62 | target := ife.Else 63 | if ife.Test != nil { 64 | test, err := ife.Test.Eval() 65 | if err != nil { 66 | return nil, err 67 | } 68 | if IsTruthy(test) { 69 | target = ife.Then 70 | } 71 | } 72 | 73 | if target == nil { 74 | return Nil{}, nil 75 | } 76 | return target.Eval() 77 | } 78 | 79 | // DoExpr represents the (do expr*) form. 80 | type DoExpr struct{ Exprs []Expr } 81 | 82 | // Eval the expression 83 | func (de DoExpr) Eval() (Any, error) { 84 | var res Any 85 | var err error 86 | 87 | for _, expr := range de.Exprs { 88 | res, err = expr.Eval() 89 | if err != nil { 90 | return nil, err 91 | } 92 | } 93 | 94 | if res == nil { 95 | return Nil{}, nil 96 | } 97 | return res, nil 98 | } 99 | 100 | // InvokeExpr performs invocation of target when evaluated. 101 | type InvokeExpr struct { 102 | Env *Env 103 | Name string 104 | Target Expr 105 | Args []Expr 106 | } 107 | 108 | // Eval the expression 109 | func (ie InvokeExpr) Eval() (Any, error) { 110 | val, err := ie.Target.Eval() 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | fn, ok := val.(Invokable) 116 | if !ok { 117 | return nil, Error{ 118 | Cause: ErrNotInvokable, 119 | Message: fmt.Sprintf("value of type '%s' is not invokable", reflect.TypeOf(val)), 120 | } 121 | } 122 | 123 | var args []Any 124 | for _, ae := range ie.Args { 125 | v, err := ae.Eval() 126 | if err != nil { 127 | return nil, err 128 | } 129 | args = append(args, v) 130 | } 131 | 132 | ie.Env.push(stackFrame{ 133 | Name: ie.Name, 134 | Args: args, 135 | Vars: map[string]Any{}, 136 | }) 137 | defer ie.Env.pop() 138 | 139 | return fn.Invoke(ie.Env, args...) 140 | } 141 | 142 | // GoExpr evaluates an expression in a separate goroutine. 143 | type GoExpr struct { 144 | Env *Env 145 | Value Any 146 | } 147 | 148 | // Eval forks the given context to get a child context and launches goroutine 149 | // with the child context to evaluate the 150 | func (ge GoExpr) Eval() (Any, error) { 151 | child := ge.Env.Fork() 152 | go func() { 153 | _, _ = child.Eval(ge.Value) 154 | }() 155 | return nil, nil 156 | } 157 | -------------------------------------------------------------------------------- /expr_test.go: -------------------------------------------------------------------------------- 1 | package parens_test 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | "time" 8 | 9 | "github.com/spy16/parens" 10 | "github.com/spy16/parens/reader" 11 | ) 12 | 13 | func TestDoExpr_Eval(t *testing.T) { 14 | t.Parallel() 15 | 16 | t.Run("No Body", func(t *testing.T) { 17 | de := parens.DoExpr{} 18 | res, err := de.Eval() 19 | requireNoErr(t, err) 20 | assertEqual(t, parens.Nil{}, res) 21 | }) 22 | 23 | t.Run("With Body", func(t *testing.T) { 24 | de := parens.DoExpr{ 25 | Exprs: []parens.Expr{ 26 | &parens.ConstExpr{Const: parens.Symbol("foo")}, 27 | }, 28 | } 29 | res, err := de.Eval() 30 | requireNoErr(t, err) 31 | assertEqual(t, parens.Symbol("foo"), res) 32 | }) 33 | } 34 | 35 | func TestIfExpr_Eval(t *testing.T) { 36 | t.Parallel() 37 | 38 | t.Run("If Nil", func(t *testing.T) { 39 | ie := parens.IfExpr{ 40 | Test: nil, 41 | Then: &parens.ConstExpr{Const: parens.String("then")}, 42 | Else: &parens.ConstExpr{Const: parens.String("else")}, 43 | } 44 | res, err := ie.Eval() 45 | requireNoErr(t, err) 46 | assertEqual(t, parens.String("else"), res) 47 | }) 48 | 49 | t.Run("If True", func(t *testing.T) { 50 | ie := parens.IfExpr{ 51 | Test: &parens.ConstExpr{Const: parens.Bool(true)}, 52 | Then: &parens.ConstExpr{Const: parens.String("then")}, 53 | Else: &parens.ConstExpr{Const: parens.String("else")}, 54 | } 55 | res, err := ie.Eval() 56 | requireNoErr(t, err) 57 | assertEqual(t, parens.String("then"), res) 58 | }) 59 | 60 | t.Run("If Val", func(t *testing.T) { 61 | ie := parens.IfExpr{ 62 | Test: &parens.ConstExpr{Const: parens.String("foo")}, 63 | Then: &parens.ConstExpr{Const: parens.String("then")}, 64 | Else: &parens.ConstExpr{Const: parens.String("else")}, 65 | } 66 | res, err := ie.Eval() 67 | requireNoErr(t, err) 68 | assertEqual(t, parens.String("then"), res) 69 | }) 70 | 71 | t.Run("No Body", func(t *testing.T) { 72 | ie := parens.IfExpr{ 73 | Test: &parens.ConstExpr{Const: parens.String("foo")}, 74 | } 75 | res, err := ie.Eval() 76 | requireNoErr(t, err) 77 | assertEqual(t, parens.Nil{}, res) 78 | }) 79 | } 80 | 81 | func TestDefExpr_Eval(t *testing.T) { 82 | t.Parallel() 83 | 84 | t.Run("Invalid Name", func(t *testing.T) { 85 | de := parens.DefExpr{ 86 | Env: parens.New(), 87 | Name: "", 88 | Value: &parens.ConstExpr{Const: parens.Int64(10)}, 89 | } 90 | v, err := de.Eval() 91 | assertErr(t, err) 92 | assertEqual(t, nil, v) 93 | }) 94 | 95 | t.Run("Success", func(t *testing.T) { 96 | de := parens.DefExpr{ 97 | Env: parens.New(), 98 | Name: "foo", 99 | Value: &parens.ConstExpr{Const: parens.Int64(10)}, 100 | } 101 | v, err := de.Eval() 102 | requireNoErr(t, err) 103 | assertEqual(t, parens.Symbol("foo"), v) 104 | }) 105 | } 106 | 107 | func TestQuoteExpr_Eval(t *testing.T) { 108 | want := parens.NewList() 109 | 110 | qe := parens.QuoteExpr{Form: want} 111 | got, err := qe.Eval() 112 | requireNoErr(t, err) 113 | 114 | assertEqual(t, want, got) 115 | } 116 | 117 | func TestGoExpr_Eval(t *testing.T) { 118 | r := reader.New(strings.NewReader("(go (def test :keyword))")) 119 | actual, err := r.One() 120 | requireNoErr(t, err) 121 | 122 | env := parens.New() 123 | _, _ = env.Eval(actual) 124 | time.Sleep(5 * time.Millisecond) 125 | 126 | actual, err = env.Eval(parens.Symbol("test")) 127 | requireNoErr(t, err) 128 | 129 | if kw, ok := actual.(parens.Keyword); !ok { 130 | t.Errorf("expected parens.Keyword, got %s", reflect.TypeOf(kw)) 131 | return 132 | } else if string(kw) != "keyword" { 133 | t.Errorf("expected keyword value of \"keyword\", got \"%s\"", string(kw)) 134 | return 135 | } 136 | } 137 | 138 | func requireNoErr(t *testing.T, err error) { 139 | if err != nil { 140 | t.Fatalf("unexpected error: %v", err) 141 | } 142 | } 143 | 144 | func assertEqual(t *testing.T, want interface{}, got interface{}) { 145 | if !reflect.DeepEqual(want, got) { 146 | t.Errorf("got=%#v\nwant=%#v", got, want) 147 | } 148 | } 149 | 150 | func assertErr(t *testing.T, err error) { 151 | if err == nil { 152 | t.Errorf("expecting error, got nil") 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spy16/parens 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spy16/parens/6697f6938da77c244bb1883beb402eed2183f395/go.sum -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | // Option can be used with New() to customize initialization of Evaluator 4 | // Instance. 5 | type Option func(env *Env) 6 | 7 | // WithGlobals sets the global variables during initialisation. If factory 8 | // is nil, a mutex based concurrent map will be used. 9 | func WithGlobals(globals map[string]Any, factory func() ConcurrentMap) Option { 10 | return func(env *Env) { 11 | if factory == nil { 12 | factory = newMutexMap 13 | } 14 | if env.globals == nil { 15 | env.globals = factory() 16 | } 17 | for k, v := range globals { 18 | env.globals.Store(k, v) 19 | } 20 | } 21 | } 22 | 23 | // WithMaxDepth sets the max depth allowed for stack. Panics if depth == 0. 24 | func WithMaxDepth(depth uint) Option { 25 | if depth == 0 { 26 | panic("maxdepth must be nonzero.") 27 | } 28 | return func(env *Env) { 29 | env.maxDepth = int(depth) 30 | } 31 | } 32 | 33 | // WithExpander sets the macro Expander to be used by the p. If nil, a builtin 34 | // Expander will be used. 35 | func WithExpander(expander Expander) Option { 36 | return func(env *Env) { 37 | if expander == nil { 38 | expander = &builtinExpander{} 39 | } 40 | env.expander = expander 41 | } 42 | } 43 | 44 | // WithAnalyzer sets the Analyzer to be used by the p. If replaceBuiltin is set, 45 | // given analyzer will be used for all forms. Otherwise, it will be used only for 46 | // forms not handled by the builtinAnalyzer. 47 | func WithAnalyzer(analyzer Analyzer) Option { 48 | return func(env *Env) { 49 | if analyzer == nil { 50 | analyzer = &BuiltinAnalyzer{ 51 | SpecialForms: map[string]ParseSpecial{ 52 | "go": parseGoExpr, 53 | "do": parseDoExpr, 54 | "if": parseIfExpr, 55 | "def": parseDefExpr, 56 | "quote": parseQuoteExpr, 57 | }, 58 | } 59 | } 60 | env.analyzer = analyzer 61 | } 62 | } 63 | 64 | func withDefaults(opts []Option) []Option { 65 | return append([]Option{ 66 | WithAnalyzer(nil), 67 | WithExpander(nil), 68 | WithMaxDepth(10000), 69 | }, opts...) 70 | } 71 | -------------------------------------------------------------------------------- /parens.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | // ErrNotFound is returned when a binding not found. 11 | ErrNotFound = errors.New("not found") 12 | 13 | // ErrInvalidBindName is returned by DefExpr when the bind name is invalid. 14 | ErrInvalidBindName = errors.New("invalid name for def") 15 | 16 | // ErrNotInvokable is returned by InvokeExpr when the target is not invokable. 17 | ErrNotInvokable = errors.New("not invokable") 18 | 19 | // ErrIncomparableTypes is returned by Any.Comp when a comparison between two tpyes 20 | // is undefined. Users should generally consider the types to be not equal in such 21 | // cases, but not assume any ordering. 22 | ErrIncomparableTypes = errors.New("incomparable types") 23 | ) 24 | 25 | // New returns a new root context initialised based on given options. 26 | func New(opts ...Option) *Env { 27 | env := &Env{ctx: context.Background(), globals: newMutexMap()} 28 | for _, opt := range withDefaults(opts) { 29 | opt(env) 30 | } 31 | return env 32 | } 33 | 34 | // Any represents any form 35 | type Any interface{} 36 | 37 | // SExpressable forms can be rendered as s-expressions. 38 | type SExpressable interface { 39 | SExpr() (string, error) 40 | } 41 | 42 | // Seq represents a sequence of values. 43 | type Seq interface { 44 | Any 45 | Count() (int, error) 46 | First() (Any, error) 47 | Next() (Seq, error) 48 | Conj(items ...Any) (Seq, error) 49 | } 50 | 51 | // Analyzer implementation is responsible for performing syntax analysis 52 | // on given form. 53 | type Analyzer interface { 54 | // Analyze should perform syntax checks for special forms etc. and 55 | // return Expr values that can be evaluated against a context. 56 | Analyze(env *Env, form Any) (Expr, error) 57 | } 58 | 59 | // Expander implementation is responsible for performing macro-expansion 60 | // where necessary. 61 | type Expander interface { 62 | // Expand should expand/rewrite the given form if it's a macro form 63 | // and return the expanded version. If given form is not macro form, 64 | // it should return nil, nil. 65 | Expand(env *Env, form Any) (Any, error) 66 | } 67 | 68 | // Invokable represents a value that can be invoked for result. 69 | type Invokable interface { 70 | Invoke(env *Env, args ...Any) (Any, error) 71 | } 72 | 73 | // Expr represents an expression that can be evaluated against a context. 74 | type Expr interface { 75 | Eval() (Any, error) 76 | } 77 | 78 | // Error is returned by all parens operations. Cause indicates the underlying 79 | // error type. Use errors.Is() with Cause to check for specific errors. 80 | type Error struct { 81 | Message string 82 | Cause error 83 | } 84 | 85 | // Is returns true if the other error is same as the cause of this error. 86 | func (e Error) Is(other error) bool { return errors.Is(e.Cause, other) } 87 | 88 | // Unwrap returns the underlying cause of the error. 89 | func (e Error) Unwrap() error { return e.Cause } 90 | 91 | func (e Error) Error() string { 92 | if e.Cause != nil { 93 | return fmt.Sprintf("%v: %s", e.Cause, e.Message) 94 | } 95 | return e.Message 96 | } 97 | -------------------------------------------------------------------------------- /parens_test.go: -------------------------------------------------------------------------------- 1 | package parens_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spy16/parens" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | p := parens.New() 11 | assertNotNil(t, p) 12 | } 13 | 14 | func assertNotNil(t *testing.T, v interface{}) { 15 | if v == nil { 16 | t.Errorf("wanted non-nil value, got nil") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /reader/errors.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | // ErrSkip can be returned by reader macro to indicate a no-op form which 10 | // should be discarded (e.g., Comments). 11 | ErrSkip = errors.New("skip expr") 12 | 13 | // ErrEOF is returned by reader when stream ends prematurely to indicate 14 | // that more data is needed to complete the current form. 15 | ErrEOF = errors.New("unexpected EOF while parsing") 16 | 17 | // ErrUnmatchedDelimiter is returned when a reader macro encounters a closing 18 | // container- delimiter without a corresponding opening delimiter (e.g. ']' 19 | // but no '['). 20 | ErrUnmatchedDelimiter = errors.New("unmatched delimiter") 21 | 22 | // ErrNumberFormat is returned when a reader macro encounters a illegally 23 | // formatted numerical form. 24 | ErrNumberFormat = errors.New("invalid number format") 25 | ) 26 | 27 | // Error is returned by all parens operations. Cause indicates the underlying 28 | // error type. Use errors.Is() with Cause to check for specific errors. 29 | type Error struct { 30 | Form string 31 | Cause error 32 | Rune rune 33 | Begin, End Position 34 | } 35 | 36 | // Is returns true if the other error is same as the cause of this error. 37 | func (e Error) Is(other error) bool { return errors.Is(e.Cause, other) } 38 | 39 | // Unwrap returns the underlying cause of the error. 40 | func (e Error) Unwrap() error { return e.Cause } 41 | 42 | func (e Error) Error() string { 43 | cause := e.Cause 44 | if errors.Is(cause, ErrUnmatchedDelimiter) { 45 | cause = fmt.Errorf("unmatched delimiter '%c'", e.Rune) 46 | } 47 | 48 | return fmt.Sprintf("error while reading: %v", cause) 49 | } 50 | -------------------------------------------------------------------------------- /reader/macros.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/spy16/parens" 11 | ) 12 | 13 | // Macro implementations can be plugged into the Reader to extend, override 14 | // or customize behavior of the reader. 15 | type Macro func(rd *Reader, init rune) (parens.Any, error) 16 | 17 | // // TODO(enhancement): implement parens.Set 18 | // // SetReader implements the reader macro for reading set from source. 19 | // func SetReader(setEnd rune, factory func() parens.Set) Macro { 20 | // return func(rd *Reader, _ rune) (parens.Any, error) { 21 | // forms, err := rd.Container(setEnd, "Set") 22 | // if err != nil { 23 | // return nil, err 24 | // } 25 | // return factory().Conj(forms...), nil 26 | // } 27 | // } 28 | 29 | // // TODO(enhancement): implement parens.Vector 30 | // // VectorReader implements the reader macro for reading vector from source. 31 | // func VectorReader(vecEnd rune, factory func() parens.Vector) Macro { 32 | // return func(rd *Reader, _ rune) (parens.Any, error) { 33 | // forms, err := rd.Container(vecEnd, "Vector") 34 | // if err != nil { 35 | // return nil, err 36 | // } 37 | 38 | // vec := factory() 39 | // for _, f := range forms { 40 | // _ = f 41 | // } 42 | 43 | // return vec, nil 44 | // } 45 | // } 46 | 47 | // // TODO(enhancement) implement parens.Map 48 | // // MapReader returns a reader macro for reading map values from source. factory 49 | // // is used to construct the map and `Assoc` is called for every pair read. 50 | // func MapReader(mapEnd rune, factory func() parens.Map) Macro { 51 | // return func(rd *Reader, _ rune) (parens.Any, error) { 52 | // forms, err := rd.Container(mapEnd, "Map") 53 | // if err != nil { 54 | // return nil, err 55 | // } 56 | 57 | // if len(forms)%2 != 0 { 58 | // return nil, errors.New("expecting even number of forms within {}") 59 | // } 60 | 61 | // m := factory() 62 | // for i := 0; i < len(forms); i += 2 { 63 | // if m.HasKey(forms[i]) { 64 | // return nil, fmt.Errorf("duplicate key: %v", forms[i]) 65 | // } 66 | 67 | // m, err = m.Assoc(forms[i], forms[i+1]) 68 | // if err != nil { 69 | // return nil, err 70 | // } 71 | // } 72 | 73 | // return m, nil 74 | // } 75 | // } 76 | 77 | // UnmatchedDelimiter implements a reader macro that can be used to capture 78 | // unmatched delimiters such as closing parenthesis etc. 79 | func UnmatchedDelimiter() Macro { 80 | return func(rd *Reader, initRune rune) (parens.Any, error) { 81 | e := rd.annotateErr(ErrUnmatchedDelimiter, rd.Position(), "").(Error) 82 | e.Rune = initRune 83 | return nil, e 84 | } 85 | } 86 | 87 | func symbolReader(symTable map[string]parens.Any) Macro { 88 | return func(rd *Reader, init rune) (parens.Any, error) { 89 | beginPos := rd.Position() 90 | 91 | s, err := rd.Token(init) 92 | if err != nil { 93 | return nil, rd.annotateErr(err, beginPos, s) 94 | } 95 | 96 | if predefVal, found := symTable[s]; found { 97 | return predefVal, nil 98 | } 99 | 100 | return parens.Symbol(s), nil 101 | } 102 | } 103 | 104 | func readNumber(rd *Reader, init rune) (parens.Any, error) { 105 | beginPos := rd.Position() 106 | 107 | numStr, err := rd.Token(init) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | decimalPoint := strings.ContainsRune(numStr, '.') 113 | isRadix := strings.ContainsRune(numStr, 'r') 114 | isScientific := strings.ContainsRune(numStr, 'e') 115 | 116 | switch { 117 | case isRadix && (decimalPoint || isScientific): 118 | return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr) 119 | 120 | case isScientific: 121 | v, err := parseScientific(numStr) 122 | if err != nil { 123 | return nil, rd.annotateErr(err, beginPos, numStr) 124 | } 125 | return v, nil 126 | 127 | case decimalPoint: 128 | v, err := strconv.ParseFloat(numStr, 64) 129 | if err != nil { 130 | return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr) 131 | } 132 | return parens.Float64(v), nil 133 | 134 | case isRadix: 135 | v, err := parseRadix(numStr) 136 | if err != nil { 137 | return nil, rd.annotateErr(err, beginPos, numStr) 138 | } 139 | return v, nil 140 | 141 | default: 142 | v, err := strconv.ParseInt(numStr, 0, 64) 143 | if err != nil { 144 | return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr) 145 | } 146 | 147 | return parens.Int64(v), nil 148 | } 149 | } 150 | 151 | func readString(rd *Reader, init rune) (parens.Any, error) { 152 | beginPos := rd.Position() 153 | 154 | var b strings.Builder 155 | for { 156 | r, err := rd.NextRune() 157 | if err != nil { 158 | if errors.Is(err, io.EOF) { 159 | err = ErrEOF 160 | } 161 | return nil, rd.annotateErr(err, beginPos, string(init)+b.String()) 162 | } 163 | 164 | if r == '\\' { 165 | r2, err := rd.NextRune() 166 | if err != nil { 167 | if errors.Is(err, io.EOF) { 168 | err = ErrEOF 169 | } 170 | 171 | return nil, rd.annotateErr(err, beginPos, string(init)+b.String()) 172 | } 173 | 174 | // TODO: Support for Unicode escape \uNN format. 175 | 176 | escaped, err := getEscape(r2) 177 | if err != nil { 178 | return nil, err 179 | } 180 | r = escaped 181 | } else if r == '"' { 182 | break 183 | } 184 | 185 | b.WriteRune(r) 186 | } 187 | 188 | return parens.String(b.String()), nil 189 | } 190 | 191 | func readComment(rd *Reader, _ rune) (parens.Any, error) { 192 | for { 193 | r, err := rd.NextRune() 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | if r == '\n' { 199 | break 200 | } 201 | } 202 | 203 | return nil, ErrSkip 204 | } 205 | 206 | func readKeyword(rd *Reader, init rune) (parens.Any, error) { 207 | beginPos := rd.Position() 208 | 209 | token, err := rd.Token(-1) 210 | if err != nil { 211 | return nil, rd.annotateErr(err, beginPos, token) 212 | } 213 | 214 | return parens.Keyword(token), nil 215 | } 216 | 217 | func readCharacter(rd *Reader, _ rune) (parens.Any, error) { 218 | beginPos := rd.Position() 219 | 220 | r, err := rd.NextRune() 221 | if err != nil { 222 | return nil, rd.annotateErr(err, beginPos, "") 223 | } 224 | 225 | token, err := rd.Token(r) 226 | if err != nil { 227 | return nil, err 228 | } 229 | runes := []rune(token) 230 | 231 | if len(runes) == 1 { 232 | return parens.Char(runes[0]), nil 233 | } 234 | 235 | v, found := charLiterals[token] 236 | if found { 237 | return parens.Char(v), nil 238 | } 239 | 240 | if token[0] == 'u' { 241 | return readUnicodeChar(token[1:], 16) 242 | } 243 | 244 | return nil, fmt.Errorf("unsupported character: '\\%s'", token) 245 | } 246 | 247 | func readList(rd *Reader, _ rune) (parens.Any, error) { 248 | const listEnd = ')' 249 | 250 | beginPos := rd.Position() 251 | 252 | forms := make([]parens.Any, 0, 32) // pre-allocate to improve performance on small lists 253 | if err := rd.Container(listEnd, "list", func(val parens.Any) error { 254 | forms = append(forms, val) 255 | return nil 256 | }); err != nil { 257 | return nil, rd.annotateErr(err, beginPos, "") 258 | } 259 | 260 | return parens.NewList(forms...), nil 261 | } 262 | 263 | func quoteFormReader(expandFunc string) Macro { 264 | return func(rd *Reader, _ rune) (parens.Any, error) { 265 | expr, err := rd.One() 266 | if err != nil { 267 | if err == io.EOF { 268 | return nil, Error{ 269 | Form: expandFunc, 270 | Cause: ErrEOF, 271 | } 272 | } else if err == ErrSkip { 273 | return nil, Error{ 274 | Form: expandFunc, 275 | Cause: errors.New("cannot quote a no-op form"), 276 | } 277 | } 278 | return nil, err 279 | } 280 | 281 | return parens.NewList(parens.Symbol(expandFunc), expr), nil 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /reader/options.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "github.com/spy16/parens" 5 | ) 6 | 7 | var defaultSymTable = map[string]parens.Any{ 8 | "nil": parens.Nil{}, 9 | "true": parens.Bool(true), 10 | "false": parens.Bool(false), 11 | } 12 | 13 | // Option values can be used with New() to configure the reader during init. 14 | type Option func(*Reader) 15 | 16 | // WithNumReader sets the number reader macro to be used by the Reader. Uses 17 | // the default number reader if nil. 18 | func WithNumReader(m Macro) Option { 19 | if m == nil { 20 | m = readNumber 21 | } 22 | return func(rd *Reader) { 23 | rd.numReader = m 24 | } 25 | } 26 | 27 | // WithSymbolReader sets the symbol reader macro to be used by the Reader. 28 | // Builds a parens.Symbol if nil. 29 | func WithSymbolReader(m Macro) Option { 30 | if m == nil { 31 | return WithBuiltinSymbolReader(defaultSymTable) 32 | } 33 | return func(rd *Reader) { 34 | rd.symReader = m 35 | } 36 | } 37 | 38 | // WithBuiltinSymbolReader configures the default symbol reader with given 39 | // symbol table. 40 | func WithBuiltinSymbolReader(symTable map[string]parens.Any) Option { 41 | m := symbolReader(symTable) 42 | return func(rd *Reader) { 43 | rd.symReader = m 44 | } 45 | } 46 | 47 | func withDefaults(opt []Option) []Option { 48 | return append([]Option{ 49 | WithNumReader(nil), 50 | WithBuiltinSymbolReader(defaultSymTable), 51 | }, opt...) 52 | } 53 | -------------------------------------------------------------------------------- /reader/reader.go: -------------------------------------------------------------------------------- 1 | // Package reader implements a highly customizable and generic reader, reader-macros 2 | // for primitive runtime types and reader-macro factories for collection types like 3 | // vector, map, set. 4 | package reader 5 | 6 | import ( 7 | "bufio" 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "math" 13 | "net" 14 | "os" 15 | "reflect" 16 | "strconv" 17 | "strings" 18 | "unicode" 19 | 20 | "github.com/spy16/parens" 21 | ) 22 | 23 | const dispatchTrigger = '#' 24 | 25 | var ( 26 | escapeMap = map[rune]rune{ 27 | '"': '"', 28 | 'n': '\n', 29 | '\\': '\\', 30 | 't': '\t', 31 | 'a': '\a', 32 | 'f': '\a', 33 | 'r': '\r', 34 | 'b': '\b', 35 | 'v': '\v', 36 | } 37 | 38 | charLiterals = map[string]rune{ 39 | "tab": '\t', 40 | "space": ' ', 41 | "newline": '\n', 42 | "return": '\r', 43 | "backspace": '\b', 44 | "formfeed": '\f', 45 | } 46 | ) 47 | 48 | // New returns a lisp reader instance which can read forms from r. Returned instance 49 | // supports only primitive data types from value package by default. Support for 50 | // custom forms can be added using SetMacro(). File name is inferred from the value & 51 | // type information of 'r' OR can be set manually on the Reader instance returned. 52 | func New(r io.Reader, opts ...Option) *Reader { 53 | rd := &Reader{ 54 | File: inferFileName(r), 55 | rs: bufio.NewReader(r), 56 | macros: map[rune]Macro{ 57 | '"': readString, 58 | ';': readComment, 59 | ':': readKeyword, 60 | '\\': readCharacter, 61 | '(': readList, 62 | ')': UnmatchedDelimiter(), 63 | '\'': quoteFormReader("quote"), 64 | '~': quoteFormReader("unquote"), 65 | '`': quoteFormReader("syntax-quote"), 66 | }, 67 | dispatch: map[rune]Macro{}, 68 | } 69 | 70 | for _, option := range withDefaults(opts) { 71 | option(rd) 72 | } 73 | 74 | return rd 75 | } 76 | 77 | // Reader consumes characters from a stream and parses them into symbolic expressions 78 | // or forms. Reader is customizable through Macro implementations which can be set as 79 | // handler for specific trigger runes. 80 | type Reader struct { 81 | File string 82 | 83 | rs io.RuneReader 84 | buf []rune 85 | line, col int 86 | lastCol int 87 | dispatching bool 88 | dispatch map[rune]Macro 89 | macros map[rune]Macro 90 | numReader, symReader Macro 91 | } 92 | 93 | // All consumes characters from stream until EOF and returns a list of all the forms 94 | // parsed. Any no-op forms (e.g., comment) will not be included in the result. 95 | func (rd *Reader) All() ([]parens.Any, error) { 96 | var forms []parens.Any 97 | 98 | for { 99 | form, err := rd.One() 100 | if err != nil { 101 | if errors.Is(err, io.EOF) { 102 | break 103 | } 104 | return nil, err 105 | } 106 | forms = append(forms, form) 107 | } 108 | 109 | return forms, nil 110 | } 111 | 112 | // One consumes characters from underlying stream until a complete form is parsed and 113 | // returns the form while ignoring the no-op forms like comments. Except EOF, all other 114 | // errors will be wrapped with reader Error type along with the positional information 115 | // obtained using Position(). 116 | func (rd *Reader) One() (parens.Any, error) { 117 | for { 118 | form, err := rd.readOne() 119 | if err != nil { 120 | if errors.Is(err, ErrSkip) { 121 | continue 122 | } 123 | return nil, err 124 | } 125 | return form, nil 126 | } 127 | } 128 | 129 | // IsTerminal returns true if the rune should terminate a form. Macro trigger runes 130 | // defined in the read table and all whitespace characters are considered terminal. 131 | // "," is also considered a whitespace character and hence a terminal. 132 | func (rd *Reader) IsTerminal(r rune) bool { 133 | if isSpace(r) { 134 | return true 135 | } 136 | 137 | if rd.dispatching { 138 | _, found := rd.dispatch[r] 139 | if found { 140 | return true 141 | } 142 | } 143 | 144 | _, found := rd.macros[r] 145 | return found 146 | } 147 | 148 | // SetMacro sets the given reader macro as the handler for init rune in the read table. 149 | // Overwrites if a macro is already present. If the macro value given is nil, entry for 150 | // the init rune will be removed from the read table. isDispatch decides if the macro is 151 | // a dispatch macro and takes effect only after a '#' sign. 152 | func (rd *Reader) SetMacro(init rune, isDispatch bool, macro Macro) { 153 | if isDispatch { 154 | if macro == nil { 155 | delete(rd.dispatch, init) 156 | return 157 | } 158 | rd.dispatch[init] = macro 159 | } else { 160 | if macro == nil { 161 | delete(rd.macros, init) 162 | return 163 | } 164 | rd.macros[init] = macro 165 | } 166 | } 167 | 168 | // NextRune returns next rune from the stream and advances the stream. 169 | func (rd *Reader) NextRune() (rune, error) { 170 | var r rune 171 | if len(rd.buf) > 0 { 172 | r = rd.buf[0] 173 | rd.buf = rd.buf[1:] 174 | } else { 175 | temp, _, err := rd.rs.ReadRune() 176 | if err != nil { 177 | return -1, err 178 | } 179 | r = temp 180 | } 181 | 182 | if r == '\n' { 183 | rd.line++ 184 | rd.lastCol = rd.col 185 | rd.col = 0 186 | } else { 187 | rd.col++ 188 | } 189 | return r, nil 190 | } 191 | 192 | // Unread returns runes consumed from the stream back to the stream. Un-reading more 193 | // runes than read is guaranteed to work but will cause inconsistency in positional 194 | // information of the Reader. 195 | func (rd *Reader) Unread(runes ...rune) { 196 | newLine := false 197 | for _, r := range runes { 198 | if r == '\n' { 199 | newLine = true 200 | break 201 | } 202 | } 203 | 204 | if newLine { 205 | rd.line-- 206 | rd.col = rd.lastCol 207 | } else { 208 | rd.col-- 209 | } 210 | 211 | rd.buf = append(runes, rd.buf...) 212 | } 213 | 214 | // Position returns information about the stream including file name and the position 215 | // of the reader. 216 | func (rd Reader) Position() Position { 217 | file := strings.TrimSpace(rd.File) 218 | return Position{ 219 | File: file, 220 | Ln: rd.line + 1, 221 | Col: rd.col, 222 | } 223 | } 224 | 225 | // SkipSpaces consumes and discards runes from stream repeatedly until a character that 226 | // is not a whitespace is identified. Along with standard unicode whitespace characters, 227 | // "," is also considered a whitespace and discarded. 228 | func (rd *Reader) SkipSpaces() error { 229 | for { 230 | r, err := rd.NextRune() 231 | if err != nil { 232 | return err 233 | } 234 | 235 | if !isSpace(r) { 236 | rd.Unread(r) 237 | break 238 | } 239 | } 240 | 241 | return nil 242 | } 243 | 244 | // Token reads one token from the reader and returns. If init is not -1, it is included 245 | // as first character in the token. 246 | func (rd *Reader) Token(init rune) (string, error) { 247 | var b strings.Builder 248 | if init != -1 { 249 | b.WriteRune(init) 250 | } 251 | 252 | for { 253 | r, err := rd.NextRune() 254 | if err != nil { 255 | if err == io.EOF { 256 | break 257 | } 258 | return b.String(), err 259 | } 260 | 261 | if rd.IsTerminal(r) { 262 | rd.Unread(r) 263 | break 264 | } 265 | 266 | b.WriteRune(r) 267 | } 268 | 269 | return b.String(), nil 270 | } 271 | 272 | // Container reads multiple forms until 'end' rune is reached. Should be used to read 273 | // collection types like List etc. formType is only used to annotate errors. 274 | func (rd Reader) Container(end rune, formType string, f func(parens.Any) error) error { 275 | for { 276 | if err := rd.SkipSpaces(); err != nil { 277 | if err == io.EOF { 278 | return Error{Cause: ErrEOF} 279 | } 280 | return err 281 | } 282 | 283 | r, err := rd.NextRune() 284 | if err != nil { 285 | if err == io.EOF { 286 | return Error{Cause: ErrEOF} 287 | } 288 | return err 289 | } 290 | 291 | if r == end { 292 | break 293 | } 294 | rd.Unread(r) 295 | 296 | expr, err := rd.readOne() 297 | if err != nil { 298 | if err == ErrSkip { 299 | continue 300 | } 301 | return err 302 | } 303 | 304 | // TODO(performance): verify `f` is inlined by the compiler 305 | if err = f(expr); err != nil { 306 | return err 307 | } 308 | } 309 | 310 | return nil 311 | } 312 | 313 | // readOne is same as One() but always returns un-annotated errors. 314 | func (rd *Reader) readOne() (parens.Any, error) { 315 | if err := rd.SkipSpaces(); err != nil { 316 | return nil, err 317 | } 318 | 319 | r, err := rd.NextRune() 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | if unicode.IsNumber(r) { 325 | return rd.numReader(rd, r) 326 | } else if r == '+' || r == '-' { 327 | r2, err := rd.NextRune() 328 | if err != nil && err != io.EOF { 329 | return nil, err 330 | } 331 | 332 | if err != io.EOF { 333 | rd.Unread(r2) 334 | if unicode.IsNumber(r2) { 335 | return rd.numReader(rd, r) 336 | } 337 | } 338 | } 339 | 340 | macro, found := rd.macros[r] 341 | if found { 342 | return macro(rd, r) 343 | } 344 | 345 | if r == dispatchTrigger { 346 | f, err := rd.execDispatch() 347 | if f != nil || err != nil { 348 | return f, err 349 | } 350 | } 351 | 352 | return rd.symReader(rd, r) 353 | } 354 | 355 | func (rd *Reader) execDispatch() (parens.Any, error) { 356 | r2, err := rd.NextRune() 357 | if err != nil { 358 | // ignore the error and let readOne handle it. 359 | return nil, nil 360 | } 361 | 362 | dispatchMacro, found := rd.dispatch[r2] 363 | if !found { 364 | rd.Unread(r2) 365 | return nil, nil 366 | } 367 | 368 | rd.dispatching = true 369 | defer func() { 370 | rd.dispatching = false 371 | }() 372 | 373 | form, err := dispatchMacro(rd, r2) 374 | if err != nil { 375 | return nil, err 376 | } 377 | 378 | return form, nil 379 | } 380 | 381 | func (rd *Reader) annotateErr(err error, beginPos Position, form string) error { 382 | if err == io.EOF || err == ErrSkip { 383 | return err 384 | } 385 | 386 | readErr := Error{} 387 | if e, ok := err.(Error); ok { 388 | readErr = e 389 | } else { 390 | readErr = Error{Cause: err} 391 | } 392 | 393 | readErr.Form = form 394 | readErr.Begin = beginPos 395 | readErr.End = rd.Position() 396 | return readErr 397 | } 398 | 399 | func readUnicodeChar(token string, base int) (parens.Char, error) { 400 | num, err := strconv.ParseInt(token, base, 64) 401 | if err != nil { 402 | return -1, fmt.Errorf("invalid unicode character: '\\%s'", token) 403 | } 404 | 405 | if num < 0 || num >= unicode.MaxRune { 406 | return -1, fmt.Errorf("invalid unicode character: '\\%s'", token) 407 | } 408 | 409 | return parens.Char(num), nil 410 | } 411 | 412 | func parseRadix(numStr string) (parens.Int64, error) { 413 | parts := strings.Split(numStr, "r") 414 | if len(parts) != 2 { 415 | return 0, fmt.Errorf("%w (radix notation): '%s'", ErrNumberFormat, numStr) 416 | } 417 | 418 | base, err := strconv.ParseInt(parts[0], 10, 64) 419 | if err != nil { 420 | return 0, fmt.Errorf("%w (radix notation): '%s'", ErrNumberFormat, numStr) 421 | } 422 | 423 | repr := parts[1] 424 | if base < 0 { 425 | base = -1 * base 426 | repr = "-" + repr 427 | } 428 | 429 | v, err := strconv.ParseInt(repr, int(base), 64) 430 | if err != nil { 431 | return 0, fmt.Errorf("%w (radix notation): '%s'", ErrNumberFormat, numStr) 432 | } 433 | 434 | return parens.Int64(v), nil 435 | } 436 | 437 | func parseScientific(numStr string) (parens.Float64, error) { 438 | parts := strings.Split(numStr, "e") 439 | if len(parts) != 2 { 440 | return 0, fmt.Errorf("%w (scientific notation): '%s'", ErrNumberFormat, numStr) 441 | } 442 | 443 | base, err := strconv.ParseFloat(parts[0], 64) 444 | if err != nil { 445 | return 0, fmt.Errorf("%w (scientific notation): '%s'", ErrNumberFormat, numStr) 446 | } 447 | 448 | pow, err := strconv.ParseInt(parts[1], 10, 64) 449 | if err != nil { 450 | return 0, fmt.Errorf("%w (scientific notation): '%s'", ErrNumberFormat, numStr) 451 | } 452 | 453 | return parens.Float64(base * math.Pow(10, float64(pow))), nil 454 | } 455 | 456 | func getEscape(r rune) (rune, error) { 457 | escaped, found := escapeMap[r] 458 | if !found { 459 | return -1, fmt.Errorf("illegal escape sequence '\\%c'", r) 460 | } 461 | 462 | return escaped, nil 463 | } 464 | 465 | func isSpace(r rune) bool { 466 | return unicode.IsSpace(r) || r == ',' 467 | } 468 | 469 | func inferFileName(rs io.Reader) string { 470 | switch r := rs.(type) { 471 | case *os.File: 472 | return r.Name() 473 | 474 | case *strings.Reader: 475 | return "" 476 | 477 | case *bytes.Reader: 478 | return "" 479 | 480 | case net.Conn: 481 | return fmt.Sprintf("", r.LocalAddr()) 482 | 483 | default: 484 | return fmt.Sprintf("<%s>", reflect.TypeOf(rs)) 485 | } 486 | } 487 | 488 | // Position represents the positional information about a value read 489 | // by reader. 490 | type Position struct { 491 | File string 492 | Ln int 493 | Col int 494 | } 495 | 496 | func (p Position) String() string { 497 | if p.File == "" { 498 | p.File = "" 499 | } 500 | return fmt.Sprintf("%s:%d:%d", p.File, p.Ln, p.Col) 501 | } 502 | -------------------------------------------------------------------------------- /reader/reader_test.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/spy16/parens" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | r io.Reader 18 | fileName string 19 | }{ 20 | { 21 | name: "WithStringReader", 22 | r: strings.NewReader(":test"), 23 | fileName: "", 24 | }, 25 | { 26 | name: "WithBytesReader", 27 | r: bytes.NewReader([]byte(":test")), 28 | fileName: "", 29 | }, 30 | { 31 | name: "WihFile", 32 | r: os.NewFile(0, "test.lisp"), 33 | fileName: "test.lisp", 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | rd := New(tt.r) 40 | if rd == nil { 41 | t.Errorf("New() should return instance of Reader, got nil") 42 | } else if rd.File != tt.fileName { 43 | t.Errorf("File = \"%s\", want = \"%s\"", rd.File, tt.name) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestReader_SetMacro(t *testing.T) { 50 | t.Run("UnsetDefaultMacro", func(t *testing.T) { 51 | rd := New(strings.NewReader("~hello")) 52 | rd.SetMacro('~', false, nil) // remove unquote operator 53 | 54 | want := parens.Symbol("~hello") 55 | 56 | got, err := rd.One() 57 | if err != nil { 58 | t.Errorf("unexpected error: %#v", err) 59 | } 60 | 61 | if !reflect.DeepEqual(got, want) { 62 | t.Errorf("got = %+v, want = %+v", got, want) 63 | } 64 | }) 65 | 66 | t.Run("DispatchMacro", func(t *testing.T) { 67 | rd := New(strings.NewReader("#$123")) 68 | // `#$` returns string "USD" 69 | rd.SetMacro('$', true, func(rd *Reader, init rune) (parens.Any, error) { 70 | return parens.String("USD"), nil 71 | }) // remove unquote operator 72 | 73 | want := parens.String("USD") 74 | 75 | got, err := rd.One() 76 | if err != nil { 77 | t.Errorf("unexpected error: %#v", err) 78 | } 79 | 80 | if !reflect.DeepEqual(got, want) { 81 | t.Errorf("got = %+v, want = %+v", got, want) 82 | } 83 | }) 84 | 85 | t.Run("CustomMacro", func(t *testing.T) { 86 | rd := New(strings.NewReader("~hello")) 87 | rd.SetMacro('~', false, func(rd *Reader, _ rune) (parens.Any, error) { 88 | var ru []rune 89 | for { 90 | r, err := rd.NextRune() 91 | if err != nil { 92 | if err == io.EOF { 93 | break 94 | } 95 | return nil, err 96 | } 97 | 98 | if rd.IsTerminal(r) { 99 | break 100 | } 101 | ru = append(ru, r) 102 | } 103 | 104 | return parens.String(ru), nil 105 | }) // override unquote operator 106 | 107 | want := parens.String("hello") 108 | 109 | got, err := rd.One() 110 | if err != nil { 111 | t.Errorf("unexpected error: %v", err) 112 | } 113 | 114 | if !reflect.DeepEqual(got, want) { 115 | t.Errorf("got = %+v, want = %+v", got, want) 116 | } 117 | }) 118 | } 119 | 120 | func TestReader_All(t *testing.T) { 121 | tests := []struct { 122 | name string 123 | src string 124 | want []parens.Any 125 | wantErr bool 126 | }{ 127 | { 128 | name: "ValidLiteralSample", 129 | src: `123 "Hello World" 12.34 -0xF +010 true nil 0b1010 \a :hello`, 130 | want: []parens.Any{ 131 | parens.Int64(123), 132 | parens.String("Hello World"), 133 | parens.Float64(12.34), 134 | parens.Int64(-15), 135 | parens.Int64(8), 136 | parens.Bool(true), 137 | parens.Nil{}, 138 | parens.Int64(10), 139 | parens.Char('a'), 140 | parens.Keyword("hello"), 141 | }, 142 | }, 143 | { 144 | name: "WithComment", 145 | src: `:valid-keyword ; comment should return errSkip`, 146 | want: []parens.Any{parens.Keyword("valid-keyword")}, 147 | }, 148 | { 149 | name: "UnterminatedString", 150 | src: `:valid-keyword "unterminated string literal`, 151 | wantErr: true, 152 | }, 153 | { 154 | name: "CommentFollowedByForm", 155 | src: `; comment should return errSkip` + "\n" + `:valid-keyword`, 156 | want: []parens.Any{parens.Keyword("valid-keyword")}, 157 | }, 158 | { 159 | name: "UnterminatedList", 160 | src: `:valid-keyword (add 1 2`, 161 | wantErr: true, 162 | }, 163 | { 164 | name: "EOFAfterQuote", 165 | src: `:valid-keyword '`, 166 | wantErr: true, 167 | }, 168 | { 169 | name: "CommentAfterQuote", 170 | src: `:valid-keyword ';hello world`, 171 | wantErr: true, 172 | }, 173 | { 174 | name: "UnbalancedParenthesis", 175 | src: `())`, 176 | wantErr: true, 177 | }, 178 | } 179 | for _, tt := range tests { 180 | t.Run(tt.name, func(t *testing.T) { 181 | got, err := New(strings.NewReader(tt.src)).All() 182 | if (err != nil) != tt.wantErr { 183 | t.Errorf("All() error = %#v, wantErr %#v", err, tt.wantErr) 184 | return 185 | } 186 | if !reflect.DeepEqual(got, tt.want) { 187 | t.Errorf("All() got = %#v, want %#v", got, tt.want) 188 | } 189 | }) 190 | } 191 | } 192 | 193 | func TestReader_One(t *testing.T) { 194 | executeReaderTests(t, []readerTestCase{ 195 | { 196 | name: "Empty", 197 | src: "", 198 | want: nil, 199 | wantErr: true, 200 | }, 201 | { 202 | name: "QuotedEOF", 203 | src: "';comment is a no-op form\n", 204 | wantErr: true, 205 | }, 206 | { 207 | name: "ListEOF", 208 | src: "( 1", 209 | wantErr: true, 210 | }, 211 | { 212 | name: "UnQuote", 213 | src: "~(x 3)", 214 | want: parens.NewList( 215 | parens.Symbol("unquote"), 216 | parens.NewList( 217 | parens.Symbol("x"), 218 | parens.Int64(3), 219 | ), 220 | ), 221 | }, 222 | }) 223 | } 224 | 225 | func TestReader_One_Number(t *testing.T) { 226 | executeReaderTests(t, []readerTestCase{ 227 | { 228 | name: "NumberWithLeadingSpaces", 229 | src: " +1234", 230 | want: parens.Int64(1234), 231 | }, 232 | { 233 | name: "PositiveInt", 234 | src: "+1245", 235 | want: parens.Int64(1245), 236 | }, 237 | { 238 | name: "NegativeInt", 239 | src: "-234", 240 | want: parens.Int64(-234), 241 | }, 242 | { 243 | name: "PositiveFloat", 244 | src: "+1.334", 245 | want: parens.Float64(1.334), 246 | }, 247 | { 248 | name: "NegativeFloat", 249 | src: "-1.334", 250 | want: parens.Float64(-1.334), 251 | }, 252 | { 253 | name: "PositiveHex", 254 | src: "0x124", 255 | want: parens.Int64(0x124), 256 | }, 257 | { 258 | name: "NegativeHex", 259 | src: "-0x124", 260 | want: parens.Int64(-0x124), 261 | }, 262 | { 263 | name: "PositiveOctal", 264 | src: "0123", 265 | want: parens.Int64(0123), 266 | }, 267 | { 268 | name: "NegativeOctal", 269 | src: "-0123", 270 | want: parens.Int64(-0123), 271 | }, 272 | { 273 | name: "PositiveBinary", 274 | src: "0b10", 275 | want: parens.Int64(2), 276 | }, 277 | { 278 | name: "NegativeBinary", 279 | src: "-0b10", 280 | want: parens.Int64(-2), 281 | }, 282 | { 283 | name: "PositiveBase2Radix", 284 | src: "2r10", 285 | want: parens.Int64(2), 286 | }, 287 | { 288 | name: "NegativeBase2Radix", 289 | src: "-2r10", 290 | want: parens.Int64(-2), 291 | }, 292 | { 293 | name: "PositiveBase4Radix", 294 | src: "4r123", 295 | want: parens.Int64(27), 296 | }, 297 | { 298 | name: "NegativeBase4Radix", 299 | src: "-4r123", 300 | want: parens.Int64(-27), 301 | }, 302 | { 303 | name: "ScientificSimple", 304 | src: "1e10", 305 | want: parens.Float64(1e10), 306 | }, 307 | { 308 | name: "ScientificNegativeExponent", 309 | src: "1e-10", 310 | want: parens.Float64(1e-10), 311 | }, 312 | { 313 | name: "ScientificWithDecimal", 314 | src: "1.5e10", 315 | want: parens.Float64(1.5e+10), 316 | }, 317 | { 318 | name: "FloatStartingWith0", 319 | src: "012.3", 320 | want: parens.Float64(012.3), 321 | wantErr: false, 322 | }, 323 | { 324 | name: "InvalidValue", 325 | src: "1ABe13", 326 | wantErr: true, 327 | }, 328 | { 329 | name: "InvalidScientificFormat", 330 | src: "1e13e10", 331 | wantErr: true, 332 | }, 333 | { 334 | name: "InvalidExponent", 335 | src: "1e1.3", 336 | wantErr: true, 337 | }, 338 | { 339 | name: "InvalidRadixFormat", 340 | src: "1r2r3", 341 | wantErr: true, 342 | }, 343 | { 344 | name: "RadixBase3WithDigit4", 345 | src: "-3r1234", 346 | wantErr: true, 347 | }, 348 | { 349 | name: "RadixMissingValue", 350 | src: "2r", 351 | wantErr: true, 352 | }, 353 | { 354 | name: "RadixInvalidBase", 355 | src: "2ar", 356 | wantErr: true, 357 | }, 358 | { 359 | name: "RadixWithFloat", 360 | src: "2.3r4", 361 | wantErr: true, 362 | }, 363 | { 364 | name: "DecimalPointInBinary", 365 | src: "0b1.0101", 366 | wantErr: true, 367 | }, 368 | { 369 | name: "InvalidDigitForOctal", 370 | src: "08", 371 | wantErr: true, 372 | }, 373 | { 374 | name: "IllegalNumberFormat", 375 | src: "9.3.2", 376 | wantErr: true, 377 | }, 378 | }) 379 | } 380 | 381 | func TestReader_One_String(t *testing.T) { 382 | executeReaderTests(t, []readerTestCase{ 383 | { 384 | name: "SimpleString", 385 | src: `"hello"`, 386 | want: parens.String("hello"), 387 | }, 388 | { 389 | name: "EscapeQuote", 390 | src: `"double quote is \""`, 391 | want: parens.String(`double quote is "`), 392 | }, 393 | { 394 | name: "EscapeTab", 395 | src: `"hello\tworld"`, 396 | want: parens.String("hello\tworld"), 397 | }, 398 | { 399 | name: "EscapeSlash", 400 | src: `"hello\\world"`, 401 | want: parens.String(`hello\world`), 402 | }, 403 | { 404 | name: "UnexpectedEOF", 405 | src: `"double quote is`, 406 | wantErr: true, 407 | }, 408 | { 409 | name: "InvalidEscape", 410 | src: `"hello \x world"`, 411 | wantErr: true, 412 | }, 413 | { 414 | name: "EscapeEOF", 415 | src: `"hello\`, 416 | wantErr: true, 417 | }, 418 | }) 419 | } 420 | 421 | func TestReader_One_Keyword(t *testing.T) { 422 | executeReaderTests(t, []readerTestCase{ 423 | { 424 | name: "SimpleASCII", 425 | src: `:test`, 426 | want: parens.Keyword("test"), 427 | }, 428 | { 429 | name: "LeadingTrailingSpaces", 430 | src: " :test ", 431 | want: parens.Keyword("test"), 432 | }, 433 | { 434 | name: "SimpleUnicode", 435 | src: `:∂`, 436 | want: parens.Keyword("∂"), 437 | }, 438 | { 439 | name: "WithSpecialChars", 440 | src: `:this-is-valid?`, 441 | want: parens.Keyword("this-is-valid?"), 442 | }, 443 | { 444 | name: "FollowedByMacroChar", 445 | src: `:this-is-valid'hello`, 446 | want: parens.Keyword("this-is-valid"), 447 | }, 448 | }) 449 | } 450 | 451 | func TestReader_One_Character(t *testing.T) { 452 | executeReaderTests(t, []readerTestCase{ 453 | { 454 | name: "ASCIILetter", 455 | src: `\a`, 456 | want: parens.Char('a'), 457 | }, 458 | { 459 | name: "ASCIIDigit", 460 | src: `\1`, 461 | want: parens.Char('1'), 462 | }, 463 | { 464 | name: "Unicode", 465 | src: `\∂`, 466 | want: parens.Char('∂'), 467 | }, 468 | { 469 | name: "Newline", 470 | src: `\newline`, 471 | want: parens.Char('\n'), 472 | }, 473 | { 474 | name: "FormFeed", 475 | src: `\formfeed`, 476 | want: parens.Char('\f'), 477 | }, 478 | { 479 | name: "Unicode", 480 | src: `\u00AE`, 481 | want: parens.Char('®'), 482 | }, 483 | { 484 | name: "InvalidUnicode", 485 | src: `\uHELLO`, 486 | wantErr: true, 487 | }, 488 | { 489 | name: "OutOfRangeUnicode", 490 | src: `\u-100`, 491 | wantErr: true, 492 | }, 493 | { 494 | name: "UnknownSpecial", 495 | src: `\hello`, 496 | wantErr: true, 497 | }, 498 | { 499 | name: "EOF", 500 | src: `\`, 501 | wantErr: true, 502 | }, 503 | }) 504 | } 505 | 506 | func TestReader_One_Symbol(t *testing.T) { 507 | executeReaderTests(t, []readerTestCase{ 508 | { 509 | name: "SimpleASCII", 510 | src: `hello`, 511 | want: parens.Symbol("hello"), 512 | }, 513 | { 514 | name: "Unicode", 515 | src: `find-∂`, 516 | want: parens.Symbol("find-∂"), 517 | }, 518 | { 519 | name: "SingleChar", 520 | src: `+`, 521 | want: parens.Symbol("+"), 522 | }, 523 | }) 524 | } 525 | 526 | func TestReader_One_List(t *testing.T) { 527 | executeReaderTests(t, []readerTestCase{ 528 | { 529 | name: "EmptyList", 530 | src: `()`, 531 | want: parens.NewList(), 532 | }, 533 | { 534 | name: "ListWithOneEntry", 535 | src: `(help)`, 536 | want: parens.NewList(parens.Symbol("help")), 537 | }, 538 | { 539 | name: "ListWithMultipleEntry", 540 | src: `(+ 0xF 3.1413)`, 541 | want: parens.NewList( 542 | parens.Symbol("+"), 543 | parens.Int64(15), 544 | parens.Float64(3.1413), 545 | ), 546 | }, 547 | { 548 | name: "ListWithCommaSeparator", 549 | src: `(+,0xF,3.1413)`, 550 | want: parens.NewList( 551 | parens.Symbol("+"), 552 | parens.Int64(15), 553 | parens.Float64(3.1413), 554 | ), 555 | }, 556 | { 557 | name: "MultiLine", 558 | src: `(+ 559 | 0xF 560 | 3.1413 561 | )`, 562 | want: parens.NewList( 563 | parens.Symbol("+"), 564 | parens.Int64(15), 565 | parens.Float64(3.1413), 566 | ), 567 | }, 568 | { 569 | name: "MultiLineWithComments", 570 | src: `(+ ; plus operator adds numerical values 571 | 0xF ; hex representation of 15 572 | 3.1413 ; value of math constant pi 573 | )`, 574 | want: parens.NewList( 575 | parens.Symbol("+"), 576 | parens.Int64(15), 577 | parens.Float64(3.1413), 578 | ), 579 | }, 580 | { 581 | name: "UnexpectedEOF", 582 | src: "(+ 1 2 ", 583 | wantErr: true, 584 | }, 585 | }) 586 | } 587 | 588 | type readerTestCase struct { 589 | name string 590 | src string 591 | want parens.Any 592 | wantErr bool 593 | } 594 | 595 | func executeReaderTests(t *testing.T, tests []readerTestCase) { 596 | t.Parallel() 597 | 598 | for _, tt := range tests { 599 | t.Run(tt.name, func(t *testing.T) { 600 | got, err := New(strings.NewReader(tt.src)).One() 601 | if (err != nil) != tt.wantErr { 602 | t.Errorf("One() error = %#v, wantErr %#v", err, tt.wantErr) 603 | return 604 | } 605 | if !reflect.DeepEqual(got, tt.want) { 606 | t.Errorf("One() got = %#v, want %#v", got, tt.want) 607 | } 608 | }) 609 | } 610 | } 611 | -------------------------------------------------------------------------------- /repl/options.go: -------------------------------------------------------------------------------- 1 | package repl 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | 8 | "github.com/spy16/parens/reader" 9 | ) 10 | 11 | // Option implementations can be provided to New() to configure the REPL 12 | // during initialization. 13 | type Option func(repl *REPL) 14 | 15 | // ReaderFactory should return an instance of reader when called. This might 16 | // be called repeatedly. See WithReaderFactory() 17 | type ReaderFactory interface { 18 | NewReader(r io.Reader) *reader.Reader 19 | } 20 | 21 | // ReaderFactoryFunc implements ReaderFactory using a function value. 22 | type ReaderFactoryFunc func(r io.Reader) *reader.Reader 23 | 24 | // NewReader simply calls the wrapped function value and returns the result. 25 | func (factory ReaderFactoryFunc) NewReader(r io.Reader) *reader.Reader { 26 | return factory(r) 27 | } 28 | 29 | // ErrMapper should map a custom Input error to nil to indicate error that 30 | // should be ignored by REPL, EOF to signal end of REPL session and any 31 | // other error to indicate a irrecoverable failure. 32 | type ErrMapper func(err error) error 33 | 34 | // WithInput sets the REPL's input stream. `nil` defaults to bufio.Scanner 35 | // backed by os.Stdin 36 | func WithInput(in Input, mapErr ErrMapper) Option { 37 | if in == nil { 38 | in = &lineReader{ 39 | scanner: bufio.NewScanner(os.Stdin), 40 | out: os.Stdout, 41 | } 42 | } 43 | 44 | if mapErr == nil { 45 | mapErr = func(e error) error { return e } 46 | } 47 | 48 | return func(repl *REPL) { 49 | repl.input = in 50 | repl.mapInputErr = mapErr 51 | } 52 | } 53 | 54 | // WithOutput sets the REPL's output stream.`nil` defaults to stdout. 55 | func WithOutput(w io.Writer) Option { 56 | if w == nil { 57 | w = os.Stdout 58 | } 59 | 60 | return func(repl *REPL) { 61 | repl.output = w 62 | } 63 | } 64 | 65 | // WithBanner sets the REPL's banner which is displayed once when the REPL 66 | // starts. 67 | func WithBanner(banner string) Option { 68 | return func(repl *REPL) { 69 | repl.banner = banner 70 | } 71 | } 72 | 73 | // WithPrompts sets the prompt to be displayed when waiting for user input 74 | // in the REPL. 75 | func WithPrompts(oneLine, multiLine string) Option { 76 | return func(repl *REPL) { 77 | repl.prompt = oneLine 78 | repl.multiPrompt = multiLine 79 | } 80 | } 81 | 82 | // WithReaderFactory can be used set factory function for initializing lisp 83 | // Reader. This is useful when you want REPL to use custom reader instance. 84 | func WithReaderFactory(factory ReaderFactory) Option { 85 | if factory == nil { 86 | factory = ReaderFactoryFunc(func(r io.Reader) *reader.Reader { 87 | return reader.New(r) 88 | }) 89 | } 90 | 91 | return func(repl *REPL) { 92 | repl.factory = factory 93 | } 94 | } 95 | 96 | // WithPrinter sets the print function for the REPL. It is useful for customizing 97 | // how different types should be rendered into human-readable character streams. 98 | // A `nil` value for p defaults to `Renderer`. 99 | func WithPrinter(p Printer) Option { 100 | if p == nil { 101 | p = Renderer{} 102 | } 103 | 104 | return func(repl *REPL) { 105 | repl.printer = p 106 | } 107 | } 108 | 109 | func withDefaults(opts []Option) []Option { 110 | return append([]Option{ 111 | WithInput(nil, nil), 112 | WithOutput(nil), 113 | WithReaderFactory(nil), 114 | WithPrinter(nil), 115 | }, opts...) 116 | } 117 | 118 | type lineReader struct { 119 | scanner *bufio.Scanner 120 | out io.Writer 121 | prompt string 122 | } 123 | 124 | func (lr *lineReader) Readline() (string, error) { 125 | lr.out.Write([]byte(lr.prompt)) 126 | 127 | if !lr.scanner.Scan() { 128 | if lr.scanner.Err() == nil { // scanner swallows EOF 129 | return lr.scanner.Text(), io.EOF 130 | } 131 | 132 | return "", lr.scanner.Err() 133 | } 134 | 135 | return lr.scanner.Text(), nil 136 | } 137 | 138 | // no-op 139 | func (lr *lineReader) SetPrompt(p string) { 140 | lr.prompt = p 141 | } 142 | -------------------------------------------------------------------------------- /repl/printer.go: -------------------------------------------------------------------------------- 1 | package repl 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // Printer can print arbitrary values to output. 9 | type Printer interface { 10 | Fprintln(w io.Writer, val interface{}) error 11 | } 12 | 13 | // BasicPrinter prints the value using fmt.Println. It applies no special formatting. 14 | type BasicPrinter struct{} 15 | 16 | // Fprintln prints val to w. 17 | func (p BasicPrinter) Fprintln(w io.Writer, val interface{}) error { 18 | _, err := fmt.Fprintln(w, val) 19 | return err 20 | } 21 | 22 | // Renderer pretty-prints the value. It checks if the value implements any of the 23 | // following interfaces (in decreasing order of preference) before printing the default 24 | // Go value. 25 | // 26 | // 1. fmt.Formatter 27 | // 2. fmt.Stringer 28 | // 3. SExpr 29 | type Renderer struct{} 30 | 31 | // SExpr can render a parseable s-expression 32 | type SExpr interface { 33 | // SExpr returns a string representation of the value, suitable for parsing with 34 | // reader.Reader. 35 | SExpr() (string, error) 36 | } 37 | 38 | // Fprintln prints val to w. 39 | func (r Renderer) Fprintln(w io.Writer, val interface{}) (err error) { 40 | switch v := val.(type) { 41 | case fmt.Formatter: 42 | _, err = fmt.Fprintf(w, "%+v\n", v) 43 | case fmt.Stringer: 44 | _, err = fmt.Fprintf(w, "%s\n", v) 45 | case SExpr: 46 | var s string 47 | if s, err = v.SExpr(); err == nil { 48 | _, err = fmt.Fprintln(w, s) 49 | } 50 | default: 51 | _, err = fmt.Fprintf(w, "%v\n", val) 52 | } 53 | 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /repl/repl.go: -------------------------------------------------------------------------------- 1 | // Package repl provides facilities to build an interactive REPL using parens. 2 | package repl 3 | 4 | import ( 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "strings" 10 | 11 | "github.com/spy16/parens" 12 | "github.com/spy16/parens/reader" 13 | ) 14 | 15 | // New returns a new instance of REPL with given parens Env. Option values 16 | // can be used to configure REPL input, output etc. 17 | func New(env *parens.Env, opts ...Option) *REPL { 18 | repl := &REPL{ 19 | rootEnv: env, 20 | currentNS: func() string { return "" }, 21 | } 22 | 23 | for _, option := range withDefaults(opts) { 24 | option(repl) 25 | } 26 | 27 | return repl 28 | } 29 | 30 | // REPL implements a read-eval-print loop for a generic Runtime. 31 | type REPL struct { 32 | rootEnv *parens.Env 33 | input Input 34 | output io.Writer 35 | mapInputErr ErrMapper 36 | currentNS func() string 37 | factory ReaderFactory 38 | 39 | banner string 40 | prompt string 41 | multiPrompt string 42 | 43 | printer Printer 44 | } 45 | 46 | // Input implementation is used by REPL to read user-input. See WithInput() 47 | // REPL option to configure an Input. 48 | type Input interface { 49 | SetPrompt(string) 50 | Readline() (string, error) 51 | } 52 | 53 | // Loop starts the read-eval-print loop. Loop runs until context is cancelled 54 | // or input stream returns an irrecoverable error (See WithInput()). 55 | func (repl *REPL) Loop(ctx context.Context) error { 56 | repl.printBanner() 57 | repl.setPrompt(false) 58 | 59 | for ctx.Err() == nil { 60 | err := repl.readEvalPrint() 61 | if err != nil { 62 | if err == io.EOF { 63 | return nil 64 | } 65 | 66 | return err 67 | } 68 | } 69 | 70 | return ctx.Err() 71 | } 72 | 73 | // readEval reads one form from the input, evaluates it and prints the result. 74 | func (repl *REPL) readEvalPrint() error { 75 | forms, err := repl.read() 76 | if err != nil { 77 | switch err.(type) { 78 | case reader.Error: 79 | _ = repl.print(err) 80 | default: 81 | return err 82 | } 83 | } 84 | 85 | if len(forms) == 0 { 86 | return nil 87 | } 88 | 89 | res, err := parens.EvalAll(repl.rootEnv, forms) 90 | if err != nil { 91 | return repl.print(err) 92 | } 93 | if len(res) == 0 { 94 | return repl.print(nil) 95 | } 96 | 97 | return repl.print(res[len(res)-1]) 98 | } 99 | 100 | func (repl *REPL) Write(b []byte) (int, error) { 101 | return repl.output.Write(b) 102 | } 103 | 104 | func (repl *REPL) print(v interface{}) error { 105 | if e, ok := v.(reader.Error); ok { 106 | s := fmt.Sprintf("%v (begin=%s, end=%s)", e, e.Begin, e.End) 107 | return repl.printer.Fprintln(repl.output, s) 108 | } 109 | return repl.printer.Fprintln(repl.output, v) 110 | } 111 | 112 | func (repl *REPL) read() ([]parens.Any, error) { 113 | var src string 114 | lineNo := 1 115 | 116 | for { 117 | repl.setPrompt(lineNo > 1) 118 | 119 | line, err := repl.input.Readline() 120 | err = repl.mapInputErr(err) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | src += line + "\n" 126 | 127 | if strings.TrimSpace(src) == "" { 128 | return nil, nil 129 | } 130 | 131 | rd := repl.factory.NewReader(strings.NewReader(src)) 132 | rd.File = "REPL" 133 | 134 | form, err := rd.All() 135 | if err != nil { 136 | if errors.Is(err, reader.ErrEOF) { 137 | lineNo++ 138 | continue 139 | } 140 | 141 | return nil, err 142 | } 143 | 144 | return form, nil 145 | } 146 | } 147 | 148 | func (repl *REPL) setPrompt(multiline bool) { 149 | if repl.prompt == "" { 150 | return 151 | } 152 | 153 | nsPrefix := repl.currentNS() 154 | prompt := repl.prompt 155 | 156 | if multiline { 157 | nsPrefix = strings.Repeat(" ", len(nsPrefix)+1) 158 | prompt = repl.multiPrompt 159 | } 160 | 161 | repl.input.SetPrompt(fmt.Sprintf("%s%s ", nsPrefix, prompt)) 162 | } 163 | 164 | func (repl *REPL) printBanner() { 165 | if repl.banner != "" { 166 | fmt.Println(repl.banner) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /special.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var ( 10 | _ = ParseSpecial(parseDoExpr) 11 | _ = ParseSpecial(parseIfExpr) 12 | _ = ParseSpecial(parseGoExpr) 13 | _ = ParseSpecial(parseDefExpr) 14 | _ = ParseSpecial(parseQuoteExpr) 15 | ) 16 | 17 | func parseDoExpr(env *Env, args Seq) (Expr, error) { 18 | var de DoExpr 19 | err := ForEach(args, func(item Any) (bool, error) { 20 | expr, err := env.Analyze(item) 21 | if err != nil { 22 | return true, err 23 | } 24 | de.Exprs = append(de.Exprs, expr) 25 | return false, nil 26 | }) 27 | return de, err 28 | } 29 | 30 | func parseIfExpr(env *Env, args Seq) (Expr, error) { 31 | count, err := args.Count() 32 | if err != nil { 33 | return nil, err 34 | } else if count != 2 && count != 3 { 35 | return nil, Error{ 36 | Cause: errors.New("invalid if form"), 37 | Message: fmt.Sprintf("requires 2 or 3 arguments, got %d", count), 38 | } 39 | } 40 | 41 | exprs := [3]Expr{} 42 | for i := 0; i < count; i++ { 43 | f, err := args.First() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | expr, err := env.Analyze(f) 49 | if err != nil { 50 | return nil, err 51 | } 52 | exprs[i] = expr 53 | 54 | args, err = args.Next() 55 | if err != nil { 56 | return nil, err 57 | } 58 | } 59 | 60 | return &IfExpr{ 61 | Test: exprs[0], 62 | Then: exprs[1], 63 | Else: exprs[2], 64 | }, nil 65 | } 66 | 67 | func parseQuoteExpr(_ *Env, args Seq) (Expr, error) { 68 | if count, err := args.Count(); err != nil { 69 | return nil, err 70 | } else if count != 1 { 71 | return nil, Error{ 72 | Cause: errors.New("invalid quote form"), 73 | Message: fmt.Sprintf("requires exactly 1 argument, got %d", count), 74 | } 75 | } 76 | 77 | first, err := args.First() 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | return QuoteExpr{ 83 | Form: first, 84 | }, nil 85 | } 86 | 87 | func parseDefExpr(env *Env, args Seq) (Expr, error) { 88 | if count, err := args.Count(); err != nil { 89 | return nil, err 90 | } else if count != 2 { 91 | return nil, Error{ 92 | Cause: errors.New("invalid def form"), 93 | Message: fmt.Sprintf("requires exactly 2 arguments, got %d", count), 94 | } 95 | } 96 | 97 | first, err := args.First() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | sym, ok := first.(Symbol) 103 | if !ok { 104 | return nil, Error{ 105 | Cause: errors.New("invalid def form"), 106 | Message: fmt.Sprintf("first arg must be symbol, not '%s'", reflect.TypeOf(first)), 107 | } 108 | } 109 | 110 | rest, err := args.Next() 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | second, err := rest.First() 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | res, err := env.Analyze(second) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return &DefExpr{ 126 | Env: env, 127 | Name: string(sym), 128 | Value: res, 129 | }, nil 130 | } 131 | 132 | func parseGoExpr(env *Env, args Seq) (Expr, error) { 133 | v, err := args.First() 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | if v == nil { 139 | return nil, Error{ 140 | Cause: errors.New("go expr requires exactly one argument"), 141 | } 142 | } 143 | 144 | return GoExpr{Env: env, Value: v}, nil 145 | } 146 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // EvalAll evaluates each value in the list against the given env and returns 10 | // a list of resultant values. 11 | func EvalAll(env *Env, vals []Any) ([]Any, error) { 12 | res := make([]Any, 0, len(vals)) 13 | for _, form := range vals { 14 | form, err := env.Eval(form) 15 | if err != nil { 16 | return nil, err 17 | } 18 | res = append(res, form) 19 | } 20 | return res, nil 21 | } 22 | 23 | // IsNil returns true if value is native go `nil` or `Nil{}`. 24 | func IsNil(v Any) bool { 25 | if v == nil { 26 | return true 27 | } 28 | _, isNilType := v.(Nil) 29 | return isNilType 30 | } 31 | 32 | // IsTruthy returns true if the value has a logical vale of `true`. 33 | func IsTruthy(v Any) bool { 34 | if IsNil(v) { 35 | return false 36 | } 37 | rv := reflect.ValueOf(v) 38 | if rv.Kind() == reflect.Bool { 39 | return rv.Bool() 40 | } 41 | 42 | return true 43 | } 44 | 45 | // SeqString returns a string representation for the sequence with given prefix 46 | // suffix and separator. 47 | func SeqString(seq Seq, begin, end, sep string) (string, error) { 48 | var b strings.Builder 49 | b.WriteString(begin) 50 | err := ForEach(seq, func(item Any) (bool, error) { 51 | if sxpr, ok := item.(SExpressable); ok { 52 | s, err := sxpr.SExpr() 53 | if err != nil { 54 | return false, err 55 | } 56 | b.WriteString(s) 57 | 58 | } else { 59 | b.WriteString(fmt.Sprintf("%#v", item)) 60 | } 61 | 62 | b.WriteString(sep) 63 | return false, nil 64 | }) 65 | 66 | if err != nil { 67 | return "", err 68 | } 69 | 70 | return strings.TrimRight(b.String(), sep) + end, err 71 | } 72 | 73 | // ForEach reads from the sequence and calls the given function for each item. 74 | // Function can return true to stop the iteration. 75 | func ForEach(seq Seq, call func(item Any) (bool, error)) (err error) { 76 | var v Any 77 | var done bool 78 | for seq != nil { 79 | if v, err = seq.First(); err != nil || v == nil { 80 | break 81 | } 82 | 83 | if done, err = call(v); err != nil || done { 84 | break 85 | } 86 | 87 | if seq, err = seq.Next(); err != nil { 88 | break 89 | } 90 | } 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package parens_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/spy16/parens" 8 | ) 9 | 10 | func TestEvalAll(t *testing.T) { 11 | t.Parallel() 12 | 13 | table := []struct { 14 | title string 15 | env *parens.Env 16 | vals []parens.Any 17 | want []parens.Any 18 | wantErr bool 19 | }{ 20 | { 21 | title: "EmptyList", 22 | env: parens.New(), 23 | vals: nil, 24 | want: []parens.Any{}, 25 | }, 26 | { 27 | title: "EvalFails", 28 | env: parens.New(), 29 | vals: []parens.Any{parens.Symbol("foo")}, 30 | wantErr: true, 31 | }, 32 | { 33 | title: "Success", 34 | env: parens.New(), 35 | vals: []parens.Any{parens.String("foo"), parens.Keyword("hello")}, 36 | want: []parens.Any{parens.String("foo"), parens.Keyword("hello")}, 37 | }, 38 | } 39 | 40 | for _, tt := range table { 41 | t.Run(tt.title, func(t *testing.T) { 42 | got, err := parens.EvalAll(tt.env, tt.vals) 43 | if (err != nil) != tt.wantErr { 44 | t.Errorf("EvalAll() error = %#v, wantErr %#v", err, tt.wantErr) 45 | return 46 | } 47 | if !reflect.DeepEqual(got, tt.want) { 48 | t.Errorf("EvalAll() got = %#v, want %#v", got, tt.want) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | package parens 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | ) 8 | 9 | var ( 10 | _ Any = Nil{} 11 | _ Any = Int64(0) 12 | _ Any = Float64(1.123123) 13 | _ Any = Bool(true) 14 | _ Any = Char('∂') 15 | _ Any = String("specimen") 16 | _ Any = Symbol("specimen") 17 | _ Any = Keyword("specimen") 18 | _ Any = (*LinkedList)(nil) 19 | 20 | _ Seq = (*LinkedList)(nil) 21 | ) 22 | 23 | // Comparable values define a partial ordering. 24 | type Comparable interface { 25 | // Comp(pare) the value to another value. Returns: 26 | // 27 | // * 0 if v == other 28 | // * 1 if v > other 29 | // * -1 if v < other 30 | // 31 | // If the values are not comparable, ErrIncomparableTypes is returned. 32 | Comp(other Any) (int, error) 33 | } 34 | 35 | // EqualityProvider asserts equality between two values. 36 | type EqualityProvider interface { 37 | Equals(other Any) (bool, error) 38 | } 39 | 40 | // Eq returns true if two values are equal. 41 | func Eq(a, b Any) (bool, error) { 42 | if eq, ok := a.(EqualityProvider); ok { 43 | return eq.Equals(b) 44 | } else if cmp, ok := a.(Comparable); ok { 45 | val, err := cmp.Comp(b) 46 | return val == 0, err 47 | } else if cmp, ok := b.(Comparable); ok { 48 | val, err := cmp.Comp(a) 49 | return val == 0, err 50 | } 51 | 52 | return false, nil 53 | } 54 | 55 | // Lt returns true if a < b 56 | func Lt(a, b Any) (bool, error) { 57 | if acmp, ok := a.(Comparable); ok { 58 | i, err := acmp.Comp(b) 59 | return i == -1, err 60 | } 61 | 62 | return false, ErrIncomparableTypes 63 | } 64 | 65 | // Gt returns true if a > b 66 | func Gt(a, b Any) (bool, error) { 67 | if acmp, ok := a.(Comparable); ok { 68 | i, err := acmp.Comp(b) 69 | return i == 1, err 70 | } 71 | 72 | return false, ErrIncomparableTypes 73 | } 74 | 75 | // Le returns true if a <= b 76 | func Le(a, b Any) (bool, error) { 77 | if acmp, ok := a.(Comparable); ok { 78 | i, err := acmp.Comp(b) 79 | return i <= 0, err 80 | } 81 | 82 | return false, ErrIncomparableTypes 83 | } 84 | 85 | // Ge returns true if a >= b 86 | func Ge(a, b Any) (bool, error) { 87 | if acmp, ok := a.(Comparable); ok { 88 | i, err := acmp.Comp(b) 89 | return i >= 0, err 90 | } 91 | 92 | return false, ErrIncomparableTypes 93 | } 94 | 95 | // Cons returns a new seq with `v` added as the first and `seq` as the rest. 96 | // seq can be nil as well. 97 | func Cons(v Any, seq Seq) (Seq, error) { 98 | newSeq := &LinkedList{ 99 | first: v, 100 | rest: seq, 101 | count: 1, 102 | } 103 | 104 | if seq != nil { 105 | cnt, err := seq.Count() 106 | if err != nil { 107 | return nil, err 108 | } 109 | newSeq.count = cnt + 1 110 | } 111 | 112 | return newSeq, nil 113 | } 114 | 115 | // NewList returns a new linked-list containing given values. 116 | func NewList(items ...Any) Seq { 117 | if len(items) == 0 { 118 | return Seq((*LinkedList)(nil)) 119 | } 120 | 121 | var err error 122 | lst := Seq(&LinkedList{}) 123 | for i := len(items) - 1; i >= 0; i-- { 124 | if lst, err = Cons(items[i], lst); err != nil { 125 | panic(err) 126 | } 127 | } 128 | 129 | return lst 130 | } 131 | 132 | // Nil represents the Value 'nil'. 133 | type Nil struct{} 134 | 135 | // SExpr returns a valid s-expression representing Nil. 136 | func (Nil) SExpr() (string, error) { return "nil", nil } 137 | 138 | // Equals returns true IFF other is nil. 139 | func (Nil) Equals(other Any) (bool, error) { return IsNil(other), nil } 140 | 141 | func (Nil) String() string { return "nil" } 142 | 143 | // Int64 represents a 64-bit integer Value. 144 | type Int64 int64 145 | 146 | // SExpr returns a valid s-expression representing Int64. 147 | func (i64 Int64) SExpr() (string, error) { return i64.String(), nil } 148 | 149 | // Equals returns true if 'other' is also an integer and has same Value. 150 | func (i64 Int64) Equals(other Any) (bool, error) { 151 | val, ok := other.(Int64) 152 | return ok && (val == i64), nil 153 | } 154 | 155 | // Comp performs comparison against another Int64. 156 | func (i64 Int64) Comp(other Any) (int, error) { 157 | if n, ok := other.(Int64); ok { 158 | switch { 159 | case i64 > n: 160 | return 1, nil 161 | case i64 < n: 162 | return -1, nil 163 | default: 164 | return 0, nil 165 | } 166 | } 167 | 168 | return 0, ErrIncomparableTypes 169 | } 170 | 171 | func (i64 Int64) String() string { return strconv.Itoa(int(i64)) } 172 | 173 | // Float64 represents a 64-bit double precision floating point Value. 174 | type Float64 float64 175 | 176 | // SExpr returns a valid s-expression representing Float64. 177 | func (f64 Float64) SExpr() (string, error) { return f64.String(), nil } 178 | 179 | // Equals returns true if 'other' is also a float and has same Value. 180 | func (f64 Float64) Equals(other Any) (bool, error) { 181 | val, ok := other.(Float64) 182 | return ok && (val == f64), nil 183 | } 184 | 185 | // Comp performs comparison against another Float64. 186 | func (f64 Float64) Comp(other Any) (int, error) { 187 | if n, ok := other.(Float64); ok { 188 | switch { 189 | case f64 > n: 190 | return 1, nil 191 | case f64 < n: 192 | return -1, nil 193 | default: 194 | return 0, nil 195 | } 196 | } 197 | 198 | return 0, ErrIncomparableTypes 199 | } 200 | 201 | func (f64 Float64) String() string { 202 | if math.Abs(float64(f64)) >= 1e16 { 203 | return fmt.Sprintf("%e", f64) 204 | } 205 | return fmt.Sprintf("%f", f64) 206 | } 207 | 208 | // Bool represents a boolean Value. 209 | type Bool bool 210 | 211 | // SExpr returns a valid s-expression representing Bool. 212 | func (b Bool) SExpr() (string, error) { return b.String(), nil } 213 | 214 | // Equals returns true if 'other' is a boolean and has same logical Value. 215 | func (b Bool) Equals(other Any) (bool, error) { 216 | val, ok := other.(Bool) 217 | return ok && (val == b), nil 218 | } 219 | 220 | func (b Bool) String() string { 221 | if b { 222 | return "true" 223 | } 224 | return "false" 225 | } 226 | 227 | // Char represents a Unicode character. 228 | type Char rune 229 | 230 | // SExpr returns a valid s-expression representing Char. 231 | func (char Char) SExpr() (string, error) { 232 | return fmt.Sprintf("\\%c", char), nil 233 | } 234 | 235 | // Equals returns true if the other Value is also a character and has same Value. 236 | func (char Char) Equals(other Any) (bool, error) { 237 | val, isChar := other.(Char) 238 | return isChar && (val == char), nil 239 | } 240 | 241 | func (char Char) String() string { return fmt.Sprintf("\\%c", char) } 242 | 243 | // String represents a string of characters. 244 | type String string 245 | 246 | // SExpr returns a valid s-expression representing String. 247 | func (str String) SExpr() (string, error) { return str.String(), nil } 248 | 249 | // Equals returns true if 'other' is string and has same Value. 250 | func (str String) Equals(other Any) (bool, error) { 251 | otherStr, isStr := other.(String) 252 | return isStr && (otherStr == str), nil 253 | } 254 | 255 | func (str String) String() string { return fmt.Sprintf("\"%s\"", string(str)) } 256 | 257 | // Symbol represents a lisp symbol Value. 258 | type Symbol string 259 | 260 | // SExpr returns a valid s-expression representing Symbol. 261 | func (sym Symbol) SExpr() (string, error) { return string(sym), nil } 262 | 263 | // Equals returns true if the other Value is also a symbol and has same Value. 264 | func (sym Symbol) Equals(other Any) (bool, error) { 265 | otherSym, isSym := other.(Symbol) 266 | return isSym && (sym == otherSym), nil 267 | } 268 | 269 | func (sym Symbol) String() string { return string(sym) } 270 | 271 | // Keyword represents a keyword Value. 272 | type Keyword string 273 | 274 | // SExpr returns a valid s-expression representing Keyword. 275 | func (kw Keyword) SExpr() (string, error) { return kw.String(), nil } 276 | 277 | // Equals returns true if the other Value is keyword and has same Value. 278 | func (kw Keyword) Equals(other Any) (bool, error) { 279 | otherKW, isKeyword := other.(Keyword) 280 | return isKeyword && (otherKW == kw), nil 281 | } 282 | 283 | func (kw Keyword) String() string { return fmt.Sprintf(":%s", string(kw)) } 284 | 285 | // LinkedList implements an immutable Seq using linked-list data structure. 286 | type LinkedList struct { 287 | count int 288 | first Any 289 | rest Seq 290 | } 291 | 292 | // SExpr returns a valid s-expression for LinkedList. 293 | func (ll *LinkedList) SExpr() (string, error) { 294 | if ll == nil { 295 | return "()", nil 296 | } 297 | 298 | return SeqString(ll, "(", ")", " ") 299 | } 300 | 301 | // Equals returns true if the other value is a LinkedList and contains the same 302 | // values. 303 | func (ll *LinkedList) Equals(other Any) (eq bool, err error) { 304 | o, ok := other.(*LinkedList) 305 | if !ok || o.count != ll.count { 306 | return 307 | } 308 | 309 | var s Seq = ll 310 | err = ForEach(o, func(any Any) (bool, error) { 311 | v, _ := s.First() 312 | 313 | veq, ok := v.(EqualityProvider) 314 | if !ok { 315 | return false, nil 316 | } 317 | 318 | if eq, err = veq.Equals(any); err != nil || !eq { 319 | return true, err 320 | } 321 | 322 | s, _ = s.Next() 323 | return false, nil 324 | }) 325 | 326 | return 327 | } 328 | 329 | // Conj returns a new list with all the items added at the head of the list. 330 | func (ll *LinkedList) Conj(items ...Any) (res Seq, err error) { 331 | if ll == nil { 332 | res = &LinkedList{} 333 | } else { 334 | res = ll 335 | } 336 | 337 | for _, item := range items { 338 | if res, err = Cons(item, res); err != nil { 339 | break 340 | } 341 | } 342 | 343 | return 344 | } 345 | 346 | // First returns the head or first item of the list. 347 | func (ll *LinkedList) First() (Any, error) { 348 | if ll == nil { 349 | return nil, nil 350 | } 351 | return ll.first, nil 352 | } 353 | 354 | // Next returns the tail of the list. 355 | func (ll *LinkedList) Next() (Seq, error) { 356 | if ll == nil { 357 | return nil, nil 358 | } 359 | return ll.rest, nil 360 | } 361 | 362 | // Count returns the number of the list. 363 | func (ll *LinkedList) Count() (int, error) { 364 | if ll == nil { 365 | return 0, nil 366 | } 367 | 368 | return ll.count, nil 369 | } 370 | -------------------------------------------------------------------------------- /values_test.go: -------------------------------------------------------------------------------- 1 | package parens_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spy16/parens" 7 | ) 8 | 9 | func TestComp(t *testing.T) { 10 | for _, tt := range []struct { 11 | desc string 12 | a, b parens.Any 13 | op func(a, b parens.Any) (bool, error) 14 | want bool 15 | wantErr bool 16 | }{ 17 | // Nil 18 | { 19 | desc: "nil == nil", 20 | a: parens.Nil{}, 21 | b: parens.Nil{}, 22 | op: parens.Eq, 23 | want: true, 24 | }, 25 | { 26 | desc: "nil == false", 27 | a: parens.Nil{}, 28 | b: parens.Bool(false), 29 | op: parens.Eq, 30 | }, 31 | 32 | // Bool 33 | { 34 | desc: "true == true", 35 | a: parens.Bool(true), 36 | b: parens.Bool(true), 37 | op: parens.Eq, 38 | want: true, 39 | }, 40 | { 41 | desc: "false == false", 42 | a: parens.Bool(true), 43 | b: parens.Bool(true), 44 | op: parens.Eq, 45 | want: true, 46 | }, 47 | { 48 | desc: "false == true", 49 | a: parens.Bool(false), 50 | b: parens.Bool(true), 51 | op: parens.Eq, 52 | }, 53 | 54 | // Int64 55 | { 56 | desc: "0 == 0", 57 | a: parens.Int64(0), 58 | b: parens.Int64(0), 59 | op: parens.Eq, 60 | want: true, 61 | }, 62 | { 63 | desc: "0 < 1", 64 | a: parens.Int64(0), 65 | b: parens.Int64(1), 66 | op: parens.Lt, 67 | want: true, 68 | }, 69 | { 70 | desc: "1 > 0", 71 | a: parens.Int64(1), 72 | b: parens.Int64(0), 73 | op: parens.Gt, 74 | want: true, 75 | }, 76 | { 77 | desc: "0 <= 1", 78 | a: parens.Int64(0), 79 | b: parens.Int64(1), 80 | op: parens.Le, 81 | want: true, 82 | }, 83 | { 84 | desc: "1 >= 0", 85 | a: parens.Int64(1), 86 | b: parens.Int64(0), 87 | op: parens.Ge, 88 | want: true, 89 | }, 90 | { 91 | desc: "0 <= 0", 92 | a: parens.Int64(0), 93 | b: parens.Int64(0), 94 | op: parens.Le, 95 | want: true, 96 | }, 97 | { 98 | desc: "0 >= 0", 99 | a: parens.Int64(0), 100 | b: parens.Int64(0), 101 | op: parens.Ge, 102 | want: true, 103 | }, 104 | 105 | // Float64 106 | { 107 | desc: "0. == 0.", 108 | a: parens.Float64(0), 109 | b: parens.Float64(0), 110 | op: parens.Eq, 111 | want: true, 112 | }, 113 | { 114 | desc: "0. < 1.", 115 | a: parens.Float64(0), 116 | b: parens.Float64(1), 117 | op: parens.Lt, 118 | want: true, 119 | }, 120 | { 121 | desc: "1. > 0.", 122 | a: parens.Float64(1), 123 | b: parens.Float64(0), 124 | op: parens.Gt, 125 | want: true, 126 | }, 127 | { 128 | desc: "0. <= 1.", 129 | a: parens.Float64(0), 130 | b: parens.Float64(1), 131 | op: parens.Le, 132 | want: true, 133 | }, 134 | { 135 | desc: "1. >= 0.", 136 | a: parens.Float64(1), 137 | b: parens.Float64(0), 138 | op: parens.Ge, 139 | want: true, 140 | }, 141 | { 142 | desc: "0. <= 0.", 143 | a: parens.Float64(0), 144 | b: parens.Float64(0), 145 | op: parens.Le, 146 | want: true, 147 | }, 148 | { 149 | desc: "0. >= 0.", 150 | a: parens.Float64(0), 151 | b: parens.Float64(0), 152 | op: parens.Ge, 153 | want: true, 154 | }, 155 | { 156 | // LinkedList 157 | desc: "(1 2 3) == (1 2 3)", 158 | a: parens.NewList(parens.Int64(1), parens.Int64(2), parens.Int64(3)), 159 | b: parens.NewList(parens.Int64(1), parens.Int64(2), parens.Int64(3)), 160 | op: parens.Eq, 161 | want: true, 162 | }, 163 | { 164 | // LinkedList 165 | desc: "(1 2 3) == (1 2 nil)", 166 | a: parens.NewList(parens.Int64(1), parens.Int64(2), parens.Int64(3)), 167 | b: parens.NewList(parens.Int64(1), parens.Int64(2), parens.Nil{}), 168 | op: parens.Eq, 169 | }, 170 | } { 171 | t.Run(tt.desc, func(t *testing.T) { 172 | got, err := tt.op(tt.a, tt.b) 173 | if (err != nil) != tt.wantErr { 174 | t.Errorf("%s error = %#v, wantErr %#v", tt.desc, err, tt.wantErr) 175 | return 176 | } 177 | 178 | if got != tt.want { 179 | t.Errorf("%s returned %t, expected %t", tt.desc, got, tt.want) 180 | } 181 | }) 182 | } 183 | } 184 | --------------------------------------------------------------------------------