Julia: Step 9, hash maps and keywords.
authorJoel Martin <github@martintribe.org>
Wed, 1 Apr 2015 02:23:22 +0000 (21:23 -0500)
committerJoel Martin <github@martintribe.org>
Wed, 1 Apr 2015 04:04:44 +0000 (23:04 -0500)
julia/core.jl
julia/printer.jl
julia/reader.jl
julia/step2_eval.jl
julia/step9_try.jl [new file with mode: 0755]
julia/types.jl

index cdf599f..f2e14dd 100644 (file)
@@ -15,8 +15,23 @@ function concat(args...)
     res
 end
 
+function do_apply(f, all_args...)
+    fn = isa(f,types.MalFunc) ? f.fn : f
+    args = concat(all_args[1:end-1], all_args[end])
+    fn(args...)
+end
+
 ns = {
     symbol("=") => (a,b) -> types.equal_Q(a, b),
+    :throw => (a) -> throw(types.MalException(a)),
+
+    symbol("nil?") => (a) -> a === nothing,
+    symbol("true?") => (a) -> a === true,
+    symbol("false?") => (a) -> a === false,
+    symbol("symbol") => (a) -> symbol(a),
+    symbol("symbol?") => (a) -> typeof(a) === Symbol,
+    symbol("keyword") => (a) -> a[1] == '\u029e' ? a : "\u029e$(a)",
+    symbol("keyword?") => (a) -> isa(a,String) && a[1] == '\u029e',
 
     symbol("pr-str") => (a...) -> join(map((e)->pr_str(e, true),a)," "),
     :str => (a...) -> join(map((e)->pr_str(e, false),a),""),
@@ -36,7 +51,18 @@ ns = {
 
     :list => (a...) -> Any[a...],
     symbol("list?") => (a) -> isa(a, Array),
+    :vector => (a...) -> tuple(a...),
+    symbol("vector?") => (a) -> isa(a, Tuple),
+    symbol("hash-map") => types.hash_map,
+    symbol("map?") => (a) -> isa(a, Dict),
+    :assoc => (a, b...) -> merge(a, types.hash_map(b...)),
+    :dissoc => (a, b...) -> foldl((x,y) -> delete!(x,y),copy(a), b),
+    :get => (a,b) -> a === nothing ? nothing : get(a,b,nothing),
+    symbol("contains?") => haskey,
+    :keys => (a) -> {keys(a)...},
+    :vals => (a) -> {values(a)...},
 
+    symbol("sequential?") => types.sequential_Q,
     :cons => (a,b) -> [Any[a], Any[b...]],
     :concat => concat,
     :nth => (a,b) -> b+1 > length(a) ? error("nth: index out of range") : a[b+1],
@@ -44,6 +70,8 @@ ns = {
     :rest => (a) -> Any[a[2:end]...],
     symbol("empty?") => isempty,
     :count => (a) -> a == nothing ? 0 : length(a),
+    :apply => do_apply,
+    :map => (a,b) -> isa(a,types.MalFunc) ? {map(a.fn,b)...} : {map(a,b)...},
     }
 
 end
index c396980..e3c2ae8 100644 (file)
@@ -11,9 +11,11 @@ function pr_str(obj, print_readably=true)
     elseif isa(obj, Tuple)
         "[$(join([pr_str(o, _r) for o=obj], " "))]"
     elseif isa(obj, Dict)
-        "{$(join(["$(o[1]) $(o[2])" for o=obj], " "))}"
+        "{$(join(["$(pr_str(o[1],_r)) $(pr_str(o[2],_r))" for o=obj], " "))}"
     elseif isa(obj, String)
-        if _r
+        if length(obj) > 0 && obj[1] == '\u029e'
+            ":$(obj[2:end])"
+        elseif _r
             str = replace(replace(replace(obj,
                                           "\\", "\\\\"),
                                  "\"", "\\\""),
index 9c09ab0..4ae5dc8 100644 (file)
@@ -2,6 +2,8 @@ module reader
 
 export read_str
 
+import types
+
 type Reader
     tokens
     position::Int64
@@ -40,6 +42,8 @@ function read_atom(rdr)
             replace(token[2:end-1],
                     "\\\"", "\""),
             "\\n", "\n")
+    elseif token[1] == ':'
+        "\u029e$(token[2:end])"
     elseif token == "nil"
         nothing
     elseif token == "true"
@@ -74,7 +78,7 @@ end
 
 function read_hash_map(rdr)
     lst = read_list(rdr, "{", "}")
-    [x[1] => x[2] for x=reshape(lst, (2, div(length(lst), 2)))]
+    types.hash_map(lst...)
 end
 
 function read_form(rdr)
index 467cff2..66b9f06 100755 (executable)
@@ -14,15 +14,15 @@ function eval_ast(ast, env)
         env[ast]
     elseif isa(ast, Array) || isa(ast, Tuple)
         map((x) -> EVAL(x,env), ast)
+    elseif isa(ast, Dict)
+        [EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
     else
         ast
     end
 end
 
 function EVAL(ast, env)
-    if !isa(ast, Array)
-        return eval_ast(ast, env)
-    end
+    if !isa(ast, Array) return eval_ast(ast, env) end
 
     # apply
     el = eval_ast(ast, env)
diff --git a/julia/step9_try.jl b/julia/step9_try.jl
new file mode 100755 (executable)
index 0000000..956fbd7
--- /dev/null
@@ -0,0 +1,188 @@
+#!/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 symbol("try*") == ast[1]
+        try
+            return EVAL(ast[2], env)
+        catch exc
+            e = string(exc)
+            if isa(exc, MalException)
+                e = exc.malval
+            elseif isa(exc, ErrorException)
+                e = exc.msg
+            else
+                e = string(e)
+            end
+            if length(ast) > 2 && ast[3][1] == symbol("catch*")
+                return EVAL(ast[3][3], Env(env, {ast[3][2]}, {e}))
+            else
+                rethrow(exc)
+            end
+        end
+    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 38612bb..0a82d24 100644 (file)
@@ -1,6 +1,10 @@
 module types
 
-export MalFunc
+export MalException, MalFunc, sequential_Q, equal_Q, hash_map
+
+type MalException <: Exception
+    malval
+end
 
 type MalFunc
     fn::Function
@@ -28,11 +32,21 @@ function equal_Q(a, b)
 
     if sequential_Q(a)
         tuple(a...) == tuple(b...)
+    elseif isa(a,String)
+        a == b
     else
         a === b
     end
 end
 
+function hash_map(lst...)
+    hm = Dict()
+    for i = 1:2:length(lst)
+        hm[lst[i]] = lst[i+1]
+    end
+    hm
+end
+
 end