feat(parser): support turbofish operator and specialize access expressions

Signed-off-by: erick-alcachofa <erick@artichoke.dev>

Overhaul the AST and parser logic to support explicit generic
instantiation in expressions (e.g., `Result::<u32, u32>::Ok(0)`). This
is achieved by implementing the "turbofish" operator (`::<>`) and
specializing how member and module access are handled.

* Added `GenericExpression` to represent generic instantiations in
  expressions.
* Updated the Pratt parser to look for `<` immediately following a `::`
  (ModuleAccess) operator. If found, it parses a `GenericExpression`
  containing the generic arguments.
* This change resolves the ambiguity between generic lists and
  comparison operators in the expression parser.

* Renamed `PointerAccessExpression` to `PointerMemberAccessExpression`.
* Refactored `MemberAccessExpression` and
  `PointerMemberAccessExpression` to store the member as an
  `ExpressionNode`. This allows the right-hand side of a `.` or `->` to
  be a complex expression (like a generic call).
* Simplified `ModuleAccessExpression` to a binary `left`/`right`
  structure, separating scope resolution from generic instantiation.

* Flattened the `Type` AST: replaced recursive `baseType` structures
  with a `Vector<TypeExpressionNode>` (`typeNodes`) to represent
  namespaced paths (e.g., `std::collections::Map`) more efficiently.
* Removed redundant `NamespacedType` and `NamespacedIdentifier` nodes.
* Simplified `GenericType` and `IdentifierType` to use direct `String`
  type names.

* Refactored `parseType` to iterate through namespaced components and
  populate the new flattened `typeNodes` vector.
* Updated the Pratt infix loop to correctly dispatch to `ModuleAccess`,
  `MemberAccess`, or `GenericExpression` based on the operator and
  lookahead tokens.
* Adjusted `toDot` and `toString` visitors to match the new AST
  definitions.
This commit is contained in:
erick-alcachofa 2025-12-27 23:08:26 -06:00
parent 09c44f3b67
commit 8dd75e3b8a
Signed by: me
GPG Key ID: 6FA5F8643444BAFA
6 changed files with 233 additions and 166 deletions

View File

@ -40,7 +40,8 @@ namespace arti::lang::ast {
struct SliceAccessExpression;
struct SliceRangeExpression;
struct MemberAccessExpression;
struct PointerAccessExpression;
struct PointerMemberAccessExpression;
struct GenericExpression;
struct ModuleAccessExpression;
struct ReflectionExpression;
struct SliceCreationExpression;
@ -59,7 +60,8 @@ namespace arti::lang::ast {
using SliceAccessExprNode = Ptr<nodes::SliceAccessExpression>;
using SliceRangeExprNode = Ptr<nodes::SliceRangeExpression>;
using MemberAccessExprNode = Ptr<nodes::MemberAccessExpression>;
using PointerAccessExprNode = Ptr<nodes::PointerAccessExpression>;
using PointerMemberAccessExprNode = Ptr<nodes::PointerMemberAccessExpression>;
using GenericExprNode = Ptr<nodes::GenericExpression>;
using ModuleAccessExprNode = Ptr<nodes::ModuleAccessExpression>;
using ReflectionExprNode = Ptr<nodes::ReflectionExpression>;
using SliceCreationExprNode = Ptr<nodes::SliceCreationExpression>;
@ -85,7 +87,8 @@ namespace arti::lang::ast {
SliceAccessExprNode,
SliceRangeExprNode,
MemberAccessExprNode,
PointerAccessExprNode,
PointerMemberAccessExprNode,
GenericExprNode,
ModuleAccessExprNode,
SliceCreationExprNode,
SliceLengthExprNode,
@ -153,23 +156,29 @@ namespace arti::lang::ast {
struct nodes::MemberAccessExpression {
SourceLocation location;
String memberName;
ExpressionNode member;
ExpressionNode object;
};
struct nodes::PointerAccessExpression {
struct nodes::PointerMemberAccessExpression {
SourceLocation location;
String memberName;
ExpressionNode member;
ExpressionNode object;
};
struct nodes::ModuleAccessExpression {
SourceLocation location;
String memberName;
ExpressionNode scope;
Vector<TypeNode> genericParams;
ExpressionNode left;
ExpressionNode right;
};
struct nodes::GenericExpression {
SourceLocation location;
ExpressionNode typeNode;
std::vector<TypeNode> genericArgs;
};
struct nodes::ReflectionExpression {

View File

@ -33,7 +33,6 @@ namespace arti::lang::ast {
struct Type;
struct GenericType;
struct IdentifierType;
struct NamespacedType;
/* Helper type node types */
struct NamespacedIdentifier;
@ -44,14 +43,12 @@ namespace arti::lang::ast {
using TypeNode = Ptr<nodes::Type>;
using GenericTypeNode = Ptr<nodes::GenericType>;
using IdentifierTypeNode = Ptr<nodes::IdentifierType>;
using NamespacedTypeNode = Ptr<nodes::NamespacedType>;
using NamespacedIdentifierNode = Ptr<nodes::NamespacedIdentifier>;
/* Variant nodes */
using TypeExpressionNode = Variant<
GenericTypeNode,
IdentifierTypeNode,
NamespacedTypeNode
IdentifierTypeNode
>;
/* Node definitions */
@ -60,27 +57,20 @@ namespace arti::lang::ast {
SourceLocation location;
Vector<TypeQualifier> qualifiers;
TypeExpressionNode baseType;
Vector<TypeExpressionNode> typeNodes;
};
struct nodes::GenericType {
SourceLocation location;
TypeExpressionNode baseType;
String typeName;
Vector<TypeNode> genericArgs;
};
struct nodes::IdentifierType {
SourceLocation location;
NamespacedIdentifierNode typeName;
};
struct nodes::NamespacedType {
SourceLocation location;
String typeName;
TypeExpressionNode baseType;
};
struct nodes::NamespacedIdentifier {

View File

@ -171,8 +171,6 @@ namespace arti::lang::ast {
std::string emit(const TypeNode &, GraphBuilder &);
std::string emit(const GenericTypeNode &, GraphBuilder &);
std::string emit(const IdentifierTypeNode &, GraphBuilder &);
std::string emit(const NamespacedTypeNode &, GraphBuilder &);
std::string emit(const NamespacedIdentifierNode &, GraphBuilder &);
std::string emit(const TypeExpressionNode &, GraphBuilder &);
std::string emit(const CharLtrlNode &, GraphBuilder &);
std::string emit(const NullLtrlNode &, GraphBuilder &);
@ -196,7 +194,8 @@ namespace arti::lang::ast {
std::string emit(const SliceAccessExprNode &, GraphBuilder &);
std::string emit(const SliceRangeExprNode &, GraphBuilder &);
std::string emit(const MemberAccessExprNode &, GraphBuilder &);
std::string emit(const PointerAccessExprNode &, GraphBuilder &);
std::string emit(const PointerMemberAccessExprNode &, GraphBuilder &);
std::string emit(const GenericExprNode &, GraphBuilder &);
std::string emit(const ModuleAccessExprNode &, GraphBuilder &);
std::string emit(const ReflectionExprNode &, GraphBuilder &);
std::string emit(const SliceCreationExprNode &, GraphBuilder &);
@ -494,15 +493,16 @@ namespace arti::lang::ast {
});
}
}
auto baseId = emit(node->baseType, g);
g.addEdge(id, baseId, "BaseType");
emitGroupVec(g, id, "TypeNodes", node->typeNodes, [&](const auto &arg) {
return emit(arg, g);
});
return id;
}
std::string emit(const GenericTypeNode &node, GraphBuilder &g) {
auto id = g.makeNode("GenericType");
auto baseId = emit(node->baseType, g);
g.addEdge(id, baseId, "BaseType");
auto typeId = g.makeNode(node->typeName);
g.addEdge(id, typeId, "TypeName");
if (! node->genericArgs.empty()) {
emitGroupVec(
g,
@ -516,30 +516,17 @@ namespace arti::lang::ast {
}
std::string emit(const IdentifierTypeNode &node, GraphBuilder &g) {
std::ignore = node;
auto id = g.makeNode("IdentifierType");
auto cid = emit(node->typeName, g);
g.addEdge(id, cid, "TypeName");
auto typeId = g.makeNode(node->typeName);
g.addEdge(id, typeId, "TypeName");
return id;
}
std::string emit(const NamespacedTypeNode &node, GraphBuilder &g) {
auto id = g.makeNode("NamespacedType");
auto baseId = emit(node->baseType, g);
g.addEdge(id, baseId, "BaseType");
auto leaf = makeLeaf(g, node->typeName);
g.addEdge(id, leaf, "TypeName");
return id;
}
std::string emit(const NamespacedIdentifierNode &node, GraphBuilder &g) {
return g.makeNode(namespacedIdentToString(node));
}
std::string emit(const TypeExpressionNode &node, GraphBuilder &g) {
auto visitor = OverloadSet{
[&g](const GenericTypeNode &n) { return emit(n, g); },
[&g](const IdentifierTypeNode &n) { return emit(n, g); },
[&g](const NamespacedTypeNode &n) { return emit(n, g); },
};
return std::visit(visitor, node);
}
@ -714,30 +701,36 @@ namespace arti::lang::ast {
std::string emit(const MemberAccessExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("MemberAccessExpression");
g.addEdge(id, emit(node->object, g), "Object");
g.addEdge(id, makeLeaf(g, node->memberName), "Member");
g.addEdge(id, emit(node->member, g), "Member");
return id;
}
std::string emit(const PointerAccessExprNode &node, GraphBuilder &g) {
std::string emit(const PointerMemberAccessExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("PointerAccessExpression");
g.addEdge(id, emit(node->object, g), "Object");
g.addEdge(id, makeLeaf(g, node->memberName), "Member");
g.addEdge(id, emit(node->member, g), "Member");
return id;
}
std::string emit(const ModuleAccessExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("ScopeAccessExpression");
g.addEdge(id, emit(node->scope, g), "Object");
if (! node->genericParams.empty()) {
auto id = g.makeNode("ModuleAccessExpression");
g.addEdge(id, emit(node->left, g), "Scope");
g.addEdge(id, emit(node->right, g), "Member");
return id;
}
std::string emit(const GenericExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("GenericExpression");
g.addEdge(id, emit(node->typeNode, g), "TypeNode");
if (! node->genericArgs.empty()) {
emitGroupVec(
g,
id,
"GenericParams",
node->genericParams,
[&](const auto &p) { return emit(p, g); }
"GenericArguments",
node->genericArgs,
[&](const auto &arg) { return emit(arg, g); }
);
}
g.addEdge(id, makeLeaf(g, node->memberName), "Member");
return id;
}
@ -788,12 +781,13 @@ namespace arti::lang::ast {
[&g](const SliceAccessExprNode &n) { return emit(n, g); },
[&g](const SliceRangeExprNode &n) { return emit(n, g); },
[&g](const MemberAccessExprNode &n) { return emit(n, g); },
[&g](const PointerAccessExprNode &n) { return emit(n, g); },
[&g](const PointerMemberAccessExprNode &n) { return emit(n, g); },
[&g](const ModuleAccessExprNode &n) { return emit(n, g); },
[&g](const SliceCreationExprNode &n) { return emit(n, g); },
[&g](const SliceLengthExprNode &n) { return emit(n, g); },
[&g](const SlicePtrExprNode &n) { return emit(n, g); },
[&g](const ReflectionExprNode &n) { return emit(n, g); },
[&g](const GenericExprNode &n) { return emit(n, g); },
};
return std::visit(visitor, node);
}

View File

@ -45,7 +45,6 @@ namespace arti::lang::ast {
std::string toString(const TypeNode &, std::string);
std::string toString(const GenericTypeNode &, std::string);
std::string toString(const IdentifierTypeNode &, std::string);
std::string toString(const NamespacedTypeNode &, std::string);
std::string toString(const NamespacedIdentifierNode &, std::string);
std::string toString(const TypeExpressionNode &, std::string);
std::string toString(const CharLtrlNode &, std::string);
@ -70,7 +69,8 @@ namespace arti::lang::ast {
std::string toString(const SliceAccessExprNode &, std::string);
std::string toString(const SliceRangeExprNode &, std::string);
std::string toString(const MemberAccessExprNode &, std::string);
std::string toString(const PointerAccessExprNode &, std::string);
std::string toString(const PointerMemberAccessExprNode &, std::string);
std::string toString(const GenericExprNode &, std::string);
std::string toString(const ModuleAccessExprNode &, std::string);
std::string toString(const ReflectionExprNode &, std::string);
std::string toString(const SliceCreationExprNode &, std::string);
@ -126,6 +126,17 @@ namespace arti::lang::ast {
<< toString(item, nextPrefix(prefix, isLastChild));
}
void appendItemString(
std::stringstream &ss,
const std::string &prefix,
const std::string &item,
bool isLastChild
) {
ss << "\n"
<< prefix << (isLastChild ? StrTreeLast : StrTreeChilds) << " "
<< item;
}
template <typename T>
void appendGroupVec(
std::stringstream &ss,
@ -529,12 +540,22 @@ namespace arti::lang::ast {
if (! qls.empty()) {
++total;
}
++total; // BaseType is always present
if (! node->typeNodes.empty()) {
++total;
}
int emitted = 0;
if (! qls.empty()) {
appendGroupLeafList(ss, prefix, "Qualifiers", qls, ++emitted == total);
}
appendGroupOne(ss, prefix, "BaseType", node->baseType, ++emitted == total);
if (! node->typeNodes.empty()) {
appendGroupVec(
ss,
prefix,
"TypeNodes",
node->typeNodes,
++emitted == total
);
}
return ss.str();
}
@ -546,7 +567,9 @@ namespace arti::lang::ast {
++total;
}
int emitted = 0;
appendGroupOne(ss, prefix, "BaseType", node->baseType, ++emitted == total);
appendItemString(ss, prefix, std::format("TypeName `{}`", node->typeName), ++emitted == total);
if (! node->genericArgs.empty()) {
appendGroupVec(
ss,
@ -560,17 +583,10 @@ namespace arti::lang::ast {
}
std::string toString(const IdentifierTypeNode &node, std::string prefix) {
std::ignore = node;
std::ignore = prefix;
std::stringstream ss;
ss << "IdentifierType";
appendGroupOne(ss, prefix, "TypeName", node->typeName, true);
return ss.str();
}
std::string toString(const NamespacedTypeNode &node, std::string prefix) {
std::stringstream ss;
ss << "NamespacedType";
appendGroupOne(ss, prefix, "BaseType", node->baseType, false);
appendGroupLeaf(ss, prefix, "TypeName", node->typeName, true);
ss << "TypeName `" << node->typeName << "`";
return ss.str();
}
@ -591,9 +607,6 @@ namespace arti::lang::ast {
[padding](const IdentifierTypeNode &node) -> std::string {
return toString(node, padding);
},
[padding](const NamespacedTypeNode &node) -> std::string {
return toString(node, padding);
},
};
return std::visit(visitor, node);
@ -837,37 +850,46 @@ namespace arti::lang::ast {
std::stringstream ss;
ss << "MemberAccessExpression";
appendGroupOne(ss, prefix, "Object", node->object, false);
appendGroupLeaf(ss, prefix, "Member", node->memberName, true);
appendGroupOne(ss, prefix, "Member", node->member, true);
return ss.str();
}
std::string toString(const PointerAccessExprNode &node, std::string prefix) {
std::string toString(const PointerMemberAccessExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "PointerAccessExpression";
appendGroupOne(ss, prefix, "Object", node->object, false);
appendGroupLeaf(ss, prefix, "Member", node->memberName, true);
appendGroupOne(ss, prefix, "Member", node->member, true);
return ss.str();
}
std::string toString(const ModuleAccessExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "ScopeAccessExpression";
ss << "ModuleAccessExpression";
int total = 2;
if (! node->genericParams.empty()) {
int emitted = 0;
appendGroupOne(ss, prefix, "Scope", node->left, ++emitted == total);
appendGroupOne(ss, prefix, "Member", node->right, ++emitted == total);
return ss.str();
}
std::string toString(const GenericExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "GenericExpression";
int total = 1;
if (! node->genericArgs.empty()) {
++total;
}
int emitted = 0;
appendGroupOne(ss, prefix, "Object", node->scope, ++emitted == total);
if (! node->genericParams.empty()) {
appendGroupOne(ss, prefix, "TypeNode", node->typeNode, ++emitted == total);
if (! node->genericArgs.empty()) {
appendGroupVec(
ss,
prefix,
"GenericParams",
node->genericParams,
"GenericArguments",
node->genericArgs,
++emitted == total
);
}
appendGroupLeaf(ss, prefix, "Member", node->memberName, ++emitted == total);
return ss.str();
}
@ -967,7 +989,7 @@ namespace arti::lang::ast {
[padding](const MemberAccessExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const PointerAccessExprNode &node) -> std::string {
[padding](const PointerMemberAccessExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const ModuleAccessExprNode &node) -> std::string {
@ -985,6 +1007,9 @@ namespace arti::lang::ast {
[padding](const ReflectionExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const GenericExprNode &node) -> std::string {
return toString(node, padding);
},
};
return std::visit(visitor, node);

View File

@ -274,6 +274,30 @@ namespace arti::lang {
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) {
@ -282,7 +306,6 @@ namespace arti::lang {
/* TODO: MemberAccess and PointerMemberAccess do not use their respective
* nodes types yet */
/* TODO: ModuleAccess do not use its respective node type yet */
if (op == ast::InfixOperator::Assignment) {
auto node = ast::MakeNode<ast::AssignExprNode>();
@ -296,6 +319,45 @@ namespace arti::lang {
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>();

View File

@ -72,7 +72,6 @@ namespace arti::lang {
Expected<ast::TypeNode> Parser::parseType() {
auto node = ast::MakeNode<ast::TypeNode>();
auto currentNode = ast::TypeExpressionNode{};
if (auto nextToken = tokenizer.peek(); ! nextToken) {
return Unexpected<>{ std::move(nextToken).error() };
@ -93,105 +92,91 @@ namespace arti::lang {
}
}
if (auto identType = parseNamespacedIdentifier(); ! identType) {
return Unexpected<>{ std::move(identType).error() };
if (auto ident = match(TokenV::tkIdentifier); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
currentNode = ast::MakeNode<ast::IdentifierTypeNode>();
else if (not ident.value()) {
auto peekToken = tokenizer.peek();
std::get<ast::IdentifierTypeNode>(currentNode)->location =
(*identType)->location;
std::get<ast::IdentifierTypeNode>(currentNode)->typeName =
std::move(identType).value();
}
if (auto lt = matchAndConsume(TokenV::opLt); ! lt) {
return Unexpected<>{ std::move(lt).error() };
}
else if (lt.value()) {
if (auto genericArgs = parseGenericArgumentsList(); ! genericArgs) {
return Unexpected<>{ std::move(genericArgs).error() };
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
else {
auto genParamsNode = ast::MakeNode<ast::GenericTypeNode>();
genParamsNode->location = std::visit(
[](const auto &node) { return node->location; },
currentNode
);
genParamsNode->baseType = std::move(currentNode);
genParamsNode->genericArgs = std::move(genericArgs).value();
currentNode = std::move(genParamsNode);
if (auto gt = consume(TokenV::opGt, "'>'"); ! gt) {
return Unexpected<>{ std::move(gt).error() };
}
}
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"identifier type name"
);
}
bool keepParsing = false;
if (auto access = matchAndConsume(TokenV::opAccess); ! access) {
return Unexpected<>{ std::move(access).error() };
}
else if (access.value()) {
keepParsing = true;
}
bool keepParsing = true;
while (keepParsing) {
auto currentNode = ast::TypeExpressionNode{};
if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) {
return Unexpected<>{ std::move(ident).error() };
}
else {
auto newNode = ast::MakeNode<ast::NamespacedTypeNode>();
newNode->location = std::visit(
[](const auto &node) { return node->location; },
currentNode
);
auto newNode = ast::MakeNode<ast::IdentifierTypeNode>();
newNode->location = {
.line = ident->line,
.column = ident->column
};
newNode->typeName = ident->strValue;
newNode->baseType = std::move(currentNode);
currentNode = std::move(newNode);
}
if (auto lt = matchAndConsume(TokenV::opLt); ! lt) {
return Unexpected<>{ std::move(lt).error() };
}
else if (lt.value()) {
if (auto genericArgs = parseGenericArgumentsList(); ! genericArgs) {
return Unexpected<>{ std::move(genericArgs).error() };
if (auto access = match(TokenV::opAccess); ! access) {
return Unexpected<>{ std::move(access).error() };
}
else if (not access.value()) {
currentNode = std::move(newNode);
keepParsing = false;
}
else {
auto genParamsNode = ast::MakeNode<ast::GenericTypeNode>();
std::ignore = tokenizer.consume();
genParamsNode->location = std::visit(
[](const auto &node) { return node->location; },
currentNode
);
if (auto access = match(TokenV::opLt); ! access) {
return Unexpected<>{ std::move(access).error() };
}
else if (not access.value()) {
currentNode = std::move(newNode);
}
else {
auto newNode = ast::MakeNode<ast::GenericTypeNode>();
genParamsNode->baseType = std::move(currentNode);
genParamsNode->genericArgs = std::move(genericArgs).value();
currentNode = std::move(genParamsNode);
newNode->location = {
.line = ident->line,
.column = ident->column
};
newNode->typeName = ident->strValue;
if (auto gt = consume(TokenV::opGt, "'>'"); ! gt) {
return Unexpected<>{ std::move(gt).error() };
if (auto args = parseGenericArgumentsList(); ! args) {
return Unexpected<>{ std::move(args).error() };
}
else {
newNode->genericArgs = std::move(args).value();
}
if (auto access = match(TokenV::opAccess); ! access) {
return Unexpected<>{ std::move(access).error() };
}
else if (not access.value()) {
keepParsing = false;
}
else {
std::ignore = tokenizer.consume();
}
currentNode = std::move(newNode);
}
}
}
if (auto access = matchAndConsume(TokenV::opAccess); ! access) {
return Unexpected<>{ std::move(access).error() };
}
else {
keepParsing = access.value();
}
node->typeNodes.emplace_back(std::move(currentNode));
}
node->baseType = std::move(currentNode);
return node;
}
@ -268,11 +253,9 @@ namespace arti::lang {
Expected<ast::Vector<ast::TypeNode>> Parser::parseGenericArgumentsList() {
auto args = ast::Vector<ast::TypeNode>{};
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected{ std::move(peekToken).error() };
if (auto lt = consume(TokenV::opLt, "'<'"); ! lt) {
return Unexpected<>{ std::move(lt).error() };
}
bool keepParsing = true;
@ -299,7 +282,7 @@ namespace arti::lang {
return Unexpected{ std::move(comma).error() };
}
else if (! comma.value()) {
if (peekToken = tokenizer.peekExpect(TokenV::opGt); ! peekToken) {
if (auto peekToken = tokenizer.peekExpect(TokenV::opGt); ! peekToken) {
return Unexpected{ std::move(peekToken).error() };
}
else {
@ -308,6 +291,10 @@ namespace arti::lang {
}
}
if (auto gt = consume(TokenV::opGt, "'>'"); ! gt) {
return Unexpected<>{ std::move(gt).error() };
}
return args;
}