artichoke-lang/lib/src/Parser/Declarations.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

823 lines
24 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>
namespace arti::lang {
Expected<ast::Optional<ast::TopLevelDeclNode>>
Parser::parseTopLevelDeclaration() {
bool exportable = false;
if (auto exported = matchAndConsume(TokenV::kwExport); ! exported) {
return Unexpected<>{ std::move(exported).error() };
}
else if (exported.value()) {
exportable = true;
}
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
if (peekToken->value == TokenV::kwImport) {
if (exportable) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"exportable declaration, ie. Struct, Enum, Function, Module"
);
}
if (auto node = parseImportDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
else if (peekToken->value == TokenV::kwUsing) {
if (exportable) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"exportable declaration, ie. Struct, Enum, Function, Module"
);
}
if (auto node = parseAliasDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
else if (peekToken->value == TokenV::kwModule) {
if (auto node = parseModuleDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
(*node)->isExported = exportable;
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
else if (peekToken->value == TokenV::kwStruct) {
if (auto node = parseStructDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
(*node)->isExported = exportable;
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
else if (peekToken->value == TokenV::kwEnum) {
if (auto node = parseEnumDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
(*node)->isExported = exportable;
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
else if (peekToken->value == TokenV::kwFn) {
if (auto node = parseFunctionDeclaration(); ! node) {
return Unexpected<>{ std::move(node).error() };
}
else {
(*node)->isExported = exportable;
return ast::TopLevelDeclNode{ std::move(node).value() };
}
}
return std::nullopt;
}
Expected<ast::ImportDeclNode> Parser::parseImportDeclaration() {
auto node = ast::MakeNode<ast::ImportDeclNode>();
if (auto kw = consume(TokenV::kwImport, "'import' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto target = parseNamespacedIdentifier(); ! target) {
return Unexpected<>{ std::move(target).error() };
}
else {
node->importTarget = std::move(target).value();
}
if (auto acc = matchAndConsume(TokenV::opAccess); ! acc) {
return Unexpected<>{ std::move(acc).error() };
}
else if (acc.value()) {
if (auto star = matchAndConsume(TokenV::opStar); ! star) {
return Unexpected<>{ std::move(star).error() };
}
else if (star.value()) {
node->importAll = true;
}
else {
auto star = tokenizer.peek();
return langException<ExceptCode::ecUnexpectedToken>(
star->line,
star->column,
toString(*star),
"identifier or '*'"
);
}
}
if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) {
return Unexpected<>{ std::move(semicolon).error() };
}
return node;
}
Expected<ast::AliasDeclNode> Parser::parseAliasDeclaration() {
auto node = ast::MakeNode<ast::AliasDeclNode>();
if (auto kw = consume(TokenV::kwUsing, "'using' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
node->alias = ident->strValue;
}
if (auto eq = consume(TokenV::opAssign, "'='"); ! eq) {
return Unexpected{ std::move(eq).error() };
}
if (auto type = parseType(); ! type) {
return Unexpected{ std::move(type).error() };
}
else {
node->target = std::move(type).value();
}
if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) {
return Unexpected<>{ std::move(semicolon).error() };
}
return node;
}
Expected<ast::ModuleDeclNode> Parser::parseModuleDeclaration() {
auto node = ast::MakeNode<ast::ModuleDeclNode>();
auto decl = ast::Optional<ast::TopLevelDeclNode>{};
bool keepParsing = true;
if (auto kw = consume(TokenV::kwModule, "'module' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto name = parseNamespacedIdentifier(); ! name) {
return Unexpected<>{ std::move(name).error() };
}
else {
node->name = std::move(name).value();
}
if (auto lsquirly = consume(TokenV::opLSquirly, "'{'"); ! lsquirly) {
return Unexpected<>{ std::move(lsquirly).error() };
}
while (keepParsing) {
if (auto ok = parseTopLevelDeclaration(); ! ok) {
return Unexpected<>{ std::move(ok).error() };
}
else {
decl = std::move(ok).value();
if (! decl.has_value()) {
keepParsing = false;
continue;
}
if (std::holds_alternative<ast::ModuleDeclNode>(*decl)) {
node->childModules.push_back(
std::get<ast::ModuleDeclNode>(std::move(*decl))
);
}
else if (std::holds_alternative<ast::StructDeclNode>(*decl)) {
node->innerDeclarations.push_back(
std::get<ast::StructDeclNode>(std::move(*decl))
);
}
else if (std::holds_alternative<ast::EnumDeclNode>(*decl)) {
node->innerDeclarations.push_back(
std::get<ast::EnumDeclNode>(std::move(*decl))
);
}
else if (std::holds_alternative<ast::FunctionDeclNode>(*decl)) {
node->innerDeclarations.push_back(
std::get<ast::FunctionDeclNode>(std::move(*decl))
);
}
else if (std::holds_alternative<ast::AliasDeclNode>(*decl)) {
node->aliasDeclarations.push_back(
std::get<ast::AliasDeclNode>(std::move(*decl))
);
}
else if (std::holds_alternative<ast::ImportDeclNode>(*decl)) {
auto importDecl = std::get<ast::ImportDeclNode>(std::move(*decl));
return langException<ExceptCode::ecImportInsideModule>(
importDecl->location.line,
importDecl->location.column
);
}
}
}
if (auto rsquirly = consume(TokenV::opRSquirly, "'{'"); ! rsquirly) {
return Unexpected<>{ std::move(rsquirly).error() };
}
return node;
}
Expected<ast::StructDeclNode> Parser::parseStructDeclaration() {
auto node = ast::MakeNode<ast::StructDeclNode>();
if (auto kw = consume(TokenV::kwStruct, "'struct' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto name = consume(TokenV::tkIdentifier, "identifier"); ! name) {
return Unexpected<>{ std::move(name).error() };
}
else {
node->name = name->strValue;
}
if (auto hasLt = matchAndConsume(TokenV::opLt); ! hasLt) {
return Unexpected<>{ std::move(hasLt).error() };
}
else if (hasLt.value()) {
if (auto params = parseGenericParamsList(); ! params) {
return Unexpected<>{ std::move(params).error() };
}
else {
node->genericParams = std::move(params).value();
if (auto hasGt = consume(TokenV::opGt, "'>'"); ! hasGt) {
return Unexpected<>{ std::move(hasGt ).error() };
}
}
}
if (auto lsquirly = consume(TokenV::opLSquirly, "'{'"); ! lsquirly) {
return Unexpected<>{ std::move(lsquirly).error() };
}
if (auto members = parseStructMembersList(); ! members) {
return Unexpected<>{ std::move(members).error() };
}
else {
node->structMembers = std::move(members).value();
}
if (auto rsquirly = consume(TokenV::opRSquirly, "'}'"); ! rsquirly) {
return Unexpected<>{ std::move(rsquirly).error() };
}
return node;
}
Expected<ast::EnumDeclNode> Parser::parseEnumDeclaration() {
auto node = ast::MakeNode<ast::EnumDeclNode>();
if (auto kw = consume(TokenV::kwEnum, "'enum' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto name = consume(TokenV::tkIdentifier, "identifier"); ! name) {
return Unexpected<>{ std::move(name).error() };
}
else {
node->name = name->strValue;
}
if (auto hasLt = matchAndConsume(TokenV::opLt); ! hasLt) {
return Unexpected<>{ std::move(hasLt).error() };
}
else if (hasLt.value()) {
if (auto params = parseGenericParamsList(); ! params) {
return Unexpected<>{ std::move(params).error() };
}
else {
node->genericParams = std::move(params).value();
if (auto hasGt = consume(TokenV::opGt, "'>'"); ! hasGt) {
return Unexpected<>{ std::move(hasGt ).error() };
}
}
}
if (auto lsquirly = consume(TokenV::opLSquirly, "'{'"); ! lsquirly) {
return Unexpected<>{ std::move(lsquirly).error() };
}
if (auto members = parseEnumMembersList(); ! members) {
return Unexpected<>{ std::move(members).error() };
}
else {
node->enumMembers = std::move(members).value();
}
if (auto rsquirly = consume(TokenV::opRSquirly, "'}'"); ! rsquirly) {
return Unexpected<>{ std::move(rsquirly).error() };
}
return node;
}
Expected<ast::Vector<ast::GenericParamNode>>
Parser::parseGenericParamsList() {
auto paramsList = ast::Vector<ast::GenericParamNode>{};
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
bool keepParsing = true;
if (auto comma = tokenizer.peek();
comma and comma->value == TokenV::opComma) {
return langException<ExceptCode::ecUnexpectedToken>(
comma->line,
comma->column,
toString(*comma),
"'typename' keyword"
);
}
while (keepParsing) {
if (auto param = parseGenericParam(); ! param) {
return Unexpected{ std::move(param).error() };
}
else {
paramsList.push_back(std::move(param).value());
}
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected{ std::move(comma).error() };
}
else if (! comma.value()) {
if (peekToken = tokenizer.peek(); ! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
else {
if (peekToken->value != TokenV::opGt) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"',' or '>'"
);
}
else {
keepParsing = false;
}
}
}
}
return paramsList;
}
Expected<ast::GenericParamNode> Parser::parseGenericParam() {
auto node = ast::MakeNode<ast::GenericParamNode>();
if (auto kw = consume(TokenV::kwTypename, "'typename' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) {
return Unexpected{ std::move(ident).error() };
}
else {
node->name = ident->strValue;
}
return node;
}
Expected<ast::Vector<ast::StructMemberNode>>
Parser::parseStructMembersList() {
auto membersList = ast::Vector<ast::StructMemberNode>{};
if (auto comma = tokenizer.peek();
comma and comma->value == TokenV::opComma) {
return langException<ExceptCode::ecUnexpectedToken>(
comma->line,
comma->column,
toString(*comma),
"identifier or '}'"
);
}
bool keepParsing = true;
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
while (keepParsing) {
if (auto member = parseStructMember(); ! member) {
return Unexpected{ std::move(member).error() };
}
else {
membersList.push_back(std::move(member).value());
}
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected<>{ std::move(comma).error() };
}
else if (! comma.value()) {
if (auto peekToken = tokenizer.peek(); ! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
else {
if (peekToken->value != TokenV::opRSquirly) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"',' or '}'"
);
}
else {
keepParsing = false;
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
}
return membersList;
}
Expected<ast::StructMemberNode> Parser::parseStructMember() {
auto node = ast::MakeNode<ast::StructMemberNode>();
if (auto ident = consume(TokenV::tkIdentifier, "'identifier'"); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
node->location = { ident->line, ident->column };
node->name = ident->strValue;
}
if (auto colon = consume(TokenV::opColon, "':'"); ! colon) {
return Unexpected{ std::move(colon).error() };
}
if (auto type = parseType(); ! type) {
return Unexpected{ std::move(type).error() };
}
else {
node->type = std::move(type).value();
}
return node;
}
Expected<ast::Vector<ast::EnumMemberNode>> Parser::parseEnumMembersList() {
auto membersList = ast::Vector<ast::EnumMemberNode>{};
if (auto comma = tokenizer.peek();
comma and comma->value == TokenV::opComma) {
return langException<ExceptCode::ecUnexpectedToken>(
comma->line,
comma->column,
toString(*comma),
"identifier or '}'"
);
}
bool keepParsing = true;
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
while (keepParsing) {
if (auto member = parseEnumMember(); ! member) {
return Unexpected{ std::move(member).error() };
}
else {
membersList.push_back(std::move(member).value());
}
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected<>{ std::move(comma).error() };
}
else if (! comma.value()) {
if (auto peekToken = tokenizer.peek(); ! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
else {
if (peekToken->value != TokenV::opRSquirly) {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"',' or '}'"
);
}
else {
keepParsing = false;
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
}
return membersList;
}
Expected<ast::EnumMemberNode> Parser::parseEnumMember() {
auto node = ast::MakeNode<ast::EnumMemberNode>();
if (auto ident = consume(TokenV::tkIdentifier, "'identifier'"); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
node->location = { ident->line, ident->column };
node->name = ident->strValue;
}
if (auto hasLParen = matchAndConsume(TokenV::opLParen); ! hasLParen) {
return Unexpected{ std::move(hasLParen).error() };
}
else if (hasLParen.value()) {
if (auto type = parseType(); ! type) {
return Unexpected{ std::move(type).error() };
}
else {
node->type = std::move(type).value();
if (auto hasRParen = consume(TokenV::opRParen, "')'"); ! hasRParen) {
return Unexpected{ std::move(hasRParen).error() };
}
}
}
return node;
}
Expected<ast::FunctionDeclNode> Parser::parseFunctionDeclaration() {
auto node = ast::MakeNode<ast::FunctionDeclNode>();
if (auto kw = consume(TokenV::kwFn, "'fn' keyword"); ! kw) {
return Unexpected<>{ std::move(kw).error() };
}
else {
node->location = { kw->line, kw->column };
}
if (auto name = consume(TokenV::tkIdentifier, "identifier"); ! name) {
return Unexpected<>{ std::move(name).error() };
}
else {
node->name = name->strValue;
}
if (auto hasLt = matchAndConsume(TokenV::opLt); ! hasLt) {
return Unexpected<>{ std::move(hasLt).error() };
}
else if (hasLt.value()) {
if (auto params = parseGenericParamsList(); ! params) {
return Unexpected<>{ std::move(params).error() };
}
else {
node->genericParams = std::move(params).value();
if (auto hasGt = consume(TokenV::opGt, "'>'"); ! hasGt) {
return Unexpected<>{ std::move(hasGt ).error() };
}
}
}
if (auto lparen = consume(TokenV::opLParen, "'('"); ! lparen) {
return Unexpected<>{ std::move(lparen).error() };
}
if (auto rparen = tokenizer.peek(); ! rparen) {
return Unexpected<>{ std::move(rparen).error() };
}
else if (rparen->value != TokenV::opRParen) {
if (auto params = parseFunctionParamsList(); ! params) {
return Unexpected<>{ std::move(params).error() };
}
else {
node->functionParams = std::move(params).value();
}
}
if (auto rparen = consume(TokenV::opRParen, "')'"); ! rparen) {
return Unexpected<>{ std::move(rparen).error() };
}
if (auto hasArrow = matchAndConsume(TokenV::opArrow); ! hasArrow) {
return Unexpected<>{ std::move(hasArrow).error() };
}
else if (hasArrow.value()) {
if (auto type = parseType(); ! type) {
return Unexpected<>{ std::move(type).error() };
}
else {
node->returnType = std::move(type).value();
}
}
if (auto body = parseCodeBlock(); ! body) {
return Unexpected<>{ std::move(body).error() };
}
else {
node->functionBody = std::move(body).value();
}
return node;
}
Expected<ast::Vector<ast::FunctionParamNode>>
Parser::parseFunctionParamsList() {
auto params = ast::Vector<ast::FunctionParamNode>{};
if (auto comma = tokenizer.peek();
comma and comma->value == TokenV::opComma) {
return langException<ExceptCode::ecUnexpectedToken>(
comma->line,
comma->column,
toString(*comma),
"identifier, 'this' keyword or ')'"
);
}
if (auto hasThis = match(TokenV::kwThis); ! hasThis) {
return Unexpected<>{ std::move(hasThis).value() };
}
else if (hasThis.value()) {
auto thisParam = parseFunctionParamThis();
if (auto thisṔaram = parseFunctionParamThis(); ! thisParam) {
return Unexpected<>{ std::move(thisParam).error() };
}
else {
params.push_back(std::move(thisParam).value());
}
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected<>{ std::move(comma).error() };
}
}
bool keepParsing = true;
while (keepParsing) {
if (auto param = parseFunctionParam(); ! param) {
return Unexpected<>{ std::move(param).error() };
}
else {
params.push_back(std::move(param).value());
if (auto comma = matchAndConsume(TokenV::opComma); ! comma) {
return Unexpected<>{ std::move(comma).error() };
}
if (auto peekToken = tokenizer.peek(); ! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
else {
if (peekToken->value == TokenV::opRParen) {
keepParsing = false;
}
}
}
}
return params;
}
Expected<ast::FunctionParamNode> Parser::parseFunctionParamThis() {
auto node = ast::MakeNode<ast::FunctionParamNode>();
if (auto thisToken = consume(TokenV::kwThis, "'this' keyword");
! thisToken) {
return Unexpected<>{ std::move(thisToken).error() };
}
else {
node->isThis = true;
node->location.line = thisToken->line;
node->location.column = thisToken->column;
node->name = "this";
if (auto type = parseType(); ! type) {
return Unexpected<>{ std::move(type).error() };
}
else {
node->type = std::move(type).value();
}
}
return node;
}
Expected<ast::FunctionParamNode> Parser::parseFunctionParam() {
auto node = ast::MakeNode<ast::FunctionParamNode>();
node->isThis = false;
if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
node->name = ident->strValue;
node->location.line = ident->line;
node->location.column = ident->column;
}
if (auto colon = consume(TokenV::opColon, "':'"); ! colon) {
return Unexpected{ std::move(colon).error() };
}
if (auto type = parseType(); ! type) {
return Unexpected{ std::move(type).error() };
}
else {
node->type = std::move(type).value();
}
return node;
}
} // namespace arti::lang