diff --git a/.gitignore b/.gitignore index 394fca6..0c6ae96 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ bin/ *.out *.bin -__pycache__/ \ No newline at end of file +__pycache__/ +compile_commands.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a4e9c..aa0d15f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,185 @@ and this project adheres to [Semantic Versioning](https://semver.org/) - while the project is in `0.x`, breaking changes may land in MINOR releases; PATCH releases are reserved for bug fixes only. +## [v1.0.0] 2026-07-01 + +- **v1.0 milestone: Complete Single-File Language** — every must-have and + should-have feature from `docs/roadmap.md`'s v1.0 checklist is now + implemented: function calls, pointer store, address-of, implicit widening, + string literal locals, pointer arithmetic (`*(ptr + i)`), break/continue, + struct field access, multiply/divide/modulo, and bitwise/shift operators. + The only items left unchecked are explicitly-optional "nice-to-have"s + (short-circuit `&&`/`||` codegen outside boolean-context use, and the + `&=`/`|=`/`^=`/`<<=`/`>>=` compound assignment forms) — the roadmap's own + goal, "someone can sit down and write a non-trivial 65C02 program without + hitting an unimplemented wall," is met; + +- **`break` and `continue`** — both now supported inside `while` and `for` + loops. The analyzer tracks a `loop_depth` counter (incremented for the + duration of a loop body) and rejects either statement outside a loop + (`ERR_BREAK_OUTSIDE_LOOP` / `ERR_CONTINUE_OUTSIDE_LOOP`). The IR generator + maintains a `loop_ctx_t { continue_label, break_label }` stack; both + statements desugar to a plain `TAC_JUMP` to the appropriate label, so + codegen needed no changes. For `for` loops, `continue` jumps to a new + label sitting between the body and the incrementer, so the incrementer + still runs before the next condition check. See + `docs/break-continue-implementation.md` for the full design writeup, + including a bonus fix along the way: the `for`-loop incrementer was being + lowered with `lower_expr` instead of `lower_stmt`, so `i = i + 1`-style + incrementers (as opposed to `++i`) were silently generating no code; + +- **Local string literal initializers** — `u8 *p = "some string";` now works + inside function bodies, not just at global scope. The string data is placed in + the ROM data section via the existing `data_fixup_t` backpatch mechanism (same + as global strings); the pointer value (ROM address of the string) is written + into the local's ZP slot during function entry via `TAC_COPY` with an + `OPERAND_CONST_STR` source. Callee-saves push and pop the pointer ZP slot + across nested calls to preserve it across function boundaries. The pointer + occupies 2 bytes of ZP for the lifetime of the function; + +- **Variable shadowing is now a semantic error** — a variable declaration + (or function parameter) that reuses a name still visible from an enclosing, + still-live scope now raises `ERR_SHADOWED_DECLARATION` instead of silently + compiling. Root cause: the IR/codegen identify a variable purely by its bare + name (`OPERAND_VAR.name`, matched via `strcmp` in `zp_map_build`), with no + per-scope qualifier, so a shadowed inner declaration (e.g. a `for (u8 i = 0; + ...)` nested inside a function that already has an outer `u8 i`) aliased the + *same* zero-page storage as its outer namesake — the inner loop's own + init/exit value silently clobbered the outer variable. This surfaced as + `break` firing on the wrong iteration in `arithmetic_demo.c02`. Reusing a + name across scopes that never overlap on the scope stack (e.g. two sibling + `for` loops each declaring their own `i`) is unaffected and remains legal; + +## [v0.2.17] 2026-06-27 + +- **ROM overflow detection** — the `EMIT()` macro now bounds-checks `code_pos` + against `ROM_SIZE` before writing, setting an `overflow` flag on `emitter_t` + instead of silently writing past the end of the 32 KB buffer. A new + `PATCH_BYTE(pos, val)` macro applies the same guard to all branch-offset and + address backpatches. `resolve_func_fixups` and `resolve_local_fixups` + early-return when overflow is already set. `generate_rom` checks `e.overflow` + independently after the code section, data section, and symbol-table phases, + printing a targeted diagnostic and returning `NULL` on failure. Previously, + programs that grew past 32 KB would corrupt the ROM buffer without any error. + `emit_symbol_table` now also writes through `EMIT()` instead of raw pointer + writes, inheriting the overflow guard. + +- **`__heap_start` implicit compiler global** — programs may declare + `decl u16 __heap_start;` to read the address of the first free RAM byte after + all user globals are allocated. The driver injects the declaration + automatically so no user `decl` is required in practice. The value is + initialized during the bootstrap sequence. Intended as a base pointer for + simple bump allocators. + +- **`__memory_top` implicit compiler global** — `decl u16 __memory_top;` + evaluates to `$3FFF`, the top of the general-purpose RAM region (see + `docs/memmap.md`). Injected alongside `__heap_start`. Both constants are + defined as `RAM_TOP` in the codegen memory map. + +- **Compiler extern two-pass allocation** — `emit_compiler_extern_inits` is + refactored into an explicit two-pass design: pass 1 allocates all RAM slots + (settling `e->ram_pos`), pass 2 emits initializers. This ensures + `__heap_start` captures the correct first-free-RAM address regardless of + declaration order. Unknown non-function externs now print a diagnostic and + cause codegen to fail instead of being silently skipped. + - `ALLOC_COMPILER_SLOT` / `EMIT_COMPILER_VALUE` — pair of local macros that + collapse the slot-allocation and value-emission boilerplate to one line each; + both are `#undef`'d immediately after the function. + +## [v0.2.16] 2026-06-27 + +- **16-bit multiply / divide / modulo (`__mul16`, `__div16`, `__sdiv16`)** — + `TAC_MUL`, `TAC_DIV`, and `TAC_MOD` on `u16`/`i16` operands now compile to + subroutine calls rather than erroring. Three new helpers: + - `__mul16` — 16-iteration shift-and-add. Correct for both `u16` and `i16` + because the low 16 bits of a two's-complement product are sign-agnostic. + Overflow silently wraps to the low 16 bits (same as C). + - `__div16` — 16-iteration shift-subtract with CMP-based comparison. Uses a + `BCS dosub` guard before the 16-bit subtract so divisors with bit 15 set + (≥ `$8000`) are handled correctly; the naive SEC-before-compare approach + clobbers the overflow carry and produces wrong quotients for those values. + - `__sdiv16` — sign wrapper around `__div16`, mirroring `__sdiv8`: encodes + signs in `HELPER_SIGN` (`$EC`, bit 7 = negate quotient, bit 6 = negate + remainder), negates both operands, calls `__div16`, then restores signs. + Follows C truncation-toward-zero convention. `needs_sdiv16 = 1` implies + `needs_div16 = 1`. + - New 16-bit helper ZP zone: `$E0–$E7` (`HELPER16_ARG1`/`ARG2`/`RES`/`REM`, + 2 bytes each), below the existing 8-bit zone at `$E8–$EC`. + - ZP operand map upper bound tightened from `$EE` to `$DF` to reflect the + new reserved zone; `zp_map_add` now enforces this with an address guard + (previously only a count guard existed). + - `EMIT_ARITH8(ROUTINE, RES_SLOT, NEEDS_FLAG)` / + `EMIT_ARITH16(ROUTINE, RES_SLOT, NEEDS_FLAG)` — pair of local macros + replacing the three verbose switch arms; bit width, arg-loading, and + result-storing all collapse to one line per dispatch branch. + - Emulator tests: `mul_u16` (300 × 13 = 3900), `mul_u16_wrap` (256 × 256 = 0, + overflow), `div_u16` (50000 / `$C001` = 1, remainder = 847 — exercises the + high-bit-divisor path). + +- **Symbol table embedded in ROM** — compiled binaries now carry a `"C02S"` + symbol table in the NOP fill area between the data section and `$FFF6`, + letting `c02-objdump` show real function names instead of auto-generated + labels. The table is always emitted by default; `--strip-debug` omits it. + Binary size stays exactly 32 KB so EEPROM flashing is unaffected. + - Footer layout: `$FFF6–$FFF7` = little-endian pointer to the table (or + `$EAEA` NOP fill if absent); `$FFF8–$FFF9` = code/data boundary + (unchanged); `$FFFA–$FFFF` = NMI/Reset/IRQ vectors. + - Format: magic `C02S` (4 bytes) + u16 entry count (LE) + entries of + u16 address (LE) + null-terminated name. All user-defined functions and + emitted helpers (`__mul8`, `__div16`, etc.) are included. + - Old binaries degrade gracefully: `$EAEA` at `$FFF6` passes the range + check but fails the magic-byte check, so the disassembler falls back to + `L0`/`L1`/… auto-labels without error. + - `c02-objdump`: `parse_symbols` reads the table and merges it into the + jump-target label map; `scan_end` for the data-section boundary scan + stops at the symbol table start rather than `$FFF8` to avoid + misidentifying table bytes as data. + - `driver.h`: `params_t` gains `int strip_debug`; `main.c` adds + `--strip-debug` to `long_options`. + +- **ZP map overflow error propagation** — previously, `zp_map_add` printed a + diagnostic to stderr and silently continued, potentially generating corrupt + code. `zp_map_add`, `zp_map_add_operand`, and `zp_map_build` now all return + `int` (0 = failure); `emit_function_from_cfg` checks `zp_map_build` and + returns 0, propagating to `generate_rom` which returns `NULL` — the same + path as all other codegen failures, ultimately exiting with + `CODE_GEN_ERROR_RET_CODE` (7). + +- **Bug fix: signed 8-bit division and modulo (`__sdiv8`)** — `TAC_DIV` and + `TAC_MOD` on `i8` operands previously routed through the unsigned `__div8` + helper, so `i8 -6 / 2` computed `250 / 2 = 125` instead of `-3`. A new + `__sdiv8` helper wraps `__div8`: it saves operand signs into a scratch byte at + `$EC` (HELPER_SIGN), negates both operands to their absolute values, calls + `__div8`, then restores the correct sign on the quotient (bit 7 of SIGN) and + remainder (bit 6 of SIGN) per C's truncation-toward-zero convention. Codegen + routes `TAC_DIV`/`TAC_MOD` through `__sdiv8` when `is_signed_type(dst.type)`; + `__sdiv8` always calls `__div8`, so `needs_sdiv8 = 1` implies `needs_div8 = 1`. + New opcode emitters: `bpl_rel`. `$EC` added to the helper ZP zone. +- Emulator tests: `div_i8` (−6 / 2 = −3, PORTB = $FD), `mod_i8` (−7 % 2 = −1, + PORTB = $FF). + +- **Bug fix: binary op operand widening and sign normalisation** — binary ops + derived both the result type and comparison signedness from the LEFT operand + only (`ir.c`), ignoring the right operand entirely. Consequences: `u8 + u16` + computed at 8 bits (the u16 high byte was silently dropped), while `u16 + u8` + was accidentally correct; `i8 < u8` used a signed compare while `u8 < i8` used + an unsigned compare — a trichotomy violation where both `a < b` and `b < a` + could be simultaneously true. Fixed in the IR generator with four new helpers: + - `ir_type_width` / `ir_is_signed` — predicates for 8-vs-16-bit and + signedness without an `ir_gen_t *` context. + - `binop_common_type(left, right)` — returns the wider type; for equal widths, + unsigned wins (C's usual arithmetic conversions), eliminating operand-order + dependence in mixed-sign comparisons. + - `emit_widen_if_needed(gen, cfg, op, target)` — emits `TAC_CAST` when the + operand type differs from the target; `OPERAND_CONST_INT` values are + re-typed in place (no instruction emitted). + - Binop lowering now normalises both operands to the common type before + arithmetic and comparison ops. Shifts are guarded separately (result type = + left, shift count is never widened). Pointer arithmetic skips widening. +- Emulator tests: `binop_widen` (`u8(1) + u16(500) = 501`, high byte = $01), + `cmp_mixed_sign` (`i8(−1) < u8(100)` with unsigned-wins → $FF reinterpreted as + 255, 255 < 100 = false, branch not taken, PORTB = $01). + ## [v0.2.15] 2026-06-26 - **Function call codegen (`TAC_CALL`)** — full caller/callee ABI using a fixed diff --git a/README.md b/README.md index f9b45c1..2bacc24 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,27 @@ [![CI](https://github.com/jackwthake/C02/actions/workflows/ci.yml/badge.svg?branch=cc02)](https://github.com/jackwthake/C02/actions/workflows/ci.yml) - +## Table of Contents + +- [Getting Started: Key Features & Architecture](#getting-started-key-features--architecture) +- [Current Status & Limitations](#current-status--limitations) + - [What works today](#what-works-today) + - [Not yet implemented](#not-yet-implemented) +- [Toolchain Usage](#toolchain-usage) + - [Compiling the Toolchain](#compiling-the-toolchain) + - [Running the Compiler](#running-the-compiler) +- [Language Specifications](#language-specifications) + - [Basic Types](#basic-types) + - [Comments](#comments) + - [Top-Level Declarations](#top-level-declarations) + - [Compiler Implicit Globals](#compiler-implicit-globals) + - [Statements](#statements) + - [Expressions](#expressions) + - [Compilation Example](#compilation-example) +- [Binary Layout](#binary-layout) + - [RAM](#ram) + - [ROM](#rom) +- [Third-Party Licenses](#third-party-licenses) ## Getting Started: Key Features & Architecture @@ -21,7 +41,7 @@ 2. **Recursive Descent Parser:** Transforms the token stream into a structured AST, treating hardware registers and standard controls as first-class grammatical constructs. 3. **Lexically Scoped Semantic Analyzer:** Two-pass validation engine over the AST. Pass 1 registers all top-level declarations (functions, structs, registers, globals) into the global symbol table. Pass 2 walks function bodies with a scoped symbol table, checking undeclared identifiers, type mismatches, argument counts/types, struct field access, lvalue validity, and return-type consistency. Invalid declarations are poisoned to prevent cascading diagnostics. 4. **IR Generator:** Lowers the analysed AST into a self-contained three-address code (TAC) intermediate representation. The IR module contains struct layouts with computed field offsets, global/register definitions with hardware addresses baked in, and one flat instruction stream per function - codegen can emit target code from the IR alone, without consulting the AST or symbol table. Supports incremental compilation: `-c` serializes the IR to a `.o` file that can be loaded back to skip the frontend entirely. -5. **Code Generator:** Emits valid 65C02 ROM binaries (32K) with a bootstrap runtime, interrupt vectors, and flat zero-page register allocation. Avoids slow stack-based execution by mapping local variables, temporaries, and parameters directly onto zero-page slots. Globals are allocated in RAM ($0200+) and initialized in the bootstrap before `JSR main`. String literals are placed in a ROM data section with backpatching fixups. Supports arithmetic (`+`, `-`, unary `-`) for all integer types (u8/i8/u16/i16), comparisons across all widths and signedness (unsigned via carry-flag, signed via N⊕V), pointer dereference, and function calls. Function calls use a fixed 2-byte-per-param ABI zone (`$EF–$FE`) for parameter passing; a callee-saves convention (PHA/PLA on all ZP slots) preserves the caller's locals across calls and enables bounded recursion. Programs compile and run on real hardware. +5. **Code Generator:** Emits valid 65C02 ROM binaries (32K) with a bootstrap runtime, interrupt vectors, and flat zero-page register allocation. Avoids slow stack-based execution by mapping local variables, temporaries, and parameters directly onto zero-page slots. Globals are allocated in RAM ($0200+) and initialized in the bootstrap before `JSR main`. String literals are placed in a ROM data section with backpatching fixups. Supports arithmetic (`+`, `-`, unary `-`) for all integer types (u8/i8/u16/i16), comparisons across all widths and signedness (unsigned via carry-flag, signed via N⊕V), pointer dereference, and function calls. Function calls use a fixed 2-byte-per-param ABI zone (`$EF–$FE`) for parameter passing; a callee-saves convention (PHA/PLA on all ZP slots) preserves the caller's locals across calls and enables bounded recursion. All emit paths are bounds-checked against the 32 KB ROM limit — programs that overflow produce a clear diagnostic rather than silent corruption. Compiler implicit globals (`__heap_start`, `__memory_top`) are injected automatically and initialized during bootstrap. Programs compile and run on real hardware. #### c02-objdump Disassembler @@ -29,12 +49,12 @@ ## Current Status & Limitations -C02 is under active, early development. The **complete frontend** (tokenizer, parser, semantic analyzer), **IR generation**, and **code generator** are functional and tested — simple programs compile to valid 65C02 ROMs and run on real hardware. +C02 has reached its **v1.0 milestone** — the complete single-file language, per `docs/roadmap.md`'s checklist. The **complete frontend** (tokenizer, parser, semantic analyzer), **IR generation**, and **code generator** are functional and tested — non-trivial programs compile to valid 65C02 ROMs and run on real hardware without hitting an "unimplemented" wall. Development continues toward v1.1+ (interrupt handlers, inline assembly, multi-file linking, optimization passes — see `docs/roadmap.md`). #### What works today - **Data movement:** variable copies, constant stores, hardware register writes. Implicit widening (u8→u16) zero-extends correctly; narrowing copies the low bytes. -- **Control flow:** `if`/`else`, `while`, `for` loops via label/jump/conditional-jump. +- **Control flow:** `if`/`else`, `while`, `for` loops via label/jump/conditional-jump, plus `break`/`continue` inside either loop form. - **Arithmetic:** `+`, `-`, unary `-`, `*`, `/`, `%` for all integer types (u8, i8, u16, i16). Width-aware multi-byte emission for 16-bit operations with carry/borrow propagation. Multiply and divide via `__mul8`/`__div8` software subroutines. - **Bitwise & shift ops:** `&`, `|`, `^`, `~`, `<<`, `>>` for all widths. Signed right shift uses the carry-from-sign-bit pattern for correct arithmetic extension. - **Comparisons:** all six relational operators (`<`, `<=`, `==`, `!=`, `>=`, `>`) for all widths (u8, u16) and signedness (unsigned via carry-flag, signed via N⊕V). 16-bit comparisons use a high-byte-first pattern. @@ -44,15 +64,18 @@ C02 is under active, early development. The **complete frontend** (tokenizer, pa - **Address-of:** `&x` resolves to the variable's ZP slot address for locals or its RAM address for globals, stored as a 16-bit pointer. - **Type casts:** `(type)expr` — widening zero/sign-extends, narrowing copies low bytes. - **Struct field access:** `s.field` and `ptr.field` (auto-deref) for both local and global structs. Field reads and writes work for by-value structs and pointer-to-struct, including `++field` / `--field`. -- **Global variables:** RAM-allocated globals with bootstrap initialization, correctly accessed via absolute addressing throughout all codegen paths. String literals placed in a ROM data section with backpatching fixups. +- **Global variables:** RAM-allocated globals with bootstrap initialization, correctly accessed via absolute addressing throughout all codegen paths. +- **String literals:** `u8 *msg = "...";` works both at global scope and as a local variable initializer inside a function body. String data is placed once in the ROM data section with backpatching fixups. +- **Compiler implicit globals:** `__heap_start` and `__memory_top` are injected automatically as `decl u16` globals and initialized during the bootstrap. `__heap_start` holds the first free RAM byte after all user globals *and* the compiler implicit globals themselves are allocated (each takes 2 bytes of RAM) — useful as a base pointer for bump allocators. `__memory_top` holds the top of the general-purpose RAM region (`$3FFF`). Both are available in any `.c02` file without a manual `decl`. - **Function calls:** full `JSR`/`RTS` ABI with up to 8 parameters passed through the `$EF–$FE` fixed-slot ABI zone. A callee-saves convention (PHA all ZP slots on entry, PLA in reverse on return) preserves caller locals across calls. Bounded recursion is supported — stack depth is limited to ≈256 / (function ZP byte count). #### Not yet implemented -- **String local variables** — `u8 *msg = "..."` only works at file scope; local string pointer initialization is not yet supported. - **Arrays** — no array type or subscript syntax (`a[i]`). Use pointer arithmetic (`*(ptr + i)`) in the meantime. -- **`break` / `continue`** — not yet supported inside loops. +- **Compound bitwise/shift assignment** — `&=`, `|=`, `^=`, `<<=`, `>>=` are not yet supported; the arithmetic compound forms (`+=`, `-=`, `*=`, `/=`, `%=`) work. +- **Short-circuit `&&`/`||` outside boolean context** — short-circuit evaluation works correctly when used directly as a loop/if condition; using the result as a plain value in other expression contexts is not yet supported. - **Missing-return detection is shallow.** A non-void function with no `return` at the end is flagged, but the analyzer does not perform full path-coverage analysis. +- **Variable shadowing is disallowed**, not silently supported — a declaration that reuses a name still visible from an enclosing scope is a compile error (codegen identifies variables by name only, so a shadowed name would alias its outer namesake's storage). Reusing a name across scopes that don't overlap (e.g. two sibling `for` loops each declaring their own `i`) is unaffected. If you're exploring the codebase: the parser ([parser.c](cc02/src/parser/parser.c)), the analyzer ([analyzer.c](cc02/src/analysis/analyzer.c)), the IR generator ([ir.c](cc02/src/ir-gen/ir.c)), and the code generator ([generator.c](cc02/src/code-gen/generator.c)) are the main files. Issues and PRs are welcome. @@ -190,6 +213,15 @@ decl u8 counter; - A `decl` for a global is `decl type name;` with no initialiser. - Redeclaring a name that already exists in the same file is an error. +##### Compiler Implicit Globals + +The compiler automatically injects a small set of `u16` globals that expose runtime memory layout information. No `decl` is needed — they are available in every translation unit. + +| Name | Value | Description | +| :--- | :--- | :--- | +| `__heap_start` | first free RAM address after all globals | Base pointer for simple bump allocators. | +| `__memory_top` | `$3FFF` | Top of the general-purpose RAM region. | + ### Statements ```c @@ -282,18 +314,76 @@ c02-objdump led_counter.bin # disassemble to inspect the output --- -### Zero-Page Hardware-Register Layout +### Binary Layout + +Every compiled binary is a flat **32 KB ROM image** (`$8000–$FFFF`) loaded at a fixed base address. The layout is always the same regardless of program size — unused space is filled with `$EA` (NOP). See [memmap.md](./docs/memmap.md) for more info on memory boundaries. + +#### RAM + +``` +$0000 ┬───────────────────────────────────────────── + │ Zero Page (see ZP table below) +$0100 ├───────────────────────────────────────────── + │ Hardware stack (6502 fixed; $01FF = top) +$0200 ├───────────────────────────────────────────── + │ User globals (RAM_START; allocated upward by allocate_globals) + ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + │ __heap_start (u16, 2 bytes — compiler implicit) + │ __memory_top (u16, 2 bytes — compiler implicit) + ├───────────────────────────────────────────── ← __heap_start value (first free byte) + │ (free for heap / dynamic use) +$3FFF ┴───────────────────────────────────────────── ← __memory_top value +``` + +#### Zero-Page Hardware-Register Layout To maximize compilation density and execution speed, the code generator reserves and maps lower RAM (`$0000–$00FF`, **The Zero Page**) to form a virtual register file: | Address Range | Identifier | Purpose | | :--- | :--- | :--- | -| **`$00`** | `FP` | **Frame Pointer:** Tracks multi-byte local variable frames in main RAM. | -| **`$02`** | `RET` | **Return Register:** Where every function or conditional puts its return value. | -| **`$04` – `$E7`** | `r0` – `r115` | **Scratch Registers:** Compiler-managed scratchpads for expression temporaries, local variables, and globals. Allocated per-function from `$04` upward. | -| **`$E8` – `$EB`** | — | **Arithmetic Helper Zone:** Fixed argument/result slots for the `__mul8` and `__div8` software routines. `$E8`/`$E9` = inputs, `$EA` = quotient/product, `$EB` = remainder. | -| **`$EC` – `$EE`** | — | Reserved for helper routines. | -| **`$EF` – `$FF`** | `a0` – `a8` | **Function ABI Zone:** Rapid parameter passing without stack overhead. Supports up to 8 sixteen-bit parameters. | +| **`$00`** | `FP` | **Frame Pointer:** Initialized to `$01FF` at startup. | +| **`$02–$03`** | `RET` | **Return Register:** Holds function return values (u8 in `$02`, u16 in `$02:$03`). | +| **`$04–$DF`** | `r0`–`r219` | **Scratch Registers:** Compiler-managed temporaries, locals, and globals. Allocated per-function from `$04` upward, striding by type size (1 byte for u8/i8, 2 for u16/i16/pointers). | +| **`$E0–$E7`** | — | **16-bit Arithmetic Helper Zone:** Fixed slots for `__mul16`, `__div16`, `__sdiv16` helpers. `$E0:$E1` = arg1, `$E2:$E3` = arg2, `$E4:$E5` = result, `$E6:$E7` = remainder. | +| **`$E8–$EC`** | — | **8-bit Arithmetic Helper Zone:** Fixed slots for `__mul8`, `__div8`, `__sdiv8` helpers. `$E8` = arg1, `$E9` = arg2, `$EA` = result, `$EB` = remainder, `$EC` = sign flags (bit 7 = negate quotient, bit 6 = negate remainder). | +| **`$ED–$EE`** | — | Reserved for future helpers. | +| **`$EF–$FF`** | `a0`–`a7` | **Function ABI Zone:** Fixed 2-byte slots for parameter passing. Caller populates before `JSR`; callee reads at entry. Supports up to 8 sixteen-bit parameters. | + +#### ROM + +``` +$8000 ┬───────────────────────────────────────────── ← Reset vector target + │ Bootstrap (SEI · CLD · stack init · global init · JSR main · halt) + ├───────────────────────────────────────────── + │ .text — function bodies (main first, then callees, then helpers) + ├───────────────────────────────────────────── ← code/data boundary marker ($FFF8–$FFF9) + │ .data — null-terminated string literals + ├───────────────────────────────────────────── + │ C02S symbol table (if --strip-debug not set) + | magic "C02S" · u16 count · [u16 addr · name\0] … + ├───────────────────────────────────────────── + │ NOP fill ($EA bytes) +$FFF6 ├───────────────────────────────────────────── + │ Symbol table pointer (LE u16; $EAEA = absent) +$FFF8 ├───────────────────────────────────────────── + │ Code/data boundary marker (LE u16; first NOP-fill byte) +$FFFA ├───────────────────────────────────────────── + │ NMI vector (LE u16) +$FFFC ├───────────────────────────────────────────── + │ Reset vector (LE u16; always $8000) +$FFFE ├───────────────────────────────────────────── + │ IRQ vector (LE u16) +$FFFF ┴───────────────────────────────────────────── +``` + +The `$FFF8–$FFF9` boundary word and the `$FFF6–$FFF7` symbol-table pointer are read by `c02-objdump` to locate the `.text`/`.data` split and resolve function names. Older binaries that predate these fields have `$EAEA` at `$FFF6` and are disassembled with auto-generated `L0`/`L1`/… labels as a fallback. + +--- + +### References + +1. [Crafting Interpreters](https://craftinginterpreters.com/contents.html) — the primary reference used throughout development. +2. [rui314/chibicc](https://github.com/rui314/chibicc) — structurally similar (recursive descent, etc.), found after starting this project; not directly followed, but worth a look. --- diff --git a/c02-objdump/src/disassembler.rs b/c02-objdump/src/disassembler.rs index e3b8ef8..c71b25c 100644 --- a/c02-objdump/src/disassembler.rs +++ b/c02-objdump/src/disassembler.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io::{Cursor, Read}; const VECTOR_TABLE_SIZE: usize = 6; +const ROM_SIZE: usize = 0x8000; #[allow(dead_code)] pub enum AddrMode { @@ -118,28 +119,41 @@ struct RomInfo { } fn parse_rom(bytes: &[u8]) -> Option { - if bytes.len() <= VECTOR_TABLE_SIZE { + if bytes.len() < ROM_SIZE { return None; } - let nmi = u16::from_le_bytes([bytes[bytes.len() - 6], bytes[bytes.len() - 5]]); - let reset = u16::from_le_bytes([bytes[bytes.len() - 4], bytes[bytes.len() - 3]]); - let irq = u16::from_le_bytes([bytes[bytes.len() - 2], bytes[bytes.len() - 1]]); + let nmi = u16::from_le_bytes([bytes[ROM_SIZE - 6], bytes[ROM_SIZE - 5]]); + let reset = u16::from_le_bytes([bytes[ROM_SIZE - 4], bytes[ROM_SIZE - 3]]); + let irq = u16::from_le_bytes([bytes[ROM_SIZE - 2], bytes[ROM_SIZE - 1]]); let base_addr = reset; - let code_bytes = &bytes[..bytes.len() - VECTOR_TABLE_SIZE]; - - let code_boundary = u16::from_le_bytes([ - bytes[bytes.len() - 8], - bytes[bytes.len() - 7], - ]); + let code_boundary = u16::from_le_bytes([bytes[ROM_SIZE - 8], bytes[ROM_SIZE - 7]]); + let symtab_ptr = u16::from_le_bytes([bytes[ROM_SIZE - 10], bytes[ROM_SIZE - 9]]); let has_boundary = code_boundary >= base_addr && code_boundary < 0xFFF0; + // Detect embedded symbol table: pointer at $FFF6, magic "C02S" at that address. + let symtab_offset: Option = { + let ptr = symtab_ptr as usize; + let base = base_addr as usize; + let offset = ptr.wrapping_sub(base); + if ptr >= base && ptr < 0xFFF6 && offset + 4 <= ROM_SIZE + && &bytes[offset..offset + 4] == b"C02S" + { + Some(offset) + } else { + None + } + }; + + // Data section scan stops at symbol table start (if any) or at boundary marker. + let scan_end = symtab_offset.unwrap_or(ROM_SIZE - 8); + let code_end = if has_boundary { (code_boundary - base_addr) as usize } else { - code_bytes + bytes[..scan_end] .iter() .rposition(|&b| b != 0xEA) .map(|i| i + 1) @@ -147,8 +161,7 @@ fn parse_rom(bytes: &[u8]) -> Option { }; let data_end = if has_boundary { - let marker_pos = bytes.len() - 8; - bytes[code_end..marker_pos] + bytes[code_end..scan_end] .iter() .rposition(|&b| b != 0xEA) .map(|i| code_end + i + 1) @@ -160,13 +173,41 @@ fn parse_rom(bytes: &[u8]) -> Option { Some(RomInfo { base_addr, code_end, data_end, has_boundary, nmi, reset, irq }) } +fn parse_symbols(bytes: &[u8], base_addr: u16) -> HashMap { + if bytes.len() < ROM_SIZE { return HashMap::new(); } + let symtab_ptr = u16::from_le_bytes([bytes[ROM_SIZE - 10], bytes[ROM_SIZE - 9]]); + let ptr = symtab_ptr as usize; + let base = base_addr as usize; + if ptr < base || ptr >= 0xFFF6 { return HashMap::new(); } + let offset = ptr - base; + if offset + 6 > ROM_SIZE || &bytes[offset..offset + 4] != b"C02S" { + return HashMap::new(); + } + let count = u16::from_le_bytes([bytes[offset + 4], bytes[offset + 5]]) as usize; + let mut map = HashMap::new(); + let mut pos = offset + 6; + for _ in 0..count { + if pos + 3 > ROM_SIZE { break; } + let addr = u16::from_le_bytes([bytes[pos], bytes[pos + 1]]); + pos += 2; + if let Some(end) = bytes[pos..ROM_SIZE].iter().position(|&b| b == 0) { + let name = String::from_utf8_lossy(&bytes[pos..pos + end]).into_owned(); + pos += end + 1; + map.insert(addr, name); + } else { + break; + } + } + map +} + pub fn dump_sections(bytes: &[u8]) { let info = match parse_rom(bytes) { Some(i) => i, None => { eprintln!("ROM too small"); return; } }; - let rom_size = bytes.len(); + let rom_size = ROM_SIZE; let base = info.base_addr; println!("ROM size: {} bytes ({:#06X})", rom_size, rom_size); @@ -221,7 +262,8 @@ pub fn disassemble(bytes: &[u8], mode: Mode) { }; let code = &bytes[..info.code_end]; - let labels = collect_jump_targets(code); + let mut labels = collect_jump_targets(code); + labels.extend(parse_symbols(bytes, info.base_addr)); let mut cursor = Cursor::new(code); let mut buffer = [0u8; 1]; @@ -503,7 +545,7 @@ pub fn dump_size(bytes: &[u8]) { None => { eprintln!("ROM too small"); return; } }; - let rom_size = bytes.len(); + let rom_size = ROM_SIZE; let code_bytes = info.code_end; let data_bytes = info.data_end - info.code_end; let used = info.data_end + VECTOR_TABLE_SIZE + 2; diff --git a/cc02/src/analysis/analyzer.c b/cc02/src/analysis/analyzer.c index e62bbca..8b8e716 100644 --- a/cc02/src/analysis/analyzer.c +++ b/cc02/src/analysis/analyzer.c @@ -189,6 +189,37 @@ symbol_t *analyzer_lookup(analyzer_t *a, const char *key) { } while(0) +/* + * Declares a local variable (a NODE_VAR_DECL or a function parameter), + * rejecting both same-scope redeclaration and shadowing of an outer scope. + * + * Shadowing is disallowed on purpose: the IR/codegen represent every + * variable by its bare name (OPERAND_VAR.name / zp_map keys on strcmp), with + * no per-scope qualifier. Two distinct "i"s in nested scopes would collapse + * onto the same zero-page storage and silently corrupt each other. Until + * variables carry a scope-unique identity end to end, the analyzer refuses + * to let an inner declaration reuse a name that's still visible from an + * enclosing scope. Sibling scopes (e.g. two back-to-back for-loops each + * declaring their own `i`) are unaffected - by the time the second loop's + * scope is pushed, the first one has already been popped and isn't part of + * the visible stack, so re-using the name there is genuinely fine. + */ +static void declare_local_variable(analyzer_t *a, node_t *node, symbol_t sym) { + symtab_t *current_scope = &a->scopes[a->depth - 1]; + if (symtab_lookup(current_scope, sym.name)) { + EMIT_NAME_ERROR(ERR_REDECLARATION, node->loc, sym.name); + return; + } + + if (analyzer_lookup(a, sym.name)) { + EMIT_NAME_ERROR(ERR_SHADOWED_DECLARATION, node->loc, sym.name); + return; + } + + analyzer_insert_symbol(a, sym.name, sym); +} + + static const type_t TYPE_ERROR = { .kind = TYPE_INVALID, .is_ptr = 0, .ptr_depth = 0 }; static const type_t TYPE_NULL = { .kind = TYPE_VOID, .is_ptr = 1, .ptr_depth = 1 }; @@ -566,7 +597,7 @@ static void analyze_stmt(analyzer_t *a, node_t *node) { } }; - INSERT_SYMBOL(node, sym); + declare_local_variable(a, node, sym); break; } @@ -610,8 +641,10 @@ static void analyze_stmt(analyzer_t *a, node_t *node) { case NODE_WHILE: resolve_expr_type(a, node->while_stmt.cond); + ++a->loop_depth; if (node->while_stmt.body) analyze_stmt(a, node->while_stmt.body); + --a->loop_depth; break; case NODE_FOR: @@ -622,11 +655,23 @@ static void analyze_stmt(analyzer_t *a, node_t *node) { resolve_expr_type(a, node->for_stmt.cond); if (node->for_stmt.incrementer) analyze_stmt(a, node->for_stmt.incrementer); + ++a->loop_depth; if (node->for_stmt.body) analyze_stmt(a, node->for_stmt.body); + --a->loop_depth; analyzer_scope_pop(a); break; + case NODE_BREAK: + if (!a->loop_depth) + EMIT_PLAIN_ERROR(ERR_BREAK_OUTSIDE_LOOP, node->loc); + break; + + case NODE_CONTINUE: + if (!a->loop_depth) + EMIT_PLAIN_ERROR(ERR_CONTINUE_OUTSIDE_LOOP, node->loc); + break; + default: resolve_expr_type(a, node); break; @@ -832,9 +877,7 @@ static void pass2_entry(analyzer_t *a, ast_t program) { } }; - if (!analyzer_insert_symbol(a, sym.name, sym)) { - EMIT_NAME_ERROR(ERR_REDECLARATION, node->loc, sym.name); - } + declare_local_variable(a, node, sym); } // analyze block diff --git a/cc02/src/analysis/analyzer.h b/cc02/src/analysis/analyzer.h index d11cb28..2d0ea48 100644 --- a/cc02/src/analysis/analyzer.h +++ b/cc02/src/analysis/analyzer.h @@ -30,6 +30,18 @@ * analyzer_lookup() walks the stack top-down (innermost scope first, down * to global at index 0) - this replaces the old Rust SymbolTable's * recursive `parent` chain with a plain loop over a flat array. + * + * SHADOWING IS DISALLOWED + * ------------------------ + * A variable declaration that reuses a name still visible from an enclosing + * scope (a live ancestor on the scope stack - not a sibling scope that's + * already been popped) is a semantic error (ERR_SHADOWED_DECLARATION), not + * an implicit shadow the way C allows. This is enforced by + * declare_local_variable() in analyzer.c, used for both NODE_VAR_DECL and + * function parameters. See that function's doc comment for the reason: + * downstream IR/codegen identify a variable purely by its bare name string, + * with no scope-qualifying suffix, so a shadowed inner declaration would + * silently alias the same storage as its outer namesake. */ #include "arena.h" @@ -44,6 +56,7 @@ typedef struct { arena_t arena; // backing arena for both scope growth and symtab entries unsigned errors; type_t current_return_type; // set by pass2 when entering a function body + unsigned loop_depth; // incremented when entering while/for bodies; break/continue require > 0 } analyzer_t; #define ANALYZER_SCOPE_ALLOC_SIZE sizeof(symtab_entry_t) * 64 @@ -66,9 +79,14 @@ void analyzer_scope_push(analyzer_t *a); void analyzer_scope_pop(analyzer_t *a); // Inserts `value` under `key` into the innermost (current) scope only. -// Returns 1 on success, 0 if `key` is already declared in *this* scope -// (shadowing an outer scope is allowed - only redeclaration within the -// same scope is rejected). +// Returns 1 on success, 0 if `key` is already declared in *this* scope. +// This is the raw symtab-level primitive and only checks the current scope +// by itself - it does NOT reject shadowing an outer scope. Callers that +// declare a variable (NODE_VAR_DECL, function params) must go through +// declare_local_variable() in analyzer.c instead, which additionally walks +// outer scopes and rejects shadowing outright (see that function's comment +// for why: codegen keys variables by bare name, so a shadowed name would +// alias the same storage as its outer namesake). int analyzer_insert_symbol(analyzer_t *a, char *key, symbol_t value); // Looks up `key` starting at the innermost scope and walking outward to diff --git a/cc02/src/code-gen/generator.c b/cc02/src/code-gen/generator.c index 92b3cfe..17e829b 100644 --- a/cc02/src/code-gen/generator.c +++ b/cc02/src/code-gen/generator.c @@ -8,9 +8,10 @@ // Memory map // ---------------------------------------------------------------- -#define ROM_SIZE 0x8000 #define RAM_START 0x0200 // 6502 hardware stack occupies 0x0100 - 0x01FF +#define RAM_TOP 0x3FFF // see docs/memmap.md #define ROM_START 0x8000 +#define ROM_SIZE 0x8000 #define FP 0x00 #define RET 0x02 @@ -33,6 +34,13 @@ #define HELPER_ARG2 0xE9 // divisor / multiplier (modified by helpers) #define HELPER_RES 0xEA // quotient / product #define HELPER_REM 0xEB // remainder (division only) +#define HELPER_SIGN 0xEC // sign flags for __sdiv8/__sdiv16 (bit7=negate quotient, bit6=negate remainder) + +// 16-bit helper ZP slots ($E0–$E7, below the 8-bit helper zone at $E8) +#define HELPER16_ARG1 0xE0 // 2 bytes: dividend/multiplicand (lo=$E0, hi=$E1) +#define HELPER16_ARG2 0xE2 // 2 bytes: divisor/multiplier (lo=$E2, hi=$E3) +#define HELPER16_RES 0xE4 // 2 bytes: quotient/product (lo=$E4, hi=$E5) +#define HELPER16_REM 0xE6 // 2 bytes: remainder (lo=$E6, hi=$E7) // ---------------------------------------------------------------- @@ -98,19 +106,25 @@ static uint8_t zp_map_lookup(zp_map_t *map, tac_operand_t *op) { // Assign the next available ZP slot to an operand (deduped, type-stride-aware). -static void zp_map_add(emitter_t *e, zp_map_t *map, tac_operand_kind_t kind, - char *name, unsigned temp_id, type_t type) { +static int zp_map_add(emitter_t *e, zp_map_t *map, tac_operand_kind_t kind, + char *name, unsigned temp_id, type_t type) { tac_operand_t probe = { .kind = kind }; if (kind == OPERAND_VAR) probe.name = name; else probe.temp_id = temp_id; if (zp_map_lookup(map, &probe) != ZP_NOT_FOUND) - return; + return 1; if (map->count >= ZP_MAP_MAX) { fprintf(stderr, "codegen: ZP map overflow (>%d operands)\n", ZP_MAP_MAX); - return; + return 0; } unsigned size = full_type_size(e, type); + if ((unsigned)map->next_addr + size > HELPER16_ARG1) { + fprintf(stderr, "codegen: ZP space exhausted (next=$%02X, need %u bytes, limit=$%02X)\n", + map->next_addr, size, HELPER16_ARG1); + return 0; + } + zp_entry_t *entry = &map->entries[map->count++]; entry->kind = kind; if (kind == OPERAND_VAR) entry->name = name; @@ -118,37 +132,41 @@ static void zp_map_add(emitter_t *e, zp_map_t *map, tac_operand_kind_t kind, entry->zp_addr = map->next_addr; entry->size = (uint8_t)size; map->next_addr += (uint8_t)size; + return 1; } // Register a TAC operand in the ZP map (dispatches var vs temp). -static void zp_map_add_operand(emitter_t *e, zp_map_t *map, tac_operand_t *op) { +static int zp_map_add_operand(emitter_t *e, zp_map_t *map, tac_operand_t *op) { if (op->kind == OPERAND_VAR) - zp_map_add(e, map, OPERAND_VAR, op->name, 0, op->type); - else if (op->kind == OPERAND_TEMP) - zp_map_add(e, map, OPERAND_TEMP, NULL, op->temp_id, op->type); + return zp_map_add(e, map, OPERAND_VAR, op->name, 0, op->type); + if (op->kind == OPERAND_TEMP) + return zp_map_add(e, map, OPERAND_TEMP, NULL, op->temp_id, op->type); + return 1; } // Build the per-function ZP map: params first, then all referenced operands. -static void zp_map_build(emitter_t *e, zp_map_t *map, cfg_t *cfg) { +static int zp_map_build(emitter_t *e, zp_map_t *map, cfg_t *cfg) { map->count = 0; map->next_addr = REG_START; for (unsigned i = 0; i < cfg->params.count; i++) { - zp_map_add(e, map, OPERAND_VAR, cfg->params.items[i].name, 0, - cfg->params.items[i].type); + if (!zp_map_add(e, map, OPERAND_VAR, cfg->params.items[i].name, 0, + cfg->params.items[i].type)) + return 0; } for (unsigned i = 0; i < cfg->block_count; i++) { basic_block_t *block = cfg->blocks[i]; for (unsigned j = 0; j < block->instr_count; j++) { tac_instr_t *inst = &block->instrs[j]; - zp_map_add_operand(e, map, &inst->dst); - zp_map_add_operand(e, map, &inst->src1); - zp_map_add_operand(e, map, &inst->src2); + if (!zp_map_add_operand(e, map, &inst->dst)) return 0; + if (!zp_map_add_operand(e, map, &inst->src1)) return 0; + if (!zp_map_add_operand(e, map, &inst->src2)) return 0; } } + return 1; } @@ -174,6 +192,7 @@ static void register_func_label(emitter_t *e, char *name, uint16_t addr) { // Backpatch all JSR placeholders with resolved function addresses. static int resolve_func_fixups(emitter_t *e) { + if (e->overflow) return 1; // ROM already corrupt; generate_rom will catch it for (unsigned i = 0; i < e->fixup_count; i++) { fixup_t *f = &e->fixups[i]; uint16_t addr = 0; @@ -215,6 +234,7 @@ static void add_fixup(emitter_t *e, char *func_name) { // Backpatch all local label placeholders (JMP/COND_JUMP) within a function. static int resolve_local_fixups(emitter_t *e) { + if (e->overflow) return 1; // ROM already corrupt; generate_rom will catch it for (unsigned i = 0; i < e->local_fixup_count; i++) { fixup_t *f = &e->local_fixups[i]; uint16_t addr = e->local_labels[f->label_id]; @@ -286,24 +306,34 @@ static void allocate_globals(emitter_t *e, ir_gen_t *gen) { // Op code emitters // ---------------------------------------------------------------- -#define EMIT(OP_CODE) e->rom[e->code_pos++] = OP_CODE - -#define OP_EMITTER_SINGLE_ARG(NAME, OP_CODE) \ - static void NAME(emitter_t *e, uint8_t byte) { \ - EMIT(OP_CODE); \ - EMIT(byte); \ +#define EMIT(OP_CODE) do { \ + if (e->code_pos >= ROM_SIZE) { e->overflow = 1; } \ + else { e->rom[e->code_pos++] = (uint8_t)(OP_CODE); } \ +} while (0) + +// Write one byte to an already-emitted position (branch offset backpatch). +// Guards against positions recorded after an overflow (which would be >= ROM_SIZE). +#define PATCH_BYTE(POS, VAL) do { \ + if ((POS) < ROM_SIZE) e->rom[(POS)] = (uint8_t)(VAL); \ + else e->overflow = 1; \ +} while (0) + +#define OP_EMITTER_SINGLE_ARG(NAME, OP_CODE) \ + static void NAME(emitter_t *e, uint8_t byte) { \ + EMIT(OP_CODE); \ + EMIT(byte); \ } -#define OP_EMITTER_NO_ARG(NAME, OP_CODE) \ - static void NAME(emitter_t *e) { \ - EMIT(OP_CODE); \ +#define OP_EMITTER_NO_ARG(NAME, OP_CODE) \ + static void NAME(emitter_t *e) { \ + EMIT(OP_CODE); \ } -#define OP_EMITTER_ABS(NAME, OP_CODE) \ - static void NAME(emitter_t *e, uint16_t addr) { \ - EMIT(OP_CODE); \ - EMIT((uint8_t)(addr & 0xFF)); \ - EMIT((uint8_t)(addr >> 8)); \ +#define OP_EMITTER_ABS(NAME, OP_CODE) \ + static void NAME(emitter_t *e, uint16_t addr) { \ + EMIT(OP_CODE); \ + EMIT((uint8_t)(addr & 0xFF)); \ + EMIT((uint8_t)(addr >> 8)); \ } OP_EMITTER_SINGLE_ARG(lda_imm, 0xA9) @@ -332,6 +362,7 @@ OP_EMITTER_SINGLE_ARG(beq_rel, 0xF0) OP_EMITTER_SINGLE_ARG(bne_rel, 0xD0) OP_EMITTER_SINGLE_ARG(bcs_rel, 0xB0) OP_EMITTER_SINGLE_ARG(bcc_rel, 0x90) +OP_EMITTER_SINGLE_ARG(bpl_rel, 0x10) OP_EMITTER_SINGLE_ARG(inc_zpg, 0xE6) OP_EMITTER_SINGLE_ARG(dec_zpg, 0xC6) @@ -384,7 +415,7 @@ static void jsr(emitter_t *e, char *func_name) { // ---------------------------------------------------------------- // Queue a fixup for a string ROM address (resolved after data section is emitted). -static void add_data_fixup(emitter_t *e, unsigned global_idx, uint8_t byte) { +static void add_data_fixup(emitter_t *e, const char *str_val, uint8_t byte) { if (e->data_fixup_count >= e->data_fixup_capacity) { unsigned cap = e->data_fixup_capacity ? e->data_fixup_capacity * 2 : 8; data_fixup_t *grown = arena_alloc(&e->arena, cap * sizeof(data_fixup_t)); @@ -395,7 +426,7 @@ static void add_data_fixup(emitter_t *e, unsigned global_idx, uint8_t byte) { } data_fixup_t *f = &e->data_fixups[e->data_fixup_count++]; f->patch_pos = e->code_pos; - f->global_idx = global_idx; + f->str_val = str_val; f->byte = byte; } @@ -417,7 +448,7 @@ static void emit_global_init(emitter_t *e, ir_gen_t *gen) { case IR_INIT_STR: for (unsigned b = 0; b < width; b++) { EMIT(0xA9); - add_data_fixup(e, i, (uint8_t)b); + add_data_fixup(e, g->str_val, (uint8_t)b); EMIT(0x00); sta_abs(e, (uint16_t)(entry->ram_addr + b)); } @@ -430,31 +461,48 @@ static void emit_global_init(emitter_t *e, ir_gen_t *gen) { // Write string literals into ROM after code and resolve data fixups. +// Each unique string value is written once; fixups for both global and local +// string pointers are patched with the correct ROM address. static void emit_data_section(emitter_t *e, ir_gen_t *gen) { + (void)gen; e->data_pos = e->code_pos; - uint16_t *str_addrs = malloc(gen->module.global_count * sizeof(uint16_t)); + if (e->data_fixup_count == 0) return; - for (unsigned i = 0; i < gen->module.global_count; i++) { - ir_global_t *g = &gen->module.globals[i]; - if (g->init_kind != IR_INIT_STR) { - str_addrs[i] = 0; - continue; + const char **unique_strs = malloc(e->data_fixup_count * sizeof(char *)); + uint16_t *unique_addrs = malloc(e->data_fixup_count * sizeof(uint16_t)); + unsigned unique_count = 0; + + for (unsigned i = 0; i < e->data_fixup_count; i++) { + const char *s = e->data_fixups[i].str_val; + unsigned found = unique_count; + for (unsigned j = 0; j < unique_count; j++) { + if (strcmp(unique_strs[j], s) == 0) { found = j; break; } } - str_addrs[i] = (uint16_t)(ROM_START + e->code_pos); - size_t len = strlen(g->str_val); - for (size_t j = 0; j <= len; j++) { - EMIT((uint8_t)g->str_val[j]); + if (found == unique_count) { + unique_strs[unique_count] = s; + unique_addrs[unique_count] = (uint16_t)(ROM_START + e->code_pos); + size_t len = strlen(s); + for (size_t k = 0; k <= len; k++) + EMIT((uint8_t)s[k]); + unique_count++; } } + if (e->overflow) { free(unique_strs); free(unique_addrs); return; } + for (unsigned i = 0; i < e->data_fixup_count; i++) { data_fixup_t *f = &e->data_fixups[i]; - uint16_t addr = str_addrs[f->global_idx]; - e->rom[f->patch_pos] = (uint8_t)((addr >> (8 * f->byte)) & 0xFF); + for (unsigned j = 0; j < unique_count; j++) { + if (strcmp(unique_strs[j], f->str_val) == 0) { + e->rom[f->patch_pos] = (uint8_t)((unique_addrs[j] >> (8 * f->byte)) & 0xFF); + break; + } + } } - free(str_addrs); + free(unique_strs); + free(unique_addrs); } @@ -507,6 +555,13 @@ static void emit_load_byte(emitter_t *e, zp_map_t *map, case OPERAND_CONST_INT: lda_imm(e, (uint8_t)((op->int_val >> (8 * byte)) & 0xFF)); break; + case OPERAND_CONST_STR: + // Emit LDA #placeholder; the immediate byte is patched by emit_data_section + // once the string's ROM address is known. + EMIT(0xA9); + add_data_fixup(e, op->str_val, (uint8_t)byte); + EMIT(0x00); + break; case OPERAND_VAR: { if (byte >= full_type_size(e, op->type)) { lda_imm(e, 0); break; } global_entry_t *g = lookup_global(e, op->name); @@ -642,7 +697,7 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { register_func_label(e, cfg->name, (uint16_t)(ROM_START + e->code_pos)); zp_map_t map; - zp_map_build(e, &map, cfg); + if (!zp_map_build(e, &map, cfg)) return 0; // main is only called by the bootstrap, which has no ZP state to preserve. // Every other callee saves the caller's ZP slots on entry and restores on return. @@ -918,10 +973,10 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { lda_imm(e, 0); EMIT(0xF0); p_done = e->code_pos; EMIT(0); // BEQ done // true: - e->rom[p_true] = (uint8_t)(e->code_pos - p_true - 1); + PATCH_BYTE(p_true, e->code_pos - p_true - 1); lda_imm(e, 1); // done: - e->rom[p_done] = (uint8_t)(e->code_pos - p_done - 1); + PATCH_BYTE(p_done, e->code_pos - p_done - 1); if (branch_op == 0xB0) { eor_imm(e, 0x01); } @@ -936,14 +991,14 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { emit_cmp_byte(e, &map, right, 0); EMIT(0xF0); p2 = e->code_pos; EMIT(0); // BEQ true // false: - e->rom[p1] = (uint8_t)(e->code_pos - p1 - 1); + PATCH_BYTE(p1, e->code_pos - p1 - 1); lda_imm(e, 0); EMIT(0xF0); p3 = e->code_pos; EMIT(0); // BEQ done // true: - e->rom[p2] = (uint8_t)(e->code_pos - p2 - 1); + PATCH_BYTE(p2, e->code_pos - p2 - 1); lda_imm(e, 1); // done: - e->rom[p3] = (uint8_t)(e->code_pos - p3 - 1); + PATCH_BYTE(p3, e->code_pos - p3 - 1); } else { emit_load_byte(e, &map, left, 1); emit_cmp_byte(e, &map, right, 1); @@ -955,11 +1010,11 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { lda_imm(e, 0); EMIT(0xF0); p3 = e->code_pos; EMIT(0); // BEQ done // true: - e->rom[p1] = (uint8_t)(e->code_pos - p1 - 1); - e->rom[p2] = (uint8_t)(e->code_pos - p2 - 1); + PATCH_BYTE(p1, e->code_pos - p1 - 1); + PATCH_BYTE(p2, e->code_pos - p2 - 1); lda_imm(e, 1); // done: - e->rom[p3] = (uint8_t)(e->code_pos - p3 - 1); + PATCH_BYTE(p3, e->code_pos - p3 - 1); } } else if (!is_signed) { // u16 unsigned ordering @@ -972,15 +1027,15 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { emit_cmp_byte(e, &map, right, 0); EMIT(0x90); p_true2 = e->code_pos; EMIT(0); // BCC true // false: - e->rom[p_false] = (uint8_t)(e->code_pos - p_false - 1); + PATCH_BYTE(p_false, e->code_pos - p_false - 1); lda_imm(e, 0); EMIT(0xF0); p_done = e->code_pos; EMIT(0); // BEQ done // true: - e->rom[p_true1] = (uint8_t)(e->code_pos - p_true1 - 1); - e->rom[p_true2] = (uint8_t)(e->code_pos - p_true2 - 1); + PATCH_BYTE(p_true1, e->code_pos - p_true1 - 1); + PATCH_BYTE(p_true2, e->code_pos - p_true2 - 1); lda_imm(e, 1); // done: - e->rom[p_done] = (uint8_t)(e->code_pos - p_done - 1); + PATCH_BYTE(p_done, e->code_pos - p_done - 1); if (branch_op == 0xB0) { eor_imm(e, 0x01); } @@ -997,24 +1052,24 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { // high bytes differ, not less → skip to false EMIT(0x4C); p_skip = e->code_pos; EMIT(0); EMIT(0); // JMP false // low_compare: - e->rom[p_low] = (uint8_t)(e->code_pos - p_low - 1); + PATCH_BYTE(p_low, e->code_pos - p_low - 1); emit_load_byte(e, &map, left, 0); emit_cmp_byte(e, &map, right, 0); EMIT(0x90); p_true2 = e->code_pos; EMIT(0); // BCC true // false: { uint16_t false_addr = (uint16_t)(ROM_START + e->code_pos); - e->rom[p_skip] = (uint8_t)(false_addr & 0xFF); - e->rom[p_skip + 1] = (uint8_t)(false_addr >> 8); + PATCH_BYTE(p_skip, false_addr & 0xFF); + PATCH_BYTE(p_skip + 1, false_addr >> 8); } lda_imm(e, 0); EMIT(0xF0); p_done = e->code_pos; EMIT(0); // BEQ done // true: - e->rom[p_true1] = (uint8_t)(e->code_pos - p_true1 - 1); - e->rom[p_true2] = (uint8_t)(e->code_pos - p_true2 - 1); + PATCH_BYTE(p_true1, e->code_pos - p_true1 - 1); + PATCH_BYTE(p_true2, e->code_pos - p_true2 - 1); lda_imm(e, 1); // done: - e->rom[p_done] = (uint8_t)(e->code_pos - p_done - 1); + PATCH_BYTE(p_done, e->code_pos - p_done - 1); if (branch_op == 0xB0) { eor_imm(e, 0x01); } @@ -1072,28 +1127,59 @@ static int emit_function_from_cfg(emitter_t *e, cfg_t *cfg) { // -- arithmetic -- -#define EMIT_INT8_HELPER(OP, ROUTINE, RESULT_SLOT, NEEDS_FLAG, ERR) \ - case OP: { \ - if (codegen_type_size(instruction->dst.type) > 1) { \ - fprintf(stderr, "codegen: u16 " ERR " not yet implemented\n"); \ - return 0; \ - } \ - emit_load_byte(e, &map, &instruction->src1, 0); \ - sta_zpg(e, HELPER_ARG1); \ - emit_load_byte(e, &map, &instruction->src2, 0); \ - sta_zpg(e, HELPER_ARG2); \ - jsr(e, ROUTINE); \ - lda_zpg(e, RESULT_SLOT); \ - emit_store_byte(e, &map, &instruction->dst, 0); \ - e->NEEDS_FLAG = 1; \ - break; \ +// Load src1/src2 into 8-bit helper slots, JSR, read one result byte. +#define EMIT_ARITH8(ROUTINE, RES_SLOT, NEEDS_FLAG) do { \ + emit_load_byte(e, &map, &instruction->src1, 0); sta_zpg(e, HELPER_ARG1); \ + emit_load_byte(e, &map, &instruction->src2, 0); sta_zpg(e, HELPER_ARG2); \ + jsr(e, ROUTINE); e->NEEDS_FLAG = 1; \ + lda_zpg(e, RES_SLOT); emit_store_byte(e, &map, &instruction->dst, 0); \ +} while (0) + +// Load src1/src2 into 16-bit helper slots, JSR, read two result bytes. +#define EMIT_ARITH16(ROUTINE, RES_SLOT, NEEDS_FLAG) do { \ + emit_load_byte(e, &map, &instruction->src1, 0); sta_zpg(e, HELPER16_ARG1); \ + emit_load_byte(e, &map, &instruction->src1, 1); sta_zpg(e, (uint8_t)(HELPER16_ARG1+1)); \ + emit_load_byte(e, &map, &instruction->src2, 0); sta_zpg(e, HELPER16_ARG2); \ + emit_load_byte(e, &map, &instruction->src2, 1); sta_zpg(e, (uint8_t)(HELPER16_ARG2+1)); \ + jsr(e, ROUTINE); e->NEEDS_FLAG = 1; \ + lda_zpg(e, RES_SLOT); emit_store_byte(e, &map, &instruction->dst, 0); \ + lda_zpg(e, (uint8_t)((RES_SLOT)+1)); emit_store_byte(e, &map, &instruction->dst, 1); \ +} while (0) + + case TAC_MUL: { + if (codegen_type_size(instruction->dst.type) > 1) + EMIT_ARITH16("__mul16", HELPER16_RES, needs_mul16); + else + EMIT_ARITH8("__mul8", HELPER_RES, needs_mul8); + break; } - EMIT_INT8_HELPER(TAC_MUL, "__mul8", HELPER_RES, needs_mul8, "multiply") - EMIT_INT8_HELPER(TAC_DIV, "__div8", HELPER_RES, needs_div8, "divide") - EMIT_INT8_HELPER(TAC_MOD, "__div8", HELPER_REM, needs_div8, "modulo") + case TAC_DIV: { + int is_s = is_signed_type(instruction->dst.type); + if (codegen_type_size(instruction->dst.type) > 1) { + if (is_s) EMIT_ARITH16("__sdiv16", HELPER16_RES, needs_sdiv16); + else EMIT_ARITH16("__div16", HELPER16_RES, needs_div16); + } else { + if (is_s) EMIT_ARITH8("__sdiv8", HELPER_RES, needs_sdiv8); + else EMIT_ARITH8("__div8", HELPER_RES, needs_div8); + } + break; + } -#undef EMIT_INT8_HELPER + case TAC_MOD: { + int is_s = is_signed_type(instruction->dst.type); + if (codegen_type_size(instruction->dst.type) > 1) { + if (is_s) EMIT_ARITH16("__sdiv16", HELPER16_REM, needs_sdiv16); + else EMIT_ARITH16("__div16", HELPER16_REM, needs_div16); + } else { + if (is_s) EMIT_ARITH8("__sdiv8", HELPER_REM, needs_sdiv8); + else EMIT_ARITH8("__div8", HELPER_REM, needs_div8); + } + break; + } + +#undef EMIT_ARITH8 +#undef EMIT_ARITH16 case TAC_BAND: { unsigned width = codegen_type_size(instruction->dst.type); @@ -1365,6 +1451,59 @@ static void emit_mul8_helper(emitter_t *e) { rts(e); } + +// i8 signed divide: HELPER_ARG1 / HELPER_ARG2 → HELPER_RES (quotient), HELPER_REM (remainder). +// Encodes signs in HELPER_SIGN (bit7=negate quotient, bit6=negate remainder), takes absolute +// values, calls __div8, then restores signs. Follows C truncation-toward-zero convention. +static void emit_sdiv8_helper(emitter_t *e) { + register_func_label(e, "__sdiv8", (uint16_t)(ROM_START + e->code_pos)); + lda_imm(e, 0); + sta_zpg(e, HELPER_SIGN); + + // If dividend (ARG1) negative: negate it, set bits 7+6 in SIGN (quotient and remainder both flip). + lda_zpg(e, HELPER_ARG1); + bpl_rel(e, 11); // skip 11 bytes if positive: + sec(e); // SEC + lda_imm(e, 0); // LDA #0 + sbc_zpg(e, HELPER_ARG1); // SBC ARG1 + sta_zpg(e, HELPER_ARG1); // STA ARG1 (= -ARG1) + lda_imm(e, 0xC0); // LDA #$C0 + sta_zpg(e, HELPER_SIGN); // STA SIGN (bits 7+6) + + // If divisor (ARG2) negative: negate it, toggle bit 7 in SIGN (quotient sign flips again). + lda_zpg(e, HELPER_ARG2); + bpl_rel(e, 13); // skip 13 bytes if positive: + sec(e); // SEC + lda_imm(e, 0); // LDA #0 + sbc_zpg(e, HELPER_ARG2); // SBC ARG2 + sta_zpg(e, HELPER_ARG2); // STA ARG2 (= -ARG2) + lda_zpg(e, HELPER_SIGN); // LDA SIGN + eor_imm(e, 0x80); // EOR #$80 (toggle bit 7) + sta_zpg(e, HELPER_SIGN); // STA SIGN + + jsr(e, "__div8"); + + // Negate quotient if bit 7 of SIGN is set. + lda_zpg(e, HELPER_SIGN); + bpl_rel(e, 7); // skip 7 bytes if bit 7 = 0: + sec(e); // SEC + lda_imm(e, 0); // LDA #0 + sbc_zpg(e, HELPER_RES); // SBC RES + sta_zpg(e, HELPER_RES); // STA RES + + // Negate remainder if bit 6 of SIGN is set. + lda_zpg(e, HELPER_SIGN); + and_imm(e, 0x40); + beq_rel(e, 7); // skip 7 bytes if bit 6 = 0: + sec(e); // SEC + lda_imm(e, 0); // LDA #0 + sbc_zpg(e, HELPER_REM); // SBC REM + sta_zpg(e, HELPER_REM); // STA REM + + rts(e); +} + + // u8 divide: HELPER_ARG1 / HELPER_ARG2 → HELPER_RES (quotient), HELPER_REM (remainder). // Uses binary long division (shift-subtract). ARG1 is consumed. // @@ -1393,6 +1532,265 @@ static void emit_div8_helper(emitter_t *e) { } +// u16 multiply: HELPER16_ARG1 * HELPER16_ARG2 → HELPER16_RES (shift-and-add). +// Both ARG operands are consumed. Correct for signed i16 (low 16 bits are sign-agnostic). +// +// Loop body layout (26 bytes per iteration): +// LSR ARG2_HI (2) | ROR ARG2_LO (2) | BCC +13 (2) | CLC (1) | LDA RES_LO (2) +// | ADC ARG1_LO (2) | STA RES_LO (2) | LDA RES_HI (2) | ADC ARG1_HI (2) +// | STA RES_HI (2) [BCC target:] | ASL ARG1_LO (2) | ROL ARG1_HI (2) +// | DEX (1) | BNE -26 (2) +static void emit_mul16_helper(emitter_t *e) { + register_func_label(e, "__mul16", (uint16_t)(ROM_START + e->code_pos)); + lda_imm(e, 0); + sta_zpg(e, HELPER16_RES); + sta_zpg(e, (uint8_t)(HELPER16_RES + 1)); + ldx_imm(e, 16); + // loop: + lsr_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); // shift multiplier right, LSB of full 16-bit → carry + ror_zpg(e, HELPER16_ARG2); + bcc_rel(e, 13); // skip add if LSB was 0 + clc(e); + lda_zpg(e, HELPER16_RES); + adc_zpg(e, HELPER16_ARG1); + sta_zpg(e, HELPER16_RES); + lda_zpg(e, (uint8_t)(HELPER16_RES + 1)); + adc_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + sta_zpg(e, (uint8_t)(HELPER16_RES + 1)); + // no_add (BCC target, byte 19 from loop start): + asl_zpg(e, HELPER16_ARG1); + rol_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + dex(e); + bne_rel(e, (uint8_t)(256u - 26u)); // back to LSR + rts(e); +} + + +// u16 divide: HELPER16_ARG1 / HELPER16_ARG2 → HELPER16_RES (quotient), HELPER16_REM (remainder). +// Uses CMP-based comparison to correctly handle divisors with bit 15 set (≥ $8000). +// ARG1 is consumed. Sets carry correctly for ROL quotient: 1=subtracted, 0=skipped. +// +// Loop body layout (45 bytes per iteration): +// ASL ARG1 (2) | ROL ARG1_HI (2) | ROL REM (2) | ROL REM_HI (2) | BCS dosub+14 (2) +// | LDA REM_HI (2) | CMP ARG2_HI (2) | BCC nosub+22 (2) | BNE dosub+6 (2) +// | LDA REM (2) | CMP ARG2 (2) | BCC nosub+14 (2) +// [dosub:] SEC (1) | LDA REM (2) | SBC ARG2 (2) | STA REM (2) | LDA REM_HI (2) +// | SBC ARG2_HI (2) | STA REM_HI (2) | SEC (1) +// [nosub:] ROL RES (2) | ROL RES_HI (2) | DEX (1) | BNE -45 (2) +static void emit_div16_helper(emitter_t *e) { + register_func_label(e, "__div16", (uint16_t)(ROM_START + e->code_pos)); + lda_imm(e, 0); + sta_zpg(e, HELPER16_REM); + sta_zpg(e, (uint8_t)(HELPER16_REM + 1)); + sta_zpg(e, HELPER16_RES); + sta_zpg(e, (uint8_t)(HELPER16_RES + 1)); + ldx_imm(e, 16); + // loop: + asl_zpg(e, HELPER16_ARG1); + rol_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + rol_zpg(e, HELPER16_REM); + rol_zpg(e, (uint8_t)(HELPER16_REM + 1)); // carry = 17th-bit overflow (REM ≥ $10000) + bcs_rel(e, 14); // overflow → must subtract regardless of compare + lda_zpg(e, (uint8_t)(HELPER16_REM + 1)); + cmp_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); + bcc_rel(e, 22); // REM_HI < ARG2_HI → no subtract + bne_rel(e, 6); // REM_HI > ARG2_HI → subtract + lda_zpg(e, HELPER16_REM); + cmp_zpg(e, HELPER16_ARG2); + bcc_rel(e, 14); // REM_LO < ARG2_LO (hi equal) → no subtract + // dosub (byte 24 from loop start): + sec(e); + lda_zpg(e, HELPER16_REM); + sbc_zpg(e, HELPER16_ARG2); + sta_zpg(e, HELPER16_REM); + lda_zpg(e, (uint8_t)(HELPER16_REM + 1)); + sbc_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); + sta_zpg(e, (uint8_t)(HELPER16_REM + 1)); + sec(e); // carry = 1: quotient bit = 1 + // nosub (byte 38 from loop start): + rol_zpg(e, HELPER16_RES); + rol_zpg(e, (uint8_t)(HELPER16_RES + 1)); + dex(e); + bne_rel(e, (uint8_t)(256u - 45u)); // back to ASL + rts(e); +} + + +// i16 signed divide: HELPER16_ARG1 / HELPER16_ARG2 → HELPER16_RES, HELPER16_REM. +// Records sign in HELPER_SIGN (bit7=negate quotient, bit6=negate remainder), +// takes absolute values, calls __div16, then restores signs. Truncates toward zero. +static void emit_sdiv16_helper(emitter_t *e) { + register_func_label(e, "__sdiv16", (uint16_t)(ROM_START + e->code_pos)); + lda_imm(e, 0); + sta_zpg(e, HELPER_SIGN); + + // If ARG1 negative: negate it, set bits 7+6 in SIGN (both quotient and remainder flip). + lda_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + bpl_rel(e, 17); // skip 17 bytes if positive + sec(e); + lda_imm(e, 0); + sbc_zpg(e, HELPER16_ARG1); + sta_zpg(e, HELPER16_ARG1); + lda_imm(e, 0); + sbc_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + sta_zpg(e, (uint8_t)(HELPER16_ARG1 + 1)); + lda_imm(e, 0xC0); + sta_zpg(e, HELPER_SIGN); + // pos_arg1 (byte 25 from function start): + + // If ARG2 negative: negate it, toggle bit 7 in SIGN (quotient sign flips again). + lda_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); + bpl_rel(e, 19); // skip 19 bytes if positive + sec(e); + lda_imm(e, 0); + sbc_zpg(e, HELPER16_ARG2); + sta_zpg(e, HELPER16_ARG2); + lda_imm(e, 0); + sbc_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); + sta_zpg(e, (uint8_t)(HELPER16_ARG2 + 1)); + lda_zpg(e, HELPER_SIGN); + eor_imm(e, 0x80); + sta_zpg(e, HELPER_SIGN); + // pos_arg2 (byte 48 from function start): + + jsr(e, "__div16"); + + // Negate quotient if bit 7 of SIGN set. + lda_zpg(e, HELPER_SIGN); + bpl_rel(e, 13); // skip 13 bytes if bit 7 = 0 + sec(e); + lda_imm(e, 0); + sbc_zpg(e, HELPER16_RES); + sta_zpg(e, HELPER16_RES); + lda_imm(e, 0); + sbc_zpg(e, (uint8_t)(HELPER16_RES + 1)); + sta_zpg(e, (uint8_t)(HELPER16_RES + 1)); + // pos_res (byte 68 from function start): + + // Negate remainder if bit 6 of SIGN set. + lda_zpg(e, HELPER_SIGN); + and_imm(e, 0x40); + beq_rel(e, 13); // skip 13 bytes if bit 6 = 0 + sec(e); + lda_imm(e, 0); + sbc_zpg(e, HELPER16_REM); + sta_zpg(e, HELPER16_REM); + lda_imm(e, 0); + sbc_zpg(e, (uint8_t)(HELPER16_REM + 1)); + sta_zpg(e, (uint8_t)(HELPER16_REM + 1)); + // pos_rem (byte 87 from function start): + + rts(e); +} + + +// ROM footer layout (offsets from ROM_START): +// $FFF6-$FFF7 SYMTABLE_START_PTR: little-endian absolute address of the "C02S" symbol +// table header, or $EAEA (NOP fill) if no table was written. The disassembler +// reads this first; old binaries that lack a table have $EAEA here, which is +// in range but fails the magic-byte check, so they degrade gracefully. +// $FFF8-$FFF9 SYMTABLE_BOUNDARY_PTR: little-endian absolute address of the first byte +// past the code+data region (= start of NOP fill). Used by the disassembler +// to know where executable/data bytes end and padding begins. +// $FFFA-$FFFF NMI / Reset / IRQ vectors (written by emit_vectors). +#define SYMTABLE_START_PTR (0xFFF6 - ROM_START) +#define SYMTABLE_BOUNDARY_PTR (0xFFF8 - ROM_START) + +// Write the "C02S" symbol table into the NOP fill area immediately after the data section, +// then store its absolute address at SYMTABLE_START_PTR so the disassembler can find it. +// Each entry is: u16 address (LE) + null-terminated name. The table is silently omitted if +// the code+data section is too large to fit it before the footer (extremely unlikely in +// practice — a fully packed 32KB image still leaves the footer region intact). +static void emit_symbol_table(emitter_t *e) { + size_t sym_size = 6; // "C02S" (4) + count u16 (2) + for (unsigned i = 0; i < e->func_label_count; i++) + sym_size += 2 + strlen(e->func_labels[i].name) + 1; + + if (e->code_pos + sym_size > SYMTABLE_START_PTR) + return; + + uint16_t symtab_addr = (uint16_t)(ROM_START + e->code_pos); + + // Magic number followed by number of symbols + EMIT('C'); EMIT('0'); EMIT('2'); EMIT('S'); + EMIT((uint8_t)(e->func_label_count & 0xFF)); + EMIT((uint8_t)(e->func_label_count >> 8)); + + for (unsigned i = 0; i < e->func_label_count; i++) { + uint16_t addr = e->func_labels[i].addr; + EMIT((uint8_t)(addr & 0xFF)); + EMIT((uint8_t)(addr >> 8)); + for (const char *n = e->func_labels[i].name; *n; n++) EMIT((uint8_t)*n); + EMIT(0); + } + + PATCH_BYTE(SYMTABLE_START_PTR, symtab_addr & 0xFF); + PATCH_BYTE(SYMTABLE_START_PTR + 1, symtab_addr >> 8); +} + + +// Pass 1: allocate 2 bytes of RAM for a compiler extern and register it in +// global_entries so lookup_global() uses absolute addressing. +#define ALLOC_COMPILER_SLOT(EXT) do { \ + uint16_t _addr = e->ram_pos; \ + e->ram_pos += 2; \ + unsigned _n = e->global_entry_count + 1; \ + global_entry_t *_g = arena_alloc(&e->arena, _n * sizeof(global_entry_t)); \ + if (e->global_entry_count > 0) \ + memcpy(_g, e->global_entries, \ + e->global_entry_count * sizeof(global_entry_t)); \ + _g[e->global_entry_count] = (global_entry_t){ \ + .name = (EXT)->name, .ram_addr = _addr, .size = 2, .type = (EXT)->type \ + }; \ + e->global_entries = _g; \ + e->global_entry_count = _n; \ +} while (0) + +// Pass 2: emit the 16-bit initialiser for an already-allocated compiler extern. +// Looks up the RAM address from global_entries by name; no-ops if not present. +#define EMIT_COMPILER_VALUE(NAME, VALUE) do { \ + global_entry_t *_ge = lookup_global(e, (char *)(NAME)); \ + if (_ge) { \ + uint16_t _val = (uint16_t)(VALUE); \ + lda_imm(e, (uint8_t)(_val & 0xFF)); sta_abs(e, _ge->ram_addr); \ + lda_imm(e, (uint8_t)(_val >> 8)); sta_abs(e, (uint16_t)(_ge->ram_addr + 1)); \ + } \ +} while (0) + + +// Allocate RAM and emit initialisers for compiler-defined externs (decl). +// Two-pass: all slots are allocated first so e->ram_pos is fully settled +// before any value is emitted. This ensures __heap_start captures the +// correct first-free-RAM address regardless of declaration order. +// Returns 0 if an extern is not a known compiler constant. +static int emit_compiler_extern_inits(emitter_t *e, ir_gen_t *gen) { + // Pass 1: allocate all slots. + for (unsigned i = 0; i < gen->module.extern_count; i++) { + ir_extern_t *ext = &gen->module.externs[i]; + if (ext->is_function) continue; + + if (strcmp(ext->name, "__heap_start") == 0) + ALLOC_COMPILER_SLOT(ext); + else if (strcmp(ext->name, "__memory_top") == 0) + ALLOC_COMPILER_SLOT(ext); + else { + fprintf(stderr, "codegen: unresolved extern '%s'\n", ext->name); + return 0; + } + } + + // Pass 2: emit initialisers (e->ram_pos is now fully settled). + // Add new compiler constants here with EMIT_COMPILER_VALUE("__name", value). + EMIT_COMPILER_VALUE("__heap_start", e->ram_pos); + EMIT_COMPILER_VALUE("__memory_top", RAM_TOP); + + return 1; +} + +#undef ALLOC_COMPILER_SLOT +#undef EMIT_COMPILER_VALUE + + // ---------------------------------------------------------------- // Main code gen // ---------------------------------------------------------------- @@ -1402,7 +1800,7 @@ static void emitter_free(emitter_t *e) { } -uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size) { +uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size, int emit_symbols) { emitter_t e = { 0 }; if (!arena_init(&e.arena, 4096)) return NULL; @@ -1420,6 +1818,14 @@ uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size) { allocate_globals(&e, gen); emit_bootstrap(&e); emit_global_init(&e, gen); + + if (!emit_compiler_extern_inits(&e, gen)) { + free(e.rom); + emitter_free(&e); + *final_rom_size = 0; + return NULL; + } + emit_call_main(&e); for (unsigned i = 0; i < gen->module.cfg_count; ++i) { @@ -1431,9 +1837,15 @@ uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size) { } } + // add helpers if (e.needs_mul8) emit_mul8_helper(&e); + if (e.needs_sdiv8) { emit_sdiv8_helper(&e); e.needs_div8 = 1; } if (e.needs_div8) emit_div8_helper(&e); + if (e.needs_mul16) emit_mul16_helper(&e); + if (e.needs_sdiv16) { emit_sdiv16_helper(&e); e.needs_div16 = 1; } + if (e.needs_div16) emit_div16_helper(&e); + if (!resolve_func_fixups(&e)) { free(e.rom); emitter_free(&e); @@ -1441,13 +1853,30 @@ uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size) { return NULL; } + #define OVERFLOW_ERROR_CHECK(msg) \ + if (e.overflow) { \ + fprintf(stderr, msg); \ + free(e.rom); \ + emitter_free(&e); \ + *final_rom_size = 0; \ + return NULL; \ + } + + OVERFLOW_ERROR_CHECK("Code section generation failed: output exceeds 32 KB ROM.\n"); + emit_data_section(&e, gen); - // code/data boundary at $FFF8 for the disassembler - unsigned boundary_pos = 0xFFF8 - ROM_START; - uint16_t code_end = (uint16_t)(ROM_START + e.data_pos); - e.rom[boundary_pos] = (uint8_t)(code_end & 0xFF); - e.rom[boundary_pos + 1] = (uint8_t)(code_end >> 8); + OVERFLOW_ERROR_CHECK("Data section generation failed: output exceeds 32 KB ROM.\n"); + + if (emit_symbols && e.func_label_count > 0) { + emit_symbol_table(&e); + OVERFLOW_ERROR_CHECK("Symbol table generation failed: output exceeds 32 KB ROM.\n"); + } + #undef OVERFLOW_ERROR_CHECK + + uint16_t code_end_addr = (uint16_t)(ROM_START + e.data_pos); + e.rom[SYMTABLE_BOUNDARY_PTR] = (uint8_t)(code_end_addr & 0xFF); + e.rom[SYMTABLE_BOUNDARY_PTR + 1] = (uint8_t)(code_end_addr >> 8); emit_vectors(&e); emitter_free(&e); diff --git a/cc02/src/code-gen/generator.h b/cc02/src/code-gen/generator.h index ef80ddd..3a86f08 100644 --- a/cc02/src/code-gen/generator.h +++ b/cc02/src/code-gen/generator.h @@ -17,7 +17,7 @@ typedef struct { char *func_name; } fixup_t; -#define ZP_MAP_MAX 112 // $04–$EE = 235 bytes, but max ~112 unique operands +#define ZP_MAP_MAX 110 // $04–$DF = 220 bytes; address guard in zp_map_add enforces $E0 hard stop typedef struct { tac_operand_kind_t kind; @@ -43,9 +43,9 @@ typedef struct { } global_entry_t; typedef struct { - size_t patch_pos; - unsigned global_idx; - uint8_t byte; + size_t patch_pos; + const char *str_val; + uint8_t byte; } data_fixup_t; typedef struct { @@ -61,6 +61,13 @@ typedef struct { int needs_mul8; int needs_div8; + int needs_sdiv8; + + int needs_mul16; + int needs_div16; + int needs_sdiv16; + + int overflow; func_label_t *func_labels; unsigned func_label_count; @@ -85,6 +92,6 @@ typedef struct { unsigned data_fixup_capacity; } emitter_t; -uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size); +uint8_t *generate_rom(ir_gen_t *gen, size_t *final_rom_size, int emit_symbols); #endif diff --git a/cc02/src/common/errors.c b/cc02/src/common/errors.c index 4e24e1b..00c449b 100644 --- a/cc02/src/common/errors.c +++ b/cc02/src/common/errors.c @@ -117,6 +117,12 @@ static void print_semantic_kind_error(error_t *e) { fprintf(stderr, "redeclaration of '%s' in this scope\n", e->name_error.name); break; + case ERR_SHADOWED_DECLARATION: + PRINT_ERR_HEADER(e); + fprintf(stderr, "declaration of '%s' shadows an outer-scope declaration " + "(shadowing is not supported - rename one of them)\n", e->name_error.name); + break; + case ERR_NOT_ASSIGNABLE: PRINT_ERR_HEADER(e); fprintf(stderr, "'%s' is not assignable\n", e->name_error.name); @@ -189,6 +195,16 @@ static void print_semantic_kind_error(error_t *e) { } break; + case ERR_BREAK_OUTSIDE_LOOP: + PRINT_ERR_HEADER(e); + fprintf(stderr, "'break' statement not within a loop\n"); + break; + + case ERR_CONTINUE_OUTSIDE_LOOP: + PRINT_ERR_HEADER(e); + fprintf(stderr, "'continue' statement not within a loop\n"); + break; + default: return; // unreachable - parser kinds handled in print_parse_kind_error } diff --git a/cc02/src/common/errors.h b/cc02/src/common/errors.h index 45fd2dc..0ba0299 100644 --- a/cc02/src/common/errors.h +++ b/cc02/src/common/errors.h @@ -43,6 +43,7 @@ typedef enum { ERR_NOT_A_FUNCTION, ERR_UNKNOWN_STRUCT, ERR_REDECLARATION, + ERR_SHADOWED_DECLARATION, // variable declaration reuses a name already visible in an enclosing scope ERR_TYPE_MISMATCH, ERR_WRONG_ARG_TYPE, ERR_WRONG_ARG_COUNT, @@ -55,6 +56,8 @@ typedef enum { ERR_NOT_A_STRUCT, // field access ('.') on a non-struct type ERR_MISSING_RETURN, // a non-void function may fall off the end without returning ERR_INCOMPLETE_STRUCT_FIELD, // by-value struct field references an incomplete/forward-declared or self-referential struct + ERR_BREAK_OUTSIDE_LOOP, // break statement not inside a while/for loop + ERR_CONTINUE_OUTSIDE_LOOP, // continue statement not inside a while/for loop } error_type_t; typedef struct { @@ -70,7 +73,7 @@ typedef struct { struct { char *name; - } name_error; // ERR_UNDECLARED_IDENTIFIER, ERR_NOT_A_FUNCTION, ERR_UNKNOWN_STRUCT, ERR_REDECLARATION, ERR_NOT_ASSIGNABLE + } name_error; // ERR_UNDECLARED_IDENTIFIER, ERR_NOT_A_FUNCTION, ERR_UNKNOWN_STRUCT, ERR_REDECLARATION, ERR_SHADOWED_DECLARATION, ERR_NOT_ASSIGNABLE struct { type_t expected; diff --git a/cc02/src/driver.c b/cc02/src/driver.c index 6ce243d..d42ec7a 100644 --- a/cc02/src/driver.c +++ b/cc02/src/driver.c @@ -179,7 +179,7 @@ static int run_codegen(params_t *params, compiler_t *c, timing_t *t) { int status = 0; size_t rom_size; - uint8_t *rom = generate_rom(&c->ir_gen, &rom_size); + uint8_t *rom = generate_rom(&c->ir_gen, &rom_size, !params->strip_debug); if (!rom) { fprintf(stderr, RED "Code generation failed.\n" RESET); t->codegen = get_time_ms() - start; @@ -211,8 +211,23 @@ int run_compiler(params_t *params, timing_t *timing) { int status = load_source(params, &c, timing); - if (status == 0 && !params->is_input_bin) + if (status == 0 && !params->is_input_bin) { + // inject compiler globals; + const char *externs = "\ndecl u16 __heap_start;\ndecl u16 __memory_top;\n"; + size_t extern_length = strlen(externs); + + c.source_size += (long)extern_length; + char *new_source = realloc(c.source_code, (size_t)c.source_size); + if (!new_source) { + compiler_cleanup(&c); + return TOKEN_ERROR_RET_CODE; + } + + c.source_code = new_source; + strcat(c.source_code, externs); + status = run_frontend(params, &c, timing); + } if (status == 0 && !params->syntax_only) status = run_ir(params, &c, timing); diff --git a/cc02/src/driver.h b/cc02/src/driver.h index 5e6da2f..3fb34e4 100644 --- a/cc02/src/driver.h +++ b/cc02/src/driver.h @@ -17,6 +17,7 @@ typedef struct { int syntax_only; int time_report; int incremental_build; + int strip_debug; int is_input_bin; char *output; diff --git a/cc02/src/ir-gen/ir-print.c b/cc02/src/ir-gen/ir-print.c index f7bf9db..c3a798d 100644 --- a/cc02/src/ir-gen/ir-print.c +++ b/cc02/src/ir-gen/ir-print.c @@ -156,6 +156,14 @@ static void print_instr(tac_instr_t *ins) { print_operand(ins->src1); } break; + + case TAC_BREAK: + printf("break"); + break; + + case TAC_CONTINUE: + printf("continue"); + break; case TAC_FIELD_LOAD: print_operand(ins->dst); @@ -190,17 +198,17 @@ static void print_cfg(cfg_t *cfg) { } printf(") -> "); print_type(cfg->return_type); - printf("\n"); + printf(" {\n"); for (unsigned i = 0; i < cfg->block_count; i++) { basic_block_t *bb = cfg->blocks[i]; - printf(" block%u:\n", bb->id); + printf("block%u:\n", bb->id); for (unsigned j = 0; j < bb->instr_count; j++) { - printf(" "); + printf(" "); print_instr(&bb->instrs[j]); } } - printf("\n"); + printf("}\n\n"); } void ir_gen_print(ir_gen_t *gen) { diff --git a/cc02/src/ir-gen/ir.c b/cc02/src/ir-gen/ir.c index b6fe948..4850d68 100644 --- a/cc02/src/ir-gen/ir.c +++ b/cc02/src/ir-gen/ir.c @@ -312,6 +312,43 @@ static unsigned new_label(cfg_t *cfg) { } +// Returns 1 for 8-bit types and pointer-less values, 2 for 16-bit / pointer types. +static int ir_type_width(type_t t) { + return (t.kind == TYPE_U16 || t.kind == TYPE_I16 || t.is_ptr) ? 2 : 1; +} + +static int ir_is_signed(type_t t) { + return t.kind == TYPE_I8 || t.kind == TYPE_I16; +} + +// Returns the common type both operands of a binary op should be widened to. +// Uses the wider operand's type; for equal widths, unsigned wins (matching C's +// usual arithmetic conversions for comparisons — prevents order-dependent +// signed/unsigned mismatch when one operand is signed and the other is not). +static type_t binop_common_type(type_t left, type_t right) { + int lw = ir_type_width(left), rw = ir_type_width(right); + type_t wider = (lw >= rw) ? left : right; + if (!ir_is_signed(left) || !ir_is_signed(right)) + wider.kind = (ir_type_width(wider) == 2) ? TYPE_U16 : TYPE_U8; + return wider; +} + +// Widens `op` to `target` type by emitting a TAC_CAST if the types differ. +// Constants are re-typed in place (no instruction needed). +static tac_operand_t emit_widen_if_needed(ir_gen_t *gen, cfg_t *cfg, + tac_operand_t op, type_t target) { + if (op.type.kind == target.kind && op.type.is_ptr == target.is_ptr) + return op; + if (op.kind == OPERAND_CONST_INT) { + op.type = target; + return op; + } + tac_operand_t dst = new_temp(cfg, target); + emit(gen, cfg, (tac_instr_t){ .op = TAC_CAST, .dst = dst, .src1 = op, .cast_type = target }); + return dst; +} + + // Lowers an expression node into TAC, returning the operand // that holds the result. Built up case by case. static tac_operand_t lower_expr(ir_gen_t *gen, cfg_t *cfg, node_t *node) { @@ -399,13 +436,27 @@ static tac_operand_t lower_expr(ir_gen_t *gen, cfg_t *cfg, node_t *node) { tac_operand_t left = lower_expr(gen, cfg, node->binop.left); tac_operand_t right = lower_expr(gen, cfg, node->binop.right); - int is_cmp = op == TAC_LT || op == TAC_LTE || - op == TAC_GT || op == TAC_GTE || - op == TAC_EQ || op == TAC_NEQ; - - type_t type = is_cmp ? (type_t){ .kind = TYPE_U8 } : left.type; - tac_operand_t dst = new_temp(cfg, type); + int is_cmp = op == TAC_LT || op == TAC_LTE || + op == TAC_GT || op == TAC_GTE || + op == TAC_EQ || op == TAC_NEQ; + int is_shift = op == TAC_SHL || op == TAC_SHR; + + type_t result_type; + if (is_shift) { + // Shift result is left's type; don't widen the count. + result_type = left.type; + } else if (!left.type.is_ptr && !right.type.is_ptr) { + // Widen both operands to the common type before the op. + type_t common = binop_common_type(left.type, right.type); + left = emit_widen_if_needed(gen, cfg, left, common); + right = emit_widen_if_needed(gen, cfg, right, common); + result_type = is_cmp ? (type_t){ .kind = TYPE_U8 } : common; + } else { + // Pointer arithmetic: don't widen (pointer ± integer, etc.). + result_type = left.type; + } + tac_operand_t dst = new_temp(cfg, result_type); emit(gen, cfg, (tac_instr_t){ .op = op, .dst = dst, .src1 = left, .src2 = right }); return dst; } @@ -678,8 +729,10 @@ static void lower_stmt(ir_gen_t *gen, cfg_t *cfg, node_t *node) { emit(gen, cfg, (tac_instr_t){ .op = TAC_COND_JUMP, .src1 = neg, .label = end_label }); // otherwise fall through to actual while block + gen->loop_stack[gen->loop_depth++] = (loop_ctx_t){ cond_label, end_label }; if (node->while_stmt.body) lower_block(gen, cfg, node->while_stmt.body); + --gen->loop_depth; // jump back to re-evaluate conditional emit(gen, cfg, (tac_instr_t){ .op = TAC_JUMP, .label = cond_label }); @@ -692,6 +745,7 @@ static void lower_stmt(ir_gen_t *gen, cfg_t *cfg, node_t *node) { case NODE_FOR: { unsigned cond_label = new_label(cfg); // for (; ; ) unsigned end_label = new_label(cfg); + unsigned incr_label = new_label(cfg); // continue jumps here to run incrementer before recheck // initter if (node->for_stmt.initialiser) @@ -711,19 +765,23 @@ static void lower_stmt(ir_gen_t *gen, cfg_t *cfg, node_t *node) { } // otherwise fall through to actual for block + gen->loop_stack[gen->loop_depth++] = (loop_ctx_t){ incr_label, end_label }; if (node->for_stmt.body) lower_block(gen, cfg, node->for_stmt.body); + --gen->loop_depth; + + // continue lands here: run incrementer then re-check condition + emit(gen, cfg, (tac_instr_t){ .op = TAC_LABEL, .label = incr_label }); // after body runs, do incrementer if (node->for_stmt.incrementer) - lower_expr(gen, cfg, node->for_stmt.incrementer); + lower_stmt(gen, cfg, node->for_stmt.incrementer); // jump back to re-evaluate conditional emit(gen, cfg, (tac_instr_t){ .op = TAC_JUMP, .label = cond_label }); - // where we jump to when cond is false, don't need it if no conditional - if (node->for_stmt.cond) - emit(gen, cfg, (tac_instr_t){ .op = TAC_LABEL, .label = end_label }); + // where we jump to when cond is false (always emitted so break always has a target) + emit(gen, cfg, (tac_instr_t){ .op = TAC_LABEL, .label = end_label }); break; } @@ -731,6 +789,18 @@ static void lower_stmt(ir_gen_t *gen, cfg_t *cfg, node_t *node) { lower_block(gen, cfg, node); break; + case NODE_CONTINUE: + if (gen->loop_depth > 0) + emit(gen, cfg, (tac_instr_t){ .op = TAC_JUMP, + .label = gen->loop_stack[gen->loop_depth - 1].continue_label }); + break; + + case NODE_BREAK: + if (gen->loop_depth > 0) + emit(gen, cfg, (tac_instr_t){ .op = TAC_JUMP, + .label = gen->loop_stack[gen->loop_depth - 1].break_label }); + break; + default: // expression statements (bare function calls like lcd_init()) lower_expr(gen, cfg, node); diff --git a/cc02/src/ir-gen/ir.h b/cc02/src/ir-gen/ir.h index 6e0f397..51056d7 100644 --- a/cc02/src/ir-gen/ir.h +++ b/cc02/src/ir-gen/ir.h @@ -109,6 +109,8 @@ typedef enum { // functions TAC_CALL, // dst = call name(args...) TAC_RETURN, // return src1 + TAC_CONTINUE, + TAC_BREAK, // structs TAC_FIELD_LOAD, // dst = src1.field @@ -255,6 +257,11 @@ typedef struct { #define IR_ARENA_CHUNK_SIZE 4096 +typedef struct { + unsigned continue_label; // TAC_JUMP target for continue (cond_label for while, incr_label for for) + unsigned break_label; // TAC_JUMP target for break (end_label) +} loop_ctx_t; + typedef struct { arena_t arena; ir_module_t module; @@ -264,6 +271,9 @@ typedef struct { unsigned reg_capacity; unsigned cfg_capacity; unsigned extern_capacity; + + loop_ctx_t loop_stack[64]; // nesting context for break/continue desugaring + int loop_depth; } ir_gen_t; diff --git a/cc02/src/lexer/tokenizer.c b/cc02/src/lexer/tokenizer.c index d84a67a..a9ed3b9 100644 --- a/cc02/src/lexer/tokenizer.c +++ b/cc02/src/lexer/tokenizer.c @@ -394,20 +394,22 @@ void print_error_line(token_location_t loc) { static int tokenize_keyword_or_identifier(token_t *tokens, unsigned *token_count, char **ptr, unsigned line, unsigned *column, char *file_path) { - MATCH_KEYWORD("fn", Kw_fn) - MATCH_KEYWORD("decl", Kw_fwd_decl) - MATCH_KEYWORD("reg", Kw_reg) - MATCH_KEYWORD("return", Kw_return) - MATCH_KEYWORD("struct", Kw_struct) - MATCH_KEYWORD("void", Kw_void) - MATCH_KEYWORD("if", Kw_if) - MATCH_KEYWORD("else", Kw_else) - MATCH_KEYWORD("while", Kw_while) - MATCH_KEYWORD("for", Kw_for) - MATCH_KEYWORD("u8", t_u8) - MATCH_KEYWORD("i8", t_i8) - MATCH_KEYWORD("u16", t_u16) - MATCH_KEYWORD("i16", t_i16) + MATCH_KEYWORD("fn", Kw_fn) + MATCH_KEYWORD("decl", Kw_fwd_decl) + MATCH_KEYWORD("reg", Kw_reg) + MATCH_KEYWORD("return", Kw_return) + MATCH_KEYWORD("struct", Kw_struct) + MATCH_KEYWORD("break", Kw_break) + MATCH_KEYWORD("continue", Kw_continue) + MATCH_KEYWORD("void", Kw_void) + MATCH_KEYWORD("if", Kw_if) + MATCH_KEYWORD("else", Kw_else) + MATCH_KEYWORD("while", Kw_while) + MATCH_KEYWORD("for", Kw_for) + MATCH_KEYWORD("u8", t_u8) + MATCH_KEYWORD("i8", t_i8) + MATCH_KEYWORD("u16", t_u16) + MATCH_KEYWORD("i16", t_i16) MATCH_KEYWORD_NUM_VAL("null", l_num, 0) // treat 'null' as a special numeric literal with value 0 MATCH_KEYWORD_NUM_VAL("false", l_num, 0) // treat 'false' as a special numeric literal with value 0 diff --git a/cc02/src/lexer/tokenizer.h b/cc02/src/lexer/tokenizer.h index de33c30..84c3c64 100644 --- a/cc02/src/lexer/tokenizer.h +++ b/cc02/src/lexer/tokenizer.h @@ -13,6 +13,8 @@ X(Kw_if, "if" , 0) \ X(Kw_else, "else" , 0) \ X(Kw_while, "while" , 0) \ + X(Kw_break, "break" , 0) \ + X(Kw_continue, "continue" , 0) \ X(Kw_for, "for" , 0) \ X(t_u8, "u8" , 0) \ X(t_i8, "i8" , 0) \ diff --git a/cc02/src/main.c b/cc02/src/main.c index 63fc9fb..27dd4d2 100644 --- a/cc02/src/main.c +++ b/cc02/src/main.c @@ -16,6 +16,7 @@ static void print_help(const char *prog_name) { fprintf(stderr, " --ir-dump Dump the IR after lowering\n"); fprintf(stderr, " --syntax-check-only Stop after syntax and semantic checks\n"); fprintf(stderr, " --time-report Prints a report showing how long each stage of compilation took\n"); + fprintf(stderr, " --strip-debug Omit symbol table from output binary\n"); fprintf(stderr, " -c, Incremental compile, generate object file\n"); fprintf(stderr, " -o, --output Specify output file\n"); } @@ -30,6 +31,7 @@ static int read_params(int argc, char * const *argv, params_t *params) { {"ir-dump", no_argument, 0, 6}, {"syntax-check-only", no_argument, 0, 4}, {"time-report", no_argument, 0, 5}, + {"strip-debug", no_argument, 0, 7}, {"output", required_argument, 0, 'o'}, {0, 0, 0, 0}, }; @@ -66,6 +68,9 @@ static int read_params(int argc, char * const *argv, params_t *params) { case 5: params->time_report = 1; break; + case 7: + params->strip_debug = 1; + break; default: fprintf(stderr, "Bad options: use %s -h to display help message\n", argv[0]); return -1; diff --git a/cc02/src/parser/parser.c b/cc02/src/parser/parser.c index 8eee5c6..9d6346f 100644 --- a/cc02/src/parser/parser.c +++ b/cc02/src/parser/parser.c @@ -824,6 +824,32 @@ static node_t *parse_stmt(parser_t *p) { return n; } + case Kw_break: { + node_t *n = alloc_node(p); + + n->kind = NODE_BREAK; + ++p->pos; + + EXPECT_SYMBOL(s_semicolon, ";", "after break statement."); + GUARD(p); + ++p->pos; + + return n; + } + + case Kw_continue: { + node_t *n = alloc_node(p); + + n->kind = NODE_CONTINUE; + ++p->pos; + + EXPECT_SYMBOL(s_semicolon, ";", "after continue statement."); + GUARD(p); + ++p->pos; + + return n; + } + case t_u8: case t_i8: case t_u16: case t_i16: case Kw_void: { node_t *n = ALLOC_NODE(p); diff --git a/cc02/src/parser/parser.h b/cc02/src/parser/parser.h index 2d97dc1..dd35a1f 100644 --- a/cc02/src/parser/parser.h +++ b/cc02/src/parser/parser.h @@ -54,6 +54,8 @@ typedef enum { NODE_WHILE, NODE_FOR, NODE_BLOCK, + NODE_BREAK, + NODE_CONTINUE, // top-level NODE_FUNCTION, diff --git a/cc02/src/parser/parser_print.c b/cc02/src/parser/parser_print.c index b317c8a..020ce69 100644 --- a/cc02/src/parser/parser_print.c +++ b/cc02/src/parser/parser_print.c @@ -79,6 +79,8 @@ static const char *node_kind_name(node_kind_t kind) { case NODE_STRUCT_INIT: return "StructInitExpr"; case NODE_FIELD_ACCESS: return "FieldAccess"; case NODE_PROGRAM: return "TranslationUnitDecl"; + case NODE_CONTINUE: return "ContinueStmt"; + case NODE_BREAK: return "BreakStmt"; default: return "Unknown"; } } @@ -139,21 +141,13 @@ static void print_ast_label(node_t *node) { print_type_suffix(node->var_decl.type); break; case NODE_ASSIGN: - printf("%s", node_kind_name(node->kind)); - break; case NODE_RETURN: - printf("%s", node_kind_name(node->kind)); - break; case NODE_IF: - printf("%s", node_kind_name(node->kind)); - break; case NODE_WHILE: - printf("%s", node_kind_name(node->kind)); - break; case NODE_FOR: - printf("%s", node_kind_name(node->kind)); - break; case NODE_BLOCK: + case NODE_CONTINUE: + case NODE_BREAK: printf("%s", node_kind_name(node->kind)); break; case NODE_FUNCTION: @@ -391,6 +385,10 @@ static void print_ast_(node_t *node, int is_last, const char *prefix) { case NODE_FWD_DECL: break; + case NODE_BREAK: + case NODE_CONTINUE: + break; + case NODE_PROGRAM: print_ast_list(node->program, child_prefix); break; diff --git a/cc02/tests/analyzer_bad_shadow.c02 b/cc02/tests/analyzer_bad_shadow.c02 new file mode 100644 index 0000000..86105dd --- /dev/null +++ b/cc02/tests/analyzer_bad_shadow.c02 @@ -0,0 +1,11 @@ +// Shadowing an outer scope's variable is rejected - see declare_local_variable() +// in analyzer.c for why (codegen keys variables by bare name, so two "x"s in +// nested scopes would alias the same storage). + +fn main() -> void { + u8 x = 1; + + if (x > 0) { + u8 x = 2; + } +} diff --git a/cc02/tests/analyzer_scoping.c02 b/cc02/tests/analyzer_scoping.c02 index 641ff0d..e35281b 100644 --- a/cc02/tests/analyzer_scoping.c02 +++ b/cc02/tests/analyzer_scoping.c02 @@ -1,15 +1,9 @@ -// Exercises scope behavior: shadowing, for-loop scoping, block scoping +// Exercises scope behavior: for-loop scoping, block scoping, and legal +// same-name reuse across scopes that never overlap on the scope stack. +// Shadowing a still-live outer scope is rejected - see analyzer_bad_shadow.c02. fn main() -> void { u8 x = 1; - - // shadowing in an if block is allowed - if (x > 0) { - u8 x = 2; - u8 y = x; - } - - // x is still the outer u8 after the block u8 a = x; // for-loop variable stays scoped to the loop diff --git a/cc02/tests/emu_binop_widen.c02 b/cc02/tests/emu_binop_widen.c02 new file mode 100644 index 0000000..44d5091 --- /dev/null +++ b/cc02/tests/emu_binop_widen.c02 @@ -0,0 +1,8 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + u8 a = 1; + u16 b = 500; + u16 c = a + b; + PORTB = (u8)(c >> 8); +} diff --git a/cc02/tests/emu_break_loop.c02 b/cc02/tests/emu_break_loop.c02 new file mode 100644 index 0000000..90bef1b --- /dev/null +++ b/cc02/tests/emu_break_loop.c02 @@ -0,0 +1,12 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + u8 i = 0; + while (true) { + ++i; + if (i == 5) { + break; + } + } + PORTB = i; +} diff --git a/cc02/tests/emu_cmp_mixed_sign.c02 b/cc02/tests/emu_cmp_mixed_sign.c02 new file mode 100644 index 0000000..149462a --- /dev/null +++ b/cc02/tests/emu_cmp_mixed_sign.c02 @@ -0,0 +1,11 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + i8 a = -1; + u8 b = 100; + u8 result = 1; + if (a < b) { + result = 0; + } + PORTB = result; +} diff --git a/cc02/tests/emu_continue_for.c02 b/cc02/tests/emu_continue_for.c02 new file mode 100644 index 0000000..8bd6771 --- /dev/null +++ b/cc02/tests/emu_continue_for.c02 @@ -0,0 +1,12 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + u8 sum = 0; + for (u8 i = 0; i < 10; ++i) { + if (i % 2 != 0) { + continue; + } + sum = sum + i; + } + PORTB = sum; +} diff --git a/cc02/tests/emu_continue_while.c02 b/cc02/tests/emu_continue_while.c02 new file mode 100644 index 0000000..1fdeb0a --- /dev/null +++ b/cc02/tests/emu_continue_while.c02 @@ -0,0 +1,14 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + u8 i = 0; + u8 sum = 0; + while (i < 10) { + ++i; + if (i % 2 != 0) { + continue; + } + sum = sum + i; + } + PORTB = sum; +} diff --git a/cc02/tests/emu_div_i8.c02 b/cc02/tests/emu_div_i8.c02 new file mode 100644 index 0000000..7445a38 --- /dev/null +++ b/cc02/tests/emu_div_i8.c02 @@ -0,0 +1,8 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + i8 a = -6; + i8 b = 2; + i8 c = a / b; + PORTB = c; +} diff --git a/cc02/tests/emu_div_u16.c02 b/cc02/tests/emu_div_u16.c02 new file mode 100644 index 0000000..a74ca76 --- /dev/null +++ b/cc02/tests/emu_div_u16.c02 @@ -0,0 +1,9 @@ +reg u16 QUOT @ 0x6000; +reg u16 REM @ 0x6002; + +fn main() -> void { + u16 a = 50000; + u16 b = 0xC001; + QUOT = a / b; + REM = a % b; +} diff --git a/cc02/tests/emu_global_ram_top.c02 b/cc02/tests/emu_global_ram_top.c02 new file mode 100644 index 0000000..44bdbfb --- /dev/null +++ b/cc02/tests/emu_global_ram_top.c02 @@ -0,0 +1,7 @@ +reg u16 PORT16 @ 0x6000; + +u8 x = 42; + +fn main() -> void { + PORT16 = __heap_start; +} diff --git a/cc02/tests/emu_local_str.c02 b/cc02/tests/emu_local_str.c02 new file mode 100644 index 0000000..ff8e4f5 --- /dev/null +++ b/cc02/tests/emu_local_str.c02 @@ -0,0 +1,8 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + u8 *msg = "Hi!"; + for (u8 *p = msg; *p != null; ++p) { + PORTB = *p; + } +} diff --git a/cc02/tests/emu_mod_i8.c02 b/cc02/tests/emu_mod_i8.c02 new file mode 100644 index 0000000..fad4fae --- /dev/null +++ b/cc02/tests/emu_mod_i8.c02 @@ -0,0 +1,8 @@ +reg u8 PORTB @ 0x6000; + +fn main() -> void { + i8 a = -7; + i8 b = 2; + i8 c = a % b; + PORTB = c; +} diff --git a/cc02/tests/emu_mul_u16.c02 b/cc02/tests/emu_mul_u16.c02 new file mode 100644 index 0000000..66d498f --- /dev/null +++ b/cc02/tests/emu_mul_u16.c02 @@ -0,0 +1,7 @@ +reg u16 PORT16 @ 0x6000; + +fn main() -> void { + u16 a = 300; + u16 b = 13; + PORT16 = a * b; +} diff --git a/cc02/tests/emu_mul_u16_wrap.c02 b/cc02/tests/emu_mul_u16_wrap.c02 new file mode 100644 index 0000000..37ae5ff --- /dev/null +++ b/cc02/tests/emu_mul_u16_wrap.c02 @@ -0,0 +1,7 @@ +reg u16 PORT16 @ 0x6000; + +fn main() -> void { + u16 a = 256; + u16 b = 256; + PORT16 = a * b; +} diff --git a/cc02/tests/emu_test.py b/cc02/tests/emu_test.py index 6b7ef52..16c012a 100644 --- a/cc02/tests/emu_test.py +++ b/cc02/tests/emu_test.py @@ -43,6 +43,7 @@ def load_and_run(bin_path, max_cycles=50000): mpu = MPU65C02() with open(bin_path, "rb") as f: rom = f.read() + rom = rom[:ROM_SIZE] # strip symbol table if present for i, byte in enumerate(rom): mpu.memory[ROM_START + i] = byte mpu.pc = mpu.memory[0xFFFC] | (mpu.memory[0xFFFD] << 8) @@ -282,6 +283,54 @@ def test_string_deref(): return True, None +def test_local_str(): + """Local string pointer (u8 *msg = \"Hi!\"), loop writes chars to PORTB.""" + source = os.path.join(SCRIPT_DIR, "emu_local_str.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != ord('!'): + return False, f"memory[$6000] = ${val:02X}, expected ${ord('!'):02X} ('!')" + return True, None + + +def test_break_loop(): + """`break` inside a `while (true)` exits after the 5th iteration, i == 5.""" + source = os.path.join(SCRIPT_DIR, "emu_break_loop.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 5: + return False, f"memory[$6000] = {val}, expected 5" + return True, None + + +def test_continue_while(): + """`continue` inside `while` skips odd i; sum of evens 2+4+...+10 = 30.""" + source = os.path.join(SCRIPT_DIR, "emu_continue_while.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 30: + return False, f"memory[$6000] = {val}, expected 30" + return True, None + + +def test_continue_for(): + """`continue` inside `for` still runs the incrementer; sum of evens 0+2+...+8 = 20.""" + source = os.path.join(SCRIPT_DIR, "emu_continue_for.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 20: + return False, f"memory[$6000] = {val}, expected 20" + return True, None + + test_cmp_u16_lt = _test_cmp("emu_cmp_u16_lt.c02", "u16 255 < 256 (high-byte-first proves LT)") test_cmp_u16_eq = _test_cmp("emu_cmp_u16_eq.c02", "u16 500 == 500 (both bytes must match)") test_cmp_u16_gt = _test_cmp("emu_cmp_u16_gt.c02", "u16 1000 > 255 (high-byte difference)") @@ -621,6 +670,109 @@ def test_mod_u8(): return True, None +def test_div_i8(): + """-6 / 2 == -3 (0xFD). Exercises quotient sign-fix path in __sdiv8.""" + source = os.path.join(SCRIPT_DIR, "emu_div_i8.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 0xFD: + return False, f"memory[$6000] = ${val:02X}, expected $FD (-3)" + return True, None + + +def test_mod_i8(): + """-7 % 2 == -1 (0xFF). Exercises remainder sign-fix path in __sdiv8.""" + source = os.path.join(SCRIPT_DIR, "emu_mod_i8.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 0xFF: + return False, f"memory[$6000] = ${val:02X}, expected $FF (-1)" + return True, None + + +def test_mul_u16(): + """300 * 13 == 3900 (0x0F3C): tests 16-bit shift-and-add with carry propagation.""" + source = os.path.join(SCRIPT_DIR, "emu_mul_u16.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + lo = mpu.memory[0x6000] + hi = mpu.memory[0x6001] + val = lo | (hi << 8) + if val != 3900: + return False, f"memory[$6000:$6001] = {val:#06x}, expected {3900:#06x}" + return True, None + + +def test_mul_u16_wrap(): + """256 * 256 == 0 (overflow wraps to low 16 bits).""" + source = os.path.join(SCRIPT_DIR, "emu_mul_u16_wrap.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + lo = mpu.memory[0x6000] + hi = mpu.memory[0x6001] + val = lo | (hi << 8) + if val != 0: + return False, f"memory[$6000:$6001] = {val:#06x}, expected 0x0000" + return True, None + + +def test_div_u16(): + """50000 / 0xC001 == 1, 50000 % 0xC001 == 847: tests high-bit divisor path.""" + source = os.path.join(SCRIPT_DIR, "emu_div_u16.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + quot = mpu.memory[0x6000] | (mpu.memory[0x6001] << 8) + rem = mpu.memory[0x6002] | (mpu.memory[0x6003] << 8) + if quot != 1: + return False, f"quotient = {quot:#06x}, expected 0x0001" + if rem != 847: + return False, f"remainder = {rem:#06x}, expected {847:#06x}" + return True, None + + +def test_global_ram_top(): + """__heap_start == $0205: u8 x@$0200, __heap_start@$0201, __memory_top@$0203, heap starts at $0205.""" + source = os.path.join(SCRIPT_DIR, "emu_global_ram_top.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] | (mpu.memory[0x6001] << 8) + if val != 0x0205: + return False, f"__heap_start = {val:#06x}, expected 0x0205" + return True, None + + +def test_binop_widen(): + """u8(1) + u16(500): narrow operand widened, result is 501 = 0x01F5 (high byte = 1).""" + source = os.path.join(SCRIPT_DIR, "emu_binop_widen.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 1: + return False, f"memory[$6000] = ${val:02X}, expected $01 (high byte of 501)" + return True, None + + +def test_cmp_mixed_sign(): + """i8(-1) < u8(100): unsigned-wins normalization => -1 reinterpreted as 255, 255<100=false.""" + source = os.path.join(SCRIPT_DIR, "emu_cmp_mixed_sign.c02") + mpu, err = compile_and_run(source) + if mpu is None: + return False, f"compilation failed: {err}" + val = mpu.memory[0x6000] + if val != 1: + return False, f"memory[$6000] = ${val:02X}, expected $01 (branch NOT taken)" + return True, None + + def test_implicit_widen(): """u8 200 widened to u16: low byte=200, high byte=0 (not garbage).""" source = os.path.join(SCRIPT_DIR, "emu_implicit_widen.c02") @@ -810,6 +962,7 @@ def test_func_recursive(): ("cmp_neq", test_cmp_neq), ("cmp_lte", test_cmp_lte), ("string_deref", test_string_deref), + ("local_str", test_local_str), ("inc_global", test_inc_global), ("cmp_global", test_cmp_global), ("cmp_u16_lt", test_cmp_u16_lt), @@ -846,6 +999,14 @@ def test_func_recursive(): ("mul_u8", test_mul_u8), ("div_u8", test_div_u8), ("mod_u8", test_mod_u8), + ("div_i8", test_div_i8), + ("mod_i8", test_mod_i8), + ("mul_u16", test_mul_u16), + ("mul_u16_wrap", test_mul_u16_wrap), + ("div_u16", test_div_u16), + ("global_ram_top", test_global_ram_top), + ("binop_widen", test_binop_widen), + ("cmp_mixed_sign", test_cmp_mixed_sign), ("lcd_simplified", test_lcd_simplified), ("field_local", test_field_local), ("field_global", test_field_global), @@ -855,6 +1016,9 @@ def test_func_recursive(): ("func_call_u16", test_func_call_u16), ("func_clobber", test_func_clobber), ("func_recursive", test_func_recursive), + ("break_loop", test_break_loop), + ("continue_while", test_continue_while), + ("continue_for", test_continue_for), ] diff --git a/cc02/tests/golden/analyzer_bad_empty_translation_unit.c02.golden b/cc02/tests/golden/analyzer_bad_empty_translation_unit.c02.golden index 3c8ad41..066857b 100644 --- a/cc02/tests/golden/analyzer_bad_empty_translation_unit.c02.golden +++ b/cc02/tests/golden/analyzer_bad_empty_translation_unit.c02.golden @@ -1 +1,4 @@ -Error, empty translation unit +analyzer_bad_empty_translation_unit.c02: error: missing or improperly defined main function + +Semantic analysis failed with 1 errors. + \ No newline at end of file diff --git a/cc02/tests/golden/analyzer_bad_shadow.c02.golden b/cc02/tests/golden/analyzer_bad_shadow.c02.golden new file mode 100644 index 0000000..9948886 --- /dev/null +++ b/cc02/tests/golden/analyzer_bad_shadow.c02.golden @@ -0,0 +1,6 @@ +analyzer_bad_shadow.c02:9:5: error: declaration of 'x' shadows an outer-scope declaration (shadowing is not supported - rename one of them) +9 | u8 x = 2; + | ^~ + +Semantic analysis failed with 1 errors. + \ No newline at end of file diff --git a/cc02/tests/golden/analyzer_basic.c02.golden b/cc02/tests/golden/analyzer_basic.c02.golden index ef656b5..acfee6d 100644 --- a/cc02/tests/golden/analyzer_basic.c02.golden +++ b/cc02/tests/golden/analyzer_basic.c02.golden @@ -4,3 +4,5 @@ global_instance variable : test_struct global_symbol variable : u16 PORTB  variable : u8 @ 0x6000 + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/analyzer_fwd_decl.c02.golden b/cc02/tests/golden/analyzer_fwd_decl.c02.golden index 354a791..37cceac 100644 --- a/cc02/tests/golden/analyzer_fwd_decl.c02.golden +++ b/cc02/tests/golden/analyzer_fwd_decl.c02.golden @@ -1,3 +1,5 @@ send_byte  function (u8 b) -> void counter  variable : u8 main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/analyzer_negative_literals.c02.golden b/cc02/tests/golden/analyzer_negative_literals.c02.golden index a8a012e..6d04526 100644 --- a/cc02/tests/golden/analyzer_negative_literals.c02.golden +++ b/cc02/tests/golden/analyzer_negative_literals.c02.golden @@ -1 +1,3 @@ main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/analyzer_pointer_field.c02.golden b/cc02/tests/golden/analyzer_pointer_field.c02.golden index f6f80c1..8e94f92 100644 --- a/cc02/tests/golden/analyzer_pointer_field.c02.golden +++ b/cc02/tests/golden/analyzer_pointer_field.c02.golden @@ -1,2 +1,4 @@ main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 Point  struct { u8 x, u8 y } diff --git a/cc02/tests/golden/analyzer_pointers.c02.golden b/cc02/tests/golden/analyzer_pointers.c02.golden index a8a012e..6d04526 100644 --- a/cc02/tests/golden/analyzer_pointers.c02.golden +++ b/cc02/tests/golden/analyzer_pointers.c02.golden @@ -1 +1,3 @@ main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/analyzer_scoping.c02.golden b/cc02/tests/golden/analyzer_scoping.c02.golden index a8a012e..6d04526 100644 --- a/cc02/tests/golden/analyzer_scoping.c02.golden +++ b/cc02/tests/golden/analyzer_scoping.c02.golden @@ -1 +1,3 @@ main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/analyzer_widening.c02.golden b/cc02/tests/golden/analyzer_widening.c02.golden index 789ef12..8078cc9 100644 --- a/cc02/tests/golden/analyzer_widening.c02.golden +++ b/cc02/tests/golden/analyzer_widening.c02.golden @@ -1,3 +1,5 @@ Pair  struct { u8 a, u16 b } takes_u16  function (u16 x) -> void main  function () -> void + __memory_top variable : u16 + __heap_start variable : u16 diff --git a/cc02/tests/golden/emu_binop_widen.c02.golden b/cc02/tests/golden/emu_binop_widen.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_break_loop.c02.golden b/cc02/tests/golden/emu_break_loop.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_cmp_mixed_sign.c02.golden b/cc02/tests/golden/emu_cmp_mixed_sign.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_continue_for.c02.golden b/cc02/tests/golden/emu_continue_for.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_continue_while.c02.golden b/cc02/tests/golden/emu_continue_while.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_div_i8.c02.golden b/cc02/tests/golden/emu_div_i8.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_div_u16.c02.golden b/cc02/tests/golden/emu_div_u16.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_global_ram_top.c02.golden b/cc02/tests/golden/emu_global_ram_top.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_local_str.c02.golden b/cc02/tests/golden/emu_local_str.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_mod_i8.c02.golden b/cc02/tests/golden/emu_mod_i8.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_mul_u16.c02.golden b/cc02/tests/golden/emu_mul_u16.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/emu_mul_u16_wrap.c02.golden b/cc02/tests/golden/emu_mul_u16_wrap.c02.golden new file mode 100644 index 0000000..e69de29 diff --git a/cc02/tests/golden/ir_fwd_decl.c02.golden b/cc02/tests/golden/ir_fwd_decl.c02.golden index 0746868..e214a67 100644 --- a/cc02/tests/golden/ir_fwd_decl.c02.golden +++ b/cc02/tests/golden/ir_fwd_decl.c02.golden @@ -2,11 +2,14 @@ --- Externs --- decl fn send_byte(u8 b) -> void decl u8 counter + decl u16 __heap_start + decl u16 __memory_top --- Functions --- -fn main() -> void - block0: - t0 = call send_byte(42) - x = counter - return +fn main() -> void { +block0: + t0 = call send_byte(42) + x = counter + return +} diff --git a/cc02/tests/golden/ir_simple_conditional.c02.golden b/cc02/tests/golden/ir_simple_conditional.c02.golden index 77bbba7..92bf3a0 100644 --- a/cc02/tests/golden/ir_simple_conditional.c02.golden +++ b/cc02/tests/golden/ir_simple_conditional.c02.golden @@ -1,16 +1,22 @@ +--- Externs --- + decl u16 __heap_start + decl u16 __memory_top + --- Functions --- -fn main() -> void - block0: - y = 2 - i = 0 - L0: - t0 = i < 255 - t1 = !t0 - if t1 goto L1 - inc y - inc i - goto L0 - L1: - return +fn main() -> void { +block0: + y = 2 + i = 0 + L0: + t0 = i < 255 + t1 = !t0 + if t1 goto L1 + inc y + L2: + inc i + goto L0 + L1: + return +} diff --git a/cc02/tests/golden/parser_bitwise_ops.c02.golden b/cc02/tests/golden/parser_bitwise_ops.c02.golden index 3174e0d..06ccfd8 100644 --- a/cc02/tests/golden/parser_bitwise_ops.c02.golden +++ b/cc02/tests/golden/parser_bitwise_ops.c02.golden @@ -1,202 +1,204 @@ `- TranslationUnitDecl - `- FunctionDecl main() -> void - `- CompoundStmt - |- VarDecl a : u8 - | `- BinaryOperator & - | |- IntegerLiteral (5) - | `- IntegerLiteral (3) - |- VarDecl b : u8 - | `- BinaryOperator | - | |- IntegerLiteral (5) - | `- IntegerLiteral (2) - |- VarDecl c : u8 - | `- BinaryOperator ^ - | |- IntegerLiteral (5) - | `- IntegerLiteral (1) - |- VarDecl d : u8 - | `- UnaryOperator ~ - | `- IntegerLiteral (5) - |- VarDecl e : u8 - | `- BinaryOperator << - | |- IntegerLiteral (1) - | `- IntegerLiteral (2) - |- VarDecl f : u8 - | `- BinaryOperator >> - | |- IntegerLiteral (16) - | `- IntegerLiteral (2) - |- VarDecl g : u8 - | `- BinaryOperator | - | |- BinaryOperator | - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | `- IntegerLiteral (4) - |- VarDecl h : u8 - | `- BinaryOperator & - | |- BinaryOperator & - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | `- IntegerLiteral (4) - |- VarDecl i : u8 - | `- BinaryOperator ^ - | |- BinaryOperator ^ - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | `- IntegerLiteral (4) - |- VarDecl j : u8 - | `- BinaryOperator << - | |- BinaryOperator << - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (1) - | `- IntegerLiteral (1) - |- VarDecl k : u8 - | `- BinaryOperator >> - | |- BinaryOperator >> - | | |- IntegerLiteral (16) - | | `- IntegerLiteral (1) - | `- IntegerLiteral (1) - |- VarDecl l : u8 - | `- BinaryOperator | - | |- IntegerLiteral (1) - | `- BinaryOperator ^ - | |- BinaryOperator & - | | |- IntegerLiteral (2) - | | `- IntegerLiteral (3) - | `- IntegerLiteral (4) - |- VarDecl m : u8 - | `- BinaryOperator << - | |- IntegerLiteral (1) - | `- BinaryOperator + - | |- IntegerLiteral (2) - | `- IntegerLiteral (1) - |- VarDecl n : u8 - | `- BinaryOperator << - | |- BinaryOperator + - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | `- IntegerLiteral (1) - |- VarDecl o : u8 - | `- BinaryOperator < - | |- BinaryOperator << - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | `- IntegerLiteral (8) - |- VarDecl p : u8 - | `- BinaryOperator && - | |- BinaryOperator & - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (1) - | `- BinaryOperator | - | |- IntegerLiteral (2) - | `- IntegerLiteral (0) - |- VarDecl q : u8 - | `- BinaryOperator & - | |- BinaryOperator == - | | |- IntegerLiteral (1) - | | `- IntegerLiteral (1) - | `- IntegerLiteral (1) - |- VarDecl r : u8 - | `- UnaryOperator ~ - | `- UnaryOperator ~ - | `- IntegerLiteral (5) - |- VarDecl s : u8 - | `- UnaryOperator ~ - | `- BinaryOperator & - | |- IntegerLiteral (5) - | `- IntegerLiteral (3) - |- VarDecl t : u8 - | `- BinaryOperator | - | |- UnaryOperator ~ - | | `- IntegerLiteral (5) - | `- IntegerLiteral (2) - |- VarDecl x : u8 - | `- IntegerLiteral (10) - |- VarDecl y : u8 - | `- BinaryOperator & - | |- Identifier x - | `- IntegerLiteral (1) - |- VarDecl z : u8* - | `- UnaryOperator & - | `- Identifier x - |- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- BinaryOperator & - | | | |- Identifier x - | | | `- IntegerLiteral (1) - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator | - | | |- Identifier x - | | `- IntegerLiteral (1) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- BinaryOperator >> - | | | |- Identifier x - | | | `- IntegerLiteral (1) - | | `- IntegerLiteral (2) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator ^ - | | |- Identifier x - | | `- IntegerLiteral (255) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- UnaryOperator ~ - | `- Identifier x - |- WhileStmt - | |- [cond] - | | `- BinaryOperator != - | | |- BinaryOperator & - | | | |- Identifier x - | | | `- IntegerLiteral (15) - | | `- IntegerLiteral (0) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator >> - | |- Identifier x - | `- IntegerLiteral (1) - |- ForStmt - | |- [init] - | | `- VarDecl i : u8 - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier i - | | `- IntegerLiteral (8) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier i - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier i - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator | - | |- Identifier x - | `- BinaryOperator << - | |- IntegerLiteral (1) - | `- Identifier i - `- ReturnStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + | |- VarDecl a : u8 + | | `- BinaryOperator & + | | |- IntegerLiteral (5) + | | `- IntegerLiteral (3) + | |- VarDecl b : u8 + | | `- BinaryOperator | + | | |- IntegerLiteral (5) + | | `- IntegerLiteral (2) + | |- VarDecl c : u8 + | | `- BinaryOperator ^ + | | |- IntegerLiteral (5) + | | `- IntegerLiteral (1) + | |- VarDecl d : u8 + | | `- UnaryOperator ~ + | | `- IntegerLiteral (5) + | |- VarDecl e : u8 + | | `- BinaryOperator << + | | |- IntegerLiteral (1) + | | `- IntegerLiteral (2) + | |- VarDecl f : u8 + | | `- BinaryOperator >> + | | |- IntegerLiteral (16) + | | `- IntegerLiteral (2) + | |- VarDecl g : u8 + | | `- BinaryOperator | + | | |- BinaryOperator | + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | `- IntegerLiteral (4) + | |- VarDecl h : u8 + | | `- BinaryOperator & + | | |- BinaryOperator & + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | `- IntegerLiteral (4) + | |- VarDecl i : u8 + | | `- BinaryOperator ^ + | | |- BinaryOperator ^ + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | `- IntegerLiteral (4) + | |- VarDecl j : u8 + | | `- BinaryOperator << + | | |- BinaryOperator << + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (1) + | | `- IntegerLiteral (1) + | |- VarDecl k : u8 + | | `- BinaryOperator >> + | | |- BinaryOperator >> + | | | |- IntegerLiteral (16) + | | | `- IntegerLiteral (1) + | | `- IntegerLiteral (1) + | |- VarDecl l : u8 + | | `- BinaryOperator | + | | |- IntegerLiteral (1) + | | `- BinaryOperator ^ + | | |- BinaryOperator & + | | | |- IntegerLiteral (2) + | | | `- IntegerLiteral (3) + | | `- IntegerLiteral (4) + | |- VarDecl m : u8 + | | `- BinaryOperator << + | | |- IntegerLiteral (1) + | | `- BinaryOperator + + | | |- IntegerLiteral (2) + | | `- IntegerLiteral (1) + | |- VarDecl n : u8 + | | `- BinaryOperator << + | | |- BinaryOperator + + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | `- IntegerLiteral (1) + | |- VarDecl o : u8 + | | `- BinaryOperator < + | | |- BinaryOperator << + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | `- IntegerLiteral (8) + | |- VarDecl p : u8 + | | `- BinaryOperator && + | | |- BinaryOperator & + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (1) + | | `- BinaryOperator | + | | |- IntegerLiteral (2) + | | `- IntegerLiteral (0) + | |- VarDecl q : u8 + | | `- BinaryOperator & + | | |- BinaryOperator == + | | | |- IntegerLiteral (1) + | | | `- IntegerLiteral (1) + | | `- IntegerLiteral (1) + | |- VarDecl r : u8 + | | `- UnaryOperator ~ + | | `- UnaryOperator ~ + | | `- IntegerLiteral (5) + | |- VarDecl s : u8 + | | `- UnaryOperator ~ + | | `- BinaryOperator & + | | |- IntegerLiteral (5) + | | `- IntegerLiteral (3) + | |- VarDecl t : u8 + | | `- BinaryOperator | + | | |- UnaryOperator ~ + | | | `- IntegerLiteral (5) + | | `- IntegerLiteral (2) + | |- VarDecl x : u8 + | | `- IntegerLiteral (10) + | |- VarDecl y : u8 + | | `- BinaryOperator & + | | |- Identifier x + | | `- IntegerLiteral (1) + | |- VarDecl z : u8* + | | `- UnaryOperator & + | | `- Identifier x + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- BinaryOperator & + | | | | |- Identifier x + | | | | `- IntegerLiteral (1) + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator | + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- BinaryOperator >> + | | | | |- Identifier x + | | | | `- IntegerLiteral (1) + | | | `- IntegerLiteral (2) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator ^ + | | | |- Identifier x + | | | `- IntegerLiteral (255) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- UnaryOperator ~ + | | `- Identifier x + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator != + | | | |- BinaryOperator & + | | | | |- Identifier x + | | | | `- IntegerLiteral (15) + | | | `- IntegerLiteral (0) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator >> + | | |- Identifier x + | | `- IntegerLiteral (1) + | |- ForStmt + | | |- [init] + | | | `- VarDecl idx : u8 + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier idx + | | | `- IntegerLiteral (8) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier idx + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier idx + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator | + | | |- Identifier x + | | `- BinaryOperator << + | | |- IntegerLiteral (1) + | | `- Identifier idx + | `- ReturnStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_conditionals.c02.golden b/cc02/tests/golden/parser_conditionals.c02.golden index fb0b81e..1eb0a70 100644 --- a/cc02/tests/golden/parser_conditionals.c02.golden +++ b/cc02/tests/golden/parser_conditionals.c02.golden @@ -1,522 +1,524 @@ `- TranslationUnitDecl - `- FunctionDecl main() -> void - `- CompoundStmt - |- VarDecl x : u8 - | `- IntegerLiteral (10) - |- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier x - | | `- IntegerLiteral (0) - | `- [then body] - | |- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator - - | | |- Identifier x - | | `- IntegerLiteral (1) - |- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator - - | | |- Identifier x - | | `- IntegerLiteral (1) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- IntegerLiteral (0) - |- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (1) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (1) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (2) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- IntegerLiteral (3) - |- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (10) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (1) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (11) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (2) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (12) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (3) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (13) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- IntegerLiteral (99) - |- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (10) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (1) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (11) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (2) - | `- [then body] - | |- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (12) - |- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier x - | | `- IntegerLiteral (0) - | `- [then body] - | |- CompoundStmt - | | `- IfStmt - | | |- [cond] - | | | `- BinaryOperator > - | | | |- Identifier x - | | | `- IntegerLiteral (5) - | | |- [then body] - | | | `- CompoundStmt - | | | `- AssignStmt - | | | |- [target] - | | | | `- Identifier x - | | | `- [value] - | | | `- IntegerLiteral (100) - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (50) - |- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- WhileStmt - | | |- [cond] - | | | `- BinaryOperator > - | | | |- Identifier x - | | | `- IntegerLiteral (5) - | | `- [body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator - - | | |- Identifier x - | | `- IntegerLiteral (1) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (1) - | `- CompoundStmt - | `- IfStmt - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier x - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (0) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier x - | | `- IntegerLiteral (1) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (2) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- IntegerLiteral (3) - |- WhileStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier x - | | `- IntegerLiteral (0) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator - - | |- Identifier x - | `- IntegerLiteral (1) - |- WhileStmt - | |- [cond] - | | `- BinaryOperator != - | | |- Identifier x - | | `- IntegerLiteral (0) - |- WhileStmt - | |- [cond] - | | `- BinaryOperator == - | | |- BinaryOperator & - | | | |- Identifier x - | | | `- IntegerLiteral (1) - | | `- IntegerLiteral (0) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator + - | |- Identifier x - | `- IntegerLiteral (1) - |- ForStmt - | |- [init] - | | `- VarDecl i : u8 - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier i - | | `- IntegerLiteral (10) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier i - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier i - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator + - | |- Identifier x - | `- Identifier i - |- VarDecl j : u8 - | `- IntegerLiteral (0) - |- ForStmt - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier j - | | `- IntegerLiteral (10) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier j - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier j - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator + - | |- Identifier x - | `- Identifier j - |- ForStmt - | |- [init] - | | `- VarDecl k : u8 - | | `- IntegerLiteral (0) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier k - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier k - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- Identifier k - | | `- IntegerLiteral (10) - | `- [then body] - | |- CompoundStmt - | | `- ReturnStmt - |- ForStmt - | |- [init] - | | `- VarDecl m : u8 - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier m - | | `- IntegerLiteral (10) - | `- [body] - | `- CompoundStmt - | |- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier x - | | `- Identifier m - | `- AssignStmt - | |- [target] - | | `- Identifier m - | `- [value] - | `- BinaryOperator + - | |- Identifier m - | `- IntegerLiteral (1) - |- ForStmt - | `- [body] - | `- CompoundStmt - | |- IfStmt - | | |- [cond] - | | | `- BinaryOperator > - | | | |- Identifier x - | | | `- IntegerLiteral (1000) - | | `- [then body] - | | |- CompoundStmt - | | | `- ReturnStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator + - | |- Identifier x - | `- IntegerLiteral (1) - |- ForStmt - | |- [init] - | | `- VarDecl n : u8 - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier n - | | `- IntegerLiteral (10) - | `- [step] - | `- AssignStmt - | |- [target] - | | `- Identifier n - | `- [value] - | `- BinaryOperator + - | |- Identifier n - | `- IntegerLiteral (1) - |- ForStmt - | |- [init] - | | `- VarDecl p : u8 - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier p - | | `- IntegerLiteral (5) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier p - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier p - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier p - | | `- IntegerLiteral (0) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (0) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier p - | | `- IntegerLiteral (1) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- IntegerLiteral (1) - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- IntegerLiteral (2) - |- WhileStmt - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier x - | | `- IntegerLiteral (50) - | `- [body] - | `- CompoundStmt - | `- IfStmt - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier x - | | `- IntegerLiteral (10) - | |- [then body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier x - | | `- IntegerLiteral (5) - | `- IfStmt - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier x - | | `- IntegerLiteral (30) - | |- [then body] - | | `- CompoundStmt - | | `- ForStmt - | | |- [init] - | | | `- VarDecl q : u8 - | | | `- IntegerLiteral (0) - | | |- [cond] - | | | `- BinaryOperator < - | | | |- Identifier q - | | | `- IntegerLiteral (3) - | | |- [step] - | | | `- AssignStmt - | | | |- [target] - | | | | `- Identifier q - | | | `- [value] - | | | `- BinaryOperator + - | | | |- Identifier q - | | | `- IntegerLiteral (1) - | | `- [body] - | | `- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier x - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier x - | | `- Identifier q - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier x - | `- [value] - | `- BinaryOperator + - | |- Identifier x - | `- IntegerLiteral (1) - `- ReturnStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + | |- VarDecl x : u8 + | | `- IntegerLiteral (10) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | `- [then body] + | | |- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator - + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator - + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- IntegerLiteral (0) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (1) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (2) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- IntegerLiteral (3) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (10) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (11) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (2) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (12) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (3) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (13) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- IntegerLiteral (99) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (10) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (11) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (2) + | | `- [then body] + | | |- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (12) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | `- [then body] + | | |- CompoundStmt + | | | `- IfStmt + | | | |- [cond] + | | | | `- BinaryOperator > + | | | | |- Identifier x + | | | | `- IntegerLiteral (5) + | | | |- [then body] + | | | | `- CompoundStmt + | | | | `- AssignStmt + | | | | |- [target] + | | | | | `- Identifier x + | | | | `- [value] + | | | | `- IntegerLiteral (100) + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (50) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- WhileStmt + | | | |- [cond] + | | | | `- BinaryOperator > + | | | | |- Identifier x + | | | | `- IntegerLiteral (5) + | | | `- [body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator - + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (1) + | | `- CompoundStmt + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (0) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier x + | | | `- IntegerLiteral (1) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (2) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- IntegerLiteral (3) + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator - + | | |- Identifier x + | | `- IntegerLiteral (1) + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator != + | | | |- Identifier x + | | | `- IntegerLiteral (0) + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- BinaryOperator & + | | | | |- Identifier x + | | | | `- IntegerLiteral (1) + | | | `- IntegerLiteral (0) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier x + | | `- IntegerLiteral (1) + | |- ForStmt + | | |- [init] + | | | `- VarDecl i : u8 + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier i + | | | `- IntegerLiteral (10) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier i + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier i + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier x + | | `- Identifier i + | |- VarDecl j : u8 + | | `- IntegerLiteral (0) + | |- ForStmt + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier j + | | | `- IntegerLiteral (10) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier j + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier j + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier x + | | `- Identifier j + | |- ForStmt + | | |- [init] + | | | `- VarDecl k : u8 + | | | `- IntegerLiteral (0) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier k + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier k + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- Identifier k + | | | `- IntegerLiteral (10) + | | `- [then body] + | | |- CompoundStmt + | | | `- ReturnStmt + | |- ForStmt + | | |- [init] + | | | `- VarDecl m : u8 + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier m + | | | `- IntegerLiteral (10) + | | `- [body] + | | `- CompoundStmt + | | |- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier x + | | | `- Identifier m + | | `- AssignStmt + | | |- [target] + | | | `- Identifier m + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier m + | | `- IntegerLiteral (1) + | |- ForStmt + | | `- [body] + | | `- CompoundStmt + | | |- IfStmt + | | | |- [cond] + | | | | `- BinaryOperator > + | | | | |- Identifier x + | | | | `- IntegerLiteral (1000) + | | | `- [then body] + | | | |- CompoundStmt + | | | | `- ReturnStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier x + | | `- IntegerLiteral (1) + | |- ForStmt + | | |- [init] + | | | `- VarDecl n : u8 + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier n + | | | `- IntegerLiteral (10) + | | `- [step] + | | `- AssignStmt + | | |- [target] + | | | `- Identifier n + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier n + | | `- IntegerLiteral (1) + | |- ForStmt + | | |- [init] + | | | `- VarDecl p : u8 + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier p + | | | `- IntegerLiteral (5) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier p + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier p + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier p + | | | `- IntegerLiteral (0) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (0) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier p + | | | `- IntegerLiteral (1) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- IntegerLiteral (1) + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- IntegerLiteral (2) + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier x + | | | `- IntegerLiteral (50) + | | `- [body] + | | `- CompoundStmt + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier x + | | | `- IntegerLiteral (10) + | | |- [then body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier x + | | | `- IntegerLiteral (5) + | | `- IfStmt + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier x + | | | `- IntegerLiteral (30) + | | |- [then body] + | | | `- CompoundStmt + | | | `- ForStmt + | | | |- [init] + | | | | `- VarDecl q : u8 + | | | | `- IntegerLiteral (0) + | | | |- [cond] + | | | | `- BinaryOperator < + | | | | |- Identifier q + | | | | `- IntegerLiteral (3) + | | | |- [step] + | | | | `- AssignStmt + | | | | |- [target] + | | | | | `- Identifier q + | | | | `- [value] + | | | | `- BinaryOperator + + | | | | |- Identifier q + | | | | `- IntegerLiteral (1) + | | | `- [body] + | | | `- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier x + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier x + | | | `- Identifier q + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier x + | | `- [value] + | | `- BinaryOperator + + | | |- Identifier x + | | `- IntegerLiteral (1) + | `- ReturnStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_functions.c02.golden b/cc02/tests/golden/parser_functions.c02.golden index 13aac52..73a78ab 100644 --- a/cc02/tests/golden/parser_functions.c02.golden +++ b/cc02/tests/golden/parser_functions.c02.golden @@ -21,77 +21,79 @@ | `- ReturnStmt | `- UnaryOperator & | `- Identifier x - `- FunctionDecl main() -> void - `- CompoundStmt - |- CallExpr no_params - |- VarDecl val : u8 - | `- CallExpr one_param - | `- IntegerLiteral (5) - |- VarDecl result : u16 - | `- CallExpr multi_params - | |- IntegerLiteral (1) - | |- IntegerLiteral (2) - | |- IntegerLiteral (3) - | `- IntegerLiteral (4) - |- VarDecl sum : u8 - | `- BinaryOperator + - | |- CallExpr one_param - | | `- IntegerLiteral (1) - | `- CallExpr one_param - | `- IntegerLiteral (2) - |- VarDecl cmp : u8 - | `- BinaryOperator > - | |- CallExpr one_param - | | `- IntegerLiteral (5) - | `- IntegerLiteral (3) - |- VarDecl nested : u8 - | `- CallExpr one_param - | `- CallExpr returns_u8 - |- IfStmt - | |- [cond] - | | `- BinaryOperator > - | | |- CallExpr one_param - | | | `- IntegerLiteral (1) - | | `- IntegerLiteral (0) - | `- [then body] - | |- CompoundStmt - | | `- CallExpr no_params - |- WhileStmt - | |- [cond] - | | `- BinaryOperator > - | | |- CallExpr one_param - | | | `- Identifier val - | | `- IntegerLiteral (0) - | `- [body] - | `- CompoundStmt - | `- AssignStmt - | |- [target] - | | `- Identifier val - | `- [value] - | `- BinaryOperator - - | |- Identifier val - | `- IntegerLiteral (1) - |- ForStmt - | |- [init] - | | `- VarDecl i : u8 - | | `- CallExpr one_param - | | `- IntegerLiteral (0) - | |- [cond] - | | `- BinaryOperator < - | | |- Identifier i - | | `- CallExpr one_param - | | `- IntegerLiteral (10) - | |- [step] - | | `- AssignStmt - | | |- [target] - | | | `- Identifier i - | | `- [value] - | | `- BinaryOperator + - | | |- Identifier i - | | `- IntegerLiteral (1) - | `- [body] - | `- CompoundStmt - | `- CallExpr no_params - |- VarDecl p : u8* - | `- CallExpr returns_ptr - `- ReturnStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + | |- CallExpr no_params + | |- VarDecl val : u8 + | | `- CallExpr one_param + | | `- IntegerLiteral (5) + | |- VarDecl result : u16 + | | `- CallExpr multi_params + | | |- IntegerLiteral (1) + | | |- IntegerLiteral (2) + | | |- IntegerLiteral (3) + | | `- IntegerLiteral (4) + | |- VarDecl sum : u8 + | | `- BinaryOperator + + | | |- CallExpr one_param + | | | `- IntegerLiteral (1) + | | `- CallExpr one_param + | | `- IntegerLiteral (2) + | |- VarDecl cmp : u8 + | | `- BinaryOperator > + | | |- CallExpr one_param + | | | `- IntegerLiteral (5) + | | `- IntegerLiteral (3) + | |- VarDecl nested : u8 + | | `- CallExpr one_param + | | `- CallExpr returns_u8 + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- CallExpr one_param + | | | | `- IntegerLiteral (1) + | | | `- IntegerLiteral (0) + | | `- [then body] + | | |- CompoundStmt + | | | `- CallExpr no_params + | |- WhileStmt + | | |- [cond] + | | | `- BinaryOperator > + | | | |- CallExpr one_param + | | | | `- Identifier val + | | | `- IntegerLiteral (0) + | | `- [body] + | | `- CompoundStmt + | | `- AssignStmt + | | |- [target] + | | | `- Identifier val + | | `- [value] + | | `- BinaryOperator - + | | |- Identifier val + | | `- IntegerLiteral (1) + | |- ForStmt + | | |- [init] + | | | `- VarDecl i : u8 + | | | `- CallExpr one_param + | | | `- IntegerLiteral (0) + | | |- [cond] + | | | `- BinaryOperator < + | | | |- Identifier i + | | | `- CallExpr one_param + | | | `- IntegerLiteral (10) + | | |- [step] + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier i + | | | `- [value] + | | | `- BinaryOperator + + | | | |- Identifier i + | | | `- IntegerLiteral (1) + | | `- [body] + | | `- CompoundStmt + | | `- CallExpr no_params + | |- VarDecl p : u8* + | | `- CallExpr returns_ptr + | `- ReturnStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_fwd_decl.c02.golden b/cc02/tests/golden/parser_fwd_decl.c02.golden index d69b71c..f99b7f8 100644 --- a/cc02/tests/golden/parser_fwd_decl.c02.golden +++ b/cc02/tests/golden/parser_fwd_decl.c02.golden @@ -1,5 +1,7 @@ `- TranslationUnitDecl |- ForwardDecl fn send_byte(u8 b) -> void |- ForwardDecl counter : u8 - `- FunctionDecl main() -> void - `- CompoundStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_global_var.c02.golden b/cc02/tests/golden/parser_global_var.c02.golden index 01370f8..7613cfd 100644 --- a/cc02/tests/golden/parser_global_var.c02.golden +++ b/cc02/tests/golden/parser_global_var.c02.golden @@ -31,5 +31,7 @@ |- GlobalVarDecl dereffed : u16 | `- PtrDeref * | `- Identifier global_ptr - `- FunctionDecl main() -> void - `- CompoundStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_literals.c02.golden b/cc02/tests/golden/parser_literals.c02.golden index 11d3e97..a99518c 100644 --- a/cc02/tests/golden/parser_literals.c02.golden +++ b/cc02/tests/golden/parser_literals.c02.golden @@ -29,51 +29,53 @@ | `- StringLiteral "hello world" |- GlobalVarDecl empty : u8* | `- StringLiteral "" - `- FunctionDecl main() -> void - `- CompoundStmt - |- VarDecl a : u8 - | `- BinaryOperator & - | |- IntegerLiteral (255) - | `- IntegerLiteral (15) - |- VarDecl b : u8 - | `- BinaryOperator | - | |- IntegerLiteral (12) - | `- IntegerLiteral (3) - |- VarDecl c : u8 - | `- BinaryOperator << - | |- IntegerLiteral (1) - | `- IntegerLiteral (4) - |- VarDecl d : u16 - | `- BinaryOperator + - | |- IntegerLiteral (4096) - | `- IntegerLiteral (4095) - |- IfStmt - | |- [cond] - | | `- IntegerLiteral (1) - | `- [then body] - | |- CompoundStmt - | | `- VarDecl x : u8 - | | `- IntegerLiteral (1) - |- WhileStmt - | |- [cond] - | | `- IntegerLiteral (0) - | `- [body] - | `- CompoundStmt - | `- VarDecl y : u8 - | `- IntegerLiteral (0) - |- VarDecl ptr : u8* - | `- IntegerLiteral (0) - |- IfStmt - | |- [cond] - | | `- BinaryOperator == - | | |- Identifier ptr - | | `- IntegerLiteral (0) - | `- [then body] - | |- CompoundStmt - | | `- AssignStmt - | | |- [target] - | | | `- Identifier ptr - | | `- [value] - | | `- UnaryOperator & - | | `- Identifier a - `- ReturnStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + | |- VarDecl a : u8 + | | `- BinaryOperator & + | | |- IntegerLiteral (255) + | | `- IntegerLiteral (15) + | |- VarDecl b : u8 + | | `- BinaryOperator | + | | |- IntegerLiteral (12) + | | `- IntegerLiteral (3) + | |- VarDecl c : u8 + | | `- BinaryOperator << + | | |- IntegerLiteral (1) + | | `- IntegerLiteral (4) + | |- VarDecl d : u16 + | | `- BinaryOperator + + | | |- IntegerLiteral (4096) + | | `- IntegerLiteral (4095) + | |- IfStmt + | | |- [cond] + | | | `- IntegerLiteral (1) + | | `- [then body] + | | |- CompoundStmt + | | | `- VarDecl x : u8 + | | | `- IntegerLiteral (1) + | |- WhileStmt + | | |- [cond] + | | | `- IntegerLiteral (0) + | | `- [body] + | | `- CompoundStmt + | | `- VarDecl y : u8 + | | `- IntegerLiteral (0) + | |- VarDecl ptr : u8* + | | `- IntegerLiteral (0) + | |- IfStmt + | | |- [cond] + | | | `- BinaryOperator == + | | | |- Identifier ptr + | | | `- IntegerLiteral (0) + | | `- [then body] + | | |- CompoundStmt + | | | `- AssignStmt + | | | |- [target] + | | | | `- Identifier ptr + | | | `- [value] + | | | `- UnaryOperator & + | | | `- Identifier a + | `- ReturnStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_registers.c02.golden b/cc02/tests/golden/parser_registers.c02.golden index 35e572a..48cfb36 100644 --- a/cc02/tests/golden/parser_registers.c02.golden +++ b/cc02/tests/golden/parser_registers.c02.golden @@ -3,5 +3,7 @@ |- RegDecl PORTA : u8 @ 6001 |- RegDecl DDRB : u8 @ 6002 |- RegDecl DDRA : u8 @ 6003 - `- FunctionDecl main() -> void - `- CompoundStmt + |- FunctionDecl main() -> void + | `- CompoundStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_struct_decl.c02.golden b/cc02/tests/golden/parser_struct_decl.c02.golden index 98eec9f..31cd2af 100644 --- a/cc02/tests/golden/parser_struct_decl.c02.golden +++ b/cc02/tests/golden/parser_struct_decl.c02.golden @@ -316,6 +316,8 @@ | | `- [value] | | `- IntegerLiteral (42) | `- ReturnStmt - `- FunctionDecl someFunc(u8 v, u16 r) -> void - `- CompoundStmt - `- ReturnStmt + |- FunctionDecl someFunc(u8 v, u16 r) -> void + | `- CompoundStmt + | `- ReturnStmt + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/golden/parser_struct_semicolon.c02.golden b/cc02/tests/golden/parser_struct_semicolon.c02.golden index 29fee22..639e881 100644 --- a/cc02/tests/golden/parser_struct_semicolon.c02.golden +++ b/cc02/tests/golden/parser_struct_semicolon.c02.golden @@ -1,19 +1,21 @@ `- TranslationUnitDecl |- StructDecl WithSemi {u8 a, u16 b} |- StructDecl WithoutSemi {u8 c} - `- FunctionDecl main() -> void - `- CompoundStmt - |- StructDecl LocalSemi {u8 d} - |- VarDecl w : WithSemi - |- AssignStmt - | |- [target] - | | `- FieldAccess .a - | | `- Identifier w - | `- [value] - | `- IntegerLiteral (1) - `- AssignStmt - |- [target] - | `- FieldAccess .b - | `- Identifier w - `- [value] - `- IntegerLiteral (2) + |- FunctionDecl main() -> void + | `- CompoundStmt + | |- StructDecl LocalSemi {u8 d} + | |- VarDecl w : WithSemi + | |- AssignStmt + | | |- [target] + | | | `- FieldAccess .a + | | | `- Identifier w + | | `- [value] + | | `- IntegerLiteral (1) + | `- AssignStmt + | |- [target] + | | `- FieldAccess .b + | | `- Identifier w + | `- [value] + | `- IntegerLiteral (2) + |- ForwardDecl __heap_start : u16 + `- ForwardDecl __memory_top : u16 diff --git a/cc02/tests/parser_bitwise_ops.c02 b/cc02/tests/parser_bitwise_ops.c02 index 648aca3..df8fe9c 100644 --- a/cc02/tests/parser_bitwise_ops.c02 +++ b/cc02/tests/parser_bitwise_ops.c02 @@ -53,8 +53,8 @@ fn main() -> void { x = x >> 1; } - for (u8 i = 0; i < 8; i = i + 1) { - x = x | (1 << i); + for (u8 idx = 0; idx < 8; idx = idx + 1) { + x = x | (1 << idx); } return; diff --git a/docs/roadmap.md b/docs/roadmap.md index bf7f470..6cde86d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -64,10 +64,10 @@ should compile to correct code. load the ZP address as a constant. For globals: load the RAM address. - [x] **Type casts / implicit widening** — `u8` → `u16` must zero-extend the high byte. Currently reads garbage. -- [ ] Variables holding string have to be initialized as globals, this needs fixing +- [x] Variables holding string have to be initialized as globals, this needs fixing - [x] `*(ptr + i)` gives analyzer error, complaining that there is a type mismatch because ptr is a ptr and i is a normal integer value -- [ ] Language currently does not support `break` or `continue` keywords +- [x] Language currently does not support `break` or `continue` keywords #### Should-have (language is painful without these) diff --git a/examples/lcd_hello_world.c02 b/examples/lcd_hello_world.c02 index 58c2974..a43700e 100644 --- a/examples/lcd_hello_world.c02 +++ b/examples/lcd_hello_world.c02 @@ -4,11 +4,10 @@ reg u8 DDRB @ 0x6002; reg u8 DDRA @ 0x6003; /* - PORTB = LCD data lines - PORTA = top 3 bits for control lines, PA7 = enable, PA6 = read/write, PA6 = register select +PORTB = LCD data lines +PORTA = top 3 bits for control lines, PA7 = enable, PA6 = read/write, PA6 = register select */ -u8 *msg = "Hello C02!"; fn lcd_send_command(u8 cmd) -> void { PORTB = cmd; // Put command on data lines @@ -24,7 +23,7 @@ fn lcd_init() -> void { // Clear the ports to start with a known state PORTB = 0; // Clear PORTB PORTA = 0; // Clear PORTA - + // Initialize the LCD (following a typical initialization sequence) lcd_send_command(0x38); // Function set: 8-bit, 2 lines, 5x8 dots lcd_send_command(0x0C); // Display on, cursor off @@ -39,8 +38,9 @@ fn lcd_putc(u8 ch) -> void { } fn main() -> void { + u8 *msg = "Hello C02!"; lcd_init(); - + for (u8 *p = msg; *p != null; ++p) { lcd_putc(*p); } diff --git a/examples/lcd_hello_world_simplified.c02 b/examples/lcd_hello_world_simplified.c02 index d977c8f..5096f67 100644 --- a/examples/lcd_hello_world_simplified.c02 +++ b/examples/lcd_hello_world_simplified.c02 @@ -7,9 +7,9 @@ reg u8 DDRA @ 0x6003; PORTB = LCD data lines PORTA = top 3 bits for control lines, PA7 = enable, PA6 = read/write, PA6 = register select */ -u8 *msg = "Hello C02!"; fn main() -> void { + u8 *msg = "Hello C02!"; // Set the data direction registers for PORTA and PORTB to output DDRB = 0xFF; // Set all pins of PORTB as output DDRA = 0xE0; // Set top 3 bits of PORTA as output (for RS, RW, E)