scala/mal.jar
scala/target
scala/project
+skew/*.js
tcl/mal.tcl
vb/*.exe
vb/*.dll
- {env: IMPL=ruby, services: [docker]}
- {env: IMPL=rust, services: [docker]}
- {env: IMPL=scala, services: [docker]}
+ - {env: IMPL=skew, services: [docker]}
- {env: IMPL=swift NO_DOCKER=1, os: osx, osx_image: xcode7}
- {env: IMPL=swift3, services: [docker]}
- {env: IMPL=swift3 NO_DOCKER=1, os: osx, osx_image: xcode8}
erlang elisp elixir es6 factor forth fsharp go groovy guile haskell \
haxe io java julia js kotlin logo lua make mal ocaml matlab miniMAL \
nim objc objpascal perl perl6 php pil plpgsql plsql powershell ps \
- python r racket rpython ruby rust scala swift swift3 tcl vb vhdl \
+ python r racket rpython ruby rust scala skew swift swift3 tcl vb vhdl \
vimscript
step0 = step0_repl
ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/release/$($(1))
scala_STEP_TO_PROG = scala/target/scala-2.11/classes/$($(1)).class
+skew_STEP_TO_PROG = skew/$($(1)).js
swift_STEP_TO_PROG = swift/$($(1))
swift3_STEP_TO_PROG = swift3/$($(1))
tcl_STEP_TO_PROG = tcl/$($(1)).tcl
Mal is a Clojure inspired Lisp interpreter.
-Mal is implemented in 62 languages:
+Mal is implemented in 63 languages:
* Ada
* GNU awk
* Ruby
* Rust
* Scala
+* Skew
* Swift
* Swift 3
* Tcl
scala -classpath target/scala*/classes stepX_YYY
```
+### Skew ###
+
+*The Skew implementation was created by [Dov Murik](https://github.com/dubek)*
+
+The Skew implementation of mal has been tested with Skew 0.7.42.
+
+```
+cd skew
+make
+node stepX_YYY.js
+```
+
+
### Swift
*The Swift implementation was created by [Keith Rollin](https://github.com/keith-rollin)*
--- /dev/null
+FROM ubuntu:vivid
+MAINTAINER Joel Martin <github@martintribe.org>
+
+##########################################################
+# General requirements for testing or common across many
+# implementations
+##########################################################
+
+RUN apt-get -y update
+
+# Required for running tests
+RUN apt-get -y install make python
+
+# Some typical implementation and test requirements
+RUN apt-get -y install curl libreadline-dev libedit-dev
+
+RUN mkdir -p /mal
+WORKDIR /mal
+
+##########################################################
+# Specific implementation requirements
+##########################################################
+
+# For building node modules
+RUN apt-get -y install g++
+
+# Add nodesource apt repo config for 0.12 stable
+RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash -
+
+# Install nodejs
+RUN apt-get -y install nodejs
+
+# Link common name
+RUN ln -sf nodejs /usr/bin/node
+
+ENV NPM_CONFIG_CACHE /mal/.npm
+
+# Skew
+RUN npm install -g skew
--- /dev/null
+STEPS = 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
+
+SOURCES_BASE = util.sk types.sk reader.sk printer.sk
+SOURCES_LISP = env.sk core.sk stepA_mal.sk
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+STEP3_DEPS = $(SOURCES_BASE) env.sk
+STEP4_DEPS = $(STEP3_DEPS) core.sk
+
+all: $(foreach s,$(STEPS),$(s).js) dist
+
+dist: mal
+
+step0_repl.js step1_read_print.js step2_eval.js step3_env.js: $(STEP3_DEPS)
+step4_if_fn_do.js step5_tco.js step6_file.js step7_quote.js step8_macros.js step9_try.js stepA_mal.js: $(STEP4_DEPS)
+
+%.js: %.sk
+ skewc --target=js --release --output-file=$@ $^
+
+mal: stepA_mal.js
+ echo "#!/usr/bin/env node" > $@
+ cat $< >> $@
+ chmod +x $@
+
+clean:
+ rm -rf step*.js mal
+
+stats: $(SOURCES)
+ @wc $^
+ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*#|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
+
+.PHONY: all dist clean stats stats-lisp
--- /dev/null
+def _printLn(s string) MalVal {
+ printLn(s)
+ return gNil
+}
+
+const ns StringMap<fn(List<MalVal>) MalVal> = {
+ "eval": (a List<MalVal>) => EVAL(a[0], repl_env),
+ "=": (a List<MalVal>) => MalVal.fromBool(a[0].equal(a[1])),
+ "throw": (a List<MalVal>) => { throw MalUserError.new(a[0]) },
+
+ "nil?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalNil),
+ "true?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalTrue),
+ "false?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalFalse),
+ "string?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalString),
+ "symbol": (a List<MalVal>) => MalSymbol.new((a[0] as MalString).val),
+ "symbol?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalSymbol),
+ "keyword": (a List<MalVal>) => MalKeyword.new((a[0] as MalString).val),
+ "keyword?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalKeyword),
+
+ "pr-str": (a List<MalVal>) => MalString.new(" ".join(a.map<string>(e => pr_str(e, true)))),
+ "str": (a List<MalVal>) => MalString.new("".join(a.map<string>(e => pr_str(e, false)))),
+ "prn": (a List<MalVal>) => _printLn(" ".join(a.map<string>(e => pr_str(e, true)))),
+ "println": (a List<MalVal>) => _printLn(" ".join(a.map<string>(e => pr_str(e, false)))),
+ "read-string": (a List<MalVal>) => read_str((a[0] as MalString).val),
+ "readline": (a List<MalVal>) => {
+ const line = readLine((a[0] as MalString).val)
+ return line == null ? gNil : MalString.new(line)
+ },
+ "slurp": (a List<MalVal>) => MalString.new(readFile((a[0] as MalString).val)),
+
+ "<": (a List<MalVal>) => MalVal.fromBool((a[0] as MalNumber).val < (a[1] as MalNumber).val),
+ "<=": (a List<MalVal>) => MalVal.fromBool((a[0] as MalNumber).val <= (a[1] as MalNumber).val),
+ ">": (a List<MalVal>) => MalVal.fromBool((a[0] as MalNumber).val > (a[1] as MalNumber).val),
+ ">=": (a List<MalVal>) => MalVal.fromBool((a[0] as MalNumber).val >= (a[1] as MalNumber).val),
+ "+": (a List<MalVal>) => MalNumber.new((a[0] as MalNumber).val + (a[1] as MalNumber).val),
+ "-": (a List<MalVal>) => MalNumber.new((a[0] as MalNumber).val - (a[1] as MalNumber).val),
+ "*": (a List<MalVal>) => MalNumber.new((a[0] as MalNumber).val * (a[1] as MalNumber).val),
+ "/": (a List<MalVal>) => MalNumber.new((a[0] as MalNumber).val / (a[1] as MalNumber).val),
+ "time-ms": (a List<MalVal>) => MalNumber.new(timeMs),
+
+ "list": (a List<MalVal>) => MalList.new(a),
+ "list?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalList),
+ "vector": (a List<MalVal>) => MalVector.new(a),
+ "vector?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalVector),
+ "hash-map": (a List<MalVal>) => MalHashMap.fromList(a),
+ "map?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalHashMap),
+ "assoc": (a List<MalVal>) => (a[0] as MalHashMap).assoc(a.slice(1)),
+ "dissoc": (a List<MalVal>) => (a[0] as MalHashMap).dissoc(a.slice(1)),
+ "get": (a List<MalVal>) => a[0] is MalNil ? gNil : (a[0] as MalHashMap).get(a[1]),
+ "contains?": (a List<MalVal>) => MalVal.fromBool((a[0] as MalHashMap).contains(a[1])),
+ "keys": (a List<MalVal>) => MalList.new((a[0] as MalHashMap).keys),
+ "vals": (a List<MalVal>) => MalList.new((a[0] as MalHashMap).vals),
+
+ "sequential?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalSequential),
+ "cons": (a List<MalVal>) => {
+ var list List<MalVal> = (a[1] as MalSequential).val.clone
+ list.prepend(a[0])
+ return MalList.new(list)
+ },
+ "concat": (a List<MalVal>) => {
+ var list List<MalVal> = []
+ a.each(e => list.append((e as MalSequential).val))
+ return MalList.new(list)
+ },
+ "nth": (a List<MalVal>) => (a[0] as MalSequential).nth((a[1] as MalNumber).val),
+ "first": (a List<MalVal>) => a[0] is MalNil ? gNil : (a[0] as MalSequential).first,
+ "rest": (a List<MalVal>) => a[0] is MalNil ? MalList.new([]) : (a[0] as MalSequential).rest,
+ "empty?": (a List<MalVal>) => MalVal.fromBool((a[0] as MalSequential).count == 0),
+ "count": (a List<MalVal>) => a[0] is MalNil ? MalNumber.new(0) : MalNumber.new((a[0] as MalSequential).count),
+ "apply": (a List<MalVal>) => {
+ const f = a[0] as MalCallable
+ var args = a.slice(1, a.count - 1)
+ args.append((a[a.count - 1] as MalSequential).val)
+ return f.call(args)
+ },
+ "map": (a List<MalVal>) => {
+ const f = a[0] as MalCallable
+ return MalList.new((a[1] as MalSequential).val.map<MalVal>(e => f.call([e])))
+ },
+
+ "conj": (a List<MalVal>) => (a[0] as MalSequential).conj(a.slice(1)),
+ "seq": (a List<MalVal>) => a[0].seq,
+
+ "meta": (a List<MalVal>) => a[0].meta,
+ "with-meta": (a List<MalVal>) => a[0].withMeta(a[1]),
+ "atom": (a List<MalVal>) => MalAtom.new(a[0]),
+ "atom?": (a List<MalVal>) => MalVal.fromBool(a[0] is MalAtom),
+ "deref": (a List<MalVal>) => (a[0] as MalAtom).val,
+ "reset!": (a List<MalVal>) => (a[0] as MalAtom).resetBang(a[1]),
+ "swap!": (a List<MalVal>) => {
+ var atom = a[0] as MalAtom
+ const oldVal = atom.val
+ var callArgs = a.slice(2)
+ callArgs.prepend(oldVal)
+ const newVal = (a[1] as MalCallable).call(callArgs)
+ return atom.resetBang(newVal)
+ },
+}
--- /dev/null
+class Env {
+ const _outer Env
+ var _data StringMap<MalVal> = {}
+
+ def new(outer Env) {
+ _outer = outer
+ }
+
+ def new(outer Env, binds List<MalVal>, exprs List<MalVal>) {
+ _outer = outer
+ for i in 0..binds.count {
+ const name = (binds[i] as MalSymbol).val
+ if name == "&" {
+ const restName = (binds[i + 1] as MalSymbol).val
+ _data[restName] = MalList.new(exprs.slice(i))
+ break
+ } else {
+ _data[name] = exprs[i]
+ }
+ }
+ }
+
+ def find(key MalSymbol) Env {
+ if key.val in _data { return self }
+ return _outer?.find(key)
+ }
+
+ def get(key MalSymbol) MalVal {
+ const env = find(key)
+ if env == null { throw MalError.new("'" + key.val + "' not found") }
+ return env._data[key.val]
+ }
+
+ def set(key MalSymbol, value MalVal) MalVal {
+ _data[key.val] = value
+ return value
+ }
+}
--- /dev/null
+def pr_str(obj MalVal, readable bool) string {
+ return obj.print(readable)
+}
--- /dev/null
+class Reader {
+ const tokens List<string>
+ var position = 0
+
+ def peek string {
+ if position >= tokens.count {
+ return null
+ }
+ return tokens[position]
+ }
+
+ def next string {
+ const token = peek
+ position++
+ return token
+ }
+}
+
+def tokenize(str string) List<string> {
+ var re = RegExp.new("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\\s\\[\\]{}('\"`,;)]*)", "g")
+ var tokens List<string> = []
+ var match string
+ while (match = re.exec(str)[1]) != "" {
+ if match[0] == ';' {
+ continue
+ }
+ tokens.append(match)
+ }
+ return tokens
+}
+
+def unescape(s string) string {
+ return s.replaceAll("\\\"", "\"").replaceAll("\\n", "\n").replaceAll("\\\\", "\\")
+}
+
+def read_atom(rdr Reader) MalVal {
+ const token = rdr.peek
+ if token == "nil" {
+ rdr.next
+ return gNil
+ }
+ if token == "true" {
+ rdr.next
+ return gTrue
+ }
+ if token == "false" {
+ rdr.next
+ return gFalse
+ }
+ switch token[0] {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) }
+ case '-' {
+ if token.count <= 1 { return MalSymbol.new(rdr.next) }
+ switch token[1] {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' { return MalNumber.new(stringToInt(rdr.next)) }
+ default { return MalSymbol.new(rdr.next) }
+ }
+ }
+ case '"' {
+ const s = rdr.next
+ if s[s.count - 1] == '"' {
+ return MalString.new(unescape(s.slice(1, s.count - 1)))
+ } else {
+ throw MalError.new("expected '\"', got EOF")
+ }
+ }
+ case ':' { return MalKeyword.new(rdr.next.slice(1)) }
+ default { return MalSymbol.new(rdr.next) }
+ }
+}
+
+def read_sequence(rdr Reader, open string, close string) List<MalVal> {
+ if rdr.next != open {
+ throw MalError.new("expected '" + open + "'")
+ }
+ var token string
+ var items List<MalVal> = []
+ while (token = rdr.peek) != close {
+ if token == null {
+ throw MalError.new("expected '" + close + "', got EOF")
+ }
+ items.append(read_form(rdr))
+ }
+ rdr.next # consume the close paren/bracket/brace
+ return items
+}
+
+def read_list(rdr Reader) MalList {
+ return MalList.new(read_sequence(rdr, "(", ")"))
+}
+
+def read_vector(rdr Reader) MalVector {
+ return MalVector.new(read_sequence(rdr, "[", "]"))
+}
+
+def read_hash_map(rdr Reader) MalHashMap {
+ return MalHashMap.fromList(read_sequence(rdr, "{", "}"))
+}
+
+def reader_macro(rdr Reader, symbol_name string) MalVal {
+ rdr.next
+ return MalList.new([MalSymbol.new(symbol_name), read_form(rdr)])
+}
+
+def read_form(rdr Reader) MalVal {
+ switch rdr.peek[0] {
+ case '\'' { return reader_macro(rdr, "quote") }
+ case '`' { return reader_macro(rdr, "quasiquote") }
+ case '~' {
+ if rdr.peek == "~" { return reader_macro(rdr, "unquote") }
+ else if rdr.peek == "~@" { return reader_macro(rdr, "splice-unquote") }
+ else { return read_atom(rdr) }
+ }
+ case '^' {
+ rdr.next
+ const meta = read_form(rdr)
+ return MalList.new([MalSymbol.new("with-meta"), read_form(rdr), meta])
+ }
+ case '@' { return reader_macro(rdr, "deref") }
+ case ')' { throw MalError.new("unexpected ')'") }
+ case '(' { return read_list(rdr) }
+ case ']' { throw MalError.new("unexpected ']'") }
+ case '[' { return read_vector(rdr) }
+ case '}' { throw MalError.new("unexpected '}'") }
+ case '{' { return read_hash_map(rdr) }
+ default { return read_atom(rdr) }
+ }
+}
+
+def read_str(str string) MalVal {
+ const tokens = tokenize(str)
+ if tokens.isEmpty { return null }
+ var rdr = Reader.new(tokens)
+ return read_form(rdr)
+}
+
+@import {
+ const RegExp dynamic
+}
--- /dev/null
+#!/bin/bash
+exec node $(dirname $0)/${STEP:-stepA_mal}.js "${@}"
--- /dev/null
+def READ(str string) string {
+ return str
+}
+
+def EVAL(ast string, env StringMap<string>) string {
+ return ast
+}
+
+def PRINT(exp string) string {
+ return exp
+}
+
+def REP(str string) string {
+ return PRINT(EVAL(READ(str), {}))
+}
+
+@entry
+def main {
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ printLn(REP(line))
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def EVAL(ast MalVal, env StringMap<string>) MalVal {
+ return ast
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+def REP(str string) string {
+ return PRINT(EVAL(READ(str), {}))
+}
+
+@entry
+def main {
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def eval_ast(ast MalVal, env StringMap<MalVal>) MalVal {
+ if ast is MalSymbol {
+ const name = (ast as MalSymbol).val
+ if !(name in env) {
+ throw MalError.new("'" + name + "' not found")
+ }
+ return env[name]
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env StringMap<MalVal>) MalVal {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ var astList = ast as MalList
+ if astList.isEmpty { return ast }
+ var evaledList = eval_ast(ast, env) as MalList
+ var fn = evaledList[0] as MalNativeFunc
+ return fn.call(evaledList.val.slice(1))
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env StringMap<MalVal> = {
+ "+": MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val + (args[1] as MalNumber).val)),
+ "-": MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val - (args[1] as MalNumber).val)),
+ "*": MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val * (args[1] as MalNumber).val)),
+ "/": MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val / (args[1] as MalNumber).val)),
+}
+
+def REP(str string) string {
+ return PRINT(EVAL(READ(str), repl_env))
+}
+
+@entry
+def main {
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ return EVAL(astList[2], letenv)
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0] as MalNativeFunc
+ return fn.call(evaledList.val.slice(1))
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def REP(str string) string {
+ return PRINT(EVAL(READ(str), repl_env))
+}
+
+@entry
+def main {
+ repl_env.set(MalSymbol.new("+"), MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val + (args[1] as MalNumber).val)))
+ repl_env.set(MalSymbol.new("-"), MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val - (args[1] as MalNumber).val)))
+ repl_env.set(MalSymbol.new("*"), MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val * (args[1] as MalNumber).val)))
+ repl_env.set(MalSymbol.new("/"), MalNativeFunc.new((args List<MalVal>) MalVal => MalNumber.new((args[0] as MalNumber).val / (args[1] as MalNumber).val)))
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ return EVAL(astList[2], letenv)
+ } else if a0sym.val == "do" {
+ const r = eval_ast(MalList.new(astList.val.slice(1)), env) as MalList
+ return r[r.count - 1]
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ return astList.count > 3 ? EVAL(astList[3], env) : gNil
+ } else {
+ return EVAL(astList[2], env)
+ }
+ } else if a0sym.val == "fn*" {
+ const argsNames = (astList[1] as MalSequential).val
+ return MalNativeFunc.new((args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0] as MalNativeFunc
+ return fn.call(evaledList.val.slice(1))
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+ repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+ if argv.count > 0 {
+ RE("(load-file \"" + argv[0] + "\")")
+ return
+ }
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def isPair(a MalVal) bool {
+ return a is MalSequential && !(a as MalSequential).isEmpty
+}
+
+def quasiquote(ast MalVal) MalVal {
+ if !isPair(ast) {
+ return MalList.new([MalSymbol.new("quote"), ast])
+ }
+ const astSeq = ast as MalSequential
+ const a0 = astSeq[0]
+ if a0.isSymbol("unquote") {
+ return astSeq[1]
+ }
+ if isPair(a0) {
+ const a0Seq = a0 as MalSequential
+ if a0Seq[0].isSymbol("splice-unquote") {
+ return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
+ }
+ }
+ return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "quote" {
+ return astList[1]
+ } else if a0sym.val == "quasiquote" {
+ ast = quasiquote(astList[1])
+ continue # TCO
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+ repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+
+ if argv.count > 0 {
+ RE("(load-file \"" + argv[0] + "\")")
+ return
+ }
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def isPair(a MalVal) bool {
+ return a is MalSequential && !(a as MalSequential).isEmpty
+}
+
+def quasiquote(ast MalVal) MalVal {
+ if !isPair(ast) {
+ return MalList.new([MalSymbol.new("quote"), ast])
+ }
+ const astSeq = ast as MalSequential
+ const a0 = astSeq[0]
+ if a0.isSymbol("unquote") {
+ return astSeq[1]
+ }
+ if isPair(a0) {
+ const a0Seq = a0 as MalSequential
+ if a0Seq[0].isSymbol("splice-unquote") {
+ return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
+ }
+ }
+ return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
+}
+
+def isMacro(ast MalVal, env Env) bool {
+ if !(ast is MalList) { return false }
+ const astList = ast as MalList
+ if astList.isEmpty { return false }
+ const a0 = astList[0]
+ if !(a0 is MalSymbol) { return false }
+ const a0Sym = a0 as MalSymbol
+ if env.find(a0Sym) == null { return false }
+ const f = env.get(a0Sym)
+ if !(f is MalFunc) { return false }
+ return (f as MalFunc).isMacro
+}
+
+def macroexpand(ast MalVal, env Env) MalVal {
+ while isMacro(ast, env) {
+ const astList = ast as MalList
+ const mac = env.get(astList[0] as MalSymbol) as MalFunc
+ ast = mac.call((astList.rest as MalSequential).val)
+ }
+ return ast
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ ast = macroexpand(ast, env)
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "quote" {
+ return astList[1]
+ } else if a0sym.val == "quasiquote" {
+ ast = quasiquote(astList[1])
+ continue # TCO
+ } else if a0sym.val == "defmacro!" {
+ var macro = EVAL(astList[2], env) as MalFunc
+ macro.setAsMacro
+ return env.set(astList[1] as MalSymbol, macro)
+ } else if a0sym.val == "macroexpand" {
+ return macroexpand(astList[1], env)
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+ repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+ RE("(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)))))))")
+ RE("(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 argv.count > 0 {
+ RE("(load-file \"" + argv[0] + "\")")
+ return
+ }
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def isPair(a MalVal) bool {
+ return a is MalSequential && !(a as MalSequential).isEmpty
+}
+
+def quasiquote(ast MalVal) MalVal {
+ if !isPair(ast) {
+ return MalList.new([MalSymbol.new("quote"), ast])
+ }
+ const astSeq = ast as MalSequential
+ const a0 = astSeq[0]
+ if a0.isSymbol("unquote") {
+ return astSeq[1]
+ }
+ if isPair(a0) {
+ const a0Seq = a0 as MalSequential
+ if a0Seq[0].isSymbol("splice-unquote") {
+ return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
+ }
+ }
+ return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
+}
+
+def isMacro(ast MalVal, env Env) bool {
+ if !(ast is MalList) { return false }
+ const astList = ast as MalList
+ if astList.isEmpty { return false }
+ const a0 = astList[0]
+ if !(a0 is MalSymbol) { return false }
+ const a0Sym = a0 as MalSymbol
+ if env.find(a0Sym) == null { return false }
+ const f = env.get(a0Sym)
+ if !(f is MalFunc) { return false }
+ return (f as MalFunc).isMacro
+}
+
+def macroexpand(ast MalVal, env Env) MalVal {
+ while isMacro(ast, env) {
+ const astList = ast as MalList
+ const mac = env.get(astList[0] as MalSymbol) as MalFunc
+ ast = mac.call((astList.rest as MalSequential).val)
+ }
+ return ast
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ ast = macroexpand(ast, env)
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "quote" {
+ return astList[1]
+ } else if a0sym.val == "quasiquote" {
+ ast = quasiquote(astList[1])
+ continue # TCO
+ } else if a0sym.val == "defmacro!" {
+ var macro = EVAL(astList[2], env) as MalFunc
+ macro.setAsMacro
+ return env.set(astList[1] as MalSymbol, macro)
+ } else if a0sym.val == "macroexpand" {
+ return macroexpand(astList[1], env)
+ } else if a0sym.val == "try*" {
+ var exc MalVal
+ try {
+ return EVAL(astList[1], env)
+ }
+ catch e MalUserError { exc = e.data }
+ catch e MalError { exc = MalString.new(e.message) }
+ catch e Error { exc = MalString.new(e.message) }
+ const catchClause = astList[2] as MalList
+ var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc])
+ return EVAL(catchClause[2], catchEnv)
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+ repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
+
+ # core.mal: defined using the language itself
+ RE("(def! not (fn* (a) (if a false true)))")
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+ RE("(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)))))))")
+ RE("(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 argv.count > 0 {
+ RE("(load-file \"" + argv[0] + "\")")
+ return
+ }
+
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalUserError {
+ printLn("Error: \(e.data.print(false))")
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+def READ(str string) MalVal {
+ return read_str(str)
+}
+
+def isPair(a MalVal) bool {
+ return a is MalSequential && !(a as MalSequential).isEmpty
+}
+
+def quasiquote(ast MalVal) MalVal {
+ if !isPair(ast) {
+ return MalList.new([MalSymbol.new("quote"), ast])
+ }
+ const astSeq = ast as MalSequential
+ const a0 = astSeq[0]
+ if a0.isSymbol("unquote") {
+ return astSeq[1]
+ }
+ if isPair(a0) {
+ const a0Seq = a0 as MalSequential
+ if a0Seq[0].isSymbol("splice-unquote") {
+ return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
+ }
+ }
+ return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
+}
+
+def isMacro(ast MalVal, env Env) bool {
+ if !(ast is MalList) { return false }
+ const astList = ast as MalList
+ if astList.isEmpty { return false }
+ const a0 = astList[0]
+ if !(a0 is MalSymbol) { return false }
+ const a0Sym = a0 as MalSymbol
+ if env.find(a0Sym) == null { return false }
+ const f = env.get(a0Sym)
+ if !(f is MalFunc) { return false }
+ return (f as MalFunc).isMacro
+}
+
+def macroexpand(ast MalVal, env Env) MalVal {
+ while isMacro(ast, env) {
+ const astList = ast as MalList
+ const mac = env.get(astList[0] as MalSymbol) as MalFunc
+ ast = mac.call((astList.rest as MalSequential).val)
+ }
+ return ast
+}
+
+def eval_ast(ast MalVal, env Env) MalVal {
+ if ast is MalSymbol {
+ return env.get(ast as MalSymbol)
+ } else if ast is MalList {
+ return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalVector {
+ return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
+ } else if ast is MalHashMap {
+ var result List<MalVal> = []
+ (ast as MalHashMap).val.each((k string, v MalVal) => {
+ result.append(EVAL(MalVal.fromHashKey(k), env))
+ result.append(EVAL(v, env))
+ })
+ return MalHashMap.fromList(result)
+ } else {
+ return ast
+ }
+}
+
+def EVAL(ast MalVal, env Env) MalVal {
+ while true {
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ ast = macroexpand(ast, env)
+ if !(ast is MalList) { return eval_ast(ast, env) }
+ const astList = ast as MalList
+ if astList.isEmpty { return ast }
+ const a0sym = astList[0] as MalSymbol
+ if a0sym.val == "def!" {
+ return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
+ } else if a0sym.val == "let*" {
+ var letenv = Env.new(env)
+ const assigns = astList[1] as MalSequential
+ for i = 0; i < assigns.count; i += 2 {
+ letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
+ }
+ ast = astList[2]
+ env = letenv
+ continue # TCO
+ } else if a0sym.val == "quote" {
+ return astList[1]
+ } else if a0sym.val == "quasiquote" {
+ ast = quasiquote(astList[1])
+ continue # TCO
+ } else if a0sym.val == "defmacro!" {
+ var macro = EVAL(astList[2], env) as MalFunc
+ macro.setAsMacro
+ return env.set(astList[1] as MalSymbol, macro)
+ } else if a0sym.val == "macroexpand" {
+ return macroexpand(astList[1], env)
+ } else if a0sym.val == "try*" {
+ var exc MalVal
+ try {
+ return EVAL(astList[1], env)
+ }
+ catch e MalUserError { exc = e.data }
+ catch e MalError { exc = MalString.new(e.message) }
+ catch e Error { exc = MalString.new(e.message) }
+ const catchClause = astList[2] as MalList
+ var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc])
+ return EVAL(catchClause[2], catchEnv)
+ } else if a0sym.val == "do" {
+ const parts = astList.val.slice(1)
+ eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
+ ast = parts[parts.count - 1]
+ continue # TCO
+ } else if a0sym.val == "if" {
+ const condRes = EVAL(astList[1], env)
+ if condRes is MalNil || condRes is MalFalse {
+ ast = astList.count > 3 ? astList[3] : gNil
+ } else {
+ ast = astList[2]
+ }
+ continue # TCO
+ } else if a0sym.val == "fn*" {
+ const argsNames = astList[1] as MalSequential
+ return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
+ } else {
+ const evaledList = eval_ast(ast, env) as MalList
+ const fn = evaledList[0]
+ const callArgs = evaledList.val.slice(1)
+ if fn is MalNativeFunc {
+ return (fn as MalNativeFunc).call(callArgs)
+ } else if fn is MalFunc {
+ const f = fn as MalFunc
+ ast = f.ast
+ env = Env.new(f.env, f.params.val, callArgs)
+ continue # TCO
+ } else {
+ throw MalError.new("Expected function as head of list")
+ }
+ }
+ }
+}
+
+def PRINT(exp MalVal) string {
+ return exp?.print(true)
+}
+
+var repl_env = Env.new(null)
+
+def RE(str string) MalVal {
+ return EVAL(READ(str), repl_env)
+}
+
+def REP(str string) string {
+ return PRINT(RE(str))
+}
+
+@entry
+def main {
+ # core.sk: defined using Skew
+ ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
+ repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
+
+ # core.mal: defined using the language itself
+ RE("(def! *host-language* \"skew\")")
+ RE("(def! not (fn* (a) (if a false true)))")
+ RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
+ RE("(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)))))))")
+ RE("(def! *gensym-counter* (atom 0))")
+ RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))")
+ RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))")
+
+ if argv.count > 0 {
+ RE("(load-file \"" + argv[0] + "\")")
+ return
+ }
+
+ RE("(println (str \"Mal [\" *host-language* \"]\"))")
+ var line string
+ while (line = readLine("user> ")) != null {
+ if line == "" { continue }
+ try {
+ printLn(REP(line))
+ }
+ catch e MalUserError {
+ printLn("Error: \(e.data.print(false))")
+ }
+ catch e MalError {
+ printLn("Error: \(e.message)")
+ }
+ catch e Error {
+ printLn("Error: \(e.message)")
+ }
+ }
+}
--- /dev/null
+;; Test recursive non-tail call function
+
+(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1))))))
+
+(sum-to 10)
+;=>55
+
+;;; no try* yet, so test completion of side-effects
+(def! res1 nil)
+;=>nil
+;;; For implementations without their own TCO this should fail and
+;;; leave res1 unchanged
+(def! res1 (sum-to 10000))
+res1
+;=>nil
--- /dev/null
+class MalError {
+ const message string
+}
+
+class MalUserError {
+ const data MalVal
+}
+
+class MalVal {
+ var _meta MalVal = gNil
+ def toHashKey string { throw MalError.new("Not allowed as hash map key") }
+ def print(readable bool) string
+ def equal(o MalVal) bool
+ def isSymbol(name string) bool { return false }
+ def seq MalVal { throw MalError.new("seq: called on non-sequence") }
+ def meta MalVal { return _meta }
+ def _setMeta(newMeta MalVal) { _meta = newMeta }
+ def withMeta(newMeta MalVal) MalVal {
+ var res = self.clone
+ res._setMeta(newMeta)
+ return res
+ }
+ def clone MalVal
+}
+
+namespace MalVal {
+ def fromHashKey(key string) MalVal {
+ if key.startsWith("S_") { return MalString.new(key.slice(2)) }
+ else if key.startsWith("K_") { return MalKeyword.new(key.slice(2)) }
+ else { throw "Illegal hash key string" }
+ }
+ def fromBool(b bool) MalVal { return b ? gTrue : gFalse }
+}
+
+class MalNil : MalVal {
+ over print(readable bool) string { return "nil" }
+ over equal(o MalVal) bool { return o is MalNil }
+ over seq MalVal { return gNil }
+ over clone MalVal { return self }
+}
+const gNil = MalNil.new
+
+class MalTrue : MalVal {
+ over print(readable bool) string { return "true" }
+ over equal(o MalVal) bool { return o is MalTrue }
+ over clone MalVal { return self }
+}
+const gTrue = MalTrue.new
+
+class MalFalse : MalVal {
+ over print(readable bool) string { return "false" }
+ over equal(o MalVal) bool { return o is MalFalse }
+ over clone MalVal { return self }
+}
+const gFalse = MalFalse.new
+
+class MalNumber : MalVal {
+ const _data int
+ over print(readable bool) string { return _data.toString }
+ def val int { return _data }
+ over equal(o MalVal) bool { return o is MalNumber && (o as MalNumber).val == val }
+ over clone MalVal { return self }
+}
+
+class MalSymbol : MalVal {
+ const _data string
+ over print(readable bool) string { return _data }
+ def val string { return _data }
+ over equal(o MalVal) bool { return o is MalSymbol && (o as MalSymbol).val == val }
+ over isSymbol(name string) bool { return _data == name }
+ over clone MalVal { return MalSymbol.new(_data) }
+}
+
+class MalString : MalVal {
+ const _data string
+ over print(readable bool) string { return readable ? "\"\(escaped_data)\"" : _data }
+ over toHashKey string { return "S_\(_data)" }
+ def val string { return _data }
+ over equal(o MalVal) bool { return o is MalString && (o as MalString).val == val }
+ def escaped_data string {
+ return _data.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n")
+ }
+ over seq MalVal { return _data.count == 0 ? gNil : MalList.new(_data.split("").map<MalVal>(e => MalString.new(e))) }
+ over clone MalVal { return MalString.new(_data) }
+}
+
+class MalKeyword : MalVal {
+ const _data string
+ over print(readable bool) string { return ":\(_data)" }
+ over toHashKey string { return "K_\(_data)" }
+ def val string { return _data }
+ over equal(o MalVal) bool { return o is MalKeyword && (o as MalKeyword).val == val }
+ over clone MalVal { return MalKeyword.new(_data) }
+}
+
+class MalSequential : MalVal {
+ const _data List<MalVal>
+ def val List<MalVal> { return _data }
+ def isEmpty bool { return _data.isEmpty }
+ def asOneString(readable bool) string {
+ return " ".join(_data.map<string>(v => v.print(readable)))
+ }
+ def count int { return _data.count }
+ def [](index int) MalVal { return _data[index] }
+ over equal(o MalVal) bool {
+ if !(o is MalSequential) { return false }
+ const oval = (o as MalSequential).val
+ if val.count != oval.count { return false }
+ for i in 0..val.count {
+ if !val[i].equal(oval[i]) { return false }
+ }
+ return true
+ }
+ def nth(position int) MalVal {
+ if position >= count { throw MalError.new("nth: index out of range") }
+ return val[position]
+ }
+ def first MalVal {
+ if isEmpty { return gNil }
+ return val[0]
+ }
+ def rest MalVal {
+ if isEmpty { return MalList.new([]) }
+ return MalList.new(val.slice(1))
+ }
+ def conj(args List<MalVal>) MalVal
+}
+
+class MalList : MalSequential {
+ over print(readable bool) string { return "(" + asOneString(readable) + ")" }
+ over seq MalVal { return isEmpty ? gNil : self }
+ over conj(args List<MalVal>) MalVal {
+ var res = args.clone
+ res.reverse
+ res.append(_data)
+ return MalList.new(res)
+ }
+ over clone MalVal { return MalList.new(_data) }
+}
+
+class MalVector : MalSequential {
+ over print(readable bool) string { return "[" + asOneString(readable) + "]" }
+ over seq MalVal { return isEmpty ? gNil : MalList.new(_data) }
+ over conj(args List<MalVal>) MalVal {
+ var res = _data.clone
+ res.append(args)
+ return MalVector.new(res)
+ }
+ over clone MalVal { return MalVector.new(_data) }
+}
+
+class MalHashMap : MalVal {
+ const _data StringMap<MalVal>
+ over print(readable bool) string {
+ var pairs List<string> = []
+ _data.each((k string, v MalVal) => pairs.append("\(MalVal.fromHashKey(k).print(readable)) \(v.print(readable))"))
+ return "{" + " ".join(pairs) + "}"
+ }
+ def val StringMap<MalVal> { return _data }
+ over equal(o MalVal) bool {
+ if !(o is MalHashMap) { return false }
+ const oh = o as MalHashMap
+ if oh.val.count != val.count { return false }
+ var allEqual = true
+ _data.each((k string, v MalVal) => {
+ if !(k in oh.val) || !(v.equal(oh.val[k])) {
+ allEqual = false
+ }
+ })
+ return allEqual
+ }
+ def assoc(kv_list List<MalVal>) MalVal {
+ var new_data = _data.clone
+ for i = 0; i < kv_list.count; i += 2 {
+ new_data[kv_list[i].toHashKey] = kv_list[i + 1]
+ }
+ return MalHashMap.new(new_data)
+ }
+ def dissoc(keys List<MalVal>) MalVal {
+ var new_data = _data.clone
+ for key in keys {
+ new_data.remove(key.toHashKey)
+ }
+ return MalHashMap.new(new_data)
+ }
+ def get(key MalVal) MalVal { return _data.get(key.toHashKey, gNil) }
+ def contains(key MalVal) bool { return key.toHashKey in _data }
+ def keys List<MalVal> {
+ return _data.keys.map<MalVal>(k => MalVal.fromHashKey(k))
+ }
+ def vals List<MalVal> { return _data.values }
+ over clone MalVal { return MalHashMap.new(_data) }
+}
+
+namespace MalHashMap {
+ def fromList(kv_list List<MalVal>) MalHashMap {
+ var result StringMap<MalVal> = {}
+ for i = 0; i < kv_list.count; i += 2 {
+ result[kv_list[i].toHashKey] = kv_list[i + 1]
+ }
+ return MalHashMap.new(result)
+ }
+}
+
+class MalCallable : MalVal {
+ const func fn(List<MalVal>) MalVal
+ def call(args List<MalVal>) MalVal {
+ return func(args)
+ }
+}
+
+class MalNativeFunc : MalCallable {
+ over print(readable bool) string { return "#<NativeFunction>" }
+ over equal(o MalVal) bool { return false }
+ over clone MalVal { return MalNativeFunc.new(func) }
+}
+
+class MalFunc : MalCallable {
+ const ast MalVal
+ const params MalSequential
+ const env Env
+ var _macro bool = false
+ def new(aAst MalVal, aParams MalSequential, aEnv Env, aFunc fn(List<MalVal>) MalVal) {
+ super(aFunc)
+ ast = aAst
+ params = aParams
+ env = aEnv
+ }
+ def isMacro bool { return _macro }
+ def setAsMacro { _macro = true }
+ over print(readable bool) string { return "#<Function args=" + params.print(true) + ">" }
+ over equal(o MalVal) bool { return false }
+ over clone MalVal {
+ var f = MalFunc.new(ast, params, env, func)
+ if isMacro { f.setAsMacro }
+ return f
+ }
+}
+
+class MalAtom : MalVal {
+ var _data MalVal
+ over print(readable bool) string { return "(atom \(_data.print(readable)))" }
+ def val MalVal { return _data }
+ over equal(o MalVal) bool { return o is MalAtom && val.equal((o as MalAtom).val) }
+ def resetBang(newData MalVal) MalVal {
+ _data = newData
+ return _data
+ }
+ over clone MalVal { return MalAtom.new(_data) }
+}
--- /dev/null
+def argv List<string> {
+ return process.argv.slice(2)
+}
+
+def timeMs int {
+ return Date.new.getTime()
+}
+
+var fs = require("fs")
+
+def readFile(filename string) string {
+ return fs.readFileSync(filename, "utf-8")
+}
+
+def writeString(s string) {
+ fs.writeSync(1, s)
+}
+
+def printLn(s string) {
+ writeString(s)
+ writeString("\n")
+}
+
+def readLine(prompt string) string {
+ writeString(prompt)
+ var buffer = Buffer.new(1024) # in newer Node this should be Buffer.alloc
+ var stdin = fs.openSync("/dev/stdin", "rs")
+ var bytesread int
+ var anycharseen = false
+ var total = 0
+ while (bytesread = fs.readSync(stdin, buffer, total, 1)) > 0 {
+ anycharseen = true
+ var lastchar = buffer.slice(total, total + bytesread).toString()
+ if lastchar == "\n" {
+ break
+ }
+ total += bytesread
+ }
+ fs.closeSync(stdin)
+ return anycharseen ? buffer.slice(0, total).toString() : null
+}
+
+def stringToInt(str string) int {
+ return parseInt(str)
+}
+
+@import {
+ const process dynamic
+ const Buffer dynamic
+ const Date dynamic
+ const Error dynamic
+
+ def parseInt(str string) int
+ def require(name string) dynamic
+}