crystal: start step4, implement 'do', 'if' and 'fn*'
authorrhysd <lin90162@yahoo.co.jp>
Thu, 7 May 2015 18:04:42 +0000 (03:04 +0900)
committerrhysd <lin90162@yahoo.co.jp>
Tue, 2 Jun 2015 17:26:58 +0000 (02:26 +0900)
crystal/env.cr
crystal/step4_if_fn_do.cr [new file with mode: 0755]

index 84e0f4f..2924924 100644 (file)
@@ -9,6 +9,20 @@ module Mal
       @data = {} of String => Mal::Type
     end
 
+    def initialize(@outer, binds, exprs : Array(Mal::Type))
+      @data = {} of String => Mal::Type
+
+      raise EvalException.new "binds must be list or vector" unless binds.is_a?(Array)
+
+      # Note:
+      # Array#zip() can't be used because overload resolution failed
+      (0..binds.size).each do |idx|
+        sym, expr = binds[idx], exprs[idx]
+        raise EvalException.new "bind list must be symbol" unless sym.is_a?(Mal::Symbol)
+        @data[sym.val] = expr
+      end
+    end
+
     def set(key, value)
       @data[key] = value
     end
diff --git a/crystal/step4_if_fn_do.cr b/crystal/step4_if_fn_do.cr
new file mode 100755 (executable)
index 0000000..051f8aa
--- /dev/null
@@ -0,0 +1,128 @@
+#! /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], args[1]
+    eval_error "invalid arguments" unless x.is_a?(Int32) && y.is_a?(Int32)
+    func.call(x, y) as Mal::Type
+  } as Mal::Func
+end
+
+def func_of(env, binds, body) : Mal::Type
+  -> (args : Array(Mal::Type)) {
+    new_env = Mal::Env.new(env, binds, args)
+    eval(body, new_env) as Mal::Type
+  } as Mal::Func
+end
+
+$repl_env = Mal::Env.new nil
+$repl_env.set("+", num_func(->(x : Int32, y : Int32){ x + y }))
+$repl_env.set("-", num_func(->(x : Int32, y : Int32){ x - y }))
+$repl_env.set("*", num_func(->(x : Int32, y : Int32){ x * y }))
+$repl_env.set("/", num_func(->(x : Int32, y : Int32){ x / y }))
+
+def eval_ast(ast, env)
+  case ast
+  when Mal::Symbol
+    if e = env.get(ast.val)
+      e
+    else
+      eval_error "'#{ast.val}' not found"
+    end
+  when Mal::List
+    ast.map{|n| eval(n, env) as Mal::Type}
+  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(ast, env)
+  return eval_ast(ast, env) unless ast.is_a?(Mal::List)
+
+  eval_error "empty list" if ast.empty?
+
+  sym = ast.first
+  eval_error "first element of list must be a symbol" unless sym.is_a?(Mal::Symbol)
+
+  case sym.val
+  when "def!"
+    eval_error "wrong number of argument for 'def!'" unless ast.size == 3
+    a1 = ast[1]
+    eval_error "1st argument of 'def!' must be symbol" unless a1.is_a?(Mal::Symbol)
+    env.set(a1.val, eval(ast[2], env) as Mal::Type)
+  when "let*"
+    eval_error "wrong number of argument for 'def!'" unless ast.size == 3
+
+    bindings = ast[1]
+    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
+      eval_error "name of binding must be specified as symbol" unless name.is_a?(Mal::Symbol)
+      new_env.set(name.val, eval(value, new_env))
+    end
+
+    eval(ast[2], new_env)
+  when "do"
+    ast.shift(1)
+    eval_ast(ast, env).last
+  when "if"
+    cond = eval(ast[1], env)
+    case cond
+    when Nil
+      ast.size >= 4 ? eval(ast[3], env) : nil
+    when false
+      ast.size >= 4 ?  eval(ast[3], env) : nil
+    else
+      eval(ast[2], env)
+    end
+  when "fn*"
+    # Note:
+    # If writing lambda expression here directly, compiler will fail to infer type of 'ast'. (Error 'Nil for empty?')
+    func_of(env, ast[1], ast[2])
+  else
+    f = eval_ast(sym, env)
+    eval_error "expected function symbol as the first symbol of list" unless f.is_a?(Mal::Func)
+    ast.shift(1)
+    f.call(eval_ast(ast, env).each_with_object([] of Mal::Type){|e, a| a << e})
+  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