A meta language idea
Meta language#
For years my dream project was creating my own language, with a syntax that follows how I think. But creating a language is one of the hardest projects you can take on: design, philosophy, implementation, tooling. After years of building skills, I felt ready to try.
I started small. I built a minimal stack-based language in C, mostly to understand how languages actually work. While doing that, another idea kept coming back: a fully configurable language. Not just configurable libraries, but configurable syntax and behavior.
How do you make a language fully configurable? From syntax to control flow, to semantics? That question stayed in my head for a long time.
In the meantime I started writing Zinc, my idea of a minimal language. At first it was just that: an idea. In Zinc I wanted macros for metaprogramming, like #define in C/C++ or macro! in Rust. Then I pushed the idea further: what if the language was based only on macros?
Zero features by default. Everything is defined by the user.
Zinc has no built-in if, no while, no for. The core feels like assembly: only goto, label, and cmp. But macros can operate on the AST and rewrite code. With those tools, you can define your own control flow.
For example, this is how an if statement is defined:
macro if $cond $then_block else $else_block {
__cond_val := $cond;
cmp __cond_val 0
goto __else;
$then_block
goto __end;
label __else:
$else_block
label __end:
}
And this is how it is used:
x := 10; // variable declaration builtin
if x > 5 {
print("x > 5");
} else {
print("x <= 5");
}
There is no real if in the language.
It is just a macro expanding to cmp, goto, and label.
This is not only a clever idea, it also leads to a very simple compiler, compared to a traditional language. Once macros are expanded, the result is a block of code that already looks like assembly.
At that point, the compiler does not need to understand high-level constructs anymore. It only has to translate goto, label, cmp, and basic operations. The work required to generate real assembly is surprisingly small.
This approach also has serious drawbacks.
The language is hard to read. Since syntax is not fixed, two codebases written in the same language can look completely different. You must first learn the macros before you can understand the code.
Errors are harder to reason about. When something goes wrong, you are often debugging macro expansions, not the code you originally wrote.
Tooling becomes more complex. Syntax highlighting, formatting, static analysis, and IDE support all depend on knowing the language structure, and here the structure is user-defined.
There are no built-in data structures. No struct, no class, no enum by default. Even basic abstractions must be implemented using macros, which increases boilerplate and cognitive load, especially at the beginning of a project.
There is also a high cognitive cost. Writing macros that manipulate the AST is powerful, but it requires deep understanding. Small mistakes in macros can lead to very confusing behavior.
The language does almost nothing by itself.
That is both its strength and its weakness.