{"id":51863190,"url":"https://github.com/ncw/ccforth","last_synced_at":"2026-07-24T11:30:25.160Z","repository":{"id":369132699,"uuid":"1185531983","full_name":"ncw/ccforth","owner":"ncw","description":"ccforth is a mostly Gforth compatible Forth 2012 compliant Forth-to-C compiler written in Go","archived":false,"fork":false,"pushed_at":"2026-06-02T10:01:55.000Z","size":1026,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-07-11T21:00:07.356Z","etag":null,"topics":["compiler","forth","gforth"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ncw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["ncw"],"patreon":"njcw","liberapay":"ncw","custom":["https://rclone.org/donate/"]}},"created_at":"2026-03-18T17:21:46.000Z","updated_at":"2026-06-02T10:05:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ncw/ccforth","commit_stats":null,"previous_names":["ncw/ccforth"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/ncw/ccforth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncw%2Fccforth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncw%2Fccforth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncw%2Fccforth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncw%2Fccforth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ncw","download_url":"https://codeload.github.com/ncw/ccforth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncw%2Fccforth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35841138,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","forth","gforth"],"created_at":"2026-07-24T11:30:20.880Z","updated_at":"2026-07-24T11:30:25.134Z","avatar_url":"https://github.com/ncw.png","language":"Go","funding_links":["https://github.com/sponsors/ncw","https://patreon.com/njcw","https://liberapay.com/ncw","https://rclone.org/donate/"],"categories":[],"sub_categories":[],"readme":"# ccforth\n\nccforth is a Forth to C compiler written in Go. It interprets\ncompile-time Forth (immediate words, meta-programming) and emits\nflattened C11 code that is compiled with gcc or clang to produce\nstandalone executables.\n\n- GitHub: https://github.com/ncw/ccforth\n\nccforth was written to explore the idea of having an interpreter and a\ncompiler running simultaneously to enable meta-programming heavy Forth\nprograms to be compiled into C. This is harder than it sounds because\nForth cannot be ahead-of-time compiled by parsing alone; programs\nroutinely execute code at compile time. The project was also an\nexperiment in how far an AI agent could progress a compiler project -\nmost of the initial implementation was done by Claude Opus 4.6. Go was\nused as the implementation language as it is low enough level to\nexpress Forth properly but easier to write than C. C was used as a\ncompiler backend as C compilers optimize well and are available\neverywhere.\n\n## Features\n\n- **Fast compiled output** — generates optimised C11, compiled with\n  gcc/clang at `-O3`. Typically 5–373x faster than Gforth on\n  [benchmarks](#benchmarks).\n- **Single binary, zero dependencies** — download one executable and\n  go. No runtime library, no install step, no Forth image files.\n- **Self-contained executables** — compiled programs are standalone\n  native binaries with no runtime dependencies (the C runtime is\n  embedded).\n- **Interactive REPL** — `ccforth -i` gives you a full interpreter\n  with tab completion and line editing for prototyping and debugging.\n- **Forth 2012 standard** — passes the official Forth 2012\n  [test suite](#forth-2012-test-suite) with 0 errors across all\n  implemented word sets.\n- **Gforth compatible** — implements `{ }` locals, `SLURP-FILE`,\n  `S+`, `SYSTEM`, `ARGC`/`ARG`, and other\n  [Gforth extensions](#gforth-compatibility) so existing code ports\n  easily.\n- **Inline C escape hatch** — `CODE` / `;CODE` lets you drop into raw\n  C for performance-critical words or direct library calls.\n- **Cross-platform** — runs on Linux, macOS, and Windows.\n- **32-bit and 64-bit** — 64-bit cells by default; the [`-32`\n  flag](#32-bit-mode) generates 32-bit cell code for cross-compiling\n  to ARM32, embedded targets, or 32-bit x86 (via `-Cflags \"-m32\"`).\n- **Readable generated C** — `ccforth -c` emits human-readable C with\n  `#line` directives mapping back to your `.fth` source, so you can\n  debug with gdb.\n- **Floating-point** — full Forth 2012 FLOATING and FLOATING EXT word\n  sets including transcendentals.\n- **Exception handling** — `CATCH` / `THROW` and the `TRY` /\n  `ENDTRY` extension, compiled via `setjmp` / `longjmp`.\n- **Locals** — `{: ... :}` and Gforth `{ ... }` syntax with typed\n  specifiers, compiled to C local variables.\n\n## How it works\n\n1. **`.fth` source** is read by the **Scanner** (lexer), which\n   tokenises it.\n2. The **Interpreter** executes immediate words and metaprogramming,\n   building a dictionary of word bodies.\n3. The **C emitter** walks the finished dictionary and generates\n   flattened C from the word bodies.\n4. The result is a self-contained **`output.c`** with the runtime\n   inlined.\n5. **gcc / clang** compiles that C11 source into a **standalone\n   executable**.\n\nForth source is processed by the **interpreter**, which executes\nimmediate words (like `IF`, `DO`, `:`, `;`) and allows the normal\nmetaprogramming tricks Forth programs do. These build up an\nintermediate representation — a list of body items containing word\nreferences, literals, and branch instructions — stored in the\n**dictionary**. The **C emitter** then walks each colon definition's\nbody and produces inlined C code using `goto`-based control flow. A\nsmall **C runtime** is embedded into the `ccforth` binary and inlined\nat the top of every generated C file, providing the data stack, return\nstack, floating-point stack, and file table. This means `ccforth -c`\noutput is fully self-contained — you can compile it with just `cc\n-std=c11 -O3 -march=native -flto -fwrapv -fno-strict-aliasing -lm\noutput.c` (`-fwrapv` and `-fno-strict-aliasing` are required for\ncorrectness).\n\n### Why a full interpreter?\n\nA Forth compiler cannot just parse — it must *execute*. Forth\nprograms routinely run code at compile time: immediate words like `IF`\nand `DO` build control-flow structures by manipulating the compiler\nstate, `CREATE`/`DOES\u003e` defines new defining words, `[CHAR]` and `[']`\nevaluate at compile time and embed the result as a literal, and\n`[IF]`/`[THEN]` conditionally includes or excludes source code.\nPrograms use these facilities to build lookup tables, define new\ncontrol structures (e.g. `CASE`/`ENDCASE`), compute constants, and\ngenerate families of words from templates — all before the program\n\"runs\". A static compiler that only translates syntax would miss all\nof this. ccforth solves this by running a full Forth interpreter first:\nevery line of source is executed, immediate words fire, the dictionary\nand memory fill up with the results, and only then does the C emitter\nwalk the finished dictionary to produce output. The interpreter *is*\nthe compiler's front end.\n\n### Key design choices\n\n- **64-bit cells** (`int64_t`) by default — a cell must be big enough\n  to hold a pointer and most platforms are 64-bit. A `-32` flag\n  generates 32-bit cell code for embedded and cross-compilation targets.\n- **Parameterised C output** — words with statically known stack\n  effects are emitted as proper C functions with parameters and return\n  values (e.g. `cell_t word_FACT(cell_t n)`), so the C compiler can\n  register-allocate stack values. Words that cannot be analysed fall\n  back to `void(void)` functions with global stack operations. Both\n  forms are `static inline`, letting the C compiler optimise across\n  word boundaries.\n- **`goto`-based control flow** — direct translation of Forth branch\n  instructions. No structured `if`/`while` reconstruction is performed.\n- **Real-pointer memory model** — a single `unsigned char[]` array\n  holds the Forth memory image (variables, strings, allotted data),\n  initialised from a snapshot taken after interpretation. All\n  addresses are real C pointers, so `@` and `!` work uniformly on both\n  dictionary resident data and `malloc`'d buffers from `ALLOCATE`.\n- **Peephole optimiser** — operates on the body before C emission.\n  Folds constants, eliminates identity operations and redundant stack\n  shuffles. See the [Performance](#performance) section for details.\n- **`#line` directives** — generated C includes `#line` directives\n  that map back to the original `.fth` source files with absolute\n  paths. This means gdb can be used to debug crashes, though you'll\n  want to pass `-Cflags -g`. Use `-no-line-directives` to disable this\n  (useful when debugging the compiler itself).\n- **Bootstrap from Forth** — `core.fth` defines many standard words in\n  Forth itself, as is traditional, and provides `CODE` words for I/O,\n  file access, and diagnostics, keeping both the Go interpreter and\n  the C runtime minimal. It is embedded in the binary for\n  distribution.\n\n## Supported Forth words\n\nFor detailed per-word documentation including stack effects, see [WORDS.md](WORDS.md).\n\n**Stack:** `DUP` `DROP` `SWAP` `OVER` `ROT` `-ROT` `NIP` `TUCK`\n`?DUP` `PICK` `DEPTH` `2DUP` `2DROP` `2SWAP` `2OVER`\n\n**Arithmetic:** `+` `-` `*` `/` `MOD` `/MOD` `*/` `*/MOD` `NEGATE`\n`ABS` `MIN` `MAX` `1+` `1-` `2*` `2/`\n\n**Double-cell arithmetic:** `D+` `D-` `DNEGATE` `DABS` `D2*` `D2/`\n`D\u003c` `D0\u003c` `D0=` `D=` `D\u003eS` `DMAX` `DMIN` `M+` `M*` `M*/` `S\u003eD`\n`UM*` `UM/MOD` `SM/REM` `FM/MOD`\n\n**Bitwise:** `AND` `OR` `XOR` `INVERT` `LSHIFT` `RSHIFT`\n\n**Comparison:** `=` `\u003c\u003e` `\u003c` `\u003e` `\u003c=` `\u003e=` `0=` `0\u003c` `0\u003e` `0\u003c\u003e`\n`U\u003c` `U\u003e` `TRUE` `FALSE`\n\n**Memory:** `@` `!` `C@` `C!` `+!` `2@` `2!` `CELL+` `CELLS`\n`CHAR+` `CHARS` `ALIGN` `ALIGNED` `ALLOT` `FILL` `ERASE` `MOVE`\n`CMOVE` `CMOVE\u003e` `PAD` `UNUSED`\n\n**I/O:** `.` `U.` `U.R` `D.` `D.R` `.R` `CR` `EMIT` `SPACE` `SPACES`\n`TYPE` `.\"` `.(` `KEY` `ACCEPT` `SOURCE` `\u003eIN` `WORD` `PARSE`\n`PARSE-NAME` `REFILL`\n\n**Pictured numeric output:** `\u003c#` `#` `#S` `#\u003e` `HOLD` `SIGN` `BASE`\n`DECIMAL`\n\n**Return stack:** `\u003eR` `R\u003e` `R@` `2\u003eR` `2R\u003e` `2R@` `N\u003eR` `NR\u003e`\n\n**Control flow:** `IF` `ELSE` `THEN` `DO` `?DO` `LOOP` `+LOOP` `I` `J`\n`LEAVE` `UNLOOP` `BEGIN` `AGAIN` `UNTIL` `WHILE` `REPEAT` `RECURSE`\n`EXIT` `CASE` `OF` `ENDOF` `ENDCASE` `AHEAD` `?DUP-IF` `?DUP-0=-IF`\n\n**Strings:** `S\"` `S\\\"` `C\"` `COUNT` `CHAR` `[CHAR]` `COMPARE`\n`SEARCH` `SLITERAL` `/STRING` `-TRAILING` `BLANK` `S+`\n\n**Defining words:** `:` `;` `:NONAME` `CONSTANT` `VARIABLE` `VALUE`\n`FVALUE` `2CONSTANT` `2VARIABLE` `2VALUE` `CREATE` `ALLOT` `,` `C,`\n`DOES\u003e` `IMMEDIATE` `DEFER` `DEFER!` `DEFER@` `IS` `ACTION-OF`\n`BUFFER:` `MARKER` `[:` `;]`\n\n**Compilation:** `STATE` `[` `]` `LITERAL` `2LITERAL` `POSTPONE` `]]` `[[` `'`\n`[']` `EXECUTE` `COMPILE,` `HERE` `FIND` `\u003eBODY` `\u003eNUMBER` `TO`\n`SAVE-INPUT` `RESTORE-INPUT`\n\n**Exception handling:** `CATCH` `THROW` `ABORT` `ABORT\"` `TRY` `IFERROR`\n`RESTORE` `ENDTRY`\n\n**File access:** `OPEN-FILE` `CREATE-FILE` `CLOSE-FILE` `READ-FILE`\n`WRITE-FILE` `DELETE-FILE` `FILE-POSITION` `FILE-SIZE`\n`REPOSITION-FILE` `RESIZE-FILE` `READ-LINE` `WRITE-LINE` `SOURCE-ID`\n`INCLUDE-FILE` `INCLUDED` `INCLUDE` `REQUIRED` `REQUIRE`\n`RENAME-FILE` `FILE-STATUS` `FLUSH-FILE` `R/O` `W/O` `R/W` `BIN`\n`SLURP-FID` `SLURP-FILE`\n\n**Floating-point:** `F+` `F-` `F*` `F/` `FNEGATE` `FABS` `FMAX`\n`FMIN` `FLOOR` `FROUND` `FSQRT` `F0\u003c` `F0=` `F\u003c` `FDUP` `FDROP`\n`FSWAP` `FOVER` `FROT` `F@` `F!` `D\u003eF` `F\u003eD` `FDEPTH` `\u003eFLOAT`\n`REPRESENT` `FLITERAL` `F.` `FLOAT+` `FLOATS` `FALIGN` `FALIGNED`\n`FVARIABLE` `FCONSTANT` `FSIN` `FCOS` `FTAN` `FASIN` `FACOS` `FATAN`\n`FATAN2` `FSINCOS` `FSINH` `FCOSH` `FTANH` `FASINH` `FACOSH` `FATANH`\n`FEXP` `FEXPM1` `FLN` `FLNP1` `FLOG` `FALOG` `F**` `F\u003eS` `S\u003eF`\n`FTRUNC` `F=` `F~` `SF!` `SF@` `DF!` `DF@` `PRECISION` `SET-PRECISION`\n`FS.` `FE.` `FVALUE`\n\n**Facility:** `BEGIN-STRUCTURE` `END-STRUCTURE` `+FIELD` `FIELD:`\n`CFIELD:` `AT-XY` `PAGE` `KEY?` `EMIT?` `MS` `TIME\u0026DATE` `UTIME` `EKEY`\n`EKEY?` `EKEY\u003eCHAR` `EKEY\u003eFKEY`\n\n**Extended-Character (UTF-8):** `XC-SIZE` `X-SIZE` `XC@+` `XC!+`\n`XCHAR+` `XCHAR-` `XEMIT` `XKEY` `XKEY?` `+X/STRING` `XC-WIDTH`\n`X-WIDTH` `EKEY\u003eXCHAR` `-TRAILING-GARBAGE` `XHOLD` `X\\STRING-`\n\n**Search order:** `FORTH-WORDLIST` `GET-ORDER` `SET-ORDER`\n`GET-CURRENT` `SET-CURRENT` `WORDLIST` `SEARCH-WORDLIST` `DEFINITIONS`\n`FORTH` `ALSO` `ONLY` `PREVIOUS` `ORDER` `VOCABULARY`\n\n**Locals:** `(LOCAL)` `{: ... :}` `{ ... }` `TO`\n\n**Memory allocation:** `ALLOCATE` `FREE` `RESIZE`\n\n**Programming tools:** `.S` `?` `DUMP` `WORDS` `[DEFINED]`\n`[UNDEFINED]` `[IF]` `[ELSE]` `[THEN]` `CODE` `;CODE` `INTERPRETER`\n`AHEAD` `CS-PICK` `CS-ROLL` `N\u003eR` `NR\u003e` `SYNONYM` `TRAVERSE-WORDLIST`\n`NAME\u003eSTRING` `NAME\u003eINTERPRET` `NAME\u003eCOMPILE`\n\n**String extensions:** `SUBSTITUTE` `REPLACES` `UNESCAPE` `PLACE`\n\n**System:** `ATEXIT` `BYE` `EVALUATE` `QUIT` `ENVIRONMENT?` `SYSTEM`\n`ARGC` `ARG` `NEXT-ARG` `SHIFT-ARGS` `GETENV` `SETENV`\n\n**C interface:** `C-FUNCTION` `C-VALUE` `C-VARIABLE` `\\C`\n\n## Inline C with CODE\n\n`CODE` and `;CODE` let you define words with raw C code that is\nemitted verbatim into the compiled output. This is useful for\nperformance-critical operations or calling C library functions\ndirectly.\n\n### Typed CODE words\n\nCODE words can declare their stack signature on the first line using\nGforth-compatible type codes. Parameters are named `p0`, `p1`, `p2`,\n... (deepest first) and outputs use `return`:\n\n```forth\nCODE FAST-ADD  n n -- n\n  return p0 + p1;\n;CODE\n\nCODE FILL  a n n --\n  memset(p0, (int)p2, (size_t)p1);\n;CODE\n```\n\nType codes:\n\n| Code | C type | Stack | Description |\n|------|--------|-------|-------------|\n| `n` | `cell_t` | data | Integer cell |\n| `a` | `void *` | data | Address (auto-cast from `cell_t`) |\n| `u` | `ucell_t` | data | Unsigned cell |\n| `r` | `double` | float | Floating-point value |\n\nTyped CODE words generate a parameterised `__p` function that the\noptimiser calls directly — no stack spill/reload needed. A\n`void(void)` wrapper is also generated for backward compatibility\n(and tree-shaken if unused).\n\n### Multi-return CODE words\n\nCODE words that return multiple values use pre-declared output\nvariables `r0`, `r1`, `r2`, ... The emitter declares them with the\ncorrect C type from the signature and appends a return epilogue that\nconstructs the struct. Do **not** use `return` or `PUSH()` — just\nassign the output variables:\n\n```forth\nCODE UM/MOD  n n n -- n n\n  unsigned __int128 _n = ((unsigned __int128)(ucell_t)p1 \u003c\u003c 64) | (ucell_t)p0;\n  r0 = (cell_t)(ucell_t)(_n % (ucell_t)p2);\n  r1 = (cell_t)(ucell_t)(_n / (ucell_t)p2);\n;CODE\n\nCODE FSINCOS  r -- r r\n  r0 = sin(p0); r1 = cos(p0);\n;CODE\n\nCODE SDL-POLL-EVENT  -- n n\n  SDL_Event _e;\n  if (SDL_PollEvent(\u0026_e)) {\n    r0 = _e.button.button; r1 = _e.type;\n  } else { r0 = 0; r1 = 0; }\n;CODE\n```\n\nMulti-return typed CODE words generate `__p` functions with struct\nreturns, so parameterised callers can call them directly without\nspill/reload.\n\n### Untyped CODE words\n\nWithout a type signature, CODE words use manual `POP()`/`PUSH()`\nmacros as before:\n\n```forth\nCODE FAST-SQUARE\n  { cell_t n = POP(); PUSH(n * n); }\n;CODE\n```\n\nYou can use the runtime macros (`PUSH`, `POP`, `TOS`, `FPUSH`,\n`FPOP`, etc.). Addresses on the stack are real C pointers — use the\n`CADDR(a)` macro (expands to `(unsigned char*)(uintptr_t)(a)`) to\ncast them for byte access.\n\n### Calling C library functions\n\n`C-FUNCTION` declares an external C function with typed parameters,\nfollowing [Gforth's calling convention](https://gforth.org/manual/Declaring-C-Functions.html):\n\n```forth\n\\C #include \u003cmath.h\u003e\nC-FUNCTION c-sqrt sqrt r -- r\n\n: MAIN  2.0e0 c-sqrt F. CR ;   \\ prints 1.41421\n```\n\n`\\C` embeds a line of C code verbatim into the generated output\n(hoisted to the top alongside `#include` lines).\n\n`C-FUNCTION` creates a typed CODE word that calls the named C\nfunction directly — no manual `POP()`/`PUSH()` needed.\n\n### C constants and variables\n\n`C-VALUE` exposes a C constant, macro, or expression as a Forth word:\n\n```forth\n\\C #include \u003cSDL2/SDL.h\u003e\nC-VALUE SDL_INIT_VIDEO SDL_INIT_VIDEO -- n\n```\n\n`C-VARIABLE` exposes a C variable's address, like a Forth `VARIABLE`:\n\n```forth\n\\C static int counter = 0;\nC-VARIABLE my-counter counter\n: MAIN  my-counter @  1+  my-counter ! ;\n```\n\nBoth follow Gforth's C interface conventions.\n\n### #include hoisting\n\nIf the C code contains `#include` directives, they are hoisted to the\ntop of the generated file and de-duplicated:\n\n```forth\nCODE C-SIN  r -- r\n  #include \u003cmath.h\u003e\n  return sin(p0);\n;CODE\n```\n\n### Dual-mode definitions\n\nIf a Go primitive with the same name already exists, `CODE` adds the\nC body to it — the word works in both modes (Go primitive in the\ninterpreter, inline C in the compiler). If a Forth colon definition\nexists, `CODE` creates a new entry that uses the C body for compiled\nmode and falls back to the Forth definition for interpretation. If\nneither exists, the CODE word only runs in compiled mode; calling it\nin the interpreter raises an error.\n\nUse `INTERPRETER` with `[IF]`/`[ELSE]`/`[THEN]` to provide portable\ndefinitions that work in both modes when there is no Go primitive:\n\n```forth\nINTERPRETER [IF]\n  : FAST-SQUARE  DUP * ;\n[ELSE]\n  CODE FAST-SQUARE  n -- n\n    return p0 * p0;\n  ;CODE\n[THEN]\n```\n\nWords that need both an interpreter implementation and an optimised C\nimplementation follow one of two patterns in `core.fth`:\n\n- **Shadowed definitions:** The Forth colon definition is placed\n  first, then a `CODE` block inside `INTERPRETER [IF] [ELSE] ...\n  [THEN]` shadows it. The `CODE` word detects the existing Forth\n  definition and preserves it as an interpreter fallback while adding\n  the C implementation for compiled output. Used by double-cell\n  arithmetic, memory alignment, and similar words.\n- **Guarded definitions:** Words that only make sense in one mode use\n  `INTERPRETER [IF] ... [ELSE] ... [THEN]` so each mode gets\n  exactly one definition. Used by I/O, file-access, and\n  floating-point I/O words where the interpreter has a Go primitive.\n\n## Reading the generated C\n\nThe generated C is designed to be readable. Use `ccforth -c` to emit\nit (add `-no-line-directives` to omit `#line` markers for easier\nreading).\n\n### Word names\n\nEach Forth word becomes one or two `static inline` C functions. Words\nwith statically analysable stack effects get a **parameterised**\nversion (suffix `__p`) that takes stack inputs as parameters and\nreturns outputs via `cell_t` or a struct, plus a `void(void)` wrapper\nfor backward compatibility. Words that cannot be analysed get only the\n`void(void)` form. The name is formed by prefixing `word_` and\nreplacing special characters:\n\n| Character | Replacement |\n|-----------|-------------|\n| `-` | `_sub_` |\n| `+` | `_add_` |\n| `*` | `_mul_` |\n| `/` | `_div_` |\n| `!` | `_store_` |\n| `@` | `_fetch_` |\n| `\u003c` | `_lt_` |\n| `\u003e` | `_gt_` |\n| `=` | `_eq_` |\n| `?` | `_q_` |\n| `.` | `_dot_` |\n| `\"` | `_quote_` |\n\nAlphanumerics pass through unchanged. Any other character becomes\n`_\u003ccodepoint\u003e_`. For example, `FM/MOD` → `word_FM_div_MOD`,\n`2\u003eR` → `word_2_gt_R`.\n\nIf a word name is unique in the dictionary, the function name is just\nthe mangled name (e.g. `word_MAIN`). If the same name has been\nredefined (e.g. a Forth colon definition shadowed by a CODE word),\nthe execution token is appended as a suffix to disambiguate\n(e.g. `word_CR_348`).\n\n### Local variable names\n\nLocal variables declared with `{: ... :}` or `{ ... }` or `(LOCAL)`\nare emitted as C locals using their Forth names, prefixed by `_local_`\n(or `_flocal_` for float locals):\n\n```forth\n: DISTANCE {: F: x F: y -- F: r :}  x x F* y y F* F+ FSQRT ;\n```\n\ngenerates:\n\n```c\nstatic inline void word_DISTANCE(void) {\n    double _flocal_y;\n    double _flocal_x;\n    _flocal_y = FPOP();\n    _flocal_x = FPOP();\n    FPUSH(_flocal_x);\n    FPUSH(_flocal_x);\n    /* ... */\n}\n```\n\nBuffer locals (`W^`, `D^`, `F^`, `C^`) get a `_buf` suffix on the\nbacking array (e.g. `_local_buf` with `_local_buf_buf[1]` as storage).\nDouble-cell locals occupy two slots; only the first slot carries the\nForth name, the second uses a numeric fallback. If two locals would\nmangle to the same C name, a numeric suffix is appended\n(e.g. `_local_x_1`, `_local_x_2`).\n\n## Forth 2012 standard compliance\n\nFor a complete per-word reference organised by standard word set, see [WORDS.md](WORDS.md).\n\nccforth targets the Forth 2012 standard. The table below summarises\nwhich word sets are implemented. \"Both\" means words work in the\ninterpreter and compile to C; \"interpreter-only\" means they work in\nthe interpreter but have no C emission.\n\n| Word set | Status | Mode | Notes |\n|----------|--------|------|-------|\n| CORE | Complete | Both | All 134 words |\n| CORE EXT | Complete | Both | All standard words |\n| BLOCK | Not implemented | | |\n| DOUBLE | Complete | Both | All 20 words |\n| DOUBLE EXT | Complete | Both | `2ROT` `2VALUE` `DU\u003c` |\n| EXCEPTION | Complete | Both | `CATCH`/`THROW` via `setjmp`/`longjmp` |\n| EXCEPTION EXT | Partial | Both | `TRY` `IFERROR` `RESTORE` `ENDTRY` |\n| FACILITY | Complete | Both | All 3 words (`AT-XY` `KEY?` `PAGE`) |\n| FACILITY EXT | Partial | Both | Structure words, `EKEY` family, `EMIT?`, `MS`, `TIME\u0026DATE`, `UTIME`; K-* constants not implemented |\n| FILE | Complete | Both | All 21 words; CODE words in core.fth use `FILE*` table |\n| FILE EXT | Partial | Both | `FILE-STATUS` `FLUSH-FILE` `INCLUDE` `REFILL` `RENAME-FILE` `REQUIRED` `REQUIRE` |\n| FLOATING | Complete | Both | 34 words |\n| FLOATING EXT | Complete | Both | Transcendentals, exp/log, `F**`, `F~`, `SF!`/`SF@`, `DF!`/`DF@`, `PRECISION`, `SET-PRECISION`, `FS.`, `FE.`, `FVALUE` |\n| LOCALS | Complete | Both | `(LOCAL)` and `{: ... :}` syntax |\n| LOCALS EXT | Partial | Both | Typed specifiers (`W:` `D:` `F:` `C:` `W^` `D^` `F^` `C^`); no `LOCALS\\|` |\n| MEMORY | Complete | Both | Real `malloc`/`free`/`realloc` in compiled code; heap allocator in interpreter |\n| SEARCH | Complete | Interpreter-only | Dictionary restructured with wordlists |\n| SEARCH EXT | Partial | Interpreter-only | `ALSO` `ONLY` `PREVIOUS` `ORDER` |\n| STRING | Complete | Both | All 8 words |\n| STRING EXT | Partial | Both | `SUBSTITUTE` `REPLACES` `UNESCAPE` |\n| XCHAR | Complete | Both | UTF-8 encode/decode, `XEMIT`, `XKEY` |\n| XCHAR EXT | Complete | Both | `+X/STRING`, `X-WIDTH`, `XHOLD`, `X\\STRING-`, `-TRAILING-GARBAGE` |\n| TOOLS | Partial | Both | `.S` `?` `DUMP` `WORDS` |\n| TOOLS EXT | Partial | Both | `[DEFINED]` `[UNDEFINED]` `[IF]` `[ELSE]` `[THEN]` `CODE` `;CODE` `INTERPRETER` `AHEAD` `CS-PICK` `CS-ROLL` `N\u003eR` `NR\u003e` `SYNONYM` `TRAVERSE-WORDLIST` `NAME\u003eSTRING` `NAME\u003eINTERPRET` `NAME\u003eCOMPILE` |\n\n### CORE word set (complete)\n\nAll 134 CORE words are implemented:\n\n`!` `#` `#\u003e` `#S` `'` `(` `*` `*/` `*/MOD` `+` `+!` `+LOOP` `,`\n`-` `.` `.\"` `/` `/MOD` `0\u003c` `0=` `1+` `1-` `2!` `2*` `2/` `2@`\n`2DROP` `2DUP` `2OVER` `2SWAP` `:` `;` `\u003c` `\u003c#` `=` `\u003e` `\u003eBODY`\n`\u003eIN` `\u003eNUMBER` `\u003eR` `?DUP` `@` `ABORT` `ABORT\"` `ABS` `ACCEPT`\n`ALIGN` `ALIGNED` `ALLOT` `AND` `BASE` `BEGIN` `BL` `C!` `C,` `C@`\n`CELL+` `CELLS` `CHAR` `CHAR+` `CHARS` `CONSTANT` `COUNT` `CR`\n`CREATE` `DECIMAL` `DEPTH` `DO` `DOES\u003e` `DROP` `DUP` `ELSE` `EMIT`\n`ENVIRONMENT?` `EVALUATE` `EXECUTE` `EXIT` `FILL` `FIND` `FM/MOD`\n`HERE` `HOLD` `I` `IF` `IMMEDIATE` `INVERT` `J` `KEY` `LEAVE`\n`LITERAL` `LOOP` `LSHIFT` `M*` `MAX` `MIN` `MOD` `MOVE` `NEGATE`\n`OR` `OVER` `POSTPONE` `QUIT` `R\u003e` `R@` `RECURSE` `REPEAT` `ROT`\n`RSHIFT` `S\"` `S\u003eD` `SIGN` `SM/REM` `SOURCE` `SPACE` `SPACES`\n`STATE` `SWAP` `THEN` `TYPE` `U.` `U\u003c` `UM*` `UM/MOD` `UNLOOP`\n`UNTIL` `VARIABLE` `WHILE` `WITHIN` `WORD` `XOR` `[` `[']` `[CHAR]`\n`]`\n\n### CORE EXT words implemented\n\n`.(` `.R` `0\u003c\u003e` `0\u003e` `\u003c\u003e` `?DO` `:NONAME` `2\u003eR` `2R\u003e` `2R@` `AGAIN`\n`BUFFER:` `BYE` `C\"` `CASE` `COMPILE,` `DEFER` `DEFER!` `DEFER@`\n`ENDCASE` `ENDOF` `ERASE` `FALSE` `HEX` `HOLDS` `IS` `ACTION-OF`\n`MARKER` `NIP` `OF` `PAD` `PARSE` `PARSE-NAME` `PICK` `REFILL`\n`RESTORE-INPUT` `ROLL` `S\\\"` `SAVE-INPUT` `TO` `TRUE` `TUCK` `U.R`\n`U\u003e` `UNUSED` `VALUE` `-ROT`\n\n### Known deviations from the standard\n\n**Interpreter-only word sets.** The following word sets work in the\ninterpreter but cannot be compiled to C:\n\n- **Search order** — dictionary manipulation is inherently an\n  interpreter-time operation.\n- **`INCLUDE-FILE` / `INCLUDED`** — loading Forth source at runtime\n  is an interpreter operation.\n\n**`CODE` / `;CODE`** are repurposed for inline C rather than assembly\nlanguage. When a CODE word is defined for a name that already has a Go\nprimitive, the primitive is augmented with the C body — the word works\nin both modes. When a Forth colon definition exists, `CODE` shadows\nit for compilation while preserving the Forth definition for\ninterpreter use. Otherwise, CODE words only execute in compiled mode.\nUse `INTERPRETER [IF]` to provide a Forth fallback for interpreter\nmode (see \"Inline C with CODE\" above).\n\n**`INTERPRETER`** is a non-standard extension that pushes `TRUE` in\ninterpreter mode and `FALSE` in compiler mode, enabling conditional\ndefinitions with `[IF]`/`[ELSE]`/`[THEN]`.\n\n**Memory allocation** uses `malloc`/`free`/`realloc` in compiled code,\nso `ALLOCATE` can allocate beyond the Forth memory size and `FREE`\ngenuinely releases memory. In the interpreter, a heap allocator grows\ndownward from the top of memory — separate from the dictionary space\n(`HERE`) — so `ALLOCATE` does not affect the data-space pointer.\nCompile-time allocations (e.g., `1024 ALLOCATE THROW CONSTANT BUF`)\nare relocated to `malloc`'d pointers at program startup via an init\nfunction in the generated C.\n\n**Floating-point literals** require an `E` or `e` exponent marker per\nthe standard (e.g., `1E`, `3.14E0`, `.5E-3`). Plain decimal numbers\nlike `3.14` are not recognised as floats.\n\n**Locals** support the `{: ... :}` syntax with initialised and\nuninitialised locals. Multiple `{:` or `{ }` blocks in a single word\nextend the same local frame (Gforth-compatible). `LOCALS|` is not\nimplemented.\n\n**`ENVIRONMENT?`** recognises a fixed set of queries and does not\nreport all implementation limits.\n\n**Cell size** is 64 bits (`int64_t`) by default, which is not the most\ncommon choice for Forth systems but is valid per the standard. The `-32`\nflag generates 32-bit (`int32_t`) cells for cross-compilation targets.\n\n**`QUIT`** clears the stacks and returns to the interpreter in\ninterpreter mode. In compiled mode it calls `exit(0)`.\n\n## Gforth compatibility\n\nccforth is not a [Gforth](https://gforth.org/) clone, but it\nimplements many Gforth extensions that make it straightforward to port\nGforth programs. This section covers what works, what doesn't, and\nwhat to watch out for.\n\n### What works\n\n**Locals.** The `{ ... }` brace syntax works identically to Gforth,\nincluding typed specifiers (`W:` `D:` `F:` `C:` for stack-initialised\nlocals, `W^` `D^` `F^` `C^` for buffer locals) and the `|` separator\nfor uninitialised locals. Multiple `{ }` blocks in a single word\nextend the same local frame, matching Gforth behaviour. The standard\n`{: ... :}` syntax is also supported.\n\n**Strings and files.** `SLURP-FID`, `SLURP-FILE`, `PLACE`, and `S+`\nwork as in Gforth.\n\n**System interface.** `ARGC`, `ARG`, `NEXT-ARG`, `SHIFT-ARGS`,\n`GETENV`, `SETENV`, `SYSTEM`, and `UTIME` all behave as in Gforth.\n\n**Structures.** Both Gforth-style (`STRUCT` / `END-STRUCT` / `FIELD`)\nand Forth 2012-style (`BEGIN-STRUCTURE` / `END-STRUCTURE` / `FIELD:`)\nstructure definitions are supported.\n\n**Control flow.** `?DUP-IF`, `?DUP-0=-IF`, and the postpone-state\nbrackets `]]` / `[[` work as in Gforth.\n\n**Exception allocation.** `EXCEPTION` ( addr u -- n ) allocates a\nunique negative throw code, matching Gforth.\n\n**C interface.** `C-FUNCTION`, `C-VALUE`, `C-VARIABLE`, and `\\C`\nfollow Gforth's calling convention for declaring external C functions,\nconstants, and variables. Type codes `n`, `a`, `u`, `r` match\nGforth's conventions. `CODE` words also accept Gforth-style type\nsignatures on the first line.\n\n**Miscellaneous.** `VOCABULARY`, `CELL`, `\u003c=`, `\u003e=`, `0\u003c=`, `0\u003e=`,\nand `F=` are all available.\n\n### Semantic differences\n\n| Topic | ccforth | Gforth |\n|-------|---------|--------|\n| Cell size | 64-bit (`int64_t`) | Typically 64-bit on modern systems, 32-bit on older ones |\n| Division | Floored (matches Gforth) | Floored |\n| Float literals | Require `E`/`e` exponent (`3.14E0`) | Also accepts `3.14` in some configurations |\n| `CODE` / `;CODE` | Inline C | Inline assembly |\n\nThe 64-bit cell size matches Gforth on modern 64-bit systems but\ndiffers from 32-bit Gforth installations. This affects `CELL+`,\n`CELLS`, and memory layout of packed data.\n\n### What doesn't work\n\n- **`LOCALS|`** — not implemented. Use `{ ... }` or `{: ... :}`\n  instead.\n- **Search order in compiled code** — `VOCABULARY`, `DEFINITIONS`,\n  `GET-ORDER`, `SET-ORDER`, etc. work in the interpreter but cannot\n  be compiled to C (dictionary manipulation is inherently an\n  interpreter-time operation).\n- **Runtime file loading** — `INCLUDE-FILE`, `INCLUDED`, `REQUIRE`,\n  `REQUIRED` are interpreter-only.\n- **`CODE` differences** — Gforth `CODE` defines words with inline\n  assembly; ccforth `CODE` defines words with inline C. The syntax\n  is the same but the body language is different. ccforth extends\n  `CODE` with optional type signatures (`CODE name n n -- n`) that\n  Gforth does not have.\n\n### Porting tips\n\n1. **Replace `LOCALS|` with `{ }`** — `LOCALS| a b c |` becomes\n   `{ a b c }` (note: stack order is reversed in `{ }` syntax).\n2. **Add exponent markers to float literals** — `3.14` →\n   `3.14E0`.\n3. **Guard platform-specific CODE words** — use\n   `INTERPRETER [IF] ... [ELSE] CODE ... ;CODE [THEN]` to provide\n   Forth fallbacks for the interpreter.\n4. **Test in the REPL first** — `ccforth -i` runs in pure\n   interpreter mode, so you can verify word-level behaviour before\n   compiling.\n\n## Installation\n\nDownload a pre-built binary from the\n[GitHub releases page](https://github.com/ncw/ccforth/releases)\nand put it on your `PATH`. Binaries are available for Linux, macOS,\nand Windows (amd64 and arm64).\n\nOr install from source with Go:\n\n```bash\ngo install github.com/ncw/ccforth/cmd/ccforth@latest\n```\n\nOr build from a checkout:\n\n```bash\ngo build -o ccforth ./cmd/ccforth\n```\n\nYou also need a C compiler (`gcc` or `clang`) on your `PATH` for\ncompiling Forth programs. The `-c` flag emits C without invoking a\ncompiler, so a C compiler is not required for that mode or for the\ninteractive REPL.\n\n## Usage\n\n```\n# Compile a Forth program to an executable\nccforth -o hello testdata/end-to-end/hello.fth\n./hello\n\n# Emit C source only (don't invoke the C compiler)\nccforth -c testdata/end-to-end/factorial.fth\n\n# Use a specific C compiler and entry word\nccforth -cc gcc -entry MAIN -o program source.fth\n\n# Start an interactive REPL\nccforth -i\n\n# Load source files then enter the REPL\nccforth -i mylib.fth\n```\n\n### Flags\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `-i` | `false` | Interactive REPL mode |\n| `-o` | `a.out` | Output executable path |\n| `-c` | `false` | Emit C only, don't compile |\n| `-cc` | `cc` | C compiler to use |\n| `-Cflags` | | Extra flags for the C compiler |\n| `-entry` | `MAIN` | Entry word called from `main()` |\n| `-memsize` | `1048576` | Forth memory size in bytes |\n| `-v` | `false` | Print emitted C to stderr |\n| `-32` | `false` | Generate 32-bit cell C code (for cross-compilation) |\n| `-no-line-directives` | `false` | Omit `#line` directives from generated C |\n\n### Interactive mode\n\nThe `-i` flag starts an interactive REPL. Source files given on the\ncommand line are loaded first, then you get a prompt where you can\ntype Forth interactively:\n\n```\n$ ccforth -i\nccforth interactive mode\nType `BYE' to exit\n1 2 + . 3  ok\n: SQUARE  DUP * ; ok\n5 SQUARE . 25  ok\nBYE\n```\n\nFeatures:\n\n- **Tab completion** of dictionary words (case-insensitive).\n- **Line editing and history** (persisted to `~/.ccforth_history`).\n- **`BYE`** or **Ctrl-D** to exit.\n\nThe REPL runs in pure interpreter mode, so `INTERPRETER` pushes `TRUE`\nand CODE-only words are skipped during bootstrap. All Go primitives\nand Forth-defined words are available.\n\n### 32-bit mode\n\nThe `-32` flag generates C code with 32-bit cells (`int32_t`) instead\nof the default 64-bit cells. This is useful for embedded targets, retro\ncomputing, and cross-compilation to 32-bit architectures.\n\nWhen `-32` is active, the Go interpreter also runs with 32-bit cell\nsemantics, so all compile-time constants (e.g. `0 INVERT 1 RSHIFT`\nfor `MAX-INT`) are computed correctly for the target. You provide the\nappropriate cross-compiler via `-cc` and any necessary flags via\n`-Cflags`:\n\n```bash\n# Cross-compile for ARM32\nccforth -32 -cc arm-linux-gnueabihf-gcc -Cflags \"-static\" -o hello hello.fth\n\n# Compile 32-bit x86 on a 64-bit Linux host\nccforth -32 -Cflags \"-m32\" -o hello hello.fth\n\n# Emit 32-bit C only (inspect generated code)\nccforth -32 -c hello.fth\n```\n\n#### 32-bit x86 on a 64-bit host\n\nThe easiest way to produce a 32-bit executable on an x86-64 machine is\nto pass `-m32` to the C compiler via `-Cflags`. You need the 32-bit C\nlibrary and development headers installed:\n\n```bash\n# Debian / Ubuntu — install 32-bit toolchain\nsudo apt install gcc-multilib\n\n# Fedora / RHEL\nsudo dnf install glibc-devel.i686 libgcc.i686\n\n# Arch\nsudo pacman -S lib32-glibc lib32-gcc-libs\n```\n\nThen compile and run as normal:\n\n```bash\nccforth -32 -Cflags \"-m32\" -o hello hello.fth\n./hello           # runs natively on x86-64 Linux\nfile hello        # ELF 32-bit LSB executable, Intel 80386 ...\n```\n\nOn macOS, `-m32` is no longer supported by Apple Clang (dropped in\nXcode 10). Use a cross-compiler or Docker instead.\n\nWhen `-32` is set, `-march=native` is omitted from the default C\ncompiler flags since it is incompatible with cross-compilation.\n\n**Limitations:**\n\n- Values exceeding 2^31 are truncated to 32 bits at interpret time,\n  which is correct Forth behavior for a 32-bit cell system.\n- User `CODE` words that assume 64-bit cells will need `#ifdef\n  CCFORTH_32BIT` guards for portable operation.\n- `FLOAT+` and `FLOATS` remain 8-byte (doubles are always 64-bit),\n  while `CELL+` and `CELLS` become 4-byte.\n\n## Examples\n\nThe `examples/` directory contains complete programs demonstrating\ndifferent ccforth features:\n\n| Example | Description | Dependencies |\n|---------|-------------|--------------|\n| [**Ray tracer**](examples/raytrace/) | Renders 3 spheres on a checkerboard ground plane to a PPM image. Demonstrates floating-point math, typed locals, recursive algorithms, and compile-time data with `CREATE`/`F,`. | None |\n| [**Mandelbrot viewer**](examples/mandelbrot/mandelbrot.fth) | Interactive Mandelbrot set explorer with click-to-zoom. Uses SDL2 for windowed rendering. | SDL2 |\n| [**Mandelbrot terminal**](examples/mandelbrot/mandelterm.fth) | Animated Mandelbrot zoom in the terminal using ANSI 24-bit color and Unicode half-block characters. | None |\n| [**Breakout**](examples/breakout/) | Classic breakout game with paddle, ball, and bricks. Uses SDL2 for rendering and input. | SDL2 |\n| [**Game Boy**](examples/gameboy/) | Game Boy DMG emulator with CPU/ALU in pure Forth. Renders in the terminal using ANSI 256-color and Unicode half-blocks. Ported from [ioccc-gameboy](https://github.com/ncw/ioccc-gameboy). | None |\n| [**Whetstone benchmark**](examples/whetstone/) | Classic double-precision floating-point benchmark. Compares C, ccforth compiled, and Gforth interpreted performance (C 1.0x, ccforth 1.4x, Gforth 41.9x). | None |\n| [**Dhrystone benchmark**](examples/dhrystone/) | Classic integer benchmark (v1.1). Compares C, ccforth compiled, and Gforth interpreted performance (C 1.0x, ccforth 3.2x, Gforth 217.5x). | None |\n| [**Towers of Hanoi**](examples/hanoi/) | Animated terminal demo with rainbow-colored disks, smooth lift/slide/drop animation, and live statistics. Configurable 1–13 disks via command line. | None |\n\n### Quick start\n\n```bash\n# Ray tracer (no dependencies)\nccforth -o raytrace examples/raytrace/raytrace.fth\n./raytrace \u003e scene.ppm\n\n# Mandelbrot terminal animation (no dependencies)\nccforth -o mandelterm examples/mandelbrot/mandelterm.fth\n./mandelterm\n\n# Mandelbrot SDL viewer (requires SDL2)\nccforth -Cflags \"-lSDL2\" -o mandelbrot examples/mandelbrot/mandelbrot.fth\n./mandelbrot\n\n# Breakout game (requires SDL2)\nccforth -Cflags \"-lSDL2\" -o breakout examples/breakout/breakout.fth\n./breakout\n\n# Game Boy emulator (no dependencies)\nccforth -Cflags \"-Iexamples/gameboy\" -o gameboy examples/gameboy/gameboy.fth\n./gameboy rom.gb\n```\n\n## Project structure\n\n```\nccforth/\n+-- cmd/ccforth/          CLI entry point\n+-- pkg/\n|   +-- scanner/          Lexer: source -\u003e tokens\n|   +-- dictionary/       Word headers, flags, lookup\n|   +-- memory/           Linear byte-addressed memory model\n|   +-- interpreter/      Outer interpreter, primitives, control flow\n|   +-- compiler/         C emitter, peephole optimiser\n|   +-- codegen/          Orchestrates complete C output\n|   +-- forth/            Bootstrap Forth definitions (core.fth)\n+-- runtime/              Embedded C runtime (runtime.c.src, embed.go)\n+-- testdata/\n|   +-- end-to-end/       Test Forth programs with expected output\n|   +-- benchmarks/       Benchmark programs\n|   +-- forth2012-test-suite/  Forth 2012 conformance test suite\n```\n\n## Testing\n\n```\ngo test ./...\n```\n\nTests run at every level:\n\n- **Unit tests** for the scanner, memory, dictionary, interpreter,\n  emitter, and peephole optimiser.\n- **End-to-end compiler tests** — each `.fth` file in\n  `testdata/end-to-end/` is compiled to C, built with gcc, run, and\n  its stdout compared against the matching `.txt` file.\n- **End-to-end interpreter tests** — the same `.fth` files are loaded\n  into the interpreter, `MAIN` is called, and output is verified.\n- **Forth 2012 test suite** — the official Forth 2012 test suite\n  (`testdata/forth2012-test-suite/`) is run through the interpreter,\n  validating standard compliance across all implemented word sets.\n\nTo add a new test, create `testdata/end-to-end/mytest.fth` (with a\n`: MAIN ... ;` word) and `testdata/end-to-end/mytest.txt` (with the\nexpected output). The test harness discovers them automatically.\n\n### Forth 2012 test suite\n\nThe [Forth 2012 test suite](https://github.com/gerryjackson/forth2012-test-suite)\nis included in the `testdata/forth2012-test-suite/` directory and run as an\nintegration test (`TestForth2012Suite`). It validates conformance to the\nForth 2012 standard across the following word sets:\n\n| Word set | Errors |\n|----------|--------|\n| Core | 0 |\n| Core extension | 0 |\n| Double | 0 |\n| Exception | 0 |\n| File-access | 0 |\n| Locals | 0 |\n| Memory-allocation | 0 |\n| Programming-tools | 0 |\n| Search-order | 0 |\n| String | 0 |\n| Block | - (not implemented) |\n\nTo run just the test suite:\n\n```\ngo test -run TestForth2012Suite -v\n```\n\nThe test parses the suite's error report and fails if any word set\nreports errors. Word sets marked `-` are not implemented and are\nexcluded from the pass/fail check.\n\n## Performance\n\nccforth does not try to be a good optimising compiler. Instead, it\ngenerates simple, transparent C that a good optimising C compiler\n(gcc, clang) can reason about. The philosophy is: **do the minimum\nwork in the emitter to remove obstacles that prevent the C compiler\nfrom doing its job**.\n\nGCC and clang already know how to allocate registers, schedule\ninstructions, unroll loops, and eliminate dead code. What they\nstruggle with is aliasing: when values live in global arrays\n(`dstack[]`, `rstack[]`, `fstack[]`), the compiler cannot prove that\na write through one pointer doesn't clobber a read through another,\nso it conservatively reloads values from memory on every access. The\noptimisations below focus on keeping hot values out of global arrays\nand reducing the number of operations the C compiler has to see\nthrough.\n\nEvery generated function is declared `static inline`, which lets the\nC compiler inline across word boundaries and optimise the resulting\ncode as a single unit. The default compiler flags are\n`-std=c11 -O3 -march=native -flto`.\n\n### Peephole optimiser\n\nOperates on the body IR (word references, literals, branches) before\nC emission. Patterns are matched in two- and three-item windows:\n\n- **Constant folding** — `LIT 3 LIT 4 +` becomes `LIT 7`. Works for\n  `+`, `-`, `*`, `AND`, `OR`, `XOR`, `LSHIFT`, `RSHIFT`.\n- **Identity elimination** — `LIT 0 +`, `LIT 1 *`, `LIT -1 AND`,\n  etc. are removed entirely.\n- **Strength reduction** — `LIT 1 +` becomes `1+`, `LIT 2 *` becomes\n  `2*`, `LIT 2 /` becomes `2/`, `DUP +` becomes `2*`, `SWAP DROP`\n  becomes `NIP`.\n- **Word combining** — `OVER OVER` becomes `2DUP`, `DROP DROP`\n  becomes `2DROP`.\n- **SWAP elimination** — `SWAP` before a commutative operation (`+`,\n  `*`, `AND`, `OR`, `XOR`, `=`, `\u003c\u003e`) is removed, since the operand\n  order doesn't matter.\n- **Return-stack cleanup** — `\u003eR` immediately followed by `R\u003e` is\n  eliminated (identity round-trip).\n\n### DO/LOOP to C local variables\n\nThis is the single most impactful optimisation. Standard Forth DO\nloops store their index and limit on the return stack — a global\narray (`rstack[]`). Every `I`, `LOOP`, and `+LOOP` reads and writes\n`rstack[rsp-1]` and `rstack[rsp-2]`, which the C compiler cannot\npromote to registers because it can't prove that other code\n(function calls, pointer writes) doesn't alias the same memory.\n\nThe emitter rewrites eligible DO/LOOP constructs to use C local\nvariables instead. With local variables, GCC promotes the loop counter\nand limit to registers. The inner loop of a DO/LOOP benchmark compiles\nto a handful of register-only instructions with no memory traffic.\n\nNested loops each get their own C locals (`_loop0_*`, `_loop1_*`,\netc.). `I` refers to the innermost loop's local, `J` to the next\nouter loop. `+LOOP` uses the same locals with the standard\nboundary-crossing check. `LEAVE` becomes a bare `goto` (no rstack\ncleanup needed). `UNLOOP` before `EXIT` is a no-op since there are\nno rstack entries to remove.\n\n**Eligibility:** in the traditional emitter, a loop falls back to\nrstack-based emission if its body contains `\u003eR`, `R\u003e`, `R@`, or\n`UNLOOP`. In the parameterised emitter, this restriction does not\napply because `\u003eR`/`R\u003e` are handled via the virtual return stack\n(see below).\n\n### Comparison + branch fusion\n\nWhen a comparison word is immediately followed by a conditional\nbranch (`IF`, `UNTIL`, `WHILE`), the emitter produces a single C\nconditional instead of two separate operations:\n\n```c\n// Before: 0= IF\nTOS = (TOS == 0) ? -1 : 0;   // comparison → flag\nif (POP() == 0) goto L;       // branch on flag\n\n// After: fused\nif (POP() != 0) goto L;       // one test, no intermediate flag\n```\n\nThis eliminates the intermediate `-1`/`0` Forth truth flag and the\nsecond conditional test. Covers all standard comparisons: `0=`,\n`0\u003c\u003e`, `0\u003c`, `0\u003e`, `=`, `\u003c\u003e`, `\u003c`, `\u003e`, `\u003c=`, `\u003e=`, `U\u003c`, `U\u003e`.\n\n### Literal + operation fusion\n\nWhen a literal is immediately followed by an arithmetic or bitwise\noperation, the emitter produces a compound assignment instead of a\npush-then-operate sequence:\n\n```c\n// Before: LIT 128 +\nPUSH(128);\nSECOND += TOS; sp--;\n\n// After: fused\nTOS += 128;\n```\n\nThis avoids a stack push and the associated array write that GCC\ncannot always optimise away. Covers `+`, `-`, `*`, `AND`, `OR`,\n`XOR`.\n\n### Parameterised C emission (stack-to-locals)\n\nThe single most impactful optimisation. Instead of every word being\n`void word(void)` with all data flowing through the global `dstack[]`\narray, words with statically known stack effects are emitted as proper\nC functions where stack inputs become parameters and outputs become\nreturn values:\n\n```c\n// Forth:  : FACT  DUP 1 \u003e IF DUP 1- RECURSE * ELSE DROP 1 THEN ;\n// Generated C:\nstatic inline cell_t word_FACT__p(cell_t s0) {\n    if (s0 \u003e 1) {\n        cell_t t0 = s0 - 1;\n        cell_t t1 = word_FACT__p(t0);\n        cell_t _m0 = s0 * t1;\n        return _m0;\n    } else {\n        return 1;\n    }\n}\n```\n\nThis lets GCC keep all values in registers instead of bouncing them\nthrough `dstack[]`. Stack shuffles like `SWAP`, `ROT`, `OVER`, and\n`DUP` become zero-cost renames in a virtual stack — no C code is\nemitted for them at all.\n\nThe same approach is applied to all three stacks:\n\n- **Data stack** — inputs become function parameters, outputs become\n  return values (single `cell_t` or a struct for multiple outputs).\n  Stack shuffles are pure name renames.\n- **Return stack** — `\u003eR` / `R\u003e` / `R@` transfer variable names\n  between a virtual data stack and a virtual return stack. In\n  compiled code the return stack is never used for return addresses\n  (C handles those), so `\u003eR`/`R\u003e` are just temporary variable\n  storage. Zero C code is emitted for them.\n- **Float stack** — `FDUP` / `FSWAP` / `FOVER` / `FROT` are\n  zero-cost name renames. `F+` / `F*` etc. become `double fN = a + b;`\n  with gcc-allocated registers. Float values are flushed to the\n  global `fstack[]` at control flow boundaries (branches, loops,\n  word calls) since the float stack does not participate in the\n  branch merge logic.\n\nA **stack effect analyser** walks each word's body IR to compute\nits `(inputs, outputs)` signature, handling branches (IF/ELSE/THEN),\nloops (DO/LOOP, BEGIN/WHILE), locals, VALUEs, and self-recursion\n(RECURSE). Words that cannot be statically analysed fall back to the\ntraditional global-stack emitter. The following constructs prevent\nparameterisation:\n\n| Construct | Reason |\n|-----------|--------|\n| `PICK` | Dynamic stack depth (index from data stack) |\n| `ROLL` | Dynamic stack depth (count from data stack) |\n| `EXECUTE` | Unknown callee stack effect |\n| `DEPTH` | Reads global `sp` which parameterised code doesn't maintain |\n| `FDEPTH` | Reads global `fsp` which parameterised code doesn't maintain |\n| `N\u003eR` / `NR\u003e` | Dynamic count from data stack |\n| `CATCH` | Modifies control flow via `setjmp`/`longjmp` |\n| Mismatched `EXIT` depth | `EXIT` leaves a different stack depth than the normal return path |\n| `CREATE ... DOES\u003e` | DOES\u003e body has unknown stack effect |\n| Mismatched branch depths | IF/ELSE paths leave different stack depths (e.g. `?DUP`) |\n| Untyped CODE words | CODE words without a type signature or stack effect table entry |\n\nNote: `?DUP-IF` and `?DUP-0=-IF` **are** parameterisable — they\ncompile to dedicated branch instructions (`BranchQDupIf` /\n`BranchQDupNot`) that the analyser handles natively. Only bare `?DUP`\n(without `IF`) is unconvertible due to its internal branch mismatch.\n\nNote: `EXIT` inside loops **is** allowed — the analyser verifies\nthat all EXIT paths leave the same stack depth as the normal return.\nAll typed CODE words (including multi-return like `-- n n`) generate\nparameterised `__p` functions. Multi-return words use output variables\n`r0`, `r1`, ... and return a struct. 92 of 101 CODE words in core.fth\nhave typed signatures; the remaining 9 (PICK, ROLL, EXECUTE, CATCH,\nN\u003eR, NR\u003e, .S, ENVIRONMENT?, \u003eFLOAT) are genuinely unconvertible due\nto dynamic stack access or variable-length effects.\n\nEach parameterised word also gets a `void(void)` **wrapper** that\npops inputs from the global stack, calls the parameterised version,\nand pushes outputs back. This ensures backward compatibility: old-style\ncallers and function pointers (`EXECUTE`, `CATCH`) work unchanged.\nWhen a parameterised word calls an unconverted word, it **spills** its\nvirtual stack to `dstack[]`, makes the call, and **reloads** the\nresults.\n\n**Tail call optimisation** converts self-recursive calls at tail\nposition into parameter reassignment plus `goto`, turning recursion\ninto iteration with constant stack space.\n\n### What we deliberately don't do\n\n- **Structured control flow reconstruction.** Reconstructing\n  `if`/`while`/`for` from flat `goto` would add complexity for\n  marginal gain — GCC's control-flow analysis handles `goto` graphs\n  well.\n\n### Benchmarks\n\nA benchmark suite in `testdata/benchmarks/` covers 12 categories.\nThe table below compares three execution methods: ccforth compiled to C\n(via `gcc -O3 -march=native -flto`), the ccforth Go interpreter, and\nGforth 0.7.3. Times are medians of 3 runs on an AMD Ryzen 7 PRO 5850U.\n\n| Benchmark          | Compiled | Interpreter |  Gforth | Gforth/Compiled | Interpreter/Compiled |\n|--------------------|----------|-------------|---------|-----------------|----------------------|\n| control-flow       |    655µs |       1.34s |   206ms |            315x |              2049x |\n| floating-point     |      7ms |       152ms |    36ms |              5x |                21x |\n| integer-arithmetic |    660µs |       274ms |    38ms |             57x |               415x |\n| locals-vs-stack    |    686µs |       1.91s |   256ms |            373x |              2787x |\n| loop-comparison    |    665µs |       374ms |    46ms |             69x |               562x |\n| matrix-multiply    |    358ms |      84.81s |  8.08s  |             23x |               237x |\n| memory-access      |      2ms |       655ms |    68ms |             29x |               279x |\n| recursion          |      7ms |       584ms |    78ms |             11x |                80x |\n| sieve              |     13ms |       4.42s |   503ms |             39x |               343x |\n| stack-manipulation |    655µs |       340ms |    49ms |             74x |               519x |\n| string-memory      |    105ms |       7.89s |   756ms |              7x |                75x |\n| word-call-return   |      2ms |       827ms |   135ms |             56x |               340x |\n\nThe compiled output is typically **5–373x faster than Gforth** and\n**21–2787x faster than the interpreter**, depending on how well the\nbenchmark's hot loop maps to C optimisation.\n\nRun the benchmarks using Go's standard benchmark framework:\n\n```\ngo test -bench BenchmarkCompiled -run ^$ -timeout 300s\ngo test -bench BenchmarkInterpreter -run ^$ -timeout 300s\ngo test -bench BenchmarkGforth -run ^$ -timeout 600s\n```\n\nRun a specific benchmark:\n\n```\ngo test -bench BenchmarkCompiled/sieve -run ^$ -timeout 60s\n```\n\n## Contributing\n\nContributions are welcome at https://github.com/ncw/ccforth. To get\nstarted:\n\n1. Fork the repository and create a feature branch.\n2. Make your changes. Run `go test ./...` and ensure all tests pass.\n3. If adding new Forth words, add both interpreter and C emitter\n   support, and include a test program in `testdata/end-to-end/`.\n4. Open a pull request with a clear description of what you changed\n   and why.\n\n### Areas where help is appreciated\n\n- **Block word set** — not currently implemented.\n- **Float stack branch merging** — the virtual float stack is flushed\n  to the global `fstack[]` at every control flow boundary (IF, ELSE,\n  BEGIN, DO, word calls). Float values that span across branches could\n  instead participate in the same merge-variable logic used for the\n  data stack, avoiding the flush/reload overhead. This would benefit\n  words like `F-ABS` (`x F0\u003c IF x FNEGATE ELSE x THEN`) where the\n  result is currently bounced through the global array at the merge.\n- **Inline small parameterised words at IR level** — copy the body\n  of small `__p` words into callers before C emission, enabling\n  cross-word peephole optimisation and reducing function call overhead.\n- **Expression folding** — collapse chains of single-use temps into\n  compound expressions (`cell_t t0 = s0 + 1; cell_t t1 = t0 * 2;` →\n  `cell_t t1 = (s0 + 1) * 2;`). This is cosmetic — gcc already does\n  it — but produces cleaner generated C.\n- More peephole patterns and dead code elimination.\n\n## License\n\nccforth is licensed under the [MIT License](LICENSE).\n\n**Output exception:** The C source code generated by ccforth (including\nthe embedded runtime) is not considered a derivative work of ccforth.\nYou may use, distribute, and license the generated code and compiled\nbinaries under any terms you choose, with no obligation to apply the\nMIT license or provide attribution to ccforth.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fncw%2Fccforth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fncw%2Fccforth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fncw%2Fccforth/lists"}