*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
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
cp $< $@
$(STEP_BINS): %: %.cr
- crystal compile --release $<
+ crystal build --release $<
step0_repl: $(STEP0_DEPS)
step1_read_print: $(STEP1_DEPS)
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
-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
require "./error"
module Mal
-
class Env
property data
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
e.data[key]
end
end
-
end
class RuntimeException < Exception
getter :thrown
+
def initialize(@thrown : Type)
super()
end
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]}"
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
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
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)
end
end
end
-
+++ /dev/null
-# 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
#! /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
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
end
def eval(x)
- x
+ x
end
def print(result)
end
end
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
begin
puts Mal.rep(line)
rescue e
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
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
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
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
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
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
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
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
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)
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
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
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
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
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
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
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
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
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
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
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
exit
end
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
begin
puts Mal.rep(line)
rescue e
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
# ("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)
)
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
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
exit
end
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
begin
puts Mal.rep(line)
rescue e
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
# ("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)
)
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
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
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
exit
end
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
begin
puts Mal.rep(line)
rescue e
#! /usr/bin/env crystal run
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
# ("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(
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
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
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
exit
end
-while line = my_readline("user> ")
+while line = Readline.readline("user> ", true)
begin
puts Mal.rep(line)
rescue e
require "colorize"
-require "./readline"
+require "readline"
require "./reader"
require "./printer"
require "./types"
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
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
# ("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(
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
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
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
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* (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
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
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)
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
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
-