From 5ba06ca0f6543f6cb9e7e4a3890d5a200a1df0b1 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Fri, 11 Sep 2026 02:45:44 +0300 Subject: [PATCH 1/3] feat: validators for the pipeline --- lib/CMakeLists.txt | 2 + .../ast/visitors/control_flow_validator.hpp | 18 ++ .../pl/core/ast/visitors/scope_validator.hpp | 55 ++++ lib/include/pl/core/validator.hpp | 2 +- .../ast/visitors/control_flow_validator.cpp | 69 +++++ .../pl/core/ast/visitors/scope_validator.cpp | 283 ++++++++++++++++++ lib/source/pl/pattern_language.cpp | 8 +- 7 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 lib/include/pl/core/ast/visitors/control_flow_validator.hpp create mode 100644 lib/include/pl/core/ast/visitors/scope_validator.hpp create mode 100644 lib/source/pl/core/ast/visitors/control_flow_validator.cpp create mode 100644 lib/source/pl/core/ast/visitors/scope_validator.cpp diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 772f4168..e363c315 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -52,6 +52,8 @@ add_library(libpl ${LIBRARY_TYPE} source/pl/core/ast/ast_node_while_statement.cpp source/pl/core/ast/visitors/ast_validator.cpp + source/pl/core/ast/visitors/control_flow_validator.cpp + source/pl/core/ast/visitors/scope_validator.cpp source/pl/core/token.cpp source/pl/core/evaluator.cpp diff --git a/lib/include/pl/core/ast/visitors/control_flow_validator.hpp b/lib/include/pl/core/ast/visitors/control_flow_validator.hpp new file mode 100644 index 00000000..035fd53e --- /dev/null +++ b/lib/include/pl/core/ast/visitors/control_flow_validator.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace pl::core { + class ControlFlowValidator final : public ASTValidator { + uint32_t m_loopsCount = 0; + + void reset() override; + + void visit(ast::ASTNodeFunctionDefinition& node) override; + void visit(ast::ASTNodeWhileStatement& node) override; + void visit(ast::ASTNodeMatchStatement& node) override; + void visit(ast::ASTNodeTryCatchStatement& node) override; + void visit(ast::ASTNodeConditionalStatement& node) override; + void visit(ast::ASTNodeControlFlowStatement& node) override; + }; +} \ No newline at end of file diff --git a/lib/include/pl/core/ast/visitors/scope_validator.hpp b/lib/include/pl/core/ast/visitors/scope_validator.hpp new file mode 100644 index 00000000..f140a6ef --- /dev/null +++ b/lib/include/pl/core/ast/visitors/scope_validator.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace pl::core { + class ScopeValidator final : public ASTValidator { + + struct Scope { + std::unordered_set m_names; + + [[nodiscard]] bool newIdentifier(const std::string& name); + + void mergeWith(Scope& from); + }; + + std::stack m_scopes; + + u32 m_curNonFuncScopes = 0; + + public: + ScopeValidator(); + + private: + void reset() override; + + void pushScope(); + void pushScope(Scope&& scope); + void popScope(); + Scope ¤tScope(); + + [[nodiscard]] bool isFunctionScope() const; + + [[nodiscard]] bool newIdentifier(const std::string& name); + + + void visit(ast::ASTNodeVariableDecl& node) override; + void visit(ast::ASTNodeArrayVariableDecl& node) override; + void visit(ast::ASTNodePointerVariableDecl& node) override; + void visit(ast::ASTNodeBitfieldField& node) override; + void visit(ast::ASTNodeMultiVariableDecl& node) override; + void visit(ast::ASTNodeTypeDecl& node) override; + void visit(ast::ASTNodeStruct& node) override; + void visit(ast::ASTNodeUnion& node) override; + void visit(ast::ASTNodeBitfield& node) override; + void visit(ast::ASTNodeBitfieldArrayVariableDecl& node) override; + void visit(ast::ASTNodeEnum& node) override; + void visit(ast::ASTNodeConditionalStatement& node) override; + void visit(ast::ASTNodeFunctionDefinition& node) override; + void visit(ast::ASTNodeCompoundStatement& node) override; + void visit(ast::ASTNodeWhileStatement& node) override; + void visit(ast::ASTNodeMatchStatement& node) override; + void visit(ast::ASTNodeTryCatchStatement& node) override; + }; +} \ No newline at end of file diff --git a/lib/include/pl/core/validator.hpp b/lib/include/pl/core/validator.hpp index 0cae19c1..14fd9445 100644 --- a/lib/include/pl/core/validator.hpp +++ b/lib/include/pl/core/validator.hpp @@ -53,7 +53,7 @@ namespace pl::core { template ... Vs> ValidatorPipeline(std::unique_ptr... validators) { - (add(validators), ...); + (add(std::move(validators)), ...); } void add(std::unique_ptr validator); diff --git a/lib/source/pl/core/ast/visitors/control_flow_validator.cpp b/lib/source/pl/core/ast/visitors/control_flow_validator.cpp new file mode 100644 index 00000000..ece3aad7 --- /dev/null +++ b/lib/source/pl/core/ast/visitors/control_flow_validator.cpp @@ -0,0 +1,69 @@ + +#include + +#include +#include +#include +#include +#include +#include + +namespace pl::core { + void ControlFlowValidator::visit(ast::ASTNodeFunctionDefinition &node) { + visitTypeErasedNodes(node.getBody()); + } + + void ControlFlowValidator::visit(ast::ASTNodeWhileStatement &node) { + ++m_loopsCount; + visitTypeErasedNodes(node.getBody()); + --m_loopsCount; + } + + void ControlFlowValidator::visit(ast::ASTNodeMatchStatement &node) { + if (const auto& default_case = node.getDefaultCase(); default_case) { + const auto& body = (*default_case).body; + visitTypeErasedNodes(body); + } + for (const auto& match_case: node.getNonDefaultCases()) { + visitTypeErasedNodes(match_case.body); + } + } + + void ControlFlowValidator::visit(ast::ASTNodeTryCatchStatement &node) { + visitTypeErasedNodes(node.getTryBody()); + visitTypeErasedNodes(node.getCatchBody()); + } + + void ControlFlowValidator::visit(ast::ASTNodeConditionalStatement &node) { + visitTypeErasedNodes(node.getTrueBody()); + visitTypeErasedNodes(node.getFalseBody()); + } + + void ControlFlowValidator::visit(ast::ASTNodeControlFlowStatement &node) { + if (m_loopsCount > 0) { + // Nothing bad. + return; + } + // Could be bad, let's check + + auto type = node.getType(); + switch (type) { + case ControlFlowStatement::Break: { + error("Break statements can only be used within a loop."); + return; + } + case ControlFlowStatement::Continue: { + error("Continue statements can only be used within a loop."); + return; + } + default: { + return; + } + } + } + + void ControlFlowValidator::reset() { + // This can't become anything else, but why not make it explicit... + m_loopsCount = 0; + } +} \ No newline at end of file diff --git a/lib/source/pl/core/ast/visitors/scope_validator.cpp b/lib/source/pl/core/ast/visitors/scope_validator.cpp new file mode 100644 index 00000000..3d7dfc83 --- /dev/null +++ b/lib/source/pl/core/ast/visitors/scope_validator.cpp @@ -0,0 +1,283 @@ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pl::core { + bool ScopeValidator::Scope::newIdentifier(const std::string &name) { + // Variables named "$padding$" are paddings and can appear multiple times per type definition + // Variables that don't have a name are anonymous and can appear multiple times per type definition + // Let's just ignore them altogether. + if (name == "$padding$" || name == "") { + return true; + } + + auto [_, inserted] = m_names.insert(name); + return inserted; + } + + void ScopeValidator::Scope::mergeWith(ScopeValidator::Scope &from) { + m_names.merge(from.m_names); + } + + ScopeValidator::ScopeValidator() { + reset(); + } + + void ScopeValidator::reset() { + m_scopes = std::stack(); + // Global scope + pushScope(); + m_curNonFuncScopes = 0; + } + + void ScopeValidator::pushScope() { + m_scopes.emplace(); + } + void ScopeValidator::pushScope(Scope &&scope) { + m_scopes.push(std::move(scope)); + } + void ScopeValidator::popScope() { + m_scopes.pop(); + } + + ScopeValidator::Scope &ScopeValidator::currentScope() { + return m_scopes.top(); + } + + [[nodiscard]] bool ScopeValidator::isFunctionScope() const { + return m_curNonFuncScopes == 0; + } + + [[nodiscard]] bool ScopeValidator::newIdentifier(const std::string &name) { + return currentScope().newIdentifier(name); + } + + void ScopeValidator::visit(ast::ASTNodeVariableDecl &node) { + const auto& var_name = node.getName(); + if (!newIdentifier(var_name)) { + error("Redeclaration of identifier '{}'", var_name); + } + } + + void ScopeValidator::visit(ast::ASTNodeArrayVariableDecl &node) { + const auto& var_name = node.getName(); + if (!newIdentifier(var_name)) { + error("Redeclaration of identifier '{}'", var_name); + } + } + + void ScopeValidator::visit(ast::ASTNodePointerVariableDecl &node) { + const auto& var_name = node.getName(); + if (!newIdentifier(var_name)) { + error("Redeclaration of identifier '{}'", var_name); + } + } + + void ScopeValidator::visit(ast::ASTNodeBitfieldField &node) { + const auto& field_name = node.getName(); + if (!newIdentifier(field_name)) { + error("Redefinition of identifier '{}'", field_name); + } + } + + void ScopeValidator::visit(ast::ASTNodeMultiVariableDecl &node) { + visitTypeErasedNodes(node.getVariables()); + } + + void ScopeValidator::visit(ast::ASTNodeTypeDecl &node) { + if (node.isForwardDeclared()) { + return; + } + // TODO: lint this + Scope typeParameterIdentifiers; + Scope nonTypeParameterIdentifiers; + + for (const auto& param: node.getTemplateParameters()) { + const auto& name = param->getName().get(); + auto isType = param->isType(); + if (isType && !typeParameterIdentifiers.newIdentifier(name)) { + error("Redefinition of type template parameter '{}'", name); + return; + } + if (!isType && !nonTypeParameterIdentifiers.newIdentifier(name)) { + error("Redefinition of non-type template parameter '{}'", name); + return; + } + } + + pushScope(std::move(nonTypeParameterIdentifiers)); + if (node.isValid()) { + visitTypeErased(node.getType()); + } + popScope(); + } + + void ScopeValidator::visit(ast::ASTNodeStruct &node) { + ++m_curNonFuncScopes; + visitTypeErasedNodes(node.getMembers()); + --m_curNonFuncScopes; + } + + void ScopeValidator::visit(ast::ASTNodeUnion &node) { + ++m_curNonFuncScopes; + visitTypeErasedNodes(node.getMembers()); + --m_curNonFuncScopes; + } + + void ScopeValidator::visit(ast::ASTNodeBitfield &node) { + ++m_curNonFuncScopes; + visitTypeErasedNodes(node.getEntries()); + --m_curNonFuncScopes; + } + + void ScopeValidator::visit(ast::ASTNodeBitfieldArrayVariableDecl &node) { + const auto& field_name = node.getName(); + if (!newIdentifier(field_name)) { + error("Redefinition of identifier '{}'", field_name); + } + } + + void ScopeValidator::visit(ast::ASTNodeEnum &node) { + // TODO: enums are broken + pushScope(); + for (const auto &[name, _] : node.getEntries()) { + if (!newIdentifier(name)) { + error("Redeclaration of enum entry '{}'", name); + return; + } + } + popScope(); + } + + void ScopeValidator::visit(ast::ASTNodeConditionalStatement &node) { + const auto initialScope = currentScope(); + + // The branches are allowed to introduce the same identifiers + if (isFunctionScope()) { + // ... which won't be accessible after 'if' ends. + + visitTypeErasedNodes(node.getTrueBody()); + currentScope() = initialScope; + + visitTypeErasedNodes(node.getFalseBody()); + currentScope() = initialScope; + return; + } + // ... which will be accessible after 'if' ends => assume both paths are taken. + + visitTypeErasedNodes(node.getTrueBody()); + auto afterIfScope = currentScope(); + + currentScope() = initialScope; + visitTypeErasedNodes(node.getFalseBody()); + + currentScope().mergeWith(afterIfScope); + } + + void ScopeValidator::visit(ast::ASTNodeFunctionDefinition &node) { + if (!newIdentifier(node.getName())) { + error("Redefinition of identifier '{}'", node.getName()); + } + + pushScope(); + for (const auto &[name, _] : node.getParams()) { + if (!newIdentifier(name)) { + error("Redefinition of function parameter '{}'", name); + return; + } + } + visitTypeErasedNodes(node.getBody()); + popScope(); + } + + void ScopeValidator::visit(ast::ASTNodeCompoundStatement &node) { + visitTypeErasedNodes(node.getStatements()); + } + + void ScopeValidator::visit(ast::ASTNodeWhileStatement &node) { + const auto initialScope = currentScope(); + visitTypeErasedNodes(node.getBody()); + currentScope() = initialScope; + } + + void ScopeValidator::visit(ast::ASTNodeMatchStatement &node) { + // The branches are allowed to introduce the same identifiers + const auto initialScope = currentScope(); + if (isFunctionScope()) { + // ... which won't be accessible after 'match' ends. + + for (const auto& match_case: node.getNonDefaultCases()) { + visitTypeErasedNodes(match_case.body); + currentScope() = initialScope; + } + + if (const auto& default_case = node.getDefaultCase(); default_case) { + visitTypeErasedNodes((*default_case).body); + currentScope() = initialScope; + } + return; + } + + // ... which will be accessible after 'match' ends => like with the if statement, + // assume all match paths are taken and accumulate the ids. + + auto resultingScope = currentScope(); + + for (const auto& match_case: node.getNonDefaultCases()) { + visitTypeErasedNodes(match_case.body); + resultingScope.mergeWith(currentScope()); + currentScope() = initialScope; + } + + if (const auto& default_case = node.getDefaultCase(); default_case) { + visitTypeErasedNodes((*default_case).body); + resultingScope.mergeWith(currentScope()); + } + + currentScope() = std::move(resultingScope); + } + + void ScopeValidator::visit(ast::ASTNodeTryCatchStatement &node) { + const auto initialScope = currentScope(); + + // The branches are allowed to introduce the same identifiers + if (isFunctionScope()) { + // ... which won't be accessible after 'try' ends. + + visitTypeErasedNodes(node.getTryBody()); + currentScope() = initialScope; + + visitTypeErasedNodes(node.getCatchBody()); + currentScope() = initialScope; + return; + } + // ... which will be accessible after 'try' ends => assume both paths are taken. + + visitTypeErasedNodes(node.getTryBody()); + auto afterTryScope = currentScope(); + + currentScope() = initialScope; + visitTypeErasedNodes(node.getCatchBody()); + + currentScope().mergeWith(afterTryScope); + } + + +} \ No newline at end of file diff --git a/lib/source/pl/pattern_language.cpp b/lib/source/pl/pattern_language.cpp index 43e7e33e..bdc78787 100644 --- a/lib/source/pl/pattern_language.cpp +++ b/lib/source/pl/pattern_language.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include + #include #include @@ -38,7 +41,10 @@ namespace pl { .lexer = std::make_unique(), .parser = std::make_unique(), .validator = std::make_unique(), - .validatorPipeline = std::make_unique(), + .validatorPipeline = std::make_unique( + std::make_unique(), + std::make_unique() + ), .evaluator = std::make_unique() }; From 85746b863e7c2914993ea7e04de24b04af9dfaea Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Fri, 11 Sep 2026 03:00:58 +0300 Subject: [PATCH 2/3] Enabled the previously added tests --- .../pl/core/ast/visitors/scope_validator.cpp | 2 +- tests/CMakeLists.txt | 18 +- .../test_pattern_error_semantics.hpp | 185 +++++++++--------- tests/source/tests.cpp | 18 +- 4 files changed, 111 insertions(+), 112 deletions(-) diff --git a/lib/source/pl/core/ast/visitors/scope_validator.cpp b/lib/source/pl/core/ast/visitors/scope_validator.cpp index 3d7dfc83..14b4f90e 100644 --- a/lib/source/pl/core/ast/visitors/scope_validator.cpp +++ b/lib/source/pl/core/ast/visitors/scope_validator.cpp @@ -105,7 +105,7 @@ namespace pl::core { if (node.isForwardDeclared()) { return; } - // TODO: lint this + // TODO: detect more clashes in a linter Scope typeParameterIdentifiers; Scope nonTypeParameterIdentifiers; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 292b15f5..7112de4f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -73,27 +73,27 @@ set(AVAILABLE_TESTS InVariableOverride SetEntriesSortedConsistency VarRedeclarationFail - #VarRedeclarationInFuncFail + VarRedeclarationInFuncFail ArrayVarRedeclarationFail PointerVarRedeclarationFail MultiVarRedeclarationFail - #MultiVarMixRedeclarationFail + MultiVarMixRedeclarationFail StructMemberRedeclarationFail UnionMemberRedeclarationFail #EnumMemberRedeclarationFail BitfieldMemberRedeclarationFail - #BitfieldArrayMemberRedeclarationFail + BitfieldArrayMemberRedeclarationFail TemplateParamRedeclarationFail NonTypeTemplateParamRedeclarationFail FuncRedeclarationFail FuncParamRedeclarationFail - #RedeclarationInFuncBodyFail - #FuncParamAndBodyRedeclarationFail + RedeclarationInFuncBodyFail + FuncParamAndBodyRedeclarationFail AllowedRedefinitions - #GlobalContinueFail - #FunctionContinueFail - #GlobalBreakFail - #FunctionBreakFail + GlobalContinueFail + FunctionContinueFail + GlobalBreakFail + FunctionBreakFail ) diff --git a/tests/include/test_patterns/test_pattern_error_semantics.hpp b/tests/include/test_patterns/test_pattern_error_semantics.hpp index d1e2fdd9..eb0b6090 100644 --- a/tests/include/test_patterns/test_pattern_error_semantics.hpp +++ b/tests/include/test_patterns/test_pattern_error_semantics.hpp @@ -131,20 +131,20 @@ namespace pl::test { u8 x; )") { } }; -// class TestPatternVarRedeclarationInFuncFail: public TestPatternFailingSemantic { -// public: -// TestPatternVarRedeclarationInFuncFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "VarRedeclarationInFuncFail", R"( -// fn f() { -// u8 x; -// u8 x; -// }; -// )") { } -// }; + class TestPatternVarRedeclarationInFuncFail: public TestPatternFailingSemantic { + public: + TestPatternVarRedeclarationInFuncFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "VarRedeclarationInFuncFail", R"( + fn f() { + u8 x; + u8 x; + }; + )") { } + }; class TestPatternArrayVarRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternArrayVarRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "ArrayVarRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "ArrayVarRedeclarationFail", R"( u8 x[2]; u8 x[2]; )") { } @@ -152,7 +152,7 @@ namespace pl::test { class TestPatternPointerVarRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternPointerVarRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "PointerVarRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "PointerVarRedeclarationFail", R"( u8* x: u8 @ 0; u8* x: u8 @ 0; )") { } @@ -160,26 +160,26 @@ namespace pl::test { class TestPatternMultiVarRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternMultiVarRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "MultiVarRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "MultiVarRedeclarationFail", R"( struct S { u8 x, x; }; )") { } }; -// class TestPatternMultiVarMixRedeclarationFail: public TestPatternFailingSemantic { -// public: -// TestPatternMultiVarMixRedeclarationFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "MultiVarMixRedeclarationFail", R"( -// struct S { -// u8 x; -// u8 x, y; -// }; -// )") { } -// }; + class TestPatternMultiVarMixRedeclarationFail: public TestPatternFailingSemantic { + public: + TestPatternMultiVarMixRedeclarationFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "MultiVarMixRedeclarationFail", R"( + struct S { + u8 x; + u8 x, y; + }; + )") { } + }; class TestPatternStructMemberRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternStructMemberRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "StructMemberRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "StructMemberRedeclarationFail", R"( struct S { u8 x; u8 x; @@ -189,7 +189,7 @@ namespace pl::test { class TestPatternUnionMemberRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternUnionMemberRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "UnionMemberRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "UnionMemberRedeclarationFail", R"( union S { u8 x; u8 x; @@ -199,7 +199,7 @@ namespace pl::test { // class TestPatternEnumMemberRedeclarationFail: public TestPatternFailingSemantic { // public: // TestPatternEnumMemberRedeclarationFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "EnumMemberRedeclarationFail", R"( +// : TestPatternFailingSemantic(evaluator, "EnumMemberRedeclarationFail", R"( // enum E : u8 { // a = 1, // a = 2, @@ -209,39 +209,38 @@ namespace pl::test { class TestPatternBitfieldMemberRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternBitfieldMemberRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "BitfieldMemberRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "BitfieldMemberRedeclarationFail", R"( bitfield B { x: 5; x: 11; }; )") { } }; - -// class TestPatternBitfieldArrayMemberRedeclarationFail: public TestPatternFailingSemantic { -// public: -// TestPatternBitfieldArrayMemberRedeclarationFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "BitfieldArrayMemberRedeclarationFail", R"( -// bitfield B { -// x : 4; -// y : 4; -// }; -// bitfield TestBitfield { -// a : 2; -// B a[2]; -// }; -// )") { } -// }; + class TestPatternBitfieldArrayMemberRedeclarationFail: public TestPatternFailingSemantic { + public: + TestPatternBitfieldArrayMemberRedeclarationFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "BitfieldArrayMemberRedeclarationFail", R"( + bitfield B { + x : 4; + y : 4; + }; + bitfield TestBitfield { + a : 2; + B a[2]; + }; + )") { } + }; class TestPatternTemplateParamRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternTemplateParamRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "TemplateParamRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "TemplateParamRedeclarationFail", R"( struct S {}; )") { } }; class TestPatternNonTypeTemplateParamRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternNonTypeTemplateParamRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "NonTypeTemplateParamRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "NonTypeTemplateParamRedeclarationFail", R"( struct S {}; )") { } }; @@ -251,7 +250,7 @@ namespace pl::test { class TestPatternFuncRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternFuncRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "FuncRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "FuncRedeclarationFail", R"( fn f() {}; fn f() {}; )") { } @@ -260,58 +259,58 @@ namespace pl::test { class TestPatternFuncParamRedeclarationFail: public TestPatternFailingSemantic { public: TestPatternFuncParamRedeclarationFail(core::Evaluator *evaluator) - : TestPatternFailingSemantic(evaluator, "FuncParamRedeclarationFail", R"( + : TestPatternFailingSemantic(evaluator, "FuncParamRedeclarationFail", R"( fn f(u8 x, u8 x) {}; )") { } }; -// class TestPatternRedeclarationInFuncBodyFail: public TestPatternFailingSemantic { -// public: -// TestPatternRedeclarationInFuncBodyFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "RedeclarationInFuncBodyFail", R"( -// fn f() { -// u8 x; -// u8 x; -// }; -// )") { } -// }; + class TestPatternRedeclarationInFuncBodyFail: public TestPatternFailingSemantic { + public: + TestPatternRedeclarationInFuncBodyFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "RedeclarationInFuncBodyFail", R"( + fn f() { + u8 x; + u8 x; + }; + )") { } + }; -// class TestPatternFuncParamAndBodyRedeclarationFail: public TestPatternFailingSemantic { -// public: -// TestPatternFuncParamAndBodyRedeclarationFail(core::Evaluator *evaluator) -// : TestPatternFailingSemantic(evaluator, "FuncParamAndBodyRedeclarationFail", R"( -// fn f(u8 x) { -// u8 x; -// }; -// )") { } -// }; + class TestPatternFuncParamAndBodyRedeclarationFail: public TestPatternFailingSemantic { + public: + TestPatternFuncParamAndBodyRedeclarationFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "FuncParamAndBodyRedeclarationFail", R"( + fn f(u8 x) { + u8 x; + }; + )") { } + }; - // class TestPatternGlobalContinueFail : public TestPatternFailingSemantic { - // public: - // TestPatternGlobalContinueFail(core::Evaluator *evaluator) - // : TestPatternFailingSemantic(evaluator, "GlobalContinueFail", R"( - // continue; - // )") { } - // }; - // class TestPatternFunctionContinueFail : public TestPatternFailingSemantic { - // public: - // TestPatternFunctionContinueFail(core::Evaluator *evaluator) - // : TestPatternFailingSemantic(evaluator, "FunctionContinueFail", R"( - // fn f() { continue; }; - // )") { } - // }; - // class TestPatternGlobalBreakFail : public TestPatternFailingSemantic { - // public: - // TestPatternGlobalBreakFail(core::Evaluator *evaluator) - // : TestPatternFailingSemantic(evaluator, "GlobalBreakFail", R"( - // break; - // )") { } - // }; - // class TestPatternFunctionBreakFail : public TestPatternFailingSemantic { - // public: - // TestPatternFunctionBreakFail(core::Evaluator *evaluator) - // : TestPatternFailingSemantic(evaluator, "FunctionBreakFail", R"( - // fn f() { break; }; - // )") { } - // }; + class TestPatternGlobalContinueFail : public TestPatternFailingSemantic { + public: + TestPatternGlobalContinueFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "GlobalContinueFail", R"( + continue; + )") { } + }; + class TestPatternFunctionContinueFail : public TestPatternFailingSemantic { + public: + TestPatternFunctionContinueFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "FunctionContinueFail", R"( + fn f() { continue; }; + )") { } + }; + class TestPatternGlobalBreakFail : public TestPatternFailingSemantic { + public: + TestPatternGlobalBreakFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "GlobalBreakFail", R"( + break; + )") { } + }; + class TestPatternFunctionBreakFail : public TestPatternFailingSemantic { + public: + TestPatternFunctionBreakFail(core::Evaluator *evaluator) + : TestPatternFailingSemantic(evaluator, "FunctionBreakFail", R"( + fn f() { break; }; + )") { } + }; } diff --git a/tests/source/tests.cpp b/tests/source/tests.cpp index 0e6510ef..1ba0eb35 100644 --- a/tests/source/tests.cpp +++ b/tests/source/tests.cpp @@ -116,25 +116,25 @@ std::array Tests = { TEST(InVariableOverride), TEST(SetEntriesSortedConsistency), TEST(VarRedeclarationFail), -// TEST(VarRedeclarationInFuncFail), + TEST(VarRedeclarationInFuncFail), TEST(ArrayVarRedeclarationFail), TEST(PointerVarRedeclarationFail), TEST(MultiVarRedeclarationFail), -// TEST(MultiVarMixRedeclarationFail), + TEST(MultiVarMixRedeclarationFail), TEST(StructMemberRedeclarationFail), TEST(UnionMemberRedeclarationFail), // TEST(EnumMemberRedeclarationFail), TEST(BitfieldMemberRedeclarationFail), -// TEST(BitfieldArrayMemberRedeclarationFail), + TEST(BitfieldArrayMemberRedeclarationFail), TEST(TemplateParamRedeclarationFail), TEST(NonTypeTemplateParamRedeclarationFail), TEST(FuncRedeclarationFail), TEST(FuncParamRedeclarationFail), -// TEST(RedeclarationInFuncBodyFail), -// TEST(FuncParamAndBodyRedeclarationFail), + TEST(RedeclarationInFuncBodyFail), + TEST(FuncParamAndBodyRedeclarationFail), TEST(AllowedRedefinitions), - // TEST(GlobalContinueFail), - // TEST(FunctionContinueFail), - // TEST(GlobalBreakFail), - // TEST(FunctionBreakFail), + TEST(GlobalContinueFail), + TEST(FunctionContinueFail), + TEST(GlobalBreakFail), + TEST(FunctionBreakFail), }; From 05b8a861663707a0c692234e7a4040eb88199342 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Fri, 11 Sep 2026 03:15:10 +0300 Subject: [PATCH 3/3] Removed the old validator class --- lib/include/pl/core/validator.hpp | 28 ---- lib/include/pl/pattern_language.hpp | 2 - lib/source/pl/core/parser_manager.cpp | 5 +- lib/source/pl/core/validator.cpp | 194 -------------------------- lib/source/pl/lib/std/pragmas.cpp | 1 - lib/source/pl/pattern_language.cpp | 11 -- 6 files changed, 2 insertions(+), 239 deletions(-) diff --git a/lib/include/pl/core/validator.hpp b/lib/include/pl/core/validator.hpp index 14fd9445..077fd95c 100644 --- a/lib/include/pl/core/validator.hpp +++ b/lib/include/pl/core/validator.hpp @@ -18,33 +18,6 @@ namespace pl::core { namespace ast { class ASTNode; } - class Validator : err::ErrorCollector { - using Result = hlp::CompileResult; - public: - Validator() = default; - - Result validate(const std::vector> &ast); - - void setRecursionDepth(const u32 limit) { - this->m_maxRecursionDepth = std::min(limit, 255); - } - - private: - bool validateNodes(const std::vector> &nodes, bool newScope = true); - bool validateNodes(const std::vector> &nodes, bool newScope = true); - bool validateNode(const std::shared_ptr& node, bool newScope = true); - bool validateNode(ast::ASTNode *node, std::unordered_set& identifiers); - - Location location() override; - - u32 m_maxRecursionDepth = 32; - u32 m_recursionDepth = 0; - - ast::ASTNode *m_lastNode = nullptr; - std::set m_validatedNodes; - std::list> m_identifiers; - }; - class ValidatorPipeline { public: using Result = hlp::CompileResult; @@ -63,5 +36,4 @@ namespace pl::core { private: std::vector> m_validators; }; - } diff --git a/lib/include/pl/pattern_language.hpp b/lib/include/pl/pattern_language.hpp index 337c1b17..d77cd7e8 100644 --- a/lib/include/pl/pattern_language.hpp +++ b/lib/include/pl/pattern_language.hpp @@ -31,7 +31,6 @@ namespace pl { class Preprocessor; class Lexer; class Parser; - class Validator; class ValidatorPipeline; class Evaluator; @@ -65,7 +64,6 @@ namespace pl { std::unique_ptr preprocessor; std::unique_ptr lexer; std::unique_ptr parser; - std::unique_ptr validator; std::unique_ptr validatorPipeline; std::unique_ptr evaluator; }; diff --git a/lib/source/pl/core/parser_manager.cpp b/lib/source/pl/core/parser_manager.cpp index e4367019..09ee4449 100644 --- a/lib/source/pl/core/parser_manager.cpp +++ b/lib/source/pl/core/parser_manager.cpp @@ -39,8 +39,6 @@ namespace pl::core { preprocessor.addPragmaHandler(name, handler); } - const auto &validator = internals.validator; - auto [tokens, preprocessorErrors] = preprocessor.preprocess(this->m_patternLanguage, source, true); if (!preprocessorErrors.empty()) { return Result::err(preprocessorErrors); @@ -60,7 +58,8 @@ namespace pl::core { if (result.hasErrs()) return Result::err(result.errs); - auto [validated, validatorErrors] = validator->validate(result.ok.value()); + const auto &validatorPipeline = internals.validatorPipeline; + auto [validated, validatorErrors] = validatorPipeline->validate(result.ok.value()); if (validated && !validatorErrors.empty()) { return Result::err(validatorErrors); } diff --git a/lib/source/pl/core/validator.cpp b/lib/source/pl/core/validator.cpp index d4939373..40252755 100644 --- a/lib/source/pl/core/validator.cpp +++ b/lib/source/pl/core/validator.cpp @@ -1,204 +1,11 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include namespace pl::core { - - Validator::Result Validator::validate(const std::vector> &ast) { - this->m_recursionDepth = 0; - this->m_validatedNodes.clear(); - this->m_identifiers.clear(); - - validateNodes(ast); - - if(this->hasErrors()) { - return Result::err(this->collectErrors()); - } - return { }; - } - - bool Validator::validateNodes(const std::vector>& nodes, const bool newScope) { - if (newScope || this->m_identifiers.empty()) - this->m_identifiers.emplace_back(); - - auto &identifiers = this->m_identifiers.back(); - - this->m_recursionDepth++; - ON_SCOPE_EXIT { - this->m_recursionDepth--; - - if (newScope) - this->m_identifiers.pop_back(); - }; - - bool success = true; - for (const auto &node : nodes) { - if (this->m_validatedNodes.contains(node.get())) - continue; - - if (node == nullptr) { - errorDesc("Null-Pointer found in AST.", "This is a parser bug. Please report it on GitHub."); - continue; - } - - this->m_lastNode = node.get(); - - // Variables named "$padding$" are paddings and can appear multiple times per type definition - identifiers.erase("$padding$"); - // Variables that don't have a name are anonymous and can appear multiple times per type definition - identifiers.erase(""); - - if (this->m_recursionDepth > this->m_maxRecursionDepth) - break; - - if(!validateNode(node.get(), identifiers)) - success = false; - } - return success; - } - - bool Validator::validateNodes(const std::vector>& nodes, bool newScope) { - // move into vector of shared_ptr - std::vector> sharedNodes; - sharedNodes.reserve(nodes.size()); - for (const auto& node : nodes) { - sharedNodes.emplace_back(node->clone()); - } - - return validateNodes(sharedNodes, newScope); - } - - - bool Validator::validateNode(const std::shared_ptr& node, const bool newScope) { - return this->validateNodes({ node }, newScope); - } - - - bool Validator::validateNode(ast::ASTNode* node, std::unordered_set& identifiers) { - if (const auto variableDeclNode = dynamic_cast(node); variableDeclNode != nullptr) { - if (!identifiers.insert(variableDeclNode->getName()).second) { - error("Redeclaration of identifier '{}'", variableDeclNode->getName()); - return false; - } - - if (!this->validateNode(variableDeclNode->getType())) - return false; - } else if (const auto arrayVariableDeclNode = dynamic_cast(node); arrayVariableDeclNode != nullptr) { - if (!identifiers.insert(arrayVariableDeclNode->getName()).second) { - error("Redeclaration of identifier '{}'", arrayVariableDeclNode->getName()); - return false; - } - - if (!this->validateNode(arrayVariableDeclNode->getType())) - return false; - } else if (const auto pointerVariableDecl = dynamic_cast(node); pointerVariableDecl != nullptr) { - if (!identifiers.insert(pointerVariableDecl->getName()).second) { - error("Redeclaration of identifier '{}'", pointerVariableDecl->getName()); - return false; - } - - if (!this->validateNode(pointerVariableDecl->getType())) - return false; - } else if (const auto bitfieldFieldDecl = dynamic_cast(node); bitfieldFieldDecl != nullptr) { - if (!identifiers.insert(bitfieldFieldDecl->getName()).second) { - error("Redefinition of identifier '{}'", bitfieldFieldDecl->getName()); - return false; - } - } else if (const auto multiVariableDecl = dynamic_cast(node); multiVariableDecl != nullptr) { - if (!this->validateNodes(multiVariableDecl->getVariables())) - return false; - } else if (const auto typeDeclNode = dynamic_cast(node); typeDeclNode != nullptr) { - if (!typeDeclNode->isForwardDeclared()) { - std::unordered_set typeParameterIdentifiers; - std::unordered_set nonTypeParameterIdentifiers; - for (const auto & parameter : typeDeclNode->getTemplateParameters()) { - auto name = parameter->getName().get(); - auto isType = parameter->isType(); - if (isType && !typeParameterIdentifiers.insert(name).second) { - error("Redefinition of type template parameter '{}'", name); - return false; - } - if (!isType && !nonTypeParameterIdentifiers.insert(name).second) { - error("Redefinition of non-type template parameter '{}'", name); - return false; - } - } - - this->m_identifiers.push_back(std::move(nonTypeParameterIdentifiers)); - ON_SCOPE_EXIT { - this->m_identifiers.pop_back(); - }; - - if (!this->validateNode(typeDeclNode->getType(), false)) - return false; - } - } else if (const auto structNode = dynamic_cast(node); structNode != nullptr) { - if (!this->validateNodes(structNode->getMembers(), false)) - return false; - } else if (const auto unionNode = dynamic_cast(node); unionNode != nullptr) { - if (!this->validateNodes(unionNode->getMembers(), false)) - return false; - } else if (const auto bitfieldNode = dynamic_cast(node); bitfieldNode != nullptr) { - if (!this->validateNodes(bitfieldNode->getEntries(), false)) - return false; - } else if (const auto enumNode = dynamic_cast(node); enumNode != nullptr) { - std::unordered_set enumIdentifiers; - for (const auto &[name, value] : enumNode->getEntries()) { - if (!enumIdentifiers.insert(name).second) { - error("Redeclaration of enum entry '{}'", name); - return false; - } - } - } else if (const auto conditionalNode = dynamic_cast(node); conditionalNode != nullptr) { - const auto prevIdentifiers = identifiers; - if (!this->validateNodes(conditionalNode->getTrueBody(), false)) - return false; - identifiers = prevIdentifiers; - if (!this->validateNodes(conditionalNode->getFalseBody(), false)) - return false; - identifiers = prevIdentifiers; - } else if (const auto functionNode = dynamic_cast(node); functionNode != nullptr) { - if (!identifiers.insert(functionNode->getName()).second) { - error("Redefinition of identifier '{}'", functionNode->getName()); - return false; - } - - std::unordered_set parameterIdentifiers; - for (const auto &[name, type] : functionNode->getParams()) { - if (!parameterIdentifiers.insert(name).second) { - error("Redefinition of function parameter '{}'", name); - return false; - } - } - } else if (const auto compoundNode = dynamic_cast(node); compoundNode != nullptr) { - if (!this->validateNodes(compoundNode->getStatements(), false)) - return false; - } - - this->m_validatedNodes.insert(node); - return true; - } - - Location Validator::location() { - return m_lastNode->getLocation(); - } - void ValidatorPipeline::add(std::unique_ptr validator) { m_validators.push_back(std::move(validator)); } @@ -227,5 +34,4 @@ namespace pl::core { } return Result::err(std::move(result)); } - } \ No newline at end of file diff --git a/lib/source/pl/lib/std/pragmas.cpp b/lib/source/pl/lib/std/pragmas.cpp index d6ebac74..21a803b7 100644 --- a/lib/source/pl/lib/std/pragmas.cpp +++ b/lib/source/pl/lib/std/pragmas.cpp @@ -48,7 +48,6 @@ namespace pl::lib::libstd { return false; runtime.getInternals().evaluator->setEvaluationDepth(*limit); - runtime.getInternals().validator->setRecursionDepth(*limit); return true; }); diff --git a/lib/source/pl/pattern_language.cpp b/lib/source/pl/pattern_language.cpp index bdc78787..e545c228 100644 --- a/lib/source/pl/pattern_language.cpp +++ b/lib/source/pl/pattern_language.cpp @@ -40,7 +40,6 @@ namespace pl { .preprocessor = std::make_unique(), .lexer = std::make_unique(), .parser = std::make_unique(), - .validator = std::make_unique(), .validatorPipeline = std::make_unique( std::make_unique(), std::make_unique() @@ -185,15 +184,6 @@ namespace pl { if (ast->empty()) return ast; - - auto [validated, validatorErrors] = this->m_internals.validator->validate(ast.value()); - wolv::util::unused(validated); - if (!validatorErrors.empty()) { - this->m_compileErrors.insert(m_compileErrors.end(), validatorErrors.begin(), validatorErrors.end()); - validatorErrors.clear(); - } - // TODO: delete the block above when ready - auto [_, validatorPipelineErrors] = this->m_internals.validatorPipeline->validate(ast.value()); if (!validatorPipelineErrors.empty()) { std::ranges::move(validatorPipelineErrors, std::back_inserter(m_compileErrors)); @@ -547,7 +537,6 @@ namespace pl { this->m_currError.reset(); this->m_compileErrors.clear(); this->m_parserManager.reset(); - this->m_internals.validator->setRecursionDepth(32); this->m_internals.preprocessor->reset(); this->m_internals.lexer->reset();