mirror of
https://github.com/kanaka/mal.git
synced 2024-10-05 18:08:55 +03:00
ruby.2 step 9
This commit is contained in:
parent
37008594bb
commit
77683d92fd
@ -14,19 +14,6 @@ module Mal
|
||||
Types::Symbol.for("*") => Types::Builtin.new { |a, b| a * b },
|
||||
Types::Symbol.for("/") => Types::Builtin.new { |a, b| a / b },
|
||||
|
||||
Types::Symbol.for("prn") => Types::Builtin.new do |mal|
|
||||
val =
|
||||
if mal.any?
|
||||
mal.first
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
|
||||
puts Mal.pr_str(val, true)
|
||||
|
||||
Types::Nil.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("list") => Types::Builtin.new do |mal|
|
||||
list = Types::List.new
|
||||
mal.each { |m| list << m }
|
||||
@ -44,6 +31,17 @@ module Mal
|
||||
is_list ? Types::True.instance : Types::False.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("vector?") => Types::Builtin.new do |mal|
|
||||
is_vector =
|
||||
if mal.any?
|
||||
Types::Vector === mal.first
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
is_vector ? Types::True.instance : Types::False.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("empty?") => Types::Builtin.new do |mal|
|
||||
is_empty =
|
||||
if mal.any?
|
||||
@ -262,7 +260,7 @@ module Mal
|
||||
Types::Symbol.for("nth") => Types::Builtin.new do |mal|
|
||||
list_or_vector, index = mal
|
||||
result = list_or_vector[index.value]
|
||||
raise IndexError if result.nil?
|
||||
raise IndexError, "Index #{index.value} is out of bounds" if result.nil?
|
||||
result
|
||||
end,
|
||||
|
||||
@ -296,6 +294,213 @@ module Mal
|
||||
else
|
||||
Types::List.new
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("throw") => Types::Builtin.new do |mal|
|
||||
to_throw, * = mal
|
||||
raise MalError, to_throw
|
||||
end,
|
||||
|
||||
Types::Symbol.for("apply") => Types::Builtin.new do |mal|
|
||||
fn, *rest = mal
|
||||
|
||||
args = Types::List.new
|
||||
|
||||
rest.flatten(1).each do |a|
|
||||
args << a
|
||||
end
|
||||
|
||||
fn.call(args)
|
||||
end,
|
||||
|
||||
Types::Symbol.for("map") => Types::Builtin.new do |mal|
|
||||
fn, rest = mal
|
||||
|
||||
map_with =
|
||||
case rest
|
||||
when Types::List, Types::Vector
|
||||
rest
|
||||
else
|
||||
raise SyntaxError, "Must pass list/vector to map!"
|
||||
end
|
||||
|
||||
results = Types::List.new
|
||||
|
||||
map_with.each do |a|
|
||||
results << fn.call(a)
|
||||
end
|
||||
|
||||
results
|
||||
end,
|
||||
|
||||
Types::Symbol.for("nil?") => Types::Builtin.new do |mal|
|
||||
if mal&.first == Types::Nil.instance
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("true?") => Types::Builtin.new do |mal|
|
||||
if mal&.first == Types::True.instance
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("false?") => Types::Builtin.new do |mal|
|
||||
if mal&.first == Types::False.instance
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("symbol?") => Types::Builtin.new do |mal|
|
||||
if mal&.first&.is_a?(Types::Symbol)
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("keyword?") => Types::Builtin.new do |mal|
|
||||
if mal&.first&.is_a?(Types::Keyword)
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("symbol") => Types::Builtin.new do |mal|
|
||||
string, * = mal
|
||||
if string
|
||||
Types::Symbol.for(string.value)
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("keyword") => Types::Builtin.new do |mal|
|
||||
string, * = mal
|
||||
if string
|
||||
Types::Keyword.for(string.value)
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("vector") => Types::Builtin.new do |mal|
|
||||
*items = mal
|
||||
|
||||
vector = Types::Vector.new
|
||||
|
||||
items.each do |i|
|
||||
vector << i
|
||||
end
|
||||
|
||||
vector
|
||||
end,
|
||||
|
||||
Types::Symbol.for("sequential?") => Types::Builtin.new do |mal|
|
||||
list_or_vector, * = mal
|
||||
|
||||
case list_or_vector
|
||||
when Types::List, Types::Vector
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("hash-map") => Types::Builtin.new do |mal|
|
||||
*items = mal
|
||||
|
||||
raise UnbalancedHashmapError if items&.size&.odd?
|
||||
|
||||
hashmap = Types::Hashmap.new
|
||||
|
||||
items.each_slice(2) do |(k, v)|
|
||||
hashmap[k] = v
|
||||
end
|
||||
|
||||
hashmap
|
||||
end,
|
||||
|
||||
Types::Symbol.for("map?") => Types::Builtin.new do |mal|
|
||||
if mal&.first&.is_a?(Types::Hashmap)
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("assoc") => Types::Builtin.new do |mal|
|
||||
hashmap, *items = mal
|
||||
|
||||
raise UnbalancedHashmapError if items&.size&.odd?
|
||||
|
||||
new_hashmap = hashmap.dup
|
||||
|
||||
items.each_slice(2) do |(k, v)|
|
||||
new_hashmap[k] = v
|
||||
end
|
||||
|
||||
new_hashmap
|
||||
end,
|
||||
|
||||
Types::Symbol.for("dissoc") => Types::Builtin.new do |mal|
|
||||
hashmap, *keys = mal
|
||||
|
||||
new_hashmap = Types::Hashmap.new
|
||||
|
||||
hashmap.keys.each do |k|
|
||||
next if keys.include?(k)
|
||||
new_hashmap[k] = hashmap[k]
|
||||
end
|
||||
|
||||
new_hashmap
|
||||
end,
|
||||
|
||||
Types::Symbol.for("get") => Types::Builtin.new do |mal|
|
||||
hashmap, key = mal
|
||||
|
||||
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
||||
hashmap[key]
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("contains?") => Types::Builtin.new do |mal|
|
||||
hashmap, key = mal
|
||||
|
||||
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("keys") => Types::Builtin.new do |mal|
|
||||
hashmap, * = mal
|
||||
|
||||
if Types::Hashmap === hashmap
|
||||
Types::List.new(hashmap.keys)
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("vals") => Types::Builtin.new do |mal|
|
||||
hashmap, * = mal
|
||||
|
||||
if Types::Hashmap === hashmap
|
||||
Types::List.new(hashmap.values)
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
@ -43,7 +43,7 @@ module Mal
|
||||
when self.class
|
||||
environment.get_value(k)
|
||||
when Types::Nil
|
||||
raise SymbolNotFoundError, "Error! Symbol #{k.value} not found."
|
||||
raise SymbolNotFoundError, "'#{k.value}' not found"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2,6 +2,18 @@ module Mal
|
||||
class Error < ::StandardError; end
|
||||
class TypeError < ::TypeError; end
|
||||
|
||||
class MalError < Error
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def message
|
||||
value.inspect
|
||||
end
|
||||
end
|
||||
|
||||
class FileNotFoundError < Error; end
|
||||
class IndexError < TypeError; end
|
||||
class SkipCommentError < Error; end
|
||||
@ -15,6 +27,7 @@ module Mal
|
||||
class NotCallableError < Error; end
|
||||
|
||||
class SymbolNotFoundError < Error; end
|
||||
class SyntaxError < TypeError; end
|
||||
|
||||
class UnbalancedEscapingError < Error; end
|
||||
class UnbalancedHashmapError < Error; end
|
||||
@ -22,5 +35,19 @@ module Mal
|
||||
class UnbalancedStringError < Error; end
|
||||
class UnbalancedVectorError < Error; end
|
||||
|
||||
class UnknownError < Error; end
|
||||
class UnknownError < Error
|
||||
attr_reader :original_error
|
||||
|
||||
def initialize(original_error)
|
||||
@original_error = original_error
|
||||
end
|
||||
|
||||
def inspect
|
||||
"UnknownError :: #{original_error.inspect}"
|
||||
end
|
||||
|
||||
def message
|
||||
"UnknownError<#{original_error.class}> :: #{original_error.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
334
impls/ruby.2/step9_try.rb
Normal file
334
impls/ruby.2/step9_try.rb
Normal file
@ -0,0 +1,334 @@
|
||||
require "readline"
|
||||
|
||||
require_relative "core"
|
||||
require_relative "env"
|
||||
require_relative "errors"
|
||||
require_relative "printer"
|
||||
require_relative "reader"
|
||||
|
||||
module Mal
|
||||
extend self
|
||||
|
||||
def boot_repl!
|
||||
@repl_env = Env.new
|
||||
|
||||
Core.ns.each do |k, v|
|
||||
@repl_env.set(k, v)
|
||||
end
|
||||
|
||||
@repl_env.set(
|
||||
Types::Symbol.for("eval"),
|
||||
|
||||
Types::Builtin.new do |mal|
|
||||
Mal.EVAL(mal.first, @repl_env)
|
||||
end
|
||||
)
|
||||
|
||||
Mal.rep("(def! not (fn* (a) (if a false true)))")
|
||||
Mal.rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
|
||||
Mal.rep("(def! *ARGV* (list))") if !run_application?
|
||||
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)))))))")
|
||||
end
|
||||
|
||||
def run_application?
|
||||
ARGV.any?
|
||||
end
|
||||
|
||||
def run!
|
||||
args = ARGV[1..].map(&:inspect)
|
||||
|
||||
if args.any?
|
||||
Mal.rep("(def! *ARGV* (list #{args.join(" ")}))")
|
||||
else
|
||||
Mal.rep("(def! *ARGV* (list))")
|
||||
end
|
||||
|
||||
puts Mal.rep("(load-file #{ARGV.first.inspect})")
|
||||
end
|
||||
|
||||
def READ(input)
|
||||
read_str(input)
|
||||
end
|
||||
|
||||
def EVAL(ast, environment)
|
||||
loop do
|
||||
ast = macro_expand(ast, environment)
|
||||
|
||||
if Types::List === ast && ast.size > 0
|
||||
case ast.first
|
||||
when Types::Symbol.for("def!")
|
||||
_, sym, val = ast
|
||||
return environment.set(sym, EVAL(val, environment))
|
||||
when Types::Symbol.for("defmacro!")
|
||||
_, sym, val = ast
|
||||
result = EVAL(val, environment)
|
||||
|
||||
case result
|
||||
when Types::Function
|
||||
return environment.set(sym, result.to_macro)
|
||||
else
|
||||
raise TypeError
|
||||
end
|
||||
when Types::Symbol.for("macroexpand")
|
||||
_, ast_rest = ast
|
||||
return macro_expand(ast_rest, environment)
|
||||
when Types::Symbol.for("let*")
|
||||
e = Env.new(environment)
|
||||
_, bindings, val = ast
|
||||
|
||||
unless Types::List === bindings || Types::Vector === bindings
|
||||
raise InvalidLetBindingsError
|
||||
end
|
||||
|
||||
until bindings.empty?
|
||||
k, v = bindings.shift(2)
|
||||
|
||||
raise InvalidLetBindingsError if k.nil?
|
||||
v = Types::Nil.instance if v.nil?
|
||||
|
||||
e.set(k, EVAL(v, e))
|
||||
end
|
||||
|
||||
if !val.nil?
|
||||
# Continue loop
|
||||
ast = val
|
||||
environment = e
|
||||
else
|
||||
return Types::Nil.instance
|
||||
end
|
||||
when Types::Symbol.for("do")
|
||||
_, *values = ast
|
||||
|
||||
if !values.nil? && values.any?
|
||||
values[0...-1].each do |v|
|
||||
EVAL(v, environment)
|
||||
end
|
||||
|
||||
# Continue loop
|
||||
ast = values.last
|
||||
else
|
||||
return Types::Nil.instance
|
||||
end
|
||||
when Types::Symbol.for("if")
|
||||
_, condition, when_true, when_false = ast
|
||||
|
||||
case EVAL(condition, environment)
|
||||
when Types::False.instance, Types::Nil.instance
|
||||
if !when_false.nil?
|
||||
# Continue loop
|
||||
ast = when_false
|
||||
else
|
||||
return Types::Nil.instance
|
||||
end
|
||||
else
|
||||
if !when_true.nil?
|
||||
# Continue loop
|
||||
ast = when_true
|
||||
else
|
||||
raise InvalidIfExpressionError
|
||||
end
|
||||
end
|
||||
when Types::Symbol.for("fn*")
|
||||
_, binds, to_eval = ast
|
||||
|
||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
||||
exprs =
|
||||
case exprs
|
||||
when Types::List, Types::Vector
|
||||
exprs
|
||||
else
|
||||
Types::List.new([exprs])
|
||||
end
|
||||
|
||||
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||
end
|
||||
when Types::Symbol.for("quote")
|
||||
_, ret = ast
|
||||
return ret
|
||||
when Types::Symbol.for("quasiquote")
|
||||
_, ast_rest = ast
|
||||
ast = quasiquote(ast_rest)
|
||||
when Types::Symbol.for("quasiquoteexpand")
|
||||
_, ast_rest = ast
|
||||
return quasiquote(ast_rest)
|
||||
when Types::Symbol.for("try*")
|
||||
_, to_try, catch_list = ast
|
||||
|
||||
begin
|
||||
return EVAL(to_try, environment)
|
||||
rescue => e
|
||||
raise e if catch_list.nil? || catch_list&.empty?
|
||||
raise SyntaxError, "try* missing proper catch*" unless catch_list&.first == Types::Symbol.for("catch*")
|
||||
|
||||
_, exception_symbol, exception_handler = catch_list
|
||||
|
||||
value =
|
||||
if e.is_a?(MalError)
|
||||
e.value
|
||||
else
|
||||
Types::String.new(e.message)
|
||||
end
|
||||
|
||||
return EVAL(
|
||||
exception_handler,
|
||||
Env.new(
|
||||
environment,
|
||||
Types::List.new([exception_symbol]),
|
||||
Types::List.new([value])
|
||||
)
|
||||
)
|
||||
end
|
||||
else
|
||||
evaluated = eval_ast(ast, environment)
|
||||
maybe_callable = evaluated.first
|
||||
|
||||
if maybe_callable.respond_to?(:call) && maybe_callable.is_mal_fn?
|
||||
# Continue loop
|
||||
ast = maybe_callable.ast
|
||||
environment = Env.new(
|
||||
maybe_callable.env,
|
||||
maybe_callable.params,
|
||||
evaluated[1..],
|
||||
)
|
||||
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||
return maybe_callable.call(evaluated[1..])
|
||||
else
|
||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||
end
|
||||
end
|
||||
elsif Types::List === ast && ast.size == 0
|
||||
return ast
|
||||
else
|
||||
return eval_ast(ast, environment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def PRINT(input)
|
||||
pr_str(input, true)
|
||||
end
|
||||
|
||||
def rep(input)
|
||||
PRINT(EVAL(READ(input), @repl_env))
|
||||
rescue InvalidHashmapKeyError => e
|
||||
"Error! Hashmap keys can only be strings or keywords."
|
||||
rescue NotCallableError => e
|
||||
e.message
|
||||
rescue SymbolNotFoundError => e
|
||||
e.message
|
||||
rescue UnbalancedEscapingError => e
|
||||
"Error! Detected unbalanced escaping. Check for matching '\\'."
|
||||
rescue UnbalancedHashmapError => e
|
||||
"Error! Detected unbalanced list. Check for matching '}'."
|
||||
rescue UnbalancedListError => e
|
||||
"Error! Detected unbalanced list. Check for matching ')'."
|
||||
rescue UnbalancedStringError => e
|
||||
"Error! Detected unbalanced string. Check for matching '\"'."
|
||||
rescue UnbalancedVectorError => e
|
||||
"Error! Detected unbalanced list. Check for matching ']'."
|
||||
rescue MalError => e
|
||||
"Error: #{pr_str(e.value, true)}"
|
||||
rescue Error, TypeError => e
|
||||
"#{e.class} -- #{e.message}"
|
||||
rescue SkipCommentError
|
||||
nil
|
||||
end
|
||||
|
||||
def eval_ast(mal, environment)
|
||||
case mal
|
||||
when Types::Symbol
|
||||
environment.get(mal)
|
||||
when Types::List
|
||||
list = Types::List.new
|
||||
mal.each { |i| list << EVAL(i, environment) }
|
||||
list
|
||||
when Types::Vector
|
||||
vec = Types::Vector.new
|
||||
mal.each { |i| vec << EVAL(i, environment) }
|
||||
vec
|
||||
when Types::Hashmap
|
||||
hashmap = Types::Hashmap.new
|
||||
mal.each { |k, v| hashmap[k] = EVAL(v, environment) }
|
||||
hashmap
|
||||
else
|
||||
mal
|
||||
end
|
||||
end
|
||||
|
||||
def quasiquote_list(mal)
|
||||
result = Types::List.new
|
||||
|
||||
mal.reverse_each do |elt|
|
||||
if elt.is_a?(Types::List) && elt.first == Types::Symbol.for("splice-unquote")
|
||||
result = Types::List.new([
|
||||
Types::Symbol.for("concat"),
|
||||
elt[1],
|
||||
result
|
||||
])
|
||||
else
|
||||
result = Types::List.new([
|
||||
Types::Symbol.for("cons"),
|
||||
quasiquote(elt),
|
||||
result
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def quasiquote(mal)
|
||||
case mal
|
||||
when Types::List
|
||||
if mal.first == Types::Symbol.for("unquote")
|
||||
mal[1]
|
||||
else
|
||||
quasiquote_list(mal)
|
||||
end
|
||||
when Types::Vector
|
||||
Types::List.new([
|
||||
Types::Symbol.for("vec"),
|
||||
quasiquote_list(mal)
|
||||
])
|
||||
when Types::Hashmap, Types::Symbol
|
||||
Types::List.new([
|
||||
Types::Symbol.for("quote"),
|
||||
mal
|
||||
])
|
||||
else
|
||||
mal
|
||||
end
|
||||
end
|
||||
|
||||
def is_macro_call?(mal, env)
|
||||
return false unless Types::List === mal
|
||||
return false unless Types::Symbol === mal.first
|
||||
val = env.get(mal.first)
|
||||
return false unless Types::Callable === val
|
||||
val.is_macro?
|
||||
rescue SymbolNotFoundError
|
||||
false
|
||||
end
|
||||
|
||||
def macro_expand(mal, env)
|
||||
while is_macro_call?(mal, env)
|
||||
macro_fn = env.get(mal.first)
|
||||
mal = macro_fn.call(mal[1..])
|
||||
end
|
||||
|
||||
mal
|
||||
end
|
||||
end
|
||||
|
||||
Mal.boot_repl!
|
||||
|
||||
if Mal.run_application?
|
||||
Mal.run!
|
||||
else
|
||||
while input = Readline.readline("user> ")
|
||||
val = Mal.rep(input)
|
||||
puts val unless val.nil?
|
||||
end
|
||||
|
||||
puts
|
||||
end
|
Loading…
Reference in New Issue
Block a user