es6/mal.js
es6/.esm-cache
factor/mal.factor
+fantom/lib
forth/mal.fs
fsharp/*.exe
fsharp/*.dll
#
IMPLS = ada awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \
- elisp elixir elm erlang es6 factor forth fsharp go groovy gst guile haskell \
+ elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gst guile haskell \
haxe hy io java js julia kotlin livescript logo lua make mal matlab miniMAL \
nasm nim objc objpascal ocaml perl perl6 php pil plpgsql plsql powershell ps \
python r racket rexx rpython ruby rust scala scheme skew swift swift3 tcl \
erlang_STEP_TO_PROG = erlang/$($(1))
es6_STEP_TO_PROG = es6/$($(1)).mjs
factor_STEP_TO_PROG = factor/$($(1))/$($(1)).factor
+fantom_STEP_TO_PROG = fantom/lib/fan/$($(1)).pod
forth_STEP_TO_PROG = forth/$($(1)).fs
fsharp_STEP_TO_PROG = fsharp/$($(1)).exe
go_STEP_TO_PROG = go/$($(1))
Mal is a Clojure inspired Lisp interpreter.
-Mal is implemented in 72 languages:
+Mal is implemented in 73 languages:
* Ada
* GNU awk
* ES6 (ECMAScript 6 / ECMAScript 2015)
* F#
* Factor
+* Fantom
* Forth
* Go
* Groovy
FACTOR_ROOTS=. factor -run=stepX_YYY
```
+### Fantom
+
+*The Fantom implementation was created by [Dov Murik](https://github.com/dubek)*
+
+The Fantom implementation of mal has been tested with Fantom 1.0.70.
+
+```
+cd fantom
+make lib/fan/stepX_YYY.pod
+STEP=stepX_YYY ./run
+```
+
### Forth
*The Forth implementation was created by [Chris Houser (chouser)](https://github.com/chouser)*
--- /dev/null
+FROM ubuntu:bionic
+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
+##########################################################
+
+# Java and Unzip
+RUN apt-get -y install openjdk-8-jdk unzip
+
+# Fantom and JLine
+RUN cd /tmp && curl -sfLO https://bitbucket.org/fantom/fan-1.0/downloads/fantom-1.0.70.zip \
+ && unzip -q fantom-1.0.70.zip \
+ && rm fantom-1.0.70.zip \
+ && mv fantom-1.0.70 /opt/fantom \
+ && cd /opt/fantom \
+ && bash adm/unixsetup \
+ && curl -sfL -o /opt/fantom/lib/java/jline.jar https://repo1.maven.org/maven2/jline/jline/2.14.6/jline-2.14.6.jar
+
+ENV PATH /opt/fantom/bin:$PATH
+ENV HOME /mal
--- /dev/null
+SOURCES_BASE = src/mallib/fan/interop.fan src/mallib/fan/reader.fan src/mallib/fan/types.fan
+SOURCES_LISP = src/mallib/fan/env.fan src/mallib/fan/core.fan src/stepA_mal/fan/main.fan
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+all: dist
+
+dist: lib/fan/mal.pod
+
+lib/fan:
+ mkdir -p $@
+
+lib/fan/mal.pod: lib/fan/stepA_mal.pod
+ cp -a $< $@
+
+lib/fan/step%.pod: src/step%/build.fan src/step%/fan/*.fan lib/fan/mallib.pod
+ FAN_ENV=util::PathEnv FAN_ENV_PATH=. fan $<
+
+lib/fan/mallib.pod: src/mallib/build.fan src/mallib/fan/*.fan lib/fan
+ FAN_ENV=util::PathEnv FAN_ENV_PATH=. fan $<
+
+clean:
+ rm -rf lib
+
+.PHONY: stats
+
+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]"
--- /dev/null
+#!/bin/bash
+export FAN_ENV=util::PathEnv
+export FAN_ENV_PATH="$(dirname $0)"
+exec fan ${STEP:-stepA_mal} "$@"
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "mallib"
+ summary = "mal library pod"
+ depends = ["sys 1.0", "compiler 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+class Core
+{
+ static private MalVal prn(MalVal[] a)
+ {
+ echo(a.join(" ") { it.toString(true) })
+ return MalNil.INSTANCE
+ }
+
+ static private MalVal println(MalVal[] a)
+ {
+ echo(a.join(" ") { it.toString(false) })
+ return MalNil.INSTANCE
+ }
+
+ static private MalVal readline(MalVal[] a)
+ {
+ line := Env.cur.prompt((a[0] as MalString).value)
+ return line == null ? MalNil.INSTANCE : MalString.make(line)
+ }
+
+ static private MalVal concat(MalVal[] a)
+ {
+ return MalList(a.reduce(MalVal[,]) |MalVal[] r, MalSeq v -> MalVal[]| { return r.addAll(v.value) })
+ }
+
+ static private MalVal apply(MalVal[] a)
+ {
+ f := a[0] as MalFunc
+ args := a[1..-2]
+ args.addAll(((MalSeq)a[-1]).value)
+ return f.call(args)
+ }
+
+ static private MalVal swap_bang(MalVal[] a)
+ {
+ atom := a[0] as MalAtom
+ MalVal[] args := [atom.value]
+ args.addAll(a[2..-1])
+ f := a[1] as MalFunc
+ return atom.set(f.call(args))
+ }
+
+ static Str:MalFunc ns()
+ {
+ return [
+ "=": MalFunc { MalTypes.toMalBool(it[0] == it[1]) },
+ "throw": MalFunc { throw MalException(it[0]) },
+
+ "nil?": MalFunc { MalTypes.toMalBool(it[0] is MalNil) },
+ "true?": MalFunc { MalTypes.toMalBool(it[0] is MalTrue) },
+ "false?": MalFunc { MalTypes.toMalBool(it[0] is MalFalse) },
+ "string?": MalFunc { MalTypes.toMalBool(it[0] is MalString && !((MalString)it[0]).isKeyword) },
+ "symbol": MalFunc { MalSymbol.makeFromVal(it[0]) },
+ "symbol?": MalFunc { MalTypes.toMalBool(it[0] is MalSymbol) },
+ "keyword": MalFunc { MalString.makeKeyword((it[0] as MalString).value) },
+ "keyword?": MalFunc { MalTypes.toMalBool(it[0] is MalString && ((MalString)it[0]).isKeyword) },
+ "number?": MalFunc { MalTypes.toMalBool(it[0] is MalInteger) },
+ "fn?": MalFunc { MalTypes.toMalBool(it[0] is MalFunc && !((it[0] as MalUserFunc)?->isMacro ?: false)) },
+ "macro?": MalFunc { MalTypes.toMalBool(it[0] is MalUserFunc && ((MalUserFunc)it[0]).isMacro) },
+
+ "pr-str": MalFunc { MalString.make(it.join(" ") |MalVal e -> Str| { e.toString(true) }) },
+ "str": MalFunc { MalString.make(it.join("") |MalVal e -> Str| { e.toString(false) }) },
+ "prn": MalFunc(#prn.func),
+ "println": MalFunc(#println.func),
+ "read-string": MalFunc { Reader.read_str((it[0] as MalString).value) },
+ "readline": MalFunc(#readline.func),
+ "slurp": MalFunc { MalString.make(File((it[0] as MalString).value.toUri).readAllStr) },
+
+ "<": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value < (it[1] as MalInteger).value) },
+ "<=": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value <= (it[1] as MalInteger).value) },
+ ">": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value > (it[1] as MalInteger).value) },
+ ">=": MalFunc { MalTypes.toMalBool((it[0] as MalInteger).value >= (it[1] as MalInteger).value) },
+ "+": MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) },
+ "-": MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) },
+ "*": MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) },
+ "/": MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) },
+ "time-ms": MalFunc { MalInteger(DateTime.nowTicks / 1000000) },
+
+ "list": MalFunc { MalList(it) },
+ "list?": MalFunc { MalTypes.toMalBool(it[0] is MalList) },
+ "vector": MalFunc { MalVector(it) },
+ "vector?": MalFunc { MalTypes.toMalBool(it[0] is MalVector) },
+ "hash-map": MalFunc { MalHashMap.fromList(it) },
+ "map?": MalFunc { MalTypes.toMalBool(it[0] is MalHashMap) },
+ "assoc": MalFunc { (it[0] as MalHashMap).assoc(it[1..-1]) },
+ "dissoc": MalFunc { (it[0] as MalHashMap).dissoc(it[1..-1]) },
+ "get": MalFunc { it[0] is MalNil ? MalNil.INSTANCE : (it[0] as MalHashMap).get2((MalString)it[1], MalNil.INSTANCE) },
+ "contains?": MalFunc { MalTypes.toMalBool((it[0] as MalHashMap).containsKey((MalString)it[1])) },
+ "keys": MalFunc { MalList((it[0] as MalHashMap).keys) },
+ "vals": MalFunc { MalList((it[0] as MalHashMap).vals) },
+
+ "sequential?": MalFunc { MalTypes.toMalBool(it[0] is MalSeq) },
+ "cons": MalFunc { MalList([it[0]].addAll((it[1] as MalSeq).value)) },
+ "concat": MalFunc(#concat.func),
+ "nth": MalFunc { (it[0] as MalSeq).nth((it[1] as MalInteger).value) },
+ "first": MalFunc { (it[0] as MalSeq)?.first ?: MalNil.INSTANCE },
+ "rest": MalFunc { (it[0] as MalSeq)?.rest ?: MalList([,]) },
+ "empty?": MalFunc { MalTypes.toMalBool((it[0] as MalSeq).isEmpty) },
+ "count": MalFunc { MalInteger(it[0].count) },
+ "apply": MalFunc(#apply.func),
+ "map": MalFunc { (it[1] as MalSeq).map(it[0]) },
+
+ "conj": MalFunc { (it[0] as MalSeq).conj(it[1..-1]) },
+ "seq": MalFunc { it[0].seq },
+
+ "meta": MalFunc { it[0].meta() },
+ "with-meta": MalFunc { it[0].with_meta(it[1]) },
+ "atom": MalFunc { MalAtom(it[0]) },
+ "atom?": MalFunc { MalTypes.toMalBool(it[0] is MalAtom) },
+ "deref": MalFunc { (it[0] as MalAtom).value },
+ "reset!": MalFunc { (it[0] as MalAtom).set(it[1]) },
+ "swap!": MalFunc(#swap_bang.func),
+
+ "fantom-eval": MalFunc { Interop.fantomEvaluate((it[0] as MalString).value) }
+ ]
+ }
+}
--- /dev/null
+class MalEnv
+{
+ private Str:MalVal data := [:]
+ private MalEnv? outer
+
+ new make(MalEnv? outer := null, MalSeq? binds := null, MalSeq? exprs := null)
+ {
+ this.outer = outer
+ if (binds != null && exprs != null)
+ {
+ for (i := 0; i < binds.count; i++)
+ {
+ if ((binds[i] as MalSymbol).value == "&")
+ {
+ set(binds[i + 1], MalList(exprs[i..-1]))
+ break
+ }
+ else
+ set(binds[i], exprs[i])
+ }
+ }
+ }
+
+ MalVal set(MalSymbol key, MalVal value)
+ {
+ data[key.value] = value
+ return value
+ }
+
+ MalEnv? find(MalSymbol key)
+ {
+ return data.containsKey(key.value) ? this : outer?.find(key)
+ }
+
+ MalVal get(MalSymbol key)
+ {
+ foundEnv := find(key) ?: throw Err("'$key.value' not found")
+ return (MalVal)foundEnv.data[key.value]
+ }
+}
--- /dev/null
+using compiler
+
+internal class Interop
+{
+ static Pod? compile(Str innerBody)
+ {
+ ci := CompilerInput
+ {
+ podName = "mal_fantom_interop_${DateTime.nowUnique}"
+ summary = ""
+ isScript = true
+ version = Version.defVal
+ log.level = LogLevel.silent
+ output = CompilerOutputMode.transientPod
+ mode = CompilerInputMode.str
+ srcStr = "class InteropDummyClass {\nstatic Obj? _evalfunc() {\n $innerBody \n}\n}"
+ srcStrLoc = Loc("mal_fantom_interop")
+ }
+ try
+ return Compiler(ci).compile.transientPod
+ catch (CompilerErr e)
+ return null
+ }
+
+ static Obj? evaluate(Str line)
+ {
+ p := compile(line)
+ if (p == null)
+ p = compile("return $line")
+ if (p == null)
+ p = compile("$line\nreturn null")
+ if (p == null)
+ return null
+ method := p.types.first.method("_evalfunc")
+ try
+ return method.call()
+ catch (Err e)
+ return null
+ }
+
+ static MalVal fantomToMal(Obj? obj)
+ {
+ if (obj == null)
+ return MalNil.INSTANCE
+ else if (obj is Bool)
+ return MalTypes.toMalBool((Bool)obj)
+ else if (obj is Int)
+ return MalInteger((Int)obj)
+ else if (obj is List)
+ return MalList((obj as List).map { fantomToMal(it) })
+ else if (obj is Map)
+ return MalHashMap.fromMap((obj as Map).map { fantomToMal(it) })
+ else
+ return MalString.make(obj.toStr)
+ }
+
+ static MalVal fantomEvaluate(Str line)
+ {
+ return fantomToMal(evaluate(line))
+ }
+}
--- /dev/null
+internal class TokenReader
+{
+ const Str[] tokens
+ private Int position := 0
+
+ new make(Str[] new_tokens) { tokens = new_tokens }
+
+ Str? peek()
+ {
+ if (position >= tokens.size) return null
+ return tokens[position]
+ }
+
+ Str next() { return tokens[position++] }
+}
+
+class Reader
+{
+ private static Str[] tokenize(Str s)
+ {
+ r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)|>
+ m := r.matcher(s)
+ tokens := Str[,]
+ while (m.find())
+ {
+ token := m.group(1)
+ if (token.isEmpty || token[0] == ';') continue
+ tokens.add(m.group(1))
+ }
+ return tokens
+ }
+
+ private static Str unescape_str(Str s)
+ {
+ return s.replace("\\\\", "\u029e").replace("\\\"", "\"").replace("\\n", "\n").replace("\u029e", "\\")
+ }
+
+ private static MalVal read_atom(TokenReader reader)
+ {
+ token := reader.next
+ intRegex := Regex <|^-?\d+$|>
+ if (token == "nil") return MalNil.INSTANCE
+ if (token == "true") return MalTrue.INSTANCE
+ if (token == "false") return MalFalse.INSTANCE
+ if (intRegex.matches(token)) return MalInteger(token.toInt)
+ if (token[0] == '"') return MalString.make(unescape_str(token[1..-2]))
+ if (token[0] == ':') return MalString.makeKeyword(token[1..-1])
+ return MalSymbol(token)
+ }
+
+ private static MalVal[] read_seq(TokenReader reader, Str open, Str close)
+ {
+ reader.next
+ values := MalVal[,]
+ token := reader.peek
+ while (token != close)
+ {
+ if (token == null) throw Err("expected '$close', got EOF")
+ values.add(read_form(reader))
+ token = reader.peek
+ }
+ if (token != close) throw Err("Missing '$close'")
+ reader.next
+ return values
+ }
+
+ private static MalVal read_form(TokenReader reader)
+ {
+ switch (reader.peek)
+ {
+ case "\'":
+ reader.next
+ return MalList([MalSymbol("quote"), read_form(reader)])
+ case "`":
+ reader.next
+ return MalList([MalSymbol("quasiquote"), read_form(reader)])
+ case "~":
+ reader.next
+ return MalList([MalSymbol("unquote"), read_form(reader)])
+ case "~@":
+ reader.next
+ return MalList([MalSymbol("splice-unquote"), read_form(reader)])
+ case "^":
+ reader.next
+ meta := read_form(reader)
+ return MalList([MalSymbol("with-meta"), read_form(reader), meta])
+ case "@":
+ reader.next
+ return MalList([MalSymbol("deref"), read_form(reader)])
+ case "(": return MalList(read_seq(reader, "(", ")"))
+ case ")": throw Err("unexpected ')'")
+ case "[": return MalVector(read_seq(reader, "[", "]"))
+ case "]": throw Err("unexpected ']'")
+ case "{": return MalHashMap.fromList(read_seq(reader, "{", "}"))
+ case "}": throw Err("unexpected '}'")
+ default: return read_atom(reader)
+ }
+ }
+
+ static MalVal read_str(Str s)
+ {
+ return read_form(TokenReader(tokenize(s)));
+ }
+}
--- /dev/null
+mixin MalVal
+{
+ virtual Str toString(Bool readable) { return toStr }
+ virtual Int count() { throw Err("count not implemented") }
+ virtual MalVal seq() { throw Err("seq not implemented") }
+ abstract MalVal meta()
+ abstract MalVal with_meta(MalVal newMeta)
+}
+
+const mixin MalValNoMeta : MalVal
+{
+ override MalVal meta() { return MalNil.INSTANCE }
+ override MalVal with_meta(MalVal newMeta) { return this }
+}
+
+const mixin MalFalseyVal
+{
+}
+
+const class MalNil : MalValNoMeta, MalFalseyVal
+{
+ static const MalNil INSTANCE := MalNil()
+ override Bool equals(Obj? that) { return that is MalNil }
+ override Str toString(Bool readable) { return "nil" }
+ override Int count() { return 0 }
+ override MalVal seq() { return this }
+}
+
+const class MalTrue : MalValNoMeta
+{
+ static const MalTrue INSTANCE := MalTrue()
+ override Bool equals(Obj? that) { return that is MalTrue }
+ override Str toString(Bool readable) { return "true" }
+}
+
+const class MalFalse : MalValNoMeta, MalFalseyVal
+{
+ static const MalFalse INSTANCE := MalFalse()
+ override Bool equals(Obj? that) { return that is MalFalse }
+ override Str toString(Bool readable) { return "false" }
+}
+
+const class MalInteger : MalValNoMeta
+{
+ const Int value
+ new make(Int v) { value = v }
+ override Bool equals(Obj? that) { return that is MalInteger && (that as MalInteger).value == value }
+ override Str toString(Bool readable) { return value.toStr }
+}
+
+abstract class MalValBase : MalVal
+{
+ private MalVal? metaVal := null
+ override Str toString(Bool readable) { return toStr }
+ override Int count() { throw Err("count not implemented") }
+ override MalVal seq() { throw Err("seq not implemented") }
+ abstract This dup()
+ override MalVal meta() { return metaVal ?: MalNil.INSTANCE }
+ override MalVal with_meta(MalVal newMeta)
+ {
+ v := dup
+ v.metaVal = newMeta
+ return v
+ }
+}
+
+class MalSymbol : MalValBase
+{
+ const Str value
+ new make(Str v) { value = v }
+ new makeFromVal(MalVal v)
+ {
+ if (v is MalSymbol) return v
+ value = (v as MalString).value
+ }
+ override Bool equals(Obj? that) { return that is MalSymbol && (that as MalSymbol).value == value }
+ override Str toString(Bool readable) { return value }
+ override This dup() { return make(value) }
+}
+
+class MalString : MalValBase
+{
+ const Str value
+ new make(Str v) { value = v }
+ new makeKeyword(Str v) { value = "\u029e$v" }
+ override Bool equals(Obj? that) { return that is MalString && (that as MalString).value == value }
+ override Str toString(Bool readable)
+ {
+ if (isKeyword) return ":${value[1..-1]}"
+ if (readable)
+ return "\"${escapeStr(value)}\""
+ else
+ return value
+ }
+ Bool isKeyword() { return !value.isEmpty && value[0] == '\u029e' }
+ static Str escapeStr(Str s)
+ {
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+ }
+ override MalVal seq()
+ {
+ if (value.size == 0) return MalNil.INSTANCE
+ return MalList(value.chars.map |Int c -> MalString| { MalString.make(Str.fromChars([c])) })
+ }
+ override This dup() { return make(value) }
+}
+
+abstract class MalSeq : MalValBase
+{
+ MalVal[] value { protected set }
+ new make(MalVal[] v) { value = v.ro }
+ override Bool equals(Obj? that) { return that is MalSeq && (that as MalSeq).value == value }
+ Bool isEmpty() { return value.isEmpty }
+ override Int count() { return value.size }
+ @Operator MalVal get(Int index) { return value[index] }
+ @Operator MalVal[] getRange(Range range) { return value[range] }
+ protected Str serialize(Bool readable) { return value.join(" ") { it.toString(readable) } }
+ abstract MalSeq drop(Int n)
+ MalVal nth(Int index) { return index < count ? get(index) : throw Err("nth: index out of range") }
+ MalVal first() { return isEmpty ? MalNil.INSTANCE : value[0] }
+ MalList rest() { return MalList(isEmpty ? [,] : value[1..-1]) }
+ MalList map(MalFunc f) { return MalList(value.map { f.call([it]) } ) }
+ abstract MalSeq conj(MalVal[] args)
+}
+
+class MalList : MalSeq
+{
+ new make(MalVal[] v) : super.make(v) {}
+ override Str toString(Bool readable) { return "(${serialize(readable)})" }
+ override MalList drop(Int n) { return make(value[n..-1]) }
+ override MalVal seq() { return isEmpty ? MalNil.INSTANCE : this }
+ override MalList conj(MalVal[] args) { return MalList(value.rw.insertAll(0, args.reverse)) }
+ override This dup() { return make(value) }
+}
+
+class MalVector : MalSeq
+{
+ new make(MalVal[] v) : super.make(v) {}
+ override Str toString(Bool readable) { return "[${serialize(readable)}]" }
+ override MalVector drop(Int n) { return make(value[n..-1]) }
+ override MalVal seq() { return isEmpty ? MalNil.INSTANCE : MalList(value) }
+ override MalVector conj(MalVal[] args) { return MalVector(value.rw.addAll(args)) }
+ override This dup() { return make(value) }
+}
+
+class MalHashMap : MalValBase
+{
+ Str:MalVal value { private set }
+ new fromList(MalVal[] lst) {
+ m := [Str:MalVal][:]
+ for (i := 0; i < lst.size; i += 2)
+ m.add((lst[i] as MalString).value, (MalVal)lst[i + 1])
+ value = m.ro
+ }
+ new fromMap(Str:MalVal m) { value = m.ro }
+ override Bool equals(Obj? that) { return that is MalHashMap && (that as MalHashMap).value == value }
+ override Str toString(Bool readable)
+ {
+ elements := Str[,]
+ value.each(|MalVal v, Str k| { elements.add(MalString.make(k).toString(readable)); elements.add(v.toString(readable)) })
+ s := elements.join(" ")
+ return "{$s}"
+ }
+ override Int count() { return value.size }
+ @Operator MalVal get(Str key) { return value[key] }
+ MalVal get2(MalString key, MalVal? def := null) { return value.get(key.value, def) }
+ Bool containsKey(MalString key) { return value.containsKey(key.value) }
+ MalVal[] keys() { return value.keys.map { MalString.make(it) } }
+ MalVal[] vals() { return value.vals }
+ MalHashMap assoc(MalVal[] args)
+ {
+ newValue := value.dup
+ for (i := 0; i < args.size; i += 2)
+ newValue.set((args[i] as MalString).value, args[i + 1])
+ return fromMap(newValue)
+ }
+ MalHashMap dissoc(MalVal[] args)
+ {
+ newValue := value.dup
+ args.each { newValue.remove((it as MalString).value) }
+ return fromMap(newValue)
+ }
+ override This dup() { return fromMap(value) }
+}
+
+class MalFunc : MalValBase
+{
+ protected |MalVal[] a -> MalVal| f
+ new make(|MalVal[] a -> MalVal| func) { f = func }
+ MalVal call(MalVal[] a) { return f(a) }
+ override Str toString(Bool readable) { return "<Function>" }
+ override This dup() { return make(f) }
+}
+
+class MalUserFunc : MalFunc
+{
+ MalVal ast { private set }
+ private MalEnv env
+ private MalSeq params
+ Bool isMacro := false
+ new make(MalVal ast, MalEnv env, MalSeq params, |MalVal[] a -> MalVal| func, Bool isMacro := false) : super.make(func)
+ {
+ this.ast = ast
+ this.env = env
+ this.params = params
+ this.isMacro = isMacro
+ }
+ MalEnv genEnv(MalSeq args) { return MalEnv(env, params, args) }
+ override Str toString(Bool readable) { return "<Function:args=${params.toString(readable)}, isMacro=${isMacro}>" }
+ override This dup() { return make(ast, env, params, f, isMacro) }
+}
+
+class MalAtom : MalValBase
+{
+ MalVal value
+ new make(MalVal v) { value = v }
+ override Str toString(Bool readable) { return "(atom ${value.toString(readable)})" }
+ override Bool equals(Obj? that) { return that is MalAtom && (that as MalAtom).value == value }
+ MalVal set(MalVal v) { value = v; return value }
+ override This dup() { return make(value) }
+}
+
+class MalTypes
+{
+ static MalVal toMalBool(Bool cond) { return cond ? MalTrue.INSTANCE : MalFalse.INSTANCE }
+ static Bool isPair(MalVal a) { return a is MalSeq && !(a as MalSeq).isEmpty }
+}
+
+const class MalException : Err
+{
+ const Str serializedValue
+ new make(MalVal v) : super.make("Mal exception") { serializedValue = v.toString(true) }
+ MalVal getValue() { return Reader.read_str(serializedValue) }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step0_repl"
+ summary = "mal step0_repl pod"
+ depends = ["sys 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+class Main
+{
+ static Str READ(Str s)
+ {
+ return s
+ }
+
+ static Str EVAL(Str ast, Str env)
+ {
+ return ast
+ }
+
+ static Str PRINT(Str exp)
+ {
+ return exp
+ }
+
+ static Str REP(Str s, Str env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ echo(REP(line, ""))
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step1_read_print"
+ summary = "mal step1_read_print pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal EVAL(MalVal ast, Str env)
+ {
+ return ast
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, Str env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, ""))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step2_eval"
+ summary = "mal step2_eval pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, Str:MalFunc env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ varName := (ast as MalSymbol).value
+ varVal := env[varName] ?: throw Err("'$varName' not found")
+ return (MalVal)varVal
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, Str:MalFunc env)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ evaled_ast := eval_ast(ast, env) as MalList
+ f := evaled_ast[0] as MalFunc
+ return f.call(evaled_ast[1..-1])
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, Str:MalFunc env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ env := [
+ "+": MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) },
+ "-": MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) },
+ "*": MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) },
+ "/": MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) }
+ ]
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step3_env"
+ summary = "mal step3_env pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol).value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := (astList[1] as MalSeq)
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ return EVAL(astList[2], let_env)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ f := evaled_ast[0] as MalFunc
+ return f.call(evaled_ast[1..-1])
+ }
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ repl_env := MalEnv()
+ repl_env.set(MalSymbol("+"), MalFunc { MalInteger((it[0] as MalInteger).value + (it[1] as MalInteger).value) })
+ repl_env.set(MalSymbol("-"), MalFunc { MalInteger((it[0] as MalInteger).value - (it[1] as MalInteger).value) })
+ repl_env.set(MalSymbol("*"), MalFunc { MalInteger((it[0] as MalInteger).value * (it[1] as MalInteger).value) })
+ repl_env.set(MalSymbol("/"), MalFunc { MalInteger((it[0] as MalInteger).value / (it[1] as MalInteger).value) })
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step4_if_fn_do"
+ summary = "mal step4_if_fn_do pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ return EVAL(astList[2], let_env)
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ return EVAL(astList[-1], env)
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ return astList.count > 3 ? EVAL(astList[3], env) : MalNil.INSTANCE
+ else
+ return EVAL(astList[2], env)
+ case "fn*":
+ return MalFunc { EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(it))) }
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ f := evaled_ast[0] as MalFunc
+ return f.call(evaled_ast[1..-1])
+ }
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step5_tco"
+ summary = "mal step5_tco pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main()
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step6_file"
+ summary = "mal step6_file pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main(Str[] args)
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+ repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) })
+ repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) }))
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
+
+ if (!args.isEmpty)
+ {
+ REP("(load-file \"${args[0]}\")", repl_env)
+ return
+ }
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step7_quote"
+ summary = "mal step7_quote pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal quasiquote(MalVal ast)
+ {
+ if (!MalTypes.isPair(ast))
+ return MalList(MalVal[MalSymbol("quote"), ast])
+ astSeq := ast as MalSeq
+ if ((astSeq[0] as MalSymbol)?.value == "unquote")
+ return astSeq[1]
+ if (MalTypes.isPair(astSeq[0]))
+ {
+ ast0Seq := astSeq[0] as MalSeq
+ if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote")
+ return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))])
+ }
+ return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))])
+ }
+
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "quote":
+ return astList[1]
+ case "quasiquote":
+ ast = quasiquote(astList[1])
+ // TCO
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main(Str[] args)
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+ repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) })
+ repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) }))
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
+
+ if (!args.isEmpty)
+ {
+ REP("(load-file \"${args[0]}\")", repl_env)
+ return
+ }
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step8_macros"
+ summary = "mal step8_macros pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal quasiquote(MalVal ast)
+ {
+ if (!MalTypes.isPair(ast))
+ return MalList(MalVal[MalSymbol("quote"), ast])
+ astSeq := ast as MalSeq
+ if ((astSeq[0] as MalSymbol)?.value == "unquote")
+ return astSeq[1]
+ if (MalTypes.isPair(astSeq[0]))
+ {
+ ast0Seq := astSeq[0] as MalSeq
+ if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote")
+ return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))])
+ }
+ return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))])
+ }
+
+ static Bool isMacroCall(MalVal ast, MalEnv env)
+ {
+ if (!(ast is MalList)) return false
+ astList := ast as MalList
+ if (astList.isEmpty) return false
+ if (!(astList[0] is MalSymbol)) return false
+ ast0 := astList[0] as MalSymbol
+ f := env.find(ast0)?.get(ast0)
+ return (f as MalUserFunc)?.isMacro ?: false
+ }
+
+ static MalVal macroexpand(MalVal ast, MalEnv env)
+ {
+ while (isMacroCall(ast, env))
+ {
+ mac := env.get((ast as MalList)[0]) as MalUserFunc
+ ast = mac.call((ast as MalSeq).drop(1).value)
+ }
+ return ast
+ }
+
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ ast = macroexpand(ast, env)
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "quote":
+ return astList[1]
+ case "quasiquote":
+ ast = quasiquote(astList[1])
+ // TCO
+ case "defmacro!":
+ f := EVAL(astList[2], env) as MalUserFunc
+ f.isMacro = true
+ return env.set(astList[1], f)
+ case "macroexpand":
+ return macroexpand(astList[1], env)
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main(Str[] args)
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+ repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) })
+ repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) }))
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
+ 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)))))))", repl_env)
+ 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))))))))", repl_env)
+
+ if (!args.isEmpty)
+ {
+ REP("(load-file \"${args[0]}\")", repl_env)
+ return
+ }
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "step9_try"
+ summary = "mal step9_try pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal quasiquote(MalVal ast)
+ {
+ if (!MalTypes.isPair(ast))
+ return MalList(MalVal[MalSymbol("quote"), ast])
+ astSeq := ast as MalSeq
+ if ((astSeq[0] as MalSymbol)?.value == "unquote")
+ return astSeq[1]
+ if (MalTypes.isPair(astSeq[0]))
+ {
+ ast0Seq := astSeq[0] as MalSeq
+ if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote")
+ return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))])
+ }
+ return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))])
+ }
+
+ static Bool isMacroCall(MalVal ast, MalEnv env)
+ {
+ if (!(ast is MalList)) return false
+ astList := ast as MalList
+ if (astList.isEmpty) return false
+ if (!(astList[0] is MalSymbol)) return false
+ ast0 := astList[0] as MalSymbol
+ f := env.find(ast0)?.get(ast0)
+ return (f as MalUserFunc)?.isMacro ?: false
+ }
+
+ static MalVal macroexpand(MalVal ast, MalEnv env)
+ {
+ while (isMacroCall(ast, env))
+ {
+ mac := env.get((ast as MalList)[0]) as MalUserFunc
+ ast = mac.call((ast as MalSeq).drop(1).value)
+ }
+ return ast
+ }
+
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ ast = macroexpand(ast, env)
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "quote":
+ return astList[1]
+ case "quasiquote":
+ ast = quasiquote(astList[1])
+ // TCO
+ case "defmacro!":
+ f := EVAL(astList[2], env) as MalUserFunc
+ f.isMacro = true
+ return env.set(astList[1], f)
+ case "macroexpand":
+ return macroexpand(astList[1], env)
+ case "try*":
+ MalVal exc := MalNil.INSTANCE
+ try
+ return EVAL(astList[1], env)
+ catch (MalException e)
+ exc = e.getValue
+ catch (Err e)
+ exc = MalString.make(e.msg)
+ catchClause := astList[2] as MalList
+ return EVAL(catchClause[2], MalEnv(env, MalList([catchClause[1]]), MalList([exc])))
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main(Str[] args)
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+ repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) })
+ repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) }))
+
+ // core.mal: defined using the language itself
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
+ 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)))))))", repl_env)
+ 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))))))))", repl_env)
+
+ if (!args.isEmpty)
+ {
+ REP("(load-file \"${args[0]}\")", repl_env)
+ return
+ }
+
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (MalException e)
+ echo("Error: ${e.serializedValue}")
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /dev/null
+class Build : build::BuildPod
+{
+ new make()
+ {
+ podName = "stepA_mal"
+ summary = "mal stepA_mal pod"
+ depends = ["sys 1.0", "mallib 1.0"]
+ srcDirs = [`fan/`]
+ outPodDir = `lib/fan/`
+ }
+}
--- /dev/null
+using mallib
+
+class Main
+{
+ static MalVal quasiquote(MalVal ast)
+ {
+ if (!MalTypes.isPair(ast))
+ return MalList(MalVal[MalSymbol("quote"), ast])
+ astSeq := ast as MalSeq
+ if ((astSeq[0] as MalSymbol)?.value == "unquote")
+ return astSeq[1]
+ if (MalTypes.isPair(astSeq[0]))
+ {
+ ast0Seq := astSeq[0] as MalSeq
+ if ((ast0Seq[0] as MalSymbol)?.value == "splice-unquote")
+ return MalList(MalVal[MalSymbol("concat"), ast0Seq[1], quasiquote(astSeq.drop(1))])
+ }
+ return MalList(MalVal[MalSymbol("cons"), quasiquote(astSeq[0]), quasiquote(astSeq.drop(1))])
+ }
+
+ static Bool isMacroCall(MalVal ast, MalEnv env)
+ {
+ if (!(ast is MalList)) return false
+ astList := ast as MalList
+ if (astList.isEmpty) return false
+ if (!(astList[0] is MalSymbol)) return false
+ ast0 := astList[0] as MalSymbol
+ f := env.find(ast0)?.get(ast0)
+ return (f as MalUserFunc)?.isMacro ?: false
+ }
+
+ static MalVal macroexpand(MalVal ast, MalEnv env)
+ {
+ while (isMacroCall(ast, env))
+ {
+ mac := env.get((ast as MalList)[0]) as MalUserFunc
+ ast = mac.call((ast as MalSeq).drop(1).value)
+ }
+ return ast
+ }
+
+ static MalVal READ(Str s)
+ {
+ return Reader.read_str(s)
+ }
+
+ static MalVal eval_ast(MalVal ast, MalEnv env)
+ {
+ switch (ast.typeof)
+ {
+ case MalSymbol#:
+ return env.get(ast)
+ case MalList#:
+ newElements := (ast as MalList).value.map { EVAL(it, env) }
+ return MalList(newElements)
+ case MalVector#:
+ newElements := (ast as MalVector).value.map { EVAL(it, env) }
+ return MalVector(newElements)
+ case MalHashMap#:
+ newElements := (ast as MalHashMap).value.map |MalVal v -> MalVal| { return EVAL(v, env) }
+ return MalHashMap.fromMap(newElements)
+ default:
+ return ast
+ }
+ }
+
+ static MalVal EVAL(MalVal ast, MalEnv env)
+ {
+ while (true)
+ {
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ ast = macroexpand(ast, env)
+ if (!(ast is MalList)) return eval_ast(ast, env)
+ astList := ast as MalList
+ if (astList.isEmpty) return ast
+ switch ((astList[0] as MalSymbol)?.value)
+ {
+ case "def!":
+ return env.set(astList[1], EVAL(astList[2], env))
+ case "let*":
+ let_env := MalEnv(env)
+ varList := astList[1] as MalSeq
+ for (i := 0; i < varList.count; i += 2)
+ let_env.set(varList[i], EVAL(varList[i + 1], let_env))
+ env = let_env
+ ast = astList[2]
+ // TCO
+ case "quote":
+ return astList[1]
+ case "quasiquote":
+ ast = quasiquote(astList[1])
+ // TCO
+ case "defmacro!":
+ f := EVAL(astList[2], env) as MalUserFunc
+ f.isMacro = true
+ return env.set(astList[1], f)
+ case "macroexpand":
+ return macroexpand(astList[1], env)
+ case "try*":
+ MalVal exc := MalNil.INSTANCE
+ try
+ return EVAL(astList[1], env)
+ catch (MalException e)
+ exc = e.getValue
+ catch (Err e)
+ exc = MalString.make(e.msg)
+ catchClause := astList[2] as MalList
+ return EVAL(catchClause[2], MalEnv(env, MalList([catchClause[1]]), MalList([exc])))
+ case "do":
+ eval_ast(MalList(astList[1..-2]), env)
+ ast = astList[-1]
+ // TCO
+ case "if":
+ if (EVAL(astList[1], env) is MalFalseyVal)
+ ast = astList.count > 3 ? astList[3] : MalNil.INSTANCE
+ else
+ ast = astList[2]
+ // TCO
+ case "fn*":
+ f := |MalVal[] a -> MalVal|
+ {
+ return EVAL(astList[2], MalEnv(env, (astList[1] as MalSeq), MalList(a)))
+ }
+ return MalUserFunc(astList[2], env, (MalSeq)astList[1], f)
+ default:
+ evaled_ast := eval_ast(ast, env) as MalList
+ switch (evaled_ast[0].typeof)
+ {
+ case MalUserFunc#:
+ user_fn := evaled_ast[0] as MalUserFunc
+ ast = user_fn.ast
+ env = user_fn.genEnv(evaled_ast.drop(1))
+ // TCO
+ case MalFunc#:
+ return (evaled_ast[0] as MalFunc).call(evaled_ast[1..-1])
+ default:
+ throw Err("Unknown type")
+ }
+ }
+ }
+ return MalNil.INSTANCE // never reached
+ }
+
+ static Str PRINT(MalVal exp)
+ {
+ return exp.toString(true)
+ }
+
+ static Str REP(Str s, MalEnv env)
+ {
+ return PRINT(EVAL(READ(s), env))
+ }
+
+ static Void main(Str[] args)
+ {
+ repl_env := MalEnv()
+ // core.fan: defined using Fantom
+ Core.ns.each |MalFunc V, Str K| { repl_env.set(MalSymbol(K), V) }
+ repl_env.set(MalSymbol("eval"), MalFunc { EVAL(it[0], repl_env) })
+ repl_env.set(MalSymbol("*ARGV*"), MalList((args.isEmpty ? args : args[1..-1]).map { MalString.make(it) }))
+
+ // core.mal: defined using the language itself
+ REP("(def! *host-language* \"fantom\")", repl_env)
+ REP("(def! not (fn* (a) (if a false true)))", repl_env)
+ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
+ 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)))))))", repl_env)
+ REP("(def! *gensym-counter* (atom 0))", repl_env)
+ REP("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env)
+ REP("(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)))))))))", repl_env)
+
+ if (!args.isEmpty)
+ {
+ REP("(load-file \"${args[0]}\")", repl_env)
+ return
+ }
+
+ REP("(println (str \"Mal [\" *host-language* \"]\"))", repl_env)
+ while (true) {
+ line := Env.cur.prompt("user> ")
+ if (line == null) break
+ if (line.isSpace) continue
+ try
+ echo(REP(line, repl_env))
+ catch (MalException e)
+ echo("Error: ${e.serializedValue}")
+ catch (Err e)
+ echo("Error: $e.msg")
+ }
+ }
+}
--- /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
+;; Testing basic fantom interop
+
+(fantom-eval "7")
+;=>7
+
+(fantom-eval "return 3 * 9")
+;=>27
+
+(fantom-eval "\"7\"")
+;=>"7"
+
+(fantom-eval "\"abcd\".upper")
+;=>"ABCD"
+
+(fantom-eval "[7,8,9]")
+;=>(7 8 9)
+
+(fantom-eval "[\"abc\": 789]")
+;=>{"abc" 789}
+
+(fantom-eval "echo(\"hello\")")
+; hello
+;=>nil
+
+(fantom-eval "[\"a\",\"b\",\"c\"].join(\" \") { \"X${it}Y\" }")
+;=>"XaY XbY XcY"
+
+(fantom-eval "[1,2,3].map { 1 + it }")
+;=>(2 3 4)
+
+(fantom-eval "Env.cur.runtime")
+;=>"java"