load-file: accept empty file or final comment, return nil
[jackhill/mal.git] / crystal / step2_eval.cr
CommitLineData
eec5fc4f 1#! /usr/bin/env crystal run
2
5185c56e 3require "readline"
eec5fc4f 4require "./reader"
5require "./printer"
6require "./types"
7
8# Note:
9# Employed downcase names because Crystal prohibits uppercase names for methods
10
97d0deb1 11module Mal
12 extend self
eec5fc4f 13
97d0deb1 14 def eval_error(msg)
15 raise Mal::EvalException.new msg
16 end
eec5fc4f 17
97d0deb1 18 def num_func(func)
5185c56e 19 ->(args : Array(Mal::Type)) {
97d0deb1 20 x, y = args[0].unwrap, args[1].unwrap
7546ae18 21 eval_error "invalid arguments" unless x.is_a?(Int64) && y.is_a?(Int64)
97d0deb1 22 Mal::Type.new func.call(x, y)
23 }
24 end
eec5fc4f 25
97d0deb1 26 def eval_ast(a, env)
5185c56e 27 return a.map { |n| eval(n, env).as(Mal::Type) } if a.is_a? Mal::List
97d0deb1 28 return a unless a
725ee0bd 29
97d0deb1 30 ast = a.unwrap
31 case ast
32 when Mal::Symbol
33 if env.has_key? ast.str
34 env[ast.str]
35 else
36 eval_error "'#{ast.str}' not found"
37 end
38 when Mal::List
5185c56e 39 ast.each_with_object(Mal::List.new) { |n, l| l << eval(n, env) }
97d0deb1 40 when Mal::Vector
5185c56e 41 ast.each_with_object(Mal::Vector.new) { |n, l| l << eval(n, env) }
97d0deb1 42 when Mal::HashMap
5185c56e
OR
43 ast.each { |k, v| ast[k] = eval(v, env) }
44 ast
eec5fc4f 45 else
97d0deb1 46 ast
eec5fc4f 47 end
eec5fc4f 48 end
eec5fc4f 49
97d0deb1 50 def read(str)
51 read_str str
52 end
eec5fc4f 53
97d0deb1 54 def eval(t, env)
55 Mal::Type.new case ast = t.unwrap
56 when Mal::List
efa2daef 57 return gen_type Mal::List if ast.empty?
eec5fc4f 58
97d0deb1 59 f = eval_ast(ast.first, env)
60 ast.shift(1)
61 args = eval_ast(ast, env)
eec5fc4f 62
97d0deb1 63 if f.is_a?(Mal::Func)
64 f.call(args)
65 else
66 eval_error "expected function symbol as the first symbol of list"
67 end
eec5fc4f 68 else
97d0deb1 69 eval_ast(t, env)
eec5fc4f 70 end
725ee0bd 71 end
eec5fc4f 72
97d0deb1 73 def print(result)
74 pr_str(result, true)
75 end
eec5fc4f 76
97d0deb1 77 def rep(str)
5185c56e 78 print(eval(read(str), REPL_ENV))
97d0deb1 79 end
eec5fc4f 80end
81
5185c56e
OR
82REPL_ENV = {
83 "+" => Mal.num_func(->(x : Int64, y : Int64) { x + y }),
84 "-" => Mal.num_func(->(x : Int64, y : Int64) { x - y }),
85 "*" => Mal.num_func(->(x : Int64, y : Int64) { x * y }),
86 "/" => Mal.num_func(->(x : Int64, y : Int64) { x / y }),
97d0deb1 87} of String => Mal::Func
88
5185c56e 89while line = Readline.readline("user> ", true)
eec5fc4f 90 begin
97d0deb1 91 puts Mal.rep(line)
dd7a4f55
JM
92 rescue e : Mal::RuntimeException
93 STDERR.puts "Error: #{pr_str(e.thrown, true)}"
eec5fc4f 94 rescue e
dd7a4f55 95 STDERR.puts "Error: #{e}"
eec5fc4f 96 end
97end