Flash C is a small C compiler with a full-screen, text-mode IDE. Both are written from scratch and both run on Linux. The compiler, fcc, reads C source code and writes a native x86-64 Linux executable directly. No assembler, linker or C library is used at any point. The IDE, fc, wraps the same compiler in pull-down menus, overlapping edit windows, an error window, option dialogs, a source-level debugger and hyper-linked online help. Its layout follows the classic Turbo Vision style IDEs. This post shows what is in the project and how it works inside.

The whole toolchain lives in one repository: the lexer and preprocessor, the parser and type checker, an optimizer, the x86-64 code generator, an ELF writer, a run-time library written in C, the IDE, a debugger and the help system. Flash C compiles C89 with a few C99 conveniences, such as // comments. C++ is deliberately not supported.

Two programs come out of the build. fcc is the command-line compiler. Running fcc hello.c produces a static ELF executable called hello in a few tens of milliseconds. fc is the IDE. It uses the very same compiler code, so the two always behave the same way.

This project comes from personal history. I have used Borland products for many years, and Borland Turbo C, Turbo C++ and Turbo Pascal were the compilers I used when I started programming. That fast, friendly, all-in-one environment, with its blue editor, pull-down menus and F-key shortcuts, is where I learned. One of the main goals of Flash C is to bring that experience back on Linux, where nothing quite like it exists today. The second goal is to learn compiler construction by building a whole compiler, from the lexer to the machine code, instead of only reading about one.

To bring the experience back, I first had to answer a question: how does a classic IDE of that era behave, in enough detail to rebuild it? To find out, I studied the executable of Turbo C++ 3.1, which is available from the Internet Archive. The study was purely static. I read the executable headers and segments, and I extracted the text tables: menu items, dialog controls, status-line hints, the diagnostic messages, the command-line switches and the color table from the settings file. Nothing was executed, and no code, library, header, help text or other file from the original product is used in Flash C. The result of the study is only a description of how the IDE looks and behaves: which menus exist and in what order, which options each dialog holds, how the Message window tracks errors, and what the diagnostics say. The method and findings are written up in analysis/EVIDENCE.md in the repository. The script that rebuilds the raw extracts is included, but the extracts themselves are not distributed.

With that as a guide, I built the compiler first. It follows a conventional pipeline. The source goes through the lexer and preprocessor, then a parser that builds a typed syntax tree, then the optimizer, and finally the code generator, which writes the ELF file.

All compiler data lives in a memory arena that is freed in one step at the start of the next compile. This lets the IDE call the compiler again and again without leaking memory. The preprocessor first splits the source into tokens, runs the directives on them, and expands macros recursively with a "busy" flag instead of hide-sets. The parser is recursive descent and builds the typed tree in a single pass. Every node already knows its C type, implicit conversions are explicit cast nodes, pointer arithmetic is already scaled, and constants are folded. Errors use the classic wording, for example Error hello.c 5: Undefined symbol 'x' in function main. The parser recovers at statement boundaries, so one run can report many errors.

The code generator is a simple accumulator machine. Each expression leaves its value in rax, and binary operators push the left operand first. Floating-point values use SSE2. The calling convention is private to Flash C: arguments...

Read more »