aboutsummaryrefslogtreecommitdiff
path: root/AGENTS
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 12:51:55 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 12:51:55 +0000
commitd1c47a328785ece9a8ffd9bbca3138818b8203b3 (patch)
tree5a057e73674f6d7ac021572007266706fecc31fd /AGENTS
parent9164871fe4982bdd12866eb28ebded68ca3dacd1 (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.md30
-rw-r--r--AGENTS/EXTENDING.md14
-rw-r--r--AGENTS/PITFALLS.md6
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)