├── go.sum ├── .gitignore ├── go.mod ├── docs ├── example.png └── website.png ├── run ├── tests ├── test8.lpy ├── test4.lpy ├── test2.lpy ├── test5.lpy ├── test6.lpy ├── test3.lpy ├── test1.lpy ├── test7.lpy └── interpreter.lpy ├── Makefile ├── LICENSE ├── cmd └── lispy.go ├── pkg └── lispy │ ├── lexer.go │ ├── lib.go │ ├── parser.go │ ├── eval.go │ └── functions.go ├── README.md └── lib └── library.lpy /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /lispy -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amirgamil/lispy 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirgamil/lispy/HEAD/docs/example.png -------------------------------------------------------------------------------- /docs/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amirgamil/lispy/HEAD/docs/website.png -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # rebuild prog if necessary 3 | make build 4 | # run prog with some arguments 5 | ./lispy "$@" -------------------------------------------------------------------------------- /tests/test8.lpy: -------------------------------------------------------------------------------- 1 | (define greeting [name] 2 | (str "Hello " name "!") 3 | ) 4 | 5 | (define morning [] 6 | (let 7 | (name (readline "Enter your name: ")) 8 | (greeting name) 9 | ) 10 | ) 11 | 12 | (morning) 13 | -------------------------------------------------------------------------------- /tests/test4.lpy: -------------------------------------------------------------------------------- 1 | ; attempt to exhaust the stack 2 | (define max-stack 150000) 3 | 4 | 5 | (define sub [n] 6 | (if (= 0 n) 7 | (println "Done!") 8 | (do 9 | (sub (dec n)) 10 | ) 11 | ) 12 | ) 13 | 14 | (sub max-stack) -------------------------------------------------------------------------------- /tests/test2.lpy: -------------------------------------------------------------------------------- 1 | ; recursion 2 | (define fact [n] (if (= n 0) 1 (* n (fact (- n 1))))) 3 | (fact 5) ;120 4 | 5 | (define fib [n] (if (<= n 1) 6 | n 7 | (+ (fib (- n 1)) (fib (- n 2))) 8 | ) 9 | ) 10 | 11 | (fib 6) ;5 -------------------------------------------------------------------------------- /tests/test5.lpy: -------------------------------------------------------------------------------- 1 | ; functions 2 | 3 | ; reg function call 4 | (define square [x] (* x x)) 5 | (square 5) 6 | 7 | ; anon function call 8 | ((fn [x] (* x x)) 5) 9 | 10 | ; passing function as a parameter 11 | (define funcParam [x] (x 5)) 12 | (funcParam (fn [x] (* x x))) 13 | -------------------------------------------------------------------------------- /tests/test6.lpy: -------------------------------------------------------------------------------- 1 | ; list 2 | (neg 5) 3 | (neg -8) 4 | 5 | (range 1 10 1) 6 | (nth (seq 5) 2) 7 | (nth (1 2 3 4) 3) 8 | (reverse (seq 10)) 9 | (reduce (range 1 101 1) + 0) 10 | (max (range 1 30 1)) 11 | (min (range 1 30 1)) 12 | (map (seq 10) inc) 13 | (map (seq 10) square) 14 | (index (1 2 3 4) 6) 15 | (map (1 2 3) square) 16 | (filter (seq 10) even?) 17 | (filter (seq 10) odd?) 18 | (filter (seq 16) (fn [x] (= (% x 3) 0))) 19 | (join (1 2 3) (4 5 6)) -------------------------------------------------------------------------------- /tests/test3.lpy: -------------------------------------------------------------------------------- 1 | 2 | (define a 3 | (hash-map 4 | "1" 1 5 | "2" 2 6 | "3" 3 7 | ) 8 | ) 9 | 10 | (get a "2") 11 | (get a "4") 12 | (add a "4" 4) 13 | (keys a) 14 | (values a) 15 | 16 | 17 | ; complex keys 18 | (define co 19 | (hash-map (list 1 1) 2 20 | (list 1 2) 3 21 | (list 2 1) 3 22 | (list 2 2) 4) 23 | ) 24 | 25 | (get co (2 1)) 26 | (+ (get co (list 2 2)) 4) 27 | (keys co) 28 | (values co) 29 | -------------------------------------------------------------------------------- /tests/test1.lpy: -------------------------------------------------------------------------------- 1 | ;binary operators 2 | (+ 2 2) ;4 3 | (- 19 4) ;15 4 | (* 5 9) ;45 5 | (/ 3 2) ;1 6 | (/ 4.0 2) ;2.0 7 | (# 2 4) ;16 8 | 9 | 10 | ;relational operators 11 | (define a 9) ;9 12 | (> 10 a) ; true 13 | (< a 4) ; false 14 | (>= a 9) ;true 15 | (<= a 7) ;false 16 | 17 | 18 | ;logical operators 19 | (and true false) ;false 20 | (and true true true 5) ;true 21 | (or false false) ;false 22 | (or true false) ;true 23 | (not false) ;true 24 | 25 | ; custom operators 26 | (divisible? 10 5) 27 | (divisible? 11 3) -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CMD= ./cmd/lispy.go 2 | RUN = go run ${CMD} 3 | 4 | all: repl build 5 | 6 | #repl is default rule for now, when we add reading from file, will add that as first 7 | repl: 8 | ${RUN} -repl 9 | 10 | build: 11 | go build -o lispy ${CMD} 12 | 13 | 14 | test: 15 | go build -o lispy ${CMD} 16 | ./lispy tests/test1.lpy 17 | ./lispy tests/test2.lpy 18 | ./lispy tests/test3.lpy 19 | ./lispy tests/test4.lpy 20 | ./lispy tests/test5.lpy 21 | ./lispy tests/test6.lpy 22 | ./lispy tests/test7.lpy 23 | ./lispy tests/test8.lpy 24 | -------------------------------------------------------------------------------- /tests/test7.lpy: -------------------------------------------------------------------------------- 1 | (each (seq 18) 2 | (fn [x] 3 | (cond 4 | (and (divisible? x 3) (divisible? x 5)) "FizzBuzz" 5 | (divisible? x 3) "Fizz" 6 | (divisible? x 5) "Buzz" 7 | (true) x 8 | ) 9 | ) 10 | ) 11 | 12 | (let (a 5) 13 | (let (b 6) 14 | (let (c 7) 15 | (+ a b c) 16 | ) 17 | ) 18 | ) 19 | 20 | (quasiquote (1 3 2 (unquote (* 2 4)))) 21 | (cond (>= 2 3) 6) 22 | (cond 23 | (>= 1 2) 3 24 | (>= 8 2) 9 25 | ) 26 | 27 | (-> (seq 7) 28 | (filter even?) 29 | (reduce + 0) 30 | ) ; 12 31 | 32 | (->> 5 33 | dec 34 | (+ 2 4) 35 | (* 3 4) 36 | ) ; 120 37 | 38 | 39 | (switch 100 40 | (21 "hi") 41 | (59 "cheeky") 42 | (65 "test") 43 | ((switch 50 44 | (45 "uh oh") 45 | (50 100)) 200) 46 | ) ; 200 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Amir Bolous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/lispy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/amirgamil/lispy/pkg/lispy" 13 | ) 14 | 15 | var Reset = "\033[0m" 16 | var Green = "\033[32m" 17 | 18 | // read 19 | func read(reader io.Reader) []lispy.Sexp { 20 | tokens := lispy.Read(reader) 21 | exprs, err := lispy.Parse(tokens) 22 | if err != nil { 23 | log.Fatal("Error parsing") 24 | } 25 | return exprs 26 | } 27 | 28 | // eval 29 | func eval(ast []lispy.Sexp, env *lispy.Env) []string { 30 | return env.Eval(ast) 31 | } 32 | 33 | // print 34 | func print(res []string) { 35 | for _, result := range res { 36 | fmt.Println(result) 37 | } 38 | } 39 | 40 | // repl 41 | func repl(str io.Reader, env *lispy.Env) { 42 | print(eval(read(str), env)) 43 | } 44 | 45 | const cliVersion = "0.1.0" 46 | const helpMessage = ` 47 | Welcome to Lispy! Hack away 48 | ` 49 | 50 | func main() { 51 | 52 | flag.Usage = func() { 53 | fmt.Printf(helpMessage, cliVersion) 54 | flag.PrintDefaults() 55 | } 56 | 57 | isRepl := flag.Bool("repl", false, "Run as an interactive repl") 58 | flag.Parse() 59 | args := flag.Args() 60 | //default to repl if no files given 61 | if *isRepl || len(args) == 0 { 62 | // repl loop 63 | reader := bufio.NewReader(os.Stdin) 64 | env := lispy.InitState() 65 | for { 66 | fmt.Print(Green + "lispy> " + Reset) 67 | // reads user input until \n by default 68 | text, err := reader.ReadString('\n') 69 | if err != nil { 70 | log.Fatal("Error reading input from the console") 71 | } else if err == io.EOF { 72 | break 73 | } 74 | repl(strings.NewReader(text), env) 75 | 76 | } 77 | } else { 78 | filePath := args[0] 79 | file, err := os.Open(filePath) 80 | if err != nil { 81 | log.Fatal("Error opening file to read!") 82 | } 83 | defer file.Close() 84 | env := lispy.InitState() 85 | repl(file, env) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/interpreter.lpy: -------------------------------------------------------------------------------- 1 | ; metacircular lispy interpreter written in lispy with a repl 2 | 3 | (define eval-expr [node env] 4 | (cond 5 | (nil? node) () 6 | (int? node) node 7 | (float? node) node 8 | ; variable 9 | (symbol? node) (get env node) 10 | 11 | ; list forms 12 | (list? node) 13 | (switch (car node) 14 | ('quote node) 15 | ('if 16 | (if (eval-expr (cadr node) env) 17 | (eval-expr (car (cddr node)) env) 18 | (eval-expr (cadr (cddr node)) env)) 19 | ) 20 | ('define (add-env (cadr node) (eval-expr (car (cddr node)) env))) 21 | ('fn 22 | (do 23 | (define params (cadr node)) 24 | (define body (car (cddr node))) 25 | ; return a function which will be the operator when apply is called 26 | (fn [& args] 27 | (do 28 | ;set-new-env env parameters arguments 29 | (set-new-env env (cadr (car node)) (car args)) 30 | ;(println env) 31 | ; eval-expr body env 32 | (eval-expr (car (cddr (car node))) env) 33 | ) 34 | ) 35 | ) 36 | ) 37 | 38 | 39 | ; evaluate function call 40 | ((car node) 41 | (do 42 | (define operator (eval-expr (car node) env)) 43 | (define operands (cdr node)) 44 | (if (= (caar node) 'fn) 45 | (operator operands) 46 | (apply operator (apply-compound env operands)) 47 | ) 48 | ) 49 | ) 50 | ) 51 | ) 52 | ) 53 | 54 | (define apply-compound [env args] 55 | (if (nil? args) 56 | () 57 | (cons (eval-expr (car args) env) (apply-compound env (cdr args))) 58 | ) 59 | ) 60 | 61 | (define add-env [env key val] 62 | (do 63 | (swap env (add env key val)) 64 | val 65 | ) 66 | ) 67 | ;parameters are named variables passed to the functions, arguments are actual valus 68 | (define set-new-env [env params args] 69 | (if (nil? params) 70 | () 71 | (do 72 | (set-new-env env (cdr params) (cdr args)) 73 | (add-env env (car params) (car args)) 74 | ) 75 | ) 76 | ) 77 | 78 | (define env 79 | (hash-map 80 | '+ + 81 | '- - 82 | '/ / 83 | '* * 84 | '= = 85 | ) 86 | ) 87 | 88 | 89 | (define eval [source] 90 | (eval-expr source env) 91 | ) 92 | 93 | 94 | (define repl-loop [line] 95 | (do 96 | (println "lispy> ") 97 | ; readstring parses into an ast 98 | (define source (readstring (readline))) 99 | (println (eval source)) 100 | ;uncomment for debugging to see env (println new-env) 101 | (repl-loop source) 102 | ) 103 | ) 104 | (repl-loop "") 105 | ;(eval '(define a 5)) 106 | ;(eval '(+ a 10)) 107 | ;(eval '(* 1 2)) 108 | ;(eval '5) 109 | ;(eval '6.0) 110 | ;(eval '(if (false) 5 6)) 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /pkg/lispy/lexer.go: -------------------------------------------------------------------------------- 1 | package lispy 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "unicode" 8 | ) 9 | 10 | /*Token type definitions*/ 11 | type TokenType string 12 | 13 | const SYMBOL TokenType = "SYMBOL" 14 | 15 | //not used 16 | // const ATOM TokenType = "ATOM" 17 | // const EXP TokenType = "EXP" 18 | // const ENV TokenType = "ENV" 19 | const EOF TokenType = "EOF" 20 | 21 | //eventually refactor to hashmap? 22 | 23 | const LPAREN TokenType = "LPAREN" 24 | const RPAREN TokenType = "RPAREN" 25 | const LSQUARE TokenType = "LSQUARE" 26 | const RSQUARE TokenType = "RSQUARE" 27 | 28 | const INTEGER TokenType = "INTEGER" 29 | const FLOAT TokenType = "FLOAT" 30 | 31 | //Symbols 32 | const STRING TokenType = "STRING" 33 | const COMMENT TokenType = "COMMENT" 34 | 35 | const ID TokenType = "ID" 36 | const IF TokenType = "IF" 37 | const DEFINE TokenType = "DEFINE" 38 | const TRUE TokenType = "TRUE" 39 | const FALSE TokenType = "FALSE" 40 | const QUOTE TokenType = "QUOTE" 41 | const UNQUOTE TokenType = "UNQUOTE" 42 | const DO TokenType = "DO" 43 | const ARRAY TokenType = "ARRAY" 44 | const MACRO TokenType = "MACRO" 45 | 46 | type Token struct { 47 | Token TokenType 48 | Literal string 49 | } 50 | 51 | /********** 52 | Lexer 53 | ************/ 54 | type Lexer struct { 55 | Input string 56 | Position int 57 | ReadPosition int 58 | Char byte 59 | } 60 | 61 | func New(input string) *Lexer { 62 | return &Lexer{Input: input, Position: 0, ReadPosition: 0, Char: 0} 63 | } 64 | 65 | func (l *Lexer) advance() { 66 | if l.ReadPosition >= len(l.Input) { 67 | //Not sure about this bit 68 | l.Char = 0 69 | } else { 70 | l.Char = l.Input[l.ReadPosition] 71 | } 72 | l.Position = l.ReadPosition 73 | l.ReadPosition += 1 74 | } 75 | 76 | func (l *Lexer) peek() byte { 77 | if l.ReadPosition >= len(l.Input) { 78 | return 0 79 | } 80 | return l.Input[l.ReadPosition] 81 | } 82 | 83 | func (l *Lexer) skipWhiteSpace() { 84 | for unicode.IsSpace(rune(l.Char)) || l.Char == '\n' { 85 | l.advance() 86 | } 87 | 88 | } 89 | 90 | func (l *Lexer) getFloat(start int) Token { 91 | //advance to skip . 92 | l.advance() 93 | for unicode.IsDigit(rune(l.peek())) { 94 | l.advance() 95 | } 96 | return newToken(FLOAT, l.Input[start:l.ReadPosition]) 97 | } 98 | 99 | func (l *Lexer) getInteger() Token { 100 | old := l.Position 101 | if l.Char == '-' { 102 | //skip first char if minus 103 | l.advance() 104 | } 105 | //peek and not advance since advance is called at the end of scanToken, and this could cause us to jump and skip a step 106 | for unicode.IsDigit(rune(l.peek())) { 107 | l.advance() 108 | } 109 | if l.peek() == '.' { 110 | return l.getFloat(old) 111 | } 112 | return newToken(INTEGER, l.Input[old:l.ReadPosition]) 113 | } 114 | 115 | func (l *Lexer) getSymbol() Token { 116 | old := l.Position 117 | for !unicode.IsSpace(rune(l.peek())) && l.peek() != 0 && l.peek() != ')' && l.peek() != ']' && l.peek() != '(' { 118 | l.advance() 119 | } 120 | //use position because when l.Char is at a space, l.ReadPosition will be one ahead 121 | val := l.Input[old:l.ReadPosition] 122 | var token Token 123 | switch val { 124 | case "define": 125 | token = newToken(DEFINE, "define") 126 | case "if": 127 | token = newToken(IF, "if") 128 | case "true": 129 | token = newToken(TRUE, "true") 130 | case "false", "nil": 131 | token = newToken(FALSE, "false") 132 | case "do": 133 | token = newToken(DO, "do") 134 | case "macro": 135 | token = newToken(MACRO, "macro") 136 | //will add others later 137 | default: 138 | token = newToken(SYMBOL, val) 139 | } 140 | return token 141 | } 142 | 143 | func newToken(token TokenType, literal string) Token { 144 | return Token{Token: token, Literal: literal} 145 | } 146 | 147 | //function to get entire string or symbol token 148 | func (l *Lexer) getUntil(until byte, token TokenType, after bool) Token { 149 | old := l.Position 150 | //get until assumes we eat the last token, which is why we don't use peek 151 | for l.Char != until && l.Char != 0 { 152 | l.advance() 153 | } 154 | if after && l.Char != 0 { 155 | l.advance() 156 | } 157 | return newToken(token, l.Input[old:l.Position]) 158 | } 159 | 160 | func (l *Lexer) scanToken() Token { 161 | //skips white space and new lines 162 | l.skipWhiteSpace() 163 | var token Token 164 | switch l.Char { 165 | case '(': 166 | token = newToken(LPAREN, "(") 167 | case ')': 168 | token = newToken(RPAREN, ")") 169 | case '[': 170 | token = newToken(LSQUARE, "[") 171 | case ']': 172 | token = newToken(RSQUARE, "]") 173 | case '\'': 174 | token = newToken(QUOTE, "'") 175 | case '-': 176 | if unicode.IsDigit(rune(l.peek())) { 177 | token = l.getInteger() 178 | } else { 179 | token = l.getSymbol() 180 | } 181 | 182 | case ';': 183 | if l.peek() == ';' { 184 | //current char is ; and next char is ; so advance twice 185 | l.advance() 186 | l.advance() 187 | token = l.getUntil(';', COMMENT, false) 188 | } else { 189 | token = l.getUntil('\n', COMMENT, false) 190 | } 191 | 192 | case '"': 193 | //skip the first " 194 | l.advance() 195 | token = l.getUntil('"', STRING, false) 196 | case 0: 197 | token = newToken(EOF, "EOF") 198 | default: 199 | if unicode.IsDigit(rune(l.Char)) { 200 | token = l.getInteger() 201 | } else { 202 | //more things potentially here 203 | token = l.getSymbol() 204 | } 205 | } 206 | l.advance() 207 | return token 208 | } 209 | 210 | func (l *Lexer) tokenize(source string) []Token { 211 | var tokens []Token 212 | //set the first character 213 | l.advance() 214 | for l.Position < len(l.Input) { 215 | next := l.scanToken() 216 | if next.Token != COMMENT { 217 | tokens = append(tokens, next) 218 | } 219 | } 220 | return tokens 221 | } 222 | 223 | //Takes as input the source code as a string and returns a list of tokens 224 | func Read(reader io.Reader) []Token { 225 | source := loadReader(reader) 226 | l := New(source) 227 | tokens := l.tokenize(source) 228 | return tokens 229 | } 230 | 231 | func loadReader(reader io.Reader) string { 232 | //todo: ReadAll puts everything in memory, very inefficient for large files 233 | //files will remain small for lispy but potentially adapt to buffered approach (reads in buffers) 234 | ltxtb, err := ioutil.ReadAll(reader) 235 | if err != nil { 236 | log.Fatal("Error trying to read source file: ", err) 237 | } 238 | return string(ltxtb) 239 | } 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lispy ✏️ 2 | ## Intro 3 | 4 | Lispy is a programming language that is inspired by Scheme and Clojure. It's a simple Lisp-dialect I built to better understand Lisp and, more generally, functional programming. 5 | 6 | For a journal documenting my process from not knowing what Lisp was, to building this language, refer to this [blog post](https://amirbolous.com/posts/pl) I wrote. 7 | 8 | Here's a taste for what it can do 9 | 10 | ![example](docs/example.png) 11 | 12 | You can tour the language and run it in the browser [here](http://lispy.amirbolous.com/) 13 | 14 | ![tour](docs/website.png) 15 | 16 | You can find the source code for this sandbox [here](https://github.com/amirgamil/lispysandbox). 17 | 18 | ## What Lispy supports 19 | - [x] Basic arithmetic operations (`+`, `-`, `*`, `/`, `%`, `#`) 20 | - `(# a b)` means raise a to the power of b 21 | - [x] Relational operators (`>`, `<`, `>=`, `<=`, `=`) and logical operators (`and`, `or`, `not`å) 22 | - [x] Bindings to variables and state with `define`, and `let` for local binding or lexical scope 23 | - [x] Reading input from the user via `readline` and string concatenation via `str` 24 | - [x] Conditionals via `if`, `when`, and `cond` 25 | - [x] Lambdas or anonymous functions via `fn,` functions via `define` 26 | - [x] Reading Lispy code from a file 27 | - [x] Macros (`quasiquote`, threading via `->`. `->>`, and a host of other ones) 28 | - [x] Tail call optimization 29 | - [x] Lists with a core library that supports functional operations like `map`, `reduce`, `range` and several more 30 | - [x] Hash maps 31 | - [x] A meta-circular interpreter to run a (more barebones) version of itself at `tests/interpreter.lpy` 32 | 33 | 34 | ## High Level Overview 35 | Lispy is written as a tree-walk interpreter in Go with a recursive-descent parser. It also has a separate lexer, although most Lisp dialects are simple enough to parse that the lexing and parsing can be combined into one stage. 36 | 37 | Because Lispy is interpreted, not compiled, it does not have a separate macro-expansion stage (that would typically be done before code is evaluated). Instead, Lispy handles macros as special functions, which it evaluates twice: once to generate the syntax of the code, and the second to run this generated syntax (as a macro would). 38 | 39 | The interpreter code can be found at `pkg/lispy/`, the integration tests can be found at `tests/` and the main Lispy library at `lib/lispy.lpy`. Here's a short sample of lispy in action: 40 | 41 | ``` 42 | (each (seq 18) 43 | (fn [x] 44 | (cond 45 | (and (divisible? x 3) (divisible? x 5)) "FizzBuzz!" 46 | (divisible? x 3) "Fizz" 47 | (divisible? x 5) "Buzz" 48 | (true) x 49 | ) 50 | ) 51 | ) 52 | ``` 53 | 54 | 55 | ### Under The Hood 56 | Under the hood, Lispy implements a high-level S-expression interface with specific structures to reprsent lists, arrays, symbols, integers, and floats. Lists in Lispy are implemented as linked lists of cons cells, from which we derive the axioms of `car`, `cdr`, and `cons`. Everything else is built on top of these building blocks. Lispy also implements a single environment for variables and functions - it does not keep separate namespaces for them. The environment is the core backbone of the interpreter which allows us to bind values to variables and functions. Lispy uses Go's recursive calls as its native stack and does not implement a separate stack frame. Each function call gets its own environment with a pointer to the parent environment. Most Lispy programs are not going to be incredibly long, thus for the sake of significant speed gains, Lispy copies over all of the data from the parent environment into the current environment. Although this is less memory-efficient and probably would not be used for a production-ready language, it made the interpreter at least 10x faster (instead of having to recurse up to parent environments to resolve a function/variable declaration) when I tested it. 57 | 58 | ### Lispy Library 59 | Lispy implements a core library (under `lib/lispy.lpy`) that builds on top of the core functionality to offer a rich variety of features. 60 | 61 | ### Tail call optimization 62 | Lispy also implements tail call optimization. Since Lispy uses Go's call stack and does not implement its own, it performs tail call elimination or optimization similar to [Ink](https://dotink.co/posts/tce/). It does this by expanding a set of recursive function calls into a flat for loop structure that allows us to reuse the same call stack and (theoretically) recurse infinitely without causing a stack overflow. 63 | 64 | ### Running Lispy 65 | To run Lispy, you have a couple of options. 66 | 1. The easiest way is to run it directly in the browser with a [sandbox](http://lispy.amirbolous.com/) I built. 67 | 2. If you want to experiment with it more freely on your local device, you can launch a repl by running `make` in the outer directory 68 | 3. If you want to run a specific file, you can run `./run `. 69 | - For context, run is an executable with a small 70 | script to run a passed in file. Note don't include the `<>` when passing a path (I included it for clarity). 71 | - You can also add the Lispy executable to your $PATH which will allow you to run `lispy ` in the terminal. If you're on Linux, you can do this with 72 | ``` 73 | $ make build 74 | $ sudo ln -s usr/local/bin 75 | ``` 76 | For context, this creates a symlink (which is just a shortcut or path to a different file) which makes the `./lispy` executable available in your path so you can just use `lispy` instead `./lispy` 77 | 78 | ### To Improve 79 | 1. Lispy doesn't handle errors very gracefully, especially in the code sandbox. It's also less strict about code that is incorrect in some way or another, meaning it may still run code that should probably raise an error. 80 | 2. Lispy could probably be a little bit faster with a couple more optimizations, but it's already surprisingly fast. As proof, try running `tests/test4.lpy` :) I think the speed is more indicative of how far modern computers have come than brilliant language design by me. 81 | 82 | ### Helpful Resources 83 | There were many resources that proved to be invaluable over the course of this project. Here's a short snippet of them: 84 | 1. [Glisp](https://github.com/zhemao/glisp/) 85 | 2. [Clojure](https://clojure.org/) 86 | 3. [Scheme](https://www.scheme.com/tspl4/) 87 | 4. [Klisp](https://github.com/thesephist/klisp/tree/main) 88 | 5. [Structure and Interpretation of Computer Programs](https://web.mit.edu/alexmv/6.037/sicp.pdf) 89 | 6. [Mal](https://github.com/kanaka/mal) 90 | 7. [Lisp in Python](http://norvig.com/lispy.html) 91 | 8. [On Lisp](https://sep.yimg.com/ty/cdn/paulgraham/onlisp.pdf?t=1595850613&) 92 | 9. [Crafting Interpreters](http://craftinginterpreters.com/) 93 | -------------------------------------------------------------------------------- /lib/library.lpy: -------------------------------------------------------------------------------- 1 | (define caar [x] (car (car x))) 2 | (define cadr [x] (car (cdr x))) 3 | (define cdar [x] (cdr (car x))) 4 | (define cddr [x] (cdr (cdr x))) 5 | 6 | 7 | ; basic expressions 8 | (define sqrt [x] (# x 0.5)) 9 | (define square [x] (* x x)) 10 | (define inc [x] (+ x 1)) 11 | (define dec [x] (- x 1)) 12 | (define abs [x] 13 | (if (>= x 0) x (* x -1)) 14 | ) 15 | (define neg [x] (- 0 x)) 16 | (define ! [x] (if x false true)) 17 | (define neg? [x] (< x 0)) 18 | (define pos? [x] (> x 0)) 19 | (define zero? [x] (= x 0)) 20 | (define divisible? [a b] (= (% a b) 0)) 21 | (define even? [x] (zero? (% x 2))) 22 | (define odd? [x] (! (even? x))) 23 | (define nil? [x] (= x ())) 24 | (define list? [x] (= (type x) "list")) 25 | (define int? [x] (= (type x) "int")) 26 | (define float? [x] (= (type x) "float")) 27 | (define symbol? [x] (= (type x) "symbol")) 28 | 29 | ; list methods 30 | (define range [start stop step] 31 | (if (< start stop) 32 | (cons start (range (+ start step) stop step)) 33 | () 34 | ) 35 | ) 36 | 37 | 38 | (define reduce [arr func current] 39 | (if (nil? arr) 40 | current 41 | (reduce (cdr arr) func (func current (car arr))) 42 | ) 43 | ) 44 | 45 | 46 | (define max [arr] 47 | (if (nil? arr) 48 | 0 49 | (reduce arr (fn [a b] (if (< a b) b a)) (car arr)) 50 | ) 51 | ) 52 | 53 | 54 | (define min [arr] 55 | (if (nil? arr) 56 | 0 57 | (reduce arr (fn [a b] (if (> a b) b a)) (car arr)) 58 | ) 59 | ) 60 | 61 | (define sum [arr] 62 | (if (nil? arr) 63 | 0 64 | (reduce arr + 0) 65 | ) 66 | ) 67 | 68 | ; defines a list from 0...x-1 69 | (define seq [x] (range 0 x 1)) 70 | 71 | 72 | (define map [arr func] 73 | (if (nil? arr) 74 | () 75 | (cons (func (car arr)) (map (cdr arr) func)) 76 | ) 77 | ) 78 | 79 | (define filter [arr func] 80 | (if (nil? arr) 81 | () 82 | (if (func (car arr)) 83 | (cons (car arr) (filter (cdr arr) func)) 84 | (filter (cdr arr) func) 85 | ) 86 | ) 87 | ) 88 | 89 | ; O(n) operation, loop through entire list and add to end 90 | (define append [arr el] 91 | (if (nil? arr) 92 | (list el) 93 | (cons (car arr) (append (cdr arr) el)) 94 | ) 95 | ) 96 | 97 | ; O(n^2) since each append is O(n) 98 | (define reverse [arr] 99 | (if (nil? arr) 100 | () 101 | (append (reverse (cdr arr)) (car arr)) 102 | ) 103 | ) 104 | 105 | 106 | (define each [arr func] 107 | (if (nil? arr) 108 | () 109 | ( 110 | do 111 | (println (func (car arr))) 112 | (each (cdr arr) func) 113 | ) 114 | ) 115 | ) 116 | 117 | ; generate unique, not previously defined symbol 118 | (define gensym [] 119 | (symbol (str "var" (* 10000 (rand)))) 120 | ) 121 | 122 | ; get nth item in list (0-indexed) 123 | (define nth [arr n] 124 | (if (= n 0) 125 | (car arr) 126 | (nth (cdr arr) (dec n)) 127 | ) 128 | ) 129 | 130 | ; get size of list 131 | (define size [arr] 132 | (do 133 | (define iterSize [n arr] 134 | (if (nil? arr) 135 | n 136 | (iterSize (inc n) (cdr arr)) 137 | ) 138 | ) 139 | (iterSize 0 arr) 140 | ) 141 | ) 142 | 143 | ; get index of item in list 144 | (define index [arr item] 145 | (do 146 | (define getIndex [index arr item] 147 | (if (nil? arr) 148 | -1 149 | (if (= (car arr) item) 150 | index 151 | (getIndex (inc index) (cdr arr) item) 152 | ) 153 | ) 154 | ) 155 | (getIndex 0 arr item) 156 | ) 157 | ) 158 | 159 | 160 | ; get last item in list 161 | (define last [arr] 162 | (if (nil? (cdr arr)) 163 | (car arr) 164 | (last (cdr arr)) 165 | ) 166 | ) 167 | 168 | ; appends arr2 to the end of arr1 169 | (define join [arr1 arr2] 170 | (if (nil? arr2) 171 | arr1 172 | (join (append arr1 (car arr2)) (cdr arr2)) 173 | ) 174 | ) 175 | 176 | ; adds element to the front of the array 177 | (define addToFront [el arr] 178 | (do 179 | (define helper [arr] 180 | (if (nil? arr) 181 | () 182 | (cons (car arr) (helper (cdr arr))) 183 | ) 184 | ) 185 | (helper (cons el arr)) 186 | ) 187 | ) 188 | 189 | 190 | ; macros 191 | 192 | 193 | ; (when (precondition) (postcondition)) 194 | (macro when [terms] 195 | (list 'if (car terms) (cadr terms)) 196 | ) 197 | 198 | ; local bindings within lexical scope 199 | (macro let [terms] 200 | (do 201 | (define declname (caar terms)) 202 | (define declval (cdar terms)) 203 | (define body (cdr terms)) 204 | (list 205 | (list 'fn [declname] body) 206 | declval 207 | ) 208 | 209 | ) 210 | ) 211 | ; ex: (quasiquote (1 2 (unquote (+ 3 4)))) => (1 2 7) 212 | ; note, by design, don't include ' before it 213 | (macro quasiquote [terms] 214 | ; note we do cons 'list so that map is called when evaluating the macro-expansion, not on the first call 215 | (cons 'list 216 | (map (car terms) 217 | (fn [term] 218 | (if (list? term) 219 | (if (= (car term) 'unquote) 220 | (cadr term) 221 | (list 'quasiquote term) 222 | ) 223 | (list 'quote term) 224 | ) 225 | ) 226 | ) 227 | ) 228 | ) 229 | 230 | ; special form of a funcCall where the last argument is a list that should be treated as parameters 231 | ; e.g. (apply fn 1 2 (3 4)) 232 | (define apply [& terms] 233 | (do 234 | (define funcCall (car terms)) 235 | (define helper [args] 236 | (if (nil? args) 237 | () 238 | (if (list? (car args)) 239 | (cons (caar args) (helper (cdar args))) 240 | (cons (car args) (helper (cdr args))) 241 | ) 242 | ) 243 | ) 244 | 245 | (applyTo funcCall (helper (cdr terms))) 246 | ) 247 | ) 248 | 249 | 250 | ; (cond (precondition) (postcondition) (precondition2) (postcondition2)...) 251 | (macro cond [terms] 252 | (if (nil? terms) 253 | () 254 | (list 'if (car terms) (cadr terms) (cons 'cond (cddr terms))) 255 | ) 256 | ) 257 | 258 | 259 | ;(switch val (case1 result1) (case2 result2)) 260 | (macro switch [statements] 261 | (do 262 | (define val (gensym)) 263 | (define match [conditions] 264 | (if (nil? conditions) 265 | (list) 266 | (list 'if (list '= val (caar conditions)) (cdar conditions) (match (cdr conditions))) 267 | ) 268 | ) 269 | (let (val (car statements)) 270 | (match (cdr statements)) 271 | ) 272 | ) 273 | ) 274 | 275 | ; thread-first 276 | ; inserts first form as the first argument (second in list) of the second form, and so forth 277 | (macro -> [terms] 278 | (do 279 | (define apply-partials [partials expr] 280 | (if (nil? partials) 281 | expr 282 | (if (symbol? (car partials)) 283 | (list (car partials) (apply-partials (cdr partials) expr)) 284 | ; if it's a list with other parameters, insert expr (recursive call) 285 | ; as second parameter into partial (note need to use cons to ensure same list for func args) 286 | (cons (caar partials) (cons (apply-partials (cdr partials) expr) (cdar partials))) 287 | ) 288 | ) 289 | ) 290 | (apply-partials (reverse (cdr terms)) (car terms)) 291 | ) 292 | ) 293 | 294 | ; thread-last 295 | ; same as -> but inserts first form as last argument (last in list) of second form, and so forth 296 | (macro ->> [terms] 297 | (do 298 | (define apply-partials [partials expr] 299 | (if (nil? partials) 300 | expr 301 | (if (symbol? (car partials)) 302 | (list (car partials) (apply-partials (cdr partials) expr)) 303 | ; if it's a list with other parameters, insert expr (recursive call) 304 | ; as last form 305 | (cons (caar partials) (append (cdar partials) (apply-partials (cdr partials) expr))) 306 | ) 307 | ) 308 | ) 309 | (apply-partials (reverse (cdr terms)) (car terms)) 310 | ) 311 | ) 312 | 313 | 314 | 315 | ; immutable key-value hashmap 316 | ; O(n) lookup with O(1) insert 317 | ; ex: (comp "key1" "val1" "key2" "val2") 318 | (macro hash-map [terms] 319 | (if (nil? terms) 320 | () 321 | (list 'cons (list 'cons (car terms) (cadr terms)) (cons 'hash-map (cddr terms))) 322 | ) 323 | ) 324 | 325 | ; O(n) recursive lookup 326 | (define get [hm key] 327 | (if (nil? hm) 328 | () 329 | (if (= key (caar hm)) 330 | (car (cdar hm)) 331 | (get (cdr hm) key) 332 | ) 333 | ) 334 | ) 335 | 336 | 337 | ; hash-maps are immutable, add returns a new hash-map with the new key, val pair 338 | ; it does not modify existing ones 339 | (define add [hm key val] 340 | (if (nil? (get hm key)) 341 | (cons (cons key val) hm) 342 | ) 343 | ) 344 | 345 | ; remove returns a hash-map with the key-value pair provided removed (if it exists in the hash-map) 346 | (define remove [hm key] 347 | (do 348 | (define val (get hm key)) 349 | (define helper [hm] 350 | (if (nil? hm) 351 | () 352 | (if (= (car (cdar hm)) val) 353 | (helper (cdr hm)) 354 | (cons (car hm) (helper (cdr hm))) 355 | ) 356 | ) 357 | ) 358 | (helper hm) 359 | ) 360 | ) 361 | 362 | ; return list of keys in the hash-map 363 | (define keys [hm] 364 | (if (nil? hm) 365 | () 366 | (cons (caar hm) (keys (cdr hm))) 367 | ) 368 | ) 369 | 370 | ; return list of values in the hash-map 371 | (define values [hm] 372 | (if (nil? hm) 373 | () 374 | (cons (car (cdar hm)) (values (cdr hm))) 375 | ) 376 | ) 377 | 378 | 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /pkg/lispy/lib.go: -------------------------------------------------------------------------------- 1 | package lispy 2 | 3 | //necessary evil to include non-go file in a package to expose the API other apps can use to run lispy code 4 | const lib = ` 5 | (define caar [x] (car (car x))) 6 | (define cadr [x] (car (cdr x))) 7 | (define cdar [x] (cdr (car x))) 8 | (define cddr [x] (cdr (cdr x))) 9 | 10 | 11 | ; basic expressions 12 | (define sqrt [x] (# x 0.5)) 13 | (define square [x] (* x x)) 14 | (define inc [x] (+ x 1)) 15 | (define dec [x] (- x 1)) 16 | (define abs [x] 17 | (if (>= x 0) x (* x -1)) 18 | ) 19 | (define neg [x] (- 0 x)) 20 | (define ! [x] (if x false true)) 21 | (define neg? [x] (< x 0)) 22 | (define pos? [x] (> x 0)) 23 | (define zero? [x] (= x 0)) 24 | (define divisible? [a b] (= (% a b) 0)) 25 | (define even? [x] (zero? (% x 2))) 26 | (define odd? [x] (! (even? x))) 27 | (define nil? [x] (= x ())) 28 | (define list? [x] (= (type x) "list")) 29 | (define int? [x] (= (type x) "int")) 30 | (define float? [x] (= (type x) "float")) 31 | (define symbol? [x] (= (type x) "symbol")) 32 | 33 | ; list methods 34 | (define range [start stop step] 35 | (if (< start stop) 36 | (cons start (range (+ start step) stop step)) 37 | () 38 | ) 39 | ) 40 | 41 | 42 | (define reduce [arr func current] 43 | (if (nil? arr) 44 | current 45 | (reduce (cdr arr) func (func current (car arr))) 46 | ) 47 | ) 48 | 49 | 50 | (define max [arr] 51 | (if (nil? arr) 52 | 0 53 | (reduce arr (fn [a b] (if (< a b) b a)) (car arr)) 54 | ) 55 | ) 56 | 57 | 58 | (define min [arr] 59 | (if (nil? arr) 60 | 0 61 | (reduce arr (fn [a b] (if (> a b) b a)) (car arr)) 62 | ) 63 | ) 64 | 65 | (define sum [arr] 66 | (if (nil? arr) 67 | 0 68 | (reduce arr + 0) 69 | ) 70 | ) 71 | 72 | ; defines a list from 0...x-1 73 | (define seq [x] (range 0 x 1)) 74 | 75 | 76 | (define map [arr func] 77 | (if (nil? arr) 78 | () 79 | (cons (func (car arr)) (map (cdr arr) func)) 80 | ) 81 | ) 82 | 83 | (define filter [arr func] 84 | (if (nil? arr) 85 | () 86 | (if (func (car arr)) 87 | (cons (car arr) (filter (cdr arr) func)) 88 | (filter (cdr arr) func) 89 | ) 90 | ) 91 | ) 92 | 93 | ; O(n) operation, loop through entire list and add to end 94 | (define append [arr el] 95 | (if (nil? arr) 96 | (list el) 97 | (cons (car arr) (append (cdr arr) el)) 98 | ) 99 | ) 100 | 101 | ; O(n^2) since each append is O(n) 102 | (define reverse [arr] 103 | (if (nil? arr) 104 | () 105 | (append (reverse (cdr arr)) (car arr)) 106 | ) 107 | ) 108 | 109 | 110 | (define each [arr func] 111 | (if (nil? arr) 112 | () 113 | ( 114 | do 115 | (println (func (car arr))) 116 | (each (cdr arr) func) 117 | ) 118 | ) 119 | ) 120 | 121 | ; generate unique, not previously defined symbol 122 | (define gensym [] 123 | (symbol (str "var" (* 10000 (rand)))) 124 | ) 125 | 126 | ; get nth item in list (0-indexed) 127 | (define nth [arr n] 128 | (if (= n 0) 129 | (car arr) 130 | (nth (cdr arr) (dec n)) 131 | ) 132 | ) 133 | 134 | ; get size of list 135 | (define size [arr] 136 | (do 137 | (define iterSize [n arr] 138 | (if (nil? arr) 139 | n 140 | (iterSize (inc n) (cdr arr)) 141 | ) 142 | ) 143 | (iterSize 0 arr) 144 | ) 145 | ) 146 | 147 | ; get index of item in list 148 | (define index [arr item] 149 | (do 150 | (define getIndex [index arr item] 151 | (if (nil? arr) 152 | -1 153 | (if (= (car arr) item) 154 | index 155 | (getIndex (inc index) (cdr arr) item) 156 | ) 157 | ) 158 | ) 159 | (getIndex 0 arr item) 160 | ) 161 | ) 162 | 163 | 164 | ; get last item in list 165 | (define last [arr] 166 | (if (nil? (cdr arr)) 167 | (car arr) 168 | (last (cdr arr)) 169 | ) 170 | ) 171 | 172 | ; appends arr2 to the end of arr1 173 | (define join [arr1 arr2] 174 | (if (nil? arr2) 175 | arr1 176 | (join (append arr1 (car arr2)) (cdr arr2)) 177 | ) 178 | ) 179 | 180 | ; adds element to the front of the array 181 | (define addToFront [el arr] 182 | (do 183 | (define helper [arr] 184 | (if (nil? arr) 185 | () 186 | (cons (car arr) (helper (cdr arr))) 187 | ) 188 | ) 189 | (helper (cons el arr)) 190 | ) 191 | ) 192 | 193 | 194 | ; macros 195 | 196 | 197 | ; (when (precondition) (postcondition)) 198 | (macro when [terms] 199 | (list 'if (car terms) (cadr terms)) 200 | ) 201 | 202 | ; local bindings within lexical scope 203 | (macro let [terms] 204 | (do 205 | (define declname (caar terms)) 206 | (define declval (cdar terms)) 207 | (define body (cdr terms)) 208 | (list 209 | (list 'fn [declname] body) 210 | declval 211 | ) 212 | 213 | ) 214 | ) 215 | ; ex: (quasiquote (1 2 (unquote (+ 3 4)))) => (1 2 7) 216 | ; note, by design, don't include ' before it 217 | (macro quasiquote [terms] 218 | ; note we do cons 'list so that map is called when evaluating the macro-expansion, not on the first call 219 | (cons 'list 220 | (map (car terms) 221 | (fn [term] 222 | (if (list? term) 223 | (if (= (car term) 'unquote) 224 | (cadr term) 225 | (list 'quasiquote term) 226 | ) 227 | (list 'quote term) 228 | ) 229 | ) 230 | ) 231 | ) 232 | ) 233 | 234 | ; special form of a funcCall where the last argument is a list that should be treated as parameters 235 | ; e.g. (apply fn 1 2 (3 4)) 236 | (define apply [& terms] 237 | (do 238 | (define funcCall (car terms)) 239 | (define helper [args] 240 | (if (nil? args) 241 | () 242 | (if (list? (car args)) 243 | (cons (caar args) (helper (cdar args))) 244 | (cons (car args) (helper (cdr args))) 245 | ) 246 | ) 247 | ) 248 | 249 | (applyTo funcCall (helper (cdr terms))) 250 | ) 251 | ) 252 | 253 | 254 | ; (cond (precondition) (postcondition) (precondition2) (postcondition2)...) 255 | (macro cond [terms] 256 | (if (nil? terms) 257 | () 258 | (list 'if (car terms) (cadr terms) (cons 'cond (cddr terms))) 259 | ) 260 | ) 261 | 262 | 263 | ;(switch val (case1 result1) (case2 result2)) 264 | (macro switch [statements] 265 | (do 266 | (define val (gensym)) 267 | (define match [conditions] 268 | (if (nil? conditions) 269 | (list) 270 | (list 'if (list '= val (caar conditions)) (cdar conditions) (match (cdr conditions))) 271 | ) 272 | ) 273 | (let (val (car statements)) 274 | (match (cdr statements)) 275 | ) 276 | ) 277 | ) 278 | 279 | ; thread-first 280 | ; inserts first form as the first argument (second in list) of the second form, and so forth 281 | (macro -> [terms] 282 | (do 283 | (define apply-partials [partials expr] 284 | (if (nil? partials) 285 | expr 286 | (if (symbol? (car partials)) 287 | (list (car partials) (apply-partials (cdr partials) expr)) 288 | ; if it's a list with other parameters, insert expr (recursive call) 289 | ; as second parameter into partial (note need to use cons to ensure same list for func args) 290 | (cons (caar partials) (cons (apply-partials (cdr partials) expr) (cdar partials))) 291 | ) 292 | ) 293 | ) 294 | (apply-partials (reverse (cdr terms)) (car terms)) 295 | ) 296 | ) 297 | 298 | ; thread-last 299 | ; same as -> but inserts first form as last argument (last in list) of second form, and so forth 300 | (macro ->> [terms] 301 | (do 302 | (define apply-partials [partials expr] 303 | (if (nil? partials) 304 | expr 305 | (if (symbol? (car partials)) 306 | (list (car partials) (apply-partials (cdr partials) expr)) 307 | ; if it's a list with other parameters, insert expr (recursive call) 308 | ; as last form 309 | (cons (caar partials) (append (cdar partials) (apply-partials (cdr partials) expr))) 310 | ) 311 | ) 312 | ) 313 | (apply-partials (reverse (cdr terms)) (car terms)) 314 | ) 315 | ) 316 | 317 | 318 | 319 | ; immutable key-value hashmap 320 | ; O(n) lookup with O(1) insert 321 | ; ex: (comp "key1" "val1" "key2" "val2") 322 | (macro hash-map [terms] 323 | (if (nil? terms) 324 | () 325 | (list 'cons (list 'cons (car terms) (cadr terms)) (cons 'hash-map (cddr terms))) 326 | ) 327 | ) 328 | 329 | ; O(n) recursive lookup 330 | (define get [hm key] 331 | (if (nil? hm) 332 | () 333 | (if (= key (caar hm)) 334 | (car (cdar hm)) 335 | (get (cdr hm) key) 336 | ) 337 | ) 338 | ) 339 | 340 | 341 | ; hash-maps are immutable, add returns a new hash-map with the new key, val pair 342 | ; it does not modify existing ones 343 | (define add [hm key val] 344 | (if (nil? (get hm key)) 345 | (cons (cons key val) hm) 346 | ) 347 | ) 348 | 349 | ; remove returns a hash-map with the key-value pair provided removed (if it exists in the hash-map) 350 | (define remove [hm key] 351 | (do 352 | (define val (get hm key)) 353 | (define helper [hm] 354 | (if (nil? hm) 355 | () 356 | (if (= (car (cdar hm)) val) 357 | (helper (cdr hm)) 358 | (cons (car hm) (helper (cdr hm))) 359 | ) 360 | ) 361 | ) 362 | (helper hm) 363 | ) 364 | ) 365 | 366 | ; return list of keys in the hash-map 367 | (define keys [hm] 368 | (if (nil? hm) 369 | () 370 | (cons (caar hm) (keys (cdr hm))) 371 | ) 372 | ) 373 | 374 | ; return list of values in the hash-map 375 | (define values [hm] 376 | (if (nil? hm) 377 | () 378 | (cons (car (cdar hm)) (values (cdr hm))) 379 | ) 380 | ) 381 | 382 | ` 383 | -------------------------------------------------------------------------------- /pkg/lispy/parser.go: -------------------------------------------------------------------------------- 1 | package lispy 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // const ( 12 | // Num = iota 13 | // List 14 | // Identifier 15 | // String 16 | // ) 17 | 18 | //Generic interface for an Sexp (any node in our AST must implement this interface) 19 | type Sexp interface { 20 | String() string 21 | Eval(*Env, *StackFrame, bool) Sexp 22 | } 23 | 24 | //Symbol 25 | //have dedicated types for strings and bools or this suffices? 26 | type SexpSymbol struct { 27 | ofType TokenType 28 | value string 29 | } 30 | 31 | func (s SexpSymbol) String() string { 32 | return s.value 33 | } 34 | 35 | // SexpInt 36 | type SexpInt int 37 | 38 | func (n SexpInt) String() string { 39 | return strconv.Itoa(int(n)) 40 | } 41 | 42 | //SexpFloat 43 | type SexpFloat float64 44 | 45 | func (n SexpFloat) String() string { 46 | return fmt.Sprintf("%f", n) 47 | } 48 | 49 | //SexpPair is an implementation of a cons cell with a head (car) and tail (cdr) 50 | //Lists in lispy are defined as linked lists of cons cells 51 | type SexpPair struct { 52 | head Sexp 53 | tail Sexp 54 | } 55 | 56 | func (l SexpPair) String() string { 57 | str := "(" 58 | if l.head == nil { 59 | return "()" 60 | } 61 | 62 | pair := l 63 | for { 64 | switch pair.tail.(type) { 65 | case SexpPair: 66 | if pair.head != nil { 67 | str += pair.head.String() + " " 68 | } 69 | pair = pair.tail.(SexpPair) 70 | continue 71 | } 72 | break 73 | } 74 | 75 | if pair.head != nil { 76 | str += pair.head.String() 77 | } else { 78 | //remove extra white space at at end 79 | str = str[:len(str)-1] 80 | } 81 | if pair.tail == nil { 82 | str += ")" 83 | } else { 84 | str += " " + pair.tail.String() + ")" 85 | } 86 | return str 87 | } 88 | 89 | type SexpArray struct { 90 | ofType TokenType 91 | value []Sexp 92 | } 93 | 94 | func (s SexpArray) String() string { 95 | args := make([]string, 0) 96 | for _, node := range s.value { 97 | args = append(args, node.String()) 98 | } 99 | return "[" + strings.Join(args, " ") + "]" 100 | } 101 | 102 | //SexpFunction Literal to store the functions when parsing them 103 | type SexpFunctionLiteral struct { 104 | name string 105 | //when we store the arguments, will call arg.String() for each arg - may need to be fixed for some edge cases 106 | arguments SexpArray 107 | body Sexp 108 | macro bool 109 | //userfunc represents a native built-in implementation (which can be overrided e.g. with macros through the body argument) 110 | userfunc LispyUserFunction 111 | } 112 | 113 | func (f SexpFunctionLiteral) String() string { 114 | if f.userfunc == nil { 115 | return fmt.Sprintf("Define (%s) on (%s)", 116 | f.name, 117 | f.arguments.String()) 118 | } else { 119 | return "Built-in native implementation function" 120 | } 121 | } 122 | 123 | type SexpFunctionCall struct { 124 | //for now keep arguments as string, in future potentially refacto wrap in SexpIdentifierNode 125 | name string 126 | arguments SexpPair 127 | //used for annonymous function calls - REMOVE, I think not being used 128 | body Sexp 129 | } 130 | 131 | func (f SexpFunctionCall) String() string { 132 | args := f.arguments.String() 133 | //Yes this is not ideal 134 | args = args[1 : len(args)-1] 135 | return fmt.Sprintf("%s %s", 136 | f.name, 137 | args) 138 | } 139 | 140 | /********** PARSING CODE ****************/ 141 | func Parse(tokens []Token) ([]Sexp, error) { 142 | idx, length := 0, len(tokens) 143 | nodes := make([]Sexp, 0) 144 | for idx < length && tokens[idx].Token != EOF { 145 | expr, add, err := parseExpr(tokens[idx:]) 146 | if err != nil { 147 | log.Fatal("Error parsing tokens: ", err) 148 | } 149 | idx += add 150 | nodes = append(nodes, expr) 151 | } 152 | return nodes, nil 153 | } 154 | 155 | //Implement a list trivially as this for now 156 | func parseList(tokens []Token) (Sexp, int, error) { 157 | idx := 0 158 | curr := SexpPair{head: nil, tail: nil} 159 | if len(tokens) == 0 { 160 | return nil, 0, errors.New("Error parsing list!") 161 | } 162 | if tokens[idx].Token == RPAREN { 163 | //return idx of 1 so we skip the RPAREN 164 | return nil, 1, nil 165 | } 166 | currExpr, add, err := parseExpr(tokens[idx:]) 167 | if err != nil { 168 | return nil, 0, err 169 | } 170 | idx += add 171 | curr.head = currExpr 172 | //recursively build out list of cons cells 173 | tailExpr, addTail, err := parseList(tokens[idx:]) 174 | if err != nil { 175 | return nil, 0, err 176 | } 177 | idx += addTail 178 | curr.tail = tailExpr 179 | return curr, idx, nil 180 | } 181 | 182 | //parses array e.g. in arguments of a function 183 | func parseArray(tokens []Token) (SexpArray, int, error) { 184 | idx, length := 0, len(tokens) 185 | arr := make([]Sexp, 0) 186 | 187 | for idx < length && tokens[idx].Token != RSQUARE { 188 | expr, add, err := parseExpr(tokens[idx:]) 189 | if err != nil { 190 | return SexpArray{}, 0, err 191 | } 192 | idx += add 193 | arr = append(arr, expr) 194 | } 195 | return SexpArray{ofType: ARRAY, value: arr}, idx + 1, nil 196 | } 197 | 198 | func getName(token Token) string { 199 | if token.Token != SYMBOL { 200 | log.Fatal("Unexpected syntax trying to define a function") 201 | } 202 | //function name will be at index 0 203 | name := token.Literal 204 | return name 205 | } 206 | 207 | //parsing 208 | func parseParameterArray(tokens []Token) (SexpArray, int, error) { 209 | //assume we're given tokens including [, so skip [ 210 | idx := 1 211 | //parse arguments first 212 | args, add, err := parseArray(tokens[idx:]) 213 | if err != nil { 214 | log.Fatal("Error parsing parameter array") 215 | } 216 | idx += add 217 | return args, idx, err 218 | 219 | } 220 | 221 | //parses a function literal 222 | func parseFunctionLiteral(tokens []Token, name string, macro bool) (Sexp, int, error) { 223 | idx := 0 224 | var args SexpArray 225 | var add int 226 | var err error 227 | if tokens[idx].Token == LSQUARE { 228 | //parse arguments first 229 | args, add, _ = parseParameterArray(tokens[idx:]) 230 | idx += add 231 | } else { 232 | //means we have a lambda expression here 233 | args = SexpArray{} 234 | } 235 | 236 | //parse body of the function which which will be an Sexpr 237 | body, addBlock, err := parseExpr(tokens[idx:]) 238 | if err != nil { 239 | return nil, 0, err 240 | } 241 | idx += addBlock 242 | //entire function include define was enclosed in (), note DON'T SKIP 1 otherwise may read code outside function 243 | return SexpFunctionLiteral{name: name, arguments: args, body: body, userfunc: nil, macro: macro}, idx + 1, nil 244 | } 245 | 246 | //parses a single expression (list or non-list) 247 | func parseExpr(tokens []Token) (Sexp, int, error) { 248 | idx := 0 249 | var expr Sexp 250 | var err error 251 | var add int 252 | if len(tokens) == 0 { 253 | return nil, 0, errors.New("Error parsing expression!") 254 | } 255 | switch tokens[idx].Token { 256 | case DEFINE: 257 | //look ahead one to check if it's a function or just data-binding 258 | if idx+2 < len(tokens) && (tokens[idx+2].Token == LSQUARE) { 259 | idx++ 260 | //skip define token 261 | name := getName(tokens[idx]) 262 | expr, add, err = parseFunctionLiteral(tokens[idx+1:], name, false) 263 | } else { 264 | expr = SexpSymbol{ofType: tokens[idx].Token, value: tokens[idx].Literal} 265 | //POSSIBLE FEATURE AMMENDMENT: If I add local binding via let similar to Clojure, will be added here 266 | add = 1 267 | } 268 | case MACRO: 269 | idx++ 270 | name := getName(tokens[idx]) 271 | expr, add, err = parseFunctionLiteral(tokens[idx+1:], name, true) 272 | case LSQUARE: 273 | //if we reach here, then parsing a quote with square brackets 274 | expr, add, _ = parseParameterArray(tokens[idx:]) 275 | case LPAREN: 276 | idx++ 277 | //check if anonymous function 278 | if idx+1 < len(tokens) && tokens[idx].Literal == "fn" && tokens[idx+1].Token == LSQUARE { 279 | //skip fn 280 | idx++ 281 | //give anonymous functions the same name because by definition, should not be able to refer 282 | //to them after they have been defined (designed to execute there and then) 283 | expr, add, err = parseFunctionLiteral(tokens[idx:], "fn", false) 284 | } else if tokens[idx].Token == RPAREN { 285 | //check for empty list 286 | expr = SexpPair{head: nil, tail: nil} 287 | add = 1 288 | } else { 289 | expr, add, err = parseList(tokens[idx:]) 290 | } 291 | case INTEGER: 292 | i, err := strconv.Atoi(tokens[idx].Literal) 293 | if err != nil { 294 | return nil, 0, err 295 | } 296 | add = 1 297 | expr = SexpInt(i) 298 | case FLOAT: 299 | i, err := strconv.ParseFloat(tokens[idx].Literal, 64) 300 | if err != nil { 301 | return nil, 0, err 302 | } 303 | expr = SexpFloat(i) 304 | add = 1 305 | case QUOTE: 306 | idx++ 307 | nextExpr, toAdd, errorL := parseExpr(tokens[idx:]) 308 | if errorL != nil { 309 | log.Fatal("Error parsing quote!") 310 | } 311 | expr = makeSList([]Sexp{SexpSymbol{ofType: QUOTE, value: "quote"}, nextExpr}) 312 | add = toAdd 313 | //eventually refactor to handle other symbols like identifiers 314 | //create a map with all of these operators pre-stored and just get, or default, passing in tokentype to check if it exists 315 | case STRING, TRUE, FALSE, IF, DO, SYMBOL: 316 | expr = SexpSymbol{ofType: tokens[idx].Token, value: tokens[idx].Literal} 317 | add = 1 318 | default: 319 | fmt.Println(tokens[idx:]) 320 | log.Fatal("error parsing") 321 | } 322 | if err != nil { 323 | return nil, 0, err 324 | } 325 | idx += add 326 | return expr, idx, nil 327 | } 328 | 329 | //helper function to convert list of Sexp to list of cons cells 330 | func makeSList(expressions []Sexp) Sexp { 331 | if len(expressions) == 0 { 332 | return nil 333 | } 334 | return consHelper(expressions[0], makeSList(expressions[1:])) 335 | } 336 | 337 | /* 338 | Grammar 339 | 340 | number : /-?[0-9]+/ ; \ 341 | symbol : '+' | '-' | '*' | '/' ; \ 342 | list : ( * ) ; \ 343 | expr : | | ; \ 344 | - symbol = operator, variable, or function 345 | lispy : /^/ * /$/ ; \ 346 | - /^/ means start of the input is required (n/a atm, don't have a start token) 347 | - /$/ means end of the input is required (EOF tag) 348 | ----------------------------------- 349 | expr: ID | NUM | list 350 | ID = identifier like variable or binding 351 | expr: ID | STR | NUM | list (but we'll ignore this for now to simplify things) 352 | list: "(" seq ")" 353 | "" here just mean that we see a token i.e. LPAREN seq RPAREN 354 | seq: | expr seq 355 | (note ^ is an empty list) 356 | 357 | 358 | 359 | ATOMS: 360 | - Strings 361 | - Symbols 362 | - Numbers 363 | */ 364 | -------------------------------------------------------------------------------- /pkg/lispy/eval.go: -------------------------------------------------------------------------------- 1 | package lispy 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | type Env struct { 11 | //pointer to parent environment 12 | parent *Env 13 | store map[string]Value 14 | //used to track stack limit for safety 15 | steps int 16 | } 17 | 18 | //Value is a reference to any Value in a Lispy program 19 | type Value interface { 20 | String() string 21 | } 22 | 23 | //Value referencing any functions 24 | type FunctionValue struct { 25 | defn *SexpFunctionLiteral 26 | } 27 | 28 | //struct to store function arguments for now 29 | type StackFrame struct { 30 | args []Sexp 31 | } 32 | 33 | //allow functionvalue to implement value 34 | func (env Env) String() string { 35 | data := make([]string, 0) 36 | for key, val := range env.store { 37 | data = append(data, key+":"+val.String()) 38 | } 39 | return strings.Join(data, " ") 40 | } 41 | 42 | func (f FunctionValue) String() string { 43 | return fmt.Sprintf("function value: %s", f.defn.String()) 44 | } 45 | 46 | const maxSteps = 200000 47 | 48 | func returnDefinedFunctions() map[string]LispyUserFunction { 49 | functions := make(map[string]LispyUserFunction) 50 | functions["car"] = car 51 | functions["cdr"] = cdr 52 | functions["cons"] = cons 53 | functions["+"] = add 54 | functions["-"] = minus 55 | functions["/"] = divide 56 | functions["*"] = multiply 57 | functions["#"] = expo 58 | functions["%"] = modulo 59 | functions["="] = equal 60 | functions[">="] = gequal 61 | functions["<="] = lequal 62 | functions[">"] = gthan 63 | functions["<"] = lthan 64 | functions["and"] = and 65 | functions["or"] = or 66 | functions["not"] = not 67 | functions["println"] = printlnStatement 68 | functions["list"] = createList 69 | functions["type"] = typeOf 70 | functions["quote"] = quote 71 | functions["rand"] = random 72 | functions["number"] = number 73 | functions["symbol"] = symbol 74 | functions["readline"] = readline 75 | functions["str"] = str 76 | functions["quote?"] = isQuote 77 | functions["applyTo"] = applyTo 78 | functions["readstring"] = readstring 79 | return functions 80 | } 81 | 82 | func InitState() *Env { 83 | //add more ops as need for function bodies, assignments etc 84 | env := new(Env) 85 | env.store = make(map[string]Value) 86 | for key, function := range returnDefinedFunctions() { 87 | env.store[key] = makeUserFunction(key, function) 88 | } 89 | env.steps = maxSteps 90 | //load library functions 91 | errLib := EvalSourceIO(lib, env) 92 | if errLib != nil { 93 | log.Fatal("Error loading library packages of lispy") 94 | } 95 | return env 96 | } 97 | 98 | func (s SexpSymbol) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 99 | dec(env) 100 | switch s.ofType { 101 | case TRUE, FALSE: 102 | return s 103 | case STRING: 104 | if s.value == "" { 105 | return SexpSymbol{ofType: FALSE, value: "false"} 106 | } 107 | return s 108 | case IF: 109 | frame.args = append(frame.args, getSexpSymbolFromBool(allowThunk)) 110 | return conditionalStatement(env, s.value, frame.args) 111 | case DEFINE: 112 | return varDefinition(env, frame.args[0].String(), frame.args[1:]) 113 | case QUOTE: 114 | return s 115 | case SYMBOL: 116 | //if no argument then it's a variable 117 | if len(frame.args) == 0 { 118 | return getVarBinding(env, s.value, frame.args) 119 | } else if s.value == "swap" { 120 | return swap(env, s.value, frame.args) 121 | } 122 | //otherwise assume this is a function call 123 | argList, isList := frame.args[0].(SexpPair) 124 | if !isList { 125 | log.Fatal("Error trying to parse arguments for function call") 126 | } 127 | //check if this is an anonymous function the macro called 128 | if s.value == "fn" { 129 | params, isArray := argList.head.(SexpArray) 130 | if !isArray { 131 | log.Fatal("Error parsing anonymous function in macro expansion!") 132 | } 133 | bodyFunc, isValid := argList.tail.(SexpPair) 134 | if !isValid { 135 | log.Fatal("Error macroexpanding anon function!") 136 | } 137 | anonFunc := SexpFunctionLiteral{name: "fn", arguments: params, body: bodyFunc.head, userfunc: nil, macro: false} 138 | return anonFunc 139 | } 140 | // fmt.Println("func name: ", s.value, " w. args: ", argList.head) 141 | funcCall := SexpFunctionCall{name: s.value, arguments: argList} 142 | return funcCall.Eval(env, frame, allowThunk) 143 | default: 144 | fmt.Println(s.ofType, " ", s.value, " args: ", frame.args) 145 | log.Fatal("Uh oh, weird symbol my dude") 146 | return nil 147 | } 148 | } 149 | 150 | func (s SexpFunctionLiteral) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 151 | funcDefinition := FunctionValue{defn: &s} 152 | //append name of function to end of args 153 | frame.args = append(frame.args, SexpSymbol{ofType: STRING, value: s.name}) 154 | funcDefinition.Eval(env, frame, allowThunk) 155 | dec(env) 156 | return funcDefinition 157 | } 158 | 159 | func (s SexpFunctionCall) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 160 | //each call should get its own environment for recursion to work 161 | functionCallEnv := new(Env) 162 | //copy store for speed, otherwise keep recursing to parents 163 | functionCallEnv.store = make(map[string]Value) 164 | for key, element := range env.store { 165 | functionCallEnv.store[key] = element 166 | } 167 | functionCallEnv.steps = env.steps 168 | functionCallEnv.parent = env 169 | dec(env) 170 | return evalFunc(functionCallEnv, &s, allowThunk) 171 | } 172 | 173 | func (n SexpPair) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 174 | var toReturn Sexp 175 | //empty string 176 | if n.head == nil { 177 | return SexpPair{} 178 | } 179 | tail, isTail := n.tail.(SexpPair) 180 | switch head := n.head.(type) { 181 | case SexpSymbol: 182 | symbol, ok := n.head.(SexpSymbol) 183 | if !ok { 184 | log.Fatal("error trying to interpret symbol") 185 | } 186 | arguments := make([]Sexp, 0) 187 | //process all arguments here for ease? 188 | switch symbol.ofType { 189 | case DEFINE: 190 | if !isTail { 191 | log.Fatal("Unexpected definition, missing value!") 192 | } 193 | newFrame := StackFrame{args: makeList(tail)} 194 | //binding to a variable 195 | toReturn = symbol.Eval(env, &newFrame, allowThunk) 196 | case QUOTE: 197 | if !isTail { 198 | log.Fatal("Error trying to interpret quote") 199 | } 200 | //don't evaluate the expression 201 | toReturn = tail.head 202 | case IF: 203 | if !isTail { 204 | fmt.Println("Error interpreting condition for the if statement") 205 | } 206 | //evaluating arguments so pass thunk as false 207 | arguments = append(arguments, tail.head.Eval(env, frame, false)) 208 | statements, isValid := tail.tail.(SexpPair) 209 | if !isValid { 210 | log.Fatal("Error please provide valid responses to the if condition!") 211 | } 212 | res := makeList(statements) 213 | arguments = append(arguments, res...) 214 | newFrame := StackFrame{args: arguments} 215 | toReturn = symbol.Eval(env, &newFrame, allowThunk) 216 | case DO: 217 | //if symbol is do, we just evaluate the nodes and return the (result of the) last node 218 | //note do's second element will be a list of lists so we need to unwrap it 219 | if !isTail { 220 | log.Fatal("Error trying to interpret do statements") 221 | } 222 | for { 223 | //need to set allowThunk to true only if this is the last expression to execute in the do statement 224 | if tail.tail != nil { 225 | toReturn = tail.head.Eval(env, frame, false) 226 | } else { 227 | toReturn = tail.head.Eval(env, frame, allowThunk) 228 | } 229 | 230 | switch tail.tail.(type) { 231 | case SexpPair: 232 | tail = tail.tail.(SexpPair) 233 | continue 234 | } 235 | break 236 | } 237 | default: 238 | //quote that was parsed 239 | toReturn = symbol.Eval(env, &StackFrame{args: []Sexp{tail}}, allowThunk) 240 | } 241 | case SexpFunctionLiteral: 242 | //anonymous function, so handle differently 243 | if head.name == "fn" { 244 | //save body of function to the env then call 245 | head.Eval(env, frame, allowThunk) 246 | //check tail != nil for anon function with no parameters 247 | if !isTail && n.tail != nil { 248 | log.Fatal("Error interpreting anonymous function parameters") 249 | } 250 | funcCall := SexpFunctionCall{name: "fn", arguments: tail, body: nil} 251 | toReturn = funcCall.Eval(env, frame, allowThunk) 252 | } else { 253 | toReturn = head.Eval(env, frame, allowThunk) 254 | //in a function literal, body should only be on Sexp, if there is more, throw an error 255 | //in a function call, arguments will be pased into SexpFunctionCall so similar idea 256 | if n.tail != nil { 257 | log.Fatal("Error interpreting function declaration or literal - ensure only one Sexp in body of function literal!") 258 | } 259 | } 260 | case SexpFunctionCall: 261 | toReturn = head.Eval(env, frame, allowThunk) 262 | case SexpPair: 263 | original, ok := n.head.(SexpPair) 264 | if ok { 265 | toReturn = original.Eval(env, frame, allowThunk) 266 | //if this is an anon function from a macro, need to set it up as such 267 | funcLiteral, isFuncLiteral := toReturn.(SexpFunctionLiteral) 268 | if isFuncLiteral && funcLiteral.name == "fn" { 269 | //this is a function call so we can use the code above under case SexpFunctionLiteral 270 | //by artificially constructing a list as such 271 | toReturn = (SexpPair{head: funcLiteral, tail: n.tail}).Eval(env, frame, allowThunk) 272 | } else { 273 | // quote, isQuote := n.head.(SexpSymbol) 274 | toReturn = n 275 | // if isQuote && quote.ofType == QUOTE { 276 | // //just a nested list so return entire list 277 | // toReturn = n 278 | // } else { 279 | // fmt.Println(n.head) 280 | // log.Fatal("Error parsing") 281 | // } 282 | 283 | } 284 | } else { 285 | //empty list, so return false 286 | toReturn = SexpSymbol{FALSE, "false"} 287 | } 288 | //if it's just a list without a symbol at the front, treat it as data and return it 289 | default: 290 | toReturn = n 291 | } 292 | dec(env) 293 | return toReturn 294 | } 295 | 296 | func (arr SexpArray) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 297 | new := make([]Sexp, 0) 298 | for index := range arr.value { 299 | new = append(new, arr.value[index].Eval(env, frame, allowThunk)) 300 | } 301 | dec(env) 302 | return SexpArray{ofType: ARRAY, value: new} 303 | } 304 | 305 | func (s SexpFloat) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 306 | dec(env) 307 | return s 308 | } 309 | 310 | func (s SexpInt) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 311 | dec(env) 312 | return s 313 | } 314 | 315 | func dec(env *Env) { 316 | if env.steps != maxSteps { 317 | env.steps -= 1 318 | if env.steps < 0 { 319 | log.Fatal("Reached maximum recursion depth :(") 320 | } 321 | } 322 | } 323 | 324 | //evaluates and interprets our AST 325 | func (env *Env) Eval(nodes []Sexp) []string { 326 | res := make([]string, 0) 327 | frame := StackFrame{} 328 | for _, node := range nodes { 329 | curr := node.Eval(env, &frame, false) 330 | if curr != nil { 331 | // fmt.Println("node: ", node, " with result: ", reflect.TypeOf(curr)) 332 | res = append(res, curr.String()) 333 | } 334 | } 335 | return res 336 | } 337 | 338 | func evalHelper(source string) ([]Sexp, error) { 339 | tokens := Read(strings.NewReader(source)) 340 | ast, err := Parse(tokens) 341 | if err != nil { 342 | return nil, errors.New("Error parsing source!") 343 | } 344 | return ast, nil 345 | } 346 | 347 | //method which exposes eval to other packages which call this as an API to get a result 348 | func EvalSource(source string) ([]string, error) { 349 | ast, err := evalHelper(source) 350 | if err != nil { 351 | return nil, err 352 | } 353 | env := InitState() 354 | //limit size of stack / number of steps for safety 355 | env.steps = 7000 356 | return env.Eval(ast), nil 357 | } 358 | 359 | //used to load library packages into the env 360 | func EvalSourceIO(source string, env *Env) error { 361 | ast, err := evalHelper(source) 362 | if err != nil { 363 | return errors.New("Error parsing source!") 364 | } 365 | env.Eval(ast) 366 | return nil 367 | } 368 | 369 | //helper function to return a list of Sexp nodes from a linked list of cons cell 370 | func makeList(s SexpPair) []Sexp { 371 | toReturn := make([]Sexp, 0) 372 | for { 373 | toReturn = append(toReturn, s.head) 374 | switch s.tail.(type) { 375 | case SexpPair: 376 | s = s.tail.(SexpPair) 377 | continue 378 | } 379 | break 380 | } 381 | return toReturn 382 | } 383 | -------------------------------------------------------------------------------- /pkg/lispy/functions.go: -------------------------------------------------------------------------------- 1 | package lispy 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "math" 8 | "math/rand" 9 | "os" 10 | "reflect" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | type LispyUserFunction func(env *Env, name string, args []Sexp) Sexp 16 | 17 | type FunctionThunkValue struct { 18 | env *Env 19 | function FunctionValue 20 | } 21 | 22 | func (thunk FunctionThunkValue) String() string { 23 | return fmt.Sprintf("function thunk: %s", thunk.function) 24 | } 25 | 26 | //not ideal 27 | func (thunk FunctionThunkValue) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 28 | return nil 29 | } 30 | 31 | /******* handle definitions *********/ 32 | //create new variable binding 33 | func varDefinition(env *Env, key string, args []Sexp) Sexp { 34 | value := args[0].Eval(env, &StackFrame{}, false) 35 | env.store[key] = value 36 | return value 37 | } 38 | 39 | //retrieve existing variable binding 40 | func getVarBinding(env *Env, key string, args []Sexp) Sexp { 41 | if v, found := env.store[key]; found { 42 | value, ok := v.(Sexp) 43 | if !ok { 44 | //means empty list passed in 45 | return SexpPair{} 46 | } 47 | // newVal := value 48 | // if next, foundDeeper := env.store[newVal.String()]; foundDeeper { 49 | // _, isFunc := next.(FunctionValue) 50 | // //make sure it's not a function value and so we don't recurse into an error 51 | // if !isFunc { 52 | // return getVarBinding(env, newVal.String(), args) 53 | // } 54 | // } 55 | return value 56 | } else if env.parent != nil { 57 | return getVarBinding(env.parent, key, args) 58 | } 59 | log.Fatal("Error, ", key, " has not previously been defined!") 60 | return nil 61 | } 62 | 63 | //create new function binding 64 | func (funcVal FunctionValue) Eval(env *Env, frame *StackFrame, allowThunk bool) Sexp { 65 | name := frame.args[len(frame.args)-1].String() 66 | //FunctionValue is a compile-time representation of a function 67 | env.store[name] = funcVal 68 | dec(env) 69 | list := []Sexp{SexpSymbol{ofType: STRING, value: funcVal.defn.name}, funcVal.defn.arguments, funcVal.defn.body} 70 | return makeSList(list) 71 | } 72 | 73 | func evalFunc(env *Env, s *SexpFunctionCall, allowThunk bool) Sexp { 74 | name := s.name 75 | node, isFuncLiteral := env.store[name].(FunctionValue) 76 | if !isFuncLiteral { 77 | //check if this is a reference to another function / variable 78 | node, isFuncLiteral = getVarBinding(env, name, []Sexp{}).(FunctionValue) 79 | if !isFuncLiteral { 80 | log.Fatal("Error, badly defined function trying to be called: ", s.name) 81 | } 82 | } 83 | //note quite critically, we need to evaluate the result of any expression arguments BEFORE we set them 84 | //(before any old values get overwritten) 85 | newExprs := make([]Sexp, 0) 86 | if node.defn.macro { 87 | macroArgs := s.arguments 88 | switch i := s.arguments.head.(type) { 89 | case SexpPair: 90 | quote, isQuote := i.head.(SexpSymbol) 91 | //skip quote so it doesn't interfere with list manipulation in the macro 92 | if isQuote && quote.value == "" { 93 | pair, isPair := i.tail.(SexpPair) 94 | if isPair { 95 | macroArgs = pair 96 | } 97 | } 98 | } 99 | //pass the args directly, macro takes in one input so we can do this directly 100 | env.store[node.defn.arguments.value[0].String()] = macroArgs 101 | // fmt.Println("macro args => ", node.defn.body) 102 | macroRes := node.defn.body.Eval(env, &StackFrame{}, false) 103 | //uncomment line below to see macro-expansion 104 | // fmt.Println("macro => ", macroRes) 105 | finalRes := macroRes.Eval(env, &StackFrame{}, allowThunk) 106 | // fmt.Println(name, " res => ", finalRes) 107 | //evaluate the result of the macro transformed input 108 | return finalRes 109 | } 110 | //otherwise not a macro, so evaluate all of the arguments before calling the function 111 | if s.arguments.head != nil { 112 | // fmt.Println("args: ", s.arguments) 113 | //quote is a special form where we don't want to evaluate the args 114 | if name == "quote" { 115 | newExprs = makeList(s.arguments) 116 | } else { 117 | for _, toEvaluate := range makeList(s.arguments) { 118 | //TODO: figure why adding this in makeList is causing problems 119 | if toEvaluate != nil { 120 | //note pass false in case this is function call 121 | evaluatedArg := toEvaluate.Eval(env, &StackFrame{}, false) 122 | newExprs = append(newExprs, evaluatedArg) 123 | // fmt.Println("arg: ", toEvaluate, " res: ", evaluatedArg) 124 | } 125 | } 126 | // fmt.Println("evaluated params: ", newExprs, "\n for ", s.name, "\n\n\n") 127 | } 128 | 129 | } 130 | variableNumberOfArgs := false 131 | //load the passed in data to the arguments of the function in the environment 132 | for i, arg := range node.defn.arguments.value { 133 | //if arg has &, means it takes variable number of arguments, so create list of cons cells and set it to name pointing to variable arg 134 | if arg.String() == "&" { 135 | env.store[node.defn.arguments.value[i+1].String()] = makeSList(newExprs[i:]) 136 | variableNumberOfArgs = true 137 | break 138 | } else { 139 | env.store[arg.String()] = newExprs[i] 140 | } 141 | } 142 | 143 | //check we have the correct number of parameters 144 | //only do this if not a macro or a built-in function (most of which take a variable number of args and handle invalid ones 145 | //internally) 146 | if len(node.defn.arguments.value) != len(newExprs) && node.defn.userfunc == nil && !variableNumberOfArgs { 147 | fmt.Println(len(node.defn.arguments.value), newExprs) 148 | log.Fatal("Incorrect number of arguments passed in to ", node.defn.name) 149 | } 150 | 151 | //Call LispyUserFunction if this is a builtin function 152 | //note if user-defined version exists, then it takes precedence (to ensure idea of macro functions correctly) 153 | if node.defn.userfunc != nil && node.defn.body == nil { 154 | return node.defn.userfunc(env, name, newExprs) 155 | } 156 | // if name == "set-new-env" { 157 | // fmt.Println("here") 158 | // } 159 | // fmt.Println(name) 160 | functionThunk := FunctionThunkValue{env: env, function: node} 161 | //if we're at a tail position inside a function body, return the thunk directly for tail call optimization 162 | if allowThunk { 163 | return functionThunk 164 | } 165 | //evaluate function 166 | return unwrapThunks(functionThunk) 167 | } 168 | 169 | //unwrap nested function calls into flat for loop structure for tail call optimization 170 | func unwrapThunks(functionThunk FunctionThunkValue) Sexp { 171 | isTail := true 172 | var funcResult Sexp 173 | for isTail { 174 | funcResult = functionThunk.function.defn.body.Eval(functionThunk.env, &StackFrame{}, true) 175 | functionThunk, isTail = funcResult.(FunctionThunkValue) 176 | //fmt.Println("cheeky -> ", isTail, " ", funcResult) 177 | } 178 | return funcResult 179 | } 180 | 181 | //helper function to take a function and return a function literal which can be saved to the environment 182 | func makeUserFunction(name string, function LispyUserFunction) FunctionValue { 183 | var sfunc SexpFunctionLiteral 184 | sfunc.name = name 185 | sfunc.userfunc = function 186 | return FunctionValue{defn: &sfunc} 187 | } 188 | 189 | /******* create list *********/ 190 | func createList(env *Env, name string, args []Sexp) Sexp { 191 | i := unwrapSList(args) 192 | if i == nil { 193 | //return empty list () 194 | return SexpPair{} 195 | } 196 | return i 197 | } 198 | 199 | /******** quote **********/ 200 | func quote(env *Env, name string, args []Sexp) Sexp { 201 | return args[0] 202 | } 203 | 204 | //helper function to convert list of args into list of cons cells 205 | //this method is very similar to makeSList, the only difference is that it unwraps quotes 206 | //so that they are no longer stored as SexpPair{head: quote, tail: actual symbol} 207 | func unwrapSList(expressions []Sexp) Sexp { 208 | if len(expressions) == 0 { 209 | return nil 210 | } 211 | switch i := expressions[0].(type) { 212 | case SexpPair: 213 | if i.tail == nil { 214 | return consHelper(i.head, unwrapSList(expressions[1:])) 215 | } 216 | } 217 | return consHelper(expressions[0], unwrapSList(expressions[1:])) 218 | 219 | } 220 | 221 | /******* cars, cons, cdr **********/ 222 | 223 | //helper function to unwrap quote data 224 | func unwrap(arg Sexp) SexpPair { 225 | pair1, isPair1 := arg.(SexpPair) 226 | if !isPair1 { 227 | //check if we only have one item 228 | switch i := arg.(type) { 229 | case SexpInt, SexpFloat, SexpArray, SexpSymbol, FunctionValue: 230 | return SexpPair{head: SexpPair{head: i, tail: nil}, tail: nil} 231 | case SexpFunctionLiteral: 232 | argList := makeSList(i.arguments.value) 233 | //set up in list format 234 | list := makeSList([]Sexp{SexpSymbol{ofType: STRING, value: i.name}, argList, i.body}) 235 | listPair, _ := list.(SexpPair) 236 | return listPair 237 | default: 238 | fmt.Println(reflect.TypeOf(arg), arg) 239 | log.Fatal("Error unwrapping for built in functions") 240 | } 241 | } 242 | return SexpPair{head: pair1, tail: nil} 243 | } 244 | 245 | func car(env *Env, name string, args []Sexp) Sexp { 246 | if len(args) == 0 { 247 | log.Fatal("Uh oh, you need to pass an argument to car") 248 | } 249 | //need to unwrap twice since function call arguments wrap inner arguments in a SexpPair 250 | //so we have SexpPair{head: SexpPair{...}} 251 | pair1 := unwrap(args[0]) 252 | switch i := pair1.head.(type) { 253 | case SexpPair: 254 | return i.head 255 | case SexpInt, SexpFloat, SexpSymbol, SexpArray: 256 | return i 257 | default: 258 | return nil 259 | } 260 | } 261 | 262 | func cdr(env *Env, name string, args []Sexp) Sexp { 263 | if len(args) == 0 { 264 | log.Fatal("Uh oh, you need to pass an argument to car") 265 | } 266 | pair1 := unwrap(args[0]) 267 | switch i := pair1.head.(type) { 268 | case SexpPair: 269 | //if we cdr a one-item list, we should return an empty list 270 | if i.tail == nil { 271 | return SexpPair{} 272 | } 273 | return i.tail 274 | case SexpInt, SexpFloat, SexpSymbol: 275 | if pair1.tail == nil { 276 | return SexpPair{} 277 | } 278 | return pair1.tail 279 | default: 280 | fmt.Println(reflect.TypeOf(i)) 281 | log.Fatal("argument 0 of cdr has wrong type!") 282 | } 283 | return nil 284 | } 285 | 286 | func cons(env *Env, name string, args []Sexp) Sexp { 287 | if len(args) < 2 { 288 | log.Fatal("Incorrect number of arguments!") 289 | } 290 | //unwrap the list in the block quote (need to evaluate first to allow for recursive calls) 291 | list := unwrap(args[1]) 292 | newHead := consHelper(args[0], list.head) 293 | return newHead 294 | } 295 | 296 | func consHelper(a Sexp, b Sexp) SexpPair { 297 | return SexpPair{a, b} 298 | } 299 | 300 | //since quote is not stored as a special form, we need an internal function to check 301 | /******* quote *********/ 302 | func isQuote(env *Env, name string, args []Sexp) Sexp { 303 | if len(args) == 0 { 304 | log.Fatal("Error checking quote type") 305 | } 306 | switch i := args[0].(type) { 307 | case SexpSymbol: 308 | if i.ofType == QUOTE || i.value == "quote" { 309 | return SexpSymbol{ofType: TRUE, value: "true"} 310 | } 311 | } 312 | return SexpSymbol{ofType: FALSE, value: "false"} 313 | } 314 | 315 | /******* swap *************/ 316 | //note swap only works for lists! 317 | func swap(env *Env, name string, args []Sexp) Sexp { 318 | if len(args) == 0 { 319 | log.Fatal("Error trying to swap element") 320 | } 321 | //enforce swap only for lists 322 | list, isList := args[0].(SexpPair) 323 | if !isList { 324 | log.Fatal("Error trying to parse arguments of swap") 325 | } 326 | newList, isNewList := list.tail.(SexpPair) 327 | if !isNewList { 328 | log.Fatal("Error swapping non-list!") 329 | } 330 | fmt.Println(list.head.String()) 331 | newVal := newList.head.Eval(env, &StackFrame{}, false) 332 | setValWhileKeyExists(env, list.head.String(), newVal) 333 | return newVal 334 | } 335 | 336 | //helper method for set 337 | func setValWhileKeyExists(env *Env, key string, val Value) { 338 | if _, found := env.store[key]; found { 339 | env.store[key] = val 340 | if env.parent != nil { 341 | setValWhileKeyExists(env.parent, key, val) 342 | } 343 | } 344 | } 345 | 346 | /******* readstring *******/ 347 | //reads one object from a string 348 | func readstring(env *Env, name string, args []Sexp) Sexp { 349 | if len(args) < 1 { 350 | log.Fatal("Error trying to read object from tring!") 351 | } 352 | stringObj, isString := args[0].(SexpSymbol) 353 | if !isString || stringObj.ofType != STRING { 354 | log.Fatal("Error trying to read an object from a non-string!") 355 | } 356 | res, err := evalHelper(stringObj.value) 357 | if err != nil { 358 | log.Fatal("Error trying to parse an object from a string !") 359 | } 360 | //readstring only reads first object 361 | return res[0] 362 | } 363 | 364 | /******* readline *********/ 365 | func readline(env *Env, name string, args []Sexp) Sexp { 366 | if len(args) > 0 { 367 | fmt.Print(args[0].String()) 368 | } 369 | scanner := bufio.NewScanner(os.Stdin) 370 | var val string 371 | if scanner.Scan() { 372 | val = scanner.Text() 373 | } 374 | 375 | return SexpSymbol{ofType: STRING, value: val} 376 | } 377 | 378 | /******* string join *********/ 379 | func str(env *Env, name string, args []Sexp) Sexp { 380 | val := "" 381 | for _, arg := range args { 382 | val += arg.String() 383 | } 384 | return SexpSymbol{ofType: STRING, value: val} 385 | } 386 | 387 | /******* handle conditional statements *********/ 388 | func conditionalStatement(env *Env, name string, args []Sexp) Sexp { 389 | //fmt.Println(args) 390 | //put thunk as last argument 391 | thunk, isThunk := args[len(args)-1].(SexpSymbol) 392 | //TODO: improve this 393 | args = args[:len(args)-1] 394 | if !isThunk { 395 | log.Fatal("Error passing thunk into conditional statement") 396 | } 397 | allowThunk := thunk.ofType == TRUE 398 | var condition bool 399 | var toReturn Sexp 400 | switch i := args[0].(type) { 401 | case SexpFloat, SexpInt: 402 | condition = true 403 | case SexpPair: 404 | //empty list 405 | if i.head == nil { 406 | condition = false 407 | } else { 408 | condition = true 409 | } 410 | case SexpSymbol: 411 | if i.ofType == FALSE { 412 | condition = false 413 | } else { 414 | condition = true 415 | } 416 | default: 417 | fmt.Println(i) 418 | log.Fatal("Error trying to interpret condition for if statement") 419 | } 420 | if condition { 421 | toReturn = args[1].Eval(env, &StackFrame{}, allowThunk) 422 | } else { 423 | if len(args) > 2 && args[2] != nil { 424 | toReturn = args[2].Eval(env, &StackFrame{}, allowThunk) 425 | } else { 426 | //no provided else block despite the condition evaluating to such 427 | toReturn = SexpSymbol{ofType: FALSE, value: "nil"} 428 | } 429 | } 430 | return toReturn 431 | } 432 | 433 | /******* handle random numbers *********/ 434 | func random(env *Env, name string, args []Sexp) Sexp { 435 | if len(args) != 0 { 436 | log.Fatal("Error generating random number") 437 | } 438 | //generate a random seed, otherwise the same random number will be generated 439 | rand.Seed(time.Now().UnixNano()) 440 | return SexpFloat(rand.Float64()) 441 | } 442 | 443 | /******* applies function to list of args similar to function applyTo in Clojure *********/ 444 | func applyTo(env *Env, name string, args []Sexp) Sexp { 445 | if len(args) < 2 { 446 | log.Fatal("Error applying function to args") 447 | } 448 | functionLiteral, isFuncLiteral := args[0].(FunctionValue) 449 | if !isFuncLiteral { 450 | if env.store[args[0].String()] != nil { 451 | functionLiteral, isFuncLiteral = env.store[args[0].String()].(FunctionValue) 452 | } 453 | if !isFuncLiteral { 454 | log.Fatal("Error trying to apply a value that is not a function") 455 | } 456 | 457 | } 458 | arguments, isArgs := args[1].(SexpPair) 459 | if !isArgs { 460 | log.Fatal("Error applyTo only operates on lists!") 461 | } 462 | return SexpFunctionCall{name: functionLiteral.defn.name, arguments: arguments}.Eval(env, &StackFrame{}, false) 463 | } 464 | 465 | /******* handle type conversions for non-list *********/ 466 | func number(env *Env, name string, args []Sexp) Sexp { 467 | if len(args) > 1 { 468 | log.Fatal("Error casting to number") 469 | } 470 | switch i := args[0].(type) { 471 | case SexpSymbol: 472 | num, err := strconv.ParseFloat(i.value, 64) 473 | if err != nil { 474 | log.Fatal(err) 475 | } 476 | return SexpFloat(num) 477 | case SexpInt: 478 | return SexpFloat(i) 479 | case SexpFloat: 480 | return i 481 | default: 482 | log.Fatal("Error casting list to number") 483 | return nil 484 | } 485 | } 486 | 487 | func symbol(env *Env, name string, args []Sexp) Sexp { 488 | if len(args) > 1 { 489 | log.Fatal("Error casting to number") 490 | } 491 | return SexpSymbol{ofType: SYMBOL, value: args[0].String()} 492 | } 493 | 494 | /******* handle println statements *********/ 495 | func printlnStatement(env *Env, name string, args []Sexp) Sexp { 496 | for _, arg := range args { 497 | //uncomment to see live prints for local stuff 498 | //fmt.Print(arg.String(), " ") 499 | return arg 500 | } 501 | fmt.Println() 502 | return nil 503 | } 504 | 505 | /******* handle logical (and or not) operations *********/ 506 | //These wrappers are necessary to map unique functions to the built-in symbols in the store 507 | //This becomes important when passing (built-in) functions as parameters without knowing ahead of time which 508 | //one will be used 509 | func and(env *Env, name string, args []Sexp) Sexp { 510 | return logicalOperator(env, "and", args) 511 | } 512 | 513 | func or(env *Env, name string, args []Sexp) Sexp { 514 | return logicalOperator(env, "or", args) 515 | } 516 | 517 | func not(env *Env, name string, args []Sexp) Sexp { 518 | return logicalOperator(env, "not", args) 519 | } 520 | 521 | func logicalOperator(env *Env, name string, args []Sexp) Sexp { 522 | result := getBoolFromTokenType(args[0].Eval(env, &StackFrame{}, false)) 523 | if len(args) == 0 { 524 | log.Fatal("Invalid syntax, pass in more than logical operator!") 525 | } 526 | //not can only take one parameter so check that first 527 | if name == "not" { 528 | if len(args) > 1 { 529 | log.Fatal("Error, cannot pass more than one logical operator to not!") 530 | } 531 | result = handleLogicalOp(name, result) 532 | } else { 533 | if len(args) < 2 { 534 | log.Fatal("Error, cannot carry out an ", name, " operator with only 1 condition!") 535 | } 536 | //for and, or, loop through the arguments and aggregate 537 | for i := 1; i < len(args); i++ { 538 | result = handleLogicalOp(name, result, getBoolFromTokenType(args[i].Eval(env, &StackFrame{}, false))) 539 | if result == false { 540 | //note we can't break early beacuse of the or operator 541 | break 542 | } 543 | } 544 | } 545 | 546 | return getSexpSymbolFromBool(result) 547 | } 548 | 549 | //helper code to keep code DRY 550 | func getBoolFromTokenType(symbol Sexp) bool { 551 | switch i := symbol.(type) { 552 | case SexpSymbol: 553 | if i.ofType == FALSE { 554 | return false 555 | } 556 | return true 557 | default: 558 | return true 559 | } 560 | } 561 | 562 | func getSexpSymbolFromBool(truthy bool) SexpSymbol { 563 | switch truthy { 564 | case true: 565 | return SexpSymbol{ofType: TRUE, value: "true"} 566 | default: 567 | return SexpSymbol{ofType: FALSE, value: "false"} 568 | } 569 | } 570 | 571 | func handleLogicalOp(name string, log ...bool) bool { 572 | var result bool 573 | switch name { 574 | case "not": 575 | result = !log[0] 576 | case "or": 577 | result = log[0] || log[1] 578 | case "and": 579 | result = log[0] && log[1] 580 | } 581 | return result 582 | } 583 | 584 | /******* handle typeOf *********/ 585 | func typeOf(env *Env, name string, args []Sexp) Sexp { 586 | var typeCurr SexpSymbol 587 | if len(args) < 1 { 588 | log.Fatal("require a parameter to check type of") 589 | } 590 | switch i := args[0].(type) { 591 | case SexpInt: 592 | typeCurr = SexpSymbol{ofType: STRING, value: "int"} 593 | case SexpFloat: 594 | typeCurr = SexpSymbol{ofType: STRING, value: "float"} 595 | case SexpPair: 596 | if i.tail == nil { 597 | return typeOf(env, name, []Sexp{i.head}) 598 | } else { 599 | typeCurr = SexpSymbol{ofType: STRING, value: "list"} 600 | } 601 | case SexpSymbol: 602 | typeCurr = SexpSymbol{ofType: STRING, value: "symbol"} 603 | case SexpFunctionLiteral, SexpFunctionCall: 604 | typeCurr = SexpSymbol{ofType: STRING, value: "list"} 605 | default: 606 | fmt.Println(i) 607 | log.Fatal("unexpected type!") 608 | } 609 | return typeCurr 610 | } 611 | 612 | /******* handle relational operations *********/ 613 | //These wrappers are necessary to map unique functions to the built-in symbols in the store 614 | //This becomes important when passing (built-in) functions as parameters without knowing ahead of time which 615 | //one will be used 616 | func equal(env *Env, name string, args []Sexp) Sexp { 617 | return relationalOperator(env, "=", args) 618 | } 619 | 620 | func gequal(env *Env, name string, args []Sexp) Sexp { 621 | return relationalOperator(env, ">=", args) 622 | } 623 | 624 | func lequal(env *Env, name string, args []Sexp) Sexp { 625 | return relationalOperator(env, "<=", args) 626 | } 627 | 628 | func gthan(env *Env, name string, args []Sexp) Sexp { 629 | return relationalOperator(env, ">", args) 630 | } 631 | 632 | func lthan(env *Env, name string, args []Sexp) Sexp { 633 | return relationalOperator(env, "<", args) 634 | } 635 | 636 | func relationalOperator(env *Env, name string, args []Sexp) Sexp { 637 | result := true 638 | tokenType := TRUE 639 | //recall we evaluated params before function call already 640 | orig := args[0] 641 | 642 | for i := 1; i < len(args); i++ { 643 | curr := args[i] 644 | switch i := orig.(type) { 645 | case SexpFloat: 646 | result = relationalOperatorMatchFloat(name, i, curr) 647 | case SexpInt: 648 | result = relationalOperatorMatchInt(name, i, curr) 649 | case SexpSymbol: 650 | result = relationalOperatorMatchSymbol(name, i, curr) 651 | case SexpPair: 652 | result = relationalOperatorMatchList(name, i, curr) 653 | case SexpFunctionLiteral: 654 | result = relationalOperatorMatchLiteral(name, i, curr) 655 | default: 656 | fmt.Println(args) 657 | log.Fatal("Error, unexpected type in relational operator") 658 | } 659 | if !result { 660 | tokenType = FALSE 661 | break 662 | } 663 | } 664 | return SexpSymbol{ofType: tokenType, value: getBoolFromString(result)} 665 | } 666 | 667 | func relationalOperatorMatchLiteral(name string, x SexpFunctionLiteral, y Sexp) bool { 668 | res := true 669 | switch i := y.(type) { 670 | case SexpFunctionLiteral: 671 | res = i.name == x.name 672 | default: 673 | res = false 674 | } 675 | return res 676 | } 677 | 678 | func relationalOperatorMatchList(name string, x SexpPair, y Sexp) bool { 679 | res := true 680 | switch i := y.(type) { 681 | case SexpPair: 682 | list1 := makeList(x) 683 | list2 := makeList(i) 684 | if len(list1) != len(list2) { 685 | res = false 686 | } else { 687 | for i := 0; i < len(list1); i++ { 688 | res = (res && list1[i] == list2[i]) 689 | } 690 | } 691 | default: 692 | res = false 693 | } 694 | return res 695 | } 696 | 697 | func relationalOperatorMatchSymbol(name string, x SexpSymbol, y Sexp) bool { 698 | var res bool 699 | switch i := y.(type) { 700 | case SexpSymbol: 701 | res = handleRelOperatorSymbols(name, x, i) 702 | default: 703 | res = false 704 | } 705 | return res 706 | } 707 | 708 | func relationalOperatorMatchFloat(name string, x SexpFloat, y Sexp) bool { 709 | var res bool 710 | switch i := y.(type) { 711 | case SexpFloat: 712 | res = handleRelOperator(name, x, i) 713 | case SexpInt: 714 | res = handleRelOperator(name, x, SexpFloat(i)) 715 | default: 716 | res = false 717 | } 718 | return res 719 | } 720 | 721 | func relationalOperatorMatchInt(name string, x SexpInt, y Sexp) bool { 722 | var res bool 723 | switch i := y.(type) { 724 | case SexpFloat: 725 | res = handleRelOperator(name, SexpFloat(x), i) 726 | case SexpInt: 727 | res = handleRelOperator(name, SexpFloat(x), SexpFloat(i)) 728 | default: 729 | res = false 730 | } 731 | return res 732 | } 733 | 734 | func getBoolFromString(boolean bool) string { 735 | var res string 736 | switch boolean { 737 | case true: 738 | res = "true" 739 | case false: 740 | res = "false" 741 | default: 742 | log.Fatal("Error with passed in bool") 743 | } 744 | return res 745 | } 746 | 747 | func handleRelOperatorSymbols(name string, x SexpSymbol, y SexpSymbol) bool { 748 | var result bool 749 | switch name { 750 | case ">": 751 | result = (x.value > y.value) 752 | case ">=": 753 | result = (x.value >= y.value) 754 | case "<": 755 | result = (x.value < y.value) 756 | case "<=": 757 | result = (x.value <= y.value) 758 | case "=": 759 | result = (x.value == y.value) 760 | } 761 | return result 762 | } 763 | 764 | func handleRelOperator(name string, x SexpFloat, y SexpFloat) bool { 765 | var result bool 766 | switch name { 767 | case ">": 768 | result = (x > y) 769 | case ">=": 770 | result = (x >= y) 771 | case "<": 772 | result = (x < y) 773 | case "<=": 774 | result = (x <= y) 775 | case "=": 776 | result = (x == y) 777 | } 778 | return result 779 | } 780 | 781 | /******* handle binary arithmetic operations *********/ 782 | //These wrappers are necessary to map unique functions to the built-in symbols in the store 783 | //This becomes important when passing (built-in) functions as parameters without knowing ahead of time which 784 | //one will be used 785 | func add(env *Env, name string, args []Sexp) Sexp { 786 | return binaryOperation(env, "+", args) 787 | } 788 | 789 | func minus(env *Env, name string, args []Sexp) Sexp { 790 | return binaryOperation(env, "-", args) 791 | } 792 | 793 | func multiply(env *Env, name string, args []Sexp) Sexp { 794 | return binaryOperation(env, "*", args) 795 | } 796 | 797 | func expo(env *Env, name string, args []Sexp) Sexp { 798 | return binaryOperation(env, "#", args) 799 | } 800 | 801 | func divide(env *Env, name string, args []Sexp) Sexp { 802 | return binaryOperation(env, "/", args) 803 | } 804 | 805 | func modulo(env *Env, name string, args []Sexp) Sexp { 806 | return binaryOperation(env, "%", args) 807 | } 808 | 809 | func binaryOperation(env *Env, name string, args []Sexp) Sexp { 810 | res := args[0] 811 | switch i := res.(type) { 812 | case SexpArray, SexpPair, SexpFunctionCall, SexpFunctionLiteral: 813 | log.Fatal("Invalid type passed to a binary operation!") 814 | case SexpSymbol: 815 | if i.value == "" { 816 | return binaryOperation(env, name, args[1:]) 817 | } 818 | } 819 | for i := 1; i < len(args); i++ { 820 | //pass in new argument under consideration first for compare operation 821 | switch term := res.(type) { 822 | case SexpFloat: 823 | res = numericMatchFloat(name, term, args[i]) 824 | case SexpInt: 825 | res = numericMatchInt(name, term, args[i]) 826 | default: 827 | log.Fatal("Invalid type passed to a binary operation!") 828 | 829 | } 830 | } 831 | return res 832 | } 833 | 834 | func numericMatchInt(name string, x SexpInt, y Sexp) Sexp { 835 | var res Sexp 836 | switch i := y.(type) { 837 | case SexpInt: 838 | res = numericOpInt(name, x, i) 839 | case SexpFloat: 840 | res = numericOpFloat(name, SexpFloat(x), i) 841 | case SexpSymbol: 842 | if i.value == "" { 843 | return x 844 | } 845 | default: 846 | fmt.Println(y) 847 | log.Fatal("Error adding two numbers!") 848 | } 849 | return res 850 | } 851 | 852 | func numericMatchFloat(name string, x SexpFloat, y Sexp) Sexp { 853 | var res Sexp 854 | switch i := y.(type) { 855 | case SexpInt: 856 | res = numericOpFloat(name, x, SexpFloat(i)) 857 | case SexpFloat: 858 | res = numericOpFloat(name, x, i) 859 | case SexpSymbol: 860 | if i.value == "" { 861 | return x 862 | } 863 | default: 864 | log.Fatal("Error adding two numbers!") 865 | } 866 | return res 867 | } 868 | 869 | func numericOpInt(name string, x SexpInt, y SexpInt) Sexp { 870 | var res Sexp 871 | switch name { 872 | case "+": 873 | res = x + y 874 | case "-": 875 | res = x - y 876 | case "/": 877 | if y == 0 { 878 | log.Fatal("Error attempted division by 0") 879 | } 880 | res = x / y 881 | case "*": 882 | res = x * y 883 | case "#": 884 | res = SexpInt(math.Pow(float64(x), float64(y))) 885 | case "%": 886 | res = x % y 887 | default: 888 | log.Fatal("Error invalid operation") 889 | } 890 | return res 891 | 892 | } 893 | 894 | func numericOpFloat(name string, x SexpFloat, y SexpFloat) Sexp { 895 | var res Sexp 896 | switch name { 897 | case "+": 898 | res = x + y 899 | case "-": 900 | res = x - y 901 | case "/": 902 | if y == 0 { 903 | log.Fatal("Error attempted division by 0") 904 | } 905 | res = x / y 906 | case "*": 907 | res = x * y 908 | case "#": 909 | res = SexpFloat(math.Pow(float64(x), float64(y))) 910 | case "%": 911 | res = SexpInt(int(x) % int(y)) 912 | default: 913 | log.Fatal("Error invalid operation") 914 | 915 | } 916 | return res 917 | } 918 | --------------------------------------------------------------------------------