diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-21 12:42:53 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-21 12:42:53 +0000 |
| commit | 0085ea1c2c7ddf32f2ad239da93f27257cf2bf6d (patch) | |
| tree | f683fbdbf764e3b20bc4eb5c99c528c5585d1d46 /AGENTS/CONVENTIONS.md | |
| parent | 37766e7471b2c855923a000df7286a7b79b83d4a (diff) | |
docs(agents): split CONVENTIONS.md into topic files under AGENTS/CONVENTIONS
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS/CONVENTIONS.md')
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 219 |
1 files changed, 17 insertions, 202 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index ed9acb1..b764915 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -1,206 +1,21 @@ # Load-bearing conventions These invariants span modules. Violating any of them breaks code you didn't touch. -(Overview and build instructions: `ARCH.md`.) +(Overview and build instructions: `ARCH.md`.) The details are split by topic under +`AGENTS/CONVENTIONS/`. -**Project text must be in English.** This applies to documentation, source comments, -user-facing messages, examples and test fixtures. Unicode test data must not contain -Chinese text. Keep user instructions in README files concise; put architecture, -implementation notes and AI guidance in the relevant `AGENTS/` directory. - -**Tokens are raw `int`.** `Lexer::gettok()` returns negative values for the `Token` -enum (`include/cps/Lexer.h`) and non-negative values for literal ASCII chars (`'('`, -`'+'`, `'<'`). Parser and CodeGen switch on both forms interchangeably; `BinaryExprAST` -stores this same int as its operator. Payloads (identifier text, literal values) arrive -out-of-band via public `Lexer` fields (`IdentifierStr`, `StringVal`, `NumVal`, -`RealVal`, `CharVal`) that must be read before the next `gettok()`. - -**Keywords are UPPERCASE and case-sensitive.** `declare` silently lexes as an -identifier. Assignment is `<-` (ASCII only), equality is single `=`, inequality `<>`, -concatenation `&` (reaches the parser as raw ASCII 38 — no token). Identifiers use -ASCII case-insensitive lookup/comparison through `IdentifierMap`, `IdentifierSet` -and `sameIdentifier`; retain source spelling in ASTs and diagnostics. This includes -functions, types, enum values, fields and builtins, but not C_TYPE import strings. -Integer tokens carry a checked unsigned magnitude through lookahead; the parser -applies the sign and i64 range check, including the minimum value. String/char literals support C++17 byte escapes and Unicode escapes (UTF-8 in STRING, ASCII only in CHAR). Comments are `//` to end of line. - -**The lexer reads a caller-owned stream.** Construct it with `Lexer(std::istream &)`; -the stream must outlive the lexer. Lookahead, line number and error state belong to -each instance. The driver opens the source file; stdin is not used for compilation. - -**Type names travel as `std::string`** (uppercase builtins such as `"INTEGER"` -and `"REAL"`; user-defined names retain their spelling) — never an enum. `TypeSystem` resolves the string to -`TypeInfo{LLVMType, Kind, per-kind payload}`. The parser's `ParseTypeName` accepts *any* -identifier as a type name except `VOID` (rejected outside a RETURNS clause via its -`AllowVoid` flag); other unknown types only fail (softly) in CodeGen. - -**C aliases are structural within the C domain.** `CIdentity` retains the canonical -C base type and pointer levels independently of the alias `Name`. Use `sameType` -for storage compatibility (including BYREF and array arguments); never equate C -types just because their LLVM types or widths match. CScalar/CPointer values are -handled by CTypeGen, not the legacy representation coercions. Cross-domain -assignments, arguments and operators require explicit casts, including literals. -Plain C char uses native signedness; pseudocode CHAR stays unsigned. Numeric -conversions use source signedness, and native floating formats use numeric_limits. -Allocation sizes still use LLVM sizeof; native C type widths use build-compiler -sizeof. C alias declarations precede casts using their names; the parser tracks -declared type names to distinguish `(Type)operand` from `(expression)`. - -**User-kind typing is nominal, and checked upstream only.** Enum/Record/Pointer -identity is case-insensitive name equality (`P1 = ^INTEGER` and `P2 = ^INTEGER` are incompatible; -same-layout records too). Their LLVM representations are indistinguishable from -builtins, so values of these kinds must NEVER reach `ArithmeticHandler` or -`coerceValueToType` without a prior name-level check — `CodeGen::emitCoercedExpr` -(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 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` -(not `Symbols`, so they survive FunctionGen's save/clear/restore). Expression -resolution order is `Symbols` first, then enum constants; collisions with -DECLARE/array/parameter/function names are compile errors at declaration time, -so the order can never shadow. TYPE declarations are top-level only, registered -by `compile()`'s two-pass pre-scan (names first — pointer pointees may -forward-reference; definitions second, in source order — a by-value record -field requires its record type to be already complete). - -**Designators degrade.** The parser turns `x` / `arr[i]` into the legacy -`VariableExprAST`/`ArrayAccessExprAST` (and their assign statements) and only -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 -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, 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`). -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 -introduced by constants or hiding constants, preserving other variables' existing -function-wide visibility. FunctionGen saves/restores Symbols and inherits visible globals plus -previously visible top-level constants (ScopeID 0). Locals may shadow globals. CodeGen isolates constant -scope bookkeeping and array metadata around each function body. Arrays also -restore metadata at control-flow block exits. - -**Dispatch is by `dynamic_cast` chain, not virtual methods.** `CodeGen::emitStmtImpl` -in `StatementGen.cc` and `emitExpr` in `ExpressionGen.cc` walk linear if-chains of -casts. **A statement type missing from the chain is silently ignored** — no error. -Adding an AST node always means touching these chains. Emit statements through -`emitStmt`, whose wrapper cleans up statement temporaries after dispatch. - -**Compiler error style: print an `error:` line, set the component's error flag, and -keep compiling.** No exceptions, no exit mid-compile. `Lexer`/`Parser`/`CodeGen` each -expose `hadError()`; AST expressions/statements share source-line metadata. Parser -entry points record token locations. CodeGen saves/restores its diagnostic line -across recursive emission and coercion; `reportError` appends `at line N`. -`CodeGen::reportError(fmt, ...)` is the helper for codegen-side -diagnostics (`ArrayHandler` reaches it through its `CodeGen &` parameter, -`FunctionGen`/`ArithmeticHandler` hold a `bool &HadError` reference). The driver exits -non-zero when any flag is set, printing `compilation terminated.` once. Errors that -prevent compilation (missing input, file I/O or invalid IR) use `fatal error:`. -Execution-stage errors do not print the compilation termination line. -The parser drops a failed statement and resyncs at the -next token that can start a statement or end a block (`syncToStatementStart`), then -continues; the failure is always diagnosed and reflected in the exit code. - -**Calls share argument marshalling.** `CodeGen::emitCall` handles global expression calls, CALL statements and bare `name(args)` statements; `emitMemberCall` resolves member access and adds a hidden receiver address. Both use `marshalCallArgs` and FunctionGen for user calls; statement calls discard results. Explicit casts may bridge C void* and nominal pseudocode pointer types. - -**User calls dispatch on the declared signature.** `FunctionGen::emitPrototype` records every -function's pseudocode signature (`FuncSig`: return type name + per-param `ParamSig` -{type name, mode, array rank/bounds}); `CodeGen::marshalCallArgs` — the single -call-marshalling path for both call-expr and call-stmt — requires BYREF arguments to be -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; 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 -parameter types to the link name; ordinary names except MAIN get no mangling. -`getOverloads` filters by visibility and `resolveCall` selects exact static types/ranks. -Return types, modes and bounds never distinguish overloads. In all cases, -`emitPrototype` rejects names that collide with runtime/libc symbols -(`isReservedRuntimeName`), builtins, or enum values. Signatures retain the first -declaration spelling for LLVM lookup, independently of call-site casing. Methods use -`Class.Method` names and a hidden leading receiver address; their FuncSig parameters -contain only explicit source parameters. Method prototypes are pre-registered too. - -**Templates substitute ASTs before ordinary emission.** Keep explicit `TemplateArgs` separate from call callee names; canonical type ids have no spaces (`Box<Pair<INTEGER,REAL>>`). `TemplateClone.cc` owns deep cloning and structural substitution; every new body AST node needs coverage there. A type parameter binds one canonical type and a pack binds a list; expanded parameter names use unlexable `#` suffixes. FunctionGen stores overload signatures in `IdentifierMap<std::deque<FuncSig>>` so instantiating another template cannot invalidate a held `FuncSig *` during marshalling. CodeGen owns cloned ASTs for the entire compile and caches each instance by declaration plus canonical arguments. - -**Template selection and context are shared by typing and emission.** When multiple definitions share a function/procedure name, every ordinary and template definition must specify OVERLOAD. `resolveCall` computes static argument types/ranks once, then tries exact ordinary, non-variadic template and variadic template tiers; same-tier ties are errors. Candidate checking must stay silent and must not instantiate speculative class types. Explicit arguments pre-bind types without allowing conversions. Instances keep the template owner for diagnostics/library behavior and the first requester in `VisibilityUnit` for lookup (`value_or(CurrentUnit)`). Deferred bodies use the symbol snapshot at the template declaration to preserve positional own constants; save and restore all caller context. Class resolution hooks must honor visibility and reserve/cache the record before resolving fields. - -**Operator overloads share static selection.** OPERATOR is a reserved keyword; -`OVERLOAD FUNCTION OPERATOR+(...)` records the token on PrototypeAST. Canonical -names contain a space (`OPERATOR +`, `Class.OPERATOR AND`) to avoid collisions with -ordinary identifiers. `OperatorGen.cc` probes visible global and left-class member -signatures without emitting diagnostics or evaluating operands. Matching is exact; -member/global ties are ambiguous and access is checked only after selection. -Both getExprTypeInfo and emission must consult this resolver before hardcoded -builtin result types. Returns may be any supported function result, including -non-BOOLEAN comparisons/logic. emitCoercedExpr rejects implicit STRING to BOOLEAN; -CASE separately requires BOOLEAN comparisons. Operators use shared argument -marshalling and exception-aware calls. Member lvalues keep their original address; -rvalues are materialized with Alive tracking and destroyed at statement end. - -**Parameter modes are sticky and BYREF is procedure-only (guide §8.3).** In -`(BYREF a : T, b : T)` the `b` is BYREF too; unannotated parameters default to -BYVAL (§8.1). A BYREF parameter in a FUNCTION is a parse error. **Array -parameters**: `ARRAY OF T` (rank 1, bounds travel with the argument) or -`ARRAY[l:u,...] OF T` (literal bounds, any rank, a contract checked at compile -time when the caller's bounds are constants, else at run time by throwing an exception). -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 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 -(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 -before dispatching to the per-builtin emit branches (still an if-chain, ahead of the -user-function fallback), and `getExprTypeInfo` reads return types from it. EOF and the memory builtins consume the call source line for runtime diagnostics; they touch mutable runtime state and can throw. - -**Program exceptions use the C++ ABI; compiler diagnostics do not.** -`ExceptionHandler` emits anonymous integer exceptions (an internal placeholder), -`invoke`/`landingpad`/`resume`, and paired `__cxa_begin_catch`/`__cxa_end_catch`. -Its handler stack and catch depth are keyed by LLVM function, so synthesizing file -helpers never inherits the caller's catches. User calls, file runtime calls and -THROW must use `ExceptionHandler::emitCall`; ordinary nonthrowing libc calls stay -plain calls. RETURN must finish all active catches after evaluating its value. -`RuntimeCheck::emitCheck` prints the source line and error reason to stderr before -throwing. Generated main catches anything unhandled, prints the generic termination -message to stderr, and returns 1. Explicit bare THROW prints no diagnostic itself. -Arrays in TRY and CATCH use separate block scopes; scalars retain existing function scope. - -**Compilation units and entry points.** SourceUnit borrows its AST list; it and the -visibility matrix must outlive compile(). Select the owning unit before each pre-pass, -prototype or body so diagnostics and name lookup use that file. TypeSystem's predicate -filters types and their enum values; FuncSig records OwnerUnit. Rebuild Symbols from -visible exports for each unit: imported constants are available immediately, own -constants only at their declaration, and globals from the beginning. GLOBAL bodies -contain only primitive scalar declarations and literal assignments; IsGlobal storage -is an LLVM GlobalVariable and must survive FunctionGen's symbol reset. User MAIN is -case insensitive, takes no parameters and returns INTEGER; its LLVM name is -__cps_main with internal linkage. The generated i32 main wrapper calls it through -ExceptionHandler, truncates its result and retains the final uncaught handler. - -Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix is reserved in every user identifier, including EXTERN declarations. - -**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. +- [LEXING.md](CONVENTIONS/LEXING.md) — English-only project text, raw `int` tokens, + UPPERCASE keywords, identifier rules, `__cps_` reserved prefix, caller-owned lexer + stream. +- [TYPES.md](CONVENTIONS/TYPES.md) — type names as strings, fixed builtin IR + representations, structural C aliases, nominal user kinds, global enum namespace. +- [STRINGS.md](CONVENTIONS/STRINGS.md) — STRING as a library class, literal forms, + and value-copy ownership rules. +- [SYMBOLS_AND_ERRORS.md](CONVENTIONS/SYMBOLS_AND_ERRORS.md) — the single `Symbols` + table and scoping, designators, `dynamic_cast` dispatch chains, error reporting. +- [CALLS.md](CONVENTIONS/CALLS.md) — argument marshalling, signature-based dispatch, + parameter modes, array parameters, builtins table. +- [TEMPLATES_AND_OPERATORS.md](CONVENTIONS/TEMPLATES_AND_OPERATORS.md) — template + substitution and selection, operator and assignment overloads. +- [EXCEPTIONS_AND_UNITS.md](CONVENTIONS/EXCEPTIONS_AND_UNITS.md) — C++ ABI exceptions, + compilation units, MAIN, library units, default standard library. |
