Skip to content

Commit fb15224

Browse files
committed
Finish step 9 so all tests pass
I had punted on properly implemented equality, that is now sorted. It was easier than I thought, but I was getting tripped up on Kotlin's slightly opaque error messaging around type checking recursion.
1 parent 520ce9b commit fb15224

File tree

5 files changed

+26
-24
lines changed

5 files changed

+26
-24
lines changed

kotlin/src/mal/core.kt

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ private fun to_fun(name: String, f: MalFn) : Pair<MalSymbol, MalFunc> =
66
malSym(name) to malFun(name, f)
77

88
// =: compare the first two parameters and return true if they are the same type and contain the same value.
9-
private fun is_equal(a: MalType, b: MalType) =
9+
private fun is_equal(a: MalType, b: MalType): Boolean =
1010
if(a::class == b::class) {
1111
when(a) {
1212
is MalNumber -> a.num == (b as MalNumber).num
@@ -18,29 +18,26 @@ private fun is_equal(a: MalType, b: MalType) =
1818
is MalSeq -> compare_lists(a, (b as MalSeq))
1919
is MalMap -> compare_maps(a, (b as MalMap))
2020
is MalFunc -> a.func == (b as MalFunc).func
21-
// XXX Not particularly useful but is_equal(a.src, b.src) hits:
22-
// error: type checking has run into a recursive problem
23-
is MalUserEx -> a.src == (b as MalUserEx).src
21+
is MalUserEx -> is_equal(a.src, (b as MalUserEx).src)
2422
else -> throw MalCoreEx("Unknown type $a in is_equal (aka =)")
2523
}
2624
}
27-
// Handle class when comparing lists & vectors.
25+
// Handle case when comparing lists & vectors.
2826
else if (a is MalSeq && b is MalSeq) {
2927
compare_lists(a, b)
3028
}
3129
else {
3230
false
3331
}
32+
3433
// 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.
35-
private fun compare_lists(a: MalSeq, b: MalSeq): Boolean {
36-
if(a.atoms.count() == b.atoms.count())
37-
return a.atoms.indices.all { v: Int -> is_equal(a.atoms[v], b.atoms[v]) }
38-
else
39-
return false
40-
}
34+
private fun compare_lists(a: MalSeq, b: MalSeq): Boolean =
35+
a.atoms.count() == b.atoms.count() &&
36+
a.atoms.indices.all { is_equal(a[it], b[it]) }
4137

42-
// TODO Implement!
43-
private fun compare_maps(a: MalMap, b: MalMap) = a == b
38+
private fun compare_maps(a: MalMap, b: MalMap): Boolean =
39+
a.pairs.size == b.pairs.size &&
40+
a.pairs.all { (k, v) -> b.pairs.contains(k) && is_equal(v, b[k]) }
4441

4542
private fun pr_str_core(seq: MalSeq) =
4643
seq.atoms.map { pr_str(it, print_readably=true) }.joinToString(" ")
@@ -51,7 +48,7 @@ private fun str_core(seq: MalSeq, joiner: String) =
5148
private val eof = ""
5249

5350
object core {
54-
val ns : Map<MalSymbol, MalFunc> = mutableMapOf(
51+
val ns : Map<MalSymbol, MalFunc> = mapOf(
5552
// Basic number ops.
5653
malSym("+") to malFun("plus") { int_ops_reducer(Int::plus, it) },
5754
malSym("-") to malFun("minus") { int_ops_reducer(Int::minus, it) },
@@ -181,7 +178,7 @@ object core {
181178
to_fun("nth") {
182179
val seq = it[0] as MalSeq
183180
val idx = it[1] as MalNumber
184-
seq[idx]
181+
if (idx.num <= seq.atoms.lastIndex) seq[idx] else throw MalUserEx("the index ${idx} is out of bounds for ${pr_str(seq)}")
185182
},
186183
to_fun("first") {
187184
val v = it[0]
@@ -206,7 +203,7 @@ object core {
206203

207204
to_fun("throw") {
208205
throw when(it.size) {
209-
0 -> MalUserEx(MalString("error raised anon"))
206+
0 -> MalUserEx("error raised anon")
210207
else -> MalUserEx(it[0])
211208
}
212209
},
@@ -220,7 +217,7 @@ object core {
220217
val fn = it[0] as MalCallable
221218
val argSeq = it.last() as MalSeq
222219
val args = it.atoms.slice(1 .. (if(it.size > 2) it.size - 2 else 0))
223-
fn(malListOf(argSeq.atoms + args))
220+
fn(malListOf(args + argSeq.atoms))
224221
},
225222
to_fun("map") {
226223
val fn = it[0] as MalCallable
@@ -272,9 +269,13 @@ object core {
272269
MalMap(m.pairs - it.tail().atoms.map { it as MalKey })
273270
},
274271
to_fun("get") {
275-
val m = it[0] as MalMap
272+
val m = it[0]
276273
val k = it[1] as MalKey
277-
m[k]
274+
when(m) {
275+
is MalMap -> m[k]
276+
is MalNil -> m
277+
else -> throw MalUserEx("Can't treat ${m} as a map")
278+
}
278279
},
279280
to_fun("contains?") {
280281
val m = it[0] as MalMap

kotlin/src/mal/env.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class Env(val outer: Env? = null,
3434

3535
// 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.
3636
fun get(key: MalSymbol): MalType {
37-
val env = find(key) ?: throw MalCoreEx("The symbol '${key.sym}' not found in env")
37+
val env = find(key) ?: throw MalUserEx("'${key.sym}' not found")
3838
return env.data.getValue(key)
3939
}
4040
}

kotlin/src/mal/reader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private fun read_atom(r: Reader, n: Int) : MalType {
104104

105105
private fun make_map(pairs: List<MalType>) : MalMap {
106106
if(pairs.size % 2 != 0)
107-
throw MalUserEx(MalString("maps requires an even number of items, got ${pairs.size} items"))
107+
throw MalUserEx("maps requires an even number of items, got ${pairs.size} items")
108108

109109
val map : MutableMap<MalKey, MalType> = mutableMapOf()
110110
for (idx in pairs.indices step 2) {

kotlin/src/mal/step9_try.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ fun EVAL(cur_ast: MalType, cur_env: Env, depth: Int) : MalType {
149149
// 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:
150150
val binds = rest[0] as MalSeq
151151
val body = rest[1]
152-
// val func = malFun("funccall")
153-
return MalUserFunc(body, binds, env) {
152+
return MalUserFunc(body, binds, env, "anon", MalNil()) {
154153
EVAL(body, Env(env, binds, it), n)
155154
}
156155
}

kotlin/src/mal/types.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ class MalUserFunc(
9090
MalUserFunc(ast, params, env, name, m, func)
9191
}
9292

93-
data class MalUserEx(val src: MalType) : Exception("Exception raised"), MalType
93+
data class MalUserEx(val src: MalType) : Exception("Exception raised"), MalType {
94+
constructor(msg: String) : this(MalString(msg))
95+
}
9496
data class MalCoreEx(val msg: String) : Exception(msg)
9597

9698
// Helper functions.

0 commit comments

Comments
 (0)