├── .gitattributes ├── meta ├── logo.png └── logo.svg ├── wasm ├── .gitignore ├── README.md ├── package.json ├── Makefile ├── index.js ├── main.go └── index.html ├── test ├── hello.lg ├── interop.lg ├── fib.lg ├── types.lg ├── async.lg ├── primes.lg ├── fun.lg ├── values.lg ├── perf.lg ├── simple.lg ├── scope.lg ├── language_test.go ├── recur.lg └── destructure.lg ├── examples ├── factorial.lg ├── goroutines.lg ├── server2.lg ├── server.lg └── gol.lg ├── .idea └── .gitignore ├── pkg ├── compiler │ ├── init.go │ ├── eval.go │ ├── cell.go │ ├── errors.go │ ├── reader_test.go │ └── compiler_test.go ├── rt │ ├── os.go │ ├── json.go │ ├── http.go │ └── core │ │ └── core.lg ├── vm │ ├── constpool.go │ ├── constpool_test.go │ ├── void.go │ ├── int.go │ ├── char.go │ ├── bool.go │ ├── chan.go │ ├── regex.go │ ├── atom.go │ ├── iterate.go │ ├── var.go │ ├── symbol.go │ ├── nil.go │ ├── repeat.go │ ├── errors.go │ ├── keyword.go │ ├── string.go │ ├── boxed.go │ ├── range.go │ ├── namespace.go │ ├── native_func.go │ ├── set.go │ ├── list.go │ ├── vector.go │ ├── value.go │ ├── vm_test.go │ ├── map.go │ ├── func.go │ └── vm.go ├── errors │ └── errors.go ├── api │ ├── api.go │ └── interop_test.go └── nrepl │ └── server.go ├── Dockerfile ├── Makefile ├── go.mod ├── .github └── workflows │ ├── go.yml │ ├── pages.yml │ └── codeql-analysis.yml ├── LICENSE ├── go.sum ├── .gitignore ├── README.md └── lg.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lg linguist-language=Clojure -------------------------------------------------------------------------------- /meta/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nooga/let-go/HEAD/meta/logo.png -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | letgo.wasm 3 | wasm_exec.js 4 | static/ 5 | logo.svg -------------------------------------------------------------------------------- /wasm/README.md: -------------------------------------------------------------------------------- 1 | # WASM let-go 2 | 3 | This is a tiny web let-go REPL. 4 | 5 | Use `make run` to try it out. 6 | -------------------------------------------------------------------------------- /wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@fastify/static": "^6.5.1", 4 | "fastify": "^4.10.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/hello.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.hello) 7 | 8 | (test "hello world" 9 | (nil? (println "hello world"))) -------------------------------------------------------------------------------- /examples/factorial.lg: -------------------------------------------------------------------------------- 1 | (ns factorial) 2 | 3 | (defn fact-h [n acc] 4 | (if (zero? n) 5 | acc 6 | (fact-h (dec n) (* acc n)))) 7 | 8 | (defn fac [n] 9 | (fact-h n 1)) 10 | 11 | (println (fac 20)) 12 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /pkg/compiler/init.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | func init() { 9 | readerInit() 10 | compilerInit() 11 | evalInit() 12 | } 13 | -------------------------------------------------------------------------------- /test/interop.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.interop) 7 | 8 | (test "time.Time example" 9 | (let [x (now) 10 | y (now)] 11 | (.After y x))) -------------------------------------------------------------------------------- /examples/goroutines.lg: -------------------------------------------------------------------------------- 1 | (ns goroutines) 2 | 3 | (def n 200000) 4 | 5 | (let [begin (now) 6 | state (atom 0)] 7 | (dotimes [_ n] 8 | (go 9 | (swap! state inc))) 10 | (while (< @state n)) 11 | (println "Spawned" @state "go blocks and finished in" (.Sub (now) begin))) -------------------------------------------------------------------------------- /test/fib.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.fib) 7 | 8 | (defn fib [n] 9 | (cond (= n 0) 1 10 | (= n 1) 1 11 | :else (+ (fib (- n 1)) (fib (- n 2))))) 12 | 13 | (test "fibonacci" 14 | (= 8 (fib 5))) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | RUN apk add --no-cache git 3 | WORKDIR /go/src/app 4 | COPY . . 5 | RUN go get -d -v ./... 6 | RUN go build -o / -v ./... 7 | 8 | #final stage 9 | FROM alpine:latest 10 | RUN apk --no-cache add ca-certificates 11 | COPY --from=builder /let-go /bin/let-go 12 | ENTRYPOINT /bin/let-go -------------------------------------------------------------------------------- /wasm/Makefile: -------------------------------------------------------------------------------- 1 | GOROOT := $(shell go env GOROOT) 2 | 3 | build: 4 | GOOS=js GOARCH=wasm go build -o letgo.wasm . 5 | cp "$(GOROOT)/misc/wasm/wasm_exec.js" . 6 | cp ../meta/logo.svg . 7 | 8 | run: build 9 | npm install 10 | node index.js 11 | 12 | pages: build 13 | mkdir static 14 | cp index.html index.js letgo.wasm wasm_exec.js logo.svg static -------------------------------------------------------------------------------- /test/types.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.types) 7 | 8 | (test "type basics" 9 | (and (nil? (type nil)) 10 | (= (type 3) (type 4)) 11 | (not= (type 3) (type "foo")) 12 | (= (type (type 3)) (type (type (type 99)))) 13 | )) 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: build 2 | ./lg 3 | 4 | build: lg.go pkg/**/* 5 | go build -ldflags="-s -w" -o lg . 6 | 7 | test: pkg/**/* 8 | go test -count=1 -v ./test 9 | 10 | clean: 11 | rm ./lg 12 | 13 | lint: install-golangci-lint 14 | golangci-lint run 15 | 16 | install-golangci-lint: 17 | which golangci-lint || GO111MODULE=off go get -u github.com/golangci/golangci-lint/cmd/golangci-lint -------------------------------------------------------------------------------- /examples/server2.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2022 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns server2 7 | 'http) 8 | 9 | ;; This is a small HTTP server example demonstrating Ring-like API 10 | 11 | (defn handler [req] 12 | (println (now) (:request-method req) (:uri req)) 13 | {:status 200 :body "hello from let-go :^)"}) 14 | 15 | (serve2 handler ":7070") -------------------------------------------------------------------------------- /test/async.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.async) 7 | 8 | (test "go block concurrency" 9 | (let [x (atom 0) 10 | m 1 11 | a (go (dotimes [_ m] (swap! x inc))) 12 | b (go (dotimes [_ (* 2 m)] (swap! x dec)))] 13 | ( { 10 | try { 11 | await fastify.listen(8080, "0.0.0.0"); 12 | fastify.log.info(`server listening on ${fastify.server.address().port}`); 13 | } catch (error) { 14 | fastify.log.error(error); 15 | } 16 | })(); -------------------------------------------------------------------------------- /pkg/rt/os.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package rt 7 | 8 | import ( 9 | "os" 10 | 11 | "github.com/nooga/let-go/pkg/vm" 12 | ) 13 | 14 | // nolint 15 | func installOsNS() { 16 | getenv, err := vm.NativeFnType.Box(os.Getenv) 17 | 18 | if err != nil { 19 | panic("http NS init failed") 20 | } 21 | 22 | ns := vm.NewNamespace("os") 23 | 24 | // vars 25 | CurrentNS = ns.Def("*ns*", ns) 26 | 27 | ns.Def("getenv", getenv) 28 | RegisterNS(ns) 29 | } 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nooga/let-go 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/alimpfard/line v0.0.0-20230131232016-03b4e7dee324 7 | github.com/stretchr/testify v1.8.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/hashicorp/go-uuid v1.0.3 13 | github.com/kr/pretty v0.3.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/zeebo/bencode v1.0.0 16 | golang.org/x/sys v0.4.0 // indirect 17 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /pkg/vm/constpool.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | type Consts struct { 4 | consts []Value 5 | index map[Value]int 6 | } 7 | 8 | func NewConsts() *Consts { 9 | return &Consts{ 10 | consts: []Value{}, 11 | index: map[Value]int{}, 12 | } 13 | } 14 | 15 | func (c *Consts) Intern(v Value) int { 16 | if i, ok := c.index[v]; ok { 17 | return i 18 | } 19 | c.consts = append(c.consts, v) 20 | i := len(c.consts) - 1 21 | c.index[v] = i 22 | return i 23 | } 24 | 25 | func (c *Consts) get(i int) Value { 26 | return c.consts[i] 27 | } 28 | 29 | func (c *Consts) count() int { 30 | return len(c.consts) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/vm/constpool_test.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestConstpool(t *testing.T) { 10 | c := NewConsts() 11 | v := []Value{Int(9), String("zoom"), Char('a'), Symbol("poop"), EmptyList, NIL, TRUE, FALSE} 12 | 13 | idx := []int{} 14 | for i := range v { 15 | idx = append(idx, c.Intern(v[i])) 16 | } 17 | 18 | assert.Equal(t, len(v), len(idx)) 19 | assert.Equal(t, len(v), c.count()) 20 | 21 | for i := range v { 22 | assert.Equal(t, v[i], c.get(idx[i])) 23 | } 24 | 25 | j := c.Intern(Int(9)) 26 | assert.Equal(t, 0, j) 27 | assert.Equal(t, Int(9), c.get(j)) 28 | } 29 | -------------------------------------------------------------------------------- /wasm/main.go: -------------------------------------------------------------------------------- 1 | //go:build js && wasm 2 | // +build js,wasm 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "syscall/js" 9 | 10 | "github.com/nooga/let-go/pkg/api" 11 | ) 12 | 13 | var lg *api.LetGo 14 | 15 | func Eval(this js.Value, args []js.Value) interface{} { 16 | x := args[0].String() 17 | v, err := lg.Run(x) 18 | if err != nil { 19 | return fmt.Sprintf("%s", err) 20 | } 21 | return v.String() 22 | } 23 | 24 | func main() { 25 | var err error 26 | lg, err = api.NewLetGo("user") 27 | if err != nil { 28 | panic("let-go runtime failed to init") 29 | } 30 | js.Global().Set("Eval", js.FuncOf(Eval)) 31 | 32 | <-make(chan struct{}) 33 | } 34 | -------------------------------------------------------------------------------- /test/primes.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.primes) 7 | 8 | (defn is-prime? [n] 9 | (if (< n 2) 10 | false 11 | (loop [i 2] 12 | (cond (= i n) true 13 | (zero? (mod n i)) false 14 | :else (recur (inc i)))))) 15 | 16 | (def N 3000) 17 | 18 | (test "primes" 19 | (= 430 20 | (count (loop [i 0 primes (list)] 21 | (if (< i N) 22 | (if (is-prime? i) 23 | (recur (inc i) (cons i primes)) 24 | (recur (inc i) primes)) 25 | primes))))) 26 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | golangci: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v2.5.1 18 | 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: 1.19.3 28 | 29 | - name: Build 30 | run: go build -v ./... 31 | 32 | - name: Test 33 | run: go test -v ./... 34 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package errors 7 | 8 | import "fmt" 9 | 10 | type Error interface { 11 | error 12 | Wrap(error) Error 13 | GetCause() error 14 | } 15 | 16 | func IsCausedBy(me error, e error) bool { 17 | if me == nil { 18 | return false 19 | } 20 | if me == e { 21 | return true 22 | } 23 | mec, ok := me.(Error) 24 | if !ok { 25 | return false 26 | } 27 | return IsCausedBy(mec.GetCause(), e) 28 | } 29 | 30 | func AddCause(e Error, s string) string { 31 | cause := e.GetCause() 32 | if cause == nil { 33 | return s 34 | } 35 | return fmt.Sprintf("%s\n\tcaused by %s", s, cause.Error()) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/compiler/eval.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/nooga/let-go/pkg/rt" 12 | "github.com/nooga/let-go/pkg/vm" 13 | ) 14 | 15 | var consts *vm.Consts 16 | 17 | func Eval(src string) (vm.Value, error) { 18 | ns := rt.NS(rt.NameCoreNS) 19 | compiler := NewCompiler(consts, ns) 20 | 21 | _, out, err := compiler.CompileMultiple(strings.NewReader(src)) 22 | if err != nil { 23 | return vm.NIL, err 24 | } 25 | 26 | return out, nil 27 | } 28 | 29 | func evalInit() { 30 | consts = vm.NewConsts() 31 | _, err := Eval(rt.CoreSrc) 32 | if err != nil { 33 | panic(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/fun.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.fun) 7 | 8 | (test "fn" 9 | (let [f1 (fn [x y] (+ x y x)) 10 | f2 #(+ %1 %2 %1)] 11 | (= (f1 1 2) (f2 1 2)))) 12 | 13 | (defn curry' [args body] 14 | (let [fa (first args) 15 | r (next args)] 16 | (if r 17 | (list 'fn (vector fa) (curry' r body)) 18 | (cons 'fn (cons (vector fa) body))))) 19 | 20 | (defmacro defn-curried [name args & body] 21 | (list 'def name (curry' args body))) 22 | 23 | ; (defn-curried chicken [a b c] (+ a b c)) 24 | ; (println (((chicken 1) 2) 3)) 25 | 26 | (defn-curried K [q i] q) 27 | (defn-curried S [x y z] ((x z) (y z))) 28 | 29 | (def I ((S K) K)) 30 | 31 | (test "SKI" (and (= (I 5) 5) 32 | (= (I 6) 6))) 33 | -------------------------------------------------------------------------------- /pkg/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/nooga/let-go/pkg/compiler" 7 | "github.com/nooga/let-go/pkg/rt" 8 | "github.com/nooga/let-go/pkg/vm" 9 | ) 10 | 11 | type LetGo struct { 12 | cp *vm.Consts 13 | c *compiler.Context 14 | } 15 | 16 | func NewLetGo(ns string) (*LetGo, error) { 17 | cp := vm.NewConsts() 18 | nso := rt.NS(ns) 19 | ret := &LetGo{ 20 | cp: cp, 21 | c: compiler.NewCompiler(cp, nso), 22 | } 23 | return ret, nil 24 | } 25 | 26 | func (l *LetGo) Def(name string, value interface{}) error { 27 | val, err := vm.BoxValue(reflect.ValueOf(value)) 28 | if err != nil { 29 | return err 30 | } 31 | l.c.CurrentNS().Def(name, val) 32 | 33 | return nil 34 | } 35 | 36 | func (l *LetGo) Run(expr string) (vm.Value, error) { 37 | c, err := l.c.Compile(expr) 38 | if err != nil { 39 | return vm.NIL, err 40 | } 41 | frame := vm.NewFrame(c, nil) 42 | return frame.Run() 43 | } 44 | -------------------------------------------------------------------------------- /examples/server.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2022 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns server 7 | 'http) 8 | 9 | ;; This is a small HTTP server example demonstrating Go interop. 10 | ;; Roughly equivalent code in Go: 11 | 12 | ;; package main 13 | ;; 14 | ;; import ("io" 15 | ;; "log" 16 | ;; "net/http") 17 | ;; 18 | ;; func main () { 19 | ;; h1 := func(w http.ResponseWriter, _ *http.Request) {io.WriteString (w, "Hello from Go :^(\n")} 20 | ;; http.HandleFunc("/", h1) 21 | ;; log.Fatal(http.ListenAndServe(":7070", nil)) 22 | ;; } 23 | 24 | ; passing a handler written in let-go to Go's `http.HandleFunc` 25 | (http/handle "/" (fn [res req] ; we get Go objects via args 26 | (println (now) (:Method req) (:URL req)) ; this is reading Go struct fields from `rec` 27 | (.WriteHeader res 200) ; this is calling a Go method on `res` 28 | (.Write res "hello from let-go :^)"))) 29 | 30 | (println "go to http://localhost:7070") 31 | (http/serve ":7070" nil) 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Marcin Gasperowicz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pkg/vm/void.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import "reflect" 9 | 10 | type theVoidType struct { 11 | zero *Void 12 | } 13 | 14 | func (t *theVoidType) String() string { return t.Name() } 15 | func (t *theVoidType) Type() ValueType { return TypeType } 16 | func (t *theVoidType) Unbox() interface{} { return reflect.TypeOf(t) } 17 | 18 | func (t *theVoidType) Name() string { return "VOID" } 19 | func (t *theVoidType) Box(_ interface{}) (Value, error) { return t.zero, nil } 20 | 21 | // Void is a Value whose only value is Void 22 | type Void struct{} 23 | 24 | // Type implements Value 25 | func (n *Void) Type() ValueType { return VoidType } 26 | 27 | // Unbox implements Value 28 | func (n *Void) Unbox() interface{} { return nil } 29 | 30 | // VoidType is the type of VoidValues 31 | var VoidType *theVoidType = &theVoidType{zero: &Void{}} 32 | 33 | // NIL is the only value of VoidType (and only instance of VoidValue) 34 | var VOID *Void = VoidType.zero 35 | 36 | func (n *Void) String() string { 37 | return "" 38 | } 39 | -------------------------------------------------------------------------------- /pkg/vm/int.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type theIntType struct { 14 | zero Int 15 | } 16 | 17 | func (t *theIntType) String() string { return t.Name() } 18 | func (t *theIntType) Type() ValueType { return TypeType } 19 | func (t *theIntType) Unbox() interface{} { return reflect.TypeOf(t) } 20 | 21 | func (t *theIntType) Name() string { return "let-go.lang.Int" } 22 | 23 | func (t *theIntType) Box(bare interface{}) (Value, error) { 24 | raw, ok := bare.(int) 25 | if !ok { 26 | return IntType.zero, NewTypeError(bare, "can't be boxed as", t) 27 | } 28 | return Int(raw), nil 29 | } 30 | 31 | // IntType is the type of IntValues 32 | var IntType *theIntType = &theIntType{zero: 0} 33 | 34 | // Int is boxed int 35 | type Int int 36 | 37 | // Type implements Value 38 | func (l Int) Type() ValueType { return IntType } 39 | 40 | // Unbox implements Unbox 41 | func (l Int) Unbox() interface{} { 42 | return int(l) 43 | } 44 | 45 | func (l Int) String() string { 46 | return fmt.Sprintf("%d", int(l)) 47 | } 48 | -------------------------------------------------------------------------------- /test/values.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.values) 7 | 8 | (test "maps" 9 | (let [m {:a 1 :b 2 :c 3}] 10 | (and (= (:a m) (m :a) (get m :a)) 11 | (= (:b m) 2) 12 | (nil? (:a (dissoc m :a))) 13 | (= (:a m) 1) 14 | (= (:c (assoc m :c 'foo)) 'foo) 15 | (= (:c m) 3) 16 | (= (:a (update m :a + 1)) 2) 17 | (= (:a (update m :a inc)) 2) 18 | (= (count m) 3) 19 | (= (dec (count m)) (count (dissoc m :a))) 20 | (= (count m) (count (dissoc m :nonexist))) 21 | (= (count (dissoc m :b :a)) 1) 22 | (= (count (assoc m :x 1 :y 2 :z 3)) 6)))) 23 | 24 | (test "range" 25 | (and (= (count (range 10)) 10) 26 | (= (count (range 5 10 3)) 2) 27 | (= (first (range 8 100)) 8))) 28 | 29 | (test "atom/swap" 30 | (let [foo (atom 5)] 31 | (swap! foo inc) 32 | (swap! foo * 10) 33 | (= 60 @foo))) 34 | 35 | (test "atom/reset" 36 | (let [foo (atom 5)] 37 | (reset! foo 10) 38 | (= 10 @foo))) -------------------------------------------------------------------------------- /pkg/vm/char.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "reflect" 10 | "unicode/utf8" 11 | ) 12 | 13 | type theCharType struct { 14 | zero Char 15 | } 16 | 17 | func (t *theCharType) String() string { return t.Name() } 18 | func (t *theCharType) Type() ValueType { return TypeType } 19 | func (t *theCharType) Unbox() interface{} { return reflect.TypeOf(t) } 20 | 21 | func (lt *theCharType) Name() string { return "let-go.lang.Character" } 22 | 23 | func (lt *theCharType) Box(bare interface{}) (Value, error) { 24 | raw, ok := bare.(rune) 25 | if !ok { 26 | return CharType.zero, NewTypeError(bare, "can't be boxed as", lt) 27 | } 28 | return Char(raw), nil 29 | } 30 | 31 | // CharType is the type of CharValues 32 | var CharType *theCharType = &theCharType{zero: utf8.RuneError} 33 | 34 | // Char is boxed rune 35 | type Char rune 36 | 37 | // Type implements Value 38 | func (l Char) Type() ValueType { return CharType } 39 | 40 | // Unbox implements Unbox 41 | func (l Char) Unbox() interface{} { 42 | return rune(l) 43 | } 44 | 45 | func (l Char) String() string { 46 | return "\\" + string(l) 47 | } 48 | -------------------------------------------------------------------------------- /test/perf.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.perf) 7 | 8 | ;; https://clojuredocs.org/clojure.core/case#example-604516ade4b0b1e3652d746d 9 | (let [x 30] 10 | ;; "good" 11 | (time (cond 12 | (= x 10) :ten 13 | (= x 20) :twenty 14 | (= x 30) :forty ;; sic 15 | :else :dunno)) ;;=> "Elapsed time: 0.0666 msecs" 16 | 17 | ;; "better" 18 | (time (condp = x 19 | 10 :ten 20 | 20 :twenty 21 | 30 :forty 22 | :dunno)) ;;=> "Elapsed time: 0.4197 msecs" 23 | 24 | ;; best in performance if else statement is executed (not shown) 25 | ;; best in readability 26 | (time (case x 27 | 10 :ten 28 | 20 :twenty 29 | 30 :forty 30 | :dunno)) ;;=> "Elapsed time: 0.0032 msecs" 31 | 32 | ;; best in performance if known branches are executed 33 | ;; worst in readability 34 | (time (if (= x 10) 35 | :ten 36 | (if (= x 20) 37 | :twenty 38 | (if (= x 30) 39 | :forty 40 | :dunno))))) ;;=> "Elapsed time: 0.0021 msecs" 41 | 42 | ;;=> :forty -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy wasm let-go to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: [main] 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: 1.19.3 29 | - name: Build WASM 30 | run: cd wasm; make pages 31 | - name: Upload artifact 32 | uses: actions/upload-pages-artifact@v1 33 | with: 34 | path: 'wasm/static' 35 | 36 | deploy: 37 | needs: build 38 | environment: 39 | name: github-pages 40 | url: ${{ steps.deployment.outputs.page_url }} 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v3 45 | - name: Setup Pages 46 | uses: actions/configure-pages@v2 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /pkg/vm/bool.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import "reflect" 9 | 10 | type theBooleanType struct { 11 | zero Boolean 12 | } 13 | 14 | func (t *theBooleanType) String() string { return t.Name() } 15 | func (t *theBooleanType) Type() ValueType { return TypeType } 16 | func (t *theBooleanType) Unbox() interface{} { return reflect.TypeOf(t) } 17 | 18 | func (t *theBooleanType) Name() string { return "let-go.lang.Boolean" } 19 | func (t *theBooleanType) Box(b interface{}) (Value, error) { 20 | rb, ok := b.(bool) 21 | if !ok { 22 | return BooleanType.zero, NewTypeError(b, "can't be boxed as", t) 23 | } 24 | return Boolean(rb), nil 25 | } 26 | 27 | // Boolean is either TRUE or FALSE 28 | type Boolean bool 29 | 30 | // Type implements Value 31 | func (n Boolean) Type() ValueType { return BooleanType } 32 | 33 | // Unbox implements Value 34 | func (n Boolean) Unbox() interface{} { return bool(n) } 35 | 36 | // BooleanType is the type of Boolean 37 | var BooleanType *theBooleanType = &theBooleanType{zero: FALSE} 38 | 39 | // TRUE is Boolean 40 | const TRUE Boolean = true 41 | 42 | // FALSE is Boolean 43 | const FALSE Boolean = false 44 | 45 | func (n Boolean) String() string { 46 | if n == TRUE { 47 | return "true" 48 | } 49 | return "false" 50 | } 51 | -------------------------------------------------------------------------------- /test/simple.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.simple) 7 | 8 | (test "comparison" 9 | (and (= 1 1) 10 | (= 1 1 1 1) 11 | (= nil nil) 12 | (= true true) 13 | (= false false) 14 | (= "goo" "goo") 15 | (= 'foo 'foo) 16 | (= :pzoo :pzoo) 17 | (not= 1 2 1) 18 | (not= :bar 'bar) 19 | (not= :bar "bar") 20 | (not= 'bar "bar") 21 | (> 5 3) 22 | (not (> 2 3)) 23 | (< 3 5) 24 | (not (< 3 2)) 25 | )) 26 | 27 | (test "logical ops" 28 | (and (= true (and)) 29 | (= nil (or)) 30 | (= 3 (and true 3)) 31 | (= false (and false 3)) 32 | (= false (and 1 2 false 3)) 33 | (= 3 (or false 3)) 34 | (= 1 (or 1 2 false 3)) 35 | (= true (not nil)) 36 | (= false (not 3)) 37 | )) 38 | 39 | (test "count" 40 | (and (zero? (count nil)) 41 | (zero? (count '())) 42 | (zero? (count [])) 43 | (zero? (count {})) 44 | (zero? (count "")) 45 | (= 3 (count ":^)")) 46 | (= 2 (count [2 3])) 47 | (= 2 (count '(2 3))) 48 | (= 2 (count {:a 1 :b 2})) 49 | )) -------------------------------------------------------------------------------- /test/scope.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.scope) 7 | 8 | (test "let simple" 9 | (let [x 1 y 2] 10 | (= 3 (+ x y)))) 11 | 12 | (test "let recursive" 13 | (let [x 1 14 | y (+ x 1) 15 | y (+ 1 y)] ; shadow the old y 16 | (= 4 (+ x y)))) 17 | 18 | (test "let nested" 19 | (let [a 1 b 2] 20 | (let [c 3 d 4] 21 | (println a b c d) 22 | (= (+ b d) (* 2 (+ a b)))))) 23 | 24 | (test "let shadow" 25 | (let [a 1 b 2] 26 | (let [a 3 c 4] 27 | (println a b) 28 | (= 5 (+ a b))))) 29 | 30 | 31 | (test "loop simple" 32 | (loop [a 1 b 2] 33 | (println a b) 34 | (= 3 (+ a b)))) 35 | 36 | (test "simple closure" 37 | (let [f (fn [x] (fn [y] (+ x y)))] ; x comes from outer scope 38 | (= 3 ((f 1) 2)))) 39 | 40 | (test "nested closure" 41 | (let [f (fn [x] (fn [y] (fn [z] (+ x (+ y z)))))] ; x and y come from outer scopes 42 | (= 6 (((f 1) 2) 3)))) 43 | 44 | (test "Y combinator" 45 | (let [Y (fn [f] ((fn [x] (x x)) 46 | (fn [x] 47 | (f (fn [y] ((x x) y)))))) 48 | fac-gen (fn [func] (fn [n] (if (zero? n) 1 (* n (func (dec n))))))] 49 | (= 120 ((Y fac-gen) 5)))) 50 | 51 | -------------------------------------------------------------------------------- /pkg/vm/chan.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type theChanType struct { 14 | } 15 | 16 | func (t *theChanType) String() string { return t.Name() } 17 | func (t *theChanType) Type() ValueType { return TypeType } 18 | func (t *theChanType) Unbox() interface{} { return reflect.TypeOf(t) } 19 | 20 | func (t *theChanType) Name() string { return "let-go.lang.Chan" } 21 | func (t *theChanType) Box(b interface{}) (Value, error) { 22 | chv := reflect.ValueOf(b) 23 | if chv.Type().Kind() != reflect.Chan { 24 | return NIL, NewTypeError(b, "is not a channel", t) 25 | } 26 | rb := make(Chan) 27 | go func() { 28 | for { 29 | v, ok := chv.Recv() 30 | if !ok { 31 | break 32 | } 33 | bv, _ := BoxValue(v) 34 | rb <- bv 35 | } 36 | close(rb) 37 | }() 38 | return Chan(rb), nil 39 | } 40 | 41 | // Chan is either TRUE or FALSE 42 | type Chan chan Value 43 | 44 | // Type implements Value 45 | func (n Chan) Type() ValueType { return ChanType } 46 | 47 | // Unbox implements Value 48 | func (n Chan) Unbox() interface{} { return n } 49 | 50 | // ChanType is the type of Chan 51 | var ChanType *theChanType = &theChanType{} 52 | 53 | func (n Chan) String() string { 54 | return fmt.Sprintf("", n) 55 | } 56 | -------------------------------------------------------------------------------- /examples/gol.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2022 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns gol) 7 | 8 | ;; This example prints 20 iterations of Conway's Game of Life in your terminal 9 | ;; 10 | ;; Usage: let-go gol.lg 11 | 12 | (def N 10) 13 | (def initial [0 0 0 0 0 0 0 0 0 0 14 | 0 0 1 0 0 0 0 0 0 0 15 | 0 0 0 1 0 0 0 0 0 0 16 | 0 1 1 1 0 0 0 0 0 0 17 | 0 0 0 0 0 0 0 0 0 0 18 | 0 0 0 0 0 0 0 0 0 0 19 | 0 0 0 0 0 0 0 0 0 0 20 | 0 0 0 0 0 0 0 0 0 0 21 | 0 0 0 0 0 0 0 0 0 0 22 | 0 0 0 0 0 0 0 0 0 0]) 23 | 24 | (defn show [state] 25 | (loop [[l & r] (partition N N state)] 26 | (when l 27 | (apply println (map {0 "." 1 "#"} l)) 28 | (recur r)))) 29 | 30 | (defn window [state i] 31 | (apply + (map #(get state %1 0) 32 | [(- i N 1) (- i N) (- i N -1) 33 | (dec i) (inc i) 34 | (+ i N -1) (+ i N) (+ i N 1)]))) 35 | 36 | (defn step [state] 37 | (mapv (fn [x i] 38 | (let [n (window state i)] 39 | (cond 40 | (and (= x 1) (< n 2)) 0 41 | (and (= x 1) (> n 3)) 0 42 | (and (= x 0) (= n 3)) 1 43 | :else x))) 44 | state 45 | (range (* N N)))) 46 | 47 | (loop [state initial i 20] 48 | (when (> i 0) 49 | (show state) 50 | (println "---") 51 | (recur (step state) (dec i)))) -------------------------------------------------------------------------------- /pkg/compiler/cell.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | import ( 9 | "github.com/nooga/let-go/pkg/vm" 10 | ) 11 | 12 | type cell interface { 13 | source() cell 14 | emit() error 15 | } 16 | 17 | type localCell struct { 18 | scope *Context 19 | local int 20 | } 21 | 22 | func (c *localCell) source() cell { 23 | return nil 24 | } 25 | 26 | func (c *localCell) emit() error { 27 | c.scope.emitWithArg(vm.OP_DUP_NTH, c.scope.sp-1-c.local) 28 | c.scope.incSP(1) 29 | return nil 30 | } 31 | 32 | type argCell struct { 33 | scope *Context 34 | arg int 35 | } 36 | 37 | func (c *argCell) source() cell { 38 | return nil 39 | } 40 | 41 | func (c *argCell) emit() error { 42 | c.scope.emitWithArg(vm.OP_LOAD_ARG, c.arg) 43 | c.scope.incSP(1) 44 | return nil 45 | } 46 | 47 | // might come in handy later 48 | 49 | //type varCell struct { 50 | // scope *Context 51 | // arg int 52 | //} 53 | // 54 | //func (c *varCell) source() cell { 55 | // return nil 56 | //} 57 | // 58 | //func (c *varCell) emit() error { 59 | // c.scope.emitWithArg(vm.OP_LOAD_ARG, c.arg) 60 | // c.scope.incSP(1) 61 | // return nil 62 | //} 63 | 64 | type closureCell struct { 65 | src cell 66 | scope *Context 67 | closure int 68 | } 69 | 70 | func (c *closureCell) source() cell { 71 | return c.src 72 | } 73 | 74 | func (c *closureCell) emit() error { 75 | c.scope.emitWithArg(vm.OP_LOAD_CLOSEDOVER, c.closure) 76 | c.scope.incSP(1) 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/vm/regex.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "regexp" 12 | ) 13 | 14 | type theRegexType struct { 15 | } 16 | 17 | func (t *theRegexType) String() string { return t.Name() } 18 | func (t *theRegexType) Type() ValueType { return TypeType } 19 | func (t *theRegexType) Unbox() interface{} { return reflect.TypeOf(t) } 20 | 21 | func (t *theRegexType) Name() string { return "let-go.lang.Regex" } 22 | 23 | func (t *theRegexType) Box(bare interface{}) (Value, error) { 24 | raw, ok := bare.(*regexp.Regexp) 25 | if !ok { 26 | return NIL, NewTypeError(bare, "can't be boxed as", t) 27 | } 28 | return &Regex{re: raw}, nil 29 | } 30 | 31 | // RegexType is the type of RegexValues 32 | var RegexType *theRegexType = &theRegexType{} 33 | 34 | // Regex is boxed int 35 | type Regex struct { 36 | re *regexp.Regexp 37 | } 38 | 39 | // Type implements Value 40 | func (l *Regex) Type() ValueType { return RegexType } 41 | 42 | // Unbox implements Unbox 43 | func (l *Regex) Unbox() interface{} { 44 | return l 45 | } 46 | 47 | func (l *Regex) String() string { 48 | return fmt.Sprintf("#%q", l.re) 49 | } 50 | 51 | func (l *Regex) ReplaceAll(s string, replacement string) string { 52 | return l.re.ReplaceAllString(s, replacement) 53 | } 54 | 55 | func NewRegex(s string) (Value, error) { 56 | re, err := regexp.Compile(s) 57 | if err != nil { 58 | return NIL, err 59 | } 60 | return &Regex{ 61 | re: re, 62 | }, nil 63 | } 64 | -------------------------------------------------------------------------------- /test/language_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package test 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | 14 | "github.com/nooga/let-go/pkg/compiler" 15 | "github.com/nooga/let-go/pkg/rt" 16 | "github.com/nooga/let-go/pkg/vm" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | var consts *vm.Consts 21 | 22 | func runFile(filename string) error { 23 | ns := rt.NS(rt.NameCoreNS) 24 | if ns == nil { 25 | fmt.Println("namespace not found") 26 | return nil 27 | } 28 | ctx := compiler.NewCompiler(consts, ns) 29 | ctx.SetSource(filename) 30 | f, err := os.Open(filename) 31 | if err != nil { 32 | return err 33 | } 34 | _, _, err = ctx.CompileMultiple(f) 35 | errc := f.Close() 36 | if err != nil { 37 | return err 38 | } 39 | if errc != nil { 40 | return errc 41 | } 42 | return nil 43 | } 44 | 45 | func TestRunner(t *testing.T) { 46 | consts = vm.NewConsts() 47 | file, err := os.Open("./") 48 | assert.NoError(t, err) 49 | names, err := file.Readdirnames(0) 50 | assert.NoError(t, err) 51 | err = file.Close() 52 | assert.NoError(t, err) 53 | outcomeVar := rt.CoreNS.Lookup("*test-flag*").(*vm.Var) 54 | for f := range names { 55 | fn := "./" + names[f] 56 | if filepath.Ext(fn) != ".lg" { 57 | continue 58 | } 59 | t.Run(names[f], func(t *testing.T) { 60 | outcomeVar.SetRoot(vm.TRUE) 61 | err := runFile(fn) 62 | assert.NoError(t, err) 63 | outcome := bool(outcomeVar.Deref().(vm.Boolean)) 64 | assert.True(t, outcome, "some tests failed") 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/vm/atom.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "sync" 12 | ) 13 | 14 | type theAtomType struct { 15 | } 16 | 17 | func (t *theAtomType) String() string { return t.Name() } 18 | func (t *theAtomType) Type() ValueType { return TypeType } 19 | func (t *theAtomType) Unbox() interface{} { return reflect.TypeOf(t) } 20 | 21 | func (t *theAtomType) Name() string { return "let-go.lang.Atom" } 22 | func (t *theAtomType) Box(b interface{}) (Value, error) { 23 | val, err := BoxValue(reflect.ValueOf(b)) 24 | if err != nil { 25 | return NIL, err 26 | } 27 | return NewAtom(val), nil 28 | } 29 | 30 | var AtomType *theAtomType = &theAtomType{} 31 | 32 | type Atom struct { 33 | root Value 34 | mu sync.RWMutex 35 | } 36 | 37 | func NewAtom(root Value) *Atom { 38 | return &Atom{ 39 | root: root, 40 | } 41 | } 42 | 43 | func (v *Atom) Reset(new Value) Value { 44 | v.mu.Lock() 45 | v.root = new 46 | v.mu.Unlock() 47 | return new 48 | } 49 | 50 | func (v *Atom) Swap(fn Fn, args []Value) (Value, error) { 51 | v.mu.Lock() 52 | ret, err := fn.Invoke(append([]Value{v.root}, args...)) 53 | v.root = ret 54 | v.mu.Unlock() 55 | return ret, err 56 | } 57 | 58 | func (v *Atom) Deref() Value { 59 | v.mu.RLock() 60 | val := v.root 61 | v.mu.RUnlock() 62 | return val 63 | } 64 | 65 | func (v *Atom) Type() ValueType { 66 | return AtomType 67 | } 68 | 69 | func (v *Atom) Unbox() interface{} { 70 | return v.Deref().Unbox() 71 | } 72 | 73 | func (v *Atom) String() string { 74 | return fmt.Sprintf("<%s %s>", AtomType, v.Deref()) 75 | } 76 | -------------------------------------------------------------------------------- /test/recur.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.recur) 7 | 8 | (test "loop/recur" 9 | (= 220 10 | (loop [val 200 11 | cnt 20] 12 | (if (pos? cnt) 13 | (recur (inc val) (dec cnt)) 14 | val)))) 15 | 16 | (test "loop/let/recur" 17 | (= 220 18 | (loop [val 200 19 | cnt 20] 20 | (let [meh 1 21 | muh 2] 22 | (if (pos? cnt) 23 | (recur (inc val) (dec cnt)) 24 | val))))) 25 | 26 | 27 | (test "fn/recur" 28 | (let [f (fn [x] 29 | (if (pos? x) 30 | (recur (dec x)) 31 | x))] 32 | (= (f 10) 0))) 33 | 34 | (test "fn/let/recur" 35 | (let [f (fn [x] 36 | (let [meh 1 37 | muh 2] 38 | (if (pos? x) 39 | (recur (dec x)) 40 | x)))] 41 | (= (f 10) 0))) 42 | 43 | (test "fn/loop/recur" 44 | (let [f (fn [n] 45 | (* 2 46 | (loop [x 0] 47 | (if (< x n) 48 | (recur (inc x)) 49 | x))))] 50 | (= (f 10) 20))) 51 | 52 | (test "fn/loop/let/recur" 53 | (let [f (fn [n] 54 | (* 2 55 | (loop [x 0] 56 | (let [meh 1 57 | muh 2] 58 | (if (< x n) 59 | (recur (inc x)) 60 | x)))))] 61 | (= (f 10) 20))) 62 | 63 | (test "dotimes" 64 | (do 65 | (dotimes [kek 100] (println kek)) 66 | true)) -------------------------------------------------------------------------------- /pkg/vm/iterate.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import "fmt" 9 | 10 | type theIterateType struct { 11 | zero *Iterate 12 | } 13 | 14 | func (t *theIterateType) String() string { return t.Name() } 15 | func (t *theIterateType) Type() ValueType { return IterateType } 16 | func (t *theIterateType) Unbox() interface{} { return nil } 17 | 18 | func (t *theIterateType) Name() string { return "let-go.lang.Iterate" } 19 | func (t *theIterateType) Box(_ interface{}) (Value, error) { return t.zero, nil } 20 | 21 | // Iterate is a Value whose only value is Iterate 22 | type Iterate struct { 23 | f Fn 24 | state Value 25 | } 26 | 27 | func (n *Iterate) Cons(value Value) Seq { 28 | return EmptyList.Cons(value) 29 | } 30 | 31 | func (n *Iterate) First() Value { 32 | return n.state 33 | } 34 | 35 | func (n *Iterate) More() Seq { 36 | return n.Next() 37 | } 38 | 39 | func (n *Iterate) Next() Seq { 40 | nv, err := n.f.Invoke([]Value{n.state}) 41 | if err != nil { 42 | return NIL 43 | } 44 | return &Iterate{ 45 | state: nv, 46 | f: n.f, 47 | } 48 | } 49 | 50 | func (n *Iterate) Seq() Seq { 51 | return n 52 | } 53 | 54 | // Type implements Value 55 | func (n *Iterate) Type() ValueType { return IterateType } 56 | 57 | // Unbox implements Value 58 | func (n *Iterate) Unbox() interface{} { return nil } 59 | 60 | // IterateType is the type of IterateValues 61 | var IterateType *theIterateType = &theIterateType{zero: nil} 62 | 63 | func (n *Iterate) String() string { 64 | return fmt.Sprintf("", n.f, n.state) 65 | } 66 | 67 | func NewIterate(f Fn, state Value) *Iterate { 68 | return &Iterate{ 69 | f: f, 70 | state: state, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/vm/var.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import "fmt" 9 | 10 | type Var struct { 11 | root Value 12 | nsref *Namespace 13 | ns string 14 | name string 15 | isMacro bool 16 | isDynamic bool 17 | isPrivate bool 18 | } 19 | 20 | func (v *Var) Invoke(values []Value) (Value, error) { 21 | f, ok := v.root.(Fn) 22 | if !ok { 23 | return NIL, fmt.Errorf("%v root does not implement Fn", v.root) 24 | } 25 | return f.Invoke(values) 26 | } 27 | 28 | func (v *Var) Arity() int { 29 | f, ok := v.root.(Fn) 30 | if !ok { 31 | return 0 // FIXME this should be an error 32 | } 33 | return f.Arity() 34 | } 35 | 36 | func NewVar(nsref *Namespace, ns string, name string) *Var { 37 | return &Var{ 38 | nsref: nsref, 39 | ns: ns, 40 | name: name, 41 | root: NIL, 42 | isMacro: false, 43 | isDynamic: false, 44 | isPrivate: false, 45 | } 46 | } 47 | 48 | func (v *Var) SetRoot(val Value) *Var { 49 | v.root = val 50 | return v 51 | } 52 | 53 | func (v *Var) Deref() Value { 54 | return v.root 55 | } 56 | 57 | func (v *Var) Type() ValueType { 58 | return v.Deref().Type() 59 | } 60 | 61 | func (v *Var) Unbox() interface{} { 62 | return v.Deref().Unbox() 63 | } 64 | 65 | func (v *Var) String() string { 66 | return fmt.Sprintf("#'%s/%s", v.ns, v.name) 67 | } 68 | 69 | func (v *Var) IsMacro() bool { 70 | return v.isMacro 71 | } 72 | 73 | func (v *Var) IsDynamic() bool { 74 | return v.isMacro 75 | } 76 | 77 | func (v *Var) IsPrivate() bool { 78 | return v.isMacro 79 | } 80 | 81 | func (v *Var) SetMacro() { 82 | v.isMacro = true 83 | } 84 | 85 | func (v *Var) SetDynamic() { 86 | v.isDynamic = true 87 | } 88 | 89 | func (v *Var) SetPrivate() { 90 | v.isPrivate = true 91 | } 92 | -------------------------------------------------------------------------------- /pkg/vm/symbol.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theSymbolType struct { 15 | zero Symbol 16 | } 17 | 18 | func (t *theSymbolType) String() string { return t.Name() } 19 | func (t *theSymbolType) Type() ValueType { return TypeType } 20 | func (t *theSymbolType) Unbox() interface{} { return reflect.TypeOf(t) } 21 | 22 | func (lt *theSymbolType) Name() string { return "let-go.lang.Symbol" } 23 | 24 | func (lt *theSymbolType) Box(bare interface{}) (Value, error) { 25 | raw, ok := bare.(fmt.Stringer) 26 | if !ok { 27 | return BooleanType.zero, NewTypeError(bare, "can't be boxed as", lt) 28 | } 29 | return Symbol(raw.String()), nil 30 | } 31 | 32 | // SymbolType is the type of Symbol values 33 | var SymbolType *theSymbolType = &theSymbolType{zero: "????BADSYMBOL????"} 34 | 35 | // Symbol is a string 36 | type Symbol string 37 | 38 | // Type implements Value 39 | func (l Symbol) Type() ValueType { return SymbolType } 40 | 41 | // Unbox implements Unbox 42 | func (l Symbol) Unbox() interface{} { 43 | return string(l) 44 | } 45 | 46 | func (l Symbol) String() string { 47 | return string(l) 48 | } 49 | 50 | func (l Symbol) Namespaced() (Value, Value) { 51 | if string(l) == "/" { 52 | return NIL, l 53 | } 54 | x := strings.SplitN(string(l), "/", 2) 55 | if len(x) == 2 { 56 | return Symbol(x[0]), Symbol(x[1]) 57 | } 58 | return NIL, Symbol(x[0]) 59 | } 60 | 61 | // FIXME make it work the other way round 62 | func (l Symbol) Name() Value { 63 | _, n := l.Namespaced() 64 | if n == NIL { 65 | return NIL 66 | } 67 | return String(n.(Symbol)) 68 | } 69 | 70 | func (l Symbol) Namespace() Value { 71 | n, _ := l.Namespaced() 72 | if n == NIL { 73 | return NIL 74 | } 75 | return String(n.(Symbol)) 76 | } 77 | -------------------------------------------------------------------------------- /pkg/api/interop_test.go: -------------------------------------------------------------------------------- 1 | package api_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/nooga/let-go/pkg/api" 7 | "github.com/nooga/let-go/pkg/vm" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSimple(t *testing.T) { 12 | c, err := api.NewLetGo("interop") 13 | assert.NoError(t, err) 14 | tests := []struct { 15 | code string 16 | result interface{} 17 | }{ 18 | {"(* 11 11)", 121}, 19 | {"(map inc [1 2 3])", []vm.Value{vm.Int(2), vm.Int(3), vm.Int(4)}}, 20 | } 21 | for _, z := range tests { 22 | v, err := c.Run(z.code) 23 | assert.NoError(t, err, z.code) 24 | assert.Equal(t, z.result, v.Unbox(), z.code) 25 | } 26 | } 27 | 28 | func TestDef(t *testing.T) { 29 | c, err := api.NewLetGo("interop") 30 | assert.NoError(t, err) 31 | err = c.Def("x", 2) 32 | assert.NoError(t, err) 33 | err = c.Def("f", func(a, b int) int { 34 | return a * b 35 | }) 36 | assert.NoError(t, err) 37 | tests := []struct { 38 | code string 39 | result interface{} 40 | }{ 41 | {"(def y (+ x 11))", 13}, 42 | {"(f x y)", 26}, 43 | } 44 | for _, z := range tests { 45 | v, err := c.Run(z.code) 46 | assert.NoError(t, err, z.code) 47 | assert.Equal(t, z.result, v.Unbox(), z.code) 48 | } 49 | } 50 | 51 | func TestChannels(t *testing.T) { 52 | c, err := api.NewLetGo("interop") 53 | assert.NoError(t, err) 54 | 55 | inch := make(chan int) 56 | outch := make(vm.Chan) 57 | 58 | err = c.Def("in", inch) 59 | assert.NoError(t, err) 60 | err = c.Def("out", outch) 61 | assert.NoError(t, err) 62 | 63 | _, err = c.Run(` 64 | (go (loop [i (! out (inc i)) 67 | (recur ( 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | type theNilType struct { 9 | zero *Nil 10 | } 11 | 12 | func (t *theNilType) String() string { return t.Name() } 13 | func (t *theNilType) Type() ValueType { return NilType } 14 | func (t *theNilType) Unbox() interface{} { return nil } 15 | 16 | func (t *theNilType) Name() string { return "nil" } 17 | func (t *theNilType) Box(_ interface{}) (Value, error) { return t.zero, nil } 18 | 19 | // Nil is a Value whose only value is Nil 20 | type Nil struct{} 21 | 22 | func (n *Nil) Conj(value Value) Collection { 23 | return EmptyList.Conj(value) 24 | } 25 | 26 | func (n *Nil) Count() Value { 27 | return Int(0) 28 | } 29 | 30 | func (n *Nil) RawCount() int { 31 | return 0 32 | } 33 | 34 | func (n *Nil) Empty() Collection { 35 | return n 36 | } 37 | 38 | func (n *Nil) Cons(value Value) Seq { 39 | return EmptyList.Cons(value) 40 | } 41 | 42 | func (n *Nil) First() Value { 43 | return n 44 | } 45 | 46 | func (n *Nil) More() Seq { 47 | return n 48 | } 49 | 50 | func (n *Nil) Next() Seq { 51 | return n 52 | } 53 | 54 | func (n *Nil) Seq() Seq { 55 | return n 56 | } 57 | 58 | func (l *Nil) Assoc(k Value, v Value) Associative { 59 | newmap := make(Map) 60 | newmap[k] = v 61 | return newmap 62 | } 63 | 64 | func (l *Nil) Dissoc(k Value) Associative { 65 | return NIL 66 | } 67 | 68 | // Type implements Value 69 | func (n *Nil) Type() ValueType { return NilType } 70 | 71 | // Unbox implements Value 72 | func (n *Nil) Unbox() interface{} { return nil } 73 | 74 | // NilType is the type of NilValues 75 | var NilType *theNilType = &theNilType{zero: nil} 76 | 77 | // NIL is the only value of NilType (and only instance of NilValue) 78 | var NIL *Nil = NilType.zero 79 | 80 | func (n *Nil) String() string { 81 | return "nil" 82 | } 83 | -------------------------------------------------------------------------------- /pkg/compiler/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | import ( 9 | "fmt" 10 | "github.com/nooga/let-go/pkg/errors" 11 | "io" 12 | ) 13 | 14 | type ReaderError struct { 15 | inputName string 16 | message string 17 | pos int 18 | line int 19 | column int 20 | cause error 21 | } 22 | 23 | func NewReaderError(r *LispReader, message string) *ReaderError { 24 | return &ReaderError{ 25 | inputName: r.inputName, 26 | pos: r.pos, 27 | line: r.line, 28 | column: r.column, 29 | message: message, 30 | } 31 | } 32 | 33 | func (r *ReaderError) IsEOF() bool { 34 | if r.cause != nil { 35 | c, ok := r.cause.(*ReaderError) 36 | if ok { 37 | return c.IsEOF() 38 | } 39 | } 40 | return r.cause == io.EOF 41 | } 42 | 43 | func (r *ReaderError) Error() string { 44 | return errors.AddCause(r, 45 | fmt.Sprintf( 46 | "Syntax error reading source at (%s:%d:%d).\n%s", 47 | r.inputName, 48 | r.line+1, 49 | r.column+1, 50 | r.message, 51 | )) 52 | } 53 | 54 | func (r *ReaderError) Wrap(err error) errors.Error { 55 | r.cause = err 56 | return r 57 | } 58 | 59 | func (r *ReaderError) GetCause() error { 60 | return r.cause 61 | } 62 | 63 | type CompileError struct { 64 | message string 65 | cause error 66 | } 67 | 68 | func NewCompileError(message string) *CompileError { 69 | return &CompileError{ 70 | message: message, 71 | } 72 | } 73 | 74 | func (r *CompileError) Error() string { 75 | return errors.AddCause(r, 76 | fmt.Sprintf("CompileError: %s", r.message)) 77 | } 78 | 79 | func (r *CompileError) Wrap(err error) errors.Error { 80 | r.cause = err 81 | return r 82 | } 83 | 84 | func (r *CompileError) GetCause() error { 85 | return r.cause 86 | } 87 | 88 | func isErrorEOF(err error) bool { 89 | if err == io.EOF { 90 | return true 91 | } 92 | rerr, ok := err.(*ReaderError) 93 | if ok { 94 | return rerr.IsEOF() 95 | } 96 | return false 97 | } 98 | -------------------------------------------------------------------------------- /pkg/vm/repeat.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import "strings" 9 | 10 | type theRepeatType struct { 11 | zero *Repeat 12 | } 13 | 14 | func (t *theRepeatType) String() string { return t.Name() } 15 | func (t *theRepeatType) Type() ValueType { return RepeatType } 16 | func (t *theRepeatType) Unbox() interface{} { return nil } 17 | 18 | func (t *theRepeatType) Name() string { return "let-go.lang.Repeat" } 19 | func (t *theRepeatType) Box(_ interface{}) (Value, error) { return t.zero, nil } 20 | 21 | // Repeat is a Value whose only value is Repeat 22 | type Repeat struct { 23 | i int 24 | val Value 25 | } 26 | 27 | func (n *Repeat) Cons(value Value) Seq { 28 | l := EmptyList 29 | for i := n.i; i > 0; i-- { 30 | l = l.Cons(n.val).(*List) 31 | } 32 | return l.Cons(value) 33 | } 34 | 35 | func (n *Repeat) First() Value { 36 | return n.val 37 | } 38 | 39 | func (n *Repeat) More() Seq { 40 | return n.Next() 41 | } 42 | 43 | func (n *Repeat) Next() Seq { 44 | i := n.i 45 | if i == 0 { 46 | return NIL 47 | } 48 | if i > 0 { 49 | i-- 50 | } 51 | return &Repeat{ 52 | val: n.val, 53 | i: i, 54 | } 55 | } 56 | 57 | func (n *Repeat) Seq() Seq { 58 | return n 59 | } 60 | 61 | func (n *Repeat) Count() Value { 62 | return Int(n.i) 63 | } 64 | 65 | func (n *Repeat) RawCount() int { 66 | return n.i 67 | } 68 | 69 | // Type implements Value 70 | func (n *Repeat) Type() ValueType { return RepeatType } 71 | 72 | // Unbox implements Value 73 | func (n *Repeat) Unbox() interface{} { return nil } 74 | 75 | // RepeatType is the type of RepeatValues 76 | var RepeatType *theRepeatType = &theRepeatType{zero: nil} 77 | 78 | func (n *Repeat) String() string { 79 | b := &strings.Builder{} 80 | b.WriteRune('(') 81 | for i := n.i; i > 0; i-- { 82 | b.WriteString(n.val.String()) 83 | if i > 1 { 84 | b.WriteRune(' ') 85 | } 86 | } 87 | b.WriteRune(')') 88 | return b.String() 89 | } 90 | 91 | func NewRepeat(val Value, i int) *Repeat { 92 | return &Repeat{ 93 | i: i, 94 | val: val, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/vm/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "github.com/nooga/let-go/pkg/errors" 11 | "reflect" 12 | ) 13 | 14 | // TypeError is a LETGO type error which mostly happens when there is a type mismatch between 15 | // either LETGO values or LETGO values and Go values. 16 | // These errors print as: 17 | // TypeError: (encountered type name) ... message ... (expected type name) 18 | type TypeError struct { 19 | message string 20 | value interface{} 21 | expected ValueType 22 | cause error 23 | } 24 | 25 | // NewTypeError creates a new type error. This error will print the 26 | // problematic value's (either interface{} or Value) type name, a message, and expected type name. 27 | func NewTypeError(value interface{}, message string, expected ValueType) *TypeError { 28 | return &TypeError{ 29 | message: message, 30 | expected: expected, 31 | value: value, 32 | } 33 | } 34 | 35 | // Error implements error 36 | func (te *TypeError) Error() string { 37 | var s string 38 | 39 | ex := "" 40 | if te.expected != nil { 41 | ex = " " + te.expected.Name() 42 | } 43 | 44 | switch te.value.(type) { 45 | case Value: 46 | s = fmt.Sprintf("TypeError: %s %s %s", te.value.(Value).Type().Name(), te.message, ex) 47 | default: 48 | s = fmt.Sprintf("TypeError: %s %s %s", reflect.TypeOf(te.value).Name(), te.message, ex) 49 | } 50 | return errors.AddCause(te, s) 51 | } 52 | 53 | func (te *TypeError) Wrap(e error) errors.Error { 54 | te.cause = e 55 | return te 56 | } 57 | 58 | func (te *TypeError) GetCause() error { 59 | return te.cause 60 | } 61 | 62 | type ExecutionError struct { 63 | message string 64 | cause error 65 | } 66 | 67 | func NewExecutionError(m string) *ExecutionError { 68 | return &ExecutionError{message: m} 69 | } 70 | 71 | func (ve *ExecutionError) Error() string { 72 | return errors.AddCause(ve, fmt.Sprintf("ExecutionError: %s", ve.message)) 73 | } 74 | 75 | func (ve *ExecutionError) Wrap(e error) errors.Error { 76 | ve.cause = e 77 | return ve 78 | } 79 | 80 | func (ve *ExecutionError) GetCause() error { 81 | return ve.cause 82 | } 83 | -------------------------------------------------------------------------------- /pkg/vm/keyword.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theKeywordType struct { 15 | zero Keyword 16 | } 17 | 18 | func (t *theKeywordType) String() string { return t.Name() } 19 | func (t *theKeywordType) Type() ValueType { return TypeType } 20 | func (t *theKeywordType) Unbox() interface{} { return reflect.TypeOf(t) } 21 | 22 | func (t *theKeywordType) Name() string { return "let-go.lang.Keyword" } 23 | 24 | func (t *theKeywordType) Box(bare interface{}) (Value, error) { 25 | raw, ok := bare.(fmt.Stringer) 26 | if !ok { 27 | return BooleanType.zero, NewTypeError(bare, "can't be boxed as", t) 28 | } 29 | return Keyword(raw.String()), nil 30 | } 31 | 32 | // KeywordType is the type of KeywordValues 33 | var KeywordType *theKeywordType = &theKeywordType{zero: "????BADKeyword????"} 34 | 35 | // Keyword is boxed int 36 | type Keyword string 37 | 38 | // Type implements Value 39 | func (l Keyword) Type() ValueType { return KeywordType } 40 | 41 | // Unbox implements Unbox 42 | func (l Keyword) Unbox() interface{} { 43 | return string(l) 44 | } 45 | 46 | func (l Keyword) String() string { 47 | return fmt.Sprintf(":%s", string(l)) 48 | } 49 | 50 | func (l Keyword) Arity() int { 51 | return -1 52 | } 53 | 54 | func (l Keyword) Invoke(pargs []Value) (Value, error) { 55 | vl := len(pargs) 56 | if vl < 1 || vl > 2 { 57 | return NIL, fmt.Errorf("wrong number of arguments %d", vl) 58 | } 59 | as, ok := pargs[0].(Lookup) 60 | if !ok { 61 | // FIXME return error 62 | return NIL, fmt.Errorf("Keyword expected Lookup") 63 | } 64 | if vl == 1 { 65 | return as.ValueAt(l), nil 66 | } 67 | return as.ValueAtOr(l, pargs[1]), nil 68 | } 69 | 70 | func (l Keyword) Namespaced() (Value, Value) { 71 | x := strings.Split(string(l), "/") 72 | if len(x) == 2 { 73 | return Symbol(x[0]), Symbol(x[1]) 74 | } 75 | return NIL, Symbol(x[0]) 76 | } 77 | 78 | // FIXME make it work the other way round 79 | func (l Keyword) Name() Value { 80 | _, n := l.Namespaced() 81 | if n == NIL { 82 | return NIL 83 | } 84 | return String(n.(Symbol)) 85 | } 86 | 87 | func (l Keyword) Namespace() Value { 88 | n, _ := l.Namespaced() 89 | if n == NIL { 90 | return NIL 91 | } 92 | return String(n.(Symbol)) 93 | } 94 | -------------------------------------------------------------------------------- /test/destructure.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | (ns test.destructure) 7 | 8 | (test "vector destructuring" 9 | (let [[x y] [1 2]] 10 | (and (= x 1) (= y 2)))) 11 | 12 | (test "vector destructuring with rest" 13 | (let [[x y & r] [1 2 3 4]] 14 | (and (= x 1) 15 | (= y 2) 16 | (= (count r) 2) 17 | (= (first r) 3) 18 | (= (second r) 4)))) 19 | 20 | (test "vector destructuring with :as" 21 | (let [[x y :as all] [1 2 3 4]] 22 | (and (= x 1) 23 | (= y 2) 24 | (= (count all) 4)))) 25 | 26 | (test "vector destructuring with rest and :as" 27 | (let [[x y & r :as all] [1 2 3 4]] 28 | (and (= x 1) 29 | (= y 2) 30 | (= (count r) 2) 31 | (= (first r) 3) 32 | (= (second r) 4) 33 | (= (count all) 4)))) 34 | 35 | (test "nested vector destructuring" 36 | (let [[[a b] y] [[1 2] [3 4]]] 37 | (and (= a 1) 38 | (= b 2) 39 | (= (count y) 2) 40 | (= (first y) 3) 41 | (= (second y) 4)))) 42 | 43 | (test "map destructuring" 44 | (let [{a :a b :b} {:a 1 :b 2}] 45 | (and (= a 1) 46 | (= b 2)))) 47 | 48 | (test "map :keys destructuring" 49 | (let [{:keys [a b]} {:a 1 :b 2}] 50 | (and (= a 1) 51 | (= b 2)))) 52 | 53 | (test "map destructuring with :as" 54 | (let [{a :a b :b :as c} {:a 1 :b 2}] 55 | (and (= a 1) 56 | (= b 2) 57 | (= (:a c) 1) 58 | (= (:b c) 2)))) 59 | 60 | (test "map :keys destructuring with :as" 61 | (let [{:keys [a b] :as c} {:a 1 :b 2}] 62 | (and (= a 1) 63 | (= b 2) 64 | (= (:a c) 1) 65 | (= (:b c) 2)))) 66 | 67 | (test "loop destructuring" 68 | (let [xs (loop [[x y & r] '(1 2 3 4) o []] 69 | (if x 70 | (recur r (conj o [x y])) 71 | o))] 72 | (and (-> xs first first (= 1)) 73 | (-> xs first second (= 2)) 74 | (-> xs second first (= 3)) 75 | (-> xs second second (= 4))))) 76 | 77 | (test "fn destructuring" 78 | (let [f (fn [{a :x b :y}] (+ a b 6))] 79 | (= 13 (f {:x 3 :y 4})))) -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '41 0 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'go' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /pkg/vm/string.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type theStringType struct { 14 | zero String 15 | } 16 | 17 | func (t *theStringType) String() string { return t.Name() } 18 | func (t *theStringType) Type() ValueType { return TypeType } 19 | func (t *theStringType) Unbox() interface{} { return reflect.TypeOf(t) } 20 | 21 | func (t *theStringType) Name() string { return "let-go.lang.String" } 22 | 23 | func (t *theStringType) Box(bare interface{}) (Value, error) { 24 | raw, ok := bare.(string) 25 | if !ok { 26 | return StringType.zero, NewTypeError(bare, "can't be boxed as", t) 27 | } 28 | return String(raw), nil 29 | } 30 | 31 | // StringType is the type of StringValues 32 | var StringType *theStringType = &theStringType{zero: ""} 33 | 34 | // String is boxed int 35 | type String string 36 | 37 | func (l String) Conj(value Value) Collection { 38 | return String(string(l) + value.String()) 39 | } 40 | 41 | func (l String) RawCount() int { 42 | return len(l) 43 | } 44 | 45 | func (l String) Count() Value { 46 | return Int(len(l)) 47 | } 48 | 49 | func (l String) Empty() Collection { 50 | return String("") 51 | } 52 | 53 | // Type implements Value 54 | func (l String) Type() ValueType { return StringType } 55 | 56 | // Unbox implements Unbox 57 | func (l String) Unbox() interface{} { 58 | return string(l) 59 | } 60 | 61 | // First implements Seq 62 | func (l String) First() Value { 63 | for _, r := range l { 64 | return Char(r) 65 | } 66 | return NIL 67 | } 68 | 69 | // More implements Seq 70 | func (l String) More() Seq { 71 | return l.Next() 72 | } 73 | 74 | // Next implements Seq 75 | func (l String) Next() Seq { 76 | if len(l) <= 1 { 77 | return NIL 78 | } 79 | ret := EmptyList 80 | s := []rune(l) 81 | for i := len(s) - 1; i >= 1; i-- { 82 | ret = ret.Conj(Char(s[i])).(*List) 83 | } 84 | return ret 85 | } 86 | 87 | // Cons implements Seq 88 | func (l String) Cons(val Value) Seq { 89 | return NIL 90 | } 91 | 92 | func (l String) Seq() Seq { 93 | if len(l) <= 1 { 94 | return NIL 95 | } 96 | ret := EmptyList 97 | s := []rune(l) 98 | for i := len(s) - 1; i >= 0; i-- { 99 | ret = ret.Conj(Char(s[i])).(*List) 100 | } 101 | return ret 102 | } 103 | 104 | func (l String) ValueAt(key Value) Value { 105 | return l.ValueAtOr(key, NIL) 106 | } 107 | 108 | func (l String) ValueAtOr(key Value, dflt Value) Value { 109 | if key == NIL { 110 | return dflt 111 | } 112 | r := []rune(l) 113 | numkey, ok := key.(Int) 114 | if !ok || numkey < 0 || int(numkey) >= len(r) { 115 | return dflt 116 | } 117 | return Char(r[numkey]) 118 | } 119 | 120 | func (l String) String() string { 121 | return fmt.Sprintf("%q", string(l)) 122 | } 123 | -------------------------------------------------------------------------------- /pkg/compiler/reader_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | import ( 9 | "strings" 10 | "testing" 11 | 12 | "github.com/nooga/let-go/pkg/vm" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestReaderBasic(t *testing.T) { 17 | cases := map[string]vm.Value{ 18 | "1": vm.Int(1), 19 | "+1": vm.Int(1), 20 | "-1": vm.Int(-1), 21 | "987654321": vm.Int(987654321), 22 | "+987654321": vm.Int(987654321), 23 | "-987654321": vm.Int(-987654321), 24 | "true": vm.TRUE, 25 | "false": vm.FALSE, 26 | "nil": vm.NIL, 27 | "foo": vm.Symbol("foo"), 28 | "()": vm.EmptyList, 29 | "( )": vm.EmptyList, 30 | "(1 2)": vm.EmptyList.Cons(vm.Int(2)).Cons(vm.Int(1)), 31 | "\"hello\"": vm.String("hello"), 32 | "\"h\\\"el\\tl\\\\o\"": vm.String("h\"el\tl\\o"), 33 | ":foo": vm.Keyword("foo"), 34 | "\\F": vm.Char('F'), 35 | "\\newline": vm.Char('\n'), 36 | "\\u1234": vm.Char('\u1234'), 37 | "\\o300": vm.Char(rune(0300)), 38 | "\\u03A9": vm.Char('Ω'), 39 | "[]": vm.ArrayVector{}, 40 | "[1 :foo true]": vm.ArrayVector{vm.Int(1), vm.Keyword("foo"), vm.TRUE}, 41 | "'foo": vm.EmptyList.Cons(vm.Symbol("foo")).Cons(vm.Symbol("quote")), 42 | "^:foo zoo": vm.EmptyList.Cons(vm.Map{vm.Keyword("foo"): vm.TRUE}).Cons(vm.Symbol("zoo")).Cons(vm.Symbol("with-meta")), 43 | "^:foo ^:bar zoo": vm.EmptyList.Cons(vm.Map{vm.Keyword("foo"): vm.TRUE, vm.Keyword("bar"): vm.TRUE}).Cons(vm.Symbol("zoo")).Cons(vm.Symbol("with-meta")), 44 | "^{:foo 1 :baz 2} ^:bar zoo": vm.EmptyList.Cons(vm.Map{vm.Keyword("foo"): vm.Int(1), vm.Keyword("baz"): vm.Int(2), vm.Keyword("bar"): vm.TRUE}).Cons(vm.Symbol("zoo")).Cons(vm.Symbol("with-meta")), 45 | "^:bar ^{:foo 1 :baz 2} zoo": vm.EmptyList.Cons(vm.Map{vm.Keyword("foo"): vm.Int(1), vm.Keyword("baz"): vm.Int(2), vm.Keyword("bar"): vm.TRUE}).Cons(vm.Symbol("zoo")).Cons(vm.Symbol("with-meta")), 46 | } 47 | 48 | for p, e := range cases { 49 | r := NewLispReader(strings.NewReader(p), "") 50 | o, err := r.Read() 51 | assert.NoError(t, err) 52 | assert.Equal(t, e, o) 53 | } 54 | } 55 | 56 | func TestSimpleCall(t *testing.T) { 57 | p := "(+ 40 2)" 58 | r := NewLispReader(strings.NewReader(p), "") 59 | o, err := r.Read() 60 | assert.NoError(t, err) 61 | 62 | out, err := vm.ListType.Box([]vm.Value{ 63 | vm.Symbol("+"), 64 | vm.Int(40), 65 | vm.Int(2), 66 | }) 67 | 68 | assert.NoError(t, err) 69 | assert.Equal(t, out, o) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/vm/boxed.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type aBoxedType struct { 14 | typ reflect.Type 15 | methods map[Symbol]*NativeFn 16 | } 17 | 18 | func (t *aBoxedType) String() string { return t.Name() } 19 | func (t *aBoxedType) Type() ValueType { return TypeType } 20 | func (t *aBoxedType) Unbox() interface{} { return t.typ } 21 | 22 | func (t *aBoxedType) Name() string { return "go." + t.typ.String() } 23 | func (t *aBoxedType) Box(value interface{}) (Value, error) { 24 | if !reflect.TypeOf(value).ConvertibleTo(t.typ) { 25 | return NIL, NewTypeError(value, "can't be boxed as", t) 26 | } 27 | return &Boxed{value, t}, nil 28 | } 29 | 30 | type Boxed struct { 31 | value interface{} 32 | typ *aBoxedType 33 | } 34 | 35 | // Type implements Value 36 | func (n *Boxed) Type() ValueType { return n.typ } 37 | 38 | // Unbox implements Value 39 | func (n *Boxed) Unbox() interface{} { return n.value } 40 | 41 | func (n *Boxed) String() string { 42 | return fmt.Sprintf("<%s %v>", n.typ.Name(), n.value) 43 | } 44 | 45 | func (n *Boxed) InvokeMethod(methodName Symbol, args []Value) (Value, error) { 46 | if n.typ.methods == nil { 47 | return NIL, fmt.Errorf("%v doesn't have any methods", n.typ) 48 | } 49 | method, ok := n.typ.methods[methodName] 50 | if !ok { 51 | return NIL, fmt.Errorf("method %s not found in %v", methodName, n.typ) 52 | } 53 | return method.Invoke(append([]Value{n}, args...)) 54 | } 55 | 56 | func (n *Boxed) ValueAt(key Value) Value { 57 | return n.ValueAtOr(key, NIL) 58 | } 59 | 60 | func (n *Boxed) ValueAtOr(key Value, dflt Value) Value { 61 | name, ok := key.Unbox().(string) 62 | if !ok { 63 | return dflt 64 | } 65 | v, err := BoxValue(reflect.ValueOf(n.value).FieldByName(name)) 66 | if err != nil { 67 | return dflt 68 | } 69 | return v 70 | } 71 | 72 | // BoxedType is the type of NilValues 73 | var BoxedTypes map[string]*aBoxedType = map[string]*aBoxedType{} 74 | 75 | func valueType(value interface{}) *aBoxedType { 76 | reflected := reflect.TypeOf(value) 77 | t, ok := BoxedTypes[reflected.Name()] 78 | if ok { 79 | return t 80 | } 81 | t = &aBoxedType{ 82 | typ: reflected, 83 | methods: nil, 84 | } 85 | methodc := reflected.NumMethod() 86 | if methodc > 0 { 87 | t.methods = map[Symbol]*NativeFn{} 88 | for i := 0; i < methodc; i++ { 89 | m := reflected.Method(i) 90 | me, err := NativeFnType.Box(m.Func.Interface()) 91 | if err != nil { 92 | // FIXME notice this somehow 93 | continue 94 | } 95 | mef, ok := me.(*NativeFn) 96 | if !ok { 97 | // FIXME notice this somehow 98 | continue 99 | } 100 | t.methods[Symbol(m.Name)] = mef 101 | } 102 | } 103 | BoxedTypes[reflected.Name()] = t 104 | return t 105 | } 106 | 107 | func NewBoxed(value interface{}) *Boxed { 108 | return &Boxed{value: value, typ: valueType(value)} 109 | } 110 | -------------------------------------------------------------------------------- /pkg/vm/range.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "reflect" 10 | ) 11 | 12 | type theRangeType struct{} 13 | 14 | func (t *theRangeType) String() string { return t.Name() } 15 | func (t *theRangeType) Type() ValueType { return TypeType } 16 | func (t *theRangeType) Unbox() interface{} { return reflect.TypeOf(t) } 17 | 18 | func (t *theRangeType) Name() string { return "let-go.lang.Range" } 19 | 20 | func (t *theRangeType) Box(bare interface{}) (Value, error) { 21 | return NIL, NewTypeError(bare, "can't be boxed as", t) 22 | } 23 | 24 | // RangeType is the type of Lists 25 | var RangeType *theRangeType = &theRangeType{} 26 | 27 | // Range is boxed singly linked list that can hold other Values. 28 | type Range struct { 29 | start int 30 | end int 31 | step int 32 | } 33 | 34 | // Type implements Value 35 | func (l *Range) Type() ValueType { return RangeType } 36 | 37 | // Unbox implements Value 38 | func (l *Range) Unbox() interface{} { 39 | return l.Seq().Unbox() 40 | } 41 | 42 | // First implements Seq 43 | func (l *Range) First() Value { 44 | return Int(l.start) 45 | } 46 | 47 | // More implements Seq 48 | func (l *Range) More() Seq { 49 | nexts := l.start + l.step 50 | if nexts < l.end { 51 | return &Range{nexts, l.end, l.step} 52 | } 53 | return EmptyList 54 | } 55 | 56 | // Next implements Seq 57 | func (l *Range) Next() Seq { 58 | nexts := l.start + l.step 59 | if nexts < l.end { 60 | return &Range{nexts, l.end, l.step} 61 | } 62 | return EmptyList 63 | } 64 | 65 | func (l *Range) Seq() Seq { 66 | var r Seq = EmptyList 67 | n := l.end - l.start - 1 68 | top := l.start + (n/l.step)*l.step 69 | for i := top; i >= l.start; i -= l.step { 70 | r = r.Cons(Int(i)) 71 | } 72 | return r 73 | } 74 | 75 | // Cons implements Seq 76 | func (l *Range) Cons(val Value) Seq { 77 | return l.Seq().Cons(val) 78 | } 79 | 80 | // Count implements Collection 81 | func (l *Range) Count() Value { 82 | return Int(l.RawCount()) 83 | } 84 | 85 | func (l *Range) RawCount() int { 86 | if l.step == 1 { 87 | return l.end - l.start 88 | } 89 | return (l.end - l.start + 1) / l.step 90 | } 91 | 92 | // Empty implements Collection 93 | func (l *Range) Empty() Collection { 94 | return EmptyList 95 | } 96 | 97 | func (l *Range) Conj(val Value) Collection { 98 | return l.Cons(val).(Collection) 99 | } 100 | 101 | func (l *Range) String() string { 102 | return l.Seq().String() 103 | } 104 | 105 | func (l *Range) ValueAt(key Value) Value { 106 | return l.ValueAtOr(key, NIL) 107 | } 108 | 109 | func (l *Range) ValueAtOr(key Value, dflt Value) Value { 110 | // FIXME: assumes positive step 111 | if key == NIL { 112 | return dflt 113 | } 114 | numkey, ok := key.(Int) 115 | if !ok { 116 | return dflt 117 | } 118 | nth := l.start + int(numkey)*l.step 119 | if nth <= l.end && nth >= l.start { 120 | return Int(nth) 121 | } 122 | return dflt 123 | } 124 | 125 | func NewRange(start, end, step Int) Value { 126 | // FIXME: Add support for negative step 127 | if end > start && step > 0 { 128 | return &Range{ 129 | int(start), int(end), int(step), 130 | } 131 | } 132 | return EmptyList 133 | } 134 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alimpfard/line v0.0.0-20230131232016-03b4e7dee324 h1:mKIYYfx8L+n2YnfasWiUUFW6B4fihPDyiOEfCSnOJ4A= 2 | github.com/alimpfard/line v0.0.0-20230131232016-03b4e7dee324/go.mod h1:ZoqDayBioO2fnUOocZ12GDctL51qiz/UGQH+zEmHFic= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 8 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 9 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 10 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 11 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 12 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 15 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 16 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 20 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 23 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 24 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 25 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 26 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 27 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 28 | github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= 29 | github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= 30 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 31 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 34 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 35 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /pkg/vm/namespace.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theNamespaceType struct{} 15 | 16 | func (t *theNamespaceType) String() string { return t.Name() } 17 | func (t *theNamespaceType) Type() ValueType { return TypeType } 18 | func (t *theNamespaceType) Unbox() interface{} { return reflect.TypeOf(t) } 19 | 20 | func (t *theNamespaceType) Name() string { return "let-go.lang.Namespace" } 21 | func (t *theNamespaceType) Box(fn interface{}) (Value, error) { 22 | return NIL, NewTypeError(fn, "can't be boxed as", t) 23 | } 24 | 25 | var NamespaceType *theNamespaceType = &theNamespaceType{} 26 | 27 | type Refer struct { 28 | ns *Namespace 29 | all bool 30 | } 31 | 32 | type Namespace struct { 33 | name string 34 | registry map[Symbol]*Var 35 | refers map[Symbol]*Refer 36 | } 37 | 38 | func (n *Namespace) Type() ValueType { return NamespaceType } 39 | 40 | // Unbox implements Unbox 41 | func (n *Namespace) Unbox() interface{} { 42 | return nil 43 | } 44 | 45 | func NewNamespace(name string) *Namespace { 46 | return &Namespace{ 47 | name: name, 48 | registry: map[Symbol]*Var{}, 49 | refers: map[Symbol]*Refer{}, 50 | } 51 | } 52 | 53 | func (n *Namespace) Def(name string, val Value) *Var { 54 | s := Symbol(name) 55 | va := NewVar(n, n.name, name) 56 | va.SetRoot(val) 57 | if val.Type() == NativeFnType { 58 | val.(*NativeFn).SetName(name) 59 | } 60 | if val.Type() == FuncType { 61 | val.(*Func).SetName(name) 62 | } 63 | n.registry[s] = va 64 | return va 65 | } 66 | 67 | func (n *Namespace) LookupOrAdd(symbol Symbol) Value { 68 | val, ok := n.registry[symbol] 69 | if !ok { 70 | return n.Def(string(symbol), NIL) 71 | } 72 | return val 73 | } 74 | 75 | func (n *Namespace) Lookup(symbol Symbol) Value { 76 | sns, sym := symbol.Namespaced() 77 | if sns == NIL { 78 | v := n.registry[sym.(Symbol)] 79 | if v == nil { 80 | for _, ref := range n.refers { 81 | v = ref.ns.registry[sym.(Symbol)] 82 | if v != nil { 83 | if v.isPrivate { 84 | return NIL 85 | } 86 | return v 87 | } 88 | } 89 | } 90 | if v == nil { 91 | return NIL 92 | } 93 | return v 94 | } 95 | refer := n.refers[sns.(Symbol)] 96 | if refer == nil { 97 | return NIL 98 | } 99 | v := refer.ns.registry[sym.(Symbol)] 100 | if v == nil || v.isPrivate { 101 | return NIL 102 | } 103 | return v 104 | } 105 | 106 | func (n *Namespace) Refer(ns *Namespace, alias string, all bool) { 107 | nom := ns.Name() 108 | if alias != "" { 109 | nom = alias 110 | } 111 | n.refers[Symbol(nom)] = &Refer{ 112 | all: all, 113 | ns: ns, 114 | } 115 | } 116 | 117 | func (n *Namespace) Name() string { 118 | return n.name 119 | } 120 | 121 | func (n *Namespace) String() string { 122 | return fmt.Sprintf("", n.Name()) 123 | } 124 | 125 | func FuzzySymbolLookup(ns *Namespace, s Symbol, lookupPrivate bool) []Symbol { 126 | ret := []Symbol{} 127 | for _, r := range ns.refers { 128 | ret = append(ret, FuzzySymbolLookup(r.ns, s, false)...) 129 | } 130 | for k := range ns.registry { 131 | if strings.HasPrefix(string(k), string(s)) { 132 | if ns.registry[k].isPrivate && !lookupPrivate { 133 | continue 134 | } 135 | ret = append(ret, k) 136 | } 137 | } 138 | return ret 139 | } 140 | -------------------------------------------------------------------------------- /pkg/vm/native_func.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type theNativeFnType struct{} 14 | 15 | func (t *theNativeFnType) String() string { return t.Name() } 16 | func (t *theNativeFnType) Type() ValueType { return TypeType } 17 | func (t *theNativeFnType) Unbox() interface{} { return reflect.TypeOf(t) } 18 | 19 | func (t *theNativeFnType) Name() string { return "let-go.lang.NativeFn" } 20 | func (t *theNativeFnType) Box(fn interface{}) (Value, error) { 21 | ty := reflect.TypeOf(fn) 22 | if ty.Kind() != reflect.Func { 23 | return NIL, NewTypeError(fn, "can't be boxed into", t) 24 | } 25 | 26 | variadric := ty.IsVariadic() 27 | 28 | v := reflect.ValueOf(fn) 29 | 30 | proxy := func(args []Value) (Value, error) { 31 | rawArgs := make([]reflect.Value, len(args)) 32 | for i := range args { 33 | if args[i] != NIL { 34 | rawArgs[i] = reflect.ValueOf(args[i].Unbox()) 35 | // FIXME handle variadric 36 | if rawArgs[i].CanConvert(ty.In(i)) { 37 | rawArgs[i] = rawArgs[i].Convert(ty.In(i)) 38 | } 39 | } else { 40 | //FIXME handle variadric 41 | rawArgs[i] = reflect.Zero(ty.In(i)) 42 | } 43 | } 44 | res := v.Call(rawArgs) 45 | lr := len(res) 46 | if lr == 0 { 47 | return NIL, nil 48 | } 49 | if lr == 1 { 50 | wv, err := BoxValue(res[0]) 51 | if err != nil { 52 | return NIL, err 53 | } 54 | return wv, nil 55 | } 56 | // assume lr == 2 && res[1] is error 57 | wv, err := BoxValue(res[0]) 58 | if err != nil { 59 | return NIL, err 60 | } 61 | errorInterface := reflect.TypeOf((*error)(nil)).Elem() 62 | if res[1].Type() == errorInterface && res[1].Interface() != nil { 63 | return wv, res[1].Interface().(error) 64 | } 65 | return wv, nil 66 | } 67 | 68 | f := &NativeFn{ 69 | arity: ty.NumIn(), 70 | isVariadric: variadric, 71 | fn: fn, 72 | proxy: proxy, 73 | } 74 | 75 | return f, nil 76 | } 77 | 78 | func (t *theNativeFnType) WrapNoErr(fn func([]Value) Value) (Value, error) { 79 | // FIXME this is ugly and unnecessary wrap in closure 80 | return t.Wrap(func(args []Value) (Value, error) { 81 | return fn(args), nil 82 | }) 83 | } 84 | 85 | func (t *theNativeFnType) Wrap(fn func([]Value) (Value, error)) (Value, error) { 86 | f := &NativeFn{ 87 | arity: -1, 88 | isVariadric: false, 89 | fn: fn, 90 | proxy: fn, 91 | } 92 | 93 | return f, nil 94 | } 95 | 96 | func (l *NativeFn) WithArity(arity int, variadric bool) *NativeFn { 97 | l.arity = arity 98 | l.isVariadric = variadric 99 | return l 100 | } 101 | 102 | var NativeFnType *theNativeFnType = &theNativeFnType{} 103 | 104 | type NativeFn struct { 105 | name string 106 | arity int 107 | isVariadric bool 108 | fn interface{} 109 | proxy func([]Value) (Value, error) 110 | } 111 | 112 | func (l *NativeFn) SetName(n string) { l.name = n } 113 | 114 | func (l *NativeFn) Type() ValueType { return NativeFnType } 115 | 116 | // Unbox implements Unbox 117 | func (l *NativeFn) Unbox() interface{} { 118 | return l.fn 119 | } 120 | 121 | func (l *NativeFn) Arity() int { 122 | return l.arity 123 | } 124 | 125 | func (l *NativeFn) Invoke(args []Value) (Value, error) { 126 | return l.proxy(args) 127 | } 128 | 129 | func (l *NativeFn) String() string { 130 | if len(l.name) > 0 { 131 | return fmt.Sprintf("", l.name, l) 132 | } 133 | return fmt.Sprintf("", l) 134 | } 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go,intellij 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,intellij 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### Intellij ### 27 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 28 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 29 | 30 | .idea 31 | 32 | # User-specific stuff 33 | .idea/**/workspace.xml 34 | .idea/**/tasks.xml 35 | .idea/**/usage.statistics.xml 36 | .idea/**/dictionaries 37 | .idea/**/shelf 38 | 39 | # Generated files 40 | .idea/**/contentModel.xml 41 | 42 | # Sensitive or high-churn files 43 | .idea/**/dataSources/ 44 | .idea/**/dataSources.ids 45 | .idea/**/dataSources.local.xml 46 | .idea/**/sqlDataSources.xml 47 | .idea/**/dynamic.xml 48 | .idea/**/uiDesigner.xml 49 | .idea/**/dbnavigator.xml 50 | 51 | # Gradle 52 | .idea/**/gradle.xml 53 | .idea/**/libraries 54 | 55 | # Gradle and Maven with auto-import 56 | # When using Gradle or Maven with auto-import, you should exclude module files, 57 | # since they will be recreated, and may cause churn. Uncomment if using 58 | # auto-import. 59 | # .idea/artifacts 60 | # .idea/compiler.xml 61 | # .idea/jarRepositories.xml 62 | # .idea/modules.xml 63 | # .idea/*.iml 64 | # .idea/modules 65 | # *.iml 66 | # *.ipr 67 | 68 | # CMake 69 | cmake-build-*/ 70 | 71 | # Mongo Explorer plugin 72 | .idea/**/mongoSettings.xml 73 | 74 | # File-based project format 75 | *.iws 76 | 77 | # IntelliJ 78 | out/ 79 | 80 | # mpeltonen/sbt-idea plugin 81 | .idea_modules/ 82 | 83 | # JIRA plugin 84 | atlassian-ide-plugin.xml 85 | 86 | # Cursive Clojure plugin 87 | .idea/replstate.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | fabric.properties 94 | 95 | # Editor-based Rest Client 96 | .idea/httpRequests 97 | 98 | # Android studio 3.1+ serialized cache file 99 | .idea/caches/build_file_checksums.ser 100 | 101 | ### Intellij Patch ### 102 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 103 | 104 | # *.iml 105 | # modules.xml 106 | # .idea/misc.xml 107 | # *.ipr 108 | 109 | # Sonarlint plugin 110 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 111 | .idea/**/sonarlint/ 112 | 113 | # SonarQube Plugin 114 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 115 | .idea/**/sonarIssues.xml 116 | 117 | # Markdown Navigator plugin 118 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 119 | .idea/**/markdown-navigator.xml 120 | .idea/**/markdown-navigator-enh.xml 121 | .idea/**/markdown-navigator/ 122 | 123 | # Cache file creation bug 124 | # See https://youtrack.jetbrains.com/issue/JBR-2257 125 | .idea/$CACHE_FILE$ 126 | 127 | # CodeStream plugin 128 | # https://plugins.jetbrains.com/plugin/12206-codestream 129 | .idea/codestream.xml 130 | 131 | # End of https://www.toptal.com/developers/gitignore/api/go,intellij 132 | /letgo 133 | /let-go 134 | /lg 135 | /letgo.wasm 136 | 137 | /scratch 138 | /scratch/* 139 | 140 | .clj-kondo 141 | .lsp 142 | *.calva-repl 143 | -------------------------------------------------------------------------------- /pkg/vm/set.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theSetType struct{} 15 | 16 | func (t *theSetType) String() string { return t.Name() } 17 | func (t *theSetType) Type() ValueType { return TypeType } 18 | func (t *theSetType) Unbox() interface{} { return reflect.TypeOf(t) } 19 | 20 | func (t *theSetType) Name() string { return "let-go.lang.Set" } 21 | 22 | func (t *theSetType) Box(bare interface{}) (Value, error) { 23 | // FIXME make this work 24 | return NIL, NewTypeError(bare, "can't be boxed as", t) 25 | } 26 | 27 | // ArrayVectorType is the type of ArrayVectors 28 | var SetType *theSetType = &theSetType{} 29 | 30 | // Set is boxed singly linked list that can hold other Values. 31 | type Set map[Value]struct{} 32 | 33 | func (l Set) Conj(value Value) Collection { 34 | ret := make(Set, len(l)+1) 35 | for k := range l { 36 | ret[k] = l[k] 37 | } 38 | ret[value] = struct{}{} 39 | return ret 40 | } 41 | 42 | func (l Set) Disj(value Value) Set { 43 | ret := make(Set, len(l)+1) 44 | for k := range l { 45 | if k == value { 46 | continue 47 | } 48 | ret[k] = l[k] 49 | } 50 | return ret 51 | } 52 | 53 | func (l Set) Contains(value Value) Boolean { 54 | if _, ok := l[value]; ok { 55 | return TRUE 56 | } 57 | return FALSE 58 | } 59 | 60 | // Type implements Value 61 | func (l Set) Type() ValueType { return SetType } 62 | 63 | func (l Set) keys() []Value { 64 | ks := make([]Value, len(l)) 65 | i := 0 66 | for k := range l { 67 | ks[i] = k 68 | i++ 69 | } 70 | return ks 71 | } 72 | 73 | // Unbox implements Value 74 | func (l Set) Unbox() interface{} { 75 | return l.keys() 76 | } 77 | 78 | // First implements Seq 79 | func (l Set) First() Value { 80 | for k := range l { 81 | return k 82 | } 83 | return NIL 84 | } 85 | 86 | func (l Set) toList() *List { 87 | lst := l.keys() 88 | ret, _ := ListType.Box(lst) 89 | return ret.(*List) 90 | } 91 | 92 | func (l Set) Seq() Seq { 93 | return l.toList() 94 | } 95 | 96 | // More implements Seq 97 | func (l Set) More() Seq { 98 | if len(l) == 1 { 99 | return EmptyList 100 | } 101 | ret := l.toList() 102 | return ret.More() 103 | } 104 | 105 | // Next implements Seq 106 | func (l Set) Next() Seq { 107 | return l.More() 108 | } 109 | 110 | // Cons implements Seq 111 | func (l Set) Cons(val Value) Seq { 112 | return l.toList().Cons(val) 113 | } 114 | 115 | // Count implements Collection 116 | func (l Set) Count() Value { 117 | return Int(len(l)) 118 | } 119 | 120 | func (l Set) RawCount() int { 121 | return len(l) 122 | } 123 | 124 | // Empty implements Collection 125 | func (l Set) Empty() Collection { 126 | return make(Set) 127 | } 128 | 129 | func NewSet(v []Value) Value { 130 | if len(v) == 0 { 131 | return make(Set) 132 | } 133 | newmap := make(Set) 134 | for _, k := range v { 135 | newmap[k] = struct{}{} 136 | } 137 | return newmap 138 | } 139 | 140 | func (l Set) String() string { 141 | b := &strings.Builder{} 142 | b.WriteString("#{") 143 | i := 0 144 | n := len(l) 145 | for k := range l { 146 | b.WriteString(k.String()) 147 | if i < n-1 { 148 | b.WriteRune(' ') 149 | } 150 | i++ 151 | } 152 | b.WriteRune('}') 153 | return b.String() 154 | } 155 | 156 | func (l Set) Arity() int { 157 | return 1 158 | } 159 | 160 | func (l Set) Invoke(pargs []Value) (Value, error) { 161 | if len(pargs) != 1 { 162 | return NIL, fmt.Errorf("wrong number of arguments %d", len(pargs)) 163 | } 164 | if _, ok := l[pargs[0]]; ok { 165 | return pargs[0], nil 166 | } 167 | return NIL, nil 168 | } 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Squishy loafer 4 |

5 | 6 | 7 | ![Tests](https://github.com/nooga/let-go/actions/workflows/go.yml/badge.svg) 8 | 9 | # let-go 10 | 11 | Greetings loafers! *(λ-gophers haha, get it?)* 12 | 13 | This is a bytecode compiler and VM for a language closely resembling Clojure, a Clojure dialect, if you will. 14 | 15 | Here are some nebulous goals in no particular order: 16 | - [x] Quality entertainment, 17 | - [ ] Making it legal to write Clojure at your Go dayjob, 18 | - [ ] Implement as much of Clojure as possible - including persistent data types and true concurrency, 19 | - [x] Provide comfy two-way interop for arbitrary functions and types, 20 | - [ ] AOT (let-go -> standalone binary) would be nice eventually, 21 | - [ ] Strech goal: let-go bytecode -> Go translation. 22 | 23 | Here are the non goals: 24 | - Stellar performance (cough cough, it seems to be faster than [Joker](https://github.com/candid82/joker)), 25 | - Being a drop-in replacement for [clojure/clojure](https://github.com/clojure/clojure) at any point, 26 | - Being a linter/formatter/tooling for Clojure in general. 27 | 28 | ## Current status 29 | 30 | It can eval a Clojure-like lisp. It's semi-good for [solving Advent of Code problems](https://github.com/nooga/aoc2022) but not for anything more serious yet. 31 | 32 | There are some goodies: 33 | 34 | - [x] Macros with syntax quote, 35 | - [x] Reader conditionals, 36 | - [x] Destructuring, 37 | - [x] Multi-arity functions, 38 | - [x] Atoms, channels & go-blocks a'la `core.async`, 39 | - [x] Regular expressions (the Go flavor), 40 | - [x] Simple `json`, `http` and `os` namespaces, 41 | - [x] Many functions ported from `clojure.core`, 42 | - [x] REPL with syntax-highlighting and completions, 43 | - [x] Simple nREPL server that seems to work with [BetterThanTomorrow/calva](https://github.com/BetterThanTomorrow/calva), 44 | 45 | I'm currently contemplating: 46 | 47 | - [ ] Dynamic variables, 48 | - [ ] Exceptions & more comprehensive runtime errors, 49 | - [ ] Numeric tower i.e. floats, bignums and other exotic number species, 50 | - [ ] Optimized persistent data-structures and laziness, 51 | - [ ] Records and protocols, 52 | - [ ] A real test suite would be nice, 53 | 54 | ## Examples 55 | 56 | See: 57 | - [My AoC 2022 solutions](https://github.com/nooga/aoc2022) for an idea of how let-go looks like, 58 | - [Examples](https://github.com/nooga/let-go/tree/main/examples) for small programs I wrote on a whim, 59 | - [Tests](https://github.com/nooga/let-go/tree/main/test) for some random "tests". 60 | 61 | 62 | ## Try online 63 | 64 | Check out [this bare-bones online REPL](https://nooga.github.io/let-go/). It runs a WASM build of let-go in your browser! 65 | 66 | ## Prerequisites and installation 67 | 68 | Building or running let-go from source requires Go 1.19. 69 | 70 | ``` 71 | go install github.com/nooga/let-go@latest 72 | ``` 73 | 74 | Try it out: 75 | 76 | ``` 77 | let-go 78 | ``` 79 | 80 | ## Running from source 81 | 82 | The best way to play with `let-go` right now is to clone this repo and run the REPL like this: 83 | 84 | ``` 85 | go run . 86 | ``` 87 | 88 | To run an expression: 89 | 90 | ``` 91 | go run . -e '(+ 1 1)' 92 | ``` 93 | 94 | To run a file: 95 | 96 | ``` 97 | go run . test/hello.lg 98 | ``` 99 | 100 | Use the `-r` flag to run the REPL after the interpreter has finished with files and `-e`: 101 | 102 | ```bash 103 | go run . -r test/simple.lg # will run simple.lg first, then open up a REPL 104 | go run . -r -e '(* fun 2)' test/simple.lg # will run simple.lg first, then (* fun 2) and REPL 105 | ``` 106 | 107 | --- 108 | [🤓 Follow me on twitter](https://twitter.com/MGasperowicz) 109 | [🐬 Check out monk.io](https://monk.io) 110 | -------------------------------------------------------------------------------- /wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 17 | 59 | 60 | 61 | 113 | 114 | 115 | 116 |
117 | 118 | 119 | -------------------------------------------------------------------------------- /pkg/compiler/compiler_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package compiler 7 | 8 | import ( 9 | "strings" 10 | "testing" 11 | 12 | "github.com/nooga/let-go/pkg/rt" 13 | "github.com/nooga/let-go/pkg/vm" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestContext_Compile(t *testing.T) { 18 | tests := map[string]interface{}{ 19 | "(+ (* 2 20) 2)": 42, 20 | "(- 10 2)": 8, 21 | `(if true "big" "meh")`: "big", 22 | `(if false "big" "meh")`: "meh", 23 | `(if nil 1 2)`: 2, 24 | `(if true 101)`: 101, 25 | `(if false 101)`: nil, 26 | `(do 1 2 3)`: 3, 27 | `(do (+ 1 2))`: 3, 28 | `(do)`: nil, 29 | `(do (def x 40) (+ x 2))`: 42, 30 | `(do (def x true) 31 | (def y (if x :big :meh)) 32 | y)`: "big", 33 | `(if \P \N \P)`: 'N', 34 | `(println "hello" "world")`: nil, 35 | `(do (def sq (fn [x] (* x x))) (sq 9))`: 81, 36 | `[1 2 (+ 1 2)]`: []vm.Value{vm.Int(1), vm.Int(2), vm.Int(3)}, 37 | `'foo`: "foo", 38 | `(quote foo)`: "foo", 39 | `{}`: map[vm.Value]vm.Value{}, 40 | `{:a 1}`: map[vm.Value]vm.Value{vm.Keyword("a"): vm.Int(1)}, 41 | } 42 | for k, v := range tests { 43 | out, err := Eval(k) 44 | assert.NoError(t, err) 45 | assert.Equal(t, v, out.Unbox()) 46 | } 47 | } 48 | 49 | func TestContext_CompileFn(t *testing.T) { 50 | out, err := Eval("(fn [x] (+ x 1))") 51 | assert.NoError(t, err) 52 | 53 | var inc func(int) int 54 | out.Unbox().(func(interface{}))(&inc) 55 | 56 | assert.NotNil(t, inc) 57 | 58 | x := 999 59 | assert.Equal(t, x, inc(x)-1) 60 | } 61 | 62 | func TestContext_CompileFnPoly(t *testing.T) { 63 | out, err := Eval("(fn [x] x)") 64 | assert.NoError(t, err) 65 | 66 | identity := out.Unbox().(func(interface{})) 67 | 68 | var intIdentity func(int) int 69 | identity(&intIdentity) 70 | 71 | var strIdentity func(string) string 72 | identity(&strIdentity) 73 | 74 | x := 999 75 | y := "foobar" 76 | assert.Equal(t, x, intIdentity(x)) 77 | assert.Equal(t, y, strIdentity(y)) 78 | } 79 | 80 | func TestContext_CompileMultiple(t *testing.T) { 81 | src := `(def parens 20) 82 | (def fun 1) 83 | (def double (fn [a] (* a 2))) 84 | (def hey! (fn [a _ b] (+ a b))) 85 | (println (hey! (double parens) 'equals (double fun)))` 86 | 87 | cp := vm.NewConsts() 88 | ns := rt.NS(rt.NameCoreNS) 89 | assert.NotNil(t, ns) 90 | ctx := NewCompiler(cp, ns) 91 | 92 | chunk, _, err := ctx.CompileMultiple(strings.NewReader(src)) 93 | assert.NoError(t, err) 94 | 95 | _, err = vm.NewFrame(chunk, nil).Run() 96 | assert.NoError(t, err) 97 | } 98 | 99 | func TestContext_CompileVar(t *testing.T) { 100 | v := vm.NewVar(rt.NS(rt.NameCoreNS), rt.NameCoreNS, "foo") 101 | 102 | out, err := Eval("(var foo)") 103 | assert.NoError(t, err) 104 | assert.Equal(t, v, out) 105 | 106 | out, err = Eval("#'foo") 107 | assert.NoError(t, err) 108 | assert.Equal(t, v, out) 109 | } 110 | 111 | func TestContext_CompileMultiArityFn(t *testing.T) { 112 | src := `(def f (fn* ([a] (+ a 1)) 113 | ([a b] (+ a b)) 114 | ([a b & r] (+ a b (second r))))) 115 | (and (= 2 (f 1)) 116 | (= 3 (f 1 2)) 117 | (= 6 (f 1 2 4 3)))` 118 | 119 | cp := vm.NewConsts() 120 | ns := rt.NS(rt.NameCoreNS) 121 | assert.NotNil(t, ns) 122 | ctx := NewCompiler(cp, ns) 123 | 124 | chunk, _, err := ctx.CompileMultiple(strings.NewReader(src)) 125 | assert.NoError(t, err) 126 | 127 | val, err := vm.NewFrame(chunk, nil).Run() 128 | assert.NoError(t, err) 129 | assert.Equal(t, vm.TRUE, val) 130 | } 131 | -------------------------------------------------------------------------------- /pkg/vm/list.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | type theListType struct{} 14 | 15 | func (t *theListType) String() string { return t.Name() } 16 | func (t *theListType) Type() ValueType { return TypeType } 17 | func (t *theListType) Unbox() interface{} { return reflect.TypeOf(t) } 18 | 19 | func (lt *theListType) Name() string { return "let-go.lang.PersistentList" } 20 | 21 | func (lt *theListType) Box(bare interface{}) (Value, error) { 22 | arr, ok := bare.([]Value) 23 | if !ok { 24 | return EmptyList, NewTypeError(bare, "can't be boxed as", lt) 25 | } 26 | var ret Seq = EmptyList 27 | n := len(arr) 28 | if n == 0 { 29 | return ret.(*List), nil 30 | } 31 | for i := range arr { 32 | ret = ret.Cons(arr[n-i-1]) 33 | } 34 | return ret.(*List), nil 35 | } 36 | 37 | // ListType is the type of Lists 38 | var ListType *theListType = &theListType{} 39 | 40 | // EmptyList is an empty List 41 | var EmptyList *List = &List{count: 0} 42 | 43 | // List is boxed singly linked list that can hold other Values. 44 | type List struct { 45 | first Value 46 | next *List 47 | count int 48 | } 49 | 50 | func (l *List) Conj(value Value) Collection { 51 | return l.Cons(value).(*List) 52 | } 53 | 54 | // Type implements Value 55 | func (l *List) Type() ValueType { return ListType } 56 | 57 | // Unbox implements Value 58 | func (l *List) Unbox() interface{} { 59 | if l.count == 0 { 60 | return []Value{} 61 | } 62 | bare := make([]Value, l.count) 63 | l.unboxInternal(&bare, 0) 64 | return bare 65 | } 66 | 67 | func (l *List) unboxInternal(target *[]Value, idx int) { 68 | if l.count == 0 { 69 | return 70 | } 71 | (*target)[idx] = l.first 72 | l.next.unboxInternal(target, idx+1) 73 | } 74 | 75 | // First implements Seq 76 | func (l *List) First() Value { 77 | if l.count == 0 { 78 | return NIL 79 | } 80 | return l.first 81 | } 82 | 83 | // More implements Seq 84 | func (l *List) More() Seq { 85 | if l.count == 0 { 86 | return l 87 | } 88 | return l.next 89 | } 90 | 91 | // Next implements Seq 92 | func (l *List) Next() Seq { 93 | if l.count == 0 { 94 | return l 95 | } 96 | return l.next 97 | } 98 | 99 | // Cons implements Seq 100 | func (l *List) Cons(val Value) Seq { 101 | return &List{ 102 | first: val, 103 | next: l, 104 | count: l.count + 1, 105 | } 106 | } 107 | 108 | func (l *List) Seq() Seq { 109 | return l 110 | } 111 | 112 | // Count implements Collection 113 | func (l *List) Count() Value { 114 | return Int(l.count) 115 | } 116 | 117 | func (l *List) RawCount() int { 118 | return l.count 119 | } 120 | 121 | // Empty implements Collection 122 | func (l *List) Empty() Collection { 123 | return EmptyList 124 | } 125 | 126 | func (l *List) ValueAt(key Value) Value { 127 | return l.ValueAtOr(key, NIL) 128 | } 129 | 130 | func (l *List) ValueAtOr(key Value, dflt Value) Value { 131 | if key == NIL { 132 | return dflt 133 | } 134 | numkey, ok := key.(Int) 135 | if !ok || numkey < 0 { 136 | return dflt 137 | } 138 | li := l 139 | for i := 0; i < int(numkey); i++ { 140 | li = li.next 141 | if li == nil { 142 | return dflt 143 | } 144 | } 145 | if li.first == nil { 146 | return NIL 147 | } 148 | return li.first 149 | } 150 | 151 | func (l *List) String() string { 152 | b := &strings.Builder{} 153 | b.WriteRune('(') 154 | li := l.Unbox().([]Value) 155 | n := len(li) 156 | for i := range li { 157 | b.WriteString(li[i].String()) 158 | if i < n-1 { 159 | b.WriteRune(' ') 160 | } 161 | } 162 | b.WriteRune(')') 163 | return b.String() 164 | } 165 | 166 | func NewList(vs []Value) Value { 167 | li, err := ListType.Box(vs) 168 | if err != nil { 169 | return EmptyList 170 | } 171 | return li 172 | } 173 | -------------------------------------------------------------------------------- /pkg/rt/json.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package rt 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/nooga/let-go/pkg/vm" 13 | ) 14 | 15 | func toValue(keywordize bool, i interface{}) (vm.Value, error) { 16 | switch i := i.(type) { 17 | case string: 18 | return vm.String(i), nil 19 | case bool: 20 | return vm.Boolean(i), nil 21 | case float64: 22 | return vm.Int(i), nil 23 | case nil: 24 | return vm.NIL, nil 25 | case []interface{}: 26 | r := make([]vm.Value, len(i)) 27 | for j := 0; j < len(i); j++ { 28 | v, e := toValue(keywordize, i[j]) 29 | if e != nil { 30 | return vm.NIL, e 31 | } 32 | r[j] = v 33 | } 34 | return vm.NewArrayVector(r), nil 35 | case map[string]interface{}: 36 | newmap := make(vm.Map) 37 | for k, v := range i { 38 | ve, e := toValue(keywordize, v) 39 | if e != nil { 40 | return vm.NIL, e 41 | } 42 | if keywordize { 43 | newmap[vm.Keyword(k)] = ve 44 | } else { 45 | newmap[vm.String(k)] = ve 46 | } 47 | 48 | } 49 | return newmap, nil 50 | default: 51 | return vm.NIL, vm.NewExecutionError("invalid JSON value") 52 | } 53 | } 54 | 55 | func fromValue(v vm.Value) (interface{}, error) { 56 | switch v.Type() { 57 | case vm.StringType: 58 | return string(v.(vm.String)), nil 59 | case vm.IntType: 60 | return int(v.(vm.Int)), nil 61 | case vm.BooleanType: 62 | return bool(v.(vm.Boolean)), nil 63 | case vm.MapType: 64 | r := map[string]interface{}{} 65 | for k, ov := range v.(vm.Map) { 66 | vv, e := fromValue(ov) 67 | if e != nil { 68 | return vm.NIL, vm.NewExecutionError("invalid VM value") 69 | } 70 | nk := k.String() 71 | if k.Type() == vm.KeywordType { 72 | nk = nk[1:] 73 | } 74 | r[nk] = vv 75 | } 76 | return r, nil 77 | case vm.KeywordType: 78 | return string(v.(vm.Keyword)), nil 79 | case vm.NilType: 80 | return nil, nil 81 | default: 82 | s, ok := v.(vm.Seq) 83 | if !ok { 84 | return v.String(), nil 85 | } 86 | r := []interface{}{} 87 | for s != vm.EmptyList { 88 | uv, e := fromValue(s.First()) 89 | if e != nil { 90 | return vm.NIL, e 91 | } 92 | r = append(r, uv) 93 | s = s.Next() 94 | } 95 | return r, nil 96 | } 97 | } 98 | 99 | func optionsHaveKeywordize(opts vm.Value) (bool, error) { 100 | o, ok := opts.(vm.Map) 101 | if !ok { 102 | return false, fmt.Errorf("read-json options are not Map") 103 | } 104 | return vm.IsTruthy(o.ValueAt(vm.Keyword("keywords?"))), nil 105 | } 106 | 107 | // nolint 108 | func installJSONNS() { 109 | readJson, err := vm.NativeFnType.Wrap(func(vs []vm.Value) (vm.Value, error) { 110 | if len(vs) < 1 || len(vs) > 2 { 111 | return vm.NIL, fmt.Errorf("wrong number of arguments %d", len(vs)) 112 | } 113 | 114 | s, ok := vs[0].(vm.String) 115 | if !ok { 116 | return vm.NIL, fmt.Errorf("read-json expected String") 117 | } 118 | 119 | keywordize := false 120 | var err error 121 | if len(vs) == 2 { 122 | keywordize, err = optionsHaveKeywordize(vs[1]) 123 | if err != nil { 124 | return vm.NIL, err 125 | } 126 | } 127 | 128 | var v interface{} 129 | err = json.Unmarshal([]byte(s), &v) 130 | if err != nil { 131 | return vm.NIL, err 132 | } 133 | 134 | return toValue(keywordize, v) 135 | }) 136 | 137 | writeJson, err := vm.NativeFnType.Wrap(func(vs []vm.Value) (vm.Value, error) { 138 | if len(vs) != 1 { 139 | return vm.NIL, fmt.Errorf("wrong number of arguments %d", len(vs)) 140 | } 141 | v, err := fromValue(vs[0]) 142 | if err != nil { 143 | return vm.NIL, err 144 | } 145 | s, err := json.Marshal(v) 146 | if err != nil { 147 | return vm.NIL, err 148 | } 149 | return vm.String(s), nil 150 | }) 151 | 152 | if err != nil { 153 | panic("json NS init failed") 154 | } 155 | 156 | ns := vm.NewNamespace("json") 157 | 158 | // vars 159 | CurrentNS = ns.Def("*ns*", ns) 160 | 161 | ns.Def("read-json", readJson) 162 | ns.Def("write-json", writeJson) 163 | RegisterNS(ns) 164 | } 165 | -------------------------------------------------------------------------------- /pkg/vm/vector.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theArrayVectorType struct{} 15 | 16 | func (t *theArrayVectorType) String() string { return t.Name() } 17 | func (t *theArrayVectorType) Type() ValueType { return TypeType } 18 | func (t *theArrayVectorType) Unbox() interface{} { return reflect.TypeOf(t) } 19 | 20 | func (lt *theArrayVectorType) Name() string { return "let-go.lang.ArrayVector" } 21 | 22 | func (lt *theArrayVectorType) Box(bare interface{}) (Value, error) { 23 | arr, ok := bare.([]Value) 24 | if !ok { 25 | return NIL, NewTypeError(bare, "can't be boxed as", lt) 26 | } 27 | 28 | return ArrayVector(arr), nil 29 | } 30 | 31 | // ArrayVectorType is the type of ArrayVectors 32 | var ArrayVectorType *theArrayVectorType = &theArrayVectorType{} 33 | 34 | // ArrayVector is boxed singly linked list that can hold other Values. 35 | type ArrayVector []Value 36 | 37 | func (l ArrayVector) Conj(val Value) Collection { 38 | ret := make([]Value, len(l)+1) 39 | copy(ret, l) 40 | ret[len(ret)-1] = val 41 | return ArrayVector(ret) 42 | } 43 | 44 | // Type implements Value 45 | func (l ArrayVector) Type() ValueType { return ArrayVectorType } 46 | 47 | // Unbox implements Value 48 | func (l ArrayVector) Unbox() interface{} { 49 | return []Value(l) 50 | } 51 | 52 | // First implements Seq 53 | func (l ArrayVector) First() Value { 54 | if len(l) == 0 { 55 | return NIL 56 | } 57 | return l[0] 58 | } 59 | 60 | // More implements Seq 61 | func (l ArrayVector) More() Seq { 62 | if len(l) <= 1 { 63 | return EmptyList 64 | } 65 | newl, _ := ListType.Box([]Value(l[1:])) 66 | return newl.(*List) 67 | } 68 | 69 | // Next implements Seq 70 | func (l ArrayVector) Next() Seq { 71 | return l.More() 72 | } 73 | 74 | // Cons implements Seq 75 | func (l ArrayVector) Cons(val Value) Seq { 76 | ret := EmptyList 77 | n := len(l) - 1 78 | for i := range l { 79 | ret = ret.Cons(l[n-i]).(*List) 80 | } 81 | return ret.Cons(val) 82 | } 83 | 84 | func (l ArrayVector) Seq() Seq { 85 | return l 86 | } 87 | 88 | // Count implements Collection 89 | func (l ArrayVector) Count() Value { 90 | return Int(len(l)) 91 | } 92 | 93 | func (l ArrayVector) RawCount() int { 94 | return len(l) 95 | } 96 | 97 | // Empty implements Collection 98 | func (l ArrayVector) Empty() Collection { 99 | return make(ArrayVector, 0) 100 | } 101 | 102 | func NewArrayVector(v []Value) Value { 103 | vk := make([]Value, len(v)) 104 | copy(vk, v) 105 | return ArrayVector(vk) 106 | } 107 | 108 | func (l ArrayVector) ValueAt(key Value) Value { 109 | return l.ValueAtOr(key, NIL) 110 | } 111 | 112 | func (l ArrayVector) ValueAtOr(key Value, dflt Value) Value { 113 | if key == NIL { 114 | return dflt 115 | } 116 | numkey, ok := key.(Int) 117 | if !ok || numkey < 0 || int(numkey) >= len(l) { 118 | return dflt 119 | } 120 | return l[int(numkey)] 121 | } 122 | 123 | func (l ArrayVector) Contains(value Value) Boolean { 124 | numkey, ok := value.(Int) 125 | if !ok || numkey < 0 || int(numkey) >= len(l) { 126 | return FALSE 127 | } 128 | return TRUE 129 | } 130 | 131 | func (l ArrayVector) Assoc(k Value, v Value) Associative { 132 | var new ArrayVector = NewArrayVector(l).(ArrayVector) 133 | ik, ok := k.(Int) 134 | if !ok { 135 | return NIL 136 | } 137 | if ik < 0 || int(ik) >= len(new) { 138 | return NIL 139 | } 140 | new[ik] = v 141 | return new 142 | } 143 | 144 | func (l ArrayVector) Dissoc(k Value) Associative { 145 | return NIL 146 | } 147 | 148 | func (l ArrayVector) Arity() int { 149 | return 1 150 | } 151 | 152 | func (l ArrayVector) Invoke(pargs []Value) (Value, error) { 153 | vl := len(pargs) 154 | if vl != 1 { 155 | return NIL, fmt.Errorf("wrong number of arguments %d", vl) 156 | } 157 | return l.ValueAt(pargs[0]), nil 158 | } 159 | 160 | func (l ArrayVector) String() string { 161 | b := &strings.Builder{} 162 | b.WriteRune('[') 163 | n := len(l) 164 | for i := range l { 165 | b.WriteString(l[i].String()) 166 | if i < n-1 { 167 | b.WriteRune(' ') 168 | } 169 | } 170 | b.WriteRune(']') 171 | return b.String() 172 | } 173 | -------------------------------------------------------------------------------- /pkg/vm/value.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | // ValueType represents a type of a Value 14 | type ValueType interface { 15 | Value 16 | Name() string 17 | Box(interface{}) (Value, error) 18 | } 19 | 20 | // Value is implemented by all LETGO values 21 | type Value interface { 22 | fmt.Stringer 23 | Type() ValueType 24 | Unbox() interface{} 25 | } 26 | 27 | // Seq is implemented by all sequence-like values 28 | type Seq interface { 29 | Value 30 | Cons(Value) Seq 31 | First() Value 32 | More() Seq 33 | Next() Seq 34 | } 35 | 36 | type Sequable interface { 37 | Value 38 | Seq() Seq 39 | } 40 | 41 | type Counted interface { 42 | Value 43 | RawCount() int 44 | Count() Value 45 | } 46 | 47 | // Collection is implemented by all collections 48 | type Collection interface { 49 | Value 50 | Counted 51 | Empty() Collection 52 | Conj(Value) Collection 53 | } 54 | 55 | type Fn interface { 56 | Value 57 | Invoke([]Value) (Value, error) 58 | Arity() int 59 | } 60 | 61 | type Associative interface { 62 | Value 63 | Assoc(Value, Value) Associative 64 | Dissoc(Value) Associative 65 | } 66 | 67 | type Lookup interface { 68 | Value 69 | ValueAt(Value) Value 70 | ValueAtOr(Value, Value) Value 71 | } 72 | 73 | type Keyed interface { 74 | Value 75 | Contains(Value) Boolean 76 | } 77 | 78 | type Receiver interface { 79 | Value 80 | InvokeMethod(Symbol, []Value) (Value, error) 81 | } 82 | 83 | type Named interface { 84 | Value 85 | Name() Value 86 | Namespace() Value 87 | } 88 | 89 | type Reference interface { 90 | Deref() Value 91 | } 92 | 93 | type theTypeType struct{} 94 | 95 | var TypeType *theTypeType = &theTypeType{} 96 | 97 | func (t *theTypeType) String() string { return t.Name() } 98 | func (t *theTypeType) Type() ValueType { return t } 99 | func (t *theTypeType) Unbox() interface{} { return reflect.TypeOf(t) } 100 | 101 | func (t *theTypeType) Name() string { return "let-go.lang.Type" } 102 | func (t *theTypeType) Box(b interface{}) (Value, error) { 103 | //FIXME this is probably not accurate 104 | return NIL, NewTypeError(b, "can't be boxed as", t) 105 | } 106 | 107 | func BoxValue(v reflect.Value) (Value, error) { 108 | if !v.IsValid() { 109 | return NIL, NewTypeError(v, "can't be boxed", nil) 110 | } 111 | if v.CanInterface() { 112 | rv, ok := v.Interface().(Value) 113 | if ok { 114 | return rv, nil 115 | } 116 | } 117 | switch v.Type().Kind() { 118 | case reflect.Int: 119 | return IntType.Box(v.Interface()) 120 | case reflect.String: 121 | return StringType.Box(v.Interface()) 122 | case reflect.Bool: 123 | return BooleanType.Box(v.Interface()) 124 | case reflect.Func: 125 | return NativeFnType.Box(v.Interface()) 126 | case reflect.Ptr: 127 | if v.IsNil() { 128 | return NIL, nil 129 | } 130 | // FIXME check if this is how we should handle pointers 131 | return BoxValue(v.Elem()) 132 | case reflect.Slice, reflect.Array: 133 | if v.IsNil() { 134 | // FIXME not sure if maybe this has to be empty coll in let-go-land 135 | return NIL, nil 136 | } 137 | in := make([]Value, v.Len()) 138 | for i := 0; i < v.Len(); i++ { 139 | e := v.Index(i) 140 | mv, err := BoxValue(e) 141 | if err != nil { 142 | return NIL, NewTypeError(e, "can't be boxed", nil).Wrap(err) 143 | } 144 | in[i] = mv 145 | } 146 | return ArrayVector(in), nil 147 | case reflect.Map: 148 | if v.IsNil() { 149 | // FIXME not sure if maybe this has to be empty coll in let-go-land 150 | return NIL, nil 151 | } 152 | in := make(map[Value]Value) 153 | iter := v.MapRange() 154 | for iter.Next() { 155 | k, err := BoxValue(iter.Key()) 156 | if err != nil { 157 | return NIL, err //FIXME wrap 158 | } 159 | v, err := BoxValue(iter.Value()) 160 | if err != nil { 161 | return NIL, err //FIXME wrap 162 | } 163 | in[k] = v 164 | } 165 | return MapType.Box(in) 166 | case reflect.Chan: 167 | if v.IsNil() { 168 | return NIL, nil 169 | } 170 | return ChanType.Box(v.Interface()) 171 | default: 172 | if v.CanInterface() { 173 | return NewBoxed(v.Interface()), nil 174 | } 175 | return NIL, NewTypeError(v, "is not boxable", nil) 176 | } 177 | } 178 | 179 | func IsTruthy(v Value) bool { 180 | return !(v == NIL || v == FALSE) 181 | } 182 | -------------------------------------------------------------------------------- /pkg/rt/http.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package rt 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "strings" 13 | 14 | "github.com/nooga/let-go/pkg/vm" 15 | ) 16 | 17 | type Handler struct { 18 | fn vm.Fn 19 | } 20 | 21 | func methodToLG(scheme string) vm.Keyword { 22 | return map[string]vm.Keyword{ 23 | "GET": vm.Keyword("get"), 24 | "POST": vm.Keyword("post"), 25 | "PUT": vm.Keyword("put"), 26 | "DELETE": vm.Keyword("delete"), 27 | "HEAD": vm.Keyword("head"), 28 | "OPTIONS": vm.Keyword("options"), 29 | }[scheme] 30 | } 31 | 32 | func (h *Handler) ServeHTTP(resp http.ResponseWriter, request *http.Request) { 33 | req := vm.Map{} 34 | 35 | req[vm.Keyword("request-method")] = methodToLG(request.Method) 36 | url := request.URL 37 | 38 | if request.TLS == nil { 39 | req[vm.Keyword("scheme")] = vm.Keyword("http") 40 | } else { 41 | req[vm.Keyword("scheme")] = vm.Keyword("https") 42 | } 43 | req[vm.Keyword("uri")] = vm.String(url.RequestURI()) 44 | req[vm.Keyword("query-string")] = vm.String(url.RawQuery) 45 | defer request.Body.Close() 46 | bytes, err := io.ReadAll(request.Body) 47 | if err != nil { 48 | resp.WriteHeader(500) 49 | _, err := resp.Write([]byte(fmt.Sprintf("%s", err))) 50 | if err != nil { 51 | fmt.Println("HTTP Error while writing error 500", err) 52 | } 53 | return 54 | } 55 | req[vm.Keyword("body")] = vm.String(bytes) 56 | req[vm.Keyword("remote-addr")] = vm.String(request.RemoteAddr) 57 | req[vm.Keyword("server-addr")] = vm.String(request.Host) 58 | req[vm.Keyword("server-port")] = vm.String(url.Port()) 59 | 60 | if len(request.Header) > 0 { 61 | hs := vm.Map{} 62 | for k, v := range request.Header { 63 | hs[vm.String(strings.ToLower(k))] = vm.String(strings.Join(v, ",")) 64 | } 65 | req[vm.Keyword("headers")] = hs 66 | } 67 | 68 | res, err := h.fn.Invoke([]vm.Value{req}) 69 | if err != nil { 70 | resp.WriteHeader(500) 71 | _, err := resp.Write([]byte(fmt.Sprintf("%s", err))) 72 | if err != nil { 73 | fmt.Println("HTTP Error while writing error 500", err) 74 | } 75 | return 76 | } 77 | 78 | ress, ok := res.(vm.Map) 79 | if !ok { 80 | resp.WriteHeader(500) 81 | _, err := resp.Write([]byte("handler returned malformed response")) 82 | if err != nil { 83 | fmt.Println("HTTP Error while writing error 500", err) 84 | } 85 | return 86 | } 87 | head := resp.Header() 88 | headers, ok := ress[vm.Keyword("headers")] 89 | if ok { 90 | for k, v := range headers.(vm.Map) { 91 | head.Add(k.Unbox().(string), v.Unbox().(string)) 92 | } 93 | } 94 | status, ok := ress[vm.Keyword("status")] 95 | if !ok { 96 | status = vm.Int(200) 97 | } 98 | body, ok := ress[vm.Keyword("body")] 99 | if !ok { 100 | body = vm.String("") 101 | } 102 | resp.WriteHeader(int(status.(vm.Int))) 103 | _, err = resp.Write([]byte(body.(vm.String))) 104 | if err != nil { 105 | fmt.Println("HTTP Error while writing error 500", err) 106 | } 107 | } 108 | 109 | // nolint 110 | func installHttpNS() { 111 | // FIXME, this should box the function directly 112 | handle, err := vm.NativeFnType.Wrap(func(vs []vm.Value) (vm.Value, error) { 113 | fnm := vs[1].Unbox().(func(interface{})) 114 | var fn func(w http.ResponseWriter, r *http.Request) interface{} 115 | fnm(&fn) 116 | http.HandleFunc(vs[0].Unbox().(string), func(w http.ResponseWriter, r *http.Request) { 117 | fn(w, r) 118 | }) 119 | return vm.NIL, nil 120 | }) 121 | 122 | if err != nil { 123 | panic("http NS init failed") 124 | } 125 | 126 | serve, err := vm.NativeFnType.Box(http.ListenAndServe) 127 | if err != nil { 128 | panic("http NS init failed") 129 | } 130 | 131 | serve2, err := vm.NativeFnType.Wrap(func(vs []vm.Value) (vm.Value, error) { 132 | if len(vs) != 2 { 133 | return vm.NIL, vm.NewExecutionError("serve expects 2 args") 134 | } 135 | addr, ok := vs[1].(vm.String) 136 | if !ok { 137 | return vm.NIL, vm.NewExecutionError("serve expected listen address as String") 138 | } 139 | handlerFunc, ok := vs[0].(vm.Fn) 140 | if !ok { 141 | return vm.NIL, vm.NewExecutionError("serve expected handler function as Fn") 142 | } 143 | handler := &Handler{fn: handlerFunc} 144 | http.ListenAndServe(string(addr), handler) 145 | return vm.NIL, nil 146 | }) 147 | if err != nil { 148 | panic("http NS init failed") 149 | } 150 | 151 | ns := vm.NewNamespace("http") 152 | 153 | // vars 154 | CurrentNS = ns.Def("*ns*", ns) 155 | 156 | ns.Def("handle", handle) 157 | ns.Def("serve", serve) 158 | ns.Def("serve2", serve2) 159 | RegisterNS(ns) 160 | } 161 | -------------------------------------------------------------------------------- /pkg/vm/vm_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "math/rand" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestNilType(t *testing.T) { 16 | myNil, err := NilType.Box(nil) 17 | assert.NoError(t, err) 18 | assert.Equal(t, NIL, myNil) 19 | assert.Equal(t, "nil", myNil.Type().Name()) 20 | assert.Nil(t, myNil.Unbox()) 21 | } 22 | 23 | func TestIntType(t *testing.T) { 24 | for i := 0; i < 100; i++ { 25 | v := rand.Int() 26 | bv, err := IntType.Box(v) 27 | assert.NoError(t, err) 28 | assert.Equal(t, bv.Type(), IntType) 29 | assert.True(t, bv.Type() == IntType) 30 | assert.Equal(t, "let-go.lang.Int", bv.Type().Name()) 31 | assert.Equal(t, v, bv.Unbox()) 32 | assert.Equal(t, v, bv.Unbox().(int)) 33 | } 34 | 35 | a, err := IntType.Box(9) 36 | assert.NoError(t, err) 37 | 38 | b, err := IntType.Box(1) 39 | assert.NoError(t, err) 40 | 41 | c, err := IntType.Box(1) 42 | assert.NoError(t, err) 43 | 44 | assert.NotEqual(t, a, b) 45 | assert.NotEqual(t, a, c) 46 | assert.Equal(t, c, b) 47 | assert.False(t, a == b) 48 | assert.True(t, c == b) 49 | 50 | bad := "bad" 51 | badInt, err := IntType.Box(bad) 52 | assert.Error(t, err) 53 | assert.Zero(t, badInt) 54 | } 55 | 56 | func TestListType(t *testing.T) { 57 | 58 | l := EmptyList 59 | assert.Zero(t, l.Count()) 60 | 61 | rawList := l.Unbox() 62 | assert.Equal(t, []Value{}, rawList) 63 | 64 | emptyList, err := ListType.Box(rawList) 65 | assert.NoError(t, err) 66 | 67 | assert.Equal(t, EmptyList, emptyList) 68 | assert.Equal(t, l, emptyList) 69 | 70 | a, err := IntType.Box(9) 71 | assert.NoError(t, err) 72 | 73 | b, err := IntType.Box(1) 74 | assert.NoError(t, err) 75 | 76 | l2 := l.Cons(a).(*List) 77 | assert.Equal(t, 1, l2.Count().Unbox()) 78 | 79 | l3 := l.Cons(b).(*List) 80 | assert.Equal(t, 1, l3.Count().Unbox()) 81 | 82 | ltype := "let-go.lang.PersistentList" 83 | assert.Equal(t, ltype, l.Type().Name()) 84 | assert.Equal(t, ltype, l2.Type().Name()) 85 | assert.Equal(t, ltype, l3.Type().Name()) 86 | assert.Same(t, l.Type(), l2.Type()) 87 | 88 | assert.NotEqual(t, l2, l3) 89 | 90 | l4 := l2.Cons(b).(*List) 91 | assert.Equal(t, 2, l4.Count().Unbox()) 92 | 93 | v2 := []Value{a} 94 | v3 := []Value{b} 95 | v4 := []Value{b, a} 96 | 97 | assert.Equal(t, v2, l2.Unbox()) 98 | assert.Equal(t, v3, l3.Unbox()) 99 | assert.Equal(t, v4, l4.Unbox()) 100 | 101 | assert.Equal(t, l4.More(), l4.Next()) 102 | 103 | assert.Equal(t, NIL, l.First()) 104 | assert.Equal(t, l, l.Next()) 105 | assert.Equal(t, l, l.More()) 106 | assert.Equal(t, EmptyList, l.Next()) 107 | assert.Equal(t, EmptyList, l.More()) 108 | 109 | assert.Equal(t, l2, l4.Next()) 110 | assert.True(t, l2 == l4.Next()) 111 | 112 | assert.Equal(t, l2.First(), l4.Next().First()) 113 | 114 | assert.Equal(t, l3.First(), l4.First()) 115 | 116 | n := 100 117 | values := make([]Value, n) 118 | for i := 0; i < n; i++ { 119 | v, err := IntType.Box(rand.Int()) 120 | assert.NoError(t, err) 121 | if err != nil { 122 | return 123 | } 124 | values[i] = v 125 | } 126 | 127 | list, err := ListType.Box(values) 128 | assert.NoError(t, err) 129 | assert.Equal(t, n, list.(*List).count) 130 | 131 | randomValues := list.Unbox().([]Value) 132 | for i := 0; i < n; i++ { 133 | assert.Equal(t, values[i], randomValues[i]) 134 | } 135 | 136 | assert.Equal(t, l, l.Empty()) 137 | assert.Equal(t, EmptyList, l.Empty()) 138 | assert.Equal(t, EmptyList, list.(*List).Empty()) 139 | assert.Equal(t, l, list.(*List).Empty()) 140 | 141 | bad := "bad" 142 | badList, err := ListType.Box(bad) 143 | assert.Error(t, err) 144 | assert.Equal(t, EmptyList, badList) 145 | } 146 | 147 | func TestSimpleCall(t *testing.T) { 148 | forty, err := IntType.Box(40) 149 | assert.NoError(t, err) 150 | two, err := IntType.Box(2) 151 | assert.NoError(t, err) 152 | 153 | plus, err := NativeFnType.Box(func(a int, b int) int { return b + a }) 154 | assert.NoError(t, err) 155 | 156 | consts := NewConsts() 157 | consts.Intern(forty) 158 | consts.Intern(two) 159 | consts.Intern(plus) 160 | 161 | c := NewCodeChunk(consts) 162 | c.maxStack = 4 163 | c.Append(OP_LOAD_CONST) 164 | c.Append32(2) 165 | 166 | c.Append(OP_LOAD_CONST) 167 | c.Append32(0) 168 | c.Append(OP_LOAD_CONST) 169 | c.Append32(1) 170 | 171 | c.Append(OP_INVOKE) 172 | c.Append32(2) 173 | 174 | c.Append(OP_RETURN) 175 | 176 | var out Value 177 | 178 | frame := NewFrame(c, nil) 179 | out, err = frame.Run() 180 | assert.NoError(t, err) 181 | 182 | assert.Equal(t, 42, out.Unbox()) 183 | } 184 | -------------------------------------------------------------------------------- /pkg/vm/map.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | type theMapType struct{} 15 | 16 | func (t *theMapType) String() string { return t.Name() } 17 | func (t *theMapType) Type() ValueType { return TypeType } 18 | func (t *theMapType) Unbox() interface{} { return reflect.TypeOf(t) } 19 | 20 | func (t *theMapType) Name() string { return "let-go.lang.Map" } 21 | 22 | func (t *theMapType) Box(bare interface{}) (Value, error) { 23 | casted, ok := bare.(map[Value]Value) 24 | if !ok { 25 | return NIL, NewTypeError(bare, "can't be boxed as", t) 26 | } 27 | 28 | return Map(casted), nil 29 | } 30 | 31 | // ArrayVectorType is the type of ArrayVectors 32 | var MapType *theMapType = &theMapType{} 33 | 34 | // Map is boxed singly linked list that can hold other Values. 35 | type Map map[Value]Value 36 | 37 | func (l Map) Conj(value Value) Collection { 38 | // FIXME this needs MapEntry 39 | if value.Type() != ArrayVectorType { 40 | // FIXME this is error 41 | return l 42 | } 43 | v := value.(ArrayVector) 44 | if len(v) != 2 { 45 | // FIXME this is error 46 | return l 47 | } 48 | ret := make(Map, len(l)+1) 49 | for k := range l { 50 | ret[k] = l[k] 51 | } 52 | ret[v[0]] = v[1] 53 | return ret 54 | } 55 | 56 | // Type implements Value 57 | func (l Map) Type() ValueType { return MapType } 58 | 59 | // Unbox implements Value 60 | func (l Map) Unbox() interface{} { 61 | return map[Value]Value(l) 62 | } 63 | 64 | // First implements Seq 65 | func (l Map) First() Value { 66 | if len(l) == 0 { 67 | return NIL 68 | } 69 | for k, v := range l { 70 | return ArrayVector{k, v} 71 | } 72 | return NIL // unreachable 73 | } 74 | 75 | func toList(l Map) *List { 76 | var lst []Value 77 | for k, v := range l { 78 | lst = append(lst, ArrayVector{k, v}) 79 | } 80 | ret, _ := ListType.Box(lst) 81 | return ret.(*List) 82 | } 83 | 84 | func (l Map) Seq() Seq { 85 | return toList(l) 86 | } 87 | 88 | // More implements Seq 89 | func (l Map) More() Seq { 90 | if len(l) == 1 { 91 | return EmptyList 92 | } 93 | ret := toList(l) 94 | return ret.More() 95 | } 96 | 97 | // Next implements Seq 98 | func (l Map) Next() Seq { 99 | return l.More() 100 | } 101 | 102 | // Cons implements Seq 103 | func (l Map) Cons(val Value) Seq { 104 | return toList(l).Cons(val) 105 | } 106 | 107 | // Count implements Collection 108 | func (l Map) Count() Value { 109 | return Int(len(l)) 110 | } 111 | 112 | func (l Map) RawCount() int { 113 | return len(l) 114 | } 115 | 116 | // Empty implements Collection 117 | func (l Map) Empty() Collection { 118 | return make(Map) 119 | } 120 | 121 | func (l Map) Assoc(k Value, v Value) Associative { 122 | // FIXME implement persistent maps :P 123 | newmap := make(Map) 124 | for ok, ov := range l { 125 | newmap[ok] = ov 126 | } 127 | newmap[k] = v 128 | return newmap 129 | } 130 | 131 | func (l Map) Dissoc(k Value) Associative { 132 | // FIXME implement persistent maps :P 133 | newmap := make(Map) 134 | for ok, ov := range l { 135 | if ok == k { 136 | continue 137 | } 138 | newmap[ok] = ov 139 | } 140 | return newmap 141 | } 142 | 143 | func (l Map) ValueAt(key Value) Value { 144 | return l.ValueAtOr(key, NIL) 145 | } 146 | 147 | func (l Map) ValueAtOr(key Value, dflt Value) Value { 148 | if key == NIL { 149 | return dflt 150 | } 151 | ret, ok := l[key] 152 | if !ok { 153 | return dflt 154 | } 155 | return ret 156 | } 157 | 158 | func (l Map) Contains(value Value) Boolean { 159 | if _, ok := l[value]; ok { 160 | return TRUE 161 | } 162 | return FALSE 163 | } 164 | 165 | func NewMap(v []Value) Value { 166 | if len(v) == 0 { 167 | return make(Map) 168 | } 169 | if len(v)%2 != 0 { 170 | // FIXME this is an error 171 | return NIL 172 | } 173 | newmap := make(Map) 174 | for i := 0; i < len(v); i += 2 { 175 | newmap[v[i]] = v[i+1] 176 | } 177 | return newmap 178 | } 179 | 180 | func (l Map) String() string { 181 | b := &strings.Builder{} 182 | b.WriteRune('{') 183 | i := 0 184 | n := len(l) 185 | for k, v := range l { 186 | b.WriteString(k.String()) 187 | b.WriteRune(' ') 188 | b.WriteString(v.String()) 189 | if i < n-1 { 190 | b.WriteRune(' ') 191 | } 192 | i++ 193 | } 194 | b.WriteRune('}') 195 | return b.String() 196 | } 197 | 198 | func (l Map) Arity() int { 199 | return -1 200 | } 201 | 202 | func (l Map) Invoke(pargs []Value) (Value, error) { 203 | vl := len(pargs) 204 | if vl < 1 || vl > 2 { 205 | // FIXME return error 206 | return NIL, fmt.Errorf("wrong number of arguments %d", vl) 207 | } 208 | if vl == 1 { 209 | return l.ValueAt(pargs[0]), nil 210 | } 211 | return l.ValueAtOr(pargs[0], pargs[1]), nil 212 | } 213 | -------------------------------------------------------------------------------- /pkg/nrepl/server.go: -------------------------------------------------------------------------------- 1 | package nrepl 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/hashicorp/go-uuid" 12 | "github.com/nooga/let-go/pkg/compiler" 13 | "github.com/nooga/let-go/pkg/vm" 14 | "github.com/zeebo/bencode" 15 | ) 16 | 17 | type session struct { 18 | ctx *compiler.Context 19 | sessionID string 20 | lastID int 21 | conn net.Conn 22 | dec *bencode.Decoder 23 | describe map[string]interface{} 24 | } 25 | 26 | func newSession(ctx *compiler.Context, conn net.Conn) *session { 27 | empty := map[string]interface{}{} 28 | return &session{ 29 | ctx: ctx, 30 | sessionID: "none", 31 | lastID: 0, 32 | conn: conn, 33 | dec: bencode.NewDecoder(conn), 34 | describe: map[string]interface{}{ 35 | "aux": empty, 36 | "status": []string{"done"}, 37 | "ops": map[string]interface{}{ 38 | "clone": empty, 39 | "eval": empty, 40 | "describe": empty, 41 | }, 42 | "versions": map[string]interface{}{ 43 | "let-go": "dev", 44 | "let-go.nrepl": "dev", 45 | }, 46 | }, 47 | } 48 | } 49 | 50 | func (s *session) handle() error { 51 | var m map[string]interface{} 52 | err := s.dec.Decode(&m) 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Println("handle", m) 57 | op := m["op"].(string) 58 | id := m["id"].(string) 59 | s.lastID, err = strconv.Atoi(id) 60 | if err != nil { 61 | return err 62 | } 63 | switch op { 64 | case "eval": 65 | code := m["code"].(string) 66 | var v vm.Value 67 | _, v, err = s.ctx.CompileMultiple(strings.NewReader(code)) 68 | if err != nil { 69 | fmt.Printf("Error evaluating: %s, %#v\n", code, err) 70 | err = s.respond(m, map[string]interface{}{ 71 | "err": fmt.Sprintf("%s", err), 72 | "status": []string{"eval-error"}, 73 | }) 74 | if err != nil { 75 | break 76 | } 77 | } 78 | fmt.Println("eval", code, v) 79 | err = s.respond(m, map[string]interface{}{ 80 | "value": v.String(), 81 | "ns": s.ctx.CurrentNS().Name(), 82 | "status": []string{"done"}, 83 | }) 84 | case "describe": 85 | err = s.respond(m, s.describe) 86 | } 87 | if err != nil { 88 | fmt.Printf("Error responding: %#v\n", err) 89 | return err 90 | } 91 | if m["session"] == nil { 92 | err = s.send(map[string]interface{}{ 93 | "new-session": s.sessionID, 94 | "status": []string{"done"}, 95 | }) 96 | if err == nil { 97 | err = s.send(s.describe) 98 | } 99 | } 100 | return err 101 | } 102 | 103 | func (s *session) respond(m map[string]interface{}, o map[string]interface{}) error { 104 | id, err := strconv.Atoi(m["id"].(string)) 105 | if err != nil { 106 | return err 107 | } 108 | session := "none" 109 | ses, ok := m["session"] 110 | if ok && ses != nil { 111 | session = ses.(string) 112 | } 113 | o["id"] = fmt.Sprintf("%d", id) 114 | o["session"] = session 115 | fmt.Println("respond", o) 116 | bs, err := bencode.EncodeBytes(o) 117 | if err != nil { 118 | return err 119 | } 120 | _, err = s.conn.Write(bs) 121 | if err != nil { 122 | return err 123 | } 124 | return nil 125 | } 126 | 127 | func (s *session) send(o map[string]interface{}) error { 128 | s.lastID += 1 129 | id := s.lastID 130 | session := s.sessionID 131 | o["id"] = fmt.Sprintf("%d", id) 132 | o["session"] = session 133 | fmt.Println("send", o) 134 | bs, err := bencode.EncodeBytes(o) 135 | if err != nil { 136 | return err 137 | } 138 | _, err = s.conn.Write(bs) 139 | if err != nil { 140 | return err 141 | } 142 | return nil 143 | } 144 | 145 | type NreplServer struct { 146 | ctx *compiler.Context 147 | listener net.Listener 148 | stop chan struct{} 149 | wg sync.WaitGroup 150 | sessions map[string]*session 151 | } 152 | 153 | func NewNreplServer(compiler *compiler.Context) *NreplServer { 154 | return &NreplServer{ctx: compiler, sessions: map[string]*session{}} 155 | } 156 | 157 | func (n *NreplServer) adoptSession(s *session) string { 158 | newID, err := uuid.GenerateUUID() 159 | if err != nil { 160 | panic("wtf uuid gen failed") 161 | } 162 | n.sessions[newID] = s 163 | s.sessionID = newID 164 | return newID 165 | } 166 | 167 | func (n *NreplServer) Start(port int) error { 168 | l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 169 | if err != nil { 170 | return err 171 | } 172 | n.listener = l 173 | n.stop = make(chan struct{}) 174 | 175 | n.wg.Add(1) 176 | go func() { 177 | for { 178 | select { 179 | case <-n.stop: 180 | goto done 181 | default: 182 | conn, err := n.listener.Accept() 183 | if err != nil { 184 | fmt.Println("error when accepting", err) 185 | continue 186 | } 187 | go func(conn net.Conn) { 188 | s := newSession(n.ctx, conn) 189 | n.adoptSession(s) 190 | for { 191 | err = s.handle() 192 | if err != nil { 193 | fmt.Println("handle failed", err) 194 | if err == io.EOF { 195 | break 196 | } 197 | } 198 | } 199 | conn.Close() 200 | }(conn) 201 | } 202 | } 203 | done: 204 | n.wg.Done() 205 | }() 206 | return nil 207 | } 208 | 209 | func (n *NreplServer) Stop() { 210 | close(n.stop) 211 | n.wg.Wait() 212 | } 213 | -------------------------------------------------------------------------------- /pkg/vm/func.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | ) 12 | 13 | type theFuncType struct{} 14 | 15 | func (t *theFuncType) String() string { return t.Name() } 16 | func (t *theFuncType) Type() ValueType { return TypeType } 17 | func (t *theFuncType) Unbox() interface{} { return reflect.TypeOf(t) } 18 | 19 | func (t *theFuncType) Name() string { return "let-go.lang.Fn" } 20 | func (t *theFuncType) Box(fn interface{}) (Value, error) { 21 | return NIL, NewTypeError(fn, "can't be boxed as", t) 22 | } 23 | 24 | var FuncType *theFuncType = &theFuncType{} 25 | 26 | type Func struct { 27 | name string 28 | arity int 29 | isVariadric bool 30 | chunk *CodeChunk 31 | } 32 | 33 | func MakeFunc(arity int, variadric bool, c *CodeChunk) *Func { 34 | return &Func{ 35 | arity: arity, 36 | isVariadric: variadric, 37 | chunk: c, 38 | } 39 | } 40 | 41 | func (l *Func) SetName(n string) { 42 | l.name = n 43 | } 44 | 45 | func (l *Func) Type() ValueType { return FuncType } 46 | 47 | type FuncInterface func(interface{}) 48 | 49 | // Unbox implements Unbox 50 | func (l *Func) Unbox() interface{} { 51 | proxy := func(in []reflect.Value) []reflect.Value { 52 | args := make([]Value, len(in)) 53 | for i := range in { 54 | a, _ := BoxValue(in[i]) // FIXME handle error 55 | args[i] = a 56 | } 57 | f := NewFrame(l.chunk, args) 58 | out, _ := f.Run() // FIXME handle error 59 | return []reflect.Value{reflect.ValueOf(out.Unbox())} 60 | } 61 | return func(fptr interface{}) { 62 | fn := reflect.ValueOf(fptr).Elem() 63 | v := reflect.MakeFunc(fn.Type(), proxy) 64 | fn.Set(v) 65 | } 66 | } 67 | 68 | func (l *Func) Arity() int { 69 | return l.arity 70 | } 71 | 72 | func (l *Func) Invoke(pargs []Value) (Value, error) { 73 | args := pargs 74 | if l.isVariadric { 75 | // pretty sure variadric should guarantee arity >= 1 76 | sargs := args[0 : l.arity-1] 77 | rest := args[l.arity-1:] 78 | restlist, err := ListType.Box(rest) 79 | if err != nil { 80 | return NIL, err 81 | } 82 | args = append(sargs, restlist) 83 | } 84 | f := NewFrame(l.chunk, args) 85 | return f.Run() 86 | } 87 | 88 | func (l *Func) String() string { 89 | if len(l.name) > 0 { 90 | return fmt.Sprintf("", l.name, l) 91 | } 92 | return fmt.Sprintf("", l) 93 | } 94 | 95 | func (l *Func) MakeClosure() Fn { 96 | return &Closure{ 97 | closedOvers: nil, 98 | fn: l, 99 | } 100 | } 101 | 102 | type Closure struct { 103 | closedOvers []Value 104 | fn *Func 105 | } 106 | 107 | func (l *Closure) Type() ValueType { return FuncType } 108 | 109 | // Unbox implements Unbox 110 | func (l *Closure) Unbox() interface{} { 111 | proxy := func(in []reflect.Value) []reflect.Value { 112 | args := make([]Value, len(in)) 113 | for i := range in { 114 | a, _ := BoxValue(in[i]) // FIXME handle error 115 | args[i] = a 116 | } 117 | f := NewFrame(l.fn.chunk, args) 118 | f.closedOvers = l.closedOvers 119 | out, _ := f.Run() // FIXME handle error 120 | return []reflect.Value{reflect.ValueOf(out.Unbox())} 121 | } 122 | return func(fptr interface{}) { 123 | fn := reflect.ValueOf(fptr).Elem() 124 | v := reflect.MakeFunc(fn.Type(), proxy) 125 | fn.Set(v) 126 | } 127 | } 128 | 129 | func (l *Closure) Arity() int { 130 | return l.fn.arity 131 | } 132 | 133 | func (l *Closure) Invoke(pargs []Value) (Value, error) { 134 | args := pargs 135 | if l.fn.isVariadric { 136 | // pretty sure variadric should guarantee arity >= 1 137 | sargs := args[0 : l.fn.arity-1] 138 | rest := args[l.fn.arity-1:] 139 | // FIXME don't swallow the error, make invoke return an error 140 | restlist, err := ListType.Box(rest) 141 | if err != nil { 142 | return NIL, err 143 | } 144 | args = append(sargs, restlist) 145 | } 146 | f := NewFrame(l.fn.chunk, args) 147 | f.closedOvers = l.closedOvers 148 | // FIXME don't swallow the error, make invoke return an error 149 | return f.Run() 150 | } 151 | 152 | func (l *Closure) String() string { 153 | return l.fn.String() 154 | } 155 | 156 | type MultiArityFn struct { 157 | fns map[int]Fn 158 | rest Fn 159 | arity int 160 | name string 161 | } 162 | 163 | func (l *MultiArityFn) Type() ValueType { return FuncType } 164 | 165 | // Unbox implements Unbox 166 | func (l *MultiArityFn) Unbox() interface{} { 167 | proxy := func(in []reflect.Value) []reflect.Value { 168 | args := make([]Value, len(in)) 169 | for i := range in { 170 | a, _ := BoxValue(in[i]) // FIXME handle error 171 | args[i] = a 172 | } 173 | out, _ := l.Invoke(args) 174 | return []reflect.Value{reflect.ValueOf(out.Unbox())} 175 | } 176 | return func(fptr interface{}) { 177 | fn := reflect.ValueOf(fptr).Elem() 178 | v := reflect.MakeFunc(fn.Type(), proxy) 179 | fn.Set(v) 180 | } 181 | } 182 | 183 | func (l *MultiArityFn) Arity() int { 184 | return l.arity 185 | } 186 | 187 | func (l *MultiArityFn) Invoke(pargs []Value) (Value, error) { 188 | le := len(pargs) 189 | if f, ok := l.fns[le]; ok { 190 | return f.Invoke(pargs) 191 | } 192 | if l.rest != nil && le >= l.rest.Arity() { 193 | return l.rest.Invoke(pargs) 194 | } 195 | return NIL, NewExecutionError(fmt.Sprintf("function %s doesn't have a %d-arity variant", l, le)) 196 | } 197 | 198 | func (l *MultiArityFn) String() string { 199 | return fmt.Sprintf("", l.name, l) 200 | } 201 | 202 | func makeMultiArity(fns []Value) (*MultiArityFn, error) { 203 | ma := &MultiArityFn{ 204 | arity: 0, 205 | fns: map[int]Fn{}, 206 | name: "", 207 | } 208 | for i := range fns { 209 | e := fns[i] 210 | f, ok := e.(Fn) 211 | if !ok { 212 | return nil, NewExecutionError("making multi-arity function failed") 213 | } 214 | if ff, ok := f.(*Func); ok { 215 | ma.name = ff.name 216 | } 217 | a := f.Arity() 218 | if a > ma.arity { 219 | ma.arity = a 220 | } 221 | if rest, ok := f.(*Func); ok && rest.isVariadric { 222 | ma.rest = rest 223 | } else { 224 | ma.fns[a] = f 225 | } 226 | } 227 | return ma, nil 228 | } 229 | -------------------------------------------------------------------------------- /lg.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | "os" 12 | "strings" 13 | "unicode" 14 | 15 | "github.com/alimpfard/line" 16 | "github.com/nooga/let-go/pkg/compiler" 17 | "github.com/nooga/let-go/pkg/nrepl" 18 | "github.com/nooga/let-go/pkg/rt" 19 | "github.com/nooga/let-go/pkg/vm" 20 | ) 21 | 22 | func motd() { 23 | message := "\u001B[1m\u001b[37;1mLET-GO\u001B[0m \u001B[36mdev\u001b[0m \u001b[90m(Ctrl-C to quit)\u001b[0m\n" 24 | fmt.Print(message) 25 | } 26 | 27 | func runForm(ctx *compiler.Context, in string) (vm.Value, error) { 28 | _, val, err := ctx.CompileMultiple(strings.NewReader(in)) 29 | if err != nil { 30 | return nil, err 31 | } 32 | // if debug { 33 | // val, err = vm.NewDebugFrame(chunk, nil).Run() 34 | // } else { 35 | // val, err = vm.NewFrame(chunk, nil).Run() 36 | // } 37 | // if err != nil { 38 | // return nil, err 39 | // } 40 | return val, err 41 | } 42 | 43 | var completionTerminators map[byte]bool 44 | var styles map[compiler.TokenKind]line.Style 45 | 46 | func repl(ctx *compiler.Context) { 47 | interrupted := false 48 | editor := line.NewEditor() 49 | prompt := ctx.CurrentNS().Name() + "=> " 50 | editor.SetInterruptHandler(func() { 51 | interrupted = true 52 | editor.Finish() 53 | }) 54 | editor.SetTabCompletionHandler(func(editor line.Editor) []line.Completion { 55 | lin := editor.Line() 56 | prefix := "" 57 | for i := len(lin) - 1; i >= -1; i-- { 58 | if (i < 0 || completionTerminators[lin[i]] || unicode.IsSpace(rune(lin[i]))) && i+1 < len(lin) { 59 | prefix = lin[i+1:] 60 | break 61 | } 62 | } 63 | cur := ctx.CurrentNS() 64 | symbols := rt.FuzzyNamespacedSymbolLookup(cur, vm.Symbol(prefix)) 65 | completions := []line.Completion{} 66 | for _, s := range symbols { 67 | completions = append(completions, line.Completion{ 68 | Text: string(s) + " ", 69 | InvariantOffset: uint32(len(prefix)), 70 | AllowCommitWithoutListing: true, 71 | }) 72 | } 73 | return completions 74 | }) 75 | editor.SetRefreshHandler(func(editor line.Editor) { 76 | lin := editor.Line() 77 | reader := compiler.NewLispReaderTokenizing(strings.NewReader(lin), "syntax") 78 | reader.Read() //nolint:errcheck // We really don't care, just need partial parse 79 | editor.StripStyles() 80 | for _, t := range reader.Tokens { 81 | if t.End == -1 { 82 | continue 83 | } 84 | style, ok := styles[t.Kind] 85 | if !ok { 86 | continue 87 | } 88 | editor.Stylize(line.Span{Start: uint32(t.Start), End: uint32(t.End), Mode: line.SpanModeByte}, style) 89 | } 90 | }) 91 | for { 92 | if interrupted { 93 | break 94 | } 95 | in, err := editor.GetLine(prompt) 96 | if err != nil { 97 | fmt.Println("prompt failed: ", err) 98 | continue 99 | } 100 | if in == "" { 101 | continue 102 | } 103 | editor.AddToHistory(in) 104 | ctx.SetSource("REPL") 105 | val, err := runForm(ctx, in) 106 | if err != nil { 107 | fmt.Println(err) 108 | } else { 109 | fmt.Println(val.String()) 110 | } 111 | prompt = ctx.CurrentNS().Name() + "=> " 112 | } 113 | } 114 | 115 | func runFile(ctx *compiler.Context, filename string) error { 116 | ctx.SetSource(filename) 117 | f, err := os.Open(filename) 118 | if err != nil { 119 | return err 120 | } 121 | _, _, err = ctx.CompileMultiple(f) 122 | errc := f.Close() 123 | if err != nil { 124 | return err 125 | } 126 | if errc != nil { 127 | return errc 128 | } 129 | //_, err = vm.NewFrame(chunk, nil).Run() 130 | //if err != nil { 131 | // return err 132 | //} 133 | return nil 134 | } 135 | 136 | var nreplServer *nrepl.NreplServer 137 | 138 | func nreplServe(ctx *compiler.Context, port int) error { 139 | nreplServer = nrepl.NewNreplServer(ctx) 140 | err := nreplServer.Start(port) 141 | if err != nil { 142 | return err 143 | } 144 | return nil 145 | } 146 | 147 | var nreplPort int 148 | var runNREPL bool 149 | var runREPL bool 150 | var expr string 151 | var debug bool 152 | 153 | func init() { 154 | flag.BoolVar(&runREPL, "r", false, "attach REPL after running given files") 155 | flag.StringVar(&expr, "e", "", "eval given expression") 156 | flag.BoolVar(&debug, "d", false, "enable VM debug mode") 157 | flag.BoolVar(&runNREPL, "n", false, "enable nREPL server") 158 | flag.IntVar(&nreplPort, "p", 2137, "set nREPL port, default is 2137") 159 | 160 | completionTerminators = map[byte]bool{ 161 | '(': true, 162 | ')': true, 163 | '[': true, 164 | ']': true, 165 | '{': true, 166 | '}': true, 167 | '"': true, 168 | '\\': true, 169 | '\'': true, 170 | '@': true, 171 | '`': true, 172 | '~': true, 173 | ';': true, 174 | '#': true, 175 | } 176 | styles = map[compiler.TokenKind]line.Style{ 177 | compiler.TokenNumber: {ForegroundColor: line.MakeXtermColor(line.XtermColorMagenta)}, 178 | compiler.TokenPunctuation: {ForegroundColor: line.MakeXtermColor(line.XtermColorYellow)}, 179 | compiler.TokenKeyword: {ForegroundColor: line.MakeXtermColor(line.XtermColorBlue)}, 180 | compiler.TokenString: {ForegroundColor: line.MakeXtermColor(line.XtermColorCyan)}, 181 | compiler.TokenSpecial: {ForegroundColor: line.MakeXtermColor(line.XtermColorUnchanged), Bold: true}, 182 | } 183 | } 184 | 185 | func initCompiler(debug bool) *compiler.Context { 186 | consts := vm.NewConsts() 187 | ns := rt.NS("user") 188 | if ns == nil { 189 | fmt.Println("namespace not found") 190 | return nil 191 | } 192 | if debug { 193 | return compiler.NewDebugCompiler(consts, ns) 194 | } else { 195 | return compiler.NewCompiler(consts, ns) 196 | } 197 | } 198 | 199 | func main() { 200 | flag.Parse() 201 | files := flag.Args() 202 | 203 | context := initCompiler(debug) 204 | 205 | ranSomething := false 206 | if len(files) >= 1 { 207 | for i := range files { 208 | err := runFile(context, files[i]) 209 | if err != nil { 210 | fmt.Println(err) 211 | continue 212 | } 213 | } 214 | ranSomething = true 215 | } 216 | 217 | if expr != "" { 218 | context.SetSource("EXPR") 219 | val, err := runForm(context, expr) 220 | if err != nil { 221 | fmt.Println(err) 222 | } else { 223 | fmt.Println(val) 224 | } 225 | ranSomething = true 226 | } 227 | 228 | if !ranSomething || runREPL { 229 | motd() 230 | if runNREPL { 231 | err := nreplServe(context, nreplPort) 232 | if err != nil { 233 | fmt.Println("failed to run nREPL server on port", nreplPort, err) 234 | } 235 | fmt.Printf("nREPL server running at tcp://127.0.0.1:%d\n", nreplPort) 236 | } 237 | repl(context) 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /meta/logo.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | λ -------------------------------------------------------------------------------- /pkg/vm/vm.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcin Gasperowicz 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | package vm 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | // Opcodes 13 | const ( 14 | OP_NOOP int32 = iota // do nothing 15 | 16 | OP_LOAD_CONST // load constant LDC (index int32) 17 | OP_LOAD_ARG // load argument LDA (index int32) 18 | 19 | OP_INVOKE // invoke function 20 | OP_RETURN // return from function 21 | 22 | OP_BRANCH_TRUE // branch if truthy BRANCH_TRUE (offset int32) 23 | OP_BRANCH_FALSE // branch if falsy BRF (offset int32) 24 | OP_JUMP // jump by offset JMP (offset int32) 25 | 26 | OP_POP // pop value from the stack and discard it 27 | OP_POP_N // save top and pop n elements from the stack POP_N (n int32) 28 | OP_DUP_NTH // duplicate nth value from the stack OPN (n int32) 29 | 30 | OP_SET_VAR // set var 31 | OP_LOAD_VAR // push var root 32 | 33 | OP_MAKE_CLOSURE // make a closure out of fn 34 | OP_LOAD_CLOSEDOVER // load closed over LDK (index int32) 35 | OP_PUSH_CLOSEDOVER // push closed over value to a closure 36 | 37 | OP_RECUR // loop recurse RECUR (offset int32, argc int32) 38 | OP_RECUR_FN // function recurse REF (argc int32) 39 | 40 | OP_TRACE_ENABLE // enable tracing 41 | OP_TRACE_DISABLE // disable tracing 42 | 43 | OP_MAKE_MULTI_ARITY // make multi-arity function (n int32) 44 | OP_TAIL_CALL // like OP_INVOKE but re-uses the frame 45 | ) 46 | 47 | func OpcodeToString(op int32) string { 48 | inst := op & 0xff 49 | sp := (op >> 16) & 0xffff 50 | ops := []string{ 51 | "NOOP", 52 | "LOAD_CONST", 53 | "LOAD_ARG", 54 | "INVOKE", 55 | "RETURN", 56 | "BRANCH_T", 57 | "BRANCH_F", 58 | "JUMP", 59 | "POP", 60 | "POP_N", 61 | "DUP_NTH", 62 | "SET_VAR", 63 | "LOAD_VAR", 64 | "MAKE_CLOSURE", 65 | "LOAD_CLOSEDOVER", 66 | "PUSH_CLOSEDOVER", 67 | "RECUR", 68 | "RECUR_FN", 69 | "TRACE_ENABLE", 70 | "TRACE_DISABLE", 71 | "MAKE_MULTI_ARITY", 72 | } 73 | if int(inst) < len(ops) { 74 | return fmt.Sprintf("%d/%-16s", sp, ops[inst]) 75 | } 76 | return "???" 77 | } 78 | 79 | // CodeChunk holds bytecode and provides facilities for reading and writing it 80 | type CodeChunk struct { 81 | maxStack int 82 | consts *Consts 83 | code []int32 84 | length int 85 | } 86 | 87 | func NewCodeChunk(consts *Consts) *CodeChunk { 88 | return &CodeChunk{ 89 | consts: consts, 90 | code: []int32{}, 91 | length: 0, 92 | } 93 | } 94 | 95 | func (c *CodeChunk) Debug() { 96 | consts := c.consts 97 | fmt.Println("code:") 98 | i := 0 99 | for i < len(c.code) { 100 | op, _ := c.Get(i) 101 | switch op & 0xff { 102 | case OP_RECUR: 103 | arg, _ := c.Get32(i + 1) 104 | arg2, _ := c.Get32(i + 2) 105 | arg3, _ := c.Get32(i + 3) 106 | fmt.Println(" ", i, ":", OpcodeToString(op), arg, arg2, arg3) 107 | i += 4 108 | case OP_LOAD_ARG, OP_BRANCH_TRUE, OP_BRANCH_FALSE, OP_JUMP, OP_POP_N, OP_DUP_NTH, OP_INVOKE, OP_LOAD_CLOSEDOVER, OP_RECUR_FN, OP_MAKE_MULTI_ARITY: 109 | arg, _ := c.Get32(i + 1) 110 | fmt.Println(" ", i, ":", OpcodeToString(op), arg) 111 | i += 2 112 | case OP_LOAD_CONST, OP_LOAD_VAR: 113 | arg, _ := c.Get32(i + 1) 114 | fmt.Println(" ", i, ":", OpcodeToString(op), arg, "<-", consts.get(arg)) 115 | i += 2 116 | default: 117 | fmt.Println(" ", i, ":", OpcodeToString(op)) 118 | i++ 119 | } 120 | } 121 | } 122 | 123 | func (c *CodeChunk) Length() int { 124 | return c.length 125 | } 126 | 127 | func (c *CodeChunk) Append(insts ...int32) { 128 | c.code = append(c.code, insts...) 129 | c.length = len(c.code) 130 | } 131 | 132 | func (c *CodeChunk) Append32(val int) { 133 | c.Append(int32(val)) 134 | } 135 | 136 | func (c *CodeChunk) AppendChunk(o *CodeChunk) { 137 | if o.maxStack > c.maxStack { 138 | c.maxStack = o.maxStack 139 | } 140 | c.code = append(c.code, o.code...) 141 | c.length += len(o.code) 142 | } 143 | 144 | func (c *CodeChunk) Get(idx int) (int32, error) { 145 | if idx >= c.length { 146 | return 0, NewExecutionError("bytecode fetch out of bounds") 147 | } 148 | return c.code[idx], nil 149 | } 150 | 151 | func (c *CodeChunk) Get32(idx int) (int, error) { 152 | n, err := c.Get(idx) 153 | return int(n), err 154 | } 155 | 156 | func (c *CodeChunk) Update32(address int, value int32) { 157 | c.code[address] = value 158 | } 159 | 160 | func (c *CodeChunk) SetMaxStack(max int) { 161 | c.maxStack = max 162 | } 163 | 164 | // Frame is a single interpreter context 165 | type Frame struct { 166 | stack []Value 167 | args []Value 168 | closedOvers []Value 169 | argc int 170 | consts *Consts 171 | constsc int 172 | code *CodeChunk 173 | ip int 174 | sp int 175 | debug bool 176 | } 177 | 178 | func NewFrame(code *CodeChunk, args []Value) *Frame { 179 | return &Frame{ 180 | stack: make([]Value, code.maxStack), 181 | args: args, 182 | argc: len(args), 183 | consts: code.consts, 184 | constsc: code.consts.count(), 185 | code: code, 186 | ip: 0, 187 | sp: 0, 188 | debug: false, 189 | } 190 | } 191 | 192 | func NewDebugFrame(code *CodeChunk, args []Value) *Frame { 193 | f := NewFrame(code, args) 194 | f.debug = true 195 | return f 196 | } 197 | 198 | func (f *Frame) push(v Value) error { 199 | if f.sp >= f.code.maxStack { 200 | f.stackDbg() 201 | return NewExecutionError("stack overflow") 202 | } 203 | f.stack[f.sp] = v 204 | f.sp++ 205 | return nil 206 | } 207 | 208 | func (f *Frame) pushMult(v []Value) error { 209 | l := len(v) 210 | if f.sp >= f.code.maxStack-l { 211 | f.stackDbg() 212 | return NewExecutionError("stack overflow") 213 | } 214 | 215 | for i := 0; i < l; i++ { 216 | f.stack[f.sp] = v[i] 217 | f.sp++ 218 | } 219 | return nil 220 | } 221 | 222 | func (f *Frame) pop() (Value, error) { 223 | if f.sp == 0 { 224 | f.stackDbg() 225 | return NIL, NewExecutionError("stack underflow") 226 | } 227 | f.sp-- 228 | v := f.stack[f.sp] 229 | //f.stack[f.sp] = nil 230 | return v, nil 231 | } 232 | 233 | func (f *Frame) nth(n int) (Value, error) { 234 | i := f.sp - 1 - n 235 | if i < 0 { 236 | f.stackDbg() 237 | return NIL, NewExecutionError("nth: stack underflow") 238 | } 239 | return f.stack[i], nil 240 | } 241 | 242 | func (f *Frame) mult(start int, count int) ([]Value, error) { 243 | if count < 0 { 244 | f.stackDbg() 245 | return nil, NewExecutionError("mult: count 0 or negative") 246 | } 247 | i := f.sp - start 248 | if i-count < 0 { 249 | f.stackDbg() 250 | return nil, NewExecutionError("mult: stack underflow") 251 | } 252 | return f.stack[i-count : i], nil 253 | } 254 | 255 | func (f *Frame) drop(n int) error { 256 | top := f.sp - 1 257 | if top < 0 { 258 | f.stackDbg() 259 | f.code.Debug() 260 | return NewExecutionError("drop: stack underflow") 261 | } 262 | f.sp -= n 263 | if f.sp < 0 { 264 | f.stackDbg() 265 | f.code.Debug() 266 | return NewExecutionError("drop: stack underflow") 267 | } 268 | return nil 269 | } 270 | 271 | func (f *Frame) stackDbg() { 272 | fmt.Printf("; stack [%d/%d]:\n", f.sp, f.code.maxStack) 273 | for i := 0; i < f.sp; i++ { 274 | fmt.Printf("; %4d: %s\n", i, f.stack[i].String()) 275 | } 276 | fmt.Println() 277 | } 278 | 279 | func (f *Frame) Run() (Value, error) { 280 | if f.debug { 281 | fmt.Print("run", f.args, "\n") 282 | f.code.Debug() 283 | } 284 | for { 285 | inst := f.code.code[f.ip] 286 | if f.debug { 287 | f.stackDbg() 288 | fmt.Println("#", f.ip, OpcodeToString(inst)) 289 | } 290 | switch inst & 0xff { 291 | case OP_NOOP: 292 | f.ip++ 293 | 294 | case OP_TRACE_ENABLE: 295 | fmt.Print("# tracing frame, args: ", f.args, "\n") 296 | f.code.Debug() 297 | f.debug = true 298 | f.ip += 1 299 | 300 | case OP_TRACE_DISABLE: 301 | f.debug = false 302 | f.ip += 1 303 | 304 | case OP_LOAD_CONST: 305 | idx := f.code.code[f.ip+1] 306 | if int(idx) >= f.constsc { 307 | return NIL, NewExecutionError("const lookup out of bounds") 308 | } 309 | err := f.push(f.consts.get(int(idx))) 310 | if err != nil { 311 | return NIL, NewExecutionError("const push failed").Wrap(err) 312 | } 313 | f.ip += 2 314 | 315 | case OP_LOAD_ARG: 316 | idx := f.code.code[f.ip+1] 317 | if int(idx) >= f.argc { 318 | return NIL, NewExecutionError("argument lookup out of bounds") 319 | } 320 | err := f.push(f.args[idx]) 321 | if err != nil { 322 | return NIL, NewExecutionError("argument push failed").Wrap(err) 323 | } 324 | f.ip += 2 325 | 326 | case OP_RETURN: 327 | v, err := f.pop() 328 | if err != nil { 329 | return NIL, NewExecutionError("return failed").Wrap(err) 330 | } 331 | return v, nil 332 | 333 | case OP_INVOKE: 334 | arity := f.code.code[f.ip+1] 335 | var out Value 336 | if arity > 0 { 337 | fraw, err := f.nth(int(arity)) 338 | if err != nil { 339 | return NIL, NewExecutionError("invoke instruction failed").Wrap(err) 340 | } 341 | fn, ok := fraw.(Fn) 342 | if !ok { 343 | return NIL, NewTypeError(fraw, "is not a function", nil) 344 | } 345 | a, err := f.mult(0, int(arity)) 346 | if err != nil { 347 | return NIL, NewExecutionError("popping arguments failed").Wrap(err) 348 | } 349 | out, err = fn.Invoke(a) 350 | if err != nil { 351 | // FIXME this is an exception, we should handle it 352 | return NIL, NewExecutionError(fmt.Sprintf("calling %s", fn.String())).Wrap(err) 353 | } 354 | err = f.drop(int(arity) + 1) 355 | if err != nil { 356 | return NIL, NewExecutionError("cleaning stack after call").Wrap(err) 357 | } 358 | } else { 359 | fraw, err := f.pop() 360 | if err != nil { 361 | return NIL, NewExecutionError("invoke instruction failed").Wrap(err) 362 | } 363 | fn, ok := fraw.(Fn) 364 | if !ok { 365 | return NIL, NewTypeError(fraw, "is not a function", nil) 366 | } 367 | out, err = fn.Invoke(nil) 368 | if err != nil { 369 | // FIXME this is an exception, we should handle it 370 | return NIL, NewExecutionError(fmt.Sprintf("calling %s", fn.String())).Wrap(err) 371 | } 372 | } 373 | err := f.push(out) 374 | if err != nil { 375 | return NIL, NewExecutionError("pushing return value failed").Wrap(err) 376 | } 377 | f.ip += 2 378 | 379 | case OP_TAIL_CALL: 380 | arity := f.code.code[f.ip+1] 381 | var out Value 382 | if arity > 0 { 383 | fraw, err := f.nth(int(arity)) 384 | if err != nil { 385 | return NIL, NewExecutionError("invoke instruction failed").Wrap(err) 386 | } 387 | fn, ok := fraw.(Fn) 388 | if !ok { 389 | return NIL, NewTypeError(fraw, "is not a function", nil) 390 | } 391 | a, err := f.mult(0, int(arity)) 392 | if err != nil { 393 | return NIL, NewExecutionError("popping arguments failed").Wrap(err) 394 | } 395 | if ff, ok := fn.(*Func); !ok { 396 | out, err = fn.Invoke(a) 397 | if err != nil { 398 | // FIXME this is an exception, we should handle it 399 | return NIL, NewExecutionError(fmt.Sprintf("calling %s", fn.String())).Wrap(err) 400 | } 401 | err = f.drop(int(arity) + 1) 402 | if err != nil { 403 | return NIL, NewExecutionError("cleaning stack after call").Wrap(err) 404 | } 405 | } else { 406 | f.code = ff.chunk 407 | f.consts = f.code.consts 408 | f.constsc = f.code.consts.count() 409 | f.ip = 0 410 | f.sp = 0 411 | if len(f.stack) < f.code.maxStack { 412 | f.stack = make([]Value, f.code.maxStack) 413 | f.args = a 414 | f.argc = len(a) 415 | } else { 416 | la := len(a) 417 | if la <= f.argc { 418 | copy(f.args, a) 419 | } else { 420 | f.args = make([]Value, la) 421 | copy(f.args, a) 422 | } 423 | f.argc = len(a) 424 | } 425 | continue 426 | } 427 | } else { 428 | fraw, err := f.pop() 429 | if err != nil { 430 | return NIL, NewExecutionError("invoke instruction failed").Wrap(err) 431 | } 432 | fn, ok := fraw.(Fn) 433 | if !ok { 434 | return NIL, NewTypeError(fraw, "is not a function", nil) 435 | } 436 | if ff, ok := fn.(*Func); !ok { 437 | out, err = fn.Invoke(nil) 438 | if err != nil { 439 | // FIXME this is an exception, we should handle it 440 | return NIL, NewExecutionError(fmt.Sprintf("calling %s", fn.String())).Wrap(err) 441 | } 442 | } else { 443 | f.code = ff.chunk 444 | f.consts = f.code.consts 445 | f.constsc = f.code.consts.count() 446 | f.ip = 0 447 | f.sp = 0 448 | if len(f.stack) < f.code.maxStack { 449 | f.stack = make([]Value, f.code.maxStack) 450 | } 451 | f.argc = 0 452 | f.args = nil 453 | continue 454 | } 455 | } 456 | err := f.push(out) 457 | if err != nil { 458 | return NIL, NewExecutionError("pushing return value failed").Wrap(err) 459 | } 460 | f.ip += 2 461 | 462 | case OP_BRANCH_TRUE: 463 | offset := f.code.code[f.ip+1] 464 | v, err := f.pop() 465 | if err != nil { 466 | return NIL, NewExecutionError("BRANCH_TRUE pop condition").Wrap(err) 467 | } 468 | if !IsTruthy(v) { 469 | f.ip += 2 470 | continue 471 | } 472 | f.ip += int(offset) 473 | 474 | case OP_BRANCH_FALSE: 475 | offset := f.code.code[f.ip+1] 476 | v, err := f.pop() 477 | if err != nil { 478 | return NIL, NewExecutionError("BRANCH_FALSE pop condition").Wrap(err) 479 | } 480 | if IsTruthy(v) { 481 | f.ip += 2 482 | continue 483 | } 484 | f.ip += int(offset) 485 | 486 | case OP_JUMP: 487 | offset := f.code.code[f.ip+1] 488 | f.ip += int(offset) 489 | 490 | case OP_POP: 491 | _, err := f.pop() 492 | if err != nil { 493 | return NIL, NewExecutionError("POP failed").Wrap(err) 494 | } 495 | f.ip++ 496 | 497 | case OP_POP_N: 498 | v, err := f.pop() 499 | if err != nil { 500 | return NIL, NewExecutionError("POP_N top value").Wrap(err) 501 | } 502 | num := f.code.code[f.ip+1] 503 | err = f.drop(int(num)) 504 | if err != nil { 505 | return NIL, NewExecutionError("POP_N drop").Wrap(err) 506 | } 507 | err = f.push(v) 508 | if err != nil { 509 | return NIL, NewExecutionError("POP_N push").Wrap(err) 510 | } 511 | f.ip += 2 512 | 513 | case OP_DUP_NTH: 514 | num := f.code.code[f.ip+1] 515 | val, err := f.nth(int(num)) 516 | if err != nil { 517 | return NIL, NewExecutionError("DUP_NTH get nth").Wrap(err) 518 | } 519 | err = f.push(val) 520 | if err != nil { 521 | return NIL, NewExecutionError("DUP_NTH push").Wrap(err) 522 | } 523 | f.ip += 2 524 | 525 | case OP_SET_VAR: 526 | val, err := f.pop() 527 | if err != nil { 528 | return NIL, NewExecutionError("SET_VAR pop value failed").Wrap(err) 529 | } 530 | varr, err := f.pop() 531 | if err != nil { 532 | return NIL, NewExecutionError("SET_VAR pop var failed").Wrap(err) 533 | } 534 | varrd, ok := varr.(*Var) 535 | if !ok { 536 | return NIL, NewExecutionError("SET_VAR invalid Var").Wrap(err) 537 | } 538 | varrd.SetRoot(val) 539 | err = f.push(varr) 540 | if err != nil { 541 | return NIL, NewExecutionError("SET_VAR push var failed").Wrap(err) 542 | } 543 | f.ip++ 544 | 545 | case OP_LOAD_VAR: 546 | idx := f.code.code[f.ip+1] 547 | if int(idx) >= f.constsc { 548 | return NIL, NewExecutionError("const lookup out of bounds") 549 | } 550 | err := f.push(f.consts.get(int(idx)).(*Var).Deref()) 551 | if err != nil { 552 | return NIL, NewExecutionError("const push failed").Wrap(err) 553 | } 554 | f.ip += 2 555 | 556 | case OP_MAKE_CLOSURE: 557 | idx := f.sp - 1 558 | if idx < 0 { 559 | return NIL, NewExecutionError("MAKE_CLOSURE stack underflow") 560 | } 561 | fn, ok := f.stack[idx].(*Func) 562 | if !ok { 563 | return NIL, NewExecutionError("MAKE_CLOSURE invalid func on stack") 564 | } 565 | f.stack[idx] = fn.MakeClosure() 566 | f.ip++ 567 | 568 | case OP_LOAD_CLOSEDOVER: 569 | idx := f.code.code[f.ip+1] 570 | // FIXME cache closedOvers count 571 | if int(idx) >= len(f.closedOvers) { 572 | return NIL, NewExecutionError("closed over lookup out of bounds") 573 | } 574 | err := f.push(f.closedOvers[idx]) 575 | if err != nil { 576 | return NIL, NewExecutionError("closed over push failed").Wrap(err) 577 | } 578 | f.ip += 2 579 | 580 | case OP_PUSH_CLOSEDOVER: 581 | val, err := f.pop() 582 | if err != nil { 583 | return NIL, NewExecutionError("popping closed over value failed").Wrap(err) 584 | } 585 | idx := f.sp - 1 586 | if idx < 0 { 587 | return NIL, NewExecutionError("PUSH_CLOSEDOVER stack overflow").Wrap(err) 588 | } 589 | cls := f.stack[idx] 590 | if cls.Type() != FuncType { 591 | return NIL, NewExecutionError("PUSH_CLOSEDOVER expected a Fn") 592 | } 593 | fun, ok := cls.(*Closure) 594 | if !ok { 595 | return NIL, NewExecutionError("PUSH_CLOSEDOVER invalid closure on stack") 596 | } 597 | fun.closedOvers = append(fun.closedOvers, val) 598 | f.ip++ 599 | 600 | case OP_RECUR_FN: 601 | arity := f.code.code[f.ip+1] 602 | a, err := f.mult(0, int(arity)) 603 | if err != nil { 604 | return NIL, NewExecutionError("popping arguments failed").Wrap(err) 605 | } 606 | copy(f.args, a) 607 | f.argc = int(arity) 608 | f.sp = 0 609 | f.ip = 0 610 | 611 | case OP_RECUR: 612 | offset := f.code.code[f.ip+1] 613 | argc := f.code.code[f.ip+2] 614 | ignore := f.code.code[f.ip+3] 615 | a, err := f.mult(0, int(argc)) 616 | if err != nil { 617 | return NIL, NewExecutionError("RECUR popping arguments failed").Wrap(err) 618 | } 619 | err = f.drop(int(argc)*2 + int(ignore)) 620 | if err != nil { 621 | return NIL, NewExecutionError("RECUR popping old locals").Wrap(err) 622 | } 623 | err = f.pushMult(a) 624 | if err != nil { 625 | return NIL, NewExecutionError("RECUR pushing new locals").Wrap(err) 626 | } 627 | 628 | f.ip -= int(offset) 629 | 630 | case OP_MAKE_MULTI_ARITY: 631 | nfns := f.code.code[f.ip+1] 632 | n := int(nfns) 633 | fns, err := f.mult(0, n) 634 | if err != nil { 635 | return NIL, NewExecutionError("MAKE_MULTI_ARITY popping arguments failed").Wrap(err) 636 | } 637 | f.sp -= n 638 | 639 | fn, err := makeMultiArity(fns) 640 | if err != nil { 641 | return NIL, NewExecutionError("MAKE_MULTI_ARITY failed").Wrap(err) 642 | } 643 | 644 | err = f.push(fn) 645 | if err != nil { 646 | return NIL, NewExecutionError("MAKE_MULTI_ARITY push failed").Wrap(err) 647 | } 648 | 649 | f.ip += 2 650 | 651 | default: 652 | return NIL, NewExecutionError("unknown instruction") 653 | } 654 | } 655 | } 656 | -------------------------------------------------------------------------------- /pkg/rt/core/core.lg: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Marcin Gasperowicz 3 | ; SPDX-License-Identifier: MIT 4 | ; 5 | 6 | ; let-go core library 7 | #?(:clj (ns core)) 8 | 9 | (def fn (fn* [& body] (cons 'fn* body))) 10 | (set-macro! (var fn)) 11 | 12 | (def defn (fn [name args & body] (list 'def name (cons 'fn (cons args body))))) 13 | (set-macro! (var defn)) 14 | 15 | ; we don't support arbitrary meta so make this identity for now 16 | (defn with-meta [x _] x) 17 | 18 | (def apply apply*) 19 | 20 | (defn defmacro [name args & body] 21 | (list 'do (cons 'defn (cons name (cons args body))) (list 'set-macro! (list 'var name)))) 22 | (set-macro! (var defmacro)) 23 | 24 | (defmacro comment [& _] nil) 25 | 26 | ; define let as alias to let* for now 27 | (defmacro let [& body] 28 | (cons 'let* body)) 29 | 30 | ; same for loop 31 | (defmacro loop [& body] 32 | (cons 'loop* body)) 33 | 34 | (defmacro when [condition & forms] 35 | (list 'if condition (cons 'do forms) nil)) 36 | 37 | (defmacro when-not [condition & forms] 38 | (list 'if condition nil (cons 'do forms))) 39 | 40 | (defmacro if-not [condition & forms] 41 | (list 'if condition (first (next forms)) (first forms))) 42 | 43 | (defn empty? [xs] (= 0 (count xs))) 44 | 45 | (defn concat-list [a b] 46 | (if (empty? a) 47 | b 48 | (cons (first a) (concat-list (next a) b)))) 49 | 50 | (def > gt) 51 | (def < lt) 52 | 53 | (def >= (fn [a b] (or (= a b) (> a b)))) 54 | (def <= (fn [a b] (or (= a b) (< a b)))) 55 | 56 | ;; this is wrong :^) 57 | (def rest next) 58 | (def parse-long parse-int) 59 | 60 | (defmacro cond [& forms] 61 | (when (>= (count forms) 2) 62 | (list 'if (first forms) (second forms) 63 | (cons 'cond (next (next forms)))))) 64 | 65 | (defmacro condp [comparator arg & forms] 66 | (let [l (count forms)] 67 | (cond (= l 0) nil 68 | ;; this is an error 69 | (= l 1) (first forms) 70 | :else (list 'if 71 | (list comparator arg (first forms)) 72 | (second forms) 73 | (cons 'condp (cons comparator (cons arg (next (next forms))))))))) 74 | 75 | (defmacro case [arg & forms] 76 | (concat-list (list 'condp '= arg) forms)) 77 | 78 | (defmacro ns [n & nss] 79 | (let [cns (list 'in-ns (list 'quote n))] 80 | ; FIXME this is not clojure syntax at all 81 | (if (empty? nss) 82 | cns 83 | (list 'do cns (cons 'use nss))))) 84 | 85 | ; makeshift test helper 86 | (def ^:dynamic *test-flag* true) 87 | 88 | (defmacro test [name & body] 89 | (let [bod (cons 'do body) 90 | pass (list 'println " \u001b[32mPASS\u001b[0m" name) 91 | fail (list 'do 92 | (list 'set! 'core/*test-flag* false) 93 | (list 'println " \u001b[31mFAIL\u001b[0m" name))] 94 | (list 'if bod pass fail))) 95 | 96 | (defn identity [x] x) 97 | 98 | (defn zero? [x] (= 0 x)) 99 | (defn pos? [x] (gt x 0)) 100 | (defn neg? [x] (lt x 0)) 101 | (defn even? [x] (= 0 (mod x 2))) 102 | (defn odd? [x] (not (even? x))) 103 | 104 | (defmacro not= [& xs] (list 'not (cons '= xs))) 105 | 106 | (defn complement [f] (fn [x] (not (f x)))) 107 | 108 | (defn nil? [x] (= nil x)) 109 | (defn some? [x] (not= nil x)) 110 | (defn true? [x] (= x true)) 111 | (defn false? [x] (= x false)) 112 | 113 | (defn inc [x] (+ x 1)) 114 | (defn dec [x] (- x 1)) 115 | 116 | ;; bleh 117 | (defn list? [x] (= (type x) (type '()))) 118 | (defn vector? [x] (= (type x) (type []))) 119 | (defn map? [x] (= (type x) (type {}))) 120 | (defn symbol? [x] (= (type x) (type 'x))) 121 | (defn keyword? [x] (= (type x) (type :x))) 122 | (defn string? [x] (= (type x) (type ""))) 123 | (defn number? [x] (= (type x) (type 0))) 124 | 125 | (defn empty? [x] (zero? (count x))) 126 | 127 | (defn set [coll] 128 | (reduce conj #{} coll)) 129 | 130 | (defn concat-vec [a b] 131 | (if (empty? b) 132 | a 133 | (reduce conj a b))) 134 | 135 | (defmacro time [& body] 136 | (let [then (gensym "time__") 137 | val (gensym) 138 | now (list 'core/now) 139 | report (list 'core/println "Elapsed:" (list '.Sub now then))] 140 | (list 'let (vector then now val (cons 'do body)) 141 | report 142 | val))) 143 | 144 | (defmacro -> [initial & forms] 145 | (if (zero? (count forms)) 146 | initial 147 | (reduce 148 | (fn [a x] 149 | (if (list? x) 150 | (cons (first x) (cons a (next x))) 151 | (list x a))) 152 | initial 153 | forms))) 154 | 155 | (defmacro ->> [initial & forms] 156 | (if (zero? (count forms)) 157 | initial 158 | (reduce 159 | (fn [a x] 160 | (if (list? x) 161 | (concat-list x (list a)) 162 | (list x a))) 163 | initial 164 | forms))) 165 | 166 | 167 | (defn spy [s x] (println s x) x) 168 | 169 | (defn mapcat [f xs] 170 | (reduce (fn [a x] (concat-list a x)) '() (map f xs))) 171 | 172 | (defn filter [f xs] 173 | (if xs 174 | (let [x (first xs) 175 | r (next xs)] 176 | (if (f x) 177 | (cons x (filter f r)) 178 | (filter f r))) 179 | (list))) 180 | 181 | (defn take [n coll] 182 | (when (and coll (> n 0)) 183 | (cons (first coll) (take (dec n) (next coll))))) 184 | 185 | (defn drop [n xs] 186 | (if (> n 0) 187 | (recur (dec n) (next xs)) 188 | xs)) 189 | 190 | (defn split-at [n coll] 191 | [(take n coll) (drop n coll)]) 192 | 193 | (defn partition 194 | ([n xs] (partition n n xs)) 195 | ([n step xs] 196 | (loop [c xs w []] 197 | (if (>= (count c) n) 198 | (recur (drop step c) (conj w (take n c))) 199 | w)))) 200 | 201 | (defmacro dotimes 202 | [bindings & body] 203 | (let [i (first bindings) 204 | n (second bindings)] 205 | `(let [n# ~n] 206 | (loop [~i 0] 207 | (when (< ~i n#) 208 | ~@body 209 | (recur (inc ~i))))))) 210 | 211 | (defmacro declare [& names] 212 | `(do ~@(map #(list 'def % nil) names))) 213 | 214 | (declare destructure*) 215 | 216 | (defn ^:private destructure-vector [n v] 217 | (loop [e v i 0 o []] 218 | (if (empty? e) 219 | (destructure* o) 220 | (let [x (first e)] 221 | (cond 222 | (= x '&) (recur (drop 2 e) i (conj o (second e) (list 'drop i n))) 223 | (= x :as) (recur (drop 2 e) i (conj o (second e) n)) 224 | :else (recur (next e) (inc i) (conj o x (list 'get n i)))))))) 225 | 226 | (defn ^:private destructure-map [n m] 227 | (let [gs (reduce (fn [a x] (conj a (first x) (list 'get n (second x)))) 228 | [] 229 | (map identity (dissoc m :keys :as))) ; ugly hax to get away with reducing empty map, fix reduce 230 | rs (if (:as m) 231 | (conj gs (:as m) n) 232 | gs)] 233 | (destructure* (if (:keys m) 234 | (reduce (fn [a x] (conj a x (list 'get n (keyword x)))) rs (:keys m)) 235 | rs)))) 236 | 237 | (defn ^:private destructure [binds] 238 | (loop [b binds o [] ob []] 239 | (if (empty? b) 240 | [ob o] 241 | (let [n (first b) f (second b)] 242 | (cond 243 | (vector? n) (let [nn (gensym "vec__") 244 | vd (destructure-vector nn n) 245 | no (conj ob nn f)] 246 | (recur (drop 2 b) (concat-vec o vd) no)) 247 | (map? n) (let [nn (gensym "map__") 248 | md (destructure-map nn n) 249 | no (conj ob nn f)] 250 | (recur (drop 2 b) (concat-vec o md) no)) 251 | :else (recur (drop 2 b) o (conj ob n f))))))) 252 | 253 | (defn ^:private destructure* [binds] 254 | (loop [b binds o []] 255 | (if (empty? b) 256 | o 257 | (let [n (first b) f (second b)] 258 | (cond 259 | (vector? n) (let [nn (gensym "vec__") 260 | vd (destructure-vector nn n) 261 | no (conj o nn f)] 262 | (recur (drop 2 b) (concat-vec no vd))) 263 | (map? n) (let [nn (gensym "map__") 264 | md (destructure-map nn n) 265 | no (conj o nn f)] 266 | (recur (drop 2 b) (concat-vec no md))) 267 | :else (recur (drop 2 b) (conj o n f))))))) 268 | 269 | ; redefine let to use destructure 270 | (defmacro let [bindings & body] 271 | `(let* ~(destructure* bindings) ~@body)) 272 | 273 | (defmacro loop [bindings & body] 274 | (let [[bs nbs] (destructure bindings)] 275 | (if (> (count nbs) 0) 276 | `(loop* ~bs (let* ~nbs ~@body)) 277 | `(loop* ~bindings ~@body)))) 278 | 279 | 280 | (defn ^:private fn-expand [bindings body] 281 | (if (some (complement symbol?) bindings) 282 | (let [padded (reduce (fn [a x] (conj a x nil)) [] bindings) 283 | [bs nbs] (destructure padded) 284 | clean (vec (filter some? bs))] 285 | `(~clean (let* ~nbs ~@body))) 286 | `(~bindings ~@body))) 287 | 288 | (defmacro fn [& forms] 289 | (cond 290 | (vector? (first forms)) 291 | (cons 'fn* (fn-expand (first forms) (next forms))) 292 | 293 | (list? (first forms)) 294 | `(fn* ~@(map #(fn-expand (first %) (next %)) forms)) 295 | 296 | :else 297 | nil ;; throw here 298 | )) 299 | 300 | (defmacro defn [name & forms] 301 | `(def ~name (fn ~@forms))) 302 | 303 | (defmacro def- [name form] 304 | `(def ^:private ~name ~form)) 305 | 306 | (defmacro defn- [name & forms] 307 | `(def ^:private ~name (fn ~@forms))) 308 | 309 | (defmacro while [test & body] 310 | `(loop* [] (when ~test ~@body (recur)))) 311 | 312 | (defmacro go [& forms] 313 | `(go* (fn* [] ~@forms))) 314 | 315 | (defn- spread 316 | [arglist] 317 | (cond 318 | (nil? arglist) nil 319 | (nil? (next arglist)) (first arglist) 320 | :else (cons (first arglist) (spread (next arglist))))) 321 | 322 | (defn list* 323 | ([args] args) 324 | ([a args] (cons a args)) 325 | ([a b args] (cons a (cons b args))) 326 | ([a b c args] (cons a (cons b (cons c args)))) 327 | ([a b c d & more] 328 | (cons a (cons b (cons c (cons d (spread more))))))) 329 | 330 | (defn apply 331 | ([f args] 332 | (apply* f (seq args))) 333 | ([f x args] 334 | (apply* f (list* x args))) 335 | ([f x y args] 336 | (apply* f (list* x y args))) 337 | ([f x y z args] 338 | (apply* f (list* x y z args))) 339 | ([f a b c d & args] 340 | (apply* f (cons a (cons b (cons c (cons d (spread args)))))))) 341 | 342 | (defn comp 343 | ([] identity) 344 | ([f] f) 345 | ([f g] 346 | (fn 347 | ([] (f (g))) 348 | ([x] (f (g x))) 349 | ([x y] (f (g x y))) 350 | ([x y z] (f (g x y z))) 351 | ([x y z & args] (f (apply g x y z args))))) 352 | ([f g & fs] 353 | (reduce comp (list* f g fs)))) 354 | 355 | (defn juxt 356 | ([f] 357 | (fn 358 | ([] [(f)]) 359 | ([x] [(f x)]) 360 | ([x y] [(f x y)]) 361 | ([x y z] [(f x y z)]) 362 | ([x y z & args] [(apply f x y z args)]))) 363 | ([f g] 364 | (fn 365 | ([] [(f) (g)]) 366 | ([x] [(f x) (g x)]) 367 | ([x y] [(f x y) (g x y)]) 368 | ([x y z] [(f x y z) (g x y z)]) 369 | ([x y z & args] [(apply f x y z args) (apply g x y z args)]))) 370 | ([f g h] 371 | (fn 372 | ([] [(f) (g) (h)]) 373 | ([x] [(f x) (g x) (h x)]) 374 | ([x y] [(f x y) (g x y) (h x y)]) 375 | ([x y z] [(f x y z) (g x y z) (h x y z)]) 376 | ([x y z & args] [(apply f x y z args) (apply g x y z args) (apply h x y z args)]))) 377 | ([f g h & fs] 378 | (let [fs (list* f g h fs)] 379 | (fn 380 | ([] (reduce #(conj %1 (%2)) [] fs)) 381 | ([x] (reduce #(conj %1 (%2 x)) [] fs)) 382 | ([x y] (reduce #(conj %1 (%2 x y)) [] fs)) 383 | ([x y z] (reduce #(conj %1 (%2 x y z)) [] fs)) 384 | ([x y z & args] (reduce #(conj %1 (apply %2 x y z args)) [] fs)))))) 385 | 386 | (defn partial 387 | ([f] f) 388 | ([f arg1] 389 | (fn 390 | ([] (f arg1)) 391 | ([x] (f arg1 x)) 392 | ([x y] (f arg1 x y)) 393 | ([x y z] (f arg1 x y z)) 394 | ([x y z & args] (apply f arg1 x y z args)))) 395 | ([f arg1 arg2] 396 | (fn 397 | ([] (f arg1 arg2)) 398 | ([x] (f arg1 arg2 x)) 399 | ([x y] (f arg1 arg2 x y)) 400 | ([x y z] (f arg1 arg2 x y z)) 401 | ([x y z & args] (apply f arg1 arg2 x y z args)))) 402 | ([f arg1 arg2 arg3] 403 | (fn 404 | ([] (f arg1 arg2 arg3)) 405 | ([x] (f arg1 arg2 arg3 x)) 406 | ([x y] (f arg1 arg2 arg3 x y)) 407 | ([x y z] (f arg1 arg2 arg3 x y z)) 408 | ([x y z & args] (apply f arg1 arg2 arg3 x y z args)))) 409 | ([f arg1 arg2 arg3 & more] 410 | (fn [& args] (apply f arg1 arg2 arg3 (concat more args))))) 411 | 412 | (defn reverse [coll] 413 | (reduce conj () coll)) 414 | 415 | (defmacro if-let 416 | ([bindings then] 417 | `(if-let ~bindings ~then nil)) 418 | ([bindings then else] 419 | (let [form (bindings 0) tst (bindings 1)] 420 | `(let [temp# ~tst] 421 | (if temp# 422 | (let [~form temp#] 423 | ~then) 424 | ~else))))) 425 | 426 | (defmacro when-let 427 | [bindings & body] 428 | (let [form (bindings 0) tst (bindings 1)] 429 | `(let [temp# ~tst] 430 | (when temp# 431 | (let [~form temp#] 432 | ~@body))))) 433 | 434 | (defmacro if-some 435 | ([bindings then] 436 | `(if-some ~bindings ~then nil)) 437 | ([bindings then else] 438 | (let [form (bindings 0) tst (bindings 1)] 439 | `(let [temp# ~tst] 440 | (if (nil? temp#) 441 | ~else 442 | (let [~form temp#] 443 | ~then)))))) 444 | 445 | (defmacro when-some 446 | [bindings & body] 447 | (let [form (bindings 0) tst (bindings 1)] 448 | `(let [temp# ~tst] 449 | (if (nil? temp#) 450 | nil 451 | (let [~form temp#] 452 | ~@body))))) 453 | 454 | (defn every? 455 | [pred coll] 456 | (cond 457 | (nil? coll) true 458 | (pred (first coll)) (recur pred (next coll)) 459 | :else false)) 460 | 461 | (defn get-in 462 | ([m ks] 463 | (reduce get m ks)) 464 | ([m ks not-found] 465 | (loop [sentinel (gensym) 466 | m m 467 | ks ks] 468 | (if ks 469 | (let [m (get m (first ks) sentinel)] 470 | (if (= sentinel m) 471 | not-found 472 | (recur sentinel m (next ks)))) 473 | m)))) 474 | 475 | (defn assoc-in 476 | [m [k & ks] v] 477 | (if ks 478 | (assoc m k (assoc-in (get m k) ks v)) 479 | (assoc m k v))) 480 | 481 | (defn update-in 482 | ([m ks f & args] 483 | (let [up (fn up [m ks f args] 484 | (let [[k & ks] ks] 485 | (if ks 486 | (assoc m k (up (get m k) ks f args)) 487 | (assoc m k (apply f (get m k) args)))))] 488 | (up m ks f args)))) 489 | 490 | (defn update 491 | ([m k f] 492 | (assoc m k (f (get m k)))) 493 | ([m k f x] 494 | (assoc m k (f (get m k) x))) 495 | ([m k f x y] 496 | (assoc m k (f (get m k) x y))) 497 | ([m k f x y z] 498 | (assoc m k (f (get m k) x y z))) 499 | ([m k f x y z & more] 500 | (assoc m k (apply f (get m k) x y z more)))) 501 | 502 | (defn fnil 503 | ([f x] 504 | (fn 505 | ([a] (f (if (nil? a) x a))) 506 | ([a b] (f (if (nil? a) x a) b)) 507 | ([a b c] (f (if (nil? a) x a) b c)) 508 | ([a b c & ds] (apply f (if (nil? a) x a) b c ds)))) 509 | ([f x y] 510 | (fn 511 | ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) 512 | ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c)) 513 | ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds)))) 514 | ([f x y z] 515 | (fn 516 | ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) 517 | ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c))) 518 | ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds))))) 519 | 520 | ;; set difference 521 | (defn difference 522 | ([s1] s1) 523 | ([s1 s2] 524 | (if (< (count s1) (count s2)) 525 | (reduce (fn [result item] 526 | (if (contains? s2 item) 527 | (disj result item) 528 | result)) 529 | s1 s1) 530 | (reduce disj s1 s2))) 531 | ([s1 s2 & sets] 532 | (reduce difference s1 (conj sets s2)))) 533 | 534 | ;; set intersection 535 | (defn intersection 536 | ([s1] s1) 537 | ([s1 s2] 538 | (if (< (count s2) (count s1)) 539 | (recur s2 s1) 540 | (reduce (fn [result item] 541 | (if (contains? s2 item) 542 | result 543 | (disj result item))) 544 | s1 (seq s1))))) 545 | 546 | (defn take-while [pred coll] 547 | (if (empty? coll) 548 | coll 549 | (when (pred (first coll)) 550 | (cons (first coll) (take-while pred (rest coll)))))) 551 | 552 | (defn drop-while [pred coll] 553 | (let [step (fn [pred coll] 554 | (let [s (seq coll)] 555 | (if (and s (pred (first s))) 556 | (recur pred (rest s)) 557 | s)))] 558 | (step pred coll))) 559 | 560 | (defn split-with [pred coll] 561 | [(take-while pred coll) (drop-while pred coll)]) 562 | 563 | (defn butlast [s] 564 | (loop [ret [] s s] 565 | (if (next s) 566 | (recur (conj ret (first s)) (next s)) 567 | (seq ret)))) 568 | 569 | (defn last [s] 570 | (if (next s) 571 | (recur (next s)) 572 | (first s))) 573 | 574 | (defn into [c r] (reduce conj c r)) 575 | 576 | (defn keep [f coll] 577 | (when-let [s (seq coll)] 578 | (let [x (f (first s))] 579 | (if (nil? x) 580 | (keep f (rest s)) 581 | (cons x (keep f (rest s))))))) 582 | 583 | (defn- keepi [f idx coll] 584 | (when-let [s (seq coll)] 585 | (let [x (f idx (first s))] 586 | (if (nil? x) 587 | (keepi f (inc idx) (rest s)) 588 | (cons x (keepi f (inc idx) (rest s))))))) 589 | 590 | (defn- mapi [f idx coll] 591 | (when-let [s (seq coll)] 592 | (cons (f idx (first s)) (mapi f (inc idx) (rest s))))) 593 | 594 | (defn map-indexed [f coll] 595 | (mapi f 0 coll)) 596 | 597 | (defn keep-indexed [f coll] 598 | (keepi f 0 coll)) 599 | 600 | (defn find [m key] 601 | (when-let [v (m key)] [key v])) 602 | 603 | (defn select-keys 604 | [map keyseq] 605 | (loop [ret {} keys (seq keyseq)] 606 | (println ret keys) 607 | (if keys 608 | (let [entry (find map (first keys))] 609 | (println "d" entry) 610 | (recur 611 | (if entry 612 | (conj ret entry) 613 | ret) 614 | (next keys))) 615 | ret))) 616 | 617 | (defn keys [m] 618 | (map first m)) 619 | 620 | (defn vals [m] 621 | (map second m)) 622 | 623 | (defn every? 624 | [pred coll] 625 | (cond 626 | (nil? (seq coll)) true 627 | (pred (first coll)) (recur pred (next coll)) 628 | :else false)) 629 | 630 | (def not-every? (comp not every?)) 631 | 632 | (def not-any? (comp not some)) 633 | 634 | (defn remove [pred coll] 635 | (filter (complement pred) coll)) 636 | 637 | (defn drop-last 638 | ([coll] (drop-last 1 coll)) 639 | ([n coll] (map (fn [x _] x) coll (drop n coll)))) 640 | 641 | (defn take-last 642 | [n coll] 643 | (loop [s (seq coll), lead (seq (drop n coll))] 644 | (if lead 645 | (recur (next s) (next lead)) 646 | s))) 647 | 648 | (defn zipmap 649 | [keys vals] 650 | (loop [map {} 651 | ks (seq keys) 652 | vs (seq vals)] 653 | (if (and ks vs) 654 | (recur (assoc map (first ks) (first vs)) 655 | (next ks) 656 | (next vs)) 657 | map))) 658 | 659 | (defn group-by 660 | [f coll] 661 | (reduce 662 | (fn [ret x] 663 | (let [k (f x)] 664 | (assoc ret k (conj (get ret k []) x)))) 665 | {} coll)) 666 | 667 | (defn partition-by [f coll] 668 | (when-let [s (seq coll)] 669 | (let [fst (first s) 670 | fv (f fst) 671 | run (cons fst (take-while #(= fv (f %)) (next s)))] 672 | (cons run (partition-by f (drop (count run) s)))))) 673 | 674 | (defn frequencies 675 | [coll] 676 | (reduce (fn [counts x] 677 | (assoc counts x (inc (get counts x 0)))) 678 | {} coll)) 679 | 680 | (defn reductions 681 | ([f coll] 682 | (if-let [s (seq coll)] 683 | (reductions f (first s) (rest s)) 684 | (list (f)))) 685 | ([f init coll] 686 | (cons init 687 | (when-let [s (seq coll)] 688 | (reductions f (f init (first s)) (rest s)))))) 689 | 690 | (defn take-nth [n coll] 691 | (when-let [s (seq coll)] 692 | (cons (first s) (take-nth n (drop n s))))) 693 | 694 | (defn interleave 695 | ([] ()) 696 | ([c1] c1) 697 | ([c1 c2] 698 | (let [s1 (seq c1) s2 (seq c2)] 699 | (when (and s1 s2) 700 | (cons (first s1) (cons (first s2) 701 | (interleave (rest s1) (rest s2))))))) 702 | ([c1 c2 & colls] 703 | (let [ss (map seq (conj colls c2 c1))] 704 | (when (every? identity ss) 705 | (concat (map first ss) (apply interleave (map rest ss))))))) 706 | 707 | (defn- distinct-step [xs seen] 708 | ((fn [[f :as xs] seen] 709 | (when-let [s (seq xs)] 710 | (if (contains? seen f) 711 | (recur (rest s) seen) 712 | (cons f (distinct-step (rest s) (conj seen f)))))) 713 | xs seen)) 714 | 715 | (defn distinct [coll] 716 | (distinct-step coll #{})) 717 | 718 | (defn distinct? 719 | ([_] true) 720 | ([x y] (not (= x y))) 721 | ([x y & more] 722 | (if (not= x y) 723 | (loop [s #{x y} [x & etc :as xs] more] 724 | (if xs 725 | (if (contains? s x) 726 | false 727 | (recur (conj s x) etc)) 728 | true)) 729 | false))) 730 | 731 | (defn nth 732 | ([coll n] (nth coll n nil)) 733 | ([coll n nf] 734 | (if (or (< n 0) (>= n (count coll))) 735 | nf 736 | (loop [c coll i n] 737 | (if (> i 0) (recur (next c) (dec i)) (first c)))))) 738 | 739 | (defmacro doseq [bind & body] 740 | `(loop [[~(first bind) & r#] ~(second bind)] 741 | (when ~(first bind) 742 | ~@body 743 | (recur r#)))) 744 | 745 | (defmacro cond-> 746 | [expr & clauses] 747 | (let [g (gensym) 748 | steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g)) 749 | (partition 2 clauses))] 750 | `(let [~g ~expr 751 | ~@(interleave (repeat g) (butlast steps))] 752 | ~(if (empty? steps) 753 | g 754 | (last steps))))) 755 | 756 | (defmacro cond->> 757 | [expr & clauses] 758 | (let [g (gensym) 759 | steps (map (fn [[test step]] `(if ~test (->> ~g ~step) ~g)) 760 | (partition 2 clauses))] 761 | `(let [~g ~expr 762 | ~@(interleave (repeat g) (butlast steps))] 763 | ~(if (empty? steps) 764 | g 765 | (last steps))))) 766 | 767 | (defmacro as-> 768 | [expr name & forms] 769 | `(let [~name ~expr 770 | ~@(interleave (repeat name) (butlast forms))] 771 | ~(if (empty? forms) 772 | name 773 | (last forms)))) 774 | 775 | (defmacro some-> 776 | [expr & forms] 777 | (let [g (gensym) 778 | steps (map (fn [step] `(if (nil? ~g) nil (-> ~g ~step))) 779 | forms)] 780 | `(let [~g ~expr 781 | ~@(interleave (repeat g) (butlast steps))] 782 | ~(if (empty? steps) 783 | g 784 | (last steps))))) 785 | 786 | (defmacro some->> 787 | [expr & forms] 788 | (let [g (gensym) 789 | steps (map (fn [step] `(if (nil? ~g) nil (->> ~g ~step))) 790 | forms)] 791 | `(let [~g ~expr 792 | ~@(interleave (repeat g) (butlast steps))] 793 | ~(if (empty? steps) 794 | g 795 | (last steps))))) --------------------------------------------------------------------------------