Ruby: add stepA_more and rest of core functions.
authorJoel Martin <github@martintribe.org>
Sun, 13 Apr 2014 19:37:56 +0000 (14:37 -0500)
committerJoel Martin <github@martintribe.org>
Sun, 13 Apr 2014 19:37:56 +0000 (14:37 -0500)
ruby/core.rb
ruby/printer.rb
ruby/reader.rb
ruby/stepA_more.rb [new file with mode: 0644]
ruby/types.rb

index 824b59b..212374a 100644 (file)
@@ -1,5 +1,11 @@
 $core_ns = {
     :"=" =>       lambda {|a,b| a == b},
+    :throw =>     lambda {|a| raise MalException.new(a), "Mal Exception"},
+    :nil? =>      lambda {|a| a == nil},
+    :true? =>     lambda {|a| a == true},
+    :false? =>    lambda {|a| a == false},
+    :symbol? =>   lambda {|a| a.is_a? Symbol},
+    :symbol? =>   lambda {|a| a.is_a? Symbol},
     :"pr-str" =>  lambda {|*a| a.map {|e| _pr_str(e, true)}.join(" ")},
     :"str" =>     lambda {|*a| a.map {|e| _pr_str(e, false)}.join("")},
     :"prn" =>     lambda {|*a| puts(a.map {|e| _pr_str(e, true)}.join(" "))},
@@ -12,8 +18,21 @@ $core_ns = {
     :- =>         lambda {|a,b| a - b},
     :* =>         lambda {|a,b| a * b},
     :/ =>         lambda {|a,b| a / b},
+
     :list =>      lambda {|*a| List.new a},
     :list? =>     lambda {|*a| a[0].is_a? List},
+    :vector =>    lambda {|*a| Vector.new a},
+    :vector? =>   lambda {|*a| a[0].is_a? Vector},
+    :"hash-map" =>lambda {|*a| Hash[a.each_slice(2).to_a]},
+    :map? =>      lambda {|a| a.is_a? Hash},
+    :assoc =>     lambda {|*a| a[0].merge(Hash[a.drop(1).each_slice(2).to_a])},
+    :dissoc =>    lambda {|*a| h = a[0].clone; a.drop(1).each{|k| h.delete k}; h},
+    :get =>       lambda {|a,b| a[b]},
+    :contains? => lambda {|a,b| a.key? b},
+    :keys =>      lambda {|a| List.new a.keys},
+    :vals =>      lambda {|a| List.new a.values},
+
+    :sequential? => lambda {|a| sequential?(a)},
     :cons =>      lambda {|a,b| List.new(b.clone.insert(0,a))},
     :concat =>    lambda {|*a| List.new(a && a.reduce(:concat) || [])},
     :nth =>       lambda {|a,b| a[b]},
@@ -21,5 +40,16 @@ $core_ns = {
     :rest =>      lambda {|a| List.new(a.size > 0 && a.drop(1) || [])},
     :empty? =>    lambda {|a| a.size == 0},
     :count =>     lambda {|a| a.size},
+    :conj =>      lambda {|*a| a[0].clone.conj(a.drop(1))},
+    :apply =>     lambda {|*a| a[0][*a[1..-2].concat(a[-1])]},
+    :map =>       lambda {|a,b| List.new(b.map {|e| a[e]})},
+
+    :"with-meta" => lambda {|a,b| x = a.clone; x.meta = b; x},
+    :meta =>      lambda {|a| a.meta},
+    :atom =>      lambda {|a| Atom.new(a)},
+    :atom? =>     lambda {|a| a.is_a? Atom},
+    :deref =>     lambda {|a| a.val},
+    :reset! =>    lambda {|a,b| a.val = b},
+    :swap! =>     lambda {|*a| a[0].val = a[1][*[a[0].val].concat(a.drop(2))]},
 }
 
index 424ca23..cfcd064 100644 (file)
@@ -7,12 +7,18 @@ def _pr_str(obj, print_readably=true)
             "(" + obj.map{|x| _pr_str(x, _r)}.join(" ") + ")"
         when Vector
             "[" + obj.map{|x| _pr_str(x, _r)}.join(" ") + "]"
+        when Hash
+            ret = []
+            obj.each{|k,v| ret.push(_pr_str(k,_r), _pr_str(v,_r))}
+            "{" + ret.join(" ") + "}"
         when String
             if _r
                 obj.inspect  # escape special characters
             else
                 obj
             end
+        when Atom
+            "(atom " + _pr_str(obj.val, true) + ")"
         when nil
             "nil"
         else
index d6d9b8d..1039105 100644 (file)
@@ -63,11 +63,15 @@ def read_form(rdr)
         when "`" then  rdr.next; List.new [:quasiquote, read_form(rdr)]
         when "~" then  rdr.next; List.new [:unquote, read_form(rdr)]
         when "~@" then rdr.next; List.new [:"splice-unquote", read_form(rdr)]
+        when "^" then  rdr.next; meta = read_form(rdr);
+                       List.new [:"with-meta", read_form(rdr), meta]
+        when "@" then  rdr.next; List.new [:deref, read_form(rdr)]
+
         when "(" then  read_list(rdr, List, "(", ")")
         when ")" then  raise "unexpected ')'"
         when "[" then  read_list(rdr, Vector, "[", "]")
         when "]" then  raise "unexpected ']'" 
-        when "{" then  raise "unexpected '{'" 
+        when "{" then  Hash[read_list(rdr, List, "{", "}").each_slice(2).to_a]
         when "}" then  raise "unexpected '}'" 
         else           read_atom(rdr)
     end
diff --git a/ruby/stepA_more.rb b/ruby/stepA_more.rb
new file mode 100644 (file)
index 0000000..ed8a4f8
--- /dev/null
@@ -0,0 +1,179 @@
+require "readline"
+require "types"
+require "reader"
+require "printer"
+require "env"
+require "core"
+
+# read
+def READ(str)
+    return read_str(str)
+end
+
+# eval
+def is_pair(x)
+    return sequential?(x) && x.size > 0
+end
+
+def quasiquote(ast)
+    if not is_pair(ast)
+        return List.new([:quote, ast])
+    elsif ast[0] == :unquote
+        return ast[1]
+    elsif is_pair(ast[0]) && ast[0][0] == :"splice-unquote"
+        #p "xxx:", ast, List.new([:concat, ast[0][1], quasiquote(ast.drop(1))])
+        return List.new([:concat, ast[0][1], quasiquote(ast.drop(1))])
+    else
+        return List.new([:cons, quasiquote(ast[0]), quasiquote(ast.drop(1))])
+    end
+end
+
+def macro_call?(ast, env)
+    return (ast.is_a?(List) &&
+            ast[0].is_a?(Symbol) &&
+            env.find(ast[0]) &&
+            env.get(ast[0]).is_a?(Function) &&
+            env.get(ast[0]).is_macro)
+end
+
+def macroexpand(ast, env)
+    while macro_call?(ast, env)
+        mac = env.get(ast[0])
+        ast = mac[*ast.drop(1)]
+    end
+    return ast
+end
+
+def eval_ast(ast, env)
+    return case ast
+        when Symbol
+            env.get(ast)
+        when List   
+            List.new ast.map{|a| EVAL(a, env)}
+        when Vector
+            Vector.new ast.map{|a| EVAL(a, env)}
+        else 
+            ast
+    end
+end
+
+def EVAL(ast, env)
+    while true
+
+    #puts "EVAL: #{_pr_str(ast, true)}"
+
+    if not ast.is_a? List
+        return eval_ast(ast, env)
+    end
+
+    # apply list
+    ast = macroexpand(ast, env)
+    return ast if not ast.is_a? List
+
+    a0,a1,a2,a3 = ast
+    case a0
+    when :def!
+        return env.set(a1, EVAL(a2, env))
+    when :"let*"
+        let_env = Env.new(env)
+        a1.each_slice(2) do |a,e|
+            let_env.set(a, EVAL(e, let_env))
+        end
+        return EVAL(a2, let_env)
+    when :quote
+        return a1
+    when :quasiquote
+        return EVAL(quasiquote(a1), env)
+    when :defmacro!
+        func = EVAL(a2, env)
+        func.is_macro = true
+        return env.set(a1, func)
+    when :macroexpand
+        return macroexpand(a1, env)
+    when :"rb*"
+        return eval(a1)
+    when :"try*"
+        begin
+            return EVAL(a1, env)
+        rescue Exception => exc
+            if exc.is_a? MalException
+                exc = exc.data
+            else
+                exc = exc.message
+            end
+            if a2 && a2[0] == :"catch*"
+                return EVAL(a2[2], Env.new(env, [a2[1]], [exc]))
+            else
+                raise esc
+            end
+        end
+    when :do
+        eval_ast(ast[1..-2], env)
+        ast = ast.last
+    when :if
+        cond = EVAL(a1, env)
+        if not cond
+            return nil if a3 == nil
+            ast = a3
+        else
+            ast = a2
+        end
+    when :"fn*"
+        return Function.new(a2, env, a1) {|*args|
+            EVAL(a2, Env.new(env, a1, args))
+        }
+    else
+        el = eval_ast(ast, env)
+        f = el[0]
+        if f.class == Function
+            ast = f.ast
+            env = f.gen_env(el.drop(1))
+        else
+            return f[*el.drop(1)]
+        end
+    end
+
+    end
+end
+
+# print
+def PRINT(exp)
+    return _pr_str(exp, true)
+end
+
+# repl
+repl_env = Env.new
+RE = lambda {|str| EVAL(READ(str), repl_env) }
+REP = lambda {|str| PRINT(EVAL(READ(str), repl_env)) }
+_ref = lambda {|k,v| repl_env.set(k, v) }
+
+# Import core functions
+$core_ns.each &_ref
+
+_ref[:"readline", lambda {|prompt| Readline.readline(prompt,true)}]
+_ref[:"read-string", lambda {|str| read_str str}]
+_ref[:eval, lambda {|ast| EVAL(ast, repl_env)}]
+_ref[:slurp, lambda {|f| File.read(f) }]
+
+# Defined using the language itself
+RE["(def! not (fn* (a) (if a false true)))"]
+RE["(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)))))))"]
+RE["(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"]
+
+RE["(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"]
+
+p Dir.pwd
+if ARGV.size > 0
+    ARGV.each {|f|
+        RE["(load-file \"" + f + "\")"]
+    }
+    exit 0
+end
+while line = Readline.readline("user> ", true)
+    begin
+        puts REP[line]
+    rescue Exception => e
+        puts "Error: #{e}" 
+        puts "\t#{e.backtrace.join("\n\t")}"
+    end
+end
index c19fda4..b16e391 100644 (file)
@@ -1,9 +1,30 @@
 require "env"
 
+class MalException < StandardError
+  attr_reader :data
+  def initialize(data)
+    @data = data
+  end
+end
+
 class List < Array
+    attr_accessor :meta
+    def conj(xs)
+        xs.each{|x| self.unshift(x)}
+        return self
+    end
 end
 
 class Vector < Array
+    attr_accessor :meta
+    def conj(xs)
+        self.push(*xs)
+        return self
+    end
+end
+
+class Hash # re-open and add meta
+    attr_accessor :meta
 end
 
 def sequential?(obj)
@@ -11,6 +32,7 @@ def sequential?(obj)
 end
 
 class Function < Proc
+    attr_accessor :meta
     attr_accessor :ast
     attr_accessor :env
     attr_accessor :params
@@ -28,3 +50,11 @@ class Function < Proc
         return Env.new(@env, @params, args)
     end
 end
+
+class Atom
+    attr_accessor :meta
+    attr_accessor :val
+    def initialize(val)
+        @val = val
+    end
+end