aboutsummaryrefslogtreecommitdiff
path: root/AGENTS
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-21 12:42:33 +0000
committer_ov4 <ov4@ov4.org>2026-10-21 12:42:33 +0000
commit8f575456f53998cb4c46e17051adefbeb7725390 (patch)
treebeae41cb375ac24cbd6ff9a0a4b7dcd2d39ad6b1 /AGENTS
parent6164db68dced67c1aa16ec8bb6831140df312797 (diff)
docs(agents): split PITFALLS.md into topic files under AGENTS/PITFALLS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS')
-rw-r--r--AGENTS/PITFALLS.md198
-rw-r--r--AGENTS/PITFALLS/classes.md58
-rw-r--r--AGENTS/PITFALLS/core-language.md66
-rw-r--r--AGENTS/PITFALLS/exceptions-and-c-interop.md44
-rw-r--r--AGENTS/PITFALLS/files-and-types.md38
-rw-r--r--AGENTS/PITFALLS/modules-and-stdlib.md23
-rw-r--r--AGENTS/PITFALLS/not-implemented.md18
-rw-r--r--AGENTS/PITFALLS/overload-template-operator.md59
8 files changed, 320 insertions, 184 deletions
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index 7bc78fb..325ae38 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -1,187 +1,17 @@
# Pitfalls
-## Sharp edges (verified, will bite)
+Verified sharp edges (they *will* bite) and the list of unimplemented 9618 features,
+split by topic under `AGENTS/PITFALLS/`.
-Every bullet below was confirmed against the source, most also by compiling and running
-test programs.
-
-- **Multi-file extensions.** INCLUDE imports symbols, never text; every included file
- must also be an input. Duplicate exports across files fail even without visibility, except distinct
- signatures whose definitions all specify OVERLOAD.
- Only one file may contain top-level code; top-level DECLARE is a main local, not an
- export. Definitions-only builds now fail without FUNCTION MAIN returning INTEGER.
- MAIN cannot coexist with top-level code. GLOBAL accepts only the five primitive
- scalar types and literal initializers; arrays, user types and computed initializers
- are rejected. Own top-level constants remain positional, imported constants do not.
-- **OUTPUT accepts comma-separated expressions (§5.1).** Values are evaluated and
- printed left to right without automatic separators; the final scalar adds a newline.
- Whole arrays and subarrays still print each element on its own line, including in
- mixed output lists; a final array adds no extra newline. INPUT accepts writable
- scalar locations, including array elements, public record/class fields and
- pointer dereferences, using the shared lvalue checks. Whole arrays and whole
- records/classes remain unsupported INPUT targets.
-- **Arrays cross function boundaries only as parameters.** `ArrayTable` is scoped per
- function now, so referencing another function's array fails cleanly as
- "Undeclared array". Whole arrays pass as `ARRAY OF T` / `ARRAY[l:u,...] OF T`
- parameters (BYVAL copies the entire buffer and never frees it); a whole-array
- argument against a scalar parameter is still a compile error. FUNCTIONs cannot
- RETURN arrays. A whole-array variable is also rejected as an operand or as a
- value of its element type (it used to leak its raw data pointer).
-- **Arrays are additionally block-scoped.** An array DECLAREd inside an
- IF/WHILE/REPEAT/FOR/TRY/CATCH body is not visible after the block ("Undeclared array") —
- its buffer setup and bound SSA values live inside the branch, so letting it
- escape meant wild-pointer writes or invalid IR. Scalars remain function-scoped
- (their alloca is hoisted to the entry block); declare at the top of the
- function like the exam board intends and neither matters.
-- **Parameter modes are sticky and BYREF is FUNCTION-forbidden (§8.3).**
- `(BYREF a : T, b : T)` makes `b` BYREF too — programs that relied on the old
- per-parameter reset (where `b` was silently BYVAL) change meaning. Literal
- arguments against an accidentally-BYREF parameter now fail with "BYREF argument
- must be a variable"; annotate `BYVAL` explicitly.
-- **`/` always yields REAL** (double FDiv), even for two INTEGERs; integer division is
- DIV/MOD only. CHR returns an owning one-byte STRING, not CHAR; CHR(0) retains one NUL byte. STR_TO_NUM always
- returns REAL. NUM_TO_STR uses std::to_string, preserving the full signed i64 range and six fractional digits for REAL. STR_TO_NUM uses std::stod with full-consumption checks.
-- **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check it
- at any new call site) and internally defaults to INTEGER for unclassified arithmetic expressions; unknown or unmatched calls now diagnose and return nullptr.
- OUTPUT reports an error for a nullptr result.
-- **CONSTANT** accepts only literals (including negative numeric literals), never expressions or other names. Constants have lexical block scope and may shadow outer names; bindings hiding constants are restored on block exit too. Functions inherit previously declared constants from their own unit and all visible imported top-level constants. Constants have no writable storage: assignment, INPUT, READFILE targets, FOR variables, BYREF and address-of are rejected.
-- **CASE OF** reads its selector once and tests expression labels in source order, with inclusive TO ranges and an optional final OTHERWISE. Label headers occupy their own source line; bodies may begin after the colon or on subsequent lines. Comparisons reuse ordinary operator rules; records/classes need BOOLEAN-returning comparison overloads, and whole arrays are unsupported.
-- The lexer emits no newline tokens; most grammar is keyword-delimited, while CASE
- uses token line information to recognize labels. Bare `RETURN` is recognized
- before block terminators and the next CASE label.
-- `verifyModule` runs at the end of `compile()`; invalid modules make the driver
- exit non-zero without writing IR.
-- The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca. The
- loop direction is decided at run time from the STEP value's sign; STEP is evaluated
- exactly once, before the loop. (The TO expression is still re-evaluated every
- iteration.)
-- **File handling (§9.1) sharp edges.** `READ`/`WRITE`/`APPEND`/`RANDOM` are reserved
- keywords now — `DECLARE READ : INTEGER` no longer compiles. `WRITEFILE f, a, b` takes
- exactly two operands (the stray `, b` is a parse error).
- READFILE's target must be a plain STRING variable — no CHAR, no `arr[i]`.
- The CIE guide's empty-line test `IF LineOfText = ""` compares contents.
- At most 16 files open at once. All runtime misuse (not open, wrong mode, reopen,
- fopen failure, READFILE past EOF) throws an anonymous exception — guard reads with
- `WHILE NOT EOF(...)`. Caveat: the open-file table is keyed by the filename *string*
- (per CIE semantics), so two spellings of one OS path (`"a.txt"` vs `"./a.txt"`) evade
- the already-open check and get two independent buffered streams — reads through the
- alias can see stale/empty data instead of an exception.
-
-- **TYPE-system sharp edges (§4).** `TYPE`/`ENDTYPE`/`SET`/`DEFINE` are reserved
- keywords now. Typing is *nominal*: two pointer types with the same pointee, or two
- records with the same layout, are incompatible. Strict pointee matching rejects the
- official guide's own worked example (`^INTEGER` pointed at a `Season` variable) —
- deliberate deviation. Enum OUTPUT prints the 0-based ordinal; a DECLAREd-but-unset
- enum *is* the first enumerator (no uninitialized state). `E ± INTEGER` needs the
- enum on the left; out-of-range results throw at run time. There is no NULL
- literal: pointers zero-init to null, dereferencing one throws, and unset-ness
- is deliberately untestable. `RETURN ^localVar` dangles — documented, unchecked.
- Record STRING fields default-construct empty strings and copy independently. Enum value names are globally reserved (they block DECLARE/parameter/function
- names). TYPE must be top-level; array fields inside records and bounded array
- parameters with non-literal bounds are dedicated compile errors. A digit run
- followed by `.` is swallowed into a REAL literal, so `1.foo` mislexes (only
- already-invalid programs can hit it).
-
-- **OVERLOAD extension.** OVERLOAD is reserved and prefixes FUNCTION/PROCEDURE
- (after PUBLIC/PRIVATE for methods). Every same-name definition must opt in.
- Only count, canonical parameter types and array rank distinguish signatures:
- return type, BYREF/BYVAL and array bounds do not. Calls match exact static types,
- even for a single OVERLOAD definition; ordinary functions still coerce arguments.
- Call visibility filters the set, but duplicate signatures conflict across all
- inputs. MAIN and builtins cannot be overloaded. NEW supports overloads; DECLARE,
- fields and arrays needing default construction require a matching no-argument NEW.
- Private access is checked after selection, never used to select a public fallback.
-- **TEMPLATE extension.** Top-level FUNCTION/PROCEDURE/CLASS only; all template parameters are types, with at most one final pack. Exact types are required even with explicit arguments (`Max<REAL>(1, 2.5)` fails). Deduction recognizes T and array element T, never the T inside Box<T>. When multiple ordinary/template functions share a name, every definition must specify OVERLOAD; exact ordinary definitions win, then non-variadic templates, then variadic templates, with same-tier ties ambiguous. Explicit arguments select templates only. Packs are final non-array parameters and expand only in call/constructor argument lists; pack fields are rejected, including in variadic classes. No non-type parameters, SIZEOF..., member/operator templates, specialization, bare injected class name or pointer-alias templates. Pointer-linked generic containers remain unsupported; use fixed fields or other supported value layouts. Class instances follow ordinary constructor rules, so a parameterized NEW alone does not permit `DECLARE B : Box<INTEGER>` without arguments. Template instances share the first requester's visibility for their cached body; positional constants come from the template declaration. Instantiation depth is limited to 64. Empty source lists `<>` are invalid and lex as inequality; `>>` remains two tokens, but adjacent `<-` and `>=` merge. Expression lookahead treats `A < B, C > (D)` as a template call; parenthesize comparisons to avoid that ambiguity.
-- **OPERATOR extension.** Unary/binary operators require OVERLOAD FUNCTION, with supported
- arity and at least one record/class/enum operand. Pointer aliases alone do not
- qualify. Exact matching considers visible globals and left-class members
- together; ties are errors, including private/public ties. Comparison and logic
- result types are declared, not assumed BOOLEAN; implicit STRING to BOOLEAN is
- rejected. CASE comparisons must return BOOLEAN. AND/OR overloads evaluate both
- operands. Temporary member receivers are destroyed at statement end, including
- exception exits; ordinary DECLARE objects remain function-scoped. Explicit calls
- use OPERATOR+(a,b) or a.OPERATOR+(b). Assignment uses member-only
- OVERLOAD PROCEDURE OPERATOR<- with one explicit parameter; it is selected before
- fallback same-class copying. Keyword NEW retains its allocation/rebinding path.
-- **CLASS subset (§10).** `CLASS`/`ENDCLASS`/`PUBLIC`/`PRIVATE` are reserved.
- CLASS is a top-level record-derived value type with optional per-member access
- keywords (no DECLARE). Omitting access defaults to public, per the guide.
- TYPE fields cannot specify access and remain public. Private fields
- and methods cannot be accessed outside methods of the owning class;
- whole-value initialization/copying is allowed. Methods modify the receiver;
- `this` aliases the object itself, not a pointer. `this <- Other` copies a value.
- Bare fields are supported, but calls to the current object's methods require
- `this.Method(...)`; unqualified calls remain global. Parameters and local
- declarations shadowing fields warn without failing compilation. Method-only
- classes are allowed; empty classes and array fields remain unsupported.
- PROCEDURE NEW is an ordinary case-insensitive member, also called automatically
- after zero initialization by DECLARE and assignment-only keyword NEW. Fields and
- array elements recursively require parameterless construction before the outer NEW.
- Class variables and BYVAL class parameters use reference slots: keyword NEW rebinds
- them, while ordinary assignment copies into their current object. Fields, array
- elements, BYREF, this and pointer dereferences receive a value copy from NEW;
- the temporary allocation leaks. Old address-of pointers retain the previous object.
- Private access and ordinary argument checks apply to automatic construction too.
- DELETE is reserved; a parameterless PROCEDURE DELETE defines an ordinary method.
- Explicit calls do not recurse into fields or affect Alive tracking. Keyword DELETE
- validates an active allocation start before destroying the object and its fields,
- then frees memory and leaves aliases dangling; FREE skips all destruction.
- DECLARE storage and BYVAL copies are destroyed in reverse declaration order
- at normal function exit or when unwinding leaves the frame. Arrays reverse their
- flat element order. Repeated DECLARE destroys its previous incarnation first.
- Lifetimes are function-scoped, even for declarations inside blocks. NEW rebinding
- keeps automatic destruction attached to original storage; heap objects are never
- auto-destroyed. Main objects are destroyed only on normal program end. Alive is
- cleared before destructor calls, preventing retries if a destructor throws.
- Private DELETE access is checked for automatic destruction too. A destructor
- throwing during unwinding propagates and interrupts remaining cleanup rather
- than terminating as C++ would.
-
-- **Exception extensions.** TRY requires exactly one parameterless CATCH and
- ENDTRY. Bare THROW raises a new anonymous exception even inside CATCH; there is
- no rethrow syntax, error object or what(). Runtime checks are catchable;
- compiler errors are not. Runtime checks print the source line and error reason
- to stderr before throwing, even when caught. Uncaught exceptions also print the
- generic termination message and return 1. Bare THROW prints no diagnostic itself.
- Native binaries must link the C++ runtime with LLVM 23's `clang++`
- (`clang++-23` on Debian/Ubuntu).
- Owned STRING values follow class cleanup during unwinding; raw array buffers are not freed and files are not automatically closed. Embedded ORC
- execution with LLVM 23 is exercised by nightly CI on Linux, Windows and macOS;
- check the native job results when changing platform-specific code.
-
-- **C type extensions.** C_STRING is a raw char pointer, not a STRING: explicitly
- cast to STRING for printing or string-library calls. C aliases share canonical CIdentity;
- do not use LLVM widths to recover signedness or nominal type identity. Plain
- char's signedness is native, unlike pseudocode CHAR. C/pseudocode conversions
- require casts even for literals. C scalar arithmetic uses native promotions,
- but / always returns REAL. Cast aliases must precede their use. long double
- may have padding; sizeof does not identify its floating-point format. Native
- C support is verified on Linux LLVM 23; cross-target is unsupported.
-- **EXTERN "C" extension.** Top-level, body-less; parameters/results are C types only,
- no BYREF/OVERLOAD/structs. Variadic arguments must be C values (default promotions).
- The driver checks every EXTERN symbol against INCLUDE_OBJ inputs before CodeGen;
- `"__libc"` must be included explicitly even though the runtime always links libc.
- Only `.o` user objects; INCLUDE_OBJ is global across inputs. `-S` does not link them.
- Identical EXTERN redeclarations (same or other files, CIdentity-equal types) are allowed;
- differing ones fail. Two EXTERNs differing only in case conflict (pseudocode names are case insensitive).
- Windows `-r` resolves C symbols from msvcrt.dll before the process-wide search: ucrtbase.dll
- may also be loaded, and its separate stdout buffer reorders EXTERN output against OUTPUT.
-
-## Not implemented (9618 features)
-
-SET types (`SET`/`DEFINE` are reserved with dedicated errors),
-random files (OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — TYPE is done, so these
-are unblocked now; the RANDOM keyword is reserved and gives a dedicated parse error),
-DATE,
-array-typed record fields, whole-array RETURN
-and class inheritance (INHERITS/SUPER). Compiler fixtures now live in `tests/` (see TESTING.md).
-Nightly CI runs the compiler suites, packaged EMBEDDED smoke checks and existing
-plugin tests. The initial standard conflicts and missing diagnostic locations are fixed on
-Linux.
-WHILE accepts both the standard header without DO and the older WHILE DO form.
-
-- **Literal escapes.** C++17 escapes are decoded by the lexer. Unicode escapes encode UTF-8 in strings; CHAR Unicode escapes must be ASCII. Ordinary STRING literals retain legacy NUL truncation: `"a\0b"` is observed as `"a"`. STRING can retain NUL bytes built through CHR or the count/character constructor; raw C text I/O truncates at NUL. The suffix `"text"C` yields C_STRING without a STRING class.
-
-- **Copy limits.** Classes may define a same-class BYREF copy constructor. Classes without one, whole TYPE records containing non-STRING owning class fields, and BYVAL arrays of such classes still copy bits; STRING-bearing aggregates recursively clone their string-bearing fields, while other owning fields still need explicit care. Class rvalues transfer ownership when consumed; discarded results and temporary receivers are destroyed at statement end.
-
-- **Default standard library.** std.cps and its imported STRING library are embedded and automatically linked. Use -no-std before explicitly supplying a replacement STRING class/library; otherwise duplicate definitions fail. Ordinary literals require a visible STRING class with NEW(C_STRING). C_TYPE/INCLUDE/EXTERN syntax strings are parser metadata and do not construct STRING. -S emits IR only; manual linking needs string.o and the C++ runtime.
+- [Core language](PITFALLS/core-language.md) — OUTPUT/INPUT, arrays and scoping,
+ parameter modes, arithmetic/builtins, CONSTANT, CASE, FOR/WHILE, literal escapes.
+- [Multi-file builds and standard library](PITFALLS/modules-and-stdlib.md) — INCLUDE,
+ MAIN/GLOBAL rules, embedded std.cps, `-no-std`, `-S`.
+- [File handling and TYPE system](PITFALLS/files-and-types.md) — file I/O keywords and
+ runtime errors, nominal typing, enums, pointers.
+- [OVERLOAD, TEMPLATE, OPERATOR](PITFALLS/overload-template-operator.md) — overload
+ resolution, template deduction limits, operator overloading rules.
+- [Classes](PITFALLS/classes.md) — access, construction, destruction order, copy limits.
+- [Exceptions and C interop](PITFALLS/exceptions-and-c-interop.md) — TRY/CATCH/THROW,
+ C types, EXTERN "C", INCLUDE_OBJ, Windows quirks.
+- [Not implemented](PITFALLS/not-implemented.md) — missing 9618 features and CI notes.
diff --git a/AGENTS/PITFALLS/classes.md b/AGENTS/PITFALLS/classes.md
new file mode 100644
index 0000000..82415d7
--- /dev/null
+++ b/AGENTS/PITFALLS/classes.md
@@ -0,0 +1,58 @@
+# CLASS subset (§10) and copy limits
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+## Declaration and access
+
+- `CLASS`/`ENDCLASS`/`PUBLIC`/`PRIVATE` are reserved.
+- CLASS is a top-level record-derived value type with optional per-member access
+ keywords (no DECLARE). Omitting access defaults to public, per the guide. TYPE
+ fields cannot specify access and remain public.
+- Private fields and methods cannot be accessed outside methods of the owning class;
+ whole-value initialization/copying is allowed.
+- Methods modify the receiver; `this` aliases the object itself, not a pointer.
+ `this <- Other` copies a value.
+- Bare fields are supported, but calls to the current object's methods require
+ `this.Method(...)`; unqualified calls remain global.
+- Parameters and local declarations shadowing fields warn without failing compilation.
+- Method-only classes are allowed; empty classes and array fields remain unsupported.
+
+## Construction
+
+- PROCEDURE NEW is an ordinary case-insensitive member, also called automatically
+ after zero initialization by DECLARE and assignment-only keyword NEW. Fields and
+ array elements recursively require parameterless construction before the outer NEW.
+- Class variables and BYVAL class parameters use reference slots: keyword NEW rebinds
+ them, while ordinary assignment copies into their current object.
+- Fields, array elements, BYREF, this and pointer dereferences receive a value copy
+ from NEW; the temporary allocation leaks. Old address-of pointers retain the
+ previous object.
+- Private access and ordinary argument checks apply to automatic construction too.
+
+## Destruction
+
+- DELETE is reserved; a parameterless PROCEDURE DELETE defines an ordinary method.
+ Explicit calls do not recurse into fields or affect Alive tracking.
+- Keyword DELETE validates an active allocation start before destroying the object
+ and its fields, then frees memory and leaves aliases dangling; FREE skips all
+ destruction.
+- DECLARE storage and BYVAL copies are destroyed in reverse declaration order at
+ normal function exit or when unwinding leaves the frame. Arrays reverse their flat
+ element order. Repeated DECLARE destroys its previous incarnation first.
+- Lifetimes are function-scoped, even for declarations inside blocks. NEW rebinding
+ keeps automatic destruction attached to original storage; heap objects are never
+ auto-destroyed. Main objects are destroyed only on normal program end.
+- Alive is cleared before destructor calls, preventing retries if a destructor
+ throws. Private DELETE access is checked for automatic destruction too.
+- A destructor throwing during unwinding propagates and interrupts remaining cleanup
+ rather than terminating as C++ would.
+
+## Copy limits
+
+- Classes may define a same-class BYREF copy constructor.
+- Classes without one, whole TYPE records containing non-STRING owning class fields,
+ and BYVAL arrays of such classes still copy bits; STRING-bearing aggregates
+ recursively clone their string-bearing fields, while other owning fields still need
+ explicit care.
+- Class rvalues transfer ownership when consumed; discarded results and temporary
+ receivers are destroyed at statement end.
diff --git a/AGENTS/PITFALLS/core-language.md b/AGENTS/PITFALLS/core-language.md
new file mode 100644
index 0000000..a1ab62d
--- /dev/null
+++ b/AGENTS/PITFALLS/core-language.md
@@ -0,0 +1,66 @@
+# Core language pitfalls
+
+Part of the [pitfalls index](../PITFALLS.md). Every bullet was confirmed against the
+source, most also by compiling and running test programs.
+
+- **OUTPUT accepts comma-separated expressions (§5.1).** Values are evaluated and
+ printed left to right without automatic separators; the final scalar adds a newline.
+ Whole arrays and subarrays still print each element on its own line, including in
+ mixed output lists; a final array adds no extra newline. INPUT accepts writable
+ scalar locations, including array elements, public record/class fields and
+ pointer dereferences, using the shared lvalue checks. Whole arrays and whole
+ records/classes remain unsupported INPUT targets.
+- **Arrays cross function boundaries only as parameters.** `ArrayTable` is scoped per
+ function now, so referencing another function's array fails cleanly as
+ "Undeclared array". Whole arrays pass as `ARRAY OF T` / `ARRAY[l:u,...] OF T`
+ parameters (BYVAL copies the entire buffer and never frees it); a whole-array
+ argument against a scalar parameter is still a compile error. FUNCTIONs cannot
+ RETURN arrays. A whole-array variable is also rejected as an operand or as a
+ value of its element type (it used to leak its raw data pointer).
+- **Arrays are additionally block-scoped.** An array DECLAREd inside an
+ IF/WHILE/REPEAT/FOR/TRY/CATCH body is not visible after the block ("Undeclared
+ array") — its buffer setup and bound SSA values live inside the branch, so letting
+ it escape meant wild-pointer writes or invalid IR. Scalars remain function-scoped
+ (their alloca is hoisted to the entry block); declare at the top of the function
+ like the exam board intends and neither matters.
+- **Parameter modes are sticky and BYREF is FUNCTION-forbidden (§8.3).**
+ `(BYREF a : T, b : T)` makes `b` BYREF too — programs that relied on the old
+ per-parameter reset (where `b` was silently BYVAL) change meaning. Literal
+ arguments against an accidentally-BYREF parameter now fail with "BYREF argument
+ must be a variable"; annotate `BYVAL` explicitly.
+- **`/` always yields REAL** (double FDiv), even for two INTEGERs; integer division is
+ DIV/MOD only.
+ - CHR returns an owning one-byte STRING, not CHAR; CHR(0) retains one NUL byte.
+ - STR_TO_NUM always returns REAL and uses std::stod with full-consumption checks.
+ - NUM_TO_STR uses std::to_string, preserving the full signed i64 range and six
+ fractional digits for REAL.
+- **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check
+ it at any new call site) and internally defaults to INTEGER for unclassified
+ arithmetic expressions; unknown or unmatched calls now diagnose and return nullptr.
+ OUTPUT reports an error for a nullptr result.
+- **CONSTANT** accepts only literals (including negative numeric literals), never
+ expressions or other names. Constants have lexical block scope and may shadow outer
+ names; bindings hiding constants are restored on block exit too. Functions inherit
+ previously declared constants from their own unit and all visible imported
+ top-level constants. Constants have no writable storage: assignment, INPUT,
+ READFILE targets, FOR variables, BYREF and address-of are rejected.
+- **CASE OF** reads its selector once and tests expression labels in source order,
+ with inclusive TO ranges and an optional final OTHERWISE. Label headers occupy their
+ own source line; bodies may begin after the colon or on subsequent lines.
+ Comparisons reuse ordinary operator rules; records/classes need BOOLEAN-returning
+ comparison overloads, and whole arrays are unsupported.
+- The lexer emits no newline tokens; most grammar is keyword-delimited, while CASE
+ uses token line information to recognize labels. Bare `RETURN` is recognized
+ before block terminators and the next CASE label.
+- `verifyModule` runs at the end of `compile()`; invalid modules make the driver
+ exit non-zero without writing IR.
+- The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca. The
+ loop direction is decided at run time from the STEP value's sign; STEP is evaluated
+ exactly once, before the loop. (The TO expression is still re-evaluated every
+ iteration.)
+- WHILE accepts both the standard header without DO and the older WHILE DO form.
+- **Literal escapes.** C++17 escapes are decoded by the lexer. Unicode escapes encode
+ UTF-8 in strings; CHAR Unicode escapes must be ASCII. Ordinary STRING literals
+ retain legacy NUL truncation: `"a\0b"` is observed as `"a"`. STRING can retain NUL
+ bytes built through CHR or the count/character constructor; raw C text I/O
+ truncates at NUL. The suffix `"text"C` yields C_STRING without a STRING class.
diff --git a/AGENTS/PITFALLS/exceptions-and-c-interop.md b/AGENTS/PITFALLS/exceptions-and-c-interop.md
new file mode 100644
index 0000000..616a839
--- /dev/null
+++ b/AGENTS/PITFALLS/exceptions-and-c-interop.md
@@ -0,0 +1,44 @@
+# Exceptions, C types and EXTERN "C"
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+## Exception extensions
+
+- TRY requires exactly one parameterless CATCH and ENDTRY.
+- Bare THROW raises a new anonymous exception even inside CATCH; there is no rethrow
+ syntax, error object or what(). Bare THROW prints no diagnostic itself.
+- Runtime checks are catchable; compiler errors are not. Runtime checks print the
+ source line and error reason to stderr before throwing, even when caught.
+- Uncaught exceptions also print the generic termination message and return 1.
+- Native binaries must link the C++ runtime with LLVM 23's `clang++` (`clang++-23` on
+ Debian/Ubuntu).
+- Owned STRING values follow class cleanup during unwinding; raw array buffers are
+ not freed and files are not automatically closed.
+- Embedded ORC execution with LLVM 23 is exercised by nightly CI on Linux, Windows
+ and macOS; check the native job results when changing platform-specific code.
+
+## C type extensions
+
+- C_STRING is a raw char pointer, not a STRING: explicitly cast to STRING for
+ printing or string-library calls.
+- C aliases share canonical CIdentity; do not use LLVM widths to recover signedness
+ or nominal type identity. Plain char's signedness is native, unlike pseudocode CHAR.
+- C/pseudocode conversions require casts even for literals. C scalar arithmetic uses
+ native promotions, but / always returns REAL.
+- Cast aliases must precede their use.
+- long double may have padding; sizeof does not identify its floating-point format.
+- Native C support is verified on Linux LLVM 23; cross-target is unsupported.
+
+## EXTERN "C" extension
+
+- Top-level, body-less; parameters/results are C types only, no BYREF/OVERLOAD/structs.
+ Variadic arguments must be C values (default promotions).
+- The driver checks every EXTERN symbol against INCLUDE_OBJ inputs before CodeGen;
+ `"__libc"` must be included explicitly even though the runtime always links libc.
+- Only `.o` user objects; INCLUDE_OBJ is global across inputs. `-S` does not link them.
+- Identical EXTERN redeclarations (same or other files, CIdentity-equal types) are
+ allowed; differing ones fail. Two EXTERNs differing only in case conflict
+ (pseudocode names are case insensitive).
+- Windows `-r` resolves C symbols from msvcrt.dll before the process-wide search:
+ ucrtbase.dll may also be loaded, and its separate stdout buffer reorders EXTERN
+ output against OUTPUT.
diff --git a/AGENTS/PITFALLS/files-and-types.md b/AGENTS/PITFALLS/files-and-types.md
new file mode 100644
index 0000000..60f1e53
--- /dev/null
+++ b/AGENTS/PITFALLS/files-and-types.md
@@ -0,0 +1,38 @@
+# File handling and the TYPE system
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+## File handling (§9.1)
+
+- `READ`/`WRITE`/`APPEND`/`RANDOM` are reserved keywords now — `DECLARE READ : INTEGER`
+ no longer compiles.
+- `WRITEFILE f, a, b` takes exactly two operands (the stray `, b` is a parse error).
+- READFILE's target must be a plain STRING variable — no CHAR, no `arr[i]`.
+- The CIE guide's empty-line test `IF LineOfText = ""` compares contents.
+- At most 16 files open at once.
+- All runtime misuse (not open, wrong mode, reopen, fopen failure, READFILE past EOF)
+ throws an anonymous exception — guard reads with `WHILE NOT EOF(...)`.
+- Caveat: the open-file table is keyed by the filename *string* (per CIE semantics),
+ so two spellings of one OS path (`"a.txt"` vs `"./a.txt"`) evade the already-open
+ check and get two independent buffered streams — reads through the alias can see
+ stale/empty data instead of an exception.
+
+## TYPE system (§4)
+
+- `TYPE`/`ENDTYPE`/`SET`/`DEFINE` are reserved keywords now.
+- Typing is *nominal*: two pointer types with the same pointee, or two records with
+ the same layout, are incompatible. Strict pointee matching rejects the official
+ guide's own worked example (`^INTEGER` pointed at a `Season` variable) —
+ deliberate deviation.
+- Enum OUTPUT prints the 0-based ordinal; a DECLAREd-but-unset enum *is* the first
+ enumerator (no uninitialized state). `E ± INTEGER` needs the enum on the left;
+ out-of-range results throw at run time. Enum value names are globally reserved
+ (they block DECLARE/parameter/function names).
+- There is no NULL literal: pointers zero-init to null, dereferencing one throws, and
+ unset-ness is deliberately untestable. `RETURN ^localVar` dangles — documented,
+ unchecked.
+- Record STRING fields default-construct empty strings and copy independently.
+- TYPE must be top-level; array fields inside records and bounded array parameters
+ with non-literal bounds are dedicated compile errors.
+- A digit run followed by `.` is swallowed into a REAL literal, so `1.foo` mislexes
+ (only already-invalid programs can hit it).
diff --git a/AGENTS/PITFALLS/modules-and-stdlib.md b/AGENTS/PITFALLS/modules-and-stdlib.md
new file mode 100644
index 0000000..102e07a
--- /dev/null
+++ b/AGENTS/PITFALLS/modules-and-stdlib.md
@@ -0,0 +1,23 @@
+# Multi-file builds and the standard library
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+- **Multi-file extensions.** INCLUDE imports symbols, never text; every included file
+ must also be an input.
+ - Duplicate exports across files fail even without visibility, except distinct
+ signatures whose definitions all specify OVERLOAD.
+ - Only one file may contain top-level code; top-level DECLARE is a main local, not
+ an export.
+ - Definitions-only builds now fail without FUNCTION MAIN returning INTEGER. MAIN
+ cannot coexist with top-level code.
+ - GLOBAL accepts only the five primitive scalar types and literal initializers;
+ arrays, user types and computed initializers are rejected.
+ - Own top-level constants remain positional, imported constants do not.
+- **Default standard library.** std.cps and its imported STRING library are embedded
+ and automatically linked.
+ - Use -no-std before explicitly supplying a replacement STRING class/library;
+ otherwise duplicate definitions fail.
+ - Ordinary literals require a visible STRING class with NEW(C_STRING).
+ - C_TYPE/INCLUDE/EXTERN syntax strings are parser metadata and do not construct
+ STRING.
+ - -S emits IR only; manual linking needs string.o and the C++ runtime.
diff --git a/AGENTS/PITFALLS/not-implemented.md b/AGENTS/PITFALLS/not-implemented.md
new file mode 100644
index 0000000..482330d
--- /dev/null
+++ b/AGENTS/PITFALLS/not-implemented.md
@@ -0,0 +1,18 @@
+# Not implemented (9618 features)
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+- SET types (`SET`/`DEFINE` are reserved with dedicated errors).
+- Random files (OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — TYPE is done, so
+ these are unblocked now; the RANDOM keyword is reserved and gives a dedicated parse
+ error).
+- DATE.
+- Array-typed record fields, whole-array RETURN.
+- Class inheritance (INHERITS/SUPER).
+
+## Status notes
+
+- Compiler fixtures now live in `tests/` (see [TESTING.md](../TESTING.md)).
+- Nightly CI runs the compiler suites, packaged EMBEDDED smoke checks and existing
+ plugin tests.
+- The initial standard conflicts and missing diagnostic locations are fixed on Linux.
diff --git a/AGENTS/PITFALLS/overload-template-operator.md b/AGENTS/PITFALLS/overload-template-operator.md
new file mode 100644
index 0000000..b7c139e
--- /dev/null
+++ b/AGENTS/PITFALLS/overload-template-operator.md
@@ -0,0 +1,59 @@
+# OVERLOAD, TEMPLATE and OPERATOR extensions
+
+Part of the [pitfalls index](../PITFALLS.md).
+
+## OVERLOAD
+
+- OVERLOAD is reserved and prefixes FUNCTION/PROCEDURE (after PUBLIC/PRIVATE for
+ methods). Every same-name definition must opt in.
+- Only count, canonical parameter types and array rank distinguish signatures: return
+ type, BYREF/BYVAL and array bounds do not.
+- Calls match exact static types, even for a single OVERLOAD definition; ordinary
+ functions still coerce arguments.
+- Call visibility filters the set, but duplicate signatures conflict across all
+ inputs. MAIN and builtins cannot be overloaded.
+- NEW supports overloads; DECLARE, fields and arrays needing default construction
+ require a matching no-argument NEW.
+- Private access is checked after selection, never used to select a public fallback.
+
+## TEMPLATE
+
+- Top-level FUNCTION/PROCEDURE/CLASS only; all template parameters are types, with at
+ most one final pack.
+- Exact types are required even with explicit arguments (`Max<REAL>(1, 2.5)` fails).
+ Deduction recognizes T and array element T, never the T inside Box<T>.
+- When multiple ordinary/template functions share a name, every definition must
+ specify OVERLOAD; exact ordinary definitions win, then non-variadic templates, then
+ variadic templates, with same-tier ties ambiguous. Explicit arguments select
+ templates only.
+- Packs are final non-array parameters and expand only in call/constructor argument
+ lists; pack fields are rejected, including in variadic classes.
+- Unsupported: non-type parameters, SIZEOF..., member/operator templates,
+ specialization, bare injected class name, pointer-alias templates. Pointer-linked
+ generic containers remain unsupported; use fixed fields or other supported value
+ layouts.
+- Class instances follow ordinary constructor rules, so a parameterized NEW alone does
+ not permit `DECLARE B : Box<INTEGER>` without arguments.
+- Template instances share the first requester's visibility for their cached body;
+ positional constants come from the template declaration. Instantiation depth is
+ limited to 64.
+- Lexing: empty source lists `<>` are invalid and lex as inequality; `>>` remains two
+ tokens, but adjacent `<-` and `>=` merge. Expression lookahead treats
+ `A < B, C > (D)` as a template call; parenthesize comparisons to avoid that
+ ambiguity.
+
+## OPERATOR
+
+- Unary/binary operators require OVERLOAD FUNCTION, with supported arity and at least
+ one record/class/enum operand. Pointer aliases alone do not qualify.
+- Exact matching considers visible globals and left-class members together; ties are
+ errors, including private/public ties.
+- Comparison and logic result types are declared, not assumed BOOLEAN; implicit
+ STRING to BOOLEAN is rejected. CASE comparisons must return BOOLEAN. AND/OR
+ overloads evaluate both operands.
+- Temporary member receivers are destroyed at statement end, including exception
+ exits; ordinary DECLARE objects remain function-scoped.
+- Explicit calls use OPERATOR+(a,b) or a.OPERATOR+(b).
+- Assignment uses member-only OVERLOAD PROCEDURE OPERATOR<- with one explicit
+ parameter; it is selected before fallback same-class copying. Keyword NEW retains
+ its allocation/rebinding path.