Advanced compilers like gcc compile codes into machine readable files according to the language in which the code has been written (e.g. C, C++, etc). In fact, they interpret the meaning of each codes according to library and functions of the corresponding languages. Correct me if I'm wrong.

I wish to better understand compilers by writing a very basic compiler (probably in C) to compile a static file (e.g. Hello World in a text file). I tried some tutorials and books, but all of them are for practical cases. They deal with compiling dynamic codes with meanings connected with the corresponding language.

How can I write a basic compiler to convert a static text into a machine readable file?

The next step will be introducing variables into the compiler; imagine that we want to write a compiler which compile only some functions of a language.

Introducing practical tutorials and resources is highly appreciated :-)

up vote 270 down vote accepted


A typical compiler does the following steps:

  • Parsing: the source text is converted to an abstract syntax tree (AST).
  • Resolution of references to other modules (C postpones this step till linking).
  • Semantic validation: weeding out syntactically correct statements that make no sense, e.g. unreachable code or duplicate declarations.
  • Equivalent transformations and high-level optimization: the AST is transformed to represent a more efficient computation with the same semantics. This includes e.g. early calculation of common subexpressions and constant expressions, eliminating excessive local assignments (see also SSA), etc.
  • Code generation: the AST is transformed into linear low-level code, with jumps, register allocation and the like. Some function calls can be inlined at this stage, some loops unrolled, etc.
  • Peephole optimization: the low-level code is scanned for simple local inefficiencies which are eliminated.

Most modern compilers (for instance, gcc and clang) repeat the last two steps once more. They use an intermediate low-level but platform-independent language for initial code generation. Then that language is converted into platform-specific code (x86, ARM, etc) doing roughly the same thing in a platform-optimized way. This includes e.g. the use of vector instructions when possible, instruction reordering to increase branch prediction efficiency, and so on.

After that, object code is ready for linking. Most native-code compilers know how to call a linker to produce an executable, but it's not a compilation step per se. In languages like Java and C# linking may be totally dynamic, done by the VM at load time.

Remember the basics

  • Make it work
  • Make it beautiful
  • Make it efficient

This classic sequence applies to all software development, but bears repetition.

Concentrate on the first step of the sequence. Create the simplest thing that could possibly work.

Read the books!

Read the Dragon Book by Aho and Ullman. This is classic and is still quite applicable today.

Modern Compiler Design is also praised.

If this stuff is too hard for you right now, read some intros on parsing first; usually parsing libraries include intros and examples.

Make sure you're comfortable working with graphs, especially trees. These things is the stuff programs are made of on the logical level.

Define your language well

Use whatever notation you want, but make sure you have a complete and consistent description of your language. This includes both syntax and semantics.

It's high time to write snippets of code in your new language as test cases for the future compiler.

Use your favorite language

It's totally OK to write a compiler in Python or Ruby or whatever language is easy for you. Use simple algorithms you understand well. The first version does not have to be fast, or efficient, or feature-complete. It only needs to be correct enough and easy to modify.

It's also OK to write different stages of a compiler in different languages, if needed.

Prepare to write a lot of tests

Your entire language should be covered by test cases; effectively it will be defined by them. Get well-acquainted with your preferred testing framework. Write tests from day one. Concentrate on 'positive' tests that accept correct code, as opposed to detection of incorrect code.

Run all the tests regularly. Fix broken tests before proceeding. It would be a shame to end up with an ill-defined language that cannot accept valid code.

Create a good parser

Parser generators are many. Pick whatever you want. You may also write your own parser from scratch, but it only worth it if syntax of your language is dead simple.

The parser should detect and report syntax errors. Write a lot of test cases, both positive and negative; reuse the code you wrote while defining the language.

Output of your parser is an abstract syntax tree.

If your language has modules, the output of the parser may be the simplest representation of 'object code' you generate. There are plenty of simple ways to dump a tree to a file and to quickly load it back.

Create a semantic validator

Most probably your language allows for syntactically correct constructions that may make no sense in certain contexts. An example is a duplicate declaration of the same variable or passing a parameter of a wrong type. The validator will detect such errors looking at the tree.

The validator will also resolve references to other modules written in your language, load these other modules and use in the validation process. For instance, this step will make sure that the number of parameters passed to a function from another module is correct.

Again, write and run a lot of test cases. Trivial cases are as indispensable at troubleshooting as smart and complex.

Generate code

Use the simplest techniques you know. Often it's OK to directly translate a language construct (like an if statement) to a lightly-parametrized code template, not unlike an HTML template.

Again, ignore efficiency and concentrate on correctness.

Target a platform-independent low-level VM

I suppose that you ignore low-level stuff unless you're keenly interested in hardware-specific details. These details are gory and complex.

Your options:

  • LLVM: allows for efficient machine code generation, usually for x86 and ARM.
  • CLR: targets .NET, mostly x86/Windows-based; has a good JIT.
  • JVM: targets Java world, quite multiplatform, has a good JIT.

Ignore optimization

Optimization is hard. Almost always optimization is premature. Generate inefficient but correct code. Implement the whole language before you try to optimize the resulting code.

Of course, trivial optimizations are OK to introduce. But avoid any cunning, hairy stuff before your compiler is stable.

So what?

If all this stuff is not too intimidating for you, please proceed! For a simple language, each of the steps may be simpler that you think.

Seeing a 'Hello world' from a program that your compiler created might be worth the effort.

  • 35
    This is one of the best answers I've seen yet. – gahooa Sep 20 '12 at 21:12
  • 9
    I think you missed a part of the question... The OP wanted to write a very basic compiler. I think you go beyond very basic here. – marco-fiset Sep 21 '12 at 12:01
  • 19
    @marco-fiset, on the contrary, I think it's an outstanding answer that does tell the OP how to do a very basic compiler, while pointing out the traps to avoid and defining more advanced phases. – smci Mar 3 '13 at 22:50
  • 6
    This is one of the best answers I have ever seen in the entire Stack Exchange universe. Kudos! – Andre Terra Dec 8 '15 at 21:12
  • 3
    Seeing a 'Hello world' from a program that your compiler created might be worth the effort. -- INDEED – slier Aug 11 '16 at 2:56

Jack Crenshaw's Let's Build a Compiler, while unfinished, is an eminently readable introduction and tutorial.

Nicklaus Wirth's Compiler Construction is a very good textbook on the basics of simple compiler construction. He focuses on top-down recursive descent, which, let's face it, is a LOT easier than lex/yacc or flex/bison. The original PASCAL compiler that his group wrote was done this way.

Other people have mentioned the various Dragon books.

  • 1
    One of the nice things about Pascal, is that everything has to be defined or declared before being used. Therefore it can be compiled in one pass. Turbo Pascal 3.0 is one such example, and there is a lot of documentation about the internals here. – tcrosley Oct 20 '15 at 2:19
  • 1
    PASCAL was specifically designed with one-pass compilation and linking in mind. Wirth's compiler book mentions multipass compilers, and adds that he knew of a PL/I compiler that took 70 (yes, seventy) passes. – John R. Strohm Oct 20 '15 at 16:24
  • Mandatory declaration before use dates back to ALGOL. Tony Hoare got his ears pinned back by the ALGOL committee when he tried to suggest adding default type rules, similar to what FORTRAN had. They already knew about the problems this could create, with typographical errors in names and default rules creating interesting bugs. – John R. Strohm Oct 20 '15 at 16:25
  • 1
    Here is a more updated and finished version of the book by the original author himself: Please edit your answer and add this :) – rafid059 Oct 27 '16 at 15:33

I'd actually start off with writing a compiler for Brainfuck. It's a fairly obtuse language to program in but it only has 8 instructions to implement. It's about as simple as you can possibly get and there are equivalent C instructions out there for the commands involved if you find the syntax off-putting.

  • 6
    But then, once you have your BF compiler ready, you have to write your code in it :( – 500 - Internal Server Error Jan 28 '16 at 18:55
  • @500-InternalServerError use the C subset method – World Engineer Jan 28 '16 at 19:11

If you really want to write machine readable code only and not targeted to a virtual machine, then you will have to read Intel manuals and understand

  • a. Linking and Loading Executable code

  • b. COFF and PE formats(for windows), alternatively understand ELF format(for Linux)

  • c. Understand .COM file formats(easier than PE)
  • d. Understand assemblers
  • e. Understand compilers and code generation engine in compilers.

Much harder done than said. I suggest you to read Compilers and Interpreters in C++ as a starting point(By Ronald Mak). Alternatively, "lets build a compiler" by Crenshaw is OK.

If you don't want to do that, you could as well write your own VM and write a code generator targeted to that VM.

Tips: Learn Flex and Bison FIRST. Then go on to build your own compiler / VM.

Good Luck!

  • 7
    I think targeting LLVM and not real machine code is about the best way available today. – 9000 Sep 20 '12 at 16:38
  • I agree, I have been following LLVM for sometime now and I should say it was one of the best things I had seen in years in terms of programmer effort needed to target it! – Aniket Inge Sep 20 '12 at 16:40
  • 2
    What about MIPS and use spim to run it? Or MIX? – user40980 Sep 20 '12 at 16:57
  • @MichaelT I have not used MIPS but I am sure it will be good. – Aniket Inge Sep 20 '12 at 17:04
  • @PrototypeStark RISC instruction set, real world processor that is still in use today (understanding it will be translatable into embedded systems). The full instruction set is at wikipedia. Looking on the net, there are lots of examples and it is used in many academic classes as a target for machine language programming. There is a bit of activity on it at SO. – user40980 Sep 20 '12 at 17:15

DIY approach for simple compiler could look like this (at least that's how my uni project looked like):

  1. Define Grammar of the language. Context-free.
  2. If your grammar isn't LL(1) yet, do it now. Note, that some rules that looked ok in plain CF grammar may turn out ugly. Perhaps your language is too complex...
  3. Write Lexer which cuts stream of text into tokens (words, numbers, literals).
  4. Write top-down recursive descent parser for your grammar, which accepts or rejects input.
  5. Add syntax tree generation into your parser.
  6. Write machine code generator from the syntax tree.
  7. Profit & Beer, alternatively you can start thinking how to do smarter parser or generate better code.

There should be plenty of literature describing each step in detail.

  • The 7th point is what OP is asking about. – Florian Margaine Sep 20 '12 at 18:50
  • 7
    1-5 are irrelevant and do not deserve such a close attention. 6 is the most interesting part. Unfortunately, most of the books follow the same pattern, after the infamous dragon book, paying too much attention to parsing and leaving code transforms out of scope. – SK-logic Sep 20 '12 at 19:51

protected by user40980 Feb 28 '14 at 22:00

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.