def get_struct_members(
        struct_name: ScopedName,
        identifier_manager: IdentifierManager) -> Dict[str, MemberDefinition]:
    """
    Returns the member definitions of a struct sorted by offset.
    """

    scope_items = identifier_manager.get_scope(struct_name).identifiers
    members = (
        (name, indentifier_def)
        for (name, indentifier_def) in scope_items.items()
        if isinstance(indentifier_def, MemberDefinition))

    return {
        name: indentifier_def
        for name, indentifier_def in sorted(members, key=lambda key_value: key_value[1].offset)
    }
def get_main_functions_to_compile(
        identifiers: IdentifierManager, main_scope: ScopedName) -> Set[ScopedName]:
    """
    Retrieves the root functions to compile from a main scope.
    The definition of which functions we need to compile is somewhat arbitrary:
    All functions explicitly defined, or aliased in the main scope.
    """
    main_functions: Set[ScopedName] = set()
    try:
        scope = identifiers.get_scope(main_scope)
        main_functions = {main_scope + name for name in scope.subscopes}
        main_functions |= {
            identifier_definition.destination
            for identifier_definition in scope.identifiers.values()
            if isinstance(identifier_definition, AliasDefinition)}
    except MissingIdentifierError:
        return set()
    return main_functions
Exemple #3
0
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, identifiers: Optional[IdentifierManager] = None):
        super().__init__()
        self.identifiers = IdentifierManager(
        ) if identifiers is None else identifiers

    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)

        ident_type = FunctionDefinition if elm.element_type == 'func' else LabelDefinition
        self.add_future_identifier(function_scope, ident_type,
                                   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:
                try:
                    self.identifiers.get_scope(alias_dst)
                except IdentifierError:
                    raise PreprocessorError(
                        f"Cannot import '{import_item.orig_identifier.name}' "
                        f"from '{elm.path.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)