mirror of
https://github.com/kanaka/mal.git
synced 2024-08-16 17:20:23 +03:00
ruby.2 step 6 (and fixes for comments and fn creation)
This commit is contained in:
parent
b76c04aacd
commit
516e56a6d7
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
206
impls/ruby.2/step6_file.rb
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user