From 0d842530928b41c0ef904df8190524f253c8931a Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 4 Jun 2015 00:39:48 +0900 Subject: [PATCH] rename mal to crisp --- src/core.cr | 94 ++++++------ src/{stepA_mal.cr => crisp.cr} | 112 +++++++------- src/env.cr | 16 +- src/error.cr | 6 +- src/printer.cr | 18 +-- src/reader.cr | 20 +-- src/step0_repl.cr | 26 ---- src/step1_read_print.cr | 32 ---- src/step2_eval.cr | 90 ----------- src/step3_env.cr | 115 -------------- src/step4_if_fn_do.cr | 130 ---------------- src/step5_tco.cr | 164 -------------------- src/step6_file.cr | 177 --------------------- src/step7_quote.cr | 207 ------------------------- src/step8_macros.cr | 253 ------------------------------ src/step9_try.cr | 270 --------------------------------- src/types.cr | 8 +- 17 files changed, 136 insertions(+), 1602 deletions(-) rename src/{stepA_mal.cr => crisp.cr} (65%) delete mode 100755 src/step0_repl.cr delete mode 100755 src/step1_read_print.cr delete mode 100755 src/step2_eval.cr delete mode 100755 src/step3_env.cr delete mode 100755 src/step4_if_fn_do.cr delete mode 100755 src/step5_tco.cr delete mode 100755 src/step6_file.cr delete mode 100755 src/step7_quote.cr delete mode 100755 src/step8_macros.cr delete mode 100755 src/step9_try.cr diff --git a/src/core.cr b/src/core.cr index 15f870d..637cd54 100644 --- a/src/core.cr +++ b/src/core.cr @@ -6,22 +6,22 @@ require "./printer" require "./reader" require "./readline" -module Mal +module Crisp macro calc_op(op) - -> (args : Array(Mal::Type)) { + -> (args : Array(Crisp::Type)) { x, y = args[0].unwrap, args[1].unwrap eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int32) && y.is_a?(Int32) - Mal::Type.new(x {{op.id}} y) + Crisp::Type.new(x {{op.id}} y) } end def self.list(args) - args.to_mal + args.to_crisp_value end def self.list?(args) - args.first.unwrap.is_a? Mal::List + args.first.unwrap.is_a? Crisp::List end def self.empty?(args) @@ -76,13 +76,13 @@ def self.slurp(args) end def self.cons(args) - head, tail = args[0] as Mal::Type, args[1].unwrap + head, tail = args[0] as Crisp::Type, args[1].unwrap eval_error "2nd arg of cons must be list" unless tail.is_a? Array - ([head] + tail).to_mal + ([head] + tail).to_crisp_value end def self.concat(args) - args.each_with_object(Mal::List.new) do |arg, list| + args.each_with_object(Crisp::List.new) do |arg, list| a = arg.unwrap eval_error "arguments of concat must be list" unless a.is_a?(Array) a.each{|e| list << e} @@ -107,10 +107,10 @@ end def self.rest(args) a0 = args[0].unwrap - return Mal::List.new if a0.nil? + return Crisp::List.new if a0.nil? eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array - return Mal::List.new if a0.empty? - a0[1..-1].to_mal + return Crisp::List.new if a0.empty? + a0[1..-1].to_crisp_value end def self.apply(args) @@ -122,9 +122,9 @@ def self.apply(args) eval_error "last argument of apply must be list or vector" unless last.is_a? Array case head - when Mal::Closure + when Crisp::Closure head.fn.call(args[1..-2] + last) - when Mal::Func + when Crisp::Func head.call(args[1..-2] + last) else eval_error "1st argument of apply must be function or closure" @@ -138,12 +138,12 @@ def self.map(args) eval_error "2nd argument of map must be list or vector" unless list.is_a? Array f = case func - when Mal::Closure then func.fn - when Mal::Func then func + when Crisp::Closure then func.fn + when Crisp::Func then func else eval_error "1st argument of map must be function" end - list.each_with_object(Mal::List.new) do |elem, mapped| + list.each_with_object(Crisp::List.new) do |elem, mapped| mapped << f.call([elem]) end end @@ -163,13 +163,13 @@ def self.false?(args) end def self.symbol?(args) - args.first.unwrap.is_a?(Mal::Symbol) + args.first.unwrap.is_a?(Crisp::Symbol) end def self.symbol(args) head = args.first.unwrap eval_error "1st argument of symbol function must be string" unless head.is_a? String - Mal::Symbol.new head + Crisp::Symbol.new head end def self.keyword(args) @@ -184,16 +184,16 @@ def self.keyword?(args) end def self.vector(args) - args.to_mal(Mal::Vector) + args.to_crisp_value(Crisp::Vector) end def self.vector?(args) - args.first.unwrap.is_a? Mal::Vector + args.first.unwrap.is_a? Crisp::Vector end def self.hash_map(args) eval_error "hash-map must take even number of arguments" unless args.size.even? - map = Mal::HashMap.new + map = Crisp::HashMap.new args.each_slice(2) do |kv| k = kv[0].unwrap eval_error "key must be string" unless k.is_a? String @@ -203,15 +203,15 @@ def self.hash_map(args) end def self.map?(args) - args.first.unwrap.is_a? Mal::HashMap + args.first.unwrap.is_a? Crisp::HashMap end def self.assoc(args) head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Crisp::HashMap eval_error "assoc must take a list and even number of arguments" unless (args.size - 1).even? - map = Mal::HashMap.new + map = Crisp::HashMap.new head.each{|k, v| map[k] = v} args[1..-1].each_slice(2) do |kv| @@ -225,9 +225,9 @@ end def self.dissoc(args) head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Crisp::HashMap - map = Mal::HashMap.new + map = Crisp::HashMap.new head.each{|k,v| map[k] = v} args[1..-1].each do |arg| @@ -241,7 +241,7 @@ end def self.get(args) a0, a1 = args[0].unwrap, args[1].unwrap - return nil unless a0.is_a? Mal::HashMap + return nil unless a0.is_a? Crisp::HashMap eval_error "2nd argument of get must be string" unless a1.is_a? String # a0[a1]? isn't available because type ofa0[a1] is infered NoReturn @@ -250,21 +250,21 @@ end def self.contains?(args) a0, a1 = args[0].unwrap, args[1].unwrap - eval_error "1st argument of get must be hashmap" unless a0.is_a? Mal::HashMap + eval_error "1st argument of get must be hashmap" unless a0.is_a? Crisp::HashMap eval_error "2nd argument of get must be string" unless a1.is_a? String a0.has_key? a1 end def self.keys(args) head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap - head.keys.each_with_object(Mal::List.new){|e,l| l << Mal::Type.new(e)} + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Crisp::HashMap + head.keys.each_with_object(Crisp::List.new){|e,l| l << Crisp::Type.new(e)} end def self.vals(args) head = args.first.unwrap - eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap - head.values.to_mal + eval_error "1st argument of assoc must be hashmap" unless head.is_a? Crisp::HashMap + head.values.to_crisp_value end def self.sequential?(args) @@ -289,36 +289,36 @@ def self.with_meta(args) end def self.atom(args) - Mal::Atom.new args.first + Crisp::Atom.new args.first end def self.atom?(args) - args.first.unwrap.is_a? Mal::Atom + args.first.unwrap.is_a? Crisp::Atom end def self.deref(args) head = args.first.unwrap - eval_error "1st argument of deref must be atom" unless head.is_a? Mal::Atom + eval_error "1st argument of deref must be atom" unless head.is_a? Crisp::Atom head.val end def self.reset!(args) head = args.first.unwrap - eval_error "1st argument of reset! must be atom" unless head.is_a? Mal::Atom + eval_error "1st argument of reset! must be atom" unless head.is_a? Crisp::Atom head.val = args[1] end def self.swap!(args) atom = args.first.unwrap - eval_error "1st argument of swap! must be atom" unless atom.is_a? Mal::Atom + eval_error "1st argument of swap! must be atom" unless atom.is_a? Crisp::Atom a = [atom.val] + args[2..-1] func = args[1].unwrap case func - when Mal::Func + when Crisp::Func atom.val = func.call a - when Mal::Closure + when Crisp::Closure atom.val = func.fn.call a else eval_error "2nd argumetn of swap! must be function" @@ -328,10 +328,10 @@ end def self.conj(args) seq = args.first.unwrap case seq - when Mal::List - (args[1..-1].reverse + seq).to_mal - when Mal::Vector - (seq + args[1..-1]).to_mal(Mal::Vector) + when Crisp::List + (args[1..-1].reverse + seq).to_crisp_value + when Crisp::Vector + (seq + args[1..-1]).to_crisp_value(Crisp::Vector) else eval_error "1st argument of conj must be list or vector" end @@ -344,11 +344,11 @@ end # Note: # Simply using ->self.some_func doesn't work macro func(name) - -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) } + -> (args : Array(Crisp::Type)) { Crisp::Type.new self.{{name.id}}(args) } end macro rel_op(op) --> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) } +-> (args : Array(Crisp::Type)) { Crisp::Type.new (args[0] {{op.id}} args[1]) } end NS = { @@ -376,7 +376,7 @@ NS = { "nth" => func(:nth) "first" => func(:first) "rest" => func(:rest) - "throw" => -> (args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] } + "throw" => -> (args : Array(Crisp::Type)) { raise Crisp::RuntimeException.new args[0] } "apply" => func(:apply) "map" => func(:map) "nil?" => func(:nil?) @@ -408,6 +408,6 @@ NS = { "swap!" => func(:swap!) "conj" => func(:conj) "time-ms" => func(:time_ms) -} of String => Mal::Func +} of String => Crisp::Func end diff --git a/src/stepA_mal.cr b/src/crisp.cr similarity index 65% rename from src/stepA_mal.cr rename to src/crisp.cr index 49361ef..edd6019 100755 --- a/src/stepA_mal.cr +++ b/src/crisp.cr @@ -14,30 +14,30 @@ require "./error" # Employed downcase names because Crystal prohibits uppercase names for methods def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) + -> (args : Array(Crisp::Type)) { + new_env = Crisp::Env.new(env, binds, args) eval(body, new_env) - } as Mal::Func + } as Crisp::Func end def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Array + return ast.map{|n| eval(n, env) as Crisp::Type} if ast.is_a? Array val = ast.unwrap - Mal::Type.new case val - when Mal::Symbol + Crisp::Type.new case val + when Crisp::Symbol if e = env.get(val.str) e else eval_error "'#{val.str}' not found" end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Mal::HashMap - new_map = Mal::HashMap.new + when Crisp::List + val.each_with_object(Crisp::List.new){|n, l| l << eval(n, env)} + when Crisp::Vector + val.each_with_object(Crisp::Vector.new){|n, l| l << eval(n, env)} + when Crisp::HashMap + new_map = Crisp::HashMap.new val.each{|k, v| new_map[k] = eval(v, env)} new_map else @@ -57,8 +57,8 @@ def quasiquote(ast) list = ast.unwrap unless pair?(list) - return Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "quote") << ast + return Crisp::Type.new( + Crisp::List.new << gen_type(Crisp::Symbol, "quote") << ast ) end @@ -66,29 +66,29 @@ def quasiquote(ast) case # ("unquote" ...) - when head.is_a?(Mal::Symbol) && head.str == "unquote" + when head.is_a?(Crisp::Symbol) && head.str == "unquote" list[1] # (("splice-unquote" ...) ...) - when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].to_mal - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) + when pair?(head) && (arg0 = head.first.unwrap).is_a?(Crisp::Symbol) && arg0.str == "splice-unquote" + tail = Crisp::Type.new list[1..-1].to_crisp_value + Crisp::Type.new( + Crisp::List.new << gen_type(Crisp::Symbol, "concat") << head[1] << quasiquote(tail) ) else - tail = Mal::Type.new list[1..-1].to_mal - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) + tail = Crisp::Type.new list[1..-1].to_crisp_value + Crisp::Type.new( + Crisp::List.new << gen_type(Crisp::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) ) end end def macro_call?(ast, env) list = ast.unwrap - return false unless list.is_a? Mal::List + return false unless list.is_a? Crisp::List return false if list.empty? sym = list.first.unwrap - return false unless sym.is_a? Mal::Symbol + return false unless sym.is_a? Crisp::Symbol func = env.find(sym.str).try(&.data[sym.str]) return false unless func && func.macro? @@ -100,14 +100,14 @@ def macroexpand(ast, env) while macro_call?(ast, env) # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol + list = ast.unwrap as Crisp::List + func_sym = list[0].unwrap as Crisp::Symbol func = env.get(func_sym.str).unwrap case func - when Mal::Func + when Crisp::Func ast = func.call(list[1..-1]) - when Mal::Closure + when Crisp::Closure ast = func.fn.call(list[1..-1]) else eval_error "macro '#{func_sym.str}' must be function: #{ast}" @@ -122,11 +122,11 @@ macro invoke_list(l, env) args = eval_ast({{l}}[1..-1], {{env}}) as Array case f - when Mal::Closure + when Crisp::Closure ast = f.ast - {{env}} = Mal::Env.new(f.env, f.params, args) + {{env}} = Crisp::Env.new(f.env, f.params, args) next # TCO - when Mal::Func + when Crisp::Func return f.call args else eval_error "expected function as the first argument: #{f}" @@ -141,24 +141,24 @@ def eval(ast, env) # 'next' in 'do...end' has a bug in crystal 0.7.1 # https://github.com/manastech/crystal/issues/659 while true - return eval_ast(ast, env) unless ast.unwrap.is_a? Mal::List + return eval_ast(ast, env) unless ast.unwrap.is_a? Crisp::List ast = macroexpand(ast, env) list = ast.unwrap - return ast unless list.is_a? Mal::List + return ast unless list.is_a? Crisp::List return ast if list.empty? head = list.first.unwrap - return invoke_list(list, env) unless head.is_a? Mal::Symbol + return invoke_list(list, env) unless head.is_a? Crisp::Symbol - return Mal::Type.new case head.str + return Crisp::Type.new case head.str when "def!" eval_error "wrong number of argument for 'def!'" unless list.size == 3 a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Crisp::Symbol env.set(a1.str, eval(list[2], env)) when "let*" eval_error "wrong number of argument for 'def!'" unless list.size == 3 @@ -167,11 +167,11 @@ def eval(ast, env) eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array eval_error "size of binding list must be even" unless bindings.size.even? - new_env = Mal::Env.new env + new_env = Crisp::Env.new env bindings.each_slice(2) do |binding| key, value = binding name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol + eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Crisp::Symbol new_env.set(name.str, eval(value, new_env)) end @@ -179,16 +179,16 @@ def eval(ast, env) next # TCO when "do" if list.empty? - ast = Mal::Type.new nil + ast = Crisp::Type.new nil next end - eval_ast(list[1..-2].to_mal, env) + eval_ast(list[1..-2].to_crisp_value, env) ast = list.last next # TCO when "if" ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) + list.size >= 4 ? list[3] : Crisp::Type.new(nil) else list[2] end @@ -198,7 +198,7 @@ def eval(ast, env) unless params.is_a? Array eval_error "'fn*' parameters must be list or vector: #{params}" end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) + Crisp::Closure.new(list[2], params, env, func_of(env, params, list[2])) when "quote" list[1] when "quasiquote" @@ -207,25 +207,25 @@ def eval(ast, env) when "defmacro!" eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol + eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Crisp::Symbol env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) when "macroexpand" macroexpand(list[1], env) when "try*" catch_list = list[2].unwrap - return eval(list[1], env) unless catch_list.is_a? Mal::List + return eval(list[1], env) unless catch_list.is_a? Crisp::List catch_head = catch_list.first.unwrap - return eval(list[1], env) unless catch_head.is_a? Mal::Symbol + return eval(list[1], env) unless catch_head.is_a? Crisp::Symbol return eval(list[1], env) unless catch_head.str == "catch*" begin eval(list[1], env) - rescue e : Mal::RuntimeException - new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) + rescue e : Crisp::RuntimeException + new_env = Crisp::Env.new(env, [catch_list[1]], [e.thrown]) eval(catch_list[2], new_env) rescue e - new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) + new_env = Crisp::Env.new(env, [catch_list[1]], [Crisp::Type.new e.message]) eval(catch_list[2], new_env) end else @@ -242,22 +242,22 @@ def rep(str) print(eval(read(str), $repl_env)) end -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ eval(args[0], $repl_env) }) +$repl_env = Crisp::Env.new nil +Crisp::NS.each{|k,v| $repl_env.set(k, Crisp::Type.new(v))} +$repl_env.set("eval", Crisp::Type.new -> (args: Array(Crisp::Type)){ eval(args[0], $repl_env) }) rep "(def! not (fn* (a) (if a false true)))" rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" rep("(def! *host-language* \"crystal\")") -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) +$argv = Crisp::List.new +$repl_env.set("*ARGV*", Crisp::Type.new $argv) unless ARGV.empty? if ARGV.size > 1 ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) + $argv << Crisp::Type.new(a) end end @@ -269,9 +269,7 @@ unless ARGV.empty? exit end -rep("(println (str \"Mal [\" *host-language* \"]\"))") - -while line = my_readline("user> ") +while line = my_readline("Crisp> ") begin puts rep(line) rescue e diff --git a/src/env.cr b/src/env.cr index 9f38d68..bed7e36 100644 --- a/src/env.cr +++ b/src/env.cr @@ -1,17 +1,17 @@ require "./types" require "./error" -module Mal +module Crisp class Env property data def initialize(@outer) - @data = {} of String => Mal::Type + @data = {} of String => Crisp::Type end - def initialize(@outer, binds, exprs : Array(Mal::Type)) - @data = {} of String => Mal::Type + def initialize(@outer, binds, exprs : Array(Crisp::Type)) + @data = {} of String => Crisp::Type eval_error "binds must be list or vector" unless binds.is_a? Array @@ -19,15 +19,15 @@ module Mal # Array#zip() can't be used because overload resolution failed (0...binds.size).each do |idx| sym = binds[idx].unwrap - eval_error "bind name must be symbol" unless sym.is_a? Mal::Symbol + eval_error "bind name must be symbol" unless sym.is_a? Crisp::Symbol if sym.str == "&" eval_error "missing variable parameter name" if binds.size == idx next_param = binds[idx+1].unwrap - eval_error "bind name must be symbol" unless next_param.is_a? Mal::Symbol - var_args = Mal::List.new + eval_error "bind name must be symbol" unless next_param.is_a? Crisp::Symbol + var_args = Crisp::List.new exprs[idx..-1].each{|e| var_args << e} if idx < exprs.size - @data[next_param.str] = Mal::Type.new var_args + @data[next_param.str] = Crisp::Type.new var_args break end diff --git a/src/error.cr b/src/error.cr index 6df309d..dde354c 100644 --- a/src/error.cr +++ b/src/error.cr @@ -1,4 +1,4 @@ -module Mal +module Crisp class ParseException < Exception end @@ -14,9 +14,9 @@ module Mal end def eval_error(msg) - raise Mal::EvalException.new msg + raise Crisp::EvalException.new msg end def parse_error(msg) - raise Mal::ParseException.new msg + raise Crisp::ParseException.new msg end diff --git a/src/printer.cr b/src/printer.cr index 7444cb2..ef88fe2 100644 --- a/src/printer.cr +++ b/src/printer.cr @@ -5,12 +5,12 @@ def pr_str(value, print_readably = true) when Nil then "nil" when Bool then value.to_s when Int32 then value.to_s - when Mal::List then "(#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")})" - when Mal::Vector then "[#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")}]" - when Mal::Symbol then value.str.to_s - when Mal::Func then "" - when Mal::Closure then "" - when Mal::HashMap + when Crisp::List then "(#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")})" + when Crisp::Vector then "[#{value.map{|v| pr_str(v, print_readably) as String}.join(" ")}]" + when Crisp::Symbol then value.str.to_s + when Crisp::Func then "" + when Crisp::Closure then "" + when Crisp::HashMap # step1_read_print.cr requires specifying type "{#{value.map{|k, v| "#{pr_str(k, print_readably)} #{pr_str(v, print_readably)}" as String}.join(" ")}}" when String @@ -22,13 +22,13 @@ def pr_str(value, print_readably = true) else print_readably ? value.inspect : value end - when Mal::Atom + when Crisp::Atom "(atom #{pr_str(value.val, print_readably)})" else - raise "invalid MalType: #{value.to_s}" + raise "invalid CrispType: #{value.to_s}" end end -def pr_str(t : Mal::Type, print_readably = true) +def pr_str(t : Crisp::Type, print_readably = true) pr_str(t.unwrap, print_readably) + (t.macro? ? " (macro)" : "") end diff --git a/src/reader.cr b/src/reader.cr index 74a5605..ea31bd0 100644 --- a/src/reader.cr +++ b/src/reader.cr @@ -46,18 +46,18 @@ class Reader end def read_list - Mal::Type.new read_sequence(Mal::List.new, '(', ')') + Crisp::Type.new read_sequence(Crisp::List.new, '(', ')') end def read_vector - Mal::Type.new read_sequence(Mal::Vector.new, '[', ']') + Crisp::Type.new read_sequence(Crisp::Vector.new, '[', ']') end def read_hashmap - types = read_sequence([] of Mal::Type, '{', '}') + types = read_sequence([] of Crisp::Type, '{', '}') parse_error "odd number of elements for hash-map: #{types.size}" if types.size.odd? - map = Mal::HashMap.new + map = Crisp::HashMap.new types.each_slice(2) do |kv| k, v = kv[0].unwrap, kv[1] @@ -69,26 +69,26 @@ class Reader end end - Mal::Type.new map + Crisp::Type.new map end def read_atom token = self.next parse_error "expected Atom but got EOF" unless token - Mal::Type.new case + Crisp::Type.new case when token =~ /^-?\d+$/ then token.to_i when token == "true" then true when token == "false" then false when token == "nil" then nil when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"") when token[0] == ':' then "\u029e#{token[1..-1]}" - else Mal::Symbol.new token + else Crisp::Symbol.new token end end def list_of(symname) - Mal::List.new << gen_type(Mal::Symbol, symname) << read_form + Crisp::List.new << gen_type(Crisp::Symbol, symname) << read_form end def read_form @@ -97,7 +97,7 @@ class Reader parse_error "unexpected EOF" unless token parse_error "unexpected comment" if token[0] == ';' - Mal::Type.new case token + Crisp::Type.new case token when "(" then read_list when ")" then parse_error "unexpected ')'" when "[" then read_vector @@ -130,7 +130,7 @@ def read_str(str) r.read_form ensure unless r.peek.nil? - raise Mal::ParseException.new "expected EOF, got #{r.peek.to_s}" + raise Crisp::ParseException.new "expected EOF, got #{r.peek.to_s}" end end end diff --git a/src/step0_repl.cr b/src/step0_repl.cr deleted file mode 100755 index e1fe58a..0000000 --- a/src/step0_repl.cr +++ /dev/null @@ -1,26 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def read(x) - x -end - -def eval(x) - x -end - -def print(x) - x -end - -def rep(x) - read(eval(print(x))) -end - -while line = my_readline("user> ") - puts rep(line) -end diff --git a/src/step1_read_print.cr b/src/step1_read_print.cr deleted file mode 100755 index 1f3896d..0000000 --- a/src/step1_read_print.cr +++ /dev/null @@ -1,32 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def read(str) - read_str str -end - -def eval(x) - x -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str))) -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step2_eval.cr b/src/step2_eval.cr deleted file mode 100755 index a9bf00d..0000000 --- a/src/step2_eval.cr +++ /dev/null @@ -1,90 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def eval_error(msg) - raise Mal::EvalException.new msg -end - -def num_func(func) - -> (args : Array(Mal::Type)) { - x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments" unless x.is_a?(Int32) && y.is_a?(Int32) - Mal::Type.new func.call(x, y) - } -end - -$repl_env = { - "+" => num_func(->(x : Int32, y : Int32){ x + y }), - "-" => num_func(->(x : Int32, y : Int32){ x - y }), - "*" => num_func(->(x : Int32, y : Int32){ x * y }), - "/" => num_func(->(x : Int32, y : Int32){ x / y }), -} of String => Mal::Func - -def eval_ast(a, env) - return a.map{|n| eval(n, env) as Mal::Type} if a.is_a? Mal::List - return a unless a - - ast = a.unwrap - case ast - when Mal::Symbol - if env.has_key? ast.str - env[ast.str] - else - eval_error "'#{ast.str}' not found" - end - when Mal::List - ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - ast.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Mal::HashMap - ast.each{|k, v| ast[k] = eval(v, env)} - else - ast - end -end - -def read(str) - read_str str -end - -def eval(t, env) - Mal::Type.new case ast = t.unwrap - when Mal::List - eval_error "empty list" if ast.empty? - - f = eval_ast(ast.first, env) - ast.shift(1) - args = eval_ast(ast, env) - - if f.is_a?(Mal::Func) - f.call(args) - else - eval_error "expected function symbol as the first symbol of list" - end - else - eval_ast(t, env) - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step3_env.cr b/src/step3_env.cr deleted file mode 100755 index 9c7fc99..0000000 --- a/src/step3_env.cr +++ /dev/null @@ -1,115 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def eval_error(msg) - raise Mal::EvalException.new msg -end - -def num_func(func) - -> (args : Array(Mal::Type)) { - x, y = args[0].unwrap, args[1].unwrap - eval_error "invalid arguments" unless x.is_a?(Int32) && y.is_a?(Int32) - Mal::Type.new func.call(x, y) - } -end - -$repl_env = Mal::Env.new nil -$repl_env.set("+", Mal::Type.new num_func(->(x : Int32, y : Int32){ x + y })) -$repl_env.set("-", Mal::Type.new num_func(->(x : Int32, y : Int32){ x - y })) -$repl_env.set("*", Mal::Type.new num_func(->(x : Int32, y : Int32){ x * y })) -$repl_env.set("/", Mal::Type.new num_func(->(x : Int32, y : Int32){ x / y })) - -def eval_ast(a, env) - return a.map{|n| eval(n, env) } if a.is_a? Array - - Mal::Type.new case ast = a.unwrap - when Mal::Symbol - if e = env.get(ast.str) - e - else - eval_error "'#{ast.str}' not found" - end - when Mal::List - ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - ast.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Mal::HashMap - new_map = Mal::HashMap.new - ast.each{|k, v| new_map[k] = eval(v, env)} - new_map - else - ast - end -end - -def read(str) - read_str str -end - -def eval(t, env) - ast = t.unwrap - - return eval_ast(t, env) unless ast.is_a?(Mal::List) - - eval_error "empty list" if ast.empty? - - sym = ast.first.unwrap - eval_error "first element of list must be a symbol" unless sym.is_a?(Mal::Symbol) - - Mal::Type.new case sym.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless ast.size == 3 - a1 = ast[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a?(Mal::Symbol) - env.set(a1.str, eval(ast[2], env) as Mal::Type) - when "let*" - eval_error "wrong number of argument for 'def!'" unless ast.size == 3 - - bindings = ast[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a?(Array) - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - name, value = binding[0].unwrap, binding[1] - eval_error "name of binding must be specified as symbol" unless name.is_a?(Mal::Symbol) - new_env.set(name.str, eval(value, new_env)) - end - - eval(ast[2], new_env) - else - f = eval_ast(ast.first, env) - ast.shift(1) - args = eval_ast(ast, env) - - if f.is_a?(Mal::Type) && (f2 = f.unwrap).is_a?(Mal::Func) - f2.call(args as Array(Mal::Type)) - else - eval_error "expected function symbol as the first symbol of list" - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step4_if_fn_do.cr b/src/step4_if_fn_do.cr deleted file mode 100755 index 3dc861c..0000000 --- a/src/step4_if_fn_do.cr +++ /dev/null @@ -1,130 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def eval_invocation(list, env) - f = eval(list.first, env).unwrap - eval_error "expected function symbol as the first symbol of list" unless f.is_a? Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) -end - -def read(str) - read_str str -end - -def eval(ast, env) - list = ast.unwrap - - return eval_ast(ast, env) unless list.is_a? Mal::List - return gen_type Mal::List if list.empty? - - head = list.first.unwrap - - Mal::Type.new case head - when Mal::Symbol - case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - eval(list[2], new_env) - when "do" - list.shift 1 - eval_ast(list, env).last - when "if" - cond = eval(list[1], env).unwrap - case cond - when Nil - list.size >= 4 ? eval(list[3], env) : nil - when false - list.size >= 4 ? eval(list[3], env) : nil - else - eval(list[2], env) - end - when "fn*" - # Note: - # If writing lambda expression here directly, compiler will fail to infer type of 'list'. (Error 'Nil for empty?') - func_of(env, list[1].unwrap, list[2]) - else - eval_invocation(list, env) - end - else - eval_invocation(list, env) - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -rep "(def! not (fn* (a) (if a false true)))" - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step5_tco.cr b/src/step5_tco.cr deleted file mode 100755 index 450e399..0000000 --- a/src/step5_tco.cr +++ /dev/null @@ -1,164 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Array(Mal::Type) - val.map{|n| eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def eval_invocation(list, env) - f = eval(list.first, env).unwrap - case f - when Mal::Closure - f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) - when Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) - else - eval_error "expected function as the first argument" - end -end - -def read(str) - read_str str -end - -macro invoke_list(l) - f = eval({{l}}.first, env).unwrap - args = eval_ast({{l}}[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) - case f - when Mal::Closure - ast = f.ast - env = Mal::Env.new(f.env, f.params, args) - next # TCO - when Mal::Func - return f.call args - else - eval_error "expected function as the first argument" - end -end - -def eval(ast, env) - # 'next' in 'do...end' has a bug in crystal 0.7.1 - # https://github.com/manastech/crystal/issues/659 - while true - list = ast.unwrap - - return eval_ast(ast, env) unless list.is_a? Mal::List - return gen_type Mal::List if list.empty? - - head = list.first.unwrap - - unless head.is_a? Mal::Symbol - invoke_list list - end - - return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - # Note: - # If writing lambda expression here directly, compiler will fail to infer type of 'list'. (Error 'Nil for empty?') - params = list[1].unwrap - unless params.is_a?(Mal::List) || params.is_a?(Mal::Vector) - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, list[1].unwrap, list[2])) - else - invoke_list list - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -rep "(def! not (fn* (a) (if a false true)))" - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step6_file.cr b/src/step6_file.cr deleted file mode 100755 index 1ffe493..0000000 --- a/src/step6_file.cr +++ /dev/null @@ -1,177 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Array(Mal::Type) - val.map{|n| eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def eval_invocation(list, env) - f = eval(list.first, env).unwrap - case f - when Mal::Closure - f.fn.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) - when Mal::Func - f.call eval_ast(list[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, env) - else - eval_error "expected function as the first argument" - end -end - -def read(str) - read_str str -end - -macro invoke_list(l, env) - f = eval({{l}}.first, {{env}}).unwrap - args = eval_ast({{l}}[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, {{env}}) - case f - when Mal::Closure - ast = f.ast - {{env}} = Mal::Env.new(f.env, f.params, args) - next # TCO - when Mal::Func - return f.call args - else - eval_error "expected function as the first argument" - end -end - -def eval(ast, env) - # 'next' in 'do...end' has a bug in crystal 0.7.1 - # https://github.com/manastech/crystal/issues/659 - while true - list = ast.unwrap - - return eval_ast(ast, env) unless list.is_a? Mal::List - return gen_type Mal::List if list.empty? - - head = list.first.unwrap - - unless head.is_a? Mal::Symbol - invoke_list(list, env) - end - - return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a?(Mal::List) || params.is_a?(Mal::Vector) - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - else - invoke_list(list, env) - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ eval(args[0], $repl_env) }) -rep "(def! not (fn* (a) (if a false true)))" -rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) - -unless ARGV.empty? - if ARGV.size > 1 - ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) - end - end - - rep "(load-file \"#{ARGV[0]}\")" - exit -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step7_quote.cr b/src/step7_quote.cr deleted file mode 100755 index 2c43818..0000000 --- a/src/step7_quote.cr +++ /dev/null @@ -1,207 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Array(Mal::Type) - val.map{|n| eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def read(str) - read_str str -end - -macro is_pair(list) - {{list}}.is_a?(Array) && !{{list}}.empty? -end - -def quasiquote(ast) - list = ast.unwrap - - unless is_pair(list) - return Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "quote") << ast - ) - end - - head = list.first.unwrap - - case - # ("unquote" ...) - when head.is_a?(Mal::Symbol) && head.str == "unquote" - list[1] - # (("splice-unquote" ...) ...) - when is_pair(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) - ) - else - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) - ) - end -end - -macro invoke_list(l, env) - f = eval({{l}}.first, {{env}}).unwrap - args = eval_ast({{l}}[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, {{env}}) - case f - when Mal::Closure - ast = f.ast - {{env}} = Mal::Env.new(f.env, f.params, args) - next # TCO - when Mal::Func - return f.call args - else - eval_error "expected function as the first argument" - end -end - -def eval(ast, env) - # 'next' in 'do...end' has a bug in crystal 0.7.1 - # https://github.com/manastech/crystal/issues/659 - while true - list = ast.unwrap - - return eval_ast(ast, env) unless list.is_a? Mal::List - return gen_type Mal::List if list.empty? - - head = list.first.unwrap - - unless head.is_a? Mal::Symbol - return invoke_list(list, env) - end - - return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a?(Mal::List) || params.is_a?(Mal::Vector) - eval_error "'fn*' parameters must be list" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - else - invoke_list(list, env) - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ eval(args[0], $repl_env) }) -rep "(def! not (fn* (a) (if a false true)))" -rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) - -unless ARGV.empty? - if ARGV.size > 1 - ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) - end - end - - begin - rep "(load-file \"#{ARGV[0]}\")" - rescue e - STDERR.puts e - end - exit -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step8_macros.cr b/src/step8_macros.cr deleted file mode 100755 index 73f1470..0000000 --- a/src/step8_macros.cr +++ /dev/null @@ -1,253 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Array(Mal::Type) - val.map{|n| eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def read(str) - read_str str -end - -macro pair?(list) - {{list}}.is_a?(Array) && !{{list}}.empty? -end - -def quasiquote(ast) - list = ast.unwrap - - unless pair?(list) - return Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "quote") << ast - ) - end - - head = list.first.unwrap - - case - # ("unquote" ...) - when head.is_a?(Mal::Symbol) && head.str == "unquote" - list[1] - # (("splice-unquote" ...) ...) - when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) - ) - else - tail = Mal::Type.new list[1..-1].each_with_object(Mal::List.new){|e,l| l << e} - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) - ) - end -end - -def macro_call?(ast, env) - list = ast.unwrap - return false unless list.is_a? Mal::List - - sym = list.first.unwrap - return false unless sym.is_a? Mal::Symbol - - func = env.find(sym.str).try(&.data[sym.str]) - return false unless func && func.macro? - - true -end - -def macroexpand(ast, env) - while macro_call?(ast, env) - - # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol - func = env.get(func_sym.str).unwrap - - case func - when Mal::Func - ast = func.call(list[1..-1]) - when Mal::Closure - ast = func.fn.call(list[1..-1]) - else - eval_error "macro '#{func_sym.str}' must be function: #{ast}" - end - end - - ast -end - -macro invoke_list(l, env) - f = eval({{l}}.first, {{env}}).unwrap - args = eval_ast({{l}}[1..-1].each_with_object(Mal::List.new){|i, l| l << i}, {{env}}) - case f - when Mal::Closure - ast = f.ast - {{env}} = Mal::Env.new(f.env, f.params, args) - next # TCO - when Mal::Func - return f.call args - else - eval_error "expected function as the first argument: #{f}" - end -end - -def eval(ast, env) - # 'next' in 'do...end' has a bug in crystal 0.7.1 - # https://github.com/manastech/crystal/issues/659 - while true - return eval_ast(ast, env) unless ast.unwrap.is_a? Mal::List - - ast = macroexpand(ast, env) - - list = ast.unwrap - - return ast unless list.is_a? Mal::List - return ast if list.empty? - - head = list.first.unwrap - - return invoke_list(list, env) unless head.is_a? Mal::Symbol - - return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].each_with_object(Mal::List.new){|i,l| l << i}, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list or vector: #{params}" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - when "defmacro!" - eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) - when "macroexpand" - macroexpand(list[1], env) - else - invoke_list(list, env) - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ eval(args[0], $repl_env) }) -rep "(def! not (fn* (a) (if a false true)))" -rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" -rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) - -unless ARGV.empty? - if ARGV.size > 1 - ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) - end - end - - begin - rep "(load-file \"#{ARGV[0]}\")" - rescue e - STDERR.puts e - end - exit -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/step9_try.cr b/src/step9_try.cr deleted file mode 100755 index 3d2c964..0000000 --- a/src/step9_try.cr +++ /dev/null @@ -1,270 +0,0 @@ -#! /usr/bin/env crystal run - -require "./readline" -require "./reader" -require "./printer" -require "./types" -require "./env" -require "./core" -require "./error" - -# Note: -# Employed downcase names because Crystal prohibits uppercase names for methods - -def func_of(env, binds, body) - -> (args : Array(Mal::Type)) { - new_env = Mal::Env.new(env, binds, args) - eval(body, new_env) - } as Mal::Func -end - -def eval_ast(ast, env) - return ast.map{|n| eval(n, env) as Mal::Type} if ast.is_a? Mal::List - - val = ast.unwrap - - Mal::Type.new case val - when Mal::Symbol - if e = env.get(val.str) - e - else - eval_error "'#{val.str}' not found" - end - when Mal::List - val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)} - when Mal::Vector - val.each_with_object(Mal::Vector.new){|n, l| l << eval(n, env)} - when Array(Mal::Type) - val.map{|n| eval(n, env)} - when Mal::HashMap - val.each{|k, v| val[k] = eval(v, env)} - val - else - val - end -end - -def read(str) - read_str str -end - -macro pair?(list) - {{list}}.is_a?(Array) && !{{list}}.empty? -end - -def quasiquote(ast) - list = ast.unwrap - - unless pair?(list) - return Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "quote") << ast - ) - end - - head = list.first.unwrap - - case - # ("unquote" ...) - when head.is_a?(Mal::Symbol) && head.str == "unquote" - list[1] - # (("splice-unquote" ...) ...) - when pair?(head) && (arg0 = head.first.unwrap).is_a?(Mal::Symbol) && arg0.str == "splice-unquote" - tail = Mal::Type.new list[1..-1].to_mal - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "concat") << head[1] << quasiquote(tail) - ) - else - tail = Mal::Type.new list[1..-1].to_mal - Mal::Type.new( - Mal::List.new << gen_type(Mal::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail) - ) - end -end - -def macro_call?(ast, env) - list = ast.unwrap - return false unless list.is_a? Mal::List - - sym = list.first.unwrap - return false unless sym.is_a? Mal::Symbol - - func = env.find(sym.str).try(&.data[sym.str]) - return false unless func && func.macro? - - true -end - -def macroexpand(ast, env) - while macro_call?(ast, env) - - # Already checked in macro_call? - list = ast.unwrap as Mal::List - func_sym = list[0].unwrap as Mal::Symbol - func = env.get(func_sym.str).unwrap - - case func - when Mal::Func - ast = func.call(list[1..-1]) - when Mal::Closure - ast = func.fn.call(list[1..-1]) - else - eval_error "macro '#{func_sym.str}' must be function: #{ast}" - end - end - - ast -end - -macro invoke_list(l, env) - f = eval({{l}}.first, {{env}}).unwrap - args = eval_ast({{l}}[1..-1].to_mal, {{env}}) - case f - when Mal::Closure - ast = f.ast - {{env}} = Mal::Env.new(f.env, f.params, args) - next # TCO - when Mal::Func - return f.call args - else - eval_error "expected function as the first argument: #{f}" - end -end - -def eval(ast, env) - # 'next' in 'do...end' has a bug in crystal 0.7.1 - # https://github.com/manastech/crystal/issues/659 - while true - return eval_ast(ast, env) unless ast.unwrap.is_a? Mal::List - - ast = macroexpand(ast, env) - - list = ast.unwrap - - return ast unless list.is_a? Mal::List - return ast if list.empty? - - head = list.first.unwrap - - return invoke_list(list, env) unless head.is_a? Mal::Symbol - - return Mal::Type.new case head.str - when "def!" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env)) - when "let*" - eval_error "wrong number of argument for 'def!'" unless list.size == 3 - - bindings = list[1].unwrap - eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array - eval_error "size of binding list must be even" unless bindings.size.even? - - new_env = Mal::Env.new env - bindings.each_slice(2) do |binding| - key, value = binding - name = key.unwrap - eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Mal::Symbol - new_env.set(name.str, eval(value, new_env)) - end - - ast, env = list[2], new_env - next # TCO - when "do" - if list.empty? - ast = Mal::Type.new nil - next - end - - eval_ast(list[1..-2].to_mal, env) - ast = list.last - next # TCO - when "if" - ast = unless eval(list[1], env).unwrap - list.size >= 4 ? list[3] : Mal::Type.new(nil) - else - list[2] - end - next # TCO - when "fn*" - params = list[1].unwrap - unless params.is_a? Array - eval_error "'fn*' parameters must be list or vector: #{params}" - end - Mal::Closure.new(list[2], params, env, func_of(env, params, list[2])) - when "quote" - list[1] - when "quasiquote" - ast = quasiquote list[1] - next # TCO - when "defmacro!" - eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3 - a1 = list[1].unwrap - eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Mal::Symbol - env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true}) - when "macroexpand" - macroexpand(list[1], env) - when "try*" - catch_list = list[2].unwrap - return eval(list[1], env) unless catch_list.is_a? Mal::List - - catch_head = catch_list.first.unwrap - return eval(list[1], env) unless catch_head.is_a? Mal::Symbol - return eval(list[1], env) unless catch_head.str == "catch*" - - begin - eval(list[1], env) - rescue e : Mal::RuntimeException - new_env = Mal::Env.new(env, [catch_list[1]], [e.thrown]) - eval(catch_list[2], new_env) - rescue e - new_env = Mal::Env.new(env, [catch_list[1]], [Mal::Type.new e.message]) - eval(catch_list[2], new_env) - end - else - invoke_list(list, env) - end - end -end - -def print(result) - pr_str(result, true) -end - -def rep(str) - print(eval(read(str), $repl_env)) -end - -$repl_env = Mal::Env.new nil -Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))} -$repl_env.set("eval", Mal::Type.new -> (args: Array(Mal::Type)){ eval(args[0], $repl_env) }) -rep "(def! not (fn* (a) (if a false true)))" -rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" -rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" -rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" - -$argv = Mal::List.new -$repl_env.set("*ARGV*", Mal::Type.new $argv) - -unless ARGV.empty? - if ARGV.size > 1 - ARGV[1..-1].each do |a| - $argv << Mal::Type.new(a) - end - end - - begin - rep "(load-file \"#{ARGV[0]}\")" - rescue e - STDERR.puts e - end - exit -end - -while line = my_readline("user> ") - begin - puts rep(line) - rescue e - STDERR.puts e - end -end diff --git a/src/types.cr b/src/types.cr index af61cb7..b8009a5 100644 --- a/src/types.cr +++ b/src/types.cr @@ -1,6 +1,6 @@ require "./printer" -module Mal +module Crisp class Symbol property :str def initialize(@str) @@ -81,7 +81,7 @@ module Mal macro rel_op(*ops) {% for op in ops %} - def {{op.id}}(other : Mal::Type) + def {{op.id}}(other : Crisp::Type) l, r = @val, other.unwrap {% for t in [Int32, String] %} if l.is_a?({{t}}) && r.is_a?({{t}}) @@ -103,11 +103,11 @@ module Mal end macro gen_type(t, *args) - Mal::Type.new {{t.id}}.new({{*args}}) + Crisp::Type.new {{t.id}}.new({{*args}}) end class Array - def to_mal(t = Mal::List) + def to_crisp_value(t = Crisp::List) each_with_object(t.new){|e, l| l << e} end end