diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-18 08:09:06 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-18 08:09:06 +0000 |
| commit | 30fced09675acfd2b9ab6db6be8359a70d3f0402 (patch) | |
| tree | 88dc4587cb71d9dc9bdd9a57c4e2de8a98d7f06e | |
| parent | 345e3d419eafb0e234aecb4a33f37efe2afbbf53 (diff) | |
feat(operators): allow member assignment operator overloads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 2 | ||||
| -rw-r--r-- | doc/language/keyword/cpsc-keyword-operator.7.md | 2 | ||||
| -rw-r--r-- | include/cps/Operator.h | 1 | ||||
| -rw-r--r-- | lib/CodeGen/ConstructorGen.cc | 14 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 6 | ||||
| -rw-r--r-- | lib/Parser/ParserFunction.cc | 18 | ||||
| -rw-r--r-- | tests/i/operator-assign/case.json | 1 | ||||
| -rw-r--r-- | tests/i/operator-assign/expected.txt | 7 | ||||
| -rw-r--r-- | tests/i/operator-assign/program.cps | 39 | ||||
| -rw-r--r-- | tests/iii/operator-assign-global/case.json | 1 | ||||
| -rw-r--r-- | tests/iii/operator-assign-global/program.cps | 3 |
11 files changed, 91 insertions, 3 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index b8ca3e9..f72cb86 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -211,3 +211,5 @@ ExceptionHandler, truncates its result and retains the final uncaught handler. Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix is reserved in every user identifier, including EXTERN declarations. **Library units.** Line-leading `#LIB` marks SourceUnit::IsLibrary. RuntimeCheck uses the externally visible i32 `__cps_user_line`; FunctionGen’s shared call hook updates it only on non-library to library calls. Runtime helper line arguments use RuntimeCheck::lineValue too. Compiler diagnostics retain the owning file location. + +Assignment operators are member-only `OVERLOAD PROCEDURE OPERATOR<-` with one explicit parameter. emitAssignment resolves the exact RHS static type before fallback storage copying. Internal assignment/constructor BYREF bindings may use designators or materialized temporaries; ordinary BYREF calls still require bare variables. diff --git a/doc/language/keyword/cpsc-keyword-operator.7.md b/doc/language/keyword/cpsc-keyword-operator.7.md index 5ba3999..090921f 100644 --- a/doc/language/keyword/cpsc-keyword-operator.7.md +++ b/doc/language/keyword/cpsc-keyword-operator.7.md @@ -57,3 +57,5 @@ OUTPUT Sum.Value # SEE ALSO [cpsc-keyword-overload(7)](cpsc-keyword-overload.7.md), [cpsc-keyword-function(7)](cpsc-keyword-function.7.md), [cpsc-keyword-class(7)](cpsc-keyword-class.7.md), [cpsc-keyword-include(7)](cpsc-keyword-include.7.md). + +A class may define `OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : ClassName)` or an overload taking another exact static type. Assignment to a class lvalue selects this member before falling back to same-class bitwise assignment. The procedure takes exactly one explicit parameter; BYREF may bind a materialized temporary. Keyword NEW assignment retains its allocation behavior. Self-assignment handling belongs to the operator body. diff --git a/include/cps/Operator.h b/include/cps/Operator.h index 304793e..acf5483 100644 --- a/include/cps/Operator.h +++ b/include/cps/Operator.h @@ -8,6 +8,7 @@ inline std::string operatorSpelling(int Op) { switch (Op) { case '+': case '-': case '*': case '/': case '&': case '<': case '>': return std::string(1, static_cast<char>(Op)); + case tok_assign: return "<-"; case tok_div: return "DIV"; case tok_mod: return "MOD"; case tok_eq: return "="; diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc index 4bf5568..9fc762c 100644 --- a/lib/CodeGen/ConstructorGen.cc +++ b/lib/CodeGen/ConstructorGen.cc @@ -142,6 +142,20 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb } return; } + if (Info && Info->IsClass) { + auto Resolution = resolveOperator(tok_assign, Info, getExprTypeInfo(Expr)); + if (Resolution.Ambiguous) { + reportError("Ambiguous assignment operator"); + return; + } + if (const FuncSig *Sig = Resolution.Signature) { + if (!checkOperatorAccess(*Sig)) return; + std::vector<Value *> Args = {Target.Addr}; + if (marshalCallArgs(*Sig, std::vector<ExprAST *>{Expr}, DiagnosticLine, Args)) + FuncGen->emitCall(*Sig, Args); + return; + } + } Value *Val = emitCoercedExpr(Expr, Info); if (Val) Builder->CreateStore(Val, Target.Addr); } diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 37df8ff..1f7bc2b 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -132,6 +132,12 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { return nullptr; } } + if (Proto->getOperator() == tok_assign && + (Proto->getOwnerClass().empty() || !Proto->isOverload() || + !sameIdentifier(Proto->getReturnType(), "VOID"))) { + reportError("Assignment operator must be an OVERLOAD member PROCEDURE"); + return nullptr; + } if (int Op = Proto->getOperator()) { size_t Count = Proto->getParams().size() + !Proto->getOwnerClass().empty(); bool ValidCount = Op == '-' ? Count == 1 || Count == 2 diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc index 65e0b85..cf8ecf8 100644 --- a/lib/Parser/ParserFunction.cc +++ b/lib/Parser/ParserFunction.cc @@ -214,12 +214,23 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerClass, bool IsPublic, bool IsOverload) { int Line = Lex.getLine(); getNextToken(); - if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete) { + if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete && CurTok != tok_operator) { cps::sourceDiagnostic(Lex.getLine(), "error: Expected procedure name\n"); return nullptr; } - std::string Name = CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr; + int Operator = 0; + bool IsOperator = CurTok == tok_operator; + std::string OperatorName; + if (IsOperator) { + OperatorName = ParseOperatorName(Operator); + if (OperatorName.empty()) return nullptr; + if (Operator != tok_assign || OwnerClass.empty() || !IsOverload) { + sourceDiagnostic(Line, "error: Assignment operator must be an OVERLOAD member PROCEDURE at line %d\n", Line); + return nullptr; + } + } + std::string Name = IsOperator ? OperatorName : CurTok == tok_new ? "NEW" : CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr; if (OwnerClass.empty() && sameIdentifier(Name, "DELETE")) { cps::sourceDiagnostic(Lex.getLine(), "error: destructor DELETE is only allowed in a CLASS at line %d\n", Line); return nullptr; @@ -228,7 +239,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC cps::sourceDiagnostic(Lex.getLine(), "error: constructor NEW is only allowed in a CLASS at line %d\n", Line); return nullptr; } - getNextToken(); + if (!IsOperator) getNextToken(); auto Args = ParsePrototypeArgs(/*AllowByRef=*/true); if (!Args) return nullptr; @@ -237,6 +248,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC return nullptr; } auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID", false, OwnerClass, IsPublic, IsOverload); + Proto->setOperator(Operator); std::vector<std::unique_ptr<StmtAST>> Body = ParseBlock({tok_endprocedure}); diff --git a/tests/i/operator-assign/case.json b/tests/i/operator-assign/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/operator-assign/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/operator-assign/expected.txt b/tests/i/operator-assign/expected.txt new file mode 100644 index 0000000..aaf45ba --- /dev/null +++ b/tests/i/operator-assign/expected.txt @@ -0,0 +1,7 @@ +copy:3 +copy:3 +copy:7 +copy:7 +copy:5 +copy:7 +7:7:7:5 diff --git a/tests/i/operator-assign/program.cps b/tests/i/operator-assign/program.cps new file mode 100644 index 0000000..d534806 --- /dev/null +++ b/tests/i/operator-assign/program.cps @@ -0,0 +1,39 @@ +CLASS Value + N : INTEGER + OVERLOAD PROCEDURE NEW() + ENDPROCEDURE + OVERLOAD PROCEDURE NEW(BYREF Other : Value) + N <- Other.N + ENDPROCEDURE + OVERLOAD PROCEDURE OPERATOR<-(BYREF Other : Value) + OUTPUT "copy:", Other.N + N <- Other.N + ENDPROCEDURE + OVERLOAD PROCEDURE OPERATOR<-(Text : C_STRING) + N <- LENGTH((STRING)Text) + ENDPROCEDURE +ENDCLASS +FUNCTION Make() RETURNS Value + DECLARE X : Value + X.N <- 7 + RETURN X +ENDFUNCTION +TYPE ValuePtr = ^Value +TYPE Box + DECLARE Item : Value +ENDTYPE +DECLARE A : Value +DECLARE B : Value +A.N <- 3 +B <- A +B <- B +B <- Make() +A <- (C_STRING)"hello" +DECLARE R : Box +R.Item <- B +DECLARE Items : ARRAY[1:1] OF Value +Items[1] <- A +DECLARE P : ValuePtr +P <- ^A +P^ <- B +OUTPUT A.N, ":", B.N, ":", R.Item.N, ":", Items[1].N diff --git a/tests/iii/operator-assign-global/case.json b/tests/iii/operator-assign-global/case.json new file mode 100644 index 0000000..7a2e21d --- /dev/null +++ b/tests/iii/operator-assign-global/case.json @@ -0,0 +1 @@ +{"errors":[{"reason":"Assignment operator must be","line":1}]} diff --git a/tests/iii/operator-assign-global/program.cps b/tests/iii/operator-assign-global/program.cps new file mode 100644 index 0000000..6f6454e --- /dev/null +++ b/tests/iii/operator-assign-global/program.cps @@ -0,0 +1,3 @@ +OVERLOAD PROCEDURE OPERATOR<-(X : INTEGER) +ENDPROCEDURE +OUTPUT 0 |
