diff options
| author | _ov4 <ov4@ov4.org> | 2026-07-26 12:51:55 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-07-26 12:51:55 +0000 |
| commit | d1c47a328785ece9a8ffd9bbca3138818b8203b3 (patch) | |
| tree | 5a057e73674f6d7ac021572007266706fecc31fd /AGENTS | |
| parent | 9164871fe4982bdd12866eb28ebded68ca3dacd1 (diff) | |
fix: close correctness gaps found by post-refactor adversarial review
Confirmed by a 17-agent review pass over the refactor diff (12 findings
verified against the code and reproduced end-to-end):
- marshalCallArgs: BYREF arguments must match the declared parameter
type exactly (was: silent bit-reinterpretation, e.g. REAL passed to
BYREF INTEGER printed garbage with exit 0); whole-array arguments are
rejected (was: callee assignment smashed the array data pointer ->
runtime SIGSEGV)
- Null guards for CHR/LCASE/UCASE/IS_NUM/NUM_TO_STR/STR_TO_NUM/ASC
argument emission (was: compiler SIGSEGV on e.g. CHR(undeclared)),
matching the guards MID/RIGHT/LEFT already had
- ParseReturnStmt: propagate expression-parse failure (was: diagnostic
printed but exit 0) and recognize bare RETURN before
ENDWHILE/UNTIL/NEXT (was: bogus diagnostic on legal early return)
- emitPrototype rejects FUNCTION/PROCEDURE names that collide with
runtime/libc symbols (was: FUNCTION strlen silently hijacked the
string runtime; arity mismatch crashed createArgumentAllocas) or with
builtins (was: silently shadowed); conflicting redefinitions keep the
first signature
- FOR STEP is evaluated exactly once, before the loop (was: once per
iteration; the runtime-direction refactor had moved it to N+1)
- RETURN values are coerced to the declared return type via the
signature table (RETURN n in a RETURNS REAL function now compiles)
- '&' coerces its operands to STRING (CHAR/number operands work);
OUTPUT of a PROCEDURE call reports a clean error
AGENTS docs updated to match. All positive samples remain byte-identical
to the pre-refactor baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS')
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 30 | ||||
| -rw-r--r-- | AGENTS/EXTENDING.md | 14 | ||||
| -rw-r--r-- | AGENTS/PITFALLS.md | 6 |
3 files changed, 28 insertions, 22 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index 6d6bb81..333af36 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -26,19 +26,19 @@ identifier as a type name except `VOID` (rejected outside a RETURNS clause via i `AllowVoid` flag); other unknown types only fail (softly) in CodeGen. **Fixed IR representation per type.** INTEGER = `i64`, REAL = `double`, BOOLEAN = `i1`, -CHAR = `i8`, STRING = opaque `ptr` to a NUL-terminated buffer. Much code infers -pseudocode types *backwards* from LLVM types (`getExprTypeInfo`, `ArithmeticHandler`'s -`isIntegerTy(64)` tests, BYREF detection), so these widths are ABI. +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. **Strings are malloc'd and never freed — by design.** Every string-producing operation (`&`, MID/LEFT/RIGHT/LCASE/UCASE, NUM_TO_STR, CHR, 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. `free` is never declared or -called. Don't "fix" leaks piecemeal; it's a global design decision. All heap-string -operations (including `&` concatenation, `StringHandler::emitConcat`) live in -`StringHandler`; `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")`. +called. Don't "fix" leaks piecemeal; it's a global design decision. Heap-string +operations live in `StringHandler` (including `&` concatenation, `emitConcat`) and +`StringConversionHandler` (NUM_TO_STR/STR_TO_NUM buffers); `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")`. **One flat symbol namespace, one table.** `Symbols` (name → `SymbolInfo{Storage, TypeName, IsArray}`) in CodeGen is the only symbol table; register through @@ -63,12 +63,14 @@ continues; the failure is always diagnosed and reflected in the exit code. **Calls dispatch on the declared signature.** `FunctionGen::emitPrototype` records every function's pseudocode signature (`FuncSig`: return type name + per-param type name and BYREF flag); `CodeGen::marshalCallArgs` — the single call-marshalling path for both -call-expr and call-stmt — requires BYREF arguments to be bare variables (their alloca is -passed) and coerces BYVAL arguments to the declared parameter type. STRING works on both -sides in both modes. Top-level prototypes are pre-registered before statement emission, -so call-before-definition is fine; a call with no registered signature is a compile -error. Function names get no mangling — a pseudocode `FUNCTION printf` collides with -libc. +call-expr and call-stmt — requires BYREF arguments to be bare variables of *exactly* the +declared type (their alloca is passed), coerces BYVAL arguments to the declared +parameter type, and rejects whole-array arguments. RETURN values are coerced to the +declared return type. STRING works on both sides in both modes. Top-level prototypes are +pre-registered before statement emission, so call-before-definition is fine; a call with +no registered signature is a compile error. Function names get no mangling, so +`emitPrototype` rejects names that collide with runtime/libc symbols +(`isReservedRuntimeName`) or with builtins. **Builtins are plain identifiers backed by one table.** All 11 builtins (LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM) parse as diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md index d5f9469..f6cf1b7 100644 --- a/AGENTS/EXTENDING.md +++ b/AGENTS/EXTENDING.md @@ -15,15 +15,17 @@ already terminated the block. **New builtin function:** add a `{Name, Arity, ReturnTypeName}` row to the static `Builtins` table at the top of `CodeGen.cc` (single source of truth — arity checking in -`emitExpr` and the return type in `getExprTypeInfo` both read it), then implement IR -emission as a method on the fitting handler (String/Char/StringConversion) and add the -name intercept in `emitExpr`'s CallExprAST section. Builtins are plain identifiers — -nothing is needed in lexer/parser. +`emitExpr`, 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 `emitExpr`'s +CallExprAST section — null-check every emitted argument before use, like the existing +branches do. Builtins are plain identifiers — nothing is needed in lexer/parser. **New binary operator:** token (if multi-char) → one line in `BinopPrecedence` (Parser ctor; levels: OR=3, AND=5, comparisons=10, `+ - &`=20, `* / DIV MOD`=40; -precedence must be > 0) → branch in `ArithmeticHandler::emitBinaryOp`, which owns *all* -binary ops and dispatches on raw LLVM operand types. +precedence must be > 0) → branch in `ArithmeticHandler::emitBinaryOp`, which dispatches +on raw LLVM operand types. Exception: `&` is intercepted upstream in `CodeGen::emitExpr` +(operands coerced to STRING, then `StringHandler::emitConcat`). **New type:** register in `TypeSystem::registerBuiltins` (correct `ElementSize` — arrays malloc `count * ElementSize`) + `TypeKind` case + `getZeroValue` + diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index cd8cc92..967d577 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -13,7 +13,8 @@ test programs. `&`. Similarly INPUT only accepts a plain identifier — no `INPUT arr[i]`. - **Arrays cannot cross function boundaries.** `ArrayTable` is never scoped; metadata SSA values belong to the declaring function, so referencing another function's array - produces invalid IR, and arrays can't be parameters. + produces invalid IR. Passing a whole array as a call argument is rejected with a + compile error. - **`/` 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 i64 with sprintf `"%d"` — corrupts values over 32 @@ -27,7 +28,8 @@ test programs. `compile()` and makes the driver exit non-zero. - The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca. The loop direction is decided at run time from the STEP value's sign; STEP is evaluated - once per iteration in the condition block. + exactly once, before the loop. (The TO expression is still re-evaluated every + iteration.) ## Not implemented (9618 features) |
