#! /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?(Int64) && y.is_a?(Int64) Mal::Type.new func.call(x, y) } end REPL_ENV = Mal::Env.new nil REPL_ENV.set("+", Mal::Type.new num_func(->(x : Int64, y : Int64) { x + y })) REPL_ENV.set("-", Mal::Type.new num_func(->(x : Int64, y : Int64) { x - y })) REPL_ENV.set("*", Mal::Type.new num_func(->(x : Int64, y : Int64) { x * y })) REPL_ENV.set("/", Mal::Type.new num_func(->(x : Int64, y : Int64) { x / y })) module Mal extend self 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) return gen_type Mal::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 end while line = Readline.readline("user> ", true) begin puts Mal.rep(line) rescue e : Mal::RuntimeException STDERR.puts "Error: #{pr_str(e.thrown, true)}" rescue e STDERR.puts "Error: #{e}" end end