Julia: step 8
authorJoel Martin <github@martintribe.org>
Sun, 29 Mar 2015 22:55:11 +0000 (17:55 -0500)
committerJoel Martin <github@martintribe.org>
Wed, 1 Apr 2015 04:04:44 +0000 (23:04 -0500)
julia/core.jl
julia/printer.jl
julia/step6_file.jl
julia/step7_quote.jl
julia/step8_macros.jl [new file with mode: 0755]
julia/types.jl

index 9e132be..cdf599f 100644 (file)
@@ -1,12 +1,22 @@
 module core
 
+import types
 import reader
 using printer
 
 export ns
 
+
+function concat(args...)
+    res = {}
+    for a=args
+        res = [res, Any[a...]]
+    end
+    res
+end
+
 ns = {
-    symbol("=") => (a,b) -> a == b,
+    symbol("=") => (a,b) -> types.equal_Q(a, b),
 
     symbol("pr-str") => (a...) -> join(map((e)->pr_str(e, true),a)," "),
     :str => (a...) -> join(map((e)->pr_str(e, false),a),""),
@@ -28,7 +38,10 @@ ns = {
     symbol("list?") => (a) -> isa(a, Array),
 
     :cons => (a,b) -> [Any[a], Any[b...]],
-    :concat => (a...) -> [a...],
+    :concat => concat,
+    :nth => (a,b) -> b+1 > length(a) ? error("nth: index out of range") : a[b+1],
+    :first => (a) -> isempty(a) ? nothing : first(a),
+    :rest => (a) -> Any[a[2:end]...],
     symbol("empty?") => isempty,
     :count => (a) -> a == nothing ? 0 : length(a),
     }
index b94d265..c396980 100644 (file)
@@ -1,5 +1,7 @@
 module printer
 
+import types
+
 export pr_str
 
 function pr_str(obj, print_readably=true)
@@ -22,6 +24,8 @@ function pr_str(obj, print_readably=true)
         end
     elseif obj == nothing
         "nil"
+    elseif typeof(obj) == types.MalFunc
+        "(fn* $(pr_str(obj.params,true)) $(pr_str(obj.ast,true)))"
     else
         string(obj)
     end
index ef3eed3..4ec6e56 100755 (executable)
@@ -24,9 +24,7 @@ end
 
 function EVAL(ast, env)
   while true
-    if !isa(ast, Array)
-        return eval_ast(ast, env)
-    end
+    if !isa(ast, Array) return eval_ast(ast, env) end
 
     # apply
     if     :def! == ast[1]
index 851b11e..8cade10 100755 (executable)
@@ -40,9 +40,7 @@ end
 
 function EVAL(ast, env)
   while true
-    if !isa(ast, Array)
-        return eval_ast(ast, env)
-    end
+    if !isa(ast, Array) return eval_ast(ast, env) end
 
     # apply
     if     :def! == ast[1]
diff --git a/julia/step8_macros.jl b/julia/step8_macros.jl
new file mode 100755 (executable)
index 0000000..8616b3b
--- /dev/null
@@ -0,0 +1,170 @@
+#!/usr/bin/env julia
+
+import reader
+import printer
+using env
+import core
+using types
+
+# READ
+function READ(str)
+    reader.read_str(str)
+end
+
+# EVAL
+function ispair(ast)
+    (isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
+end
+
+function quasiquote(ast)
+    if !ispair(ast)
+        [[:quote], Any[ast]]
+    elseif ast[1] == :unquote
+        ast[2]
+    elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote")
+        [[:concat], Any[ast[1][2]], Any[quasiquote(ast[2:end])]]
+    else
+        [[:cons], Any[quasiquote(ast[1])], Any[quasiquote(ast[2:end])]]
+    end
+end
+
+function ismacroCall(ast, env)
+    return isa(ast, Array) &&
+           isa(ast[1], Symbol) &&
+           find(env, ast[1]) != nothing &&
+           isa(get(env, ast[1]), MalFunc) &&
+           get(env, ast[1]).ismacro
+end
+
+function macroexpand(ast, env)
+    while ismacroCall(ast, env)
+        mac = get(env, ast[1])
+        ast = mac.fn(ast[2:end]...)
+    end
+    ast
+end
+
+function eval_ast(ast, env)
+    if typeof(ast) == Symbol
+        get(env,ast)
+    elseif isa(ast, Array) || isa(ast, Tuple)
+        map((x) -> EVAL(x,env), ast)
+    else
+        ast
+    end
+end
+
+function EVAL(ast, env)
+  while true
+    #println("EVAL: $(printer.pr_str(ast,true))")
+    if !isa(ast, Array) return eval_ast(ast, env) end
+
+    # apply
+    ast = macroexpand(ast, env)
+    if !isa(ast, Array) return ast end
+
+    if     :def! == ast[1]
+        return set(env, ast[2], EVAL(ast[3], env))
+    elseif symbol("let*") == ast[1]
+        let_env = Env(env)
+        for i = 1:2:length(ast[2])
+            set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
+        end
+        env = let_env
+        ast = ast[3]
+        # TCO loop
+    elseif :quote == ast[1]
+        return ast[2]
+    elseif :quasiquote == ast[1]
+        ast = quasiquote(ast[2])
+        # TCO loop
+    elseif :defmacro! == ast[1]
+        func = EVAL(ast[3], env)
+        func.ismacro = true
+        return set(env, ast[2], func)
+    elseif :macroexpand == ast[1]
+        return macroexpand(ast[2], env)
+    elseif :do == ast[1]
+        eval_ast(ast[2:end-1], env)
+        ast = ast[end]
+        # TCO loop
+    elseif :if == ast[1]
+        cond = EVAL(ast[2], env)
+        if cond === nothing || cond === false
+            if length(ast) >= 4
+                ast = ast[4]
+                # TCO loop
+            else
+                return nothing
+            end
+        else
+            ast = ast[3]
+            # TCO loop
+        end
+    elseif symbol("fn*") == ast[1]
+        return MalFunc(
+            (args...) -> EVAL(ast[3], Env(env, ast[2], args)),
+            ast[3], env, ast[2])
+    else
+        el = eval_ast(ast, env)
+        f, args = el[1], el[2:end]
+        if isa(f, MalFunc)
+            ast = f.ast
+            env = Env(f.env, f.params, args)
+            # TCO loop
+        else
+            return f(args...)
+        end
+    end
+  end
+end
+
+# PRINT
+function PRINT(exp)
+    printer.pr_str(exp)
+end
+
+# REPL
+repl_env = nothing
+function REP(str)
+    return PRINT(EVAL(READ(str), repl_env))
+end
+
+# core.jl: defined using Julia
+repl_env = Env(nothing, core.ns)
+set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
+set(repl_env, symbol("*ARGV*"), ARGS[2:end])
+
+# core.mal: defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+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)))))))")
+REP("(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))))))))")
+
+
+if length(ARGS) > 0
+    REP("(load-file \"$(ARGS[1])\")")
+    exit(0)
+end
+
+while true
+    print("user> ")
+    flush(STDOUT)
+    line = readline(STDIN)
+    if line == ""
+        break
+    end
+    line = chomp(line)
+    try
+        println(REP(line))
+    catch e
+        if isa(e, ErrorException)
+            println("Error: $(e.msg)")
+        else
+            println("Error: $(string(e))")
+        end
+        bt = catch_backtrace()
+        Base.show_backtrace(STDERR, bt)
+        println()
+    end
+end
index 0f7b4ea..38612bb 100644 (file)
@@ -7,6 +7,30 @@ type MalFunc
     ast
     env
     params
+    ismacro
+end
+
+# ismacro default to false
+function MalFunc(fn, ast, env, params)
+    MalFunc(fn, ast, env, params, false)
+end
+
+function sequential_Q(obj)
+    isa(obj, Array) || isa(obj, Tuple)
+end
+
+function equal_Q(a, b)
+    ota = typeof(a)
+    otb = typeof(b)
+    if !(ota === otb || (sequential_Q(a) && sequential_Q(b)))
+        return false
+    end
+
+    if sequential_Q(a)
+        tuple(a...) == tuple(b...)
+    else
+        a === b
+    end
 end
 
 end