aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile11
-rw-r--r--README.md99
-rw-r--r--clojure/project.clj2
-rw-r--r--clojure/src/stepA_mal.clj2
-rw-r--r--docs/TODO6
-rw-r--r--make/tests/stepA_mal.mal2
-rw-r--r--matlab/+types/Reader.m (renamed from matlab/Reader.m)0
-rw-r--r--matlab/Makefile2
-rw-r--r--matlab/reader.m2
-rw-r--r--miniMAL/package.json8
-rw-r--r--nim/Makefile33
-rw-r--r--nim/core.nim216
-rw-r--r--nim/env.nim25
-rw-r--r--nim/mal.nimble11
-rw-r--r--nim/nim.cfg2
-rw-r--r--nim/printer.nim27
-rw-r--r--nim/reader.nim108
-rw-r--r--nim/step0_repl.nim11
-rw-r--r--nim/step1_read_print.nim14
-rw-r--r--nim/step2_eval.nim52
-rw-r--r--nim/step3_env.nim70
-rw-r--r--nim/step4_if_fn_do.nim104
-rw-r--r--nim/step5_tco.nim115
-rw-r--r--nim/step6_file.nim125
-rw-r--r--nim/step7_quote.nim146
-rw-r--r--nim/step8_macros.nim171
-rw-r--r--nim/step9_try.nim190
-rw-r--r--nim/stepA_mal.nim191
-rw-r--r--nim/types.nim148
-rw-r--r--ocaml/core.ml5
-rw-r--r--process/guide.md45
-rwxr-xr-xruntest.py54
33 files changed, 1942 insertions, 56 deletions
diff --git a/.gitignore b/.gitignore
index 2b05376..6c346cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
*/experiments
make/mal.mk
+miniMAL/node_modules
js/node_modules
js/mal.js
js/mal_web.js
diff --git a/Makefile b/Makefile
index 85652a6..eae6ee6 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ PYTHON = python
IMPLS = bash c clojure coffee cs forth go haskell java js lua make mal \
ocaml matlab miniMAL perl php ps python r racket ruby rust \
- scala vb
+ scala vb nim
step0 = step0_repl
step1 = step1_read_print
@@ -38,12 +38,13 @@ EXCLUDE_TESTS += test^racket^step5 # test completes
EXCLUDE_TESTS += test^ruby^step5 # test completes, even at 100,000
EXCLUDE_TESTS += test^rust^step5 # no catching stack overflows
EXCLUDE_TESTS += test^ocaml^step5 # test completes, even at 1,000,000
+EXCLUDE_TESTS += test^nim^step5 # test completes, even at 100,000
# interop tests now implemented yet
EXCLUDE_TESTS += test^cs^stepA test^go^stepA test^haskell^stepA \
test^java^stepA test^mal^stepA test^mal^step0 \
test^php^stepA test^ps^stepA test^python^stepA \
- test^ruby^stepA
+ test^ruby^stepA test^rust^stepA test^vb^stepA
EXCLUDE_PERFS = perf^mal # TODO: fix this
@@ -79,6 +80,7 @@ ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/$($(1))
scala_STEP_TO_PROG = scala/$($(1)).scala
vb_STEP_TO_PROG = vb/$($(1)).exe
+nim_STEP_TO_PROG = nim/$($(1))
# Needed some argument munging
COMMA = ,
@@ -112,11 +114,12 @@ ruby_RUNSTEP = ruby ../$(2) $(3)
rust_RUNSTEP = ../$(2) $(3)
scala_RUNSTEP = sbt 'run-main $($(1))$(if $(3), $(3),)'
vb_RUNSTEP = mono ../$(2) --raw $(3)
+nim_RUNSTEP = ../$(2) $(3)
# Extra options to pass to runtest.py
-cs_TEST_OPTS = --redirect
+cs_TEST_OPTS = --mono
mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
-vb_TEST_OPTS = --redirect
+vb_TEST_OPTS = --mono
# Derived lists
diff --git a/README.md b/README.md
index 1885174..0602c01 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Mal is a Clojure inspired Lisp interpreter.
-Mal is implemented in 26 different languages:
+Mal is implemented in 27 different languages:
* Bash shell
* C
@@ -21,6 +21,7 @@ Mal is implemented in 26 different languages:
* mal itself
* MATLAB
* [miniMAL](https://github.com/kanaka/miniMAL)
+* Nim
* OCaml
* Perl
* PHP
@@ -34,10 +35,11 @@ Mal is implemented in 26 different languages:
* Visual Basic.NET
-Mal is a [learning tool](process/guide.md). Each implementation of mal is separated into 11
-incremental, self-contained (and testable) steps that demonstrate core
-concepts of Lisp. The last step is capable of self-hosting (running
-the mal implemenation of mal).
+Mal is a learning tool. See the ([make-a-lisp process
+guide](process/guide.md)). Each implementation of mal is separated
+into 11 incremental, self-contained (and testable) steps that
+demonstrate core concepts of Lisp. The last step is capable of
+self-hosting (running the mal implemenation of mal).
The mal (make a lisp) steps are:
@@ -190,6 +192,19 @@ cd make
make -f stepX_YYY.mk
```
+### Nim 0.10.3
+
+Running the Nim implementation of mal requires Nim's current devel branch
+(0.10.3) or later, and the nre library installed.
+
+```
+cd nim
+make
+ # OR
+nimble build
+./stepX_YYY
+```
+
### OCaml 4.01.0
```
@@ -220,11 +235,11 @@ implemented in less than 1024 bytes of JavaScript. To run the miniMAL
implementation of mal you need to download/install the miniMAL
interpreter (which requires Node.js).
```
-# Download miniMAL itself
-git clone https://github.com/kanaka/miniMAL ../miniMAL.git
-export PATH=`pwd`/miniMAL.git:$PATH
-# Now run mal implementated in miniMAL
cd miniMAL
+# Download miniMAL and dependencies
+npm install
+export PATH=`pwd`/node_modules/minimal-lisp/:$PATH
+# Now run mal implementation in miniMAL
miniMAL ./stepX_YYY
```
@@ -334,12 +349,14 @@ mono ./stepX_YYY.exe
## Running tests
-The are nearly 500 generic Mal tests (for all implementations) in the
-`tests/` directory. Each step has a corresponding test file containing
-tests specific to that step. The `runtest.py` test harness uses
-pexpect to launch a Mal step implementation and then feeds the tests
-one at a time to the implementation and compares the output/return
-value to the expected output/return value.
+### Functional tests
+
+The are nearly 500 generic functional tests (for all implementations)
+in the `tests/` directory. Each step has a corresponding test file
+containing tests specific to that step. The `runtest.py` test harness
+uses pexpect to launch a Mal step implementation and then feeds the
+tests one at a time to the implementation and compares the
+output/return value to the expected output/return value.
To simplify the process of running tests, a top level Makefile is
provided with convenient test targets.
@@ -370,7 +387,7 @@ make test^step2
make test^step7
```
-* To run a specifc step against a single implementation:
+* To run tests for a specifc step against a single implementation:
```
make test^IMPL^stepX
@@ -380,6 +397,56 @@ make test^ruby^step3
make test^ps^step4
```
+### Self-hosted functional tests
+
+* To run the functional tests in self-hosted mode, you specify `mal`
+ as the test implementation and use the `MAL_IMPL` make variable
+ to change the underlying host language (default is JavaScript):
+```
+make MAL_IMPL=IMPL test^mal^step2
+
+# e.g.
+make test^mal^step2 # js is default
+make MAL_IMPL=ruby test^mal^step2
+make MAL_IMPL=python test^mal^step2
+```
+
+
+### Performance tests
+
+* To run performance tests against a single implementation:
+```
+make perf^IMPL
+
+# e.g.
+make perf^js
+```
+
+* To run performance tests against all implementations:
+```
+make perf
+```
+
+### Generating language statistics
+
+* To report line and byte stastics for a single implementation:
+```
+make stats^IMPL
+
+# e.g.
+make stats^js
+```
+
+* To report line and bytes stastics for general Lisp code (env, core
+ and stepA):
+```
+make stats-lisp^IMPL
+
+# e.g.
+make stats-lisp^js
+```
+
+
## License
Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public
diff --git a/clojure/project.clj b/clojure/project.clj
index 3d7ff2e..447d643 100644
--- a/clojure/project.clj
+++ b/clojure/project.clj
@@ -19,7 +19,7 @@
:step7 {:main step7-quote}
:step8 {:main step8-macros}
:step9 {:main step9-try}
- :stepA {:main stepA-interop}}
+ :stepA {:main stepA-mal}}
:main stepA-more)
diff --git a/clojure/src/stepA_mal.clj b/clojure/src/stepA_mal.clj
index 6ed9964..ce289b6 100644
--- a/clojure/src/stepA_mal.clj
+++ b/clojure/src/stepA_mal.clj
@@ -1,4 +1,4 @@
-(ns stepA-interop
+(ns stepA-mal
(:refer-clojure :exclude [macroexpand])
(:require [clojure.repl]
[readline]
diff --git a/docs/TODO b/docs/TODO
index e586f52..68c5393 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -5,12 +5,12 @@ All:
- test to make sure slurp captures final newline
- make sure errors propagate/print properly when self-hosted
- - change perf test to run for 10 seconds and then calculate number
+ * change perf test to run for 10 seconds and then calculate number
of iterations per second
- redefine (defmacro!) as (def! (macro*))
- runtest expect fixes:
* stop using expect, so we can drop --raw option
- - fix C#, VB
+ * fix C#, VB
- fix long line splitting in runtest
- regular expression matching in runtest
- add re (use in rep) everywhere and use that (to avoid printing)
@@ -85,7 +85,7 @@ Java:
- MAL formatting is a bit off with mal/clojurewest2014.mal
Javascript:
- - interop: callbacks using Mal functions
+ - interop: adopt techniques from miniMAL
- fix "user> " prompt with mal/clojurewest2014.mal
Lua:
diff --git a/make/tests/stepA_mal.mal b/make/tests/stepA_mal.mal
index 9b1a2f9..768a929 100644
--- a/make/tests/stepA_mal.mal
+++ b/make/tests/stepA_mal.mal
@@ -14,6 +14,6 @@
(make* "$(foreach v,a b c,X$(v)Y)")
;=>"XaY XbY XcY"
-(read-string (make* "($(foreach v,1 2 3,$(call gmsl_plus,1,$(v))))"))
+(read-string (make* "($(foreach v,1 2 3,$(call int_add,1,$(v))))"))
;=>(2 3 4)
diff --git a/matlab/Reader.m b/matlab/+types/Reader.m
index c18ea54..c18ea54 100644
--- a/matlab/Reader.m
+++ b/matlab/+types/Reader.m
diff --git a/matlab/Makefile b/matlab/Makefile
index f03d94d..eba8fa4 100644
--- a/matlab/Makefile
+++ b/matlab/Makefile
@@ -1,7 +1,7 @@
SOURCES_BASE = types.m types/Nil.m types/MalException.m \
types/Symbol.m types/List.m types/Vector.m \
types/HashMap.m types/Function.m types/Atom.m \
- Reader.m reader.m printer.m
+ types/Reader.m reader.m printer.m
SOURCES_LISP = Env.m core.m stepA_mal.m
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
diff --git a/matlab/reader.m b/matlab/reader.m
index 84f6806..e0018ca 100644
--- a/matlab/reader.m
+++ b/matlab/reader.m
@@ -115,7 +115,7 @@ classdef reader
function ast = read_str(str)
%fprintf('in read_str\n');
tokens = reader.tokenize(str);
- rdr = Reader(tokens);
+ rdr = types.Reader(tokens);
ast = reader.read_form(rdr);
end
end
diff --git a/miniMAL/package.json b/miniMAL/package.json
new file mode 100644
index 0000000..24d7a03
--- /dev/null
+++ b/miniMAL/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "mal-miniMAL",
+ "version": "0.0.1",
+ "description": "Make a Lisp (mal) language implemented in miniMAL",
+ "dependencies": {
+ "minimal-lisp": "0.0.3"
+ }
+}
diff --git a/nim/Makefile b/nim/Makefile
new file mode 100644
index 0000000..e8c450e
--- /dev/null
+++ b/nim/Makefile
@@ -0,0 +1,33 @@
+#####################
+
+SOURCES_BASE = types.nim reader.nim printer.nim
+SOURCES_LISP = env.nim core.nim stepA_mal.nim
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#####################
+
+SRCS = step0_repl.nim step1_read_print.nim step2_eval.nim step3_env.nim \
+ step4_if_fn_do.nim step5_tco.nim step6_file.nim step7_quote.nim \
+ step8_macros.nim step9_try.nim stepA_mal.nim
+BINS = $(SRCS:%.nim=%)
+
+#####################
+
+all: $(BINS) mal
+
+mal: $(word $(words $(BINS)),$(BINS))
+ cp $< $@
+
+$(BINS): %: %.nim
+ nim -d:release c $@
+
+clean:
+ rm -rf nimcache/ $(BINS)
+ rm -f mal
+
+.PHONY: stats stats-lisp
+
+stats: $(SOURCES)
+ @wc $^
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
diff --git a/nim/core.nim b/nim/core.nim
new file mode 100644
index 0000000..e5ca517
--- /dev/null
+++ b/nim/core.nim
@@ -0,0 +1,216 @@
+import strutils, rdstdin, tables, algorithm, times, types, printer, reader
+
+type MalError* = object of Exception
+ t*: MalType
+
+# String functions
+proc pr_str(xs: varargs[MalType]): MalType =
+ str(xs.map(proc(x: MalType): string = x.pr_str(true)).join(" "))
+
+proc do_str(xs: varargs[MalType]): MalType =
+ str(xs.map(proc(x: MalType): string = x.pr_str(false)).join)
+
+proc prn(xs: varargs[MalType]): MalType =
+ echo xs.map(proc(x: MalType): string = x.pr_str(true)).join(" ")
+ result = nilObj
+
+proc println(xs: varargs[MalType]): MalType =
+ let line = xs.map(proc(x: MalType): string = x.pr_str(false)).join(" ")
+ echo line.replace("\\n", "\n")
+ result = nilObj
+
+proc read_str(xs: varargs[MalType]): MalType =
+ read_str(xs[0].str)
+
+proc readline(xs: varargs[MalType]): MalType =
+ str readLineFromStdin(xs[0].str)
+
+proc slurp(xs: varargs[MalType]): MalType =
+ str readFile(xs[0].str)
+
+proc cons(xs: varargs[MalType]): MalType =
+ result = list(xs[0])
+ for x in xs[1].list: result.list.add x
+
+proc concat(xs: varargs[MalType]): MalType =
+ result = list()
+ for x in xs:
+ for i in x.list:
+ result.list.add i
+
+proc nth(xs: varargs[MalType]): MalType =
+ if xs[1].number < xs[0].list.len: return xs[0].list[xs[1].number]
+ else: raise newException(ValueError, "nth: index out of range")
+
+proc first(xs: varargs[MalType]): MalType =
+ if xs[0].kind in {List, Vector} and xs[0].list.len > 0:
+ xs[0].list[0]
+ else: nilObj
+
+proc rest(xs: varargs[MalType]): MalType =
+ if xs[0].kind in {List, Vector} and xs[0].list.len > 0:
+ list xs[0].list[1 .. -1]
+ else: list()
+
+proc throw(xs: varargs[MalType]): MalType =
+ raise (ref MalError)(t: list xs)
+
+proc assoc(xs: varargs[MalType]): MalType =
+ result = hash_map()
+ result.hash_map = xs[0].hash_map
+ for i in countup(1, xs.high, 2):
+ result.hash_map[xs[i].str] = xs[i+1]
+
+proc dissoc(xs: varargs[MalType]): MalType =
+ result = hash_map()
+ result.hash_map = xs[0].hash_map
+ for i in 1 .. xs.high:
+ if result.hash_map.hasKey(xs[i].str): result.hash_map.del(xs[i].str)
+
+proc get(xs: varargs[MalType]): MalType =
+ if xs[0].kind == HashMap:
+ result = xs[0].hash_map[xs[1].str]
+ if not result.isNil: return
+
+ result = nilObj
+
+proc contains_q(xs: varargs[MalType]): MalType =
+ boolObj xs[0].hash_map.hasKey(xs[1].str)
+
+proc keys(xs: varargs[MalType]): MalType =
+ result = list()
+ for key in xs[0].hash_map.keys:
+ result.list.add str(key)
+
+proc vals(xs: varargs[MalType]): MalType =
+ result = list()
+ for value in xs[0].hash_map.values:
+ result.list.add value
+
+proc conj(xs: varargs[MalType]): MalType =
+ if xs[0].kind == List:
+ result = list()
+ for i in countdown(xs.high, 1):
+ result.list.add xs[i]
+ result.list.add xs[0].list
+ else:
+ result = vector()
+ result.list.add xs[0].list
+ for i in 1..xs.high:
+ result.list.add xs[i]
+ result.meta = xs[0].meta
+
+proc apply(xs: varargs[MalType]): MalType =
+ var s = newSeq[MalType]()
+ if xs.len > 2:
+ for j in 1 .. xs.high-1:
+ s.add xs[j]
+ s.add xs[xs.high].list
+ xs[0].getFun()(s)
+
+proc map(xs: varargs[MalType]): MalType =
+ result = list()
+ for i in 0 .. xs[1].list.high:
+ result.list.add xs[0].getFun()(xs[1].list[i])
+
+proc with_meta(xs: varargs[MalType]): MalType =
+ new result
+ result[] = xs[0][]
+ result.meta = xs[1]
+
+proc meta(xs: varargs[MalType]): MalType =
+ if not xs[0].meta.isNil: xs[0].meta
+ else: nilObj
+
+proc deref(xs: varargs[MalType]): MalType =
+ xs[0].val
+
+proc reset_bang(xs: varargs[MalType]): MalType =
+ xs[0].val = xs[1]
+ result = xs[0].val
+
+proc swap_bang(xs: varargs[MalType]): MalType =
+ var args = @[xs[0].val]
+ for i in 2 .. xs.high:
+ args.add xs[i]
+ xs[0].val = xs[1].getFun()(args)
+ result = xs[0].val
+
+proc time_ms(xs: varargs[MalType]): MalType =
+ number int(epochTime() * 1000)
+
+template wrapNumberFun(op: expr): expr =
+ fun proc(xs: varargs[MalType]): MalType =
+ number op(xs[0].number, xs[1].number)
+
+template wrapBoolFun(op: expr): expr =
+ fun proc(xs: varargs[MalType]): MalType =
+ if op(xs[0].number, xs[1].number): trueObj else: falseObj
+
+let ns* = {
+ "+": wrapNumberFun(`+`),
+ "-": wrapNumberFun(`-`),
+ "*": wrapNumberFun(`*`),
+ "/": wrapNumberFun(`div`),
+
+ "<": wrapBoolFun(`<`),
+ "<=": wrapBoolFun(`<=`),
+ ">": wrapBoolFun(`>`),
+ ">=": wrapBoolFun(`>=`),
+
+ "list": fun list,
+ "list?": fun list_q,
+ "vector": fun vector,
+ "vector?": fun vector_q,
+ "hash-map": fun hash_map,
+ "map?": fun hash_map_q,
+ "empty?": fun empty_q,
+ "assoc": fun assoc,
+ "dissoc": fun dissoc,
+ "get": fun get,
+ "contains?": fun contains_q,
+ "keys": fun keys,
+ "vals": fun vals,
+
+ "=": fun equal,
+
+ "pr-str": fun pr_str,
+ "str": fun do_str,
+ "prn": fun prn,
+ "println": fun println,
+
+ "read-string": fun read_str,
+ "readline": fun readline,
+ "slurp": fun slurp,
+
+ "sequential?": fun seq_q,
+ "cons": fun cons,
+ "concat": fun concat,
+ "count": fun count,
+ "nth": fun nth,
+ "first": fun first,
+ "rest": fun rest,
+ "conj": fun conj,
+ "apply": fun apply,
+ "map": fun map,
+
+ "throw": fun throw,
+
+ "nil?": fun nil_q,
+ "true?": fun true_q,
+ "false?": fun false_q,
+ "symbol": fun symbol,
+ "symbol?": fun symbol_q,
+ "keyword": fun keyword,
+ "keyword?": fun keyword_q,
+
+ "with-meta": fun with_meta,
+ "meta": fun meta,
+ "atom": fun atom,
+ "atom?": fun atom_q,
+ "deref": fun deref,
+ "reset!": fun reset_bang,
+ "swap!": fun swap_bang,
+
+ "time-ms": fun time_ms,
+}
diff --git a/nim/env.nim b/nim/env.nim
new file mode 100644
index 0000000..7432d09
--- /dev/null
+++ b/nim/env.nim
@@ -0,0 +1,25 @@
+import tables, types
+
+proc initEnv*(outer: Env = nil, binds, exprs: MalType = nilObj): Env =
+ result = Env(data: initTable[string, MalType](), outer: outer)
+
+ if binds.kind in {List, Vector}:
+ for i, e in binds.list:
+ if e.str == "&":
+ result.data[binds.list[i+1].str] = list(exprs.list[i .. -1])
+ break
+ else:
+ result.data[e.str] = exprs.list[i]
+
+proc set*(e: var Env, key: string, value: MalType): MalType {.discardable.} =
+ e.data[key] = value
+ value
+
+proc find*(e: Env, key: string): Env =
+ if e.data.hasKey(key): return e
+ if e.outer != nil: return e.outer.find(key)
+
+proc get*(e: Env, key: string): MalType =
+ let env = e.find(key)
+ if env == nil: raise newException(ValueError, "'" & key & "' not found")
+ env.data[key]
diff --git a/nim/mal.nimble b/nim/mal.nimble
new file mode 100644
index 0000000..5ec4b7f
--- /dev/null
+++ b/nim/mal.nimble
@@ -0,0 +1,11 @@
+[Package]
+name = "mal"
+version = "1.0"
+author = "Dennis Felsing"
+description = "Mal code in Nim"
+license = "MIT"
+
+bin = "step0_repl, step1_read_print, step2_eval, step3_env, step4_if_fn_do, step5_tco, step6_file, step7_quote, step8_macros, step9_try, stepA_mal"
+
+[Deps]
+Requires = "nim >= 0.10.3, nre >= 0.6.0"
diff --git a/nim/nim.cfg b/nim/nim.cfg
new file mode 100644
index 0000000..dbf197a
--- /dev/null
+++ b/nim/nim.cfg
@@ -0,0 +1,2 @@
+deadCodeElim: off
+gc: markandsweep
diff --git a/nim/printer.nim b/nim/printer.nim
new file mode 100644
index 0000000..912b8a9
--- /dev/null
+++ b/nim/printer.nim
@@ -0,0 +1,27 @@
+import strutils, sequtils, tables, types
+
+proc str_handle(x: string, pr = true): string =
+ if x.len > 0 and x[0] == '\xff':
+ result = ":" & x[1 .. x.high]
+ elif pr: result = "\"" & x.replace("\\", "\\\\").replace("\"", "\\\"") & "\""
+ else: result = x
+
+proc pr_str*(m: MalType, pr = true): string =
+ case m.kind
+ of Nil: result = "nil"
+ of True: result = "true"
+ of False: result = "false"
+ of Fun: result = "#<function>"
+ of MalFun: result = "#<malfun>"
+ of Atom: result = "(atom " & m.val.pr_str & ")"
+ of Symbol: result = m.str
+ of String: result = m.str.str_handle(pr)
+ of Number: result = $m.number
+ of List: result = "(" & m.list.mapIt(string, it.pr_str(pr)).join(" ") & ")"
+ of Vector: result = "[" & m.list.mapIt(string, it.pr_str(pr)).join(" ") & "]"
+ of HashMap:
+ result = "{"
+ for key, val in m.hash_map.pairs:
+ if result.len > 1: result.add " "
+ result.add key.str_handle & " " & val.pr_str(pr)
+ result.add "}"
diff --git a/nim/reader.nim b/nim/reader.nim
new file mode 100644
index 0000000..10fce10
--- /dev/null
+++ b/nim/reader.nim
@@ -0,0 +1,108 @@
+import nre, optional_t, strutils, types
+
+let
+ tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"""
+ intRE = re"-?[0-9]+$"
+
+type
+ Blank* = object of Exception
+
+ Reader = object
+ tokens: seq[string]
+ position: int
+
+proc next(r: var Reader): string =
+ if r.position >= r.tokens.len:
+ result = nil
+ else:
+ result = r.tokens[r.position]
+ inc r.position
+
+proc peek(r: Reader): string =
+ if r.position >= r.tokens.len: nil
+ else: r.tokens[r.position]
+
+proc tokenize(str: string): seq[string] =
+ result = @[]
+ for match in str.findIter(tokenRE):
+ if match.captures[0][0] != ';':
+ result.add match.captures[0]
+
+proc read_form(r: var Reader): MalType
+
+proc read_seq(r: var Reader, fr, to: string): seq[MalType] =
+ result = @[]
+ var t = r.next
+ if t != fr: raise newException(ValueError, "expected '" & fr & "'")
+
+ t = r.peek
+ while t != to:
+ if t == nil: raise newException(ValueError, "expected '" & to & "', got EOF")
+ result.add r.read_form
+ t = r.peek
+ discard r.next
+
+proc read_list(r: var Reader): MalType =
+ result = list r.read_seq("(", ")")
+
+proc read_vector(r: var Reader): MalType =
+ result = vector r.read_seq("[", "]")
+
+proc read_hash_map(r: var Reader): MalType =
+ result = hash_map r.read_seq("{", "}")
+
+proc read_atom(r: var Reader): MalType =
+ let t = r.next
+ if t.match(intRE): number t.parseInt
+ elif t[0] == '"': str t[1 .. <t.high].replace("\\\"", "\"")
+ elif t[0] == ':': keyword t[1 .. t.high]
+ elif t == "nil": nilObj
+ elif t == "true": trueObj
+ elif t == "false": falseObj
+ else: symbol t
+
+proc read_form(r: var Reader): MalType =
+ if r.peek[0] == ';':
+ discard r.next
+ return nilObj
+ case r.peek
+ of "'":
+ discard r.next
+ result = list(symbol "quote", r.read_form)
+ of "`":
+ discard r.next
+ result = list(symbol "quasiquote", r.read_form)
+ of "~":
+ discard r.next
+ result = list(symbol "unquote", r.read_form)
+ of "~@":
+ discard r.next
+ result = list(symbol "splice-unquote", r.read_form)
+ of "^":
+ discard r.next
+ let meta = r.read_form
+ result = list(symbol "with-meta", r.read_form, meta)
+ of "@":
+ discard r.next
+ result = list(symbol "deref", r.read_form)
+
+ # list
+ of "(": result = r.read_list
+ of ")": raise newException(ValueError, "unexpected ')'")
+
+ # vector
+ of "[": result = r.read_vector
+ of "]": raise newException(ValueError, "unexpected ']'")
+
+ # hash-map
+ of "{": result = r.read_hash_map
+ of "}": raise newException(ValueError, "unexpected '}'")
+
+ # atom
+ else: result = r.read_atom
+
+proc read_str*(str: string): MalType =
+ var r = Reader(tokens: str.tokenize)
+ if r.tokens.len == 0:
+ raise newException(Blank, "Blank line")
+ r.read_form
diff --git a/nim/step0_repl.nim b/nim/step0_repl.nim
new file mode 100644
index 0000000..6ae7d89
--- /dev/null
+++ b/nim/step0_repl.nim
@@ -0,0 +1,11 @@
+import rdstdin
+
+proc read(str: string): string = str
+
+proc eval(ast: string): string = ast
+
+proc print(exp: string): string = exp
+
+while true:
+ let line = readLineFromStdin("user> ")
+ echo line.read.eval.print
diff --git a/nim/step1_read_print.nim b/nim/step1_read_print.nim
new file mode 100644
index 0000000..4be5898
--- /dev/null
+++ b/nim/step1_read_print.nim
@@ -0,0 +1,14 @@
+import rdstdin, types, reader, printer
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType): MalType = ast
+
+proc print(exp: MalType): string = exp.pr_str
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.read.eval.print
+ except:
+ echo getCurrentExceptionMsg()
diff --git a/nim/step2_eval.nim b/nim/step2_eval.nim
new file mode 100644
index 0000000..075b4ac
--- /dev/null
+++ b/nim/step2_eval.nim
@@ -0,0 +1,52 @@
+import rdstdin, tables, sequtils, types, reader, printer
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType, env: Table[string, MalType]): MalType
+
+proc eval_ast(ast: MalType, env: Table[string, MalType]): MalType =
+ case ast.kind
+ of Symbol:
+ if not env.hasKey(ast.str):
+ raise newException(ValueError, "'" & ast.str & "' not found")
+ result = env[ast.str]
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: Table[string, MalType]): MalType =
+ case ast.kind
+ of List:
+ let el = ast.eval_ast(env)
+ el.list[0].fun(el.list[1 .. -1])
+ else:
+ ast.eval_ast(env)
+
+proc print(exp: MalType): string = exp.pr_str
+
+template wrapNumberFun(op: expr): expr =
+ fun proc(xs: varargs[MalType]): MalType = number op(xs[0].number, xs[1].number)
+
+let repl_env = toTable({
+ "+": wrapNumberFun `+`,
+ "-": wrapNumberFun `-`,
+ "*": wrapNumberFun `*`,
+ "/": wrapNumberFun `div`,
+})
+
+proc rep(str: string): string =
+ str.read.eval(repl_env).print
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except:
+ echo getCurrentExceptionMsg()
diff --git a/nim/step3_env.nim b/nim/step3_env.nim
new file mode 100644
index 0000000..98438fe
--- /dev/null
+++ b/nim/step3_env.nim
@@ -0,0 +1,70 @@
+import rdstdin, tables, sequtils, types, reader, printer, env
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType, env: var Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of List:
+ let
+ a0 = ast.list[0]
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+
+ case a0.str
+ of "def!":
+ result = env.set(a1.str, a2.eval(env))
+ of "let*":
+ var letEnv: Env
+ letEnv.deepCopy(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ letEnv.set(a1.list[i].str, a1.list[i+1].eval(letEnv))
+ else: discard
+ result = a2.eval(letEnv)
+ else:
+ let el = ast.eval_ast(env)
+ result = el.list[0].fun(el.list[1 .. -1])
+ else:
+ result = ast.eval_ast(env)
+
+proc print(exp: MalType): string = exp.pr_str
+
+template wrapNumberFun(op: expr): expr =
+ fun proc(xs: varargs[MalType]): MalType = number op(xs[0].number, xs[1].number)
+
+var repl_env = initEnv()
+
+repl_env.set("+", wrapNumberFun(`+`))
+repl_env.set("-", wrapNumberFun(`-`))
+repl_env.set("*", wrapNumberFun(`*`))
+repl_env.set("/", wrapNumberFun(`div`))
+#repl_env.set("/", wrapNumberFun(proc(x,y: int): int = int(x.float / y.float)))
+
+proc rep(str: string): string =
+ str.read.eval(repl_env).print
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step4_if_fn_do.nim b/nim/step4_if_fn_do.nim
new file mode 100644
index 0000000..a4a8dd1
--- /dev/null
+++ b/nim/step4_if_fn_do.nim
@@ -0,0 +1,104 @@
+import rdstdin, tables, sequtils, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType, env: var Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of List:
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ result = env.set(a1.str, a2.eval(env))
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var letEnv: Env
+ letEnv.deepCopy(env)
+
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ letEnv.set(a1.list[i].str, a1.list[i+1].eval(letEnv))
+ else: discard
+ result = a2.eval(letEnv)
+
+ of "do":
+ let el = (list ast.list[1 .. -1]).eval_ast(env)
+ result = el.list[el.list.high]
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: result = ast.list[3].eval(env)
+ else: result = nilObj
+ else: result = a2.eval(env)
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ result = fun(proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv))
+
+ else:
+ let el = ast.eval_ast(env)
+ result = el.list[0].fun(el.list[1 .. -1])
+
+ else:
+ let el = ast.eval_ast(env)
+ result = el.list[0].fun(el.list[1 .. -1])
+
+ else:
+ result = ast.eval_ast(env)
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+
+# core.nim: defined using nim
+proc rep(str: string): string =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal itself
+discard rep "(def! not (fn* (a) (if a false true)))"
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step5_tco.nim b/nim/step5_tco.nim
new file mode 100644
index 0000000..bbbb160
--- /dev/null
+++ b/nim/step5_tco.nim
@@ -0,0 +1,115 @@
+import rdstdin, tables, sequtils, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType, env: var Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: var Env): MalType =
+ var ast = ast
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ return env.set(a1.str, a2.eval(env))
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else:
+ defaultApply()
+
+ else:
+ defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+
+# core.nim: defined using nim
+proc rep(str: string): string =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal itself
+discard rep "(def! not (fn* (a) (if a false true)))"
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step6_file.nim b/nim/step6_file.nim
new file mode 100644
index 0000000..a0a569e
--- /dev/null
+++ b/nim/step6_file.nim
@@ -0,0 +1,125 @@
+import rdstdin, tables, sequtils, os, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc eval(ast: MalType, env: var Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: var Env): MalType =
+ var ast = ast
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ return env.set(a1.str, a2.eval(env))
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else:
+ defaultApply()
+
+ else:
+ defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
+var ps = commandLineParams()
+repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
+
+
+# core.nim: defined using nim
+proc rep(str: string): string {.discardable.} =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal itself
+rep "(def! not (fn* (a) (if a false true)))"
+rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+
+if paramCount() >= 1:
+ rep "(load-file \"" & paramStr(1) & "\")"
+ quit()
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except Blank: discard
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step7_quote.nim b/nim/step7_quote.nim
new file mode 100644
index 0000000..848913d
--- /dev/null
+++ b/nim/step7_quote.nim
@@ -0,0 +1,146 @@
+import rdstdin, tables, sequtils, os, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc is_pair(x: MalType): bool =
+ x.kind in {List, Vector} and x.list.len > 0
+
+proc quasiquote(ast: MalType): MalType =
+ if not ast.is_pair:
+ return list(symbol "quote", ast)
+ elif ast.list[0] == symbol "unquote":
+ return ast.list[1]
+ elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
+ return list(symbol "concat", ast.list[0].list[1],
+ quasiquote(list ast.list[1 .. -1]))
+ else:
+ return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
+
+proc eval(ast: MalType, env: var Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: var Env): MalType =
+ var ast = ast
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ return env.set(a1.str, a2.eval(env))
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "quote":
+ return ast.list[1]
+
+ of "quasiquote":
+ ast = ast.list[1].quasiquote
+ # Continue loop (TCO)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else:
+ defaultApply()
+
+ else:
+ defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
+var ps = commandLineParams()
+repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
+
+
+# core.nim: defined using nim
+proc rep(str: string): string {.discardable.} =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal itself
+rep "(def! not (fn* (a) (if a false true)))"
+rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
+
+if paramCount() >= 1:
+ rep "(load-file \"" & paramStr(1) & "\")"
+ quit()
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except Blank: discard
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step8_macros.nim b/nim/step8_macros.nim
new file mode 100644
index 0000000..96981a7
--- /dev/null
+++ b/nim/step8_macros.nim
@@ -0,0 +1,171 @@
+import rdstdin, tables, sequtils, os, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc is_pair(x: MalType): bool =
+ x.kind in {List, Vector} and x.list.len > 0
+
+proc quasiquote(ast: MalType): MalType =
+ if not ast.is_pair:
+ return list(symbol "quote", ast)
+ elif ast.list[0] == symbol "unquote":
+ return ast.list[1]
+ elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
+ return list(symbol "concat", ast.list[0].list[1],
+ quasiquote(list ast.list[1 .. -1]))
+ else:
+ return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
+
+proc is_macro_call(ast: MalType, env: Env): bool =
+ ast.kind == List and ast.list[0].kind == Symbol and
+ env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q
+
+proc macroexpand(ast: MalType, env: Env): MalType =
+ result = ast
+ while result.is_macro_call(env):
+ let mac = env.get(result.list[0].str)
+ result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
+
+proc eval(ast: MalType, env: Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: Env): MalType =
+ var ast = ast
+ var env = env
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ ast = ast.macroexpand(env)
+ if ast.kind != List: return ast
+ if ast.list.len == 0: return ast
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ return env.set(a1.str, a2.eval(env))
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "quote":
+ return ast.list[1]
+
+ of "quasiquote":
+ ast = ast.list[1].quasiquote
+ # Continue loop (TCO)
+
+ of "defmacro!":
+ var fun = ast.list[2].eval(env)
+ fun.malfun.is_macro = true
+ return env.set(ast.list[1].str, fun)
+
+ of "macroexpand":
+ return ast.list[1].macroexpand(env)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else:
+ defaultApply()
+
+ else:
+ defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
+var ps = commandLineParams()
+repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
+
+
+# core.nim: defined using nim
+proc rep(str: string): string {.discardable.} =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal 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 paramCount() >= 1:
+ rep "(load-file \"" & paramStr(1) & "\")"
+ quit()
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except Blank: discard
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/step9_try.nim b/nim/step9_try.nim
new file mode 100644
index 0000000..6d77ac7
--- /dev/null
+++ b/nim/step9_try.nim
@@ -0,0 +1,190 @@
+import rdstdin, tables, sequtils, os, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc is_pair(x: MalType): bool =
+ x.kind in {List, Vector} and x.list.len > 0
+
+proc quasiquote(ast: MalType): MalType =
+ if not ast.is_pair:
+ return list(symbol "quote", ast)
+ elif ast.list[0] == symbol "unquote":
+ return ast.list[1]
+ elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
+ return list(symbol "concat", ast.list[0].list[1],
+ quasiquote(list ast.list[1 .. -1]))
+ else:
+ return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
+
+proc is_macro_call(ast: MalType, env: Env): bool =
+ ast.kind == List and ast.list[0].kind == Symbol and
+ env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q
+
+proc macroexpand(ast: MalType, env: Env): MalType =
+ result = ast
+ while result.is_macro_call(env):
+ let mac = env.get(result.list[0].str)
+ result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
+
+proc eval(ast: MalType, env: Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: Env): MalType =
+ var ast = ast
+ var env = env
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ ast = ast.macroexpand(env)
+ if ast.kind != List: return ast
+ if ast.list.len == 0: return ast
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ res = a2.eval(env)
+ return env.set(a1.str, res)
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "quote":
+ return ast.list[1]
+
+ of "quasiquote":
+ ast = ast.list[1].quasiquote
+ # Continue loop (TCO)
+
+ of "defmacro!":
+ var fun = ast.list[2].eval(env)
+ fun.malfun.is_macro = true
+ return env.set(ast.list[1].str, fun)
+
+ of "macroexpand":
+ return ast.list[1].macroexpand(env)
+
+ of "try*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ if a2.list[0].str == "catch*":
+ try:
+ return a1.eval(env)
+ except MalError:
+ let exc = (ref MalError) getCurrentException()
+ var catchEnv = initEnv(env, list a2.list[1], exc.t)
+ return a2.list[2].eval(catchEnv)
+ except:
+ let exc = getCurrentExceptionMsg()
+ var catchEnv = initEnv(env, list a2.list[1], list str(exc))
+ return a2.list[2].eval(catchEnv)
+ else:
+ return a1.eval(env)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else:
+ defaultApply()
+
+ else:
+ defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
+var ps = commandLineParams()
+repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
+
+
+# core.nim: defined using nim
+proc rep(str: string): string {.discardable.} =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal 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 paramCount() >= 1:
+ rep "(load-file \"" & paramStr(1) & "\")"
+ quit()
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except Blank: discard
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/stepA_mal.nim b/nim/stepA_mal.nim
new file mode 100644
index 0000000..b138bd9
--- /dev/null
+++ b/nim/stepA_mal.nim
@@ -0,0 +1,191 @@
+import rdstdin, tables, sequtils, os, types, reader, printer, env, core
+
+proc read(str: string): MalType = str.read_str
+
+proc is_pair(x: MalType): bool =
+ x.kind in {List, Vector} and x.list.len > 0
+
+proc quasiquote(ast: MalType): MalType =
+ if not ast.is_pair:
+ return list(symbol "quote", ast)
+ elif ast.list[0] == symbol "unquote":
+ return ast.list[1]
+ elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
+ return list(symbol "concat", ast.list[0].list[1],
+ quasiquote(list ast.list[1 .. -1]))
+ else:
+ return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
+
+proc is_macro_call(ast: MalType, env: Env): bool =
+ ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and
+ env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).macro_q
+
+proc macroexpand(ast: MalType, env: Env): MalType =
+ result = ast
+ while result.is_macro_call(env):
+ let mac = env.get(result.list[0].str)
+ result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
+
+proc eval(ast: MalType, env: Env): MalType
+
+proc eval_ast(ast: MalType, env: var Env): MalType =
+ case ast.kind
+ of Symbol:
+ result = env.get(ast.str)
+ of List:
+ result = list ast.list.mapIt(MalType, it.eval(env))
+ of Vector:
+ result = vector ast.list.mapIt(MalType, it.eval(env))
+ of HashMap:
+ result = hash_map()
+ for k, v in ast.hash_map.pairs:
+ result.hash_map[k] = v.eval(env)
+ else:
+ result = ast
+
+proc eval(ast: MalType, env: Env): MalType =
+ var ast = ast
+ var env = env
+
+ template defaultApply =
+ let el = ast.eval_ast(env)
+ let f = el.list[0]
+ case f.kind
+ of MalFun:
+ ast = f.malfun.ast
+ env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
+ else:
+ return f.fun(el.list[1 .. -1])
+
+ while true:
+ if ast.kind != List: return ast.eval_ast(env)
+
+ ast = ast.macroexpand(env)
+ if ast.kind != List: return ast
+ if ast.list.len == 0: return ast
+
+ let a0 = ast.list[0]
+ case a0.kind
+ of Symbol:
+ case a0.str
+ of "def!":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ res = a2.eval(env)
+ return env.set(a1.str, res)
+
+ of "let*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var let_env = Env(env)
+ case a1.kind
+ of List, Vector:
+ for i in countup(0, a1.list.high, 2):
+ let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env))
+ else: raise newException(ValueError, "Illegal kind in let*")
+ ast = a2
+ env = let_env
+ # Continue loop (TCO)
+
+ of "quote":
+ return ast.list[1]
+
+ of "quasiquote":
+ ast = ast.list[1].quasiquote
+ # Continue loop (TCO)
+
+ of "defmacro!":
+ var fun = ast.list[2].eval(env)
+ fun.malfun.is_macro = true
+ return env.set(ast.list[1].str, fun)
+
+ of "macroexpand":
+ return ast.list[1].macroexpand(env)
+
+ of "try*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ if a2.list[0].str == "catch*":
+ try:
+ return a1.eval(env)
+ except MalError:
+ let exc = (ref MalError) getCurrentException()
+ var catchEnv = initEnv(env, list a2.list[1], exc.t)
+ return a2.list[2].eval(catchEnv)
+ except:
+ let exc = getCurrentExceptionMsg()
+ var catchEnv = initEnv(env, list a2.list[1], list str(exc))
+ return a2.list[2].eval(catchEnv)
+ else:
+ return a1.eval(env)
+
+ of "do":
+ let last = ast.list.high
+ let el = (list ast.list[1 .. <last]).eval_ast(env)
+ ast = ast.list[last]
+ # Continue loop (TCO)
+
+ of "if":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ cond = a1.eval(env)
+
+ if cond.kind in {Nil, False}:
+ if ast.list.len > 3: ast = ast.list[3]
+ else: ast = nilObj
+ else: ast = a2
+
+ of "fn*":
+ let
+ a1 = ast.list[1]
+ a2 = ast.list[2]
+ var env2 = env
+ let fn = proc(a: varargs[MalType]): MalType =
+ var newEnv = initEnv(env2, a1, list(a))
+ a2.eval(newEnv)
+ return malfun(fn, a2, a1, env)
+
+ else: defaultApply()
+
+ else: defaultApply()
+
+proc print(exp: MalType): string = exp.pr_str
+
+var repl_env = initEnv()
+
+for k, v in ns.items:
+ repl_env.set(k, v)
+repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env)))
+var ps = commandLineParams()
+repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str)))
+
+
+# core.nim: defined using nim
+proc rep(str: string): string {.discardable.} =
+ str.read.eval(repl_env).print
+
+# core.mal: defined using mal 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))))))))"
+rep "(def! *host-language* \"nim\")"
+
+if paramCount() >= 1:
+ rep "(load-file \"" & paramStr(1) & "\")"
+ quit()
+
+rep "(println (str \"Mal [\" *host-language* \"]\"))"
+
+while true:
+ try:
+ let line = readLineFromStdin("user> ")
+ echo line.rep
+ except Blank: discard
+ except:
+ echo getCurrentExceptionMsg()
+ echo getCurrentException().getStackTrace()
diff --git a/nim/types.nim b/nim/types.nim
new file mode 100644
index 0000000..56983ff
--- /dev/null
+++ b/nim/types.nim
@@ -0,0 +1,148 @@
+import tables, strutils
+
+type
+ MalTypeKind* = enum Nil, True, False, Number, Symbol, String,
+ List, Vector, HashMap, Fun, MalFun, Atom
+
+ FunType = proc(a: varargs[MalType]): MalType
+
+ MalFunType* = ref object
+ fn*: FunType
+ ast*: MalType
+ params*: MalType
+ env*: Env
+ is_macro*: bool
+
+ MalType* = ref object
+ case kind*: MalTypeKind
+ of Nil, True, False: nil
+ of Number: number*: int
+ of String, Symbol: str*: string
+ of List, Vector: list*: seq[MalType]
+ of HashMap: hash_map*: Table[string, MalType]
+ of Fun:
+ fun*: FunType
+ is_macro*: bool
+ of MalFun: malfun*: MalFunType
+ of Atom: val*: MalType
+
+ meta*: MalType
+
+ Env* = ref object
+ data*: Table[string, MalType]
+ outer*: Env
+
+let nilObj* = MalType(kind: Nil)
+let trueObj* = MalType(kind: True)
+let falseObj* = MalType(kind: False)
+
+proc number*(x: int): MalType = MalType(kind: Number, number: x)
+
+proc symbol*(x: string): MalType = MalType(kind: Symbol, str: x)
+
+proc str*(x: string): MalType {.procvar.} = MalType(kind: String, str: x)
+
+proc keyword*(x: string): MalType = MalType(kind: String, str: "\xff" & x)
+
+proc atom*(x: MalType): MalType =
+ result = MalType(kind: Atom)
+ result.val = x
+
+proc list*(xs: varargs[MalType]): MalType {.procvar.} =
+ result = MalType(kind: List, list: newSeq[MalType](xs.len))
+ for i, x in xs: result.list[i] = x
+
+proc vector*(xs: varargs[MalType]): MalType {.procvar.} =
+ result = MalType(kind: Vector, list: newSeq[MalType](xs.len))
+ for i, x in xs: result.list[i] = x
+
+proc hash_map*(xs: varargs[MalType]): MalType {.procvar.} =
+ result = MalType(kind: HashMap, hash_map: initTable[string, MalType]())
+ for i in countup(0, xs.high, 2):
+ let s = case xs[i].kind
+ of String: xs[i].str
+ else: xs[i].str
+ result.hash_map[s] = xs[i+1]
+
+proc macro_q*(x: MalType): bool =
+ if x.kind == Fun: result = x.is_macro
+ elif x.kind == MalFun: result = x.malfun.is_macro
+ else: raise newException(ValueError, "no function")
+
+proc getFun*(x: MalType): FunType =
+ if x.kind == Fun: result = x.fun
+ elif x.kind == MalFun: result = x.malfun.fn
+ else: raise newException(ValueError, "no function")
+
+proc fun*(x: proc(xs: varargs[MalType]): MalType, is_macro = false): MalType =
+ MalType(kind: Fun, fun: x, is_macro: is_macro)
+
+proc malfun*(fn: auto, ast, params: MalType,
+ env: Env, is_macro = false): MalType =
+ MalType(kind: MalFun, malfun: MalFunType(fn: fn, ast: ast, params: params,
+ env: env, is_macro: is_macro))
+
+proc boolObj*(b: bool): MalType =
+ if b: trueObj else: falseObj
+
+proc list_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == List
+
+proc vector_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == Vector
+
+proc seq_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind in {List, Vector}
+
+proc hash_map_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == HashMap
+
+proc empty_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].list.len == 0
+
+proc nil_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == Nil
+
+proc true_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == True
+
+proc false_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == False
+
+proc symbol*(xs: varargs[MalType]): MalType {.procvar.} =
+ symbol(xs[0].str)
+
+proc symbol_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == Symbol
+
+proc keyword*(xs: varargs[MalType]): MalType {.procvar.} =
+ keyword(xs[0].str)
+
+proc keyword_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj(xs[0].kind == String and xs[0].str[0] == '\xff')
+
+proc atom*(xs: varargs[MalType]): MalType {.procvar.} =
+ atom(xs[0])
+
+proc atom_q*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0].kind == Atom
+
+proc count*(xs: varargs[MalType]): MalType {.procvar.} =
+ number if xs[0].kind == Nil: 0 else: xs[0].list.len
+
+proc `==`*(x, y: MalType): bool =
+ if not (x.kind in {List, Vector} and y.kind in {List, Vector}):
+ if x.kind != y.kind: return false
+ result = case x.kind
+ of Nil, True, False: true
+ of Number: x.number == y.number
+ of Symbol, String: x.str == y.str
+ of List, Vector: x.list == y.list
+ of HashMap: x.hash_map == y.hash_map
+ of Fun: x.fun == y.fun and
+ x.is_macro == y.is_macro
+ of MalFun: x.malfun == y.malfun
+ of Atom: x.val == y.val
+
+proc equal*(xs: varargs[MalType]): MalType {.procvar.} =
+ boolObj xs[0] == xs[1]
diff --git a/ocaml/core.ml b/ocaml/core.ml
index 20f68b6..12bf3c3 100644
--- a/ocaml/core.ml
+++ b/ocaml/core.ml
@@ -64,7 +64,10 @@ let init env = begin
Env.set env (Types.symbol "vector?")
(Types.fn (function [T.Vector _] -> T.Bool true | _ -> T.Bool false));
Env.set env (Types.symbol "empty?")
- (Types.fn (function [T.List {T.value = []}] -> T.Bool true | _ -> T.Bool false));
+ (Types.fn (function
+ | [T.List {T.value = []}] -> T.Bool true
+ | [T.Vector {T.value = []}] -> T.Bool true
+ | _ -> T.Bool false));
Env.set env (Types.symbol "count")
(Types.fn (function
| [T.List {T.value = xs}]
diff --git a/process/guide.md b/process/guide.md
index 6c6887c..df7eb5e 100644
--- a/process/guide.md
+++ b/process/guide.md
@@ -104,6 +104,7 @@ compared/described:
* http://learnxinyminutes.com/
* http://hyperpolyglot.org/
* http://rosettacode.org/
+* http://rigaux.org/language-study/syntax-across-languages/
Do not let yourself be bogged down by specific problems. While the
make-a-lisp process is structured as a series of steps, the reality is
@@ -161,10 +162,11 @@ This step is basically just creating a skeleton of your interpreter.
language is a statically typed) and `rep` calls them in order
passing the return to the input of the next.
-* Add a main loop that repeatedly prints a prompt, gets a line of
- input from the user, calls `rep` with that line of input, and then
- prints out the result from `rep`. It should also exit when you send
- it an EOF (often Ctrl-D).
+* Add a main loop that repeatedly prints a prompt (needs to be
+ "user> " for later tests to pass), gets a line of input from the
+ user, calls `rep` with that line of input, and then prints out the
+ result from `rep`. It should also exit when you send it an EOF
+ (often Ctrl-D).
* If you are using a compiled (ahead-of-time rather than just-in-time)
language, then create a Makefile (or appropriate project definition
@@ -366,7 +368,7 @@ and each step will give progressively more bang for the buck.
* keyword: just a string stored with unicode prefix (or char 127 if
no unicode support).
* vector: can be implemented with same underlying type as list if
- there is some mechanism for marking/distringuishing from a list.
+ there is some mechanism for marking/distinguishing from a list.
* hash-map: only need to implement string keys (which enables
keyword keys since they are just special strings).
@@ -483,7 +485,7 @@ the changes that will be made during this step:
diff -urp ../process/step2_eval.txt ../process/step3_env.txt
```
-* Copy `step2_eval.qx` to `step2_env.qx`.
+* Copy `step2_eval.qx` to `step3_env.qx`.
* Create `env.qx` to hold the environment definition.
@@ -1109,6 +1111,37 @@ Now go to the top level, run the step 8 tests:
make test^quux^step8
```
+There is a reasonably good chance that the macro tests will not pass
+the first time. Although the implementation of macros is fairly
+simple, debugging runtime bugs with macros can be fairly tricky. If
+you do run into subtle problems that are difficult to solve, let me
+recommend a couple of approaches:
+
+* Use the macroexpand special form to eliminate one of the layers of
+ indirection (to expand but skip evaluate). This will often reveal
+ the source of the issue.
+* Add a debug print statement to the top of your main `eval` function
+ (inside the TCO loop) to print the current value of `ast` (hint use
+ `pr_str` to get easier to debug output). Pull up the step8
+ implementation from another language and uncomment its `eval`
+ function (yes, I give you permission to violate the rule this once).
+ Run the two side-by-side. The first difference is likely to point to
+ the bug.
+
+Congratulations! You now have a Lisp interpreter with a super power
+that most non-Lisp languages can only dream of (I have it on good
+authority that languages dream when you are not using them). If you
+are not already familiar with Lisp macros, I suggest the following
+excercise: write a recursive macro that handles postfixed mal code
+(with the function as the last parameter instead of the first). Or
+not. I have not actually done so myself, but I have heard it is an
+interesting excercise.
+
+In the next step you will add try/catch style exception handling to
+your implementation in addition to some new core functions. After
+step9 you will be very close to having a fully self-hosting mal
+implementation. Let us continue!
+
### Optional
diff --git a/runtest.py b/runtest.py
index e90fc68..a7f0e44 100755
--- a/runtest.py
+++ b/runtest.py
@@ -3,7 +3,7 @@
import os, sys, re
import argparse, time
-import pty
+import pty, signal, atexit
from subprocess import Popen, STDOUT, PIPE
from select import select
@@ -22,8 +22,8 @@ parser.add_argument('--test-timeout', default=20, type=int,
help="default timeout for each individual test action")
parser.add_argument('--pre-eval', default=None, type=str,
help="Mal code to evaluate prior to running the test")
-parser.add_argument('--redirect', action='store_true',
- help="Run implementation in bash and redirect output to /dev/null")
+parser.add_argument('--mono', action='store_true',
+ help="Use workarounds Mono/.Net Console misbehaviors")
parser.add_argument('test_file', type=argparse.FileType('r'),
help="a test file formatted as with mal test data")
@@ -32,17 +32,25 @@ parser.add_argument('mal_cmd', nargs="*",
"specify a Mal command line with dashed options.")
class Runner():
- def __init__(self, args, redirect=False):
- print "args: %s" % repr(args)
- if redirect:
- print "using redirect"
- self.p = Popen(args, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ def __init__(self, args, mono=False):
+ #print "args: %s" % repr(args)
+ self.mono = mono
+
+ # Cleanup child process on exit
+ atexit.register(self.cleanup)
+
+ if mono:
+ self.p = Popen(args, bufsize=0,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT,
+ preexec_fn=os.setsid)
self.stdin = self.p.stdin
self.stdout = self.p.stdout
else:
# provide tty to get 'interactive' readline to work
master, slave = pty.openpty()
- self.p = Popen(args, bufsize=0, stdin=slave, stdout=slave, stderr=STDOUT)
+ self.p = Popen(args, bufsize=0,
+ stdin=slave, stdout=slave, stderr=STDOUT,
+ preexec_fn=os.setsid)
self.stdin = os.fdopen(master, 'r+b', 0)
self.stdout = self.stdin
@@ -53,11 +61,14 @@ class Runner():
def read_to_prompt(self, prompts, timeout):
end_time = time.time() + timeout
while time.time() < end_time:
- [outs,_,_] = select([self.stdin], [], [], 1)
- if self.stdin in outs:
- new_data = self.stdin.read(1)
+ [outs,_,_] = select([self.stdout], [], [], 1)
+ if self.stdout in outs:
+ new_data = self.stdout.read(1)
#print "new_data: '%s'" % new_data
- self.buf += new_data
+ if self.mono:
+ self.buf += new_data.replace("\n", "\r\n")
+ else:
+ self.buf += new_data
for prompt in prompts:
regexp = re.compile(prompt)
match = regexp.search(self.buf)
@@ -69,12 +80,16 @@ class Runner():
return buf
return None
- def write(self, str):
- self.stdout.write(str)
+ def writeline(self, str):
+ self.stdin.write(str + "\n")
+ if self.mono:
+ # Simulate echo
+ self.buf += str + "\r\n"
def cleanup(self):
+ #print "cleaning up"
if self.p:
- self.p.terminate()
+ os.killpg(self.p.pid, signal.SIGTERM)
self.p = None
@@ -83,7 +98,7 @@ test_data = args.test_file.read().split('\n')
if args.rundir: os.chdir(args.rundir)
-r = Runner(args.mal_cmd, redirect=args.redirect)
+r = Runner(args.mal_cmd, mono=args.mono)
test_idx = 0
@@ -133,7 +148,6 @@ def assert_prompt(timeout):
else:
print "Did not get 'user> ' or 'mal-user> ' prompt"
print " Got : %s" % repr(r.buf)
- r.cleanup()
sys.exit(1)
@@ -156,7 +170,7 @@ while test_data:
sys.stdout.flush()
expected = "%s%s%s%s" % (form, sep, out, ret)
- r.write(form + "\n")
+ r.writeline(form)
try:
res = r.read_to_prompt(['\r\nuser> ', '\nuser> ',
'\r\nmal-user> ', '\nmal-user> '],
@@ -171,9 +185,7 @@ while test_data:
fail_cnt += 1
except:
print "Got Exception"
- r.cleanup()
sys.exit(1)
-r.cleanup()
if fail_cnt > 0:
print "FAILURES: %d" % fail_cnt