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

ruby.2 step 6 (and fixes for comments and fn creation)

This commit is contained in:
Ryan Cook 2021-11-30 20:32:59 -07:00 committed by Joel Martin
parent b76c04aacd
commit 516e56a6d7
8 changed files with 293 additions and 18 deletions

View File

@ -6,7 +6,10 @@ module Mal
def ns
{
Types::Symbol.for("+") => Types::Builtin.new { |a, b| a + b },
Types::Symbol.for("+") => Types::Builtin.new do |a, b|
a + b
end,
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 },
@ -175,6 +178,53 @@ module Mal
Types::Symbol.for("println") => Types::Builtin.new do |mal|
puts mal.map { |m| Mal.pr_str(m, false) }.join(" ")
Types::Nil.instance
end,
Types::Symbol.for("read-string") => Types::Builtin.new do |mal|
if mal.first.is_a?(Types::String)
Mal.read_str(mal.first.value)
else
Types::Nil.instance
end
end,
Types::Symbol.for("slurp") => Types::Builtin.new do |mal|
if mal.first.is_a?(Types::String)
if File.exist?(mal.first.value)
Types::String.new(File.read(mal.first.value))
else
raise FileNotFoundError, mal.first.value
end
else
Types::Nil.instance
end
end,
Types::Symbol.for("atom") => Types::Builtin.new do |mal|
Types::Atom.new(mal.first)
end,
Types::Symbol.for("atom?") => Types::Builtin.new do |mal|
mal.first.is_a?(Types::Atom) ? Types::True.instance : Types::False.instance
end,
Types::Symbol.for("deref") => Types::Builtin.new do |mal|
mal.first.is_a?(Types::Atom) ? mal.first.value : Types::Nil.instance
end,
Types::Symbol.for("reset!") => Types::Builtin.new do |mal|
atom, value = mal
if value.nil?
value = Types::Nil.instance
end
atom.value = value
end,
Types::Symbol.for("swap!") => Types::Builtin.new do |mal|
atom, fn, *args = mal
atom.value = fn.call(Types::List.new([atom.value, *args]))
end
}
end

View File

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

View File

@ -16,17 +16,17 @@ module Mal
if print_readably
pr_str_keyword(mal)
else
mal.inspect
":#{mal.value}"
end
when Types::String
if print_readably
pr_str_string(mal)
else
mal.inspect
mal.value
end
when Types::Atom
mal.inspect
when Types::Callable
"(atom #{pr_str(mal.value, print_readably)})"
when Types::Base, Types::Callable
mal.inspect
else
raise InvalidTypeError

View File

@ -22,6 +22,8 @@ module Mal
read_false(reader)
when /\A-?\d+(\.\d+)?/
read_number(reader)
when /\A;/
raise SkipCommentError
else
read_symbol(reader)
end
@ -157,7 +159,9 @@ module Mal
end
def read_str(input)
read_form(Reader.new(tokenize(input)))
tokenized = tokenize(input)
raise SkipCommentError if tokenized.empty?
read_form(Reader.new(tokenized))
end
def read_string(reader)
@ -222,7 +226,7 @@ module Mal
def tokenize(input)
input.scan(TOKEN_REGEX).flatten.each_with_object([]) do |token, tokens|
if token != ""
if token != "" && !token.start_with?(";")
tokens << token
end
end

View File

@ -33,6 +33,8 @@ module Mal
"Error! Detected unbalanced string. Check for matching '\"'."
rescue UnbalancedVectorError => e
"Error! Detected unbalanced list. Check for matching ']'."
rescue SkipCommentError
nil
end
end

View File

@ -85,7 +85,7 @@ module Mal
when Types::Symbol.for("fn*")
_, binds, to_eval = ast
Types::Function.new do |exprs|
Types::Function.new(to_eval, binds, environment) do |exprs|
EVAL(to_eval, Env.new(environment, binds, exprs))
end
else

206
impls/ruby.2/step6_file.rb Normal file
View File

@ -0,0 +1,206 @@
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?
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
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|
exprs =
if exprs.is_a?(Types::List)
exprs
else
Types::List.new([*exprs])
end
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?
# 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
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!
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

@ -4,13 +4,21 @@ module Mal
class Vector < ::Array; end
class Hashmap < ::Hash; end
class Atom < ::Struct.new(:value)
class Base < ::Struct.new(:value)
def inspect
value.to_s
value.inspect
end
end
class Keyword < Atom
class String < Base; end
class Atom < Base
def inspect
"Atom<#{value.inspect}>"
end
end
class Keyword < Base
def self.for(value)
@_keywords ||= {}
@ -22,7 +30,7 @@ module Mal
end
end
class Number < Atom
class Number < Base
def +(other)
self.class.new(value + other.value)
end
@ -40,9 +48,7 @@ module Mal
end
end
class String < Atom; end
class Symbol < Atom
class Symbol < Base
def self.for(value)
@_symbols ||= {}
@ -52,9 +58,13 @@ module Mal
@_symbols[value] = new(value)
end
end
def inspect
value
end
end
class Nil < Atom
class Nil < Base
def self.instance
@_instance ||= new(nil)
end
@ -64,13 +74,13 @@ module Mal
end
end
class True < Atom
class True < Base
def self.instance
@_instance ||= new(true)
end
end
class False < Atom
class False < Base
def self.instance
@_instance ||= new(false)
end