diff --git a/.gitignore b/.gitignore index 5a3697e..b368a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ build/** install/** +cpm-package-lock.cmake + TODO.md diff --git a/add-notice.sh b/add-notice.sh new file mode 100755 index 0000000..963ec4b --- /dev/null +++ b/add-notice.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Path to your notice file +NOTICE_FILE="./NOTICE" + +# Check if notice file exists +if [ ! -f "$NOTICE_FILE" ]; then + echo "Error: $NOTICE_FILE not found!" + exit 1 +fi + +# Find all .cpp, .hpp, .h, and .cc files +# Excluding the .git directory +find . -type d -name ".git" -prune -o -type d -name "build" -prune -o -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.cc" \) -print | while read -r file; do + + # Check if the file already contains a specific keyword from your notice + # to avoid double-prepending (e.g., "Copyright") + if grep -q "Copyright" "$file"; then + echo "Skipping $file (License already exists)" + else + echo "Adding notice to $file" + # Create a temp file: notice + newline + original content + { cat "$NOTICE_FILE"; echo ""; cat "$file"; } > "$file.tmp" && mv "$file.tmp" "$file" + fi +done + +echo "Done!" diff --git a/docs/grammar.ebnf b/docs/grammar.ebnf index a99b01b..23e99cc 100644 --- a/docs/grammar.ebnf +++ b/docs/grammar.ebnf @@ -170,7 +170,7 @@ non_exportable_declaration = "switch" "(" ")" "{" * ? "}" = - ( "(" ")" )? "->" + ( "|" "|" )? "->" = "->" diff --git a/frontend/src/main.cpp b/frontend/src/main.cpp index 3e67a25..a5536c5 100644 --- a/frontend/src/main.cpp +++ b/frontend/src/main.cpp @@ -1,5 +1,78 @@ -#include +//============================================================================// +// // +// 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 . // +// // +//============================================================================// -int main(int, char **) { - std::println("[LOG] Hello world"); +#include +#include +#include + +#include + +std::string sanitizePath(std::string_view path) { + namespace fs = std::filesystem; + fs::path p{ path }; + return p.filename().string(); +} + +int main(int argc, char **argv) { + using namespace arti::lang; + + if (argc < 2) { + std::println("Usage:\n {} ", argv[0]); + return -1; + } + + std::ifstream file; + file.open(argv[1]); + + if (! file.is_open()) { + std::println("Failed to open file {}", argv[1]); + return -1; + } + + std::string buffer{ std::istreambuf_iterator(file), + std::istreambuf_iterator() }; + + auto parser = Parser{ sanitizePath(argv[1]), buffer }; + + auto res = parser.parse(); + + if (! res) { + std::println( + "Error at line {} column {}", + res.error().line, + res.error().column + ); + + std::println("{}", res.error().message); + + return -1; + } + + auto ast = std::move(res).value(); + + std::println("# AST"); + std::println("```markdown"); + std::println("{}", ast::toString(ast)); + std::println("```"); + + // std::println("{}", ast::toDot(ast)); } diff --git a/lib/cmake/inputs/Info.hpp.in b/lib/cmake/inputs/Info.hpp.in index 3ddea3f..5afa5a5 100644 --- a/lib/cmake/inputs/Info.hpp.in +++ b/lib/cmake/inputs/Info.hpp.in @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Coroutine/Generator.hpp b/lib/include/artichoke/Coroutine/Generator.hpp index 899f8f1..839bdbb 100644 --- a/lib/include/artichoke/Coroutine/Generator.hpp +++ b/lib/include/artichoke/Coroutine/Generator.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Coroutine/Utils.hpp b/lib/include/artichoke/Coroutine/Utils.hpp index 6a6341c..89f39eb 100644 --- a/lib/include/artichoke/Coroutine/Utils.hpp +++ b/lib/include/artichoke/Coroutine/Utils.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #define yield co_yield diff --git a/lib/include/artichoke/Parser/AST/AST.hpp b/lib/include/artichoke/Parser/AST/AST.hpp index d45cc33..8a8a10c 100644 --- a/lib/include/artichoke/Parser/AST/AST.hpp +++ b/lib/include/artichoke/Parser/AST/AST.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Parser/AST/Common.hpp b/lib/include/artichoke/Parser/AST/Common.hpp index 740ba63..8fbd275 100644 --- a/lib/include/artichoke/Parser/AST/Common.hpp +++ b/lib/include/artichoke/Parser/AST/Common.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -28,35 +50,61 @@ 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, + ObjectLiteral, }; enum class CompoundAssignOperator { diff --git a/lib/include/artichoke/Parser/AST/Declarations.hpp b/lib/include/artichoke/Parser/AST/Declarations.hpp index 744eb8e..cac62f0 100644 --- a/lib/include/artichoke/Parser/AST/Declarations.hpp +++ b/lib/include/artichoke/Parser/AST/Declarations.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Parser/AST/Expressions.hpp b/lib/include/artichoke/Parser/AST/Expressions.hpp index 64599d4..79ff4dd 100644 --- a/lib/include/artichoke/Parser/AST/Expressions.hpp +++ b/lib/include/artichoke/Parser/AST/Expressions.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -10,39 +32,43 @@ namespace arti::lang::ast { /* Main declaration node types */ struct IdentifierExpression; - struct UnaryExpression; - struct BinaryExpression; + struct PrefixExpression; + struct InfixExpression; struct AssignExpression; struct CompoundAssignExpression; struct FunctionCallExpression; struct SliceAccessExpression; struct SliceRangeExpression; struct MemberAccessExpression; - struct PointerAccessExpression; - struct ScopeAccessExpression; + struct PointerMemberAccessExpression; + struct GenericExpression; + struct ModuleAccessExpression; struct ReflectionExpression; struct SliceCreationExpression; struct SliceLengthExpression; struct SlicePtrExpression; + struct TypeExpression; } // namespace nodes /* Public Aliases */ using IdentifierExprNode = Ptr; - using UnaryExprNode = Ptr; - using BinaryExprNode = Ptr; + using PrefixExprNode = Ptr; + using InfixExprNode = Ptr; using AssignExprNode = Ptr; using CompoundAssignExprNode = Ptr; using FunctionCallExprNode = Ptr; using SliceAccessExprNode = Ptr; using SliceRangeExprNode = Ptr; using MemberAccessExprNode = Ptr; - using PointerAccessExprNode = Ptr; - using ScopeAccessExprNode = Ptr; + using PointerMemberAccessExprNode = Ptr; + using GenericExprNode = Ptr; + using ModuleAccessExprNode = Ptr; using ReflectionExprNode = Ptr; using SliceCreationExprNode = Ptr; using SliceLengthExprNode = Ptr; using SlicePtrExprNode = Ptr; + using TypeExprNode = Ptr; /* Variant nodes */ using ExpressionNode = Variant< @@ -52,23 +78,24 @@ namespace arti::lang::ast { FloatLtrlNode, IntegerLtrlNode, BooleanLtrlNode, - StructLtrlNode, - SliceLtrlNode, + ObjectLtrlNode, IdentifierExprNode, - UnaryExprNode, - BinaryExprNode, + PrefixExprNode, + InfixExprNode, AssignExprNode, CompoundAssignExprNode, FunctionCallExprNode, SliceAccessExprNode, SliceRangeExprNode, MemberAccessExprNode, - PointerAccessExprNode, - ScopeAccessExprNode, + PointerMemberAccessExprNode, + GenericExprNode, + ModuleAccessExprNode, SliceCreationExprNode, SliceLengthExprNode, SlicePtrExprNode, - ReflectionExprNode + ReflectionExprNode, + TypeExprNode >; /* Node definitions */ @@ -77,16 +104,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; }; @@ -131,23 +158,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::ScopeAccessExpression { + struct nodes::ModuleAccessExpression { SourceLocation location; - String memberName; - ExpressionNode scope; - Vector genericParams; + ExpressionNode left; + ExpressionNode right; + }; + + struct nodes::GenericExpression { + SourceLocation location; + + ExpressionNode typeNode; + std::vector genericArgs; }; struct nodes::ReflectionExpression { @@ -176,30 +209,37 @@ 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 fields; }; - struct nodes::StructLiteralNamedInitializer { + struct nodes::ObjectLiteralPositionalInitializer { SourceLocation location; - Vector fields; + Vector fields; }; - struct nodes::StructLiteralPositionalInitializer { + struct nodes::ObjectLiteral { SourceLocation location; - Vector fields; - }; + ExpressionNode type; + Optional initializer; + }; + + struct nodes::TypeExpression { + SourceLocation location; + + TypeNode type; + }; } // namespace arti::lang::ast diff --git a/lib/include/artichoke/Parser/AST/Literals.hpp b/lib/include/artichoke/Parser/AST/Literals.hpp index 3699b8e..74a6aef 100644 --- a/lib/include/artichoke/Parser/AST/Literals.hpp +++ b/lib/include/artichoke/Parser/AST/Literals.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -15,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 @@ -33,22 +53,19 @@ namespace arti::lang::ast { using FloatLtrlNode = Ptr; using IntegerLtrlNode = Ptr; using BooleanLtrlNode = Ptr; - using StructLtrlNode = Ptr; - using SliceLtrlNode = Ptr; + using ObjectLtrlNode = Ptr; - using StructLtrlNamedFieldInitNode = - Ptr; - using StructLtrlPositionalInitNode = - Ptr; - using StructLtrlNamedInitializerNode = - Ptr; - using StructLtrlPositionalInitializerNode = - Ptr; + using ObjectLtrlNamedFieldInitNode = + Ptr; + using ObjectLtrlNamedInitializerNode = + Ptr; + using ObjectLtrlPositionalInitializerNode = + Ptr; /* Variant nodes */ - using StructLtrlInitializerNode = Variant< - StructLtrlNamedInitializerNode, - StructLtrlPositionalInitializerNode + using ObjectLtrlInitializerNode = Variant< + ObjectLtrlNamedInitializerNode, + ObjectLtrlPositionalInitializerNode >; /* Node definitions */ @@ -87,20 +104,6 @@ namespace arti::lang::ast { Boolean value; }; - struct nodes::StructLiteral { - SourceLocation location; - - TypeNode type; - Optional initializer; - }; - - struct nodes::SliceLiteral { - SourceLocation location; - - TypeNode type; - Optional initializer; - }; - /* INFO: Helper types definitions are on Expressions.hpp * due to dependency in ExpressionNode variant. */ diff --git a/lib/include/artichoke/Parser/AST/Statements.hpp b/lib/include/artichoke/Parser/AST/Statements.hpp index 23834a0..adfc6d1 100644 --- a/lib/include/artichoke/Parser/AST/Statements.hpp +++ b/lib/include/artichoke/Parser/AST/Statements.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -71,7 +93,8 @@ namespace arti::lang::ast { WhileStmtNode, DoWhileStmtNode, InfLoopStmtNode, - ExpressionStmtNode + ExpressionStmtNode, + CodeBlockStmtNode >; using ElseBranchNode = Variant< diff --git a/lib/include/artichoke/Parser/AST/Types.hpp b/lib/include/artichoke/Parser/AST/Types.hpp index f3bf72e..37b2143 100644 --- a/lib/include/artichoke/Parser/AST/Types.hpp +++ b/lib/include/artichoke/Parser/AST/Types.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -11,7 +33,6 @@ namespace arti::lang::ast { struct Type; struct GenericType; struct IdentifierType; - struct NamespacedType; /* Helper type node types */ struct NamespacedIdentifier; @@ -22,14 +43,12 @@ namespace arti::lang::ast { using TypeNode = Ptr; using GenericTypeNode = Ptr; using IdentifierTypeNode = Ptr; - using NamespacedTypeNode = Ptr; using NamespacedIdentifierNode = Ptr; /* Variant nodes */ using TypeExpressionNode = Variant< GenericTypeNode, - IdentifierTypeNode, - NamespacedTypeNode + IdentifierTypeNode >; /* Node definitions */ @@ -38,27 +57,20 @@ namespace arti::lang::ast { SourceLocation location; Vector qualifiers; - TypeExpressionNode baseType; + Vector typeNodes; }; struct nodes::GenericType { SourceLocation location; - TypeExpressionNode baseType; + String typeName; Vector genericArgs; }; struct nodes::IdentifierType { SourceLocation location; - NamespacedIdentifierNode typeName; - }; - - struct nodes::NamespacedType { - SourceLocation location; - String typeName; - TypeExpressionNode baseType; }; struct nodes::NamespacedIdentifier { diff --git a/lib/include/artichoke/Parser/Parser.hpp b/lib/include/artichoke/Parser/Parser.hpp index e69de29..4a52bed 100644 --- a/lib/include/artichoke/Parser/Parser.hpp +++ b/lib/include/artichoke/Parser/Parser.hpp @@ -0,0 +1,211 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#pragma once + +#include +#include + +namespace arti::lang { + + struct Parser { + Parser(std::string source) noexcept; + + Parser(std::string unitName, std::string source) noexcept; + + Parser(Parser &&) noexcept; + Parser &operator=(Parser &&) noexcept; + + Parser(const Parser &) noexcept = delete; + Parser &operator=(const Parser &) noexcept = delete; + + Expected parse(); + + Expected + consume(TokenV type, std::string_view message); + + Expected + matchAndConsume(TokenV type); + + Expected + match(TokenV type, std::size_t offset = 0); + + Expected> + parseTopLevelDeclaration(); + + Expected + parseImportDeclaration(); + + Expected + parseAliasDeclaration(); + + Expected + parseModuleDeclaration(); + + Expected + parseStructDeclaration(); + + Expected + parseEnumDeclaration(); + + Expected> + parseGenericParamsList(); + + Expected + parseGenericParam(); + + Expected> + parseStructMembersList(); + + Expected + parseStructMember(); + + Expected> + parseEnumMembersList(); + + Expected + parseEnumMember(); + + Expected + parseFunctionDeclaration(); + + Expected + parseNamespacedIdentifier(); + + Expected + parseType(); + + Expected> + parseTypeQualifiers(); + + Expected> + parseGenericArgumentsList(); + + Expected> + parseFunctionParamsList(); + + Expected + parseFunctionParam(); + + Expected + parseFunctionParamThis(); + + Expected + parseCodeBlock(); + + Expected> + parseStatement(); + + Expected + parseVariableStatement(); + + Expected + parseIfStatement(); + + Expected + parseElseStatement(); + + Expected + parseDeferStatement(); + + Expected + parseErrDeferStatement(); + + Expected + parseReturnStatement(); + + Expected + parseBreakStatement(); + + Expected + parseContinueStatement(); + + Expected + parseMatchStatement(); + + Expected + parseSwitchStatement(); + + Expected + parseForLoopStatement(); + + Expected + parseCForStatement(); + + Expected + parseRangeForStatement(); + + Expected + parseWhileStatement(); + + Expected + parseDoWhileStatement(); + + Expected + parseInfLoopStatement(); + + Expected + parseExpressionStatement(); + + Expected + parseExpression(std::uint16_t p = 0); + + Expected> + parsePrimaryExpression(); + + Expected + parsePrefixExpression(); + + Expected + parseInfixExpression(ast::ExpressionNode lhs); + + Expected + parsePostfixExpression(ast::ExpressionNode lhs); + + Expected + parseIdentifierExpression(); + + Expected + parseCharLiteral(); + + Expected + parseNullLiteral(); + + Expected + parseStringLiteral(); + + Expected + parseFloatLiteral(); + + Expected + parseIntegerLiteral(); + + Expected + parseBooleanLiteral(); + + private: + std::string unitName; + std::string sourceCode; + Tokenizer tokenizer; + }; + +} diff --git a/lib/include/artichoke/Parser/Pratt.hpp b/lib/include/artichoke/Parser/Pratt.hpp new file mode 100644 index 0000000..d24d29f --- /dev/null +++ b/lib/include/artichoke/Parser/Pratt.hpp @@ -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 . // +// // +//============================================================================// + +#include + +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); + +} diff --git a/lib/include/artichoke/Tokenizer/Token.hpp b/lib/include/artichoke/Tokenizer/Token.hpp index 5192d8a..d258958 100644 --- a/lib/include/artichoke/Tokenizer/Token.hpp +++ b/lib/include/artichoke/Tokenizer/Token.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Tokenizer/Tokenizer.hpp b/lib/include/artichoke/Tokenizer/Tokenizer.hpp index 33c5485..0bf5dda 100644 --- a/lib/include/artichoke/Tokenizer/Tokenizer.hpp +++ b/lib/include/artichoke/Tokenizer/Tokenizer.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -25,7 +47,11 @@ namespace arti::lang { Expected consume(std::size_t n = 1) noexcept; Expected peek(std::size_t n = 0) noexcept; - Expected peekExpect(std::size_t n, TokenV tokenType) noexcept; + Expected peekExpect( + TokenV tokenType, + std::string_view message = "", + std::size_t n = 0 + ) noexcept; bool finished() const noexcept; diff --git a/lib/include/artichoke/Tokenizer/TokenizerRange.hpp b/lib/include/artichoke/Tokenizer/TokenizerRange.hpp index 63891d5..bca3f63 100644 --- a/lib/include/artichoke/Tokenizer/TokenizerRange.hpp +++ b/lib/include/artichoke/Tokenizer/TokenizerRange.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Util/Demangle.hpp b/lib/include/artichoke/Util/Demangle.hpp index 20db504..bffe357 100644 --- a/lib/include/artichoke/Util/Demangle.hpp +++ b/lib/include/artichoke/Util/Demangle.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Util/Expected.hpp b/lib/include/artichoke/Util/Expected.hpp index 2c2a20b..5ab2933 100644 --- a/lib/include/artichoke/Util/Expected.hpp +++ b/lib/include/artichoke/Util/Expected.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include @@ -17,6 +39,10 @@ namespace arti::lang { ecInvalidCharacter, ecInvalidIndex, ecInvalidComment, + ecUnexpectedToken, + ecExpectedSemicolon, + ecImportInsideModule, + ecUnimplemented, }; struct Exception { @@ -71,6 +97,24 @@ namespace arti::lang { else if constexpr (code == ecInvalidComment) { return "Invalid comment found, missing '*/' end of comment"; } + else if constexpr (code == ecUnexpectedToken) { + return std::format( + "Found unexpected token '{}', expected {}", + std::forward(args)... + ); + } + else if constexpr (code == ecExpectedSemicolon) { + return std::format( + "Expected ';', got '{}'", + std::forward(args)... + ); + } + else if constexpr (code == ecImportInsideModule) { + return "Cannot use import statements inside a module declaration"; + } + else if constexpr (code == ecUnimplemented) { + return "Unimplemented"; + } else { return "Unknown error"; } diff --git a/lib/include/artichoke/Util/OverloadSet.hpp b/lib/include/artichoke/Util/OverloadSet.hpp index 5ad8bbb..3371304 100644 --- a/lib/include/artichoke/Util/OverloadSet.hpp +++ b/lib/include/artichoke/Util/OverloadSet.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once namespace arti::lang { diff --git a/lib/include/artichoke/Util/Strings.hpp b/lib/include/artichoke/Util/Strings.hpp index e011a69..cc18b4a 100644 --- a/lib/include/artichoke/Util/Strings.hpp +++ b/lib/include/artichoke/Util/Strings.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/include/artichoke/Util/TrieMap.hpp b/lib/include/artichoke/Util/TrieMap.hpp index c6cbe3c..a871ee2 100644 --- a/lib/include/artichoke/Util/TrieMap.hpp +++ b/lib/include/artichoke/Util/TrieMap.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include diff --git a/lib/src/Parser/AST/toDot.cpp b/lib/src/Parser/AST/toDot.cpp index 4be334a..ed8dfd8 100644 --- a/lib/src/Parser/AST/toDot.cpp +++ b/lib/src/Parser/AST/toDot.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include @@ -59,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(); } @@ -133,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 &); @@ -142,24 +178,23 @@ 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 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 &); 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 ScopeAccessExprNode &, 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 &); std::string emit(const SliceLengthExprNode &, GraphBuilder &); @@ -188,6 +223,7 @@ namespace arti::lang::ast { std::string emit(const ElseBranchNode &, GraphBuilder &); std::string emit(const DeferableNode &, GraphBuilder &); std::string emit(const PreLoopStmtNode &, GraphBuilder &); + std::string emit(const TypeExprNode &, GraphBuilder &); // Helpers for making leaf nodes with backticked values inline std::string makeLeaf(GraphBuilder &g, std::string_view value) { @@ -456,15 +492,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, @@ -478,30 +515,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); } @@ -530,19 +554,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) { @@ -553,7 +566,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"); @@ -563,15 +576,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) { @@ -582,7 +587,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) { @@ -592,10 +597,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); }, }; @@ -607,8 +612,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); @@ -616,8 +621,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); @@ -676,30 +681,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 ScopeAccessExprNode &node, GraphBuilder &g) { - auto id = g.makeNode("ScopeAccessExpression"); - g.addEdge(id, emit(node->scope, g), "Object"); - if (! node->genericParams.empty()) { + std::string emit(const ModuleAccessExprNode &node, GraphBuilder &g) { + 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; } @@ -739,23 +750,24 @@ 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 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); }, [&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 ScopeAccessExprNode &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); }, + [&g](const TypeExprNode &n) { return emit(n, g); }, }; return std::visit(visitor, node); } @@ -980,6 +992,7 @@ namespace arti::lang::ast { [&g](const DoWhileStmtNode &n) { return emit(n, g); }, [&g](const InfLoopStmtNode &n) { return emit(n, g); }, [&g](const ExpressionStmtNode &n) { return emit(n, g); }, + [&g](const CodeBlockStmtNode &n) { return emit(n, g); }, }; return std::visit(visitor, node); } @@ -1008,6 +1021,12 @@ namespace arti::lang::ast { return std::visit(visitor, node); } + std::string emit(const TypeExprNode &node, GraphBuilder &g) { + auto id = g.makeNode("TypeExpression"); + g.addEdge(id, emit(node->type, g), "Type"); + return id; + } + } // namespace // Public API diff --git a/lib/src/Parser/AST/toString.cpp b/lib/src/Parser/AST/toString.cpp index c66949e..3874db7 100644 --- a/lib/src/Parser/AST/toString.cpp +++ b/lib/src/Parser/AST/toString.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include @@ -23,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); @@ -32,24 +53,23 @@ 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 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); 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 ScopeAccessExprNode &, 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); std::string toString(const SliceLengthExprNode &, std::string); @@ -78,8 +98,9 @@ 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(const TypeExprNode &, std::string); + std::string toString(PrefixOperator op); + std::string toString(InfixOperator op); std::string toString(CompoundAssignOperator op); const auto StrTreeNoNode = "│ "; @@ -104,6 +125,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 void appendGroupVec( std::stringstream &ss, @@ -507,12 +539,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(); } @@ -524,7 +566,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, @@ -538,17 +582,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(); } @@ -569,9 +606,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); @@ -608,30 +642,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; @@ -651,7 +664,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); @@ -660,15 +673,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); @@ -676,7 +681,7 @@ namespace arti::lang::ast { } std::string toString( - const StructLtrlPositionalInitializerNode &node, + const ObjectLtrlPositionalInitializerNode &node, std::string prefix ) { std::stringstream ss; @@ -686,12 +691,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); }, }; @@ -703,9 +708,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( @@ -719,9 +724,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( @@ -815,37 +820,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 ScopeAccessExprNode &node, std::string prefix) { + 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(); } @@ -912,19 +926,16 @@ 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 { 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 { @@ -945,10 +956,10 @@ 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 ScopeAccessExprNode &node) -> std::string { + [padding](const ModuleAccessExprNode &node) -> std::string { return toString(node, padding); }, [padding](const SliceCreationExprNode &node) -> std::string { @@ -963,6 +974,12 @@ namespace arti::lang::ast { [padding](const ReflectionExprNode &node) -> std::string { return toString(node, padding); }, + [padding](const GenericExprNode &node) -> std::string { + return toString(node, padding); + }, + [padding](const TypeExprNode &node) -> std::string { + return toString(node, padding); + }, }; return std::visit(visitor, node); @@ -1397,6 +1414,9 @@ namespace arti::lang::ast { [padding](const ExpressionStmtNode &node) -> std::string { return toString(node, padding); }, + [padding](const CodeBlockStmtNode &node) -> std::string { + return toString(node, padding); + } }; return std::visit(visitor, node); @@ -1441,46 +1461,67 @@ namespace arti::lang::ast { return std::visit(visitor, node); } - std::string toString(UnaryOperator op) { - using enum UnaryOperator; + std::string toString(const TypeExprNode &node, std::string prefix) { + std::stringstream ss; + ss << "TypeÉxpression"; + appendItem(ss, prefix, node->type, true); + return ss.str(); + } + + 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(); } diff --git a/lib/src/Parser/Declarations.cpp b/lib/src/Parser/Declarations.cpp new file mode 100644 index 0000000..ba20620 --- /dev/null +++ b/lib/src/Parser/Declarations.cpp @@ -0,0 +1,822 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +namespace arti::lang { + + Expected> + 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( + 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( + 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 Parser::parseImportDeclaration() { + auto node = ast::MakeNode(); + + 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( + star->line, + star->column, + toString(*star), + "identifier or '*'" + ); + } + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + return node; + } + + Expected Parser::parseAliasDeclaration() { + auto node = ast::MakeNode(); + + 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 Parser::parseModuleDeclaration() { + auto node = ast::MakeNode(); + auto decl = ast::Optional{}; + 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(*decl)) { + node->childModules.push_back( + std::get(std::move(*decl)) + ); + } + else if (std::holds_alternative(*decl)) { + node->innerDeclarations.push_back( + std::get(std::move(*decl)) + ); + } + else if (std::holds_alternative(*decl)) { + node->innerDeclarations.push_back( + std::get(std::move(*decl)) + ); + } + else if (std::holds_alternative(*decl)) { + node->innerDeclarations.push_back( + std::get(std::move(*decl)) + ); + } + else if (std::holds_alternative(*decl)) { + node->aliasDeclarations.push_back( + std::get(std::move(*decl)) + ); + } + else if (std::holds_alternative(*decl)) { + auto importDecl = std::get(std::move(*decl)); + + return langException( + importDecl->location.line, + importDecl->location.column + ); + } + } + } + + if (auto rsquirly = consume(TokenV::opRSquirly, "'{'"); ! rsquirly) { + return Unexpected<>{ std::move(rsquirly).error() }; + } + + return node; + } + + Expected Parser::parseStructDeclaration() { + auto node = ast::MakeNode(); + + 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 Parser::parseEnumDeclaration() { + auto node = ast::MakeNode(); + + 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> + Parser::parseGenericParamsList() { + auto paramsList = ast::Vector{}; + + 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( + 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( + peekToken->line, + peekToken->column, + toString(*peekToken), + "',' or '>'" + ); + } + else { + keepParsing = false; + } + } + } + } + + return paramsList; + } + + Expected Parser::parseGenericParam() { + auto node = ast::MakeNode(); + + 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> + Parser::parseStructMembersList() { + auto membersList = ast::Vector{}; + + if (auto comma = tokenizer.peek(); + comma and comma->value == TokenV::opComma) { + return langException( + 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( + 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 Parser::parseStructMember() { + auto node = ast::MakeNode(); + + 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> Parser::parseEnumMembersList() { + auto membersList = ast::Vector{}; + + if (auto comma = tokenizer.peek(); + comma and comma->value == TokenV::opComma) { + return langException( + 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( + 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 Parser::parseEnumMember() { + auto node = ast::MakeNode(); + + 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 Parser::parseFunctionDeclaration() { + auto node = ast::MakeNode(); + + 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> + Parser::parseFunctionParamsList() { + auto params = ast::Vector{}; + + if (auto comma = tokenizer.peek(); + comma and comma->value == TokenV::opComma) { + return langException( + 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 Parser::parseFunctionParamThis() { + auto node = ast::MakeNode(); + + 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 Parser::parseFunctionParam() { + auto node = ast::MakeNode(); + + 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 diff --git a/lib/src/Parser/Expressions.cpp b/lib/src/Parser/Expressions.cpp new file mode 100644 index 0000000..e696667 --- /dev/null +++ b/lib/src/Parser/Expressions.cpp @@ -0,0 +1,817 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include +#include + +namespace arti::lang { + + Expected + Parser::parseExpression(std::uint16_t minBindingPower) { + auto peekToken = tokenizer.peek(); + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + bool keepParsing = true; + ast::Optional 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 (peekToken->value == TokenV::opLBracket) { + if (auto close = match(TokenV::opRBracket, 1); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (! close.value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "']'" + ); + } + + if (auto type = parseType(); ! type) { + return Unexpected<>{ std::move(type).error() }; + } + else { + auto node = ast::MakeNode(); + node->location = type.value()->location; + node->type = std::move(type).value(); + lhs = std::move(node); + } + } + 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( + 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( + 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> + 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(); + + 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(); + + 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 + 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(); + + node->location = { + .line = peekToken->line, + .column = peekToken->column + }; + + node->op = op; + node->right = std::move(rhs).value(); + + return node; + } + + Expected + Parser::parseInfixExpression(ast::ExpressionNode lhs) { + auto peekToken = tokenizer.peek(); + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + std::ignore = tokenizer.consume(); + + auto op = pratt::getInfixOperator(peekToken->value); + auto [lbp, rbp] = pratt::infixBindingPower(op); + + if (op == ast::InfixOperator::ModuleAccess) { + if (auto isGeneric = match(TokenV::opLt); ! isGeneric) { + return Unexpected<>{ std::move(isGeneric).error() }; + } + else if (isGeneric.value()) { + auto node = ast::MakeNode(); + + node->location = { + .line = peekToken->line, + .column = peekToken->column + }; + + if (auto args = parseGenericArgumentsList(); ! args) { + return Unexpected<>{ std::move(args).error() }; + } + else { + node->typeNode = std::move(lhs); + node->genericArgs = std::move(args).value(); + } + + return node; + } + } + + auto rhs = parseExpression(rbp); + + if (! rhs) { + return Unexpected<>{ std::move(rhs).error() }; + } + + /* TODO: MemberAccess and PointerMemberAccess do not use their respective + * nodes types yet */ + if (op == ast::InfixOperator::Assignment) { + auto node = ast::MakeNode(); + + node->location = { + .line = peekToken->line, + .column = peekToken->column + }; + + node->left = std::move(lhs); + node->right = std::move(rhs).value(); + + return node; + } + else if (op == ast::InfixOperator::ModuleAccess) { + auto node = ast::MakeNode(); + + 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(); + + 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(); + + 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(); + + 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(); + + 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 + 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 node = std::nullopt; + + if (op == ast::PostfixOperator::FunctionCall) { + auto newNode = ast::MakeNode(); + + 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( + ntok->line, + ntok->column, + toString(*ntok), + "',' or ')'" + ); + } + else { + stillParams = false; + } + } + } + } + + if (auto close = consume(TokenV::opRParen, "')'"); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + + newNode->callee = std::move(lhs); + + node = std::move(newNode); + } + else if (op == ast::PostfixOperator::ObjectLiteral) { + auto newNode = ast::MakeNode(); + + 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(); + + 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(); + + 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( + 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(); + + 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( + 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) { + bool isSlice = false; + bool skipSliceEnd = false; + bool skipSliceStart = false; + + if (auto skipLeft = matchAndConsume(TokenV::opColon); ! skipLeft) { + return Unexpected<>{ std::move(skipLeft).error() }; + } + else if (skipLeft.value()) { + isSlice = true; + skipSliceStart = true; + + if (auto close = matchAndConsume(TokenV::opRBracket); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + skipSliceEnd = true; + } + } + + auto idxExpr = ast::Optional{}; + + if (! skipSliceStart) { + 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()) { + isSlice = true; + + if (auto close = matchAndConsume(TokenV::opRBracket); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + skipSliceEnd = true; + } + } + + idxExpr = std::move(idx).value(); + } + + if (isSlice) { + auto newNode = ast::MakeNode(); + + newNode->location = { + .line = peekToken->line, + .column = peekToken->column + }; + + if (! skipSliceStart) { + newNode->start = std::move(idxExpr).value(); + } + + if (! skipSliceEnd) { + 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(); + + newNode->location = { + .line = peekToken->line, + .column = peekToken->column + }; + + newNode->index = std::move(idxExpr).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(); + + 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(); + + 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(); + + 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(); + + 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( + peekToken->line, + peekToken->column, + toString(*peekToken), + "postfix operator, i.e. " + "any of ( /*TODO*/ )" + ); + } + + return std::move(node).value(); + } + + +} // namespace arti::lang diff --git a/lib/src/Parser/Literals.cpp b/lib/src/Parser/Literals.cpp new file mode 100644 index 0000000..ebb668a --- /dev/null +++ b/lib/src/Parser/Literals.cpp @@ -0,0 +1,178 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +namespace arti::lang { + + Expected + Parser::parseCharLiteral() { + auto node = ast::MakeNode(); + + 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(ltrl->strValue[1]); + } + + return node; + } + + Expected + Parser::parseNullLiteral() { + auto node = ast::MakeNode(); + + 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 + Parser::parseStringLiteral() { + auto node = ast::MakeNode(); + + 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 + Parser::parseFloatLiteral() { + auto node = ast::MakeNode(); + + 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 + Parser::parseIntegerLiteral() { + auto node = ast::MakeNode(); + + 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 + Parser::parseBooleanLiteral() { + auto node = ast::MakeNode(); + + 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( + peekToken->line, + peekToken->column, + toString(*peekToken), + "boolean literal, i.e. " + "any of ( true, false )" + ); + } + + std::ignore = tokenizer.consume(); + + return node; + } + + Expected + Parser::parseIdentifierExpression() { + auto node = ast::MakeNode(); + + 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 diff --git a/lib/src/Parser/Operators.cpp b/lib/src/Parser/Operators.cpp new file mode 100644 index 0000000..ab1416c --- /dev/null +++ b/lib/src/Parser/Operators.cpp @@ -0,0 +1,292 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +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 opLBracket: + 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: + case opLSquirly: + 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; + case opLSquirly: + return ObjectLiteral; + 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; + } + } + +} + diff --git a/lib/src/Parser/Parser.cpp b/lib/src/Parser/Parser.cpp index e69de29..a701eb8 100644 --- a/lib/src/Parser/Parser.cpp +++ b/lib/src/Parser/Parser.cpp @@ -0,0 +1,105 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +namespace arti::lang { + + Parser::Parser(std::string source) noexcept + : unitName{} + , sourceCode{ source } + , tokenizer{ source } { } + + Parser::Parser(std::string unitName, std::string source) noexcept + : unitName{ unitName } + , sourceCode{ source } + , tokenizer{ source } { } + + Expected Parser::parse() { + auto unit = ast::MakeNode(); + auto decl = ast::Optional{}; + bool keepParsing = true; + + unit->unitName = this->unitName; + + 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; + } + else { + unit->declarations.push_back(std::move(decl).value()); + } + } + } + + if (auto eof = consume(TokenV::tkEOF, "end of compilation unit"); ! eof) { + return Unexpected<>{ std::move(eof).error() }; + } + + return unit; + } + + Expected Parser::consume(TokenV type, std::string_view message) { + auto peeked = tokenizer.peekExpect(type, message); + + if (! peeked) { + return Unexpected<>{ std::move(peeked).error() }; + } + + std::ignore = tokenizer.consume(); + + return peeked; + } + + Expected Parser::matchAndConsume(TokenV type) { + auto peeked = tokenizer.peek(); + + if (! peeked) { + return Unexpected<>{ std::move(peeked).error() }; + } + + if (peeked->value != type) { + return false; + } + + std::ignore = tokenizer.consume(); + + return true; + } + + Expected Parser::match(TokenV type, std::size_t offset) { + auto peeked = tokenizer.peek(offset); + + if (! peeked) { + return Unexpected<>{ std::move(peeked).error() }; + } + + return (peeked->value == type); + } + +} // namespace arti::lang diff --git a/lib/src/Parser/Pratt.cpp b/lib/src/Parser/Pratt.cpp new file mode 100644 index 0000000..b71bcf7 --- /dev/null +++ b/lib/src/Parser/Pratt.cpp @@ -0,0 +1,115 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +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: return { 23, 24 }; + 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: + case ast::PostfixOperator::ObjectLiteral: + return 19; + default: + return 0; + } + } + +} // namespace arti::lang::pratt diff --git a/lib/src/Parser/Statements.cpp b/lib/src/Parser/Statements.cpp new file mode 100644 index 0000000..b1405e2 --- /dev/null +++ b/lib/src/Parser/Statements.cpp @@ -0,0 +1,1327 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include +#include + +#include + +namespace arti::lang { + + Expected Parser::parseCodeBlock() { + auto node = ast::MakeNode(); + + auto stmt = ast::Optional{}; + bool keepParsing = true; + + if (auto lsquirly = consume(TokenV::opLSquirly, "'{'"); ! lsquirly) { + return Unexpected<>{ std::move(lsquirly).error() }; + } + + if (auto close = match(TokenV::opRSquirly); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + keepParsing = false; + } + + while (keepParsing) { + if (auto ok = parseStatement(); ! ok) { + return Unexpected<>{ std::move(ok).error() }; + } + else { + stmt = std::move(ok).value(); + + if (! stmt.has_value()) { + keepParsing = false; + } + else { + node->statements.push_back(std::move(stmt).value()); + } + } + + if (auto close = match(TokenV::opRSquirly); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + keepParsing = false; + } + } + + if (auto rsquirly = consume(TokenV::opRSquirly, "'}'"); ! rsquirly) { + return Unexpected<>{ std::move(rsquirly).error() }; + } + + return node; + } + + Expected> + Parser::parseStatement() { + ast::Optional label; + + auto peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + if (peekToken->value == TokenV::tkIdentifier) { + if (auto isLabel = match(TokenV::opLabel, 1); ! isLabel) { + return Unexpected<>{ std::move(isLabel).error() }; + } + else if (isLabel.value()) { + auto labelName = consume(TokenV::tkIdentifier, "identifier").value(); + std::ignore = consume(TokenV::opLabel, "':='"); + + peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + label = labelName.strValue; + } + } + + if (peekToken->value == TokenV::kwLet || + peekToken->value == TokenV::kwDef) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseVariableStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwIf) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseIfStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwDefer) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseDeferStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwErrDefer) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseErrDeferStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwReturn) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseReturnStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwBreak) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseBreakStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwContinue) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseContinueStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwMatch) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseMatchStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwSwitch) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseSwitchStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwFor) { + if (auto stmt = parseForLoopStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + if (label.has_value()) { + std::visit( + OverloadSet( + [&label](ast::CForStmtNode &stmt) { + stmt->label = label.value(); + }, + [&label](ast::RangeForStmtNode &stmt) { + stmt->label = label.value(); + }, + [](auto &) {} + ), + stmt.value() + ); + } + + return std::move(stmt).value(); + } + } + else if (peekToken->value == TokenV::kwWhile) { + if (auto stmt = parseWhileStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + if (label.has_value()) { + stmt.value()->label = label.value(); + } + + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwDo) { + if (auto stmt = parseDoWhileStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + if (label.has_value()) { + stmt.value()->label = label.value(); + } + + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::kwLoop) { + if (auto stmt = parseInfLoopStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + if (label.has_value()) { + stmt.value()->label = label.value(); + } + + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else if (peekToken->value == TokenV::opLSquirly) { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseCodeBlock(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + else { + if (label.has_value()) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "loop keyword, i.e. any of ( for, while, do, loop )" + ); + } + + if (auto stmt = parseExpressionStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + return ast::StatementNode{ std::move(stmt).value() }; + } + } + + return std::nullopt; + } + + Expected + Parser::parseVariableStatement() { + auto node = ast::MakeNode(); + auto peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + node->location = { + .line = peekToken->line, + .column = peekToken->column, + }; + + if (peekToken->value == TokenV::kwLet) { + node->mutability = ast::Mutability::Mutable; + } + else if (peekToken->value == TokenV::kwDef) { + node->mutability = ast::Mutability::Constant; + } + else { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "variable declaration keyword, i.e. any of ( let, def )" + ); + } + std::ignore = tokenizer.consume(); + + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + node->name = ident->strValue; + } + + if (auto colon = matchAndConsume(TokenV::opColon); ! colon) { + return Unexpected<>{ std::move(colon).error() }; + } + else if (colon.value()) { + if (auto type = parseType(); ! type) { + return Unexpected<>{ std::move(type).error() }; + } + else { + node->type = std::move(type).value(); + } + } + + if (auto assign = matchAndConsume(TokenV::opAssign); ! assign) { + return Unexpected<>{ std::move(assign).error() }; + } + else if(assign.value()) { + if (auto expr = parseExpression(); ! expr) { + return Unexpected<>{ std::move(expr).error() }; + } + else { + node->initializer = std::move(expr).value(); + } + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + if (node->type == std::nullopt and node->initializer == std::nullopt) { + /* TODO: Is this the correct error code for this case? */ + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "variable declaration type or initializer" + ); + } + + return node; + } + + Expected + Parser::parseIfStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwIf, "'if' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->condition = std::move(condition).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto unwrap = matchAndConsume(TokenV::opOr); ! unwrap) { + return Unexpected<>{ std::move(unwrap).error() }; + } + else if (unwrap.value()) { + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + node->unwrappedVar = ident->strValue; + } + + if (auto closeUnwrap = consume(TokenV::opOr, "'|'"); ! closeUnwrap) { + return Unexpected<>{ std::move(closeUnwrap).error() }; + } + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + if (auto hasElse = match(TokenV::kwElse); ! hasElse) { + return Unexpected<>{ std::move(hasElse).error() }; + } + else if (hasElse.value()) { + if (auto elseStmt = parseElseStatement(); ! elseStmt) { + return Unexpected<>{ std::move(elseStmt).error() }; + } + else { + node->elseBranch = std::move(elseStmt).value(); + } + } + + return node; + } + + Expected + Parser::parseElseStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwElse, "'else' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto tailIf = match(TokenV::kwIf); ! tailIf) { + return Unexpected<>{ std::move(tailIf).error() }; + } + else if (tailIf.value()) { + node.reset(); + + if (auto ifStmt = parseIfStatement(); ! ifStmt) { + return Unexpected<>{ std::move(ifStmt).error() }; + } + else { + return std::move(ifStmt).value(); + } + } + + if (auto unwrap = matchAndConsume(TokenV::opOr); ! unwrap) { + return Unexpected<>{ std::move(unwrap).error() }; + } + else if (unwrap.value()) { + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + node->unwrappedVar = ident->strValue; + } + + if (auto closeUnwrap = consume(TokenV::opOr, "'|'"); ! closeUnwrap) { + return Unexpected<>{ std::move(closeUnwrap).error() }; + } + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + return node; + } + + Expected + Parser::parseDeferStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwDefer, "'defer' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto isBlock = match(TokenV::opLSquirly); ! isBlock) { + return Unexpected<>{ std::move(isBlock).error() }; + } + else if (isBlock.value()) { + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + } + else { + auto peekToken = tokenizer.peek(); + + if (not peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + if (auto stmt = parseExpressionStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + node->body = std::move(stmt).value(); + } + } + + return node; + } + + Expected + Parser::parseErrDeferStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwErrDefer, "'errdefer' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto isBlock = match(TokenV::opLSquirly); ! isBlock) { + return Unexpected<>{ std::move(isBlock).error() }; + } + else if (isBlock.value()) { + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + } + else { + auto peekToken = tokenizer.peek(); + + if (not peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + if (auto stmt = parseExpressionStatement(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + node->body = std::move(stmt).value(); + } + } + + return node; + } + + Expected + Parser::parseReturnStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwReturn, "'return' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto skipExpr = match(TokenV::opSemicolon); ! skipExpr) { + return Unexpected<>{ std::move(skipExpr).error() }; + } + else if (! skipExpr.value()) { + if (auto stmt = parseExpression(); ! stmt) { + return Unexpected<>{ std::move(stmt).error() }; + } + else { + node->value = std::move(stmt).value(); + } + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + return node; + } + + Expected + Parser::parseBreakStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwBreak, "'break' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->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 { + node->label = ident->strValue; + } + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + return node; + } + + Expected + Parser::parseContinueStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwContinue, "'continue' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->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 { + node->label = ident->strValue; + } + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + return node; + } + + Expected + Parser::parseMatchStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwMatch, "'match' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->value = std::move(condition).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto lSquirly = consume(TokenV::opLSquirly, "'{'"); ! lSquirly) { + return Unexpected<>{ std::move(lSquirly).error() }; + } + + bool keepParsing = true; + + while (keepParsing) { + if (auto isDefault = match(TokenV::kwUnderscore); ! isDefault) { + return Unexpected<>{ std::move(isDefault).error() }; + } + else if (isDefault.value()) { + keepParsing = false; + + if (auto under = consume(TokenV::kwUnderscore, "'_' keyword"); + ! under) { + return Unexpected<>{ std::move(under).error() }; + } + + if (auto arrow = consume(TokenV::opArrow, "'->'"); ! arrow) { + return Unexpected<>{ std::move(arrow).error() }; + } + + if (auto defCase = parseCodeBlock(); ! defCase) { + return Unexpected<>{ std::move(defCase).error() }; + } + else { + node->defaultCase = std::move(defCase).value(); + } + } + else { + auto curCase = ast::MakeNode(); + + if (auto type = parseType(); ! type) { + return Unexpected<>{ std::move(type).error() }; + } + else { + curCase->location = type.value()->location; + curCase->matchType = std::move(type).value(); + } + + if (auto unwrap = matchAndConsume(TokenV::opOr); ! unwrap) { + return Unexpected<>{ std::move(unwrap).error() }; + } + else if (unwrap.value()) { + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); + ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + curCase->unwrappedVar = ident->strValue; + } + + if (auto closeUnwrap = consume(TokenV::opOr, "'|'"); ! closeUnwrap) { + return Unexpected<>{ std::move(closeUnwrap).error() }; + } + } + + if (auto arrow = consume(TokenV::opArrow, "'->'"); ! arrow) { + return Unexpected<>{ std::move(arrow).error() }; + } + + if (auto caseBody = parseCodeBlock(); ! caseBody) { + return Unexpected<>{ std::move(caseBody).error() }; + } + else { + curCase->body = std::move(caseBody).value(); + } + + node->matchCases.push_back(std::move(curCase)); + } + + if (auto close = match(TokenV::opRSquirly); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + keepParsing = false; + } + } + + if (auto rSquirly = consume(TokenV::opRSquirly, "'}'"); ! rSquirly) { + return Unexpected<>{ std::move(rSquirly).error() }; + } + + return node; + } + + Expected + Parser::parseSwitchStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwSwitch, "'switch' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->value = std::move(condition).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto lSquirly = consume(TokenV::opLSquirly, "'{'"); ! lSquirly) { + return Unexpected<>{ std::move(lSquirly).error() }; + } + + uint16_t limit = + pratt::infixBindingPower(ast::InfixOperator::PointerMemberAccess).right; + + bool keepParsing = true; + + while (keepParsing) { + if (auto isDefault = match(TokenV::kwUnderscore); ! isDefault) { + return Unexpected<>{ std::move(isDefault).error() }; + } + else if (isDefault.value()) { + keepParsing = false; + + if (auto under = consume(TokenV::kwUnderscore, "'_' keyword"); + ! under) { + return Unexpected<>{ std::move(under).error() }; + } + + if (auto arrow = consume(TokenV::opArrow, "'->'"); ! arrow) { + return Unexpected<>{ std::move(arrow).error() }; + } + + if (auto defCase = parseCodeBlock(); ! defCase) { + return Unexpected<>{ std::move(defCase).error() }; + } + else { + node->defaultCase = std::move(defCase).value(); + } + } + else { + auto curCase = ast::MakeNode(); + + if (auto expr = parseExpression(limit); ! expr) { + return Unexpected<>{ std::move(expr).error() }; + } + else { + curCase->location = std::visit( + [](const auto &exprNode) -> ast::SourceLocation { + return exprNode->location; + }, + expr.value() + ); + curCase->matchExpr = std::move(expr).value(); + } + + if (auto arrow = consume(TokenV::opArrow, "'->'"); ! arrow) { + return Unexpected<>{ std::move(arrow).error() }; + } + + if (auto caseBody = parseCodeBlock(); ! caseBody) { + return Unexpected<>{ std::move(caseBody).error() }; + } + else { + curCase->body = std::move(caseBody).value(); + } + + node->switchCases.push_back(std::move(curCase)); + } + + if (auto close = match(TokenV::opRSquirly); ! close) { + return Unexpected<>{ std::move(close).error() }; + } + else if (close.value()) { + keepParsing = false; + } + } + + if (auto rSquirly = consume(TokenV::opRSquirly, "'}'"); ! rSquirly) { + return Unexpected<>{ std::move(rSquirly).error() }; + } + + return node; + } + + Expected + Parser::parseForLoopStatement() { + bool isRange = false; + + auto isLet = match(TokenV::kwLet, 2); + if (! isLet) { + return Unexpected<>{ std::move(isLet).error() }; + } + + auto isDef = match(TokenV::kwDef, 2); + if (! isDef) { + return Unexpected<>{ std::move(isDef).error() }; + } + + if (isLet.value() || isDef.value()) { + auto hasRangeOp = match(TokenV::opLabel, 4); + + if (! hasRangeOp) { + return Unexpected<>{ std::move(hasRangeOp).error() }; + } + + isRange = hasRangeOp.value(); + } + + if (isRange) { + return parseRangeForStatement().transform( + [](auto &&val) -> ast::StatementNode { + return std::forward(val); + } + ); + } + else { + return parseCForStatement().transform( + [](auto &&val) -> ast::StatementNode { + return std::forward(val); + } + ); + } + } + + Expected + Parser::parseCForStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwFor, "'for' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto skipPre = matchAndConsume(TokenV::opSemicolon); ! skipPre) { + return Unexpected<>{ std::move(skipPre).error() }; + } + else if (not skipPre.value()) { + auto isLet = match(TokenV::kwLet); + if (! isLet) { + return Unexpected<>{ std::move(isLet).error() }; + } + + auto isDef = match(TokenV::kwDef); + if (! isDef) { + return Unexpected<>{ std::move(isDef).error() }; + } + + if (isLet.value() || isDef.value()) { + if (auto preStmt = parseVariableStatement(); ! preStmt) { + return Unexpected<>{ std::move(preStmt).error() }; + } + else { + node->preLoop = std::move(preStmt).value(); + } + } + else { + if (auto preStmt = parseExpressionStatement(); ! preStmt) { + return Unexpected<>{ std::move(preStmt).error() }; + } + else { + node->preLoop = std::move(preStmt).value(); + } + } + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->condition = std::move(condition).value(); + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + if (auto skipPost = match(TokenV::opRParen); ! skipPost) { + return Unexpected<>{ std::move(skipPost).error() }; + } + else if (not skipPost.value()) { + if (auto postStmt = parseExpression(); ! postStmt) { + return Unexpected<>{ std::move(postStmt).error() }; + } + else { + node->postLoop = std::move(postStmt).value(); + } + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + return node; + } + + Expected + Parser::parseRangeForStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwFor, "'for' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto isLet = matchAndConsume(TokenV::kwLet); ! isLet) { + return Unexpected<>{ std::move(isLet).error() }; + } + else if (isLet.value()) { + node->varMutability = ast::Mutability::Mutable; + } + else if (auto isDef = matchAndConsume(TokenV::kwDef); ! isDef) { + return Unexpected<>{ std::move(isDef).error() }; + } + else if (isDef.value()) { + node->varMutability = ast::Mutability::Constant; + } + else { + auto peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "variable declarator, i.e. any of ( let, def )" + ); + } + + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + node->varName = ident->strValue; + } + + if (auto assign = consume(TokenV::opLabel, "':='"); ! assign) { + return Unexpected<>{ std::move(assign).error() }; + } + + if (auto range = parseExpression(); ! range) { + return Unexpected<>{ std::move(range).error() }; + } + else { + node->range = std::move(range).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + return node; + } + + Expected + Parser::parseWhileStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwWhile, "'while' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column + }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->condition = std::move(condition).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto unwrap = matchAndConsume(TokenV::opOr); ! unwrap) { + return Unexpected<>{ std::move(unwrap).error() }; + } + else if (unwrap.value()) { + if (auto ident = consume(TokenV::tkIdentifier, "identifier"); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else { + node->unwrappedVar = ident->strValue; + } + + if (auto closeUnwrap = consume(TokenV::opOr, "'|'"); ! closeUnwrap) { + return Unexpected<>{ std::move(closeUnwrap).error() }; + } + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + if (auto hasElse = match(TokenV::kwElse); ! hasElse) { + return Unexpected<>{ std::move(hasElse).error() }; + } + else if (hasElse.value()) { + if (auto elseStmt = parseElseStatement(); ! elseStmt) { + return Unexpected<>{ std::move(elseStmt).error() }; + } + else { + node->elseBranch = std::move(elseStmt).value(); + } + } + + return node; + } + + Expected + Parser::parseDoWhileStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwDo, "'do' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column + }; + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + if (auto kw = consume(TokenV::kwWhile, "'while' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + + if (auto lParen = consume(TokenV::opLParen, "'('"); ! lParen) { + return Unexpected<>{ std::move(lParen).error() }; + } + + if (auto condition = parseExpression(); ! condition) { + return Unexpected<>{ std::move(condition).error() }; + } + else { + node->condition = std::move(condition).value(); + } + + if (auto rParen = consume(TokenV::opRParen, "')'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + if (auto rParen = consume(TokenV::opSemicolon, "';'"); ! rParen) { + return Unexpected<>{ std::move(rParen).error() }; + } + + return node; + } + + Expected + Parser::parseInfLoopStatement() { + auto node = ast::MakeNode(); + + if (auto kw = consume(TokenV::kwLoop, "'loop' keyword"); ! kw) { + return Unexpected<>{ std::move(kw).error() }; + } + else { + node->location = { + .line = kw->line, + .column = kw->column, + }; + } + + if (auto body = parseCodeBlock(); ! body) { + return Unexpected<>{ std::move(body).error() }; + } + else { + node->body = std::move(body).value(); + } + + return node; + } + + Expected + Parser::parseExpressionStatement() { + auto node = ast::MakeNode(); + + if (auto expr = parseExpression(); ! expr) { + return Unexpected<>{ std::move(expr).error() }; + } + else { + node->location = std::visit( + [](const auto &exprNode) -> ast::SourceLocation { + return exprNode->location; + }, + expr.value() + ); + node->expression = std::move(expr).value(); + } + + if (auto semicolon = consume(TokenV::opSemicolon, "';'"); ! semicolon) { + return Unexpected<>{ std::move(semicolon).error() }; + } + + return node; + } + +} // namespace arti::lang diff --git a/lib/src/Parser/Types.cpp b/lib/src/Parser/Types.cpp new file mode 100644 index 0000000..ec18e98 --- /dev/null +++ b/lib/src/Parser/Types.cpp @@ -0,0 +1,301 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + +#include + +namespace arti::lang { + + Expected Parser::parseNamespacedIdentifier() { + auto node = ast::MakeNode(); + + bool keepParsing = true; + + while (keepParsing) { + 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 { + node->location = { .line = ident->line, .column = ident->column }; + node->identParts.emplace_back(ident->strValue); + } + } + else { + return node; + } + + if (auto access = match(TokenV::opAccess); ! access) { + return Unexpected<>{ std::move(access).error() }; + } + else if (access.value()) { + if (auto ident = match(TokenV::tkIdentifier, 1); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else if (not ident.value()) { + keepParsing = false; + } + else { + if (auto colon = consume(TokenV::opAccess, "':'"); ! colon) { + return Unexpected<>{ std::move(colon).error() }; + } + } + } + else { + keepParsing = false; + } + } + + return node; + } + + Expected Parser::parseType() { + auto node = ast::MakeNode(); + + if (auto nextToken = tokenizer.peek(); ! nextToken) { + return Unexpected<>{ std::move(nextToken).error() }; + } + else { + node->location = { .line = nextToken->line, .column = nextToken->column }; + } + + if (auto ident = match(TokenV::tkIdentifier); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else if (not ident.value()) { + if (auto qualifiers = parseTypeQualifiers(); ! qualifiers) { + return Unexpected<>{ std::move(qualifiers).error() }; + } + else { + node->qualifiers = std::move(qualifiers).value(); + } + } + + if (auto ident = match(TokenV::tkIdentifier); ! ident) { + return Unexpected<>{ std::move(ident).error() }; + } + else if (not ident.value()) { + auto peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "identifier type name" + ); + } + + 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(); + + newNode->location = { + .line = ident->line, + .column = ident->column + }; + newNode->typeName = ident->strValue; + + 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 { + std::ignore = tokenizer.consume(); + + 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(); + + newNode->location = { + .line = ident->line, + .column = ident->column + }; + newNode->typeName = ident->strValue; + + 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); + } + } + } + + node->typeNodes.emplace_back(std::move(currentNode)); + } + + return node; + } + + Expected> Parser::parseTypeQualifiers() { + auto qualifs = ast::Vector{}; + + auto peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + enum { None, AfterOptional, AfterMutable } state = None; + + while (true) { + switch (peekToken->value) { + using enum TokenV; + + case opStar: + qualifs.push_back(ast::TypeQualifier::Pointer); + state = None; + break; + case opMut: + if (state == AfterMutable) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "non mutable type qualifier, i.e. any of ( *, ?, [] )" + ); + } + qualifs.push_back(ast::TypeQualifier::Mutable); + state = AfterMutable; + break; + case opOpt: + if (state == AfterOptional) { + return langException( + peekToken->line, + peekToken->column, + toString(*peekToken), + "non optional type qualifier, i.e. any of ( *, $, [] )" + ); + } + qualifs.push_back(ast::TypeQualifier::Optional); + state = AfterOptional; + break; + case opLBracket: + std::ignore = tokenizer.consume(); + + peekToken = tokenizer.peekExpect(opRBracket); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + + qualifs.push_back(ast::TypeQualifier::Slice); + state = None; + break; + default: + return qualifs; + } + + std::ignore = tokenizer.consume(); + + peekToken = tokenizer.peek(); + + if (! peekToken) { + return Unexpected<>{ std::move(peekToken).error() }; + } + } + + return qualifs; + } + + Expected> Parser::parseGenericArgumentsList() { + auto args = ast::Vector{}; + + if (auto lt = consume(TokenV::opLt, "'<'"); ! lt) { + return Unexpected<>{ std::move(lt).error() }; + } + + bool keepParsing = true; + + if (auto comma = tokenizer.peek(); + comma and comma->value == TokenV::opComma) { + return langException( + comma->line, + comma->column, + toString(*comma), + "type" + ); + } + + while (keepParsing) { + if (auto type = parseType(); ! type) { + return Unexpected<>{ std::move(type).error() }; + } + else { + args.push_back(std::move(type).value()); + } + + if (auto comma = matchAndConsume(TokenV::opComma); ! comma) { + return Unexpected{ std::move(comma).error() }; + } + else if (! comma.value()) { + if (auto peekToken = tokenizer.peekExpect(TokenV::opGt); ! peekToken) { + return Unexpected{ std::move(peekToken).error() }; + } + else { + keepParsing = false; + } + } + } + + if (auto gt = consume(TokenV::opGt, "'>'"); ! gt) { + return Unexpected<>{ std::move(gt).error() }; + } + + return args; + } + +} // namespace arti::lang diff --git a/lib/src/Tokenizer/Token.cpp b/lib/src/Tokenizer/Token.cpp index 9f74897..106b673 100644 --- a/lib/src/Tokenizer/Token.cpp +++ b/lib/src/Tokenizer/Token.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/lib/src/Tokenizer/Tokenizer.cpp b/lib/src/Tokenizer/Tokenizer.cpp index d919078..11d7862 100644 --- a/lib/src/Tokenizer/Tokenizer.cpp +++ b/lib/src/Tokenizer/Tokenizer.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include @@ -106,22 +128,25 @@ namespace arti::lang { return tokensBuffer.at(n); } - Expected - Tokenizer::peekExpect(std::size_t n, TokenV tokenType) noexcept { + Expected Tokenizer::peekExpect( + TokenV tokenType, + std::string_view message, + std::size_t n + ) noexcept { + if (message.empty()) { + message = toString(tokenType); + } + if (tokensBuffer.size() > (n + 1)) { auto tokenAt = tokensBuffer.at(n); if (tokenAt.value != tokenType) { - return Unexpected<> { - Exception{ - .line = tokenAt.line, - .column = tokenAt.column, - .message = std::format( - "Expected token of type {}, got {}", - toString(tokenType), toString(tokenAt) - ) - } - }; + return langException( + tokenAt.line, + tokenAt.column, + toString(tokenAt), + message + ); } return tokenAt; @@ -129,7 +154,7 @@ namespace arti::lang { auto token = peek(n); - if (!token) { + if (! token) { return token; } @@ -141,22 +166,18 @@ namespace arti::lang { tokensBuffer.pop_back(); tokensBuffer.push_back(*token); token->column += 1; - token->strValue = std::string_view{token->strValue.begin() + 1, 1}; + token->strValue = std::string_view{ token->strValue.begin() + 1, 1 }; tokensBuffer.push_back(*token); token = peekTok; } if (token->value != tokenType) { - return Unexpected<> { - Exception{ - .line = token->line, - .column = token->column, - .message = std::format( - "Expected token of type {}, got {}", - toString(tokenType), toString(*token) - ) - } - }; + return langException( + token->line, + token->column, + toString(*token), + message + ); } return *token; diff --git a/lib/src/Tokenizer/TokenizerRange.cpp b/lib/src/Tokenizer/TokenizerRange.cpp index 299c816..f4b7f0e 100644 --- a/lib/src/Tokenizer/TokenizerRange.cpp +++ b/lib/src/Tokenizer/TokenizerRange.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/lib/src/Util/Demangle.cpp b/lib/src/Util/Demangle.cpp index 6b2569b..5fe39a4 100644 --- a/lib/src/Util/Demangle.cpp +++ b/lib/src/Util/Demangle.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Api.cpp b/tests/Tokenizer/src/Api.cpp index 68b6f50..3a61970 100644 --- a/tests/Tokenizer/src/Api.cpp +++ b/tests/Tokenizer/src/Api.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Comments.cpp b/tests/Tokenizer/src/Comments.cpp index 30cbdc6..3679a16 100644 --- a/tests/Tokenizer/src/Comments.cpp +++ b/tests/Tokenizer/src/Comments.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Identifiers.cpp b/tests/Tokenizer/src/Identifiers.cpp index b34317d..5b2841b 100644 --- a/tests/Tokenizer/src/Identifiers.cpp +++ b/tests/Tokenizer/src/Identifiers.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Keywords.cpp b/tests/Tokenizer/src/Keywords.cpp index fd85cec..cfc2c76 100644 --- a/tests/Tokenizer/src/Keywords.cpp +++ b/tests/Tokenizer/src/Keywords.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Numbers.cpp b/tests/Tokenizer/src/Numbers.cpp index 1fac2f5..bdb5e36 100644 --- a/tests/Tokenizer/src/Numbers.cpp +++ b/tests/Tokenizer/src/Numbers.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Operators.cpp b/tests/Tokenizer/src/Operators.cpp index cca6eca..d38a7db 100644 --- a/tests/Tokenizer/src/Operators.cpp +++ b/tests/Tokenizer/src/Operators.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/Tokenizer/src/Strings.cpp b/tests/Tokenizer/src/Strings.cpp index a16a57c..b518a0a 100644 --- a/tests/Tokenizer/src/Strings.cpp +++ b/tests/Tokenizer/src/Strings.cpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #include #include diff --git a/tests/include/Utils.hpp b/tests/include/Utils.hpp index 84f9d46..f42f02d 100644 --- a/tests/include/Utils.hpp +++ b/tests/include/Utils.hpp @@ -1,3 +1,25 @@ +//============================================================================// +// // +// 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 . // +// // +//============================================================================// + #pragma once #include