Master the fundamental concepts of semantic analysis through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksAfter parsing, the compiler checks whether the program obeys language rules: types must match, variables must be declared, functions called correctly. Clang's Sema module and Java's javac attribution phase are large implementations of what you build in miniature.
Verify operators receive compatible operands (int + int, not int + struct). Ensure assignments match types. Confirm conditionals are boolean or truthy per language rules. Reject invalid implicit conversions before code generation.
cLoading…
Production compilers embed this step inside a longer pipeline. GCC flows through cpp, cc1, assembly, and ld; Clang uses the driver, Sema, LLVM IR passes, and a target backend. LLVM bitcode, JVM bytecode, and WASM are other familiar IRs at the same layer. The exercise isolates one pass so you can test it alone before chaining it to the next stage.
You will implement a type checker that walks the AST and validates operator and assignment types. This exercise asks you to annotate expressions with types and emit errors when language rules are violated.
Write the type checker for a small C subset: compute the type of every expression and enforce the rules for operators, conditions, declarations, assignments, calls and returns.
Functions written one statement per line (blank lines ignored, indentation irrelevant):
| Line | Meaning |
|---|---|
TYPE NAME(TYPE p, TYPE q) { | function header: () or (void) for no parameters |
TYPE NAME; / TYPE NAME = EXPR; | local declaration |
NAME = EXPR; | assignment |
return; / return EXPR; | return |
EXPR; | expression statement (usually a call) |
if (EXPR) { / while (EXPR) { | block with a condition |
} | end of the innermost block or function |
Types: char, int, float, void (return types only). Expressions: integer literals (int), literals with a . (float), variables, calls f(a, b), + - * /, comparisons < > ==, parentheses. A function can call itself and any function defined above it. Variables are block-scoped; parameters are in scope for the whole body.
+ - * /: char is promoted to int, the result is the wider of the two (int < float).< > ==: operands as for arithmetic; the result is int.char → int → float) is fine; anything narrower is an error.void value may not be an operand, a condition, an initializer, an assigned value, an argument, or a returned value.cLoading…
Check each expression left to right, depth-first (a call's arguments before the call itself), and an assignment's right-hand side before its target. With an argument-count error, skip that call's argument conversions (the call still has the function's return type). Finish with functions=F errors=E.
Input:
cLoading…
Output:
cLoading…
check_expr() returning a Type (with an error type), a function-signature table, and a scoped variable table.convert(from, to, context) used by every conversion site, so the four contexts share one rule.Hidden tests cover recursion, nested calls, block-scoped variables that go out of scope, void calls in conditions and arithmetic, wrong argument counts, and a clean program.