|
| 1 | +require "readline" |
| 2 | + |
| 3 | +require_relative "core" |
| 4 | +require_relative "env" |
| 5 | +require_relative "errors" |
| 6 | +require_relative "printer" |
| 7 | +require_relative "reader" |
| 8 | + |
| 9 | +module Mal |
| 10 | + extend self |
| 11 | + |
| 12 | + def boot_repl! |
| 13 | + @repl_env = Env.new |
| 14 | + |
| 15 | + Core.ns.each do |k, v| |
| 16 | + @repl_env.set(k, v) |
| 17 | + end |
| 18 | + |
| 19 | + @repl_env.set( |
| 20 | + Types::Symbol.for("eval"), |
| 21 | + |
| 22 | + Types::Builtin.new do |mal| |
| 23 | + Mal.EVAL(mal.first, @repl_env) |
| 24 | + end |
| 25 | + ) |
| 26 | + |
| 27 | + Mal.rep("(def! not (fn* (a) (if a false true)))") |
| 28 | + Mal.rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))") |
| 29 | + Mal.rep("(def! *ARGV* (list))") if !run_application? |
| 30 | + end |
| 31 | + |
| 32 | + def run_application? |
| 33 | + ARGV.any? |
| 34 | + end |
| 35 | + |
| 36 | + def run! |
| 37 | + Mal.rep("(def! *ARGV* (list #{ARGV[1..].map(&:inspect).join(" ")}))") |
| 38 | + Mal.rep("(load-file #{ARGV.first.inspect})") |
| 39 | + end |
| 40 | + |
| 41 | + def READ(input) |
| 42 | + read_str(input) |
| 43 | + end |
| 44 | + |
| 45 | + def EVAL(ast, environment) |
| 46 | + loop do |
| 47 | + if Types::List === ast && ast.size > 0 |
| 48 | + case ast.first |
| 49 | + when Types::Symbol.for("def!") |
| 50 | + _, sym, val = ast |
| 51 | + return environment.set(sym, EVAL(val, environment)) |
| 52 | + when Types::Symbol.for("let*") |
| 53 | + e = Env.new(environment) |
| 54 | + _, bindings, val = ast |
| 55 | + |
| 56 | + unless Types::List === bindings || Types::Vector === bindings |
| 57 | + raise InvalidLetBindingsError |
| 58 | + end |
| 59 | + |
| 60 | + until bindings.empty? |
| 61 | + k, v = bindings.shift(2) |
| 62 | + |
| 63 | + raise InvalidLetBindingsError if k.nil? |
| 64 | + v = Types::Nil.instance if v.nil? |
| 65 | + |
| 66 | + e.set(k, EVAL(v, e)) |
| 67 | + end |
| 68 | + |
| 69 | + if !val.nil? |
| 70 | + # Continue loop |
| 71 | + ast = val |
| 72 | + environment = e |
| 73 | + else |
| 74 | + return Types::Nil.instance |
| 75 | + end |
| 76 | + when Types::Symbol.for("do") |
| 77 | + _, *values = ast |
| 78 | + |
| 79 | + if !values.nil? && values.any? |
| 80 | + values[0...-1].each do |v| |
| 81 | + EVAL(v, environment) |
| 82 | + end |
| 83 | + |
| 84 | + # Continue loop |
| 85 | + ast = values.last |
| 86 | + else |
| 87 | + return Types::Nil.instance |
| 88 | + end |
| 89 | + when Types::Symbol.for("if") |
| 90 | + _, condition, when_true, when_false = ast |
| 91 | + |
| 92 | + case EVAL(condition, environment) |
| 93 | + when Types::False.instance, Types::Nil.instance |
| 94 | + if !when_false.nil? |
| 95 | + # Continue loop |
| 96 | + ast = when_false |
| 97 | + else |
| 98 | + return Types::Nil.instance |
| 99 | + end |
| 100 | + else |
| 101 | + if !when_true.nil? |
| 102 | + # Continue loop |
| 103 | + ast = when_true |
| 104 | + else |
| 105 | + raise InvalidIfExpressionError |
| 106 | + end |
| 107 | + end |
| 108 | + when Types::Symbol.for("fn*") |
| 109 | + _, binds, to_eval = ast |
| 110 | + |
| 111 | + return Types::Function.new(to_eval, binds, environment) do |exprs| |
| 112 | + exprs = |
| 113 | + if exprs.is_a?(Types::List) |
| 114 | + exprs |
| 115 | + else |
| 116 | + Types::List.new([*exprs]) |
| 117 | + end |
| 118 | + |
| 119 | + EVAL(to_eval, Env.new(environment, binds, exprs)) |
| 120 | + end |
| 121 | + when Types::Symbol.for("quote") |
| 122 | + _, ret = ast |
| 123 | + return ret |
| 124 | + when Types::Symbol.for("quasiquote") |
| 125 | + _, ast_rest = ast |
| 126 | + ast = quasiquote(ast_rest) |
| 127 | + when Types::Symbol.for("quasiquoteexpand") |
| 128 | + _, ast_rest = ast |
| 129 | + return quasiquote(ast_rest) |
| 130 | + else |
| 131 | + evaluated = eval_ast(ast, environment) |
| 132 | + maybe_callable = evaluated.first |
| 133 | + |
| 134 | + if maybe_callable.respond_to?(:call) && maybe_callable.is_mal_fn? |
| 135 | + # Continue loop |
| 136 | + ast = maybe_callable.ast |
| 137 | + environment = Env.new( |
| 138 | + maybe_callable.env, |
| 139 | + maybe_callable.params, |
| 140 | + evaluated[1..], |
| 141 | + ) |
| 142 | + elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn? |
| 143 | + return maybe_callable.call(evaluated[1..]) |
| 144 | + else |
| 145 | + raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." |
| 146 | + end |
| 147 | + end |
| 148 | + elsif Types::List === ast && ast.size == 0 |
| 149 | + return ast |
| 150 | + else |
| 151 | + return eval_ast(ast, environment) |
| 152 | + end |
| 153 | + end |
| 154 | + end |
| 155 | + |
| 156 | + def PRINT(input) |
| 157 | + pr_str(input, true) |
| 158 | + end |
| 159 | + |
| 160 | + def rep(input) |
| 161 | + PRINT(EVAL(READ(input), @repl_env)) |
| 162 | + rescue InvalidHashmapKeyError => e |
| 163 | + "Error! Hashmap keys can only be strings or keywords." |
| 164 | + rescue NotCallableError => e |
| 165 | + e.message |
| 166 | + rescue SymbolNotFoundError => e |
| 167 | + e.message |
| 168 | + rescue UnbalancedEscapingError => e |
| 169 | + "Error! Detected unbalanced escaping. Check for matching '\\'." |
| 170 | + rescue UnbalancedHashmapError => e |
| 171 | + "Error! Detected unbalanced list. Check for matching '}'." |
| 172 | + rescue UnbalancedListError => e |
| 173 | + "Error! Detected unbalanced list. Check for matching ')'." |
| 174 | + rescue UnbalancedStringError => e |
| 175 | + "Error! Detected unbalanced string. Check for matching '\"'." |
| 176 | + rescue UnbalancedVectorError => e |
| 177 | + "Error! Detected unbalanced list. Check for matching ']'." |
| 178 | + rescue SkipCommentError |
| 179 | + nil |
| 180 | + end |
| 181 | + |
| 182 | + def eval_ast(mal, environment) |
| 183 | + case mal |
| 184 | + when Types::Symbol |
| 185 | + environment.get(mal) |
| 186 | + when Types::List |
| 187 | + list = Types::List.new |
| 188 | + mal.each { |i| list << EVAL(i, environment) } |
| 189 | + list |
| 190 | + when Types::Vector |
| 191 | + vec = Types::Vector.new |
| 192 | + mal.each { |i| vec << EVAL(i, environment) } |
| 193 | + vec |
| 194 | + when Types::Hashmap |
| 195 | + hashmap = Types::Hashmap.new |
| 196 | + mal.each { |k, v| hashmap[k] = EVAL(v, environment) } |
| 197 | + hashmap |
| 198 | + else |
| 199 | + mal |
| 200 | + end |
| 201 | + end |
| 202 | + |
| 203 | + def quasiquote_list(mal) |
| 204 | + result = Types::List.new |
| 205 | + |
| 206 | + mal.reverse_each do |elt| |
| 207 | + if elt.is_a?(Types::List) && elt.first == Types::Symbol.for("splice-unquote") |
| 208 | + result = Types::List.new([ |
| 209 | + Types::Symbol.for("concat"), |
| 210 | + elt[1], |
| 211 | + result |
| 212 | + ]) |
| 213 | + else |
| 214 | + result = Types::List.new([ |
| 215 | + Types::Symbol.for("cons"), |
| 216 | + quasiquote(elt), |
| 217 | + result |
| 218 | + ]) |
| 219 | + end |
| 220 | + end |
| 221 | + |
| 222 | + result |
| 223 | + end |
| 224 | + |
| 225 | + def quasiquote(mal) |
| 226 | + case mal |
| 227 | + when Types::List |
| 228 | + if mal.first == Types::Symbol.for("unquote") |
| 229 | + mal[1] |
| 230 | + else |
| 231 | + quasiquote_list(mal) |
| 232 | + end |
| 233 | + when Types::Vector |
| 234 | + Types::List.new([ |
| 235 | + Types::Symbol.for("vec"), |
| 236 | + quasiquote_list(mal) |
| 237 | + ]) |
| 238 | + when Types::Hashmap, Types::Symbol |
| 239 | + Types::List.new([ |
| 240 | + Types::Symbol.for("quote"), |
| 241 | + mal |
| 242 | + ]) |
| 243 | + else |
| 244 | + mal |
| 245 | + end |
| 246 | + end |
| 247 | +end |
| 248 | + |
| 249 | +Mal.boot_repl! |
| 250 | + |
| 251 | +if Mal.run_application? |
| 252 | + Mal.run! |
| 253 | +else |
| 254 | + while input = Readline.readline("user> ") |
| 255 | + val = Mal.rep(input) |
| 256 | + puts val unless val.nil? |
| 257 | + end |
| 258 | + |
| 259 | + puts |
| 260 | +end |
0 commit comments