DEV Community

Cover image for Advanced Kotlin syntax abuse
Alan Evans
Alan Evans

Posted on

Advanced Kotlin syntax abuse

Kotlin’s operator overloading and infix notations provide a great opportunity to create a more natural reading codebase, but they also provide a great opportunity to create wonderfully nonsensical code.

In this article, I’m going to show how this outputs Hello World!

Heart shaped code block

Brainfuck

First if you’re not already familiar with the language Brainfuck, BF for short from now on. It’s an esoteric, Turing complete language that consists of just a handful of symbols:

+ Add 1
- Subtract 1
> Move to the next memory address
< Move to the previous memory address
[ if the current memory address is zero, skip to the matching ]
] if the current memory address is non-zero, skip back to the matching [
. Print the current address as a char
, Read a value from the input

A “Hello world!” in BF is:

++++++++[>++++[>++>+++>+++>+<<<<-]
>+>+>->>+[<]<-]>>.>---.+++++++..++
+.>>.<-.<.+++.------.--------.>>+.
>++.
Enter fullscreen mode Exit fullscreen mode

If you would like to understand how this produces this output, Wikipedia explains this well https://en.wikipedia.org/wiki/Brainfuck#Hello_World! This article is more about how I created this travesty of a syntax to be acceptable to the Kotlin complier!

Note that the above BF hello world mostly matches up with my Kotlin hello world example albeit with some different symbols.

+ Add 1
- Subtract 1
r rather than > to move to the next memory address
l rather than < to move to the previous memory address
[ if the current memory address is zero, skip to the matching ]
] if the current memory address is non-zero, skip back to the matching [
o Print the current address as a char
n NOOP, its purpose explained later

Getting started

A BF program, like any other, is a list of commands. So I decided I needed to model a Command, and a Program:

sealed class Command {
    object Add : Command()
    object Subtract : Command()
    object MoveRight : Command()
    object MoveLeft : Command()
    object Print : Command()
    class Loop(val loopContent: Program)
}

class Program(val commands: List<Command>)
Enter fullscreen mode Exit fullscreen mode

Note that I have modelled the loop command as containing another program within the loop. I now have a tree structure.

I want to keep this all immutable, so I will make an append method, that will create a new program with the additional command. But first, I’d like a way of testing this all, so I’m going to create some toString methods:

sealed class Command {
    object Add : Command() {
        override fun toString() = "+"
    }

    object Subtract : Command() {
        override fun toString() = "-"
    }

    object MoveRight : Command() {
        override fun toString() = ">"
    }

    object MoveLeft : Command() {
        override fun toString() = "<"
    }

    object Print : Command() {
        override fun toString() = "."
    }

    class Loop(val loopContent: Program) : Command() {
        override fun toString() = "[$loopContent]"
    }
}

class Program(val commands: List<Command> = emptyList()) {
    override fun toString(): String {
        return commands.joinToString(separator = "")
    }
}
Enter fullscreen mode Exit fullscreen mode

Now our test cases can ensure that the program contents are as expected before we actually get to the point of wanting to execute it.

@Test
fun `can append command`() {
    assertEquals("+", Program(emptyList()).append(Command.Add).toString())
}

@Test
fun `can append 2 commands`() {
    assertEquals("+-", Program(emptyList()).append(Command.Add).append(Command.Subtract).toString())
}

@Test
fun `can append a loop`() {
    assertEquals(
        "+[.]", Program(emptyList()).append(Command.Add)
            .append(Command.Loop(Program(emptyList()).append(Command.Print))).toString()
    )
}
fun Program.append(command: Command) = Program(commands + command)
Enter fullscreen mode Exit fullscreen mode

We can now build the whole hello world in this fashion and assert the program structure is correct.

val helloWorld: Program = Program(emptyList()).append(Command.Add).append(Command.Add).append(Command.Add).append(Command.Add)
    .append(Command.Add).append(Command.Add)
    .append(Command.Add).append(Command.Add)
    .append(
        Command.Loop(
            Program(emptyList()).append(Command.MoveRight).append(Command.Add).append(Command.Add)
                .append(Command.Add).append(Command.Add).append(
                    Command.Loop(
                        Program(emptyList()).append(Command.MoveRight).append(Command.Add).append(Command.Add)
                            .append(Command.MoveRight).append(Command.Add).append(Command.Add)
                            .append(Command.Add).append(Command.MoveRight).append(Command.Add)
                            .append(Command.Add).append(Command.Add).append(Command.MoveRight)
                            .append(Command.Add).append(Command.MoveLeft).append(Command.MoveLeft)
                            .append(Command.MoveLeft).append(Command.MoveLeft).append(Command.Subtract)
                    )
                ).append(Command.MoveRight).append(Command.Add).append(Command.MoveRight)
                .append(Command.Add)
                .append(Command.MoveRight).append(Command.Subtract).append(Command.MoveRight)
                .append(Command.MoveRight).append(Command.Add)
                .append(Command.Loop(Program(emptyList()).append(Command.MoveLeft)))
                .append(Command.MoveLeft)
                .append(Command.Subtract)
        )
    ).append(Command.MoveRight).append(Command.MoveRight).append(Command.Print).append(Command.MoveRight)
    .append(Command.Subtract).append(Command.Subtract)
    .append(Command.Subtract).append(Command.Print).append(Command.Add).append(Command.Add)
    .append(Command.Add).append(Command.Add)
    .append(Command.Add).append(Command.Add).append(Command.Add).append(Command.Print).append(Command.Print)
    .append(Command.Add).append(Command.Add)
    .append(Command.Add).append(Command.Print).append(Command.MoveRight).append(Command.MoveRight)
    .append(Command.Print).append(Command.MoveLeft).append(Command.Subtract)
    .append(Command.Print).append(Command.MoveLeft).append(Command.Print).append(Command.Add)
    .append(Command.Add).append(Command.Add).append(Command.Print)
    .append(Command.Subtract).append(Command.Subtract).append(Command.Subtract).append(Command.Subtract)
    .append(Command.Subtract)
    .append(Command.Subtract).append(Command.Print).append(Command.Subtract).append(Command.Subtract)
    .append(Command.Subtract)
    .append(Command.Subtract).append(Command.Subtract).append(Command.Subtract).append(Command.Subtract)
    .append(Command.Subtract)
    .append(Command.Print).append(Command.MoveRight).append(Command.MoveRight).append(Command.Add)
    .append(Command.Print).append(Command.MoveRight).append(Command.Add)
    .append(Command.Add).append(Command.Print)
Enter fullscreen mode Exit fullscreen mode

Oh boy, that badly needs a refactor and some cleaner syntax but at least the test is neat:

@Test
fun `can build hello world program structure`() {
    assertEquals(
        "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
        helloWorld.toString()
    )
}
Enter fullscreen mode Exit fullscreen mode

Before we do any refactoring, let’s try and make this work with an execute method.

First we need to model the memory of the machine, known as a tape.

class Tape(size: Int = 30000) {
    private val data = Array(size) { 0 }
    private var pointer = 0;
}
Enter fullscreen mode Exit fullscreen mode

This is just an array and a movable pointer into that array.

Executing commands is pretty trivial, and you should see that they line up easily with the descriptions of the symbols given earlier in the article.

The hardest part about writing a BF machine is getting the loops working correctly, but our in-memory tree structure makes even this easy.

fun Program.execute(tape: Tape = Tape(), writer: PrintWriter = PrintWriter(System.out.writer())) {
    for (command in commands) {
        when (command) {
            is Command.Add -> tape.add()
            is Command.Subtract -> tape.subtract()
            is Command.MoveRight -> tape.moveRight()
            is Command.MoveLeft -> tape.moveLeft()
            is Command.Print -> writer.print(tape.currentAsChar())
            is Command.Loop -> while (tape.current() != 0) {
                command.loopContent.execute(tape, writer)
            }
        }
    }
    writer.flush()
}

class Tape(size: Int = 30000) {
    private val data = Array(size) { 0 }
    private var pointer = 0;

    fun add() {
        data[pointer]++
    }

    fun subtract() {
        data[pointer]--
    }

    fun moveRight() {
        pointer++
    }

    fun moveLeft() {
        pointer--
    }

    fun current(): Int {
        return data[pointer]
    }

    fun currentAsChar(): Char {
        return current().toChar()
    }
}
Enter fullscreen mode Exit fullscreen mode

Now to run it:

@Test
fun `run hello world`() {
    val stringWriter = StringWriter()
    helloWorld.execute(Tape(), PrintWriter(stringWriter))

    assertEquals("Hello World!\n", stringWriter.toString())
}
Enter fullscreen mode Exit fullscreen mode

It all works, so it's time to refactor.

Firstly, Program(emptyList) appears a lot, it’s always the same and as Program is immutable, this can be extracted to a constant, n. I have chosen n to stand for NOOP, it does nothing, in fact it’s not really a command at all, just an empty program. It’s the identity program, like an identity matrix, it changes no program it is appended to.

val n = Program(emptyList())
Enter fullscreen mode Exit fullscreen mode

Now I want to start overloading operators. Firstly so that this test passes:

@Test
fun `add command`() {
    assertEquals("+", (n + n).toString())
}
Enter fullscreen mode Exit fullscreen mode

And I’m going to let IntelliJ be my accomplice:

IntelliJ suggesting to add Program.plus

// Not complete!
operator fun Program.plus(program: Program): Program {
    return append(Command.Add)
}
Enter fullscreen mode Exit fullscreen mode

This will get us past the first test, but not this one:

@Test
fun `add commands preserve left and right programs`() {
    assertEquals("<+>", (n.append(Command.MoveLeft) + n.append(Command.MoveRight)).toString())
}
Enter fullscreen mode Exit fullscreen mode

However, this will:

operator fun Program.plus(program: Program)
  = append(Command.Add).append(program)
With the help of a new append overload to concatenate whole programs:

fun Program.append(program: Program)
  = Program(commands + program.commands)
Enter fullscreen mode Exit fullscreen mode

Our first crime against Kotlin has now been committed, plus now does not only add the left and right operands, but inserts a command too so that the the whole is now greater than the sum of the parts, which is a fine saying, but not very mathematically accurate!

So how do we get multiple + s in a row to make sense, well we’ll write a test and let IntelliJ tell us how!

@Test
fun `multiple plus symbols`() {
    assertEquals("<++>", (n.append(Command.MoveLeft) + + n.append(Command.MoveRight)).toString())
}
Enter fullscreen mode Exit fullscreen mode

IntelliJ suggesting to add Program.unaryPlus

Thanks IntellJ, you’re making this easy!

operator fun Program.unaryPlus()
  = n.append(Command.Add).append(this)
Enter fullscreen mode Exit fullscreen mode

I’m not sure why unary plus can be overridden to be honest, or when you might even want to use it. Unary minus makes some sense, a way to produce a negative version of your type, and maybe Kotlin just allows the unary plus for completeness?

In any case, we can now start to construct the hello world, we can have the eight + symbols at the front of the program:

@Test
fun `8 plus symbols`() {
    assertEquals("++++++++", (+ + + + + + + + n).toString())
}
Enter fullscreen mode Exit fullscreen mode

Note the presence of n, without which the compiler would have no idea we’re talking about the Program type. We can insert these wherever we need to tell the Kotlin compiler about the type.

Also note that for the first time here, we’ve got to the point where white space matters. Using IntelliJ auto-formatter will bring all these unary pluses together and cause a syntax error. It’s our first big clue that we’re getting off the beaten path here!

That’s it for the plus, and the procedure for deriving the minus support is identical.

operator fun Program.unaryMinus()
  = n.append(Command.Subtract).append(this)
operator fun Program.minus(program: Program)
  = append(Command.Subtract).append(program)
Enter fullscreen mode Exit fullscreen mode

In a way the behavior of these minus implementations are even more unexpected than that of the plus implementations as nothing is subtracted, the programs only get longer!

The Loop

The next interesting language feature to misuse is the array indexer to give us the loop syntax.

I employ the same pattern, write what I want it to look like and let IntelliJ make its suggestions in good faith:

@Test
fun `loops using array syntax`() {
    assertEquals(">[-]", (n.append(Command.MoveRight) [ n.append(Command.Subtract) ]).toString())
}
Enter fullscreen mode Exit fullscreen mode

IntelliJ suggesting to add Program.get

operator fun Program.get(loopInner: Program)
  = append(Command.Loop(loopInner))
Enter fullscreen mode Exit fullscreen mode

Ok, that was quick let’s recap what I’ve done there, I’ve taken an overload that was designed to let you look stuff up in your custom classes as though they were arrays/lists and I’ve made it create a mutation of the current program with an appended loop command which contains another program. This is a truly terrible misuse of this feature!

But where does applying that leave helloWorld:

val helloWorld: Program = + + + + + + + + n [
        n.append(Command.MoveRight) + + + + n[
                n.append(Command.MoveRight) +  + n
                    .append(Command.MoveRight) +  +
                + n.append(Command.MoveRight) +
                        +  + n.append(Command.MoveRight)
                        + n.append(Command.MoveLeft).append(Command.MoveLeft)
                    .append(Command.MoveLeft).append(Command.MoveLeft) - n
        ].append(Command.MoveRight) + n.append(Command.MoveRight) +
                n.append(Command.MoveRight) - n.append(Command.MoveRight)
            .append(Command.MoveRight) +
                n[n.append(Command.MoveLeft)]
                    .append(Command.MoveLeft)
                - n
].append(Command.MoveRight).append(Command.MoveRight).append(Command.Print).append(Command.MoveRight) - - -
n.append(Command.Print) + + + + + + + n.append(Command.Print).append(Command.Print) + + +
n.append(Command.Print).append(Command.MoveRight).append(Command.MoveRight).append(Command.Print).append(Command.MoveLeft) -
        n.append(Command.Print).append(Command.MoveLeft).append(Command.Print) + + + n.append(Command.Print) - - -  - - -
n.append(Command.Print) -  - - -  -  -  - - n.append(Command.Print).append(Command.MoveRight).append(Command.MoveRight) +
        n.append(Command.Print).append(Command.MoveRight) + + n.append(Command.Print)
Enter fullscreen mode Exit fullscreen mode

Move Left and Right

A lot of things remaining can be seen to be extractable, things like n.append(Command.MoveRight).

I’d love to use > and < but, although these can be overridden in a way, they both boil down to the overridable compareTo which doesn’t tell you which operator was actually used so I am forced to choose my own symbols for these commands.

val o = n.append(Command.Print)
val r = n.append(Command.MoveRight)
val l = n.append(Command.MoveLeft)

val helloWorld: Program = + + + + + + + + n [  r + + + + n [
        r + + r + + + r + + + r +
                l.append(Command.MoveLeft).append(Command.MoveLeft).append(Command.MoveLeft) - n
].append(Command.MoveRight) + r + r - r
    .append(Command.MoveRight) + n [ l ]
    .append(Command.MoveLeft) - n
].append(Command.MoveRight).append(Command.MoveRight).append(Command.Print).append(Command.MoveRight) - - -
o + + + + + + + o.append(Command.Print) + + +
o.append(Command.MoveRight).append(Command.MoveRight).append(Command.Print).append(Command.MoveLeft) -
        o.append(Command.MoveLeft).append(Command.Print) + + +o - - -  - - -
o -  - - -  -  -  - - o.append(Command.MoveRight).append(Command.MoveRight) +
        o.append(Command.MoveRight) + + o
Enter fullscreen mode Exit fullscreen mode

So, now all that is left are a few appends. What if we just replace them with what we want and let IntelliJ suggest something, it’s worked so far:

IntelliJ suggesting to add extension function Program.l

infix fun Program.l(program: Program)
  = append(Command.MoveLeft).append(program)
infix fun Program.r(program: Program)
  = append(Command.MoveRight).append(program)
infix fun Program.o(program: Program)
  = append(Command.Print).append(program)
Enter fullscreen mode Exit fullscreen mode

Now we have arrived at our horrific destination:

val helloWorld: Program =
    + + + + + + + + n [ r + + + + n [ r + + r + + + r + + + r +
    l l l l - n ] r + r + r - r r + n [ l ] l - n ] r r o r - -
    - o + + + + + + + o o + + + o r r o l - o l o + + + o - - -
    - - - o - - - - - - - - o r r + o r + + o
Enter fullscreen mode Exit fullscreen mode

I must be careful of new line locations at this point, but this can be solved by wrapping in ( ).

All that remains is to change our execute method into an invoke:

operator fun Program.invoke(tape: Tape = Tape(), writer: PrintWriter = PrintWriter(System.out.writer())) { ... same as execute
Enter fullscreen mode Exit fullscreen mode

And we can now actually execute BrainFuck inside Kotlin, simply by wrapping in ( ) and using a following pair of () to cause the invocation.

(
  + + + + + + + + n [ r + + + + n [ r + + r + + + r + + + r + l l
  l l - n ] r + r + r - r r + n [ l ] l - n ] r r o r - - - o + +
  + + + + + o o + + + o r r o l - o l o + + + o - - - - - - o - -
  - - - - - - o r r + o r + + o
)()
Enter fullscreen mode Exit fullscreen mode

Which makes for an interesting sight in the IDE as the seemingly random colours hint at our overloading of various vals and infix methods:

IntelliJ screen shot of main with different colors

What’s next?

This is a Turing complete language within a language, add support for the input stream , with say i and you can truly implement anything you wish with just these symbols:

l + o - r n i and square brackets

Doom anyone?


Originally posted in 2020 on medium https://westonal.medium.com/advanced-kotlin-syntax-abuse-b9f5e46230e4

Code available at https://github.com/westonal/KotlinBrainFuck

Top comments (0)