Skip to content

Commit

Permalink
Add semantic analysis visitor (#772)
Browse files Browse the repository at this point in the history
* Add new visitor to run semantic analysis related checks
 - Use log system instead of only throw
 - Add a new check: destructor blocks only in point process type mod file
* Always run Semantic analysis
* Added tests
  • Loading branch information
alkino committed Nov 26, 2021
1 parent 8543cc7 commit 4a67187
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 16 deletions.
4 changes: 3 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,9 @@ int main(int argc, const char* argv[]) {
/// Check some rules that ast should follow
{
logger->info("Running semantic analysis visitor");
SemanticAnalysisVisitor().visit_program(*ast);
if (SemanticAnalysisVisitor().check(*ast)) {
return 1;
}
}

/// construct symbol table
Expand Down
44 changes: 41 additions & 3 deletions src/visitors/semantic_analysis_visitor.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,69 @@
#include "visitors/semantic_analysis_visitor.hpp"
#include "ast/function_block.hpp"
#include "ast/procedure_block.hpp"
#include "ast/program.hpp"
#include "ast/suffix.hpp"
#include "ast/table_statement.hpp"
#include "utils/logger.hpp"
#include "visitors/visitor_utils.hpp"

namespace nmodl {
namespace visitor {

bool SemanticAnalysisVisitor::check(const ast::Program& node) {
check_fail = false;

/// <-- This code is for check 2
const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX});
if (!suffix_node.empty()) {
const auto& suffix = std::dynamic_pointer_cast<const ast::Suffix>(suffix_node[0]);
const auto& type = suffix->get_type()->get_node_name();
is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL");
}
/// -->

visit_program(node);
return check_fail;
}

void SemanticAnalysisVisitor::visit_procedure_block(const ast::ProcedureBlock& node) {
/// <-- This code is for check 1
in_procedure_function = true;
one_arg_in_procedure_function = node.get_parameters().size() == 1;
node.visit_children(*this);
in_procedure_function = false;
/// -->
}

void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& node) {
/// <-- This code is for check 1
in_procedure_function = true;
one_arg_in_procedure_function = node.get_parameters().size() == 1;
node.visit_children(*this);
in_procedure_function = false;
/// -->
}

void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement&) {
/// <-- This code is for check 1
if (in_procedure_function && !one_arg_in_procedure_function) {
throw std::runtime_error(
"The procedure or function containing the TABLE statement should contains exactly one "
"argument.");
logger->critical(
"SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement "
"should contains exactly one argument.");
check_fail = true;
}
/// -->
}

void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& node) {
/// <-- This code is for check 2
if (!is_point_process) {
logger->warn(
"SemanticAnalysisVisitor :: This mod file is not point process but contains a "
"destructor.");
check_fail = true;
}
/// -->
}

} // namespace visitor
Expand Down
21 changes: 17 additions & 4 deletions src/visitors/semantic_analysis_visitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
*
* Current checks:
*
* - Check that a function or a procedure containing a TABLE statement contains only one argument
* 1. Check that a function or a procedure containing a TABLE statement contains only one argument
* (mandatory in mod2c).
* 2. Check that destructor blocks are only inside mod file that are point_process
*/
#include "ast/ast.hpp"
#include "visitors/ast_visitor.hpp"
Expand All @@ -34,19 +35,31 @@ namespace visitor {

class SemanticAnalysisVisitor: public ConstAstVisitor {
private:
bool check_fail = false;

/// true if the procedure or the function contains only one argument
bool one_arg_in_procedure_function = false;
/// true if we are in a procedure or a function block
bool in_procedure_function = false;
/// true if the mod file is of type point process
bool is_point_process = false;

public:
SemanticAnalysisVisitor() = default;

/// Store if we are in a procedure and if the arity of this is 1
void visit_procedure_block(const ast::ProcedureBlock& node) override;

/// Store if we are in a function and if the arity of this is 1
void visit_function_block(const ast::FunctionBlock& node) override;

/// Visit a table statement and check that the arity of the block were 1
void visit_table_statement(const ast::TableStatement& node) override;

/// Visit destructor and check that the file is of type POINT_PROCESS or ARTIFICIAL_CELL
void visit_destructor_block(const ast::DestructorBlock& node) override;

public:
SemanticAnalysisVisitor() = default;

bool check(const ast::Program& node);
};

/** \} */ // end of visitor_classes
Expand Down
57 changes: 49 additions & 8 deletions test/unit/visitor/semantic_analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ using nmodl::parser::NmodlDriver;
// Procedure/Function inlining tests
//=============================================================================

void run_semantic_analysis_visitor(const std::string& text) {
bool run_semantic_analysis_visitor(const std::string& text) {
NmodlDriver driver;
const auto& ast = driver.parse_string(text);
SemanticAnalysisVisitor().visit_program(*ast);
return SemanticAnalysisVisitor{}.check(*ast);
}

SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") {
Expand All @@ -37,8 +37,8 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") {
ainf = 1
}
)";
THEN("throw") {
REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text));
THEN("fail") {
REQUIRE(run_semantic_analysis_visitor(nmodl_text));
}
}
GIVEN("Procedure with exactly one argument") {
Expand All @@ -48,8 +48,8 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") {
ainf = 1
}
)";
THEN("no throw") {
REQUIRE_NOTHROW(run_semantic_analysis_visitor(nmodl_text));
THEN("pass") {
REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text));
}
}
GIVEN("Procedure with less than one argument") {
Expand All @@ -59,8 +59,49 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") {
ainf = 1
}
)";
THEN("throw") {
REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text));
THEN("fail") {
REQUIRE(run_semantic_analysis_visitor(nmodl_text));
}
}
}

SCENARIO("Destructor block", "[visitor][semantic_analysis]") {
GIVEN("A point-process mod file, with a destructor") {
std::string nmodl_text = R"(
DESTRUCTOR { : Destructor is before
}
NEURON {
POINT_PROCESS test
}
)";
THEN("pass") {
REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text));
}
}
GIVEN("A artifial-cell mod file, with a destructor") {
std::string nmodl_text = R"(
NEURON {
ARTIFICIAL_CELL test
}
DESTRUCTOR {
}
)";
THEN("pass") {
REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text));
}
}
GIVEN("A non point-process mod file, with a destructor") {
std::string nmodl_text = R"(
NEURON {
}
DESTRUCTOR {
}
)";
THEN("fail") {
REQUIRE(run_semantic_analysis_visitor(nmodl_text));
}
}
}

0 comments on commit 4a67187

Please sign in to comment.