From 5d5c14920a9aa3397d4b479c22e37392be5557f4 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 13 Oct 2026 08:47:52 +0000 Subject: feat: support comma-separated OUTPUT expressions Co-Authored-By: Claude Fable 5.1 --- AGENTS/PITFALLS.md | 13 +++++++------ AGENTS/SUBSYSTEMS.md | 7 ++++--- include/cps/AST.h | 6 +++--- lib/CodeGen/CodeGen.cc | 29 ++++++++++++++++------------- lib/Parser/Parser.cc | 12 +++++++++--- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index fd53883..ea6a4d7 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -5,10 +5,11 @@ Every bullet below was confirmed against the source, most also by compiling and running test programs. -- **OUTPUT takes exactly one expression.** `OUTPUT a, ", ", b` prints `a`, then the - stray `, ", ", b` produces parse errors (recovery resyncs at the next statement - start, so following statements survive; the compile still fails). Concatenate with - `&`. Similarly INPUT only accepts a plain identifier — no `INPUT arr[i]`. +- **OUTPUT accepts comma-separated expressions (§5.1).** Values are evaluated and + printed left to right without automatic separators; the final scalar adds a newline. + Whole arrays and subarrays still print each element on its own line, including in + mixed output lists; a final array adds no extra newline. INPUT only accepts a plain + identifier — no `INPUT arr[i]`. - **Arrays cross function boundaries only as parameters.** `ArrayTable` is scoped per function now, so referencing another function's array fails cleanly as "Undeclared array". Whole arrays pass as `ARRAY OF T` / `ARRAY[l:u,...] OF T` @@ -44,7 +45,7 @@ test programs. 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). + exactly two operands (the stray `, b` is a parse error). 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 = ""` compares contents. At most 16 files open at once. All runtime misuse (not open, wrong mode, reopen, @@ -75,6 +76,6 @@ test programs. CASE OF, CONSTANT, SET types (`SET`/`DEFINE` are reserved with dedicated errors), random files (OPENFILE...FOR RANDOM/SEEK/GETRECORD/PUTRECORD — TYPE is done, so these are unblocked now; the RANDOM keyword is reserved and gives a dedicated parse error), -DATE, multi-expression OUTPUT, INPUT into array elements, string escape sequences, +DATE, INPUT into array elements, string escape sequences, array-typed record fields, whole-array RETURN. 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 d4d1189..ecdf3ff 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -16,9 +16,10 @@ hand-roll indexing or element loads. Array parameters: `bindArrayParameter` rebu callee-side metadata from the flattened `ptr + 2R bounds` ABI (BYVAL = malloc+memcpy whole copy), `emitArrayArgument` validates and pushes the caller side. `OUTPUT arr` (and partially-indexed accesses) is intercepted by -`ArrayHandler::tryEmitArrayOutput` — the first branch of OUTPUT dispatch, emitting -nested print loops (element kinds that are not outputtable, i.e. records/pointers, -are a compile error there) — so any rework of OUTPUT must preserve that interception. +`ArrayHandler::tryEmitArrayOutput` — the first branch for each expression in an OUTPUT +list, emitting nested print loops (element kinds that are not outputtable, i.e. records/pointers, +are a compile error there). Arrays keep their per-element newlines and dispatch +continues with the next expression; a final array requires no extra newline. **Functions** (`FunctionGen`): plain LLVM functions, external linkage, exact pseudocode name. Params are `ParamDecl{name, typeString, mode, array rank/bounds}`. BYVAL args are diff --git a/include/cps/AST.h b/include/cps/AST.h index f24d987..d98613d 100644 --- a/include/cps/AST.h +++ b/include/cps/AST.h @@ -150,10 +150,10 @@ public: }; class OutputStmtAST : public StmtAST { - std::unique_ptr Expr; + std::vector> Exprs; public: - OutputStmtAST(std::unique_ptr Expr) : Expr(std::move(Expr)) {} - ExprAST *getExpr() const { return Expr.get(); } + OutputStmtAST(std::vector> Exprs) : Exprs(std::move(Exprs)) {} + const std::vector> &getExprs() const { return Exprs; } }; class IfStmtAST : public StmtAST { diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index d5942e9..c0da7ee 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -1138,20 +1138,23 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } if (auto *Out = dynamic_cast(Stmt)) { - bool Handled = Arrays->tryEmitArrayOutput(Out->getExpr(), *this); - if (Handled) return; - - Value *Val = emitExpr(Out->getExpr()); - const TypeInfo *TypeInfo = getExprTypeInfo(Out->getExpr()); - if (!TypeInfo) { - reportError("Cannot infer the type of the OUTPUT expression"); - return; - } - if (TypeInfo->isVoid()) { - reportError("Cannot OUTPUT the result of a PROCEDURE call"); - return; + const auto &Exprs = Out->getExprs(); + for (size_t I = 0; I < Exprs.size(); ++I) { + ExprAST *Expr = Exprs[I].get(); + if (Arrays->tryEmitArrayOutput(Expr, *this)) continue; + + Value *Val = emitExpr(Expr); + const TypeInfo *TypeInfo = getExprTypeInfo(Expr); + if (!TypeInfo) { + reportError("Cannot infer the type of the OUTPUT expression"); + continue; + } + if (TypeInfo->isVoid()) { + reportError("Cannot OUTPUT the result of a PROCEDURE call"); + continue; + } + emitOutputValue(Val, TypeInfo, I + 1 == Exprs.size()); } - emitOutputValue(Val, TypeInfo, true); return; } diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index 260472c..25fc342 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -555,9 +555,15 @@ std::unique_ptr Parser::ParseStatementImpl() { } else if (CurTok == tok_output) { getNextToken(); - auto Expr = ParseExpression(); - if (!Expr) return nullptr; - return std::make_unique(std::move(Expr)); + std::vector> Exprs; + while (true) { + auto Expr = ParseExpression(); + if (!Expr) return nullptr; + Exprs.push_back(std::move(Expr)); + if (CurTok != ',') break; + getNextToken(); + } + return std::make_unique(std::move(Exprs)); } else if (CurTok == tok_if) { return ParseIfStmt(); -- cgit v1.3.1-4-g156e