mirror of
https://github.com/kanaka/mal.git
synced 2024-09-11 13:55:55 +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
|
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 },
|
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|
|
Types::Symbol.for("println") => Types::Builtin.new do |mal|
|
||||||
puts mal.map { |m| Mal.pr_str(m, false) }.join(" ")
|
puts mal.map { |m| Mal.pr_str(m, false) }.join(" ")
|
||||||
Types::Nil.instance
|
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
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -2,6 +2,9 @@ module Mal
|
|||||||
class Error < ::StandardError; end
|
class Error < ::StandardError; end
|
||||||
class TypeError < ::TypeError; end
|
class TypeError < ::TypeError; end
|
||||||
|
|
||||||
|
class SkipCommentError < Error; end
|
||||||
|
class FileNotFoundError < Error; end
|
||||||
|
|
||||||
class InvalidHashmapKeyError < TypeError; end
|
class InvalidHashmapKeyError < TypeError; end
|
||||||
class InvalidIfExpressionError < TypeError; end
|
class InvalidIfExpressionError < TypeError; end
|
||||||
class InvalidLetBindingsError < TypeError; end
|
class InvalidLetBindingsError < TypeError; end
|
||||||
|
@ -16,17 +16,17 @@ module Mal
|
|||||||
if print_readably
|
if print_readably
|
||||||
pr_str_keyword(mal)
|
pr_str_keyword(mal)
|
||||||
else
|
else
|
||||||
mal.inspect
|
":#{mal.value}"
|
||||||
end
|
end
|
||||||
when Types::String
|
when Types::String
|
||||||
if print_readably
|
if print_readably
|
||||||
pr_str_string(mal)
|
pr_str_string(mal)
|
||||||
else
|
else
|
||||||
mal.inspect
|
mal.value
|
||||||
end
|
end
|
||||||
when Types::Atom
|
when Types::Atom
|
||||||
mal.inspect
|
"(atom #{pr_str(mal.value, print_readably)})"
|
||||||
when Types::Callable
|
when Types::Base, Types::Callable
|
||||||
mal.inspect
|
mal.inspect
|
||||||
else
|
else
|
||||||
raise InvalidTypeError
|
raise InvalidTypeError
|
||||||
|
@ -22,6 +22,8 @@ module Mal
|
|||||||
read_false(reader)
|
read_false(reader)
|
||||||
when /\A-?\d+(\.\d+)?/
|
when /\A-?\d+(\.\d+)?/
|
||||||
read_number(reader)
|
read_number(reader)
|
||||||
|
when /\A;/
|
||||||
|
raise SkipCommentError
|
||||||
else
|
else
|
||||||
read_symbol(reader)
|
read_symbol(reader)
|
||||||
end
|
end
|
||||||
@ -157,7 +159,9 @@ module Mal
|
|||||||
end
|
end
|
||||||
|
|
||||||
def read_str(input)
|
def read_str(input)
|
||||||
read_form(Reader.new(tokenize(input)))
|
tokenized = tokenize(input)
|
||||||
|
raise SkipCommentError if tokenized.empty?
|
||||||
|
read_form(Reader.new(tokenized))
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_string(reader)
|
def read_string(reader)
|
||||||
@ -222,7 +226,7 @@ module Mal
|
|||||||
|
|
||||||
def tokenize(input)
|
def tokenize(input)
|
||||||
input.scan(TOKEN_REGEX).flatten.each_with_object([]) do |token, tokens|
|
input.scan(TOKEN_REGEX).flatten.each_with_object([]) do |token, tokens|
|
||||||
if token != ""
|
if token != "" && !token.start_with?(";")
|
||||||
tokens << token
|
tokens << token
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -33,6 +33,8 @@ module Mal
|
|||||||
"Error! Detected unbalanced string. Check for matching '\"'."
|
"Error! Detected unbalanced string. Check for matching '\"'."
|
||||||
rescue UnbalancedVectorError => e
|
rescue UnbalancedVectorError => e
|
||||||
"Error! Detected unbalanced list. Check for matching ']'."
|
"Error! Detected unbalanced list. Check for matching ']'."
|
||||||
|
rescue SkipCommentError
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, 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))
|
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||||
end
|
end
|
||||||
else
|
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 Vector < ::Array; end
|
||||||
class Hashmap < ::Hash; end
|
class Hashmap < ::Hash; end
|
||||||
|
|
||||||
class Atom < ::Struct.new(:value)
|
class Base < ::Struct.new(:value)
|
||||||
def inspect
|
def inspect
|
||||||
value.to_s
|
value.inspect
|
||||||
end
|
end
|
||||||
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)
|
def self.for(value)
|
||||||
@_keywords ||= {}
|
@_keywords ||= {}
|
||||||
|
|
||||||
@ -22,7 +30,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Number < Atom
|
class Number < Base
|
||||||
def +(other)
|
def +(other)
|
||||||
self.class.new(value + other.value)
|
self.class.new(value + other.value)
|
||||||
end
|
end
|
||||||
@ -40,9 +48,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class String < Atom; end
|
class Symbol < Base
|
||||||
|
|
||||||
class Symbol < Atom
|
|
||||||
def self.for(value)
|
def self.for(value)
|
||||||
@_symbols ||= {}
|
@_symbols ||= {}
|
||||||
|
|
||||||
@ -52,9 +58,13 @@ module Mal
|
|||||||
@_symbols[value] = new(value)
|
@_symbols[value] = new(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Nil < Atom
|
class Nil < Base
|
||||||
def self.instance
|
def self.instance
|
||||||
@_instance ||= new(nil)
|
@_instance ||= new(nil)
|
||||||
end
|
end
|
||||||
@ -64,13 +74,13 @@ module Mal
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class True < Atom
|
class True < Base
|
||||||
def self.instance
|
def self.instance
|
||||||
@_instance ||= new(true)
|
@_instance ||= new(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class False < Atom
|
class False < Base
|
||||||
def self.instance
|
def self.instance
|
||||||
@_instance ||= new(false)
|
@_instance ||= new(false)
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user