mirror of
https://github.com/kanaka/mal.git
synced 2024-08-17 09:40:21 +03:00
ruby.2 step A; functional self-hosting of mal on mal; refactor if internal mal fn arg passing;
This commit is contained in:
parent
77683d92fd
commit
f1be2c471f
@ -1,3 +1,5 @@
|
|||||||
|
require "readline"
|
||||||
|
|
||||||
require_relative "types"
|
require_relative "types"
|
||||||
|
|
||||||
module Mal
|
module Mal
|
||||||
@ -6,59 +8,57 @@ module Mal
|
|||||||
|
|
||||||
def ns
|
def ns
|
||||||
{
|
{
|
||||||
Types::Symbol.for("+") => Types::Builtin.new do |a, b|
|
Types::Symbol.for("+") => Types::Builtin.new("+") do |a, b|
|
||||||
a + b
|
a + b
|
||||||
end,
|
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 },
|
||||||
|
|
||||||
Types::Symbol.for("list") => Types::Builtin.new do |mal|
|
Types::Symbol.for("list") => Types::Builtin.new("list") do |*mal|
|
||||||
list = Types::List.new
|
list = Types::List.new
|
||||||
mal.each { |m| list << m }
|
mal.each { |m| list << m }
|
||||||
list
|
list
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("list?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("list?") => Types::Builtin.new("list?") do |list = nil|
|
||||||
is_list =
|
list.is_a?(Types::List) ? Types::True.instance : Types::False.instance
|
||||||
if mal.any?
|
|
||||||
Types::List === mal.first
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
is_list ? Types::True.instance : Types::False.instance
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("vector?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("vector?") => Types::Builtin.new("vector?") do |vector = nil|
|
||||||
is_vector =
|
vector.is_a?(Types::Vector) ? Types::True.instance : Types::False.instance
|
||||||
if mal.any?
|
|
||||||
Types::Vector === mal.first
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
is_vector ? Types::True.instance : Types::False.instance
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("empty?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("string?") => Types::Builtin.new("string?") do |string = nil|
|
||||||
|
string.is_a?(Types::String) ? Types::True.instance : Types::False.instance
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("number?") => Types::Builtin.new("number?") do |number = nil|
|
||||||
|
number.is_a?(Types::Number) ? Types::True.instance : Types::False.instance
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("fn?") => Types::Builtin.new("fn?") do |fn = nil|
|
||||||
|
fn.is_a?(Types::Callable) && !fn.is_macro? ? Types::True.instance : Types::False.instance
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("macro?") => Types::Builtin.new("macro?") do |macro = nil|
|
||||||
|
macro.is_a?(Types::Callable) && macro.is_macro? ? Types::True.instance : Types::False.instance
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("empty?") => Types::Builtin.new("empty?") do |list_or_vector = nil|
|
||||||
is_empty =
|
is_empty =
|
||||||
if mal.any?
|
case list_or_vector
|
||||||
case mal.first
|
when Types::List, Types::Vector
|
||||||
when Types::List, Types::Vector
|
list_or_vector.empty?
|
||||||
mal.first.empty?
|
|
||||||
else
|
|
||||||
Types::True.instance
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
Types::True.instance
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
is_empty ? Types::True.instance : Types::False.instance
|
is_empty ? Types::True.instance : Types::False.instance
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("count") => Types::Builtin.new do |mal|
|
Types::Symbol.for("count") => Types::Builtin.new("count") do |*mal|
|
||||||
count =
|
count =
|
||||||
if mal.any?
|
if mal.any?
|
||||||
case mal.first
|
case mal.first
|
||||||
@ -74,9 +74,7 @@ module Mal
|
|||||||
Types::Number.new(count)
|
Types::Number.new(count)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("=") => Types::Builtin.new do |mal|
|
Types::Symbol.for("=") => Types::Builtin.new("=") do |a, b|
|
||||||
a, b = mal
|
|
||||||
|
|
||||||
if a.nil? || b.nil?
|
if a.nil? || b.nil?
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
else
|
else
|
||||||
@ -88,9 +86,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("<") => Types::Builtin.new do |mal|
|
Types::Symbol.for("<") => Types::Builtin.new("<") do |a, b|
|
||||||
a, b = mal
|
|
||||||
|
|
||||||
if a.nil? || b.nil?
|
if a.nil? || b.nil?
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
else
|
else
|
||||||
@ -106,9 +102,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("<=") => Types::Builtin.new do |mal|
|
Types::Symbol.for("<=") => Types::Builtin.new("<=") do |a, b|
|
||||||
a, b = mal
|
|
||||||
|
|
||||||
if a.nil? || b.nil?
|
if a.nil? || b.nil?
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
else
|
else
|
||||||
@ -124,9 +118,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for(">") => Types::Builtin.new do |mal|
|
Types::Symbol.for(">") => Types::Builtin.new(">") do |a, b|
|
||||||
a, b = mal
|
|
||||||
|
|
||||||
if a.nil? || b.nil?
|
if a.nil? || b.nil?
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
else
|
else
|
||||||
@ -142,9 +134,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for(">=") => Types::Builtin.new do |mal|
|
Types::Symbol.for(">=") => Types::Builtin.new(">=") do |a, b|
|
||||||
a, b = mal
|
|
||||||
|
|
||||||
if a.nil? || b.nil?
|
if a.nil? || b.nil?
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
else
|
else
|
||||||
@ -160,59 +150,57 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("pr-str") => Types::Builtin.new do |mal|
|
Types::Symbol.for("pr-str") => Types::Builtin.new("pr-str") do |*mal|
|
||||||
Types::String.new(mal.map { |m| Mal.pr_str(m, true) }.join(" "))
|
Types::String.new(mal.map { |m| Mal.pr_str(m, true) }.join(" "))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("str") => Types::Builtin.new do |mal|
|
Types::Symbol.for("str") => Types::Builtin.new("str") do |*mal|
|
||||||
Types::String.new(mal.map { |m| Mal.pr_str(m, false) }.join(""))
|
Types::String.new(mal.map { |m| Mal.pr_str(m, false) }.join(""))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("prn") => Types::Builtin.new do |mal|
|
Types::Symbol.for("prn") => Types::Builtin.new("prn") do |*mal|
|
||||||
puts mal.map { |m| Mal.pr_str(m, true) }.join(" ")
|
puts mal.map { |m| Mal.pr_str(m, true) }.join(" ")
|
||||||
Types::Nil.instance
|
Types::Nil.instance
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("println") => Types::Builtin.new do |mal|
|
Types::Symbol.for("println") => Types::Builtin.new("println") 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,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("read-string") => Types::Builtin.new do |mal|
|
Types::Symbol.for("read-string") => Types::Builtin.new("read-string") do |string = nil|
|
||||||
if mal.first.is_a?(Types::String)
|
if string.is_a?(Types::String)
|
||||||
Mal.read_str(mal.first.value)
|
Mal.read_str(string.value)
|
||||||
else
|
else
|
||||||
Types::Nil.instance
|
Types::Nil.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("slurp") => Types::Builtin.new do |mal|
|
Types::Symbol.for("slurp") => Types::Builtin.new("slurp") do |file = nil|
|
||||||
if mal.first.is_a?(Types::String)
|
if file.is_a?(Types::String)
|
||||||
if File.exist?(mal.first.value)
|
if File.exist?(file.value)
|
||||||
Types::String.new(File.read(mal.first.value))
|
Types::String.new(File.read(file.value))
|
||||||
else
|
else
|
||||||
raise FileNotFoundError, mal.first.value
|
raise FileNotFoundError, file.value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Types::Nil.instance
|
Types::Nil.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("atom") => Types::Builtin.new do |mal|
|
Types::Symbol.for("atom") => Types::Builtin.new("atom") do |mal|
|
||||||
Types::Atom.new(mal.first)
|
Types::Atom.new(mal)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("atom?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("atom?") => Types::Builtin.new("atom?") do |maybe_atom|
|
||||||
mal.first.is_a?(Types::Atom) ? Types::True.instance : Types::False.instance
|
maybe_atom.is_a?(Types::Atom) ? Types::True.instance : Types::False.instance
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("deref") => Types::Builtin.new do |mal|
|
Types::Symbol.for("deref") => Types::Builtin.new("deref") do |maybe_atom|
|
||||||
mal.first.is_a?(Types::Atom) ? mal.first.value : Types::Nil.instance
|
maybe_atom.is_a?(Types::Atom) ? maybe_atom.value : Types::Nil.instance
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("reset!") => Types::Builtin.new do |mal|
|
Types::Symbol.for("reset!") => Types::Builtin.new("reset!") do |atom, value|
|
||||||
atom, value = mal
|
|
||||||
|
|
||||||
if value.nil?
|
if value.nil?
|
||||||
value = Types::Nil.instance
|
value = Types::Nil.instance
|
||||||
end
|
end
|
||||||
@ -220,17 +208,15 @@ module Mal
|
|||||||
atom.value = value
|
atom.value = value
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("swap!") => Types::Builtin.new do |mal|
|
Types::Symbol.for("swap!") => Types::Builtin.new("swap!") do |atom, fn, *args|
|
||||||
atom, fn, *args = mal
|
atom.value = fn.call(Types::Args.new([atom.value, *args]))
|
||||||
atom.value = fn.call(Types::List.new([atom.value, *args]))
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("cons") => Types::Builtin.new do |mal|
|
Types::Symbol.for("cons") => Types::Builtin.new("cons") do |val, list_or_vector|
|
||||||
val, list_or_vector = mal
|
|
||||||
Types::List.new([val, *list_or_vector])
|
Types::List.new([val, *list_or_vector])
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("concat") => Types::Builtin.new do |mal|
|
Types::Symbol.for("concat") => Types::Builtin.new("concat") do |*mal|
|
||||||
list = Types::List.new
|
list = Types::List.new
|
||||||
|
|
||||||
mal.each do |l|
|
mal.each do |l|
|
||||||
@ -240,33 +226,30 @@ module Mal
|
|||||||
list
|
list
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("vec") => Types::Builtin.new do |mal|
|
Types::Symbol.for("vec") => Types::Builtin.new("vec") do |list_or_vector|
|
||||||
case mal.first
|
case list_or_vector
|
||||||
when Types::List
|
when Types::List
|
||||||
vec = Types::Vector.new
|
vec = Types::Vector.new
|
||||||
|
|
||||||
mal.first.each do |m|
|
list_or_vector.each do |m|
|
||||||
vec << m
|
vec << m
|
||||||
end
|
end
|
||||||
|
|
||||||
vec
|
vec
|
||||||
when Types::Vector
|
when Types::Vector
|
||||||
mal.first
|
list_or_vector
|
||||||
else
|
else
|
||||||
raise TypeError
|
raise TypeError, "invalid `vec` arguments, must be vector or list"
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("nth") => Types::Builtin.new do |mal|
|
Types::Symbol.for("nth") => Types::Builtin.new("nth") do |list_or_vector, index|
|
||||||
list_or_vector, index = mal
|
|
||||||
result = list_or_vector[index.value]
|
result = list_or_vector[index.value]
|
||||||
raise IndexError, "Index #{index.value} is out of bounds" if result.nil?
|
raise IndexError, "Index #{index.value} is out of bounds" if result.nil?
|
||||||
result
|
result
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("first") => Types::Builtin.new do |mal|
|
Types::Symbol.for("first") => Types::Builtin.new("first") do |list_or_vector|
|
||||||
list_or_vector, * = mal
|
|
||||||
|
|
||||||
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
|
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
|
||||||
result = list_or_vector.first
|
result = list_or_vector.first
|
||||||
|
|
||||||
@ -280,9 +263,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("rest") => Types::Builtin.new do |mal|
|
Types::Symbol.for("rest") => Types::Builtin.new("rest") do |list_or_vector|
|
||||||
list_or_vector, * = mal
|
|
||||||
|
|
||||||
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
|
if !list_or_vector.nil? && list_or_vector != Types::Nil.instance
|
||||||
result = list_or_vector[1..]
|
result = list_or_vector[1..]
|
||||||
|
|
||||||
@ -296,15 +277,12 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("throw") => Types::Builtin.new do |mal|
|
Types::Symbol.for("throw") => Types::Builtin.new("throw") do |to_throw|
|
||||||
to_throw, * = mal
|
|
||||||
raise MalError, to_throw
|
raise MalError, to_throw
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("apply") => Types::Builtin.new do |mal|
|
Types::Symbol.for("apply") => Types::Builtin.new("apply") do |fn, *rest|
|
||||||
fn, *rest = mal
|
args = Types::Args.new
|
||||||
|
|
||||||
args = Types::List.new
|
|
||||||
|
|
||||||
rest.flatten(1).each do |a|
|
rest.flatten(1).each do |a|
|
||||||
args << a
|
args << a
|
||||||
@ -313,68 +291,57 @@ module Mal
|
|||||||
fn.call(args)
|
fn.call(args)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("map") => Types::Builtin.new do |mal|
|
Types::Symbol.for("map") => Types::Builtin.new("map") do |fn, *rest|
|
||||||
fn, rest = mal
|
|
||||||
|
|
||||||
map_with =
|
|
||||||
case rest
|
|
||||||
when Types::List, Types::Vector
|
|
||||||
rest
|
|
||||||
else
|
|
||||||
raise SyntaxError, "Must pass list/vector to map!"
|
|
||||||
end
|
|
||||||
|
|
||||||
results = Types::List.new
|
results = Types::List.new
|
||||||
|
|
||||||
map_with.each do |a|
|
rest.flatten(1).each do |a|
|
||||||
results << fn.call(a)
|
results << fn.call(Types::Args.new([a]))
|
||||||
end
|
end
|
||||||
|
|
||||||
results
|
results
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("nil?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("nil?") => Types::Builtin.new("nil?") do |mal|
|
||||||
if mal&.first == Types::Nil.instance
|
if mal == Types::Nil.instance
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("true?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("true?") => Types::Builtin.new("true?") do |mal|
|
||||||
if mal&.first == Types::True.instance
|
if mal == Types::True.instance
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("false?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("false?") => Types::Builtin.new("false?") do |mal|
|
||||||
if mal&.first == Types::False.instance
|
if mal == Types::False.instance
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("symbol?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("symbol?") => Types::Builtin.new("symbol?") do |mal|
|
||||||
if mal&.first&.is_a?(Types::Symbol)
|
if mal.is_a?(Types::Symbol)
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("keyword?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("keyword?") => Types::Builtin.new("keyword?") do |mal|
|
||||||
if mal&.first&.is_a?(Types::Keyword)
|
if mal.is_a?(Types::Keyword)
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("symbol") => Types::Builtin.new do |mal|
|
Types::Symbol.for("symbol") => Types::Builtin.new("symbol") do |string|
|
||||||
string, * = mal
|
|
||||||
if string
|
if string
|
||||||
Types::Symbol.for(string.value)
|
Types::Symbol.for(string.value)
|
||||||
else
|
else
|
||||||
@ -382,18 +349,15 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("keyword") => Types::Builtin.new do |mal|
|
Types::Symbol.for("keyword") => Types::Builtin.new("keyword") do |keyword|
|
||||||
string, * = mal
|
if keyword
|
||||||
if string
|
Types::Keyword.for(keyword.value)
|
||||||
Types::Keyword.for(string.value)
|
|
||||||
else
|
else
|
||||||
Types::Nil.instance
|
Types::Nil.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("vector") => Types::Builtin.new do |mal|
|
Types::Symbol.for("vector") => Types::Builtin.new("vector") do |*items|
|
||||||
*items = mal
|
|
||||||
|
|
||||||
vector = Types::Vector.new
|
vector = Types::Vector.new
|
||||||
|
|
||||||
items.each do |i|
|
items.each do |i|
|
||||||
@ -403,9 +367,7 @@ module Mal
|
|||||||
vector
|
vector
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("sequential?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("sequential?") => Types::Builtin.new("sequential?") do |list_or_vector|
|
||||||
list_or_vector, * = mal
|
|
||||||
|
|
||||||
case list_or_vector
|
case list_or_vector
|
||||||
when Types::List, Types::Vector
|
when Types::List, Types::Vector
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
@ -414,10 +376,8 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("hash-map") => Types::Builtin.new do |mal|
|
Types::Symbol.for("hash-map") => Types::Builtin.new("hash-map") do |*items|
|
||||||
*items = mal
|
raise UnbalancedHashmapError, "unbalanced hashmap error, arguments must be even" if items&.size&.odd?
|
||||||
|
|
||||||
raise UnbalancedHashmapError if items&.size&.odd?
|
|
||||||
|
|
||||||
hashmap = Types::Hashmap.new
|
hashmap = Types::Hashmap.new
|
||||||
|
|
||||||
@ -428,18 +388,16 @@ module Mal
|
|||||||
hashmap
|
hashmap
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("map?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("map?") => Types::Builtin.new("map?") do |mal|
|
||||||
if mal&.first&.is_a?(Types::Hashmap)
|
if mal.is_a?(Types::Hashmap)
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
Types::False.instance
|
Types::False.instance
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("assoc") => Types::Builtin.new do |mal|
|
Types::Symbol.for("assoc") => Types::Builtin.new("assoc") do |hashmap, *items|
|
||||||
hashmap, *items = mal
|
raise UnbalancedHashmapError, "unbalanced hashmap error, arguments must be even" if items.size&.odd?
|
||||||
|
|
||||||
raise UnbalancedHashmapError if items&.size&.odd?
|
|
||||||
|
|
||||||
new_hashmap = hashmap.dup
|
new_hashmap = hashmap.dup
|
||||||
|
|
||||||
@ -450,9 +408,7 @@ module Mal
|
|||||||
new_hashmap
|
new_hashmap
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("dissoc") => Types::Builtin.new do |mal|
|
Types::Symbol.for("dissoc") => Types::Builtin.new("dissoc") do |hashmap, *keys|
|
||||||
hashmap, *keys = mal
|
|
||||||
|
|
||||||
new_hashmap = Types::Hashmap.new
|
new_hashmap = Types::Hashmap.new
|
||||||
|
|
||||||
hashmap.keys.each do |k|
|
hashmap.keys.each do |k|
|
||||||
@ -463,9 +419,7 @@ module Mal
|
|||||||
new_hashmap
|
new_hashmap
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("get") => Types::Builtin.new do |mal|
|
Types::Symbol.for("get") => Types::Builtin.new("get") do |hashmap, key|
|
||||||
hashmap, key = mal
|
|
||||||
|
|
||||||
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
||||||
hashmap[key]
|
hashmap[key]
|
||||||
else
|
else
|
||||||
@ -473,9 +427,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("contains?") => Types::Builtin.new do |mal|
|
Types::Symbol.for("contains?") => Types::Builtin.new("contains?") do |hashmap, key|
|
||||||
hashmap, key = mal
|
|
||||||
|
|
||||||
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
if Types::Hashmap === hashmap && key && hashmap.key?(key)
|
||||||
Types::True.instance
|
Types::True.instance
|
||||||
else
|
else
|
||||||
@ -483,9 +435,7 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("keys") => Types::Builtin.new do |mal|
|
Types::Symbol.for("keys") => Types::Builtin.new("keys") do |hashmap|
|
||||||
hashmap, * = mal
|
|
||||||
|
|
||||||
if Types::Hashmap === hashmap
|
if Types::Hashmap === hashmap
|
||||||
Types::List.new(hashmap.keys)
|
Types::List.new(hashmap.keys)
|
||||||
else
|
else
|
||||||
@ -493,14 +443,91 @@ module Mal
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Types::Symbol.for("vals") => Types::Builtin.new do |mal|
|
Types::Symbol.for("vals") => Types::Builtin.new("vals") do |hashmap|
|
||||||
hashmap, * = mal
|
|
||||||
|
|
||||||
if Types::Hashmap === hashmap
|
if Types::Hashmap === hashmap
|
||||||
Types::List.new(hashmap.values)
|
Types::List.new(hashmap.values)
|
||||||
else
|
else
|
||||||
Types::Nil.instance
|
Types::Nil.instance
|
||||||
end
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("readline") => Types::Builtin.new("readline") do |prompt = nil|
|
||||||
|
prompt =
|
||||||
|
if prompt.nil?
|
||||||
|
"user> "
|
||||||
|
else
|
||||||
|
prompt.value
|
||||||
|
end
|
||||||
|
|
||||||
|
input = Readline.readline(prompt)
|
||||||
|
|
||||||
|
if input.nil?
|
||||||
|
Types::Nil.instance
|
||||||
|
else
|
||||||
|
Types::String.new(input)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("meta") => Types::Builtin.new("meta") do |value|
|
||||||
|
case value
|
||||||
|
when Types::List, Types::Vector, Types::Hashmap, Types::Callable
|
||||||
|
value.meta
|
||||||
|
else
|
||||||
|
Types::Nil.instance
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("with-meta") => Types::Builtin.new("with-meta") do |value, meta|
|
||||||
|
case value
|
||||||
|
when Types::List, Types::Vector, Types::Hashmap, Types::Callable
|
||||||
|
new_value = value.dup
|
||||||
|
new_value.meta = meta
|
||||||
|
new_value
|
||||||
|
else
|
||||||
|
raise TypeError, "Unable to use meta with #{Mal.pr_str(value)}"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("time-ms") => Types::Builtin.new("time-ms") do
|
||||||
|
Types::Number.new((Time.now.to_f.round(3) * 1000).to_i)
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("conj") => Types::Builtin.new("conj") do |list_or_vector, *new_elems|
|
||||||
|
case list_or_vector
|
||||||
|
when Types::List
|
||||||
|
Types::List.new([*new_elems.reverse, *list_or_vector])
|
||||||
|
when Types::Vector
|
||||||
|
Types::Vector.new([*list_or_vector, *new_elems])
|
||||||
|
else
|
||||||
|
raise TypeError, "Unable to `conj` with <#{Mal.pr_str(list_or_vector)}>, must be list or vector"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Types::Symbol.for("seq") => Types::Builtin.new("seq") do |sequential|
|
||||||
|
case sequential
|
||||||
|
when Types::List
|
||||||
|
if sequential.any?
|
||||||
|
sequential
|
||||||
|
else
|
||||||
|
Types::Nil.instance
|
||||||
|
end
|
||||||
|
when Types::Vector
|
||||||
|
if sequential.any?
|
||||||
|
Types::List.new(sequential)
|
||||||
|
else
|
||||||
|
Types::Nil.instance
|
||||||
|
end
|
||||||
|
when Types::String
|
||||||
|
if !sequential.value.empty?
|
||||||
|
Types::List.new(sequential.value.chars.map { |c| Types::String.new(c) })
|
||||||
|
else
|
||||||
|
Types::Nil.instance
|
||||||
|
end
|
||||||
|
when Types::Nil
|
||||||
|
Types::Nil.instance
|
||||||
|
else
|
||||||
|
raise TypeError, "Unable to `seq` with <#{Mal.pr_str(sequential)}>, must be list, vector, string, or nil"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -13,7 +13,7 @@ module Mal
|
|||||||
spread_next = true
|
spread_next = true
|
||||||
else
|
else
|
||||||
if spread_next
|
if spread_next
|
||||||
set(b, exprs[(i - 1)..(exprs.length - 1)] || Types::Nil.instance)
|
set(b, Types::List.new(exprs[(i - 1)..]) || Types::Nil.instance)
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
set(b, exprs[i] || Types::Nil.instance)
|
set(b, exprs[i] || Types::Nil.instance)
|
||||||
|
@ -29,7 +29,7 @@ module Mal
|
|||||||
when Types::Base, Types::Callable
|
when Types::Base, Types::Callable
|
||||||
mal.inspect
|
mal.inspect
|
||||||
else
|
else
|
||||||
raise InvalidTypeError
|
raise InvalidTypeError, "unable to print value <#{mal.inspect}>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ module Mal
|
|||||||
when /\A"(?:\\.|[^\\"])*"\z/
|
when /\A"(?:\\.|[^\\"])*"\z/
|
||||||
read_string(reader)
|
read_string(reader)
|
||||||
when /\A"/
|
when /\A"/
|
||||||
raise UnbalancedStringError
|
raise UnbalancedStringError, "unbalanced string << #{reader.peek.inspect} >>"
|
||||||
when /\A:/
|
when /\A:/
|
||||||
read_keyword(reader)
|
read_keyword(reader)
|
||||||
when "nil"
|
when "nil"
|
||||||
@ -73,13 +73,13 @@ module Mal
|
|||||||
key = read_form(reader)
|
key = read_form(reader)
|
||||||
|
|
||||||
unless Types::String === key || Types::Keyword === key
|
unless Types::String === key || Types::Keyword === key
|
||||||
raise InvalidHashmapKeyError
|
raise InvalidHashmapKeyError, "invalid hashmap key, must be string or keyword"
|
||||||
end
|
end
|
||||||
|
|
||||||
if reader.peek != "}"
|
if reader.peek != "}"
|
||||||
value = read_form(reader)
|
value = read_form(reader)
|
||||||
else
|
else
|
||||||
raise UnbalancedHashmapError
|
raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
hashmap[key] = value
|
hashmap[key] = value
|
||||||
@ -90,7 +90,7 @@ module Mal
|
|||||||
rescue Error => e
|
rescue Error => e
|
||||||
case e
|
case e
|
||||||
when InvalidReaderPositionError
|
when InvalidReaderPositionError
|
||||||
raise UnbalancedHashmapError
|
raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'"
|
||||||
else
|
else
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
@ -115,7 +115,7 @@ module Mal
|
|||||||
rescue Error => e
|
rescue Error => e
|
||||||
case e
|
case e
|
||||||
when InvalidReaderPositionError
|
when InvalidReaderPositionError
|
||||||
raise UnbalancedListError
|
raise UnbalancedListError, "unbalanced list error, missing closing ')'"
|
||||||
else
|
else
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
@ -133,7 +133,7 @@ module Mal
|
|||||||
when /\d+/
|
when /\d+/
|
||||||
Types::Number.new(reader.next.to_i)
|
Types::Number.new(reader.next.to_i)
|
||||||
else
|
else
|
||||||
raise InvalidTypeError
|
raise InvalidTypeError, "invalid number syntax, only supports integers/floats"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ module Mal
|
|||||||
substitute_escaped_chars!(value)
|
substitute_escaped_chars!(value)
|
||||||
|
|
||||||
if raw_value.length <= 1 || raw_value[-1] != '"'
|
if raw_value.length <= 1 || raw_value[-1] != '"'
|
||||||
raise UnbalancedStringError
|
raise UnbalancedStringError, "unbalanced string error, missing closing '\"'"
|
||||||
end
|
end
|
||||||
|
|
||||||
Types::String.new(value)
|
Types::String.new(value)
|
||||||
@ -205,7 +205,7 @@ module Mal
|
|||||||
rescue Error => e
|
rescue Error => e
|
||||||
case e
|
case e
|
||||||
when InvalidReaderPositionError
|
when InvalidReaderPositionError
|
||||||
raise UnbalancedVectorError
|
raise UnbalancedVectorError, "unbalanced vector error, missing closing ']'"
|
||||||
else
|
else
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
@ -253,7 +253,7 @@ module Mal
|
|||||||
|
|
||||||
def peek
|
def peek
|
||||||
if @position > @tokens.size - 1
|
if @position > @tokens.size - 1
|
||||||
raise InvalidReaderPositionError
|
raise InvalidReaderPositionError, "invalid reader position error, unable to parse mal expression"
|
||||||
end
|
end
|
||||||
|
|
||||||
@tokens[@position]
|
@tokens[@position]
|
||||||
|
@ -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(to_eval, binds, environment) 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
|
||||||
@ -93,7 +93,7 @@ module Mal
|
|||||||
maybe_callable = evaluated.first
|
maybe_callable = evaluated.first
|
||||||
|
|
||||||
if maybe_callable.respond_to?(:call)
|
if maybe_callable.respond_to?(:call)
|
||||||
maybe_callable.call(evaluated[1..])
|
maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
else
|
else
|
||||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||||
end
|
end
|
||||||
|
@ -89,7 +89,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, binds, to_eval = ast
|
||||||
|
|
||||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
return 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
|
||||||
@ -97,7 +97,7 @@ module Mal
|
|||||||
maybe_callable = evaluated.first
|
maybe_callable = evaluated.first
|
||||||
|
|
||||||
if maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
if maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||||
return maybe_callable.call(evaluated[1..])
|
return maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
elsif maybe_callable.respond_to?(:call) && maybe_callable.is_mal_fn?
|
elsif maybe_callable.respond_to?(:call) && maybe_callable.is_mal_fn?
|
||||||
# Continue loop
|
# Continue loop
|
||||||
ast = maybe_callable.ast
|
ast = maybe_callable.ast
|
||||||
|
@ -19,8 +19,8 @@ module Mal
|
|||||||
@repl_env.set(
|
@repl_env.set(
|
||||||
Types::Symbol.for("eval"),
|
Types::Symbol.for("eval"),
|
||||||
|
|
||||||
Types::Builtin.new do |mal|
|
Types::Builtin.new("eval") do |mal|
|
||||||
Mal.EVAL(mal.first, @repl_env)
|
Mal.EVAL(mal, @repl_env)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,14 +108,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, binds, to_eval = ast
|
||||||
|
|
||||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
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))
|
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -131,7 +124,7 @@ module Mal
|
|||||||
evaluated[1..],
|
evaluated[1..],
|
||||||
)
|
)
|
||||||
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||||
return maybe_callable.call(evaluated[1..])
|
return maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
else
|
else
|
||||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||||
end
|
end
|
||||||
|
@ -19,8 +19,8 @@ module Mal
|
|||||||
@repl_env.set(
|
@repl_env.set(
|
||||||
Types::Symbol.for("eval"),
|
Types::Symbol.for("eval"),
|
||||||
|
|
||||||
Types::Builtin.new do |mal|
|
Types::Builtin.new("eval") do |mal|
|
||||||
Mal.EVAL(mal.first, @repl_env)
|
Mal.EVAL(mal, @repl_env)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,14 +108,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, binds, to_eval = ast
|
||||||
|
|
||||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
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))
|
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||||
end
|
end
|
||||||
when Types::Symbol.for("quote")
|
when Types::Symbol.for("quote")
|
||||||
@ -140,7 +133,7 @@ module Mal
|
|||||||
evaluated[1..],
|
evaluated[1..],
|
||||||
)
|
)
|
||||||
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||||
return maybe_callable.call(evaluated[1..])
|
return maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
else
|
else
|
||||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||||
end
|
end
|
||||||
|
@ -19,8 +19,8 @@ module Mal
|
|||||||
@repl_env.set(
|
@repl_env.set(
|
||||||
Types::Symbol.for("eval"),
|
Types::Symbol.for("eval"),
|
||||||
|
|
||||||
Types::Builtin.new do |mal|
|
Types::Builtin.new("eval") do |mal|
|
||||||
Mal.EVAL(mal.first, @repl_env)
|
Mal.EVAL(mal, @repl_env)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,14 +124,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, binds, to_eval = ast
|
||||||
|
|
||||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
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))
|
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||||
end
|
end
|
||||||
when Types::Symbol.for("quote")
|
when Types::Symbol.for("quote")
|
||||||
@ -156,7 +149,7 @@ module Mal
|
|||||||
evaluated[1..],
|
evaluated[1..],
|
||||||
)
|
)
|
||||||
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||||
return maybe_callable.call(evaluated[1..])
|
return maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
else
|
else
|
||||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||||
end
|
end
|
||||||
@ -276,7 +269,12 @@ module Mal
|
|||||||
def macro_expand(mal, env)
|
def macro_expand(mal, env)
|
||||||
while is_macro_call?(mal, env)
|
while is_macro_call?(mal, env)
|
||||||
macro_fn = env.get(mal.first)
|
macro_fn = env.get(mal.first)
|
||||||
mal = macro_fn.call(mal[1..])
|
|
||||||
|
if (args = mal[1..]).any?
|
||||||
|
mal = macro_fn.call(Types::Args.new(mal[1..]))
|
||||||
|
else
|
||||||
|
mal = macro_fn.call
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
mal
|
mal
|
||||||
|
@ -19,7 +19,7 @@ module Mal
|
|||||||
@repl_env.set(
|
@repl_env.set(
|
||||||
Types::Symbol.for("eval"),
|
Types::Symbol.for("eval"),
|
||||||
|
|
||||||
Types::Builtin.new do |mal|
|
Types::Builtin.new("eval") do |mal|
|
||||||
Mal.EVAL(mal.first, @repl_env)
|
Mal.EVAL(mal.first, @repl_env)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
@ -131,15 +131,7 @@ module Mal
|
|||||||
when Types::Symbol.for("fn*")
|
when Types::Symbol.for("fn*")
|
||||||
_, binds, to_eval = ast
|
_, binds, to_eval = ast
|
||||||
|
|
||||||
return Types::Function.new(to_eval, binds, environment) do |exprs|
|
return Types::Function.new(to_eval, binds, environment) do |*exprs|
|
||||||
exprs =
|
|
||||||
case exprs
|
|
||||||
when Types::List, Types::Vector
|
|
||||||
exprs
|
|
||||||
else
|
|
||||||
Types::List.new([exprs])
|
|
||||||
end
|
|
||||||
|
|
||||||
EVAL(to_eval, Env.new(environment, binds, exprs))
|
EVAL(to_eval, Env.new(environment, binds, exprs))
|
||||||
end
|
end
|
||||||
when Types::Symbol.for("quote")
|
when Types::Symbol.for("quote")
|
||||||
@ -191,7 +183,7 @@ module Mal
|
|||||||
evaluated[1..],
|
evaluated[1..],
|
||||||
)
|
)
|
||||||
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
elsif maybe_callable.respond_to?(:call) && !maybe_callable.is_mal_fn?
|
||||||
return maybe_callable.call(evaluated[1..])
|
return maybe_callable.call(Types::Args.new(evaluated[1..]))
|
||||||
else
|
else
|
||||||
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable."
|
||||||
end
|
end
|
||||||
@ -313,7 +305,12 @@ module Mal
|
|||||||
def macro_expand(mal, env)
|
def macro_expand(mal, env)
|
||||||
while is_macro_call?(mal, env)
|
while is_macro_call?(mal, env)
|
||||||
macro_fn = env.get(mal.first)
|
macro_fn = env.get(mal.first)
|
||||||
mal = macro_fn.call(mal[1..])
|
|
||||||
|
if (args = mal[1..]).any?
|
||||||
|
mal = macro_fn.call(Types::Args.new(mal[1..]))
|
||||||
|
else
|
||||||
|
mal = macro_fn.call
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
mal
|
mal
|
||||||
|
345
impls/ruby.2/stepA_mal.rb
Normal file
345
impls/ruby.2/stepA_mal.rb
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
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("eval") do |mal|
|
||||||
|
Mal.EVAL(mal, @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! *host-language* \"ruby.2\")")
|
||||||
|
Mal.rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
|
||||||
|
|
||||||
|
if !run_application?
|
||||||
|
Mal.rep("(def! *ARGV* (list))")
|
||||||
|
Mal.rep("(println (str \"Mal [\" \*host-language\* \"]\"))")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_application?
|
||||||
|
ARGV.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def run!
|
||||||
|
args = ARGV[1..].map(&:inspect)
|
||||||
|
|
||||||
|
if args.any?
|
||||||
|
Mal.rep("(def! *ARGV* (list #{args.join(" ")}))")
|
||||||
|
else
|
||||||
|
Mal.rep("(def! *ARGV* (list))")
|
||||||
|
end
|
||||||
|
|
||||||
|
file = File.absolute_path(ARGV.first)
|
||||||
|
|
||||||
|
Dir.chdir(File.dirname(file)) do
|
||||||
|
Mal.rep("(load-file #{file.inspect})")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def READ(input)
|
||||||
|
read_str(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
def EVAL(ast, environment)
|
||||||
|
loop do
|
||||||
|
ast = macro_expand(ast, environment)
|
||||||
|
|
||||||
|
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("defmacro!")
|
||||||
|
_, sym, val = ast
|
||||||
|
result = EVAL(val, environment)
|
||||||
|
|
||||||
|
case result
|
||||||
|
when Types::Function
|
||||||
|
return environment.set(sym, result.to_macro)
|
||||||
|
else
|
||||||
|
raise TypeError, "defmacro! must be bound to a function"
|
||||||
|
end
|
||||||
|
when Types::Symbol.for("macroexpand")
|
||||||
|
_, ast_rest = ast
|
||||||
|
return macro_expand(ast_rest, environment)
|
||||||
|
when Types::Symbol.for("let*")
|
||||||
|
e = Env.new(environment)
|
||||||
|
_, bindings, val = ast
|
||||||
|
bindings = bindings.dup # TODO note bugfix let bindings w/ TCO loop and destructive mutation (shift)
|
||||||
|
|
||||||
|
unless Types::List === bindings || Types::Vector === bindings
|
||||||
|
raise InvalidLetBindingsError, "let* bindings must be a list or vector"
|
||||||
|
end
|
||||||
|
|
||||||
|
until bindings.empty?
|
||||||
|
k, v = bindings.shift(2)
|
||||||
|
|
||||||
|
raise InvalidLetBindingsError, "Invalid let* bindings 'nil' key" 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, "No expression to evaluate when true"
|
||||||
|
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
|
||||||
|
when Types::Symbol.for("quote")
|
||||||
|
_, ret = ast
|
||||||
|
return ret
|
||||||
|
when Types::Symbol.for("quasiquote")
|
||||||
|
_, ast_rest = ast
|
||||||
|
ast = quasiquote(ast_rest)
|
||||||
|
when Types::Symbol.for("quasiquoteexpand")
|
||||||
|
_, ast_rest = ast
|
||||||
|
return quasiquote(ast_rest)
|
||||||
|
when Types::Symbol.for("try*")
|
||||||
|
_, to_try, catch_list = ast
|
||||||
|
|
||||||
|
begin
|
||||||
|
return EVAL(to_try, environment)
|
||||||
|
rescue => e
|
||||||
|
raise e if catch_list.nil? || catch_list&.empty?
|
||||||
|
raise SyntaxError, "try* missing proper catch*" unless catch_list&.first == Types::Symbol.for("catch*")
|
||||||
|
|
||||||
|
_, exception_symbol, exception_handler = catch_list
|
||||||
|
|
||||||
|
value =
|
||||||
|
if e.is_a?(MalError)
|
||||||
|
e.value
|
||||||
|
else
|
||||||
|
Types::String.new(e.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
return EVAL(
|
||||||
|
exception_handler,
|
||||||
|
Env.new(
|
||||||
|
environment,
|
||||||
|
Types::List.new([exception_symbol]),
|
||||||
|
Types::List.new([value])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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?
|
||||||
|
if (args = evaluated[1..]).any?
|
||||||
|
return maybe_callable.call(Types::Args.new(args))
|
||||||
|
else
|
||||||
|
return maybe_callable.call(Types::Args.new)
|
||||||
|
end
|
||||||
|
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 MalError => e
|
||||||
|
"Error: #{pr_str(e.value, true)}"
|
||||||
|
rescue Error, TypeError => e
|
||||||
|
"#{e.class} -- #{e.message}"
|
||||||
|
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
|
||||||
|
|
||||||
|
def quasiquote_list(mal)
|
||||||
|
result = Types::List.new
|
||||||
|
|
||||||
|
mal.reverse_each do |elt|
|
||||||
|
if elt.is_a?(Types::List) && elt.first == Types::Symbol.for("splice-unquote")
|
||||||
|
result = Types::List.new([
|
||||||
|
Types::Symbol.for("concat"),
|
||||||
|
elt[1],
|
||||||
|
result
|
||||||
|
])
|
||||||
|
else
|
||||||
|
result = Types::List.new([
|
||||||
|
Types::Symbol.for("cons"),
|
||||||
|
quasiquote(elt),
|
||||||
|
result
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def quasiquote(mal)
|
||||||
|
case mal
|
||||||
|
when Types::List
|
||||||
|
if mal.first == Types::Symbol.for("unquote")
|
||||||
|
mal[1]
|
||||||
|
else
|
||||||
|
quasiquote_list(mal)
|
||||||
|
end
|
||||||
|
when Types::Vector
|
||||||
|
Types::List.new([
|
||||||
|
Types::Symbol.for("vec"),
|
||||||
|
quasiquote_list(mal)
|
||||||
|
])
|
||||||
|
when Types::Hashmap, Types::Symbol
|
||||||
|
Types::List.new([
|
||||||
|
Types::Symbol.for("quote"),
|
||||||
|
mal
|
||||||
|
])
|
||||||
|
else
|
||||||
|
mal
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_macro_call?(mal, env)
|
||||||
|
return false unless Types::List === mal
|
||||||
|
return false unless Types::Symbol === mal.first
|
||||||
|
val = env.get(mal.first)
|
||||||
|
return false unless Types::Callable === val
|
||||||
|
val.is_macro?
|
||||||
|
rescue SymbolNotFoundError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def macro_expand(mal, env)
|
||||||
|
while is_macro_call?(mal, env)
|
||||||
|
macro_fn = env.get(mal.first)
|
||||||
|
|
||||||
|
if (args = mal[1..]).any?
|
||||||
|
mal = macro_fn.call(Types::Args.new(mal[1..]))
|
||||||
|
else
|
||||||
|
mal = macro_fn.call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mal
|
||||||
|
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
|
@ -1,18 +1,45 @@
|
|||||||
module Mal
|
module Mal
|
||||||
module Types
|
module Types
|
||||||
|
class Args < ::Array
|
||||||
|
end
|
||||||
|
|
||||||
class List < ::Array
|
class List < ::Array
|
||||||
|
def meta
|
||||||
|
@meta ||= Types::Nil.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
def meta=(value)
|
||||||
|
@meta = value
|
||||||
|
end
|
||||||
|
|
||||||
def to_list
|
def to_list
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Vector < ::Array
|
class Vector < ::Array
|
||||||
|
def meta
|
||||||
|
@meta ||= Types::Nil.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
def meta=(value)
|
||||||
|
@meta = value
|
||||||
|
end
|
||||||
|
|
||||||
def to_list
|
def to_list
|
||||||
List.new(self)
|
List.new(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Hashmap < ::Hash; end
|
class Hashmap < ::Hash
|
||||||
|
def meta
|
||||||
|
@meta ||= Types::Nil.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
def meta=(value)
|
||||||
|
@meta = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Base < ::Struct.new(:value)
|
class Base < ::Struct.new(:value)
|
||||||
def inspect
|
def inspect
|
||||||
@ -101,12 +128,14 @@ module Mal
|
|||||||
@fn = block
|
@fn = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(args)
|
def call(args = nil)
|
||||||
@fn.call(args)
|
args = Types::Args.new if args.nil?
|
||||||
|
raise unless args.is_a?(Types::Args)
|
||||||
|
@fn.call(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
raise NotImplementedError
|
raise NotImplementedError, "invalid callable"
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_mal_fn?
|
def is_mal_fn?
|
||||||
@ -116,11 +145,26 @@ module Mal
|
|||||||
def is_macro?
|
def is_macro?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def meta
|
||||||
|
@meta ||= Types::Nil.instance
|
||||||
|
end
|
||||||
|
|
||||||
|
def meta=(value)
|
||||||
|
@meta = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Builtin < Callable
|
class Builtin < Callable
|
||||||
|
attr_reader :name
|
||||||
|
|
||||||
|
def initialize(name, &block)
|
||||||
|
@name = name
|
||||||
|
@fn = block
|
||||||
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<builtin>"
|
"#<builtin '#{name}'>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user