Skip to content

Latest commit

 

History

History
237 lines (207 loc) · 80.5 KB

File metadata and controls

237 lines (207 loc) · 80.5 KB

G Language (G-C6 Omnisphere) — Master Context Manual

Version: Phase 45 Complete (v1.0 Release) Target Audience: AI Compiler Engineers & Human Maintainers Prime Directive: Read this document completely before making any changes. Do not assume features exist. Do not invent syntax. Adhere strictly to the implemented state.

1. Project Identity & Vision

G Language (file extensions .g / .gufran) is a universal systems language. It compiles pure human prose (English SVO, Bangla/Japanese SOV) into deterministic, formally verified, hyper-optimized code that speaks directly to any hardware: x86-64 and ARM64 (syscall assembly), AVR and ESP32 (raw MMIO with zero headers), and quantum processors (OpenQASM 2.0) — while retaining C99 as the default export for seamless integration with existing C ecosystems. It is designed to scale from bare-metal microcontrollers (AVR, ESP32) to desktop game engines, web servers, and quantum circuits, featuring zero syntax, automatic memory safety, and compile-time metaprogramming. The compiler is currently written in Rust, and the standard project tooling is the Cardinal Package Manager (cpm).

2. Repository Layout

g-language/
├── .github/workflows/   release.yml — CI/CD: push a v* tag → matrix build
│                        (ubuntu/windows/macos) → zip dist/ → GitHub Release
├── .gitignore         ignores /target, /dist, and root scratch outputs (/*.c, /*.s, ...)
├── Cargo.toml         workspace manifest (bins: g, gufran, cpm, apm, g-lsp)
├── Cargo.lock
├── LICENSE            MIT license (SK GUFRAN AHMED)
├── README.md          GitHub landing page
├── CHANGELOG.md       the journey, Phase 1 → v1.0 (the Cardinal Release)
├── MANUAL.md          this file — implementation manual & phase ledger
├── build_release.ps1  reproduces dist/ from source + TCC (Windows)
├── build_release.sh   reproduces dist/ from source + TCC from source (Linux/macOS)
├── docs/              rfc-0001.md (grammar table and language charter)
├── examples/          G source: Stage 2-5 bootstrap compilers (lexer/parser/
│                      validator/codegen.g), Stage 5 stdlibs (json.g, http.g,
│                      server.g, api_server.g, web_app.g), build_script.g,
│                      bootstrap.g (self-hosted C99 emitter), baremetal_*.g,
│                      and demos (hello, counter, cli_tool, file_scan, ...)
├── runtime/           g_rt.h (cross-platform C99 runtime) + http_shim.c/.h (libcurl FFI shim)
│                      + tcp_shim.h (header-only Winsock/socket shim) + cli_shim.h (CLI args/env)
│                      + fs_shim.h (directory listing / file exists / current time)
│                      + math_shim.h / regex_shim.h (Phase 41) + os_shim.h (Phase 44) + ws2_32.def
├── src/               Rust compiler core (lexer, parser, HIR/MIR/SSA, optimizer, C99 backends)
│                      + gufran.rs, cpm.rs, apm.rs (bins), lsp.rs (g-lsp — Language Server
│                      Protocol server, Phase 40 — completion/hover/goto-definition)
├── stdlib/            Standard Library modules in pure verse (net.g — TCP network abstraction;
│                      os.g — process execution; fs.g — filesystem wrapper)
├── tests/             Integration tests (compile.rs, e2e.rs) + unit tests in src/
└── dist/              Standalone distribution template (Phase 31), git-ignored: g.exe + gufran.exe
                       + cpm.exe/apm.exe + g-lsp.exe (Phase 40) + bundled TCC + runtime/ + stdlib/

Standalone dist/ layout:

dist/
├── g.exe               (Rust-compiled G compiler)
├── gufran.exe          (identical alias binary)
├── cpm.exe / apm.exe   (Cardinal Package Manager, Phase 36)
├── g-lsp.exe           (Language Server Protocol server, Phase 40 — completion/hover/goto-definition)
├── runtime/            (g_rt.h, cli_shim.h, fs_shim.h, tcp_shim.h, math_shim.h,
│                       regex_shim.h, os_shim.h, ws2_32.def)
├── stdlib/             (net.g, os.g, fs.g — imported via `Import "stdlib/net.g"` etc.)
└── bin/                (bundled TCC: tcc.exe + libtcc.dll + include/ + lib/)

3. The Golden Rules (Anti-Hallucination Constraints)

  1. Zero Syntax: The surface is human prose. There are NO brackets {}, NO semicolons ;, NO colons :, and NO indentation significance.
  2. Implicit Scopes: Blocks (If, For each, Define routine) are closed by narrative transitions (After., Finally., After processing...) or a period .. The keyword End is forbidden (G-001 rejects it). EOF always closes (Phase 38): reaching the end of the file automatically closes every open routine, loop, conditional, record, and parallel block silently — no trailing Finally. or end. is ever required, and no G-071 implicit scope closure assumed diagnostic is emitted for EOF closures (G-071 still fires when a block is implicitly closed mid-file by the next statement).
  3. Determinism: Identical input → identical output. Bit-for-bit reproducible builds via stable hashing, canonical symbol ordering, and platform-independent iteration.
  4. Safety: Memory safe, thread safe, null safe, overflow safe (where profile guarantees). Memory is scope-owned and freed on every exit path.
  5. No Assumptions: If a feature is not listed in this manual as "Complete," it does not exist. Do not use it in tests or generated code.

3. Exact Implementation State (Phases 1-18)

  • Core Pipeline: g CLI tool (Rust core). Lexer → Semantic Graph → HIR → Type Unification → Ownership/Capability Gates → MIR → SSA → Optimizer → C99 Backend.
  • Backends: Desktop (arena runtime), AVR (bare-metal ISR + MMIO), ESP32 (FreeRTOS).
  • Logic & Data: Routines (Define routine/Execute), Compound Conditionals (and/or/not), Strings (followed by/Length of), Lists (List of/Add to/Count of), Loops (Repeat N times/For each X in Y), Records (Define record), ADTs (Optional/Result with is present/value of).
  • Loop Control (Phase 32): Break. exits the innermost enclosing loop; Continue. jumps to the next iteration. Both are legal only inside While/Repeat/For each (and Forever/In parallel-free loop bodies); anywhere else is G-010 "'break' is only allowed inside a loop" / "'continue' is only allowed inside a loop". Lowering is context-aware: inside Repeat/For each (which lower to real C for loops) they emit C break;/continue;; inside While/Forever (label/jump lowering) they resolve at MIR build time to Jump instructions targeting the loop's back-edge/exit labels (an unreachable exit label is synthesized for Forever, which otherwise has none).
  • Bare Return (Phase 32): a Return. with no value is legal in any routine without returning <Type> (i.e. void routines) and returns immediately; it lowers to SsaOp::Retgoto g_done; (never writes g_ret). The old G-070 warning 'return' requires a value to return is gone. Type-checked: Return. in a routine returning <Type> → G-020 "routine returns <T> but 'return' provides no value (use 'return <value>')"; Return <expr>. in a void routine → G-020 "routine returns no value but 'return' provides <T> (use a bare 'Return.')". Return. outside a routine is still G-010.
  • CLI Arguments & Environment (Phase 33): three new expressions — Get argument count (→ Number, lowers to g_argc()), Get argument <N> (Number index → Text, lowers to g_argv(g_tN)), Get env "<NAME>" (Text name → Text, lowers to g_get_env(g_tN)). Parser: three parse_unary blocks dispatched on the exact words get argument / get env. Type-checked: a non-Number Get argument index → G-020 "Get argument requires a Number index, found <T>"; a non-Text Get env name → G-020 "Get env requires a Text variable name, found <T>". Codegen: the Desktop (host) profile emits #include "cli_shim.h" (new runtime/cli_shim.h — included unconditionally on the host profile, like g_rt.h) and its main is now int main(int argc, char **argv) which seeds g_cli_argc/g_cli_argv globals; the shim exposes g_argc()/g_argv(double)/g_get_env(g_str) as views into process-lifetime OS storage (no arena copy; out-of-range argument or missing env var → empty ""). Game (SDL) and Web (Emscripten) profiles keep int main(void) and do not include the shim (using CLI ops there fails at C compile — embedded AVR/ESP32 likewise unchanged). --run forwarding (Phase 33): g file.g --run arg1 arg2 now forwards everything after --run to the child — tcc -run file.c arg1 arg2 (tcc natively passes them) and the compiled-.exe path alike; the tcc-api in-memory path forwards them too (tcc.run(&argv)).
  • File System Operations & Date/Time (Phase 34): three new expressions — List directory <Text> (→ List of Text, lowers to g_list_dir(&arena, g_tN)), File exists <Text> (→ Flag/Bool, lowers to (g_file_exists(g_tN) != 0)), Current time (→ Number, lowers to g_time_now(), UNIX epoch seconds). Parser: three parse_unary blocks on list directory / file exists / current time. Type-checked: a non-Text List directory/File exists path → G-020 "List directory requires a Text path, found <T>" / "File exists requires a Text path, found <T>". Truthy conditions (Phase 34): a condition with no comparison phrase — If File exists "x" then, If B then — is now a truthy test: parse_cond_primary re-parses the bare expression (previously parse_cond_lhs discarded any lhs without a following comparison operator, producing spurious unrecognized concept cascades), Cond::Truthy requires the expression to type-check as Flag/Bool (else G-020 "condition must be a Flag (Bool), found <T>"), and it lowers to != 0. If File exists "x" is true then still parses as a plain Cmp against true. Codegen: #include "fs_shim.h" is emitted only if the program uses one of the three ops (prog_uses_fs scan, same shape as prog_uses_file_io); runtime/fs_shim.h is header-only (inline with --run), Windows uses FindFirstFileA/FindNextFileA + GetFileAttributesA, POSIX uses opendir/readdir + stat, time is time(NULL) from <time.h>; listing entries are arena-backed g_str (same pattern as g_str_split, so the generated epilogue's g_vec_*_free needs no per-entry frees), . and .. are skipped, and a missing/unreadable directory yields an empty vector (never NULL — For each over it is safe, Count of yields 0). Note the deviation from the directive's literal signature: g_list_dir takes g_arena *a first (as g_str_split does) so entries live in the program arena instead of leaking per-process-heap strings.
  • g_str_split arity fix (Phase 34, found live): codegen previously emitted the 2-argument call g_str_split(g_tN, g_tN) while g_rt.h defines the 3-argument g_str_split(g_arena *a, g_str s, g_str sep) — a latent Phase 26+ bug that made every Desktop program using Split fail to compile (cannot cast 'struct g_str' to 'struct g_arena *'). No test caught it because none executed Split end-to-end. Now emitted as g_str_split(&arena, g_tN, g_tN); new e2e test split_executes_end_to_end locks it (prints a/b/c).
  • Statement connector , and (Phase 35, self-hosting enabler): a comma-and connector allows chained clauses after a terminator — set Word to Execute TrimPeriod with Value of Tokens at I , if Word is Print then ... . The and arm in parse_statement bumps and and parses the next statement; , and if <cond> then <body> parses a nested if inline (sharing the enclosing block's closer, subject to the same deferral rules as Otherwise if chains, including an explicit end.).
  • Create expression (Phase 35, self-hosting enabler): set Tokens to Create List of Textparse_unary on create accepts a/an then parse_payload_type and returns default_init (Expr::ListLit/TableLit). Create <Record> still takes the record-init path; both feed the existing NewList/NewTable MIR lowering.
  • Codegen bug fixed while bootstrapping (Phase 35, found live): set Tokens to Create List of Text as the RHS of a set (not a standalone Create ... called ...) lowers through Stmt::Assign, whose list/table-literal special-case (NewList/NewTable) existed only in Stmt::Decl — the SSA temp was emitted with no declaration and TCC failed with bootstrap.c:125: error: 'g_t9' undeclared. mir.rs Stmt::Assign now special-cases ListLit/TableLit exactly like Stmt::Decl.
  • Cardinal Package Manager — cpm / apm (Phase 36): a standalone package-manager CLI (src/cpm.rs; src/apm.rs is a one-line include!("cpm.rs") alias bin; both added as [[bin]] targets and shipped in dist/ by build_release.ps1). cpm init <name> creates <name>/ with an exact-format cardinal.toml ([package] / name / version = "0.1.0" / entry = "main.g") and a main.g that prints Hello from CPM; cpm run shells dist/g.exe main.g --run; cpm build shells dist/g.exe main.g --out build/main.c. The compiler is resolved as <cpm-exe-dir>/g.exe (the dist/ layout) with a g-on-PATH fallback.
  • cpm add <path> & local dependencies (Phase 37): cpm add <folder> reads the dependency's cardinal.toml ([package] name), validates it, and records it in the current package's cardinal.toml under a new [dependencies] section (created if absent; an existing entry for the same name is replaced). Format: [dependencies] then my_lib = "path/to/my_lib" (backslashes normalized to /). cpm run and cpm build now parse [dependencies] and, for every entry, append -I <path> to the g.exe command line before --run / --out — the compiler's import resolver checks -I directories (tier 2) after the importing file's own directory, so Import "my_lib.g" resolves to <dep>/my_lib.g. The compiler-side plumbing: compile_full(source, base_dir, extra_imports, target, out_c) is the new lib.rs entry; compile_at / compile_for_target delegate with &[]; locate_import / resolve_modules / resolve_import thread extra_imports through both top-level and nested import resolution; g <file.g> -I <dir> (repeatable, also -g mode) is parsed by src/main.rs into import_dirs. Verified live: cpm add on a mylib package, then cpm run executing Greet with "CPM" from the dependency, and cpm build writing build/main.c containing the dependency's routine.
  • --target routing (Phase 36): g <file.g> --target <t> (also -g mode) — c99 (default, unchanged emit/emit_desktop path) or baremetal-x86 / baremetal-arm64 / baremetal-avr / baremetal-esp32 / baremetal-quantum. src/main.rs parses the flag and calls the new compile_for_target (lib.rs), which runs the full pipeline (lex/parse/types/ownership/capabilities/MIR/SSA/optimizer) and then routes codegen to codegen::emit_baremetal instead of codegen::emit. Unknown targets → G-072.
  • Universal & Quantum Bare-Metal backends (Phase 36): codegen::emit_baremetal(target, prog, profile, board, graph) dispatches to five backends that need no C compiler and no headers:
    • x86-64 (baremetal-x86, export .s): GNU as, Intel syntax, Linux System V ABI. Print "Hello" emits the canonical template — .rodata msg: .ascii "Hello\n", _start with mov rax,1; mov rdi,1; lea rsi,[msg]; mov rdx,len; syscall then mov rax,60; xor rdi,rdi; syscall. Multiple prints emit one sys_write each (msg, msg0, msg1, …). Since Phase 39, _start: opens a 64-byte stack frame (push rbp / mov rbp, rsp / sub rsp, 64) and every variable lives in a qword [rbp-8], [rbp-16], [rbp-24], … slot (8 bytes each, unlimited count) — Set X to 42 emits mov qword [rbp-8], 42; the frame is torn down with mov rsp, rbp / pop rbp before sys_exit. Lengths are bytes + 1 for the appended \n.
    • ARM64 (baremetal-arm64, export .s): AArch64 template — mov x8,#64; mov x0,#1; ldr x1,=msg; mov x2,len; svc #0 then mov x8,#93; mov x0,#0; svc #0. Since Phase 42, _start: opens a stack frame (stp x29, x30, [sp, -16]! / mov x29, sp / sub sp, sp, 64) and every variable lives in a [sp, 16], [sp, 24], [sp, 32], … slot (8 bytes each, unlimited count; the first 16 bytes hold the saved frame pointer x29 / link register x30) — Set A to 1 emits mov x9, #1 / str x9, [sp, 16]; the frame is torn down with add sp, sp, 64 / ldp x29, x30, [sp], 16 before sys_exit. This matches the x86-64 stack-frame architecture (Phase 39), so both major ISAs now support any number of variables with no .data spill.
    • Bare-metal control flow (Phase 38) & stack frames (Phase 39 x86-64 / Phase 42 ARM64): While loops and If conditionals with plus/minus arithmetic run natively on x86-64 and ARM64. x86-64 variables are stack slotsWhile X is less than 5 do Set X to X plus 1. emits (inside the frame) .L_start_1: / cmp qword [rbp-8], 5 / jge .L_end_1 (inverse jump) / inc qword [rbp-8] / body / jmp .L_start_1 / .L_end_1:; If X is greater than 5 then Set X to X minus 1. emits cmp qword [rbp-8], 5 / jle .L_end_1 / dec qword [rbp-8] / .L_end_1:. Variable-vs-variable comparisons and copies route the lhs/source through the rax scratch register (mov rax, qword [rbp-8] / cmp rax, qword [rbp-40] — x86 has no mem-mem cmp/mov/add/sub encodings). ARM64 uses the same stack-frame architecture (Phase 42)Set X to 42 emits mov x9, #42 / str x9, [sp, 16]; While A is less than E loads both slots into the x9/x10 scratch registers before comparing (ldr x9, [sp, 16] / ldr x10, [sp, 48] / cmp x9, x10 — AArch64 has no mem-mem cmp); Set A to A plus 1 emits ldr x9, [sp, 16] / add x9, x9, #1 / str x9, [sp, 16]; the inverse branches are b.ge/b.le/b.gt/b.lt/b.ne/b.eq and loops jump with b. The emit-time label-resolution pass (emit_bm_flow/bm_cmp_line/print_label) names the loop-back/else targets .L_end_N at the CondJump, the then-block label from the following SsaOp::Label (skipped when the jump already fell through), and any remaining targets .L_N as the Jump ops are emitted; spurious labels fall away in the final sweep. slot_op(arch, slot) produces the operand for a slot — qword [rbp-N] on x86, [sp, 16+8*slot] on ARM64 — so bm_mov_imm/bm_inc/bm_dec/bm_add_imm/bm_sub_imm/bm_cmp/bm_mov_reg/bm_add_reg/bm_sub_reg all operate on the same operand strings.
    • AVR (baremetal-avr, export .c): raw MMIO C, /* BARE-METAL MMIO for AVR */, no #include. Set LED on. emits *(volatile unsigned char*)0x24 = 0x20; (DDRB) + *(volatile unsigned char*)0x25 = 0x20; (PORTB) + while(1) {}. Trigger: a slot named LED (or a SetPin/TogglePin op); otherwise just the spin loop.
    • ESP32 (baremetal-esp32, export .c): raw MMIO C — *(volatile unsigned int*)0x60004004 = 0x04000000; (GPIO_ENABLE_W1TS) + *(volatile unsigned int*)0x60004008 = 0x04000000; (GPIO_OUT_W1TS) + while(1) {}. Same LED trigger.
    • Quantum (baremetal-quantum, export .qasm): OpenQASM 2.0 — OPENQASM 2.0; qreg q[1]; creg c[1]; h q[0]; measure q[0] -> c[0]; (Set Qubit to superposition. maps to a Hadamard gate).
    • Backends extract Print messages (SsaOp::PrintDefConstStr → string pool) and numeric assigns (Store of ConstNum → slot name) from the SSA program; nested op bodies (Repeat/ForEach/Comptime/Parallel) are walked recursively. While/If need no special walk — they lower to flat Label/CondJump/Jump ops in the main stream, which the emit loop handles directly.
  • Bare-metal Target words & Set <x> on. (Phase 36, parser): parse_target accepts the modifier baremetal plus arch words — x86_64/x86/x86-64/arm64/aarch64/quantumProfile::Kernel, avr → Embedded/board avr, esp32 → Embedded/board esp32 — so Target G Baremetal avr. compiles warning-free (the pipeline runs under the mapped profile; --target picks the backend). parse_set accepts a bare trailing on/off (no to): Set LED on.Assign { LED: Bool(true) } (previously G-070). The quantum example's Set Qubit to superposition. parses via the silent prose fallback (string literal) — the quantum backend ignores statement content by design.
  • Modules & I/O: Import "file.g", Read file, Write file (Returns Result, G-050 if unhandled). Import resolution (Phase 31) tries, in order: (1) the path next to the importing file (<file_dir>/<path>), (2) the directory holding the g executable (<g_exe>/<path> — the standalone dist/ layout, so Import "stdlib/net.g" finds dist/stdlib/net.g from any working directory), (3) the repository root (<repo>/<path> — dev layout, cargo test / target/release/g). The same three tiers apply to nested imports (relative to the importing module's directory). G-072 if no tier yields a readable file.
  • Concurrency: In parallel do, Spawn Task, Channel of, Send to, Receive from. (Move semantics G-011, Data race G-060).
  • ESP32: FreeRTOS tasks/queues, Wi-Fi/TCP HAL.
  • Game & Web: ECS (Create Entity, Add Component), WebAssembly (Emscripten).
  • Metaprogramming: At compile time blocks, Size of, Type of.
  • Advanced Data: Routine Returns (returning <Type>), List Indexing/Mutation (Value of X at Y), Tables (Table of K to V), Text Ops (Split, Substring, contains), Table Iteration (For each Key in), Safe Lookups (Returns Optional<T>), String Escapes (\n, \t, \r, \", \\ in string literals, resolved at tokenization and re-escaped as C literals by codegen).
  • Recursive Data & Enums: Recursive Records (forward typedefs), Custom Enums (Define enum with If is <Variant>).
  • FFI: Import C function, In unsafe context do ... End unsafe. (Note: End unsafe is the ONLY exception to the "no End keyword" rule). Calls use Call c function <name> with <expr>, <expr>. (comma-separated args). The Desktop backend emits #include <header> plus an extern <ret> <name>(<params>); declaration for each declared FFI function; the ESP32 backend does the same in its own emitter.
  • Running Generated C: g <file.g> --run (or the identical alias gufran <file.g> --run) locates the C compiler in this order: (1) a bundled TCC at <g_exe>/bin/tcc[.exe] (or <g_exe>/../bin/tcc[.exe] when g sits inside bin/) — resolved via std::env::current_exe() so it works from any working directory; (2) tcc/gcc/clang/cc on PATH (probed with --version then -v). Include paths are absolute and layout-aware: bundle mode passes -I <bundle>/runtime -I <bundle>/bin/include; repo mode passes -I <repo>/runtime. Platform link flags (Phase 30): Windows appends -L <runtime_dir> -lws2_32 (the repo ships ws2_32.def in runtime/, since the bundled TCC has no ws2_32.lib/lib/ws2_32.def); Linux/macOS append -lpthread. G-072 if no compiler is found.
  • Language Server — g-lsp (Phase 40): dist/g-lsp.exe (built from src/lsp.rs, shipped by build_release.ps1) is a minimal Language Server Protocol server speaking JSON-RPC 2.0 over stdio with Content-Length framed messages — the same wire format VS Code expects. It reads framed messages from stdin (headers Content-Length: N until a blank line, then exactly N body bytes; EOF exits cleanly), handles initialize (replies with {"capabilities":{"textDocumentSync":1}} — full-document sync), textDocument/didOpen and textDocument/didChange (full text from textDocument/text, or incremental contentChanges[0].text), shutdown, exit, and responds null to unknown requests so clients never hang. On every open/change it runs the document through g_lang::analyze_for_tooling(source, None) — the full diagnostic pipeline (parse, import resolution, type check, ownership, capabilities, MIR, SSA, optimizer) and the retained SymbolTable, so tooling works even on documents with fatal errors, where compile_at returns Err and would discard the symbols — and converts the G-### diagnostics into a textDocument/publishDiagnostics notification: G Spans (1-based line/col) map to LSP 0-based ranges (start at line-1, col-1, one character wide), severities map 1=Error / 2=Warning / 3=Info, and each diagnostic carries code (e.g. G-020), source: "g", and the message. Since Phase 40 it also serves IDE features from the symbol table:
    • Autocomplete (textDocument/completion): the identifier under the cursor (word_at — the alphanumeric/underscore run containing the position, clamped to the document) filters the symbol table; variables return {"label": X, "kind": 6, "detail": "Number"} (CompletionItemKind::Variable), routines {"kind": 3} (CompletionItemKind::Function), and the core keywords Print, Set, If, While, For each, Target are always offered ({"kind": 14}, CompletionItemKind::Keyword), matched by their first token so typing For suggests For each. Items are sorted alphabetically; an empty word (whitespace/past-EOF position) returns everything.
    • Hover (textDocument/hover): looks the word up in the symbol table and returns Markdown contents**Variable:** X \n**Type:** Number for variables (type via Kind::name()), **Routine:** Count for routines — or null when the word is not a known symbol.
    • Goto-definition (textDocument/definition): returns a single Location ({"uri": ..., "range": ...}) whose 0-based start is the Span where the variable was first declared (Set X to ..., Create/declarations, For each iterators, Read file ... into X, routine parameters), one column wide. The first declaration wins, so later re-Sets keep pointing at the original site.
    • VS Code configuration: a trivial extension (package.json with a language server activation event) starts the server via { "command": "dist/g-lsp.exe", "args": [] } in its contributes.languages / vscode.languageserver section; with the G file open, squiggles appear under each G-### error/warning on every keystroke (didChange) and on open (didOpen), Ctrl+Space offers variables/routines/keywords, hovering shows the type, and F12 jumps to the declaration. Verified end-to-end with examples/completion_test.g (Target G Desktop. Set X to 42. Print X.): a live stdin session returned {"label":"X","kind":6,"detail":"Number"} for completion at the X of Set X to 42 (0-based line 0, character 22), {"contents":{"kind":"markdown","value":"**Variable:** X\n**Type:** Number"}} for hover, and the Location {"range":{"start":{"line":0,"character":18},...},"uri":"file:///completion_test.g"} for definition (the span of the Set X to 42 statement); the directive's literal position (line 1, column 10) is past EOF and degrades gracefully — completion returns all symbols plus the six keywords, hover returns null.
  • HTTP (Stage 5 stdlib): examples/http.g declares g_http_get (Text → Text) from runtime/http_shim.h; the shim (runtime/http_shim.c) performs a libcurl GET with follow-redirects and a 10s timeout, captures the body into a process-lifetime arena (identical representation to G's arena strings), frees all libcurl memory via curl_easy_cleanup, and returns the body as a G Text. Bodies > 64 KiB return empty.
  • TCP Server (Phase 30 stdlib): examples/server.g is a native TCP web server. It imports nine g_tcp_* functions from runtime/tcp_shim.h — a header-only shim (definitions live in the header, so --run compiles it inline with the generated program; no separate .c to link). On Windows the shim declares the minimal Winsock2 surface itself (WSAStartup, socket, bind, listen, accept, recv, send, closesocket) because the bundled TCC's winapi/ ships no winsock2.h, and links them against ws2_32 via the repo's runtime/ws2_32.def import lib (tcc resolves DLL imports through .def files) plus -lws2_32. All G Numbers map to C double, so every wrapper speaks double and reports failure as -1.0. StartServer(PortNumber) runs WSAStartup → socket → bind(0.0.0.0:Port, htons byte-swapped manually) → listen(backlog 8) → an infinite While 1 is 1 do ... end. accept loop that recv's the request into a 64 KiB static buffer, Prints it, replies HTTP/1.1 200 OK with a Content-Length: 11 body Hello world (real CRLF via the Phase 30 \r escape), and closes. Verified end-to-end: dist\g.exe examples\server.g --run then browsing http://localhost:8080 returns Hello world and the server log shows the raw GET / HTTP/1.1 request. Note: g_tcp_bind's two Number parameters are named Port and PortNumber in the import because the FFI extern declaration must not collide with the shim's internal helper names; C parameter names are cosmetic anyway.
  • Standard Library net.g (Phase 31): stdlib/net.g is the first pure-verse Standard Library module — it encapsulates the Phase 30 TCP FFI so user code never touches Import C function or In unsafe context (Quirk 4-style shim imports live only inside the module). It re-declares the nine g_tcp_* functions from tcp_shim.h and exports five idiomatic routines: NetStart(Port) (Winsock init → socket → bind → listen; returns the listening descriptor or -1), NetAccept(Socket) (client descriptor or -1), NetReceive(Client) (the buffered request Text), NetSend(Client, Data), and NetClose(Socket). Modules declare no Target (a spliced module's Target would be discarded anyway — the importing program's directive wins). On Windows the generated program links ws2_32 via the -lws2_32 flag and runtime/ws2_32.def (Phase 30); a server running under --run executes inside the tcc process (tcc -run runs in-process, so the LISTENING socket's PID is tcc.exe, whose parent is g.exe) — since Phase 32, killing g.exe (Ctrl+C/Stop-Process) automatically kills tcc.exe via the --run kill-on-close job object, freeing the port.
  • High-Level HTTP Framework in net.g (Phase 37): two new pure-verse routines on top of the five TCP routines, so a web app never touches raw request strings. HttpSendResponse with Number called Client, Number called Status, Text called ContentType, Text called Body. builds the full response — "HTTP/1.1 " followed by Text of Status followed by "\r\nContent-Type: " followed by ContentType followed by "\r\nContent-Length: " followed by Text of Length of Body followed by "\r\n\r\n" followed by Body (real \r\n via string-literal escapes) — and sends it with Execute NetSend with Client, H.. HttpGetPath with Text called Request returning Text. extracts the path: Set Parts to Split Request by " ", then If Count of Parts is greater than 1 then Return Value of Parts at 1. (the 0-indexed second element of GET /api HTTP/1.1/api), Return "/". as the malformed-request fallback. Compiler bug fixed en route (found live): mir.rs expr_kind had no Concat arm, so Set H to "..." followed by ... inside a routine inferred the local as Kind::Num — codegen emitted double g_s4 = 0; /* H */ and assigned a g_str into it, and TCC died with error: invalid aggregate type for register load at the first --run. Added Expr::Concat(_, _, _) => Kind::Str (mir.rs, matching the existing init_kind arm in ownership.rs). Locked by new compile tests: http_framework_path_parsing_and_response_codegen, http_framework_string_local_infers_str_kind, extra_import_dirs_resolve_cpm_dependency_imports.
  • Idiomatic Web App (Phase 37): examples/web_app.gTarget G Desktop. + Import "stdlib/net.g". + NetStart with 8080, If Server is -1 → print failure + bare Return., else While 1 is 1 do: NetAcceptif Client is not -1 then NetReceiveHttpGetPathif Path is "/api"HttpSendResponse with Client and 200 and "application/json" and "{\"status\":\"ok\"}", otherwise → 404 "text/plain" "Not Found"NetClose. All statements are the directive's exact verse (comma connectors, and-joined arguments, narrative closers); the only deviations are the outer Define routine main. scope (the language rejects a top-level Return. — G-010 'return' is only allowed inside a routine — documented rule) and the dropped top-level trailing Finally. (unneeded since Phase 38 — EOF closes the routine silently). The server starts silently (it only Prints on bind failure) and binds 8080. Verified live: dist\g.exe examples\web_app.g --run binds 8080; Invoke-WebRequest http://localhost:8080/api200 body {"status":"ok"}; http://localhost:8080/other404 exception; server logs zero errors and survives both requests.
  • Print flushing (Phase 31): g_print_str now calls fflush(stdout) so server request logs appear immediately even when stdout is redirected to a file (block-buffered C stdout previously hid them until buffer fill/exit).
  • JSON (Stage 5 stdlib): examples/json.g implements a recursive-descent JSON parser in pure verse. ParseJson (Text → JsonValue) dispatches through ParseValue (string/number/bool/null/object/array), ParseObject builds a Table of Text to JsonValue via Add K to Obj with value V, ParseArray builds a List of JsonValue, ParseString scans quoted text with verbatim \x escape pass-through, ParseNumber scans -digits.digits and constructs NumberVal of Float via Number of Out, and SkipWs skips \n\t. Every parser routine returns a result record (ParseResult = {JsonValue Payload, Number Next} / TextResult) carrying the value AND the next position — pure-G functional state threading (see Quirk 7). Describe prints any JsonValue through an exhaustive variant chain, rendering numeric payloads via Text of value of V as NumberVal (i.e. g_format_num).
  • Number to Text Conversion (Text of <Number>): Text of X (X Number or Float) formats a number into text and yields a Text. Lowers to the runtime helper g_format_num(g_tN, &arena)snprintf(buf, 64, "%.17g", val) (round-trip safe, e.g. 42"42", 3.5"3.5") returning an arena-owned, NUL-terminated g_str. Type-checked: Text of <non-Number> is G-020.
  • Text to Number Conversion (Number of <Text>): Number of X (where X is Text) is an expression that parses the leading number of the string and yields a Number. Desktop/ESP32 lower it to strtod(X.p, NULL); AVR uses atof(X.p) — both handle fractional text like "3.5" (Phase 26 replaced the Phase 25 strtoll/atoi, which truncated at the decimal point). Safe because every arena string is NUL-terminated (g_arena_str writes a terminator and reserves len + 1 bytes). Empty or non-numeric text yields 0; trailing junk is ignored. Type-checked: Number of <non-Text> is G-020.
  • Cross-Platform Runtime (Phase 27): runtime/g_rt.h compiles on Windows (_WIN32windows.h + Sleep(ms)), Linux/macOS (__unix__/__APPLE__unistd.h + usleep(ms * 1000)), AVR (__AVR__ → avr-libc util/delay.h + _delay_loop_2; no POSIX headers), and ESP32 (ESP_PLATFORMesp_timer.h + esp_timer_get_time busy wait). The arena allocator and all string/vector/map operations use only C99 stdio.h/stdlib.h/string.h and are fully OS-agnostic.

4. The Bootstrap Protocol (Stage 6 Complete)

  • Stage 1: Rust compiler core (Done).
  • Stage 2: examples/lexer.g — Tokenize routine in pure verse. (Done).
  • Stage 3: examples/parser.g — ConceptNode synthesis from tokens. (Done).
  • Stage 4: examples/validator.g — Semantic Validator consuming List of AstNode. (Done).
  • Stage 5: examples/codegen.g — C99 emitter consuming List of AstNode. (Done).
  • Stage 6: examples/bootstrap.g — a C99 emitter for a subset of G, written in pure verse, that compiles examples/test_bootstrap.g and emits output.c; the emitted C is then compiled with the bundled TCC and executed to print BootstrapSuccess. (Done — Phase 35. Moved from the repo root into examples/ during the Phase 45 v1.0 folderization.)

Stage 6 loop, end-to-end (Phase 35)

  1. dist\g.exe examples\bootstrap.g --out bootstrap.c — compiles the self-hosting emitter in pure verse: 0 diagnostics, exit 0.
  2. dist\bin\tcc.exe -I dist\runtime bootstrap.c -o bootstrap.exe — builds the standalone emitter.
  3. .\bootstrap.exe examples\test_bootstrap.g — prints bootstrap: input examples\test_bootstrap.g then bootstrap: wrote output.c exactly once, exit 0.
  4. output.c contains main() with double x = 42;, if (x == 42) { + printf("%s\n", "BootstrapSuccess"); + } — correct block closure.
  5. dist\bin\tcc.exe -I dist\runtime output.c -o test_output.exe then .\test_output.exe → prints BootstrapSuccess, exit 0.

examples/bootstrap.g supports Set, Print, If, and end tokens in its input subset and generates double variable declarations, if guards, and printf lines. It exercises the Phase 35 language surface itself: the , and if statement connector, the create expression (set Tokens to Create List of Text), for each over nested splits, while loops with narrative After. closers, Otherwise if ... Otherwise chains, Execute <routine> with <args> calls, and the deferred-closer rule below.

5. CRITICAL ARCHITECTURAL QUIRKS & KNOWN BUGS

READ THIS CAREFULLY.

Host Toolchain Note (updated Phase 45): G is fully standalone. dist/ bundles the working TCC (tcc.exe + libtcc.dll — tcc.exe dynamically links it, a missing DLL aborts with 0xC0000135 — + include/ + lib/) plus runtime/ with g_rt.h, cli_shim.h (Phase 33 — CLI arguments & env), fs_shim.h (Phase 34 — file system & time), tcp_shim.h, math_shim.h + regex_shim.h (Phase 41), os_shim.h (Phase 44 — process execution & file size), and ws2_32.def. build_release.ps1 reproduces dist/ from cargo build --release (the cargo call runs via cmd /c so TCC's stderr progress can't trip PS 5.1's $ErrorActionPreference = "Stop"): on this machine it copies the TCC install at C:\Users\Laptop\tcc\tcc; on CI/fresh machines (no such folder) it downloads the official tcc-0.9.27-win64-bin.zip from Savannah and expands it. build_release.sh (Linux/macOS) mirrors the script and compiles TCC 0.9.27 from source (wget/curl the tarball, ./configure && make, copy tcc + include/ + lib/ into dist/bin/); on Apple Silicon the TCC build fails (0.9.27 has no darwin-arm64 target) and the script warns instead of failing - g --run then falls back to tcc/gcc/clang on PATH (macOS ships clang). Verified: with the system TCC folder renamed off PATH, dist\g.exe examples\json.g --run compiles and executes using only the bundle (G, true, 3.5) — and dist\g.exe examples\server.g --run serves http://localhost:8080 (Hello world in a browser). The tcc-api in-memory feature still fails at link time (ld: cannot find -ltcc via x86_64-w64-mingw32-gcc) and is unused; the subprocess path is used instead. The e2e --run tests run for real when a compiler is on PATH and skip otherwise. Windows/TCC has no <regex.h> (probed: tcc -E on #include <regex.h>error: include file 'regex.h' not found) — regex_shim.h therefore ships a POSIX regcomp/regexec implementation for non-Windows and a bounded fallback on Windows (see Phase 41 ledger).

--run child cleanup (Phase 32): on Windows, --run now spawns the child (tcc.exe -run, or the compiled .exe) via Command::spawn and immediately assigns it to a kill-on-close Windows Job Object (CreateJobObjectW + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, raw kernel32 FFI in src/main.rs jobctl module — no external crates). The job handle lives only in g.exe, so whenever g.exe dies — Ctrl+C, Stop-Process, or crash — the OS terminates every process in the job. A --run TCP server therefore can no longer orphan its tcc.exe (which holds the LISTENING socket in-process, see Phase 31 note) and leave port 8080 stuck. Verified live: start dist\g.exe examples\api_server.g --run, GET /api → 200, Stop-Process the g.exetcc.exe dies within 2s and Get-NetTCPConnection -LocalPort 8080 -State Listen returns 0 immediately; a fresh --run rebinds the port right away (TIME_WAIT entries from test clients remain for a few minutes but never block the rebind).

Quirk 4: FFI Limits (Phase 23 findings)

  • No pointer types: G's type system (number, float, string, bool, pin) cannot express void *, CURL *, or const char *; Text maps to the g_str struct passed by value.
  • No variadics: FFI arity is exact (G-020 "FFI function '{}' takes {} argument(s), got {}"), and every called FFI function must declare returning <Type> (G-020 "FFI function '{}' does not return a value" otherwise).
  • Raw libcurl is not callable: declaring curl_easy_init/curl_easy_setopt/curl_easy_perform/curl_easy_cleanup via FFI would emit extern long long ... prototypes that conflict with the real declarations in curl/curl.h (and varargs/write-callbacks are unexpressible). examples/http.g therefore wraps libcurl in runtime/http_shim.c (a C shim with correct prototypes), which the directive explicitly permits ("or use a wrapper").
  • Call C function is an expression: it must appear as an Assign RHS (Set X to Call c function ...); a bare Call c function curl_easy_cleanup with X. statement would fail typechecking since void-returning FFI calls are rejected.
  • Routine calls in an Assign RHS need Execute/Call: Set Page to Get with Url. (no keyword) silently parses to the string literal "Get with Url" via the prose fallback — always write Set Page to Execute Get with Url.

Quirk 5: Enum Variants Require Payloads (Phase 24 findings)

  • Every enum variant MUST declare of <Type>: Define enum E with A of Text and B of Text. — a payload-less variant is rejected with G-070 variant 'NullVal' requires 'of <Type>' and its recovery truncates the rest of the enum declaration (later variants silently vanish → cascading G-010 unknown-variant errors). NullVal of Text (payload unused, tag check If V is NullVal still works) is the workaround.

Quirk 6: Enum Variant Matches Are Exhaustiveness-Checked

  • If V is StringVal then ... end. on a multi-variant enum fails with G-010 non-exhaustive match on enum 'JsonValue': variant 'X' not covered. Every enum test needs a full Otherwise If V is <next> ... chain ending in a plain Otherwise (the checker recurses only when each Otherwise holds exactly one If on the same variable). This forces full chains even when only one branch is meaningful.

Quirk 7: Routines Cannot See Module-Level Variables

  • Routine environments are seeded with parameters + module channels only (types.rs check_program). A module-level Pos is 0. is invisible inside a routine (G-010 undeclared identifier 'Pos'), and by-value records/tables mutated on a param do not propagate back to the caller (struct copies; heap reallocs invalidate the caller's view). Shared mutable state across recursive routines is therefore IMPOSSIBLE; examples/json.g threads state functionally: every parse routine takes (Src, At) and returns (Payload, Next) records.

Quirk 1: Narrative Transitions & parse_body

  • parse_body returns None on an unrecoverable parse failure and Some(()) at EOF. Since Phase 38, EOF mid-block is NOT an error: reaching the end of the file implicitly closes every open scope (finish_block returns silently when the cursor is at EOF, before the G-071 lookup; parse_body returns Some(()) at EOF so ?-carrying callers like parse_while/parse_if keep the fully-parsed statement instead of dropping it). Mid-file implicit closure (a block followed by an unrelated statement, e.g. Define record User with Number called Age. followed by a Print) still emits G-071 implicit scope closure assumed at end of <block>.
  • Rule: a .g file may now simply run to the end — the final After./Finally./end. is optional. Closers are still required between blocks, exactly as in Quirk 8.

Quirk 8: Deferred Closers & Otherwise if Chains (Phase 35 refinement)

  • An Otherwise if ... Otherwise ... chain is a single statement; only the chain's root if owns the block closer. A chain defers finish_block so the enclosing block's closer (After./Finally./end.) closes the whole chain: For each X in Y do If A then ... Otherwise If B then ... Otherwise ... After. — the After. closes the For each, and the chain root never closes itself.
  • Exception (the fix that made Stage 6 work): if the token after the chain is an explicit end., the root if must consume it (deferral applies only to narrative transitions). Implemented as if !chained || self.at_word("end") { finish_block } in parse_if, and the same guard in parse_otherwise_body's chain-link branch and the , and if arm.
  • Subtle trap (found while fixing validator.g): finish_block on a lowercase end must also consume the trailing . (self.accept_sym('.')). Otherwise a chain link that consumed its end leaves a dangling . token, and the enclosing chain root's at_word("end") lookahead fails — the for-each/routine then steals the wrong end., producing G-071 dangling 'end', G-070 unrecognized concept 'Finally', and G-010 'return' is only allowed inside a routine on sources with per-level end. closers (e.g. examples/validator.g).

Quirk 2: Record Declaration Order

  • Main slots are ordered by first-use, not declaration order. Tests must not assume slot indices (g_s0, g_s1) map directly to the order of Create statements in source.

Quirk 3: AVR/ESP32 Text Ops

  • g_str_split, g_str_substring, g_str_contains are Desktop-only. On AVR/ESP32, they emit compile-safe stubs. Do not use them in logic that must execute on Micro profiles.

Quirk 9: Greedy Argument Evaluation (Phase 44 findings)

  • Routine arguments parsed via with <expr> greedily consume trailing conjunctions like followed by. Execute FileSize with TccPath followed by " bytes" binds the concatenation INSIDE the argument — the generated C passes concat(TccPath, " bytes") as the Path (found live in Phase 44: TCC exists. Size: -1). This is correct greedy natural-language parsing, not a bug: the argument is a full expression up to the statement terminator. To pass a complex concatenated expression as an argument, assign it to an intermediate variable first (e.g. Set SizeStr to Text of Size followed by " bytes". Execute Print with SizeStr.).

6. Phase Ledger (Phase 45 — v1.0 Release, Folderization & Documentation)

Task: Prepare the repository for a public v1.0 GitHub release: folderize into a clean open-source structure, add licensing, a changelog, a polished landing page, and verify the dist/ folder as a pristine standalone artifact.

  1. Folderization (done): root cleaned of scratch outputs (baremetal_*.s/.c, quantum.qasm, _space.g deleted; generated api_server.c/json.c/validator.c/web_app.c removed from examples/); bootstrap.g moved from the repo root into examples/ (Stage 6 ledger updated with the new paths). .gitignore updated: /dist, root-scoped scratch patterns (/*.c, /*.s, /*.qasm, …) so tracked sources under runtime//examples/ stay trackable. The final layout matches the directive's tree exactly (docs/rfc-0001.md was MISSING — the README/MANUAL referenced it but the file did not exist — and has been created from the implemented grammar table + language charter; see item 4).
  2. Licensing (done): LICENSE — MIT, Copyright (c) 2026 SK GUFRAN AHMED (the authoritative file created in GitHub Desktop; the interim "Gufran / Cardinal System" draft was superseded by it, and the README credit line was aligned to the real holder); README links it and states it in the header.
  3. Changelog (done): CHANGELOG.md[1.0.0] - The Cardinal Release, grouped per the directive: Core Compiler (Phases 1-5), Concurrency & ECS (6-8), Metaprogramming (9-10), Data Structures & Stdlib (14-15, 24, 41, 44, 44.1), Bare-Metal Revolution (36, 38, 39, 42, 43), Tooling & IDE (22, 29, 30, 40), Self-Hosting (35), Infrastructure.
  4. README overhaul (done): new landing page — ASCII architecture diagram (Prose → Lexer → Semantic Graph → HIR → Type Unification → Ownership Gates → MIR → SSA → Optimizer → C99 / x86-64 / ARM64 / OpenQASM), status banner (Phase 45 Complete (v1.0 Release), 545 tests, MIT), Quick Start via the dist/ folder, a 19-item feature checklist, the folderized repository tree, example programs, CLI reference, and diagnostics. docs/rfc-0001.md created (grammar table + charter) to satisfy the mandated layout and the existing MANUAL/README references.
  5. Standalone verification (done): build_release.ps1 regenerated dist/; system TCC was already absent from PATH (Get-Command tcc → none) and dist\g.exe examples\build_script.g --run used only the bundle: Found 29 files (32 before folderization: -4 generated .c artifacts, +1 bootstrap.g moved into examples/) / TCC exists. Size: 23552 bytes / tcc version 0.9.27 (x86_64 Windows) / TCC exit code: 0, exit 0. dist/ listing confirmed: g.exe, gufran.exe, cpm.exe, apm.exe, g-lsp.exe, bin/, runtime/, stdlib/.
  6. Tests & docs (done): no test path changes were required (no .rs reference to bootstrap.g or other moved files; include_str! paths are relative). cargo build --release zero warnings; 545 tests green (337 unit + 14 g-lsp + 185 compile + 9 e2e). MANUAL version → Phase 45 Complete (v1.0 Release); repo layout sections rewritten for the folderized state.
  7. Cross-platform build scripts (done): build_release.sh (Linux/macOS) mirrors build_release.ps1: cargo build --release, assembles dist/ (g, gufran, cpm, apm, g-lsp + runtime shims + stdlib), then downloads tcc-0.9.27.tar.bz2 from Savannah and compiles it (./configure && make) into dist/bin/ (tcc + include/ + lib/). On Apple Silicon the TCC build fails (0.9.27 has no darwin-arm64 target) and the script warns and continues — g --run falls back to tcc/gcc/clang on PATH (macOS ships clang). build_release.ps1 gained a CI path: when C:\Users\Laptop\tcc\tcc is absent it downloads the official tcc-0.9.27-win64-bin.zip and expands it (recursive tcc.exe search tolerates the bundle's internal layout), so GitHub Actions windows-latest needs no pre-installed TCC.
  8. GitHub Actions CI/CD (done): .github/workflows/release.yml — triggers on v* tags (plus workflow_dispatch), matrix [ubuntu-latest, windows-latest, macos-latest], dtolnay/rust-toolchain@stable, runs the platform build script, zips dist/ (Compress-Archive on Windows, zip on Unix), and uploads the zip to the GitHub Release via softprops/action-gh-release@v2 (permissions: contents: write; fail_on_unmatched_files: true catches a missing zip). Pushing tag v1.0.0 therefore produces three release assets: g-language-ubuntu-latest.zip, g-language-windows-latest.zip, g-language-macos-latest.zip.

7. Phase Ledger (Phase 44 — OS Process Execution & Filesystem Wrapper)

Task: Expand the standard library beyond networking and math: stdlib/os.g (run shell commands, query file sizes) and stdlib/fs.g (directory listing, existence) as pure-verse routines over a new header-only C shim, verified through a build-script example that inspects the real TCC bundle.

  1. Shim (done): runtime/os_shim.h — header-only, external-linkage (the generated program emits extern double ... AFTER the include, Phase 41 contract): double g_system(g_str cmd) runs the command via system() and returns the exit code as a double (the FFI maps G's Number to C double); double g_file_size(g_str path) returns the byte size via FindFirstFileA on Windows (nFileSizeHigh/Low) and stat() on POSIX, -1.0 when the file is missing. Strings are normalised to NUL-termination through a private arena (same pattern as regex_shim.h; generated literals are arena-backed g_str). Windows cmd.exe handling (Phase 44.1): /\ rewriting, and commands containing spaces get their executable path quoted — the longest prefix ending at a space that names an existing non-directory file (the whole command is also a candidate, so a spaced exe with no arguments works). Wrapping the WHOLE command in one pair of quotes does NOT work: cmd.exe /c strips the outer quotes and re-splits at the first space ("C:\Program Files\app.exe -v" fails; verified empirically through system() and direct cmd /c). No existing file found → the first token is quoted (cmd builtins like echo). Commands already starting with " pass through untouched; POSIX passes through unmodified (sh tokenises differently; quoting would break ~//globs).
  2. Stdlib (done): stdlib/os.gImport C function g_system from "os_shim.h" with Text called Cmd returning Number. + g_file_size ... with Text called Path returning Number., wrapper routines RunCommand with Text called Cmd returning Number. and FileSize with Text called Path returning Number. using the proven In unsafe context do Set R to Call c function ... with Cmd. After. Return R. Finally. shape. stdlib/fs.gListDir with Text called Path returning List of Text. (Return List directory Path. Finally.) and Exists with Text called Path returning Flag. (Return File exists Path. Finally.) — pure wrappers over the Phase 34 builtins. The directive's After. (instead of End unsafe.) parses cleanly as the unsafe-scope closer.
  3. Example & verification (done): examples/build_script.g — the directive's build-script verse: imports stdlib/os.g + stdlib/fs.g, ListDirs examples/, checks dist/bin/tcc.exe exists, prints its real byte size, runs dist/bin/tcc.exe -v, prints the exit code (exact file content below). dist\g.exe examples\build_script.g --run (TCC -run, real execution) prints: Found 29 files (32 before the Phase 45 folderization removed 4 generated .c artifacts from examples/ and moved bootstrap.g in) / TCC exists. Size: 23552 bytes / tcc version 0.9.27 (x86_64 Windows) / TCC exit code: 0, exit 0. One correction to the directive's verse was required (live find): Text of Execute FileSize with TccPath followed by " bytes" binds followed by INSIDE the with argument (the generated C passes concat(TccPath, " bytes") as the Path → -1) — the concatenation must be split: Set TccSize to Execute FileSize with TccPath. then Print "TCC exists. Size: " followed by Text of TccSize followed by " bytes". Also, the non-TCC --run path keeps -lm (Phase 41); no new link flags are required for stat()/system() (msvcrt).
  4. Toolchain (done): build_release.ps1 copies runtime/os_shim.h into dist/runtime/ (the stdlib glob already ships os.g/fs.g into dist/stdlib/). src/main.rs needed no changes: run_c_via_subprocess/run_generated_c already pass -I <runtime> (bundle mode), and FFI shim includes follow the Phase 41 contract — #include <os_shim.h> + extern are emitted when os.g is imported, whether or not the wrapper is called.
  5. Tests & docs (done): 4 new compile tests — os_fs_wrappers_lower_to_shim_calls (os_shim.h include, all four g_r_* wrappers, g_system(g_t, g_file_size(g_t, g_file_exists(g_t, g_list_dir(&arena, g_t), os_shim_emitted_on_import_even_if_os_routine_unused (documents the import-not-callsite include rule), os_routine_parameter_must_be_text (G-020 argument to routine 'RunCommand' should be String, found Number), build_script_example_compiles_clean. cargo build --release zero warnings; 544 tests green (337 unit + 14 g-lsp + 184 compile + 9 e2e — prior 540 + 4).
  6. Phase 44.1 (blocker resolution, done): (a) FFI #include deduplication — new emit_ffi_declarations(out, prog) helper (src/codegen.rs) tracks emitted headers in a HashSet<String> so each header appears once per compilation unit (extern declarations still per function); used by both the Desktop and ESP32 emitters; locked by os_shim_emitted_once_despite_two_ffi_imports_from_same_header (asserts exactly one #include <os_shim.h>). (b) Auto-quoting for spaced commands (above). (c) Quirk 9 documents the greedy with <expr> argument rule (found live in Phase 44: Text of Execute FileSize with TccPath followed by " bytes" passed the concatenation as the Path → -1). cargo build --release zero warnings; 545 tests green (337 unit + 14 g-lsp + 185 compile + 9 e2e).

8. Phase Ledger (Phase 43 — Bare-Metal Interrupts & Timers)

Task: Map G's When and Every constructs directly to bare-metal hardware interrupts — no RTOS framework on AVR (raw avr-libc ISRs), native FreeRTOS task scheduling on ESP32 — making G an IoT-first language.

  1. Parser & HIR (done): src/parser.rsEvery <N> ms do <action>. (parse_every_timer, dispatched when the token after every is a Number, before the generic-binding fallback) lowers to the existing Stmt::Event { EventSpec::TimerMs { ms } } handler pipeline (seconds/second units multiply by 1000; ms/milliseconds/do are optional words), so the action flows through MIR → SSA → prog.handlers unchanged. When interrupt occurs / When the interrupt occurs (parse_event_spec) parses to the new EventSpec::Interrupt (src/hir.rs), a top-level handler, not sequential code. Parser tests: parse_every_ms_lowers_to_timer_event, parse_every_seconds_lowers_to_ms, parse_when_interrupt_occurs, parse_when_the_interrupt_occurs.
  2. AVR timers & interrupts (done): emit_avr_mmio (src/codegen.rs) — for Every <N> ms do, main() configures Timer0 in CTC mode before while(1): TCCR0A = (1 << WGM01); / TCCR0B = (1 << CS02) | (1 << CS00); (1024 prescaler, 16 MHz → 15625 Hz) / OCR0A = (N*15625-500)/1000; (approximate; 10 ms → 155, exactly the directive's value) / TIMSK0 = (1 << OCIE0A);, then sei();. The action body becomes a real avr-libc handler ISR(TIMER0_COMPA_vect) { ... } (emitted after main). For When interrupt occurs: EICRA = (1 << ISC01); / EIMSK = (1 << INT0); / sei(); and ISR(INT0_vect) { ... }. Event programs gain #include <avr/io.h> / #include <avr/interrupt.h> (pure pin programs stay header-free, Phase 36 style) plus static slot declarations so Emitter-emitted stores compile. Set LED on/off inside a body lowers to the hardware line (PORTB |=/~(1 << PB5) — pin 13, matching the Phase 36 MMIO convention) instead of the dead led-slot store. The MIR handler terminator SsaOp::Ret is filtered (no g_done label on bare metal). The Embedded-Arduino profile also gained When interrupt occurs → INT0 (avr_event_vector), and Embedded-ESP32 skips it deterministically with a comment.
  3. ESP32 FreeRTOS tasks (done): emit_esp32_mmioEvery <N> ms do emits #include "freertos/FreeRTOS.h" + #include "freertos/task.h", the action lifted into void g_task_1(void* arg) { while(1) { <body> vTaskDelay(pdMS_TO_TICKS(<N>)); } }, and void app_main(void) (the ESP-IDF entry point) starts it with xTaskCreate(g_task_1, "g_task_1", 2048, NULL, 1, NULL); plus a keep-alive for (;;) { vTaskDelay(pdMS_TO_TICKS(1000)); }. Set LED on in the body writes GPIO2's raw MMIO write-to-set register (0x60004008), off uses the write-to-clear (0x6000400C). Programs without timers keep the Phase 36 int main(void) shape verbatim.
  4. Examples & verification (done): examples/baremetal_avr_isr.g (Target G Baremetal avr. Every 10 ms do Set LED on.) and examples/baremetal_esp32_task.g (Target G Baremetal esp32. Every 10 ms do Set LED on.) — pure verse, no trailing closers. dist\g.exe examples\baremetal_avr_isr.g --target baremetal-avr --out baremetal_avr_isr.c (exit 0): the emitted C contains TCCR0A = (1 << WGM01);, TCCR0B = (1 << CS02) | (1 << CS00);, OCR0A = 155;, TIMSK0 = (1 << OCIE0A);, sei(); and ISR(TIMER0_COMPA_vect) { / PORTB |= (1 << PB5); (full file pasted in the Phase 43 report). dist\g.exe examples\baremetal_esp32_task.g --target baremetal-esp32 --out baremetal_esp32_task.c (exit 0): contains #include "freertos/FreeRTOS.h", void g_task_1(void* arg) { / while(1) { / vTaskDelay(pdMS_TO_TICKS(10)); and xTaskCreate(g_task_1, "g_task_1", 2048, NULL, 1, NULL); in app_main. When interrupt occurs verified separately: EICRA = (1 << ISC01); / EIMSK = (1 << INT0); / sei(); / ISR(INT0_vect) { ... }.
  5. Tests & docs (done): 4 new compile tests — baremetal_avr_every_timer_emits_ctc_isr (includes, all four config lines, OCR0A=155, sei, ISR body, no goto g_done), baremetal_avr_when_interrupt_emits_int0_isr (EICRA/EIMSK/sei/ISR), baremetal_esp32_every_timer_emits_freertos_task (includes, task function, vTaskDelay, xTaskCreate, no goto g_done), baremetal_esp32_without_timer_keeps_phase36_main (Phase 36 shape preserved) — plus the 4 parser unit tests. cargo build --release zero warnings; 540 tests green (337 unit + 14 g-lsp + 180 compile + 9 e2e — prior 532 + 8).

9. Phase Ledger (Phase 42 — ARM64 Stack Frames & Cross-Compilation)

Task: Bring the x86-64 stack-frame architecture to the ARM64 (AArch64) backend so any number of variables works on Apple Silicon, Raspberry Pi, and ARM servers — no C compiler needed.

  1. Stack frame (done): emit_arm64_asm (src/codegen.rs) opens _start: with the standard AArch64 prologue stp x29, x30, [sp, -16]! (save frame pointer + link register, pre-indexed 16-byte push) / mov x29, sp / sub sp, sp, 64 (64-byte locals area), and the shared emit_bm_flow epilogue tears the frame down with add sp, sp, 64 / ldp x29, x30, [sp], 16 before mov x8, #93 (sys_exit). slot_op(BmArch::Arm64, slot) maps slot N to [sp, 16 + 8*N] — the first 16 bytes below the locals area are the saved x29/x30, so variables land at [sp, 16], [sp, 24], [sp, 32], … — unlimited variables, no .data spill. bm_mov_imm (Set A to 1mov x9, #1 / str x9, [sp, 16]), bm_inc/bm_add_imm (Set A to A plus 1ldr x9, [sp, 16] / add x9, x9, #1 / str x9, [sp, 16]), and bm_cmp (mem-mem → ldr x9, [sp, 16] / ldr x10, [sp, 48] / cmp x9, x10; mem-imm → ldr x9, [sp, 16] / cmp x9, #5) all route memory operands through the x9/x10 scratch registers, mirroring the x86 rax-routing of Phase 39.
  2. Example & verification (done): examples/baremetal_arm_stack.g — the directive's verse verbatim, one line, no trailing closers: Target G Baremetal arm64. Set A to 1. Set B to 2. Set C to 3. Set D to 4. Set E to 5. While A is less than E do Set A to A plus 1. Print "Looping". dist\g.exe examples\baremetal_arm_stack.g --target baremetal-arm64 --out baremetal_arm_stack.s writes the prologue (stp x29, x30, [sp, -16]! / mov x29, sp / sub sp, sp, 64), the five stores mov x9, #1..#5 / str x9, [sp, 16][sp, 48], the loop with .L_start_1: / ldr x9, [sp, 16] / ldr x10, [sp, 48] / cmp x9, x10 / b.ge .L_end_1 / ldr x9, [sp, 16] / add x9, x9, #1 / str x9, [sp, 16] / sys_write Looping\n / b .L_start_1 / .L_end_1: and the epilogue add sp, sp, 64 / ldp x29, x30, [sp], 16 / sys_exit (full .s pasted in the Phase 42 report). Exit 0, zero diagnostics.
  3. Tests & docs (done): baremetal_arm64_loop_emits_canonical_asm migrated from the Phase 38 register asserts (cmp x19, #5, add x19, x19, #1) to stack asserts (ldr x9, [sp, 16] + cmp x9, #5; ldr x9, [sp, 16] / add x9, x9, #1 / str x9, [sp, 16]); new baremetal_arm64_stack_frame_five_vars locks the prologue (stp x29, x30, [sp, -16]! / mov x29, sp / sub sp, sp, 64), all five [sp, 16][sp, 48] stores with their constants, the mem-mem compare, the scratch-register increment, the epilogue (add sp, sp, 64 / ldp x29, x30, [sp], 16), and epilogue-before-sys_exit ordering. Dead code removed for the new architecture (spilled_nums — no more .data spill — and the unused label_of). cargo build --release zero warnings; 532 tests green (333 unit + 176 compile + 9 e2e — prior 531 + 1).

10. Phase Ledger (Phase 41 — Math + Regex Standard Library)

Task: Grow the standard library beyond networking: stdlib/math.g (Sin, Cos, Floor, Random) and stdlib/regex.g (RegexMatch, RegexExtract) as C FFI shims, verified through a data-pipeline example.

  1. Shims (done): runtime/math_shim.h — external-linkage wrappers (the generated program emits extern double g_math_sin(double); AFTER the include, so static/inline would violate C 6.2.2): sin/cos/floor from <math.h>, g_math_random(void) = rand() / (RAND_MAX + 1.0) (deterministic — msvcrt's fixed seed, good for tests). runtime/regex_shim.hint g_regex_match(g_str, g_str) and g_str g_regex_extract(g_str, g_str, double): on non-Windows, regcomp(REG_EXTENDED)/regexec with regmatch_t capture (group (int)index, empty result for no-match/unused-group/bad-pattern, regfree on every path). On Windows/TCC (no <regex.h>): g_regex_match = strstr substring search (pattern treated literally — documented limitation), and g_regex_extract = a small anchored capture engine supporting literal runs, ., [^X]/[X] single-char classes with +/*, and (...) groups, searching from each start position (POSIX-regexec semantics); anything outside that subset returns an empty string. Strings are normalized (NUL-terminated via g_arena_str) and results live in a private 64KB g_regex_arena inside the shim.
  2. Deviation from the directive's 4-arg prototype (documented): the directive specified g_str g_regex_extract(g_arena *a, g_str pattern, g_str text, double index); the G FFI boundary cannot pass the arena — SsaVal::FfiCall emits name(g_t0, g_t1, g_t2) with exactly the declared parameters and the generated extern mirrors the G declaration (codegen.rs ~3905). A 4-arg C function would be a compile error. g_regex_extract therefore takes exactly the three G-declared parameters and uses the internal arena.
  3. Stdlib (done): stdlib/math.g + stdlib/regex.g follow the proven http.g pattern — Import C function <name> from "<shim>.h" with <Type> called <Param>, … returning <Type>., wrapper routines with Set R to Call c function … . inside In unsafe context do … End unsafe. then Return R., closed by Finally.. Two language additions were required: Flag joined Type::from_word as a Bool synonym (the directive's returning Flag previously parsed as Record("Flag") → G-070), and RegexMatch's import declares returning Flag (C int), NOT returning Number as written in the directive — a Number-typed FFI result cannot be Return-ed from a Flag-returning routine (G-010 routine returns Bool but 'return' provides Number). The routine parameter is named Text per the directive (with Text called Text); type position is resolved by the type vocabulary before expression resolution, so the name works.
  4. Example & verification (done): examples/data_pipeline.gImport "stdlib/math.g". + Import "stdlib/regex.g"., extract gufran.dev from https://gufran.dev/path with RegexExtract with "https://([^/]+)/", Raw, 1, print RandomSinFloor, and a RegexMatch If/Else. Two corrections to the directive's sample were required: (a) bare routine calls in expressions are prose — Set Domain to RegexExtract with … silently becomes the STRING "RegexExtract with …" (Quirk 4, Set Page to Get with Url. behaves identically) — every call is written Execute <name> with …; (b) the directive's argument order (with Raw, "https://([^/]+)/", 1) is positional, so the pattern-first call with "https://([^/]+)/", Raw, 1 is required for gufran.dev; (c) Set Random to Random. self-shadows (LHS is declared before the RHS resolves) → renamed Set Chance to Random.; (d) capitalized End. is rejected in pure verse (G-001) → the If block closes with After., the routine with Finally.. Live run of dist\g.exe examples\data_pipeline.g --run: prints gufran.dev, a sine value (e.g. 0.00125122), 0 (floor), and No Match — the last is EXPECTED on Windows/TCC: the strstr fallback treats https://([^/]+)/ literally, it is not a substring of the URL, so RegexMatch is 0; on POSIX the same program prints Match.
  5. Toolchain (done): --run's gcc/clang path now links -lm (after the source file, correct link order; TCC needs none — msvcrt has sin/cos/floor). build_release.ps1 copies both shims into dist/runtime/; the existing stdlib glob ships math.g/regex.g into dist/stdlib/.
  6. Tests & docs (done): 4 new unit tests in src/lib.rsflag_is_bool_type_synonym (regression for the Flag keyword), stdlib_math_imports_and_emits_ffi (asserts #include <math_shim.h>, extern double g_math_sin, extern double g_math_floor, g_math_sin( in the emitted C), stdlib_regex_imports_and_emits_ffi (#include <regex_shim.h>, extern int g_regex_match — the Flag→int contract, extern g_str g_regex_extract, both calls emitted), and data_pipeline_example_compiles (compiles examples/data_pipeline.g end-to-end with import resolution). cargo build --release zero warnings; 531 tests green (333 unit + 175 compile + 9 e2e — prior 527 + 4).

11. Phase Ledger (Phase 40 — IDE Tooling: Symbol Table + Completion, Hover, Goto-Definition)

Task: Make G a modern IDE language — the compiler must retain a symbol table as it type-checks (variables, routine parameters, routines), and g-lsp must serve autocomplete, hover tooltips, and goto-definition from it, even on documents with errors.

  1. Symbol table (done): new src/symbols.rsSymbolTable (name → SymbolInfo { span: Span, kind: SymbolKind }), with SymbolKind::Variable(Kind) and SymbolKind::Routine; first declaration of a name wins. types::check_program delegates to check_program_with_symbols(&Program, &mut SymbolTable), and the table is populated at every declaration site the checker walks: Stmt::Decl (Create X …), Stmt::Assign when the name is new (Set X to 42Variable(Number)), For each iterators (element type for Lists, Str otherwise), Read file … into X (Str), routine parameters (typed Variable; HIR params carry no per-parameter span, so they point at the routine header span), and every RoutineDef (SymbolKind::Routine). CompileOutput gains pub symbols: SymbolTable, and compile_at/compile_full populate it. New g_lang::analyze_for_tooling(source, base_dir) -> (Vec<Diagnostic>, SymbolTable): the full diagnostic pipeline (parse, imports, type check, ownership, capabilities, MIR, SSA, optimizer) but returns diagnostics AND symbols — compile_at returns Err (and discards symbols) on fatal errors, so the LSP uses analyze_for_tooling to serve tooling on broken documents too.
  2. LSP features (done): lsp.rs gains an LspServer { docs: HashMap<uri, Doc{source, symbols}> } state — didOpen/didChange analyze the document once and cache the symbol table, and the three requests serve from the cache (no recompile per keystroke). word_at(source, line, character) extracts the identifier under the cursor (the alphanumeric/underscore run containing the 0-based position, clamped; past-EOF → empty). textDocument/completion{"isIncomplete": false, "items": [...]} with label/kind/detail: variables kind 6 (CompletionItemKind::Variable, detail = Kind::name(), e.g. Number), routines kind 3 (Function, detail "routine"), core keywords Print/Set/If/While/For each/Target kind 14 (Keyword, matched by first token so ForFor each); prefix = the word under the cursor (empty word → everything), items sorted alphabetically. textDocument/hover{"contents": {"kind": "markdown", "value": "**Variable:** X\n**Type:** Number"}} (routines: **Routine:** Count); unknown words → null result. textDocument/definition → single Location {"uri", "range"} from the symbol's declaration Span (1-based → 0-based, one column wide).
  3. Example & verification (done): examples/completion_test.g — the directive's file verbatim: Target G Desktop. Set X to 42. Print X. Live stdin session against dist/g-lsp.exe (initialize + didOpen + completion/hover/definition at 0-based (0, 22) — the X of Set X to 42): completion → {"id":1,...,"items":[{"detail":"Number","kind":6,"label":"X"}]}; hover → {"id":2,...,"contents":{"kind":"markdown","value":"**Variable:** X\n**Type:** Number"}}; definition → {"id":3,...,"range":{"end":{"character":19,"line":0},"start":{"character":18,"line":0}},"uri":"file:///completion_test.g"} (the Set X to 42 statement span). The directive's literal position (line 1, column 10) is past EOF (the file is one line): completion degrades gracefully to all symbols + the six keywords, hover returns null.
  4. Tests & docs (done): 7 new LSP unit tests — word_at_cursor_extracts_identifier, completion_returns_variable_and_keywords (X kind 6 + all six keywords kind 14; prefix filtering to exactly one item), completion_returns_routines_as_functions (Define routine Count with Number called x returning Number: → Count kind 3, x kind 6), hover_returns_markdown_with_type, hover_unknown_word_returns_null, definition_returns_location_span (line 1), definition_of_routine_points_at_header — plus e2e lsp_serves_completion_hover_and_definition_on_stdin (opens completion_test.g content, fires all three requests, asserts "id":1 + "label":"X" + "kind":6, "id":2 + **Variable:** X + **Type:** Number, "id":3 + the file:///completion.g Location at line 0). cargo build --release zero warnings; 527 tests green (343 unit + 175 compile + 9 e2e).

12. Phase Ledger (Phase 39 — LSP Server + Bare-Metal Stack Frames)

Task: Give developers real-time diagnostics in VS Code via a Language Server Protocol server, and expand the bare-metal x86-64 backend from three registers to a full stack frame so any number of variables works — proving G is a true systems language.

  1. Language Server (done): new [[bin]] g-lsp (src/lsp.rs) — a zero-dependency LSP server (only serde_json added to Cargo.toml) reading Content-Length-framed JSON-RPC from stdin. read_message parses header lines until the blank line, then read_exact's exactly N body bytes (EOF → clean exit). initialize responds {"capabilities":{"textDocumentSync":1}}; textDocument/didOpen + textDocument/didChange extract the document text (textDocument/text, or contentChanges[0].text for changes), call g_lang::compile_at(text, None, None), and emit a textDocument/publishDiagnostics notification — G Span (1-based line/col) → LSP 0-based range, severities 1/2/3, code = G-###, source: "g". Unknown requests get null responses (clients never hang); shutdown/exit/stdin-EOF terminate. build_release.ps1 ships dist/g-lsp.exe.
  2. Bare-metal stack frames (done): emit_x86_asm opens every _start: with push rbp / mov rbp, rsp / sub rsp, 64 # Allocate 64 bytes for local variables and closes with mov rsp, rbp / pop rbp before sys_exit (also on the empty-program path). slot_reg became slot_op: x86 slots map to qword [rbp-8], [rbp-16], [rbp-24], … (8 bytes each — no more three-register limit, no .data spill on x86); ARM64 keeps x19/x20/x21 + .data spill. Set X to 0mov qword [rbp-8], 0; inc/dec/add/sub operate on the stack operand in place (inc qword [rbp-8]); memory-to-memory cmp/mov/add/sub (x86 has no such encodings) route through the rax scratch register — While A is less than E emits mov rax, qword [rbp-8] / cmp rax, qword [rbp-40]. walk_ops already covers the backend-relevant nesting — While/If lower to flat Label/CondJump/Jump in the main op stream, so emit_bm_flow's linear pass (which walk_ops-driven collectors feed) handles them directly.
  3. Stack example (done): examples/baremetal_stack.g — the directive's verse verbatim, no trailing closers: Target G Baremetal x86_64. Set A to 1. Set B to 2. Set C to 3. Set D to 4. Set E to 5. While A is less than E do Set A to A plus 1. Print "Looping".dist\g.exe examples\baremetal_stack.g --target baremetal-x86 --out baremetal_stack.s writes the full prologue, [rbp-8][rbp-40] stores, the rax-routed compare, inc qword [rbp-8], and the epilogue (pasted in the Phase 39 report).
  4. Verify (done): g-lsp.exe fed the directive's payload (Content-Length: 149 — the directive's claimed 115 is short by 34 bytes — didOpen with "Set X to .") responds with a publishDiagnostics notification carrying G-071 info no Target directive found; assuming Desktop profile; a genuine error document (Target G Desktop.\nReturn 5.) produces a severity-1 G-010 (locked by unit tests).
  5. Tests & docs (done): x86 bare-metal tests migrated from register asserts to stack asserts (baremetal_x86_set_num_emits_register_movebaremetal_x86_set_num_emits_stack_store; loop/if tests now assert cmp qword [rbp-8], 5, inc/dec qword [rbp-8]); new baremetal_x86_stack_frame_five_vars locks prologue/epilogue/offsets/epilogue-before-sys_exit; 7 LSP unit tests (framing, severity mapping, initialize capabilities, didOpen/didChange notifications, prose-fallback info) + e2e lsp_serves_publish_diagnostics_on_stdin (spawns g-lsp.exe, writes a framed payload, closes stdin, asserts the publishDiagnostics frame). cargo build --release zero warnings; 519 tests green (336 unit + 175 compile + 8 e2e).

13. Phase Ledger (Phase 38 — Implicit EOF Scoping + Bare-Metal Control Flow)

Task: Let .g files end naturally (no trailing closers) and give the bare-metal backends real control flow (While/If with plus/minus arithmetic on x86-64 and ARM64).

  1. Implicit EOF scoping (done): parse_body returns Some(()) at EOF (was None, which parse_while/parse_if propagated through ? — a file that ran out of closers silently dropped entire loops: the loop example compiled to an EMPTY body, and the SSA only emitted Store { tmp: 0, slot: 0 }); finish_block returns silently when the cursor is at EOF, before the G-071 implicit scope closure assumed lookup. Mid-file implicit closure still reports G-071 (locked by the new parse_record_midfile_implicit_end_info unit test; the old parse_record_implicit_end_info EOF variant became parse_record_eof_closed_silently). Example: examples/baremetal_loop.g has no trailing closer and compiles clean.
  2. Bare-metal control flow (done): emit_x86_asm/emit_arm64_asm now call emit_bm_flow(arch, ops, prog) with slot_reg mapping — rbx/rcx/rdx (x86) and x19/x20/x21 (ARM64). While/If lower to flat Label/CondJump/Jump in the SSA stream; bm_cmp_line + bm_inverse_jcc emit the condition, print_label resolves .L_start_N/.L_end_N/.L_N names at emit time (loop-back and else targets named at the CondJump; the then-label taken from the following Label op when the jump falls through; remaining labels named by the Jump ops; the final sweep drops unreferenced labels). bm_math (add/sub) folds plus/minus into inc/dec/add/sub on the slot register. Verified x86 output matches the directive's template exactly (.L_start_1: / cmp rbx, 5 / jge .L_end_1 / inc rbx / jmp .L_start_1 / .L_end_1:); ARM64 verified (cmp x19, #5 / b.ge .L_end_1 / add x19, x19, #1 / b .L_start_1) after fixing a ##5 double-hash (bm_cmp stripped the # prefix bm_imm already produced). The if-test (Set X to 10. If X is greater than 5 then Set X to X minus 1.) emits cmp rbx, 5 / jle .L_end_1 / dec rbx / .L_end_1: and executes correctly.
  3. Examples cleaned (done): every example now ends naturally at EOF — trailing closers removed from examples/api_server.g, cli_tool.g, codegen.g, counter.g, http.g, json.g, lexer.g, server.g, validator.g, web_app.g (plus repo-root bootstrap.g). All 19 compilable .g examples compile exit 0 (bare-metal ones with their --target); lexer.g was also fixed — its final For each Token in Tokens was a top-level statement referencing the routine-local Tokens (pre-existing G-010, present before Phase 38); it now lives inside the Tokenize routine body and is closed by EOF. examples/test_bootstrap.g is deliberately untouched: it is the input to the bootstrap.exe subset emitter, which needs the end token to emit the closing }. examples/baremetal_loop.g created from the directive's verse: Target G Baremetal x86_64. Set X to 0. While X is less than 5 do Set X to X plus 1. Print "Looping".
  4. Verify (done): dist\g.exe examples\json.g --run prints G, true, 3.5; dist\g.exe examples\baremetal_loop.g --target baremetal-x86 --out baremetal_loop.s writes the canonical .s (pasted in the Phase 38 report).
  5. Tests & docs (done): the Phase 36 test baremetal_x86_set_num_emits_data_quad became baremetal_x86_set_num_emits_register_move (variables are registers now); new compile tests lock the bare-metal x86 and ARM64 loop assembly, the bare-metal if-minus flow, EOF-closed While/routine/nested blocks (no G-071), and mid-file implicit closure (both statements still emitted); new unit tests cover the record EOF/mid-file G-071 split. cargo build --release zero warnings; 510 tests green (329 unit + 174 compile + 7 e2e).