From 344206d57e834b2394cbe2ec0df50b04a8b3c171 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sun, 26 Jul 2026 15:45:40 +0000 Subject: feat: implement CIE 9.1 text file handling (OPENFILE/READFILE/WRITEFILE/CLOSEFILE/EOF) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AGENTS/ARCH.md | 5 +++-- AGENTS/CONVENTIONS.md | 26 +++++++++++++++----------- AGENTS/EXTENDING.md | 3 ++- AGENTS/PITFALLS.md | 23 ++++++++++++++++++----- AGENTS/SUBSYSTEMS.md | 27 +++++++++++++++++++++++---- 5 files changed, 61 insertions(+), 23 deletions(-) (limited to 'AGENTS') 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)` +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. -- cgit v1.3.1-4-g156e