← board

C source frontend — compile C function bodies (statements + expressions)

Why "trivial subset, mostly solved" is half-true

Header import solved the DECLARATION half — and that is the hard ABI half (layout, alignment, calling). It is genuinely mature. But the STATEMENT/EXPRESSION half — compiling C code — is essentially greenfield:

Current body compiler (ParseCStatementAST / ParseCProgram in cparser.inc):

So the work is real. Scope it honestly as "the C body frontend", not a polish pass on header import.

Key architectural fact (drives the design)

.h vs .c is naming convention only — after preprocessing it's one token stream. #include is textual paste; the translation unit is one .c plus what it pulls in. The convention (decls in .h, definitions in .c) exists because of the One Definition Rule / linker duplicate-symbol constraint, not a language rule. Consequence: the body frontend is the same parser as header import, just with body-skipping turned off. CHeaderMode is a pragmatic switch, not a language boundary — building on the existing parser is correct, not a rewrite.

Slices

Ordered by dependency. Each its own commit(s) + test.

A — C lexer fidelity (unblocks everything)

clexer.inc currently collapses multi-char C operators, losing information:

C source lexed as today needed
++ += tkPlus distinct inc / tkPlusEq
-- -= tkMinus distinct dec / tkMinusEq
-> tkDot ✅ keep (field access)
&& tkAnd (== bitwise &) distinct logical-and
|| tkOr (== bitwise |) distinct logical-or
<< >> two tkLt/tkGt shift tokens
^ — (none) XOR token
? : ternary tokens
*= /= %= &= |= ^= <<= >>= collapsed compound-assign tokens

Add the distinct tokens. Acceptance: lexer round-trips every C operator to a distinct token; existing header import unaffected (regression-test the import suite).

B — C expression compiler (C precedence + semantics)

Replace the Pascal-ParseExpr borrow with a real C expression parser at C precedence: assignment + compound-assign, ?:, || && | ^ &, equality, relational, shift, additive, multiplicative, unary (* & ! ~ - + ++ -- prefix + postfix), cast (type)expr, sizeof, primary (literal, ident, call, [], ./->, ( )), comma operator. C semantics: integer promotion, usual arithmetic conversions, array→pointer decay, pointer arithmetic scaled by element size, char/string literals with escapes. Lower to existing IR. Acceptance: arithmetic/pointer/struct-access expression tests match gcc output (oracle).

C — statements

if/else, while, do..while, for, switch/case/default (+ fallthrough), break, continue, local declarations with initializers (C89 top-of-block and C99 mid-block), expression statements (assignment, call), ++/-- and compound-assign as statements. goto/labels deferred (rare; note as optional). Acceptance: control-flow + loop programs match gcc output; break/continue interop with codegen.

D — multi-function programs + globals

Unify so a .c file compiles all defined functions (today ParseCProgram only does main; ParseCSubroutine has a body path used in unit mode — merge the drivers). Global variable definitions with storage + initializers (today skipped to ;); static/extern linkage; static locals; string/char literal data. Acceptance: multi-function .c with globals + inter-function calls runs; matches gcc output.

E — function-like macros (embedded needs them)

Preprocessor today surfaces only object-like integer #define as consts (RegisterCMacroConsts skips CPMFunction). Add function-like macro expansion (bitSet(x,b), min(a,b), common Arduino idioms), conditional compilation in body context, project-header #include resolution. Acceptance: a .c using function-like macros expands + compiles correctly.

F — embedded layout / ABI (gates real hardware structs)

From c-skipped-features-audit.md, currently stripped — low-priority for desktop C, central for Arduino/ESP:

Non-goals (explicit)

Testing strategy

Landmines / notes

Acceptance (overall)

A real, non-trivial .c program (multi-function, structs, loops, pointer arithmetic, function-like macros) compiles with PXX and produces byte-identical stdout to gcc, on x86-64 and under cross-bootstrap. Slices A–D = "compile real C"; E–F = "compile real embedded C". C++ subset tracked separately.

Log