aboutsummaryrefslogtreecommitdiff
path: root/coffee/step9_try.coffee
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-11-08 16:56:36 -0600
committerJoel Martin <github@martintribe.org>2015-01-09 16:16:45 -0600
commit891c3f3b478292ad0bfca44b0dc098a2aecc9a5d (patch)
treed30c7923ffee7699cfca94af84f3be297448fff2 /coffee/step9_try.coffee
parent9b3362e86a57ed7f14c5fd018c37713185e0c154 (diff)
downloadmal-891c3f3b478292ad0bfca44b0dc098a2aecc9a5d.tar.gz
mal-891c3f3b478292ad0bfca44b0dc098a2aecc9a5d.zip
CoffeeScript: add all steps. Self-hosting.
Diffstat (limited to 'coffee/step9_try.coffee')
-rw-r--r--coffee/step9_try.coffee136
1 files changed, 136 insertions, 0 deletions
diff --git a/coffee/step9_try.coffee b/coffee/step9_try.coffee
new file mode 100644
index 0000000..d7919e7
--- /dev/null
+++ b/coffee/step9_try.coffee
@@ -0,0 +1,136 @@
+readline = require "./node_readline.coffee"
+types = require "./types.coffee"
+reader = require "./reader.coffee"
+printer = require "./printer.coffee"
+Env = require("./env.coffee").Env
+core = require("./core.coffee")
+
+# read
+READ = (str) -> reader.read_str str
+
+# eval
+is_pair = (x) -> types._sequential_Q(x) && x.length > 0
+
+quasiquote = (ast) ->
+ if !is_pair(ast) then [types._symbol('quote'), ast]
+ else if ast[0].name == 'unquote' then ast[1]
+ else if is_pair(ast[0]) && ast[0][0].name == 'splice-unquote'
+ [types._symbol('concat'), ast[0][1], quasiquote(ast[1..])]
+ else
+ [types._symbol('cons'), quasiquote(ast[0]), quasiquote(ast[1..])]
+
+is_macro_call = (ast, env) ->
+ return types._list_Q(ast) && types._symbol_Q(ast[0]) &&
+ env.find(ast[0].name) && env.get(ast[0].name).__ismacro__
+
+macroexpand = (ast, env) ->
+ while is_macro_call(ast, env)
+ ast = env.get(ast[0].name)(ast[1..]...)
+ ast
+
+
+
+eval_ast = (ast, env) ->
+ if types._symbol_Q(ast) then env.get ast.name
+ else if types._list_Q(ast) then ast.map((a) -> EVAL(a, env))
+ else if types._vector_Q(ast)
+ types._vector(ast.map((a) -> EVAL(a, env))...)
+ else if types._hash_map_Q(ast)
+ new_hm = {}
+ new_hm[k] = EVAL(ast[k],env) for k,v of ast
+ new_hm
+ else ast
+
+EVAL = (ast, env) ->
+ loop
+ #console.log "EVAL:", printer._pr_str ast
+ if !types._list_Q ast then return eval_ast ast, env
+
+ # apply list
+ ast = macroexpand ast, env
+ if !types._list_Q ast then return ast
+
+ [a0, a1, a2, a3] = ast
+ switch a0.name
+ when "def!"
+ return env.set(a1.name, EVAL(a2, env))
+ when "let*"
+ let_env = new Env(env)
+ for k,i in a1 when i %% 2 == 0
+ let_env.set(a1[i].name, EVAL(a1[i+1], let_env))
+ ast = a2
+ env = let_env
+ when "quote"
+ return a1
+ when "quasiquote"
+ ast = quasiquote(a1)
+ when "defmacro!"
+ f = EVAL(a2, env)
+ f.__ismacro__ = true
+ return env.set(a1.name, f)
+ when "macroexpand"
+ return macroexpand(a1, env)
+ when "try*"
+ try return EVAL(a1, env)
+ catch exc
+ if a2 && a2[0].name == "catch*"
+ if exc instanceof Error then exc = exc.message
+ return EVAL a2[2], new Env(env, [a2[1]], [exc])
+ else
+ throw exc
+ when "do"
+ eval_ast(ast[1..-2], env)
+ ast = ast[ast.length-1]
+ when "if"
+ cond = EVAL(a1, env)
+ if cond == null or cond == false
+ if a3? then ast = a3 else return null
+ else
+ ast = a2
+ when "fn*"
+ return types._function(EVAL, a2, env, a1)
+ else
+ [f, args...] = eval_ast ast, env
+ if types._function_Q(f)
+ ast = f.__ast__
+ env = f.__gen_env__(args)
+ else
+ return f(args...)
+
+
+# print
+PRINT = (exp) -> printer._pr_str exp, true
+
+# repl
+repl_env = new Env()
+rep = (str) -> PRINT(EVAL(READ(str), repl_env))
+
+# core.coffee: defined using CoffeeScript
+repl_env.set k, v for k,v of core.ns
+repl_env.set 'eval', (ast) -> EVAL(ast, repl_env)
+repl_env.set '*ARGV*', []
+
+# core.mal: defined using the language itself
+rep("(def! *host-language* \"CoffeeScript\")")
+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 process? && process.argv.length > 2
+ repl_env.set '*ARGV*', process.argv[3..]
+ rep('(load-file "' + process.argv[2] + '")')
+ process.exit 0
+
+# repl loop
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+while (line = readline.readline("user> ")) != null
+ continue if line == ""
+ try
+ console.log rep line
+ catch exc
+ continue if exc instanceof reader.BlankException
+ if exc.stack then console.log exc.stack
+ else console.log exc
+
+# vim: ts=2:sw=2