Update Crystal implementation
authorOmar Roth <omarroth@hotmail.com>
Sat, 27 Oct 2018 20:20:36 +0000 (15:20 -0500)
committerOmar Roth <omarroth@hotmail.com>
Mon, 29 Oct 2018 23:20:02 +0000 (18:20 -0500)
20 files changed:
README.md
crystal/Makefile
crystal/core.cr
crystal/env.cr
crystal/error.cr
crystal/printer.cr
crystal/reader.cr
crystal/readline.cr [deleted file]
crystal/step0_repl.cr
crystal/step1_read_print.cr
crystal/step2_eval.cr
crystal/step3_env.cr
crystal/step4_if_fn_do.cr
crystal/step5_tco.cr
crystal/step6_file.cr
crystal/step7_quote.cr
crystal/step8_macros.cr
crystal/step9_try.cr
crystal/stepA_mal.cr
crystal/types.cr

index aa10fe1..ecda6eb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -290,7 +290,7 @@ coffee ./stepX_YYY
 
 *The Crystal implementation of mal was created by [Linda_pp](https://github.com/rhysd)*
 
-The Crystal implementation of mal has been tested with Crystal 0.18.4.
+The Crystal implementation of mal has been tested with Crystal 0.26.1.
 
 ```
 cd crystal
index a26b02d..8373692 100644 (file)
@@ -2,7 +2,6 @@ STEPS = step0_repl.cr step1_read_print.cr step2_eval.cr step3_env.cr \
         step4_if_fn_do.cr step5_tco.cr step6_file.cr step7_quote.cr \
        step8_macros.cr step9_try.cr stepA_mal.cr
 
-STEP0_DEPS = readline.cr
 STEP1_DEPS = $(STEP0_DEPS) reader.cr printer.cr
 STEP2_DEPS = $(STEP1_DEPS) types.cr
 STEP3_DEPS = $(STEP2_DEPS) env.cr
@@ -19,7 +18,7 @@ mal: $(LAST_STEP_BIN)
        cp $< $@
 
 $(STEP_BINS): %: %.cr
-       crystal compile --release $<
+       crystal build --release $<
 
 step0_repl: $(STEP0_DEPS)
 step1_read_print: $(STEP1_DEPS)
@@ -30,7 +29,7 @@ step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal
 clean:
        rm -rf $(STEP_BINS) mal .crystal
 
-stats: types.cr error.cr readline.cr reader.cr printer.cr env.cr core.cr stepA_mal.cr
+stats: types.cr error.cr reader.cr printer.cr env.cr core.cr stepA_mal.cr
        @wc $^
        @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
 stats-lisp: env.cr core.cr stepA_mal.cr
dissimilarity index 97%
index 2694cf8..52b66c8 100644 (file)
-require "time"
-
-require "./types"
-require "./error"
-require "./printer"
-require "./reader"
-require "./readline"
-
-module Mal
-
-macro calc_op(op)
-  -> (args : Array(Mal::Type)) {
-    x, y = args[0].unwrap, args[1].unwrap
-    eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64)
-    Mal::Type.new(x {{op.id}} y)
-  }
-end
-
-def self.list(args)
-  args.to_mal
-end
-
-def self.list?(args)
-  args.first.unwrap.is_a? Mal::List
-end
-
-def self.empty?(args)
-  a = args.first.unwrap
-  a.is_a?(Array) ? a.empty? : false
-end
-
-def self.count(args)
-  a = args.first.unwrap
-  case a
-  when Array
-    a.size.to_i64
-  when Nil
-    0i64
-  else
-    eval_error "invalid argument for function 'count'"
-  end
-end
-
-def self.pr_str_(args)
-  args.map{|a| pr_str(a)}.join(" ")
-end
-
-def self.str(args)
-  args.map{|a| pr_str(a, false)}.join
-end
-
-def self.prn(args)
-  puts self.pr_str_(args)
-  nil
-end
-
-def self.println(args)
-  puts args.map{|a| pr_str(a, false)}.join(" ")
-  nil
-end
-
-def self.read_string(args)
-  head = args.first.unwrap
-  eval_error "argument of read-str must be string" unless head.is_a? String
-  read_str head
-end
-
-def self.slurp(args)
-  head = args.first.unwrap
-  eval_error "argument of slurp must be string" unless head.is_a? String
-  begin
-    File.read head
-  rescue e : Errno
-    eval_error "no such file"
-  end
-end
-
-def self.cons(args)
-  head, tail = args[0] as Mal::Type, args[1].unwrap
-  eval_error "2nd arg of cons must be list" unless tail.is_a? Array
-  ([head] + tail).to_mal
-end
-
-def self.concat(args)
-  args.each_with_object(Mal::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}
-  end
-end
-
-def self.nth(args)
-  a0, a1 = args[0].unwrap, args[1].unwrap
-  eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array
-  eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64
-  a0[a1]
-end
-
-def self.first(args)
-  a0 = args[0].unwrap
-
-  return nil if a0.nil?
-  eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array
-  a0.empty? ? nil : a0.first
-end
-
-def self.rest(args)
-  a0 = args[0].unwrap
-
-  return Mal::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
-end
-
-def self.apply(args)
-  eval_error "apply must take at least 2 arguments" unless args.size >= 2
-
-  head = args.first.unwrap
-  last = args.last.unwrap
-
-  eval_error "last argument of apply must be list or vector" unless last.is_a? Array
-
-  case head
-  when Mal::Closure
-    head.fn.call(args[1..-2] + last)
-  when Mal::Func
-    head.call(args[1..-2] + last)
-  else
-    eval_error "1st argument of apply must be function or closure"
-  end
-end
-
-def self.map(args)
-  func = args.first.unwrap
-  list = args[1].unwrap
-
-  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
-      else                   eval_error "1st argument of map must be function"
-      end
-
-  list.each_with_object(Mal::List.new) do |elem, mapped|
-    mapped << f.call([elem])
-  end
-end
-
-def self.nil_value?(args)
-  args.first.unwrap.nil?
-end
-
-def self.true?(args)
-  a = args.first.unwrap
-  a.is_a?(Bool) && a
-end
-
-def self.false?(args)
-  a = args.first.unwrap
-  a.is_a?(Bool) && !a
-end
-
-def self.symbol?(args)
-  args.first.unwrap.is_a?(Mal::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
-end
-
-def self.string?(args)
-  head = args.first.unwrap
-  head.is_a?(String) && (head.empty? || head[0] != '\u029e')
-end
-
-def self.keyword(args)
-  head = args.first.unwrap
-  eval_error "1st argument of symbol function must be string" unless head.is_a? String
-  "\u029e" + head
-end
-
-def self.keyword?(args)
-  head = args.first.unwrap
-  head.is_a?(String) && !head.empty? && head[0] == '\u029e'
-end
-
-def self.number?(args)
-  args.first.unwrap.is_a?(Int64)
-end
-
-def self.fn?(args)
-  return false if args.first.macro?
-  head = args.first.unwrap
-  head.is_a?(Mal::Func) || head.is_a?(Mal::Closure)
-end
-
-def self.macro?(args)
-  args.first.macro?
-end
-
-def self.vector(args)
-  args.to_mal(Mal::Vector)
-end
-
-def self.vector?(args)
-  args.first.unwrap.is_a? Mal::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
-  args.each_slice(2) do |kv|
-    k = kv[0].unwrap
-    eval_error "key must be string" unless k.is_a? String
-    map[k] = kv[1]
-  end
-  map
-end
-
-def self.map?(args)
-  args.first.unwrap.is_a? Mal::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 "assoc must take a list and even number of arguments" unless (args.size - 1).even?
-
-  map = Mal::HashMap.new
-  head.each{|k, v| map[k] = v}
-
-  args[1..-1].each_slice(2) do |kv|
-    k = kv[0].unwrap
-    eval_error "key must be string" unless k.is_a? String
-    map[k] = kv[1]
-  end
-
-  map
-end
-
-def self.dissoc(args)
-  head = args.first.unwrap
-  eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap
-
-  map = Mal::HashMap.new
-  head.each{|k,v| map[k] = v}
-
-  args[1..-1].each do |arg|
-    key = arg.unwrap
-    eval_error "key must be string" unless key.is_a? String
-    map.delete key
-  end
-
-  map
-end
-
-def self.get(args)
-  a0, a1 = args[0].unwrap, args[1].unwrap
-  return nil unless a0.is_a? Mal::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
-  a0.has_key?(a1) ? a0[a1] : nil
-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 "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)}
-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
-end
-
-def self.sequential?(args)
-  args.first.unwrap.is_a? Array
-end
-
-def self.readline(args)
-  head = args.first.unwrap
-  eval_error "1st argument of readline must be string" unless head.is_a? String
-  my_readline head
-end
-
-def self.meta(args)
-  m = args.first.meta
-  m.nil? ? nil : m
-end
-
-def self.with_meta(args)
-  t = args.first.dup
-  t.meta = args[1]
-  t
-end
-
-def self.atom(args)
-  Mal::Atom.new args.first
-end
-
-def self.atom?(args)
-  args.first.unwrap.is_a? Mal::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
-  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
-  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
-
-  a = [atom.val] + args[2..-1]
-
-  func = args[1].unwrap
-  case func
-  when Mal::Func
-    atom.val = func.call a
-  when Mal::Closure
-    atom.val = func.fn.call a
-  else
-    eval_error "2nd argumetn of swap! must be function"
-  end
-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)
-  else
-    eval_error "1st argument of conj must be list or vector"
-  end
-end
-
-def self.seq(args)
-  obj = args.first.unwrap
-  case obj
-  when nil
-    nil
-  when Mal::List
-    return nil if obj.empty?
-    obj
-  when Mal::Vector
-    return nil if obj.empty?
-    obj.to_mal
-  when String
-    return nil if obj.empty?
-    obj.split("").each_with_object(Mal::List.new){|e,l| l << Mal::Type.new(e)}
-  else
-    eval_error "argument of seq must be list or vector or string or nil"
-  end
-end
-
-def self.time_ms(args)
-  Time.now.epoch_ms.to_i64
-end
-
-# Note:
-# Simply using ->self.some_func doesn't work
-macro func(name)
-  -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) }
-end
-
-macro rel_op(op)
--> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) }
-end
-
-NS = {
-  "+"           => calc_op(:+),
-  "-"           => calc_op(:-),
-  "*"           => calc_op(:*),
-  "/"           => calc_op(:/),
-  "list"        => func(:list),
-  "list?"       => func(:list?),
-  "empty?"      => func(:empty?),
-  "count"       => func(:count),
-  "="           => rel_op(:==),
-  "<"           => rel_op(:<),
-  ">"           => rel_op(:>),
-  "<="          => rel_op(:<=),
-  ">="          => rel_op(:>=),
-  "pr-str"      => func(:pr_str_),
-  "str"         => func(:str),
-  "prn"         => func(:prn),
-  "println"     => func(:println),
-  "read-string" => func(:read_string),
-  "slurp"       => func(:slurp),
-  "cons"        => func(:cons),
-  "concat"      => func(:concat),
-  "nth"         => func(:nth),
-  "first"       => func(:first),
-  "rest"        => func(:rest),
-  "throw"       => -> (args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] },
-  "apply"       => func(:apply),
-  "map"         => func(:map),
-  "nil?"        => func(:nil_value?),
-  "true?"       => func(:true?),
-  "false?"      => func(:false?),
-  "symbol?"     => func(:symbol?),
-  "symbol"      => func(:symbol),
-  "string?"     => func(:string?),
-  "keyword"     => func(:keyword),
-  "keyword?"    => func(:keyword?),
-  "number?"     => func(:number?),
-  "fn?"         => func(:fn?),
-  "macro?"      => func(:macro?),
-  "vector"      => func(:vector),
-  "vector?"     => func(:vector?),
-  "hash-map"    => func(:hash_map),
-  "map?"        => func(:map?),
-  "assoc"       => func(:assoc),
-  "dissoc"      => func(:dissoc),
-  "get"         => func(:get),
-  "contains?"   => func(:contains?),
-  "keys"        => func(:keys),
-  "vals"        => func(:vals),
-  "sequential?" => func(:sequential?),
-  "readline"    => func(:readline),
-  "meta"        => func(:meta),
-  "with-meta"   => func(:with_meta),
-  "atom"        => func(:atom),
-  "atom?"       => func(:atom?),
-  "deref"       => func(:deref),
-  "deref"       => func(:deref),
-  "reset!"      => func(:reset!),
-  "swap!"       => func(:swap!),
-  "conj"        => func(:conj),
-  "seq"         => func(:seq),
-  "time-ms"     => func(:time_ms),
-} of String => Mal::Func
-
-end
+require "time"
+
+require "readline"
+require "./types"
+require "./error"
+require "./printer"
+require "./reader"
+
+module Mal
+  macro calc_op(op)
+    -> (args : Array(Mal::Type)) {
+      x, y = args[0].unwrap, args[1].unwrap
+      eval_error "invalid arguments for binary operator {{op.id}}" unless x.is_a?(Int64) && y.is_a?(Int64)
+      Mal::Type.new(x {{op.id}} y)
+    }
+  end
+
+  def self.list(args)
+    args.to_mal
+  end
+
+  def self.list?(args)
+    args.first.unwrap.is_a? Mal::List
+  end
+
+  def self.empty?(args)
+    a = args.first.unwrap
+    a.is_a?(Array) ? a.empty? : false
+  end
+
+  def self.count(args)
+    a = args.first.unwrap
+    case a
+    when Array
+      a.size.to_i64
+    when Nil
+      0i64
+    else
+      eval_error "invalid argument for function 'count'"
+    end
+  end
+
+  def self.pr_str_(args)
+    args.map { |a| pr_str(a) }.join(" ")
+  end
+
+  def self.str(args)
+    args.map { |a| pr_str(a, false) }.join
+  end
+
+  def self.prn(args)
+    puts self.pr_str_(args)
+    nil
+  end
+
+  def self.println(args)
+    puts args.map { |a| pr_str(a, false) }.join(" ")
+    nil
+  end
+
+  def self.read_string(args)
+    head = args.first.unwrap
+    eval_error "argument of read-str must be string" unless head.is_a? String
+    read_str head
+  end
+
+  def self.slurp(args)
+    head = args.first.unwrap
+    eval_error "argument of slurp must be string" unless head.is_a? String
+    begin
+      File.read head
+    rescue e : Errno
+      eval_error "no such file"
+    end
+  end
+
+  def self.cons(args)
+    head, tail = args[0].as(Mal::Type), args[1].unwrap
+    eval_error "2nd arg of cons must be list" unless tail.is_a? Array
+    ([head] + tail).to_mal
+  end
+
+  def self.concat(args)
+    args.each_with_object(Mal::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 }
+    end
+  end
+
+  def self.nth(args)
+    a0, a1 = args[0].unwrap, args[1].unwrap
+    eval_error "1st argument of nth must be list or vector" unless a0.is_a? Array
+    eval_error "2nd argument of nth must be integer" unless a1.is_a? Int64
+    a0[a1]
+  end
+
+  def self.first(args)
+    a0 = args[0].unwrap
+
+    return nil if a0.nil?
+    eval_error "1st argument of first must be list or vector or nil" unless a0.is_a? Array
+    a0.empty? ? nil : a0.first
+  end
+
+  def self.rest(args)
+    a0 = args[0].unwrap
+
+    return Mal::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
+  end
+
+  def self.apply(args)
+    eval_error "apply must take at least 2 arguments" unless args.size >= 2
+
+    head = args.first.unwrap
+    last = args.last.unwrap
+
+    eval_error "last argument of apply must be list or vector" unless last.is_a? Array
+
+    case head
+    when Mal::Closure
+      head.fn.call(args[1..-2] + last)
+    when Mal::Func
+      head.call(args[1..-2] + last)
+    else
+      eval_error "1st argument of apply must be function or closure"
+    end
+  end
+
+  def self.map(args)
+    func = args.first.unwrap
+    list = args[1].unwrap
+
+    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
+        else                   eval_error "1st argument of map must be function"
+        end
+
+    list.each_with_object(Mal::List.new) do |elem, mapped|
+      mapped << f.call([elem])
+    end
+  end
+
+  def self.nil_value?(args)
+    args.first.unwrap.nil?
+  end
+
+  def self.true?(args)
+    a = args.first.unwrap
+    a.is_a?(Bool) && a
+  end
+
+  def self.false?(args)
+    a = args.first.unwrap
+    a.is_a?(Bool) && !a
+  end
+
+  def self.symbol?(args)
+    args.first.unwrap.is_a?(Mal::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
+  end
+
+  def self.string?(args)
+    head = args.first.unwrap
+    head.is_a?(String) && (head.empty? || head[0] != '\u029e')
+  end
+
+  def self.keyword(args)
+    head = args.first.unwrap
+    eval_error "1st argument of symbol function must be string" unless head.is_a? String
+    "\u029e" + head
+  end
+
+  def self.keyword?(args)
+    head = args.first.unwrap
+    head.is_a?(String) && !head.empty? && head[0] == '\u029e'
+  end
+
+  def self.number?(args)
+    args.first.unwrap.is_a?(Int64)
+  end
+
+  def self.fn?(args)
+    return false if args.first.macro?
+    head = args.first.unwrap
+    head.is_a?(Mal::Func) || head.is_a?(Mal::Closure)
+  end
+
+  def self.macro?(args)
+    args.first.macro?
+  end
+
+  def self.vector(args)
+    args.to_mal(Mal::Vector)
+  end
+
+  def self.vector?(args)
+    args.first.unwrap.is_a? Mal::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
+    args.each_slice(2) do |kv|
+      k = kv[0].unwrap
+      eval_error "key must be string" unless k.is_a? String
+      map[k] = kv[1]
+    end
+    map
+  end
+
+  def self.map?(args)
+    args.first.unwrap.is_a? Mal::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 "assoc must take a list and even number of arguments" unless (args.size - 1).even?
+
+    map = Mal::HashMap.new
+    head.each { |k, v| map[k] = v }
+
+    args[1..-1].each_slice(2) do |kv|
+      k = kv[0].unwrap
+      eval_error "key must be string" unless k.is_a? String
+      map[k] = kv[1]
+    end
+
+    map
+  end
+
+  def self.dissoc(args)
+    head = args.first.unwrap
+    eval_error "1st argument of assoc must be hashmap" unless head.is_a? Mal::HashMap
+
+    map = Mal::HashMap.new
+    head.each { |k, v| map[k] = v }
+
+    args[1..-1].each do |arg|
+      key = arg.unwrap
+      eval_error "key must be string" unless key.is_a? String
+      map.delete key
+    end
+
+    map
+  end
+
+  def self.get(args)
+    a0, a1 = args[0].unwrap, args[1].unwrap
+    return nil unless a0.is_a? Mal::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
+    a0.has_key?(a1) ? a0[a1] : nil
+  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 "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) }
+  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
+  end
+
+  def self.sequential?(args)
+    args.first.unwrap.is_a? Array
+  end
+
+  def self.readline(args)
+    head = args.first.unwrap
+    eval_error "1st argument of readline must be string" unless head.is_a? String
+    Readline.readline(head, true)
+  end
+
+  def self.meta(args)
+    m = args.first.meta
+    m.nil? ? nil : m
+  end
+
+  def self.with_meta(args)
+    t = args.first.dup
+    t.meta = args[1]
+    t
+  end
+
+  def self.atom(args)
+    Mal::Atom.new args.first
+  end
+
+  def self.atom?(args)
+    args.first.unwrap.is_a? Mal::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
+    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
+    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
+
+    a = [atom.val] + args[2..-1]
+
+    func = args[1].unwrap
+    case func
+    when Mal::Func
+      atom.val = func.call a
+    when Mal::Closure
+      atom.val = func.fn.call a
+    else
+      eval_error "2nd argumetn of swap! must be function"
+    end
+  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)
+    else
+      eval_error "1st argument of conj must be list or vector"
+    end
+  end
+
+  def self.seq(args)
+    obj = args.first.unwrap
+    case obj
+    when nil
+      nil
+    when Mal::List
+      return nil if obj.empty?
+      obj
+    when Mal::Vector
+      return nil if obj.empty?
+      obj.to_mal
+    when String
+      return nil if obj.empty?
+      obj.split("").each_with_object(Mal::List.new) { |e, l| l << Mal::Type.new(e) }
+    else
+      eval_error "argument of seq must be list or vector or string or nil"
+    end
+  end
+
+  def self.time_ms(args)
+    Time.now.epoch_ms.to_i64
+  end
+
+  # Note:
+  # Simply using ->self.some_func doesn't work
+  macro func(name)
+    -> (args : Array(Mal::Type)) { Mal::Type.new self.{{name.id}}(args) }
+  end
+
+  macro rel_op(op)
+    -> (args : Array(Mal::Type)) { Mal::Type.new (args[0] {{op.id}} args[1]) }
+  end
+
+  NS = {
+    "+"           => calc_op(:+),
+    "-"           => calc_op(:-),
+    "*"           => calc_op(:*),
+    "/"           => calc_op(:/),
+    "list"        => func(:list),
+    "list?"       => func(:list?),
+    "empty?"      => func(:empty?),
+    "count"       => func(:count),
+    "="           => rel_op(:==),
+    "<"           => rel_op(:<),
+    ">"           => rel_op(:>),
+    "<="          => rel_op(:<=),
+    ">="          => rel_op(:>=),
+    "pr-str"      => func(:pr_str_),
+    "str"         => func(:str),
+    "prn"         => func(:prn),
+    "println"     => func(:println),
+    "read-string" => func(:read_string),
+    "slurp"       => func(:slurp),
+    "cons"        => func(:cons),
+    "concat"      => func(:concat),
+    "nth"         => func(:nth),
+    "first"       => func(:first),
+    "rest"        => func(:rest),
+    "throw"       => ->(args : Array(Mal::Type)) { raise Mal::RuntimeException.new args[0] },
+    "apply"       => func(:apply),
+    "map"         => func(:map),
+    "nil?"        => func(:nil_value?),
+    "true?"       => func(:true?),
+    "false?"      => func(:false?),
+    "symbol?"     => func(:symbol?),
+    "symbol"      => func(:symbol),
+    "string?"     => func(:string?),
+    "keyword"     => func(:keyword),
+    "keyword?"    => func(:keyword?),
+    "number?"     => func(:number?),
+    "fn?"         => func(:fn?),
+    "macro?"      => func(:macro?),
+    "vector"      => func(:vector),
+    "vector?"     => func(:vector?),
+    "hash-map"    => func(:hash_map),
+    "map?"        => func(:map?),
+    "assoc"       => func(:assoc),
+    "dissoc"      => func(:dissoc),
+    "get"         => func(:get),
+    "contains?"   => func(:contains?),
+    "keys"        => func(:keys),
+    "vals"        => func(:vals),
+    "sequential?" => func(:sequential?),
+    "readline"    => func(:readline),
+    "meta"        => func(:meta),
+    "with-meta"   => func(:with_meta),
+    "atom"        => func(:atom),
+    "atom?"       => func(:atom?),
+    "deref"       => func(:deref),
+    "deref"       => func(:deref),
+    "reset!"      => func(:reset!),
+    "swap!"       => func(:swap!),
+    "conj"        => func(:conj),
+    "seq"         => func(:seq),
+    "time-ms"     => func(:time_ms),
+  } of String => Mal::Func
+end
index 572ceea..9c7a462 100644 (file)
@@ -2,7 +2,6 @@ require "./types"
 require "./error"
 
 module Mal
-
   class Env
     property data
 
@@ -23,10 +22,10 @@ module Mal
 
         if sym.str == "&"
           eval_error "missing variable parameter name" if binds.size == idx
-          next_param = binds[idx+1].unwrap
+          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
-          exprs[idx..-1].each{|e| var_args << e} if idx < exprs.size
+          exprs[idx..-1].each { |e| var_args << e } if idx < exprs.size
           @data[next_param.str] = Mal::Type.new var_args
           break
         end
@@ -64,5 +63,4 @@ module Mal
       e.data[key]
     end
   end
-
 end
index b308a8a..fb8f56c 100644 (file)
@@ -9,6 +9,7 @@ module Mal
 
   class RuntimeException < Exception
     getter :thrown
+
     def initialize(@thrown : Type)
       super()
     end
index cb9bf36..b6aeaab 100644 (file)
@@ -5,17 +5,17 @@ def pr_str(value, print_readably = true)
   when Nil          then "nil"
   when Bool         then value.to_s
   when Int64        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::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 "<function>"
   when Mal::Closure then "<closure>"
   when Mal::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(" ")}}"
+    "{#{value.map { |k, v| "#{pr_str(k, print_readably)} #{pr_str(v, print_readably)}".as(String) }.join(" ")}}"
   when String
     case
-    when value.empty?()
+    when value.empty?
       print_readably ? value.inspect : value
     when value[0] == '\u029e'
       ":#{value[1..-1]}"
index 34b4692..3fbbd7c 100644 (file)
@@ -30,7 +30,7 @@ class Reader
   def read_sequence(init, open, close)
     token = self.next
     parse_error "expected '#{open}', got EOF" unless token
-    parse_error "expected '#{open}', got #{token}" unless  token[0] == open
+    parse_error "expected '#{open}', got #{token}" unless token[0] == open
 
     loop do
       token = peek
@@ -81,11 +81,11 @@ class Reader
     when token == "true"    then true
     when token == "false"   then false
     when token == "nil"     then nil
-    when token[0] == '"'    then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"",
-                                                             "\\n"  => "\n",
-                                                             "\\\\" => "\\"})
-    when token[0] == ':'    then "\u029e#{token[1..-1]}"
-    else                         Mal::Symbol.new token
+    when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"",
+                                                          "\\n"  => "\n",
+                                                          "\\\\" => "\\"})
+    when token[0] == ':' then "\u029e#{token[1..-1]}"
+    else                      Mal::Symbol.new token
     end
   end
 
@@ -118,12 +118,11 @@ class Reader
     else read_atom
     end
   end
-
 end
 
 def tokenize(str)
   regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
-  str.scan(regex).map{|m| m[1]}.reject(&.empty?)
+  str.scan(regex).map { |m| m[1] }.reject(&.empty?)
 end
 
 def read_str(str)
@@ -136,4 +135,3 @@ def read_str(str)
     end
   end
 end
-
diff --git a/crystal/readline.cr b/crystal/readline.cr
deleted file mode 100644 (file)
index e57099a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-# Note:
-# Crystal already has "readline" library.
-# I implemented a subset of it again for practice.
-
-@[Link("readline")]
-lib LibReadline
-  fun readline(prompt : UInt8*) : UInt8*
-  fun add_history(line : UInt8*)
-end
-
-def my_readline(prompt = "")
-  line = LibReadline.readline(prompt)
-  if line
-    LibReadline.add_history(line)
-    String.new(line)
-  else
-    nil
-  end
-ensure
-  LibC.free(line as Void*) if line
-end
index e1fe58a..a9c67d6 100755 (executable)
@@ -1,26 +1,26 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 
 # Note:
 # Employed downcase names because Crystal prohibits uppercase names for methods
 
 def read(x)
-    x
+  x
 end
 
 def eval(x)
-    x
+  x
 end
 
 def print(x)
-    x
+  x
 end
 
 def rep(x)
-    read(eval(print(x)))
+  read(eval(print(x)))
 end
 
-while line = my_readline("user> ")
-    puts rep(line)
+while line = Readline.readline("user> ")
+  puts rep(line)
 end
index 9da58c3..cdc05d5 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 
@@ -15,7 +15,7 @@ module Mal
   end
 
   def eval(x)
-      x
+    x
   end
 
   def print(result)
@@ -27,7 +27,7 @@ module Mal
   end
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index f93b957..eeef93c 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -16,7 +16,7 @@ module Mal
   end
 
   def num_func(func)
-    -> (args : Array(Mal::Type)) {
+    ->(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)
@@ -24,7 +24,7 @@ module Mal
   end
 
   def eval_ast(a, env)
-    return a.map{|n| eval(n, env) as Mal::Type} if a.is_a? Mal::List
+    return a.map { |n| eval(n, env).as(Mal::Type) } if a.is_a? Mal::List
     return a unless a
 
     ast = a.unwrap
@@ -36,11 +36,12 @@ module Mal
         eval_error "'#{ast.str}' not found"
       end
     when Mal::List
-      ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      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)}
+      ast.each { |k, v| ast[k] = eval(v, env) }
+      ast
     else
       ast
     end
@@ -74,18 +75,18 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 end
 
-$repl_env = {
-  "+" => Mal.num_func(->(x : Int64, y : Int64){ x + y }),
-  "-" => Mal.num_func(->(x : Int64, y : Int64){ x - y }),
-  "*" => Mal.num_func(->(x : Int64, y : Int64){ x * y }),
-  "/" => Mal.num_func(->(x : Int64, y : Int64){ x / y }),
+REPL_ENV = {
+  "+" => Mal.num_func(->(x : Int64, y : Int64) { x + y }),
+  "-" => Mal.num_func(->(x : Int64, y : Int64) { x - y }),
+  "*" => Mal.num_func(->(x : Int64, y : Int64) { x * y }),
+  "/" => Mal.num_func(->(x : Int64, y : Int64) { x / y }),
 } of String => Mal::Func
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index 62543e8..171dd89 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -14,24 +14,24 @@ def eval_error(msg)
 end
 
 def num_func(func)
-  -> (args : Array(Mal::Type)) {
+  ->(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 }))
+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
+    return a.map { |n| eval(n, env) } if a.is_a? Array
 
     Mal::Type.new case ast = a.unwrap
     when Mal::Symbol
@@ -41,12 +41,12 @@ module Mal
         eval_error "'#{ast.str}' not found"
       end
     when Mal::List
-      ast.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      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)}
+      ast.each { |k, v| new_map[k] = eval(v, env) }
       new_map
     else
       ast
@@ -71,7 +71,7 @@ module Mal
       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)
+      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
 
@@ -93,7 +93,7 @@ module Mal
       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))
+        f2.call(args.as(Array(Mal::Type)))
       else
         eval_error "expected function symbol as the first symbol of list"
       end
@@ -105,11 +105,11 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index de3e65b..c20f130 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,11 +34,11 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      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.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -48,7 +48,7 @@ module Mal
   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)
+    f.call eval_ast(list[1..-1].each_with_object(Mal::List.new) { |i, l| l << i }, env)
   end
 
   def read(str)
@@ -58,7 +58,7 @@ module Mal
   def eval(ast, env)
     list = ast.unwrap
 
-    return eval_ast(ast, env)          unless list.is_a? Mal::List
+    return eval_ast(ast, env) unless list.is_a? Mal::List
     return gen_type Mal::List if list.empty?
 
     head = list.first.unwrap
@@ -96,7 +96,7 @@ module Mal
         when Nil
           list.size >= 4 ? eval(list[3], env) : nil
         when false
-          list.size >= 4 ?  eval(list[3], env) : nil
+          list.size >= 4 ? eval(list[3], env) : nil
         else
           eval(list[2], env)
         end
@@ -117,15 +117,15 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 end
 
-$repl_env = Mal::Env.new nil
-Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))}
+REPL_ENV = Mal::Env.new nil
+Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) }
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ")
   begin
     puts Mal.rep(line)
   rescue e
index 061293f..e56db4a 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,13 +34,13 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Array(Mal::Type)
-      val.map{|n| eval(n, env)}
+      val.map { |n| eval(n, env).as(Mal::Type) }
     when Mal::HashMap
-      val.each{|k, v| val[k] = eval(v, env)}
+      val.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -51,9 +51,9 @@ module Mal
     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)
+      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)
+      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
@@ -94,55 +94,55 @@ module Mal
       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? Array
-            eval_error "'fn*' parameters must be list"
-          end
-          Mal::Closure.new(list[2], params, env, func_of(env, list[1].unwrap, list[2]))
+      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
-          invoke_list list
+          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? Array
+          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
 
@@ -151,15 +151,15 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 end
 
-$repl_env = Mal::Env.new nil
-Mal::NS.each{|k,v| $repl_env.set(k, Mal::Type.new(v))}
+REPL_ENV = Mal::Env.new nil
+Mal::NS.each { |k, v| REPL_ENV.set(k, Mal::Type.new(v)) }
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index 594ed92..9997a2f 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,13 +34,13 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Array(Mal::Type)
-      val.map{|n| eval(n, env)}
+      val.map { |n| eval(n, env).as(Mal::Type) }
     when Mal::HashMap
-      val.each{|k, v| val[k] = eval(v, env)}
+      val.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -51,9 +51,9 @@ module Mal
     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)
+      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)
+      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
@@ -94,53 +94,53 @@ module Mal
       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? Array
-            eval_error "'fn*' parameters must be list"
-          end
-          Mal::Closure.new(list[2], params, env, func_of(env, params, list[2]))
+      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
-          invoke_list(list, env)
+          list[2]
         end
+        next # TCO
+      when "fn*"
+        params = list[1].unwrap
+        unless params.is_a? Array
+          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
 
@@ -149,22 +149,22 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 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)){ Mal.eval(args[0], $repl_env) })
+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)) { Mal.eval(args[0], REPL_ENV) })
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 Mal.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)
+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)
+      argv << Mal::Type.new(a)
     end
   end
 
@@ -172,7 +172,7 @@ unless ARGV.empty?
   exit
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index a1d5708..92d2ab4 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,13 +34,13 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Array(Mal::Type)
-      val.map{|n| eval(n, env)}
+      val.map { |n| eval(n, env).as(Mal::Type) }
     when Mal::HashMap
-      val.each{|k, v| val[k] = eval(v, env)}
+      val.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -70,14 +70,14 @@ module Mal
     # ("unquote" ...)
     when head.is_a?(Mal::Symbol) && head.str == "unquote"
       list[1]
-    # (("splice-unquote" ...) ...)
+      # (("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}
+      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}
+      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)
       )
@@ -115,58 +115,58 @@ module Mal
       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? Array
-            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
+      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
-          invoke_list(list, env)
+          list[2]
         end
+        next # TCO
+      when "fn*"
+        params = list[1].unwrap
+        unless params.is_a? Array
+          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
 
@@ -175,22 +175,22 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 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)){ Mal.eval(args[0], $repl_env) })
+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)) { Mal.eval(args[0], REPL_ENV) })
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 Mal.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)
+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)
+      argv << Mal::Type.new(a)
     end
   end
 
@@ -202,7 +202,7 @@ unless ARGV.empty?
   exit
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index 5f1c63a..061649b 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,13 +34,13 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Array(Mal::Type)
-      val.map{|n| eval(n, env)}
+      val.map { |n| eval(n, env).as(Mal::Type) }
     when Mal::HashMap
-      val.each{|k, v| val[k] = eval(v, env)}
+      val.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -70,14 +70,14 @@ module Mal
     # ("unquote" ...)
     when head.is_a?(Mal::Symbol) && head.str == "unquote"
       list[1]
-    # (("splice-unquote" ...) ...)
+      # (("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}
+      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}
+      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)
       )
@@ -99,10 +99,9 @@ module Mal
 
   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(Mal::List)
+      func_sym = list[0].unwrap.as(Mal::Symbol)
       func = env.get(func_sym.str).unwrap
 
       case func
@@ -151,65 +150,65 @@ module Mal
       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)
+      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
-          invoke_list(list, env)
+          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
 
@@ -218,25 +217,25 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 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)){ Mal.eval(args[0], $repl_env) })
+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)) { Mal.eval(args[0], REPL_ENV) })
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
 Mal.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)))))))"
 Mal.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)
+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)
+      argv << Mal::Type.new(a)
     end
   end
 
@@ -248,7 +247,7 @@ unless ARGV.empty?
   exit
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index 74685d4..4853cc8 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env crystal run
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -15,14 +15,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.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
+    return ast.map { |n| eval(n, env).as(Mal::Type) } if ast.is_a? Mal::List
 
     val = ast.unwrap
 
@@ -34,13 +34,13 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Array(Mal::Type)
-      val.map{|n| eval(n, env)}
+      val.map { |n| eval(n, env).as(Mal::Type) }
     when Mal::HashMap
-      val.each{|k, v| val[k] = eval(v, env)}
+      val.each { |k, v| val[k] = eval(v, env) }
       val
     else
       val
@@ -70,7 +70,7 @@ module Mal
     # ("unquote" ...)
     when head.is_a?(Mal::Symbol) && head.str == "unquote"
       list[1]
-    # (("splice-unquote" ...) ...)
+      # (("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(
@@ -99,10 +99,9 @@ module Mal
 
   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(Mal::List)
+      func_sym = list[0].unwrap.as(Mal::Symbol)
       func = env.get(func_sym.str).unwrap
 
       case func
@@ -151,82 +150,82 @@ module Mal
       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
+      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
-          invoke_list(list, env)
+          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
 
@@ -235,25 +234,25 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 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)){ Mal.eval(args[0], $repl_env) })
+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)) { Mal.eval(args[0], REPL_ENV) })
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
 Mal.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)))))))"
 Mal.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)
+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)
+      argv << Mal::Type.new(a)
     end
   end
 
@@ -265,7 +264,7 @@ unless ARGV.empty?
   exit
 end
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index baaa8a6..f71be26 100755 (executable)
@@ -2,7 +2,7 @@
 
 require "colorize"
 
-require "./readline"
+require "readline"
 require "./reader"
 require "./printer"
 require "./types"
@@ -17,14 +17,14 @@ module Mal
   extend self
 
   def func_of(env, binds, body)
-    -> (args : Array(Mal::Type)) {
+    ->(args : Array(Mal::Type)) {
       new_env = Mal::Env.new(env, binds, args)
       eval(body, new_env)
-    } as Mal::Func
+    }.as(Mal::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(Mal::Type) } if ast.is_a? Array
 
     val = ast.unwrap
 
@@ -36,12 +36,12 @@ module Mal
         eval_error "'#{val.str}' not found"
       end
     when Mal::List
-      val.each_with_object(Mal::List.new){|n, l| l << eval(n, env)}
+      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)}
+      val.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
     when Mal::HashMap
       new_map = Mal::HashMap.new
-      val.each{|k, v| new_map[k] = eval(v, env)}
+      val.each { |k, v| new_map[k] = eval(v, env) }
       new_map
     else
       val
@@ -71,7 +71,7 @@ module Mal
     # ("unquote" ...)
     when head.is_a?(Mal::Symbol) && head.str == "unquote"
       list[1]
-    # (("splice-unquote" ...) ...)
+      # (("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(
@@ -101,10 +101,9 @@ module Mal
 
   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(Mal::List)
+      func_sym = list[0].unwrap.as(Mal::Symbol)
       func = env.get(func_sym.str).unwrap
 
       case func
@@ -122,7 +121,7 @@ module Mal
 
   macro invoke_list(l, env)
     f = eval({{l}}.first, {{env}}).unwrap
-    args = eval_ast({{l}}[1..-1], {{env}}) as Array
+    args = eval_ast({{l}}[1..-1], {{env}}).as(Array)
 
     case f
     when Mal::Closure
@@ -158,82 +157,82 @@ module Mal
       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
+      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
-          invoke_list(list, env)
+          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
 
@@ -242,13 +241,13 @@ module Mal
   end
 
   def rep(str)
-    print(eval(read(str), $repl_env))
+    print(eval(read(str), REPL_ENV))
   end
 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)){ Mal.eval(args[0], $repl_env) })
+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)) { Mal.eval(args[0], REPL_ENV) })
 Mal.rep "(def! not (fn* (a) (if a false true)))"
 Mal.rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
 Mal.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)))))))"
@@ -257,13 +256,13 @@ Mal.rep "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn*
 Mal.rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"
 Mal.rep("(def! *host-language* \"crystal\")")
 
-$argv = Mal::List.new
-$repl_env.set("*ARGV*", Mal::Type.new $argv)
+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)
+      argv << Mal::Type.new(a)
     end
   end
 
@@ -277,7 +276,7 @@ end
 
 Mal.rep("(println (str \"Mal [\" *host-language* \"]\"))")
 
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
   begin
     puts Mal.rep(line)
   rescue e
index 0c879f9..10c36da 100644 (file)
@@ -1,50 +1,14 @@
 require "./printer"
 
 module Mal
-  class Symbol
-    property :str
-    def initialize(@str : String)
-    end
-
-    def ==(other : Symbol)
-      @str == other.str
-    end
-  end
-
-  class List < Array(Type)
-  end
-
-  class Vector < Array(Type)
-  end
-
-  class HashMap < Hash(String, Type)
-  end
-
-  class Atom
-    property :val
-    def initialize(@val : Type)
-    end
-
-    def ==(rhs : Atom)
-      @val == rhs.val
-    end
-  end
-
-  class Closure
-    property :ast, :params, :env, :fn
-    def initialize(@ast : Type, @params : List | Vector, @env : Env, @fn : Func)
-    end
-  end
-
   class Type
     alias Func = (Array(Type) -> Type)
-    alias ValueType = Nil | Bool | Int64 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom
 
     property :is_macro, :meta
 
     def initialize(@val : ValueType)
       @is_macro = false
-      @meta = nil as Type?
+      @meta = nil.as(Type | Nil)
     end
 
     def initialize(other : Type)
@@ -96,6 +60,45 @@ module Mal
     rel_op :<, :>, :<=, :>=
   end
 
+  class Symbol
+    property :str
+
+    def initialize(@str : String)
+    end
+
+    def ==(other : Symbol)
+      @str == other.str
+    end
+  end
+
+  class List < Array(Type)
+  end
+
+  class Vector < Array(Type)
+  end
+
+  class HashMap < Hash(String, Type)
+  end
+
+  class Atom
+    property :val
+
+    def initialize(@val : Type)
+    end
+
+    def ==(rhs : Atom)
+      @val == rhs.val
+    end
+  end
+
+  class Closure
+    property :ast, :params, :env, :fn
+
+    def initialize(@ast : Type, @params : Array(Mal::Type) | List | Vector, @env : Env, @fn : Func)
+    end
+  end
+
+  alias Type::ValueType = Nil | Bool | Int64 | String | Symbol | List | Vector | HashMap | Func | Closure | Atom
   alias Func = Type::Func
 end
 
@@ -105,7 +108,6 @@ end
 
 class Array
   def to_mal(t = Mal::List)
-    each_with_object(t.new){|e, l| l << e}
+    each_with_object(t.new) { |e, l| l << e }
   end
 end
-