Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b21ed59
Clear out previous Kotlin implementation
Mar 9, 2019
0d44331
Implement step0
Mar 9, 2019
b20f07a
Fix indenting
Mar 9, 2019
5eb74c7
Implement step1
Mar 9, 2019
05a56c3
Implement step2
Jul 11, 2019
adaac1b
Implement step 3
broquaint Mar 8, 2020
bba8152
Implement rest of step 4
broquaint Mar 8, 2020
0cb5599
Implement "not" in mal
broquaint Mar 10, 2020
46c0b74
Implement step 5
broquaint Mar 13, 2020
e7a11b4
Implement step 6
broquaint Mar 14, 2020
08762d9
Implement atom deref syntax
broquaint Mar 15, 2020
6cc5699
WIP Implement step 7
broquaint Mar 16, 2020
c7b1f45
WIP Step 7
broquaint Mar 17, 2020
3762285
WIP step 7
broquaint Mar 18, 2020
50700f0
WIP Implement step 7 splice-unquote
broquaint Mar 18, 2020
fb631b3
WIP Step 7 implement quote reader macros
broquaint Mar 18, 2020
78888f8
WIP Implement step 7 fix vector implementation
broquaint Mar 19, 2020
bc5fedc
Reify MalFunc and MalUserFunc
broquaint Mar 19, 2020
35862a7
Implement step 8
broquaint Mar 19, 2020
9859a6d
Implement step 8 deferrable nth, first & rest
broquaint Mar 19, 2020
799cc10
Simplify list and vector reading
broquaint Mar 19, 2020
cf0382e
Implement keyword & hashmap types
broquaint Mar 20, 2020
7999dce
Implement deferrable rest parameter functionality
broquaint Mar 21, 2020
7f7bf04
Support escaping in strings
broquaint Mar 21, 2020
952aa6e
Implement loading of mal files
broquaint Mar 23, 2020
bf84ea6
Add (deferrable) macros from step 8
broquaint Mar 23, 2020
de71bc8
Implement step 9 try*/catch*
broquaint Mar 26, 2020
0f9d219
Implement step 9 apply & map
broquaint Mar 27, 2020
c99e1dd
Implement step 9 predicates
broquaint Mar 27, 2020
00b3cad
Implement step 9 deferrable functions
broquaint Mar 27, 2020
4a1a1e4
Implement step A meta functions
broquaint Mar 28, 2020
75424b3
Use when over if in read_atom
broquaint Mar 28, 2020
4b43b18
Apply private in more places
broquaint Mar 28, 2020
0551222
Detangle MalKeyword from MalString
broquaint Mar 28, 2020
a64ba63
Add `*host-language*` to the env
broquaint Mar 28, 2020
b661b95
Add gensym
broquaint Mar 28, 2020
f9e8c99
Fix else-less if bug
broquaint Mar 29, 2020
cb7dd6a
End the REPL on ^D
broquaint Mar 29, 2020
f53e4d9
Extend meta info to aggregate types
broquaint Mar 29, 2020
17fdc27
Implement deferrable step A functions
broquaint Mar 29, 2020
5ce394a
Support negative numbers
broquaint Mar 29, 2020
499877f
Sort out fiddly pr_str code
broquaint Mar 29, 2020
284a41e
Handle quoted strings better
broquaint Mar 30, 2020
9332533
Update step2 code to get tests passing
broquaint Apr 1, 2020
5c5717d
Fix step 3 to get all tests passing
broquaint Apr 1, 2020
d9da325
Fix step 4 to get most tests passing
broquaint Apr 1, 2020
9847ea1
Hopefully final fix for string escaping
broquaint Apr 1, 2020
20a2251
Bump debug from 2.6.1 to 3.2.6 in /ts
dependabot[bot] Apr 1, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kotlin/src/mal/.dir-locals.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
((nil . ((indent-tabs-mode . nil)
(kotlin-tab-width . 4)
)))
549 changes: 320 additions & 229 deletions kotlin/src/mal/core.kt

Large diffs are not rendered by default.

52 changes: 28 additions & 24 deletions kotlin/src/mal/env.kt
Original file line number Diff line number Diff line change
@@ -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<MalSymbol>?, exprs: Sequence<MalType>?) {
val data = HashMap<String, MalType>()
val data : MutableMap<MalSymbol, MalType> = 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<MalType>())))
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)
}
}
57 changes: 57 additions & 0 deletions kotlin/src/mal/eval.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
val repl_env : Map<String, MalFunc> = 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<String, MalFunc>, 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<String, MalFunc>, 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
}
}
}
63 changes: 39 additions & 24 deletions kotlin/src/mal/printer.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
package mal
// Use named variables otherwise we have to escape escaping x_x
private val q = "\u0022" // '"' U+0022 &quot; QUOTATION MARK (Other_Punctuation)
private val bs = "\u005C" // '\' U+005C &bsol; 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<MalType>, 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<MalString, MalType>, 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<MalKey, MalType> -> 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
}
}
Loading