DSLs in the Age of LLMs

DSLs in the Age of LLMs

There are certain classes of problems that can benefit from creating a small language, called a Domain Specific Language (DSL).

In the past that was often a difficult endeavor, but LLMs change that.

In this post I will walk through what they are, why they are useful, how LLMs change the equation, and present one that I built using Claude Code.

What is a DSL?

Languages like Python or Rust are general purpose: you can use them for anything. A DSL is trying to capture a small domain.

A couple of popular examples:

  • SQL
  • Regular expressions
  • CSS

With SQL you can ask questions like: which customers placed more than one order this year, how much did each spend, biggest spenders first?

SELECT c.name, SUM(o.amount) AS total
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.placed_at >= '2026-01-01'
GROUP BY c.name
HAVING COUNT(*) > 1
ORDER BY total DESC;

You get to just declare what you want, and the language and the engine figure it out.

Behind the scenes there is a lot going on. To answer those seven lines the database has to:

  • Plan out the query
  • Take a snapshot of the data so nothing gets updated mid-query and causes inconsistencies
  • Read from the tables, searching indexes where it can
  • Join the data together
  • Group the rows by customer and add up the totals
  • Filter out results that don't match
  • Sort the results

If you had to write the code that did all of that for every query, it would be a nightmare.

Databases are crazy complex: multiple tables, columns, and everything underneath them. But SQL presents a view of that world that lets you reason about and work with just the data, not the whole system that makes it possible.

Why they are powerful

Alfred North Whitehead, in An Introduction to Mathematics (1911):

By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and in effect increases the mental power of the race.

Fred Brooks, in No Silver Bullet (1986), breaks solving problems into two parts: essential complexity (the problem) versus accidental complexity (everything the tools make you do to get there). SQL lets us think about the essential complexity and not the accidental complexity.

That is the beauty of a good DSL. Better abstractions allow better thinking, chunking, or compressing concepts so you can hold more of the problem in your head at one time.

Why there are not many

A DSL is like any tool. There are tradeoffs to when you use one. Most of the time they are overkill, but at times they make a lot of sense.

But even then there is a cost. Building and maintaining a small language is challenging, and when bugs arise you now have to figure out if it is a language problem or an engine problem. Does the underlying code have an issue? Or is the language failing before it even gets there?

There is also a decent amount of knowledge needed just to build even a small language. Languages have a bunch of steps, like parsing, building ASTs, running them, etc.

That all has to be written and maintained. I think you can see where I am going with this.

How LLMs change the equation

Current LLMs can write all that code with no problem. In fact, they have probably seen more examples of small language code than of whatever domain you are working in. Your domain is probably niche, but a DSL is basically all CS fundamentals.

An LLM can write a thousand lines of code very quickly, faster than anyone can retain. But by putting things into a DSL, a human only has to reason about the small language and not all the code behind it. Build in a solid test suite and you can get to a point where it can be reliable.

We get:

  • A small language that allows us to reason at a higher level about our domain
  • A shared space where an LLM and a person can interact
  • A smaller surface area to reason about, while letting the LLM write as much code as needed

LLM handles expression. DSL constrains representation. Interpreter handles truth. Human can inspect the bridge.

Zeitfolge

Zeitfolge is a DSL that I use daily to handle a large class of date and time problems.

Writing code to work with dates and times is miserable in most programming languages. Even with good libraries you are fighting the machine: a datetime with no time zone attached fails silently, "the 15th" is a different day in every zone, and you end up writing your own interval math before you get anywhere near the actual question.

Zeitfolge has collapsed a bunch of separate scripts and tools into one small language.

The one I use most is visa planning. Some countries have complicated rules on how long you can stay and when you count as a tax resident. US citizens can stay up to 90 days in a rolling 180-day window in most of the EU. It is a pain to keep track of. I used to have an 86-line Python script for it. In Zeitfolge it is one line:

rolling days of trips in 180 days limit 90   # every trailing 180-day window, against the cap

Another one that comes up constantly: San Francisco, London, and Bangalore each answer calls 07:00 to 22:00 local. When can all three be on the same call?

span = 2026-09-15 .. 2026-09-15

timezone = America/Los_Angeles
sf  = every day 07:00 .. 22:00
timezone = Europe/London
ldn = every day 07:00 .. 22:00
timezone = Asia/Kolkata
blr = every day 07:00 .. 22:00

window = (sf & span) & (ldn & span) & (blr & span)
show window in America/Los_Angeles, Europe/London, Asia/Kolkata
#  → 07:00–09:30 SF · 15:00–17:30 London · 19:30–22:00 Bangalore

Every line is about people and their hours. The time zone math, the day boundaries, daylight saving, all of it is the engine's problem.

And for a small language you don't need an MCP server or anything. You can paste the language spec into a fresh session and any modern model can write Zeitfolge programs in one go.

A few live examples:

  • Schengen day counter — how many of my 90 days are used, and when they come back
  • Callable hour — the meeting window above, with a visualizer
  • Deploy freeze — assert a release lands inside shipping hours and outside a holiday

Tips for building your own

Start very small, using examples of things you want to do, one at a time.

I have tried just describing at a high level what I wanted, but that didn't turn out well. Starting with an example works much better. I want a time-based DSL, but let's start with one thing: I want to be able to diff two dates and then see it in the visualizer. Ok, now let's add that. At each step, review and make design decisions.

You get a more organically grown language from concrete examples. I would also use the rule of three a lot: if I wanted to add something, I would still wait until another couple of examples proved the pattern. Then you have three good examples for a new feature.

Have it start with tests out of the gate. Every language feature gets a test.

DSLs have always been a great idea that was too expensive to justify. That cost just dropped. If there is a domain you keep fighting with, it might finally be time to build that DSL.