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.
This commit is contained in:
erick-alcachofa 2025-12-28 00:12:43 -06:00
parent 8dd75e3b8a
commit 3180ca4662
Signed by: me
GPG Key ID: 6FA5F8643444BAFA
9 changed files with 228 additions and 139 deletions

View File

@ -104,6 +104,7 @@ namespace arti::lang::ast {
PtrToSlice,
SliceToPtr,
Reflect,
ObjectLiteral,
};
enum class CompoundAssignOperator {

View File

@ -76,8 +76,7 @@ namespace arti::lang::ast {
FloatLtrlNode,
IntegerLtrlNode,
BooleanLtrlNode,
StructLtrlNode,
SliceLtrlNode,
ObjectLtrlNode,
IdentifierExprNode,
PrefixExprNode,
InfixExprNode,
@ -207,30 +206,31 @@ namespace arti::lang::ast {
ExpressionNode object;
};
struct nodes::StructLiteralNamedFieldInit {
struct nodes::ObjectLiteralNamedFieldInit {
SourceLocation location;
String fieldName;
ExpressionNode fieldValue;
};
struct nodes::StructLiteralPositionalInit {
struct nodes::ObjectLiteralNamedInitializer {
SourceLocation location;
ExpressionNode fieldValue;
Vector<ObjectLtrlNamedFieldInitNode> fields;
};
struct nodes::StructLiteralNamedInitializer {
struct nodes::ObjectLiteralPositionalInitializer {
SourceLocation location;
Vector<StructLtrlNamedFieldInitNode> fields;
Vector<ExpressionNode> fields;
};
struct nodes::StructLiteralPositionalInitializer {
struct nodes::ObjectLiteral {
SourceLocation location;
Vector<StructLtrlPositionalInitNode> fields;
};
ExpressionNode type;
Optional<ObjectLtrlInitializerNode> initializer;
};
} // namespace arti::lang::ast

View File

@ -37,14 +37,12 @@ namespace arti::lang::ast {
struct FloatLiteral;
struct IntegerLiteral;
struct BooleanLiteral;
struct StructLiteral;
struct SliceLiteral;
struct ObjectLiteral;
/* Helper declaration node types */
struct StructLiteralNamedFieldInit;
struct StructLiteralPositionalInit;
struct StructLiteralNamedInitializer;
struct StructLiteralPositionalInitializer;
struct ObjectLiteralNamedFieldInit;
struct ObjectLiteralNamedInitializer;
struct ObjectLiteralPositionalInitializer;
} // namespace nodes
@ -55,22 +53,19 @@ namespace arti::lang::ast {
using FloatLtrlNode = Ptr<nodes::FloatLiteral>;
using IntegerLtrlNode = Ptr<nodes::IntegerLiteral>;
using BooleanLtrlNode = Ptr<nodes::BooleanLiteral>;
using StructLtrlNode = Ptr<nodes::StructLiteral>;
using SliceLtrlNode = Ptr<nodes::SliceLiteral>;
using ObjectLtrlNode = Ptr<nodes::ObjectLiteral>;
using StructLtrlNamedFieldInitNode =
Ptr<nodes::StructLiteralNamedFieldInit>;
using StructLtrlPositionalInitNode =
Ptr<nodes::StructLiteralPositionalInit>;
using StructLtrlNamedInitializerNode =
Ptr<nodes::StructLiteralNamedInitializer>;
using StructLtrlPositionalInitializerNode =
Ptr<nodes::StructLiteralPositionalInitializer>;
using ObjectLtrlNamedFieldInitNode =
Ptr<nodes::ObjectLiteralNamedFieldInit>;
using ObjectLtrlNamedInitializerNode =
Ptr<nodes::ObjectLiteralNamedInitializer>;
using ObjectLtrlPositionalInitializerNode =
Ptr<nodes::ObjectLiteralPositionalInitializer>;
/* Variant nodes */
using StructLtrlInitializerNode = Variant<
StructLtrlNamedInitializerNode,
StructLtrlPositionalInitializerNode
using ObjectLtrlInitializerNode = Variant<
ObjectLtrlNamedInitializerNode,
ObjectLtrlPositionalInitializerNode
>;
/* Node definitions */
@ -109,20 +104,6 @@ namespace arti::lang::ast {
Boolean value;
};
struct nodes::StructLiteral {
SourceLocation location;
TypeNode type;
Optional<StructLtrlInitializerNode> initializer;
};
struct nodes::SliceLiteral {
SourceLocation location;
TypeNode type;
Optional<StructLtrlPositionalInitializerNode> initializer;
};
/* INFO: Helper types definitions are on Expressions.hpp
* due to dependency in ExpressionNode variant. */

View File

@ -178,13 +178,11 @@ namespace arti::lang::ast {
std::string emit(const FloatLtrlNode &, GraphBuilder &);
std::string emit(const IntegerLtrlNode &, GraphBuilder &);
std::string emit(const BooleanLtrlNode &, GraphBuilder &);
std::string emit(const StructLtrlNode &, GraphBuilder &);
std::string emit(const SliceLtrlNode &, GraphBuilder &);
std::string emit(const StructLtrlNamedFieldInitNode &, GraphBuilder &);
std::string emit(const StructLtrlPositionalInitNode &, GraphBuilder &);
std::string emit(const StructLtrlNamedInitializerNode &, GraphBuilder &);
std::string emit(const StructLtrlPositionalInitializerNode&, GraphBuilder&);
std::string emit(const StructLtrlInitializerNode &, GraphBuilder &);
std::string emit(const ObjectLtrlNode &, GraphBuilder &);
std::string emit(const ObjectLtrlNamedFieldInitNode &, GraphBuilder &);
std::string emit(const ObjectLtrlNamedInitializerNode &, GraphBuilder &);
std::string emit(const ObjectLtrlPositionalInitializerNode&, GraphBuilder&);
std::string emit(const ObjectLtrlInitializerNode &, GraphBuilder &);
std::string emit(const IdentifierExprNode &, GraphBuilder &);
std::string emit(const PrefixExprNode &, GraphBuilder &);
std::string emit(const InfixExprNode &, GraphBuilder &);
@ -555,19 +553,8 @@ namespace arti::lang::ast {
}
// Struct/Slice literals and initializers
std::string emit(const StructLtrlNode &node, GraphBuilder &g) {
auto id = g.makeNode("StructLiteral");
auto t = emit(node->type, g);
g.addEdge(id, t, "Type");
if (node->initializer) {
auto cid = emit(*node->initializer, g);
g.addEdge(id, cid, "Elements");
}
return id;
}
std::string emit(const SliceLtrlNode &node, GraphBuilder &g) {
auto id = g.makeNode("SliceLiteral");
std::string emit(const ObjectLtrlNode &node, GraphBuilder &g) {
auto id = g.makeNode("ObjectLiteral");
auto t = emit(node->type, g);
g.addEdge(id, t, "Type");
if (node->initializer) {
@ -578,7 +565,7 @@ namespace arti::lang::ast {
}
std::string
emit(const StructLtrlNamedFieldInitNode &node, GraphBuilder &g) {
emit(const ObjectLtrlNamedFieldInitNode &node, GraphBuilder &g) {
auto id = g.makeNode("FieldInitializer");
auto leaf = makeLeaf(g, node->fieldName);
g.addEdge(id, leaf, "Field");
@ -588,15 +575,7 @@ namespace arti::lang::ast {
}
std::string
emit(const StructLtrlPositionalInitNode &node, GraphBuilder &g) {
auto id = g.makeNode("PositionalInitializer");
auto val = emit(node->fieldValue, g);
g.addEdge(id, val, "Value");
return id;
}
std::string
emit(const StructLtrlNamedInitializerNode &node, GraphBuilder &g) {
emit(const ObjectLtrlNamedInitializerNode &node, GraphBuilder &g) {
auto id = g.makeNode("InitializerList");
if (! node->fields.empty()) {
emitGroupVec(g, id, "Elements", node->fields, [&](const auto &f) {
@ -607,7 +586,7 @@ namespace arti::lang::ast {
}
std::string
emit(const StructLtrlPositionalInitializerNode &node, GraphBuilder &g) {
emit(const ObjectLtrlPositionalInitializerNode &node, GraphBuilder &g) {
auto id = g.makeNode("InitializerList");
if (! node->fields.empty()) {
emitGroupVec(g, id, "Elements", node->fields, [&](const auto &f) {
@ -617,10 +596,10 @@ namespace arti::lang::ast {
return id;
}
std::string emit(const StructLtrlInitializerNode &node, GraphBuilder &g) {
std::string emit(const ObjectLtrlInitializerNode &node, GraphBuilder &g) {
auto visitor = OverloadSet{
[&g](const StructLtrlNamedInitializerNode &n) { return emit(n, g); },
[&g](const StructLtrlPositionalInitializerNode &n) {
[&g](const ObjectLtrlNamedInitializerNode &n) { return emit(n, g); },
[&g](const ObjectLtrlPositionalInitializerNode &n) {
return emit(n, g);
},
};
@ -770,8 +749,7 @@ namespace arti::lang::ast {
[&g](const FloatLtrlNode &n) { return emit(n, g); },
[&g](const IntegerLtrlNode &n) { return emit(n, g); },
[&g](const BooleanLtrlNode &n) { return emit(n, g); },
[&g](const StructLtrlNode &n) { return emit(n, g); },
[&g](const SliceLtrlNode &n) { return emit(n, g); },
[&g](const ObjectLtrlNode &n) { return emit(n, g); },
[&g](const IdentifierExprNode &n) { return emit(n, g); },
[&g](const PrefixExprNode &n) { return emit(n, g); },
[&g](const InfixExprNode &n) { return emit(n, g); },

View File

@ -53,13 +53,11 @@ namespace arti::lang::ast {
std::string toString(const FloatLtrlNode &, std::string);
std::string toString(const IntegerLtrlNode &, std::string);
std::string toString(const BooleanLtrlNode &, std::string);
std::string toString(const StructLtrlNode &, std::string);
std::string toString(const SliceLtrlNode &, std::string);
std::string toString(const StructLtrlNamedFieldInitNode &, std::string);
std::string toString(const StructLtrlPositionalInitNode &, std::string);
std::string toString(const StructLtrlNamedInitializerNode &, std::string);
std::string toString(const StructLtrlPositionalInitializerNode&, std::string);
std::string toString(const StructLtrlInitializerNode &, std::string);
std::string toString(const ObjectLtrlNode &, std::string);
std::string toString(const ObjectLtrlNamedFieldInitNode &, std::string);
std::string toString(const ObjectLtrlNamedInitializerNode &, std::string);
std::string toString(const ObjectLtrlPositionalInitializerNode&, std::string);
std::string toString(const ObjectLtrlInitializerNode &, std::string);
std::string toString(const IdentifierExprNode &, std::string);
std::string toString(const PrefixExprNode &, std::string);
std::string toString(const InfixExprNode &, std::string);
@ -643,30 +641,9 @@ namespace arti::lang::ast {
return std::format("BooleanLiteral {}", node->value ? "true" : "false");
}
std::string toString(const StructLtrlNode &node, std::string prefix) {
std::string toString(const ObjectLtrlNode &node, std::string prefix) {
std::stringstream ss;
ss << "StructLiteral";
int total = 1;
if (node->initializer) {
++total;
}
int emitted = 0;
appendGroupOne(ss, prefix, "Type", node->type, ++emitted == total);
if (node->initializer) {
appendGroupOne(
ss,
prefix,
"Elements",
*node->initializer,
++emitted == total
);
}
return ss.str();
}
std::string toString(const SliceLtrlNode &node, std::string prefix) {
std::stringstream ss;
ss << "SliceLiteral";
ss << "ObjectLiteral";
int total = 1;
if (node->initializer) {
++total;
@ -686,7 +663,7 @@ namespace arti::lang::ast {
}
std::string
toString(const StructLtrlNamedFieldInitNode &node, std::string prefix) {
toString(const ObjectLtrlNamedFieldInitNode &node, std::string prefix) {
std::stringstream ss;
ss << "FieldInitializer";
appendGroupLeaf(ss, prefix, "Field", node->fieldName, false);
@ -695,15 +672,7 @@ namespace arti::lang::ast {
}
std::string
toString(const StructLtrlPositionalInitNode &node, std::string prefix) {
std::stringstream ss;
ss << "PositionalInitializer";
appendGroupOne(ss, prefix, "Value", node->fieldValue, true);
return ss.str();
}
std::string
toString(const StructLtrlNamedInitializerNode &node, std::string prefix) {
toString(const ObjectLtrlNamedInitializerNode &node, std::string prefix) {
std::stringstream ss;
ss << "InitializerList";
appendGroupVec(ss, prefix, "Elements", node->fields, true);
@ -711,7 +680,7 @@ namespace arti::lang::ast {
}
std::string toString(
const StructLtrlPositionalInitializerNode &node,
const ObjectLtrlPositionalInitializerNode &node,
std::string prefix
) {
std::stringstream ss;
@ -721,12 +690,12 @@ namespace arti::lang::ast {
}
std::string
toString(const StructLtrlInitializerNode &node, std::string padding) {
toString(const ObjectLtrlInitializerNode &node, std::string padding) {
auto visitor = OverloadSet{
[padding](const StructLtrlNamedInitializerNode &node) -> std::string {
[padding](const ObjectLtrlNamedInitializerNode &node) -> std::string {
return toString(node, padding);
},
[padding](const StructLtrlPositionalInitializerNode &node)
[padding](const ObjectLtrlPositionalInitializerNode &node)
-> std::string { return toString(node, padding); },
};
@ -956,10 +925,7 @@ namespace arti::lang::ast {
[padding](const BooleanLtrlNode &node) -> std::string {
return toString(node, padding);
},
[padding](const StructLtrlNode &node) -> std::string {
return toString(node, padding);
},
[padding](const SliceLtrlNode &node) -> std::string {
[padding](const ObjectLtrlNode &node) -> std::string {
return toString(node, padding);
},
[padding](const IdentifierExprNode &node) -> std::string {

View File

@ -504,6 +504,13 @@ namespace arti::lang {
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
}
return membersList;
@ -585,6 +592,13 @@ namespace arti::lang {
}
}
}
if (auto close = match(TokenV::opRSquirly); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
keepParsing = false;
}
}
return membersList;

View File

@ -449,13 +449,6 @@ namespace arti::lang {
}
}
}
if (auto close = match(TokenV::opRParen); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
stillParams = false;
}
}
if (auto close = consume(TokenV::opRParen, "')'"); ! close) {
@ -466,6 +459,155 @@ namespace arti::lang {
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();

View File

@ -97,6 +97,7 @@ namespace arti::lang::pratt {
case opPtrSlice:
case opSlicePtr:
case opReflect:
case opLSquirly:
return true;
default:
return false;
@ -223,6 +224,8 @@ namespace arti::lang::pratt {
return SliceToPtr;
case opReflect:
return Reflect;
case opLSquirly:
return ObjectLiteral;
default:
return Uninitialized;
}

View File

@ -88,9 +88,10 @@ namespace arti::lang::pratt {
case ast::InfixOperator::BoolAndAssignment:
case ast::InfixOperator::BoolOrAssignment:
case ast::InfixOperator::LShiftAssignment:
case ast::InfixOperator::RShiftAssignment: return { 2, 1 };
default: return { 0, 0 };
case ast::InfixOperator::RShiftAssignment:
return { 2, 1 };
default:
return { 0, 0 };
}
}
@ -103,8 +104,11 @@ namespace arti::lang::pratt {
case ast::PostfixOperator::SliceSize:
case ast::PostfixOperator::PtrToSlice:
case ast::PostfixOperator::SliceToPtr:
case ast::PostfixOperator::Reflect: return 19;
default: return 0;
case ast::PostfixOperator::Reflect:
case ast::PostfixOperator::ObjectLiteral:
return 19;
default:
return 0;
}
}