aboutsummaryrefslogtreecommitdiff
path: root/AGENTS
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 12:04:56 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 12:04:56 +0000
commitefc86b5c7625cf4c7074319f42f68807a25bde9d (patch)
tree0e2e1b5d9d336b1744602b3e47817f8a6b9c15f2 /AGENTS
parent4df7c22b99349dcf2175d69679eafa175d49aa9f (diff)
refactor: single builtin registry, retire keyword route for builtins
All 11 builtins now live in one static {Name, Arity, ReturnTypeName} table in CodeGen.cc: emitExpr checks arity against it (uniform "Error: X expects N args" diagnostics) and getExprTypeInfo reads return types from it, removing the duplicated hard-coded table. LENGTH/MID/RIGHT/LEFT/LCASE/UCASE lose their lexer tokens and ParseStringBuiltin; they parse as ordinary identifier calls exactly like ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM already did. Behavior note: these six names are no longer reserved words. AGENTS docs updated for the new builtin story and the batch-1 error contract (exit codes, verifyModule, fixed descending FOR). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS')
-rw-r--r--AGENTS/ARCH.md19
-rw-r--r--AGENTS/CONVENTIONS.md29
-rw-r--r--AGENTS/EXTENDING.md10
-rw-r--r--AGENTS/PITFALLS.md12
4 files changed, 34 insertions, 36 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 63087ac..a23297b 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -23,19 +23,16 @@ mkdir -p build && cd build && cmake -DLLVM_DIR=/usr/lib/llvm-18/cmake .. && make
clang-18 prog.ll -o prog && ./prog
```
-Three facts about the driver (`tools/driver/main.cc`, 17 lines) that break naive automation:
+Facts about the driver (`tools/driver/main.cc`) that break naive automation:
- **No argv.** `./cpsc file.cps` blocks forever on stdin; you must redirect `<`.
-- **IR and error messages share stderr.** A failed parse still dumps (partial) IR,
- prefixed by `Error:` lines that make clang reject the file.
-- **Exit code is always 0**, even on errors. Grepping stderr for `Error:` catches most
- failures but has false negatives: some parse paths return nullptr without printing
- anything (an identifier statement not followed by `<-`/`[`, an unrecognized statement
- token), the keyword builtins' arity checks print without the `Error:` prefix
- (`LENGTH expects 1 arg`), and the identifier-route builtins
- (ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM) fail arity checks silently. Those statements
- just vanish from otherwise-valid IR. The only reliable check is running the program
- and diffing its output.
+- **IR and error messages share stderr.** A failed compile still dumps (partial) IR,
+ mixed with `Error:` lines that make clang reject the file.
+- **Exit code is non-zero iff a diagnostic was emitted.** Lexer, Parser and CodeGen each
+ track an error flag (`hadError()`); every failure path prints an `Error:` line, and
+ `main` returns 1 when any flag is set. The emitted module is also checked with
+ `verifyModule`, so invalid IR flips the exit code too. Check the exit code — do not
+ grep stderr.
Build pin: **LLVM 18 only.** LLVM 19 breaks compilation (`ArithmeticHandler.cc` uses
`llvm::Module` methods relying on LLVM 18's transitive `#include`; fix would be adding
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index e3c67de..c05734f 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -49,12 +49,14 @@ maps around a function body, but `ArrayHandler::ArrayTable` is *not* scoped (see
`emitExpr` walk a linear if-chain of casts. **A statement type missing from the chain is
silently ignored** — no error. Adding an AST node always means touching these chains.
-**Error style everywhere: `fprintf(stderr, ...)` then return nullptr/void, and keep
-compiling.** No exceptions, no exit, no error counter. Most messages start with
-`Error:`; the keyword builtins' arity checks in `emitExpr` lack the prefix, and the
-identifier-route builtins' arity checks and several parse failures print nothing at all.
-The parser drops a failed statement, skips one token, and continues, so erroneous
-statements simply vanish from the program.
+**Error style everywhere: 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()`; `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. The parser still drops a failed statement, skips one
+token, and continues — later statements can be swallowed during recovery, but the
+failure is always diagnosed and reflected in the exit code.
**BYREF is a pointer-typed parameter, inferred structurally at call sites.**
`FunctionGen` emits pointer params for BYREF; call sites (in CodeGen, duplicated for
@@ -67,11 +69,10 @@ garbage at runtime. Only declared-BYREF STRING parameters work, and STRING argum
never be literals/expressions. Function names get no mangling — a pseudocode
`FUNCTION printf` collides with libc.
-**Builtins have two routes and one trap.** LENGTH/MID/RIGHT/LEFT/LCASE/UCASE are
-reserved keywords with tokens, normalized back into ordinary `CallExprAST` by the
-parser; ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM need no lexer/parser support at all —
-identifiers may contain `_` and `Name(args)` already parses as a call. Both routes are
-intercepted by name-string comparison in `emitExpr`'s `CallExprAST` branch *before* the
-user-function fallback. The trap: builtin return types are duplicated in
-`getExprTypeInfo`'s hard-coded table — forget that second edit and OUTPUT prints the
-result with the wrong format, with no compile error.
+**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
+ordinary calls — no lexer/parser support, and the names are not reserved words. The
+static `Builtins` table at the top of `CodeGen.cc` ({Name, Arity, ReturnTypeName}) is
+the single source of truth: `emitExpr`'s `CallExprAST` branch 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.
diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md
index 62cdf40..3aab424 100644
--- a/AGENTS/EXTENDING.md
+++ b/AGENTS/EXTENDING.md
@@ -11,10 +11,12 @@ statements — terminator keyword + tok_eof guard) → `dynamic_cast` branch in
`if (!Builder->GetInsertBlock()->getTerminator())` — a RETURN in the body may have
already terminated the block.
-**New builtin function:** implement IR emission as a method on the fitting handler
-(String/Char/StringConversion), add the name intercept in `emitExpr`'s CallExprAST
-section with an arg-count check, **and** add the name to `getExprTypeInfo`'s table.
-Identifier-shaped names need nothing in lexer/parser.
+**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.
**New binary operator:** token (if multi-char) → one line in `BinopPrecedence`
(Parser ctor; levels: OR=3, AND=5, comparisons=10, `+ - &`=20, `* / DIV MOD`=40;
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index 3b123fe..b068dee 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -7,11 +7,6 @@ test programs.
- **STRING `=` / `<>` compares pointer identity, not contents.** No strcmp anywhere;
two equal heap strings compare unequal.
-- **Descending FOR loops never work.** CodeGen picks the loop direction at compile time
- by dynamic_casting the STEP to a negative `IntegerExprAST` — but `-1` parses as the
- expression `0 - 1` (the lexer has no signed literals), so that branch is dead code and
- *every* FOR compiles with an ascending `<=` test. `FOR i <- 5 TO 1 STEP -1` runs zero
- iterations (empirically verified).
- **OUTPUT takes exactly one expression.** `OUTPUT a, ", ", b` silently drops everything
after the comma — and if the dropped tail ends in an identifier, error recovery also
swallows the first token of the *next* statement. Concatenate with `&`. Similarly
@@ -29,8 +24,11 @@ test programs.
inference prints garbage instead of erroring.
- The lexer emits no newline tokens; the grammar is purely keyword-delimited. Bare
`RETURN` is only recognized right before ENDIF/ELSE/ENDFUNCTION/ENDPROCEDURE.
-- `verifyFunction` results are ignored; invalid modules still print.
-- The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca.
+- Invalid modules still print their IR, but `verifyModule` runs at the end of
+ `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.
## Not implemented (9618 features)