From 5659592a84834e85b574c19bb259e2cb893c9abe Mon Sep 17 00:00:00 2001 From: paxcut Date: Fri, 28 Aug 2026 17:33:35 -0700 Subject: [PATCH 1/2] fix: Compound assignments for rvalued variables Even though the assignment operator is now supported for rvalued variables compound assignment still resulted on parser errors. This PR aims at adding full support of all compound assignments to rvalued variables. The previous implementation of assignment for rvalues was left intact and a new set of functions were added to integrate rvalues with the existing lvalues compound assignment support. --- lib/include/pl/core/parser.hpp | 5 +- lib/source/pl/core/parser.cpp | 87 +++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/lib/include/pl/core/parser.hpp b/lib/include/pl/core/parser.hpp index 9cd21b72..68d10dd6 100644 --- a/lib/include/pl/core/parser.hpp +++ b/lib/include/pl/core/parser.hpp @@ -173,6 +173,7 @@ namespace pl::core { hlp::safe_unique_ptr parseFunctionStatement(bool needsSemicolon = true); hlp::safe_unique_ptr parseFunctionVariableAssignment(const std::string &lvalue); hlp::safe_unique_ptr parseFunctionVariableCompoundAssignment(const std::string &lvalue); + hlp::safe_unique_ptr parseFunctionVariableCompoundAssignment(TokenIter curr); hlp::safe_unique_ptr parseFunctionControlFlowStatement(); std::vector> parseStatementBody(const std::function()> &memberParser); hlp::safe_unique_ptr parseFunctionWhileLoop(); @@ -210,7 +211,9 @@ namespace pl::core { std::vector> parseNamespace(); std::vector> parseStatements(); - std::optional parseCompoundAssignment(const Token &token); + std::optional isCompoundAssignmentOperator(const Token &token); + std::optional isCompoundAssignmentOperator(); + std::optional isCompoundAssignmentOperator(i32 offset); std::optional parseDocComment(bool global); diff --git a/lib/source/pl/core/parser.cpp b/lib/source/pl/core/parser.cpp index aebe3687..4c1c40c0 100644 --- a/lib/source/pl/core/parser.cpp +++ b/lib/source/pl/core/parser.cpp @@ -934,9 +934,11 @@ namespace pl::core { statement = parseFunctionVariableAssignment(getValue(-2).get()); else if (sequence(tkn::Operator::Dollar, tkn::Operator::Assign)) statement = parseFunctionVariableAssignment("$"); - else if (const auto identifierOffset = parseCompoundAssignment(tkn::Literal::Identifier); identifierOffset.has_value()) + else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) statement = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); - else if (parseCompoundAssignment(tkn::Operator::Dollar).has_value()) + else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + statement = parseFunctionVariableCompoundAssignment(curr.value()); + else if (isCompoundAssignmentOperator(tkn::Operator::Dollar).has_value()) statement = parseFunctionVariableCompoundAssignment("$"); else if (oneOf(tkn::Keyword::Return, tkn::Keyword::Break, tkn::Keyword::Continue)) statement = parseFunctionControlFlowStatement(); @@ -1001,6 +1003,30 @@ namespace pl::core { return create(lvalue, std::move(rvalue)); } + hlp::safe_unique_ptr Parser::parseFunctionVariableCompoundAssignment(TokenIter curr) { + m_curr = curr; + auto lhs = parseRValue(); + Token::Operator op = getValue(0); + next(); + if (op == Token::Operator::BoolLessThan) { + op = Token::Operator::LeftShift; + next(); + } else if (op == Token::Operator::BoolGreaterThan) { + op = Token::Operator::RightShift; + next(); + } + next(); + + auto rhs = parseMathematicalExpression(); + if (rhs == nullptr) + return nullptr; + + auto lhsCopy = lhs->clone(); + rhs = create(std::move(lhsCopy), std::move(rhs), op); + + return create(std::move(lhs), std::move(rhs)); + } + hlp::safe_unique_ptr Parser::parseFunctionVariableCompoundAssignment(const std::string &lvalue) { auto op = getValue(-2); @@ -1884,12 +1910,14 @@ namespace pl::core { if (sequence(tkn::Operator::Dollar, tkn::Operator::Assign)) member = parseFunctionVariableAssignment("$"); - else if (parseCompoundAssignment(tkn::Operator::Dollar).has_value()) + else if (isCompoundAssignmentOperator(tkn::Operator::Dollar).has_value()) member = parseFunctionVariableCompoundAssignment("$"); else if (sequence(tkn::Literal::Identifier, tkn::Operator::Assign)) { member = parseFunctionVariableAssignment(getValue(-2).get()); - } else if (const auto identifierOffset = parseCompoundAssignment(tkn::Literal::Identifier); identifierOffset.has_value()) + } else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) member = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); + else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + member = parseFunctionVariableCompoundAssignment(curr.value()); else if (MATCHES((sequence(tkn::Literal::Identifier) || sequence(tkn::Keyword::Parent) || sequence(tkn::Keyword::This)) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) member = parseRValueAssignment(); else if (peek(tkn::Keyword::Const) || peek(tkn::Keyword::BigEndian) || peek(tkn::Keyword::LittleEndian) || peek(tkn::ValueType::Any) || peek(tkn::Literal::Identifier)) { @@ -2160,8 +2188,10 @@ namespace pl::core { if (identifier != nullptr) identifier->setType(Token::Identifier::IdentifierType::LocalVariable); member = parseFunctionVariableAssignment(variableName); - } else if (const auto identifierOffset = parseCompoundAssignment(tkn::Literal::Identifier); identifierOffset.has_value()) + } else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) member = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); + else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + member = parseFunctionVariableCompoundAssignment(curr.value()); else if (MATCHES(optional(tkn::Keyword::Unsigned) && sequence(tkn::Literal::Identifier, tkn::Operator::Colon))) { auto fieldName = getValue(-2).get(); auto identifier = std::get_if(&((m_curr[-2]).value)); @@ -2643,8 +2673,10 @@ namespace pl::core { statement = parseFunctionVariableAssignment(getValue(-2).get()); else if (sequence(tkn::Operator::Dollar, tkn::Operator::Assign)) statement = parseFunctionVariableAssignment("$"); - else if (const auto identifierOffset = parseCompoundAssignment(tkn::Literal::Identifier); identifierOffset.has_value()) + else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) statement = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); + else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + statement = parseFunctionVariableCompoundAssignment(curr.value()); else if (MATCHES(sequence(tkn::Keyword::Using, tkn::Literal::Identifier))) statement = parseUsingDeclaration(); else if (sequence(tkn::Keyword::Import)) @@ -2704,23 +2736,52 @@ namespace pl::core { return hlp::moveToVector(std::move(statement)); } - std::optional Parser::parseCompoundAssignment(const Token &token) { + std::optional Parser::isCompoundAssignmentOperator(const Token &token) { + auto save = m_curr; + + if (sequence(token)) { + if (auto offset = isCompoundAssignmentOperator(-1); offset.has_value()) + return offset; + } + + m_curr = save; + return std::nullopt; + } + + std::optional Parser::isCompoundAssignmentOperator(i32 offset) { const static std::array SingleTokens = { tkn::Operator::Plus, tkn::Operator::Minus, tkn::Operator::Star, tkn::Operator::Slash, tkn::Operator::Percent, tkn::Operator::BitOr, tkn::Operator::BitAnd, tkn::Operator::BitXor }; const static std::array DoubleTokens = { tkn::Operator::BoolLessThan, tkn::Operator::BoolGreaterThan }; - for (auto &singleToken : SingleTokens) { - if (sequence(token, singleToken, tkn::Operator::Assign)) - return -3; + for (auto &token : SingleTokens) { + if (sequence(token, tkn::Operator::Assign)) + return -2 + offset; } - for (auto &doubleTokens : DoubleTokens) { - if (sequence(token, doubleTokens, doubleTokens, tkn::Operator::Assign)) - return -4; + for (auto &token : DoubleTokens) { + if (sequence(token, token, tkn::Operator::Assign)) + return -3 + offset; } return std::nullopt; } + std::optional Parser::isCompoundAssignmentOperator() { + auto save = this->m_curr; + + if (MATCHES(sequence(tkn::Literal::Identifier) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) { + auto result = m_curr; + auto lhs = parseRValue(); + if (lhs == nullptr) { + m_curr = save; + return std::nullopt; + } + if (isCompoundAssignmentOperator(-1).has_value()) + return result; + } + m_curr = save; + return std::nullopt; + } + hlp::safe_shared_ptr Parser::addType(const std::string &name, hlp::safe_unique_ptr &&node) { auto typeName = getNamespacePrefixedNames(name).back(); From b971a62d978e436994709de043d940966af54357 Mon Sep 17 00:00:00 2001 From: paxcut Date: Sat, 29 Aug 2026 22:18:54 -0700 Subject: [PATCH 2/2] fix: Compound assignments for rvalued variables parent and this keywords were not being detected even when they can be used. parent can be assigned to in function statements too. Added a system to select which keywords can be used during detection to generate compiler errors when possible. Many thanks to @majocane in discord for reporting the missing operations and help debugging and figuring thinks out. --- lib/include/pl/core/parser.hpp | 9 ++++++++- lib/source/pl/core/parser.cpp | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/include/pl/core/parser.hpp b/lib/include/pl/core/parser.hpp index 68d10dd6..fd2251f6 100644 --- a/lib/include/pl/core/parser.hpp +++ b/lib/include/pl/core/parser.hpp @@ -71,6 +71,13 @@ namespace pl::core { std::string m_aliasNamespaceString; std::string m_autoNamespace; + enum class AllowKeywords : u8 { + None = 0, + Parent, + This, + Both + }; + Location location() override; // error helpers void errorHere(const std::string &message); @@ -212,7 +219,7 @@ namespace pl::core { std::vector> parseStatements(); std::optional isCompoundAssignmentOperator(const Token &token); - std::optional isCompoundAssignmentOperator(); + std::optional isCompoundAssignmentOperator(AllowKeywords allowKeywords = AllowKeywords::None); std::optional isCompoundAssignmentOperator(i32 offset); std::optional parseDocComment(bool global); diff --git a/lib/source/pl/core/parser.cpp b/lib/source/pl/core/parser.cpp index 4c1c40c0..c9489a43 100644 --- a/lib/source/pl/core/parser.cpp +++ b/lib/source/pl/core/parser.cpp @@ -936,7 +936,7 @@ namespace pl::core { statement = parseFunctionVariableAssignment("$"); else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) statement = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); - else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + else if (const auto curr = isCompoundAssignmentOperator(AllowKeywords::Parent); curr.has_value()) statement = parseFunctionVariableCompoundAssignment(curr.value()); else if (isCompoundAssignmentOperator(tkn::Operator::Dollar).has_value()) statement = parseFunctionVariableCompoundAssignment("$"); @@ -957,7 +957,7 @@ namespace pl::core { } else if (sequence(tkn::Keyword::For, tkn::Separator::LeftParenthesis)) { statement = parseFunctionForLoop(); needsSemicolon = false; - } else if (MATCHES(sequence(tkn::Literal::Identifier) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) { + } else if (MATCHES((sequence(tkn::Literal::Identifier) || sequence(tkn::Keyword::Parent)) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) { statement = parseRValueAssignment(); } else if (sequence(tkn::Literal::Identifier)) { const auto originalPos = this->m_curr; @@ -1916,7 +1916,7 @@ namespace pl::core { member = parseFunctionVariableAssignment(getValue(-2).get()); } else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) member = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); - else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + else if (const auto curr = isCompoundAssignmentOperator(AllowKeywords::Both); curr.has_value()) member = parseFunctionVariableCompoundAssignment(curr.value()); else if (MATCHES((sequence(tkn::Literal::Identifier) || sequence(tkn::Keyword::Parent) || sequence(tkn::Keyword::This)) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) member = parseRValueAssignment(); @@ -2190,8 +2190,10 @@ namespace pl::core { member = parseFunctionVariableAssignment(variableName); } else if (const auto identifierOffset = isCompoundAssignmentOperator(tkn::Literal::Identifier); identifierOffset.has_value()) member = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); - else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) + else if (const auto curr = isCompoundAssignmentOperator(AllowKeywords::Parent); curr.has_value()) member = parseFunctionVariableCompoundAssignment(curr.value()); + else if (MATCHES((sequence(tkn::Literal::Identifier) || sequence(tkn::Keyword::Parent)) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) + member = parseRValueAssignment(); else if (MATCHES(optional(tkn::Keyword::Unsigned) && sequence(tkn::Literal::Identifier, tkn::Operator::Colon))) { auto fieldName = getValue(-2).get(); auto identifier = std::get_if(&((m_curr[-2]).value)); @@ -2677,6 +2679,8 @@ namespace pl::core { statement = parseFunctionVariableCompoundAssignment(getValue(*identifierOffset).get()); else if (const auto curr = isCompoundAssignmentOperator(); curr.has_value()) statement = parseFunctionVariableCompoundAssignment(curr.value()); + else if (MATCHES(sequence(tkn::Literal::Identifier) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) + statement = parseRValueAssignment(); else if (MATCHES(sequence(tkn::Keyword::Using, tkn::Literal::Identifier))) statement = parseUsingDeclaration(); else if (sequence(tkn::Keyword::Import)) @@ -2765,10 +2769,11 @@ namespace pl::core { return std::nullopt; } - std::optional Parser::isCompoundAssignmentOperator() { + std::optional Parser::isCompoundAssignmentOperator(AllowKeywords allowKeywords) { auto save = this->m_curr; - - if (MATCHES(sequence(tkn::Literal::Identifier) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) { + bool allowParent = ((u32)allowKeywords & (u32)AllowKeywords::Parent) != 0; + bool allowThis = ((u32)allowKeywords & (u32)AllowKeywords::This) != 0; + if (MATCHES((sequence(tkn::Literal::Identifier) || (allowParent && sequence(tkn::Keyword::Parent)) || (allowThis && sequence(tkn::Keyword::This))) && (peek(tkn::Separator::Dot) || (peek(tkn::Separator::LeftBracket, 0) && !peek(tkn::Separator::LeftBracket, 1))))) { auto result = m_curr; auto lhs = parseRValue(); if (lhs == nullptr) {