NEURON
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"
21 #include "ast/table_statement.hpp"
23 #include "utils/logger.hpp"
25 
26 namespace nmodl {
27 namespace visitor {
28 
29 
31  // check that we do not have any duplicate TABLE statement variables in PROCEDUREs
32  const auto& procedure_nodes = collect_nodes(node, {ast::AstNodeType::PROCEDURE_BLOCK});
33  std::unordered_set<std::string> procedure_vars{};
34  for (const auto& proc_node: procedure_nodes) {
35  const auto& table_nodes = collect_nodes(*proc_node, {ast::AstNodeType::TABLE_STATEMENT});
36  for (const auto& table_node: table_nodes) {
37  const auto& table_vars =
38  std::dynamic_pointer_cast<const ast::TableStatement>(table_node)->get_table_vars();
39  for (const auto& table_var: table_vars) {
40  const auto& [var_name,
41  inserted] = procedure_vars.insert(table_var->get_node_name());
42  if (!inserted) {
43  logger->critical(
44  fmt::format("SemanticAnalysisVisitor :: TABLE statement variable {} used "
45  "in multiple tables",
46  *var_name));
47  check_fail = true;
48  }
49  }
50  }
51  }
52 
53  return check_fail;
54 }
55 
57  // check a given FUNCTION has a return statement, and return true if yes, false if no
58  const auto& func_name = node.get_node_name();
59  // get all binary expressions that are of the form x = y
60  const auto& binary_expr_nodes = collect_nodes(node, {ast::AstNodeType::BINARY_EXPRESSION});
61  for (const auto& binary_expr_node: binary_expr_nodes) {
62  const auto& expr = std::dynamic_pointer_cast<const ast::BinaryExpression>(binary_expr_node);
63  const auto& lhs = expr->get_lhs();
64  const auto& op = expr->get_op();
65  if (op.eval() == "=" && lhs->get_node_name() == func_name) {
66  return true;
67  }
68  }
69  return false;
70 }
71 
73  // check a given FUNCTION has a VERBATIM block; those can interfere with detecting return
74  // statements
75  const auto& func_name = node.get_node_name();
76  const auto& verbatim_blocks = collect_nodes(node, {ast::AstNodeType::VERBATIM});
77  return !verbatim_blocks.empty();
78 }
79 
80 
82  // check that all functions have a return statement, i.e. that there is a statement of the form
83  // <funcname> = <expr> somewhere in each FUNCTION block
84  const auto& function_nodes = collect_nodes(node, {ast::AstNodeType::FUNCTION_BLOCK});
85  for (const auto& func_node: function_nodes) {
86  const auto& func = std::dynamic_pointer_cast<const ast::FunctionBlock>(func_node);
87  const auto& has_return_statement = check_function_has_return_statement(*func);
88  const auto& has_verbatim_block = check_function_has_verbatim_block(*func);
89  if (!has_return_statement) {
90  if (!has_verbatim_block) {
91  logger->warn(fmt::format(
92  "SemanticAnalysisVisitor :: FUNCTION {} does not have a return statement",
93  func->get_node_name()));
94  } else {
95  logger->warn(
96  fmt::format("SemanticAnalysisVisitor :: FUNCTION {} does not have an explicit "
97  "return statement, but VERBATIM block detected (possible return "
98  "statement in VERBATIM block?)",
99  func->get_node_name()));
100  }
101  }
102  }
103 }
104 
105 
107  // check that there are no RANGE variables which have the same name as a FUNCTION or PROCEDURE
108  const auto& range_nodes = collect_nodes(node, {ast::AstNodeType::RANGE_VAR});
109  std::set<std::string> range_vars{};
110  for (const auto& range_node: range_nodes) {
111  range_vars.insert(range_node->get_node_name());
112  }
113 
114  const auto& function_nodes =
116  std::set<std::string> func_vars{};
117  for (const auto& function_node: function_nodes) {
118  func_vars.insert(function_node->get_node_name());
119  }
120 
121  std::vector<std::string> result;
122  std::set_intersection(func_vars.begin(),
123  func_vars.end(),
124  range_vars.begin(),
125  range_vars.end(),
126  std::back_inserter(result));
127  return !result.empty();
128 }
129 
131  check_fail = false;
132  program_symtab = node.get_symbol_table();
133 
134  /// <-- This code is for check 2
135  const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX});
136  if (!suffix_node.empty()) {
137  const auto& suffix = std::dynamic_pointer_cast<const ast::Suffix>(suffix_node[0]);
138  const auto& type = suffix->get_type()->get_node_name();
139  is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL");
140  }
141  /// -->
142 
144 
145  /// <-- This code is for check 4
146  using namespace symtab::syminfo;
147  const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var;
148 
149  const auto& sym_table = node.get_symbol_table();
150  assert(sym_table != nullptr);
151 
152  // get all ion variables
153  const auto& ion_variables = sym_table->get_variables_with_properties(with_prop, false);
154 
155  /// make sure ion variables aren't redefined in a `CONSTANT` block.
156  for (const auto& var: ion_variables) {
157  if (var->has_any_property(NmodlType::constant_var)) {
158  logger->critical(
159  fmt::format("SemanticAnalysisVisitor :: ion variable {} from the USEION statement "
160  "can not be re-declared in a CONSTANT block",
161  var->get_name()));
162  check_fail = true;
163  }
164  }
165  /// -->
166 
168 
170  return check_fail;
171 }
172 
174  /// <-- This code is for check 8
175  const auto& derivative_block_nodes = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK});
176  if (derivative_block_nodes.size() > 1) {
177  logger->critical("It is not supported to have several DERIVATIVE blocks");
178  check_fail = true;
179  }
180  /// -->
181  node.visit_children(*this);
182 }
183 
185  /// <-- This code is for check 1
186  in_procedure = true;
187  one_arg_in_procedure_function = node.get_parameters().size() == 1;
188  node.visit_children(*this);
189  in_procedure = false;
190  /// -->
191 }
192 
194  /// <-- This code is for check 1
195  in_function = true;
196  one_arg_in_procedure_function = node.get_parameters().size() == 1;
197  node.visit_children(*this);
198  in_function = false;
199  /// -->
200 }
201 
203  /// <-- This code is a portion of check 9
204  // There are only two contexts where a random_var is allowed. As the first arg of a random
205  // function or as an item in the RANDOM declaration.
206  // Only the former needs checking.
207  auto name = node.get_node_name();
208 
209  // only check for variables exist in the symbol table (e.g. SUFFIX has type Name but it's not
210  // variable)
211  // if variable is not RANDOM then nothing to check for it
212  auto symbol = program_symtab->lookup(name);
213  if (!symbol || !symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
214  return;
215  }
216 
217  auto parent = node.get_parent();
218 
219  // if it's RANDOM var declaration in NEURON block then nothing to do
220  if (parent && parent->is_random_var()) {
221  return;
222  }
223 
224  if (parent && parent->is_var_name()) {
225  parent = parent->get_parent();
226  if (parent && parent->is_function_call()) {
227  auto fname = parent->get_node_name();
228  // if function is a random function then check if the current
229  // name is the function's first argument
230  if (is_random_construct_function(fname)) {
231  auto rfun = dynamic_cast<ast::FunctionCall*>(parent);
232  const auto& arguments = rfun->get_arguments();
233  if (!arguments.empty() && arguments.front()->is_var_name() &&
234  arguments.front()->get_node_name() == name) {
235  // if this is a first argument to function then there
236  // is no problem
237  node.visit_children(*this);
238  return;
239  }
240  }
241  }
242  }
243 
244  // Otherwise, we have an error
245  auto position = node.get_token()->position();
246  logger->critical(
247  fmt::format("SemanticAnalysisVisitor :: RANDOM variable {} at {}"
248  " can be used only as the first arg of a random function",
249  node.get_node_name(),
250  position));
251  check_fail = true;
252 
253  node.visit_children(*this);
254  /// -->
255 }
256 
258  /// <-- This code is a portion of check 9
259  // The first arg of a RANDOM function must be a random_var
260  // Otherwise it's an error
261  auto fname = node.get_node_name();
262  auto position = node.get_name()->get_token()->position();
263 
264  if (is_random_construct_function(fname)) {
265  const auto& arguments = node.get_arguments();
266  if (!arguments.empty()) {
267  auto arg0 = arguments.front();
268  if (arg0->is_var_name()) {
269  auto name = arg0->get_node_name();
270  auto symbol = program_symtab->lookup(name);
271  if (symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
272  node.visit_children(*this);
273  return;
274  }
275  }
276  }
277  logger->critical(
278  fmt::format("SemanticAnalysisVisitor :: random function {} at {} :: The first arg must "
279  "be a random variable",
280  fname,
281  position));
282  check_fail = true;
283  }
284 
285  if (is_nrn_pointing(fname)) {
286  if (size_t args_size = node.get_arguments().size(); args_size != 1) {
287  logger->critical(
288  fmt::format("nrn_pointing excepts exactly one argument, got: {}", args_size));
289  check_fail = true;
290  }
291  }
292 
293  node.visit_children(*this);
294  /// -->
295 }
296 
298  /// <-- This code is for check 1
300  logger->critical(
301  "SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement "
302  "should contains exactly one argument.");
303  check_fail = true;
304  }
305  /// -->
306  /// <-- This code is for check 3
307  const auto& table_vars = tableStmt.get_table_vars();
308  if (in_function && !table_vars.empty()) {
309  logger->critical(
310  "SemanticAnalysisVisitor :: TABLE statement in FUNCTION cannot have a table name "
311  "list.");
312  }
313  if (in_procedure && table_vars.empty()) {
314  logger->critical(
315  "SemanticAnalysisVisitor :: TABLE statement in PROCEDURE must have a table name list.");
316  }
317  /// -->
318 }
319 
321  /// <-- This code is for check 2
322  if (!is_point_process) {
323  logger->warn(
324  "SemanticAnalysisVisitor :: This mod file is not point process but contains a "
325  "destructor.");
326  check_fail = true;
327  }
328  /// -->
329 }
330 
332  /// <-- This code is for check 5
333  for (const auto& n: node.get_variables()) {
334  if (n->get_value()->get_value() != "t") {
335  logger->warn(
336  "SemanticAnalysisVisitor :: '{}' cannot be used as an independent variable, only "
337  "'t' is allowed.",
338  n->get_value()->get_value());
339  }
340  }
341  /// -->
342 }
343 
345  /// <-- This code is for check 7
346  if (node.get_parameters().size() < 1) {
347  logger->critical(
348  "SemanticAnalysisVisitor :: Function table '{}' must have one or more arguments.",
349  node.get_node_name());
350  check_fail = true;
351  }
352  /// -->
353 }
354 
356  /// <-- This code is for check 6
357  if (accel_backend) {
358  logger->error("PROTECT statement is not supported with GPU execution");
359  }
360  if (in_mutex) {
361  logger->warn("SemanticAnalysisVisitor :: Find a PROTECT inside a already locked part.");
362  }
363  /// -->
364 }
365 
367  /// <-- This code is for check 6
368  if (accel_backend) {
369  logger->error("MUTEXLOCK statement is not supported with GPU execution");
370  }
371  if (in_mutex) {
372  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXLOCK inside an already locked part.");
373  }
374  in_mutex = true;
375  /// -->
376 }
377 
379  /// <-- This code is for check 6
380  if (accel_backend) {
381  logger->error("MUTEXUNLOCK statement is not supported with GPU execution");
382  }
383  if (!in_mutex) {
384  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXUNLOCK outside a locked part.");
385  }
386  in_mutex = false;
387  /// -->
388 }
389 
390 } // namespace visitor
391 } // namespace nmodl
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Represents a DESTRUCTOR block in the NMODL.
const ExpressionVector & get_arguments() const noexcept
Getter for member variable FunctionCall::arguments.
Represents a INDEPENDENT block in the NMODL.
Represent MUTEXLOCK statement in NMODL.
Definition: mutex_lock.hpp:38
Represent MUTEXUNLOCK statement in NMODL.
Represents a name.
Definition: name.hpp:44
Represents top level AST node for whole NMODL input.
Definition: program.hpp:39
Represents TABLE statement in NMODL.
const NameVector & get_table_vars() const noexcept
Getter for member variable TableStatement::table_vars.
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)
void visit_program(const ast::Program &node) override
Check number of DERIVATIVE blocks.
void visit_destructor_block(const ast::DestructorBlock &node) override
Visit destructor and check that the file is of type POINT_PROCESS or ARTIFICIAL_CELL.
bool accel_backend
true if accelerator backend is used for code generation
void visit_independent_block(const ast::IndependentBlock &node) override
Visit independent block and check if one of the variable is not t.
void visit_procedure_block(const ast::ProcedureBlock &node) override
Store if we are in a procedure and if the arity of this is 1.
void visit_mutex_lock(const ast::MutexLock &node) override
Look if MUTEXLOCK is inside a locked block.
void check_functions_have_return_statements(const ast::Program &node)
Check functions have return statements, log warning otherwise.
void visit_table_statement(const ast::TableStatement &node) override
Visit a table statement and check that the arity of the block were 1.
void visit_mutex_unlock(const ast::MutexUnlock &node) override
Look if MUTEXUNLOCK is outside a locked block.
bool in_mutex
true if we are inside a mutex locked part
bool in_function
true if we are in a function block
bool in_procedure
true if we are in a procedure block
void visit_protect_statement(const ast::ProtectStatement &node) override
Look if protect is inside a locked block.
void visit_function_table_block(const ast::FunctionTableBlock &node) override
Visit function table to check that number of args > 0.
void visit_name(const ast::Name &node) override
Only use of random_var is as first arg in random function.
void visit_function_call(const ast::FunctionCall &node) override
random function first arg must be random_var
void visit_function_block(const ast::FunctionBlock &node) override
Store if we are in a function and if the arity of this is 1.
bool is_point_process
true if the mod file is of type point process
bool one_arg_in_procedure_function
true if the procedure or the function contains only one argument
bool check_name_conflict(const ast::Program &node)
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Auto generated AST classes declaration.
virtual std::string get_node_name() const
Return name of of the node.
Definition: ast.cpp:28
@ DERIVATIVE_BLOCK
type of ast::DerivativeBlock
@ BINARY_EXPRESSION
type of ast::BinaryExpression
@ RANGE_VAR
type of ast::RangeVar
@ FUNCTION_BLOCK
type of ast::FunctionBlock
@ VERBATIM
type of ast::Verbatim
@ TABLE_STATEMENT
type of ast::TableStatement
@ SUFFIX
type of ast::Suffix
@ PROCEDURE_BLOCK
type of ast::ProcedureBlock
double(* func)(double)
Definition: hoc_init.cpp:85
#define assert(ex)
Definition: hocassrt.h:24
Auto generated AST classes declaration.
double var(InputIterator begin, InputIterator end)
Definition: ivocvect.h:108
const char * name
Definition: init.cpp:16
static bool check_function_has_return_statement(const ast::FunctionBlock &node)
static bool check_function_has_verbatim_block(const ast::FunctionBlock &node)
encapsulates code generation backend implementations
Definition: ast_common.hpp:26
bool is_random_construct_function(const std::string &name)
Is given name a one of the function for RANDOM construct.
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
logger_type logger
Definition: logger.cpp:34
bool is_nrn_pointing(const std::string &name)
Is given name nrn_pointing.
static char suffix[256]
Definition: nocpout.cpp:135
static Node * node(Object *)
Definition: netcvode.cpp:291
int const size_t const size_t n
Definition: nrngsl.h:10
short type
Definition: cabvars.h:10
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Visitor to check some semantic rules on the AST
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Auto generated AST classes declaration.
Implement various classes to represent various Symbol properties.
Auto generated AST classes declaration.
Utility functions for visitors implementation.