aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-17 06:52:23 +0000
committer_ov4 <ov4@ov4.org>2026-10-17 06:52:23 +0000
commit39661855c0816ac248be437704c76f58359cb5f0 (patch)
tree202b014805e40208e3adb861f4a97d31770f13e6
parentc89477d615fb0ce4d89786ef848ffe6aca1da088 (diff)
feat(classes): construct declared objects and nested fields and arrays
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/cps/AST.h9
-rw-r--r--include/cps/CodeGen.h7
-rw-r--r--lib/CodeGen/ArrayHandler.cc18
-rw-r--r--lib/CodeGen/ConstructorGen.cc39
-rw-r--r--lib/CodeGen/Designator.cc21
-rw-r--r--lib/CodeGen/ValueTypeGen.cc5
-rw-r--r--lib/Parser/ParserType.cc10
-rw-r--r--tests/i/class-constructors/case.json1
-rw-r--r--tests/i/class-constructors/expected.txt17
-rw-r--r--tests/i/class-constructors/notes.md5
-rw-r--r--tests/i/class-constructors/program.cps42
-rw-r--r--tests/i/class-new-methods/notes.md2
-rw-r--r--tests/i/class-new-methods/program.cps2
-rw-r--r--tests/iii/class-constructor-default-errors/case.json32
-rw-r--r--tests/iii/class-constructor-default-errors/program.cps21
16 files changed, 220 insertions, 12 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7451188..127812b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -58,6 +58,7 @@ add_library(CPSCodeGen STATIC
lib/CodeGen/StatementGen.cc
lib/CodeGen/ConstantGen.cc
lib/CodeGen/ControlFlowGen.cc
+ lib/CodeGen/ConstructorGen.cc
lib/CodeGen/ValueTypeGen.cc
lib/CodeGen/OutputGen.cc
lib/CodeGen/Designator.cc
diff --git a/include/cps/AST.h b/include/cps/AST.h
index c7764c6..c2754a5 100644
--- a/include/cps/AST.h
+++ b/include/cps/AST.h
@@ -125,9 +125,14 @@ public:
class DeclareStmtAST : public StmtAST {
std::string Name;
std::string Type;
+ std::vector<std::unique_ptr<ExprAST>> Args;
+ bool HasParens;
public:
- DeclareStmtAST(const std::string &Name, const std::string &Type = "INTEGER")
- : Name(Name), Type(Type) {}
+ DeclareStmtAST(const std::string &Name, const std::string &Type = "INTEGER",
+ std::vector<std::unique_ptr<ExprAST>> Args = {}, bool HasParens = false)
+ : Name(Name), Type(Type), Args(std::move(Args)), HasParens(HasParens) {}
+ const auto &getArgs() const { return Args; }
+ bool hasParens() const { return HasParens; }
const std::string &getName() const { return Name; }
const std::string &getType() const { return Type; }
};
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 593cde0..1813d9d 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -36,6 +36,13 @@ class CodeGen {
std::string CurrentClass;
const PrototypeAST *findMethod(const std::string &Class, const std::string &Name) const;
llvm::Value *emitMemberCall(MemberCallExprAST *Call);
+ llvm::Value *emitMethodCall(const std::string &Class, llvm::Value *Receiver,
+ const std::string &Name,
+ const std::vector<std::unique_ptr<ExprAST>> &Args, int Line);
+ bool typeNeedsConstruction(const std::string &TypeName) const;
+ bool emitConstruct(const std::string &TypeName, llvm::Value *Addr,
+ const std::vector<std::unique_ptr<ExprAST>> &Args, int Line);
+ bool emitDefaultConstruct(const std::string &TypeName, llvm::Value *Addr, int Line);
struct ConstantScope {
unsigned ID;
IdentifierMap<std::optional<SymbolInfo>> Saved;
diff --git a/lib/CodeGen/ArrayHandler.cc b/lib/CodeGen/ArrayHandler.cc
index 5eb8a52..337e5f4 100644
--- a/lib/CodeGen/ArrayHandler.cc
+++ b/lib/CodeGen/ArrayHandler.cc
@@ -139,6 +139,24 @@ void ArrayHandler::emitArrayDeclare(ArrayDeclareStmtAST *Stmt, CodeGen &CG) {
Builder->CreateStore(Ptr, Alloca);
CG.registerSymbol(Name, Alloca, ElemInfo->Name, true);
+ if (CG.typeNeedsConstruction(ElemInfo->Name)) {
+ BasicBlock *Preheader = Builder->GetInsertBlock();
+ BasicBlock *Loop = BasicBlock::Create(*TheContext, "construct_elements", TheFunction);
+ BasicBlock *Body = BasicBlock::Create(*TheContext, "construct_element", TheFunction);
+ BasicBlock *Done = BasicBlock::Create(*TheContext, "constructed_array", TheFunction);
+ Builder->CreateBr(Loop);
+ Builder->SetInsertPoint(Loop);
+ auto *Index = Builder->CreatePHI(Type::getInt64Ty(*TheContext), 2, "construct_index");
+ Index->addIncoming(ConstantInt::get(Type::getInt64Ty(*TheContext), 0), Preheader);
+ Builder->CreateCondBr(Builder->CreateICmpULT(Index, TotalElements), Body, Done);
+ Builder->SetInsertPoint(Body);
+ Value *Element = Builder->CreateGEP(ElemInfo->LLVMType, Ptr, Index);
+ CG.emitDefaultConstruct(ElemInfo->Name, Element, Stmt->getLine());
+ Value *Next = Builder->CreateAdd(Index, ConstantInt::get(Type::getInt64Ty(*TheContext), 1));
+ Index->addIncoming(Next, Builder->GetInsertBlock());
+ Builder->CreateBr(Loop);
+ Builder->SetInsertPoint(Done);
+ }
}
void ArrayHandler::computeDimsAndMultipliers(const std::string &Name,
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
new file mode 100644
index 0000000..0d4d732
--- /dev/null
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -0,0 +1,39 @@
+#include "cps/CodeGen.h"
+
+using namespace llvm;
+using namespace cps;
+
+bool CodeGen::typeNeedsConstruction(const std::string &TypeName) const {
+ if (findMethod(TypeName, "NEW")) return true;
+ const TypeInfo *Info = resolveType(TypeName);
+ if (Info && Info->asRecord()) {
+ for (const auto &[Name, Field] : Info->Record.Fields)
+ if (typeNeedsConstruction(Field.TypeName)) return true;
+ }
+ return false;
+}
+
+bool CodeGen::emitConstruct(const std::string &TypeName, Value *Addr,
+ const std::vector<std::unique_ptr<ExprAST>> &Args, int Line) {
+ SaveAndRestore<int> Location(DiagnosticLine, Line);
+ const TypeInfo *Info = resolveType(TypeName);
+ if (!Info) return false;
+ if (Info->asRecord()) {
+ for (const auto &[Name, Field] : Info->Record.Fields) {
+ if (!typeNeedsConstruction(Field.TypeName)) continue;
+ Value *FieldAddr = Builder->CreateStructGEP(Info->LLVMType, Addr, Field.Index);
+ if (!emitDefaultConstruct(Field.TypeName, FieldAddr, Line)) return false;
+ }
+ }
+ if (findMethod(TypeName, "NEW"))
+ return emitMethodCall(TypeName, Addr, "NEW", Args, Line) != nullptr;
+ if (!Args.empty()) {
+ reportError("Type %s has no constructor NEW but arguments were supplied", TypeName.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool CodeGen::emitDefaultConstruct(const std::string &TypeName, Value *Addr, int Line) {
+ return emitConstruct(TypeName, Addr, {}, Line);
+}
diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc
index 13eb25f..1ebd158 100644
--- a/lib/CodeGen/Designator.cc
+++ b/lib/CodeGen/Designator.cc
@@ -78,18 +78,25 @@ Value *CodeGen::emitMemberCall(MemberCallExprAST *Call) {
SaveAndRestore<int> Location(DiagnosticLine, Call->getLine());
LValueInfo Receiver;
if (!emitLValue(Call->getReceiver(), Receiver)) return nullptr;
- const PrototypeAST *Method = findMethod(Receiver.TypeName, Call->getMethod());
+ return emitMethodCall(Receiver.TypeName, Receiver.Addr, Call->getMethod(),
+ Call->getArgs(), Call->getLine());
+}
+
+Value *CodeGen::emitMethodCall(const std::string &Class, Value *Receiver,
+ const std::string &Name,
+ const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line) {
+ SaveAndRestore<int> Location(DiagnosticLine, Line);
+ const PrototypeAST *Method = findMethod(Class, Name);
if (!Method) {
- reportError("Type %s has no method %s", Receiver.TypeName.c_str(), Call->getMethod().c_str());
+ reportError("Type %s has no method %s", Class.c_str(), Name.c_str());
return nullptr;
}
- if (!Method->isPublic() && !sameIdentifier(CurrentClass, Receiver.TypeName)) {
- reportError("Cannot access private method %s of type %s",
- Call->getMethod().c_str(), Receiver.TypeName.c_str());
+ if (!Method->isPublic() && !sameIdentifier(CurrentClass, Class)) {
+ reportError("Cannot access private method %s of type %s", Name.c_str(), Class.c_str());
return nullptr;
}
- std::vector<Value*> Args = {Receiver.Addr};
- if (!marshalCallArgs(Method->getLinkName(), Call->getArgs(), Call->getLine(), Args)) return nullptr;
+ std::vector<Value*> Args = {Receiver};
+ if (!marshalCallArgs(Method->getLinkName(), ArgExprs, Line, Args)) return nullptr;
return FuncGen->emitCall(Method->getLinkName(), Args);
}
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index 16c3e0b..baa384c 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -280,4 +280,9 @@ void CodeGen::emitDeclareStmt(DeclareStmtAST *Stmt) {
}
registerSymbol(Stmt->getName(), Alloca, Info->Name, false);
+ if (Stmt->hasParens() && !ClassMethods.count(Info->Name)) {
+ reportError("Constructor arguments require a class type");
+ return;
+ }
+ emitConstruct(Info->Name, Alloca, Stmt->getArgs(), Stmt->getLine());
}
diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc
index dab71a1..dfeacf1 100644
--- a/lib/Parser/ParserType.cc
+++ b/lib/Parser/ParserType.cc
@@ -308,7 +308,15 @@ std::unique_ptr<StmtAST> Parser::ParseDeclare() {
if (TypeStr.empty()) {
return nullptr;
}
- return std::make_unique<DeclareStmtAST>(Name, TypeStr);
+ bool HasParens = CurTok == '(';
+ std::vector<std::unique_ptr<ExprAST>> Args;
+ if (HasParens) {
+ getNextToken();
+ auto Parsed = ParseExprList(')', true);
+ if (!Parsed) return nullptr;
+ Args = std::move(*Parsed);
+ }
+ return std::make_unique<DeclareStmtAST>(Name, TypeStr, std::move(Args), HasParens);
}
}
diff --git a/tests/i/class-constructors/case.json b/tests/i/class-constructors/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/class-constructors/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/class-constructors/expected.txt b/tests/i/class-constructors/expected.txt
new file mode 100644
index 0000000..cb57e7c
--- /dev/null
+++ b/tests/i/class-constructors/expected.txt
@@ -0,0 +1,17 @@
+leaf:0
+leaf:0
+name
+0
+leaf:0
+leaf:0
+leaf:0
+leaf:0
+3,3
+leaf:0
+leaf:0
+pair:6
+3,3
+leaf:0
+3
+leaf:0
+3
diff --git a/tests/i/class-constructors/notes.md b/tests/i/class-constructors/notes.md
new file mode 100644
index 0000000..0145a0a
--- /dev/null
+++ b/tests/i/class-constructors/notes.md
@@ -0,0 +1,5 @@
+# Class constructors
+
+Human review: pending. Expected output was independently traced by AI.
+
+The project extends guide section 10.2 with automatic stack, field and array construction. Leaf observes its zeroed Value before setting it to 3. A and B each construct once; Named stores name; Plain has no constructor and stays zero. Four array elements each construct once. Wrapped constructs Pair's two Leaf fields in order before Pair observes their sum of 6. Each execution of the loop declaration zeroes and constructs Local again, so both iterations print leaf:0 then 3 despite storing 99 in the previous iteration.
diff --git a/tests/i/class-constructors/program.cps b/tests/i/class-constructors/program.cps
new file mode 100644
index 0000000..2b85b4d
--- /dev/null
+++ b/tests/i/class-constructors/program.cps
@@ -0,0 +1,42 @@
+CLASS Leaf
+ Value : INTEGER
+ PROCEDURE New()
+ OUTPUT "leaf:", Value
+ Value <- 3
+ ENDPROCEDURE
+ENDCLASS
+CLASS Pair
+ First : Leaf
+ Second : Leaf
+ PROCEDURE NEW()
+ OUTPUT "pair:", First.Value + Second.Value
+ ENDPROCEDURE
+ENDCLASS
+TYPE Wrapped
+ DECLARE Item : Pair
+ENDTYPE
+CLASS Named
+ Text : STRING
+ PROCEDURE NEW(Name : STRING)
+ Text <- Name
+ ENDPROCEDURE
+ENDCLASS
+CLASS Plain
+ Value : INTEGER
+ENDCLASS
+DECLARE A : Leaf
+DECLARE B : Leaf()
+DECLARE N : Named("name")
+OUTPUT N.Text
+DECLARE P : Plain()
+OUTPUT P.Value
+DECLARE Items : ARRAY[-1:0, 2:3] OF Leaf
+OUTPUT Items[-1,2].Value, ",", Items[0,3].Value
+DECLARE W : Wrapped
+OUTPUT W.Item.First.Value, ",", W.Item.Second.Value
+DECLARE Index : INTEGER
+FOR Index <- 1 TO 2
+ DECLARE Local : Leaf
+ OUTPUT Local.Value
+ Local.Value <- 99
+NEXT Index
diff --git a/tests/i/class-new-methods/notes.md b/tests/i/class-new-methods/notes.md
index 53192fb..cc7cffa 100644
--- a/tests/i/class-new-methods/notes.md
+++ b/tests/i/class-new-methods/notes.md
@@ -2,4 +2,4 @@
Human review: pending. Expected output was independently traced by AI.
-NEW is an ordinary procedure: the bare member call stores x, CALL with mixed case stores call, and Reset calls this.NEW to store again. DECLARE is extended with arguments in the next implementation step.
+NEW is an ordinary procedure: the bare member call stores x, CALL with mixed case stores call, and Reset calls this.NEW to store again. The initial constructor value is overwritten before output.
diff --git a/tests/i/class-new-methods/program.cps b/tests/i/class-new-methods/program.cps
index 573fe0f..880f538 100644
--- a/tests/i/class-new-methods/program.cps
+++ b/tests/i/class-new-methods/program.cps
@@ -7,7 +7,7 @@ CLASS Box
this.NEW("again")
ENDPROCEDURE
ENDCLASS
-DECLARE Obj : Box
+DECLARE Obj : Box("initial")
Obj.NEW("x")
OUTPUT Obj.Value
CALL Obj.new("call")
diff --git a/tests/iii/class-constructor-default-errors/case.json b/tests/iii/class-constructor-default-errors/case.json
new file mode 100644
index 0000000..adcefe8
--- /dev/null
+++ b/tests/iii/class-constructor-default-errors/case.json
@@ -0,0 +1,32 @@
+{
+ "errors": [
+ {
+ "reason": "Incorrect # arguments passed to Required.NEW",
+ "line": 15
+ },
+ {
+ "reason": "Incorrect # arguments passed to Required.NEW",
+ "line": 16
+ },
+ {
+ "reason": "has no constructor NEW",
+ "line": 17
+ },
+ {
+ "reason": "Cannot access private method NEW",
+ "line": 18
+ },
+ {
+ "reason": "Incorrect # arguments passed to Required.NEW",
+ "line": 19
+ },
+ {
+ "reason": "Incorrect # arguments passed to Required.NEW",
+ "line": 20
+ },
+ {
+ "reason": "Constructor arguments require a class type",
+ "line": 21
+ }
+ ]
+}
diff --git a/tests/iii/class-constructor-default-errors/program.cps b/tests/iii/class-constructor-default-errors/program.cps
new file mode 100644
index 0000000..43dfef7
--- /dev/null
+++ b/tests/iii/class-constructor-default-errors/program.cps
@@ -0,0 +1,21 @@
+CLASS Required
+ PROCEDURE NEW(Number : INTEGER)
+ ENDPROCEDURE
+ENDCLASS
+CLASS Plain
+ Value : INTEGER
+ENDCLASS
+CLASS Hidden
+ PRIVATE PROCEDURE NEW()
+ ENDPROCEDURE
+ENDCLASS
+TYPE Wrapped
+ DECLARE Item : Required
+ENDTYPE
+DECLARE Missing : Required
+DECLARE EmptyArgs : Required()
+DECLARE Extra : Plain(1)
+DECLARE PrivateObject : Hidden
+DECLARE Items : ARRAY[1:2] OF Required
+DECLARE Nested : Wrapped
+DECLARE Scalar : INTEGER()