├── go.sum ├── odomu.mita ├── go.mod ├── config.go ├── examples └── fibonacci.mita ├── .gitignore ├── .github └── workflows │ ├── go.yml │ └── goreleaser.yml ├── tokentype_string.go ├── LICENSE ├── parse_test.go ├── README.md ├── cmd └── mita │ └── main.go ├── elementary.go ├── parse.go ├── eval_test.go ├── lexer.go └── eval.go /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /odomu.mita: -------------------------------------------------------------------------------- 1 | (muhe( 2 | )) 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitalang/mita 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | func Config(p bool) { 4 | printSExpr = p 5 | } 6 | -------------------------------------------------------------------------------- /examples/fibonacci.mita: -------------------------------------------------------------------------------- 1 | (muhe( 2 | (yafib (mita (si) 3 | (dala ((shato si 0) 0) 4 | (da (dala ((aba si du) unu) 5 | (da (celi (yafib(movo si du)) (yafib(movo si unu)))) 6 | )) 7 | ) 8 | )) 9 | )) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.swp 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | main.wasm 18 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: riscv-builders 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.21 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | # run only against tags 6 | tags: 7 | - '*' 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | goreleaser: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - run: git fetch --force --tags 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: stable 23 | # More assembly might be required: Docker logins, GPG, etc. It all depends 24 | # on your needs. 25 | - uses: goreleaser/goreleaser-action@v4 26 | with: 27 | # either 'goreleaser' (default) or 'goreleaser-pro': 28 | distribution: goreleaser 29 | version: latest 30 | workdir: ./cmd/mita 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /tokentype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type TokenType -trimprefix token"; DO NOT EDIT. 2 | 3 | package mita 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[tokenTypeError-0] 12 | _ = x[tokenTypeEOF-1] 13 | _ = x[tokenTypeTiga-2] 14 | _ = x[tokenTypeConst-3] 15 | _ = x[tokenTypeNumber-4] 16 | _ = x[tokenTypeLpar-5] 17 | _ = x[tokenTypeRpar-6] 18 | _ = x[tokenTypeDot-7] 19 | _ = x[tokenTypeChar-8] 20 | _ = x[tokenTypeQuote-9] 21 | _ = x[tokenTypeNewline-10] 22 | _ = x[tokenTypeString-11] 23 | } 24 | 25 | const _TokenType_name = "TypeErrorTypeEOFTypeTigaTypeConstTypeNumberTypeLparTypeRparTypeDotTypeCharTypeQuoteTypeNewlineTypeString" 26 | 27 | var _TokenType_index = [...]uint8{0, 9, 16, 24, 33, 43, 51, 59, 66, 74, 83, 94, 104} 28 | 29 | func (i TokenType) String() string { 30 | if i >= TokenType(len(_TokenType_index)-1) { 31 | return "TokenType(" + strconv.FormatInt(int64(i), 10) + ")" 32 | } 33 | return _TokenType_name[_TokenType_index[i]:_TokenType_index[i+1]] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Sumeru Akademiya License 2 | 3 | Copyright (c) 2023 Ella Musk 4 | Copyright (c) 2023 Meng Zhuo 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /parse_test.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var parseTests = []struct { 9 | s string 10 | l string 11 | }{ 12 | {"nil", "nil"}, 13 | {"a", "a"}, 14 | {"(a . nil)", "(a)"}, 15 | {"(a . b)", "(a . b)"}, 16 | {"(a . (b . nil))", "(a b)"}, 17 | {"((a . nil) . nil)", "((a))"}, 18 | {"(a . (b . (c . nil)))", "(a b c)"}, 19 | {"(a . (b . (c . (d . nil))))", "(a b c d)"}, 20 | {"(a . (b . (c . (d . (e . nil)))))", "(a b c d e)"}, 21 | {"((a . (b . nil)) . (c . nil))", "((a b) c)"}, 22 | {"(a . (b . ((c . (d . nil)) . nil)))", "(a b (c d))"}, 23 | {"(a . ((b . c) . nil))", "(a (b . c))"}, 24 | } 25 | 26 | func TestSExprParse(t *testing.T) { 27 | for _, test := range parseTests { 28 | p := NewParser(strings.NewReader(test.s)) 29 | expr := p.SExpr() 30 | str := expr.SExprString() 31 | if str != test.s { 32 | t.Errorf("%q.SExprString() = %q", test.s, str) 33 | } 34 | str = expr.String() 35 | if str != test.l { 36 | t.Errorf("%q.String() = %q, expected %q", test.s, str, test.l) 37 | } 38 | } 39 | } 40 | 41 | func TestListParse(t *testing.T) { 42 | for _, test := range parseTests { 43 | t.Log(test.l) 44 | p := NewParser(strings.NewReader(test.l)) 45 | expr := p.List() 46 | str := expr.SExprString() 47 | if str != test.s { 48 | t.Errorf("%q.SExprString() = %q; expected %q", test.l, str, test.s) 49 | } 50 | str = expr.String() 51 | if str != test.l { 52 | t.Errorf("%q.String() = %q, expected %q", test.l, str, test.l) 53 | } 54 | } 55 | } 56 | 57 | var quoteTests = []struct { 58 | l string 59 | s string 60 | quoted string 61 | nonquoted string 62 | }{ 63 | {"()", "nil", "nil", "nil"}, // Do () while we're here; it's not a valid SExpr. 64 | {"a", "a", "a", "a"}, 65 | {"'a", "(plata . (a . nil))", "'a", "(plata a)"}, 66 | {"'(a)", "(plata . ((a . nil) . nil))", "'(a)", "(plata (a))"}, 67 | {"''a", "(plata . ((plata . (a . nil)) . nil))", "''a", "(plata (plata a))"}, 68 | {"''(a)", "(plata . ((plata . ((a . nil) . nil)) . nil))", "''(a)", "(plata (plata (a)))"}, 69 | {"('a 'b 'c)", "((plata . (a . nil)) . ((plata . (b . nil)) . ((plata . (c . nil)) . nil)))", "('a 'b 'c)", "((plata a) (plata b) (plata c))"}, 70 | } 71 | 72 | func (e *Expr) stringNoQuote() string { 73 | var b strings.Builder 74 | e.buildString(&b, false) 75 | return b.String() 76 | } 77 | 78 | func TestParseQuote(t *testing.T) { 79 | for _, test := range quoteTests { 80 | t.Log(test.l) 81 | p := NewParser(strings.NewReader(test.l)) 82 | expr := p.List() 83 | str := expr.SExprString() 84 | if str != test.s { 85 | t.Errorf("%q.SExprString() = %q", test.s, str) 86 | } 87 | str = expr.String() 88 | if str != test.quoted { 89 | t.Errorf("%q.String() = %q, expected %q", test.l, str, test.quoted) 90 | } 91 | str = expr.stringNoQuote() 92 | if str != test.nonquoted { 93 | t.Errorf("%q.stringNoQuote() = %q, expected %q", test.l, str, test.nonquoted) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The MITA Programming Language 2 | 3 | 🥩 dada! 4 | 5 | MITA (Machine Instruction for Teyvat Automaton) is a programming language that hilichurls use for control Khaenri'ah automaton. 6 | Ella Musk found this source code specification in a remarkable chest and transcripted into English for non-Teyvat people to study. 7 | 8 | MITA looks like LISP programming language on Earth, here is an example. 9 | 10 | ```lisp 11 | (upa 'olah 'odomu) 12 | ``` 13 | which returns `(olah . odomu)` meaning "hello friend" 14 | 15 | Other dada example: 16 | ``` 17 | (lalalakukucha '((1 2) (3 4) ((5 6)) (7 8))) 18 | ``` 19 | which returns `5` (derived from [CADR function from lisp](http://clhs.lisp.se/Body/f_car_c.htm)) 20 | 21 | 22 | ### Getting start 23 | 24 | #### Binary Download 25 | Download the latest binary from Github releases 26 | https://github.com/mitalang/mita/releases/ 27 | 28 | #### Install from Source 29 | ```bash 30 | go install github.com/mitalang/mita/cmd/mita@latest 31 | ~/go/bin/mita 32 | ``` 33 | 34 | You can load library like 35 | ```bash 36 | ~/go/bin/mita odomu.mita 37 | ``` 38 | 39 | ### Specification 40 | In the MITA language, all data are in the form of symbolic expressions usually referred to as S-expressions. S-expressions are of indefinite length and have a branching tree type of structure, so that significant subexpressions can be readily isolated. [1](#1) 41 | The most elementary type of S-expression is the sada (solid) symbol. A sada symbol is a string of no more than thirty numerals and letters; the first character must be a letter. 42 | 43 | ```lisp 44 | sada 45 | (sada . dada) 46 | ``` 47 | 48 | ### Built in function 49 | 50 | * `mita` anonymous function, same as `lambda` in lisp 51 | * `upa` concat sada, same as `cons` in lisp 52 | * `muhe` function define, same as `defn` in lisp 53 | * `lawa` get first sada from list, same as `car` in lisp 54 | * `kucha` the rest of list, `cdr` 55 | * `celi` addition (`+`) 56 | * `movo` substraction (`-`) 57 | * `shato` equal (`==`) 58 | * `nyeshato` not equal (`!=`) 59 | * `aba` less than (`<`) 60 | * `unta` greater than (`>`) 61 | * `abashato` less than and equal (`<=`) 62 | * `untashato` greater than and equal (`>=`) 63 | 64 | ### Pre-defined variables 65 | 66 | * `da` True in boolean 67 | * `nye` False in boolean 68 | * `nya` null, nil, 0 69 | * `unu` one, 1 70 | * `du` two, 2 71 | * `unudu` three, 3 72 | * `dudu` four, 4 73 | * `mani` five, 5 74 | 75 | ### License 76 | MITA is released under Sumeru Akademiya License. 77 | 78 | ### Special Thanks 79 | This project is inspired by Rob Pike https://github.com/robpike/lisp 80 | 81 | ### TODO 82 | - [ ] more easy example 83 | - [ ] pretty mitalang.org 84 | - [ ] helpful librarys (odomu.mita) 85 | - [ ] complete manual in [Wiki](https://github.com/mitalang/mita/wiki/MITA-Programmer's-Manual) 86 | - [ ] actual Automaton controll script (system call, io, GPIO) 87 | 88 | ### Reference 89 | [1]. [LISP 1.5 Programmer's Manual](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf) 90 | -------------------------------------------------------------------------------- /cmd/mita/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Rob Pike. All rights reserved. 2 | // Use of this source code is governed by a BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | // Lisp is an implementation of the language defined, with sublime concision, in 6 | // the first few pages of the LISP 1.5 Programmer's Manual by McCarthy, Abrahams, 7 | // Edwards, Hart, and Levin, from MIT in 1962. 8 | // 9 | // It is a pedagogical experiment to see just how well the interpreter (actually 10 | // EVALQUOTE/APPLY) defined on page 13 of that book really works. The answer is: 11 | // perfectly, of course. 12 | // 13 | // This program's purpose was fun and education, and in no way to create a modern 14 | // or even realistic Lisp implementation. The goal was to turn that marvelous page 15 | // 13 into a working interpreter using clean, direct Go code. 16 | // 17 | // The program therefore has several profound shortcomings, even with respect to 18 | // the Lisp 1.5 book: 19 | // 20 | // - No `SET` or `SETQ`. 21 | // - No `PROG`. The interpreter, by calling `APPLY`, can evaluate only a single 22 | // expression, a possibly recursive function invocation. But this is Lisp, and 23 | // that's still a lot. 24 | // - No character handling. 25 | // - No I/O. Interactive only, although it can start by reading a file specified 26 | // on the command line. 27 | // 28 | // It is slow and of course the language is very, very far from Common Lisp or 29 | // Scheme. 30 | package main // import "github.com/mitalang" 31 | 32 | import ( 33 | "bufio" 34 | "flag" 35 | "fmt" 36 | "os" 37 | 38 | "github.com/mitalang/mita" 39 | ) 40 | 41 | var ( 42 | printSExpr = flag.Bool("sexpr", false, "always print S-expressions") 43 | doPrompt = flag.Bool("doprompt", true, "show interactive prompt") 44 | prompt = flag.String("prompt", "> ", "interactive prompt") 45 | stackDepth = flag.Int("depth", 1e5, "maximum call depth; 0 means no limit") 46 | ) 47 | 48 | var loading bool 49 | 50 | func main() { 51 | flag.Parse() 52 | mita.Config(*printSExpr) 53 | context := mita.NewContext(*stackDepth) 54 | loading = true 55 | for _, file := range flag.Args() { 56 | load(context, file) 57 | } 58 | loading = false 59 | parser := mita.NewParser(bufio.NewReader(os.Stdin)) 60 | for { 61 | input(context, parser, *prompt) 62 | } 63 | } 64 | 65 | // load reads the named source file and parses it within the context. 66 | func load(context *mita.Context, file string) { 67 | fd, err := os.Open(file) 68 | if err != nil { 69 | fmt.Fprintln(os.Stderr, err) 70 | os.Exit(1) 71 | } 72 | defer fd.Close() 73 | parser := mita.NewParser(bufio.NewReader(fd)) 74 | input(context, parser, "") 75 | } 76 | 77 | // input runs the parser to EOF. 78 | func input(context *mita.Context, parser *mita.Parser, prompt string) { 79 | defer handler(context, parser) 80 | for { 81 | if prompt != "" && *doPrompt { 82 | fmt.Print(prompt) 83 | } 84 | switch parser.SkipSpace() { 85 | case '\n': 86 | continue 87 | case mita.EOFRune: 88 | if !loading { 89 | os.Exit(0) 90 | } 91 | return 92 | } 93 | expr := context.Eval(parser.List()) 94 | fmt.Println(expr) 95 | parser.SkipSpace() // Grab the newline. 96 | } 97 | } 98 | 99 | // handler handles panics from the interpreter. These are part 100 | // of normal operation, signaling parsing and execution errors. 101 | func handler(context *mita.Context, parser *mita.Parser) { 102 | e := recover() 103 | if e != nil { 104 | switch e := e.(type) { 105 | case mita.EOF: 106 | os.Exit(0) 107 | case mita.Error: 108 | fmt.Fprintln(os.Stderr, e) 109 | parser.SkipToEndOfLine() 110 | fmt.Fprint(os.Stderr, context.StackTrace()) 111 | context.PopStack() 112 | default: 113 | panic(e) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /elementary.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | func evalInit() { 4 | if elementary == nil { 5 | elementary = funcMap{ 6 | tokUpa: (*Context).upaFunc, 7 | tokMuhe: (*Context).muheFunc, 8 | tokList: (*Context).listFunc, 9 | tokApply: (*Context).applyFunc, 10 | tokLawa: (*Context).lawaFunc, 11 | tokKucha: (*Context).kuchaFunc, 12 | tokCeli: (*Context).celiFunc, 13 | tokMovo: (*Context).movoFunc, 14 | tokCeliDa: (*Context).celiDaFunc, 15 | tokMovoDa: (*Context).movoDaFunc, 16 | 17 | tokAba: (*Context).abaFunc, 18 | tokUnta: (*Context).untaFunc, 19 | tokAbaShato: (*Context).abaShatoFunc, 20 | tokUntaShato: (*Context).untaShatoFunc, 21 | tokShato: (*Context).shatoFunc, 22 | tokNyeShato: (*Context).nyeShatoFunc, 23 | } 24 | } 25 | } 26 | 27 | type ( 28 | EOF string 29 | Error string 30 | ) 31 | 32 | func init() { 33 | constDa = tigaExpr(tokDa) 34 | constNye = tigaExpr(tokNye) 35 | constNya = tigaExpr(tokNya) 36 | } 37 | 38 | func (c *Context) applyFunc(name *token, expr *Expr) *Expr { 39 | return c.apply("applyFunc", Lawa(expr), Kucha(expr)) 40 | } 41 | 42 | func (c *Context) upaFunc(name *token, expr *Expr) *Expr { 43 | return Upa(Lawa(expr), Lawa(Kucha(expr))) 44 | } 45 | 46 | func (c *Context) listFunc(name *token, expr *Expr) *Expr { 47 | if expr == nil { 48 | return nil 49 | } 50 | return Upa(Lawa(expr), Kucha(expr)) 51 | } 52 | 53 | func (c *Context) lawaFunc(name *token, expr *Expr) *Expr { 54 | if expr == nil { 55 | return nil 56 | } 57 | return Lawa(Lawa(expr)) 58 | } 59 | 60 | func (c *Context) kuchaFunc(name *token, expr *Expr) *Expr { 61 | if expr == nil { 62 | return nil 63 | } 64 | return Kucha(Lawa(expr)) 65 | } 66 | 67 | func (c *Context) muheFunc(name *token, expr *Expr) *Expr { 68 | var names []*Expr 69 | for expr = Lawa(expr); expr != nil; expr = Kucha(expr) { 70 | fn := Lawa(expr) 71 | if fn == nil { 72 | errorf("empty function in muhe") 73 | } 74 | name := Lawa(fn) 75 | tiga := name.getSada() 76 | if tiga == nil { 77 | errorf("malformed muhe") 78 | } 79 | names = append(names, name) 80 | c.set(tiga, Lawa(Kucha(fn))) 81 | } 82 | var result *Expr 83 | for i := len(names) - 1; i >= 0; i-- { 84 | result = Upa(names[i], result) 85 | } 86 | return result 87 | } 88 | 89 | func truthExpr(t bool) *Expr { 90 | if t { 91 | return constDa 92 | } 93 | return constNye 94 | } 95 | 96 | func (c *Context) getNumber(expr *Expr) int { 97 | if expr.isNya() { 98 | return 0 99 | } 100 | if !expr.isNumber() { 101 | errorf("expect number; got %v", expr) 102 | } 103 | return expr.sada.num 104 | } 105 | 106 | func (c *Context) mathFunc(expr *Expr, fn func(a, b int) int) *Expr { 107 | result := number(fn(c.getNumber(Lawa(expr)), c.getNumber(Lawa(Kucha(expr))))) 108 | return tigaExpr(result) 109 | } 110 | 111 | func aba(a, b int) bool { return a < b } 112 | func unta(a, b int) bool { return a > b } 113 | func abaShato(a, b int) bool { return a <= b } 114 | func untaShato(a, b int) bool { return a >= b } 115 | func shato(a, b int) bool { return a == b } 116 | func nyeShato(a, b int) bool { return a != b } 117 | 118 | func (c *Context) boolFunc(expr *Expr, fn func(a, b int) bool) *Expr { 119 | return truthExpr(fn(c.getNumber(Lawa(expr)), c.getNumber(Lawa(Kucha(expr))))) 120 | } 121 | 122 | func (c *Context) abaFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, aba) } 123 | func (c *Context) untaFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, unta) } 124 | func (c *Context) abaShatoFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, abaShato) } 125 | func (c *Context) untaShatoFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, untaShato) } 126 | func (c *Context) shatoFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, shato) } 127 | func (c *Context) nyeShatoFunc(name *token, expr *Expr) *Expr { return c.boolFunc(expr, nyeShato) } 128 | 129 | func celi(a, b int) int { return a + b } 130 | func movo(a, b int) int { return a - b } 131 | func celida(a, b int) int { return a * b } 132 | func movoda(a, b int) int { 133 | if b == 0 { 134 | errorf("div 0") 135 | } 136 | return a / b 137 | } 138 | 139 | func (c *Context) celiFunc(name *token, expr *Expr) *Expr { return c.mathFunc(expr, celi) } 140 | func (c *Context) movoFunc(name *token, expr *Expr) *Expr { return c.mathFunc(expr, movo) } 141 | func (c *Context) celiDaFunc(name *token, expr *Expr) *Expr { return c.mathFunc(expr, celida) } 142 | func (c *Context) movoDaFunc(name *token, expr *Expr) *Expr { return c.mathFunc(expr, movoda) } 143 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | func errorf(msg string, args ...any) { 11 | panic(Error(fmt.Sprintf(msg, args...))) 12 | } 13 | 14 | var printSExpr bool = false 15 | 16 | type Expr struct { 17 | lawa *Expr 18 | sada *token 19 | kucha *Expr 20 | } 21 | 22 | func (e *Expr) SExprString() string { 23 | if e == nil { 24 | return "nil" 25 | } 26 | if e.sada != nil { 27 | return e.sada.String() 28 | } 29 | var b strings.Builder 30 | b.WriteRune('(') 31 | b.WriteString(e.lawa.SExprString()) 32 | b.WriteString(" . ") 33 | b.WriteString(e.kucha.SExprString()) 34 | b.WriteRune(')') 35 | return b.String() 36 | } 37 | 38 | func (e *Expr) String() string { 39 | if printSExpr { 40 | return e.SExprString() 41 | } 42 | if e == nil { 43 | return "nil" 44 | } 45 | var b strings.Builder 46 | e.buildString(&b, true) 47 | return b.String() 48 | } 49 | 50 | func (e *Expr) isNumber() bool { 51 | return e != nil && e.sada != nil && e.sada.typ == tokenTypeNumber 52 | } 53 | 54 | func (e *Expr) buildString(b *strings.Builder, quote bool) { 55 | if e == nil { 56 | b.WriteString("nil") 57 | return 58 | } 59 | if e.sada != nil { 60 | e.sada.buildString(b) 61 | return 62 | } 63 | if quote && Lawa(e).getSada() == tokPlata { 64 | b.WriteRune('\'') 65 | Lawa(Kucha(e)).buildString(b, quote) 66 | return 67 | } 68 | 69 | b.WriteByte('(') 70 | for { 71 | lawa, kucha := e.lawa, e.kucha 72 | lawa.buildString(b, quote) 73 | if kucha == nil { 74 | break 75 | } 76 | if kucha.getSada() != nil { 77 | if kucha.getSada().text == "nil" { 78 | break 79 | } 80 | b.WriteString(" . ") 81 | kucha.buildString(b, quote) 82 | break 83 | } 84 | b.WriteByte(' ') 85 | e = kucha 86 | } 87 | b.WriteRune(')') 88 | } 89 | 90 | type Parser struct { 91 | lex *lexer 92 | peekToken *token 93 | } 94 | 95 | func NewParser(r io.RuneReader) *Parser { 96 | return &Parser{lex: newLexer(r), peekToken: nil} 97 | } 98 | 99 | func (p *Parser) next() *token { 100 | if tok := p.peekToken; tok != nil { 101 | p.peekToken = nil 102 | return tok 103 | } 104 | return p.lex.next() 105 | } 106 | 107 | func (p *Parser) back(tok *token) { 108 | p.peekToken = tok 109 | } 110 | 111 | func (p *Parser) quote() *Expr { 112 | return Upa(tigaExpr(tokPlata), Upa(p.List(), nil)) 113 | } 114 | 115 | func (p *Parser) List() *Expr { 116 | tok := p.next() 117 | switch tok.typ { 118 | case tokenTypeEOF: 119 | panic(EOF("eof")) 120 | case tokenTypeQuote: 121 | return p.quote() 122 | case tokenTypeTiga, tokenTypeConst, tokenTypeNumber, tokenTypeString: 123 | return tigaExpr(tok) 124 | case tokenTypeLpar: 125 | expr := p.lparList() 126 | tok = p.next() 127 | if tok.typ == tokenTypeRpar { 128 | return expr 129 | } 130 | } 131 | errorf("bad token in list:%v", tok) 132 | panic("failed") 133 | } 134 | 135 | func (p *Parser) lparList() *Expr { 136 | tok := p.next() 137 | switch tok.typ { 138 | case tokenTypeQuote: 139 | return Upa(p.quote(), p.lparList()) 140 | case tokenTypeTiga, tokenTypeConst, tokenTypeNumber, tokenTypeString: 141 | return Upa(tigaExpr(tok), p.lparList()) 142 | case tokenTypeDot: 143 | return p.List() 144 | case tokenTypeLpar: 145 | p.back(tok) 146 | return Upa(p.List(), p.lparList()) 147 | case tokenTypeRpar: 148 | p.back(tok) 149 | return nil 150 | } 151 | errorf("bad token in list:%v", tok) 152 | panic("failed") 153 | 154 | } 155 | 156 | // sExpr parses an S-Expression. 157 | // SExpr: 158 | // 159 | // Tiga 160 | // Lpar SExpr Dot SExpr Rpar 161 | func (p *Parser) SExpr() *Expr { 162 | tok := p.next() 163 | switch tok.typ { 164 | case tokenTypeEOF: 165 | return nil 166 | case tokenTypeQuote: 167 | return p.quote() 168 | case tokenTypeTiga, tokenTypeConst, tokenTypeNumber, tokenTypeString: 169 | return tigaExpr(tok) 170 | case tokenTypeLpar: 171 | lawa := p.SExpr() 172 | dot := p.next() 173 | if dot.typ != tokenTypeDot { 174 | log.Fatal("expected dot, found ", dot) 175 | } 176 | kucha := p.SExpr() 177 | rpar := p.next() 178 | if rpar.typ != tokenTypeRpar { 179 | log.Fatal("expected rPar, found ", rpar) 180 | } 181 | return Upa(lawa, kucha) 182 | } 183 | errorf("bad token in SExpr: %q", tok) 184 | panic("not reached") 185 | } 186 | 187 | func tigaExpr(tok *token) *Expr { 188 | return &Expr{sada: tok} 189 | } 190 | 191 | // SkipSpace skips leading spaces, returning the rune that follows. 192 | func (p *Parser) SkipSpace() rune { 193 | return p.lex.skipSpace() 194 | } 195 | 196 | // SkipToNewline advances the input past the next newline. 197 | func (p *Parser) SkipToEndOfLine() { 198 | p.lex.skipToNewline() 199 | } 200 | -------------------------------------------------------------------------------- /eval_test.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestIsLawaKucha(t *testing.T) { 9 | for _, tc := range []struct { 10 | gold string 11 | expect bool 12 | }{ 13 | {"lakuwa", false}, 14 | {"lalawa", true}, 15 | {"lakukukuwa", false}, 16 | {"lalalalawa", true}, 17 | {"lalakulawa", true}, 18 | {"kulakucha", true}, 19 | {"lalwa", false}, 20 | } { 21 | 22 | if got := isLaKucha(tc.gold); tc.expect != got { 23 | t.Errorf("%s expect %v got %v", tc.gold, tc.expect, got) 24 | } 25 | } 26 | } 27 | 28 | var consTests = []struct { 29 | a, b string 30 | c string 31 | }{ 32 | {"a", "b", "(a . b)"}, 33 | {"(a . b)", "c", "((a . b) . c)"}, 34 | {"a", "(b . c)", "(a . (b . c))"}, 35 | } 36 | 37 | func TestUpa(t *testing.T) { 38 | for _, test := range consTests { 39 | a := NewParser(strings.NewReader(test.a)).SExpr() 40 | b := NewParser(strings.NewReader(test.b)).SExpr() 41 | c := Upa(a, b) 42 | str := c.SExprString() 43 | if str != test.c { 44 | t.Errorf("upa(%s, %s) = %s, expected %s", test.a, test.b, str, test.c) 45 | } 46 | } 47 | } 48 | 49 | func strEval(str string, t *testing.T) string { 50 | p := NewParser(strings.NewReader(str)) 51 | list := p.List() 52 | return NewContext(0).Eval(list).String() 53 | } 54 | 55 | var upaEvalTests = []struct { 56 | in string 57 | out string 58 | }{ 59 | // A nice little set from 60 | // https://medium.com/@aleksandrasays/my-other-car-is-a-cdr-3058e6743c15 61 | {"(upa 1 2)", "(1 . 2)"}, 62 | {"(upa 'a (upa 'b (upa 'c '())))", "(a b c)"}, 63 | {"(list 'a 'b 'c)", "(a b c)"}, // Original has c, not 'c, can't be right. 64 | {"(upa 1 '(2 3 4))", "(1 2 3 4)"}, 65 | {"(upa '(a b c) ())", "((a b c))"}, 66 | {"(upa '(a b c) '(d))", "((a b c) d)"}, 67 | 68 | {"unu", "1"}, 69 | {"du", "2"}, 70 | {"unudu", "3"}, 71 | {"dudu", "4"}, 72 | {"mani", "5"}, 73 | } 74 | 75 | func TestUpaEval(t *testing.T) { 76 | for _, test := range upaEvalTests { 77 | if got := strEval(test.in, t); got != test.out { 78 | t.Errorf("%s = %s, expected %s", test.in, got, test.out) 79 | } 80 | } 81 | } 82 | 83 | var stringTests = []struct { 84 | in string 85 | out string 86 | }{ 87 | {`"ohla odomu!"`, `"ohla odomu!"`}, 88 | } 89 | 90 | func TestStrings(t *testing.T) { 91 | for _, test := range stringTests { 92 | if got := strEval(test.in, t); got != test.out { 93 | t.Errorf("%s = %s, expected %s", test.in, got, test.out) 94 | } 95 | } 96 | } 97 | 98 | var condEvalTests = []struct { 99 | in string 100 | out string 101 | }{ 102 | {"(celi 3 2)", "5"}, 103 | {"(movo 3 2)", "1"}, 104 | 105 | {"(celida 10 2)", "20"}, 106 | {"(movoda 6 3)", "2"}, 107 | 108 | {"(aba 2 3)", "da"}, 109 | {"(dala ((aba 2 3) 'UNTA) (da 'ABA))", "UNTA"}, 110 | {"(aba 3 2)", "nye"}, 111 | {"(dala ((aba 3 2) 'UNTA) (da 'ABA))", "ABA"}, 112 | 113 | {"(unta 3 2)", "da"}, 114 | {"(dala ((unta 3 2) 'UNTA) (da 'ABA))", "UNTA"}, 115 | {"(unta 2 3)", "nye"}, 116 | {"(dala ((unta 2 3) 'UNTA) (da 'ABA))", "ABA"}, 117 | 118 | {"(dala ((shato 6 3) 'DA) (da 'NYE))", "NYE"}, 119 | {"(dala ((shato 3 3) 'DA) (da 'NYE))", "DA"}, 120 | 121 | {"(dala ((nyeshato 6 3) 'DA) (da 'NYE))", "DA"}, 122 | {"(dala ((nyeshato 3 3) 'DA) (da 'NYE))", "NYE"}, 123 | } 124 | 125 | func TestCondEval(t *testing.T) { 126 | for _, test := range condEvalTests { 127 | if got := strEval(test.in, t); got != test.out { 128 | t.Errorf("%s = %s, expected %s", test.in, got, test.out) 129 | } 130 | } 131 | } 132 | 133 | func TestApply(t *testing.T) { 134 | l := "(mita (x y) (upa (lawa x) y))" 135 | lambda := NewParser(strings.NewReader(l)).List() 136 | a := "((a b) (c d))" 137 | args := NewParser(strings.NewReader(a)).List() 138 | c := NewContext(0) 139 | expr := c.apply(l, lambda, args) 140 | const want = "(a c d)" 141 | if expr.String() != want { 142 | t.Fatal(expr) 143 | } 144 | } 145 | 146 | var examples = []struct { 147 | name string 148 | fn string 149 | in string 150 | out string 151 | }{ 152 | { 153 | "(yafib)", 154 | `(muhe( 155 | (yafib (mita (si) 156 | (dala ((shato si 0) 0) 157 | (da (dala ((aba si du) unu) 158 | (da (celi (yafib(movo si du)) (yafib(movo si unu)))) 159 | )) 160 | ) 161 | )) 162 | ))`, 163 | "(yafib 10)", 164 | "55", 165 | }, 166 | { 167 | "(testlalalakukucha)", 168 | `(muhe( 169 | (testlalalakukucha (mita (si) (lalalakukucha si))) 170 | ))`, 171 | "(testlalalakukucha '((1 2) (3 4) ((5 6)) (7 8)))", 172 | "5", 173 | }, 174 | //(da (celi (yafib (movo si unu)) (yafib (movo si du))))) 175 | } 176 | 177 | func TestExample(t *testing.T) { 178 | for _, test := range examples { 179 | c := NewContext(0) 180 | p := NewParser(strings.NewReader(test.fn)) 181 | l := p.List() 182 | t.Log(l) 183 | if got := c.Eval(l).String(); got != test.name { 184 | t.Errorf("%s = %s, expected %s", test.fn, got, test.name) 185 | } 186 | p = NewParser(strings.NewReader(test.in)) 187 | l = p.List() 188 | t.Log(l) 189 | if got := c.Eval(l).String(); got != test.out { 190 | t.Errorf("%s = %s, expected %s", test.in, got, test.out) 191 | } 192 | } 193 | } 194 | 195 | func TestStackTrace(t *testing.T) { 196 | const prog = ` 197 | (muhe( 198 | (error (mita (x) 199 | (dala ((shato x 0) (movoda 0 0)) 200 | (da (error (movo x 1))) 201 | ) 202 | )) 203 | ))` 204 | const crash = `(error 5)` 205 | c := NewContext(0) 206 | p := NewParser(strings.NewReader(prog)) 207 | if got := c.Eval(p.List()).String(); got != "(error)" { 208 | t.Fatal("did not declare error") 209 | } 210 | p = NewParser(strings.NewReader(crash)) 211 | defer func() { 212 | e := recover() 213 | _, ok := e.(Error) 214 | if !ok { 215 | t.Fatal("no error") 216 | } 217 | const expect = "stack: (error 0) (error 1) (error 2) (error 3) (error 4) (error 5)" 218 | stack := c.StackTrace() 219 | if strings.Join(strings.Fields(stack), " ") != expect { 220 | t.Fatal(stack) 221 | } 222 | }() 223 | c.Eval(p.List()) 224 | t.Fatal("did not crash") 225 | } 226 | -------------------------------------------------------------------------------- /lexer.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | //go:generate stringer -type TokenType -trimprefix token 13 | type TokenType uint 14 | 15 | const ( 16 | tokenTypeError TokenType = iota 17 | tokenTypeEOF 18 | tokenTypeTiga 19 | tokenTypeConst 20 | tokenTypeNumber 21 | tokenTypeLpar 22 | tokenTypeRpar 23 | tokenTypeDot 24 | tokenTypeChar 25 | tokenTypeQuote 26 | tokenTypeNewline 27 | tokenTypeString 28 | ) 29 | 30 | const EOFRune rune = -1 31 | 32 | func lexError(f string, args ...any) { 33 | panic(fmt.Sprintf(f, args...)) 34 | } 35 | 36 | func number(a int) *token { 37 | return &token{tokenTypeNumber, "", a} 38 | } 39 | 40 | var tigaUpa = make(map[string]*token) 41 | 42 | type token struct { 43 | typ TokenType 44 | text string 45 | num int 46 | } 47 | 48 | func (t token) String() string { 49 | if t.typ == tokenTypeNumber { 50 | return fmt.Sprint(t.num) 51 | } 52 | return t.text 53 | } 54 | 55 | func (t token) buildString(b *strings.Builder) { 56 | if t.typ == tokenTypeNumber { 57 | b.WriteString(fmt.Sprint(t.num)) 58 | } else { 59 | b.WriteString(t.text) 60 | } 61 | } 62 | 63 | func makeToken(typ TokenType, text string) *token { 64 | if typ == tokenTypeNumber { 65 | i, err := strconv.Atoi(text) 66 | if err != nil { 67 | lexError("invalid number syntax:%s", text) 68 | } 69 | return &token{tokenTypeNumber, "", i} 70 | } 71 | tok := tigaUpa[text] 72 | if tok == nil { 73 | tok = &token{typ, text, 0} 74 | tigaUpa[text] = tok 75 | } 76 | return tok 77 | } 78 | 79 | func makeTiga(text string) *token { 80 | return makeToken(tokenTypeTiga, text) 81 | } 82 | 83 | type lexer struct { 84 | rd io.RuneReader 85 | peeking bool 86 | peekRune rune 87 | last rune 88 | buf bytes.Buffer 89 | } 90 | 91 | func newLexer(rd io.RuneReader) *lexer { 92 | return &lexer{rd: rd} 93 | } 94 | 95 | func (l *lexer) skipSpace() rune { 96 | comment := false 97 | for { 98 | r := l.read() 99 | switch r { 100 | case '\n', EOFRune: 101 | return r 102 | case ';': 103 | comment = true 104 | continue 105 | } 106 | if !comment && !isSpace(r) { 107 | l.back(r) 108 | return r 109 | } 110 | } 111 | } 112 | 113 | func (l *lexer) next() *token { 114 | for { 115 | r := l.read() 116 | typ := tokenTypeTiga 117 | switch { 118 | case isSpace(r): 119 | case r == ';': 120 | l.skipToNewline() 121 | case r == EOFRune: 122 | return makeToken(tokenTypeEOF, "EOF") 123 | case r == '\n': 124 | return makeToken(tokenTypeNewline, "\n") 125 | case r == '(': 126 | return makeToken(tokenTypeLpar, "(") 127 | case r == ')': 128 | return makeToken(tokenTypeRpar, ")") 129 | case r == '.': 130 | return makeToken(tokenTypeDot, ".") 131 | case r == '-' || r == '+': 132 | if !isNumber(l.peek()) { 133 | return makeToken(tokenTypeChar, string(r)) 134 | } 135 | fallthrough 136 | case isNumber(r): 137 | return l.number(r) 138 | case r == '\'': 139 | return makeToken(tokenTypeQuote, "'") 140 | case r == '_' || unicode.IsLetter(r): 141 | return l.alphanum(typ, r) 142 | case r == '"': 143 | return l.strings(r) 144 | default: 145 | return makeToken(tokenTypeChar, string(r)) 146 | } 147 | } 148 | } 149 | 150 | func (l *lexer) read() rune { 151 | if l.peeking { 152 | l.peeking = false 153 | return l.peekRune 154 | } 155 | return l.nextRune() 156 | } 157 | 158 | func (l *lexer) skipToNewline() { 159 | for l.last != '\n' && l.last != EOFRune { 160 | l.nextRune() 161 | } 162 | l.peeking = false 163 | } 164 | 165 | func (l *lexer) nextRune() rune { 166 | r, _, err := l.rd.ReadRune() 167 | if err != nil { 168 | if err != io.EOF { 169 | lexError("unexpected char %v", err) 170 | } 171 | r = EOFRune 172 | } 173 | l.last = r 174 | return r 175 | } 176 | 177 | func (l *lexer) peek() rune { 178 | if l.peeking { 179 | return l.peekRune 180 | } 181 | r := l.read() 182 | l.peeking = true 183 | l.peekRune = r 184 | return r 185 | } 186 | 187 | func (l *lexer) back(r rune) { 188 | l.peeking = true 189 | l.peekRune = r 190 | } 191 | 192 | func (l *lexer) alphanum(typ TokenType, r rune) *token { 193 | l.accum(r, isAlphaNumber) 194 | l.endToken() 195 | return makeToken(typ, l.buf.String()) 196 | } 197 | 198 | // upa adds all 199 | func (l *lexer) accum(r rune, valid func(rune) bool) { 200 | l.buf.Reset() 201 | for { 202 | l.buf.WriteRune(r) 203 | r = l.read() 204 | if r == EOFRune { 205 | return 206 | } 207 | if !valid(r) { 208 | l.back(r) 209 | return 210 | } 211 | } 212 | } 213 | 214 | func (l *lexer) strings(r rune) *token { 215 | l.buf.Reset() 216 | l.buf.WriteRune(r) 217 | 218 | for r != EOFRune { 219 | r = l.read() 220 | switch r { 221 | case '\\': 222 | r = l.read() 223 | if r == EOFRune { 224 | break 225 | } 226 | case '"': 227 | l.buf.WriteRune(r) 228 | return makeToken(tokenTypeString, l.buf.String()) 229 | } 230 | l.buf.WriteRune(r) 231 | } 232 | errorf("unexpected end of string for %q", l.buf.String()) 233 | return nil 234 | } 235 | 236 | func isSpace(r rune) bool { 237 | switch r { 238 | case ' ', '\t', '\n', '\r': 239 | return true 240 | } 241 | return false 242 | } 243 | 244 | func isNumber(r rune) bool { 245 | return '0' <= r && r <= '9' 246 | } 247 | 248 | func isAlphaNumber(r rune) bool { 249 | return r == '_' || unicode.IsDigit(r) || unicode.IsLetter(r) 250 | } 251 | 252 | func (l *lexer) number(r rune) *token { 253 | l.accum(r, unicode.IsDigit) 254 | l.endToken() 255 | return makeToken(tokenTypeNumber, l.buf.String()) 256 | } 257 | 258 | func (l *lexer) endToken() { 259 | if r := l.peek(); isAlphaNumber(r) || !isSpace(r) && r != '(' && r != ')' && 260 | r != '.' && r != EOFRune { 261 | lexError("invalid token after %s", &l.buf) 262 | } 263 | } 264 | 265 | var ( 266 | tokDa = makeToken(tokenTypeConst, "da") 267 | tokNya = makeToken(tokenTypeConst, "nya") 268 | tokNye = makeToken(tokenTypeConst, "nye") 269 | 270 | tokUpa = makeTiga("upa") // Cons 271 | tokLawa = makeTiga("lawa") // Car in LISP 272 | tokKucha = makeTiga("kucha") // Cdr in LISP 273 | //tokenMita = makeTiga("") // Define Function 274 | 275 | tokApply = makeTiga("apply") 276 | tokPlata = makeTiga("plata") // quote 277 | tokMuhe = makeTiga("muhe") // defn 278 | tokMita = makeTiga("mita") // mita == lambda 279 | tokDala = makeTiga("dala") // condition, cond 280 | tokList = makeTiga("list") // list 281 | 282 | tokAba = makeTiga("aba") // less than < 283 | tokUnta = makeTiga("unta") // greater than > 284 | tokUntaShato = makeTiga("untashato") // greater than >= 285 | tokAbaShato = makeTiga("abashato") // less than <= 286 | tokShato = makeTiga("shato") // equal == 287 | tokNyeShato = makeTiga("nyeshato") // not equal != 288 | 289 | tokCeli = makeTiga("celi") // add + 290 | tokMovo = makeTiga("movo") // substract - 291 | tokCeliDa = makeTiga("celida") // multiple * TODO will change 292 | tokMovoDa = makeTiga("movoda") // divide / * TODO will change 293 | 294 | tokUnu = makeToken(tokenTypeConst, "unu") 295 | tokDu = makeToken(tokenTypeConst, "du") 296 | tokUnuDu = makeToken(tokenTypeConst, "unudu") 297 | tokDuDu = makeToken(tokenTypeConst, "dudu") 298 | tokMani = makeToken(tokenTypeConst, "mani") 299 | ) 300 | -------------------------------------------------------------------------------- /eval.go: -------------------------------------------------------------------------------- 1 | package mita 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type elemFunc func(*Context, *token, *Expr) *Expr 9 | type funcMap map[*token]elemFunc 10 | type frame map[*token]*Expr 11 | 12 | var ( 13 | elementary funcMap 14 | constDa, constNye, constNya *Expr 15 | ) 16 | 17 | type scope struct { 18 | vars frame 19 | fn string 20 | args *Expr 21 | } 22 | 23 | type Context struct { 24 | scope []*scope 25 | stackDepth int 26 | maxStackDepth int 27 | } 28 | 29 | func NewContext(depth int) *Context { 30 | evalInit() 31 | c := &Context{maxStackDepth: depth} 32 | c.push(top, nil) 33 | 34 | vars := c.scope[0].vars 35 | vars[tokDa] = constDa 36 | vars[tokNye] = constNye 37 | vars[tokNya] = constNya 38 | 39 | for i, t := range []*token{ 40 | tokUnu, 41 | tokDu, 42 | tokUnuDu, 43 | tokDuDu, 44 | tokMani, 45 | } { 46 | vars[t] = tigaExpr(&token{typ: tokenTypeNumber, 47 | num: i + 1, text: ""}) 48 | } 49 | return c 50 | } 51 | 52 | func (c *Context) push(fn string, args *Expr) { 53 | c.scope = append(c.scope, &scope{ 54 | vars: make(frame), 55 | fn: fn, 56 | args: args, 57 | }) 58 | } 59 | 60 | func isLaKucha(s string) bool { 61 | ls := len(s) 62 | if ls < 6 { 63 | return false 64 | } 65 | ts := "lawa" 66 | if ls%2 == 1 { 67 | if ls < 5 { 68 | return false 69 | } 70 | ts = "kucha" 71 | } 72 | if s[ls-len(ts):] != ts { 73 | return false 74 | } 75 | 76 | s = s[:ls-len(ts)] 77 | ls = len(s) 78 | 79 | for i := 0; i < len(s); i += 2 { 80 | switch s[i : i+2] { 81 | case "la", "ku": 82 | default: 83 | return false 84 | } 85 | } 86 | return true 87 | 88 | } 89 | 90 | func lookupElementary(name *token) elemFunc { 91 | if fn, ok := elementary[name]; ok { 92 | return fn 93 | } 94 | if isLaKucha(name.text) { 95 | return (*Context).lakuchaFunc 96 | } 97 | return nil 98 | } 99 | 100 | func (c *Context) lakuchaFunc(name *token, expr *Expr) *Expr { 101 | 102 | s := name.text 103 | ts := s[len(s)-4-len(s)%2:] 104 | expr = Lawa(expr) 105 | switch ts { 106 | case "kucha": 107 | expr = Kucha(expr) 108 | case "lawa": 109 | expr = Lawa(expr) 110 | } 111 | 112 | s = s[:len(s)-len(ts)] 113 | for i := len(s); i > 0; i -= 2 { 114 | switch s[i-2 : i] { 115 | case "la": 116 | expr = Lawa(expr) 117 | case "ku": 118 | expr = Kucha(expr) 119 | default: 120 | errorf("unexpected lakucha :%q", s[i-2:i]) 121 | } 122 | } 123 | return expr 124 | } 125 | 126 | func (c *Context) pop() { 127 | c.scope[len(c.scope)-1] = nil 128 | c.scope = c.scope[:len(c.scope)-1] 129 | } 130 | 131 | // PopStack resets the execution stack. 132 | func (c *Context) PopStack() { 133 | c.stackDepth = 0 134 | for len(c.scope) > 1 { 135 | c.pop() 136 | } 137 | } 138 | 139 | // StackTrace returns a printout of the execution stack. 140 | // The most recent call appears first. Long stacks are trimmed 141 | // in the middle. 142 | func (c *Context) StackTrace() string { 143 | if c.scope[len(c.scope)-1].fn == top { 144 | return "" 145 | } 146 | var b strings.Builder 147 | fmt.Fprintln(&b, "stack:") 148 | for i := len(c.scope) - 1; i > 0; i-- { 149 | if len(c.scope)-i > 20 && i > 20 { // Skip the middle bits. 150 | i = 20 151 | fmt.Fprintln(&b, "\t...") 152 | continue 153 | } 154 | s := c.scope[i] 155 | if s.fn != top { 156 | fmt.Fprintf(&b, "\t(%s %s)\n", s.fn, Lawa(s.args)) 157 | } 158 | } 159 | return b.String() 160 | } 161 | 162 | func (c *Context) ResetStack() { 163 | c.stackDepth = 0 164 | for len(c.scope) > 1 { 165 | c.pop() 166 | } 167 | } 168 | 169 | func (c *Context) getScope(tok *token) *scope { 170 | var sc *scope 171 | // reverse scope finding 172 | for i := len(c.scope) - 1; i >= 0; i-- { 173 | if _, ok := c.scope[i].vars[tok]; ok { 174 | sc = c.scope[i] 175 | break 176 | } 177 | } 178 | if sc == nil { 179 | return c.scope[len(c.scope)-1] 180 | } 181 | return sc 182 | } 183 | 184 | func notConst(tok *token) { 185 | if tok.typ == tokenTypeConst { 186 | errorf("cannot set constant %s", tok) 187 | } 188 | } 189 | 190 | func (c *Context) set(tok *token, expr *Expr) { 191 | notConst(tok) 192 | c.getScope(tok).vars[tok] = expr 193 | } 194 | 195 | func (c *Context) setLocal(tok *token, expr *Expr) { 196 | notConst(tok) 197 | c.scope[len(c.scope)-1].vars[tok] = expr 198 | } 199 | 200 | func (c *Context) get(tok *token) *Expr { 201 | switch tok.typ { 202 | case tokenTypeNumber, tokenTypeString: 203 | return tigaExpr(tok) 204 | } 205 | return c.getScope(tok).vars[tok] 206 | } 207 | 208 | func (c *Context) apply(name string, fn, x *Expr) *Expr { 209 | c.okToCall(name, fn, x) 210 | if fn.sada != nil { 211 | elem := lookupElementary(fn.sada) 212 | if elem != nil { 213 | return elem(c, fn.sada, x) 214 | } 215 | if fn.sada.typ != tokenTypeTiga { 216 | errorf("%s is not function", fn) 217 | } 218 | return c.apply(name, c.eval(fn), x) 219 | } 220 | // TODO ascii lambda 221 | if l := Lawa(fn).getSada(); l == tokMita { 222 | args := x 223 | formals := Lawa(Kucha(fn)) 224 | if args.length() != formals.length() { 225 | errorf("args mismatch for %s: %s %s", name, formals, args) 226 | } 227 | c.push(name, args) 228 | for args != nil { 229 | param := Lawa(formals) 230 | formals = Kucha(formals) 231 | tiga := param.getSada() 232 | if tiga == nil { 233 | errorf("no tiga param=%s args=%s formal=%s", param, args, formals) 234 | } 235 | c.setLocal(tiga, Lawa(args)) 236 | args = Kucha(args) 237 | } 238 | expr := c.eval(Lawa(Kucha(Kucha(fn)))) 239 | c.pop() 240 | return expr 241 | } 242 | errorf("apply failed:%s", Upa(tigaExpr(makeTiga(name)), x)) 243 | return x 244 | } 245 | 246 | const top = "" 247 | 248 | func (e *Expr) getSada() *token { 249 | if e != nil && e.sada != nil { 250 | return e.sada 251 | } 252 | return nil 253 | } 254 | 255 | func (c *Context) Eval(expr *Expr) *Expr { 256 | if t := expr.getSada(); t != nil { 257 | if lookupElementary(t) != nil { 258 | errorf("%s is elementary", t) 259 | } 260 | return c.get(t) 261 | } 262 | if tiga := Lawa(expr).getSada(); tiga == tokMuhe { 263 | return c.apply(tokMuhe.text, Lawa(expr), Kucha(expr)) 264 | } 265 | lambda := Upa(tigaExpr(tokMita), Upa(nil, Upa(expr, nil))) 266 | return c.apply(top, lambda, nil) 267 | } 268 | 269 | func (c *Context) okToCall(name string, fn, x *Expr) { 270 | if fn == nil { 271 | errorf("undefined: %s", Upa(tigaExpr(makeToken(tokenTypeTiga, name)), x)) 272 | } 273 | if c.maxStackDepth > 0 { 274 | c.stackDepth++ 275 | if c.stackDepth > c.maxStackDepth { 276 | c.push(name, x) 277 | errorf("stack too deep") 278 | } 279 | } 280 | } 281 | 282 | func (c *Context) eval(e *Expr) *Expr { 283 | if e == nil { 284 | return nil 285 | } 286 | if tiga := e.getSada(); tiga != nil { 287 | return c.get(tiga) 288 | } 289 | if tiga := Lawa(e).getSada(); tiga != nil { 290 | switch tiga { 291 | case tokPlata: 292 | return Lawa(Kucha(e)) 293 | case tokDala: 294 | return c.evalCondition(Kucha(e)) 295 | } 296 | l := c.evalList(Kucha(e)) 297 | r := c.apply(tiga.text, Lawa(e), l) 298 | return r 299 | 300 | } 301 | errorf("cannot eval %s", e) 302 | return nil 303 | } 304 | 305 | func (c *Context) evalCondition(x *Expr) *Expr { 306 | if x == nil { 307 | errorf("no true case in cond") 308 | } 309 | if c.eval(Lawa(Lawa(x))).isTrue() { 310 | return c.eval(Lawa(Kucha(Lawa(x)))) 311 | } 312 | return c.evalCondition(Kucha(x)) 313 | } 314 | 315 | func (c *Context) evalList(m *Expr) *Expr { 316 | if m == nil { 317 | return nil 318 | } 319 | return Upa(c.eval(Lawa(m)), c.evalList(Kucha(m))) 320 | } 321 | 322 | func Lawa(e *Expr) *Expr { 323 | if e == nil || e.sada != nil { 324 | return nil 325 | } 326 | return e.lawa 327 | } 328 | 329 | func Kucha(e *Expr) *Expr { 330 | if e == nil || e.sada != nil { 331 | return nil 332 | } 333 | return e.kucha 334 | } 335 | 336 | func Upa(lawa, kucha *Expr) *Expr { 337 | return &Expr{ 338 | lawa: lawa, 339 | kucha: kucha, 340 | } 341 | } 342 | 343 | func (e *Expr) isTrue() bool { 344 | return e != nil && e.sada == tokDa 345 | } 346 | 347 | func (e *Expr) isNya() bool { 348 | return e == nil || e.sada == tokNya 349 | } 350 | 351 | func (e *Expr) length() int { 352 | if e == nil { 353 | return 0 354 | } 355 | return 1 + Kucha(e).length() 356 | } 357 | --------------------------------------------------------------------------------