diff options
| author | Joel Martin <github@martintribe.org> | 2015-03-03 22:46:09 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-03-03 22:46:09 -0600 |
| commit | 2b0fa48326927683fa19f399b3edb8aae3dbcd36 (patch) | |
| tree | 4f0d2b3b6b9e397f968f977cc38ec9a5b8b200f1 | |
| parent | 58ba5af4704c4539ee7d3814dfeff8672577f86a (diff) | |
| parent | a2cd0a3adae2ccf2566122bcd90230d905ab59dc (diff) | |
| download | mal-2b0fa48326927683fa19f399b3edb8aae3dbcd36.tar.gz mal-2b0fa48326927683fa19f399b3edb8aae3dbcd36.zip | |
Merge pull request #20 from def-/master
Add Nim
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | nim/Makefile | 33 | ||||
| -rw-r--r-- | nim/core.nim | 215 | ||||
| -rw-r--r-- | nim/env.nim | 25 | ||||
| -rw-r--r-- | nim/mal.nimble | 11 | ||||
| -rw-r--r-- | nim/nim.cfg | 2 | ||||
| -rw-r--r-- | nim/printer.nim | 27 | ||||
| -rw-r--r-- | nim/reader.nim | 108 | ||||
| -rw-r--r-- | nim/step0_repl.nim | 11 | ||||
| -rw-r--r-- | nim/step1_read_print.nim | 14 | ||||
| -rw-r--r-- | nim/step2_eval.nim | 52 | ||||
| -rw-r--r-- | nim/step3_env.nim | 70 | ||||
| -rw-r--r-- | nim/step4_if_fn_do.nim | 104 | ||||
| -rw-r--r-- | nim/step5_tco.nim | 115 | ||||
| -rw-r--r-- | nim/step6_file.nim | 125 | ||||
| -rw-r--r-- | nim/step7_quote.nim | 146 | ||||
| -rw-r--r-- | nim/step8_macros.nim | 169 | ||||
| -rw-r--r-- | nim/step9_try.nim | 190 | ||||
| -rw-r--r-- | nim/stepA_interop.nim | 190 | ||||
| -rw-r--r-- | nim/types.nim | 148 |
21 files changed, 1774 insertions, 2 deletions
@@ -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,6 +38,7 @@ 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 \ @@ -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,6 +114,7 @@ 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 = --mono @@ -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 @@ -191,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 ``` diff --git a/nim/Makefile b/nim/Makefile new file mode 100644 index 0000000..1da1ae6 --- /dev/null +++ b/nim/Makefile @@ -0,0 +1,33 @@ +##################### + +SOURCES_BASE = types.nim reader.nim printer.nim +SOURCES_LISP = env.nim core.nim stepA_interop.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_interop.nim +BINS = $(SRCS:%.nim=%) + +##################### + +all: $(BINS) mal + +mal: $(word $(words $(BINS)),$(BINS)) + cp $< $@ + +$(BINS): + 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..5fc9e63 --- /dev/null +++ b/nim/core.nim @@ -0,0 +1,215 @@ +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: + xs[0].hash_map[xs[1].str] + else: + 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 = + result = xs[0] + new result.meta + result.meta[] = xs[1] + +proc meta(xs: varargs[MalType]): MalType = + if xs[0].meta != nil: 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..db35b0b --- /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_interop" + +[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..55df3f9 --- /dev/null +++ b/nim/nim.cfg @@ -0,0 +1,2 @@ +d: pcreDynlib +deadCodeElim: off diff --git a/nim/printer.nim b/nim/printer.nim new file mode 100644 index 0000000..95eb840 --- /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..856a6a6 --- /dev/null +++ b/nim/reader.nim @@ -0,0 +1,108 @@ +import nre, optional_t, strutils, types + +var + 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..a03dbd1 --- /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 = + template defaultApply = + let el = ast.eval_ast(env) + let f = el.list[0] + case f.kind + of MalFun: + ast = f.malfun.ast + env = initEnv(env, f.malfun.params, list(el.list[1 .. -1])) + else: + return f.fun(el.list[1 .. -1]) + + var ast = ast + 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].eval(env) + # 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, env2) + + 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..6f0e02a --- /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 = + template defaultApply = + let el = ast.eval_ast(env) + let f = el.list[0] + case f.kind + of MalFun: + ast = f.malfun.ast + env = initEnv(env, f.malfun.params, list(el.list[1 .. -1])) + else: + return f.fun(el.list[1 .. -1]) + + var ast = ast + 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].eval(env) + # 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, env2) + + 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..58fc3b6 --- /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 = + template defaultApply = + let el = ast.eval_ast(env) + let f = el.list[0] + case f.kind + of MalFun: + ast = f.malfun.ast + env = initEnv(env, f.malfun.params, list(el.list[1 .. -1])) + else: + return f.fun(el.list[1 .. -1]) + + var ast = ast + 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].eval(env) + # 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, env2) + + 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..43c1cb4 --- /dev/null +++ b/nim/step8_macros.nim @@ -0,0 +1,169 @@ +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: 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 = + template defaultApply = + let el = ast.eval_ast(env) + let f = el.list[0] + case f.kind + of MalFun: + ast = f.malfun.ast + env = initEnv(env, f.malfun.params, list(el.list[1 .. -1])) + else: + return f.fun(el.list[1 .. -1]) + + var ast = ast + 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].eval(env) + # 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, env2) + + 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..688817d --- /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].eval(env) + # 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_interop.nim b/nim/stepA_interop.nim new file mode 100644 index 0000000..48dd9dd --- /dev/null +++ b/nim/stepA_interop.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.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\")" + +rep "(println (str \"Mal [\" *host-language* \"]\"))" +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/types.nim b/nim/types.nim new file mode 100644 index 0000000..b5e1bec --- /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* = 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*: TableRef[string, MalType] + of Fun: + fun*: FunType + is_macro*: bool + of MalFun: malfun*: MalFunType + of Atom: val*: ref MalType + + meta*: ref MalType + + Env* = ref object + data*: Table[string, MalType] + outer*: Env + +const nilObj* = MalType(kind: Nil) +const trueObj* = MalType(kind: True) +const 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) + new result.val + result.val[] = x + +proc list*(xs: varargs[MalType]): MalType {.procvar.} = + result = MalType(kind: List, list: @[]) + for x in xs: result.list.add x + +proc vector*(xs: varargs[MalType]): MalType {.procvar.} = + result = MalType(kind: Vector, list: @[]) + for x in xs: result.list.add x + +proc hash_map*(xs: varargs[MalType]): MalType {.procvar.} = + result = MalType(kind: HashMap, hash_map: newTable[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: x.is_macro + else: x.malfun.is_macro + +proc getFun*(x: MalType): FunType = + if x.kind == Fun: result = x.fun + elif x.kind == MalFun: result = x.malfun.fn + else: echo x.kind + +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] |
