def get_inline_expression_symbols(cls, ast: ASTNode) -> List[VariableSymbol]: """ For the handed over AST node, this method collects all inline expression variable symbols in it. :param ast: a single AST node :return: a list of all inline expression variable symbols """ from pynestml.visitors.ast_higher_order_visitor import ASTHigherOrderVisitor from pynestml.meta_model.ast_variable import ASTVariable res = list() def loc_get_vars(node): if isinstance(node, ASTVariable): res.append(node) ast.accept(ASTHigherOrderVisitor(visit_funcs=loc_get_vars)) ret = list() for var in res: if '\'' not in var.get_complete_name(): symbol = ast.get_scope().resolve_to_symbol( var.get_complete_name(), SymbolKind.VARIABLE) if symbol is not None and symbol.is_inline_expression: ret.append(symbol) return ret
def visit_variable(self, node: ASTNode): """ Visits each kernel and checks if it is used correctly. :param node: a single node. """ for kernelName in self.__kernels: # in order to allow shadowing by local scopes, we first check if the element has been declared locally symbol = node.get_scope().resolve_to_symbol( kernelName, SymbolKind.VARIABLE) # if it is not a kernel just continue if symbol is None: continue if not symbol.is_kernel(): continue if node.get_complete_name() == kernelName: parent = self.__neuron_node.get_parent(node) if parent is not None: if isinstance(parent, ASTKernel): continue grandparent = self.__neuron_node.get_parent(parent) if grandparent is not None and isinstance( grandparent, ASTFunctionCall): grandparent_func_name = grandparent.get_name() if grandparent_func_name == 'convolve': continue code, message = Messages.get_kernel_outside_convolve( kernelName) Logger.log_message(code=code, message=message, log_level=LoggingLevel.ERROR, error_position=node.get_source_position())
def get_all_variables(cls, node: ASTNode) -> List[str]: """Make a list of all variable symbol names that are in ``node``""" class ASTVariablesFinderVisitor(ASTVisitor): _variables = [] def __init__(self): super(ASTVariablesFinderVisitor, self).__init__() def visit_declaration(self, node): symbol = node.get_scope().resolve_to_symbol(node.get_variables()[0].get_complete_name(), SymbolKind.VARIABLE) if symbol is None: code, message = Messages.get_variable_not_defined(node.get_variable().get_complete_name()) Logger.log_message(code=code, message=message, error_position=node.get_source_position(), log_level=LoggingLevel.ERROR, astnode=node) return self._variables.append(symbol) if node is None: return [] visitor = ASTVariablesFinderVisitor() node.accept(visitor) all_variables = [v.name for v in visitor._variables] return all_variables
def add_suffix_to_variable_name(cls, var_name: str, astnode: ASTNode, suffix: str, scope=None): """add suffix to variable by given name recursively throughout astnode""" from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor from pynestml.symbols.variable_symbol import BlockType from pynestml.symbols.variable_symbol import VariableSymbol, BlockType, VariableType def replace_var(_expr=None): if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): var = _expr.get_variable() elif isinstance(_expr, ASTVariable): var = _expr else: return if not suffix in var.get_name() \ and not var.get_name() == "t" \ and var.get_name() == var_name: var.set_name(var.get_name() + suffix) # if scope is not None: # symbol = VariableSymbol(name=var.get_name(), block_type=BlockType.PARAMETERS, # type_symbol=var.get_type_symbol(), # variable_type=VariableType.VARIABLE) # scope.add_symbol(symbol) # var.update_scope(scope) # assert scope.resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE) is not None # assert scope.resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE).block_type == BlockType.PARAMETERS # #var.accept(ASTSymbolTableVisitor()) astnode.accept(ASTHigherOrderVisitor(lambda x: replace_var(x)))
def replace_with_external_variable(cls, var_name, node: ASTNode, suffix, new_scope, alternate_name=None): """ Replace all occurrences of variables (``ASTVariable``s) (e.g. ``post_trace'``) in the node with ``ASTExternalVariable``s, indicating that they are moved to the postsynaptic neuron. """ def replace_var(_expr=None): if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): var = _expr.get_variable() elif isinstance(_expr, ASTVariable): var = _expr else: return if var.get_name() != var_name: return ast_ext_var = ASTExternalVariable(var.get_name() + suffix, differential_order=var.get_differential_order(), source_position=var.get_source_position()) if alternate_name: ast_ext_var.set_alternate_name(alternate_name) ast_ext_var.update_alt_scope(new_scope) from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor ast_ext_var.accept(ASTSymbolTableVisitor()) if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): Logger.log_message(None, -1, "ASTSimpleExpression replacement made (var = " + str( ast_ext_var.get_name()) + ") in expression: " + str(node.get_parent(_expr)), None, LoggingLevel.INFO) _expr.set_variable(ast_ext_var) return if isinstance(_expr, ASTVariable): if isinstance(node.get_parent(_expr), ASTAssignment): node.get_parent(_expr).lhs = ast_ext_var Logger.log_message(None, -1, "ASTVariable replacement made in expression: " + str(node.get_parent(_expr)), None, LoggingLevel.INFO) elif isinstance(node.get_parent(_expr), ASTSimpleExpression) and node.get_parent(_expr).is_variable(): node.get_parent(_expr).set_variable(ast_ext_var) elif isinstance(node.get_parent(_expr), ASTDeclaration): # variable could occur on the left-hand side; ignore. Only replace if it occurs on the right-hand side. pass else: Logger.log_message(None, -1, "Error: unhandled use of variable " + var_name + " in expression " + str(_expr), None, LoggingLevel.INFO) raise Exception() return p = node.get_parent(var) Logger.log_message(None, -1, "Error: unhandled use of variable " + var_name + " in expression " + str(p), None, LoggingLevel.INFO) raise Exception() node.accept(ASTHigherOrderVisitor(lambda x: replace_var(x)))
def log_message(cls, node: ASTNode = None, code: MessageCode = None, message: str = None, error_position: ASTSourceLocation = None, log_level: LoggingLevel = None): """ Logs the handed over message on the handed over. If the current logging is appropriate, the message is also printed. :param node: the node in which the error occurred :param code: a single error code :param error_position: the position on which the error occurred. :param message: a message. :param log_level: the corresponding log level. """ if cls.log_frozen: return if cls.curr_message is None: cls.init_logger(LoggingLevel.INFO) from pynestml.meta_model.ast_node import ASTNode from pynestml.utils.ast_source_location import ASTSourceLocation assert (node is None or isinstance(node, ASTNode)), \ '(PyNestML.Logger) Wrong type of node provided (%s)!' % type(node) assert (error_position is None or isinstance(error_position, ASTSourceLocation)), \ '(PyNestML.Logger) Wrong type of error position provided (%s)!' % type(error_position) from pynestml.meta_model.ast_neuron import ASTNeuron if isinstance(node, ASTNeuron): cls.log[cls.curr_message] = (node.get_artifact_name(), node, log_level, code, error_position, message) elif cls.current_node is not None: cls.log[cls.curr_message] = (cls.current_node.get_artifact_name(), cls.current_node, log_level, code, error_position, message) cls.curr_message += 1 if cls.no_print: return if cls.logging_level.value <= log_level.value: to_print = '[' + str(cls.curr_message) + ',' to_print = (to_print + (node.get_name() + ', ' if node is not None else cls.current_node.get_name() + ', ' if cls.current_node is not None else 'GLOBAL, ')) to_print = to_print + str(log_level.name) to_print = to_print + (', ' + str(error_position) if error_position is not None else '') + ']: ' to_print = to_print + str(message) print(to_print) return
def add_suffix_to_variable_name(cls, var_name: str, astnode: ASTNode, suffix: str): """add suffix to variable by given name recursively throughout astnode""" def replace_var(_expr=None): if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): var = _expr.get_variable() elif isinstance(_expr, ASTVariable): var = _expr else: return if not suffix in var.get_name() \ and not var.get_name() == "t" \ and var.get_name() == var_name: var.set_name(var.get_name() + suffix) astnode.accept(ASTHigherOrderVisitor(lambda x: replace_var(x)))
def collect_variable_names_in_expression( cls, expr: ASTNode) -> List[ASTVariable]: """ Collect all occurrences of variables (`ASTVariable`), kernels (`ASTKernel`) XXX ... :param expr: expression to collect the variables from :return: a list of variables """ vars_used_ = [] def collect_vars(_expr=None): var = None if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): var = _expr.get_variable() elif isinstance(_expr, ASTVariable): var = _expr if var: vars_used_.append(var) expr.accept(ASTHigherOrderVisitor(lambda x: collect_vars(x))) return vars_used_
def get_all_variables_used_in_convolutions(cls, node: ASTNode, parent_node: ASTNode) -> List[str]: """Make a list of all variable symbol names that are in ``node`` and used in a convolution""" from pynestml.codegeneration.ast_transformers import ASTTransformers class ASTAllVariablesUsedInConvolutionVisitor(ASTVisitor): _variables = [] parent_node = None def __init__(self, node, parent_node): super(ASTAllVariablesUsedInConvolutionVisitor, self).__init__() self.node = node self.parent_node = parent_node def visit_function_call(self, node): func_name = node.get_name() if func_name == 'convolve': symbol_buffer = node.get_scope().resolve_to_symbol(str(node.get_args()[1]), SymbolKind.VARIABLE) input_port = ASTTransformers.get_input_port_by_name( self.parent_node.get_input_blocks(), symbol_buffer.name) if input_port: found_parent_assignment = False node_ = node while not found_parent_assignment: node_ = self.parent_node.get_parent(node_) # XXX TODO also needs to accept normal ASTExpression, ASTAssignment? if isinstance(node_, ASTInlineExpression): found_parent_assignment = True var_name = node_.get_variable_name() self._variables.append(var_name) if node is None: return [] visitor = ASTAllVariablesUsedInConvolutionVisitor(node, parent_node) node.accept(visitor) return visitor._variables
def get_all_messages_of_node(cls, node: ASTNode) -> List[Tuple[ASTNode, LoggingLevel, str]]: """ Returns all messages which have been reported for a certain node. :param node: a single node instance :return: a list of messages with their levels. :rtype: list((str,Logging_Level) """ if node is None: return cls.get_log() ret = list() for (artifactName, node_i, logLevel, code, errorPosition, message) in cls.log.values(): if (node_i == node if node is not None else True) and \ (node.get_artifact_name() == artifactName if node is not None else True): ret.append((node, logLevel, message)) return ret
def recursive_dependent_variables_search(cls, vars: List[str], node: ASTNode) -> List[str]: """ Collect all the variable names used in the defining expressions of a list of variables. :param vars: list of variable names moved from synapse to neuron :param node: ASTNode to perform the recursive search :return: list of variable names from the recursive search """ for var in vars: assert type(var) is str vars_used = [] vars_to_check = set([var for var in vars]) vars_checked = set() while vars_to_check: var = None for _var in vars_to_check: if not _var in vars_checked: var = _var break if not var: # all variables checked break decls = cls.get_declarations_from_block( var, node.get_equations_blocks()) if decls: assert len(decls) == 1 decl = decls[0] if (type(decl) in [ASTDeclaration, ASTReturnStmt] and decl.has_expression()) \ or type(decl) is ASTInlineExpression: vars_used.extend( cls.collect_variable_names_in_expression( decl.get_expression())) elif type(decl) is ASTOdeEquation: vars_used.extend( cls.collect_variable_names_in_expression( decl.get_rhs())) elif type(decl) is ASTKernel: for expr in decl.get_expressions(): vars_used.extend( cls.collect_variable_names_in_expression(expr)) else: raise Exception("Unknown type " + str(type(decl))) vars_used = [str(var) for var in vars_used] vars_to_check = vars_to_check.union(set(vars_used)) vars_checked.add(var) return list(set(vars_checked))
def get_all_messages_of_level_and_or_node( cls, node: ASTNode, level: LoggingLevel) -> List[Tuple[ASTNode, LoggingLevel, str]]: """ Returns all messages which have a certain logging level, or have been reported for a certain node, or both. :param node: a single node instance :param level: a logging level :return: a list of messages with their levels. """ if level is None and node is None: return cls.get_log() ret = list() for (artifactName, node_i, logLevel, code, errorPosition, message) in cls.log.values(): if (level == logLevel if level is not None else True) and (node if node is not None else True) and ( node.get_artifact_name() == artifactName if node is not None else True): ret.append((node, logLevel, message)) return ret
def check_co_co(cls, neuron: ASTNode, after_ast_rewrite: bool): """ Checks if this coco applies for the handed over neuron. Models which use not defined elements are not correct. :param neuron: a single neuron instance. :type neuron: ast_neuron """ # for each variable in all expressions, check if the variable has been defined previously expression_collector_visitor = ASTExpressionCollectorVisitor() neuron.accept(expression_collector_visitor) expressions = expression_collector_visitor.ret for expr in expressions: for var in expr.get_variables(): symbol = var.get_scope().resolve_to_symbol( var.get_complete_name(), SymbolKind.VARIABLE) # this part is required to check that we handle invariants differently expr_par = neuron.get_parent(expr) if symbol is None: # check if this symbol is actually a type, e.g. "mV" in the expression "(1 + 2) * mV" symbol = var.get_scope().resolve_to_symbol( var.get_complete_name(), SymbolKind.TYPE) if symbol is None: # symbol has not been defined; neither as a variable name nor as a type symbol code, message = Messages.get_variable_not_defined( var.get_name()) Logger.log_message( node=neuron, code=code, message=message, log_level=LoggingLevel.ERROR, error_position=var.get_source_position()) # first check if it is part of an invariant # if it is the case, there is no "recursive" declaration # so check if the parent is a declaration and the expression the invariant elif isinstance( expr_par, ASTDeclaration) and expr_par.get_invariant() == expr: # in this case its ok if it is recursive or defined later on continue # now check if it has been defined before usage, except for predefined symbols, buffers and variables added by the AST transformation functions elif (not symbol.is_predefined) \ and symbol.block_type != BlockType.INPUT_BUFFER_CURRENT \ and symbol.block_type != BlockType.INPUT_BUFFER_SPIKE \ and not symbol.get_referenced_object().get_source_position().is_added_source_position(): # except for parameters, those can be defined after if ((not symbol.get_referenced_object( ).get_source_position().before(var.get_source_position())) and (not symbol.block_type in [BlockType.PARAMETERS, BlockType.INTERNALS])): code, message = Messages.get_variable_used_before_declaration( var.get_name()) Logger.log_message( node=neuron, message=message, error_position=var.get_source_position(), code=code, log_level=LoggingLevel.ERROR) # now check that they are now defined recursively, e.g. V_m mV = V_m + 1 # todo: we should not check this for invariants if (symbol.get_referenced_object().get_source_position( ).encloses(var.get_source_position()) and not symbol.get_referenced_object(). get_source_position().is_added_source_position()): code, message = Messages.get_variable_defined_recursively( var.get_name()) Logger.log_message( node=neuron, code=code, message=message, error_position=symbol.get_referenced_object( ).get_source_position(), log_level=LoggingLevel.ERROR) # now check for each assignment whether the left hand side variable is defined vis = ASTAssignedVariableDefinedVisitor(neuron, after_ast_rewrite) neuron.accept(vis)