{"name":"chaotic-aggressor.nu","source":"// ============================================================\n// chaotic-aggressor.nu — a HOSTILE pre-1.0 grammar/compiler stress test.\n//\n// This is the meaner sibling of chaotic-showcase.nu. Where the showcase\n// politely combined three corners (float density / recursive ADT / closure\n// combinators), the AGGRESSOR's job is to bend the language until something\n// creaks — then keep the program valid anyway. Everything below compiles and\n// runs; the genuine weaknesses it flushed out are catalogued at the very\n// bottom (the \"AGGRESSOR LAB\") as minimal repros, kept inside comments so the\n// file as a whole still builds.\n//\n// The vehicle is a tiny CONCATENATIVE STACK VM (an RPN \"Forth in a teacup\").\n// One program exercises, simultaneously:\n//\n//   (1) GENERICS + monomorphisation — a generic `Stack [T]` whose element\n//       allocation forwards the type variable into `alloc [T]`, mutated\n//       through `inout` exclusive borrows that are themselves forwarded\n//       inout→inout across call frames.\n//   (2) TRAIT dispatch — a `% Show` trait monomorphised by first-argument\n//       LLVM type (`show 42` → i64 impl, `show 3.5` → double impl).\n//   (3) A multi-payload recursive-style ENUM (`Op`) destructured by `??`\n//       with an OR-PATTERN (`Nop | Halt`), a GUARDED arm (`Bin c ? …`), a\n//       2-payload binding (`Clamp lo hi`) and a wildcard — all in one match.\n//   (4) HIGHER-ORDER dispatch — the binary operators live in a heap\n//       `[ (@ f f f) ]` slice-of-closures jump table, indexed by opcode.\n//   (5) CLOSURES THAT RETURN CLOSURES — `compose` returns a closure that\n//       captures two other closures (escape + double capture).\n//   (6) DENSE PREFIX FLOAT ARITHMETIC with no grouping tokens — a degree-3\n//       Horner evaluation written as one nested prefix chain.\n//   (7) Compile-time CONST FOLDING, LIFO `defer`, nested member-path\n//       assignment, variable-index pointer stores, and nested ternaries.\n// ============================================================\n\n$ `stdlib/core/mem.nu`\n$ `stdlib/std/float.nu`\n\n// Compile-time const fold: 1 << 6 == 64. Folded by const_eval_int, never\n// reaches IR as an expression.\n: i STACK_CAP << 1 6\n\n// ── (1) A generic typed stack ───────────────────────────────────────\n// The element store is `*T`; `stack_new` forwards the type variable T\n// straight into `alloc [T]`. Every mutator takes the stack by `inout`\n// (exclusive mutable borrow) so writes hit the caller's binding in place.\n\n: Stack [T] { * T data i len i cap }\n\n@ stack_new [T] i cap → ( Stack T ) {\n    ^ @ ( Stack T ) { # *T ( alloc [T] cap ) 0 cap }\n}\n\n@ stack_push [T] inout ( Stack T ) s T v → v {\n    // nested member-path assignment + variable-index pointer store:\n    // s.data[s.len] = v\n    = . . s data . s len v\n    = . s len + . s len 1\n}\n\n@ stack_pop [T] inout ( Stack T ) s → T {\n    = . s len - . s len 1\n    ^ . . s data . s len\n}\n\n@ stack_peek [T] inout ( Stack T ) s → T {\n    ^ . . s data - . s len 1\n}\n\n// ── (2) A trait dispatched by concrete first-arg type ───────────────\n\n% Show [T] { @ show T x → v }\n\n% Show f { @ show f x → v { ( nurl_print ( float_to_string x ) ) ( nurl_print `\\n` ) } }\n\n% Show i { @ show i x → v { ( nurl_print ( nurl_str_int x ) ) ( nurl_print `\\n` ) } }\n\n// ── (3) The instruction set ─────────────────────────────────────────\n// Lit/Bin carry payloads; Clamp carries TWO; Nop/Halt are tag-only so they\n// can share an or-pattern. (Deliberately NO 4-payload variant — see the\n// AGGRESSOR LAB note on the 3-binding match cap.)\n\n: | Op {\n    Lit f\n    Bin i\n    Dup\n    Neg\n    Clamp f f\n    Nop\n    Halt\n}\n\n// ── (4)+(7) The interpreter ─────────────────────────────────────────\n// `s` is inout and is forwarded inout→inout into every stack mutator.\n// `ops` is the closure jump table. One `??` carries every match shape the\n// grammar allows at once.\n\n@ vm_run inout ( Stack f ) s [Op prog [( @ f f f ) ops → f {\n    // LIFO defer: registered first, runs last.\n    ; { ( nurl_print `[trace] vm halted, depth=` ) ( nurl_print ( nurl_str_int . s len ) ) ( nurl_print `\\n` ) }\n    ; { ( nurl_print `[trace] vm starting\\n` ) }\n\n    ~ op prog {\n        ?? op {\n            Lit x → ( stack_push [f] s x )\n            Dup → { : f t ( stack_peek [f] s ) ( stack_push [f] s t ) }\n            Neg → { : f t ( stack_pop [f] s ) ( stack_push [f] s - 0.0 t ) }\n            // 2-payload binding + a nested ternary with zero grouping tokens:\n            //   clamp t into [lo,hi] = (t<lo) ? lo : (t>hi) ? hi : t\n            Clamp lo hi → {\n                : f t ( stack_pop [f] s )\n                ( stack_push [f] s ? < t lo lo ? > t hi hi t )\n            }\n            // GUARDED arm: only valid opcodes 0..3 dispatch through the table.\n            // `& >= c 0 <= c 3` is a binary `&` over two binary comparisons.\n            Bin c ? & >= c 0 <= c 3 → {\n                : f rhs ( stack_pop [f] s )\n                : f lhs ( stack_pop [f] s )\n                : ( @ f f f ) fn . ops c\n                ( stack_push [f] s ( fn lhs rhs ) )\n            }\n            // OR-PATTERN over two tag-only variants:\n            Nop | Halt → {}\n            // Catch-all — required because the guarded Bin arm cannot, on its\n            // own, satisfy exhaustiveness (a false guard falls through here).\n            _ → ( nurl_print `[trace] bad opcode\\n` )\n        }\n    }\n    ^ ( stack_peek [f] s )\n}\n\n// ── (5) Closures returning closures: capture-by-snapshot of two closures ─\n\n@ compose ( @ f f ) g ( @ f f ) h → ( @ f f ) {\n    ^ \\ f x → f { ^ ( g ( h x ) ) }\n}\n\n// ── (6) Dense prefix arithmetic: Horner form of 3x³ − 2x² + x − 5 ────\n//   (((3·x + -2)·x + 1)·x + -5)   — one nested prefix chain, no parens.\n@ poly f x → f {\n    ^ + * + * + * 3.0 x -2.0 x 1.0 x -5.0\n}\n\n@ main → i {\n    ( nurl_print `=== chaotic-aggressor ===\\n` )\n    ( nurl_print `STACK_CAP (1<<6) = ` ) ( nurl_print ( nurl_str_int STACK_CAP ) ) ( nurl_print `\\n` )\n\n    // (4) Build the opcode jump table: 0=add 1=sub 2=mul 3=div.\n    : [( @ f f f ) ops [( @ f f f ) |\n    \\ f a f b → f { ^ + a b }\n    \\ f a f b → f { ^ - a b }\n    \\ f a f b → f { ^ * a b }\n    \\ f a f b → f { ^ / a b }\n    ]\n\n    // The program:  3 4 * 5 +  Dup *  Clamp[0,100]\n    //   3*4=12 ; 12+5=17 ; dup→17 17 ; *→289 ; clamp→100\n    : [Op prog [Op |\n    @ Op { Lit 3.0 }\n    @ Op { Lit 4.0 }\n    @ Op { Bin 2 }\n    @ Op { Lit 5.0 }\n    @ Op { Bin 0 }\n    @ Op { Nop }\n    @ Op { Dup }\n    @ Op { Bin 2 }\n    @ Op { Clamp 0.0 100.0 }\n    @ Op { Halt }\n    ]\n\n    // (1) inout stack, forwarded into vm_run which forwards it again.\n    : ~ ( Stack f ) st ( stack_new [f] STACK_CAP )\n    : f result ( vm_run st prog ops )\n\n    ( nurl_print `vm result = ` ) ( show result )  // (2) trait dispatch on f\n\n    // (5) compose two escaping closures: (x → x+1) then (x → 2x)\n    : ( @ f f ) inc \\ f x → f { ^ + x 1.0 }\n    : ( @ f f ) dbl \\ f x → f { ^ * x 2.0 }\n    : ( @ f f ) xform ( compose dbl inc )  // dbl ∘ inc\n    ( nurl_print `2*(result+1) = ` ) ( show ( xform result ) )\n\n    // (6) dense prefix Horner polynomial at x=2 → 3*8 -2*4 +2 -5 = 13\n    ( nurl_print `poly(2) = ` ) ( show ( poly 2.0 ) )\n\n    // (2) trait dispatch on i too, to prove monomorphisation split.\n    ( nurl_print `opcode count = ` ) ( show . ops length )\n\n    ^ 0\n}\n\n// ============================================================\n// AGGRESSOR LAB — four edges this demo flushed out, ALL NOW FIXED on the\n// fix/aggressor-findings branch (compiler/nurlc.nu). Each line below is valid\n// per spec/grammar.ebnf; what changed is how the compiler now treats it. Kept\n// in comments so this file still compiles. Run any snippet through\n// `./check.sh` to see the new behaviour.\n//\n// L1 ✓ FIXED — SLICE ELEMENT ACCESS BY A PARAMETER INDEX.\n//   `. slice idx` with `idx` a bare PARAMETER used to lower the index as a\n//   load from a stack slot the parameter never had, emitting `load i64, i64*`\n//   with an EMPTY pointer operand — IR nurlc accepted (rc 0) and only clang\n//   rejected. gen_member now resolves the index exactly like gen_ident\n//   (param → `%name`, local → load `__ptr`, const → load `@name`), so it just\n//   works — no local-copy workaround needed:\n//       @ at [ i xs i k → i { ^ . xs k }          // → returns xs[k]\n//   (This is why `vm_run` above can index `ops` by the opcode directly.)\n//\n// L2 ✓ FIXED — TYPE KEYWORD AS A VALUE.\n//   A bare type keyword in operand position used to lower to a void SSA value\n//   (`add void void, 100`) that only clang rejected. The operator / complement\n//   / logical-not paths now reject the void/unit literal `v` at the source via\n//   die_if_void:\n//       : i x + v 100   // error: binary operator's left operand is the\n//                       //        void/unit value 'v' …\n//   (Same diagnostic now covers the `~ v xs { … }` foreach-binding trap.)\n//\n// L3 ✓ FIXED (grammar widened) — COMPOUND generic_inst ARGUMENTS.\n//   The compiler always accepted compound type arguments — a nested\n//   application `( Pair ( Box i ) i )`, a pointer `* T`, an option `? T` — but\n//   grammar v2.2 said `generic_inst = '(' IDENT IDENT+ ')'` (base idents only).\n//   The grammar + spec now define a shared `generic_arg` allowing those forms\n//   (slice `[T` args remain unsupported and now give a clean diagnostic). Spec\n//   and implementation agree ahead of the 1.0 lock.\n//\n// L4 ✓ FIXED (now diagnosed) — ESCAPING `:~` CAPTURE NO LONGER SILENT.\n//   By-value capture of a mutable binding is the documented default, but a\n//   counter closure that mutates it printed 1,1,1 (fresh snapshot per call)\n//   with zero warning. Assigning to a by-value-captured binding now emits a\n//   warning explaining the write is discarded and pointing at the supported\n//   shared-mutation shape (a `: ~` multi-field struct captured by reference):\n//       @ make_counter → ( @ i ) { : ~ i n 0  ^ \\ → i { = n + n 1  ^ n } }\n//   (An escaping mutable-state closure is intentionally not expressible: the\n//   by-ref capture is a stack reference the escape checker forbids escaping.)\n//\n// ALSO already fixed earlier (was a v0.9.8 playground/MCP-only regression):\n//   4-payload `??` destructuring — `?? b { Quad a b c d → + + + a b c d … }`\n//   compiles and returns the right value.\n// ============================================================\n","bytes":10756}