1
1
mirror of https://github.com/kanaka/mal.git synced 2024-10-05 18:08:55 +03:00

ruby.2 step 3

This commit is contained in:
Ryan Cook 2021-11-22 11:34:21 -07:00 committed by Joel Martin
parent 918f370924
commit 229f30fdf1
5 changed files with 212 additions and 16 deletions

40
impls/ruby.2/env.rb Normal file
View File

@ -0,0 +1,40 @@
require_relative "errors"
require_relative "types"
module Mal
class Env
def initialize(outer = nil)
@outer = outer
@data = {}
end
def set(k, v)
@data[k] = v
end
def find(k)
if @data.key?(k)
self
elsif !@outer.nil?
@outer.find(k)
else
Types::Nil.instance
end
end
def get(k)
environment = find(k)
case environment
when self.class
environment.get_value(k)
when Types::Nil
raise SymbolNotFoundError, "Error! Symbol #{k.value} not found."
end
end
def get_value(k)
@data[k]
end
end
end

View File

@ -3,6 +3,7 @@ module Mal
class TypeError < ::TypeError; end
class InvalidHashmapKeyError < TypeError; end
class InvalidLetBindingsError < TypeError; end
class InvalidReaderPositionError < Error; end
class InvalidTypeError < TypeError; end

View File

@ -27,13 +27,14 @@ module Mal
def read_deref(reader)
list = Types::List.new
list << Types::Symbol.new("deref")
list << Types::Symbol.for("deref")
list << read_form(reader)
list
end
def read_false(reader)
Types::False.new(reader.next)
reader.advance!
Types::False.instance
end
def read_form(reader)
@ -95,7 +96,7 @@ module Mal
value = reader.next.dup[1...]
substitute_escaped_chars!(value)
Types::Keyword.new(value)
Types::Keyword.for(value)
end
def read_list(reader)
@ -117,7 +118,8 @@ module Mal
end
def read_nil(reader)
Types::Nil.new(reader.next)
reader.advance!
Types::Nil.instance
end
def read_number(reader)
@ -133,21 +135,21 @@ module Mal
def read_quasiquote(reader)
list = Types::List.new
list << Types::Symbol.new("quasiquote")
list << Types::Symbol.for("quasiquote")
list << read_form(reader)
list
end
def read_quote(reader)
list = Types::List.new
list << Types::Symbol.new("quote")
list << Types::Symbol.for("quote")
list << read_form(reader)
list
end
def read_splice_unquote(reader)
list = Types::List.new
list << Types::Symbol.new("splice-unquote")
list << Types::Symbol.for("splice-unquote")
list << read_form(reader)
list
end
@ -170,16 +172,17 @@ module Mal
end
def read_symbol(reader)
Types::Symbol.new(reader.next)
Types::Symbol.for(reader.next)
end
def read_true(reader)
Types::True.new(reader.next)
reader.advance!
Types::True.instance
end
def read_unquote(reader)
list = Types::List.new
list << Types::Symbol.new("unquote")
list << Types::Symbol.for("unquote")
list << read_form(reader)
list
end
@ -204,7 +207,7 @@ module Mal
def read_with_metadata(reader)
list = Types::List.new
list << Types::Symbol.new("with-meta")
list << Types::Symbol.for("with-meta")
first = read_form(reader)
second = read_form(reader)

118
impls/ruby.2/step3_env.rb Normal file
View File

@ -0,0 +1,118 @@
require "readline"
require_relative "env"
require_relative "errors"
require_relative "printer"
require_relative "reader"
module Mal
extend self
@repl_env = Env.new
@repl_env.set(Types::Symbol.for('+'), -> (a, b) { a + b })
@repl_env.set(Types::Symbol.for('-'), -> (a, b) { a - b })
@repl_env.set(Types::Symbol.for('*'), -> (a, b) { a * b })
@repl_env.set(Types::Symbol.for('/'), -> (a, b) { a / b })
def READ(input)
read_str(input)
end
def EVAL(ast, environment)
if Types::List === ast && ast.size > 0
case ast.first
when Types::Symbol.for("def!")
_, sym, val = ast
environment.set(sym, EVAL(val, 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?
EVAL(val, e)
else
Types::Nil.instance
end
else
evaluated = eval_ast(ast, environment)
maybe_callable = evaluated.first
if maybe_callable.respond_to?(:call)
maybe_callable.call(*evaluated[1..])
else
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
end
end
elsif Types::List === ast && ast.size == 0
ast
else
eval_ast(ast, environment)
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 ']'."
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
end
while input = Readline.readline("user> ")
puts Mal.rep(input)
end
puts

View File

@ -5,7 +5,18 @@ module Mal
class Hashmap < ::Hash; end
class Atom < ::Struct.new(:value); end
class Keyword < Atom; end
class Keyword < Atom
def self.for(value)
@_keywords ||= {}
if @_keywords.key?(value)
@_keywords[value]
else
@_keywords[value] = new(value)
end
end
end
class Number < Atom
def +(other)
@ -26,22 +37,45 @@ module Mal
end
class String < Atom; end
class Symbol < Atom; end
class Symbol < Atom
def self.for(value)
@_symbols ||= {}
if @_symbols.key?(value)
@_symbols[value]
else
@_symbols[value] = new(value)
end
end
end
class Nil < Atom
def initialize(_value)
def self.instance
@_instance ||= new
end
def initialize
@value = nil
end
end
class True < Atom
def initialize(_value)
def self.instance
@_instance ||= new
end
def initialize
@value = true
end
end
class False < Atom
def initialize(_value)
def self.instance
@_instance ||= new
end
def initialize
@value = false
end
end