From 85a3582612b027600cd310f27419eb77606bddcd Mon Sep 17 00:00:00 2001 From: Username404 Date: Sun, 6 Aug 2023 19:49:35 +0200 Subject: [PATCH] Parser.hpp: Add basic parsing of if/elseif/else statements Signed-off-by: Username404 --- src/headers/parsing/ParseComponents.hpp | 36 +++++++++++++++++++ src/headers/parsing/Parser.hpp | 36 +++++++++++++++++-- src/headers/parsing/ReservedIdentifiers.hpp | 4 +-- src/headers/transpiler/Target.hpp | 7 ++-- .../implementations/GodotScript.hpp | 16 +++++++++ src/headers/transpiler/implementations/Js.hpp | 21 +++++++++++ .../transpiler/implementations/Lua.hpp | 19 ++++++++++ src/headers/transpiler/implementations/Py.hpp | 21 +++++++++++ 8 files changed, 153 insertions(+), 7 deletions(-) diff --git a/src/headers/parsing/ParseComponents.hpp b/src/headers/parsing/ParseComponents.hpp index e4301b1..13de2d0 100644 --- a/src/headers/parsing/ParseComponents.hpp +++ b/src/headers/parsing/ParseComponents.hpp @@ -130,6 +130,42 @@ namespace StandardComponents { struct Call: ParseTree, Reference { using Reference::Reference; }; + struct Operator: ParseComponent { // TODO Parse operators in Parser.hpp + tok::type operator_type; + Operator(const convertible_to auto& operator_token): operator_type() { + using enum tok::type; + static_assert((operator_token == PLUS or operator_token == HYPHEN or operator_token == ASTERISK or operator_token == DIVIDE or operator_token == LCOMP or operator_token == RCOMP)); + operator_type = operator_token; + } + }; + struct Condition: ParseTree { + Condition(const ParseTree left, const ParseTree right, const Operator& operator_token) { + *this << left << operator_token << right; + } + explicit Condition(const derived_from auto condition) { *this << condition; } + struct Statement; + protected: + Condition() = default; + }; + struct Condition::Statement: ParseTree { + struct Branch; + Condition condition; + ParseTree else_branches; + bool is_last_branch_conditional() const; + Statement(Condition _condition): ParseTree(), condition(move(_condition)), else_branches() {} + protected: + Statement(): condition(), else_branches() {} + }; + struct Condition::Statement::Branch: Condition::Statement { + inline bool is_conditional() const { return not condition.empty(); }; + using Condition::Statement::Statement; Branch(): Condition::Statement() {}; + private: + using Condition::Statement::is_last_branch_conditional; + }; + bool Condition::Statement::is_last_branch_conditional() const { + const auto* last = (not else_branches.empty()) ? dynamic_cast(else_branches[else_branches.size() - 1].get()) : nullptr; + return (last == nullptr and (not this->condition.empty())) or (last != nullptr and not last->condition.empty()); + } struct Function: NamedIdentifier, ParseTree { ParseTree parameters; diff --git a/src/headers/parsing/Parser.hpp b/src/headers/parsing/Parser.hpp index edc22cb..3c0a7f1 100644 --- a/src/headers/parsing/Parser.hpp +++ b/src/headers/parsing/Parser.hpp @@ -88,11 +88,43 @@ namespace Parser { } else { unsigned int parametersDistance = 0; if (next.toktype == LPAR) { - const auto closing = find_if(lexed.begin() + i, lexed.end(), [](const tok& token){ return token.toktype == RPAR; }); + const auto closing = find_corresponding(lexed.begin() + i + 2, lexed.end(), LPAR, RPAR); parametersDistance = distance(lexed.begin() + i, closing); i += parametersDistance; } - if (nextAre({LCOMP, LCOMP, LBRACE})) { + const bool is_branch = current.toktext == ID(CONDITION_STATEMENT_BRANCH); + const bool is_conditional_branch = (not is_branch) and current.toktext == ID(CONDITION_STATEMENT_CONDITIONAL_BRANCH); + if (current.toktext == ID(CONDITION_STATEMENT) or is_branch or is_conditional_branch) { + if (is_branch or is_conditional_branch) { + const auto& last_id = parseTree.at(parseTree.size() - 1)->getId(); + if (last_id != typeid(Condition::Statement)) { + parsingError(current, "unexpected \"" + current.toktext + "\" without preceding if statement"); + } + } + if (*(lexed.begin() + i + 1) != tok::LBRACE) parsingError(*(lexed.begin() + i), "missing statement body"); + const auto body_end = find_corresponding(lexed.begin() + i + 2, lexed.end(), LBRACE, RBRACE); + auto* statement = (is_branch or is_conditional_branch) ? dynamic_cast(parseTree[parseTree.size() - 1].get()) : nullptr; + auto& else_branches = statement->else_branches; + if ((is_conditional_branch or is_branch) and not statement->is_last_branch_conditional()) + parsingError(current, "<- unexpected branch", true); + optional condition; + // TODO Check that the condition is valid + if (next.toktype == LPAR and ((lexed.begin() + i - parametersDistance) + 2) < lexed.begin() + i) + condition.emplace(parse((lexed.begin() + i - parametersDistance) + 2, lexed.begin() + i)); + if (condition.has_value() or is_branch) { + if (is_conditional_branch or is_branch) { + auto branch = (condition.has_value() ? (Condition::Statement::Branch(move(condition.value()))) : Condition::Statement::Branch()); + branch.ParseTree::operator=(parse(lexed.begin() + i + 2, body_end)); + else_branches << branch; + } else { + Condition::Statement new_statement (move(condition.value())); + new_statement.ParseTree::operator=(parse(lexed.begin() + i + 2, body_end)); + parseTree << new_statement; + } + i += distance(lexed.begin() + i, body_end); + } else if (not is_branch) parsingError(current, "missing condition after an \"" + current.toktext + "\" statement"); + continue; + } else if (nextAre({LCOMP, LCOMP, LBRACE})) { Function function(current.toktext); if (parametersDistance > 2) function.parameters = parse(filter_comma_list(lexed.begin() + ((i + 2) - parametersDistance), lexed.begin() + i)); // TODO Parse parameters correctly diff --git a/src/headers/parsing/ReservedIdentifiers.hpp b/src/headers/parsing/ReservedIdentifiers.hpp index fed4b5c..39dca1b 100644 --- a/src/headers/parsing/ReservedIdentifiers.hpp +++ b/src/headers/parsing/ReservedIdentifiers.hpp @@ -7,11 +7,11 @@ #include static constinit const array identifiers { - "class", "structure", "print", "print_line" + "class", "structure", "print", "print_line", "if", "else", "elseif" }; enum class ReservedIdentifier: size_t { - CLASS, STRUCT, PRINT_FUNCTION, PRINT_LINE_FUNCTION + CLASS, STRUCT, PRINT_FUNCTION, PRINT_LINE_FUNCTION, CONDITION_STATEMENT, CONDITION_STATEMENT_BRANCH, CONDITION_STATEMENT_CONDITIONAL_BRANCH }; inline const char* ID(const ReservedIdentifier& identifier) { diff --git a/src/headers/transpiler/Target.hpp b/src/headers/transpiler/Target.hpp index 96dcd6c..324d909 100644 --- a/src/headers/transpiler/Target.hpp +++ b/src/headers/transpiler/Target.hpp @@ -108,13 +108,13 @@ protected: void separate_transpileTree(const T& parseTree, const unsigned short& indentationLevel = 0) { transpileTree(parseTree, indentationLevel, [this, &parseTree](const auto& iterator){ if ((newLines || iterator + 1 == parseTree.cend()) && use_uniqueLineSeparator()) output << uniqueLineSeparator().value(); - if (iterator + 1 != parseTree.cend()) output << separator; + if (iterator + 1 < parseTree.cend()) output << separator; }); } IS(ParseTree) void separate_transpileTree(const T& parseTree, const string_view separation_characters) { transpileTree(parseTree, 0, [this, &parseTree, &separation_characters](const auto& iterator){ - if (iterator + 1 != parseTree.cend()) { output << separation_characters; } + if (iterator + 1 < parseTree.cend()) { output << separation_characters; } }); } typedef optional optional_string; @@ -137,7 +137,8 @@ public: separate_transpileTree(parseComponent, ", "); output << ')'; ), - make_task(StandardComponents::types::Integer, output << setprecision(parseComponent.precision) << fixed << parseComponent.value;) + make_task(StandardComponents::types::Integer, output << setprecision(parseComponent.precision) << fixed << parseComponent.value;), + make_task(StandardComponents::Condition, output << '('; transpileTree(parseComponent); output << ')';) })); return fullMap; }; diff --git a/src/headers/transpiler/implementations/GodotScript.hpp b/src/headers/transpiler/implementations/GodotScript.hpp index d658f71..5deff36 100644 --- a/src/headers/transpiler/implementations/GodotScript.hpp +++ b/src/headers/transpiler/implementations/GodotScript.hpp @@ -16,6 +16,22 @@ struct GsTarget: Target { output << "):" << separator << indentation; if (parseComponent.empty()) output << "pass"; separate_transpileTree(parseComponent, 1); + ), + make_task(Condition::Statement, + output << "if ("; transpileTree(parseComponent.condition); output << "):" << separator << indentation; + if (parseComponent.empty()) output << "pass"; + separate_transpileTree(parseComponent, 1); + if (not parseComponent.else_branches.empty()) { + output << separator; + separate_transpileTree(parseComponent.else_branches); + } + ), + make_task(Condition::Statement::Branch, + if (parseComponent.is_conditional()) { + output << "elif ("; transpileTree(parseComponent.condition); output << ")"; + } else output << "else"; + output << ':' << separator << indentation; + separate_transpileTree(parseComponent, 1); ) }; } diff --git a/src/headers/transpiler/implementations/Js.hpp b/src/headers/transpiler/implementations/Js.hpp index ce8f5c7..b32aac3 100644 --- a/src/headers/transpiler/implementations/Js.hpp +++ b/src/headers/transpiler/implementations/Js.hpp @@ -20,6 +20,27 @@ struct JsTarget: Target { separate_transpileTree(parseComponent, 1); if (newLines and not parseComponent.empty()) output << separator; output << '}'; + ), + make_task(Condition::Statement, + output << "if ("; transpileTree(parseComponent.condition); output << ") {"; + if (not parseComponent.empty()) { + if (newLines) output << separator << indentation; } + separate_transpileTree(parseComponent, 1); + if (not parseComponent.else_branches.empty()) { + if (newLines) output << separator; + output << '}'; + separate_transpileTree(parseComponent.else_branches, string((newLines) ? separator : "") + '}'); + } + if (newLines) output << separator; + output << '}'; + ), + make_task(Condition::Statement::Branch, + if (parseComponent.is_conditional()) { + output << " else if ("; transpileTree(parseComponent.condition); output << ')'; + } else output << " else"; + output << " {"; + if (newLines) output << separator << indentation; + separate_transpileTree(parseComponent, 1); ) }; } diff --git a/src/headers/transpiler/implementations/Lua.hpp b/src/headers/transpiler/implementations/Lua.hpp index 84939ad..a45d152 100644 --- a/src/headers/transpiler/implementations/Lua.hpp +++ b/src/headers/transpiler/implementations/Lua.hpp @@ -26,6 +26,25 @@ struct LuaTarget: Target { separate_transpileTree(parseComponent, 1); if (not parseComponent.empty()) output << separator; output << "end"; + ), + make_task(Condition::Statement, + output << "if ("; transpileTree(parseComponent.condition); output << ") then"; + if (not parseComponent.empty()) { + output << separator; if (newLines) output << indentation; } + separate_transpileTree(parseComponent, 1); + if (not parseComponent.else_branches.empty()) { + output << separator; + separate_transpileTree(parseComponent.else_branches); + } + output << separator << "end"; + ), + make_task(Condition::Statement::Branch, + if (parseComponent.is_conditional()) { + output << "elseif ("; transpileTree(parseComponent.condition); output << ") then"; + } else output << "else "; + output << separator; + if (newLines) output << indentation; + separate_transpileTree(parseComponent, 1); ) }; } diff --git a/src/headers/transpiler/implementations/Py.hpp b/src/headers/transpiler/implementations/Py.hpp index 16081a7..77aaef8 100644 --- a/src/headers/transpiler/implementations/Py.hpp +++ b/src/headers/transpiler/implementations/Py.hpp @@ -16,6 +16,27 @@ struct PyTarget: Target { if (parseComponent.empty()) output << "pass"; separate_transpileTree(parseComponent, 1); ), + make_task(Condition::Statement, + output << "if ("; transpileTree(parseComponent.condition); output << "):" << separator << indentation; + if (parseComponent.empty()) output << "pass"; + separate_transpileTree(parseComponent, 1); + if (not parseComponent.else_branches.empty()) { + output << separator; + separate_transpileTree(parseComponent.else_branches); + } + ), + make_task(Condition::Statement::Branch, + if (parseComponent.is_conditional()) { + output << "elif ("; transpileTree(parseComponent.condition); output << ")"; + } else output << "else"; + output << ':' << separator << indentation; + separate_transpileTree(parseComponent, 1); + ), + make_task(StandardComponents::Reference, + if (parseComponent.name == "true" or parseComponent.name == "false") { + output << (char) toupper(parseComponent.name.front()) << parseComponent.name.substr(1); + } else output << parseComponent.name; + ) }; } using Target::Target;