On 14 April 2020, I committed feat: basic parsing to a new repository for a programming language called Abla. Four days later it could already execute some code at compile time, which was the part I actually cared about. Parsing integers is useful, but making the compiler run your code while it is compiling your other code is much more fun.
That first version now lives in the old-master branch. It was a Kotlin compiler using ANTLR and LLVM, and it grew to 177 commits between April and September 2020. It had functions, classes, lambdas, extension functions, imports, type inference, external calls and a basic implementation of arbitrary compile-time execution.
Then I stopped working on it.
I do not remember one dramatic technical reason for stopping. Creating a programming language is simply a ridiculous amount of work. Every small feature opens another collection of problems: parsing, types, ownership, diagnostics, code generation, tooling, standard libraries, and the tiny detail of producing programs that work. The repository stopped, but the idea did not really go away.
The idea was to make compile time as important as runtime. Most systems eventually collect generators, build scripts, schema compilers and small languages around the main application. Those tools are often written in another language and connected through files, templates and hope. I wanted ordinary Abla code to be able to run during compilation, inspect the program and generate or transform it using the same language as the application.
Six years later, AI made returning to that idea much more practical. It can produce a lot of code very quickly, including a lot of code that is almost correct. A compiler is an excellent way to discover the exact limits of "almost correct". If the ownership checker accepts an invalid move, or an LLVM block has the wrong predecessor, enthusiasm does not make the generated executable less broken.
Still, having something that can help trace a problem across a parser, type checker, runtime and test suite changes the amount of ground one person can cover. So on 1 August 2026, I started again.
Starting again, without the old compiler
The current master is a clean implementation rather than a continuation of the Kotlin compiler. More importantly, the compiler is now written in Abla itself. A published bootstrap compiler builds the current source, and the result can rebuild itself to a byte-identical fixed point. LLVM is still underneath it, but there is no C++ compiler implementation hiding beside the real one.
The small example that describes the central idea is still this:
fun square(value: int): int = value * value
compile fun compileAdd(left: int, right: int): int = left + right
fun main: int {
val generated = #square(6)
generated + #compileAdd(2, 2)
}
The # asks for an expression to be evaluated during compilation. Both calls are completed by the compiler, so the generated main returns 40. Compile-time code uses the same language model as runtime code, but it does not get invisible access to the machine. Files, environment variables, the network, the clock and native functions are explicit capabilities with limits. Reproducible builds become difficult rather quickly when a compile function can quietly ask the internet what day it is.
There is now an ownership and borrowing model, deterministic package locks, programmable build graphs, native compilation, JIT execution, a REPL and a small HTTP server. There are also implementations and tests around defer, generators and structured concurrency. Some of these surfaces are deliberately narrow because making a small rule reliable is more useful than documenting a large rule that only works on Tuesdays.
The raw x86-64 Linux target produces a static executable with its own startup, allocator and system-call boundary, without libc or a dynamic interpreter. That is not the same as saying Abla is ready for every microcontroller, but it establishes the freestanding boundary needed to move towards embedded targets. At the other end, the compiler can produce WebAssembly modules, and an Android example builds an arm64 native library plus the integration needed to package an APK or AAB.
The intention is one language that can eventually go from embedded systems to native servers, mobile applications and browser code. There is a lot between "can emit the artifact" and "is pleasant to use in production", but at least the compiler architecture is no longer tied to one platform.
Subparsers, because one grammar is apparently not enough
One of my favourite parts is compile-time subparsers. A library can register a parser written in Abla and introduce an embedded syntax without adding special knowledge to the compiler.
For example, the JSON library can provide this:
#import("abla/json")
fun main: int {
val frozen = #$json {"number": 20, "name": "abla"}
val runtime = $json {"number": 20}
frozen.getInt("number") + runtime.getInt("number")
}
The $json parser runs during compilation because it needs to understand the source, but the typed expression it creates can run either at compile time or runtime. The parser can hand control back to the Abla parser for interpolated expressions, and subparsers can be nested. The result still goes through the normal resolver, type checker, ownership checker and IR verifier.
This makes it possible for libraries to own syntax for things like JSON, HTML, shaders, queries, binary protocols or hardware descriptions without turning all of them into compiler keywords. It is powerful enough to become confusing, so parser nesting, compile-time effects and generated syntax are bounded and validated. Giving programmers arbitrary compile-time execution is fun; giving every build an arbitrary opportunity to corrupt itself is less fun.
Building a website in Abla
I also created Abla MVC, an experimental precompiled web framework written in Abla. Its $mvcview subparser turns HTML-like syntax into a typed and escaped HTML tree, while inspecting action calls after normal name and type resolution.
An ordinary action can execute on the native server. An action marked @client can be extracted into a WebAssembly module and execute in the browser. The view emits metadata for a small browser runtime instead of inserting handwritten JavaScript into every button.
fun counterView(model: CounterModel): MvcHtml = $mvcview
<section>
<span id="counter">{model.counter}</span>
<button onclick={incrementCounter(1)} update="#counter">
Increment on server
</button>
<button onclick={incrementLocally(model.counter)} update="#counter">
Increment in browser
</button>
</section>
The framework can generate server-action registration and POST dispatch, extract eligible client handlers, build the Wasm artifact, and choose the correct path in the browser. It also has declarative polling which pauses when the page is hidden and avoids overlapping requests.
abla.dev is now a native Abla application that uses Abla MVC through a locked GitHub package import. The page has one counter that executes on the server and another that runs in the browser as generated WebAssembly. A five-second heartbeat shows how many tabs are currently active. It is a small demo, but it proves the path from language feature, to framework, to compiled native server, to browser code.
It is also very unfinished. Client actions currently only support integer arguments and results, sessions are in memory, and production CSRF protection and hardened request handling are still on the list. I am calling it a developer preview because "framework with several important security items still on the list" is not a particularly comforting production label.
The production-language part
Abla is still experimental. Syntax can change, APIs can disappear, and the standard library is nowhere near the size required for a general production language. The compiler has serious reliability gates—self-hosting, fixed-point builds, native conformance, ownership tests, transactional outputs and libc-free checks—but passing compiler tests is not the same thing as having an ecosystem.
I do intend to take it there.
The part I find most interesting is not merely using one syntax everywhere. It is allowing libraries to extend parsing, compilation and build graphs while keeping generated code inside the same type and safety checks as handwritten code. An embedded library, a web framework and a package provider should be able to ask the compiler for different artifacts without each becoming a permanent compiler feature.
Maybe this will become a fully fledged production language. Maybe I will open this post in another six years and discover that I stopped after adding one especially complicated type-system feature. My history of unfinished projects suggests keeping both options available.
For now, though, Abla compiles its own compiler, produces native and WebAssembly programs, builds an Android application, and serves its own website. That is considerably harder to dismiss as a folder of ideas.
And yes, AI helped me write this post after reading the repositories and their history. At this point it would be strange if it did not.