From 5a54ecdade484168846b8db83a7f41c5275059c0 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 20 Oct 2026 12:02:09 +0000 Subject: 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 --- AGENTS/ARCH.md | 12 ++--- AGENTS/CONVENTIONS.md | 48 +++++++------------- AGENTS/EXTENDING.md | 8 ++-- AGENTS/MAN.md | 2 +- AGENTS/PITFALLS.md | 18 ++++---- AGENTS/SUBSYSTEMS.md | 19 ++++---- AGENTS/TESTING.md | 2 +- README.md | 2 +- doc/cpsc.1.md | 11 +++-- doc/language/cpsc-language-overview.7.md | 11 +++-- doc/language/function/cpsc-function-asc.7.md | 4 +- doc/language/function/cpsc-function-chr.7.md | 4 +- doc/language/function/cpsc-function-is_num.7.md | 2 + doc/language/function/cpsc-function-lcase.7.md | 5 ++- doc/language/function/cpsc-function-left.7.md | 2 + doc/language/function/cpsc-function-length.7.md | 4 +- doc/language/function/cpsc-function-mid.7.md | 2 + .../function/cpsc-function-num_to_str.7.md | 2 + doc/language/function/cpsc-function-right.7.md | 2 + .../function/cpsc-function-str_to_num.7.md | 4 +- doc/language/function/cpsc-function-ucase.7.md | 5 ++- doc/language/keyword/cpsc-keyword-c_type.7.md | 4 +- doc/language/keyword/cpsc-keyword-class.7.md | 2 +- doc/language/keyword/cpsc-keyword-constant.7.md | 2 +- doc/language/keyword/cpsc-keyword-declare.7.md | 2 +- doc/language/keyword/cpsc-keyword-global.7.md | 4 +- doc/language/keyword/cpsc-keyword-input.7.md | 2 +- doc/language/keyword/cpsc-keyword-operator.7.md | 2 +- doc/language/keyword/cpsc-keyword-output.7.md | 2 +- pseudocode-lib/README.md | 51 ++++++++++++---------- tests/README.md | 4 +- 31 files changed, 132 insertions(+), 112 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> ──> 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. - -**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. - -**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. - -**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")`. +of the resolved value. + +**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. + +**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. + +**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. diff --git a/README.md b/README.md index 3cb6ffc..1d83814 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Details can be found here: [Pseudocode Guide for Teachers](https://www.cambridge - **Runtime checks**: Throw exceptions for array bounds errors, division by zero, file misuse, enum range errors and null pointer dereferences. - **Exception handling**: Catch exceptions with stack unwinding using the compiler extensions [TRY/CATCH/ENDTRY and THROW](doc/language/keyword/cpsc-keyword-try.7.md). - **Arrays**: Support multiple dimensions and custom lower and upper bounds. -- **Strings**: Support dynamic strings and built-in string functions. +- **Strings**: Provide the owning STRING class and string functions through an embedded standard library, loaded and linked by default; use `-no-std` to opt out. - **Functions and procedures**: Pass values, arrays and records by value; procedures also support `BYREF` parameters. - **Text files**: Support `OPENFILE`, `READFILE`, `WRITEFILE`, `CLOSEFILE` and `EOF`. - **User-defined types**: Support enumerated types, pointers and nested records. diff --git a/doc/cpsc.1.md b/doc/cpsc.1.md index 566e3a1..8359a25 100644 --- a/doc/cpsc.1.md +++ b/doc/cpsc.1.md @@ -6,7 +6,7 @@ cpsc - compile and run CIE pseudocode # SYNOPSIS -**cpsc** [**-r**] [**-S**] [**-o** *output*] *source* [*source* ...] +**cpsc** [**-r**] [**-S**] [**-no-std**] [**-o** *output*] *source* [*source* ...] **cpsc** **-h** @@ -20,6 +20,8 @@ Each input is a compilation unit. Use top-level `INCLUDE "lib.cps"` to import an Exactly one file may contain top-level executable statements (including top-level DECLARE). Alternatively, define a parameterless `FUNCTION MAIN RETURNS INTEGER` in one file and have no top-level executable statements anywhere. Builds containing only definitions and no MAIN fail with `error: no main program`. GLOBAL blocks declare shared primitive variables. INCLUDE, GLOBAL and MAIN entry points are compiler extensions beyond the Cambridge guide. +The embedded standard library is loaded and linked automatically. Its std.cps imports string.cps, which defines the STRING class and ordinary string functions. No extra files are needed for default compilation. With **-no-std**, supply any required library inputs explicitly; ordinary string literals require a visible STRING class. A literal with an adjacent uppercase C suffix, such as `"hello"C`, has type C_STRING and does not require that class. + # OPTIONS **-h** @@ -29,11 +31,14 @@ Exactly one file may contain top-level executable statements (including top-leve : Save to the specified path. Without **-S**, save a native executable; with **-S**, save LLVM IR. Use the path exactly as given; its extension does not select the output format. May be specified only once. With **-r**, keep this file even if execution fails. **-S** -: Save textual LLVM IR instead of a native executable. Without **-o**, use the entry source filename with a `.ll` extension beside the source file. May be combined with **-r** to save IR and run the program. +: Save textual LLVM IR instead of a native executable. Without **-o**, use the entry source filename with a `.ll` extension beside the source file. May be combined with **-r** to save IR and run the program. Manually linking saved IR that uses the standard library requires the matching string.o and C++ runtime; native output and **-r** supply them automatically. **-r** : Run the compiled program with the runner selected at build time (see EXECUTION MODES). Save output first when **-S** or **-o** is also present; otherwise do not save a file. The program inherits stdin, stdout and stderr; `INPUT` can read terminal or piped input. +**-no-std** +: Disable automatic standard-library loading and linking. Explicit INCLUDE and INCLUDE_OBJ inputs still work. + # NATIVE COMPILATION Native compilation uses LLVM, the Clang driver and LLD embedded in `cpsc`. Required startup objects, runtime archives and system-library link inputs are embedded too and unpacked temporarily when linking. No external compiler, linker or development SDK is needed. Generate programs for the compiler's own operating system and CPU architecture; cross-compilation is not supported. @@ -120,4 +125,4 @@ Generated IR uses the C++ exception ABI. Native binaries need the C++ runtime; E # SEE ALSO -[cpsc-language-overview(7)](language/cpsc-language-overview.7.md) describes supported syntax and links to the keyword and built-in function manuals. +[cpsc-language-overview(7)](language/cpsc-language-overview.7.md) describes supported syntax and links to the keyword, builtin and standard-library function manuals. diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index f04e044..1256e43 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -8,7 +8,7 @@ cpsc-language-overview - supported cpsc pseudocode syntax Keywords are uppercase and case-sensitive. Identifiers, including built-in function names, are case insensitive. Indentation is for readability; blocks use explicit closing keywords. Identifiers begin with a letter or underscore and continue with letters, digits, or underscores. -Assign with ASCII `<-`: `Count <- Count + 1`. Compare with `=`, `<>`, `<`, `>`, `<=`, and `>=`. Built-in STRING comparisons compare contents, case-sensitively. Use `+`, `-`, `*`, and `/` for arithmetic; built-in `/` always returns REAL. Use `&` to join strings. +Assign with ASCII `<-`: `Count <- Count + 1`. Compare with `=`, `<>`, `<`, `>`, `<=`, and `>=`. Standard-library STRING comparisons compare contents, case-sensitively. Use `+`, `-`, `*`, and `/` for arithmetic; built-in `/` always returns REAL. Use `&` to join strings. User-defined types can overload operators with OVERLOAD FUNCTION OPERATOR; expressions then use the declared result type. See [cpsc-keyword-operator(7)](keyword/cpsc-keyword-operator.7.md). STRING cannot be implicitly converted to BOOLEAN. @@ -16,7 +16,7 @@ Precedence, from highest to lowest: unary operators, `* / DIV MOD`, `+ - &`, com Parenthesized type casts, such as (C_INT)Number, have unary precedence. C_INT, C_CHAR, C_STRING and C_DOUBLE are native C aliases; TYPE name = C_TYPE("...") imports other supported C base types and pointers. Conversions between C and pseudocode types must be explicit. These are compiler extensions; see [cpsc-keyword-c_type(7)](keyword/cpsc-keyword-c_type.7.md). EXTERN "C" declares C functions from objects named with INCLUDE_OBJ; see [cpsc-keyword-extern(7)](keyword/cpsc-keyword-extern.7.md). -Literals include integers, decimal real numbers, TRUE/FALSE, strings in double quotes, and single-byte characters in single quotes. Negative numbers use unary minus. String and character literals support C++17 simple escapes (`\' \" \? \\ \a \b \f \n \r \t \v`), one to three octal digits, and `\x` followed by hexadecimal digits (byte values only). Unicode escapes `\uXXXX` and `\UXXXXXXXX` encode valid Unicode scalar values as UTF-8 in strings; in CHAR they must be ASCII. Unknown escapes are errors. Built-in STRING is NUL-terminated, so `"a\0b"` is observed as `"a"`. Comments begin with `//` and continue to the end of the line. +Literals include integers, decimal real numbers, TRUE/FALSE, strings in double quotes, and single-byte characters in single quotes. Negative numbers use unary minus. String and character literals support C++17 simple escapes (`\' \" \? \\ \a \b \f \n \r \t \v`), one to three octal digits, and `\x` followed by hexadecimal digits (byte values only). Unicode escapes `\uXXXX` and `\UXXXXXXXX` encode valid Unicode scalar values as UTF-8 in strings; in CHAR they must be ASCII. Unknown escapes are errors. Ordinary STRING literals retain NUL truncation, so `"a\0b"` is observed as `"a"`. An adjacent uppercase C suffix, as in `"text"C`, produces C_STRING directly. Comments begin with `//` and continue to the end of the line. Declare variables before use. See DECLARE for built-in types and arrays, TYPE for records/enumerations/pointers, and CLASS for public/private fields and methods. @@ -63,7 +63,9 @@ Multi-file builds use the compiler extensions INCLUDE and GLOBAL. Pass every sou Closing and auxiliary keywords are documented with their main constructs: THEN/ELSE/ENDIF with IF; OF/OTHERWISE/ENDCASE with CASE; CATCH/ENDTRY/THROW with TRY; ENDWHILE with WHILE; UNTIL with REPEAT; TO/STEP/NEXT with FOR; RETURN/RETURNS/BYVAL/BYREF and function/procedure closing keywords with FUNCTION/PROCEDURE; READ/WRITE/APPEND with OPENFILE; ARRAY/OF and INTEGER/REAL/BOOLEAN/CHAR/STRING with DECLARE; ENDGLOBAL with GLOBAL; ENDTYPE with TYPE; EXTERN_FUNCTION/EXTERN_PROCEDURE with EXTERN; PUBLIC/PRIVATE/ENDCLASS with CLASS. TRUE/FALSE are BOOLEAN literals. -# BUILT-IN FUNCTIONS +# STRING LIBRARY FUNCTIONS + +These ordinary functions and the STRING class come from the embedded standard library, loaded and linked automatically unless **-no-std** is given. Ordinary string literals require a visible STRING class. - [cpsc-function-length(7)](function/cpsc-function-length.7.md): find a string's length. - [cpsc-function-mid(7)](function/cpsc-function-mid.7.md): extract part of a string. @@ -76,6 +78,9 @@ Closing and auxiliary keywords are documented with their main constructs: THEN/E - [cpsc-function-is_num(7)](function/cpsc-function-is_num.7.md): test whether text represents a number. - [cpsc-function-num_to_str(7)](function/cpsc-function-num_to_str.7.md): convert a number to text. - [cpsc-function-str_to_num(7)](function/cpsc-function-str_to_num.7.md): convert text to a real number. + +# BUILT-IN FUNCTIONS + - [cpsc-function-eof(7)](function/cpsc-function-eof.7.md): test for the end of a text file. - [cpsc-function-malloc(7)](function/cpsc-function-malloc.7.md): allocate raw memory. - [cpsc-function-realloc(7)](function/cpsc-function-realloc.7.md): resize an explicit allocation. diff --git a/doc/language/function/cpsc-function-asc.7.md b/doc/language/function/cpsc-function-asc.7.md index 17ba8fe..6cc8cb6 100644 --- a/doc/language/function/cpsc-function-asc.7.md +++ b/doc/language/function/cpsc-function-asc.7.md @@ -13,7 +13,9 @@ ASC(text : STRING) RETURNS INTEGER # DESCRIPTION -Return the unsigned byte code of the character, or of the first byte of a string. Use a nonempty string; for ASCII characters this is the ASCII code. +Provided by the standard string library, loaded and linked by default. + +Return the unsigned byte code of the character, or of the first byte of a string. An empty string returns zero; for ASCII characters this is the ASCII code. # EXAMPLE diff --git a/doc/language/function/cpsc-function-chr.7.md b/doc/language/function/cpsc-function-chr.7.md index 1eab413..cc471a5 100644 --- a/doc/language/function/cpsc-function-chr.7.md +++ b/doc/language/function/cpsc-function-chr.7.md @@ -12,7 +12,9 @@ CHR(code : INTEGER) RETURNS STRING # DESCRIPTION -Convert the low eight bits of code to a one-byte string. The result is STRING, not CHAR. Use ASCII codes 1 to 127 for ordinary text; code 0 produces an empty string. +Provided by the standard string library, loaded and linked by default. + +Convert the low eight bits of code to a one-byte string. The result is STRING, not CHAR. Use ASCII codes 1 to 127 for ordinary text; code 0 produces one NUL byte, so LENGTH(CHR(0)) is 1. # EXAMPLE diff --git a/doc/language/function/cpsc-function-is_num.7.md b/doc/language/function/cpsc-function-is_num.7.md index a1fe34e..efb5f1a 100644 --- a/doc/language/function/cpsc-function-is_num.7.md +++ b/doc/language/function/cpsc-function-is_num.7.md @@ -12,6 +12,8 @@ IS_NUM(text : STRING) RETURNS BOOLEAN # DESCRIPTION +Provided by the standard string library, loaded and linked by default. + Return true for a nonempty string fully consumed by the runtime's numeric conversion. Leading whitespace is accepted; trailing whitespace or other trailing text is rejected. # EXAMPLE diff --git a/doc/language/function/cpsc-function-lcase.7.md b/doc/language/function/cpsc-function-lcase.7.md index a2cbf44..0d542be 100644 --- a/doc/language/function/cpsc-function-lcase.7.md +++ b/doc/language/function/cpsc-function-lcase.7.md @@ -7,12 +7,15 @@ cpsc-function-lcase - convert text to lower case # SYNOPSIS ```text +LCASE(character : CHAR) RETURNS CHAR LCASE(text : STRING) RETURNS STRING ``` # DESCRIPTION -Return a copy with letters converted to lower case. Unlike guide section 5.5's CHAR-to-CHAR signature, cpsc accepts text and returns STRING. Use ASCII text for predictable character conversion. +Provided by the standard string library, loaded and linked by default. + +Return a copy with letters converted to lower case. The CHAR overload follows guide section 5.5; the STRING overload is a compatibility extension. Use ASCII text for predictable character conversion. # EXAMPLE diff --git a/doc/language/function/cpsc-function-left.7.md b/doc/language/function/cpsc-function-left.7.md index 8f27e24..5bb5002 100644 --- a/doc/language/function/cpsc-function-left.7.md +++ b/doc/language/function/cpsc-function-left.7.md @@ -12,6 +12,8 @@ LEFT(text : STRING, count : INTEGER) RETURNS STRING # DESCRIPTION +Provided by the standard string library, loaded and linked by default. + Return up to count bytes from the beginning. Counts larger than the string length return the whole string; negative counts return an empty string. # EXAMPLE diff --git a/doc/language/function/cpsc-function-length.7.md b/doc/language/function/cpsc-function-length.7.md index fcdc47a..470b975 100644 --- a/doc/language/function/cpsc-function-length.7.md +++ b/doc/language/function/cpsc-function-length.7.md @@ -12,7 +12,9 @@ LENGTH(text : STRING) RETURNS INTEGER # DESCRIPTION -Return the number of bytes before the string terminator. String operations are byte-based, so this is not a Unicode character count. +Provided by the standard string library, loaded and linked by default. + +Return the number of bytes in the owning string, including embedded NUL bytes. String operations are byte-based, so this is not a Unicode character count. # EXAMPLE diff --git a/doc/language/function/cpsc-function-mid.7.md b/doc/language/function/cpsc-function-mid.7.md index 0956b43..e033543 100644 --- a/doc/language/function/cpsc-function-mid.7.md +++ b/doc/language/function/cpsc-function-mid.7.md @@ -12,6 +12,8 @@ MID(text : STRING, start : INTEGER, count : INTEGER) RETURNS STRING # DESCRIPTION +Provided by the standard string library, loaded and linked by default. + Return up to count bytes starting at the one-based position start. Positions below 1 clamp to 1; positions beyond the end return an empty string. The count clamps to the available bytes; negative counts return an empty string. # EXAMPLE diff --git a/doc/language/function/cpsc-function-num_to_str.7.md b/doc/language/function/cpsc-function-num_to_str.7.md index fb8e6fd..39b6890 100644 --- a/doc/language/function/cpsc-function-num_to_str.7.md +++ b/doc/language/function/cpsc-function-num_to_str.7.md @@ -12,6 +12,8 @@ NUM_TO_STR(number : INTEGER or REAL) RETURNS STRING # DESCRIPTION +Provided by the standard string library, loaded and linked by default. + Format INTEGER as signed decimal text and REAL with six digits after the decimal point. # EXAMPLE diff --git a/doc/language/function/cpsc-function-right.7.md b/doc/language/function/cpsc-function-right.7.md index 2f70fbd..89e58af 100644 --- a/doc/language/function/cpsc-function-right.7.md +++ b/doc/language/function/cpsc-function-right.7.md @@ -12,6 +12,8 @@ RIGHT(text : STRING, count : INTEGER) RETURNS STRING # DESCRIPTION +Provided by the standard string library, loaded and linked by default. + Return up to count bytes from the end. Use a nonnegative count. Counts larger than the string length return the whole string. # EXAMPLE diff --git a/doc/language/function/cpsc-function-str_to_num.7.md b/doc/language/function/cpsc-function-str_to_num.7.md index b0ceef2..acdf60b 100644 --- a/doc/language/function/cpsc-function-str_to_num.7.md +++ b/doc/language/function/cpsc-function-str_to_num.7.md @@ -12,7 +12,9 @@ STR_TO_NUM(text : STRING) RETURNS REAL # DESCRIPTION -Return REAL, including for integer-looking text. Conversion can stop at trailing text; invalid text returns zero. Use IS_NUM first when the whole string must be a valid number. +Provided by the standard string library, loaded and linked by default. + +Return REAL, including for integer-looking text. The whole string must be consumed. Invalid text, trailing junk and values outside the REAL range throw a catchable exception. Use IS_NUM first to check numeric text without throwing for invalid input. # EXAMPLE diff --git a/doc/language/function/cpsc-function-ucase.7.md b/doc/language/function/cpsc-function-ucase.7.md index 728ae14..868c448 100644 --- a/doc/language/function/cpsc-function-ucase.7.md +++ b/doc/language/function/cpsc-function-ucase.7.md @@ -7,12 +7,15 @@ cpsc-function-ucase - convert text to upper case # SYNOPSIS ```text +UCASE(character : CHAR) RETURNS CHAR UCASE(text : STRING) RETURNS STRING ``` # DESCRIPTION -Return a copy with letters converted to upper case. Unlike guide section 5.5's CHAR-to-CHAR signature, cpsc accepts text and returns STRING. Use ASCII text for predictable character conversion. +Provided by the standard string library, loaded and linked by default. + +Return a copy with letters converted to upper case. The CHAR overload follows guide section 5.5; the STRING overload is a compatibility extension. Use ASCII text for predictable character conversion. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-c_type.7.md b/doc/language/keyword/cpsc-keyword-c_type.7.md index 4ac4141..beff643 100644 --- a/doc/language/keyword/cpsc-keyword-c_type.7.md +++ b/doc/language/keyword/cpsc-keyword-c_type.7.md @@ -23,7 +23,7 @@ A cast has unary precedence and converts its following operand. Declare a type a C/pseudocode assignment, argument passing and mixed arithmetic require an explicit cast, including ordinary literals: use n <- (C_INT)1 and n + (C_INT)1. Different C scalar types use C++ numeric conversions, integer promotions and usual arithmetic conversions. BYREF requires the same type and performs no conversion. Comparisons and logical operations return BOOLEAN. The / operator always performs real division and returns REAL, including with two C integer operands; DIV and MOD accept C integers after promotion. C values used directly as IF/WHILE conditions need a cast to BOOLEAN. -C scalar values support INPUT and OUTPUT. Plain char prints and reads a character; signed char and unsigned char use numeric input/output. _Bool uses the existing TRUE/FALSE output and numeric input. C pointers start null and support prefix ^ for address-of, suffix ^ for dereference, and = or <> between the same C pointer type. Dereferencing null throws; void pointers cannot be dereferenced. Pointer arithmetic and direct pointer INPUT/OUTPUT are unsupported. To print a C string, cast it to STRING. STRING/char* casts share the original buffer without allocation; a null char* becomes an empty STRING. Pointer validity and writable storage remain the caller's responsibility. +C scalar values support INPUT and OUTPUT. Plain char prints and reads a character; signed char and unsigned char use numeric input/output. _Bool uses the existing TRUE/FALSE output and numeric input. C pointers start null and support prefix ^ for address-of, suffix ^ for dereference, and = or <> between the same C pointer type. Dereferencing null throws; void pointers cannot be dereferenced. Pointer arithmetic and direct pointer INPUT/OUTPUT are unsupported. To print a C string, cast it to STRING. Casting char* to STRING constructs an owning copy; a null char* becomes empty. Casting STRING to char* borrows its buffer until the string changes or is destroyed. A literal such as `"text"C` has type C_STRING without constructing STRING. Pointer validity and writable storage remain the caller's responsibility. # EXAMPLE @@ -36,7 +36,7 @@ DECLARE Text : C_STRING Number <- (C_INT)42 Pointer <- ^Number Pointer^ <- Number + (C_INT)1 -Text <- (C_STRING)"Result" +Text <- "Result"C OUTPUT (STRING)Text, ": ", Number OUTPUT Number / (C_INT)2 ``` diff --git a/doc/language/keyword/cpsc-keyword-class.7.md b/doc/language/keyword/cpsc-keyword-class.7.md index c17f3ef..139f97e 100644 --- a/doc/language/keyword/cpsc-keyword-class.7.md +++ b/doc/language/keyword/cpsc-keyword-class.7.md @@ -40,7 +40,7 @@ A constructor is a PROCEDURE named NEW, following guide section 10.2. It may be `DECLARE Object : ClassName`, `DECLARE Object : ClassName()` and `DECLARE Object : ClassName(arguments)` zero the object's storage, construct its fields recursively in declaration order, then call its NEW procedure. These automatic DECLARE, field and array calls are compiler extensions. Array elements and class-valued fields in records or classes use parameterless construction, before the enclosing constructor runs. A required constructor argument cannot be omitted; supplying arguments to a class without NEW is an error. Empty parentheses are allowed for a class without NEW. Constructor arguments after ARRAY OF are unsupported. A declaration executed again, such as inside a loop, first destroys its previous live incarnation, then zeroes and constructs its original storage again. -An `OVERLOAD PROCEDURE NEW(BYREF Other : ClassName)` defines a copy constructor. BYVAL class lvalue arguments and RETURN of class lvalues invoke it; a same-class BYVAL constructor is rejected. Classes without one retain bitwise copying. Whole records and BYVAL arrays still copy their class fields or elements shallowly. Class results transfer ownership directly into BYVAL arguments, RETURN and ordinary assignment without an assignment overload. Discarded results and temporary receivers live until the statement ends, including exception exits. Ordinary user BYREF calls still require bare variables; constructor calls can bind temporaries. +An `OVERLOAD PROCEDURE NEW(BYREF Other : ClassName)` defines a copy constructor. BYVAL class lvalue arguments and RETURN of class lvalues invoke it; a same-class BYVAL constructor is rejected. Classes without one retain bitwise copying for fields without STRING ownership. Records and BYVAL arrays recursively clone STRING-bearing fields or elements. Other owning class fields retain the existing shallow-copy limitations. Class results transfer ownership directly into BYVAL arguments, RETURN and ordinary assignment without an assignment overload. Discarded results and temporary receivers live until the statement ends, including exception exits. Ordinary user BYREF calls still require bare variables; constructor calls can bind temporaries. The keyword NEW is allowed only as the complete right side of assignment: `Target <- NEW ClassName`, `Target <- NEW ClassName()` or `Target <- NEW ClassName(arguments)`. Target must have exactly the named class type. NEW uses the checked MALLOC allocation path, zeroes storage and constructs fields before calling NEW. A method-only class receives at least one byte of heap storage so its object address is non-null. Allocation failure reports a runtime error and throws; constructor exceptions also propagate to TRY/CATCH. Private constructor access and argument checks follow ordinary member-call rules. diff --git a/doc/language/keyword/cpsc-keyword-constant.7.md b/doc/language/keyword/cpsc-keyword-constant.7.md index abc39cf..55960c5 100644 --- a/doc/language/keyword/cpsc-keyword-constant.7.md +++ b/doc/language/keyword/cpsc-keyword-constant.7.md @@ -12,7 +12,7 @@ CONSTANT identifier = literal # DESCRIPTION -Declare a read-only value whose type is inferred from its literal: INTEGER, REAL, BOOLEAN, CHAR, or STRING. A negative integer or real literal is allowed. As required by the Cambridge guide, variables, other constants, expressions, casts, and function calls cannot initialize a CONSTANT. +Declare a read-only value whose type is inferred from its literal: INTEGER, REAL, BOOLEAN, CHAR, STRING, or C_STRING for a literal with the C suffix. A negative integer or real literal is allowed. As required by the Cambridge guide, variables, other constants, expressions, casts, and function calls cannot initialize a CONSTANT. A constant is visible from its declaration to the end of its enclosing block. A top-level constant is also available in functions and procedures defined after its declaration. Functions do not inherit constants from another function or from an enclosing statement block. diff --git a/doc/language/keyword/cpsc-keyword-declare.7.md b/doc/language/keyword/cpsc-keyword-declare.7.md index 428a934..6138916 100644 --- a/doc/language/keyword/cpsc-keyword-declare.7.md +++ b/doc/language/keyword/cpsc-keyword-declare.7.md @@ -13,7 +13,7 @@ DECLARE name : ARRAY[lower:upper, ...] OF type # DESCRIPTION -Declare one variable per statement. Built-in types are INTEGER (signed 64-bit), REAL, BOOLEAN, CHAR (one byte), and STRING. User-defined TYPE and CLASS names are also accepted. Variables start with zero, FALSE, an empty string, or a zero-initialized user-defined value. +Declare one variable per statement. Built-in types are INTEGER (signed 64-bit), REAL, BOOLEAN, and CHAR (one byte). STRING is an owning class supplied by the default standard library. User-defined TYPE and CLASS names are also accepted. Variables start with zero, FALSE, an empty string, or a zero-initialized user-defined value. C_INT, C_CHAR, C_STRING and C_DOUBLE are native C aliases for int, char, char* and double. C_TYPE can introduce aliases for other supported C base types and pointers. C values start with zero or null; assignment from pseudocode types requires an explicit cast. See [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md). diff --git a/doc/language/keyword/cpsc-keyword-global.7.md b/doc/language/keyword/cpsc-keyword-global.7.md index 643e0bb..652b9ff 100644 --- a/doc/language/keyword/cpsc-keyword-global.7.md +++ b/doc/language/keyword/cpsc-keyword-global.7.md @@ -15,9 +15,9 @@ ENDGLOBAL # DESCRIPTION -GLOBAL and ENDGLOBAL are compiler extensions. Any number of GLOBAL blocks may appear at top level. Their bodies allow only DECLARE of INTEGER, REAL, CHAR, STRING or BOOLEAN variables and literal assignments to declared global variables. Negative numeric literals are allowed. Arrays, records, classes, C aliases, constructor arguments, calls, NEW and computed expressions are unsupported. +GLOBAL and ENDGLOBAL are compiler extensions. Any number of GLOBAL blocks may appear at top level. Their bodies allow only DECLARE of INTEGER, REAL, CHAR, STRING or BOOLEAN variables and literal assignments to declared global variables. Negative numeric literals are allowed. Arrays, records, other classes, C aliases, constructor arguments, calls, NEW and computed expressions are unsupported. -Global variables have program lifetime and shared writable storage. They are visible throughout their own source file and in files importing them with INCLUDE, including inside functions and methods. Local variables and parameters may shadow them. Initializers are constants established before entry; omitted initializers use zero, FALSE or null, with null STRING values read as empty strings. A STRING literal initializes a pointer to shared literal storage. +Global variables have program lifetime and shared writable storage. They are visible throughout their own source file and in files importing them with INCLUDE, including inside functions and methods. Local variables and parameters may shadow them. Literal initializers are established before user code runs. Omitted initializers use zero or FALSE; STRING globals construct empty owning values, and STRING literal assignments copy their contents. A GLOBAL block does not count as top-level executable code. A build still requires top-level executable statements in exactly one file, or a parameterless FUNCTION MAIN returning INTEGER. diff --git a/doc/language/keyword/cpsc-keyword-input.7.md b/doc/language/keyword/cpsc-keyword-input.7.md index 4c6c309..2b4da57 100644 --- a/doc/language/keyword/cpsc-keyword-input.7.md +++ b/doc/language/keyword/cpsc-keyword-input.7.md @@ -16,7 +16,7 @@ Read into an INTEGER, REAL, BOOLEAN, CHAR, or STRING location: a previously decl Native C scalar targets are also supported, using their actual widths and signedness. Plain C char reads a character; signed/unsigned char read numbers. C _Bool reads a number and stores whether it is nonzero. C pointers, including C_STRING, cannot be INPUT targets. See [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md). -The target address is evaluated once per INPUT execution, before reading the value. Array bounds, unset pointers and private fields are checked as for assignment. Constants, whole arrays, whole records/classes, enums and pointer values cannot be INPUT targets. A dereferenced pointer may select a supported target type. +The target address is evaluated once per INPUT execution, before reading the value. Array bounds, unset pointers and private fields are checked as for assignment. Constants, whole arrays, other whole records/classes, enums and pointer values cannot be INPUT targets. A dereferenced pointer may select a supported target type. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-operator.7.md b/doc/language/keyword/cpsc-keyword-operator.7.md index 0673416..d420c21 100644 --- a/doc/language/keyword/cpsc-keyword-operator.7.md +++ b/doc/language/keyword/cpsc-keyword-operator.7.md @@ -39,7 +39,7 @@ Member operators on variables, fields, array elements and pointer dereferences m Explicit calls use `OPERATOR+(A, B)` for global definitions and `A.OPERATOR+(B)` for members. They also work as statements, optionally prefixed with CALL, to discard results. Class-valued call results support explicit member operator calls, such as `Make().OPERATOR+(B)`. Indexing, function-call, address-of and dereference operators cannot be overloaded. -A class may define `OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : ClassName)` or an overload taking another exact static type. Assignment to a class lvalue selects this member before falling back to same-class bitwise assignment. The procedure takes exactly one explicit parameter; BYREF may bind a designator or materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body. +A class may define `OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : ClassName)` or an overload taking another exact static type. Assignment to a class lvalue selects this member before falling back to same-class assignment; STRING-bearing aggregates copy their owned string fields, while other fields retain their existing copy behavior. The procedure takes exactly one explicit parameter; BYREF may bind a designator or materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-output.7.md b/doc/language/keyword/cpsc-keyword-output.7.md index 3f90959..06d9910 100644 --- a/doc/language/keyword/cpsc-keyword-output.7.md +++ b/doc/language/keyword/cpsc-keyword-output.7.md @@ -12,7 +12,7 @@ OUTPUT expression, ... # DESCRIPTION -Print expressions from left to right without automatic separators, then add a newline. Strings print their contents; BOOLEAN prints TRUE or FALSE. Whole arrays and partially indexed arrays print their elements one per line; a final array adds no extra newline. Records, classes, and pointers cannot be printed directly. +Print expressions from left to right without automatic separators, then add a newline. Strings print their contents; BOOLEAN prints TRUE or FALSE. Whole arrays and partially indexed arrays print their elements one per line; a final array adds no extra newline. Other records, classes, and pointers cannot be printed directly. C scalars print with their native type's format and C argument promotions. Plain char prints a character, other C integers print numbers, and _Bool prints TRUE or FALSE. C_STRING is a pointer: cast it to STRING to print its contents. See [cpsc-keyword-c_type(7)](cpsc-keyword-c_type.7.md). diff --git a/pseudocode-lib/README.md b/pseudocode-lib/README.md index 9182b14..e0819bf 100644 --- a/pseudocode-lib/README.md +++ b/pseudocode-lib/README.md @@ -1,36 +1,41 @@ -# Experimental string library +# Standard pseudocode library -Build with `cmake --build build`. Include the built library in your program and pass it as a compiler input: +`cpsc` embeds `std.cps`, its imported `string.cps`, and the matching `string.o`. Programs use STRING and its functions without INCLUDE statements or extra command-line inputs: ```text -INCLUDE "build/pseudocode-lib/_exp_string.cps" -DECLARE Greeting : _EXP_STRING((C_STRING)"hello") -CALL _EXP_STRING_OUTPUT(Greeting & _EXP_STRING_NUM_TO_STR(42)) +DECLARE Greeting : STRING +Greeting <- "hello" +OUTPUT Greeting & NUM_TO_STR(42) ``` -```sh -build/cpsc prog.cps build/pseudocode-lib/_exp_string.cps -build/cpsc prog.cps build/pseudocode-lib/_exp_string.cps -r -``` - -INCLUDE paths are relative to the program. Adjust them when the program is elsewhere. - -`_EXP_STRING` owns an unbounded `std::string` through one handle. Constructors accept no arguments, another `_EXP_STRING`, `C_STRING`, or `(INTEGER count, C_CHAR character)`. Cast literals to C types explicitly; built-in STRING is not a constructor argument. Copy construction and assignment own independent strings. `_HANDLE` is public only for library implementation access; do not modify or free it. - -The library provides `&`, `_EXP_STRING_NUM_TO_STR` (INTEGER/REAL), `_EXP_STRING_STR_TO_NUM` (REAL), `_EXP_STRING_LENGTH` (byte length) and `_EXP_STRING_OUTPUT` (text plus newline). Numeric formatting uses `std::to_string`; parsing uses `std::stod` and requires full consumption. C++ standard exceptions print the user call line and are catchable with TRY/CATCH. The built-in STRING remains available with its existing lifetime and NUL limitations. Owning class values inside whole-record or BYVAL-array copies still have bitwise-copy limitations. +Use `-no-std` to disable automatic loading and linking. For an explicit library build, include the built unit from your program and pass every imported unit as a compiler input: -`_EXP_STRING_LEFT(S : _EXP_STRING, Count : INTEGER)` returns a new `_EXP_STRING` containing up to Count bytes from the beginning. Nonpositive counts return an empty string; counts exceeding the length return the whole string. Embedded NUL bytes are preserved. +```text +INCLUDE "build/pseudocode-lib/std.cps" +OUTPUT LENGTH("hello") +``` -`_EXP_STRING_RIGHT(S : _EXP_STRING, Count : INTEGER)` returns a new `_EXP_STRING` containing up to Count bytes from the end. Nonpositive counts return an empty string; counts exceeding the length return the whole string. Embedded NUL bytes are preserved. +```sh +cmake --build build +build/cpsc -no-std prog.cps build/pseudocode-lib/std.cps build/pseudocode-lib/string.cps -r +``` -`_EXP_STRING_MID(S : _EXP_STRING, Start : INTEGER, Count : INTEGER)` returns a new `_EXP_STRING` containing up to Count bytes from the one-based Start position. Starts below 1 clamp to 1; starts beyond the end and nonpositive counts return an empty string. Counts clamp to the remaining bytes. Embedded NUL bytes are preserved. +INCLUDE paths are relative to the including source. Adjust the example paths when the program is elsewhere. Native compilation and `-r` automatically link the required library object. `-S` only emits IR; manually linking that IR requires `build/pseudocode-lib/string.o` and a compatible C++ runtime. -`_EXP_STRING_LCASE(S : _EXP_STRING)` returns a new `_EXP_STRING` with letters converted to lower case. Its `(Ch : CHAR)` overload returns CHAR, following guide section 5.5. Nonletters and embedded NUL bytes are preserved; the source is unchanged. Use ASCII for predictable case conversion. +STRING is an owning class backed by `std::string`. Constructors accept no arguments, another STRING, C_STRING, or `(INTEGER count, C_CHAR character)`. Copies and assignments are independent, including STRING fields inside records and BYVAL arrays. `_HANDLE` is public for implementation access; do not modify or free it. -`_EXP_STRING_UCASE(S : _EXP_STRING)` returns a new `_EXP_STRING` with letters converted to upper case. Its `(Ch : CHAR)` overload returns CHAR, following guide section 5.5. Nonletters and embedded NUL bytes are preserved; the source is unchanged. Use ASCII for predictable case conversion. +Ordinary double-quoted literals construct STRING through `NEW(C_STRING)` and require a visible STRING class. They retain the legacy NUL truncation: `"a\0b"` constructs `"a"`. The adjacent uppercase suffix `"hello"C` creates C_STRING directly and works with `-no-std`. Explicit casts convert C_STRING to an owning STRING, treating null as empty. `(C_STRING)Text` and `Text.C_STR()` borrow a NUL-terminated pointer valid until Text changes or is destroyed. INPUT reads an unbounded whitespace-delimited word; OUTPUT and file I/O use NUL-terminated text. -`_EXP_STRING_ASC(S : _EXP_STRING)` returns the unsigned code of the first byte as INTEGER, or 0 for an empty string. Its `(Ch : CHAR)` overload returns the unsigned byte code of Ch. For ASCII characters this is the ASCII code. +The library provides `&`, six case-sensitive content comparisons, and these ordinary functions: -`_EXP_STRING_CHR(Code : INTEGER)` returns a new `_EXP_STRING` containing the low eight bits of Code as one byte. Unlike built-in STRING, `_EXP_STRING` preserves NUL: `_EXP_STRING_CHR(0)` has length 1. +- `LENGTH(S)` returns the byte length, including embedded NUL bytes constructed through CHR or the count/character constructor. +- `LEFT(S, Count)` and `RIGHT(S, Count)` return up to Count bytes. Nonpositive counts return empty; oversized counts return the whole string. +- `MID(S, Start, Count)` uses a one-based Start. Starts below 1 clamp to 1; starts beyond the end and nonpositive counts return empty. Counts clamp to the remaining bytes. +- `LCASE(CHAR)` and `UCASE(CHAR)` return CHAR, following guide section 5.5. STRING overloads return independent strings. Nonletters and embedded NUL bytes are preserved; use ASCII for predictable case conversion. +- `ASC(CHAR)` and `ASC(STRING)` return the unsigned character or first-byte code; an empty string returns zero. +- `CHR(Code)` returns a one-byte STRING containing the low eight bits. `LENGTH(CHR(0))` is 1. +- `NUM_TO_STR(INTEGER)` and `NUM_TO_STR(REAL)` use C++17 `std::to_string`, with six fractional digits for REAL. +- `STR_TO_NUM(S)` returns REAL using `std::stod` and requires full consumption. Invalid text, trailing junk, and out-of-range values throw. +- `IS_NUM(S)` tests full consumption by `strtod` without throwing for invalid text. Leading whitespace is accepted; trailing whitespace, trailing junk, empty input, and embedded NUL bytes are rejected. -`_EXP_STRING_IS_NUM(S : _EXP_STRING)` returns BOOLEAN indicating whether a nonempty string is fully consumed by `strtod`. Leading whitespace is accepted; trailing whitespace, other trailing text, and embedded NUL bytes are rejected. Invalid numeric text returns FALSE without throwing. +Library C++ exceptions print the user call line and can be caught with TRY/CATCH. Library functions follow the guide where specified; LEFT, STRING case conversion, byte-code and numeric-conversion functions remain compatibility extensions. diff --git a/tests/README.md b/tests/README.md index bc05b18..0529042 100644 --- a/tests/README.md +++ b/tests/README.md @@ -11,7 +11,7 @@ perl tests/summarize.pl build/test-results Use `ctest --test-dir build -L '^ii$' --output-on-failure` for one suite, or `-R '^i/arrays$'` for one case. CTest returns failure if any case fails; it continues through the other cases. There are no expected-failure exemptions. Rejection of valid CIE syntax must not be treated as a successful negative test. -The suites are **i**: independently traced fixed outputs, **ii**: C++ differential tests with Perl inputs, and **iii**: rejected programs with required causes and source lines. Fixed expectations are **pending human review**; each case's notes explain the trace and distinguish CIE rules from project extensions. REAL calculations are observed through BOOLEAN/INTEGER output; The experimental string-library fixture additionally checks its specified C++17 numeric text formatting. +The suites are **i**: independently traced fixed outputs, **ii**: C++ differential tests with Perl inputs, and **iii**: rejected programs with required causes and source lines. Fixed expectations are **pending human review**; each case's notes explain the trace and distinguish CIE rules from project extensions. REAL calculations are observed through BOOLEAN/INTEGER output; The string-library fixture additionally checks its specified C++17 numeric text formatting. The default is 10,000 random records per differential group, plus fixed boundary records. Both generated native programs and the configured `-r` path are checked; NONE builds only run native programs. Each source is compiled once per execution path and processes its records in one batch. Tests normalize only CRLF to LF; spaces, blank lines, case and final newlines remain significant. Generated text files use the same comparison rule. @@ -32,6 +32,8 @@ For adding cases and runner interfaces, see [AGENTS/TESTING.md](../AGENTS/TESTIN A case with `objects` in `case.json` builds `.c` and `.cc` files into `.o` files for `INCLUDE_OBJ` (see `i/extern-c-object` and `i/extern-cc-object`). CMake passes the project C and C++ compilers. +A manifest may set `compiler_args`, such as `["-no-std"]`, for all compiler invocations. Default tests use the embedded standard library. + Multi-file fixtures use a `sources` array in `case.json`, listing all compiler inputs relative to the case directory in command-line order. The default remains `["program.cps"]`. Included libraries live beside the fixture source or in subdirectories; INCLUDE paths stay relative to their including source. Sources are passed separately to both native compilation and the configured runner. Negative cases may add `file` to an error expectation; the reason, line and exact resolved source path must occur on the same diagnostic. Run the cross-file cases (functions, shared globals, imported types, include cycles, MAIN, exception unwinding and rejection diagnostics) with: -- cgit v1.3.1-4-g156e