aboutsummaryrefslogtreecommitdiff
path: root/AGENTS
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 15:45:40 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 15:45:40 +0000
commit344206d57e834b2394cbe2ec0df50b04a8b3c171 (patch)
tree0eb282bc9878bf990496a5d840ca8492b3cfc508 /AGENTS
parentd1c47a328785ece9a8ffd9bbca3138818b8203b3 (diff)
feat: implement CIE 9.1 text file handling (OPENFILE/READFILE/WRITEFILE/CLOSEFILE/EOF)
Front end: OPENFILE/READFILE/WRITEFILE/CLOSEFILE plus reserved mode keywords READ/WRITE/APPEND (RANDOM reserved with a dedicated not-implemented error); statement nodes in new FileAST.h, parsing in new ParserFile.cc; EOF joins the Builtins table as the 12th builtin and CallExprAST now carries a line number for its runtime diagnostics. Runtime: new FileHandler (StringHandler-shaped leaf) lazily synthesizes internal __cps_file_* helper functions and a 16-slot open-file table keyed by filename content (strcmp — STRING '=' is pointer identity). EOF is an fgetc/ungetc peek; READFILE grows a malloc/realloc line buffer and strips a trailing CR; fopen uses text modes so mingw-w64 line translation works. File-free programs get byte-identical IR. All misuse (not open, wrong mode, reopen, fopen failure, read past EOF, table full) dies with the existing "[Fatal] line N:" pattern via a new RuntimeCheck::emitErrorAndExit overload taking runtime printf args. Also fixes a pre-existing ArithmeticHandler hole surfaced by adversarial review: mixed-width integer operands (i1 BOOLEAN / i8 CHAR vs i64) now widen via ZExt instead of emitting invalid IR, and remaining type mismatches get a clean diagnostic; -EOF(f) and -IS_NUM(x) compile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'AGENTS')
-rw-r--r--AGENTS/ARCH.md5
-rw-r--r--AGENTS/CONVENTIONS.md26
-rw-r--r--AGENTS/EXTENDING.md3
-rw-r--r--AGENTS/PITFALLS.md23
-rw-r--r--AGENTS/SUBSYSTEMS.md27
5 files changed, 61 insertions, 23 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index cad017f..cbf4b65 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -47,12 +47,13 @@ untested.)
| `lib/Lexer/Lexer.cc` | Entire lexer: one function, `Lexer::gettok()` |
| `lib/Parser/Parser.cc` | Statements, expressions, precedence table |
| `lib/Parser/ParserFunction.cc` | FUNCTION/PROCEDURE/CALL/RETURN parsing |
+| `lib/Parser/ParserFile.cc` | OPENFILE/READFILE/WRITEFILE/CLOSEFILE parsing |
| `lib/CodeGen/CodeGen.cc` | Hub: dispatch, coercion, control flow, INPUT/OUTPUT, main() |
| `lib/CodeGen/*Handler.cc`, `FunctionGen.cc`, `RuntimeCheck.cc`, `TypeSystem.cc` | Helpers owned by CodeGen |
| `CMakeLists.txt` | 3 static libs (CPSLexer, CPSParser, CPSCodeGen) + `cpsc`. **New .cc files must be added to the source lists here** |
-AST nodes live in `include/cps/AST.h` (+ `FunctionAST.h`); they are pure data with
-virtual destructors only — no codegen/visitor methods.
+AST nodes live in `include/cps/AST.h` (+ `FunctionAST.h`, `FileAST.h`); they are pure
+data with virtual destructors only — no codegen/visitor methods.
## Detail docs
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index 333af36..03a9452 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -31,14 +31,16 @@ 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. 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")`.
+(`&`, MID/LEFT/RIGHT/LCASE/UCASE, NUM_TO_STR, CHR, READFILE, 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.
+Heap-string operations live in `StringHandler` (including `&` concatenation,
+`emitConcat`), `StringConversionHandler` (NUM_TO_STR/STR_TO_NUM buffers) and
+`FileHandler` (READFILE line buffers — its `__cps_file_read` helper owns the project's
+only `realloc`); `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
@@ -72,10 +74,12 @@ 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
+**Builtins are plain identifiers backed by one table.** All 12 builtins
+(LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM/EOF) 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.
+user-function fallback), and `getExprTypeInfo` reads return types from it. EOF is the
+odd one out: its intercept consumes `CallExprAST::getLine()` (runtime diagnostics) and
+it is the only builtin that touches mutable runtime state and can exit(1) at run time.
diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md
index f6cf1b7..07549e3 100644
--- a/AGENTS/EXTENDING.md
+++ b/AGENTS/EXTENDING.md
@@ -4,7 +4,8 @@ Step-by-step patterns for adding features. Read `CONVENTIONS.md` first — every
below assumes those invariants.
**New statement:** token in `Lexer.h` enum + keyword match in `Lexer.cc`'s if-chain →
-node class in `AST.h` (unique_ptr children, move ctor, raw-ptr getters) → `ParseXxx` in
+node class in `AST.h` or a feature header (`FunctionAST.h`/`FileAST.h` precedent;
+unique_ptr children, move ctor, raw-ptr getters) → `ParseXxx` in
`Parser.cc` + branch in `ParseStatementImpl` (block statements parse their body with
`ParseBlock({terminator tokens})`; comma-separated expression lists use
`ParseExprList(close, allowEmpty)`; add the new statement's leading keyword to
diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md
index 967d577..e24b4bc 100644
--- a/AGENTS/PITFALLS.md
+++ b/AGENTS/PITFALLS.md
@@ -30,11 +30,24 @@ test programs.
loop direction is decided at run time from the STEP value's sign; STEP is evaluated
exactly once, before the loop. (The TO expression is still re-evaluated every
iteration.)
+- **File handling (§9.1) sharp edges.** `READ`/`WRITE`/`APPEND`/`RANDOM` are reserved
+ keywords now — `DECLARE READ : INTEGER` no longer compiles. `WRITEFILE f, a, b` takes
+ exactly two operands (the stray `, b` is a parse error, like multi-expression OUTPUT).
+ READFILE's target must be a plain STRING variable — no CHAR, no `arr[i]` (mirrors
+ INPUT). The CIE guide's empty-line test `IF LineOfText = ""` silently fails here
+ because STRING `=` is pointer identity — use `LENGTH(LineOfText) = 0`. At most 16
+ files open at once. All runtime misuse (not open, wrong mode, reopen, fopen failure,
+ READFILE past EOF) is a `[Fatal] line %d:` exit(1), by design — guard reads with
+ `WHILE NOT EOF(...)`. Caveat: the open-file table is keyed by the filename *string*
+ (per CIE semantics), so two spellings of one OS path (`"a.txt"` vs `"./a.txt"`) evade
+ the already-open check and get two independent buffered streams — reads through the
+ alias can see stale/empty data instead of a Fatal.
## Not implemented (9618 features)
-CASE OF, CONSTANT, TYPE/ENDTYPE (records/enums/pointers), file handling
-(OPENFILE/READFILE/WRITEFILE/CLOSEFILE/EOF), DATE, multi-expression OUTPUT, INPUT into
-array elements, string escape sequences. `README.md` lists `type` and `file` as the
-intended next work. No tests and no CI exist; verify changes by compiling sample
-programs end-to-end (command sequence in `ARCH.md`).
+CASE OF, CONSTANT, TYPE/ENDTYPE (records/enums/pointers), random files
+(OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — blocked on TYPE; the RANDOM keyword
+is reserved and gives a dedicated parse error), DATE, multi-expression OUTPUT, INPUT
+into array elements, string escape sequences. `README.md` lists `type` as the intended
+next work. No tests and no CI exist; verify changes by compiling sample programs
+end-to-end (command sequence in `ARCH.md`).
diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md
index f26cddc..6a89e2b 100644
--- a/AGENTS/SUBSYSTEMS.md
+++ b/AGENTS/SUBSYSTEMS.md
@@ -1,6 +1,6 @@
# Subsystem notes
-Deep-dives on the three CodeGen helper subsystems. (Invariants: `CONVENTIONS.md`;
+Deep-dives on the CodeGen helper subsystems. (Invariants: `CONVENTIONS.md`;
known bugs: `PITFALLS.md`.)
**Arrays** (`ArrayHandler`): a DECLAREd array is one malloc'd flat buffer (memset to
@@ -27,6 +27,25 @@ calling a name with no signature is a compile error (no extern is fabricated). B
emission is inverted: CodeGen injects a `StmtEmitter` callback, and must restore its
own insert point afterwards.
-**Runtime checks** (`RuntimeCheck`): only array bounds checking is actually wired up.
-`emitDivZeroCheck` exists but has **zero call sites** — division by zero (for `/`, DIV
-and MOD) is currently unchecked; the TODO sits in `ArithmeticHandler.cc`.
+**Runtime checks** (`RuntimeCheck`): only array bounds checking and the file checks are
+actually wired up. `emitDivZeroCheck` exists but has **zero call sites** — division by
+zero (for `/`, DIV and MOD) is currently unchecked; the TODO sits in
+`ArithmeticHandler.cc`. The public `emitErrorAndExit(Cond, Msg, ArrayRef<Value*>)`
+overload exists for failure sites whose printf arguments (line, filename) are runtime
+values — FileHandler's helpers use it; the private compile-time-line form delegates to
+it.
+
+**Files** (`FileHandler`): text-file runtime for §9.1. StringHandler-shaped leaf —
+holds Context/Builder/Module/RuntimeCheck refs only, never sees CodeGen, AST nodes or
+the symbol table; `CodeGen::emitStmt`'s four file branches (and the EOF builtin
+intercept) evaluate/coerce operands and hand over `llvm::Value*`s plus the statement
+line. On first file use, `ensureRuntime()` synthesizes the whole runtime once — the
+only module-level helper functions in the project: internal-linkage `__cps_file_find/
+open/read/write/close/eof` plus a 16-slot open-file table (`__cps_file_names/handles/
+modes` parallel globals; mode byte 0 = free). Files are keyed by filename *content*
+(strcmp scan) because STRING `=` is pointer identity; the name pointer is stored as-is
+(strings are immutable and never freed). EOF is an fgetc/ungetc peek; READFILE grows a
+malloc/realloc buffer per line and strips one trailing `\r`; fopen modes are text
+`"r"/"w"/"a"` (Windows CRT translates CRLF). File-free programs get byte-identical IR —
+nothing is declared or emitted eagerly. Helper bodies are built with the *shared*
+Builder (RuntimeCheck emits through the same one), saving/restoring the insert block.