Initial Parser Implementation and Feature Completion #1

Merged
me merged 16 commits from parser-dev into main 2025-12-28 11:54:12 -06:00
10 changed files with 1316 additions and 107 deletions
Showing only changes of commit 5762497f56 - Show all commits

View File

@ -50,35 +50,60 @@ namespace arti::lang::ast {
Optional,
};
enum class UnaryOperator {
enum class PrefixOperator {
Uninitialized,
Not,
Minus,
BitNot,
Ampersand,
Star,
MemPtr,
DerefPtr,
};
enum class BinaryOperator {
enum class InfixOperator {
Uninitialized,
Modulo,
Addition,
Substraction,
Division,
Multiplication,
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterEqual,
GreaterThan,
LessEqual,
BitAnd,
BitXor,
BitOr,
GreaterEqual,
LeftShift,
RightShift,
Adition,
Substraction,
Multiplication,
Division,
Modulo,
BoolAnd,
BoolOr,
BitAnd,
BitOr,
BitXor,
Assignment,
ModuleAccess,
MemberAccess,
PointerMemberAccess,
AdditionAssignment,
SubstractionAssignment,
MultiplicationAssignment,
DivisionAssignment,
ModuloAssignment,
BitAndAssignment,
BitOrAssignment,
LShiftAssignment,
RShiftAssignment,
BoolAndAssignment,
BoolOrAssignment,
};
enum class PostfixOperator {
Uninitialized,
FunctionCall,
SliceAccess,
SliceSize,
PtrToSlice,
SliceToPtr,
Reflect,
};
enum class CompoundAssignOperator {

View File

@ -32,8 +32,8 @@ namespace arti::lang::ast {
/* Main declaration node types */
struct IdentifierExpression;
struct UnaryExpression;
struct BinaryExpression;
struct PrefixExpression;
struct InfixExpression;
struct AssignExpression;
struct CompoundAssignExpression;
struct FunctionCallExpression;
@ -41,7 +41,7 @@ namespace arti::lang::ast {
struct SliceRangeExpression;
struct MemberAccessExpression;
struct PointerAccessExpression;
struct ScopeAccessExpression;
struct ModuleAccessExpression;
struct ReflectionExpression;
struct SliceCreationExpression;
struct SliceLengthExpression;
@ -51,8 +51,8 @@ namespace arti::lang::ast {
/* Public Aliases */
using IdentifierExprNode = Ptr<nodes::IdentifierExpression>;
using UnaryExprNode = Ptr<nodes::UnaryExpression>;
using BinaryExprNode = Ptr<nodes::BinaryExpression>;
using PrefixExprNode = Ptr<nodes::PrefixExpression>;
using InfixExprNode = Ptr<nodes::InfixExpression>;
using AssignExprNode = Ptr<nodes::AssignExpression>;
using CompoundAssignExprNode = Ptr<nodes::CompoundAssignExpression>;
using FunctionCallExprNode = Ptr<nodes::FunctionCallExpression>;
@ -60,7 +60,7 @@ namespace arti::lang::ast {
using SliceRangeExprNode = Ptr<nodes::SliceRangeExpression>;
using MemberAccessExprNode = Ptr<nodes::MemberAccessExpression>;
using PointerAccessExprNode = Ptr<nodes::PointerAccessExpression>;
using ScopeAccessExprNode = Ptr<nodes::ScopeAccessExpression>;
using ModuleAccessExprNode = Ptr<nodes::ModuleAccessExpression>;
using ReflectionExprNode = Ptr<nodes::ReflectionExpression>;
using SliceCreationExprNode = Ptr<nodes::SliceCreationExpression>;
using SliceLengthExprNode = Ptr<nodes::SliceLengthExpression>;
@ -77,8 +77,8 @@ namespace arti::lang::ast {
StructLtrlNode,
SliceLtrlNode,
IdentifierExprNode,
UnaryExprNode,
BinaryExprNode,
PrefixExprNode,
InfixExprNode,
AssignExprNode,
CompoundAssignExprNode,
FunctionCallExprNode,
@ -86,7 +86,7 @@ namespace arti::lang::ast {
SliceRangeExprNode,
MemberAccessExprNode,
PointerAccessExprNode,
ScopeAccessExprNode,
ModuleAccessExprNode,
SliceCreationExprNode,
SliceLengthExprNode,
SlicePtrExprNode,
@ -99,16 +99,16 @@ namespace arti::lang::ast {
String identifierName;
};
struct nodes::UnaryExpression {
struct nodes::PrefixExpression {
SourceLocation location;
UnaryOperator op;
PrefixOperator op;
ExpressionNode right;
};
struct nodes::BinaryExpression {
struct nodes::InfixExpression {
SourceLocation location;
BinaryOperator op;
InfixOperator op;
ExpressionNode left;
ExpressionNode right;
};
@ -164,7 +164,7 @@ namespace arti::lang::ast {
ExpressionNode object;
};
struct nodes::ScopeAccessExpression {
struct nodes::ModuleAccessExpression {
SourceLocation location;
String memberName;

View File

@ -167,7 +167,40 @@ namespace arti::lang {
parseExpressionStatement();
Expected<ast::ExpressionNode>
parseExpression();
parseExpression(std::uint16_t p = 0);
Expected<ast::Optional<ast::ExpressionNode>>
parsePrimaryExpression();
Expected<ast::ExpressionNode>
parsePrefixExpression();
Expected<ast::ExpressionNode>
parseInfixExpression(ast::ExpressionNode lhs);
Expected<ast::ExpressionNode>
parsePostfixExpression(ast::ExpressionNode lhs);
Expected<ast::IdentifierExprNode>
parseIdentifierExpression();
Expected<ast::CharLtrlNode>
parseCharLiteral();
Expected<ast::NullLtrlNode>
parseNullLiteral();
Expected<ast::StringLtrlNode>
parseStringLiteral();
Expected<ast::FloatLtrlNode>
parseFloatLiteral();
Expected<ast::IntegerLtrlNode>
parseIntegerLiteral();
Expected<ast::BooleanLtrlNode>
parseBooleanLiteral();
private:
std::string unitName;

View File

@ -0,0 +1,48 @@
//============================================================================//
// //
// 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::pratt {
struct BindingPower {
std::uint16_t left;
std::uint16_t right;
};
ast::PostfixOperator getPostfixOperator(TokenV tokenType);
ast::PrefixOperator getPrefixOperator(TokenV tokenType);
ast::InfixOperator getInfixOperator(TokenV tokenType);
bool isPostfixOperator(TokenV tokenType);
bool isPrefixOperator(TokenV tokenType);
bool isInfixOperator(TokenV tokenType);
std::uint16_t postfixBindingPower(ast::PostfixOperator op);
std::uint16_t prefixBindingPower(ast::PrefixOperator op);
BindingPower infixBindingPower(ast::InfixOperator op);
bool isCompoundAssignOperator(ast::InfixOperator op);
ast::CompoundAssignOperator getCompoundOperatorType(ast::InfixOperator op);
}

View File

@ -81,41 +81,57 @@ namespace arti::lang::ast {
};
// Operator label helpers (matching AST.cpp)
std::string toString(UnaryOperator op) {
using enum UnaryOperator;
std::string toString(PrefixOperator op) {
using enum PrefixOperator;
switch (op) {
case Not: return "Not (!)";
case Minus: return "Minus (-)";
case BitNot: return "BitNot (~)";
case Ampersand: return "Ampersand (&)";
case Star: return "Star (*)";
case MemPtr: return "MemPtr (&)";
case DerefPtr: return "DerefPtr (*)";
default: std::unreachable(); break;
}
std::unreachable();
}
std::string toString(BinaryOperator op) {
using enum BinaryOperator;
std::string toString(InfixOperator op) {
using enum InfixOperator;
switch (op) {
case Equal: return "Equal (==)";
case NotEqual: return "NotEqual (!=)";
case GreaterThan: return "GreaterThan (>)";
case LessThan: return "LessThan (<)";
case GreaterEqual: return "GreaterEqual (>=)";
case LessEqual: return "LessEqual (<=)";
case BitAnd: return "BitAnd (&)";
case BitXor: return "BitXor (^)";
case BitOr: return "BitOr (|)";
case LeftShift: return "LeftShift (<<)";
case RightShift: return "RightShift (>>)";
case Adition: return "Adition (+)";
case Substraction: return "Substraction (-)";
case Multiplication: return "Multiplication (*)";
case Division: return "Division (/)";
case Modulo: return "Modulo (%)";
case BoolAnd: return "BoolAnd (&&)";
case BoolOr: return "BoolOr (||)";
default: std::unreachable(); break;
case Equal: return "Equal (==)";
case NotEqual: return "NotEqual (!=)";
case GreaterThan: return "GreaterThan (>)";
case LessThan: return "LessThan (<)";
case GreaterEqual: return "GreaterEqual (>=)";
case LessEqual: return "LessEqual (<=)";
case BitAnd: return "BitAnd (&)";
case BitXor: return "BitXor (^)";
case BitOr: return "BitOr (|)";
case LeftShift: return "LeftShift (<<)";
case RightShift: return "RightShift (>>)";
case Addition: return "Addition (+)";
case Substraction: return "Substraction (-)";
case Multiplication: return "Multiplication (*)";
case Division: return "Division (/)";
case Modulo: return "Modulo (%)";
case BoolAnd: return "BoolAnd (&&)";
case BoolOr: return "BoolOr (||)";
case Assignment: return "Assignment (=)";
case ModuleAccess: return "ModuleAccess (::)";
case MemberAccess: return "MemberAccess (.)";
case PointerMemberAccess: return "PointerMemberAccess (->)";
case AdditionAssignment: return "AdditionAssignment (+=)";
case SubstractionAssignment: return "SubstractionAssignment (-=)";
case MultiplicationAssignment: return "MultiplicationAssignment (*=)";
case DivisionAssignment: return "DivisionAssignment (/=)";
case ModuloAssignment: return "ModuloAssignment (%=)";
case BitAndAssignment: return "BitAndAssignment (&=)";
case BitOrAssignment: return "BitOrAssignment (|=)";
case BoolAndAssignment: return "BoolAndAssignment (&&=)";
case BoolOrAssignment: return "BoolOrAssignment (||=)";
case LShiftAssignment: return "LShiftAssignment (<<=)";
case RShiftAssignment: return "RShiftAssignment (>>=)";
default: std::unreachable(); break;
}
std::unreachable();
}
@ -172,8 +188,8 @@ namespace arti::lang::ast {
std::string emit(const StructLtrlPositionalInitializerNode&, GraphBuilder&);
std::string emit(const StructLtrlInitializerNode &, GraphBuilder &);
std::string emit(const IdentifierExprNode &, GraphBuilder &);
std::string emit(const UnaryExprNode &, GraphBuilder &);
std::string emit(const BinaryExprNode &, GraphBuilder &);
std::string emit(const PrefixExprNode &, GraphBuilder &);
std::string emit(const InfixExprNode &, GraphBuilder &);
std::string emit(const AssignExprNode &, GraphBuilder &);
std::string emit(const CompoundAssignExprNode &, GraphBuilder &);
std::string emit(const FunctionCallExprNode &, GraphBuilder &);
@ -181,7 +197,7 @@ namespace arti::lang::ast {
std::string emit(const SliceRangeExprNode &, GraphBuilder &);
std::string emit(const MemberAccessExprNode &, GraphBuilder &);
std::string emit(const PointerAccessExprNode &, GraphBuilder &);
std::string emit(const ScopeAccessExprNode &, GraphBuilder &);
std::string emit(const ModuleAccessExprNode &, GraphBuilder &);
std::string emit(const ReflectionExprNode &, GraphBuilder &);
std::string emit(const SliceCreationExprNode &, GraphBuilder &);
std::string emit(const SliceLengthExprNode &, GraphBuilder &);
@ -629,8 +645,8 @@ namespace arti::lang::ast {
return g.makeNode(std::format("Identifier `{}`", node->identifierName));
}
std::string emit(const UnaryExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("UnaryExpression");
std::string emit(const PrefixExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("PrefixExpression");
auto opLeaf = makeLeaf(g, toString(node->op));
g.addEdge(id, opLeaf, "Operator");
auto rhs = emit(node->right, g);
@ -638,8 +654,8 @@ namespace arti::lang::ast {
return id;
}
std::string emit(const BinaryExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("BinaryExpression");
std::string emit(const InfixExprNode &node, GraphBuilder &g) {
auto id = g.makeNode("InfixExpression");
auto opLeaf = makeLeaf(g, toString(node->op));
g.addEdge(id, opLeaf, "Operator");
auto lhs = emit(node->left, g);
@ -709,7 +725,7 @@ namespace arti::lang::ast {
return id;
}
std::string emit(const ScopeAccessExprNode &node, GraphBuilder &g) {
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()) {
@ -764,8 +780,8 @@ namespace arti::lang::ast {
[&g](const StructLtrlNode &n) { return emit(n, g); },
[&g](const SliceLtrlNode &n) { return emit(n, g); },
[&g](const IdentifierExprNode &n) { return emit(n, g); },
[&g](const UnaryExprNode &n) { return emit(n, g); },
[&g](const BinaryExprNode &n) { return emit(n, g); },
[&g](const PrefixExprNode &n) { return emit(n, g); },
[&g](const InfixExprNode &n) { return emit(n, g); },
[&g](const AssignExprNode &n) { return emit(n, g); },
[&g](const CompoundAssignExprNode &n) { return emit(n, g); },
[&g](const FunctionCallExprNode &n) { return emit(n, g); },
@ -773,7 +789,7 @@ namespace arti::lang::ast {
[&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 ScopeAccessExprNode &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); },

View File

@ -62,8 +62,8 @@ namespace arti::lang::ast {
std::string toString(const StructLtrlPositionalInitializerNode&, std::string);
std::string toString(const StructLtrlInitializerNode &, std::string);
std::string toString(const IdentifierExprNode &, std::string);
std::string toString(const UnaryExprNode &, std::string);
std::string toString(const BinaryExprNode &, std::string);
std::string toString(const PrefixExprNode &, std::string);
std::string toString(const InfixExprNode &, std::string);
std::string toString(const AssignExprNode &, std::string);
std::string toString(const CompoundAssignExprNode &, std::string);
std::string toString(const FunctionCallExprNode &, std::string);
@ -71,7 +71,7 @@ namespace arti::lang::ast {
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 ScopeAccessExprNode &, std::string);
std::string toString(const ModuleAccessExprNode &, std::string);
std::string toString(const ReflectionExprNode &, std::string);
std::string toString(const SliceCreationExprNode &, std::string);
std::string toString(const SliceLengthExprNode &, std::string);
@ -100,8 +100,8 @@ namespace arti::lang::ast {
std::string toString(const ElseBranchNode &, std::string);
std::string toString(const DeferableNode &, std::string);
std::string toString(const PreLoopStmtNode &, std::string);
std::string toString(UnaryOperator op);
std::string toString(BinaryOperator op);
std::string toString(PrefixOperator op);
std::string toString(InfixOperator op);
std::string toString(CompoundAssignOperator op);
const auto StrTreeNoNode = "";
@ -725,9 +725,9 @@ namespace arti::lang::ast {
return std::format("Identifier `{}`", node->identifierName);
}
std::string toString(const UnaryExprNode &node, std::string prefix) {
std::string toString(const PrefixExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "UnaryExpression";
ss << "PrefixExpression";
int total = 2;
int emitted = 0;
appendGroupLeaf(
@ -741,9 +741,9 @@ namespace arti::lang::ast {
return ss.str();
}
std::string toString(const BinaryExprNode &node, std::string prefix) {
std::string toString(const InfixExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "BinaryExpression";
ss << "InfixExpression";
int total = 3;
int emitted = 0;
appendGroupLeaf(
@ -849,7 +849,7 @@ namespace arti::lang::ast {
return ss.str();
}
std::string toString(const ScopeAccessExprNode &node, std::string prefix) {
std::string toString(const ModuleAccessExprNode &node, std::string prefix) {
std::stringstream ss;
ss << "ScopeAccessExpression";
int total = 2;
@ -943,10 +943,10 @@ namespace arti::lang::ast {
[padding](const IdentifierExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const UnaryExprNode &node) -> std::string {
[padding](const PrefixExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const BinaryExprNode &node) -> std::string {
[padding](const InfixExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const AssignExprNode &node) -> std::string {
@ -970,7 +970,7 @@ namespace arti::lang::ast {
[padding](const PointerAccessExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const ScopeAccessExprNode &node) -> std::string {
[padding](const ModuleAccessExprNode &node) -> std::string {
return toString(node, padding);
},
[padding](const SliceCreationExprNode &node) -> std::string {
@ -1466,46 +1466,60 @@ namespace arti::lang::ast {
return std::visit(visitor, node);
}
std::string toString(UnaryOperator op) {
using enum UnaryOperator;
std::string toString(PrefixOperator op) {
using enum PrefixOperator;
switch (op) {
case Not: return "Not (!)";
case Minus: return "Minus (-)";
case BitNot: return "BitNot (~)";
case Ampersand: return "Ampersand (&)";
case Star: return "Star (*)";
case MemPtr: return "MemPtr (&)";
case DerefPtr: return "DerefPtr (*)";
default: std::unreachable(); break;
}
std::unreachable();
}
std::string toString(BinaryOperator op) {
using enum BinaryOperator;
std::string toString(InfixOperator op) {
using enum InfixOperator;
switch (op) {
case Equal: return "Equal (==)";
case NotEqual: return "NotEqual (!=)";
case GreaterThan: return "GreaterThan (>)";
case LessThan: return "LessThan (<)";
case GreaterEqual: return "GreaterEqual (>=)";
case LessEqual: return "LessEqual (<=)";
case BitAnd: return "BitAnd (&)";
case BitXor: return "BitXor (^)";
case BitOr: return "BitOr (|)";
case LeftShift: return "LeftShift (<<)";
case RightShift: return "RightShift (>>)";
case Adition: return "Adition (+)";
case Substraction: return "Substraction (-)";
case Multiplication: return "Multiplication (*)";
case Division: return "Division (/)";
case Modulo: return "Modulo (%)";
case BoolAnd: return "BoolAnd (&&)";
case BoolOr: return "BoolOr (||)";
default: std::unreachable(); break;
}
case Equal: return "Equal (==)";
case NotEqual: return "NotEqual (!=)";
case GreaterThan: return "GreaterThan (>)";
case LessThan: return "LessThan (<)";
case GreaterEqual: return "GreaterEqual (>=)";
case LessEqual: return "LessEqual (<=)";
case BitAnd: return "BitAnd (&)";
case BitXor: return "BitXor (^)";
case BitOr: return "BitOr (|)";
case LeftShift: return "LeftShift (<<)";
case RightShift: return "RightShift (>>)";
case Addition: return "Addition (+)";
case Substraction: return "Substraction (-)";
case Multiplication: return "Multiplication (*)";
case Division: return "Division (/)";
case Modulo: return "Modulo (%)";
case BoolAnd: return "BoolAnd (&&)";
case BoolOr: return "BoolOr (||)";
case Assignment: return "Assignment (=)";
case ModuleAccess: return "ModuleAccess (::)";
case MemberAccess: return "MemberAccess (.)";
case PointerMemberAccess: return "PointerMemberAccess (->)";
case AdditionAssignment: return "AdditionAssignment (+=)";
case SubstractionAssignment: return "SubstractionAssignment (-=)";
case MultiplicationAssignment: return "MultiplicationAssignment (*=)";
case DivisionAssignment: return "DivisionAssignment (/=)";
case ModuloAssignment: return "ModuloAssignment (%=)";
case BitAndAssignment: return "BitAndAssignment (&=)";
case BitOrAssignment: return "BitOrAssignment (|=)";
case BoolAndAssignment: return "BoolAndAssignment (&&=)";
case BoolOrAssignment: return "BoolOrAssignment (||=)";
case LShiftAssignment: return "LShiftAssignment (<<=)";
case RShiftAssignment: return "RShiftAssignment (>>=)";
default: std::unreachable(); break;
}
std::unreachable();
}

View File

@ -21,12 +21,530 @@
//============================================================================//
#include <artichoke/Parser/Parser.hpp>
#include <artichoke/Parser/Pratt.hpp>
namespace arti::lang {
Expected<ast::ExpressionNode>
Parser::parseExpression() {
return {};
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);
auto rhs = parseExpression(rbp);
if (! rhs) {
return Unexpected<>{ std::move(rhs).error() };
}
/* 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>();
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
node->left = std::move(lhs);
node->right = 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 = match(TokenV::opRParen); ! close) {
return Unexpected<>{ std::move(close).error() };
}
else if (close.value()) {
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::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

View File

@ -20,3 +20,159 @@
// //
//============================================================================//
#include <artichoke/Parser/Parser.hpp>
namespace arti::lang {
Expected<ast::CharLtrlNode>
Parser::parseCharLiteral() {
auto node = ast::MakeNode<ast::CharLtrlNode>();
if (auto ltrl = consume(TokenV::tkCharacter, "character literal"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
node->value = static_cast<uint8_t>(ltrl->strValue[1]);
}
return node;
}
Expected<ast::NullLtrlNode>
Parser::parseNullLiteral() {
auto node = ast::MakeNode<ast::NullLtrlNode>();
if (auto ltrl = consume(TokenV::kwNull, "null keyword"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
}
return node;
}
Expected<ast::StringLtrlNode>
Parser::parseStringLiteral() {
auto node = ast::MakeNode<ast::StringLtrlNode>();
if (auto ltrl = consume(TokenV::tkString, "string literal"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
ltrl->strValue.remove_suffix(1);
ltrl->strValue.remove_prefix(1);
node->value = ltrl->strValue;
}
return node;
}
Expected<ast::FloatLtrlNode>
Parser::parseFloatLiteral() {
auto node = ast::MakeNode<ast::FloatLtrlNode>();
if (auto ltrl = consume(TokenV::tkDecimal, "number literal"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
/* TODO: This could throw? */
std::string value{ ltrl->strValue };
node->value = std::stold(value);
}
return node;
}
Expected<ast::IntegerLtrlNode>
Parser::parseIntegerLiteral() {
auto node = ast::MakeNode<ast::IntegerLtrlNode>();
if (auto ltrl = consume(TokenV::tkInteger, "integer literal"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
/* TODO: This could throw? */
std::string value{ ltrl->strValue };
node->value = std::stoul(value);
}
return node;
}
Expected<ast::BooleanLtrlNode>
Parser::parseBooleanLiteral() {
auto node = ast::MakeNode<ast::BooleanLtrlNode>();
auto peekToken = tokenizer.peek();
if (! peekToken) {
return Unexpected<>{ std::move(peekToken).error() };
}
node->location = {
.line = peekToken->line,
.column = peekToken->column
};
if (peekToken->value == TokenV::kwTrue) {
node->value = true;
}
else if (peekToken->value == TokenV::kwFalse) {
node->value = false;
}
else {
return langException<ExceptCode::ecUnexpectedToken>(
peekToken->line,
peekToken->column,
toString(*peekToken),
"boolean literal, i.e. "
"any of ( true, false )"
);
}
std::ignore = tokenizer.consume();
return node;
}
Expected<ast::IdentifierExprNode>
Parser::parseIdentifierExpression() {
auto node = ast::MakeNode<ast::IdentifierExprNode>();
if (auto ltrl = consume(TokenV::tkIdentifier, "identifier"); ! ltrl) {
return Unexpected<>{ std::move(ltrl).error() };
}
else {
node->location = {
.line = ltrl->line,
.column = ltrl->column
};
node->identifierName = ltrl->strValue;
}
return node;
}
} // namespace arti::lang

View File

@ -0,0 +1,288 @@
//============================================================================//
// //
// 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/Pratt.hpp>
namespace arti::lang::pratt {
bool isPrefixOperator(TokenV tokenType) {
switch(tokenType) {
using enum TokenV;
case opHyphen:
case opBang:
case opStar:
case opTilde:
case opAnd:
case opLParen:
case kwNot:
return true;
default:
return false;
}
}
bool isInfixOperator(TokenV tokenType) {
switch(tokenType) {
using enum TokenV;
case opDot:
case opMod:
case opPlus:
case opHyphen:
case opSlash:
case opStar:
case opAssign:
case opAccess:
case opEq:
case opNeq:
case opLt:
case opGt:
case opLtEq:
case opGtEq:
case opLShift:
case opRShift:
case opBoolAnd:
case opBoolOr:
case kwAnd:
case kwOr:
case opAnd:
case opOr:
case opCaret:
case opArrow:
case opPlusAssign:
case opHyphenAssign:
case opStarAssign:
case opSlashAssign:
case opModAssign:
case opAndAssign:
case opOrAssign:
case opLShiftAssign:
case opRShiftAssign:
case opBoolAndAssign:
case opBoolOrAssign:
return true;
default:
return false;
}
}
bool isPostfixOperator(TokenV tokenType) {
switch(tokenType) {
using enum TokenV;
case opLParen:
case opLBracket:
case opSliceSize:
case opPtrSlice:
case opSlicePtr:
case opReflect:
return true;
default:
return false;
}
}
ast::PrefixOperator getPrefixOperator(TokenV tokenType) {
using enum ast::PrefixOperator;
switch(tokenType) {
using enum TokenV;
case opHyphen:
return Minus;
case kwNot:
case opBang:
return Not;
case opStar:
return DerefPtr;
case opTilde:
return BitNot;
case opAnd:
return MemPtr;
default:
return Uninitialized;
}
}
ast::InfixOperator getInfixOperator(TokenV tokenType) {
using enum ast::InfixOperator;
switch(tokenType) {
using enum TokenV;
case opMod:
return Modulo;
case opPlus:
return Addition;
case opHyphen:
return Substraction;
case opSlash:
return Division;
case opStar:
return Multiplication;
case opEq:
return Equal;
case opNeq:
return NotEqual;
case opLt:
return LessThan;
case opGt:
return GreaterThan;
case opLtEq:
return LessEqual;
case opGtEq:
return GreaterEqual;
case opLShift:
return LeftShift;
case opRShift:
return RightShift;
case kwAnd:
case opBoolAnd:
return BoolAnd;
case kwOr:
case opBoolOr:
return BoolOr;
case opAnd:
return BitAnd;
case opOr:
return BitOr;
case opCaret:
return BitXor;
case opAssign:
return Assignment;
case opAccess:
return ModuleAccess;
case opDot:
return MemberAccess;
case opArrow:
return PointerMemberAccess;
case opPlusAssign:
return AdditionAssignment;
case opHyphenAssign:
return SubstractionAssignment;
case opStarAssign:
return MultiplicationAssignment;
case opSlashAssign:
return DivisionAssignment;
case opModAssign:
return ModuloAssignment;
case opAndAssign:
return BitAndAssignment;
case opOrAssign:
return BitOrAssignment;
case opLShiftAssign:
return LShiftAssignment;
case opRShiftAssign:
return RShiftAssignment;
case opBoolAndAssign:
return BoolAndAssignment;
case opBoolOrAssign:
return BoolOrAssignment;
default:
return Uninitialized;
}
}
ast::PostfixOperator getPostfixOperator(TokenV tokenType) {
using enum ast::PostfixOperator;
switch(tokenType) {
using enum TokenV;
case opLParen:
return FunctionCall;
case opLBracket:
return SliceAccess;
case opSliceSize:
return SliceSize;
case opPtrSlice:
return PtrToSlice;
case opSlicePtr:
return SliceToPtr;
case opReflect:
return Reflect;
default:
return Uninitialized;
}
}
bool isCompoundAssignOperator(ast::InfixOperator op) {
switch(op) {
using enum ast::InfixOperator;
case AdditionAssignment:
case SubstractionAssignment:
case MultiplicationAssignment:
case DivisionAssignment:
case ModuloAssignment:
case BitAndAssignment:
case BitOrAssignment:
case LShiftAssignment:
case RShiftAssignment:
case BoolAndAssignment:
case BoolOrAssignment:
return true;
default:
return false;
}
}
ast::CompoundAssignOperator getCompoundOperatorType(ast::InfixOperator op) {
switch(op) {
using enum ast::InfixOperator;
case AdditionAssignment:
return ast::CompoundAssignOperator::Addition;
case SubstractionAssignment:
return ast::CompoundAssignOperator::Substraction;
case MultiplicationAssignment:
return ast::CompoundAssignOperator::Multiplication;
case DivisionAssignment:
return ast::CompoundAssignOperator::Division;
case ModuloAssignment:
return ast::CompoundAssignOperator::Modulo;
case BitAndAssignment:
return ast::CompoundAssignOperator::BitAnd;
case BitOrAssignment:
return ast::CompoundAssignOperator::BitOr;
case LShiftAssignment:
return ast::CompoundAssignOperator::LeftShift;
case RShiftAssignment:
return ast::CompoundAssignOperator::RightShift;
case BoolAndAssignment:
return ast::CompoundAssignOperator::BoolAnd;
case BoolOrAssignment:
return ast::CompoundAssignOperator::BoolOr;
default:
return ast::CompoundAssignOperator::Uninitialized;
}
}
}

111
lib/src/Parser/Pratt.cpp Normal file
View File

@ -0,0 +1,111 @@
//============================================================================//
// //
// 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/Pratt.hpp>
namespace arti::lang::pratt {
std::uint16_t prefixBindingPower(ast::PrefixOperator op) {
switch (op) {
// Unary operators generally bind very tightly
case ast::PrefixOperator::Not:
case ast::PrefixOperator::Minus:
case ast::PrefixOperator::BitNot:
case ast::PrefixOperator::MemPtr:
case ast::PrefixOperator::DerefPtr:
return 17; // Should be higher than most infix but lower than postfix
default: return 0;
}
}
BindingPower infixBindingPower(ast::InfixOperator op) {
switch (op) {
// Member Access (Highest)
case ast::InfixOperator::ModuleAccess:
case ast::InfixOperator::MemberAccess:
case ast::InfixOperator::PointerMemberAccess: return { 21, 22 };
// Multiplicative
case ast::InfixOperator::Multiplication:
case ast::InfixOperator::Division:
case ast::InfixOperator::Modulo: return { 15, 16 };
// Additive
case ast::InfixOperator::Addition:
case ast::InfixOperator::Substraction: return { 13, 14 };
// Shift
case ast::InfixOperator::LeftShift:
case ast::InfixOperator::RightShift: return { 11, 12 };
// Relational
case ast::InfixOperator::LessThan:
case ast::InfixOperator::GreaterThan:
case ast::InfixOperator::LessEqual:
case ast::InfixOperator::GreaterEqual: return { 9, 10 };
// Equality
case ast::InfixOperator::Equal:
case ast::InfixOperator::NotEqual: return { 7, 8 };
// Bitwise
case ast::InfixOperator::BitAnd: return { 6, 7 };
case ast::InfixOperator::BitXor: return { 5, 6 };
case ast::InfixOperator::BitOr: return { 4, 5 };
// Logical
case ast::InfixOperator::BoolAnd: return { 3, 4 };
case ast::InfixOperator::BoolOr: return { 1, 2 };
// Assignment (Right-associative: left > right)
case ast::InfixOperator::Assignment:
case ast::InfixOperator::AdditionAssignment:
case ast::InfixOperator::SubstractionAssignment:
case ast::InfixOperator::MultiplicationAssignment:
case ast::InfixOperator::DivisionAssignment:
case ast::InfixOperator::ModuloAssignment:
case ast::InfixOperator::BitAndAssignment:
case ast::InfixOperator::BitOrAssignment:
case ast::InfixOperator::BoolAndAssignment:
case ast::InfixOperator::BoolOrAssignment:
case ast::InfixOperator::LShiftAssignment:
case ast::InfixOperator::RShiftAssignment: return { 2, 1 };
default: return { 0, 0 };
}
}
std::uint16_t postfixBindingPower(ast::PostfixOperator op) {
switch (op) {
// Postfix usually has the highest precedence (e.g., function calls,
// slicing)
case ast::PostfixOperator::FunctionCall:
case ast::PostfixOperator::SliceAccess:
case ast::PostfixOperator::SliceSize:
case ast::PostfixOperator::PtrToSlice:
case ast::PostfixOperator::SliceToPtr:
case ast::PostfixOperator::Reflect: return 19;
default: return 0;
}
}
} // namespace arti::lang::pratt