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 
8 #include "fmt/ranges.h"
9 
11 #include "ast/breakpoint_block.hpp"
12 #include "ast/function_block.hpp"
13 #include "ast/function_call.hpp"
16 #include "ast/procedure_block.hpp"
17 #include "ast/program.hpp"
18 #include "ast/range_var.hpp"
19 #include "ast/statement_block.hpp"
20 #include "ast/string.hpp"
21 #include "ast/suffix.hpp"
23 #include "ast/table_statement.hpp"
25 #include "utils/logger.hpp"
27 
28 namespace nmodl {
29 namespace visitor {
30 
31 
33  // check that we do not have any duplicate TABLE statement variables in PROCEDUREs
34  const auto& procedure_nodes = collect_nodes(node, {ast::AstNodeType::PROCEDURE_BLOCK});
35  std::unordered_set<std::string> procedure_vars{};
36  for (const auto& proc_node: procedure_nodes) {
37  const auto& table_nodes = collect_nodes(*proc_node, {ast::AstNodeType::TABLE_STATEMENT});
38  for (const auto& table_node: table_nodes) {
39  const auto& table_vars =
40  std::dynamic_pointer_cast<const ast::TableStatement>(table_node)->get_table_vars();
41  for (const auto& table_var: table_vars) {
42  const auto& [var_name,
43  inserted] = procedure_vars.insert(table_var->get_node_name());
44  if (!inserted) {
45  logger->critical(
46  fmt::format("SemanticAnalysisVisitor :: TABLE statement variable {} used "
47  "in multiple tables",
48  *var_name));
49  check_fail = true;
50  }
51  }
52  }
53  }
54 
55  return check_fail;
56 }
57 
59  // check a given FUNCTION has a return statement, and return true if yes, false if no
60  const auto& func_name = node.get_node_name();
61  // get all binary expressions that are of the form x = y
62  const auto& binary_expr_nodes = collect_nodes(node, {ast::AstNodeType::BINARY_EXPRESSION});
63  for (const auto& binary_expr_node: binary_expr_nodes) {
64  const auto& expr = std::dynamic_pointer_cast<const ast::BinaryExpression>(binary_expr_node);
65  const auto& lhs = expr->get_lhs();
66  const auto& op = expr->get_op();
67  if (op.eval() == "=" && lhs->get_node_name() == func_name) {
68  return true;
69  }
70  }
71  return false;
72 }
73 
75  // check a given FUNCTION has a VERBATIM block; those can interfere with detecting return
76  // statements
77  const auto& func_name = node.get_node_name();
78  const auto& verbatim_blocks = collect_nodes(node, {ast::AstNodeType::VERBATIM});
79  return !verbatim_blocks.empty();
80 }
81 
82 
84  // check that all functions have a return statement, i.e. that there is a statement of the form
85  // <funcname> = <expr> somewhere in each FUNCTION block
86  const auto& function_nodes = collect_nodes(node, {ast::AstNodeType::FUNCTION_BLOCK});
87  for (const auto& func_node: function_nodes) {
88  const auto& func = std::dynamic_pointer_cast<const ast::FunctionBlock>(func_node);
89  const auto& has_return_statement = check_function_has_return_statement(*func);
90  const auto& has_verbatim_block = check_function_has_verbatim_block(*func);
91  if (!has_return_statement) {
92  if (!has_verbatim_block) {
93  logger->warn(fmt::format(
94  "SemanticAnalysisVisitor :: FUNCTION {} does not have a return statement",
95  func->get_node_name()));
96  } else {
97  logger->warn(
98  fmt::format("SemanticAnalysisVisitor :: FUNCTION {} does not have an explicit "
99  "return statement, but VERBATIM block detected (possible return "
100  "statement in VERBATIM block?)",
101  func->get_node_name()));
102  }
103  }
104  }
105 }
106 
107 
109  // check that there are no RANGE variables which have the same name as a FUNCTION or PROCEDURE
110  const auto& range_nodes = collect_nodes(node, {ast::AstNodeType::RANGE_VAR});
111  std::set<std::string> range_vars{};
112  for (const auto& range_node: range_nodes) {
113  range_vars.insert(range_node->get_node_name());
114  }
115 
116  const auto& function_nodes =
118  std::set<std::string> func_vars{};
119  for (const auto& function_node: function_nodes) {
120  func_vars.insert(function_node->get_node_name());
121  }
122 
123  std::vector<std::string> result;
124  std::set_intersection(func_vars.begin(),
125  func_vars.end(),
126  range_vars.begin(),
127  range_vars.end(),
128  std::back_inserter(result));
129  const auto& ret_value = !result.empty();
130  if (ret_value) {
131  logger->critical("Duplicate name(s) found: {}", fmt::join(result, ", "));
132  }
133  return ret_value;
134 }
135 
137  check_fail = false;
138  program_symtab = node.get_symbol_table();
139 
140  /// <-- This code is for check 2
141  const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX});
142  if (!suffix_node.empty()) {
143  const auto& suffix = std::dynamic_pointer_cast<const ast::Suffix>(suffix_node[0]);
144  const auto& type = suffix->get_type()->get_node_name();
145  is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL");
146  }
147  /// -->
148 
150 
151  /// <-- This code is for check 4
152  using namespace symtab::syminfo;
153  const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var;
154 
155  const auto& sym_table = node.get_symbol_table();
156  assert(sym_table != nullptr);
157 
158  // get all ion variables
159  const auto& ion_variables = sym_table->get_variables_with_properties(with_prop, false);
160 
161  /// make sure ion variables aren't redefined in a `CONSTANT` block.
162  for (const auto& var: ion_variables) {
163  if (var->has_any_property(NmodlType::constant_var)) {
164  logger->critical(
165  fmt::format("SemanticAnalysisVisitor :: ion variable {} from the USEION statement "
166  "can not be re-declared in a CONSTANT block",
167  var->get_name()));
168  check_fail = true;
169  }
170  }
171  /// -->
172 
174 
176  return check_fail;
177 }
178 
180  /// <-- This code is for check 8
181  const auto& derivative_block_nodes = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK});
182  if (derivative_block_nodes.size() > 1) {
183  logger->critical("It is not supported to have several DERIVATIVE blocks");
184  check_fail = true;
185  }
186  /// -->
187  node.visit_children(*this);
188 }
189 
191  /// <-- This code is for check 1
192  in_procedure = true;
193  one_arg_in_procedure_function = node.get_parameters().size() == 1;
194  node.visit_children(*this);
195  in_procedure = false;
196  /// -->
197 }
198 
200  /// <-- This code is for check 1
201  in_function = true;
202  one_arg_in_procedure_function = node.get_parameters().size() == 1;
203  node.visit_children(*this);
204  in_function = false;
205  /// -->
206 }
207 
209  /// <-- This code is a portion of check 9
210  // There are only two contexts where a random_var is allowed. As the first arg of a random
211  // function or as an item in the RANDOM declaration.
212  // Only the former needs checking.
213  auto name = node.get_node_name();
214 
215  // only check for variables exist in the symbol table (e.g. SUFFIX has type Name but it's not
216  // variable)
217  // if variable is not RANDOM then nothing to check for it
218  auto symbol = program_symtab->lookup(name);
219  if (!symbol || !symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
220  return;
221  }
222 
223  auto parent = node.get_parent();
224 
225  // if it's RANDOM var declaration in NEURON block then nothing to do
226  if (parent && parent->is_random_var()) {
227  return;
228  }
229 
230  if (parent && parent->is_var_name()) {
231  parent = parent->get_parent();
232  if (parent && parent->is_function_call()) {
233  auto fname = parent->get_node_name();
234  // if function is a random function then check if the current
235  // name is the function's first argument
236  if (is_random_construct_function(fname)) {
237  auto rfun = dynamic_cast<ast::FunctionCall*>(parent);
238  const auto& arguments = rfun->get_arguments();
239  if (!arguments.empty() && arguments.front()->is_var_name() &&
240  arguments.front()->get_node_name() == name) {
241  // if this is a first argument to function then there
242  // is no problem
243  node.visit_children(*this);
244  return;
245  }
246  }
247  }
248  }
249 
250  // Otherwise, we have an error
251  auto position = node.get_token()->position();
252  logger->critical(
253  fmt::format("SemanticAnalysisVisitor :: RANDOM variable {} at {}"
254  " can be used only as the first arg of a random function",
255  node.get_node_name(),
256  position));
257  check_fail = true;
258 
259  node.visit_children(*this);
260  /// -->
261 }
262 
264  /// <-- This code is a portion of check 9
265  // The first arg of a RANDOM function must be a random_var
266  // Otherwise it's an error
267  auto fname = node.get_node_name();
268  auto position = node.get_name()->get_token()->position();
269 
270  if (is_random_construct_function(fname)) {
271  const auto& arguments = node.get_arguments();
272  if (!arguments.empty()) {
273  auto arg0 = arguments.front();
274  if (arg0->is_var_name()) {
275  auto name = arg0->get_node_name();
276  auto symbol = program_symtab->lookup(name);
277  if (symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) {
278  node.visit_children(*this);
279  return;
280  }
281  }
282  }
283  logger->critical(
284  fmt::format("SemanticAnalysisVisitor :: random function {} at {} :: The first arg must "
285  "be a random variable",
286  fname,
287  position));
288  check_fail = true;
289  }
290 
291  if (is_nrn_pointing(fname)) {
292  if (size_t args_size = node.get_arguments().size(); args_size != 1) {
293  logger->critical(
294  fmt::format("nrn_pointing excepts exactly one argument, got: {}", args_size));
295  check_fail = true;
296  }
297  }
298 
299  if (is_nrn_state_disc(fname)) {
300  // check that a call to `state_discontinuity` has exactly 2 arguments
301  if (size_t args_size = node.get_arguments().size(); args_size != 2) {
302  logger->critical("state_discontinuity accepts exactly two arguments, got: {}",
303  args_size);
304  check_fail = true;
305  return;
306  }
307  // check that the first arg is a variable
308  const auto& first = node.get_arguments()[0];
309  if (!first->is_var_name()) {
310  logger->critical(
311  "state_discontinuity first arg must be a variable, not a compound expression");
312  check_fail = true;
313  return;
314  }
315  }
316 
317  node.visit_children(*this);
318  /// -->
319 }
320 
322  /// <-- This code is for check 1
324  logger->critical(
325  "SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement "
326  "should contains exactly one argument.");
327  check_fail = true;
328  }
329  /// -->
330  /// <-- This code is for check 3
331  const auto& table_vars = tableStmt.get_table_vars();
332  if (in_function && !table_vars.empty()) {
333  logger->critical(
334  "SemanticAnalysisVisitor :: TABLE statement in FUNCTION cannot have a table name "
335  "list.");
336  }
337  if (in_procedure && table_vars.empty()) {
338  logger->critical(
339  "SemanticAnalysisVisitor :: TABLE statement in PROCEDURE must have a table name list.");
340  }
341  /// -->
342 }
343 
345  /// <-- This code is for check 2
346  if (!is_point_process) {
347  logger->warn(
348  "SemanticAnalysisVisitor :: This mod file is not point process but contains a "
349  "destructor.");
350  check_fail = true;
351  }
352  /// -->
353 }
354 
356  /// <-- This code is for check 5
357  for (const auto& n: node.get_variables()) {
358  if (n->get_value()->get_value() != "t") {
359  logger->warn(
360  "SemanticAnalysisVisitor :: '{}' cannot be used as an independent variable, only "
361  "'t' is allowed.",
362  n->get_value()->get_value());
363  }
364  }
365  /// -->
366 }
367 
369  /// <-- This code is for check 7
370  if (node.get_parameters().size() < 1) {
371  logger->critical(
372  "SemanticAnalysisVisitor :: Function table '{}' must have one or more arguments.",
373  node.get_node_name());
374  check_fail = true;
375  }
376  /// -->
377 }
378 
380  /// <-- This code is for check 6
381  if (accel_backend) {
382  logger->error("PROTECT statement is not supported with GPU execution");
383  }
384  if (in_mutex) {
385  logger->warn("SemanticAnalysisVisitor :: Find a PROTECT inside a already locked part.");
386  }
387  /// -->
388 }
389 
391  /// <-- This code is for check 6
392  if (accel_backend) {
393  logger->error("MUTEXLOCK statement is not supported with GPU execution");
394  }
395  if (in_mutex) {
396  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXLOCK inside an already locked part.");
397  }
398  in_mutex = true;
399  /// -->
400 }
401 
403  /// <-- This code is for check 6
404  if (accel_backend) {
405  logger->error("MUTEXUNLOCK statement is not supported with GPU execution");
406  }
407  if (!in_mutex) {
408  logger->warn("SemanticAnalysisVisitor :: Found a MUTEXUNLOCK outside a locked part.");
409  }
410  in_mutex = false;
411  /// -->
412 }
413 
414 } // namespace visitor
415 } // 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.
bool is_nrn_state_disc(const std::string &name)
Checks if given function name is state_discontinuity.
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.