artichoke-lang/lib/src/Parser/Expressions.cpp
erick-alcachofa 3180ca4662
feat(parser): implement object literals to unify struct and slice syntax
Signed-off-by: erick-alcachofa <erick@artichoke.dev>

Implement support for object literals using a unified syntax for both
struct and slice initialization. Since the parser lacks the semantic
context to distinguish between a struct or a slice at this stage, both
are represented by the new `ObjectLiteral` AST node.

initialization within curly braces following a type expression:
* **Named Initializers**: Uses the `.field = value` syntax (e.g.,
  `Point { .x = 10, .y = 20 }`).
* **Positional Initializers**: Uses a comma-separated list of
  expressions (e.g., `[]i32 { 1, 2, 3 }`).

* Renamed `StructLiteral` and `SliceLiteral` nodes to `ObjectLiteral`.
* Refactored initialization helper nodes (e.g.,
  `StructLiteralNamedFieldInit` is now `ObjectLiteralNamedFieldInit`).
* Unified the representation in `Expressions.hpp` and `Literals.hpp` to
  use a single `ObjectLiteral` struct containing a `type` and an
  optional `initializer`.

* Integrated the opening brace `{` (`opLSquirly`) as a high-precedence
  postfix operator (binding power 19).
* Implemented parsing logic in `Expressions.cpp` to handle the
  transition from a type expression to an object initializer.
* Updated `toDot` and `toString` visitors to handle the unified
  `ObjectLiteral` nodes and their respective initializer variants.

* Improved robustness in `Declarations.cpp` by ensuring list parsing
  correctly handles closing braces in specific edge cases.
2025-12-28 00:12:43 -06:00

755 lines
22 KiB
C++

//============================================================================//
// //
// artichoke programming language //
// //
// Copyright (C) 2025 Erick Saul Guzman Ramos, whoami.artichoke.dev //
// //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as published //
// by the Free Software Foundation, either version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
// //
//============================================================================//
#include <artichoke/Parser/Parser.hpp>
#include <artichoke/Parser/Pratt.hpp>
namespace arti::lang {
Expected<ast::ExpressionNode>
Parser::parseExpression(std::uint16_t minBindingPower) {
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
bool keepParsing = true;
ast::Optional<ast::ExpressionNode> lhs = std::nullopt;
if (peekToken->value == TokenV::opLParen) {
std::ignore = tokenizer.consume();
if (auto lhsExpr = parseExpression(); ! lhsExpr) {
return Unexpected<>{ std::move(lhsExpr).error() };
}
else {
if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) {
return Unexpected<>{ std::move(rParen).error() };
}
lhs = std::move(lhsExpr).value();
}
}
else if (pratt::isPrefixOperator(peekToken->value)) {
if (auto newLhs = parsePrefixExpression(); ! newLhs) {
return Unexpected<>{ std::move(newLhs).error() };
}
else {
lhs = std::move(newLhs).value();
}
}
else {
if (auto expr = parsePrimaryExpression(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else if (not expr.value().has_value()) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"primary expression, i.e. "
"any of ( null, boolean, number, character, string, identifier )"
);
}
else {
lhs = std::move(expr).value().value();
}
}
while (keepParsing) {
peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
if (pratt::isPostfixOperator(peekToken->value)) {
auto op = pratt::getPostfixOperator(peekToken->value);
auto bindingPower = pratt::postfixBindingPower(op);
if (bindingPower < minBindingPower) {
keepParsing = false;
}
else {
if (auto newLhs = parsePostfixExpression(std::move(lhs).value());
! newLhs) {
return Unexpected<>{ std::move(newLhs).error() };
}
else {
lhs = std::move(newLhs).value();
}
}
}
else if (pratt::isInfixOperator(peekToken->value)) {
auto op = pratt::getInfixOperator(peekToken->value);
auto [lbp, rbp] = pratt::infixBindingPower(op);
if (lbp < minBindingPower) {
keepParsing = false;
}
else {
if (auto newLhs = parseInfixExpression(std::move(lhs).value());
! newLhs) {
return Unexpected<>{ std::move(newLhs).error() };
}
else {
lhs = std::move(newLhs).value();
}
}
}
else {
keepParsing = false;
}
}
if (not lhs.has_value()) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"primary expression, i.e. "
"any of ( null, boolean, number, character, string, identifier )"
);
}
else {
return std::move(lhs).value();
}
}
Expected<ast::Optional<ast::ExpressionNode>>
Parser::parsePrimaryExpression() {
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
if (peekToken->value == TokenV::tkInteger) {
if (auto expr = parseIntegerLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::tkDecimal) {
if (auto expr = parseFloatLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::tkCharacter) {
if (auto expr = parseCharLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::tkString) {
if (auto expr = parseStringLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::kwTrue ||
peekToken->value == TokenV::kwFalse) {
if (auto expr = parseBooleanLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::kwNull) {
if (auto expr = parseNullLiteral(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::tkIdentifier) {
if (auto expr = parseIdentifierExpression(); ! expr) {
return Unexpected<>{ std::move(expr).error() };
}
else {
return std::move(expr).value();
}
}
else if (peekToken->value == TokenV::kwThis) {
auto node = ast::MakeNode<ast::IdentifierExprNode>();
if (auto ltrl = consume(TokenV::kwThis, "'this' keyword"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
node->identifierName = ltrl->strValue;
}
return node;
}
else if (peekToken->value == TokenV::kwUnderscore) {
auto node = ast::MakeNode<ast::IdentifierExprNode>();
if (auto ltrl = consume(TokenV::kwUnderscore, "'_' keyword"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
node->identifierName = ltrl->strValue;
}
return node;
}
return std::nullopt;
}
Expected<ast::ExpressionNode>
Parser::parsePrefixExpression() {
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
auto op = pratt::getPrefixOperator(peekToken->value);
auto bindingPower = pratt::prefixBindingPower(op);
std::ignore = tokenizer.consume();
auto rhs = parseExpression(bindingPower);
auto node = ast::MakeNode<ast::PrefixExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->op = op;
node->right = std::move(rhs).value();
return node;
}
Expected<ast::ExpressionNode>
Parser::parseInfixExpression(ast::ExpressionNode lhs) {
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
std::ignore = tokenizer.consume();
auto op = pratt::getInfixOperator(peekToken->value);
auto [lbp, rbp] = pratt::infixBindingPower(op);
if (op == ast::InfixOperator::ModuleAccess) {
if (auto isGeneric = match(TokenV::opLt); ! isGeneric) {
return Unexpected<>{ std::move(isGeneric).error() };
}
else if (isGeneric.value()) {
auto node = ast::MakeNode<ast::GenericExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
if (auto args = parseGenericArgumentsList(); ! args) {
return Unexpected<>{ std::move(args).error() };
}
else {
node->typeNode = std::move(lhs);
node->genericArgs = std::move(args).value();
}
return node;
}
}
auto rhs = parseExpression(rbp);
if (! rhs) {
return Unexpected<>{ std::move(rhs).error() };
}
/* TODO: MemberAccess and PointerMemberAccess do not use their respective
* nodes types yet */
if (op == ast::InfixOperator::Assignment) {
auto node = ast::MakeNode<ast::AssignExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->left = std::move(lhs);
node->right = std::move(rhs).value();
return node;
}
else if (op == ast::InfixOperator::ModuleAccess) {
auto node = ast::MakeNode<ast::ModuleAccessExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->left = std::move(lhs);
node->right = std::move(rhs).value();
return node;
}
else if (op == ast::InfixOperator::MemberAccess) {
auto node = ast::MakeNode<ast::MemberAccessExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->object = std::move(lhs);
node->member = std::move(rhs).value();
return node;
}
else if (op == ast::InfixOperator::PointerMemberAccess) {
auto node = ast::MakeNode<ast::PointerMemberAccessExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->object = std::move(lhs);
node->member = std::move(rhs).value();
return node;
}
else if (pratt::isCompoundAssignOperator(op)) {
auto node = ast::MakeNode<ast::CompoundAssignExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->op = pratt::getCompoundOperatorType(op);
node->left = std::move(lhs);
node->right = std::move(rhs).value();
return node;
}
else {
auto node = ast::MakeNode<ast::InfixExprNode>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->op = op;
node->left = std::move(lhs);
node->right = std::move(rhs).value();
return node;
}
}
Expected<ast::ExpressionNode>
Parser::parsePostfixExpression(ast::ExpressionNode lhs) {
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
std::ignore = tokenizer.consume();
auto op = pratt::getPostfixOperator(peekToken->value);
auto bindingPower = pratt::postfixBindingPower(op);
std::optional<ast::ExpressionNode> node = std::nullopt;
if (op == ast::PostfixOperator::FunctionCall) {
auto newNode = ast::MakeNode<ast::FunctionCallExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
bool stillParams = true;
if (auto close = match(TokenV::opRParen); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
stillParams = false;
}
while (stillParams) {
auto arg = parseExpression();
if (! arg) {
return Unexpected<>{ std::move(arg).error() };
}
newNode->arguments.emplace_back(std::move(arg).value());
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected{ std::move(comma).error() };
}
else if (! comma.value()) {
if (auto ntok = tokenizer.peek(); ! ntok) {
return Unexpected{ std::move(ntok).error() };
}
else {
if (ntok->value != TokenV::opRParen) {
return langException<ExceptCode::ecUnexpectedToken>(
ntok->line,
ntok->column,
toString(*ntok),
"',' or ')'"
);
}
else {
stillParams = false;
}
}
}
}
if (auto close = consume(TokenV::opRParen, "')'"); ! close) {
return Unexpected<>{ std::move(close).error() };
}
newNode->callee = std::move(lhs);
node = std::move(newNode);
}
else if (op == ast::PostfixOperator::ObjectLiteral) {
auto newNode = ast::MakeNode<ast::ObjectLtrlNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
bool stillParams = true;
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
stillParams = false;
}
if (auto isNamed = match(TokenV::opDot); ! isNamed) {
return Unexpected<>{ std::move(isNamed).error() };
}
else if (isNamed.value()) {
auto initializerNode =
ast::MakeNode<ast::ObjectLtrlNamedInitializerNode>();
initializerNode->location = newNode->location;
while (stillParams) {
auto currLocation = ast::SourceLocation{};
if (auto dot = consume(TokenV::opDot, "'.'"); ! dot) {
return Unexpected<>{ std::move(dot).error() };
}
else {
currLocation.line = dot->line;
currLocation.column = dot->column;
}
if (auto ident = consume(TokenV::tkIdentifier, "identifier");
! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
if (auto eq = consume(TokenV::opAssign, "'='"); ! eq) {
return Unexpected<>{ std::move(eq).error() };
}
auto value = parseExpression();
if (! value) {
return Unexpected<>{ std::move(value).error() };
}
auto init = ast::MakeNode<ast::ObjectLtrlNamedFieldInitNode>();
init->location = currLocation;
init->fieldName = ident->strValue;
init->fieldValue = std::move(value).value();
initializerNode->fields.push_back(std::move(init));
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected{ std::move(comma).error() };
}
else if (! comma.value()) {
if (auto ntok = tokenizer.peek(); ! ntok) {
return Unexpected{ std::move(ntok).error() };
}
else {
if (ntok->value != TokenV::opRSquirly) {
return langException<ExceptCode::ecUnexpectedToken>(
ntok->line,
ntok->column,
toString(*ntok),
"',' or '}'"
);
}
else {
stillParams = false;
}
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
stillParams = false;
}
}
newNode->initializer = std::move(initializerNode);
}
else {
auto initializerNode =
ast::MakeNode<ast::ObjectLtrlPositionalInitializerNode>();
initializerNode->location = newNode->location;
while (stillParams) {
auto arg = parseExpression();
if (! arg) {
return Unexpected<>{ std::move(arg).error() };
}
initializerNode->fields.push_back(std::move(arg).value());
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected{ std::move(comma).error() };
}
else if (! comma.value()) {
if (auto ntok = tokenizer.peek(); ! ntok) {
return Unexpected{ std::move(ntok).error() };
}
else {
if (ntok->value != TokenV::opRSquirly) {
return langException<ExceptCode::ecUnexpectedToken>(
ntok->line,
ntok->column,
toString(*ntok),
"',' or '}'"
);
}
else {
stillParams = false;
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
stillParams = false;
}
}
newNode->initializer = std::move(initializerNode);
}
if (auto close = consume(TokenV::opRSquirly, "'}'"); ! close) {
return Unexpected<>{ std::move(close).error() };
}
newNode->type = std::move(lhs);
node = std::move(newNode);
}
else if (op == ast::PostfixOperator::SliceAccess) {
auto idx = parseExpression();
if (! idx) {
return Unexpected<>{ std::move(idx).error() };
}
if (auto range = matchAndConsume(TokenV::opColon); ! range) {
return Unexpected<>{ std::move(range).error() };
}
else if (range.value()) {
auto newNode = ast::MakeNode<ast::SliceRangeExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
newNode->start = std::move(idx).value();
auto endIdx = parseExpression();
if (! endIdx) {
return Unexpected<>{ std::move(endIdx).error() };
}
newNode->end = std::move(endIdx).value();
if (auto close = consume(TokenV::opRBracket, "']'"); ! close) {
return Unexpected<>{ std::move(close).error() };
}
newNode->slice = std::move(lhs);
node = std::move(newNode);
}
else {
auto newNode = ast::MakeNode<ast::SliceAccessExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
newNode->index = std::move(idx).value();
if (auto close = consume(TokenV::opRBracket, "']'"); ! close) {
return Unexpected<>{ std::move(close).error() };
}
newNode->slice = std::move(lhs);
node = std::move(newNode);
}
}
else if (op == ast::PostfixOperator::SliceSize) {
auto newNode = ast::MakeNode<ast::SliceLengthExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
newNode->object = std::move(lhs);
node = std::move(newNode);
}
else if (op == ast::PostfixOperator::PtrToSlice) {
auto newNode = ast::MakeNode<ast::SliceCreationExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
auto len = parseExpression();
if (! len) {
return Unexpected<>{ std::move(len).error() };
}
newNode->length = std::move(len).value();
if (auto close = consume(TokenV::opRBracket, "']'"); ! close) {
return Unexpected<>{ std::move(close).error() };
}
newNode->object = std::move(lhs);
node = std::move(newNode);
}
else if (op == ast::PostfixOperator::SliceToPtr) {
auto newNode = ast::MakeNode<ast::SlicePtrExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
newNode->object = std::move(lhs);
node = std::move(newNode);
}
else if (op == ast::PostfixOperator::Reflect) {
auto newNode = ast::MakeNode<ast::ReflectionExprNode>();
newNode->location = {
.line = peekToken->line,
.column = peekToken->column
};
if (auto ident = match(TokenV::tkIdentifier); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else if (ident.value()) {
if (auto ident = consume(TokenV::tkIdentifier, "identifier");
! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
newNode->attribute = ident->strValue;
}
}
newNode->object = std::move(lhs);
node = std::move(newNode);
}
if (not node.has_value()) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"postfix operator, i.e. "
"any of ( /*TODO*/ )"
);
}
return std::move(node).value();
}
} // namespace arti::lang