1
1
mirror of https://github.com/kanaka/mal.git synced 2024-07-14 17:10:30 +03:00

ruby.2 step 8

This commit is contained in:
Ryan Cook 2021-12-01 14:33:46 -07:00 committed by Joel Martin
parent 8624da0aa3
commit 37008594bb
4 changed files with 385 additions and 7 deletions

View File

@ -257,6 +257,45 @@ module Mal
else
raise TypeError
end
end,
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?
result
end,
Types::Symbol.for("first") => Types::Builtin.new do |mal|
list_or_vector, * = mal
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
result = list_or_vector.first
if result.nil?
result = Types::Nil.instance
end
result
else
Types::Nil.instance
end
end,
Types::Symbol.for("rest") => Types::Builtin.new do |mal|
list_or_vector, * = mal
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
result = list_or_vector[1..]
if result.nil?
result = Types::List.new
end
result.to_list
else
Types::List.new
end
end
}
end

View File

@ -2,8 +2,9 @@ module Mal
class Error < ::StandardError; end
class TypeError < ::TypeError; end
class SkipCommentError < Error; end
class FileNotFoundError < Error; end
class IndexError < TypeError; end
class SkipCommentError < Error; end
class InvalidHashmapKeyError < TypeError; end
class InvalidIfExpressionError < TypeError; end

View File

@ -0,0 +1,297 @@
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!
Mal.rep("(def! *ARGV* (list #{ARGV[1..].map(&:inspect).join(" ")}))")
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 =
if exprs.is_a?(Types::List)
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)
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 SkipCommentError
nil
rescue TypeError
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

View File

@ -1,7 +1,17 @@
module Mal
module Types
class List < ::Array; end
class Vector < ::Array; end
class List < ::Array
def to_list
self
end
end
class Vector < ::Array
def to_list
List.new(self)
end
end
class Hashmap < ::Hash; end
class Base < ::Struct.new(:value)
@ -98,16 +108,20 @@ module Mal
def inspect
raise NotImplementedError
end
def is_mal_fn?
false
end
def is_macro?
false
end
end
class Builtin < Callable
def inspect
"#<builtin>"
end
def is_mal_fn?
false
end
end
class Function < Callable
@ -127,6 +141,33 @@ module Mal
def is_mal_fn?
true
end
def to_macro
Macro.new(ast, params, env, &@fn)
end
end
class Macro < Callable
attr_reader :ast, :params, :env
def initialize(ast, params, env, &block)
@ast = ast
@params = params
@env = env
@fn = block
end
def inspect
"#<macro>"
end
def is_mal_fn?
true
end
def is_macro?
true
end
end
end
end