User Guide
semantic_analysis_visitor.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Blue Brain Project, EPFL.
3  * See the top-level LICENSE file for details.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
10 #include "ast/function_block.hpp"
11 #include "ast/function_call.hpp"
14 #include "ast/procedure_block.hpp"
15 #include "ast/program.hpp"
16 #include "ast/range_var.hpp"
17 #include "ast/statement_block.hpp"
18 #include "ast/string.hpp"
19 #include "ast/suffix.hpp"
20 #include "ast/table_statement.hpp"
22 #include "utils/logger.hpp"
24 
25 namespace nmodl {
26 namespace visitor {
27 
28 
30  // check that we do not have any duplicate TABLE statement variables in PROCEDUREs
31  const auto& procedure_nodes = collect_nodes(node, {ast::AstNodeType::PROCEDURE_BLOCK});
32  std::unordered_set<std::string> procedure_vars{};
33  for (const auto& proc_node: procedure_nodes) {
34  const auto& table_nodes = collect_nodes(*proc_node, {ast::AstNodeType::TABLE_STATEMENT});
35  for (const auto& table_node: table_nodes) {
36  const auto& table_vars =
37  std::dynamic_pointer_cast<const ast::TableStatement>(table_node)->get_table_vars();
38  for (const auto& table_var: table_vars) {
39  const auto& [var_name,
40  inserted] = procedure_vars.insert(table_var->get_node_name());
41  if (!inserted) {
42  logger->critical(
43  fmt::format("SemanticAnalysisVisitor :: TABLE statement variable {} used "
44  "in multiple tables",
45  *var_name));
46  check_fail = true;
47  }
48  }
49  }
50  }
51 
52  return check_fail;
53 }
54 
55 
57  // check that there are no RANGE variables which have the same name as a FUNCTION or PROCEDURE
58  const auto& range_nodes = collect_nodes(node, {ast::AstNodeType::RANGE_VAR});
59  std::set<std::string> range_vars{};
60  for (const auto& range_node: range_nodes) {
61  range_vars.insert(range_node->get_node_name());
62  }
63 
64  const auto& function_nodes =
66  std::set<std::string> func_vars{};
67  for (const auto& function_node: function_nodes) {
68  func_vars.insert(function_node->get_node_name());
69  }
70 
71  std::vector<std::string> result;
72  std::set_intersection(func_vars.begin(),
73  func_vars.end(),
74  range_vars.begin(),
75  range_vars.end(),
76  std::back_inserter(result));
77  return !result.empty();
78 }
79 
81  check_fail = false;
83 
84  /// <-- This code is for check 2
85  const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX});
86  if (!suffix_node.empty()) {
87  const auto& suffix = std::dynamic_pointer_cast<const ast::Suffix>(suffix_node[0]);
88  const auto& type = suffix->get_type()->get_node_name();
89  is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL");
90  }
91  /// -->
92 
94 
95  /// <-- This code is for check 4
96  using namespace symtab::syminfo;
97  const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var;
98 
99  const auto& sym_table = node.get_symbol_table();
100  assert(sym_table != nullptr);
101 
102  // get all ion variables
103  const auto& ion_variables = sym_table->get_variables_with_properties(with_prop, false);
104 
105  /// make sure ion variables aren't redefined in a `CONSTANT` block.
106  for (const auto& var: ion_variables) {
107  if (var->has_any_property(NmodlType::constant_var)) {
108  logger->critical(
109  fmt::format("SemanticAnalysisVisitor :: ion variable {} from the USEION statement "
110  "can not be re-declared in a CONSTANT block",
111  var->get_name()));
112  check_fail = true;
113  }
114  }
115  /// -->
116 
117  visit_program(node);
118  return check_fail;
119 }
120 
122  /// <-- This code is for check 8
123  const auto& derivative_block_nodes = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK});
124  if (derivative_block_nodes.size() > 1) {
125  logger->critical("It is not supported to have several DERIVATIVE blocks");
126  check_fail = true;
127  }
128  /// -->
129  node.visit_children(*this);
130 }
131 
133  /// <-- This code is for check 1
134  in_procedure = true;
135  one_arg_in_procedure_function = node.get_parameters().size() == 1;
136  node.visit_children(*this);
137  in_procedure = false;
138  /// -->
139 }
140 
142  /// <-- This code is for check 1
143  in_function = true;
144  one_arg_in_procedure_function = node.get_parameters().size() == 1;
145  node.visit_children(*this);
146  in_function = false;
147  /// -->
148 }
149 
151  /// <-- This code is a portion of check 9
152  // There are only two contexts where a random_var is allowed. As the first arg of a random
153  // function or as an item in the RANDOM declaration.
154  // Only the former needs checking.
155  auto name = node.get_node_name();
156 
157  // only check for variables exist in the symbol table (e.g. SUFFIX has type Name but it's not
158  // variable)
159  // if variable is not RANDOM then nothing to check for it
160  auto symbol = program_symtab->lookup(name);
161  if (!symbol || !symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
162  return;
163  }
164 
165  auto parent = node.get_parent();
166 
167  // if it's RANDOM var declaration in NEURON block then nothing to do
168  if (parent && parent->is_random_var()) {
169  return;
170  }
171 
172  if (parent && parent->is_var_name()) {
173  parent = parent->get_parent();
174  if (parent && parent->is_function_call()) {
175  auto fname = parent->get_node_name();
176  // if function is a random function then check if the current
177  // name is the function's first argument
178  if (is_random_construct_function(fname)) {
179  auto rfun = dynamic_cast<ast::FunctionCall*>(parent);
180  const auto& arguments = rfun->get_arguments();
181  if (!arguments.empty() && arguments.front()->is_var_name() &&
182  arguments.front()->get_node_name() == name) {
183  // if this is a first argument to function then there
184  // is no problem
185  node.visit_children(*this);
186  return;
187  }
188  }
189  }
190  }
191 
192  // Otherwise, we have an error
193  auto position = node.get_token()->position();
194  logger->critical(
195  fmt::format("SemanticAnalysisVisitor :: RANDOM variable {} at {}"
196  " can be used only as the first arg of a random function",
197  node.get_node_name(),
198  position));
199  check_fail = true;
200 
201  node.visit_children(*this);
202  /// -->
203 }
204 
206  /// <-- This code is a portion of check 9
207  // The first arg of a RANDOM function must be a random_var
208  // Otherwise it's an error
209  auto fname = node.get_node_name();
210  auto position = node.get_name()->get_token()->position();
211 
212  if (is_random_construct_function(fname)) {
213  const auto& arguments = node.get_arguments();
214  if (!arguments.empty()) {
215  auto arg0 = arguments.front();
216  if (arg0->is_var_name()) {
217  auto name = arg0->get_node_name();
218  auto symbol = program_symtab->lookup(name);
219  if (symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
220  node.visit_children(*this);
221  return;
222  }
223  }
224  }
225  logger->critical(
226  fmt::format("SemanticAnalysisVisitor :: random function {} at {} :: The first arg must "
227  "be a random variable",
228  fname,
229  position));
230  check_fail = true;
231  }
232 
233  if (is_nrn_pointing(fname)) {
234  if (size_t args_size = node.get_arguments().size(); args_size != 1) {
235  logger->critical(
236  fmt::format("nrn_pointing excepts exactly one argument, got: {}", args_size));
237  check_fail = true;
238  }
239  }
240 
241  node.visit_children(*this);
242  /// -->
243 }
244 
246  /// <-- This code is for check 1
248  logger->critical(
249  "SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement "
250  "should contains exactly one argument.");
251  check_fail = true;
252  }
253  /// -->
254  /// <-- This code is for check 3
255  const auto& table_vars = tableStmt.get_table_vars();
256  if (in_function && !table_vars.empty()) {
257  logger->critical(
258  "SemanticAnalysisVisitor :: TABLE statement in FUNCTION cannot have a table name "
259  "list.");
260  }
261  if (in_procedure && table_vars.empty()) {
262  logger->critical(
263  "SemanticAnalysisVisitor :: TABLE statement in PROCEDURE must have a table name list.");
264  }
265  /// -->
266 }
267 
269  /// <-- This code is for check 2
270  if (!is_point_process) {
271  logger->warn(
272  "SemanticAnalysisVisitor :: This mod file is not point process but contains a "
273  "destructor.");
274  check_fail = true;
275  }
276  /// -->
277 }
278 
280  /// <-- This code is for check 5
281  for (const auto& n: node.get_variables()) {
282  if (n->get_value()->get_value() != "t") {
283  logger->warn(
284  "SemanticAnalysisVisitor :: '{}' cannot be used as an independent variable, only "
285  "'t' is allowed.",
286  n->get_value()->get_value());
287  }
288  }
289  /// -->
290 }
291 
293  /// <-- This code is for check 7
294  if (node.get_parameters().size() < 1) {
295  logger->critical(
296  "SemanticAnalysisVisitor :: Function table '{}' must have one or more arguments.",
297  node.get_node_name());
298  check_fail = true;
299  }
300  /// -->
301 }
302 
304  /// <-- This code is for check 6
305  if (accel_backend) {
306  logger->error("PROTECT statement is not supported with GPU execution");
307  }
308  if (in_mutex) {
309  logger->warn("SemanticAnalysisVisitor :: Find a PROTECT inside a already locked part.");
310  }
311  /// -->
312 }
313 
315  /// <-- This code is for check 6
316  if (accel_backend) {
317  logger->error("MUTEXLOCK statement is not supported with GPU execution");
318  }
319  if (in_mutex) {
320  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXLOCK inside an already locked part.");
321  }
322  in_mutex = true;
323  /// -->
324 }
325 
327  /// <-- This code is for check 6
328  if (accel_backend) {
329  logger->error("MUTEXUNLOCK statement is not supported with GPU execution");
330  }
331  if (!in_mutex) {
332  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXUNLOCK outside a locked part.");
333  }
334  in_mutex = false;
335  /// -->
336 }
337 
338 } // namespace visitor
339 } // namespace nmodl
nmodl::ast::FunctionCall::get_node_name
std::string get_node_name() const override
Return name of the node.
Definition: ast.cpp:7065
nmodl::ast::AstNodeType::DERIVATIVE_BLOCK
@ DERIVATIVE_BLOCK
type of ast::DerivativeBlock
nmodl::visitor::SemanticAnalysisVisitor::visit_function_table_block
void visit_function_table_block(const ast::FunctionTableBlock &node) override
Visit function table to check that number of args > 0.
Definition: semantic_analysis_visitor.cpp:292
nmodl::ast::AstNodeType::FUNCTION_BLOCK
@ FUNCTION_BLOCK
type of ast::FunctionBlock
nmodl::ast::ProcedureBlock::get_parameters
const ArgumentVector & get_parameters() const noexcept override
Getter for member variable ProcedureBlock::parameters.
Definition: procedure_block.hpp:201
nmodl::ast::Name::visit_children
void visit_children(visitor::Visitor &v) override
visit children i.e.
Definition: ast.cpp:799
nmodl::visitor::SemanticAnalysisVisitor::check_name_conflict
bool check_name_conflict(const ast::Program &node)
Definition: semantic_analysis_visitor.cpp:56
nmodl::ast::FunctionBlock
TODO.
Definition: function_block.hpp:39
semantic_analysis_visitor.hpp
Visitor to check some semantic rules on the AST
nmodl::visitor::SemanticAnalysisVisitor::visit_mutex_unlock
void visit_mutex_unlock(const ast::MutexUnlock &node) override
Look if MUTEXUNLOCK is outside a locked block.
Definition: semantic_analysis_visitor.cpp:326
nmodl::ast::FunctionTableBlock::get_parameters
const ArgumentVector & get_parameters() const noexcept override
Getter for member variable FunctionTableBlock::parameters.
Definition: function_table_block.hpp:199
nmodl::visitor::SemanticAnalysisVisitor::in_mutex
bool in_mutex
true if we are inside a mutex locked part
Definition: semantic_analysis_visitor.hpp:61
nmodl::visitor::SemanticAnalysisVisitor::in_function
bool in_function
true if we are in a function block
Definition: semantic_analysis_visitor.hpp:57
nmodl::ast::FunctionTableBlock
TODO.
Definition: function_table_block.hpp:39
nmodl::visitor::SemanticAnalysisVisitor::check
bool check(const ast::Program &node)
Definition: semantic_analysis_visitor.cpp:80
nmodl::visitor::SemanticAnalysisVisitor::accel_backend
bool accel_backend
true if accelerator backend is used for code generation
Definition: semantic_analysis_visitor.hpp:51
nmodl::ast::FunctionCall::get_name
std::shared_ptr< Name > get_name() const noexcept
Getter for member variable FunctionCall::name.
Definition: function_call.hpp:157
nmodl
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
function_table_block.hpp
Auto generated AST classes declaration.
nmodl::ast::MutexLock
Represent MUTEXLOCK statement in NMODL.
Definition: mutex_lock.hpp:38
nmodl::ast::TableStatement
Represents TABLE statement in NMODL.
Definition: table_statement.hpp:39
symbol_properties.hpp
Implement various classes to represent various Symbol properties.
nmodl::ast::FunctionBlock::visit_children
void visit_children(visitor::Visitor &v) override
visit children i.e.
Definition: ast.cpp:3980
nmodl::visitor::SemanticAnalysisVisitor::visit_protect_statement
void visit_protect_statement(const ast::ProtectStatement &node) override
Look if protect is inside a locked block.
Definition: semantic_analysis_visitor.cpp:303
nmodl::ast::ProcedureBlock::visit_children
void visit_children(visitor::Visitor &v) override
visit children i.e.
Definition: ast.cpp:4177
nmodl::logger
logger_type logger
Definition: logger.cpp:34
nmodl::is_random_construct_function
bool is_random_construct_function(const std::string &name)
Is given name a one of the function for RANDOM construct.
Definition: visitor_utils.cpp:297
nmodl::visitor::SemanticAnalysisVisitor::visit_name
void visit_name(const ast::Name &node) override
Only use of random_var is as first arg in random function.
Definition: semantic_analysis_visitor.cpp:150
procedure_block.hpp
Auto generated AST classes declaration.
nmodl::visitor::SemanticAnalysisVisitor::visit_independent_block
void visit_independent_block(const ast::IndependentBlock &node) override
Visit independent block and check if one of the variable is not t.
Definition: semantic_analysis_visitor.cpp:279
nmodl::ast::Name::get_token
const ModToken * get_token() const noexcept override
Return associated token for the current ast node.
Definition: name.hpp:141
nmodl::visitor::SemanticAnalysisVisitor::program_symtab
symtab::SymbolTable * program_symtab
Definition: semantic_analysis_visitor.hpp:49
nmodl::ModToken::position
std::string position() const
Return position of the token as string.
Definition: modtoken.cpp:14
nmodl::ast::FunctionTableBlock::get_node_name
std::string get_node_name() const override
Return name of the node.
Definition: ast.cpp:3804
nmodl::ast::AstNodeType::PROCEDURE_BLOCK
@ PROCEDURE_BLOCK
type of ast::ProcedureBlock
string.hpp
Auto generated AST classes declaration.
visitor_utils.hpp
Utility functions for visitors implementation.
breakpoint_block.hpp
Auto generated AST classes declaration.
program.hpp
Auto generated AST classes declaration.
statement_block.hpp
Auto generated AST classes declaration.
nmodl::ast::FunctionBlock::get_parameters
const ArgumentVector & get_parameters() const noexcept override
Getter for member variable FunctionBlock::parameters.
Definition: function_block.hpp:201
range_var.hpp
Auto generated AST classes declaration.
nmodl::ast::IndependentBlock::get_variables
const NameVector & get_variables() const noexcept
Getter for member variable IndependentBlock::variables.
Definition: independent_block.hpp:180
nmodl::ast::FunctionCall
TODO.
Definition: function_call.hpp:38
nmodl::visitor::SemanticAnalysisVisitor::in_procedure
bool in_procedure
true if we are in a procedure block
Definition: semantic_analysis_visitor.hpp:55
nmodl::ast::DestructorBlock
Represents a DESTRUCTOR block in the NMODL.
Definition: destructor_block.hpp:53
nmodl::visitor::SemanticAnalysisVisitor::check_table_vars
bool check_table_vars(const ast::Program &node)
Definition: semantic_analysis_visitor.cpp:29
independent_block.hpp
Auto generated AST classes declaration.
nmodl::ast::ProtectStatement
TODO.
Definition: protect_statement.hpp:38
nmodl::visitor::SemanticAnalysisVisitor::visit_procedure_block
void visit_procedure_block(const ast::ProcedureBlock &node) override
Store if we are in a procedure and if the arity of this is 1.
Definition: semantic_analysis_visitor.cpp:132
nmodl::ast::MutexUnlock
Represent MUTEXUNLOCK statement in NMODL.
Definition: mutex_unlock.hpp:38
nmodl::ast::AstNodeType::SUFFIX
@ SUFFIX
type of ast::Suffix
nmodl::visitor::SemanticAnalysisVisitor::check_fail
bool check_fail
Definition: semantic_analysis_visitor.hpp:47
nmodl::is_nrn_pointing
bool is_nrn_pointing(const std::string &name)
Is given name nrn_pointing.
Definition: visitor_utils.cpp:301
nmodl::ast::Program::visit_children
void visit_children(visitor::Visitor &v) override
visit children i.e.
Definition: ast.cpp:12906
suffix.hpp
Auto generated AST classes declaration.
nmodl::ast::FunctionCall::get_arguments
const ExpressionVector & get_arguments() const noexcept
Getter for member variable FunctionCall::arguments.
Definition: function_call.hpp:166
nmodl::visitor::SemanticAnalysisVisitor::visit_mutex_lock
void visit_mutex_lock(const ast::MutexLock &node) override
Look if MUTEXLOCK is inside a locked block.
Definition: semantic_analysis_visitor.cpp:314
nmodl::collect_nodes
std::vector< std::shared_ptr< const ast::Ast > > collect_nodes(const ast::Ast &node, const std::vector< ast::AstNodeType > &types)
traverse node recursively and collect nodes of given types
Definition: visitor_utils.cpp:206
function_block.hpp
Auto generated AST classes declaration.
function_call.hpp
Auto generated AST classes declaration.
nmodl::ast::TableStatement::get_table_vars
const NameVector & get_table_vars() const noexcept
Getter for member variable TableStatement::table_vars.
Definition: table_statement.hpp:166
nmodl::ast::ProcedureBlock
TODO.
Definition: procedure_block.hpp:39
nmodl::visitor::SemanticAnalysisVisitor::visit_table_statement
void visit_table_statement(const ast::TableStatement &node) override
Visit a table statement and check that the arity of the block were 1.
Definition: semantic_analysis_visitor.cpp:245
nmodl::ast::FunctionCall::visit_children
void visit_children(visitor::Visitor &v) override
visit children i.e.
Definition: ast.cpp:7072
nmodl::ast::Name::get_node_name
std::string get_node_name() const override
Return name of the node.
Definition: ast.cpp:795
logger.hpp
Implement logger based on spdlog library.
nmodl::visitor::SemanticAnalysisVisitor::one_arg_in_procedure_function
bool one_arg_in_procedure_function
true if the procedure or the function contains only one argument
Definition: semantic_analysis_visitor.hpp:53
nmodl::visitor::SemanticAnalysisVisitor::visit_function_call
void visit_function_call(const ast::FunctionCall &node) override
random function first arg must be random_var
Definition: semantic_analysis_visitor.cpp:205
nmodl::visitor::SemanticAnalysisVisitor::is_point_process
bool is_point_process
true if the mod file is of type point process
Definition: semantic_analysis_visitor.hpp:59
nmodl::ast::Program::get_symbol_table
symtab::SymbolTable * get_symbol_table() const override
Return associated symbol table for the current ast node.
Definition: program.hpp:153
nmodl::ast::IndependentBlock
Represents a INDEPENDENT block in the NMODL.
Definition: independent_block.hpp:46
nmodl::visitor::SemanticAnalysisVisitor::visit_function_block
void visit_function_block(const ast::FunctionBlock &node) override
Store if we are in a function and if the arity of this is 1.
Definition: semantic_analysis_visitor.cpp:141
nmodl::ast::Name
Represents a name.
Definition: name.hpp:44
nmodl::ast::Program
Represents top level AST node for whole NMODL input.
Definition: program.hpp:39
nmodl::symtab::syminfo::NmodlType::random_var
@ random_var
Randomvar Type.
nmodl::visitor::SemanticAnalysisVisitor::visit_destructor_block
void visit_destructor_block(const ast::DestructorBlock &node) override
Visit destructor and check that the file is of type POINT_PROCESS or ARTIFICIAL_CELL.
Definition: semantic_analysis_visitor.cpp:268
table_statement.hpp
Auto generated AST classes declaration.
nmodl::visitor::SemanticAnalysisVisitor::visit_program
void visit_program(const ast::Program &node) override
Check number of DERIVATIVE blocks.
Definition: semantic_analysis_visitor.cpp:121
nmodl::symtab::SymbolTable::lookup
std::shared_ptr< Symbol > lookup(const std::string &name) const
check if symbol with given name exist in the current table (but not in parents)
Definition: symbol_table.hpp:199
nmodl::ast::AstNodeType::TABLE_STATEMENT
@ TABLE_STATEMENT
type of ast::TableStatement
nmodl::ast::Ast::get_parent
virtual Ast * get_parent() const
Parent getter.
Definition: ast.cpp:311
nmodl::ast::Ast::get_node_name
virtual std::string get_node_name() const
Return name of of the node.
Definition: ast.cpp:28
nmodl::ast::AstNodeType::RANGE_VAR
@ RANGE_VAR
type of ast::RangeVar