A C compiler written in C.
My goal for this project is to learn how compilers work by building a functional compiler. So far I've implemented a lexer, a parser and code generation. Currently it supports:
- arithmetic operators:
+,-,*,/,% - bitwise operators:
&,|,^,<<,>> - unary operators:
-,~,! - increment/decrement: prefix
++x,--xand postfixx++,x-- - comparison operators:
<,>,<=,>=,==,!= - logical operators:
&&,|| - ternary conditional:
cond ? a : b - local and global variable declarations, assignments, and references
- compound assignment:
+=,-=,*=,/= if/elsestatementswhileloopsforloopsbreakandcontinue- multiple functions with up to 6 named parameters; a call site can pass more than 6 arguments, with the extras going on the stack and
rspkept 16-byte aligned - function prototypes:
int foo(int a);forward-declares a function, enabling calls before the definition and mutual recursion voidreturn type and barereturn;- pointers:
int *p, address-of&x, dereference*p, pointer parameters - pointer arithmetic:
p + n,p - n,p++,p--, pointer difference, andp[i]indexing - arrays:
int a[N], element accessa[i], brace initializersint a[3] = {1, 2, 3}and size-inferredint a[] = {1, 2, 3}, decay to pointer when passed to functions - N-dimensional arrays:
int a[2][3],char c[2][3][4]as locals and globals, element accessa[i][j], row-major layout, nested brace initializersint a[2][3] = {{1, 2, 3}, {4, 5, 6}}(flat lists work too), outer size inferred from the initializer withint a[][2] = {1, 2, 3, 4}, and a partial index likea[i]gives the row address so it can be passed asint * - arrays of pointers:
char *a[N],int *a[N]as locals and globals, with brace initializerschar *names[3] = {"aa", "bb", "cc"}; each slot is 8 bytes,a[i]yields the pointer anda[i][j]indexes through it - global arrays and pointers:
int a[N],char buf[N],int *p,char *sat file scope, with initializersint a[4] = {1, 2, 3, 4}, size-inferredint a[] = {1, 2},char *s = "hi"andint *p = &g; scalar initializers fold constant expressions at compile time (2 * 3 + 1, enum constants,sizeof), array elements left out of the list stay zero, and anything declared without an initializer is zeroed chartype: declarations, assignments, arithmetic, arrayschar a[N], pointerchar *p, function parameters- character literals:
'a','0',' 'and the escapes\n,\t,\r,\0,\a,\b,\f,\v,\\,\',\"; the lexer decodes each one to its byte value and emits it as a number, so a character literal works anywhere a number does, includingcase 'a':labels and array initializerschar s[] = {'h', 'i', '\0'}; octal'\101'and hex'\x41'escapes are not supported sizeof(type):sizeof(int)→ 4,sizeof(char)→ 1,sizeof(int *)/sizeof(char *)→ 8- structs:
struct name { fields; }definitions, local struct variables, member access and assignment via., struct pointer declarationsstruct T *p, member access and assignment via->, struct pointer function parameters,int,char,longand pointer fields with real sizes and offsets (charpacks to 1 byte,intaligns to 4,longand pointers align to 8, total size rounds up to the widest field) - struct pointer fields:
struct node *nextinside a struct, including self-referential types, so linked lists and trees work - member chains:
p->next->val,a[i].x,s.p->f; every postfix step builds on the address of the previous one - struct arrays:
struct T a[N]as locals and globals, element accessa[i].field, decay to a pointer when passed to functions - global structs:
struct T g;,struct T *gp;andstruct T a[N];at file scope, with brace initializers laid out field by field with real padding, so the keyword-table shapestruct kw table[3] = { {"int", 11}, {"char", 22} };works; pointer fields take a string literal,&otheror0, and a struct array needs an explicit size since the parser does not know the field count - struct pointer arithmetic:
p + n,p++,p--step by the full struct size sizeof(struct T)reports the real laid-out size,sizeof(struct T *)→ 8- address of an element or member:
&a[i],&s.field,&p->field switch/case/default: integer switch with fallthrough,breakexits the switchdo/whileloops: body runs at least once,breakandcontinuework as expected- enums:
enum name { A, B = 5, C };at file scope, constants fold to numbers at parse time, usable in expressions and ascaselabels #define NAME value: object-like macros, expanded in the lexer, value can be any token sequence, macros can reference other macros, works as array sizes- nested block scoping:
{ int x = 5; }declaresxonly for the duration of the block; inner variables shadow outer ones with the same name and the outer name comes back when the block exits unsigned intandunsigned char: zero-extension on char load (movzbl), unsigned division (divl/divqwithxor edx), unsigned right shift (shrl/shrq), unsigned comparison flags (setb/seta/setbe/setae)long: 64-bit integer, 64-bit arithmetic (addq/subq/imulq/idivq),movqloads and stores, works as local variables, function parameters, and return types- struct value return:
struct T func(...)returns the struct inrax(≤8 bytes) orrax:rdx(≤16 bytes) per the System V AMD64 ABI; caller unpacks into a local withstruct T v = func(...) - struct value parameters:
func(struct T p)passes the struct in one register (≤8 bytes) or two registers (≤16 bytes); struct value args must be local variable references - type casting:
(int),(char),(long),(unsigned),(unsigned char),(int *),(char *); truncates or extends the value to the target type;(char)sign-extends from byte,(unsigned char)zero-extends,(long)sign-extends to 64 bits - function pointers:
int (*fp)(int, int)declarations, assignment from function names (fp = add), indirect calls (fp(a, b)), and function pointer parameters (int apply(int (*fn)(int), int x)); function names used as values decay to their address vialeaq; indirect calls emitcall *%rax - variadic functions:
int sum(int n, ...)definitions and prototypes, withva_list,va_start(ap, last),va_arg(ap, type)andva_end(ap); the prologue of a variadic function spills the six integer argument registers to a save area andva_argwalks that area first before falling through to the arguments the caller left on the stack, matching the System V AMD64 layout, so ava_listcan be handed straight to a libc function likevsnprintforvprintf; call sites zeroalbefore calling anything variadic; floating point arguments are not supported since redix has no floating point types //line comments and/* */block comments
make
./redix input.c
gcc -o out out.s
./out
make test
Test files live in tests/. Each file has a // expect: N comment on the
first line indicating the expected exit code.
MIT