mirror of
https://github.com/kanaka/mal.git
synced 2024-07-14 17:10:30 +03:00
ruby.2 step 4 & 5
This commit is contained in:
parent
229f30fdf1
commit
b76c04aacd
182
impls/ruby.2/core.rb
Normal file
182
impls/ruby.2/core.rb
Normal file
@ -0,0 +1,182 @@
|
||||
require_relative "types"
|
||||
|
||||
module Mal
|
||||
module Core
|
||||
extend self
|
||||
|
||||
def ns
|
||||
{
|
||||
Types::Symbol.for("+") => Types::Builtin.new { |a, b| a + b },
|
||||
Types::Symbol.for("-") => Types::Builtin.new { |a, b| a - b },
|
||||
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 }
|
||||
list
|
||||
end,
|
||||
|
||||
Types::Symbol.for("list?") => Types::Builtin.new do |mal|
|
||||
is_list =
|
||||
if mal.any?
|
||||
Types::List === mal.first
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
is_list ? Types::True.instance : Types::False.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("empty?") => Types::Builtin.new do |mal|
|
||||
is_empty =
|
||||
if mal.any?
|
||||
case mal.first
|
||||
when Types::List, Types::Vector
|
||||
mal.first.empty?
|
||||
else
|
||||
Types::True.instance
|
||||
end
|
||||
else
|
||||
Types::True.instance
|
||||
end
|
||||
|
||||
is_empty ? Types::True.instance : Types::False.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("count") => Types::Builtin.new do |mal|
|
||||
count =
|
||||
if mal.any?
|
||||
case mal.first
|
||||
when Types::List, Types::Vector
|
||||
mal.first.size
|
||||
else
|
||||
0
|
||||
end
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
Types::Number.new(count)
|
||||
end,
|
||||
|
||||
Types::Symbol.for("=") => Types::Builtin.new do |mal|
|
||||
a, b = mal
|
||||
|
||||
if a.nil? || b.nil?
|
||||
Types::False.instance
|
||||
else
|
||||
if a == b
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("<") => Types::Builtin.new do |mal|
|
||||
a, b = mal
|
||||
|
||||
if a.nil? || b.nil?
|
||||
Types::False.instance
|
||||
else
|
||||
if a.is_a?(Types::Number) && b.is_a?(Types::Number)
|
||||
if a.value < b.value
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("<=") => Types::Builtin.new do |mal|
|
||||
a, b = mal
|
||||
|
||||
if a.nil? || b.nil?
|
||||
Types::False.instance
|
||||
else
|
||||
if a.is_a?(Types::Number) && b.is_a?(Types::Number)
|
||||
if a.value <= b.value
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for(">") => Types::Builtin.new do |mal|
|
||||
a, b = mal
|
||||
|
||||
if a.nil? || b.nil?
|
||||
Types::False.instance
|
||||
else
|
||||
if a.is_a?(Types::Number) && b.is_a?(Types::Number)
|
||||
if a.value > b.value
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for(">=") => Types::Builtin.new do |mal|
|
||||
a, b = mal
|
||||
|
||||
if a.nil? || b.nil?
|
||||
Types::False.instance
|
||||
else
|
||||
if a.is_a?(Types::Number) && b.is_a?(Types::Number)
|
||||
if a.value >= b.value
|
||||
Types::True.instance
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
else
|
||||
Types::False.instance
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Types::Symbol.for("pr-str") => Types::Builtin.new do |mal|
|
||||
Types::String.new(mal.map { |m| Mal.pr_str(m, true) }.join(" "))
|
||||
end,
|
||||
|
||||
Types::Symbol.for("str") => Types::Builtin.new do |mal|
|
||||
Types::String.new(mal.map { |m| Mal.pr_str(m, false) }.join(""))
|
||||
end,
|
||||
|
||||
Types::Symbol.for("prn") => Types::Builtin.new do |mal|
|
||||
puts mal.map { |m| Mal.pr_str(m, true) }.join(" ")
|
||||
Types::Nil.instance
|
||||
end,
|
||||
|
||||
Types::Symbol.for("println") => Types::Builtin.new do |mal|
|
||||
puts mal.map { |m| Mal.pr_str(m, false) }.join(" ")
|
||||
Types::Nil.instance
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
@ -3,9 +3,23 @@ require_relative "types"
|
||||
|
||||
module Mal
|
||||
class Env
|
||||
def initialize(outer = nil)
|
||||
def initialize(outer = nil, binds = Types::List.new, exprs = Types::List.new)
|
||||
@outer = outer
|
||||
@data = {}
|
||||
|
||||
spread_next = false
|
||||
binds.each_with_index do |b, i|
|
||||
if b.value == "&"
|
||||
spread_next = true
|
||||
else
|
||||
if spread_next
|
||||
set(b, exprs[(i - 1)..(exprs.length - 1)] || Types::Nil.instance)
|
||||
break
|
||||
else
|
||||
set(b, exprs[i] || Types::Nil.instance)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set(k, v)
|
||||
|
@ -3,6 +3,7 @@ module Mal
|
||||
class TypeError < ::TypeError; end
|
||||
|
||||
class InvalidHashmapKeyError < TypeError; end
|
||||
class InvalidIfExpressionError < TypeError; end
|
||||
class InvalidLetBindingsError < TypeError; end
|
||||
class InvalidReaderPositionError < Error; end
|
||||
class InvalidTypeError < TypeError; end
|
||||
|
@ -12,26 +12,22 @@ module Mal
|
||||
"[#{mal.map { |m| pr_str(m, print_readably) }.join(" ")}]"
|
||||
when Types::Hashmap
|
||||
"{#{mal.map { |k, v| [pr_str(k, print_readably), pr_str(v, print_readably)].join(" ") }.join(" ")}}"
|
||||
when Types::Nil
|
||||
"nil"
|
||||
when Types::True
|
||||
"true"
|
||||
when Types::False
|
||||
"false"
|
||||
when Types::Keyword
|
||||
if print_readably
|
||||
pr_str_keyword(mal)
|
||||
else
|
||||
mal.value.to_s
|
||||
mal.inspect
|
||||
end
|
||||
when Types::String
|
||||
if print_readably
|
||||
pr_str_string(mal)
|
||||
else
|
||||
mal.value.to_s
|
||||
mal.inspect
|
||||
end
|
||||
when Types::Atom
|
||||
mal.value.to_s
|
||||
mal.inspect
|
||||
when Types::Callable
|
||||
mal.inspect
|
||||
else
|
||||
raise InvalidTypeError
|
||||
end
|
||||
|
@ -8,8 +8,10 @@ module Mal
|
||||
|
||||
def read_atom(reader)
|
||||
case reader.peek
|
||||
when /\A"/
|
||||
when /\A"(?:\\.|[^\\"])*"\z/
|
||||
read_string(reader)
|
||||
when /\A"/
|
||||
raise UnbalancedStringError
|
||||
when /\A:/
|
||||
read_keyword(reader)
|
||||
when "nil"
|
||||
@ -161,13 +163,13 @@ module Mal
|
||||
def read_string(reader)
|
||||
raw_value = reader.next.dup
|
||||
|
||||
value = raw_value[1...-1]
|
||||
substitute_escaped_chars!(value)
|
||||
|
||||
if raw_value.length <= 1 || raw_value[-1] != '"'
|
||||
raise UnbalancedStringError
|
||||
end
|
||||
|
||||
value = raw_value[1...-1]
|
||||
substitute_escaped_chars!(value)
|
||||
|
||||
Types::String.new(value)
|
||||
end
|
||||
|
||||
@ -257,13 +259,6 @@ module Mal
|
||||
private
|
||||
|
||||
def substitute_escaped_chars!(string_or_keyword)
|
||||
string_or_keyword.gsub!('\"','"')
|
||||
string_or_keyword.gsub!('\n',"\n")
|
||||
|
||||
if string_or_keyword.count('\\') % 2 != 0
|
||||
raise UnbalancedEscapingError
|
||||
end
|
||||
|
||||
string_or_keyword.gsub!('\\\\','\\')
|
||||
string_or_keyword.gsub!(/\\./, {"\\\\" => "\\", "\\n" => "\n", "\\\"" => '"'})
|
||||
end
|
||||
end
|
||||
|
162
impls/ruby.2/step4_if_fn_do.rb
Normal file
162
impls/ruby.2/step4_if_fn_do.rb
Normal file
@ -0,0 +1,162 @@
|
||||
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
|
||||
|
||||
Mal.rep("(def! not (fn* (a) (if a false true)))")
|
||||
end
|
||||
|
||||
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
|
||||
when Types::Symbol.for("do")
|
||||
_, *values = ast
|
||||
|
||||
if !values.nil?
|
||||
evaluated = Types::List.new
|
||||
|
||||
values.each do |v|
|
||||
evaluated << EVAL(v, environment)
|
||||
end
|
||||
|
||||
evaluated.last
|
||||
else
|
||||
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?
|
||||
EVAL(when_false, environment)
|
||||
else
|
||||
Types::Nil.instance
|
||||
end
|
||||
else
|
||||
if !when_true.nil?
|
||||
EVAL(when_true, environment)
|
||||
else
|
||||
raise InvalidIfExpressionError
|
||||
end
|
||||
end
|
||||
when Types::Symbol.for("fn*")
|
||||
_, binds, to_eval = ast
|
||||
|
||||
Types::Function.new do |exprs|
|
||||
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||
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
|
||||
|
||||
Mal.boot_repl!
|
||||
|
||||
while input = Readline.readline("user> ")
|
||||
puts Mal.rep(input)
|
||||
end
|
||||
|
||||
puts
|
||||
|
||||
|
175
impls/ruby.2/step5_tco.rb
Normal file
175
impls/ruby.2/step5_tco.rb
Normal file
@ -0,0 +1,175 @@
|
||||
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
|
||||
|
||||
Mal.rep("(def! not (fn* (a) (if a false true)))")
|
||||
end
|
||||
|
||||
def READ(input)
|
||||
read_str(input)
|
||||
end
|
||||
|
||||
def EVAL(ast, environment)
|
||||
loop do
|
||||
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("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|
|
||||
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||
end
|
||||
else
|
||||
evaluated = eval_ast(ast, environment)
|
||||
maybe_callable = evaluated.first
|
||||
|
||||
if maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||
return maybe_callable.call(evaluated[1..])
|
||||
elsif 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..],
|
||||
)
|
||||
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 ']'."
|
||||
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
|
||||
|
||||
Mal.boot_repl!
|
||||
|
||||
while input = Readline.readline("user> ")
|
||||
puts Mal.rep(input)
|
||||
end
|
||||
|
||||
puts
|
||||
|
||||
|
@ -4,7 +4,11 @@ module Mal
|
||||
class Vector < ::Array; end
|
||||
class Hashmap < ::Hash; end
|
||||
|
||||
class Atom < ::Struct.new(:value); end
|
||||
class Atom < ::Struct.new(:value)
|
||||
def inspect
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class Keyword < Atom
|
||||
def self.for(value)
|
||||
@ -52,31 +56,66 @@ module Mal
|
||||
|
||||
class Nil < Atom
|
||||
def self.instance
|
||||
@_instance ||= new
|
||||
@_instance ||= new(nil)
|
||||
end
|
||||
|
||||
def initialize
|
||||
@value = nil
|
||||
def inspect
|
||||
"nil"
|
||||
end
|
||||
end
|
||||
|
||||
class True < Atom
|
||||
def self.instance
|
||||
@_instance ||= new
|
||||
end
|
||||
|
||||
def initialize
|
||||
@value = true
|
||||
@_instance ||= new(true)
|
||||
end
|
||||
end
|
||||
|
||||
class False < Atom
|
||||
def self.instance
|
||||
@_instance ||= new
|
||||
@_instance ||= new(false)
|
||||
end
|
||||
end
|
||||
|
||||
class Callable
|
||||
def initialize(&block)
|
||||
@fn = block
|
||||
end
|
||||
|
||||
def initialize
|
||||
@value = false
|
||||
def call(args)
|
||||
@fn.call(args)
|
||||
end
|
||||
|
||||
def inspect
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
class Builtin < Callable
|
||||
def inspect
|
||||
"#<builtin>"
|
||||
end
|
||||
|
||||
def is_mal_fn?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Function < Callable
|
||||
attr_reader :ast, :params, :env
|
||||
|
||||
def initialize(ast, params, env, &block)
|
||||
@ast = ast
|
||||
@params = params
|
||||
@env = env
|
||||
@fn = block
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<function>"
|
||||
end
|
||||
|
||||
def is_mal_fn?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user