diff --git a/kotlin/src/mal/.dir-locals.el b/kotlin/src/mal/.dir-locals.el new file mode 100644 index 00000000..2201cbcf --- /dev/null +++ b/kotlin/src/mal/.dir-locals.el @@ -0,0 +1,3 @@ +((nil . ((indent-tabs-mode . nil) + (kotlin-tab-width . 4) + ))) diff --git a/kotlin/src/mal/core.kt b/kotlin/src/mal/core.kt index bc312c8b..d6796bce 100644 --- a/kotlin/src/mal/core.kt +++ b/kotlin/src/mal/core.kt @@ -1,239 +1,330 @@ -package mal - -import java.io.File -import java.util.* - -val ns = hashMapOf( - envPair("+", { a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) }), - envPair("-", { a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) }), - envPair("*", { a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) }), - envPair("/", { a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) }), - - envPair("list", { a: ISeq -> MalList(a) }), - envPair("list?", { a: ISeq -> if (a.first() is MalList) TRUE else FALSE }), - envPair("empty?", { a: ISeq -> if (a.first() !is ISeq || !(a.first() as ISeq).seq().any()) TRUE else FALSE }), - envPair("count", { a: ISeq -> - if (a.first() is ISeq) MalInteger((a.first() as ISeq).count().toLong()) else MalInteger(0) - }), - - envPair("=", { a: ISeq -> pairwiseEquals(a) }), - envPair("<", { a: ISeq -> pairwiseCompare(a, { x, y -> x.value < y.value }) }), - envPair("<=", { a: ISeq -> pairwiseCompare(a, { x, y -> x.value <= y.value }) }), - envPair(">", { a: ISeq -> pairwiseCompare(a, { x, y -> x.value > y.value }) }), - envPair(">=", { a: ISeq -> pairwiseCompare(a, { x, y -> x.value >= y.value }) }), - - envPair("pr-str", { a: ISeq -> - MalString(a.seq().map({ it -> pr_str(it, print_readably = true) }).joinToString(" ")) - }), - envPair("str", { a: ISeq -> - MalString(a.seq().map({ it -> pr_str(it, print_readably = false) }).joinToString("")) - }), - envPair("prn", { a: ISeq -> - println(a.seq().map({ it -> pr_str(it, print_readably = true) }).joinToString(" ")) - NIL - }), - envPair("println", { a: ISeq -> - println(a.seq().map({ it -> pr_str(it, print_readably = false) }).joinToString(" ")) - NIL - }), - - envPair("read-string", { a: ISeq -> - val string = a.first() as? MalString ?: throw MalException("slurp requires a string parameter") - read_str(string.value) - }), - envPair("slurp", { a: ISeq -> - val name = a.first() as? MalString ?: throw MalException("slurp requires a filename parameter") - val text = File(name.value).readText() - MalString(text) - }), - - envPair("cons", { a: ISeq -> - val list = a.nth(1) as? ISeq ?: throw MalException("cons requires a list as its second parameter") - val mutableList = list.seq().toCollection(LinkedList()) - mutableList.addFirst(a.nth(0)) - MalList(mutableList) - }), - envPair("concat", { a: ISeq -> MalList(a.seq().flatMap({ it -> (it as ISeq).seq() }).toCollection(LinkedList())) }), - - envPair("nth", { a: ISeq -> - val list = a.nth(0) as? ISeq ?: throw MalException("nth requires a list as its first parameter") - val index = a.nth(1) as? MalInteger ?: throw MalException("nth requires an integer as its second parameter") - if (index.value >= list.count()) throw MalException("index out of bounds") - list.nth(index.value.toInt()) - }), - envPair("first", { a: ISeq -> - if (a.nth(0) == NIL) NIL - else { - val list = a.nth(0) as? ISeq ?: throw MalException("first requires a list parameter") - if (list.seq().any()) list.first() else NIL +private fun int_ops_reducer(f: (Int, Int) -> Int, args: MalSeq): MalNumber = + args.atoms.map { v: MalType -> v as MalNumber } + .reduce { acc, v -> MalNumber(f(acc.num, v.num)) } + +private fun to_fun(name: String, f: MalFn) : Pair = + malSym(name) to malFun(name, f) + +// =: compare the first two parameters and return true if they are the same type and contain the same value. +private fun is_equal(a: MalType, b: MalType) = + if(a::class == b::class) { + when(a) { + is MalNumber -> a.num == (b as MalNumber).num + is MalString -> a.str == (b as MalString).str + is MalSymbol -> a.sym == (b as MalSymbol).sym + is MalKeyword -> a.kw == (b as MalKeyword).kw + is MalBoolean -> a.bool == (b as MalBoolean).bool + is MalNil -> true + is MalSeq -> compare_lists(a, (b as MalSeq)) + is MalMap -> compare_maps(a, (b as MalMap)) + is MalFunc -> a.func == (b as MalFunc).func + // XXX Not particularly useful but is_equal(a.src, b.src) hits: + // error: type checking has run into a recursive problem + is MalUserEx -> a.src == (b as MalUserEx).src + else -> throw MalCoreEx("Unknown type $a in is_equal (aka =)") + } + } + // Handle class when comparing lists & vectors. + else if (a is MalSeq && b is MalSeq) { + compare_lists(a, b) + } + else { + false + } +// In the case of equal length lists, each element of the list should be compared for equality and if they are the same return true, otherwise false. +private fun compare_lists(a: MalSeq, b: MalSeq): Boolean { + if(a.atoms.count() == b.atoms.count()) + return a.atoms.indices.all { v: Int -> is_equal(a.atoms[v], b.atoms[v]) } + else + return false +} + +// TODO Implement! +private fun compare_maps(a: MalMap, b: MalMap) = a == b + +private fun pr_str_core(seq: MalSeq) = + seq.atoms.map { pr_str(it, print_readably=true) }.joinToString(" ") + +private fun str_core(seq: MalSeq, joiner: String) = + seq.atoms.map { pr_str(it, print_readably=false) }.joinToString(joiner) + +private val eof = "" + +object core { + val ns : Map = mutableMapOf( + // Basic number ops. + malSym("+") to malFun("plus") { int_ops_reducer(Int::plus, it) }, + malSym("-") to malFun("minus") { int_ops_reducer(Int::minus, it) }, + malSym("*") to malFun("times") { int_ops_reducer(Int::times, it) }, + malSym("/") to malFun("div") { int_ops_reducer(Int::div, it) }, + + // pr-str: calls `pr_str` on each argument with `print_readably` set to true, joins the results with " " and returns the new string. + to_fun("pr-str") { + MalString(pr_str_core(it)) + }, + // `str`: calls `pr_str` on each argument with `print_readably` set to false, concatenates the results together ("" separator), and returns the new string. + to_fun("str") { + MalString(str_core(it, joiner = "")) + }, + // prn: calls `pr_str` on each argument with `print_readably` set to true, joins the results with " ", prints the string to the screen and then returns `nil`. + to_fun("prn") { + println(pr_str_core(it)) + MalNil() + }, + // `println`: calls `pr_str` on each argument with `print_readably` set to false, joins the results with " ", prints the string to the screen and then returns `nil`. + to_fun("println") { + println(str_core(it, joiner = " ")) + MalNil() + }, + + // list: take the parameters and return them as a list. + to_fun("list") { it }, // we always get a list at this point + // list?: return true if the first parameter is a list, false otherwise. + to_fun("list?") { MalBoolean(it[0] is MalList) }, + // empty?: treat the first parameter as a list and return true if the list is empty and false if it contains any elements. + to_fun("empty?") { MalBoolean(it[0] is MalSeq && (it[0] as MalSeq).atoms.isEmpty()) }, + // count: treat the first parameter as a list and return the number of elements that it contains. + to_fun("count") { + val seq = it[0] + MalNumber(if (seq is MalSeq) seq.atoms.count() else 0) + }, + + // =: see is_equal + malSym("=") to malFun("equals?") { + val a = it[0] + val b = it[1] + MalBoolean(is_equal(a, b)) + }, + // <, <=, >, and >=: treat the first two parameters as numbers and do the corresponding numeric comparison, returning either true or false. + malSym("<") to malFun("lt") { + val a = it[0] as MalNumber + val b = it[1] as MalNumber + MalBoolean(a.num < b.num) + }, + malSym("<=") to malFun("lt-eq") { + val a = it[0] as MalNumber + val b = it[1] as MalNumber + MalBoolean(a.num <= b.num) + }, + malSym(">") to malFun("gt") { + val a = it[0] as MalNumber + val b = it[1] as MalNumber + MalBoolean(a.num > b.num) + }, + malSym(">=") to malFun("gt-eq") { + val a = it[0] as MalNumber + val b = it[1] as MalNumber + MalBoolean(a.num >= b.num) + }, + + to_fun("read-string") { + read_str((it[0] as MalString).str) + }, + + to_fun("slurp") { + MalString(java.io.File((it[0] as MalString).str).readText()) + }, + + to_fun("atom") { + MalCljAtom(it[0]) + }, + to_fun("atom?") { + MalBoolean(it[0] is MalCljAtom) + }, + to_fun("deref") { + (it[0] as MalCljAtom).value + }, + to_fun("reset!") { + val a = it[0] as MalCljAtom + a.value = it[1] + a.value + }, + to_fun("swap!") { + val atom = it[0] as MalCljAtom + val fn = it[1] as MalCallable + // Pull out args if there are any. + val args = it.atoms.slice(2 .. (if(it.size > 2) it.size - 1 else 1)) + // Call the function with atom value + any args. + val res = fn(malListOf(listOf(atom.value) + args)) + atom.value = res + res + }, + + to_fun("cons") { + val rest = if(it.size > 1) it[1] as MalSeq else emptyMalList() + malListOf(listOf(it.head()) + rest.atoms) + }, + to_fun("concat") { + malListOf(it.atoms.flatMap { (it as MalSeq).atoms }) + }, + to_fun("conj") { + val seq = it[0] as MalSeq + val rest = it.tail().atoms + when(seq) { + is MalList -> malListOf(rest.reversed() + seq.atoms) + else -> MalVector(seq.atoms + rest) + } + }, + // takes a list, vector, string, or nil. If an empty list, empty vector, or empty string ("") is passed in then nil is returned. Otherwise, a list is returned unchanged, a vector is converted into a list, and a string is converted to a list that containing the original string split into single character strings. + to_fun("seq") { + val s = it.head() + when(s) { + is MalList -> if(s.size > 0) s else MalNil() + is MalVector -> if(s.size > 0) malListOf(s.atoms) else MalNil() + is MalString -> if(s.str.count() > 0) + malListOf(s.str.chunked(1).map(::MalString)) + else MalNil() + else -> MalNil() + } + }, + + to_fun("nth") { + val seq = it[0] as MalSeq + val idx = it[1] as MalNumber + seq[idx] + }, + to_fun("first") { + val v = it[0] + if (v is MalNil) { + MalNil() + } + else if (v is MalSeq) { + if(v.size == 0) MalNil() else v.head() } - }), - envPair("rest", { a: ISeq -> - if (a.nth(0) == NIL) MalList() else { - val list = a.nth(0) as? ISeq ?: throw MalException("rest requires a list parameter") - MalList(list.rest()) + throw MalCoreEx("Can't fall 'first' on " + pr_str(v)) } - }), - - envPair("throw", { a: ISeq -> - val throwable = a.nth(0) - throw MalCoreException(pr_str(throwable), throwable) - }), - - envPair("apply", { a: ISeq -> - val function = a.nth(0) as MalFunction - val params = MalList() - a.seq().drop(1).forEach({ it -> - if (it is ISeq) { - it.seq().forEach({ x -> params.conj_BANG(x) }) - } else { - params.conj_BANG(it) - } - }) - function.apply(params) - }), - - envPair("map", { a: ISeq -> - val function = a.nth(0) as MalFunction - MalList((a.nth(1) as ISeq).seq().map({ it -> - val params = MalList() - params.conj_BANG(it) - function.apply(params) - }).toCollection(LinkedList())) - }), - - envPair("nil?", { a: ISeq -> if (a.nth(0) == NIL) TRUE else FALSE }), - envPair("true?", { a: ISeq -> if (a.nth(0) == TRUE) TRUE else FALSE }), - envPair("false?", { a: ISeq -> if (a.nth(0) == FALSE) TRUE else FALSE }), - envPair("string?", { a: ISeq -> - if (a.nth(0) is MalString && !(a.nth(0) is MalKeyword)) TRUE else FALSE - }), - envPair("symbol?", { a: ISeq -> if (a.nth(0) is MalSymbol) TRUE else FALSE }), - - envPair("symbol", { a: ISeq -> MalSymbol((a.nth(0) as MalString).value) }), - envPair("keyword", { a: ISeq -> - val param = a.nth(0) - if (param is MalKeyword) param else MalKeyword((a.nth(0) as MalString).value) - }), - envPair("keyword?", { a: ISeq -> if (a.nth(0) is MalKeyword) TRUE else FALSE }), - envPair("number?", { a: ISeq -> if (a.nth(0) is MalInteger) TRUE else FALSE }), - envPair("fn?", { a: ISeq -> if ((a.nth(0) as? MalFunction)?.is_macro ?: true) FALSE else TRUE }), - envPair("macro?", { a: ISeq -> if ((a.nth(0) as? MalFunction)?.is_macro ?: false) TRUE else FALSE }), - - envPair("vector", { a: ISeq -> MalVector(a) }), - envPair("vector?", { a: ISeq -> if (a.nth(0) is MalVector) TRUE else FALSE }), - - envPair("hash-map", { a: ISeq -> - val map = MalHashMap() - pairwise(a).forEach({ it -> map.assoc_BANG(it.first as MalString, it.second) }) - map - }), - envPair("map?", { a: ISeq -> if (a.nth(0) is MalHashMap) TRUE else FALSE }), - envPair("assoc", { a: ISeq -> - val map = MalHashMap(a.first() as MalHashMap) - pairwise(a.rest()).forEach({ it -> map.assoc_BANG(it.first as MalString, it.second) }) - map - }), - envPair("dissoc", { a: ISeq -> - val map = MalHashMap(a.first() as MalHashMap) - a.rest().seq().forEach({ it -> map.dissoc_BANG(it as MalString) }) - map - }), - envPair("get", { a: ISeq -> - val map = a.nth(0) as? MalHashMap - val key = a.nth(1) as MalString - map?.elements?.get(key) ?: NIL - }), - envPair("contains?", { a: ISeq -> - val map = a.nth(0) as? MalHashMap - val key = a.nth(1) as MalString - if (map?.elements?.get(key) != null) TRUE else FALSE - }), - envPair("keys", { a: ISeq -> - val map = a.nth(0) as MalHashMap - MalList(map.elements.keys.toCollection(LinkedList())) - }), - envPair("vals", { a: ISeq -> - val map = a.nth(0) as MalHashMap - MalList(map.elements.values.toCollection(LinkedList())) - }), - envPair("count", { a: ISeq -> - val seq = a.nth(0) as? ISeq - if (seq != null) MalInteger(seq.count().toLong()) else ZERO - }), - envPair("sequential?", { a: ISeq -> if (a.nth(0) is ISeq) TRUE else FALSE }), - - envPair("with-meta", { a: ISeq -> - val obj = a.nth(0) - val metadata = a.nth(1) - obj.with_meta(metadata) - }), - envPair("meta", { a: ISeq -> a.first().metadata }), - - envPair("conj", { a: ISeq -> (a.first() as ISeq).conj(a.rest()) }), - envPair("seq", { a: ISeq -> - val obj = a.nth(0) - if (obj is ISeq) { - if (obj.count() == 0) NIL - else MalList(obj.seq().toCollection(LinkedList())) - } else if (obj is MalString && !(obj is MalKeyword)) { - if (obj.value.length == 0) NIL - else { - var strs = obj.value.map({ c -> MalString(c.toString()) }) - MalList(strs.toCollection(LinkedList())) - } - } else { - NIL + }, + to_fun("rest") { + val v = it[0] + if (v is MalSeq) { + v.tail() } - }), - - envPair("atom", { a: ISeq -> MalAtom(a.first()) }), - envPair("atom?", { a: ISeq -> if (a.first() is MalAtom) TRUE else FALSE }), - envPair("deref", { a: ISeq -> (a.first() as MalAtom).value }), - envPair("reset!", { a: ISeq -> - val atom = a.nth(0) as MalAtom - val value = a.nth(1) - atom.value = value - value - }), - envPair("swap!", { a: ISeq -> - val atom = a.nth(0) as MalAtom - val function = a.nth(1) as MalFunction - - val params = MalList() - params.conj_BANG(atom.value) - a.seq().drop(2).forEach({ it -> params.conj_BANG(it) }) - - val value = function.apply(params) - atom.value = value - - value - }), - - envPair("readline", { a: ISeq -> - val prompt = a.first() as MalString - try { - MalString(readline(prompt.value)) - } catch (e: java.io.IOException) { - throw MalException(e.message) - } catch (e: EofException) { - NIL + else { + throw MalCoreEx("Can't fall 'rest' on " + pr_str(v)) } - }), + }, - envPair("time-ms", { a: ISeq -> MalInteger(System.currentTimeMillis()) }) -) + to_fun("throw") { + throw when(it.size) { + 0 -> MalUserEx(MalString("error raised anon")) + else -> MalUserEx(it[0]) + } + }, -private fun envPair(k: String, v: (ISeq) -> MalType): Pair = Pair(MalSymbol(k), MalFunction(v)) + to_fun("apply") { + // The first argument is a function and the last argument + // is list (or vector). The arguments between the function + // and the last argument (if there are any) are + // concatenated with the final argument to create the + // arguments that are used to call the function. + val fn = it[0] as MalCallable + val argSeq = it.last() as MalSeq + val args = it.atoms.slice(1 .. (if(it.size > 2) it.size - 2 else 0)) + fn(malListOf(argSeq.atoms + args)) + }, + to_fun("map") { + val fn = it[0] as MalCallable + val args = it[1] as MalSeq + malListOf(args.atoms.map { fn(malListOf(it)) }) + }, -private fun pairwise(s: ISeq): List> { - val (keys, vals) = s.seq().withIndex().partition({ it -> it.index % 2 == 0 }) - return keys.map({ it -> it.value }).zip(vals.map({ it -> it.value })) -} + to_fun("nil?") { + MalBoolean(it[0] is MalNil) + }, + to_fun("true?") { + MalBoolean(it[0] == MalBoolean(true)) + }, + to_fun("false?") { + MalBoolean(it[0] == MalBoolean(false)) + }, + to_fun("symbol?") { + MalBoolean(it[0] is MalSymbol) + }, + + to_fun("symbol") { + MalSymbol((it[0] as MalString).str) + }, + to_fun("keyword") { + val kw = it[0] + if (kw is MalKeyword) kw else MalKeyword((kw as MalString).str) + }, + to_fun("keyword?") { + MalBoolean(it[0] is MalKeyword) + }, + to_fun("vector") { + MalVector(it.atoms) + }, + to_fun("vector?") { + MalBoolean(it[0] is MalVector) + }, + to_fun("hash-map") { + make_map(it) + }, + to_fun("map?") { + MalBoolean(it[0] is MalMap) + }, + to_fun("assoc") { + val m = it[0] as MalMap + MalMap(m.pairs + make_map(it.tail()).pairs) + }, + to_fun("dissoc") { + val m = it[0] as MalMap + MalMap(m.pairs - it.tail().atoms.map { it as MalKey }) + }, + to_fun("get") { + val m = it[0] as MalMap + val k = it[1] as MalKey + m[k] + }, + to_fun("contains?") { + val m = it[0] as MalMap + MalBoolean(m.pairs.contains(it[1])) + }, + to_fun("keys") { + val m = it[0] as MalMap + malListOf(m.pairs.keys.toList()) + }, + to_fun("vals") { + val m = it[0] as MalMap + malListOf(m.pairs.values.toList()) + }, + to_fun("sequential?") { + MalBoolean(it[0] is MalSeq) + }, -private fun pairwiseCompare(s: ISeq, pred: (MalInteger, MalInteger) -> Boolean): MalConstant = - if (pairwise(s).all({ it -> pred(it.first as MalInteger, it.second as MalInteger) })) TRUE else FALSE + to_fun("readline") { + print((it[0] as MalString).str) + val line = readLine() + if (line == null) MalNil() else MalString(line.trim()) + }, -private fun pairwiseEquals(s: ISeq): MalConstant = - if (pairwise(s).all({ it -> it.first == it.second })) TRUE else FALSE + to_fun("meta") { + val f = it[0] as MalMeta + f.meta + }, + to_fun("with-meta") { + val f = it[0] as MalMeta + f.withMeta(it[1]) + }, + + to_fun("time-ms") { + MalNumber(java.time.Instant.now().toEpochMilli().toInt()) + }, + + to_fun("string?") { + MalBoolean(it[0] is MalString) + }, + to_fun("number?") { + MalBoolean(it[0] is MalNumber) + }, + to_fun("fn?") { + val f = it[0] + MalBoolean(f is MalCallable && !f.isMacro) + }, + to_fun("macro?") { + val f = it[0] + MalBoolean(f is MalCallable && f.isMacro) + } + ) +} diff --git a/kotlin/src/mal/env.kt b/kotlin/src/mal/env.kt index fa7b5991..627bb191 100644 --- a/kotlin/src/mal/env.kt +++ b/kotlin/src/mal/env.kt @@ -1,36 +1,40 @@ -package mal +// Define an Env object that is instantiated with a single outer parameter and starts with an empty associative data structure property data. +class Env(val outer: Env? = null, + val binds: MalSeq = emptyMalList(), + val exprs: MalSeq = emptyMalList()) { -import java.util.* - -class Env(val outer: Env?, binds: Sequence?, exprs: Sequence?) { - val data = HashMap() + val data : MutableMap = mutableMapOf() init { - if (binds != null && exprs != null) { - val itb = binds.iterator() - val ite = exprs.iterator() - while (itb.hasNext()) { - val b = itb.next() - if (b.value != "&") { - set(b, if (ite.hasNext()) ite.next() else NIL) - } else { - if (!itb.hasNext()) throw MalException("expected a symbol name for varargs") - set(itb.next(), MalList(ite.asSequence().toCollection(LinkedList()))) - break - } + // Modify the constructor/initializer for environments, so that if a "&" symbol is encountered in the binds list, the next symbol in the binds list after the "&" is bound to the rest of the exprs list that has not been bound yet. + var vals = exprs.atoms + bind_loop@ for(idx in binds.atoms.indices) { + val bind = binds.atoms[idx] as MalSymbol + if (bind == malSym("&")) { + val lastBind = binds.atoms[idx + 1] as MalSymbol + set(lastBind, malListOf(vals.slice(idx .. vals.size - 1))) + break@bind_loop + } + else { + set(bind, vals[idx]) } } } - constructor() : this(null, null, null) - constructor(outer: Env?) : this(outer, null, null) - - fun set(key: MalSymbol, value: MalType): MalType { - data.put(key.value, value) + // Define three methods for the Env object: + // set: takes a symbol key and a mal value and adds to the data structure + fun set(sym: MalSymbol, value: MalType): MalType { + data.set(sym, value) return value } - fun find(key: MalSymbol): MalType? = data[key.value] ?: outer?.find(key) + // find: takes a symbol key and if the current environment contains that key then return the environment. If no key is found and outer is not nil then call find (recurse) on the outer environment. + fun find(sym: MalSymbol): Env? = + if(data.contains(sym)) this else outer?.find(sym) - fun get(key: MalSymbol): MalType = find(key) ?: throw MalException("'${key.value}' not found") + // get: takes a symbol key and uses the find method to locate the environment with the key, then returns the matching value. If no key is found up the outer chain, then throws/raises a "not found" error. + fun get(key: MalSymbol): MalType { + val env = find(key) ?: throw MalCoreEx("The symbol '${key.sym}' not found in env") + return env.data.getValue(key) + } } diff --git a/kotlin/src/mal/eval.kt b/kotlin/src/mal/eval.kt new file mode 100644 index 00000000..040092a6 --- /dev/null +++ b/kotlin/src/mal/eval.kt @@ -0,0 +1,57 @@ +val repl_env : Map = mapOf( + "+" to MalFunc({ a, b -> MalNumber((a as MalNumber).num + (b as MalNumber).num) }), + "-" to MalFunc({ a, b -> MalNumber((a as MalNumber).num - (b as MalNumber).num) }), + "*" to MalFunc({ a, b -> MalNumber((a as MalNumber).num * (b as MalNumber).num) }), + "/" to MalFunc({ a, b -> MalNumber((a as MalNumber).num / (b as MalNumber).num) }) +) + +// Create a new function eval_ast which takes ast (mal data type) and +// an associative structure (the environment from above). eval_ast +// switches on the type of ast as follows: +// +// * symbol: lookup the symbol in the environment structure and return the value or raise an error if no value is found +// * list: return a new list that is the result of calling EVAL on each of the members of the list +// * otherwise just return the original ast value + +fun eval_ast(ast: MalType, env: Map, depth: Int) : MalType { +// println("eval_ast: ".repeat(depth) + pr_str(ast)) + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth) }.toList()) + is MalSymbol -> env[ast.sym] ?: throw Exception("Unknown symbol '${ast.sym}'") + else -> ast + } +} + +// Modify EVAL to check if the first parameter ast is a list. +// * ast is not a list: then return the result of calling eval_ast on it. +// * ast is a empty list: return ast unchanged. +// * ast is a list: call eval_ast to get a new evaluated list. Take the first +// item of the evaluated list and call it as function using the rest of the +// evaluated list as its arguments. + +var eval_count = 0 +fun EVAL(ast: MalType, env: Map, depth: Int) : MalType { + println(" EVAL: ".repeat(depth) + pr_str(ast)) + + eval_count += 1 + if (depth > 200 || eval_count > 500) { + throw Exception("Recursion fail :(") + } + + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + println("calling "+ast.head()+" with "+ast.tail()) + val l = eval_ast(ast, env, depth + 1) + val f = ((l as MalList).head() as MalFunc) + val r = f(l.tail()) + println("result: ".repeat(depth) + pr_str(r)) + return r + } + } +} diff --git a/kotlin/src/mal/printer.kt b/kotlin/src/mal/printer.kt index 25a2233a..4cab7931 100644 --- a/kotlin/src/mal/printer.kt +++ b/kotlin/src/mal/printer.kt @@ -1,27 +1,42 @@ -package mal +// Use named variables otherwise we have to escape escaping x_x +private val q = "\u0022" // '"' U+0022 " QUOTATION MARK (Other_Punctuation) +private val bs = "\u005C" // '\' U+005C \ REVERSE SOLIDUS (Other_Punctuation) -fun pr_str(malType: MalType, print_readably: Boolean = false): String = - when (malType) { - is MalInteger -> malType.value.toString() - is MalKeyword -> ":" + malType.value.substring(1) - is MalString -> - if (print_readably) { - "\"" + malType.value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + "\"" - } else malType.value - is MalConstant -> malType.value - is MalSymbol -> malType.value - is MalFunction -> "#" + malType - is MalCoreException -> pr_str(malType.value, print_readably) - is MalException -> "\"" + (malType.message ?: "exception") + "\"" - is MalList -> pr_str(malType.elements, "(", ")", print_readably) - is MalVector -> pr_str(malType.elements, "[", "]", print_readably) - is MalHashMap -> malType.elements.map({ it -> pr_str(it, print_readably) }).joinToString(" ", "{", "}") - is MalAtom -> "(atom " + pr_str(malType.value, print_readably) + ")" - else -> throw MalPrinterException("Unrecognized MalType: " + malType) - } +// Reflect any changes in reader.kt +private val printEscapeMap = mapOf( + "$q" to "$bs$q", // " to \" + "\n" to "${bs}n", // ␤ to \n + "$bs" to "$bs$bs" // \ to \\ +) +// Bleurgh, the escapes need escapes as they become interpolated into Regex ;_; +// So we need to manage three levels escaping different >_< +private val printEscapes = + Regex(listOf(q, "${bs}n", "$bs$bs").joinToString("|", "(", ")")) -private fun pr_str(coll: Collection, start: String, end: String, print_readably: Boolean = false): String = - coll.map({ it -> pr_str(it, print_readably) }).joinToString(" ", start, end) +private fun malstr_as_string(s: String) = + q + s.replace(printEscapes) { printEscapeMap.get(it.value) ?: it.value } + q -private fun pr_str(mapEntry: Map.Entry, print_readably: Boolean = false): String = - pr_str(mapEntry.key, print_readably) + " " + pr_str(mapEntry.value, print_readably) +// … does the opposite of read_str: take a mal data structure and +// return a string representation of it. +fun pr_str(v: MalType, print_readably: Boolean = true) : String { + // Close over print_readably to simplify code below. + val pr = { w: MalType -> pr_str(w, print_readably) } + val pr_map = { p: Map.Entry -> pr(p.key) + " " + pr(p.value) } + + // Inner function to save repeating 'print_readably' in every call. + return when(v) { + is MalList -> v.atoms.map(pr).joinToString(" ", "(", ")") + is MalVector -> v.atoms.map(pr).joinToString(" ", "[", "]") + is MalMap -> v.pairs.map(pr_map).joinToString(" ", "{", "}") + is MalNumber -> v.num.toString() + is MalKeyword -> ":${v.kw}" + is MalString -> if(print_readably) malstr_as_string(v.str) else v.str + is MalSymbol -> v.sym + is MalBoolean -> v.bool.toString() + is MalCljAtom -> "(atom ${pr(v.value)})" + is MalNil -> "nil" + is MalCallable -> "#<${v.name}>" + is MalUserEx -> "Exception raised: ${pr(v.src)}" + else -> "Can't stringify a "+v::class + } +} diff --git a/kotlin/src/mal/reader.kt b/kotlin/src/mal/reader.kt index 48b258e3..5905cd21 100644 --- a/kotlin/src/mal/reader.kt +++ b/kotlin/src/mal/reader.kt @@ -1,156 +1,181 @@ -package mal - -import kotlin.text.Regex - -val TOKEN_REGEX = Regex("[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)") -val ATOM_REGEX = Regex("(^-?[0-9]+$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|^\"(.*)$|:(.*)|(^[^\"]*$)") - -class Reader(sequence: Sequence) { - val tokens = sequence.iterator() - var current = advance() - - fun next(): String? { - var result = current - current = advance() - return result - } - - fun peek(): String? = current - - private fun advance(): String? = if (tokens.hasNext()) tokens.next() else null +// For each match captured within the parenthesis starting at char 6 +// of the regular expression a new token will be created. + +// TODO Support escaped double quotes e.g "foo \"bar\" baz" -> """foo "bar" baz""" +private var tokenizer = Regex(""" +# Matches any number of whitespaces or commas +[\s,]* +(?: + # Captures the special two-characters ~@ (tokenized). + ~@ | + # Captures any special single character, one of []{}()'`~^@ (tokenized). + [\[\]{}()'`~^@] | + # Starts capturing at a double-quote and stops at the next double-quote unless + # it was preceded by a backslash in which case it includes it until the next + # double-quote (tokenized). It will also match unbalanced strings (no ending + # double-quote) which should be reported as an error. + "(?:\\"|[^"])*"? | + # Captures any sequence of characters starting with ; (tokenized). + ;.* | + # Captures a sequence of zero or more non special characters (e.g. symbols, + # numbers, "true", "false", and "nil") and is sort of the inverse of the one + # above that captures special characters (tokenized). + [^\s\[\]{}('"`,;)]* +) +""", +setOf(RegexOption.COMMENTS, RegexOption.MULTILINE) +) + +private val commas = Regex("""^[\s,]*|[,\s]*$""", RegexOption.MULTILINE) + +// This function will take a single string and return an array/list of all the tokens (strings) in it. +fun tokenize(s: String) : List { + // For some reason this fails where findAll doesn't. + // if (!tokenizer.matches(s)) { + // throw MalCoreEx("Failed tokenizing") + // } + return tokenizer.findAll(s) + .map { it.value.replace(commas, "") } + .filter { it.length > 0 } + .filter { !it.startsWith(";") } + .toList() } -fun read_str(input: String?): MalType { - val tokens = tokenizer(input) ?: return NIL - return read_form(Reader(tokens)) +// This object will store the tokens and a position. +class Reader(val tokens: List) { + var pos = 0 + // returns the token at the current position and increments the position + fun next() = tokens[pos++] + // Check whether we're at the end. + fun isLast() = pos == (tokens.size - 1) + // just returns the token at the current position. + fun peek() = tokens[pos] } -fun tokenizer(input: String?): Sequence? { - if (input == null) return null +private fun is_number(s: String) = Regex("-?\\d+").matches(s) - return TOKEN_REGEX.findAll(input) - .map({ it -> it.groups[1]?.value as String }) - .filter({ it != "" && !it.startsWith(";")}) -} +private fun is_bool(s: String) = setOf("true", "false").contains(s) -fun read_form(reader: Reader): MalType = - when (reader.peek()) { - null -> throw MalContinue() - "(" -> read_list(reader) - ")" -> throw MalReaderException("expected form, got ')'") - "[" -> read_vector(reader) - "]" -> throw MalReaderException("expected form, got ']'") - "{" -> read_hashmap(reader) - "}" -> throw MalReaderException("expected form, got '}'") - "'" -> read_shorthand(reader, "quote") - "`" -> read_shorthand(reader, "quasiquote") - "~" -> read_shorthand(reader, "unquote") - "~@" -> read_shorthand(reader, "splice-unquote") - "^" -> read_with_meta(reader) - "@" -> read_shorthand(reader, "deref") - else -> read_atom(reader) - } - -fun read_list(reader: Reader): MalType = read_sequence(reader, MalList(), ")") -fun read_vector(reader: Reader): MalType = read_sequence(reader, MalVector(), "]") +private fun make_atom_deref(token: String) = MalList(listOf("deref", token).map(::malSym)) -private fun read_sequence(reader: Reader, sequence: IMutableSeq, end: String): MalType { - reader.next() +// Use named variables otherwise we have to escape escaping x_x +private val q = "\u0022" // '"' U+0022 " QUOTATION MARK (Other_Punctuation) +private val bs = "\u005C" // '\' U+005C \ REVERSE SOLIDUS (Other_Punctuation) - do { - val form = when (reader.peek()) { - null -> throw MalReaderException("expected '$end', got EOF") - end -> { reader.next(); null } - else -> read_form(reader) - } +// Reflect any changes in printer.kt +private var readEscapeMap = mapOf( + "$bs$q" to q, // \" to " + "${bs}n" to "\n", // \n to ␤ + "$bs$bs" to bs // \\ to \ +) +// Bleurgh, the escapes need escapes as they become interpolated into Regex ;_; +private var readEscapes = Regex(listOf("$bs$bs$bs$bs", "(? throw MalReaderException("expected '}', got EOF") - "}" -> { reader.next(); null } - else -> { - var key = read_form(reader) - if (key !is MalString) { - throw MalReaderException("hash-map keys must be strings or keywords") - } - value = when (reader.peek()) { - null -> throw MalReaderException("expected form, got EOF") - else -> read_form(reader) - } - key - } - } +private fun read_atom(r: Reader, n: Int) : MalType { + // println("Reading atom: " + r) + val t = r.next() + return when { + t[0] == '"' -> make_string(t.substring(1 .. t.lastIndex)) + t[0] == ':' -> MalKeyword(t.substring(1 .. t.lastIndex)) + t[0] == '^' -> make_with_meta(r, n) + is_number(t) -> MalNumber(t.toInt()) + is_bool(t) -> MalBoolean(t == "true") + t == "nil" -> MalNil() + t == "@" -> make_atom_deref(r.next()) + t == "'" -> malListOf(malSym("quote"), read_form(r, n)) + t == "`" -> malListOf(malSym("quasiquote"), read_form(r, n)) + t == "~" -> malListOf(malSym("unquote"), read_form(r, n)) + t == "~@" -> malListOf(malSym("splice-unquote"), read_form(r, n)) + else -> MalSymbol(t) + } +} - if (key != null) { - hashMap.assoc_BANG(key, value as MalType) - } - } while (key != null) +private fun make_map(pairs: List) : MalMap { + if(pairs.size % 2 != 0) + throw MalUserEx(MalString("maps requires an even number of items, got ${pairs.size} items")) - return hashMap + val map : MutableMap = mutableMapOf() + for (idx in pairs.indices step 2) { + val k = pairs[idx] as MalKey + val v = pairs[idx + 1] + map[k] = v + } + return MalMap(map) } -fun read_shorthand(reader: Reader, symbol: String): MalType { - reader.next() +fun make_map(pairs: MalSeq) = make_map(pairs.atoms) - val list = MalList() - list.conj_BANG(MalSymbol(symbol)) - list.conj_BANG(read_form(reader)) +private var readLimit = 0 - return list +// Safety limit to prevent the REPL never coming back. +private fun check_limit() { + readLimit++ + if (readLimit > 1024) { + throw MalCoreEx("Parser found no end :/") + } } -fun read_with_meta(reader: Reader): MalType { - reader.next() - - val meta = read_form(reader) - val obj = read_form(reader) - - val list = MalList() - list.conj_BANG(MalSymbol("with-meta")) - list.conj_BANG(obj) - list.conj_BANG(meta) +// This function will peek at the first token in the Reader object and +// switch on the first character of that token. If the character is a +// left paren then read_list is called with the Reader +// object. Otherwise, read_atom is called with the Reader Object. The +// return value from read_form is a mal data type. +fun read_form(r: Reader, n: Int) : MalType { +// println("v1> " + " ".repeat(n) + "read_form") + try { + return when(r.peek()) { + "(" -> MalList(read_seq(")", r, n + 1)) + "[" -> MalVector(read_seq("]", r, n + 1)) + "{" -> make_map(read_seq("}", r, n + 1)) + else -> read_atom(r, n + 1) + } + } + catch(e: IndexOutOfBoundsException) { + throw MalCoreEx("Unexpected end of input, unbalanced paren/brace/bracket?") + } +} +private fun read_seq(endTok: String, r: Reader, n: Int) : List { + r.next() // Move past the opening paren. +// val say = { m: String -> println("v1> " + " ".repeat(n) + m) } + val list : MutableList = mutableListOf() + while(r.peek() != endTok) { +// say("at token: " + r.peek()) + list.add(read_form(r, n)) + check_limit() + } + if(!r.isLast()) r.next() +// say("returning list!") return list } -fun read_atom(reader: Reader): MalType { - val next = reader.next() ?: throw MalReaderException("Unexpected null token") - val groups = ATOM_REGEX.find(next)?.groups ?: throw MalReaderException("Unrecognized token: " + next) - - return if (groups[1]?.value != null) { - MalInteger(groups[1]?.value?.toLong() ?: throw MalReaderException("Error parsing number: " + next)) - } else if (groups[2]?.value != null) { - NIL - } else if (groups[3]?.value != null) { - TRUE - } else if (groups[4]?.value != null) { - FALSE - } else if (groups[5]?.value != null) { - MalString((groups[5]?.value as String).replace(Regex("""\\(.)""")) - { m: MatchResult -> - if (m.groups[1]?.value == "n") "\n" - else m.groups[1]?.value.toString() - }) - } else if (groups[6]?.value != null) { - throw MalReaderException("expected '\"', got EOF") - } else if (groups[7]?.value != null) { - MalKeyword(groups[7]?.value as String) - } else if (groups[8]?.value != null) { - MalSymbol(groups[8]?.value as String) - } else { - throw MalReaderException("Unrecognized token: " + next) +private fun read_form_safely(r: Reader) : MalType { + try { + return if(r.tokens.isEmpty()) { + emptyMalList() + } + else { + read_form(r, 0) + } + } + finally { + readLimit = 0 } } + +// This function will call tokenize and then create a new Reader +// object instance with the tokens. Then it will call read_form with +// the Reader instance. +fun read_str(s: String) = read_form_safely(Reader(tokenize(s))) diff --git a/kotlin/src/mal/readline.kt b/kotlin/src/mal/readline.kt index 97902386..8b137891 100644 --- a/kotlin/src/mal/readline.kt +++ b/kotlin/src/mal/readline.kt @@ -1,8 +1 @@ -package mal -class EofException : Exception("EOF") - -fun readline(prompt: String): String { - print(prompt) - return readLine() ?: throw EofException() -} diff --git a/kotlin/src/mal/step0_repl.kt b/kotlin/src/mal/step0_repl.kt index 1ced37d3..c7ae70c1 100644 --- a/kotlin/src/mal/step0_repl.kt +++ b/kotlin/src/mal/step0_repl.kt @@ -1,19 +1,14 @@ -package mal +fun READ(s: String) = s -fun main(args: Array) { - fun read(input: String?): String? = input - fun eval(expression: String?): String? = expression - fun print(result: String?): String? = result +fun EVAL(s: String) = s + +fun PRINT(s: String) = println(s) - while (true) { - val input = readline("user> ") +fun rep(s: String) = PRINT(EVAL(READ(s))) - try { - println(print(eval(read(input)))) - } catch (e: EofException) { - break - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) +fun main(args: Array) { + while(true) { + print("user> ") + readLine()?.let { rep(it) } } - } } diff --git a/kotlin/src/mal/step1_read_print.kt b/kotlin/src/mal/step1_read_print.kt index 18ee081d..1b3b1765 100644 --- a/kotlin/src/mal/step1_read_print.kt +++ b/kotlin/src/mal/step1_read_print.kt @@ -1,22 +1,23 @@ -package mal +fun READ(s: String) = read_str(s) -fun main(args: Array) { - fun read(input: String?): MalType = read_str(input) - fun eval(expression: MalType): MalType = expression - fun print(result: MalType) = pr_str(result, print_readably = true) +fun EVAL(s: MalType) = s + +fun PRINT(v: MalType) = pr_str(v) - while (true) { - val input = readline("user> ") +fun rep(s: String) { + println(PRINT(EVAL(READ(s)))) +} + +fun main(args: Array) { + while(true) { + print("user> ") try { - println(print(eval(read(input)))) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) + readLine()?.let { rep(it) } + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) } } } + diff --git a/kotlin/src/mal/step2_eval.kt b/kotlin/src/mal/step2_eval.kt index 630745a1..7eb04441 100644 --- a/kotlin/src/mal/step2_eval.kt +++ b/kotlin/src/mal/step2_eval.kt @@ -1,45 +1,81 @@ -package mal - -fun read(input: String?): MalType = read_str(input) - -fun eval(ast: MalType, env: Map): MalType = - if (ast is MalList && ast.count() > 0) { - val evaluated = eval_ast(ast, env) as ISeq - if (evaluated.first() !is MalFunction) throw MalException("cannot execute non-function") - (evaluated.first() as MalFunction).apply(evaluated.rest()) - } else eval_ast(ast, env) - -fun eval_ast(ast: MalType, env: Map): MalType = - when (ast) { - is MalSymbol -> env[ast.value] ?: throw MalException("'${ast.value}' not found") - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast +fun READ(s: String) = read_str(s) + +val repl_env : Map = mapOf( + "+" to malFun("+") { MalNumber((it[0] as MalNumber).num + (it[1] as MalNumber).num) }, + "-" to malFun("-") { MalNumber((it[0] as MalNumber).num - (it[1] as MalNumber).num) }, + "*" to malFun("*") { MalNumber((it[0] as MalNumber).num * (it[1] as MalNumber).num) }, + "/" to malFun("/") { MalNumber((it[0] as MalNumber).num / (it[1] as MalNumber).num) } +) + +// Create a new function eval_ast which takes ast (mal data type) and +// an associative structure (the environment from above). eval_ast +// switches on the type of ast as follows: +// +// * symbol: lookup the symbol in the environment structure and return the value or raise an error if no value is found +// * list: return a new list that is the result of calling EVAL on each of the members of the list +// * otherwise just return the original ast value + +fun eval_ast(ast: MalType, env: Map, depth: Int) : MalType { +// print("eval_ast: ".repeat(depth)) +// println(PRINT(ast)) + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env[ast.sym] ?: throw Exception("Unknown symbol '${ast.sym}'") + else -> ast + } +} + +// Modify EVAL to check if the first parameter ast is a list. +// * ast is not a list: then return the result of calling eval_ast on it. +// * ast is a empty list: return ast unchanged. +// * ast is a list: call eval_ast to get a new evaluated list. Take the first +// item of the evaluated list and call it as function using the rest of the +// evaluated list as its arguments. + +var eval_count = 0 +fun EVAL(ast: MalType, env: Map, depth: Int) : MalType { +// print("EVAL____: ".repeat(depth)) +// println(PRINT(ast)) + + eval_count += 1 + if (depth > 200 || eval_count > 500) { + throw Exception("Recursion fail :(") + } + + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val l = eval_ast(ast, env, depth + 1) + val f = ((l as MalList).head() as MalFunc) + return f(l.tail()) } + } +} -fun print(result: MalType) = pr_str(result, print_readably = true) +fun PRINT(v: MalType) = pr_str(v) -fun main(args: Array) { - val env = hashMapOf( - Pair("+", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) })), - Pair("-", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) })), - Pair("*", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) })), - Pair("/", MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) })) - ) +fun rep(s: String) { + println(PRINT(EVAL(READ(s), repl_env, 0))) +} - while (true) { - val input = readline("user> ") +fun main(args: Array) { + while(true) { + print("user> ") try { - println(print(eval(read(input), env))) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) + readLine()?.let { rep(it) } + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) } } } + + diff --git a/kotlin/src/mal/step3_env.kt b/kotlin/src/mal/step3_env.kt index 021ac487..b4f59967 100644 --- a/kotlin/src/mal/step3_env.kt +++ b/kotlin/src/mal/step3_env.kt @@ -1,61 +1,128 @@ -package mal - -fun read(input: String?): MalType = read_str(input) - -fun eval(ast: MalType, env: Env): MalType = - if (ast is MalList && ast.count() > 0) { - val first = ast.first() - if (first is MalSymbol && first.value == "def!") { - env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - } else if (first is MalSymbol && first.value == "let*") { - val child = Env(env) - val bindings = ast.nth(1) - if (bindings !is ISeq) throw MalException("expected sequence as the first parameter to let*") - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - val value = eval(it.next(), child) - child.set(key as MalSymbol, value) +fun READ(s: String) = read_str(s) + +// Create a new function eval_ast which takes ast (mal data type) and +// an associative structure (the environment from above). eval_ast +// switches on the type of ast as follows: +// +// * symbol: lookup the symbol in the environment structure and return the value or raise an error if no value is found +// * list: return a new list that is the result of calling EVAL on each of the members of the list +// * otherwise just return the original ast value + +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { +// print("eval_ast: ".repeat(depth)) +// println(PRINT(ast)) + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env.get(ast) + else -> ast + } +} + +// Modify EVAL to check if the first parameter ast is a list. +// * ast is not a list: then return the result of calling eval_ast on it. +// * ast is a empty list: return ast unchanged. +// * ast is a list: call eval_ast to get a new evaluated list. Take the first +// item of the evaluated list and call it as function using the rest of the +// evaluated list as its arguments. + +/* +Modify the apply section of EVAL to switch on the first element of the +list: + + symbol "def!": call the set method of the current environment +(second parameter of EVAL called env) using the unevaluated first +parameter (second list element) as the symbol key and the evaluated +second parameter as the value. + + symbol "let*": create a new environment using the current +environment as the outer value and then use the first parameter as a +list of new bindings in the "let*" environment. Take the second +element of the binding list, call EVAL using the new "let*" +environment as the evaluation environment, then call set on the "let*" +environment using the first binding list element as the key and the +evaluated second element as the value. This is repeated for each +odd/even pair in the binding list. Note in particular, the bindings +earlier in the list can be referred to by later bindings. Finally, the +second parameter (third element) of the original let* form is +evaluated using the new "let*" environment and the result is returned +as the result of the let* (the new let environment is discarded upon +completion). + + otherwise: call eval_ast on the list and apply the first element to the rest as before. + +*/ + +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env +} + +var eval_count = 0 +fun EVAL(ast: MalType, env: Env, depth: Int) : MalType { +// print("EVAL____: ".repeat(depth)) +// println(PRINT(ast)) + + eval_count += 1 + if (depth > 200 || eval_count > 500) { + throw Exception("Recursion fail :(") + } + + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val op = ast.head() + val args = ast.tail() + if(op is MalSymbol) { + when(op.sym) { + "def!" -> { + val v = EVAL(args[1], env, depth + 1) + return env.set((args[0] as MalSymbol), v) + } + "let*" -> return EVAL(args[1], make_env((args[0] as MalSeq), env, depth), depth + 1) } - eval(ast.nth(2), child) - } else { - val evaluated = eval_ast(ast, env) as ISeq - if (evaluated.first() !is MalFunction) throw MalException("cannot execute non-function") - (evaluated.first() as MalFunction).apply(evaluated.rest()) } - } else eval_ast(ast, env) - -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast + val l = eval_ast(ast, env, depth + 1) + val f = ((l as MalList).head() as MalFunc) + return f(l.tail()) } + } +} -fun print(result: MalType) = pr_str(result, print_readably = true) +fun PRINT(v: MalType) = pr_str(v) -fun main(args: Array) { - val env = Env() - env.set(MalSymbol("+"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger + y as MalInteger }) })) - env.set(MalSymbol("-"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger - y as MalInteger }) })) - env.set(MalSymbol("*"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger * y as MalInteger }) })) - env.set(MalSymbol("/"), MalFunction({ a: ISeq -> a.seq().reduce({ x, y -> x as MalInteger / y as MalInteger }) })) +val repl_env = Env().apply { + set(MalSymbol("+"), malFun("+") { MalNumber((it[0] as MalNumber).num + (it[1] as MalNumber).num) }) + set(MalSymbol("-"), malFun("-") { MalNumber((it[0] as MalNumber).num - (it[1] as MalNumber).num) }) + set(MalSymbol("*"), malFun("*") { MalNumber((it[0] as MalNumber).num * (it[1] as MalNumber).num) }) + set(MalSymbol("/"), malFun("/") { MalNumber((it[0] as MalNumber).num / (it[1] as MalNumber).num) }) +} - while (true) { - val input = readline("user> ") +fun rep(s: String) { + println(PRINT(EVAL(READ(s), repl_env, 0))) +} + +fun main(args: Array) { + while(true) { + print("user> ") try { - println(print(eval(read(input), env))) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) + readLine()?.let { rep(it) } + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) } } } + diff --git a/kotlin/src/mal/step4_if_fn_do.kt b/kotlin/src/mal/step4_if_fn_do.kt index ff7ae5c5..42018bcf 100644 --- a/kotlin/src/mal/step4_if_fn_do.kt +++ b/kotlin/src/mal/step4_if_fn_do.kt @@ -1,103 +1,121 @@ -package mal - -fun read(input: String?): MalType = read_str(input) - -fun eval(ast: MalType, env: Env): MalType = - if (ast is MalList && ast.count() > 0) { - val first = ast.first() - if (first is MalSymbol) { - when (first.value) { - "def!" -> eval_def_BANG(ast, env) - "let*" -> eval_let_STAR(ast, env) - "fn*" -> eval_fn_STAR(ast, env) - "do" -> eval_do(ast, env) - "if" -> eval_if(ast, env) - else -> eval_function_call(ast, env) - } - } else eval_function_call(ast, env) - } else eval_ast(ast, env) +fun READ(s: String) = read_str(s) + +// Create a new function eval_ast which takes ast (mal data type) and +// an associative structure (the environment from above). eval_ast +// switches on the type of ast as follows: +// +// * symbol: lookup the symbol in the environment structure and return the value or raise an error if no value is found +// * list: return a new list that is the result of calling EVAL on each of the members of the list +// * otherwise just return the original ast value + +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { +// print("eval_ast: ".repeat(depth)) +// println(PRINT(ast)) + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env.get(ast) + else -> ast + } +} -private fun eval_def_BANG(ast: ISeq, env: Env): MalType = - env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) +/* -private fun eval_let_STAR(ast: ISeq, env: Env): MalType { - val child = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") + do: Evaluate all the elements of the list using eval_ast and return the final evaluated element. + if: Evaluate the first parameter (second element). If the result (condition) is anything other than nil or false, then evaluate the second parameter (third element of the list) and return the result. Otherwise, evaluate the third parameter (fourth element) and return the result. If condition is false and there is no third parameter, then just return nil. + fn*: Return a new function closure. The body of that closure does the following: + Create a new environment using env (closed over from outer scope) as the outer parameter, the first parameter (second list element of ast from the outer scope) as the binds parameter, and the parameters to the closure as the exprs parameter. + Call EVAL on the second parameter (third list element of ast from outer scope), using the new environment. Use the result as the return value of the closure. - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") +*/ - val value = eval(it.next(), child) - child.set(key as MalSymbol, value) +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) } - - return eval(ast.nth(2), child) + return new_env } -private fun eval_fn_STAR(ast: ISeq, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val symbols = binds.seq().filterIsInstance() - val body = ast.nth(2) - - return MalFunction({ s: ISeq -> - eval(body, Env(env, symbols, s.seq())) - }) -} - -private fun eval_do(ast: ISeq, env: Env): MalType = - (eval_ast(MalList(ast.rest()), env) as ISeq).seq().last() +fun is_true(cond : MalType) = + when(cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true + } -private fun eval_if(ast: ISeq, env: Env): MalType { - val check = eval(ast.nth(1), env) +var eval_count = 0 +fun EVAL(ast: MalType, env: Env, depth: Int) : MalType { +// print("EVAL____: ".repeat(depth)) +// println(PRINT(ast)) - return if (check != NIL && check != FALSE) { - eval(ast.nth(2), env) - } else if (ast.count() > 3) { - eval(ast.nth(3), env) - } else NIL -} + // Only use n when recursing into EVAL + val n = depth + 1 + eval_count += 1 + if (depth > 10000 || eval_count > 50000) { + throw Exception("Recursion fail :(") + } -private fun eval_function_call(ast: ISeq, env: Env): MalType { - val evaluated = eval_ast(ast, env) as ISeq - val first = evaluated.first() as? MalFunction ?: throw MalException("cannot execute non-function") - return first.apply(evaluated.rest()) + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val op = ast.head() + val args = ast.tail() + if(op is MalSymbol) { + when(op.sym) { + "def!" -> { + val v = EVAL(args[1], env, n) + return env.set((args[0] as MalSymbol), v) + } + "let*" -> return EVAL(args[1], make_env((args[0] as MalSeq), env, depth), n) + "do" -> return (eval_ast(args, env, depth) as MalList).last() + "if" -> { + val body = if(is_true(EVAL(args[0], env, n))) args[1] else + if(args.atoms.count() == 3) args[2] else MalNil() + return EVAL(body, env, n) + } + "fn*" -> { + val binds = args[0] as MalSeq + val body = args[1] + return malFun("funccall") { EVAL(body, Env(env, binds, it), n) } + } + } + } + val l = eval_ast(ast, env, depth) as MalList + val f = l.head() as MalFunc + return f(l.tail()) + } + } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } +fun PRINT(v: MalType) = pr_str(v) -fun print(result: MalType) = pr_str(result, print_readably = true) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } +} fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) - - rep("(def! not (fn* (a) (if a false true)))", repl_env) - - while (true) { - val input = readline("user> ") + rep("(def! not (fn* [v] (if v false true)))") + while(true) { + print("user> ") try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + readLine()?.let { println(rep(it)) } + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) + eval_count = 0 } } } + diff --git a/kotlin/src/mal/step5_tco.kt b/kotlin/src/mal/step5_tco.kt index cfc750f3..164158ad 100644 --- a/kotlin/src/mal/step5_tco.kt +++ b/kotlin/src/mal/step5_tco.kt @@ -1,103 +1,142 @@ -package mal +fun READ(s: String) = read_str(s) -fun read(input: String?): MalType = read_str(input) +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalSymbol -> env.get(ast) + else -> ast + } +} -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env +fun make_env(pairs: MalList, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env +} - while (true) { - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") +fun is_true(cond : MalType) = + when(cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true + } - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) - } +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while(true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw Exception("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + return env.set((rest[0] as MalSymbol), v) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalList), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.tail() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else ast + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalList + val body = rest[1] + val func = malFun("funccall") { + EVAL(body, Env(env, binds, it), n) + } + return MalUserFunc(body, binds, env, func) + } + } } - "if" -> { - val check = eval(ast.nth(1), env) - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() + + if(func is MalUserFunc) { + // set ast to the ast attribute of f. + // Generate a new environment using the env and params attributes of f as the outer and binds arguments and rest ast arguments (list elements 2 through the end) as the exprs argument. Set env to the new environment. Continue at the beginning of the loop. + ast = func.ast + // set func.params -> args somehow? + val binds = func.params.atoms.mapIndexed { index, atom -> + val k = atom as MalSymbol + val v = args[index] + listOf(k,v) + }.flatten().let { MalList(it) } + env = make_env(binds, func.env, depth) + continue@eval_loop // TCO } - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } + else if(func is MalFunc) { + return func(args) + } + else { + throw Exception("Don't know what to do with " + func) } } - } else return eval_ast(ast, env) + } } } + +fun PRINT(v: MalType) = pr_str(v) -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } - -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } } -fun print(result: MalType) = pr_str(result, print_readably = true) - -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) - fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) - - rep("(def! not (fn* (a) (if a false true)))", repl_env) - - while (true) { - val input = readline("user> ") + rep("(def! not (fn* [v] (if v false true)))") + while(true) { + print("user> ") try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + readLine()?.let { println(rep(it)) } + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) + eval_count = 0 } } } + diff --git a/kotlin/src/mal/step6_file.kt b/kotlin/src/mal/step6_file.kt index bbb24a07..3ea5a370 100644 --- a/kotlin/src/mal/step6_file.kt +++ b/kotlin/src/mal/step6_file.kt @@ -1,114 +1,165 @@ -package mal +fun READ(s: String) = read_str(s) -import java.util.* +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalSymbol -> env.get(ast) + else -> ast + } +} -fun read(input: String?): MalType = read_str(input) +fun make_env(pairs: MalList, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env +} -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env +fun is_true(cond : MalType) = + when(cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true + } - while (true) { - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { +// val say = { m: String -> println(" ".repeat(depth) + m) } + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while(true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw Exception("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { +// say(pr_str(ast[0])) + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + val name = (rest[0] as MalSymbol) + if (v is MalUserFunc) { + v.fn.name = name.sym + } + return env.set(name, v) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalList), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.last() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else ast + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalList + val body = rest[1] + val func = malFun("funccall") { + EVAL(body, Env(env, binds, it), n) + } + return MalUserFunc(body, binds, env, func) + } } - - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() } - "if" -> { - val check = eval(ast.nth(1), env) - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() + + if(func is MalUserFunc) { + // set ast to the ast attribute of f. + // Generate a new environment using the env and params attributes of f as the outer and binds arguments and rest ast arguments (list elements 2 through the end) as the exprs argument. Set env to the new environment. Continue at the beginning of the loop. + ast = func.ast + val binds = func.params.atoms.mapIndexed { index, atom -> + val k = atom as MalSymbol + val v = args[index] + listOf(k,v) + }.flatten().let { MalList(it) } + env = make_env(binds, func.env, depth) + continue@eval_loop // TCO } - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } + else if(func is MalFunc) { + return func(args) + } + else { + return func +// throw Exception("Don't know what to do with " + func) } } - } else return eval_ast(ast, env) + } } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } +fun PRINT(v: MalType) = pr_str(v) -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } } -fun print(result: MalType) = pr_str(result, print_readably = true) - -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) - fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) - - repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) }).toCollection(LinkedList()))) - repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), 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) + repl_env.set(malSym("eval"), malFun("eval") { + val res = eval_ast(it, repl_env, 0) + when(res) { + is MalList -> res.last() + else -> res + } + }) + repl_env.set(malSym("*ARGV*"), malListOf(args.map(::MalString))) - if (args.any()) { - rep("(load-file \"${args[0]}\")", repl_env) - return - } + rep("""(def! load-file (fn* [f] (eval (read-string (str "(do " (slurp f) ")")))))""") + rep("(def! not (fn* [v] (if v false true)))") - while (true) { - val input = readline("user> ") + repl@ while(true) { + print("user> ") try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + val line = readLine() ?: continue@repl + if (line.trim() == "quit") { + println("Bye!") + break@repl + } + println(rep(line)) + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) + e.printStackTrace() + eval_count = 0 } } } + diff --git a/kotlin/src/mal/step7_quote.kt b/kotlin/src/mal/step7_quote.kt index b8d4ab46..d1ae46d8 100644 --- a/kotlin/src/mal/step7_quote.kt +++ b/kotlin/src/mal/step7_quote.kt @@ -1,148 +1,195 @@ -package mal - -import java.util.* - -fun read(input: String?): MalType = read_str(input) - -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env - - while (true) { - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") - - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) - } +fun READ(s: String) = read_str(s) - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() - } - "if" -> { - val check = eval(ast.nth(1), env) - - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL - } - "quote" -> return ast.nth(1) - "quasiquote" -> ast = quasiquote(ast.nth(1)) - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } - } - } - } else return eval_ast(ast, env) +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalSymbol -> env.get(ast) + else -> ast } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } - -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) - - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env } -private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any() +fun is_true(cond: MalType) = + when (cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true + } -private fun quasiquote(ast: MalType): MalType { - if (!is_pair(ast)) { - val quoted = MalList() - quoted.conj_BANG(MalSymbol("quote")) - quoted.conj_BANG(ast) - return quoted +// First implement a helper function `is_pair` that returns true if the +// parameter is a non-empty list. +fun is_pair(p: MalType) = p is MalList && p.size > 0 + +fun quasiquote(ast: MalType) : MalType { + return if (ast is MalList && ast.size > 0) { + val fst = ast.head() + val rest = ast.tail() + if (fst == malSym("unquote")) + rest.head() + else if (fst is MalList && fst.head() == malSym("splice-unquote")) + malListOf(malSym("concat"), fst.tail().head(), quasiquote(rest)) + else + malListOf(malSym("cons"), quasiquote(fst), quasiquote(rest)) + } + else { + malListOf(malSym("quote"), ast) } +} - val seq = ast as ISeq - var first = seq.first() +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { +// val say = { m: String -> println(" ".repeat(depth) + m) } + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while (true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw Exception("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - if ((first as? MalSymbol)?.value == "unquote") { - return seq.nth(1) - } + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { +// say(pr_str(ast[0])) + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + val name = (rest[0] as MalSymbol) + if (v is MalUserFunc) { + v.fn.name = name.sym + } + return env.set(name, v) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalSeq), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.last() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else ast + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalSeq + val body = rest[1] + val func = malFun("funccall") { + EVAL(body, Env(env, binds, it), n) + } + return MalUserFunc(body, binds, env, func) + } + "quote" -> { + return rest.head() + } + // This is called from `EVAL` with the first `ast` argument (second list element) and then `ast` is set to the result and execution continues at the top of the loop (TCO). + "quasiquote" -> { + // TODO TCO + // XXX Support >1 args? + ast = quasiquote(rest[0]) + continue@eval_loop // TCO + } + } + } - if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") { - val spliced = MalList() - spliced.conj_BANG(MalSymbol("concat")) - spliced.conj_BANG(first.nth(1)) - spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return spliced + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() + + if(func is MalUserFunc) { + // set ast to the ast attribute of f. + // Generate a new environment using the env and params attributes of f as the outer and binds arguments and rest ast arguments (list elements 2 through the end) as the exprs argument. Set env to the new environment. Continue at the beginning of the loop. + ast = func.ast + val binds = func.params.atoms.mapIndexed { index, atom -> + val k = atom as MalSymbol + val v = args[index] + listOf(k,v) + }.flatten().let { MalList(it) } + env = make_env(binds, func.env, depth) + continue@eval_loop // TCO + } + else if(func is MalFunc) { + return func(args) + } + else { + return func +// throw Exception("Don't know what to do with " + func) + } + } + } } - - val consed = MalList() - consed.conj_BANG(MalSymbol("cons")) - consed.conj_BANG(quasiquote(ast.first())) - consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return consed } -fun print(result: MalType) = pr_str(result, print_readably = true) - -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) +fun PRINT(v: MalType) = pr_str(v) -fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) - repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) }).toCollection(LinkedList()))) - repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) })) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } +} - 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) +fun main(args: Array) { + repl_env.set(malSym("eval"), malFun("eval") { + val res = eval_ast(it, repl_env, 0) + when(res) { + is MalList -> res.last() + else -> res + } + }) + repl_env.set(malSym("*ARGV*"), malListOf(args.map(::MalString))) - if (args.any()) { - rep("(load-file \"${args[0]}\")", repl_env) - return - } + rep("""(def! load-file (fn* [f] (eval (read-string (str "(do " (slurp f) ")")))))""") + rep("(def! not (fn* [v] (if v false true)))") - while (true) { - val input = readline("user> ") + repl@ while(true) { + print("user> ") try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + val line = readLine() ?: continue@repl + if (setOf("quit","exit").contains(line.trim())) { + println("Bye!") + break@repl + } + println(rep(line)) + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) + e.printStackTrace() + eval_count = 0 } } } + diff --git a/kotlin/src/mal/step8_macros.kt b/kotlin/src/mal/step8_macros.kt index 929ccfb2..eda82443 100644 --- a/kotlin/src/mal/step8_macros.kt +++ b/kotlin/src/mal/step8_macros.kt @@ -1,180 +1,238 @@ -package mal - -import java.util.* - -fun read(input: String?): MalType = read_str(input) - -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env - - while (true) { - ast = macroexpand(ast, env) - - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") - - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) - } - - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() - } - "if" -> { - val check = eval(ast.nth(1), env) - - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL - } - "quote" -> return ast.nth(1) - "quasiquote" -> ast = quasiquote(ast.nth(1)) - "defmacro!" -> return defmacro(ast, env) - "macroexpand" -> return macroexpand(ast.nth(1), env) - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } - } - } - } else return eval_ast(ast, env) +fun READ(s: String) = read_str(s) + +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env.get(ast) + else -> ast } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } - -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) - - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env } -private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any() - -private fun quasiquote(ast: MalType): MalType { - if (!is_pair(ast)) { - val quoted = MalList() - quoted.conj_BANG(MalSymbol("quote")) - quoted.conj_BANG(ast) - return quoted +fun is_true(cond: MalType) = + when (cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true } - val seq = ast as ISeq - var first = seq.first() - - if ((first as? MalSymbol)?.value == "unquote") { - return seq.nth(1) +// Not implemented as safe-casing is gooder. +// fun is_pair(p: MalType) = p is MalList && p.size > 0 + +fun quasiquote(ast: MalType) : MalType { + return if (ast is MalList && ast.size > 0) { + val fst = ast.head() + val rest = ast.tail() + if (fst == malSym("unquote")) + rest.head() + else if (fst is MalList && fst.head() == malSym("splice-unquote")) + malListOf(malSym("concat"), fst.tail().head(), quasiquote(rest)) + else + malListOf(malSym("cons"), quasiquote(fst), quasiquote(rest)) } - - if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") { - val spliced = MalList() - spliced.conj_BANG(MalSymbol("concat")) - spliced.conj_BANG(first.nth(1)) - spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return spliced + else { + malListOf(malSym("quote"), ast) } - - val consed = MalList() - consed.conj_BANG(MalSymbol("cons")) - consed.conj_BANG(quasiquote(ast.first())) - consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return consed } -private fun is_macro_call(ast: MalType, env: Env): Boolean { - val ast_list = ast as? MalList ?: return false - if (ast_list.count() == 0) return false - val symbol = ast_list.first() as? MalSymbol ?: return false - val function = env.find(symbol) as? MalFunction ?: return false - - return function.is_macro +fun is_macro_call(ast: MalType, env: Env) : Boolean { + if(ast is MalList && ast[0] is MalSymbol) { + val sym = ast[0] as MalSymbol + val e = env.find(sym) ?: return false + val v = e.get(sym) + return v is MalCallable && v.isMacro + } + else { + return false + } } -private fun macroexpand(_ast: MalType, env: Env): MalType { - var ast = _ast - while (is_macro_call(ast, env)) { - val symbol = (ast as MalList).first() as MalSymbol - val function = env.find(symbol) as MalFunction - ast = function.apply(ast.rest()) +fun macroexpand(orig_ast: MalType, env: Env) : MalType { + var ast = orig_ast + while(is_macro_call(ast, env)) { + val l = ast as MalList + // Inside the loop, the first element of the ast list (a symbol), is looked up in the environment to get the macro function + val f = env.get(l.head() as MalSymbol) as MalUserFunc + // This macro function is then called/applied with the rest of the ast elements (2nd through the last) as arguments. + // The return value of the macro call becomes the new value of ast. + ast = f(l.tail()) } return ast } -private fun defmacro(ast: MalList, env: Env): MalType { - val macro = eval(ast.nth(2), env) as MalFunction - macro.is_macro = true +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { +// val say = { m: String -> println(" ".repeat(depth) + m) } + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while (true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw Exception("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - return env.set(ast.nth(1) as MalSymbol, macro) + ast = macroexpand(ast, env) + + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + val name = (rest[0] as MalSymbol) + if (v is MalUserFunc) { + v.name = name.sym + } + return env.set(name, v) + } + "defmacro!" -> { + val v = EVAL(rest[1], env, n) + if (v !is MalUserFunc) { + throw Exception("Can't defmacro! with a: "+pr_str(v)) + } + else { + v.isMacro = true + } + val name = (rest[0] as MalSymbol) + return env.set(name, v) + + } + "macroexpand" -> { + return macroexpand(rest[0], env) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalSeq), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.last() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else ast + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalSeq + val body = rest[1] +// val func = malFun("funccall") + return MalUserFunc(body, binds, env) { + EVAL(body, Env(env, binds, it), n) + } + } + "quote" -> { + return rest.head() + } + // This is called from `EVAL` with the first `ast` argument (second list element) and then `ast` is set to the result and execution continues at the top of the loop (TCO). + "quasiquote" -> { + // TODO TCO + // XXX Support >1 args? + ast = quasiquote(rest[0]) + continue@eval_loop // TCO + } + } + } + + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() + + if(func is MalUserFunc) { + ast = func.ast + env = Env(func.env, func.params, args) + continue@eval_loop // TCO + } + else if(func is MalFunc) { + return func(args) + } + else { + return func +// throw Exception("Don't know what to do with " + func) + } + } + } + } } -fun print(result: MalType) = pr_str(result, print_readably = true) +fun PRINT(v: MalType) = pr_str(v) -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) -fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } +} - repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) }).toCollection(LinkedList()))) - repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) })) +fun main(args: Array) { + repl_env.set(malSym("eval"), malFun("eval") { + val res = eval_ast(it, repl_env, 0) + when(res) { + is MalList -> res.last() + else -> res + } + }) - 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) + rep("""(def! load-file (fn* [f] (eval (read-string (str "(do " (slurp f) ")")))))""") + rep("""(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))""") + rep("""(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))""") + rep("(def! not (fn* [v] (if v false true)))") - if (args.any()) { - rep("(load-file \"${args[0]}\")", repl_env) - return + if(args.size > 0) { + repl_env.set(malSym("*ARGV*"), malListOf(args.drop(1).map(::MalString))) + rep("""(load-file "${args[0]}")""") } - - while (true) { - val input = readline("user> ") - - try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + else { + repl_env.set(malSym("*ARGV*"), emptyMalList()) + repl@ while(true) { + print("user> ") + + try { + val line = readLine() ?: continue@repl + if (setOf("quit","exit").contains(line.trim())) { + println("Bye!") + break@repl + } + println(rep(line)) + } + catch(e: Exception) { + println("Oh dear:" + e.toString()) + e.printStackTrace() + eval_count = 0 + } } } } + diff --git a/kotlin/src/mal/step9_try.kt b/kotlin/src/mal/step9_try.kt index 03d44f4e..913557df 100644 --- a/kotlin/src/mal/step9_try.kt +++ b/kotlin/src/mal/step9_try.kt @@ -1,196 +1,268 @@ -package mal - -import java.util.* - -fun read(input: String?): MalType = read_str(input) - -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env - - while (true) { - ast = macroexpand(ast, env) - - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") - - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) - } - - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() - } - "if" -> { - val check = eval(ast.nth(1), env) - - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL - } - "quote" -> return ast.nth(1) - "quasiquote" -> ast = quasiquote(ast.nth(1)) - "defmacro!" -> return defmacro(ast, env) - "macroexpand" -> return macroexpand(ast.nth(1), env) - "try*" -> return try_catch(ast, env) - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } - } - } - } else return eval_ast(ast, env) +fun READ(s: String) = read_str(s) + +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env.get(ast) + else -> ast } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } - -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) - - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env } -private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any() - -private fun quasiquote(ast: MalType): MalType { - if (!is_pair(ast)) { - val quoted = MalList() - quoted.conj_BANG(MalSymbol("quote")) - quoted.conj_BANG(ast) - return quoted +fun is_true(cond: MalType) = + when (cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true } - val seq = ast as ISeq - var first = seq.first() - - if ((first as? MalSymbol)?.value == "unquote") { - return seq.nth(1) +// Not implemented as safe-casing is gooder. +// fun is_pair(p: MalType) = p is MalList && p.size > 0 + +fun quasiquote(ast: MalType) : MalType { + return if (ast is MalList && ast.size > 0) { + val fst = ast.head() + val rest = ast.tail() + if (fst == malSym("unquote")) + rest.head() + else if (fst is MalList && fst.head() == malSym("splice-unquote")) + malListOf(malSym("concat"), fst.tail().head(), quasiquote(rest)) + else + malListOf(malSym("cons"), quasiquote(fst), quasiquote(rest)) } - - if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") { - val spliced = MalList() - spliced.conj_BANG(MalSymbol("concat")) - spliced.conj_BANG(first.nth(1)) - spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return spliced + else { + malListOf(malSym("quote"), ast) } - - val consed = MalList() - consed.conj_BANG(MalSymbol("cons")) - consed.conj_BANG(quasiquote(ast.first())) - consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return consed } -private fun is_macro_call(ast: MalType, env: Env): Boolean { - val ast_list = ast as? MalList ?: return false - if (ast_list.count() == 0) return false - val symbol = ast_list.first() as? MalSymbol ?: return false - val function = env.find(symbol) as? MalFunction ?: return false - - return function.is_macro +fun is_macro_call(ast: MalType, env: Env) : Boolean { + if(ast is MalList && ast[0] is MalSymbol) { + val sym = ast[0] as MalSymbol + val e = env.find(sym) ?: return false + val v = e.get(sym) + return v is MalCallable && v.isMacro + } + else { + return false + } } -private fun macroexpand(_ast: MalType, env: Env): MalType { - var ast = _ast - while (is_macro_call(ast, env)) { - val symbol = (ast as MalList).first() as MalSymbol - val function = env.find(symbol) as MalFunction - ast = function.apply(ast.rest()) +fun macroexpand(orig_ast: MalType, env: Env) : MalType { + var ast = orig_ast + while(is_macro_call(ast, env)) { + val l = ast as MalList + // Inside the loop, the first element of the ast list (a symbol), is looked up in the environment to get the macro function + val f = env.get(l.head() as MalSymbol) as MalUserFunc + // This macro function is then called/applied with the rest of the ast elements (2nd through the last) as arguments. + // The return value of the macro call becomes the new value of ast. + ast = f(l.tail()) } return ast } -private fun defmacro(ast: MalList, env: Env): MalType { - val macro = eval(ast.nth(2), env) as MalFunction - macro.is_macro = true +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { +// val say = { m: String -> println(" ".repeat(depth) + m) } + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while (true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw MalCoreEx("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - return env.set(ast.nth(1) as MalSymbol, macro) -} + ast = macroexpand(ast, env) -private fun try_catch(ast: MalList, env: Env): MalType = - try { - eval(ast.nth(1), env) - } catch (e: Exception) { - if (ast.count() < 3) { throw e } - val thrown = if (e is MalException) e else MalException(e.message) - val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + val name = (rest[0] as MalSymbol) + if (v is MalUserFunc) { + v.name = name.sym + } + return env.set(name, v) + } + "defmacro!" -> { + val v = EVAL(rest[1], env, n) + if (v !is MalUserFunc) { + throw MalCoreEx("Can't defmacro! with a: "+pr_str(v)) + } + else { + v.isMacro = true + } + val name = (rest[0] as MalSymbol) + return env.set(name, v) + + } + "macroexpand" -> { + return macroexpand(rest[0], env) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalSeq), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.last() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else ast + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalSeq + val body = rest[1] +// val func = malFun("funccall") + return MalUserFunc(body, binds, env) { + EVAL(body, Env(env, binds, it), n) + } + } + "quote" -> { + return rest.head() + } + // This is called from `EVAL` with the first `ast` argument (second list element) and then `ast` is set to the result and execution continues at the top of the loop (TCO). + "quasiquote" -> { + // TODO TCO + // XXX Support >1 args? + ast = quasiquote(rest[0]) + continue@eval_loop // TCO + } + "try*" -> { + val body = rest[0] + return try { + EVAL(body, env, n) + } + catch(e: MalUserEx) { + if(rest.size > 1) { + val catchSeq = rest[1] as MalSeq + val catchSym = catchSeq[0] + if (catchSym != malSym("catch*")) + throw MalCoreEx("Expected 'catch*', got '${pr_str(catchSym)}'") + val exBind = catchSeq[1] as MalSymbol + val catchBody = catchSeq[2] + val catchEnv = Env(env, malListOf(exBind), malListOf(e.src)) + EVAL(catchBody, catchEnv, n) + } + else { + // Throw it up if nothing can catch it + throw e + } + } + } + } + } - val catchBody = (ast.nth(2) as MalList).nth(2) - val catchEnv = Env(env) - catchEnv.set(symbol, thrown) + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() - eval(catchBody, catchEnv) + if(func is MalUserFunc) { + ast = func.ast + env = Env(func.env, func.params, args) + continue@eval_loop // TCO + } + else if(func is MalFunc) { + return func(args) + } + else { + return func +// throw MalCoreEx("Don't know what to do with " + func) + } + } + } } +} -fun print(result: MalType) = pr_str(result, print_readably = true) +fun PRINT(v: MalType) = pr_str(v) -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) -fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } +} - repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) }).toCollection(LinkedList()))) - repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) })) +fun main(args: Array) { + repl_env.set(malSym("eval"), malFun("eval") { + val res = eval_ast(it, repl_env, 0) + when(res) { + is MalList -> res.last() + else -> res + } + }) - 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) + rep("""(def! load-file (fn* [f] (eval (read-string (str "(do " (slurp f) ")")))))""") + rep("""(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))""") + rep("""(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))""") + rep("(def! not (fn* [v] (if v false true)))") - if (args.any()) { - rep("(load-file \"${args[0]}\")", repl_env) - return + if(args.size > 0) { + repl_env.set(malSym("*ARGV*"), malListOf(args.drop(1).map(::MalString))) + rep("""(load-file "${args[0]}")""") } - - while (true) { - val input = readline("user> ") - - try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + else { + repl_env.set(malSym("*ARGV*"), emptyMalList()) + repl@ while(true) { + print("user> ") + + try { + val line = readLine() ?: continue@repl + if (setOf("quit","exit").contains(line.trim())) { + println("Bye!") + break@repl + } + println(rep(line)) + } + catch(e: Throwable) { + println( + when(e) { + is MalUserEx -> "Exception raised: " + pr_str(e.src) + is MalCoreEx -> "Error encountered: " + e.msg + else -> "Non mal exception: " + e.message + } + ) + e.printStackTrace() + } + finally { + eval_count = 0 + } } } } + diff --git a/kotlin/src/mal/stepA_mal.kt b/kotlin/src/mal/stepA_mal.kt index 5bf73632..ee39a838 100644 --- a/kotlin/src/mal/stepA_mal.kt +++ b/kotlin/src/mal/stepA_mal.kt @@ -1,199 +1,301 @@ -package mal - -import java.util.* - -fun read(input: String?): MalType = read_str(input) - -fun eval(_ast: MalType, _env: Env): MalType { - var ast = _ast - var env = _env - - while (true) { - ast = macroexpand(ast, env) - - if (ast is MalList) { - if (ast.count() == 0) return ast - when ((ast.first() as? MalSymbol)?.value) { - "def!" -> return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) - "let*" -> { - val childEnv = Env(env) - val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") - - val it = bindings.seq().iterator() - while (it.hasNext()) { - val key = it.next() - if (!it.hasNext()) throw MalException("odd number of binding elements in let*") - childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) - } - - env = childEnv - ast = ast.nth(2) - } - "fn*" -> return fn_STAR(ast, env) - "do" -> { - eval_ast(ast.slice(1, ast.count() - 1), env) - ast = ast.seq().last() - } - "if" -> { - val check = eval(ast.nth(1), env) - - if (check !== NIL && check !== FALSE) { - ast = ast.nth(2) - } else if (ast.count() > 3) { - ast = ast.nth(3) - } else return NIL - } - "quote" -> return ast.nth(1) - "quasiquote" -> ast = quasiquote(ast.nth(1)) - "defmacro!" -> return defmacro(ast, env) - "macroexpand" -> return macroexpand(ast.nth(1), env) - "try*" -> return try_catch(ast, env) - else -> { - val evaluated = eval_ast(ast, env) as ISeq - val firstEval = evaluated.first() - - when (firstEval) { - is MalFnFunction -> { - ast = firstEval.ast - env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) - } - is MalFunction -> return firstEval.apply(evaluated.rest()) - else -> throw MalException("cannot execute non-function") - } - } - } - } else return eval_ast(ast, env) +fun READ(s: String) = read_str(s) + +fun eval_ast(ast: MalType, env: Env, depth: Int) : MalType { + return when(ast) { + is MalList -> MalList(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalVector -> MalVector(ast.atoms.map { EVAL(it, env, depth + 1) }.toList()) + is MalMap -> malMapOf(ast.pairs.map { (k,v) -> k to EVAL(v, env, depth + 1) }) + is MalSymbol -> env.get(ast) + else -> ast } } -fun eval_ast(ast: MalType, env: Env): MalType = - when (ast) { - is MalSymbol -> env.get(ast) - is MalList -> ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalVector -> ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) - is MalHashMap -> ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) - else -> ast - } - -private fun fn_STAR(ast: MalList, env: Env): MalType { - val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") - val params = binds.seq().filterIsInstance() - val body = ast.nth(2) - - return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) +fun make_env(pairs: MalSeq, outer_env: Env, depth: Int) : Env { + val new_env = Env(outer_env) + for (idx in pairs.atoms.indices step 2) { + val k = pairs.atoms[idx] as MalSymbol + val v = pairs.atoms[idx + 1] + new_env.set(k, EVAL(v, new_env, depth + 1)) + } + return new_env } -private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any() - -private fun quasiquote(ast: MalType): MalType { - if (!is_pair(ast)) { - val quoted = MalList() - quoted.conj_BANG(MalSymbol("quote")) - quoted.conj_BANG(ast) - return quoted +fun is_true(cond: MalType) = + when (cond) { + is MalNil -> false + is MalBoolean -> cond.bool + else -> true } - val seq = ast as ISeq - var first = seq.first() - - if ((first as? MalSymbol)?.value == "unquote") { - return seq.nth(1) +// Not implemented as safe-casing is gooder. +// fun is_pair(p: MalType) = p is MalList && p.size > 0 + +fun quasiquote(ast: MalType) : MalType { + return if (ast is MalList && ast.size > 0) { + val fst = ast.head() + val rest = ast.tail() + if (fst == malSym("unquote")) + rest.head() + else if (fst is MalList && fst.head() == malSym("splice-unquote")) + malListOf(malSym("concat"), fst.tail().head(), quasiquote(rest)) + else + malListOf(malSym("cons"), quasiquote(fst), quasiquote(rest)) } - - if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") { - val spliced = MalList() - spliced.conj_BANG(MalSymbol("concat")) - spliced.conj_BANG(first.nth(1)) - spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return spliced + else { + malListOf(malSym("quote"), ast) } - - val consed = MalList() - consed.conj_BANG(MalSymbol("cons")) - consed.conj_BANG(quasiquote(ast.first())) - consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toCollection(LinkedList())))) - return consed } -private fun is_macro_call(ast: MalType, env: Env): Boolean { - val ast_list = ast as? MalList ?: return false - if (ast_list.count() == 0) return false - val symbol = ast_list.first() as? MalSymbol ?: return false - val function = env.find(symbol) as? MalFunction ?: return false - - return function.is_macro +fun is_macro_call(ast: MalType, env: Env) : Boolean { + if(ast is MalList && ast.size > 0 && ast[0] is MalSymbol) { + val sym = ast[0] as MalSymbol + val e = env.find(sym) ?: return false + val v = e.get(sym) + return v is MalCallable && v.isMacro + } + else { + return false + } } -private fun macroexpand(_ast: MalType, env: Env): MalType { - var ast = _ast - while (is_macro_call(ast, env)) { - val symbol = (ast as MalList).first() as MalSymbol - val function = env.find(symbol) as MalFunction - ast = function.apply(ast.rest()) +fun macroexpand(orig_ast: MalType, env: Env) : MalType { + var ast = orig_ast + while(is_macro_call(ast, env)) { + val l = ast as MalList + // Inside the loop, the first element of the ast list (a symbol), is looked up in the environment to get the macro function + val f = env.get(l.head() as MalSymbol) as MalUserFunc + // This macro function is then called/applied with the rest of the ast elements (2nd through the last) as arguments. + // The return value of the macro call becomes the new value of ast. + ast = f(l.tail()) } return ast } -private fun defmacro(ast: MalList, env: Env): MalType { - val macro = eval(ast.nth(2), env) as MalFunction - macro.is_macro = true +var eval_count = 0 +fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType { +// val say = { m: String -> println(" ".repeat(depth) + m) } + // Only use n when recursing into EVAL + val n = depth + 1 + // Allow modification of where in the ast is being evaluated. + var ast = cur_ast + // Allow modification of which env is pointed at while evaluating. + var env = cur_env + eval_loop@ while (true) { + eval_count++ + // The depth check is low enough so as not to hit a terminal StackOverflow error. + // But eval_count is just an arbitrary limit. + if (depth > 1012 || eval_count > 654321) { + throw MalCoreEx("Recursion/eval limit hit: depth ${depth} / evalled ${eval_count}!") + } - return env.set(ast.nth(1) as MalSymbol, macro) -} + ast = macroexpand(ast, env) -private fun try_catch(ast: MalList, env: Env): MalType = - try { - eval(ast.nth(1), env) - } catch (e: Exception) { - if (ast.count() < 3) { throw e } - val thrown = if (e is MalException) e else MalException(e.message) - val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol + if (ast !is MalList) { + return eval_ast(ast, env, depth) + } + else { + if (ast.atoms.isEmpty()) { + return ast + } + else { + val next = ast.head() + val rest = ast.tail() + if(next is MalSymbol) { + when(next.sym) { + "def!" -> { + val v = EVAL(rest[1], env, n) + val name = (rest[0] as MalSymbol) + if (v is MalUserFunc) { + v.name = name.sym + } + return env.set(name, v) + } + "defmacro!" -> { + val v = EVAL(rest[1], env, n) + if (v !is MalUserFunc) { + throw MalCoreEx("Can't defmacro! with a: "+pr_str(v)) + } + else { + v.isMacro = true + } + val name = (rest[0] as MalSymbol) + return env.set(name, v) + + } + "macroexpand" -> { + return macroexpand(rest[0], env) + } + "let*" -> { + // Set env (i.e. the local variable passed in as second parameter of EVAL) to the new let environment. + env = make_env((rest[0] as MalSeq), env, depth) + // Set ast (i.e. the local variable passed in as first parameter of EVAL) to be the second ast argument. + ast = ast[2] + continue@eval_loop // TCO + } + "do" -> { + // change the eval_ast call to evaluate all the parameters except for the last (2nd list element up to but not including last). + eval_ast(rest.butlast(), env, depth) + // Set ast to the last element of ast. Continue at the beginning of the loop (env stays unchanged). + ast = rest.last() + continue@eval_loop // TCO + } + "if" -> { + // the condition continues to be evaluated, however, rather than evaluating the true or false branch, ast is set to the unevaluated value of the chosen branch. + ast = if(is_true(EVAL(rest[0], env, n))) rest[1] else + if(rest.atoms.count() == 3) rest[2] else MalNil() + continue@eval_loop // TCO + } + "fn*" -> { + // The return value from the fn* special form will now become an object/structure with attributes that allow the default invoke case of EVAL to do TCO on mal functions. Those attributes are: + val binds = rest[0] as MalSeq + val body = rest[1] + return MalUserFunc(body, binds, env, "anon", MalNil()) { + EVAL(body, Env(env, binds, it), n) + } + } + "quote" -> { + return rest.head() + } + // This is called from `EVAL` with the first `ast` argument (second list element) and then `ast` is set to the result and execution continues at the top of the loop (TCO). + "quasiquote" -> { + // TODO TCO + // XXX Support >1 args? + ast = quasiquote(rest[0]) + continue@eval_loop // TCO + } + "try*" -> { + val body = rest[0] + return try { + EVAL(body, env, n) + } + catch(e: MalUserEx) { + if(rest.size > 1) { + val catchSeq = rest[1] as MalSeq + val catchSym = catchSeq[0] + if (catchSym != malSym("catch*")) + throw MalCoreEx("Expected 'catch*', got '${pr_str(catchSym)}'") + val exBind = catchSeq[1] as MalSymbol + val catchBody = catchSeq[2] + val catchEnv = Env(env, malListOf(exBind), malListOf(e.src)) + EVAL(catchBody, catchEnv, n) + } + else { + // Throw it up if nothing can catch it + throw e + } + } + } + } + } - val catchBody = (ast.nth(2) as MalList).nth(2) - val catchEnv = Env(env) - catchEnv.set(symbol, thrown) + val op = eval_ast(ast, env, depth) as MalList + val func = op.head() + val args = op.tail() - eval(catchBody, catchEnv) + if(func is MalUserFunc) { + ast = func.ast + env = Env(func.env, func.params, args) + continue@eval_loop // TCO + } + else if(func is MalFunc) { + return func(args) + } + else { + return func +// throw MalCoreEx("Don't know what to do with " + func) + } + } + } } +} -fun print(result: MalType) = pr_str(result, print_readably = true) +fun PRINT(v: MalType) = pr_str(v) -fun rep(input: String, env: Env): String = - print(eval(read(input), env)) +fun rep(s: String) = PRINT(EVAL(READ(s), repl_env, 0)) + +val repl_env = Env().apply { + core.ns.forEach { (k,v) -> set(k, v) } +} fun main(args: Array) { - val repl_env = Env() - ns.forEach({ it -> repl_env.set(it.key, it.value) }) - - repl_env.set(MalSymbol("*host-language*"), MalString("kotlin")) - repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) }).toCollection(LinkedList()))) - repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), 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.any()) { - rep("(load-file \"${args[0]}\")", repl_env) - return + repl_env.set(malSym("eval"), malFun("eval") { + val res = eval_ast(it, repl_env, 0) + when(res) { + is MalList -> res.last() + else -> res + } + }) + repl_env.set(malSym("*host-language*"), MalString("Kotlin")) + + rep("""(do +(def! *gensym-counter* (atom 0)) + +(def! gensym + (fn* [] + (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x))))))) + +(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))))))))) + +(def! load-file + (fn* [f] + (eval (read-string (str "(do " (slurp f) ")"))))) + +(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))))))) + +(def! not + (fn* [v] + (if v false true))) +)""") + rep("") + + if(args.size > 0) { + repl_env.set(malSym("*ARGV*"), malListOf(args.drop(1).map(::MalString))) + rep("""(load-file "${args[0]}")""") } - - rep("(println (str \"Mal [\" *host-language* \"]\"))", repl_env) - while (true) { - val input = readline("user> ") - try { - println(rep(input, repl_env)) - } catch (e: EofException) { - break - } catch (e: MalContinue) { - } catch (e: MalException) { - println("Error: " + e.message) - } catch (t: Throwable) { - println("Uncaught " + t + ": " + t.message) - t.printStackTrace() + else { + repl_env.set(malSym("*ARGV*"), emptyMalList()) + rep("""(println (str "Mal [" *host-language* "]"))""") + repl@ while(true) { + print("user> ") + + try { + val line = readLine() ?: break@repl + if (setOf("quit", "exit").contains(line.trim())) { + println("Bye!") + break@repl + } + println(rep(line)) + } + catch(e: Throwable) { + println( + when(e) { + is MalUserEx -> "Exception raised: " + pr_str(e.src) + is MalCoreEx -> "Error encountered: " + e.msg + else -> "Non mal exception: " + e.message + } + ) + e.printStackTrace() + } + finally { + eval_count = 0 + } } } } + diff --git a/kotlin/src/mal/types.kt b/kotlin/src/mal/types.kt index a981f4ec..f9b98616 100644 --- a/kotlin/src/mal/types.kt +++ b/kotlin/src/mal/types.kt @@ -1,222 +1,102 @@ -package mal +// If your target language is statically typed then you will need some +// way for read_form to return a variant or subclass type. For example, +// if your language is object oriented, then you can define a top level +// MalType (in types.qx) that all your mal data types inherit from. The +// MalList type (which also inherits from MalType) will contain a +// list/array of other MalTypes. If your language is dynamically typed +// then you can likely just return a plain list/array of other mal types. -import java.util.* +interface MalType -open class MalException(message: String?) : Exception(message), MalType { - override var metadata: MalType = NIL - override fun with_meta(meta: MalType): MalType { - val exception = MalException(message) - exception.metadata = meta - return exception - } -} +interface MalAtom : MalType -class MalContinue() : MalException("continue") -class MalReaderException(message: String) : MalException(message) -class MalPrinterException(message: String) : MalException(message) +data class MalNumber(val num: Int) : MalAtom -class MalCoreException(message: String, val value: MalType) : MalException(message) { - override fun with_meta(meta: MalType): MalType { - val exception = MalCoreException(message as String, value) - exception.metadata = meta - return exception - } -} +data class MalSymbol(val sym: String) : MalAtom -interface MalType { - var metadata: MalType - fun with_meta(meta: MalType): MalType -} +data class MalBoolean(val bool: Boolean) : MalAtom -open class MalConstant(val value: String) : MalType { - override var metadata: MalType = NIL +interface MalKey : MalType - override fun equals(other: Any?): Boolean = other is MalConstant && value.equals(other.value) - override fun hashCode(): Int = value.hashCode() - - override fun with_meta(meta: MalType): MalType { - val obj = MalConstant(value) - obj.metadata = meta - return obj - } -} +data class MalString(val str: String) : MalKey +data class MalKeyword(val kw: String) : MalKey -class MalInteger(val value: Long) : MalType { - override var metadata: MalType = NIL +// Would use MalAtom but that's already a thing :/ +data class MalCljAtom(var value : MalType) : MalType - operator fun plus(a: MalInteger): MalInteger = MalInteger(value + a.value) - operator fun minus(a: MalInteger): MalInteger = MalInteger(value - a.value) - operator fun times(a: MalInteger): MalInteger = MalInteger(value * a.value) - operator fun div(a: MalInteger): MalInteger = MalInteger(value / a.value) - operator fun compareTo(a: MalInteger): Int = value.compareTo(a.value) +class MalNil : MalAtom - override fun equals(other: Any?): Boolean = other is MalInteger && value.equals(other.value) - - override fun with_meta(meta: MalType): MalType { - val obj = MalInteger(value) - obj.metadata = meta - return obj - } +interface MalMeta { + val meta: MalType + fun withMeta(m: MalType): MalType } -class MalSymbol(val value: String) : MalType { - override var metadata: MalType = NIL - - override fun equals(other: Any?): Boolean = other is MalSymbol && value.equals(other.value) +interface MalSeq : MalMeta, MalType { + val atoms: List - override fun with_meta(meta: MalType): MalType { - val obj = MalSymbol(value) - obj.metadata = meta - return obj - } -} + val size: Int get() = atoms.size + fun head() = atoms[0] + fun tail() = MalList(atoms.slice(1 .. atoms.size - 1)) + fun last() = atoms.last() + fun butlast() = MalList(atoms.slice(0 .. atoms.size - 2)) -open class MalString(value: String) : MalConstant(value) { - override fun with_meta(meta: MalType): MalType { - val obj = MalString(value) - obj.metadata = meta - return obj - } + operator fun get(index: Int): MalType = atoms[index] + operator fun get(index: MalNumber): MalType = atoms[index.num] + // TODO Maybe implement complementN too? } -class MalKeyword(value: String) : MalString("\u029E" + value) { - override fun with_meta(meta: MalType): MalType { - val obj = MalKeyword(value) - obj.metadata = meta - return obj - } +data class MalList(override val atoms: List, override val meta: MalType = MalNil()) : MalSeq { + // XXX Not technically a copy ... + override fun withMeta(m: MalType) = + MalList(atoms, m) } - -interface ILambda : MalType { - fun apply(seq: ISeq): MalType +data class MalVector(override val atoms: List, override val meta: MalType = MalNil()) : MalSeq { + // XXX Not technically a copy ... + override fun withMeta(m: MalType) = + MalVector(atoms, m) } -open class MalFunction(val lambda: (ISeq) -> MalType) : MalType, ILambda { - var is_macro: Boolean = false - override var metadata: MalType = NIL +class MalMap(val pairs: Map, override val meta: MalType = MalNil()) : MalMeta, MalType { + operator fun get(k: MalKey): MalType = pairs[k] ?: MalNil() - override fun apply(seq: ISeq): MalType = lambda(seq) - - override fun with_meta(meta: MalType): MalType { - val obj = MalFunction(lambda) - obj.metadata = meta - return obj - } + // XXX Not technically a copy ... + override fun withMeta(m: MalType) = + MalMap(pairs, m) } -class MalFnFunction(val ast: MalType, val params: Sequence, val env: Env, lambda: (ISeq) -> MalType) : MalFunction(lambda) { - override fun with_meta(meta: MalType): MalType { - val obj = MalFnFunction(ast, params, env, lambda) - obj.metadata = meta - return obj - } -} +typealias MalFn = (MalSeq) -> MalType -interface ISeq : MalType { - fun seq(): Sequence - fun first(): MalType - fun rest(): ISeq - fun nth(n: Int): MalType - fun count(): Int - fun slice(fromIndex: Int, toIndex: Int): ISeq - fun conj(s: ISeq): ISeq -} +abstract class MalCallable(val func: MalFn, var name: String, override val meta: MalType) : MalMeta, MalType { + var isMacro = false -interface IMutableSeq : ISeq { - fun conj_BANG(form: MalType) + operator fun invoke(args: MalSeq) = func(args) } -abstract class MalSequence(val elements: MutableList) : MalType, IMutableSeq { - override var metadata: MalType = NIL - - override fun seq(): Sequence = elements.asSequence() - override fun first(): MalType = elements.first() - override fun nth(n: Int): MalType = elements.elementAt(n) - override fun count(): Int = elements.count() - - override fun conj_BANG(form: MalType) { - elements.add(form) - } - - override fun equals(other: Any?): Boolean = - (other is ISeq) - && elements.size == other.count() - && elements.asSequence().zip(other.seq()).all({ it -> it.first == it.second }) +// Allow name to be set after the fact so functions in Env are named. +class MalFunc(func: MalFn, name: String = "anon", meta: MalType = MalNil()) : MalCallable(func, name, meta) { + override fun withMeta(m: MalType) = + MalFunc(func, name, m) } -class MalList(elements: MutableList) : MalSequence(elements) { - constructor() : this(LinkedList()) - constructor(s: ISeq) : this(s.seq().toCollection(LinkedList())) - - override fun rest(): ISeq = MalList(elements.drop(1).toCollection(LinkedList())) - - override fun slice(fromIndex: Int, toIndex: Int): MalList = - MalList(elements.subList(fromIndex, toIndex)) - - override fun conj(s: ISeq): ISeq { - val list = LinkedList(elements) - s.seq().forEach({ it -> list.addFirst(it) }) - return MalList(list) - } - - override fun with_meta(meta: MalType): MalType { - val obj = MalList(elements) - obj.metadata = meta - return obj - } +class MalUserFunc( + val ast: MalType, + val params: MalSeq, + val env: Env, + name: String, + meta: MalType, + func: MalFn +) : MalCallable(func, name, meta) { + override fun withMeta(m: MalType) = + MalUserFunc(ast, params, env, name, m, func) } -class MalVector(elements: MutableList) : MalSequence(elements) { - override var metadata: MalType = NIL - - constructor() : this(ArrayList()) - constructor(s: ISeq) : this(s.seq().toCollection(ArrayList())) - - override fun rest(): ISeq = MalVector(elements.drop(1).toCollection(ArrayList())) - - override fun slice(fromIndex: Int, toIndex: Int): MalVector = - MalVector(elements.subList(fromIndex, toIndex)) - - override fun conj(s: ISeq): ISeq = MalVector(elements.plus(s.seq()).toCollection(ArrayList())) - - override fun with_meta(meta: MalType): MalType { - val obj = MalVector(elements) - obj.metadata = meta - return obj - } -} - -class MalHashMap() : MalType { - override var metadata: MalType = NIL - - val elements = HashMap() - - constructor(other: MalHashMap) : this() { - other.elements.forEach({ it -> assoc_BANG(it.key, it.value) }) - } - - fun assoc_BANG(key: MalString, value: MalType) = elements.put(key, value) - - fun dissoc_BANG(key: MalString) { - elements.remove(key) - } - - override fun with_meta(meta: MalType): MalType { - val obj = MalHashMap(this) - obj.metadata = meta - return obj - } - - override fun equals(other: Any?): Boolean = - (other is MalHashMap) && elements.equals(other.elements) -} - -class MalAtom(var value: MalType) : MalType { - override var metadata: MalType = NIL - override fun with_meta(meta: MalType): MalType = throw UnsupportedOperationException() -} +data class MalUserEx(val src: MalType) : Exception("Exception raised"), MalType +data class MalCoreEx(val msg: String) : Exception(msg) -val NIL = MalConstant("nil") -val TRUE = MalConstant("true") -val FALSE = MalConstant("false") -val ZERO = MalInteger(0) +// Helper functions. +fun emptyMalList() = MalList(listOf()) +fun malListOf(vararg elems: MalType) = malListOf(elems.asList()) +fun malListOf(elems: List) = MalList(elems) +fun malMapOf(elems: List>) = MalMap(mapOf(*elems.toTypedArray())) +fun malSym(sym: String) = MalSymbol(sym) +fun malFun(name: String, f: MalFn) = MalFunc(f, name) diff --git a/ts/yarn.lock b/ts/yarn.lock index 043c1050..bf3cea5a 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -2,37 +2,39 @@ # yarn lockfile v1 -"@types/ffi@0.0.19": - version "0.0.19" - resolved "https://registry.yarnpkg.com/@types/ffi/-/ffi-0.0.19.tgz#bda12de999e1a6e4532b844497318dbaeb984f82" +"@types/ffi-napi@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-2.4.0.tgz#20eb7f0459b9a23eab68e7e799adf24dd58ad873" dependencies: "@types/node" "*" - "@types/ref" "*" - "@types/ref-struct" "*" + "@types/ref-napi" "*" + "@types/ref-struct-di" "*" "@types/node@*", "@types/node@^7.0.5": version "7.0.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.5.tgz#96a0f0a618b7b606f1ec547403c00650210bfbb7" -"@types/ref-struct@*": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/ref-struct/-/ref-struct-0.0.28.tgz#b840a8ac495411515dcae209010d5ac661550e84" +"@types/ref-napi@*": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-1.4.0.tgz#860fdae7931999ef3ca4f23f53720053a961e143" dependencies: - "@types/ref" "*" + "@types/node" "*" -"@types/ref@*": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/ref/-/ref-0.0.28.tgz#15a61253ed1259038b47499de1c9b0cbca57f55c" +"@types/ref-struct-di@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.0.tgz#4e7a20c8b2f25a6e4b3cb4a0d615835b61ab8d84" dependencies: - "@types/node" "*" + "@types/ref-napi" "*" balanced-match@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" -bindings@1, bindings@~1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" +bindings@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + dependencies: + file-uri-to-path "1.0.0" bluebird@^3.0.5: version "3.4.7" @@ -59,11 +61,11 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -debug@2: - version "2.6.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: - ms "0.7.2" + ms "^2.1.1" editorconfig@^0.13.2: version "0.13.2" @@ -74,15 +76,30 @@ editorconfig@^0.13.2: lru-cache "^3.2.0" sigmund "^1.0.1" -ffi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ffi/-/ffi-2.2.0.tgz#bf18b04666a29f71227ed56895d5430af47042fa" +ffi-napi@^2.4.0: + version "2.4.7" + resolved "https://registry.yarnpkg.com/ffi-napi/-/ffi-napi-2.4.7.tgz#730d8f49e301314d7495407bb50690f2c797b170" + dependencies: + bindings "^1.3.0" + debug "^3.1.0" + get-uv-event-loop-napi-h "^1.0.5" + node-addon-api "1.6.1" + ref-napi "^1.4.0" + ref-struct-di "^1.1.0" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + +get-symbol-from-current-process-h@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz#510af52eaef873f7028854c3377f47f7bb200265" + +get-uv-event-loop-napi-h@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz#42b0b06b74c3ed21fbac8e7c72845fdb7a200208" dependencies: - bindings "~1.2.0" - debug "2" - nan "2" - ref "1" - ref-struct "1" + get-symbol-from-current-process-h "^1.0.1" glob-expand@^0.2.1: version "0.2.1" @@ -131,13 +148,17 @@ minimatch@^2.0.1: dependencies: brace-expansion "^1.0.0" -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" -nan@2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" +node-addon-api@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.1.tgz#a9881c8dbc6400bac6ddedcb96eccf8051678536" + +node-addon-api@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" once@^1.3.0: version "1.4.0" @@ -149,20 +170,19 @@ pseudomap@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" -ref-struct@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ref-struct/-/ref-struct-1.1.0.tgz#5d5ee65ad41cefc3a5c5feb40587261e479edc13" +ref-napi@^1.4.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-1.4.3.tgz#c9495a4670a18655b3d45472284cc1fdac03e314" dependencies: - debug "2" - ref "1" + bindings "^1.3.0" + debug "^3.1.0" + node-addon-api "^2.0.0" -ref@1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ref/-/ref-1.3.4.tgz#724d2bf8ac85f8c8db194d3d85be6efe416bc1e5" +ref-struct-di@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.0.tgz#d252144eb449608ccf2e5c12fda35f8153bd3760" dependencies: - bindings "1" - debug "2" - nan "2" + debug "^3.1.0" sigmund@^1.0.1: version "1.0.1"