aboutsummaryrefslogtreecommitdiff
path: root/AGENTS
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-20 12:02:09 +0000
committer_ov4 <ov4@ov4.org>2026-10-20 12:02:09 +0000
commit5a54ecdade484168846b8db83a7f41c5275059c0 (patch)
treef3ce7fd6c81e8a747b9a2e19b94588c307bef243 /AGENTS
parentbe964b4df4db9f42e0b7d615c1cbfa6cf268e646 (diff)
docs(strings): document the embedded standard library and STRING values
Describe -no-std, C_STRING literals, library function semantics and owning string lifetimes in the manuals and contributor guidance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS')
-rw-r--r--AGENTS/ARCH.md12
-rw-r--r--AGENTS/CONVENTIONS.md42
-rw-r--r--AGENTS/EXTENDING.md8
-rw-r--r--AGENTS/MAN.md2
-rw-r--r--AGENTS/PITFALLS.md18
-rw-r--r--AGENTS/SUBSYSTEMS.md19
-rw-r--r--AGENTS/TESTING.md2
7 files changed, 41 insertions, 62 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 26fdf7f..75202cd 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -12,10 +12,8 @@ files ──> Lexer ──> Parser ──> vector<unique_ptr<StmtAST>> ──> C
```
Fixed value representation (ABI — much code infers types backwards from these):
-INTEGER = `i64`, REAL = `double`, BOOLEAN = `i1`, CHAR = `i8`, STRING = opaque `ptr`
-to a NUL-terminated buffer. User-defined types (§4): enum = `i64` (0-based ordinal),
-pointer = opaque `ptr`, record = named struct `%record.NAME`. Enum/pointer are
-LLVM-identical to INTEGER/STRING **by design** — their identity lives in the type
+INTEGER = `i64`, REAL = `double`, BOOLEAN = `i1`, CHAR = `i8`. STRING is the standard-library class `%record.STRING`, owning a std::string handle; C_STRING is a raw NUL-terminated pointer. User-defined types (§4): enum = `i64` (0-based ordinal),
+pointer = opaque `ptr`, record = named struct `%record.NAME`. Enums and pointers are LLVM-identical to INTEGER/C_STRING **by design** — their identity lives in the type
*name*, enforced upstream (see CONVENTIONS.md). Allocation byte sizes are never computed
host-side: the module has no DataLayout string, so every allocation size is a null-GEP
`sizeof` constant (`TypeSystem::getSizeOfConstant`) folded by the backend.
@@ -86,7 +84,7 @@ successful checks on the native runner.
Driver behavior (`tools/driver/main.cc`):
-- One or more source files, with optional `-S`, `-o path` and `-r`; options may appear among inputs.
+- One or more source files, with optional `-S`, `-o path`, `-r` and `-no-std`; options may appear among inputs.
Each stream is parsed independently. INCLUDE paths are relative to the including file,
matched against command-line inputs with fs::equivalent. The driver rejects repeated
inputs and computes transitive symbol visibility; cycles do not copy statements.
@@ -320,7 +318,7 @@ do not modify the real release for local verification.
| `lib/CodeGen/ExpressionGen.cc`, `StatementGen.cc`, `ControlFlowGen.cc` | Expression/statement dispatch and control-flow emission |
| `lib/CodeGen/OperatorGen.cc`, `include/cps/Operator.h` | Exact global/member operator selection, calls and temporary receivers; shared operator spellings |
| `lib/CodeGen/ConstructorGen.cc` | Construction, copying, assignment, destruction and deferred object cleanup |
-| `lib/CodeGen/GlobalGen.cc` | Primitive GLOBAL declaration and constant-initializer passes |
+| `lib/CodeGen/GlobalGen.cc` | GLOBAL declarations and scalar/owning STRING initializers |
| `lib/CodeGen/ConstantGen.cc` | Literal constants, declaration conflicts and lexical binding restoration |
| `lib/CodeGen/ValueTypeGen.cc`, `OutputGen.cc`, `Builtins.h` | Expression typing, coercion/declarations, scalar output and the shared builtin table |
| `lib/CodeGen/Designator.cc`, `TypeCheckGen.cc` | TYPE pre-pass, member lookup/calls and lvalue access; type admission and user-kind binary operators |
@@ -360,3 +358,5 @@ source layout, naming, prose formatting and the small checks needed for changes.
**Library units.** Line-leading `#LIB` marks SourceUnit::IsLibrary. RuntimeCheck uses the externally visible i32 `__cps_user_line`; FunctionGen’s shared call hook updates it only on non-library to library calls. Runtime helper line arguments use RuntimeCheck::lineValue too. Compiler diagnostics retain the owning file location.
The embedded JIT lazily loads the captured C++ standard-library and compiler-support archives using StaticLibraryDefinitionGenerator. Members defining exception ABI or unwind symbols are filtered so all exception frames use the host runtime; additional host ABI functions are exposed explicitly. Archive buffers own their extracted bytes for the JIT lifetime.
+
+**Standard library.** Build-time embedding includes pseudocode-lib/std.cps, its imported string.cps and the matching string.o. StandardLibrary extracts these for parsing and linking; user units import std.cps by default. -no-std disables automatic loading/linking. STRING is an ordinary class; StringValueGen bridges literals, C_STRING conversions and I/O. String functions, concatenation and comparisons use normal function/operator resolution. Installed/released cpsc needs no separate library directory; manually linking saved IR still requires the matching string.o and C++ runtime.
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index f964a50..5863548 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -54,7 +54,7 @@ builtins, so values of these kinds must NEVER reach `ArithmeticHandler` or
(assignments, BYVAL args, RETURN, IF/WHILE/REPEAT/FOR operands, file operands,
builtin args) and the typed `CodeGen::emitBinaryOp` dispatcher in `ExpressionGen.cc`
guard these paths. The dispatcher resolves overloads and handles C/user kinds
-before the builtin STRING/arithmetic paths. `coerceValueToType` hard-errors
+before builtin arithmetic. `coerceValueToType` hard-errors
("Internal: ...") if a user-kind target reaches it.
**Enum value names are a flat, globally reserved namespace** owned by `TypeSystem`
@@ -72,37 +72,19 @@ builds `DesignatorExprAST`/`DesignatorAssignStmtAST` for chains involving `.`,
`^` or multiple accessors — existing programs keep byte-identical ASTs.
`CodeGen::emitLValue` resolves any of these to `{address, type name}`, emitting
index and null-deref checks on the way; `loadFromLValue` owns the rvalue read
-and the STRING null→`""` guard.
+of the resolved value.
-**Fixed IR representation per type.** INTEGER = `i64`, REAL = `double`, BOOLEAN = `i1`,
-CHAR = `i8`, STRING = opaque `ptr` to a NUL-terminated buffer. Some code still infers
-pseudocode types *backwards* from LLVM types (`getExprTypeInfo`'s fallback,
-`ArithmeticHandler`'s `isIntegerTy(64)` tests), so these widths are ABI.
+**Fixed IR representation per builtin type.** INTEGER = `i64`, REAL = `double`, BOOLEAN = `i1`, CHAR = `i8`. STRING is the ordinary standard-library class `%record.STRING`, not a builtin pointer type. C_STRING remains a raw NUL-terminated C pointer. Some arithmetic code still infers types backwards from LLVM widths; preserve the builtin scalar ABI.
-**Reference storage does not imply address comparison.** Compare values by content;
-only explicitly declared pointer types compare addresses. STRING's six comparison
-operators use `strcmp` in `StringHandler`, dispatched by pseudocode type in CodeGen
-before ArithmeticHandler. Ordering is case-sensitive byte lexicographic order;
-mixed STRING/non-STRING comparisons are errors. Whole-array comparisons remain unsupported; records/classes require an explicit
-operator overload for comparisons.
+**STRING values use the class machinery.** `string.cps` owns a std::string through an internal handle. Ordinary literals construct STRING through NEW(C_STRING); suffixed `"text"C` literals produce C_STRING directly. Ordinary literals retain legacy NUL truncation. `StringValueGen.cc` bridges construction, C_STR access and file operands; INPUT calls READ_INPUT. String functions and all concatenation/comparison operators are ordinary library definitions, with exact overload selection. Comparisons use case-sensitive byte ordering. A missing STRING class or required bridge method is a compile error.
-**Built-in strings are malloc'd and never freed — by design.** Every string-producing operation
-(`&`, MID/LEFT/RIGHT/LCASE/UCASE, NUM_TO_STR, CHR, READFILE, CHAR/number→STRING
-coercion) mallocs a fresh buffer; string literals and BOOLEAN→STRING coercion instead
-return pointers to shared module globals, so a STRING value may alias a global. `MemoryHandler` declares `free` for explicit user memory only; it must never free internal STRING buffers. Don't "fix" leaks piecemeal; it's a global design decision.
-Heap-string operations live in `StringHandler` (including `&` concatenation,
-`emitConcat`), `StringConversionHandler` (NUM_TO_STR/STR_TO_NUM buffers) and
-`FileHandler` (READFILE line buffers — its `__cps_file_read` helper grows line buffers with `realloc`); `CodeGen` also keeps its own `MallocFunc` for the char→STRING coercion
-and INPUT STRING buffers — declared in `SetupExternalFunctions`, never fetched with a
-bare `getFunction("malloc")`.
+**Ownership follows value copies.** STRING constructors/assignment prevent shared handles. Records and BYVAL arrays containing STRING recursively copy their string-bearing fields/elements; RETURN and temporary cleanup preserve ownership. C_STRING casts borrow a STRING buffer or construct an owning STRING copy in the other direction. FileHandler owns copied open-file names and frees them on close; READFILE buffers are copied into STRING and released. Explicit MemoryHandler allocations remain separate from these internal objects.
**One symbol namespace, one table.** `Symbols` (name → `SymbolInfo{Storage,
-TypeName, IsArray, ConstantValue, ScopeID, IsReference, IsGlobal}`) in CodeGen is the only symbol table.
+TypeName, IsArray, ConstantValue, ScopeID, IsReference, IsGlobal, ReadOnly}`) in CodeGen is the only symbol table.
Use `symbolAddress` for an object address: class variables and BYVAL class parameters
store their current object address in a reference slot (`IsReference`).
-A non-null ConstantValue denotes a read-only CONSTANT with no Storage. Read it
-through `emitExpr`; all writable/address-taking paths must reject it through
-`requireWritable`. Register variables through `registerSymbol()` (parameters are
+Use SymbolInfo::isConstant() for constant checks: scalar literals use ConstantValue; owning STRING constants use ReadOnly plus Storage. Read values through emitExpr. Writable/address-taking paths reject constants through requireWritable; internal STRING copy/assignment source bindings may read their storage. Register variables through `registerSymbol()` (parameters are
also bound directly by FunctionGen). `prepareSymbolDeclaration` diagnoses
same-scope conflicts involving constants before declaration emission. Unique
scope IDs distinguish sibling blocks; `emitScopedBlock` restores only bindings
@@ -142,8 +124,7 @@ call-marshalling path for both call-expr and call-stmt — requires BYREF argume
bare variables of *exactly* the declared type (their `symbolAddress` is passed), admits BYVAL
arguments through `emitOwnedValue`, and dispatches whole-array arguments to
`ArrayHandler::emitArrayArgument`. RETURN also uses `emitOwnedValue` against the
-declared return type. Class lvalues invoke a same-class BYREF OVERLOAD NEW when
-defined, otherwise they copy bits; class rvalues transfer ownership. Other values
+declared return type. Class lvalues invoke a same-class BYREF OVERLOAD NEW when defined; STRING-bearing aggregates clone their owned fields. Other classes without a copy constructor copy bits; class rvalues transfer ownership. Other values
use `emitCoercedExpr`. Top-level prototypes are pre-registered (after the TYPE
pre-pass) before statement emission, so call-before-definition is fine; a call with
no registered signature is a compile error. OVERLOAD signatures append canonical
@@ -178,11 +159,10 @@ time when the caller's bounds are constants, else at run time by throwing an exc
The LLVM ABI flattens each rank-R array parameter into a data `ptr` plus
`kBoundArgsPerDim` (=2) i64 bounds per dimension; the callee rebuilds its
`ArrayMetadata` from those (`ArrayHandler::bindArrayParameter` via the binder
-callback CodeGen injects into FunctionGen). BYVAL arrays are whole
-malloc+memcpy copies at callee entry; BYREF binds the caller's buffer.
+callback CodeGen injects into FunctionGen). BYVAL arrays copy their buffer at callee entry and clone STRING-bearing elements; BYREF binds the caller's buffer.
**Builtins are plain identifiers backed by one table.** Builtins
-(LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM/EOF/MALLOC/FREE/REALLOC) parse as
+(EOF/MALLOC/FREE/REALLOC) parse as
ordinary calls — no lexer/parser support, and the names are not reserved words. The
inline constexpr `Builtins` table in the internal `lib/CodeGen/Builtins.h` ({Name, Arity, ReturnTypeName}) is
the single source of truth: `emitCall` checks arity against it
@@ -218,3 +198,5 @@ Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix i
**Library units.** Line-leading `#LIB` marks SourceUnit::IsLibrary. RuntimeCheck uses the externally visible i32 `__cps_user_line`; FunctionGen’s shared call hook updates it only on non-library to library calls. Runtime helper line arguments use RuntimeCheck::lineValue too. Compiler diagnostics retain the owning file location.
Assignment operators are member-only `OVERLOAD PROCEDURE OPERATOR<-` with one explicit parameter. emitAssignment resolves the exact RHS static type before fallback storage copying. Internal assignment/constructor BYREF bindings may use designators or materialized temporaries; ordinary BYREF calls still require bare variables.
+
+**Default standard library.** The driver extracts embedded std.cps, its imported units and matching objects to a temporary directory that survives linking/execution. User units implicitly import std.cps; normal transitive visibility applies. -no-std suppresses all default library inputs and objects. Explicit libraries retain the ordinary INCLUDE/command-line rules. Source files and objects are embedded by a build-time Perl generator. Saved -S output remains IR only.
diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md
index a34cfa6..bb297d4 100644
--- a/AGENTS/EXTENDING.md
+++ b/AGENTS/EXTENDING.md
@@ -21,7 +21,7 @@ already terminated the block.
`Builtins` table in the internal `lib/CodeGen/Builtins.h` (single source of truth — arity checking in
`emitCall`, the return type in `getExprTypeInfo`, and the reject-user-redefinition check
all read it), then implement IR emission as a method on the fitting handler
-(String/StringConversion) or inline, and add the name intercept in `CodeGen::emitCall` — null-check every emitted argument before use, like the existing
+(for example MemoryHandler) or inline, and add the name intercept in `CodeGen::emitCall` — null-check every emitted argument before use, like the existing
branches do. `emitCall` lives in `ExpressionGen.cc`. Builtins are plain identifiers — nothing is needed in lexer/parser.
**New binary operator:** token (if multi-char) → one line in `BinopPrecedence`
@@ -29,9 +29,7 @@ branches do. `emitCall` lives in `ExpressionGen.cc`. Builtins are plain identifi
precedence must be > 0) → branch in the shared `CodeGen::emitBinaryOp` value dispatcher
in `ExpressionGen.cc` or its `ArithmeticHandler::emitBinaryOp` leaf, which dispatches
on raw LLVM operand types. Preserve overload selection and C/user-kind checks
-before reaching that leaf. Exceptions in `CodeGen::emitBinaryOp`: `&` coerces operands
-to STRING then calls `StringHandler::emitConcat`; STRING comparisons require two
-STRING operands by pseudocode type then call `StringHandler::emitCompare`.
+before reaching that leaf. STRING concatenation and comparisons are ordinary library operator overloads; implement string operations in pseudocode-lib rather than adding compiler builtin branches.
**New builtin type:** register in `TypeSystem::registerBuiltins` in `NativeTypeSystem.cc` (sizes are derived —
never hand-written) + `TypeKind` case in the **exhaustive, default-less switches**:
@@ -79,3 +77,5 @@ also be registered in EmbeddedRunner for static-runtime JIT symbol lookup.
**New libc dependency:** declare via `getOrInsertFunction` in the owning component's
`setupExternalFunctions()` (re-declaring the same symbol in two handlers is fine — it
dedupes by name).
+
+**Standard-library functions:** define ordinary functions/operators in pseudocode-lib/string.cps with C ABI helpers in string.cc as needed. std.cps imports all public library units. CMake rebuilds and embeds source/object changes; do not add string functions to Builtins.h. Ordinary string literals need STRING.NEW(C_STRING); the compiler bridges OUTPUT/casts/files through C_STR(), INPUT through READ_INPUT(), and READFILE through assignment from C_STRING.
diff --git a/AGENTS/MAN.md b/AGENTS/MAN.md
index b942674..80e563c 100644
--- a/AGENTS/MAN.md
+++ b/AGENTS/MAN.md
@@ -7,7 +7,7 @@ Read this file before editing the manual sources or their build, packaging and i
- `doc/cpsc.1.md`: command-line manual, section 1.
- `doc/language/cpsc-language-overview.7.md`: syntax overview and index, section 7.
- `doc/language/keyword/cpsc-keyword-NAME.7.md`: keyword manuals.
-- `doc/language/function/cpsc-function-NAME.7.md`: built-in function manuals.
+- `doc/language/function/cpsc-function-NAME.7.md`: builtin and standard-library function manuals.
Use lowercase filenames and preserve underscores: `NUM_TO_STR` becomes `cpsc-function-num_to_str.7.md`. Keep closing and auxiliary keywords with the main construct; for example, IF includes THEN, ELSE and ENDIF.
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index 178e173..8d2d890 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -39,9 +39,8 @@ test programs.
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 a STRING (2-byte heap buffer), not CHAR. STR_TO_NUM always
- returns REAL. NUM_TO_STR formats INTEGER with bounded snprintf `"%lld"` (the full signed
- i64 range) and REAL with `"%f"`. REAL buffers cover the full fixed-point double range.
+ 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.
@@ -77,8 +76,7 @@ test programs.
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 stay null until read (normalized to `""` per-load, not at copy
- time). Enum value names are globally reserved (they block DECLARE/parameter/function
+ 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
@@ -146,12 +144,12 @@ test programs.
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).
- Unwinding does not free heap strings/arrays or close files. Embedded ORC
+ 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 builtins. C aliases share canonical CIdentity;
+ 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,
@@ -181,6 +179,8 @@ plugin tests. The initial standard conflicts and missing diagnostic locations ar
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. Built-in STRING remains NUL-terminated: `"a\0b"` is observed as `"a"`.
+- **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 class fields, and BYVAL arrays still copy bits; owning handles inside these aggregate copies need explicit care. Class rvalues transfer ownership when consumed; discarded results and temporary receivers are destroyed at statement end.
+- **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.
diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md
index fc83fac..23a5270 100644
--- a/AGENTS/SUBSYSTEMS.md
+++ b/AGENTS/SUBSYSTEMS.md
@@ -12,11 +12,10 @@ CodeGen's `FunctionDefAST` branch, mirroring the Symbols discipline). Element si
`sizeof` constants (`ElementSizeC`), never host-side numbers. Every access goes through
`emitCheckedIndices` (validates the index count for the caller's semantics, then
per-dimension coerce + `emitIndexCheck`) → `computeFlatIndex` → `loadElement` (load +
-the STRING null-guard); the public `emitElementAddress` exposes the same checked
+the typed value load); the public `emitElementAddress` exposes the same checked
pipeline as an address for the lvalue layer (`Form[i].YearGroup`, `^arr[i]`) — never
hand-roll indexing or element loads. Array parameters: `bindArrayParameter` rebuilds
-callee-side metadata from the flattened `ptr + 2R bounds` ABI (BYVAL = malloc+memcpy
-whole copy), `emitArrayArgument` validates and pushes the caller side.
+callee-side metadata from the flattened `ptr + 2R bounds` ABI (BYVAL = allocated buffer copy with independent STRING-bearing elements), `emitArrayArgument` validates and pushes the caller side.
`OUTPUT arr` (and partially-indexed accesses) is intercepted by
`ArrayHandler::tryEmitArrayOutput` — the first branch for each expression in an OUTPUT
list, emitting nested print loops (element kinds that are not outputtable, i.e. records/pointers,
@@ -82,8 +81,7 @@ constants live in a TypeSystem-owned table, resolved in `emitExpr`'s
`VariableExprAST` branch after `Symbols` misses. The lvalue layer
(`emitLValue`/`loadFromLValue`) resolves designator chains to `{address, type name}`;
`emitCoercedExpr` is the single name-level admission gate; `emitUserKindBinaryOp`
-intercepts built-in binary operators ahead of ArithmeticHandler (which, like StringHandler and
-FileHandler, is deliberately ignorant of user kinds). The enum range check after
+intercepts built-in binary operators ahead of ArithmeticHandler (which, like FileHandler, is deliberately ignorant of user kinds). The enum range check after
`E ± INTEGER` is the only site that can produce an invalid ordinal — every other
producer (constants, same-name copies, zero-init, memset-0 arrays) is valid by
construction, which is why 0-based ordinals are load-bearing.
@@ -174,11 +172,11 @@ its statically linked ABI symbols with ORC `absoluteSymbols`, while LLJIT with
JITLink manages unwind frames (generic EH-frame on Linux, compact-unwind or
EH-frame on macOS, COFF SEH registration on Windows x86_64). Nightly CI exercises
embedded exception execution on all three platforms.
-This does not change heap-string/array lifetimes or automatically close files.
+Owned STRING values follow class cleanup. Raw array buffers are not freed and files are not automatically closed.
**Files** (`FileHandler`): text-file runtime for §9.1.
`FileHandler.cc` owns setup, lookup/open and emission entry points;
-`FileRuntimeIO.cc` builds the read/write/close/EOF helpers. StringHandler-shaped leaf —
+`FileRuntimeIO.cc` builds the read/write/close/EOF helpers. Leaf helper —
holds Context/Builder/Module/RuntimeCheck/ExceptionHandler refs only, never sees
CodeGen, AST nodes or the symbol table; `CodeGen::emitStmtImpl`'s four file branches (and the EOF builtin
intercept) evaluate/coerce operands and hand over `llvm::Value*`s plus the statement
@@ -187,8 +185,7 @@ line. Calls into throwing helpers go through ExceptionHandler. On first file use
file module-level helpers: internal-linkage `__cps_file_find/
open/read/write/close/eof` plus a 16-slot open-file table (`__cps_file_names/handles/
modes` parallel globals; mode byte 0 = free). Files are keyed by filename *content*
-(strcmp scan), consistent with STRING comparison; the name pointer is stored as-is
-(strings are immutable and never freed). EOF is an fgetc/ungetc peek; READFILE grows a
+(strcmp scan), consistent with STRING comparison; the table owns a copied filename until CLOSEFILE, independently of temporary STRING lifetimes. EOF is an fgetc/ungetc peek; READFILE grows a
malloc/realloc buffer per line and strips one trailing `\r`; fopen modes are text
`"r"/"w"/"a"` (Windows CRT translates CRLF). File-free programs get byte-identical IR —
nothing is declared or emitted eagerly. Helper bodies are built with the *shared*
@@ -196,6 +193,6 @@ Builder (RuntimeCheck emits through the same one), saving/restoring the insert b
**Explicit memory** (`MemoryHandler`): lazily emits internal allocation/find/release/resize helpers and a linked list of live allocation start addresses. Explicit MALLOC/REALLOC and keyword NEW allocations enter the list; STRING/ARRAY internals retain their existing ownership. `emitValidate` always calls the find helper, including for null, so keyword DELETE validates before running destructors. FREE validates non-null addresses, unlinks and frees the tracking node, releases the block and returns the original address. Zero-byte allocation returns null. REALLOC validates non-null addresses before its zero-size release path; null behaves as MALLOC. Positive-size resizing updates the node only after success, leaving the old block and record intact on failure. Node allocation precedes the user allocation; if the latter fails, its node is freed before the diagnostic and anonymous throw. Helpers use RuntimeCheck and ExceptionHandler, including source-line arguments and per-function unwind preparation. Sizes accept INTEGER and C integers and convert to native size_t. Address reuse can make stale aliases indistinguishable from current allocations; dereferences are not checked against this list.
-**Class copies and temporaries.** Callers construct BYVAL class lvalue arguments with a same-class BYREF OVERLOAD NEW when defined, otherwise they copy bits; the callee's RegisterCopy hook takes ownership. emitOwnedValue constructs lvalue copies and keeps pending arguments Alive until marshalling succeeds. RETURN copies lvalues and transfers rvalues. Whole records and BYVAL arrays retain shallow copies of class fields/elements. StackObject::Temporary uses statement cleanup and deferred temporary-only cleanup before local handlers, while ordinary objects remain function-scoped. Function-result method receivers are materialized by the same path as operator receivers. Assignment first selects an exact member OPERATOR<-; without one, it stores the same-class value directly rather than calling a copy constructor.
+**Class copies and temporaries.** Callers construct BYVAL class lvalue arguments with a same-class BYREF OVERLOAD NEW when defined, otherwise they copy bits; the callee's RegisterCopy hook takes ownership. emitOwnedValue constructs lvalue copies and keeps pending arguments Alive until marshalling succeeds. RETURN copies lvalues and transfers rvalues. Records and BYVAL arrays recursively clone STRING-bearing fields/elements. Other owning class fields retain the existing shallow-copy limits. StackObject::Temporary uses statement cleanup and deferred temporary-only cleanup before local handlers, while ordinary objects remain function-scoped. Function-result method receivers are materialized by the same path as operator receivers. Assignment first selects an exact member OPERATOR<-; without one, STRING-bearing aggregate assignment clones string fields from lvalues or transfers them from rvalues, releasing the target's old string fields without invoking the enclosing class destructor; other classes retain their existing bitwise assignment behavior.
-**Experimental library** (`pseudocode-lib/`): the default build compiles each .cc with the host compiler into build/pseudocode-lib/*.o and copies .cps units beside them. `_EXP_STRING` is a #LIB class with a single public internal void* handle owning std::string. The `_exp_string_impl_*` C ABI uses checked operations that print __cps_user_line and throw 0 on std::exception. Class copy/assignment methods prevent handle sharing; functions accept BYVAL copies. OUTPUT uses C stdout buffers. Install/release do not yet ship these artifacts.
+**Standard library** (`pseudocode-lib/`): the default build compiles each .cc with the host compiler into build/pseudocode-lib/*.o, copies .cps units beside them, and embeds all these outputs in cpsc. StandardLibrary extracts the resources through linking/execution; the driver implicitly imports std.cps unless -no-std is set. std.cps imports string.cps. STRING is a #LIB class with a public internal void* handle owning std::string. The _string_impl_* C ABI reports __cps_user_line and throws 0 on std::exception. Functions/operators use ordinary resolution; StringValueGen bridges literals, C_STR, C conversions and file operands. C_STRING literals have an adjacent uppercase C suffix. INPUT delegates to READ_INPUT, OUTPUT uses C stdout buffers, and READFILE invokes assignment from C_STRING then frees its temporary buffer. Source/object resources are embedded in installed/released cpsc; saved IR requires the object when manually linked.
diff --git a/AGENTS/TESTING.md b/AGENTS/TESTING.md
index 585689a..08b7bbd 100644
--- a/AGENTS/TESTING.md
+++ b/AGENTS/TESTING.md
@@ -24,4 +24,4 @@ Perl generators use Random.pm's SHA-256 sequence with explicit big-endian extrac
Before committing a test change, build the affected references and run the affected CTest cases. Compiler defects may fail: inspect retained diagnostics and document the failure accurately. Before committing workflow changes, run actionlint and retain the existing release API simulation/package smoke checks without contacting the real release. No compiler implementation changes belong in a tests-only task.
-The `_EXP_STRING` integration fixture lives in `tests/library/exp-string`. CMake copies it to the build tree as `test-cases/i/exp-string`, where relative source paths reach the built library and its object file. It uses the ordinary native/runner harness and appears as `i/exp-string` in CTest.
+The explicit STRING library integration fixture lives in `tests/library/exp-string`. CMake copies it to the build tree as `test-cases/i/exp-string`, where relative source paths reach the built library and its object file. It passes -no-std to avoid loading a second library copy. Manifests may supply compiler_args for every native/IR/runner compiler invocation. It uses the ordinary native/runner harness and appears as `i/exp-string` in CTest.