The trick to reading it:
* next() is the lexer
* expr() is a precedence-climbing expression parser
* stmt() parses statements and generates code
* main() has the virtual machine loop.
The trick to reading it:
* next() is the lexer
* expr() is a precedence-climbing expression parser
* stmt() parses statements and generates code
* main() has the virtual machine loop.
* It never bothers to produce a machine code. The use of stack-based VM simplifies the single-pass compilation, and it never makes use of unknown library functions so no machine-specific knowledge is required (like dlsym in Bellard's OTCC [1]).
* It chose its primitives wisely. It never implements structs and returning with values, for example, but the code is carefully structured that the lack of them doesn't make it harder to read.
* And yet it has tons of little tricks. Switching from r-value to l-value (triggered when infix `=` or postfix `++`/`--` are read) is a single opcode fix. Reserved words are initialized from an imaginary source code. The type is represented by a single number 2n+k where n is the number of indirections.
What does it mean? Could you point to a place in the source?
p = "char else enum if int return sizeof while "
"open read close printf malloc free memset memcmp exit void main";
i = Char; while (i <= While) { next(); id[Tk] = i++; } // add keywords to symbol table
i = OPEN; while (i <= EXIT) { next(); id[Class] = Sys; id[Type] = INT; id[Val] = i++; } // add library to symbol table
next(); id[Tk] = Char; // handle void type
next(); idmain = id; // keep track of main
Throughout the entire source code p is a source code pointer, but at the very beginning of the program it is a string containing all reserved words and library functions, and they are read with the same lexing function `next` to the symbol table before the memory for the actual source code is allocated.For a just-as-interesting "sequel", look at C4x86: https://github.com/EarlGray/c4
The real size of this code, after putting each statement on its own line, would be on the order of 300%.
Imagine this:
if (tk == Mul) { next(); *++e = PSH; expr(Inc); *++e = MUL; ty = INT; }
Turning into this: if (tk == Mul) {
next();
*++e = PSH;
expr(Inc);
*++e = MUL;
ty = INT;
} else if (tk == Mul) { next(); *++e = PSH; expr(Inc); *++e = MUL; ty = INT; }
else if (tk == Div) { next(); *++e = PSH; expr(Inc); *++e = DIV; ty = INT; }
else if (tk == Mod) { next(); *++e = PSH; expr(Inc); *++e = MOD; ty = INT; } if (tk == ']') next(); else { printf("%d: close bracket expected\n", line); exit(-1); }
if (t > PTR) { *++e = PSH; *++e = IMM; *++e = sizeof(int); *++e = MUL; }
else if (t < PTR) { printf("%d: pointer type expected\n", line); exit(-1); }I think I agree with you that this part could be refactored a bit. I would be tempted to put the "PSH" corresponding to the "i" next to when we parse the "i". I would also write the check that "p" has a pointer type before the code that indexes it.
else if (tk == Brak) {
next(); *++e = PSH; expr(Assign); *++e = PSH;
if (tk == ']') next(); else { printf("%d: close bracket expected\n", line); exit(-1); }
if (t < PTR) { printf("%d: pointer type expected\n", line); exit(-1); }
*++e = IMM; *++e = sizeof(int); *++e = MUL; *++e = ADD;
*++e = ((ty = t - PTR) == CHAR) ? LC : LI;
} else if (tk == Mul) { applyOperator(MUL); }
else if (tk == Div) { applyOperator(DIV); }
else if (tk == Mod) { applyOperator(MOD); }
But then you're not conforming to their arbitrary idea of "minimalism = fewer functions".I definitely have some admiration for their picking a goal and following through on it, and there are a few tricks in there that are downright brilliant, but let's not pretend this is about effective communication.
If we are going to force autoformatters, we might as well just use annotated ASTs instead of text so we all see our own chosen view of the code.
What's more impressive is that it's self-hosted and implements just the subset of C required to compile itself, which makes it harder to keep the code short, but it manages anyways.
$ cc float.c && ./a.out && ./c4 -s float.c && ./c4 float.c && echo 'struct { int a; } s;' >struct.c && cc -c struct.c && ./c4 -s struct.c
hello, world 1.250000
1: #include <stdio.h>
2:
3: int main()
4: {
5: printf("hello, world %f\n", 1.25);
ENT 0
IMM 83656704
PSH
IMM 1
PSH
IMM 25
PSH
PRTF
ADJ 3
6: return 0;
IMM 0
LEV
7: }
LEV
hello, world 0.000000
exit(0) cycle = 13
1: bad global declaration
$