I spent weeks building BrainJuck. A Brainfuck-to-JVM compiler, written by hand in Node.js, with zero dependencies. Weeks reading hex dumps, fighting the StackMapTable, miscalculating jump offsets at two in the morning. I already wrote about it (in Portuguese).

Yesterday I opened Claude Code and sent a prompt. A single prompt. No back and forth, no corrections, no guiding. I pasted it and went to make coffee:

create a compiler from Brainfuck to JVM in Node.js with no dependencies. Full Node.js.

the interface should be ./compiler somefile.bf SomeFile

i should be able to run SomeFile just by running "java SomeFile"

every brainfuck command should be accepted and the produced .class must not have a warning.
You must test everything, from the parser to the generation of the .class.

When I came back, three minutes later, the compiler was done. 51 tests passing, a valid .class, Hello World running with no warnings on stderr. One prompt. Three minutes.

My first reaction was to laugh. The second was to go quiet.

What it generated

Claude’s compiler is a single file, ~300 lines. Parser, constant pool, bytecode generation, StackMapTable, .class assembly - all in one place. It works. All eight Brainfuck commands compile correctly to JVM bytecode. The loops generate ifeq/goto with correct offsets. The append_frame in the StackMapTable declares the right local variables. javap -v shows a valid class.

The test suite is decent: it tests the parser in isolation, the constant pool, the bytecode generation, the StackMapTable, the structure of the .class, and runs 19 integration tests that compile Brainfuck, execute it with java, and check the output. Hello World, nested loops, input, cell wrapping, all 8 commands together.

It works. No tricks, no hacks. It even got the append_frame right on the first try - fine, on the second. On the first it used same_frame and got a VerifyError, exactly like I did. But it fixed it by itself in seconds.

Where mine is better

BrainJuck is not just a compiler that works. It’s a compiler that thinks.

Claude’s parser takes +++ and generates three separate iadds. My parser combines them into an increment(3) and emits a sipush 3 + iadd. Less bytecode, less work for the JVM.

BrainJuck has an IR layer between parsing and code generation. That means I can add optimizations without touching the bytecode generator. Claude went straight from parsing to bytecode - it works, but it’s rigid.

Mine tracks the pointer position at compile time. >>><< becomes a move_head(1) with an absolute position. Claude’s generates five separate iincs. Again - it works, but it’s naive.

The architecture has real separation: index.js for the parser, class_generator.js for .class assembly, helpers/jvm.js for the opcodes. Claude stuffed everything into one file. For 300 lines, that’s fine. For evolving the thing, it’s not.

Where Claude’s is better

The test coverage. I have to admit it.

I have 6 unit tests and 1 integration test. Claude generated 51 tests covering each layer individually. It tests edge cases I hadn’t even thought of - the empty loop [], consecutive loops [][], cell wrapping, an empty program.

My integration test is solid - it compiles Hello World, runs it with java, checks stdout and stderr. But it’s one scenario. Claude tested 19 different integration scenarios.

What actually matters

This is where things get honest.

Claude’s compiler works. If someone came to me with “I need a Brainfuck-to-JVM compiler by tomorrow” and I used Claude, the job would be delivered. Nobody would look at the generated .class and know it was made in 3 minutes.

But I would know nothing.

I wouldn’t know that the constant pool is 1-indexed and that entry 0 doesn’t exist. I wouldn’t know that baload sign-extends to int. I wouldn’t know that slot 0 of the local variables is reserved for the method’s arguments. I wouldn’t know that the StackMapTable’s offset_delta is calculated relative to the previous frame, not to the start of the method. I wouldn’t know the difference between same_frame and append_frame, or why the first frame of a method with branches has to be an append_frame if you declared local variables beyond the signature.

None of it. My head would be empty.

When I was debugging BrainJuck at two in the morning, comparing hex dumps against the JVM spec, getting the offset math wrong for the tenth time - that was knowledge going in. Every VerifyError was a lesson. Every wrong byte I found in the hex dump was a new connection in my brain.

Claude didn’t have to debug any of that. It already knew. It was trained on the JVM spec, on thousands of similar implementations, on decades of accumulated knowledge. For Claude, generating a correct StackMapTable is interpolation. For me, it was the final boss of the project.

It’s not about the AI being bad

I use AI to write code. I use Claude Code practically every day. I’m not against it, I don’t think it will destroy the profession, I’m not afraid of losing my job. Let that be clear.

But there is a difference between using AI to speed up work you understand and using AI to do work you don’t understand. In the first case, you gain time. In the second, you gain an illusion.

BrainJuck took weeks. Claude’s compiler took 3 minutes. The end result is similar - both generate a valid .class, both compile Hello World, both pass the JVM verifier. But after those weeks, I know how the JVM works on the inside. I can read bytecode, I know what a VerifyError means, I can dissect a .class with xxd. Those weeks gave me something no prompt does.

Claude’s compiler is better tested than mine. It’s faster to produce. If I wanted, I could take its code and improve it - add the optimizations mine has, split it into modules, evolve the architecture.

But I would never have the foundation to do that if I hadn’t gone through those weeks first.

The real test

If I hand you Claude’s compiler and ask you to add an optimization - collapsing [-] into a direct clear cell in the bytecode - could you do it? If a new VerifyError shows up, would you know where to start?

If you built your own, the answer is yes. If AI built it for you, the honest answer is probably no.

That’s the point. It’s not that AI writes bad code. The code is good. It’s that good code you don’t understand is exactly as useful as bad code you don’t understand. Either way, when it breaks, you’re lost.

If you’re learning

If you’re a developer and you want to understand compilers, virtual machines, bytecode - build one by hand. At least once. It doesn’t have to be Brainfuck, it doesn’t have to be the JVM. Pick any simple language and compile it to any target. What matters is going through the process.

Once you understand it, then yes - use AI to move faster, to test more scenarios, to explore variations. AI is an absurd tool when you know what you’re doing. When you don’t, it’s just a generator of false confidence.

BrainJuck is on GitHub. Read the code, play with it, break it. And if you feel like it, build your own.

Thanks for reading!