aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-17 05:08:42 +0000
committer_ov4 <ov4@ov4.org>2026-10-17 05:08:42 +0000
commita6b8afcfb61e1d321405fe8713beaec2aa6320a2 (patch)
treea621462bd6f858d1e78495e8c850a2e7321f4809
parent5c3d4d059ae70ea72b52e89940746d61c1d962db (diff)
feat(input): support array elements and member targets
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
-rw-r--r--include/cps/AST.h6
-rw-r--r--include/cps/CodeGen.h2
-rw-r--r--lib/CodeGen/CTypeGen.cc6
-rw-r--r--lib/CodeGen/StatementGen.cc29
-rw-r--r--lib/Parser/Parser.cc19
-rw-r--r--tests/i/input-targets/case.json1
-rw-r--r--tests/i/input-targets/expected.txt11
-rw-r--r--tests/i/input-targets/input.txt2
-rw-r--r--tests/i/input-targets/notes.md7
-rw-r--r--tests/i/input-targets/program.cps56
-rw-r--r--tests/iii/input-invalid-targets/case.json32
-rw-r--r--tests/iii/input-invalid-targets/notes.md3
-rw-r--r--tests/iii/input-invalid-targets/program.cps22
13 files changed, 162 insertions, 34 deletions
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<ExprAST> Target;
public:
- InputStmtAST(const std::string &Name) : Name(Name) {}
- const std::string &getName() const { return Name; }
+ InputStmtAST(std::unique_ptr<ExprAST> 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<std::unique_ptr<StmtAST>> &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<InputStmtAST*>(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<Value*> 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<StmtAST> 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<DesignatorAccess> Dummy;
- ParseDesignatorSuffix(Dummy);
- fprintf(stderr, "error: INPUT target must be a plain variable at line %d\n", Line);
- return nullptr;
- }
- return std::make_unique<InputStmtAST>(Name);
+ std::vector<DesignatorAccess> Accesses;
+ if (!ParseDesignatorSuffix(Accesses)) return nullptr;
+ std::unique_ptr<ExprAST> Target;
+ if (Accesses.empty())
+ Target = std::make_unique<VariableExprAST>(Name);
+ else if (Accesses.size() == 1 && Accesses[0].Kind == AccessKind::Index)
+ Target = std::make_unique<ArrayAccessExprAST>(Name, std::move(Accesses[0].Indices), Line);
+ else
+ Target = std::make_unique<DesignatorExprAST>(Name, std::move(Accesses), Line);
+ Target->setLine(Line);
+ return std::make_unique<InputStmtAST>(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