So I revisited Day 13 to commit some crimes agains...
# advent-of-code
a
So I revisited Day 13 to commit some crimes against Kotlin today. I wondered what would happen if instead of parsing the input "properly" (which I did on the day, and took me far too long), I instead turned it into some Kotlin code with simple string replacements and then tried to dynamically load the result into the running process (like calling
eval
in Python). TL;DR is that it works, but it ain't pretty 😂 Details in 🧵
😂 8
Class loading Code It's slow and gross and I love it - the compile in Step 2 takes like 30s . A step-by-step explanation: Step 1 - build dynamic Kotlin code Convert to "kotlin code" via easy
.replace
calls so that e.g.
"[[1],4]"
becomes
"listOf(listOf(1), 4)"
. One slight edge case is that empty ones need explicitly typing, so
"[]"
becomes
"listOf<Any>()"
Bundle this up into a valid Kotlin class. At the end of all this, we end up with a String like:
Copy code
class ListClass {
    fun list0() = listOf(listOf(1), 4)
    fun list1() = listOf(...)
    fun allLists() = listOf(list0(), list1(), ...)
}
I'm using individual functions rather than one big list to avoid running into a "Method too large" error when we compile it (see next step 😂). Step 2 Write the string to a file, then shell out to kotlinc to generate a raw class file from the Kotlin file.
Copy code
Runtime.getRuntime().exec("kotlinc -no-jdk -no-reflect $file")
Step 3 Dynamically load the class file into the currently running JVM via a custom
ClassLoader
that makes use of the
defineClass
function.
Copy code
class DynamicClassInjector : ClassLoader() {
    fun injectClass(name: String): Class<*> {
        val file = File("$name.class")
        val bytes = file.readBytes()
        return defineClass(
            name,
            bytes, 0, bytes.size
        )
    }
}
Step 4 Use reflection to construct an instance of our new class and invoke the method we're interested in, getting our parsed list.
Copy code
val clazz = injectDynamicClass("ListClass", listClass)
val instance = clazz.getDeclaredConstructor().newInstance()
return clazz.getMethod("allLists").invoke(instance) as List<List<Any>>
r
I was watching Jonathan Paulson's video solution of Day 13. It was quite amusing to me when I saw that I had spent 15 to 20 minutes on the parsing, and he literally wrote:
Copy code
p1 = eval(p1)
p2 = eval(p2)
in like 2 seconds and got to the same place.

https://youtu.be/_l22L_0BHIo?t=24

a
yeah, a friend of mine is doing it in Python this year and also used
eval
. When he showed me I was... a little peeved 😁
r
I like your attempt at replicating it 🙂
Next up: use ksp to generate the classes that represent the input!
e
you don't have to shell out to the compiler. it's available as a JAR, which you can load in-process
or use the scripting support! that's where I thought you were gonna go with this at first glance…
a
I thought there must be a gradle plugin/JAR but couldn't find it (after admittedly not much googling)! The idea of adding some raw shell to the already hellish mess kinda appealed too tbh. not checked out kotlin scripting stuff before, one to add to the list of things to check out at some point
r
The parsing is why I've only done this one in Python. Didn't want to bother