I’ve been working on a domain-specific language for a point-and-click framework called Escoria built on top of the Godot game engine.
The original DSL was implemented as a regular language, largely using regular expressions. It was flexible in the sense that it accepted a wide range of inputs, but that flexibility came at a cost.
There were very few meaningful constraints.
As a result:
- pre-compilation validation was limited
- runtime behaviour was harder to reason about
- error handling was brittle
- and extending the language became increasingly difficult
The system accepted many inputs, but it wasn’t always clear which ones were valid in a meaningful way.
It also made the developer experience harder to reason about and less predictable: Feedback would frequently come far too late (or not at all), often without enough relevant information.
So I rewrote the DSL as a context-free language with a grammar closer to that of Python or Godot's own GDScript.
That change introduced more structure.
It also made the system more capable.
The Wrong Kind of Flexibility
At first glance, the original DSL seemed more flexible.
Because it was defined using regular expressions, it could accept a wide variety of forms. There were fewer rules about how expressions needed to be structured.
But that kind of flexibility was superficial.
Without a grammar:
- you can’t easily validate structure
- you can’t enforce ordering or nesting
- you can’t reason about intent
- and you can’t provide meaningful feedback when something goes wrong
The system becomes permissive, but not expressive.
It accepts input, but it doesn’t understand it.
Introducing Constraints
Moving to a context-free grammar changed that.
By defining explicit rules for how statements are structured, the DSL became:
- easier to validate before execution
- more predictable at runtime
- more resilient to malformed input
- and easier to extend in a controlled way
Instead of accepting “anything that matches,” the language now accepts only what fits the grammar.
That restriction turns out to be extremely powerful.
Because once structure is enforced:
- errors can be caught earlier
- behaviour becomes more consistent
- and new features can build on a stable foundation
The system becomes less permissive, but more capable.
--
A Brief Note on the Underlying Models
The distinction between regular and context-free languages is well-established in computer science, so I won’t go into much depth here.
At a high level (and at the risk of oversimplifying):
- A regular language can be described using patterns (e.g. regular expressions). It has no real notion of nested structure or hierarchy.
- A context-free language introduces a grammar that allows for hierarchical structure — things like sequencing, grouping, and nesting.
That distinction turns out to matter in practice.
The original DSL, being regular, could match patterns, but it couldn’t express structure in a meaningful way.
The revised DSL is defined by a context-free grammar and parsed using a custom LL(1) recursive-descent parser with a small amount of additional lookahead.
That shift — from pattern matching to structural parsing — is what made meaningful validation and extensibility possible.
Constraint Enables Expression
The interesting part is that this change didn’t reduce what the DSL could do.
In fact, it expanded it.
With a well-defined grammar, it became possible to introduce:
- more complex constructs
- clearer sequencing of actions
- better validation and feedback
- and more advanced behaviour overall
In other words, by reducing the space of valid inputs, the system became easier to reason about — and therefore easier to extend.
The constraints didn’t limit the language.
Quite the opposite: They made it usable.
From DSLs to Systems
This kind of capability isn’t just a property of domain-specific languages.
It shows up in software systems more broadly.
Systems that appear flexible often lack meaningful constraints:
- boundaries are informal
- dependencies are loosely controlled
- behaviour is only partially defined
- and structure emerges organically over time
At first, this feels productive.
Changes are easy to make. New ideas can be introduced quickly.
But over time, the lack of constraints makes the system harder to reason about.
The problem isn’t that the system allows too little.
It’s that it allows too much without distinction.
Constraints as a Design Tool
Introducing constraints changes how a system evolves.
When structure is enforced:
- invalid states become harder to express
- behaviour becomes easier to predict
- and changes must pass through well-defined boundaries
This is familiar territory in domain-driven design.
Concepts like:
- bounded contexts
- aggregates
- ubiquitous language
...all exist to constrain how a system can change.
They reduce ambiguity.
They force decisions to be explicit.
And they make the system easier to understand over time.
The DSL behaves in the same way.
By enforcing a grammar, it limits what can be expressed — and in doing so, makes valid expressions clearer and more reliable.
Where This Matters Most
These kinds of constraints become more valuable as systems grow.
In small systems, or early in a project, many assumptions can remain implicit.
As complexity increases:
- more developers interact with the system
- more features are introduced
- and more changes happen concurrently
At that point, implicit assumptions begin to break down.
Without constraints, the system doesn’t fail immediately.
It drifts.
The DSL example makes this visible in a contained environment.
In larger systems, the same pattern appears — just at a different scale.
Although not used in the example above, design techniques such as spec-driven design really begin to shine once the use of constraints is truly appreciated.
A Note on AI
One place where this becomes particularly visible is in AI-assisted development.
AI systems operate best when the space of possible inputs and outputs is constrained.
In unconstrained environments, they are forced to infer intent.
In constrained systems, they can focus on implementation.
This mirrors the behaviour of the DSL:
- when structure is implicit, the system guesses
- when structure is explicit, the system operates predictably
The more constrained the system, the more predictable the outcome.
These points explain in large part the appeal of constraints-based development techniques such as spec-driven design.
Closing Thoughts
The original DSL was flexible in a superficial sense.
It accepted a wide range of inputs, but it lacked the structure needed to interpret them meaningfully.
By introducing constraints — through a grammar and its parser — the system became more predictable, more extensible, and ultimately more capable.
This pattern shows up everywhere in software.
Constraints don’t just limit systems.
They make them usable.
Top comments (0)