aboutsummaryrefslogtreecommitdiff
path: root/AGENTS/CONVENTIONS.md
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 11:15:57 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 11:15:57 +0000
commit0b4cdf7b28b3855d2dee18d91e9319d66a8ec25f (patch)
tree5b82182df4a08833f6c08065c70a7de2bdaba969 /AGENTS/CONVENTIONS.md
parent6d0165fd586739db2042b01be8301bdcb907091d (diff)
docs: add AGENTS/ architecture docs for AI-assisted development
ARCH.md holds the overview (pipeline, build/run, layout); details are split into CONVENTIONS.md, EXTENDING.md, SUBSYSTEMS.md and PITFALLS.md. All claims were verified against the source, most also empirically by compiling and running test programs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS/CONVENTIONS.md')
-rw-r--r--AGENTS/CONVENTIONS.md77
1 files changed, 77 insertions, 0 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
new file mode 100644
index 0000000..e3c67de
--- /dev/null
+++ b/AGENTS/CONVENTIONS.md
@@ -0,0 +1,77 @@
+# Load-bearing conventions
+
+These invariants span modules. Violating any of them breaks code you didn't touch.
+(Overview and build instructions: `ARCH.md`.)
+
+**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). No escape sequences
+in string/char literals. Comments are `//` to end of line.
+
+**The lexer can run once per process, from stdin only.** `gettok()` keeps its lookahead
+in a function-local `static int LastChar` and calls `getchar()` directly. No re-lexing,
+no file paths, and unit tests can't lex two programs in one process.
+
+**Types travel as uppercase `std::string`** (`"INTEGER"`, `"REAL"`, `"BOOLEAN"`,
+`"CHAR"`, `"STRING"`, `"VOID"`) — never an enum. `TypeSystem` resolves the string to
+`TypeInfo{LLVMType, ElementSize, Kind, ...}`. 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 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.
+
+**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 declared but has
+zero call sites. Don't "fix" leaks piecemeal; it's a global design decision.
+
+**One flat symbol namespace.** Two parallel maps in CodeGen — `NamedValues`
+(name → storage `Value*`) and `Symbols` (name → `SymbolInfo{Storage, TypeName,
+IsArray}`) — must always be updated in lock-step: via `registerSymbol()`, or directly
+but both together as `FunctionGen::createArgumentAllocas` does for parameters. (The
+per-type handlers' `emitDeclare` methods set `NamedValues` alone — that is dead code;
+don't imitate it.) There is no scope stack. `FunctionGen` saves/clears/restores both
+maps around a function body, but `ArrayHandler::ArrayTable` is *not* scoped (see
+`PITFALLS.md`).
+
+**Dispatch is by `dynamic_cast` chain, not virtual methods.** `CodeGen::emitStmt` and
+`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.
+
+**BYREF is a pointer-typed parameter, inferred structurally at call sites.**
+`FunctionGen` emits pointer params for BYREF; call sites (in CodeGen, duplicated for
+call-expr and call-stmt) treat *any* pointer-typed callee param as BYREF and require the
+argument to be a bare variable whose alloca is passed. The callee side, however, binds
+by the *declared* flag. Since STRING itself lowers to `ptr`, the two sides disagree for
+STRING: the caller always passes the alloca, but a declared-BYVAL STRING param (the
+default) copies that alloca *address* and treats it as the string data pointer — reading
+garbage at runtime. Only declared-BYREF STRING parameters work, and STRING arguments can
+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.