From f1be2c471faac14b815bc12e9f1632459828ee38 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Fri, 10 Dec 2021 17:56:47 -0700 Subject: [PATCH] ruby.2 step A; functional self-hosting of mal on mal; refactor if internal mal fn arg passing; --- impls/ruby.2/core.rb | 335 +++++++++++++++++--------------- impls/ruby.2/env.rb | 2 +- impls/ruby.2/printer.rb | 2 +- impls/ruby.2/reader.rb | 18 +- impls/ruby.2/step4_if_fn_do.rb | 4 +- impls/ruby.2/step5_tco.rb | 4 +- impls/ruby.2/step6_file.rb | 15 +- impls/ruby.2/step7_quote.rb | 15 +- impls/ruby.2/step8_macros.rb | 22 +-- impls/ruby.2/step9_try.rb | 21 +- impls/ruby.2/stepA_mal.rb | 345 +++++++++++++++++++++++++++++++++ impls/ruby.2/types.rb | 54 +++++- 12 files changed, 617 insertions(+), 220 deletions(-) create mode 100644 impls/ruby.2/stepA_mal.rb diff --git a/impls/ruby.2/core.rb b/impls/ruby.2/core.rb index ad114036..70452cca 100644 --- a/impls/ruby.2/core.rb +++ b/impls/ruby.2/core.rb @@ -1,3 +1,5 @@ +require "readline" + require_relative "types" module Mal @@ -6,59 +8,57 @@ module Mal def ns { - Types::Symbol.for("+") => Types::Builtin.new do |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 }, - Types::Symbol.for("list") => Types::Builtin.new do |mal| + Types::Symbol.for("list") => Types::Builtin.new("list") 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 + Types::Symbol.for("list?") => Types::Builtin.new("list?") do |list = nil| + list.is_a?(Types::List) ? Types::True.instance : Types::False.instance end, - Types::Symbol.for("vector?") => Types::Builtin.new do |mal| - is_vector = - if mal.any? - Types::Vector === mal.first - else - false - end - - is_vector ? Types::True.instance : Types::False.instance + Types::Symbol.for("vector?") => Types::Builtin.new("vector?") do |vector = nil| + vector.is_a?(Types::Vector) ? Types::True.instance : Types::False.instance 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 = - if mal.any? - case mal.first - when Types::List, Types::Vector - mal.first.empty? - else - Types::True.instance - end + case list_or_vector + when Types::List, Types::Vector + list_or_vector.empty? else - Types::True.instance + true end is_empty ? Types::True.instance : Types::False.instance end, - Types::Symbol.for("count") => Types::Builtin.new do |mal| + Types::Symbol.for("count") => Types::Builtin.new("count") do |*mal| count = if mal.any? case mal.first @@ -74,9 +74,7 @@ module Mal Types::Number.new(count) end, - Types::Symbol.for("=") => Types::Builtin.new do |mal| - a, b = mal - + Types::Symbol.for("=") => Types::Builtin.new("=") do |a, b| if a.nil? || b.nil? Types::False.instance else @@ -88,9 +86,7 @@ module Mal end end, - Types::Symbol.for("<") => Types::Builtin.new do |mal| - a, b = mal - + Types::Symbol.for("<") => Types::Builtin.new("<") do |a, b| if a.nil? || b.nil? Types::False.instance else @@ -106,9 +102,7 @@ module Mal end end, - Types::Symbol.for("<=") => Types::Builtin.new do |mal| - a, b = mal - + Types::Symbol.for("<=") => Types::Builtin.new("<=") do |a, b| if a.nil? || b.nil? Types::False.instance else @@ -124,9 +118,7 @@ module Mal end end, - Types::Symbol.for(">") => Types::Builtin.new do |mal| - a, b = mal - + Types::Symbol.for(">") => Types::Builtin.new(">") do |a, b| if a.nil? || b.nil? Types::False.instance else @@ -142,9 +134,7 @@ module Mal end end, - Types::Symbol.for(">=") => Types::Builtin.new do |mal| - a, b = mal - + Types::Symbol.for(">=") => Types::Builtin.new(">=") do |a, b| if a.nil? || b.nil? Types::False.instance else @@ -160,59 +150,57 @@ module Mal 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(" ")) 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("")) 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(" ") Types::Nil.instance 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(" ") 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) + Types::Symbol.for("read-string") => Types::Builtin.new("read-string") do |string = nil| + if string.is_a?(Types::String) + Mal.read_str(string.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)) + Types::Symbol.for("slurp") => Types::Builtin.new("slurp") do |file = nil| + if file.is_a?(Types::String) + if File.exist?(file.value) + Types::String.new(File.read(file.value)) else - raise FileNotFoundError, mal.first.value + raise FileNotFoundError, file.value end else Types::Nil.instance end end, - Types::Symbol.for("atom") => Types::Builtin.new do |mal| - Types::Atom.new(mal.first) + Types::Symbol.for("atom") => Types::Builtin.new("atom") do |mal| + Types::Atom.new(mal) end, - Types::Symbol.for("atom?") => Types::Builtin.new do |mal| - mal.first.is_a?(Types::Atom) ? Types::True.instance : Types::False.instance + Types::Symbol.for("atom?") => Types::Builtin.new("atom?") do |maybe_atom| + maybe_atom.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 + Types::Symbol.for("deref") => Types::Builtin.new("deref") do |maybe_atom| + maybe_atom.is_a?(Types::Atom) ? maybe_atom.value : Types::Nil.instance end, - Types::Symbol.for("reset!") => Types::Builtin.new do |mal| - atom, value = mal - + Types::Symbol.for("reset!") => Types::Builtin.new("reset!") do |atom, value| if value.nil? value = Types::Nil.instance end @@ -220,17 +208,15 @@ module Mal 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])) + Types::Symbol.for("swap!") => Types::Builtin.new("swap!") do |atom, fn, *args| + atom.value = fn.call(Types::Args.new([atom.value, *args])) end, - Types::Symbol.for("cons") => Types::Builtin.new do |mal| - val, list_or_vector = mal + Types::Symbol.for("cons") => Types::Builtin.new("cons") do |val, list_or_vector| Types::List.new([val, *list_or_vector]) end, - Types::Symbol.for("concat") => Types::Builtin.new do |mal| + Types::Symbol.for("concat") => Types::Builtin.new("concat") do |*mal| list = Types::List.new mal.each do |l| @@ -240,33 +226,30 @@ module Mal list end, - Types::Symbol.for("vec") => Types::Builtin.new do |mal| - case mal.first + Types::Symbol.for("vec") => Types::Builtin.new("vec") do |list_or_vector| + case list_or_vector when Types::List vec = Types::Vector.new - mal.first.each do |m| + list_or_vector.each do |m| vec << m end vec when Types::Vector - mal.first + list_or_vector else - raise TypeError + raise TypeError, "invalid `vec` arguments, must be vector or list" end end, - Types::Symbol.for("nth") => Types::Builtin.new do |mal| - list_or_vector, index = mal + Types::Symbol.for("nth") => Types::Builtin.new("nth") do |list_or_vector, index| result = list_or_vector[index.value] raise IndexError, "Index #{index.value} is out of bounds" if result.nil? result end, - Types::Symbol.for("first") => Types::Builtin.new do |mal| - list_or_vector, * = mal - + Types::Symbol.for("first") => Types::Builtin.new("first") do |list_or_vector| if !list_or_vector.nil? && list_or_vector != Types::Nil.instance result = list_or_vector.first @@ -280,9 +263,7 @@ module Mal end end, - Types::Symbol.for("rest") => Types::Builtin.new do |mal| - list_or_vector, * = mal - + Types::Symbol.for("rest") => Types::Builtin.new("rest") do |list_or_vector| if !list_or_vector.nil? && list_or_vector != Types::Nil.instance result = list_or_vector[1..] @@ -296,15 +277,12 @@ module Mal end end, - Types::Symbol.for("throw") => Types::Builtin.new do |mal| - to_throw, * = mal + Types::Symbol.for("throw") => Types::Builtin.new("throw") do |to_throw| raise MalError, to_throw end, - Types::Symbol.for("apply") => Types::Builtin.new do |mal| - fn, *rest = mal - - args = Types::List.new + Types::Symbol.for("apply") => Types::Builtin.new("apply") do |fn, *rest| + args = Types::Args.new rest.flatten(1).each do |a| args << a @@ -313,68 +291,57 @@ module Mal fn.call(args) end, - Types::Symbol.for("map") => Types::Builtin.new do |mal| - fn, rest = mal - - map_with = - case rest - when Types::List, Types::Vector - rest - else - raise SyntaxError, "Must pass list/vector to map!" - end - + Types::Symbol.for("map") => Types::Builtin.new("map") do |fn, *rest| results = Types::List.new - map_with.each do |a| - results << fn.call(a) + rest.flatten(1).each do |a| + results << fn.call(Types::Args.new([a])) end results end, - Types::Symbol.for("nil?") => Types::Builtin.new do |mal| - if mal&.first == Types::Nil.instance + Types::Symbol.for("nil?") => Types::Builtin.new("nil?") do |mal| + if mal == Types::Nil.instance Types::True.instance else Types::False.instance end end, - Types::Symbol.for("true?") => Types::Builtin.new do |mal| - if mal&.first == Types::True.instance + Types::Symbol.for("true?") => Types::Builtin.new("true?") do |mal| + if mal == Types::True.instance Types::True.instance else Types::False.instance end end, - Types::Symbol.for("false?") => Types::Builtin.new do |mal| - if mal&.first == Types::False.instance + Types::Symbol.for("false?") => Types::Builtin.new("false?") do |mal| + if mal == Types::False.instance Types::True.instance else Types::False.instance end end, - Types::Symbol.for("symbol?") => Types::Builtin.new do |mal| - if mal&.first&.is_a?(Types::Symbol) + Types::Symbol.for("symbol?") => Types::Builtin.new("symbol?") do |mal| + if mal.is_a?(Types::Symbol) Types::True.instance else Types::False.instance end end, - Types::Symbol.for("keyword?") => Types::Builtin.new do |mal| - if mal&.first&.is_a?(Types::Keyword) + Types::Symbol.for("keyword?") => Types::Builtin.new("keyword?") do |mal| + if mal.is_a?(Types::Keyword) Types::True.instance else Types::False.instance end end, - Types::Symbol.for("symbol") => Types::Builtin.new do |mal| - string, * = mal + Types::Symbol.for("symbol") => Types::Builtin.new("symbol") do |string| if string Types::Symbol.for(string.value) else @@ -382,18 +349,15 @@ module Mal end end, - Types::Symbol.for("keyword") => Types::Builtin.new do |mal| - string, * = mal - if string - Types::Keyword.for(string.value) + Types::Symbol.for("keyword") => Types::Builtin.new("keyword") do |keyword| + if keyword + Types::Keyword.for(keyword.value) else Types::Nil.instance end end, - Types::Symbol.for("vector") => Types::Builtin.new do |mal| - *items = mal - + Types::Symbol.for("vector") => Types::Builtin.new("vector") do |*items| vector = Types::Vector.new items.each do |i| @@ -403,9 +367,7 @@ module Mal vector end, - Types::Symbol.for("sequential?") => Types::Builtin.new do |mal| - list_or_vector, * = mal - + Types::Symbol.for("sequential?") => Types::Builtin.new("sequential?") do |list_or_vector| case list_or_vector when Types::List, Types::Vector Types::True.instance @@ -414,10 +376,8 @@ module Mal end end, - Types::Symbol.for("hash-map") => Types::Builtin.new do |mal| - *items = mal - - raise UnbalancedHashmapError if items&.size&.odd? + Types::Symbol.for("hash-map") => Types::Builtin.new("hash-map") do |*items| + raise UnbalancedHashmapError, "unbalanced hashmap error, arguments must be even" if items&.size&.odd? hashmap = Types::Hashmap.new @@ -428,18 +388,16 @@ module Mal hashmap end, - Types::Symbol.for("map?") => Types::Builtin.new do |mal| - if mal&.first&.is_a?(Types::Hashmap) + Types::Symbol.for("map?") => Types::Builtin.new("map?") do |mal| + if mal.is_a?(Types::Hashmap) Types::True.instance else Types::False.instance end end, - Types::Symbol.for("assoc") => Types::Builtin.new do |mal| - hashmap, *items = mal - - raise UnbalancedHashmapError if items&.size&.odd? + Types::Symbol.for("assoc") => Types::Builtin.new("assoc") do |hashmap, *items| + raise UnbalancedHashmapError, "unbalanced hashmap error, arguments must be even" if items.size&.odd? new_hashmap = hashmap.dup @@ -450,9 +408,7 @@ module Mal new_hashmap end, - Types::Symbol.for("dissoc") => Types::Builtin.new do |mal| - hashmap, *keys = mal - + Types::Symbol.for("dissoc") => Types::Builtin.new("dissoc") do |hashmap, *keys| new_hashmap = Types::Hashmap.new hashmap.keys.each do |k| @@ -463,9 +419,7 @@ module Mal new_hashmap end, - Types::Symbol.for("get") => Types::Builtin.new do |mal| - hashmap, key = mal - + Types::Symbol.for("get") => Types::Builtin.new("get") do |hashmap, key| if Types::Hashmap === hashmap && key && hashmap.key?(key) hashmap[key] else @@ -473,9 +427,7 @@ module Mal end end, - Types::Symbol.for("contains?") => Types::Builtin.new do |mal| - hashmap, key = mal - + Types::Symbol.for("contains?") => Types::Builtin.new("contains?") do |hashmap, key| if Types::Hashmap === hashmap && key && hashmap.key?(key) Types::True.instance else @@ -483,9 +435,7 @@ module Mal end end, - Types::Symbol.for("keys") => Types::Builtin.new do |mal| - hashmap, * = mal - + Types::Symbol.for("keys") => Types::Builtin.new("keys") do |hashmap| if Types::Hashmap === hashmap Types::List.new(hashmap.keys) else @@ -493,14 +443,91 @@ module Mal end end, - Types::Symbol.for("vals") => Types::Builtin.new do |mal| - hashmap, * = mal - + Types::Symbol.for("vals") => Types::Builtin.new("vals") do |hashmap| if Types::Hashmap === hashmap Types::List.new(hashmap.values) else Types::Nil.instance 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 diff --git a/impls/ruby.2/env.rb b/impls/ruby.2/env.rb index 060dc065..e6bb765b 100644 --- a/impls/ruby.2/env.rb +++ b/impls/ruby.2/env.rb @@ -13,7 +13,7 @@ module Mal spread_next = true else 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 else set(b, exprs[i] || Types::Nil.instance) diff --git a/impls/ruby.2/printer.rb b/impls/ruby.2/printer.rb index 1b658829..256e5a05 100644 --- a/impls/ruby.2/printer.rb +++ b/impls/ruby.2/printer.rb @@ -29,7 +29,7 @@ module Mal when Types::Base, Types::Callable mal.inspect else - raise InvalidTypeError + raise InvalidTypeError, "unable to print value <#{mal.inspect}>" end end diff --git a/impls/ruby.2/reader.rb b/impls/ruby.2/reader.rb index 38ba0322..be572c61 100644 --- a/impls/ruby.2/reader.rb +++ b/impls/ruby.2/reader.rb @@ -11,7 +11,7 @@ module Mal when /\A"(?:\\.|[^\\"])*"\z/ read_string(reader) when /\A"/ - raise UnbalancedStringError + raise UnbalancedStringError, "unbalanced string << #{reader.peek.inspect} >>" when /\A:/ read_keyword(reader) when "nil" @@ -73,13 +73,13 @@ module Mal key = read_form(reader) unless Types::String === key || Types::Keyword === key - raise InvalidHashmapKeyError + raise InvalidHashmapKeyError, "invalid hashmap key, must be string or keyword" end if reader.peek != "}" value = read_form(reader) else - raise UnbalancedHashmapError + raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'" end hashmap[key] = value @@ -90,7 +90,7 @@ module Mal rescue Error => e case e when InvalidReaderPositionError - raise UnbalancedHashmapError + raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'" else raise e end @@ -115,7 +115,7 @@ module Mal rescue Error => e case e when InvalidReaderPositionError - raise UnbalancedListError + raise UnbalancedListError, "unbalanced list error, missing closing ')'" else raise e end @@ -133,7 +133,7 @@ module Mal when /\d+/ Types::Number.new(reader.next.to_i) else - raise InvalidTypeError + raise InvalidTypeError, "invalid number syntax, only supports integers/floats" end end @@ -171,7 +171,7 @@ module Mal substitute_escaped_chars!(value) if raw_value.length <= 1 || raw_value[-1] != '"' - raise UnbalancedStringError + raise UnbalancedStringError, "unbalanced string error, missing closing '\"'" end Types::String.new(value) @@ -205,7 +205,7 @@ module Mal rescue Error => e case e when InvalidReaderPositionError - raise UnbalancedVectorError + raise UnbalancedVectorError, "unbalanced vector error, missing closing ']'" else raise e end @@ -253,7 +253,7 @@ module Mal def peek if @position > @tokens.size - 1 - raise InvalidReaderPositionError + raise InvalidReaderPositionError, "invalid reader position error, unable to parse mal expression" end @tokens[@position] diff --git a/impls/ruby.2/step4_if_fn_do.rb b/impls/ruby.2/step4_if_fn_do.rb index b833c163..5133f517 100644 --- a/impls/ruby.2/step4_if_fn_do.rb +++ b/impls/ruby.2/step4_if_fn_do.rb @@ -85,7 +85,7 @@ module Mal when Types::Symbol.for("fn*") _, 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)) end else @@ -93,7 +93,7 @@ module Mal maybe_callable = evaluated.first if maybe_callable.respond_to?(:call) - maybe_callable.call(evaluated[1..]) + maybe_callable.call(Types::Args.new(evaluated[1..])) else raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." end diff --git a/impls/ruby.2/step5_tco.rb b/impls/ruby.2/step5_tco.rb index 21cfee17..d6b4085c 100644 --- a/impls/ruby.2/step5_tco.rb +++ b/impls/ruby.2/step5_tco.rb @@ -89,7 +89,7 @@ module Mal when Types::Symbol.for("fn*") _, 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)) end else @@ -97,7 +97,7 @@ module Mal maybe_callable = evaluated.first 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? # Continue loop ast = maybe_callable.ast diff --git a/impls/ruby.2/step6_file.rb b/impls/ruby.2/step6_file.rb index 6f4098d6..3325d5e7 100644 --- a/impls/ruby.2/step6_file.rb +++ b/impls/ruby.2/step6_file.rb @@ -19,8 +19,8 @@ module Mal @repl_env.set( Types::Symbol.for("eval"), - Types::Builtin.new do |mal| - Mal.EVAL(mal.first, @repl_env) + Types::Builtin.new("eval") do |mal| + Mal.EVAL(mal, @repl_env) end ) @@ -108,14 +108,7 @@ module Mal 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 - + return Types::Function.new(to_eval, binds, environment) do |*exprs| EVAL(to_eval, Env.new(environment, binds, exprs)) end else @@ -131,7 +124,7 @@ module Mal evaluated[1..], ) 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 raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." end diff --git a/impls/ruby.2/step7_quote.rb b/impls/ruby.2/step7_quote.rb index 17ec94a8..837ac721 100644 --- a/impls/ruby.2/step7_quote.rb +++ b/impls/ruby.2/step7_quote.rb @@ -19,8 +19,8 @@ module Mal @repl_env.set( Types::Symbol.for("eval"), - Types::Builtin.new do |mal| - Mal.EVAL(mal.first, @repl_env) + Types::Builtin.new("eval") do |mal| + Mal.EVAL(mal, @repl_env) end ) @@ -108,14 +108,7 @@ module Mal 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 - + return Types::Function.new(to_eval, binds, environment) do |*exprs| EVAL(to_eval, Env.new(environment, binds, exprs)) end when Types::Symbol.for("quote") @@ -140,7 +133,7 @@ module Mal evaluated[1..], ) 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 raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." end diff --git a/impls/ruby.2/step8_macros.rb b/impls/ruby.2/step8_macros.rb index 8834d0a3..f65deea5 100644 --- a/impls/ruby.2/step8_macros.rb +++ b/impls/ruby.2/step8_macros.rb @@ -19,8 +19,8 @@ module Mal @repl_env.set( Types::Symbol.for("eval"), - Types::Builtin.new do |mal| - Mal.EVAL(mal.first, @repl_env) + Types::Builtin.new("eval") do |mal| + Mal.EVAL(mal, @repl_env) end ) @@ -124,14 +124,7 @@ module Mal 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 - + return Types::Function.new(to_eval, binds, environment) do |*exprs| EVAL(to_eval, Env.new(environment, binds, exprs)) end when Types::Symbol.for("quote") @@ -156,7 +149,7 @@ module Mal evaluated[1..], ) 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 raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." end @@ -276,7 +269,12 @@ module Mal def macro_expand(mal, env) while is_macro_call?(mal, env) 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 mal diff --git a/impls/ruby.2/step9_try.rb b/impls/ruby.2/step9_try.rb index ea40a5f7..9cfa409c 100644 --- a/impls/ruby.2/step9_try.rb +++ b/impls/ruby.2/step9_try.rb @@ -19,7 +19,7 @@ module Mal @repl_env.set( Types::Symbol.for("eval"), - Types::Builtin.new do |mal| + Types::Builtin.new("eval") do |mal| Mal.EVAL(mal.first, @repl_env) end ) @@ -131,15 +131,7 @@ module Mal when Types::Symbol.for("fn*") _, binds, to_eval = ast - 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 - + return Types::Function.new(to_eval, binds, environment) do |*exprs| EVAL(to_eval, Env.new(environment, binds, exprs)) end when Types::Symbol.for("quote") @@ -191,7 +183,7 @@ module Mal evaluated[1..], ) 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 raise NotCallableError, "Error! #{PRINT(maybe_callable)} is not callable." end @@ -313,7 +305,12 @@ module Mal def macro_expand(mal, env) while is_macro_call?(mal, env) 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 mal diff --git a/impls/ruby.2/stepA_mal.rb b/impls/ruby.2/stepA_mal.rb new file mode 100644 index 00000000..16283a15 --- /dev/null +++ b/impls/ruby.2/stepA_mal.rb @@ -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 diff --git a/impls/ruby.2/types.rb b/impls/ruby.2/types.rb index c048d6ef..d50c50b4 100644 --- a/impls/ruby.2/types.rb +++ b/impls/ruby.2/types.rb @@ -1,18 +1,45 @@ module Mal module Types + class Args < ::Array + end + class List < ::Array + def meta + @meta ||= Types::Nil.instance + end + + def meta=(value) + @meta = value + end + def to_list self end end class Vector < ::Array + def meta + @meta ||= Types::Nil.instance + end + + def meta=(value) + @meta = value + end + def to_list List.new(self) 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) def inspect @@ -101,12 +128,14 @@ module Mal @fn = block end - def call(args) - @fn.call(args) + def call(args = nil) + args = Types::Args.new if args.nil? + raise unless args.is_a?(Types::Args) + @fn.call(*args) end def inspect - raise NotImplementedError + raise NotImplementedError, "invalid callable" end def is_mal_fn? @@ -116,11 +145,26 @@ module Mal def is_macro? false end + + def meta + @meta ||= Types::Nil.instance + end + + def meta=(value) + @meta = value + end end class Builtin < Callable + attr_reader :name + + def initialize(name, &block) + @name = name + @fn = block + end + def inspect - "#" + "#" end end