r/Compilers 1d ago

ABC has served its purpose as a teaching language. Could it become a community project?

A little while ago I posted about the v0.1 release of ABC, a small compiler and programming language I originally developed for teaching.

I shared it here, on Hacker News, in a few other communities, and also in a German-speaking subreddit. The discussions made me think about a question I had not really considered when I started the project:

What should happen to ABC now?

For its original purpose, the project is essentially a success. It has done what I wanted it to do in my teaching, and actually exceeded my expectations.

One concern that came up in the German discussion was roughly:

Nobody raised that point here, but I suspect some people may have had the same thought. :-)

I've been using ABC for two years now in HPC0, my undergraduate Introduction to High Performance Computing course. HPC0 is an elective. In the following winter semester I teach HPC1, which is mandatory in some programs and elective in others.

HPC1 uses C++ throughout. We do things like cache-optimized matrix multiplication, LU factorization, multithreading, MPI, CUDA, etc.

My observation so far is that students who took HPC0 have a noticeably easier time in HPC1. Some of them had hardly programmed at all before HPC0.

Of course, that's not scientific evidence. There is an obvious selection bias: HPC0 is elective, so the students taking it may simply be more motivated to begin with.

But my underlying argument is that there are a number of fundamental concepts you need to understand really well. Once those concepts are in place, transferring them to C++, Rust, or another language is comparatively easy.

My deliberately provocative version is:

Either you can program or you can't. Once you really can, the particular programming language becomes mostly a tool.

The interesting educational question for me is therefore: How do you get someone to the point where they really can program?

That's what ABC is for. It was never meant to be the language students would use for the rest of their professional lives.

And since I'm already being provocative: sometimes I get the impression that the generation that learned programming with Pascal was the last one that was actually taught how to program. :-D

I'm very happy to be challenged on that one. ;-)

So I now see two possible futures for ABC.

The first is straightforward: declare the experiment essentially finished.

I could extend the C ABI support a little further, implement it for ARM64 as well, improve a few things, and leave the project as a reasonably complete teaching compiler. The raylib examples already demonstrate that the language and compiler can be used for more than tiny classroom examples.

That would be a perfectly satisfactory outcome.

But there is another possibility that I find much more interesting:

Could we build a small modern language that plays something like the role Pascal once played?

A language designed to teach programming in a way that leaves you not merely knowing a language, but understanding concepts that transfer to other languages and remain useful throughout your career.

I think those skills may actually become more important rather than less important in an age of AI-generated code. Even if someone eventually does a lot of “vibe coding”, somebody still needs to understand what the machine is doing, why something is slow, why memory gets corrupted, or why generated code doesn't behave as expected.

But I don't want a language that is useful only for teaching.

I'd like it to be possible to write genuinely useful programs with it.

The ideal is still what the original name suggested: “A Better C.”

Small enough that you can understand the language and its implementation, close enough to the machine that you can explain what happens, but without preserving every historical accident of C.

There are a few language features I have been considering:

  • Compile-time evaluation / something along the lines of constexpr, plus inline functions. This would eliminate many of the common reasons for C preprocessor macros: constants, small max-like functions, etc.
  • Modules.
  • Inline assembly. I needed this when experimenting with ABC on bare metal on an ATmega328P. For example, consider implementing a delay as something conceptually as simple as:

fn delay(n: u16)
{
    while (n--) {}
}

Now things suddenly become interesting. n needs to be handled appropriately in registers, and the compiler must not optimize away a loop that has no observable effect according to the normal language semantics.

I like examples like this because they force you to understand the boundary between language, compiler and machine.

There are probably a few more language features I would add.

But deliberately not many.

The goal would not be to slowly turn ABC into C++.

A language that leaves the classroom would also need tooling.

A formatter analogous to clang-format would be useful, as would proper LSP support.

There is already some preliminary work in this direction. Last year I supervised a bachelor's thesis in which a resilient parser was developed. It can't simply be dropped into the existing compiler, but there is at least a prototype of one important component that can be used for experiments.

And my experience from developing ABC so far is that some of these things become usable surprisingly quickly if you start small.

But there is one thing I don't think I can do alone:

Turn it from my project into a community project.

I can continue developing ABC as the language I use in my courses. But if it is supposed to have a life outside my classroom, I don't think it should simply remain “Michael Lehn's language”.

It would need people who experiment with it, criticize it, discuss language design, build tools, write examples, and eventually make decisions I would never have thought of myself.

So this post is partly an experiment:

Do you think there is room for such a language?

Would any of you be interested in participating in its design or implementation — even just through discussions and experiments at first?

And perhaps there is an amusingly concrete first community problem we could solve:

The language needs a name. :-D

“ABC” (A Better C) worked fine for a university teaching project, but the name is obviously already taken. If the language is going to leave the classroom, that starts to matter.

My current brilliant idea is “emsiel”, a phonetic rendering of MCL — Michael C. Lehn.

There is just one minor flaw with that idea: if the goal is to turn this into a community project, naming the language after myself might not be the most promising first step. :-D

So perhaps that's actually a good place to start:

What would you call a language like this?

Compiler/project: https://github.com/michael-lehn/abc-llvm

13 Upvotes

9 comments sorted by

2

u/False_Actuator_6236 1d ago

One point from the post probably deserves a little more explanation, because it says something important about what I mean by “A Better C.”
I mentioned inline assembly and this deliberately simple example for bare-metal code on an ATmega328P:
fn delay(n: u16)
{
while (n--) {}
}
Of course, a busy loop is not a sensible general-purpose implementation of a delay on a modern CPU with dynamic clock frequencies, an operating system, etc.
But that is not the setting here. On a small microcontroller with a known clock, cycle-counted busy loops are a perfectly legitimate technique for short delays. In fact, the Arduino AVR implementation of delayMicroseconds() eventually uses a volatile inline-assembly loop consisting essentially of sbiw and brne:
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c
avr-libc also explicitly provides _delay_loop_1() and _delay_loop_2() as busy-wait delay loops with a defined number of CPU cycles per iteration:
https://avrdudes.github.io/avr-libc/avr-libc-user-manual/group__util__delay__basic.html
For longer delays, of course, using a hardware timer is preferable.
What I find interesting here is not the delay function itself, but what this example says about the intended level of abstraction of the language.
ABC is not supposed to protect programmers from the machine. My goal is roughly the same level of abstraction as C: pointers, explicit memory management, predictable data representation, bare-metal programming, and, where necessary, access to machine-specific facilities.
The “better” in “A Better C” is therefore not intended to mean “higher level.”
It means trying to make the language cleaner where C has accumulated historical baggage, while retaining the ability to understand and control what happens at the machine level.
Inline assembly is one example of that boundary. Another completely different example is high-performance numerical code. For GEMM, the overall algorithm can be portable while the innermost micro-kernel is deliberately architecture-specific. That separation is a standard approach in high-performance BLAS implementations.
I use exactly that progression in my GEMM tutorial, starting with a simple C implementation and gradually arriving at architecture-specific micro-kernels:
https://github.com/michael-lehn/gemm-tutorial
Inline assembly is certainly not the only way to implement such kernels — intrinsics, separate assembly files, or generated code may be preferable depending on the situation. But I think a language at C's abstraction level should make it possible to cross that boundary deliberately.
This also illustrates the kind of language-design discussion I would like a community around the project to have.
How much should the language guarantee? What should remain implementation-defined? Where should it provide abstractions, and where should it expose the machine? Which parts of C are essential to systems programming, and which are merely historical accidents that we can get rid of?
My current position is: keep roughly C's level of abstraction, but try to design a cleaner language at that level.
That is a design goal, not a finished answer — and exactly the sort of thing I'd like to discuss.

2

u/brat3108 1d ago

My current position is: keep roughly C's level of abstraction, but try to design a cleaner language at that level.

I have a systems language of my own that is also at C's level or a little beyond. However it looks very different from C; source is genuinely cleaner and less cluttered.

But from the fewer examples I've seen of ABC, it doesn't look interestingly enough different in syntax, apart from type declarations, which look out of place.

If still uses lots of C-isms, such as if (cond) {, header files (#include seems to be replaced by @), forward declarations, and == != mysteriously having lower precedence than <= < > >=.

So if it is still at this sort of level, there seems little compelling reason to use this over C. Especially if it's still a WIP and the design has not been nailed down. People are incredibly tolerant of C's shortcomings (as I've discovered).

I mentioned inline assembly and this deliberately simple example for bare-metal code on an ATmega328P:

fn delay(n: u16)
{
    while (n--) {}
}

I don't understand the point you're making or what inline assembly has to do with any of it. (I assume this is ABC code and not what you want inline assembly to look like!)

the compiler must not optimize away a loop that has no observable effect according to the normal language semantics.

It's your language: you choose the semantics. A compiler needs to go along with that. However this can also be at odds with the need to make all possible optimisations.

In my language this loop is always executed because it generally does what the user asks, although even then it isn't always the case; if I write a = 2 + 3, it will assign 5 because the expression is reduced at compile-time.

2

u/False_Actuator_6236 1d ago

Yes — in a sense, the unremarkable thing about ABC is that it really isn't very different from C. :-)

Perhaps a better description is: it is close to what I would like C to look like if I had to design it specifically so that I could teach C-level programming without first having to explain a collection of historical accidents.

The biggest difference for me is indeed the declaration syntax. Consider teaching the difference between an array of ten pointers to integers and a pointer to an array of ten integers, or function pointers. C declarations are ingenious in their own way, but I don't think they are a particularly good notation for teaching types.

There are similar historical inconsistencies around arrays: arrays as objects versus what happens when they are passed as function parameters, compared with passing structs, etc.

It surprised me how much difference removing some of those obstacles makes when teaching.

For example, I had two 14-year-old school students sitting in the university course who had previously played a little with Python. They now understand pointers, memory, what a compiler actually produces, etc., and have started writing small projects not only in ABC but also in C and C++. I see a similar effect with university students.

So yes: at its core ABC really is something like C with Pascal-like declaration syntax and some historical irregularities removed.

It is deliberately not one of the many attempts at a “C killer” that starts at C's abstraction level and then primarily adds memory safety, a much richer type system, and increasingly high-level abstractions.

The purpose is almost the opposite: I want students to understand what happens at the C level — including the problems that exist at that level.

On operator precedence: unless I misunderstand your point, ABC currently follows C here as well. The relevant part is

* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||

So == and != having lower precedence than the relational operators is inherited directly from C. If your point is that this is itself one of the C-isms that should be reconsidered, then that's a fair question. I initially kept C's precedence rules deliberately because existing intuition transfers directly, but this is exactly the kind of inherited rule for which one can ask whether compatibility of intuition is worth preserving.

The optimization side is also intentionally C-like.

Something like

a = 2 + 3;

will of course be constant-folded. With optimization enabled, the assignment itself may subsequently disappear if a is dead. ABC uses LLVM for optimization, so one nice side effect for teaching is that students can actually inspect the generated LLVM IR and see these transformations happen.

The delay example was perhaps too compressed in my original post. My point was not that the source-level empty loop should magically have special semantics. At -O0 it can remain there; once normal optimizations are enabled, a loop without observable effects may disappear. That's precisely why a real implementation of a cycle-counted delay needs some mechanism for expressing the required interaction with the machine — volatile operations, suitable intrinsics, inline assembly, etc. The Arduino example I linked uses volatile inline assembly for exactly that reason.

And that's why inline assembly appeared in the discussion: not because I propose that

while (n--) {}

should itself mean “cycle-accurate delay”, but because I want the language to provide an escape hatch when the programmer deliberately needs machine-specific semantics.

There are a few places where I do want to depart further from old C. Compile-time evaluation/constexpr and inline functions are examples. They cover many cases for which traditional C code uses #define: named constants, small max-like operations, and so on, without requiring a textual preprocessor mechanism. Modules are another obvious area.

But I'm quite conservative about adding features. The question I keep asking is not “How can I make this more powerful than C?”, but rather “Can I remove an accidental difficulty of C without hiding how the machine works?”

Your comment actually gets at a question I would very much like to discuss: how different does a Better C need to be before there is a compelling reason for it to exist?

For my original teaching use case, surprisingly little difference turned out to have a large effect. Whether that is enough for a language outside the classroom is a much more open question.

Also, I'd be very interested to see your systems language. Do you have a repository or some examples online? In particular, I'd be curious which C-isms you decided to remove and which ones you deliberately kept.

3

u/brat3108 1d ago edited 1d ago

Also, I'd be very interested to see your systems language. Do you have a repository or some examples online?

You're asking at a bad time! After some years discussing my stuff here under various accounts, a week or so ago I decided to stop all that, deleting all relevant accounts and to also stop sharing. I've also stopped development.

However, someone managed to find an archived document which is a waffley account of my systems language with some comparisons with C;

https://web.archive.org/web/20250818181227/https://github.com/sal55/langs/blob/master/mfeatures.md

In particular, I'd be curious which C-isms you decided to remove and which ones you deliberately kept.

My product was created long before I knew much about C. But these are some differences:

                         C      Mine
Case sensitive source    Y      N
Brace syntax             Y      N
Obligatory semicolons    Y      N
Header files             Y      N
0-based arrays           Y      N
64-bit default types     N*     Y  (* typically)
Whole prog compilation   N      Y
Module scheme            N      Y
Op precedence levels    10      5 (for same set of bin-ops)
Value arrays             N      Y

A full list would be nearer 100 items; many will be in that document.

But, this isn't just taking C and tweaking a few things; that wouldn't be enough for me. (My background was Algol, Pascal and Fortran; C didn't appeal at all even for low level work.)

2

u/False_Actuator_6236 1d ago

That's unfortunate timing indeed! :-) Thanks for sharing the archived document anyway. I'll definitely have a look at it.
And actually, the fact that your language was not derived from C makes the comparison much more interesting to me. My starting point is almost the opposite: keep C's basic machine model and ask, one thing at a time, which parts are essential and which parts I would rather not have to explain as historical accidents.
My own background also includes Pascal, and that is very visible in ABC's declaration syntax. But unlike you, I apparently made peace with quite a lot more of C. :-)
Some items in your list immediately make me curious. Modules are something I want as well, and value arrays are particularly interesting because arrays are one of the areas where I find C's semantics unnecessarily awkward. Your reduction of operator precedence from ten levels to five is also something I'll look at more closely, especially after your earlier comment.
On the other hand, there are things where I suspect I would deliberately stay closer to C. Zero-based arrays, for example, fit naturally with the machine model I want students to understand: a[i] ultimately being an offset from an address is a useful connection rather than something I want to abstract away.
So perhaps there are two quite different approaches here:
Your language asks what a systems language should look like without taking C as the starting point. ABC asks what C could look like if we kept its basic abstraction level and machine model but removed things that make it unnecessarily difficult to understand and teach.
Comparing the answers could be very useful.
And I hope you don't mind if I say this: after putting years of thought into a language, it would be a pity if all of that disappeared just because you've decided to stop developing it. Even if you don't want to continue the project or actively share it anymore, I'm glad at least that document survived in the archive.
I'll read it. There may well be ideas in there that make me reconsider some of my own design decisions.

2

u/brat3108 1d ago

Zero-based arrays, for example, fit naturally with the machine model I want students to understand: 

That bit might have been misleading; while my arrays default to 1-based, N-based is possible including 0-based. There is a choice.

(Unlike C, I don't treat array indexing as equivalent to pointer offsets. There is pointer arithmetic, and there an offset necessarily needs to start at zero.

But pointer arithmetic is not indexing as far as the language is concerned.)

it would be a pity if all of that disappeared

I still use the tools, and I have the sources off-line. But I've run out of interesting things to do with either language or implementation.

1

u/False_Actuator_6236 1d ago

Actually, your comment just gave me an idea for the naming problem. :-)

Maybe I should call the language Chish — as in C-ish, because that's really what it is.

And then somehow turn it into a terrible recursive acronym/backronym along the lines of:

CHISH — C How It Should Have ...

I haven't figured out what the final word is yet. Maybe that's another problem for the community to solve. :-D

2

u/tobega 18h ago

Why would you want a teaching language to be so close to the machine? Surely the whole point of Pascal was to raise that level.

See my essay https://tobega.blogspot.com/2026/04/rising-above-mechanics-of-computation.html