# Writing a C Compiler

**URL:** <https://forum.ada-lang.io/t/writing-a-c-compiler/1024>\
**Category:** General\
**Created:** [July 27, 2024, 3:23am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024 "2024-07-27T03:23:12Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [August 10, 2024, 8:27pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/21 "2024-08-10T20:27:14Z")

</div>

It’s a Three-Address Code (TAC) defined in the book… Sort of a high level assembly IR where each instruction has up to three operands: two sources and a destination.

If you’re at all interested in this stuff, I highly recommend picking up a copy of the book. No Starch has 32% off everything this weekend with the **DC32** coupon code.

> **[Writing a C Compiler](https://nostarch.com/writing-c-compiler)**
>
> A fun, hands-on guide to writing your own compiler for a real-world programming language.

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [August 15, 2024, 7:05am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/22 "2024-08-15T07:05:51Z")

</div>

## Chapter 5: Local Variables

The bug I encountered in Chapter 1 where `return2` was parsed as the `return` keyword came back to haunt me. I was missing `'0' .. '9'` character range in identifier names. Chapter 5 tests include a variable named `void2` which was incorrectly parsed as a `void` token. After fixing the lexer, I re-ran all of the previous chapters’ tests and everything looks fine now.

The rest of the implementation went fairly smoothly once I got through reading the chapter, which took longer than usual as I kept losing focus. Variable resolution just doesn’t excite me and feels like a bit of a chore.

I refactored most of the `Unbounded_String` operations on `Identifier`s into a single `WACC.Strings` package just to avoid duplicating the `Make_Identifier` logic in both the `Semantic_Analysis` and `TACKY` packages. I expect the `Strings` package to grow more functionality over time.

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [August 16, 2024, 6:26pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/23 "2024-08-16T18:26:34Z")

</div>

## Chapter 6: if Statements and Conditional Expressions

I never liked ternary operators in C. The list of examples for precedence rules and undefined behavior in this chapter really drive home how dangerous they are. Still, it’s part of the langauge, so we must. I liked how the later stages of the compiler didn’t need updating here. We already have conditional jump instructions, so adding if statements is really just a frontend concern.

I’m a bit surprised that compound statements get their own chapter, but I’m sure I’ll understand soon.

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [August 21, 2024, 8:34pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/24 "2024-08-21T20:34:59Z")

</div>

## Chapter 7: Compound Statements

Oh. Scoping rules. That’s why. I like the variable renaming and map copy approach here. Seems simpler than what Crafting Interpreters does with a nested tree of scopes you have to traverse to resolve.

I did a bit of refactoring in the semantic analysis pass here to more closely match the function names in the book’s pseudocode. Every time I try to be more clever than the book, I have trouble remembering why I did it a few days later. There’s a lesson there, certainly.

## Chapter 8: Loops

There’s a lot of subtle behavior happening in this chapter that took me a while to wrap my head around. For loops in particular have a lot of moving parts. I’ve fallen into the trap of using `null` pointers to represent optional values. I may go back and try to fix this at some point, but it would be a large refactor of the ASDL data structures. I’ll live with it for now.

The extra credit `goto` statements I added in chapter 6 are broken now. The `goto_bypass_init_exp` test has a goto jump to a label inside a `for` loop. This is supposed to skip executing the `for` initializer. From my understanding of the assembly output, it does this correctly, but I’m not certain. If the label the `goto` is jumping to is indeed in the wrong place, I think I need to add a new fixup pass in the assembly stage that moves the label if it precedes a `For_Init_Node`. I’m not certain this is the right approach though.

---

<div class="post-metadata">

**Author:** ![Tom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/t/3d9bf3/32.png) [@Tom](https://forum.ada-lang.io/u/Tom)\
**Post date:** [August 25, 2024, 10:50am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/25 "2024-08-25T10:50:11Z")

</div>

Hi you might prefer the book “Crafting a Compiler” by Charles N. Fischer and Richard J LeBlanc, Jr bublished by Benjamen Cummings 1988. Tt’s all about writing an Ada compiler in Ada of course. It covers all the basic theory and has lots of Ada code in it. Great project. I would love to write an almost Ada compiler and get rid of a few things I find a bit irritating about it. However after more than 3000 hours of Ada and years of C/C++ before that I am hooked on Ada. Even so far as to write www.adaworks.it no its an http not https. Sorry. Server written in Ada though.  
Tom

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [August 26, 2024, 5:30pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/26 "2024-08-26T17:30:31Z")

</div>

> [@JeremyGrosser](#):
>
> I wrote the lexer by hand rather than using regex as suggested in the book. `GNAT.Regexp` is not PCRE-compatible and I didn’t want to try to work around the edge cases there. Maybe this will come back to bite me, but seems okay for now.

My advice: **never** use RegEx, not even for “recognizing integers”.

> [@JeremyGrosser](#):
>
> I wrote a `WACC.IO` package to abstract all of the file operations. The whole input file is read into memory with `System.Mmap` upon open and writing uses `Ada.Text_IO`. There are many opportunities for future optimization.

An alternative to this would be to use EVIL’s [file-interface generic](https://github.com/OneWingedShark/EVIL/blob/8532aeb46c19e256ccc6dbc5e0452d44f1cf0965/src/evil-util-files.ads) (providing a self-closing file-type w/ stream access) and/or Byron’s [Readington subsystem](https://github.com/OneWingedShark/Byron/blob/18305e400be9a31d1b1e55a6da4d984c6a8926df/src/reader/readington.adb) (stream to Wide\_Wide\_String [full Unicode]).

> [@JeremyGrosser](#):
>
> Codegen went suprisingly smoothly. Having a separate data structure for assembly seems like overkill at this point, but I can see how it might be useful for optimization passes later.

Another option is [Using a High-level Language as a Cross Assembler](https://dl.acm.org/doi/pdf/10.1145/954269.954277). (3 page paper)

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [December 9, 2024, 12:23am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/27 "2024-12-09T00:23:18Z")

</div>

Any progress on this?

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [December 9, 2024, 1:10am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/28 "2024-12-09T01:10:59Z")

</div>

I’m almost done with chapter 9. There are some more notes in the GitHub repo: [GitHub - JeremyGrosser/wacc](https://github.com/JeremyGrosser/wacc?tab=readme-ov-file#chapter-9-functions)

I got distracted by nice summer weather, then had some health issues that prevented me from working on it for a few months. I figure I’ll get back into the compiler book next year after Advent of Code.

---

<div class="post-metadata">

**Author:** ![F-Loyer](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/f-loyer/32/531_2.png) [@F-Loyer](https://forum.ada-lang.io/u/F-Loyer)\
**Post date:** [December 15, 2024, 11:09pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/29 "2024-12-15T23:09:46Z")

</div>

The preference for a language which supports pattern matching doesn’t surprise me. The Bootstraping of Rustc uses OCaml which supports this feature. Why the choice of such an unusual language if not for such a reason?

Then a simple optimisation pass - here a constant folding - could be written (here in OCaml):

```ocaml
let rec constant_folding expr =
   match expr with
   | Addition(expr1,expr2) -> 
       (match constant_folding expr1, constant_folding expr2 with
           | Integer(i1), Integer(i2) -> Integer(i1+i2)
           | opt1,opt2 -> Adition(opt1,opt2)
       )
   ...

```

With a language that doesn’t support pattern matchning, such an algorithm would need longer expressions.

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [December 16, 2024, 3:11am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/30 "2024-12-16T03:11:24Z")

</div>

> [@F-Loyer](#):
>
> Why the choice of such an unusual language if not for such a reason?

IIRC, OCaml and SML are both well-defined, stable, consistent/constant/completed languages — meaning that they share none of the variability, lack of standardization, and churn that Rust has.

---

<div class="post-metadata">

**Author:** ![F-Loyer](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/f-loyer/32/531_2.png) [@F-Loyer](https://forum.ada-lang.io/u/F-Loyer)\
**Post date:** [December 16, 2024, 5:56am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/31 "2024-12-16T05:56:50Z")

</div>

I would say rustc can’t be chosen to bootstrap itself. But among all the other languages, OCaml has been chosen for multiple reason, and one of them could be the pattern matching. (The type safety and the functional paradigm surely may be other reasons).

Now the Rustc is self-hosted.

---

<div class="post-metadata">

**Author:** ![wutka](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/wutka/32/854_2.png) [@wutka](https://forum.ada-lang.io/u/wutka)\
**Post date:** [March 4, 2026, 1:32am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/33 "2026-03-04T01:32:53Z")

</div>

I know it has been close to 2 years since this thread started, but I took a glance at your code, trying to pick up tips on structuring my Ada code better. I did an Ocaml version of the compiler, although only through chapter 10 (if you stopped at 9, you made the right choice!). I have lately been working on one in Ada. I have a tendency to just return structs or have them as out parameters, but I notice you use access types pretty much everywhere. Is that the typical Ada way to do things? So far, I have only used access types for expressions because of the recursive references. I was particularly wondering about vectors, because it seemed like it would be better to have a vector of structs than a vector of pointers to structs. I’m not asking this as a criticism, I just want to write better Ada.

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [March 4, 2026, 2:34am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/34 "2026-03-04T02:34:28Z")

</div>

No, it is not necessary to use access types and generally they should be avoided or abstracted away inside containers if possible.

I made the mistake of using `access` in this codebase to indicate an optional field, where `null` is the absence of a value. I really should have used a discriminated type or something like the generic [optional](https://github.com/mosteo/optional) crate instead. If I were to rewrite my compiler, I’d do it differently.

Chapter 10 is really a stress test of how well structured and maintainable your codebase is. It requires refactoring several passes all at once. I found it to be quite difficult with the way I structured the AST as discriminated record types. I did eventually get it working, but it took several days of reading x86\_64 assembly and comparing it to the output from the author’s reference implementation. Not fun.

I do want to finish the book at some point, but I think I want to start over, either with an object-based approach or in an interpreted language with better introspection primitives.

If you’re looking for an example of a very well structured compiler, check out @sttaft’s [parasail](https://github.com/parasail-lang/parasail/tree/main/semantics).

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [March 4, 2026, 7:40pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/35 "2026-03-04T19:40:10Z")

</div>

6 posts were split to a new topic: [Semantic tree generation and traversal](https://forum.ada-lang.io/t/semantic-tree-generation-and-traversal/4224)

---

<div class="post-metadata">

**Author:** ![bjorn-lundin](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/bjorn-lundin/32/106_2.png) [@bjorn-lundin](https://forum.ada-lang.io/u/bjorn-lundin)\
**Post date:** [March 8, 2026, 12:56pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/36 "2026-03-08T12:56:03Z")

</div>

> [@JeremyGrosser](#):
>
> I made the mistake of using `access` in this codebase to indicate an optional field, where `null` is the absence of a value.

The AWS tool wsdl2aws generates ada stub code from WSDL docs that define SOAP calls.

The way it indicates an optional value is by wrapping it in a Vector from Ada.Containers,

and add a pre-condition that there can only be 0 or 1 element in that vector.

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [March 9, 2026, 4:09am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/37 "2026-03-09T04:09:51Z")

</div>

> [@JeremyGrosser](#):
>
> I made the mistake of using `access` in this codebase to indicate an optional field, where `null` is the absence of a value. I really should have used a discriminated type or something like the generic [optional](https://github.com/mosteo/optional) crate instead.

If you’re using a set of properties, where it’s extant/non-extant, boolean-arrays are nice: `(Aspect1 | Aspect3 => True, Aspect2 => False)`.  
If you’re using a collection of properties, an array of `Abstract_Property'Class` is nice. Remember `Properties'(2..1 => <>)` is the null-range empty-array.  
Lastly, one thing to consider is using a multiway-tree (Ada.Containers) instantiated with `Root_Thing'Class` as the element. (I’m using this style with my DIANA-inspired IR.) — Tip: you can use a `Cursor` from the tree in the children to indicate relative links.

> [@JeremyGrosser](#):
>
> I found it to be quite difficult with the way I structured the AST as discriminated record types. I did eventually get it working, but it took several days of reading x86\_64 assembly and comparing it to the output from the author’s reference implementation. Not fun.

Interesting.  
Was it discriminated records themselves, or just your approach?  
I’m thinking of adding an `Execute` on the root/abstract type of the DIANA, so that it would allow me to make the nodes run/interpret themselves.

---

<div class="post-metadata">

**Author:** ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)\
**Post date:** [March 9, 2026, 5:13am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/38 "2026-03-09T05:13:04Z")

</div>

> [@OneWingedShark](#):
>
> Was it discriminated records themselves, or just your approach?

It’s been a while since I worked on it, but I think the actual bug ended up being incorrect PC-relative offset calculation. Effectively off-by-ones in a couple places that added up.

The discriminants made the code difficult to read as I didn’t know which version of the record type I was looking at in each procedure, so had to work backwards to figure that out or break in the debugger. This is tedious when you’re looking at a dozen different procedures that operate on the same type with different discriminants.

---

<div class="post-metadata">

**Author:** ![charlie5](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/charlie5/32/626_2.png) [@charlie5](https://forum.ada-lang.io/u/charlie5)\
**Post date:** [March 9, 2026, 11:24am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/39 "2026-03-09T11:24:46Z")

</div>

> [@OneWingedShark](#):
>
> If you’re using a collection of properties, an array of `Abstract_Property'Class` is nice.

Isn’t an array of a Class-wide type illegal ?

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [March 9, 2026, 4:43pm UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/40 "2026-03-09T16:43:30Z")

</div>

> [@JeremyGrosser](#):
>
> The discriminants made the code difficult to read as I didn’t know which version of the record type I was looking at in each procedure,

There’s a trick that you can use — subtypes.

```ada
Type Discriminant is (This, That, The_Other);
Type Example( X : Discriminant ) is null record;

Subtype This_Example is Example( X => This );
Subtype That_Example is Example( X => That );
Subtype Other_Example is Example( X => The_Other );

```

Another technique is to abuse defaults and overloading to subsume one or more parameter/factor/discriminant — this only works where you can “scoop up” the appropriate item with defaults — I have an example of using overloading and defaults to manually do dispatching internally for a language’s type-conversions: [here](https://github.com/OneWingedShark/Risi/blob/894edaa3805de4ad49b08360a127280ff5109516/src/risi_script-types-implementation-convert.adb), note that the `From_Type` us defaulted from the context, and the formal parameters for the conversion take conversion-functions producing the `To_Type`, thus allowing the instantiation only specifying the target-type, then instantiating with the possible target-types on the same name yields an overload-set, the correct one being chosen by the type of the `Whatever_Value` in the aggregate. (Thus collapsing the selection into defaults and overloading.)

---

<div class="post-metadata">

**Author:** ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)\
**Post date:** [March 10, 2026, 1:34am UTC](https://forum.ada-lang.io/t/writing-a-c-compiler/1024/41 "2026-03-10T01:34:49Z")

</div>

> [@charlie5](#):
>
> Isn’t an array of a Class-wide type illegal ?

It might be, I could have been thinking `Vector` when I wrote that.

[Previous page](https://forum.ada-lang.io/t/writing-a-c-compiler/1024.md?page=1)

[Next page](https://forum.ada-lang.io/t/writing-a-c-compiler/1024.md?page=3)
