profile picture

Jasper rough cut: July 2026

October 10, 2026 - jasper crystal compilers ai rough-cut

Can AI build a programming language if a person owns the design and decides what "done" means? July: a fully typed, Ruby/Crystal-like language with no GC, and the real cost in hours and tokens.

Background

Notes from building Jasper, one month at a time

Since I started working with Ruby back in 2004 I was fascinated with its syntax and readability, but not its performance. Since then I've theorized, prototyped and played with the what if idea of a compiled version of it.

Even when YARV came out in 2007, and then 1.9 became the foundation of today's Ruby, I tried to compile to different VMs, like NekoVM (initial PoC here) and many others.

Anyway, years passed and Ruby got faster, and while it remained beautiful, still lacked that performance I was used to when working with C.

Then Crystal appeared: inferred types, native binaries, all that I wanted from Ruby, resulting in my go-to option when looking at crafting tools and applications for the past 10 years.

However, that itch of trying new things never disappeared. So I wanted to explore that what if, this time sitting at the designer's seat and steering the direction of a Ruby/Crystal inspired language, aimed to be the playground for testing some theories around.

Jasper is that language, taking design and inspiration from Ruby and Crystal, but applying design techniques from other languages, like Zig, Swift, Nim and others.

I also wanted to test something out: could this be delivered using AI? What would that look like? How can we ensure it delivers what it should and how effective it is? I will include at the end of each post a summary of time (human) and money (tokens) spent in the process.

The following is the summary of what shipped, what I decided, what broke and any other bug caught along the way.

This is a rough cut and some of these decisions may change.

Rough cut: July 2026

Not my first time writing a programming language (lexer, parsers, etc), but the challenge is that this is the first time I'm just designing it and defining the acceptance criteria while delegating the coding aspects to an agent.

So this time, I started with a design spec and a plan before any single line of code: I will create the first compiler in Crystal (codename: Green Jasper), which will serve to bootstrap the compiler written in Jasper itself (codename: Red Jasper).

Initial design decisions

What is the MVP of a programming language? If we use the cake analogy, the MVP cannot be the eggs, or the milk, or the flour, but a vertical slice of the baked cake.

This means the compiler should be able to parse a basic syntax and then generate the expected executable for it.

To keep things simple, decided that Jasper will be fully typed: no inference of types except when assigning to local variables:

# ok, local variables are inferred
a = Foo.new

# ok
def foo(bar : Int32) : Int32 
  42
end

# Compiler error (no types)
def bar(baz)
end

Since we are talking about errors, I got used to LSP and tree-sitter-like approaches: error-tolerant parsing that allows you to parse, recover and capture errors on the source code.

Crystal's parser and AST are hand-written. Since I will be delegating the code writing to an agent, I need to enforce and validate what it will produce.

Decided to take Ruby's Prism approach for the AST: nodes and diagnostics (errors) will be generated from a YAML config file and use ECR templates to output the code.

That way, a drift from the expectations can be caught by re-running the generator.

Validating the work

To avoid the "there will be no bug if there was no code" scenario, the test harness was set up to perform crosscheck comparison between Crystal and Jasper.

Since I'm designing Jasper to be a subset of Crystal, the same file builds with both compilers.

The AST shape, the expected errors or the final output would be placed as fixtures (goldens) and provided to the agent to validate the work as it progressed.

Managing memory

Talking about Foo.new, it means we need to allocate memory for that instance. I wanted to avoid GC but without having to allocate/free manually each time.

Looking at other languages like Swift or Nim, the ARC (Automatic Reference Counting) approach made sense: the compiler already analyzes when something goes in or out of scope, so why not inject RefCount tracking (retain/release) that allows us to free that memory once the counter reaches 0.

But, would it perform better? Would these extra calls for reference counting impact the performance I wanted to achieve in the first place? This should be benchmarked before anything.

From AST to real code

Every time that I prototyped a programming language, I dreaded the lowering of the AST into machine code.

Nowadays everybody is using LLVM, but you need to have a PhD to fully understand bytecode generation. QBE SSA is simpler, but it limits the platforms you can target.

Thankfully I found kostya/myc in the Crystal forum. It is a small IR made precisely for building programming languages:

your AST -> myc IR -> LLVM / QBE / C -> binary

So you don't need to talk to LLVM or QBE, just output a textual IR file and then delegate to myc to use the nuances of each backend to generate your binaries.

It allowed me to get Hello World and Fibonacci examples compiling very quickly.

I don't think that without myc this experiment would have been possible.

What got implemented

Last but not least: incremental compilation.

Initially implemented, like Crystal, generation of IR per class. But changed things to so IR and object files per method:

class Foo
  def bar : Int32
    42
  end
end

Foo.new.bar()

The above code will generate different units for each part:

Each one is a separate .myc file that gets compiled into object file .o and then linked into the final executable.

Each unit is saved and the cache key is hashed on the contents of IR itself, so a change to a method body will generate a different unit and that one will be linked against the existing ones.

A quick demo:

There is no bug-free software

Even if the AI wrote the code and validated against the Crystal version, most of the issues were around memory management and when to detect objects were in or out of scope to properly release them. This took several spikes of tests and corrections:

Stack and numbers

Stack

Tokens & cost