aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2015-01-02 23:20:00 -0600
committerJoel Martin <github@martintribe.org>2015-01-09 16:16:55 -0600
commitf522319598c701efde91a78b07110d7039a8c906 (patch)
treec903469df13f81ffb7d706680c5eaafadbb90471
parent5400d4bf5e7fe7f968a4553f55101de962a39ef7 (diff)
downloadmal-f522319598c701efde91a78b07110d7039a8c906.tar.gz
mal-f522319598c701efde91a78b07110d7039a8c906.zip
Racket: add steps0-A. Self-hosting.
- Some additioanl tests. - Split step9 tests into optional but self-hosting requirements (metadata on functions) and other optional (conj, metadata on collections).
-rw-r--r--Makefile4
-rw-r--r--README.md15
-rw-r--r--docs/steps/step0_repl.txt11
-rw-r--r--docs/steps/step1_read_print.txt14
-rw-r--r--docs/steps/step2_eval.txt25
-rw-r--r--docs/steps/step3_env.txt38
-rw-r--r--docs/steps/step4_if_fn_do.txt76
-rw-r--r--docs/steps/step5_tco.txt78
-rw-r--r--docs/steps/step6_file.txt85
-rw-r--r--docs/steps/step7_quote.txt92
-rw-r--r--docs/steps/step8_macros.txt102
-rw-r--r--docs/steps/step9_try.txt109
-rw-r--r--docs/steps/stepA_interop.txt112
-rw-r--r--docs/steps/stepA_interop2.txt131
-rw-r--r--racket/Makefile12
-rw-r--r--racket/core.rkt101
-rw-r--r--racket/env.rkt47
-rw-r--r--racket/printer.rkt44
-rw-r--r--racket/reader.rkt85
-rw-r--r--racket/readline.rkt15
-rwxr-xr-xracket/step0_repl.rkt27
-rwxr-xr-xracket/step1_read_print.rkt30
-rwxr-xr-xracket/step2_eval.rkt49
-rwxr-xr-xracket/step3_env.rkt61
-rwxr-xr-xracket/step4_if_fn_do.rkt82
-rwxr-xr-xracket/step5_tco.rkt91
-rwxr-xr-xracket/step6_file.rkt97
-rwxr-xr-xracket/step7_quote.rkt119
-rwxr-xr-xracket/step8_macros.rkt143
-rwxr-xr-xracket/step9_try.rkt160
-rwxr-xr-xracket/stepA_interop.rkt163
-rw-r--r--racket/types.rkt110
-rw-r--r--tests/incB.mal4
-rw-r--r--tests/incC.mal6
-rw-r--r--tests/step1_read_print.mal33
-rw-r--r--tests/step3_env.mal6
-rw-r--r--tests/step6_file.mal5
-rw-r--r--tests/step9_try.mal151
38 files changed, 2452 insertions, 81 deletions
diff --git a/Makefile b/Makefile
index 33588b7..7671fcf 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ PYTHON = python
#
IMPLS = bash c clojure coffee cs go haskell java js make mal perl \
- php ps python r ruby rust scala vb
+ php ps python r racket ruby rust scala vb
step0 = step0_repl
step1 = step1_read_print
@@ -64,6 +64,7 @@ php_STEP_TO_PROG = php/$($(1)).php
ps_STEP_TO_PROG = ps/$($(1)).ps
python_STEP_TO_PROG = python/$($(1)).py
r_STEP_TO_PROG = r/$($(1)).r
+racket_STEP_TO_PROG = racket/$($(1)).rkt
ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/$($(1))
scala_STEP_TO_PROG = scala/$($(1)).scala
@@ -86,6 +87,7 @@ php_RUNSTEP = php ../$(2) $(3)
ps_RUNSTEP = $(4)gs -q -I./ -dNODISPLAY -- ../$(2) $(3)$(4)
python_RUNSTEP = $(PYTHON) ../$(2) $(3)
r_RUNSTEP = Rscript ../$(2) $(3)
+racket_RUNSTEP = ../$(2) $(3)
ruby_RUNSTEP = ruby ../$(2) $(3)
rust_RUNSTEP = ../$(2) $(3)
scala_RUNSTEP = sbt 'run-main $($(1))$(if $(3), $(3),)'
diff --git a/README.md b/README.md
index 1a6f7af..1059151 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Mal is an Clojure inspired Lisp interpreter.
-Mal is implemented in 20 different languages:
+Mal is implemented in 21 different languages:
* Bash shell
* C
@@ -22,6 +22,7 @@ Mal is implemented in 20 different languages:
* Postscript
* Python
* R
+* Racket
* Ruby
* Rust
* Scala
@@ -208,7 +209,17 @@ The R implementation of mal requires R (r-base-core) to run.
```
cd r
make libs
-Rscript stepX_YYY.rb
+Rscript stepX_YYY.r
+```
+
+### Racket (5.3)
+
+The Racket implementation of mal requires the Racket
+compiler/interpreter to run.
+
+```
+cd racket
+./stepX_YYY.rb
```
### Ruby (1.8)
diff --git a/docs/steps/step0_repl.txt b/docs/steps/step0_repl.txt
new file mode 100644
index 0000000..db31e58
--- /dev/null
+++ b/docs/steps/step0_repl.txt
@@ -0,0 +1,11 @@
+--- step0_repl ----------------------------------
+READ(str): return str
+
+EVAL(ast,any): return ast
+
+PRINT(exp): return exp
+
+rep(str): return PRINT(EVAL(READ(str),""))
+
+main loop: println(rep(readline("user> ")))
+
diff --git a/docs/steps/step1_read_print.txt b/docs/steps/step1_read_print.txt
new file mode 100644
index 0000000..3a0fd7c
--- /dev/null
+++ b/docs/steps/step1_read_print.txt
@@ -0,0 +1,14 @@
+--- step1_read_print ----------------------------
+import reader, printer
+
+READ(str): return reader.read_str(str)
+
+EVAL(ast,env): return ast
+
+PRINT(exp): return printer.pr_str(exp)
+
+rep(str): return PRINT(EVAL(READ(str),""))
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
diff --git a/docs/steps/step2_eval.txt b/docs/steps/step2_eval.txt
new file mode 100644
index 0000000..beb5500
--- /dev/null
+++ b/docs/steps/step2_eval.txt
@@ -0,0 +1,25 @@
+--- step2_eval ----------------------------------
+import types, reader, printer
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return lookup(env, ast) OR raise "'" + ast + "' not found"
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = {'+: add_fn, ...}
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
diff --git a/docs/steps/step3_env.txt b/docs/steps/step3_env.txt
new file mode 100644
index 0000000..a1de572
--- /dev/null
+++ b/docs/steps/step3_env.txt
@@ -0,0 +1,38 @@
+--- step3_env -----------------------------------
+import types, reader, printer, env
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: return EVAL(ast[2], ...)
+ _default_: f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+repl_env.set('+, add_fn)
+ ...
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null)
+ data = hash_map()
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
diff --git a/docs/steps/step4_if_fn_do.txt b/docs/steps/step4_if_fn_do.txt
new file mode 100644
index 0000000..805d9b1
--- /dev/null
+++ b/docs/steps/step4_if_fn_do.txt
@@ -0,0 +1,76 @@
+--- step4_if_fn_do ------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: return EVAL(ast[2], ...)
+ 'do: return eval_ast(rest(ast), env)[-1]
+ 'if: return EVAL(EVAL(ast[1], env) ? ast[2] : ast[3], env)
+ 'fn*: return (...a) -> EVAL(ast[2], new Env(env, ast[1], a))
+ _default_: f, args = eval_ast(ast, env)
+ return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/docs/steps/step5_tco.txt b/docs/steps/step5_tco.txt
new file mode 100644
index 0000000..30e2a46
--- /dev/null
+++ b/docs/steps/step5_tco.txt
@@ -0,0 +1,78 @@
+--- step5_tco -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/docs/steps/step6_file.txt b/docs/steps/step6_file.txt
new file mode 100644
index 0000000..bccf8fa
--- /dev/null
+++ b/docs/steps/step6_file.txt
@@ -0,0 +1,85 @@
+--- step6_file ----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'empty?: empty?,
+ 'count: count}
diff --git a/docs/steps/step7_quote.txt b/docs/steps/step7_quote.txt
new file mode 100644
index 0000000..c166257
--- /dev/null
+++ b/docs/steps/step7_quote.txt
@@ -0,0 +1,92 @@
+--- step7_quote ---------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'empty?: empty?,
+ 'count: count}
diff --git a/docs/steps/step8_macros.txt b/docs/steps/step8_macros.txt
new file mode 100644
index 0000000..da68191
--- /dev/null
+++ b/docs/steps/step8_macros.txt
@@ -0,0 +1,102 @@
+--- step8_macros --------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro function
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count}
diff --git a/docs/steps/step9_try.txt b/docs/steps/step9_try.txt
new file mode 100644
index 0000000..f0bb5c9
--- /dev/null
+++ b/docs/steps/step9_try.txt
@@ -0,0 +1,109 @@
+--- step9_try -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro function
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol: symbol,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'readline: readline,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count,
+ 'apply: apply,
+ 'map: map}
diff --git a/docs/steps/stepA_interop.txt b/docs/steps/stepA_interop.txt
new file mode 100644
index 0000000..9657468
--- /dev/null
+++ b/docs/steps/stepA_interop.txt
@@ -0,0 +1,112 @@
+--- step9_try -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro function
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! *host-language* \"racket\")")
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol: symbol,
+ 'symbol?: symbol?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'readline: readline,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+ 'time-ms cur-epoch-millis,
+
+ 'list: list,
+ 'list?: list?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count,
+ 'apply: apply,
+ 'map: map}
diff --git a/docs/steps/stepA_interop2.txt b/docs/steps/stepA_interop2.txt
new file mode 100644
index 0000000..1273411
--- /dev/null
+++ b/docs/steps/stepA_interop2.txt
@@ -0,0 +1,131 @@
+--- step9_try -----------------------------------
+import types, reader, printer, env, core
+
+READ(str): return reader.read_str(str)
+
+pair?(ast): return ... // true if non-empty sequence
+quasiquote(ast): return ... // quasiquote
+
+macro?(ast, env): return ... // true if macro function
+macroexpand(ast, env): return ... // recursive macro expansion
+
+eval_ast(ast,env):
+ switch type(ast):
+ symbol: return env.get(ast)
+ list,vector: return ast.map((x) -> EVAL(x,env))
+ hash: return ast.map((k,v) -> list(k, EVAL(v,env)))
+ _default_: return ast
+
+EVAL(ast,env):
+ while true:
+ if not list?(ast): return eval_ast(ast, env)
+ switch ast[0]:
+ 'def!: return env.set(ast[1], EVAL(ast[2], env))
+ 'let*: env = ...; ast = ast[2] // TCO
+ 'quote: return ast[1]
+ 'quasiquote: ast = quasiquote(ast[1]) // TCO
+ 'defmacro!: return ... // like def!, but set macro property
+ 'macroexpand: return macroexpand(ast[1], env)
+ 'try*: return ... // try/catch native and malval exceptions
+ 'do: ast = eval_ast(ast[1..-1], env)[-1] // TCO
+ 'if: EVAL(ast[1], env) ? ast = ast[2] : ast = ast[3] // TCO
+ 'fn*: return new MalFunc(...)
+ _default_: f, args = eval_ast(ast, env)
+ if malfunc?(ast[0]): ast = ast[0].fn; env = ... // TCO
+ else: return apply(f, args)
+
+PRINT(exp): return printer.pr_str(exp)
+
+repl_env = new Env()
+rep(str): return PRINT(EVAL(READ(str),repl_env))
+
+;; core.EXT: defined using Racket
+core.ns.map((k,v) -> (repl_env.set(k, v)))
+repl_env.set('eval, (ast) -> EVAL(ast, repl-env))
+repl_env.set('*ARGV*, cmdline_args[1..])
+
+;; core.mal: defined using the language itself
+rep("(def! *host-language* \"racket\")")
+rep("(def! not (fn* (a) (if a false true)))")
+rep("(def! load-file (fn* (f) ...))")
+rep("(defmacro! cond (fn* (& xs) ...))")
+rep("(defmacro! or (fn* (& xs) ...))")
+
+if cmdline_args: rep("(load-file \"" + args[0] + "\")"); exit 0
+
+rep("(println (str \"Mal [\" *host-language* \"]\"))")
+main loop:
+ try: println(rep(readline("user> ")))
+ catch e: println("Error: ", e)
+
+--- env module ----------------------------------
+class Env (outer=null,binds=[],exprs=[])
+ data = hash_map()
+ foreach b, i in binds:
+ if binds[i] == '&: data[binds[i+1]] = exprs.drop(i); break
+ else: data[binds[i]] = exprs[i]
+ set(k,v): return data.set(k,v)
+ find(k): return data.has(k) ? this : (if outer ? find(outer) : null)
+ get(k): return data.find(k).get(k) OR raise "'" + k + "' not found"
+
+--- core module ---------------------------------
+ns = {'=: equal?,
+ 'throw: throw,
+
+ 'nil?: nil?,
+ 'true?: true?,
+ 'false?: false?,
+ 'symbol: symbol,
+ 'symbol?: symbol?,
+ 'keyword: keyword,
+ 'keyword?: keyword?,
+
+ 'pr-str: (a) -> a.map(|s| pr_str(e,true)).join(" ")),
+ 'str: (a) -> a.map(|s| pr_str(e,false)).join("")),
+ 'prn: (a) -> println(a.map(|s| pr_str(e,true)).join(" ")),
+ 'println: (a) -> println(a.map(|s| pr_str(e,false)).join(" ")),
+ 'read-string: read_str,
+ 'readline: readline,
+ 'slurp read-file,
+
+ '<: lt,
+ '<=: lte,
+ '>: gt,
+ '>=: gte,
+ '+: add,
+ '-: sub,
+ '*: mult,
+ '/: div,
+
+ 'list: list,
+ 'list?: list?,
+ 'vector: vector,
+ 'vector?: vector?,
+ 'hash-map: hash_map,
+ 'map?: hash_map?,
+ 'assoc: assoc,
+ 'dissoc: dissoc,
+ 'get: get,
+ 'contains?: contains?,
+ 'keys: keys,
+ 'vals: vals,
+
+ 'sequential? sequential?,
+ 'cons: (a) -> concat([a[0]], a[1]),
+ 'concat: (a) -> reduce(concat, [], a),
+ 'nth: (a) -> a[0][a[1]] OR raise "nth: index out of range",
+ 'first: (a) -> a[0][0] OR nil,
+ 'rest: (a) -> a[0][1..] OR list(),
+ 'empty?: empty?,
+ 'count: count,
+ 'apply: apply,
+ 'map: map,
+ 'conj: conj,
+
+ 'meta: (a) -> a[0].meta,
+ 'with-meta: (a) -> a[0].with_meta(a[1]),
+ 'atom: (a) -> new Atom(a[0]),
+ 'atom?: (a) -> type(a[0]) == "atom",
+ 'deref: (a) -> a[0].val,
+ 'reset!: (a) -> a[0].val = a[1],
+ 'swap!: swap!}
diff --git a/racket/Makefile b/racket/Makefile
new file mode 100644
index 0000000..a472c2f
--- /dev/null
+++ b/racket/Makefile
@@ -0,0 +1,12 @@
+SOURCES_BASE = types.rkt reader.rkt printer.rkt
+SOURCES_LISP = env.rkt core.rkt stepA_interop.rkt
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+all:
+
+.PHONY: stats
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/racket/core.rkt b/racket/core.rkt
new file mode 100644
index 0000000..1cb41bf
--- /dev/null
+++ b/racket/core.rkt
@@ -0,0 +1,101 @@
+#lang racket
+
+(provide core_ns)
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt")
+
+(define (throw exc)
+ (raise (make-mal-exn "mal exception"
+ (current-continuation-marks)
+ exc)))
+
+;; Sequence functions
+(define conj
+ (lambda a
+ (if (vector? (first a))
+ (vector-append (first a) (list->vector (rest a)))
+ (append (reverse (rest a)) (first a)))))
+
+;; Meta functions
+(define (meta obj)
+ (cond [(malfunc? obj) (malfunc-meta obj)]
+ [else nil]))
+
+(define (with-meta obj m)
+ (cond [(malfunc? obj) (struct-copy malfunc obj [meta m])]
+ [else (raise "metadata not supported on type")]))
+
+;; Atom functions
+
+(define swap!
+ (lambda a
+ (let* ([atm (first a)]
+ [f (second a)]
+ [args (cons (atom-val atm) (rest (rest a)))]
+ [val (apply f args)])
+ (set-atom-val! atm val)
+ val)))
+
+(define core_ns
+ (hash
+ '= _equal?
+ 'throw throw
+
+ 'nil? _nil?
+ 'true? (lambda (x) (eq? x #t))
+ 'false? (lambda (x) (eq? x #f))
+ 'symbol (lambda (s) (if (symbol? s) s (string->symbol s)))
+ 'symbol? symbol?
+ 'keyword (lambda (s) (if (_keyword? s) s (_keyword s)))
+ 'keyword? _keyword?
+
+ 'pr-str (lambda a (pr_lst a #t " "))
+ 'str (lambda a (pr_lst a #f ""))
+ 'prn (lambda a (printf "~a~n" (pr_lst a #t " ")) nil)
+ 'println (lambda a (printf "~a~n" (pr_lst a #f " ")) nil)
+ 'read-string (lambda (s) (read_str s))
+ 'readline readline
+ 'slurp (lambda (f) (port->string (open-input-file f)))
+
+ '< <
+ '<= <=
+ '> >
+ '>= >=
+ '+ +
+ '- -
+ '* *
+ '/ /
+ 'time-ms (lambda () (round (current-inexact-milliseconds)))
+
+ 'list list
+ 'list? list?
+ 'vector vector
+ 'vector? vector?
+ 'hash-map hash
+ 'map? hash?
+ 'assoc _assoc
+ 'dissoc _dissoc
+ 'get _get
+ 'contains? dict-has-key?
+ 'keys hash-keys
+ 'vals hash-values
+
+ 'sequential? _sequential?
+ 'cons (lambda a (cons (first a) (_to_list (second a))))
+ 'concat (lambda a (apply append (map _to_list a)))
+ 'nth _nth
+ 'first _first
+ 'rest _rest
+ 'empty? _empty?
+ 'count _count
+ 'apply apply
+ 'map (lambda (f s) (_to_list (_map f s)))
+ 'conj conj
+
+ 'meta meta
+ 'with-meta with-meta
+ 'atom atom
+ 'atom? atom?
+ 'deref (lambda (a) (atom-val a))
+ 'reset! (lambda (a v) (set-atom-val! a v) v)
+ 'swap! swap!))
diff --git a/racket/env.rkt b/racket/env.rkt
new file mode 100644
index 0000000..8e47b63
--- /dev/null
+++ b/racket/env.rkt
@@ -0,0 +1,47 @@
+#lang racket
+
+(provide Env%)
+
+(require "types.rkt")
+
+(define Env%
+ (class object%
+ (init outer binds exprs)
+ (super-new)
+ (define _outer outer)
+ (define _binds (_to_list binds))
+ (define _exprs (_to_list exprs))
+ (define data (make-hash))
+ (let ([vargs (member '& _binds)])
+ (if vargs
+ (begin
+ (map (lambda (b e) (hash-set! data b e))
+ (drop-right _binds 2)
+ (take _exprs (- (length _binds) 2)))
+ (hash-set! data
+ (last _binds)
+ (drop _exprs (- (length _binds) 2))))
+ (map (lambda (b e) (hash-set! data b e))
+ _binds
+ _exprs)))
+
+ (define/public (set k v)
+ (hash-set! data k v)
+ v)
+ (define/public (find k)
+ (cond
+ [(hash-has-key? data k) this]
+ [(not (null? _outer)) (send _outer find k)]
+ [else null]))
+ (define/public (_get k)
+ (hash-ref data k))
+ (define/public (get k)
+ (let ([e (find k)])
+ (if (null? e)
+ (raise (string-append "'"
+ (symbol->string k)
+ "' not found"))
+ (send e _get k))))))
+
+
+
diff --git a/racket/printer.rkt b/racket/printer.rkt
new file mode 100644
index 0000000..07a8bb8
--- /dev/null
+++ b/racket/printer.rkt
@@ -0,0 +1,44 @@
+#lang racket
+
+(provide pr_str pr_lst)
+
+(require "types.rkt")
+
+(define (pr_str obj print_readably)
+ (let ([_r print_readably])
+ (cond
+ [(list? obj)
+ (string-join (map (lambda (o) (pr_str o _r)) obj)
+ " " #:before-first "(" #:after-last ")")]
+ [(vector? obj)
+ (string-join (map (lambda (o) (pr_str o _r)) (vector->list obj))
+ " " #:before-first "[" #:after-last "]")]
+ [(hash? obj)
+ (string-join (dict-map obj (lambda (k v)
+ (format "~a ~a"
+ (pr_str k _r)
+ (pr_str v _r))))
+ " " #:before-first "{" #:after-last "}")]
+ [(string? obj)
+ (if (regexp-match #px"^\u029e" obj)
+ (format ":~a" (substring obj 1))
+ (if _r
+ (format "\"~a\""
+ (string-replace
+ (string-replace
+ (string-replace obj "\\" "\\\\")
+ "\"" "\\\"")
+ "\n" "\\n"))
+ obj))]
+ [(number? obj) (number->string obj)]
+ [(symbol? obj) (symbol->string obj)]
+ [(atom? obj) (format "(atom ~a)" (atom-val obj))]
+ [(_nil? obj) "nil"]
+ [(eq? #t obj) "true"]
+ [(eq? #f obj) "false"]
+ [else (format "~a" obj)])))
+
+(define (pr_lst lst print_readably sep)
+ (string-join
+ (map (lambda (s) (pr_str s print_readably)) lst)
+ sep))
diff --git a/racket/reader.rkt b/racket/reader.rkt
new file mode 100644
index 0000000..6db2e67
--- /dev/null
+++ b/racket/reader.rkt
@@ -0,0 +1,85 @@
+#lang racket
+
+(provide read_str)
+
+(require "types.rkt")
+
+(define Reader%
+ (class object%
+ (init tokens)
+ (super-new)
+ (define toks tokens)
+ (define position 0)
+ (define/public (next)
+ (cond [(>= position (length toks)) null]
+ [else (begin
+ (set! position (+ 1 position))
+ (list-ref toks (- position 1)))]))
+ (define/public (peek)
+ (cond [(>= position (length toks)) null]
+ [else (list-ref toks position )]))))
+
+
+(define (tokenize str)
+ (filter-not (lambda (s) (or (equal? s "") (equal? (substring s 0 1) ";")))
+ (regexp-match* #px"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;[^\n]*|[^\\s\\[\\]{}('\"`,;)]*)"
+ str #:match-select cadr)))
+
+(define (read_atom rdr)
+ (let ([token (send rdr next)])
+ (cond [(regexp-match #px"^-?[0-9]+$" token)
+ (string->number token)]
+ [(regexp-match #px"^-?[0-9][0-9.]*$" token)
+ (string->number token)]
+ [(regexp-match #px"^\".*\"$" token)
+ (string-replace
+ (string-replace
+ (substring token 1 (- (string-length token) 1))
+ "\\\"" "\"")
+ "\\n" "\n")]
+ [(regexp-match #px"^:" token) (_keyword (substring token 1))]
+ [(equal? "nil" token) nil]
+ [(equal? "true" token) #t]
+ [(equal? "false" token) #f]
+ [else (string->symbol token)])))
+
+(define (read_list_entries rdr end)
+ (let ([tok (send rdr peek)])
+ (cond
+ [(eq? tok '()) (raise (string-append "expected '" end "'"))]
+ [(equal? end tok) '()]
+ [else
+ (cons (read_form rdr) (read_list_entries rdr end))])))
+
+(define (read_list rdr start end)
+ (let ([token (send rdr next)])
+ (if (equal? start token)
+ (let ([lst (read_list_entries rdr end)])
+ (send rdr next)
+ lst)
+ (raise (string-append "expected '" start "'")))))
+
+(define (read_form rdr)
+ (let ([token (send rdr peek)])
+ (if (null? token)
+ (raise (make-blank-exn "blank line" (current-continuation-marks)))
+ (cond
+ [(equal? "'" token) (send rdr next) (list 'quote (read_form rdr))]
+ [(equal? "`" token) (send rdr next) (list 'quasiquote (read_form rdr))]
+ [(equal? "~" token) (send rdr next) (list 'unquote (read_form rdr))]
+ [(equal? "~@" token) (send rdr next) (list 'splice-unquote (read_form rdr))]
+ [(equal? "^" token) (send rdr next)
+ (let ([meta (read_form rdr)])
+ (list 'with-meta (read_form rdr) meta))]
+ [(equal? "@" token) (send rdr next) (list 'deref (read_form rdr))]
+
+ [(equal? ")" token) (raise "unexpected ')'")]
+ [(equal? "(" token) (read_list rdr "(" ")")]
+ [(equal? "]" token) (raise "unexpected ']'")]
+ [(equal? "[" token) (list->vector (read_list rdr "[" "]"))]
+ [(equal? "}" token) (raise "unexpected '}'")]
+ [(equal? "{" token) (apply hash (read_list rdr "{" "}"))]
+ [else (read_atom rdr)]))))
+
+(define (read_str str)
+ (read_form (new Reader% [tokens (tokenize str)])))
diff --git a/racket/readline.rkt b/racket/readline.rkt
new file mode 100644
index 0000000..0a60c17
--- /dev/null
+++ b/racket/readline.rkt
@@ -0,0 +1,15 @@
+#lang racket
+
+(provide readline)
+
+(require "types.rkt")
+
+(define (readline prompt)
+ (_printf "~a" prompt)
+ (let ([line (read-line (current-input-port) 'any)])
+ (if (eq? eof line)
+ nil
+ line)))
+
+
+
diff --git a/racket/step0_repl.rkt b/racket/step0_repl.rkt
new file mode 100755
index 0000000..d09d5ba
--- /dev/null
+++ b/racket/step0_repl.rkt
@@ -0,0 +1,27 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "types.rkt")
+
+;; read
+(define (READ str)
+ str)
+
+;; eval
+(define (EVAL ast env)
+ ast)
+
+;; print
+(define (PRINT exp)
+ exp)
+
+;; repl
+(define (rep str)
+ (PRINT (EVAL (READ str) "")))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (printf "~a~n" (rep line))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step1_read_print.rkt b/racket/step1_read_print.rkt
new file mode 100755
index 0000000..a5d8ac7
--- /dev/null
+++ b/racket/step1_read_print.rkt
@@ -0,0 +1,30 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (EVAL ast env)
+ ast)
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define (rep str)
+ (PRINT (EVAL (READ str) "")))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step2_eval.rkt b/racket/step2_eval.rkt
new file mode 100755
index 0000000..ce4d563
--- /dev/null
+++ b/racket/step2_eval.rkt
@@ -0,0 +1,49 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "types.rkt" "readline.rkt" "reader.rkt" "printer.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast)
+ (or (hash-ref env ast
+ (lambda () (raise (string-append "'"
+ (symbol->string ast)
+ "' not found")))))]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env (hash '+ + '- - '* * '/ /))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step3_env.rkt b/racket/step3_env.rkt
new file mode 100755
index 0000000..fa735b8
--- /dev/null
+++ b/racket/step3_env.rkt
@@ -0,0 +1,61 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env%
+ [outer null]
+ [binds '(+ - * /)]
+ [exprs (list + - * /)]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step4_if_fn_do.rkt b/racket/step4_if_fn_do.rkt
new file mode 100755
index 0000000..8401397
--- /dev/null
+++ b/racket/step4_if_fn_do.rkt
@@ -0,0 +1,82 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (last (eval-ast (rest ast) env))]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (apply f args))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+
+;; core.mal: defined using the language itself
+(rep "(def! not (fn* (a) (if a false true)))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step5_tco.rkt b/racket/step5_tco.rkt
new file mode 100755
index 0000000..0fbdf9c
--- /dev/null
+++ b/racket/step5_tco.rkt
@@ -0,0 +1,91 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+
+;; core.mal: defined using the language itself
+(rep "(def! not (fn* (a) (if a false true)))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(repl-loop)
diff --git a/racket/step6_file.rkt b/racket/step6_file.rkt
new file mode 100755
index 0000000..627fb9a
--- /dev/null
+++ b/racket/step6_file.rkt
@@ -0,0 +1,97 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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) \")\")))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step7_quote.rkt b/racket/step7_quote.rkt
new file mode 100755
index 0000000..2b7baff
--- /dev/null
+++ b/racket/step7_quote.rkt
@@ -0,0 +1,119 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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) \")\")))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step8_macros.rkt b/racket/step8_macros.rkt
new file mode 100755
index 0000000..7016a12
--- /dev/null
+++ b/racket/step8_macros.rkt
@@ -0,0 +1,143 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/step9_try.rkt b/racket/step9_try.rkt
new file mode 100755
index 0000000..434ec44
--- /dev/null
+++ b/racket/step9_try.rkt
@@ -0,0 +1,160 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ ;(printf "~a~n" (pr_str ast true))
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'try* a0)
+ (if (eq? 'catch* (_nth (_nth ast 2) 0))
+ (let ([efn (lambda (exc)
+ (EVAL (_nth (_nth ast 2) 2)
+ (new Env%
+ [outer env]
+ [binds (list (_nth (_nth ast 2) 1))]
+ [exprs (list exc)])))])
+ (with-handlers
+ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))]
+ [string? (lambda (exc) (efn exc))]
+ [exn:fail? (lambda (exc) (efn (format "~a" exc)))])
+ (EVAL (_nth ast 1) env)))
+ (EVAL (_nth ast 1)))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; 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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [mal-exn? (lambda (exc) (printf "Error: ~a~n"
+ (pr_str (mal-exn-val exc) true)))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (repl-loop)))
diff --git a/racket/stepA_interop.rkt b/racket/stepA_interop.rkt
new file mode 100755
index 0000000..9b816cb
--- /dev/null
+++ b/racket/stepA_interop.rkt
@@ -0,0 +1,163 @@
+#!/usr/bin/env racket
+#lang racket
+
+(require "readline.rkt" "types.rkt" "reader.rkt" "printer.rkt"
+ "env.rkt" "core.rkt")
+
+;; read
+(define (READ str)
+ (read_str str))
+
+;; eval
+(define (is-pair x)
+ (and (_sequential? x) (> (_count x) 0)))
+
+(define (quasiquote ast)
+ (cond
+ [(not (is-pair ast))
+ (list 'quote ast)]
+
+ [(equal? 'unquote (_nth ast 0))
+ (_nth ast 1)]
+
+ [(and (is-pair (_nth ast 0))
+ (equal? 'splice-unquote (_nth (_nth ast 0) 0)))
+ (list 'concat (_nth (_nth ast 0) 1) (quasiquote (_rest ast)))]
+
+ [else
+ (list 'cons (quasiquote (_nth ast 0)) (quasiquote (_rest ast)))]))
+
+(define (macro? ast env)
+ (and (list? ast)
+ (symbol? (first ast))
+ (not (equal? null (send env find (first ast))))
+ (let ([fn (send env get (first ast))])
+ (and (malfunc? fn) (malfunc-macro? fn)))))
+
+(define (macroexpand ast env)
+ (if (macro? ast env)
+ (let ([mac (malfunc-fn (send env get (first ast)))])
+ (macroexpand (apply mac (rest ast)) env))
+ ast))
+
+(define (eval-ast ast env)
+ (cond
+ [(symbol? ast) (send env get ast)]
+ [(_sequential? ast) (_map (lambda (x) (EVAL x env)) ast)]
+ [(hash? ast) (make-hash
+ (dict-map ast (lambda (k v) (cons k (EVAL v env)))))]
+ [else ast]))
+
+(define (EVAL ast env)
+ ;(printf "~a~n" (pr_str ast true))
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ (let ([ast (macroexpand ast env)])
+ (if (not (list? ast))
+ ast
+ (let ([a0 (_nth ast 0)])
+ (cond
+ [(eq? 'def! a0)
+ (send env set (_nth ast 1) (EVAL (_nth ast 2) env))]
+ [(eq? 'let* a0)
+ (let ([let-env (new Env% [outer env] [binds null] [exprs null])])
+ (_map (lambda (b_e)
+ (send let-env set (_first b_e)
+ (EVAL (_nth b_e 1) let-env)))
+ (_partition 2 (_to_list (_nth ast 1))))
+ (EVAL (_nth ast 2) let-env))]
+ [(eq? 'quote a0)
+ (_nth ast 1)]
+ [(eq? 'quasiquote a0)
+ (EVAL (quasiquote (_nth ast 1)) env)]
+ [(eq? 'defmacro! a0)
+ (let* ([func (EVAL (_nth ast 2) env)]
+ [mac (struct-copy malfunc func [macro? #t])])
+ (send env set (_nth ast 1) mac))]
+ [(eq? 'macroexpand a0)
+ (macroexpand (_nth ast 1) env)]
+ [(eq? 'try* a0)
+ (if (eq? 'catch* (_nth (_nth ast 2) 0))
+ (let ([efn (lambda (exc)
+ (EVAL (_nth (_nth ast 2) 2)
+ (new Env%
+ [outer env]
+ [binds (list (_nth (_nth ast 2) 1))]
+ [exprs (list exc)])))])
+ (with-handlers
+ ([mal-exn? (lambda (exc) (efn (mal-exn-val exc)))]
+ [string? (lambda (exc) (efn exc))]
+ [exn:fail? (lambda (exc) (efn (format "~a" exc)))])
+ (EVAL (_nth ast 1) env)))
+ (EVAL (_nth ast 1)))]
+ [(eq? 'do a0)
+ (eval-ast (drop (drop-right ast 1) 1) env)
+ (EVAL (last ast) env)]
+ [(eq? 'if a0)
+ (let ([cnd (EVAL (_nth ast 1) env)])
+ (if (or (eq? cnd nil) (eq? cnd #f))
+ (if (> (length ast) 3)
+ (EVAL (_nth ast 3) env)
+ nil)
+ (EVAL (_nth ast 2) env)))]
+ [(eq? 'fn* a0)
+ (malfunc
+ (lambda args (EVAL (_nth ast 2)
+ (new Env% [outer env]
+ [binds (_nth ast 1)]
+ [exprs args])))
+ (_nth ast 2) env (_nth ast 1) #f nil)]
+ [else (let* ([el (eval-ast ast env)]
+ [f (first el)]
+ [args (rest el)])
+ (if (malfunc? f)
+ (EVAL (malfunc-ast f)
+ (new Env%
+ [outer (malfunc-env f)]
+ [binds (malfunc-params f)]
+ [exprs args]))
+ (apply f args)))]))))))
+
+;; print
+(define (PRINT exp)
+ (pr_str exp true))
+
+;; repl
+(define repl-env
+ (new Env% [outer null] [binds null] [exprs null]))
+(define (rep str)
+ (PRINT (EVAL (READ str) repl-env)))
+
+(for () ;; ignore return values
+
+;; core.rkt: defined using Racket
+(hash-for-each core_ns (lambda (k v) (send repl-env set k v)))
+(send repl-env set 'eval (lambda [ast] (EVAL ast repl-env)))
+(send repl-env set '*ARGV* (list))
+
+;; core.mal: defined using the language itself
+(rep "(def! *host-language* \"racket\")")
+(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))))))))")
+
+)
+
+(define (repl-loop)
+ (let ([line (readline "user> ")])
+ (when (not (eq? nil line))
+ (with-handlers
+ ([string? (lambda (exc) (printf "Error: ~a~n" exc))]
+ [mal-exn? (lambda (exc) (printf "Error: ~a~n"
+ (pr_str (mal-exn-val exc) true)))]
+ [blank-exn? (lambda (exc) null)])
+ (printf "~a~n" (rep line)))
+ (repl-loop))))
+(let ([args (current-command-line-arguments)])
+ (if (> (vector-length args) 0)
+ (for () (rep (string-append "(load-file \"" (vector-ref args 0) "\")")))
+ (begin
+ (rep "(println (str \"Mal [\" *host-language* \"]\"))")
+ (repl-loop))))
diff --git a/racket/types.rkt b/racket/types.rkt
new file mode 100644
index 0000000..6ca29e6
--- /dev/null
+++ b/racket/types.rkt
@@ -0,0 +1,110 @@
+#lang racket
+
+(provide blank-exn? make-blank-exn mal-exn? make-mal-exn mal-exn-val
+ malfunc malfunc? malfunc-fn
+ malfunc-ast malfunc-env malfunc-params malfunc-macro? malfunc-meta
+ _partition _equal? _printf
+ nil _nil? _keyword _keyword?
+ _to_list _sequential? _count _empty? _nth _first _rest _map
+ _assoc _dissoc _get
+ atom atom? atom-val set-atom-val!)
+
+(define-struct (blank-exn exn:fail:user) ())
+(define-struct (mal-exn exn:fail:user) [val])
+
+(define nil%
+ (class object%
+ (super-new)))
+
+(define nil (new nil%))
+
+(define (_nil? obj)
+ (eq? nil obj))
+
+(struct malfunc [fn ast env params macro? meta]
+ #:property prop:procedure (struct-field-index fn))
+
+;; General functions
+
+;; From: http://stackoverflow.com/questions/8725832/how-to-split-list-into-evenly-sized-chunks-in-racket-scheme/8731622#8731622
+(define (_partition n xs)
+ (if (null? xs)
+ '()
+ (let ((first-chunk (take xs n))
+ (rest (drop xs n)))
+ (cons first-chunk (_partition n rest)))))
+
+(define (_equal? a b)
+ (cond
+ [(and (list? a) (vector? b))
+ (equal? a (vector->list b))]
+ [(and (vector? a) (list? b))
+ (equal? (vector->list a) b)]
+ [else (equal? a b)]))
+
+;; printf with flush
+(define _printf (lambda a (apply printf a) (flush-output)))
+
+;; Keywords
+(define (_keyword str)
+ (string-append "\u029e" str))
+
+(define (_keyword? k)
+ (and (string? k) (regexp-match? #px"^\u029e" k)))
+
+
+;; Lists and vectors
+
+(define (_to_list a)
+ (if (vector? a) (vector->list a) a))
+
+(define (_sequential? seq)
+ (or (vector? seq) (list? seq)))
+
+(define (_count seq)
+ (cond [(_nil? seq) 0]
+ [(vector? seq) (vector-length seq)]
+ [else (length seq)]))
+
+(define (_empty? seq)
+ (eq? 0 (_count seq)))
+
+(define (_nth seq idx)
+ (cond [(>= idx (_count seq)) (raise "nth: index out of range")]
+ [(vector? seq) (vector-ref seq idx)]
+ [else (list-ref seq idx)]))
+
+(define (_first seq)
+ (cond [(vector? seq) (if (_empty? seq) nil (vector-ref seq 0))]
+ [else (if (_empty? seq) nil (list-ref seq 0))]))
+
+(define (_rest seq)
+ (cond [(vector? seq) (if (_empty? seq) '() (rest (vector->list seq)))]
+ [else (if (_empty? seq) '() (rest seq))]))
+
+(define (_map f seq)
+ (cond [(vector? seq) (vector-map f seq)]
+ [else (map f seq)]))
+
+;; Hash maps
+(define _assoc
+ (lambda args
+ (let ([new-hm (hash-copy (first args))]
+ [pairs (_partition 2 (rest args))])
+ (map (lambda (k_v)
+ (hash-set! new-hm (first k_v) (second k_v))) pairs)
+ new-hm)))
+
+(define _dissoc
+ (lambda args
+ (let ([new-hm (hash-copy (first args))])
+ (map (lambda (k) (hash-remove! new-hm k)) (rest args))
+ new-hm)))
+
+(define (_get hm k)
+ (cond [(_nil? hm) nil]
+ [(dict-has-key? hm k) (hash-ref hm k)]
+ [else nil]))
+
+;; Atoms
+(struct atom [val] #:mutable)
diff --git a/tests/incB.mal b/tests/incB.mal
index 1c68810..519bdf4 100644
--- a/tests/incB.mal
+++ b/tests/incB.mal
@@ -3,10 +3,6 @@
(def! inc5 (fn* (a) ;; a comment after code
(+ 5 a)))
-;; Test map split across lines
-(def! mymap {"a"
- 1})
-
(prn "incB.mal finished")
"incB.mal return string"
diff --git a/tests/incC.mal b/tests/incC.mal
new file mode 100644
index 0000000..e6f5041
--- /dev/null
+++ b/tests/incC.mal
@@ -0,0 +1,6 @@
+(def! mymap {"a"
+ 1})
+
+(prn "incC.mal finished")
+"incC.mal return string"
+
diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal
index 57b2e34..4ac08cb 100644
--- a/tests/step1_read_print.mal
+++ b/tests/step1_read_print.mal
@@ -60,6 +60,22 @@ abc-def
(1 2, 3,,,,),,
;=>(1 2 3)
+;; Testing read of quoting
+'1
+;=>(quote 1)
+'(1 2 3)
+;=>(quote (1 2 3))
+`1
+;=>(quasiquote 1)
+`(1 2 3)
+;=>(quasiquote (1 2 3))
+~1
+;=>(unquote 1)
+~(1 2 3)
+;=>(unquote (1 2 3))
+~@(1 2 3)
+;=>(splice-unquote (1 2 3))
+
;;
;; Testing reader errors
;;; TODO: fix these so they fail correctly
@@ -107,23 +123,6 @@ abc-def
1; comment after expression
;=>1
-;; Testing read of quoting
-'1
-;=>(quote 1)
-'(1 2 3)
-;=>(quote (1 2 3))
-`1
-;=>(quasiquote 1)
-`(1 2 3)
-;=>(quasiquote (1 2 3))
-~1
-;=>(unquote 1)
-~(1 2 3)
-;=>(unquote (1 2 3))
-~@(1 2 3)
-;=>(splice-unquote (1 2 3))
-
-
;; Testing read of ^/metadata
^{"a" 1} [1 2 3]
;=>(with-meta [1 2 3] {"a" 1})
diff --git a/tests/step3_env.mal b/tests/step3_env.mal
index 26372e6..8fb4c42 100644
--- a/tests/step3_env.mal
+++ b/tests/step3_env.mal
@@ -35,6 +35,12 @@ x
;;
;; -------- Optional Functionality --------
+;; Testing let* with vector bindings
+(let* [z 9] z)
+;=>9
+(let* [p (+ 2 3) q (+ 2 p)] (+ p q))
+;=>12
+
;; Testing vector evaluation
(let* (a 5 b 6) [3 4 a [b 7] 8])
;=>[3 4 5 [6 7] 8]
diff --git a/tests/step6_file.mal b/tests/step6_file.mal
index d681532..5c7e32f 100644
--- a/tests/step6_file.mal
+++ b/tests/step6_file.mal
@@ -46,6 +46,11 @@
(inc5 7)
;=>12
+;; Testing map literal across multiple lines in a file
+(load-file "../tests/incC.mal")
+mymap
+;=>{"a" 1}
+
;;; TODO: really a step5 test
;; Testing that vector params not broken by TCO
(def! g (fn* [] 78))
diff --git a/tests/step9_try.mal b/tests/step9_try.mal
index 3781e4d..4093e7d 100644
--- a/tests/step9_try.mal
+++ b/tests/step9_try.mal
@@ -1,6 +1,9 @@
;;
;; Testing try*/catch*
+(try* 123 (catch* e 456))
+;=>123
+
(try* (abc 1 2) (catch* exc (prn "exc is:" exc))))
; "exc is:" "'abc' not found"
;=>nil
@@ -10,8 +13,8 @@
;;;; "exc is:" ["data" "foo"] ;;;;=>7
;;;;=>7
-(try* (throw ["data" "foo"]) (catch* exc (do (prn "err:" exc) 7)))
-; "err:" ["data" "foo"]
+(try* (throw (list "data" "foo")) (catch* exc (do (prn "err:" exc) 7)))
+; "err:" ("data" "foo")
;=>7
(try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7)))
@@ -61,7 +64,7 @@
;=>6
(map double nums)
;=>(2 4 6)
-(map (fn* [x] (symbol? x)) (list 1 (symbol "two") "three"))
+(map (fn* (x) (symbol? x)) (list 1 (symbol "two") "three"))
;=>(false true false)
;;
@@ -86,7 +89,8 @@
;=>"\"hello\""
;;
-;; -------- Optional Functionality --------
+;; ------- Optional Functionality ----------
+;; ------- (Needed for self-hosting) -------
;; Testing symbol and keyword functions
(symbol? :abc)
@@ -119,6 +123,11 @@
(sequential? "abc")
;=>false
+
+;; Testing map function with vectors
+(map (fn* (a) (* 2 a)) [1 2 3])
+;=>(2 4 6)
+
;; Testing vector functions
(vector? [10 11])
@@ -242,58 +251,21 @@
;;
-;; Testing conj function
-(conj (list) 1)
-;=>(1)
-(conj (list 1) 2)
-;=>(2 1)
-(conj (list 2 3) 4)
-;=>(4 2 3)
-(conj (list 2 3) 4 5 6)
-;=>(6 5 4 2 3)
-(conj (list 1) (list 2 3))
-;=>((2 3) 1)
-
-(conj [] 1)
-;=>[1]
-(conj [1] 2)
-;=>[1 2]
-(conj [2 3] 4)
-;=>[2 3 4]
-(conj [2 3] 4 5 6)
-;=>[2 3 4 5 6]
-(conj [1] [2 3])
-;=>[1 [2 3]]
+;; Testing metadata on functions
;;
-;; Testing metadata
-(meta [1 2 3])
-;=>nil
+;; Testing metadata on mal functions
(meta (fn* (a) a))
;=>nil
-(with-meta [1 2 3] {"a" 1})
-;=>[1 2 3]
+(meta (with-meta (fn* (a) a) {"b" 1}))
+;=>{"b" 1}
-(meta (with-meta [1 2 3] {"a" 1}))
-;=>{"a" 1}
-
-(meta (with-meta [1 2 3] "abc"))
+(meta (with-meta (fn* (a) a) "abc"))
;=>"abc"
-(meta (with-meta (list 1 2 3) {"a" 1}))
-;=>{"a" 1}
-
-(meta (with-meta {"abc" 123} {"a" 1}))
-;=>{"a" 1}
-
-;;; Not actually supported by Clojure
-;;;(meta (with-meta (atom 7) {"a" 1}))
-;;;;=>{"a" 1}
-
-(def! l-wm (with-meta [4 5 6] {"b" 2}))
-;=>[4 5 6]
+(def! l-wm (with-meta (fn* (a) a) {"b" 2}))
(meta l-wm)
;=>{"b" 2}
@@ -312,20 +284,10 @@
(meta f-wm)
;=>{"abc" 1}
-
(def! f-wm2 ^{"abc" 1} (fn* [a] (+ 1 a)))
(meta f-wm2)
;=>{"abc" 1}
-;; Testing metadata on builtin functions
-(meta +)
-;=>nil
-(def! f-wm3 ^{"def" 2} +)
-(meta f-wm3)
-;=>{"def" 2}
-(meta +)
-;=>nil
-
;;
;; Make sure closures and metadata co-exist
(def! gen-plusX (fn* (x) (with-meta (fn* (b) (+ x b)) {"meta" 1})))
@@ -393,3 +355,78 @@
(f)
;=>9
+
+;;
+;; ------- Optional Functionality --------------
+;; ------- (Not needed for self-hosting) -------
+
+;;
+;; Testing conj function
+(conj (list) 1)
+;=>(1)
+(conj (list 1) 2)
+;=>(2 1)
+(conj (list 2 3) 4)
+;=>(4 2 3)
+(conj (list 2 3) 4 5 6)
+;=>(6 5 4 2 3)
+(conj (list 1) (list 2 3))
+;=>((2 3) 1)
+
+(conj [] 1)
+;=>[1]
+(conj [1] 2)
+;=>[1 2]
+(conj [2 3] 4)
+;=>[2 3 4]
+(conj [2 3] 4 5 6)
+;=>[2 3 4 5 6]
+(conj [1] [2 3])
+;=>[1 [2 3]]
+
+
+;;
+;; Testing metadata on collections
+
+(meta [1 2 3])
+;=>nil
+
+(with-meta [1 2 3] {"a" 1})
+;=>[1 2 3]
+
+(meta (with-meta [1 2 3] {"a" 1}))
+;=>{"a" 1}
+
+(meta (with-meta [1 2 3] "abc"))
+;=>"abc"
+
+(meta (with-meta (list 1 2 3) {"a" 1}))
+;=>{"a" 1}
+
+(meta (with-meta {"abc" 123} {"a" 1}))
+;=>{"a" 1}
+
+;;; Not actually supported by Clojure
+;;;(meta (with-meta (atom 7) {"a" 1}))
+;;;;=>{"a" 1}
+
+(def! l-wm (with-meta [4 5 6] {"b" 2}))
+;=>[4 5 6]
+(meta l-wm)
+;=>{"b" 2}
+
+(meta (with-meta l-wm {"new_meta" 123}))
+;=>{"new_meta" 123}
+(meta l-wm)
+;=>{"b" 2}
+
+;;
+;; Testing metadata on builtin functions
+(meta +)
+;=>nil
+(def! f-wm3 ^{"def" 2} +)
+(meta f-wm3)
+;=>{"def" 2}
+(meta +)
+;=>nil
+