From ceb58abd4becfa9c663312f2588122f010092116 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0torc?= <storcond@fit.cvut.cz>
Date: Sat, 15 Apr 2023 13:40:00 +0200
Subject: [PATCH] cli: Handle command level in grammar

Remove all logic related to handling of command scope from visitor.
The handling is done in the grammar and in the parser.
No tricks with parser actions are used.
---
 alib2cli/src/grammar/AltCliParser.g4         | 24 +++++++++-----
 alib2cli/src/parser/AltVisitor.Command.cpp   | 28 ++++------------
 alib2cli/src/parser/AltVisitor.h             | 35 --------------------
 alib2cli/test-src/aql/InvalidGrammarTest.cpp | 14 ++++----
 alib2cli/test-src/cli/AutocompleteTest.cpp   |  2 +-
 5 files changed, 30 insertions(+), 73 deletions(-)

diff --git a/alib2cli/src/grammar/AltCliParser.g4 b/alib2cli/src/grammar/AltCliParser.g4
index 088e9052b..41a5dd7a2 100644
--- a/alib2cli/src/grammar/AltCliParser.g4
+++ b/alib2cli/src/grammar/AltCliParser.g4
@@ -6,7 +6,7 @@ options {
 
 // Initial rule
 parse
-	: NEWLINE* command (NEWLINE command?)* EOF
+	: NEWLINE* top_level_command (NEWLINE top_level_command?)* EOF
 	| NEWLINE* EOF;
 
 arg
@@ -80,7 +80,7 @@ semicolon_command
 	:	block # BlockSemicolonCommand
 	| 	command_if # IfSemicolonCommand
 	| 	command_while # WhileSemicolonCommand
-	|	command SEMICOLON # SemicolonCommand
+	|	nested_level_command SEMICOLON # SemicolonCommand
 	;
 
 introspect_cast_from_to
@@ -97,7 +97,7 @@ introspect_command
 	|	KW_DENORMALIZATIONS # IntrospectDeormalizations
 	|	KW_VARIABLES variable? # IntrospectVariables
 	|	KW_BINDINGS binding_name? # IntrospectBindings
-	|	KW_AST command # IntrospectAst
+	|	KW_AST top_level_command # IntrospectAst
 	;
 
 batch
@@ -147,13 +147,21 @@ command
 	|	block # BlockCommand
 	|	KW_EVAL ( INTEGER | identifier | string ) # Eval
 	|	KW_INTERPRET ( identifier | string ) # Interpret
-	|	command_if # If
+	|	KW_DECLARE qualified_type DOLAR_SIGN arg ASSIGN_OPERATOR expression # Declare // Should be top-level command?
+	;
+
+nested_level_command
+	: 	command # NestedLevelCommand
+	| 	command_if # If
 	|	command_while # While
 	|	(KW_BREAK | KW_CONTINUE) # CycleControl
-	|	KW_DECLARE qualified_type DOLAR_SIGN arg ASSIGN_OPERATOR expression # Declare
-	|   KW_UNDECLARE ( INTEGER | identifier | string ) LEFT_PAREN (qualified_type (COMMA qualified_type)*)? RIGHT_PAREN # UndeclareFunction
-	|	KW_PROCEDURE ( INTEGER | identifier | string ) LEFT_PAREN ( (runnable_param ( COMMA runnable_param )*)? ) RIGHT_PAREN command # Procedure
-	|	KW_FUNCTION ( INTEGER | identifier | string ) LEFT_PAREN ( (runnable_param ( COMMA runnable_param )*)? ) RIGHT_PAREN KW_RETURNING qualified_type command # Function
+	;
+
+top_level_command
+	:	command # TopLevelCommand
+	| 	KW_UNDECLARE ( INTEGER | identifier | string ) LEFT_PAREN (qualified_type (COMMA qualified_type)*)? RIGHT_PAREN # UndeclareFunction
+	|	KW_PROCEDURE ( INTEGER | identifier | string ) LEFT_PAREN ( (runnable_param ( COMMA runnable_param )*)? ) RIGHT_PAREN nested_level_command # Procedure
+	|	KW_FUNCTION ( INTEGER | identifier | string ) LEFT_PAREN ( (runnable_param ( COMMA runnable_param )*)? ) RIGHT_PAREN KW_RETURNING qualified_type nested_level_command # Function
 	;
 
 expression
diff --git a/alib2cli/src/parser/AltVisitor.Command.cpp b/alib2cli/src/parser/AltVisitor.Command.cpp
index b219d8433..1d3550465 100644
--- a/alib2cli/src/parser/AltVisitor.Command.cpp
+++ b/alib2cli/src/parser/AltVisitor.Command.cpp
@@ -40,8 +40,6 @@ using QualifedType = std::pair<abstraction::TypeQualifiers::TypeQualifierSet, st
 
 std::any AltVisitor::visitIfCommand(AltCliParser::IfCommandContext* ctx)
 {
-    ensureScope("if", Scope::Local);
-
     auto expression = castToUnique<Expression>(visit(ctx->condition));
     auto thenBranch = castToUnique<Command>(visit(ctx->then_branch));
 
@@ -55,8 +53,6 @@ std::any AltVisitor::visitIfCommand(AltCliParser::IfCommandContext* ctx)
 
 std::any AltVisitor::visitWhileCommand(AltCliParser::WhileCommandContext* ctx)
 {
-    ensureScope("while", Scope::Local);
-
     auto condition = castToUnique<Expression>(visit(ctx->condition));
     auto body = castToUnique<Command>(visit(ctx->body));
 
@@ -67,10 +63,10 @@ std::any AltVisitor::visitParse(AltCliParser::ParseContext* ctx)
 {
     ext::vector<std::unique_ptr<Command>> commands;
 
-    if (ctx->command().empty()) {
+    if (ctx->top_level_command().empty()) {
         commands.emplace_back(new EOTCommand());
     } else {
-        fillList<Command>(commands, ctx->command());
+        fillList<Command>(commands, ctx->top_level_command());
     }
 
     return new CommandList(std::move(commands));
@@ -85,13 +81,9 @@ std::any AltVisitor::visitPrint(AltCliParser::PrintContext* ctx)
 
 std::any AltVisitor::visitBlock(AltCliParser::BlockContext* ctx)
 {
-    incNestedLevel();
-
     ext::vector<std::unique_ptr<Command>> list;
-
     fillList<Command>(list, ctx->semicolon_command());
 
-    decNestedLevel();
     return retPtr<Command, CommandList>(std::move(list));
 }
 
@@ -117,7 +109,7 @@ std::any AltVisitor::visitWhileSemicolonCommand(AltCliParser::WhileSemicolonComm
 
 std::any AltVisitor::visitSemicolonCommand(AltCliParser::SemicolonCommandContext* ctx)
 {
-    return visit(ctx->command());
+    return visit(ctx->nested_level_command());
 }
 
 std::any AltVisitor::visitQuit(AltCliParser::QuitContext* ctx)
@@ -140,8 +132,6 @@ std::any AltVisitor::visitReturn(AltCliParser::ReturnContext* ctx)
 
 std::any AltVisitor::visitCycleControl(AltCliParser::CycleControlContext* ctx)
 {
-    ensureScope("break/continue", Scope::Local);
-
     if (ctx->KW_BREAK() != nullptr)
         return retPtr<Command, BreakCommand>();
 
@@ -209,8 +199,6 @@ std::any AltVisitor::visitHelp(AltCliParser::HelpContext* ctx)
 
 std::any AltVisitor::visitFunction(AltCliParser::FunctionContext* ctx)
 {
-    ensureScope("function", Scope::Global);
-
     std::string name;
     if (ctx->string() != nullptr)
         name = cast<std::string>(visit(ctx->string()));
@@ -223,15 +211,13 @@ std::any AltVisitor::visitFunction(AltCliParser::FunctionContext* ctx)
 
     auto params = visitRunnableParams(ctx->runnable_param());
     auto retType = visitQualifiedType(ctx->qualified_type());
-    auto body = castToUnique<Command>(visit(ctx->command()));
+    auto body = castToUnique<Command>(visit(ctx->nested_level_command()));
 
     return retPtr<Command, DeclareRunnableCommand>(std::move(name), std::move(params), std::move(retType), std::move(body));
 }
 
 std::any AltVisitor::visitProcedure(AltCliParser::ProcedureContext* ctx)
 {
-    ensureScope("procedure", Scope::Global);
-
     std::string name;
     if (ctx->string() != nullptr)
         name = cast<std::string>(visit(ctx->string()));
@@ -243,15 +229,13 @@ std::any AltVisitor::visitProcedure(AltCliParser::ProcedureContext* ctx)
         invalidParse("Invalid procedure name");
 
     auto params = visitRunnableParams(ctx->runnable_param());
-    auto body = castToUnique<Command>(visit(ctx->command()));
+    auto body = castToUnique<Command>(visit(ctx->nested_level_command()));
 
     return retPtr<Command, DeclareRunnableCommand>(std::move(name), std::move(params), std::move(body));
 }
 
 std::any AltVisitor::visitUndeclareFunction(AltCliParser::UndeclareFunctionContext* ctx)
 {
-    ensureScope("undeclare", Scope::Global);
-
     std::string name;
     if (ctx->identifier() != nullptr)
         name = ctx->identifier()->getText();
@@ -400,7 +384,7 @@ std::any AltVisitor::visitCalc(AltCliParser::CalcContext* ctx)
 
 std::any AltVisitor::visitIntrospectAst(AltCliParser::IntrospectAstContext* ctx)
 {
-    auto command = castToUnique<Command>(visit(ctx->command()));
+    auto command = castToUnique<Command>(visit(ctx->top_level_command()));
 
     return retPtr<Command, AstIntrospectionCommand>(std::move(command));
 }
diff --git a/alib2cli/src/parser/AltVisitor.h b/alib2cli/src/parser/AltVisitor.h
index 718fe2ea9..78577768a 100644
--- a/alib2cli/src/parser/AltVisitor.h
+++ b/alib2cli/src/parser/AltVisitor.h
@@ -193,41 +193,6 @@ struct AltVisitor : public AltCliParserBaseVisitor {
 
     std::any visitContainerStatement(AltCliParser::ContainerStatementContext* ctx) override;
 
-private:
-    enum class Scope {
-        Global,
-        Local,
-    };
-
-    size_t nestedLevel = 0;
-
-    void incNestedLevel()
-    {
-        nestedLevel++;
-    }
-
-    void decNestedLevel()
-    {
-        nestedLevel--;
-    }
-
-    Scope isGlobalScope() const
-    {
-        return nestedLevel == 0 ? Scope::Global : Scope::Local;
-    }
-
-    void ensureScope(const std::string& commandName, const Scope& scope, const std::source_location location = std::source_location::current()) const
-    {
-        if (scope != isGlobalScope()) {
-            std::string scopeName = scope == Scope::Global ? "global" : "local";
-            std::ostringstream out;
-            out << "Invalid scope for " << commandName << " command. Expected scope was '" << scopeName << "'." << std::endl;
-            out << "At " << location.file_name() << ":" << location.line() << std::endl;
-            out << "\t" << location.function_name() << std::endl;
-            throw std::runtime_error(out.str());
-        }
-    }
-
 protected:
     std::any defaultResult() override;
 
diff --git a/alib2cli/test-src/aql/InvalidGrammarTest.cpp b/alib2cli/test-src/aql/InvalidGrammarTest.cpp
index e329bec63..55fdc5d08 100644
--- a/alib2cli/test-src/aql/InvalidGrammarTest.cpp
+++ b/alib2cli/test-src/aql/InvalidGrammarTest.cpp
@@ -13,44 +13,44 @@ TEST_CASE("Invalid grammar - Scope")
 {
     SECTION("Undeclare")
     {
-        CHECK_THROWS_AS(newParseString("begin undeclare a (); end"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("begin undeclare a (); end"), exception::CommonException);
         CHECK_NOTHROW(newParseString("undeclare a ()"));
     }
 
     SECTION("If")
     {
-        CHECK_THROWS_AS(newParseString("if (1) then begin print 1; end"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("if (1) then begin print 1; end"), exception::CommonException);
         CHECK_NOTHROW(newParseString("begin if (1) then print 1; end"));
     }
 
 
     SECTION("While")
     {
-        CHECK_THROWS_AS(newParseString("while (1) do begin print 1; end"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("while (1) do begin print 1; end"), exception::CommonException);
         CHECK_NOTHROW(newParseString("begin while (1) do print 1; end"));
     }
 
     SECTION("Break")
     {
-        CHECK_THROWS_AS(newParseString("break"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("break"), exception::CommonException);
         CHECK_NOTHROW(newParseString("begin break; end"));
     }
 
     SECTION("Continue")
     {
-        CHECK_THROWS_AS(newParseString("continue"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("continue"), exception::CommonException);
         CHECK_NOTHROW(newParseString("begin continue; end"));
     }
 
     SECTION("Function")
     {
-        CHECK_THROWS_AS(newParseString("begin function func () returning auto return 1; end"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("begin function func () returning auto return 1; end"), exception::CommonException);
         CHECK_NOTHROW(newParseString("function func () returning auto return 1"));
     }
 
     SECTION("Procedure")
     {
-        CHECK_THROWS_AS(newParseString("begin procedure func () print 1; end"), std::runtime_error);
+        CHECK_THROWS_AS(newParseString("begin procedure func () print 1; end"), exception::CommonException);
         CHECK_NOTHROW(newParseString("procedure func () print 1"));
     }
 }
diff --git a/alib2cli/test-src/cli/AutocompleteTest.cpp b/alib2cli/test-src/cli/AutocompleteTest.cpp
index 7d815e7da..1f212c37d 100644
--- a/alib2cli/test-src/cli/AutocompleteTest.cpp
+++ b/alib2cli/test-src/cli/AutocompleteTest.cpp
@@ -10,7 +10,7 @@ TEST_CASE("Autocomplete")
 
         auto suggestions = autocomplete.getSuggestions("", "");
 
-        CHECK(suggestions.size() == 26);
+        CHECK(suggestions.size() == 22);
     }
 
     SECTION("pri")
-- 
GitLab