From a6b8afcfb61e1d321405fe8713beaec2aa6320a2 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sat, 17 Oct 2026 05:08:42 +0000 Subject: feat(input): support array elements and member targets Co-Authored-By: Claude Fable 5.1 --- include/cps/AST.h | 6 ++-- include/cps/CodeGen.h | 2 +- lib/CodeGen/CTypeGen.cc | 6 ++-- lib/CodeGen/StatementGen.cc | 29 ++++++--------- lib/Parser/Parser.cc | 19 +++++----- tests/i/input-targets/case.json | 1 + tests/i/input-targets/expected.txt | 11 ++++++ tests/i/input-targets/input.txt | 2 ++ tests/i/input-targets/notes.md | 7 ++++ tests/i/input-targets/program.cps | 56 +++++++++++++++++++++++++++++ tests/iii/input-invalid-targets/case.json | 32 +++++++++++++++++ tests/iii/input-invalid-targets/notes.md | 3 ++ tests/iii/input-invalid-targets/program.cps | 22 ++++++++++++ 13 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 tests/i/input-targets/case.json create mode 100644 tests/i/input-targets/expected.txt create mode 100644 tests/i/input-targets/input.txt create mode 100644 tests/i/input-targets/notes.md create mode 100644 tests/i/input-targets/program.cps create mode 100644 tests/iii/input-invalid-targets/case.json create mode 100644 tests/iii/input-invalid-targets/notes.md create mode 100644 tests/iii/input-invalid-targets/program.cps diff --git a/include/cps/AST.h b/include/cps/AST.h index 5aadc21..c7764c6 100644 --- a/include/cps/AST.h +++ b/include/cps/AST.h @@ -170,10 +170,10 @@ public: }; class InputStmtAST : public StmtAST { - std::string Name; + std::unique_ptr Target; public: - InputStmtAST(const std::string &Name) : Name(Name) {} - const std::string &getName() const { return Name; } + InputStmtAST(std::unique_ptr Target) : Target(std::move(Target)) {} + ExprAST *getTarget() const { return Target.get(); } }; class OutputStmtAST : public StmtAST { diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 3c80eb8..883cf71 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -89,7 +89,7 @@ class CodeGen { llvm::Value *emitCBinaryOp(int Op, llvm::Value *L, const TypeInfo *Left, llvm::Value *R, const TypeInfo *Right, int Line); void emitCOutput(llvm::Value *Val, const TypeInfo *Info, bool AppendNewline); - void emitCInput(const SymbolInfo *Symbol, const TypeInfo *Info); + void emitCInput(llvm::Value *Addr, const TypeInfo *Info); // TYPE-system registration (Designator.cc) and operators (TypeCheckGen.cc). void runTypePrePass(const std::vector> &Statements); diff --git a/lib/CodeGen/CTypeGen.cc b/lib/CodeGen/CTypeGen.cc index 80a7d3b..7623103 100644 --- a/lib/CodeGen/CTypeGen.cc +++ b/lib/CodeGen/CTypeGen.cc @@ -177,7 +177,7 @@ void CodeGen::emitCOutput(Value *Val, const TypeInfo *Info, bool AppendNewline) Builder->CreateCall(PrintfFunc, {Fmt, Val}); } -void CodeGen::emitCInput(const SymbolInfo *Symbol, const TypeInfo *Info) { +void CodeGen::emitCInput(Value *Addr, const TypeInfo *Info) { if (Info->Kind != TypeKind::CScalar || Info->isVoid()) { reportError("INPUT is not supported for C pointer or void type %s", Info->Name.c_str()); return; @@ -191,9 +191,9 @@ void CodeGen::emitCInput(const SymbolInfo *Symbol, const TypeInfo *Info) { Builder->CreateCall(ScanfFunc, {Fmt, Temp}); Value *Input = Builder->CreateLoad(NativeInt->LLVMType, Temp); Value *Bool = convertNumericValue(Input, NativeInt, Info); - Builder->CreateStore(Bool, Symbol->Storage); + Builder->CreateStore(Bool, Addr); return; } Value *Fmt = Builder->CreateGlobalString(Info->CInputFormat, "fmt_c_input", 0, TheModule.get()); - Builder->CreateCall(ScanfFunc, {Fmt, Symbol->Storage}); + Builder->CreateCall(ScanfFunc, {Fmt, Addr}); } diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 3ea0670..2c80437 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -121,29 +121,20 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } if (auto *In = dynamic_cast(Stmt)) { - const SymbolInfo *Info = getSymbolInfo(In->getName()); - if (!Info) { - reportError("Unknown variable name %s", In->getName().c_str()); - return; - } - if (!requireWritable(*Info, In->getName())) return; - if (Info->IsArray) { - reportError("INPUT for entire array %s is not supported", In->getName().c_str()); - return; - } - - const TypeInfo *TypeInfo = resolveType(Info->TypeName); + LValueInfo Target; + if (!emitLValue(In->getTarget(), Target)) return; + const TypeInfo *TypeInfo = resolveType(Target.TypeName); if (!TypeInfo) { - reportError("Unknown type for INPUT %s", In->getName().c_str()); + reportError("Unknown type for INPUT %s", Target.TypeName.c_str()); return; } std::vector Args; if (TypeInfo->isC()) { - emitCInput(Info, TypeInfo); + emitCInput(Target.Addr, TypeInfo); } else if (TypeInfo->isReal()) { Args.push_back(ScanfFloatFormatStr); - Args.push_back(Info->Storage); + Args.push_back(Target.Addr); Builder->CreateCall(ScanfFunc, Args); } else if (TypeInfo->isBoolean()) { AllocaInst *TempInt = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), "tmp_bool_input"); @@ -153,21 +144,21 @@ void CodeGen::emitStmt(StmtAST *Stmt) { Value *Val = Builder->CreateLoad(Type::getInt64Ty(*TheContext), TempInt); Value *BoolVal = Builder->CreateICmpNE(Val, ConstantInt::get(*TheContext, APInt(64, 0)), "bool_cast"); - Builder->CreateStore(BoolVal, Info->Storage); + Builder->CreateStore(BoolVal, Target.Addr); } else if (TypeInfo->isString()) { Value *Mem = Builder->CreateCall(MallocFunc, ConstantInt::get(*TheContext, APInt(64, 1024)), "input_str"); Args.push_back(ScanfStringFormatStr); Args.push_back(Mem); Builder->CreateCall(ScanfFunc, Args); - Builder->CreateStore(Mem, Info->Storage); + Builder->CreateStore(Mem, Target.Addr); } else if (TypeInfo->isChar()) { Value *CharFmt = Builder->CreateGlobalString(" %c", "fmt_in_char", 0, TheModule.get()); Args.push_back(CharFmt); - Args.push_back(Info->Storage); + Args.push_back(Target.Addr); Builder->CreateCall(ScanfFunc, Args); } else if (TypeInfo->Kind == TypeKind::Integer) { Args.push_back(ScanfFormatStr); - Args.push_back(Info->Storage); + Args.push_back(Target.Addr); Builder->CreateCall(ScanfFunc, Args); } else { // Enum/Record/Pointer and any future kind: scanf'ing raw bytes diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index f2fcbc8..d4d04d2 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -116,14 +116,17 @@ std::unique_ptr Parser::ParseStatementImpl() { std::string Name = Lex.IdentifierStr; int Line = Lex.getLine(); getNextToken(); - if (CurTok == '[' || CurTok == '.' || CurTok == '^') { - // Consume the whole designator so it cannot replay as statements. - std::vector Dummy; - ParseDesignatorSuffix(Dummy); - fprintf(stderr, "error: INPUT target must be a plain variable at line %d\n", Line); - return nullptr; - } - return std::make_unique(Name); + std::vector Accesses; + if (!ParseDesignatorSuffix(Accesses)) return nullptr; + std::unique_ptr Target; + if (Accesses.empty()) + Target = std::make_unique(Name); + else if (Accesses.size() == 1 && Accesses[0].Kind == AccessKind::Index) + Target = std::make_unique(Name, std::move(Accesses[0].Indices), Line); + else + Target = std::make_unique(Name, std::move(Accesses), Line); + Target->setLine(Line); + return std::make_unique(std::move(Target)); } else if (CurTok == tok_output) { getNextToken(); diff --git a/tests/i/input-targets/case.json b/tests/i/input-targets/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/input-targets/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/input-targets/expected.txt b/tests/i/input-targets/expected.txt new file mode 100644 index 0000000..75640cd --- /dev/null +++ b/tests/i/input-targets/expected.txt @@ -0,0 +1,11 @@ +10:20:30:40 +80:Ada:TRUE:A +TRUE:-7 +60:70 +index evaluated +90 +[Fatal] line 45: Array index out of bounds +bounds caught +[Fatal] line 51: Dereference of an unset pointer +pointer caught +100 diff --git a/tests/i/input-targets/input.txt b/tests/i/input-targets/input.txt new file mode 100644 index 0000000..dc0af77 --- /dev/null +++ b/tests/i/input-targets/input.txt @@ -0,0 +1,2 @@ +10 20 30 +40 50 Ada 1 A 2.5 -7 60 70 80 90 100 diff --git a/tests/i/input-targets/notes.md b/tests/i/input-targets/notes.md new file mode 100644 index 0000000..dded40c --- /dev/null +++ b/tests/i/input-targets/notes.md @@ -0,0 +1,7 @@ +# INPUT targets + +Human review: pending. Expected output was independently traced by AI, not captured from cpsc. + +Basis: CIE guide p. 15 section 5.1 allows individual data-structure elements. Native C input, numeric BOOLEAN input, whitespace-delimited STRING input and exceptions follow project behavior. + +Trace: the loop stores 10, 20, 30; the grid receives 40. Student 2 receives 50, Ada, true, A, 2.5 and -7, then its score becomes 80 through P. The class fields receive 60 and 70. NextIndex prints once and selects Scores[1], which receives 90. Bounds and null-pointer failures occur before reading input, leaving 100 for the final valid input. diff --git a/tests/i/input-targets/program.cps b/tests/i/input-targets/program.cps new file mode 100644 index 0000000..d7628d3 --- /dev/null +++ b/tests/i/input-targets/program.cps @@ -0,0 +1,56 @@ +TYPE Student + DECLARE Score : INTEGER + DECLARE Name : STRING + DECLARE Passed : BOOLEAN + DECLARE Grade : CHAR + DECLARE Average : REAL + DECLARE Native : C_INT +ENDTYPE +CLASS Box + Value : INTEGER + Details : Student +ENDCLASS +TYPE StudentPtr = ^Student +DECLARE Scores : ARRAY[1:3] OF INTEGER +DECLARE Grid : ARRAY[-1:0,2:3] OF INTEGER +DECLARE Students : ARRAY[1:2] OF Student +DECLARE Item : Box +DECLARE P : StudentPtr +DECLARE j : INTEGER +FOR j <- 1 TO 3 + INPUT Scores[j] +NEXT j +INPUT Grid[-1,3] +INPUT Students[2].Score +INPUT Students[2].Name +INPUT Students[2].Passed +INPUT Students[2].Grade +INPUT Students[2].Average +INPUT Students[2].Native +INPUT Item.Value +INPUT Item.Details.Score +P <- ^Students[2] +INPUT P^.Score +OUTPUT Scores[1], ":", Scores[2], ":", Scores[3], ":", Grid[-1,3] +OUTPUT Students[2].Score, ":", Students[2].Name, ":", Students[2].Passed, ":", Students[2].Grade +OUTPUT Students[2].Average = 2.5, ":", Students[2].Native +OUTPUT Item.Value, ":", Item.Details.Score +FUNCTION NextIndex() RETURNS INTEGER + OUTPUT "index evaluated" + RETURN 1 +ENDFUNCTION +INPUT Scores[NextIndex()] +OUTPUT Scores[1] +TRY + INPUT Scores[4] +CATCH + OUTPUT "bounds caught" +ENDTRY +DECLARE Unset : StudentPtr +TRY + INPUT Unset^.Score +CATCH + OUTPUT "pointer caught" +ENDTRY +INPUT Scores[1] +OUTPUT Scores[1] diff --git a/tests/iii/input-invalid-targets/case.json b/tests/iii/input-invalid-targets/case.json new file mode 100644 index 0000000..be4d749 --- /dev/null +++ b/tests/iii/input-invalid-targets/case.json @@ -0,0 +1,32 @@ +{ + "errors": [ + { + "reason": "Constant Limit is not a writable location", + "line": 16 + }, + { + "reason": "Cannot access private field Hidden of type Box", + "line": 17 + }, + { + "reason": "Whole array Scores is not an assignable location", + "line": 18 + }, + { + "reason": "INPUT is not supported for a value of type Student", + "line": 19 + }, + { + "reason": "INPUT is not supported for a value of type StudentPtr", + "line": 20 + }, + { + "reason": "INPUT is not supported for a value of type Season", + "line": 21 + }, + { + "reason": "INPUT is not supported for C pointer or void type C_STRING", + "line": 22 + } + ] +} diff --git a/tests/iii/input-invalid-targets/notes.md b/tests/iii/input-invalid-targets/notes.md new file mode 100644 index 0000000..b92b0cb --- /dev/null +++ b/tests/iii/input-invalid-targets/notes.md @@ -0,0 +1,3 @@ +# Invalid INPUT targets + +Constants, private fields and whole arrays are not writable scalar locations. Whole records, enums, pseudocode pointers and C pointers remain unsupported INPUT value types. These diagnostics preserve the existing restrictions while accepting scalar array elements and public fields. diff --git a/tests/iii/input-invalid-targets/program.cps b/tests/iii/input-invalid-targets/program.cps new file mode 100644 index 0000000..6c5dc89 --- /dev/null +++ b/tests/iii/input-invalid-targets/program.cps @@ -0,0 +1,22 @@ +TYPE Student + DECLARE Score : INTEGER +ENDTYPE +CLASS Box + PRIVATE Hidden : INTEGER +ENDCLASS +TYPE StudentPtr = ^Student +TYPE Season = (Spring, Summer) +CONSTANT Limit = 3 +DECLARE Scores : ARRAY[1:2] OF INTEGER +DECLARE Item : Box +DECLARE Person : Student +DECLARE P : StudentPtr +DECLARE S : Season +DECLARE Text : C_STRING +INPUT Limit +INPUT Item.Hidden +INPUT Scores +INPUT Person +INPUT P +INPUT S +INPUT Text -- cgit v1.3.1-4-g156e