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 | |
| 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')
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 219 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/CALLS.md | 60 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/EXCEPTIONS_AND_UNITS.md | 48 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/LEXING.md | 34 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/STRINGS.md | 20 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md | 56 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md | 54 | ||||
| -rw-r--r-- | AGENTS/CONVENTIONS/TYPES.md | 48 |
8 files changed, 337 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. diff --git a/AGENTS/CONVENTIONS/CALLS.md b/AGENTS/CONVENTIONS/CALLS.md new file mode 100644 index 0000000..261b06f --- /dev/null +++ b/AGENTS/CONVENTIONS/CALLS.md @@ -0,0 +1,60 @@ +# Conventions: calls, parameters and builtins + +Part of [Load-bearing conventions](../CONVENTIONS.md). Templates and operator +overloads, which build on call resolution, are in [TEMPLATES_AND_OPERATORS.md](TEMPLATES_AND_OPERATORS.md). + +**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. Method prototypes are pre-registered too. +- 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. + +**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. diff --git a/AGENTS/CONVENTIONS/EXCEPTIONS_AND_UNITS.md b/AGENTS/CONVENTIONS/EXCEPTIONS_AND_UNITS.md new file mode 100644 index 0000000..023354d --- /dev/null +++ b/AGENTS/CONVENTIONS/EXCEPTIONS_AND_UNITS.md @@ -0,0 +1,48 @@ +# Conventions: exceptions, compilation units and the standard library + +Part of [Load-bearing conventions](../CONVENTIONS.md). + +**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. + +**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. + +**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/CONVENTIONS/LEXING.md b/AGENTS/CONVENTIONS/LEXING.md new file mode 100644 index 0000000..81b01d1 --- /dev/null +++ b/AGENTS/CONVENTIONS/LEXING.md @@ -0,0 +1,34 @@ +# Conventions: text, tokens and lexing + +Part of [Load-bearing conventions](../CONVENTIONS.md). + +**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. + +Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix is +reserved in every user identifier, including EXTERN declarations. + +**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. diff --git a/AGENTS/CONVENTIONS/STRINGS.md b/AGENTS/CONVENTIONS/STRINGS.md new file mode 100644 index 0000000..2908167 --- /dev/null +++ b/AGENTS/CONVENTIONS/STRINGS.md @@ -0,0 +1,20 @@ +# Conventions: STRING values and ownership + +Part of [Load-bearing conventions](../CONVENTIONS.md). + +**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. diff --git a/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md b/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md new file mode 100644 index 0000000..3e3c3ad --- /dev/null +++ b/AGENTS/CONVENTIONS/SYMBOLS_AND_ERRORS.md @@ -0,0 +1,56 @@ +# Conventions: symbols, dispatch and diagnostics + +Part of [Load-bearing conventions](../CONVENTIONS.md). + +**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. + +**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. + +**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. diff --git a/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md b/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md new file mode 100644 index 0000000..7177b23 --- /dev/null +++ b/AGENTS/CONVENTIONS/TEMPLATES_AND_OPERATORS.md @@ -0,0 +1,54 @@ +# Conventions: templates and operator overloads + +Part of [Load-bearing conventions](../CONVENTIONS.md). Basic call resolution is in +[CALLS.md](CALLS.md). + +**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. + +**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. diff --git a/AGENTS/CONVENTIONS/TYPES.md b/AGENTS/CONVENTIONS/TYPES.md new file mode 100644 index 0000000..ff6ddf8 --- /dev/null +++ b/AGENTS/CONVENTIONS/TYPES.md @@ -0,0 +1,48 @@ +# Conventions: type system + +Part of [Load-bearing conventions](../CONVENTIONS.md). + +**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. + +**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. + +**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). |
