def get_struct_definition( struct_name: ScopedName, identifier_manager: IdentifierManager) -> StructDefinition: """ Returns the struct definition of a struct given its full name (no alias resolution). """ struct_def = identifier_manager.get_by_full_name(struct_name) if struct_def is None: raise MissingIdentifierError(struct_name) if not isinstance(struct_def, StructDefinition): raise DefinitionError(f"""\ Expected '{struct_name}' to be a {StructDefinition.TYPE}. Found: '{struct_def.TYPE}'.""" ) return struct_def
class IdentifierCollector(Visitor): """ Collects all the identifiers in a code element. Uses a partial visitor. """ # A dict from code element types to the identifier type they define. IDENTIFIER_DEFINERS = { CodeElementConst: ConstDefinition, CodeElementLabel: LabelDefinition, CodeElementReference: ReferenceDefinition, CodeElementLocalVariable: ReferenceDefinition, CodeElementTemporaryVariable: ReferenceDefinition, CodeElementReturnValueReference: ReferenceDefinition, } def __init__(self): super().__init__() self.identifiers = IdentifierManager() def add_identifier(self, name: ScopedName, identifier_definition: IdentifierDefinition, location: Optional[Location]): """ Adds an identifier with name 'name' and the given identifier definition at location 'location'. """ existing_definition = self.identifiers.get_by_full_name(name) if existing_definition is not None: if not isinstance(existing_definition, FutureIdentifierDefinition) or \ not isinstance(identifier_definition, FutureIdentifierDefinition): raise PreprocessorError(f"Redefinition of '{name}'.", location=location) if (existing_definition.identifier_type, identifier_definition.identifier_type) != ( ReferenceDefinition, ReferenceDefinition): # Redefinition is only allowed in reference rebinding. raise PreprocessorError(f"Redefinition of '{name}'.", location=location) self.identifiers.add_identifier(name, identifier_definition) def add_future_identifier(self, name: ScopedName, identifier_type: type, location: Optional[Location]): """ Adds a future identifier with name 'name' of type 'identifier_type' at location 'location'. """ self.add_identifier(name=name, identifier_definition=FutureIdentifierDefinition( identifier_type=identifier_type), location=location) def visit(self, obj): if type(obj) in self.IDENTIFIER_DEFINERS: definition_type = self.IDENTIFIER_DEFINERS[type(obj)] identifier = _get_identifier(obj) self.add_future_identifier(self.current_scope + identifier.name, definition_type, identifier.location) return super().visit(obj) def _visit_default(self, obj): assert isinstance(obj, (CodeBlock, CodeElement)), \ f'Received unexpected object of type {type(obj).__name__}.' def visit_CodeElementFunction(self, elm: CodeElementFunction): """ Registers the function's identifier, arguments and return values, and then recursively visits the code block contained in the function. """ function_scope = self.current_scope + elm.name if elm.element_type == 'struct': self.add_future_identifier(function_scope, StructDefinition, elm.identifier.location) return args_scope = function_scope + CodeElementFunction.ARGUMENT_SCOPE implicit_args_scope = function_scope + CodeElementFunction.IMPLICIT_ARGUMENT_SCOPE rets_scope = function_scope + CodeElementFunction.RETURN_SCOPE def handle_struct_def(identifier_list: Optional[IdentifierList], struct_name: ScopedName): location = elm.identifier.location if identifier_list is not None: location = identifier_list.location self.add_future_identifier(name=struct_name, identifier_type=StructDefinition, location=location) def handle_function_arguments( identifier_list: Optional[IdentifierList], struct_name: ScopedName): handle_struct_def(identifier_list=identifier_list, struct_name=struct_name) if identifier_list is None: return for arg_id in identifier_list.identifiers: if arg_id.name == N_LOCALS_CONSTANT: raise PreprocessorError( f"The name '{N_LOCALS_CONSTANT}' is reserved and cannot be used as an " 'argument name.', location=arg_id.location) # Within a function, arguments are also accessible directly. self.add_future_identifier(function_scope + arg_id.name, ReferenceDefinition, arg_id.location) handle_function_arguments(identifier_list=elm.arguments, struct_name=args_scope) handle_function_arguments(identifier_list=elm.implicit_arguments, struct_name=implicit_args_scope) handle_struct_def(identifier_list=elm.returns, struct_name=rets_scope) # Make sure there is no name collision. if elm.implicit_arguments is not None: implicit_arg_names = { arg_id.name for arg_id in elm.implicit_arguments.identifiers } arg_and_return_identifiers = list(elm.arguments.identifiers) if elm.returns is not None: arg_and_return_identifiers += elm.returns.identifiers for arg_id in arg_and_return_identifiers: if arg_id.name in implicit_arg_names: raise PreprocessorError( 'Arguments and return values cannot have the same name of an implicit ' 'argument.', location=arg_id.location) self.add_future_identifier(function_scope, LabelDefinition, elm.identifier.location) # Add SIZEOF_LOCALS for current block at identifier definition location if available. self.add_future_identifier(function_scope + N_LOCALS_CONSTANT, ConstDefinition, elm.identifier.location) super().visit_CodeElementFunction(elm) def visit_CodeElementUnpackBinding(self, elm: CodeElementUnpackBinding): """ Registers all the unpacked identifiers. """ for identifier in elm.unpacking_list.identifiers: if identifier.name == '_': continue self.add_future_identifier(self.current_scope + identifier.name, ReferenceDefinition, identifier.location) def visit_CodeElementIf(self, obj: CodeElementIf): assert obj.label_neq is not None assert obj.label_end is not None self.add_future_identifier(name=self.current_scope + obj.label_neq, identifier_type=LabelDefinition, location=obj.location) self.add_future_identifier(name=self.current_scope + obj.label_end, identifier_type=LabelDefinition, location=obj.location) self.visit(obj.main_code_block) if obj.else_code_block is not None: self.visit(obj.else_code_block) def visit_CodeBlock(self, code_block: CodeBlock): """ Collects all identifiers in a code block. """ for elm in code_block.code_elements: self.visit(elm.code_elm) def visit_CodeElementImport(self, elm: CodeElementImport): for import_item in elm.import_items: alias_dst = ScopedName.from_string( elm.path.name) + import_item.orig_identifier.name local_identifier = import_item.identifier # Ensure destination is a valid identifier. if self.identifiers.get_by_full_name(alias_dst) is None: raise PreprocessorError( f"Scope '{elm.path.name}' does not include identifier " f"'{import_item.orig_identifier.name}'.", location=import_item.orig_identifier.location) # Add alias to identifiers. self.add_identifier( name=self.current_scope + local_identifier.name, identifier_definition=AliasDefinition(destination=alias_dst), location=import_item.identifier.location) def visit_CodeElementWith(self, elm: CodeElementWith): for aliased_identifier in elm.identifiers: if aliased_identifier.local_name is not None: self.add_future_identifier( name=self.current_scope + aliased_identifier.local_name.name, identifier_type=ReferenceDefinition, location=aliased_identifier.local_name.location) self.visit(elm.code_block)