示例#1
0
    def collect(self) -> SsbLabelJumpBlueprint:
        self.ctx: ExplorerScriptParser.If_headerContext
        is_positive = self._is_positive

        self.ctx: ExplorerScriptParser.If_headerContext
        # Complex branches
        if self._header_cmplx_handler:
            if isinstance(self._header_cmplx_handler, OperationCompileHandler):
                # An operation as condition
                op = self._header_cmplx_handler.collect()
                if len(op) != 1:
                    raise SsbCompilerError(
                        _("Invalid content for an if-header"))
                op = op[0]
                if op.op_code.name not in OPS_BRANCH.keys():
                    raise SsbCompilerError(
                        f(
                            _("Invalid operation for if condition: {op.op_code.name} (line {self.ctx.start.line})"
                              )))
                jmp = SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                            op.op_code.name, op.params)
                jmp.set_jump_is_positive(is_positive)
                return jmp
            else:
                # A regular complex if condition
                tmpl: SsbLabelJumpBlueprint = self._header_cmplx_handler.collect(
                )
                tmpl.set_jump_is_positive(is_positive)
                return tmpl

        raise SsbCompilerError(
            f(_("Unknown if operation in line {self.ctx.start.line}).")))
示例#2
0
    def collect(self) -> List[SsbOperation]:
        ops = []
        self.ctx: ExplorerScriptParser.Ctx_blockContext
        if self._for_id is None:
            raise SsbCompilerError(_("No target ID set for with(){} block."))
        if not self._sub_stmt:
            raise SsbCompilerError(
                _("A with(){} block needs exactly one statement."))
        for_type = str(self.ctx.ctx_header().CTX_TYPE())

        if for_type == 'actor':
            ops.append(self._generate_operation(OPS_CTX_LIVES, [self._for_id]))
        elif for_type == 'object':
            ops.append(self._generate_operation(OPS_CTX_OBJECT,
                                                [self._for_id]))
        elif for_type == 'performer':
            ops.append(
                self._generate_operation(OPS_CTX_PERFORMER, [self._for_id]))
        else:
            raise SsbCompilerError(
                f(_("Invalid with(){{}} target type '{for_type}'.")))

        sub_ops = self._sub_stmt.collect()
        if len(sub_ops) != 1:
            raise SsbCompilerError(
                _("A with(){} block needs exactly one binary operation. "
                  "The handler for it generated multiple operations."))
        ops += sub_ops

        return ops
示例#3
0
    def collect(self) -> SsbLabelJumpBlueprint:
        if self.scn_var_target is None:
            raise SsbCompilerError(_("No variable for assignment."))
        if self.operator not in [
                SsbOperator.EQ, SsbOperator.LE, SsbOperator.LT, SsbOperator.GE,
                SsbOperator.GT
        ]:
            raise SsbCompilerError(
                f(
                    _("The only supported operators for scn if "
                      "conditions are ==,<,<=,>,>= (line {self.ctx.start.line})"
                      )))

        scn_value = exps_int(str(self.ctx.INTEGER(0)))
        level_value = exps_int(str(self.ctx.INTEGER(1)))

        if self.operator == SsbOperator.LE:
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW_BEFORE,
                [self.scn_var_target, scn_value, level_value])
        if self.operator == SsbOperator.LT:
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_BEFORE,
                [self.scn_var_target, scn_value, level_value])
        if self.operator == SsbOperator.GE:
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW_AFTER,
                [self.scn_var_target, scn_value, level_value])
        if self.operator == SsbOperator.GT:
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_AFTER,
                [self.scn_var_target, scn_value, level_value])
        return SsbLabelJumpBlueprint(
            self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW,
            [self.scn_var_target, scn_value, level_value])
 def _check_cycles(self):
     for v in self._dependency_graph.vs:
         if any(self._has_path(out_e.target, v) for out_e in v.out_edges()):
             raise SsbCompilerError(f(_("Dependency cycle detected while trying to resolve macros"
                                        " (for macro '{v['name']}').")))
         # Check direct cycles
         if any(v.index == e.target for e in v.out_edges()):
             raise SsbCompilerError(f(_("Dependency cycle detected while trying to resolve macros"
                                        " (for macro '{v['name']}').")))
    def collect(self) -> List[SsbOperation]:
        if self.var_target is None:
            raise SsbCompilerError(_("No variable for dungeon_mode set."))
        if self.value is None:
            raise SsbCompilerError(_("No value for dungeon_mode set."))

        return [
            self._generate_operation(OPS_FLAG__SET_DUNGEON_MODE,
                                     [self.var_target, self.value])
        ]
示例#6
0
    def collect(self) -> SsbOperation:
        if self.scn_var_target is None:
            raise SsbCompilerError(_("No variable set for scn switch condition."))

        index = exps_int(str(self.ctx.INTEGER()))
        if index == 0:
            return self._generate_operation(OP_SWITCH_SCENARIO, [self.scn_var_target])
        elif index == 1:
            return self._generate_operation(OP_SWITCH_SCENARIO_LEVEL, [self.scn_var_target])
        raise SsbCompilerError(f(_("Index for scn() if condition must be 0 or 1 (line {self.ctx.start.line}).")))
 def _push_handler(self, ctx: ParserRuleContext, compile_handler: Type[T],
                   **kwargs) -> Tuple[any, T]:
     """Pushes the handler on the stack, visits the children and returns result and handler."""
     self._current_handlers.append(
         compile_handler(ctx, self.compiler_ctx, **kwargs))
     retval = self.visitChildren(ctx)
     h = self._current_handlers.pop()
     assert id(h.ctx) == id(ctx), _(
         "Fatal compilation error: Unexpected compilation handler on stack."
     )
     assert not isinstance(
         h, NullCompileHandler), _("Fatal compilation error: Stack error.")
     return retval, h
示例#8
0
 def collect(self) -> List[SsbOperation]:
     if self.is_message_case:
         raise SsbCompilerError(_("Invalid message switch case call."))
     self.compiler_ctx.add_switch_case(self)
     retval = self._process_block(False)
     self.compiler_ctx.remove_switch_case()
     return retval
示例#9
0
    def collect(self) -> List[SsbOperation]:
        if self.scn_var_target is None and not self.ctx.DUNGEON_RESULT():
            raise SsbCompilerError(_("No target for reset."))

        if self.ctx.DUNGEON_RESULT():
            return [self._generate_operation(OPS_FLAG__RESET_DUNGEON_RESULT, [])]

        return [self._generate_operation(OPS_FLAG__RESET_SCENARIO, [self.scn_var_target])]
示例#10
0
    def collect(self) -> SsbLabelJumpBlueprint:
        if self.value is None:
            raise SsbCompilerError(_("No value set for if condition."))

        if self.is_menu_2:
            return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                         OP_CASE_MENU2, [self.value])
        return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_CASE_MENU,
                                     [self.value])
示例#11
0
    def collect(self) -> List[SsbOperation]:
        if self.var_target is None:
            raise SsbCompilerError(_("No variable for assignment."))
        if self.operator is None:
            raise SsbCompilerError(_("No operator set for assignment."))
        if self.value is None:
            raise SsbCompilerError(_("No value set for assignment."))

        if self.ctx.INTEGER():
            index = exps_int(str(self.ctx.INTEGER()))
            # CalcBit / SetPerformance
            if self.value_is_a_variable:
                raise SsbCompilerError(
                    f(
                        _("value(X) can not be used with index based assignments "
                          "(line {self.ctx.start.line}).")))
            if str(self.var_target
                   ) == self.compiler_ctx.performance_progress_list_var_name:
                return [
                    self._generate_operation(OPS_FLAG__SET_PERFORMANCE,
                                             [index, self.value])
                ]
            return [
                self._generate_operation(OPS_FLAG__CALC_BIT,
                                         [self.var_target, index, self.value])
            ]

        # CalcValue / CalcVariable / Set
        if self.value_is_a_variable:
            return [
                self._generate_operation(
                    OPS_FLAG__CALC_VARIABLE,
                    [self.var_target, self.operator.value, self.value])
            ]
        if self.operator == SsbCalcOperator.ASSIGN:
            return [
                self._generate_operation(OPS_FLAG__SET,
                                         [self.var_target, self.value])
            ]
        return [
            self._generate_operation(
                OPS_FLAG__CALC_VALUE,
                [self.var_target, self.operator.value, self.value])
        ]
示例#12
0
    def _resolve_imported_file(self, dir_name):
        """
        Returns the full paths to all imports specified in self.imports.
        If any file can not be found, raises an SsbCompilerError.
        """
        fs = []
        for import_file in self.imports:
            if import_file.startswith('.') or import_file.startswith('/'):
                # Relative or absolute import
                abs_path = os.path.realpath(
                    str(
                        PurePath(
                            PurePosixPath(dir_name).joinpath(
                                PurePosixPath(import_file)))))
                if not os.path.exists(abs_path):
                    raise SsbCompilerError(
                        f(
                            _("The file to import ('{import_file}') was not found."
                              )))
            else:
                # Relative to one of the lookup paths
                abs_path = None
                path_parts = import_file.split('/')
                if '.' in path_parts or '..' in path_parts:
                    raise SsbCompilerError(
                        f(
                            _("Invalid import: '{import_file}'. Non absolute/relative "
                              "imports must not contain relative paths.")))
                for lp in self.lookup_paths:
                    abs_path_c = os.path.realpath(
                        str(
                            PurePath(dir_name).joinpath(
                                PurePosixPath(lp).joinpath(import_file))))
                    if os.path.exists(abs_path_c):
                        abs_path = abs_path_c
                        break
                if abs_path is None:
                    raise SsbCompilerError(
                        f(
                            _("The file to import ('{import_file}') was not found."
                              )))
            fs.append(abs_path)

        return fs
示例#13
0
    def collect(self) -> List[SsbOperation]:
        self.ctx: ExplorerScriptParser.Message_switch_blockContext
        if self.ctx.MESSAGE_SWITCH_MONOLOGUE():
            switch_op = self._generate_operation(
                OP_MESSAGE_SWITCH_MONOLOGUE,
                [self._switch_header_handler.collect()])
        elif self.ctx.MESSAGE_SWITCH_TALK():
            switch_op = self._generate_operation(
                OP_MESSAGE_SWITCH_TALK,
                [self._switch_header_handler.collect()])
        else:
            raise SsbCompilerError(_("Invalid message switch."))
        case_ops = []
        for h in self._case_handlers:
            if not h.is_message_case:
                raise SsbCompilerError(
                    f(
                        _("A message_ switch can only contain cases with strings "
                          "(line {self.ctx.start.line}).")))
            header_handler = h.collect_header_handler()
            if header_handler.get_header_handler_type(
            ) != IntegerLikeCompileHandler:
                raise SsbCompilerError(
                    f(
                        _("Invalid case type for message_ switch (line {self.ctx.start.line})."
                          )))
            string = h.get_text()
            value_blueprint = header_handler.collect()
            # We obviously don't want the bluprint
            value = value_blueprint.params[0]
            case_ops.append(
                self._generate_operation(OP_CASE_TEXT, [value, string]))
        if self._default_handler:
            if not self._default_handler.is_message_case:
                raise SsbCompilerError(
                    f(
                        _("A message_ switch can only contain cases with strings (line {self.ctx.start.line})."
                          )))
            case_ops.append(
                self._generate_operation(OP_DEFAULT_TEXT,
                                         [self._default_handler.get_text()]))

        return [switch_op] + case_ops
    def collect(self) -> List[SsbOperation]:
        if self.var_target is None:
            raise SsbCompilerError(_("No variable for assignment set."))

        return [
            self._generate_operation(OPS_FLAG__SET_SCENARIO, [
                self.var_target,
                exps_int(str(self.ctx.INTEGER(0))),
                exps_int(str(self.ctx.INTEGER(1)))
            ])
        ]
示例#15
0
    def collect(self) -> SsbLabelJumpBlueprint:
        if self.var_target is None:
            raise SsbCompilerError(_("No variable for if condition."))
        if self.operator is None:
            raise SsbCompilerError(_("No operator set for if condition."))
        if self.value is None:
            raise SsbCompilerError(_("No value set for if condition."))

        if self.value_is_a_variable:
            # BranchVariable
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_VARIABLE,
                [self.var_target, self.operator.value, self.value])
        if self.operator == SsbOperator.EQ:
            # Branch
            return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                         OP_BRANCH,
                                         [self.var_target, self.value])
        # BranchValue
        return SsbLabelJumpBlueprint(
            self.compiler_ctx, self.ctx, OP_BRANCH_VALUE,
            [self.var_target, self.operator.value, self.value])
示例#16
0
    def collect(self) -> SsbLabelJumpBlueprint:
        self.ctx: ExplorerScriptParser.If_h_bitContext
        if self.var_target is None:
            raise SsbCompilerError(_("No variable in if condition."))

        var_target_name = None
        if hasattr(self.var_target, 'name'):
            var_target_name = self.var_target.name

        index = exps_int(str(self.ctx.INTEGER()))
        is_simple_positive = self.ctx.NOT() is None

        if var_target_name == self.compiler_ctx.performance_progress_list_var_name:
            return SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_BRANCH_PERFORMANCE,
                [index, 1 if is_simple_positive else 0])
        elif not is_simple_positive:
            raise SsbCompilerError(
                f(
                    _("The variable {var_target_name} can not be used with 'not' "
                      "(line {self.ctx.start.line}).")))
        return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                     OP_BRANCH_BIT, [self.var_target, index])
示例#17
0
    def collect(self) -> SsbLabelJumpBlueprint:
        if self.operator is None:
            raise SsbCompilerError(_("No operator set for if condition."))
        if self.value is None:
            raise SsbCompilerError(_("No value set for if condition."))

        if self.value_is_a_variable:
            # CaseVariable
            return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                         OP_CASE_VARIABLE,
                                         [self.operator.value, self.value])

        #if self.operator == SsbOperator.EQ:
        #    # Case
        #    return SsbLabelJumpBlueprint(
        #        self.compiler_ctx, self.ctx,
        #        OP_CASE, [self.value]
        #    )

        # CaseValue
        return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx,
                                     OP_CASE_VALUE,
                                     [self.operator.value, self.value])
示例#18
0
    def collect(self) -> SsbOperation:
        self.ctx: ExplorerScriptParser.Case_headerContext

        # Complex branches
        if self._header_cmplx_handler:
            if isinstance(self._header_cmplx_handler,
                          IntegerLikeCompileHandler):
                # Switch
                return self._generate_operation(
                    OP_SWITCH, [self._header_cmplx_handler.collect()])
            elif isinstance(self._header_cmplx_handler,
                            OperationCompileHandler):
                # An operation as condition
                op = self._header_cmplx_handler.collect()
                if len(op) != 1:
                    raise SsbCompilerError(
                        _("Invalid content for a switch-header"))
                op = op[0]
                return self._generate_operation(op.op_code.name, op.params)
            else:
                # A regular complex if condition
                return self._header_cmplx_handler.collect()

        raise SsbCompilerError(_("Unknown switch operation."))
示例#19
0
    def collect(self) -> List[SsbOperation]:
        self.ctx: ExplorerScriptParser.Macro_callContext
        name = str(self.ctx.MACRO_CALL())[1:]
        args: List[SsbOpParam] = []
        if self.arg_list_handler:
            args = self.arg_list_handler.collect()
        if name not in self.compiler_ctx.macros.keys():
            raise SsbCompilerError(f(_("Macro {name} not found.")))
        macro = self.compiler_ctx.macros[name]

        self.compiler_ctx.source_map_builder.next_macro_opcode_called_in(
            None, self.ctx.start.line - 1, self.ctx.start.column)
        return macro.build(self.compiler_ctx.counter_ops,
                           self.compiler_ctx.counter_labels,
                           dict(zip(macro.variables, args)),
                           self.compiler_ctx.source_map_builder)
示例#20
0
 def add(self, obj: any):
     if isinstance(obj, CaseBlockCompileHandler):
         self._case_handlers.append(obj)
         return
     if isinstance(obj, DefaultCaseBlockCompileHandler):
         if self._default_handler is not None:
             raise SsbCompilerError(
                 f(
                     _("A switch block can only have a single default case (line {self.ctx.start.line}"
                       )))
         self._default_handler = obj
         return
     if isinstance(obj, IntegerLikeCompileHandler):
         self._switch_header_handler = obj
         return
     self._raise_add_error(obj)
示例#21
0
    def collect(self) -> SsbLabelJumpBlueprint:
        self.ctx: ExplorerScriptParser.Case_headerContext

        # Complex branches
        if self._header_cmplx_handler:
            if isinstance(self._header_cmplx_handler,
                          IntegerLikeCompileHandler):
                # Case
                return SsbLabelJumpBlueprint(
                    self.compiler_ctx, self.ctx, OP_CASE,
                    [self._header_cmplx_handler.collect()])
            else:
                # A regular complex if condition
                return self._header_cmplx_handler.collect()

        raise SsbCompilerError(_("Unknown case operation."))
示例#22
0
    def add(self, obj: any):
        # supports:
        # simple_stmt := operation | cntrl_stmt | jump | call | assignment - For labels a logical error is raised.
        if isinstance(obj, OperationCompileHandler) or isinstance(obj, ControlStatementCompileHandler) \
                or isinstance(obj, JumpCompileHandler) or isinstance(obj, CallCompileHandler) \
                or isinstance(obj, AbstractAssignmentCompileHandler):
            self._check_sub_stmt()
            self._sub_stmt = obj
            return
        if isinstance(obj, LabelCompileHandler):
            raise SsbCompilerError(
                _("A with(){} block can not contain labels."))

        # integer_like (for for_id)
        if isinstance(obj, IntegerLikeCompileHandler):
            self._for_id = obj.collect()
            return

        self._raise_add_error(obj)
示例#23
0
 def get_text(self) -> SsbOpParam:
     if not self.is_message_case:
         raise SsbCompilerError(_("Invalid message switch case call."))
     return self._added_string_handler.collect()
示例#24
0
    def collect(self) -> SsbOperation:
        if self.value is None:
            raise SsbCompilerError(_("No value set for random switch condition."))

        return self._generate_operation(OP_SWITCH_RANDOM, [self.value])
 def __str__(self):
     return f(_("line {self.line}:{self.column}: {self.msg}"))
    def collect(self) -> List[SsbOperation]:
        if self.var_target is None:
            raise SsbCompilerError(_("No variable for clear."))

        return [self._generate_operation(OPS_FLAG__CLEAR, [self.var_target])]
示例#27
0
    def build(self, op_idx_counter: Counter, lbl_idx_counter: Counter, parameters: Dict[str, SsbOpParam],
              smb: SourceMapBuilder) -> List[SsbOperation]:
        """
        Returns new built opcodes for this macro. SsbOpConstants in the blueprints, that have the names of variables,
        are replaced with the values from the parameter dict. The keys for the dict are the names of the variables,
        all must be defined.
        Also updates the source map with macro opcode / position mark entries.
        """
        # Check:
        for var_name in self.variables:
            if var_name not in parameters.keys():
                raise ValueError(f(_("Value for macro variable {var_name} not provided.")))

        # Macro callstack: Push our outer call
        len_real_ops_in_blueprints = len([o for o in self.blueprints if not isinstance(o, SsbLabel)]) + 1
        parameter_mapping = self._create_parameter_mapping(parameters)
        smb.macro_context__push(op_idx_counter.count + len_real_ops_in_blueprints, parameter_mapping)

        # Add the macro start label if we are processed later as a sub-macro, so that the parent macro can push
        # our ops to the callstack.
        out_ops: List[SsbOperation] = [MacroStartSsbLabel(
            lbl_idx_counter(), -1, len_real_ops_in_blueprints, parameter_mapping, f"Macro call {self.name} start label."
        )]

        # End label, also for sub-macros and when we are processing a return statement
        end_label = MacroEndSsbLabel(
            lbl_idx_counter(), -1, f"Macro call {self.name} end label."
        )

        # Maps blueprint label ids to new actual labels
        new_labels: Dict[int, SsbLabel] = {}

        for blueprint_op in self.blueprints:
            # If this a start / end of a macro, update the macro callstack
            if isinstance(blueprint_op, MacroStartSsbLabel):
                smb.macro_context__push(op_idx_counter.count + blueprint_op.length_of_macro,
                                        self._replace_in_param_mapping(blueprint_op.parameter_mapping, parameters))
            elif isinstance(blueprint_op, MacroEndSsbLabel):
                smb.macro_context__pop()

            if isinstance(blueprint_op, SsbLabel):
                if blueprint_op.id not in new_labels.keys():
                    # Copy the label with a new proper index
                    new_labels[blueprint_op.id] = self._copy_blueprint_label(
                        lbl_idx_counter, blueprint_op
                    )
                    new_labels[blueprint_op.id].markers = blueprint_op.markers.copy()
                out_ops.append(new_labels[blueprint_op.id])
            elif isinstance(blueprint_op, SsbLabelJump):
                if blueprint_op.label.id not in new_labels.keys():
                    # Copy the label with a new proper index
                    new_labels[blueprint_op.label.id] = self._copy_blueprint_label(
                        lbl_idx_counter, blueprint_op.label
                    )
                    new_labels[blueprint_op.label.id].markers = blueprint_op.label.markers.copy()
                new_root = self._build_op(op_idx_counter, blueprint_op.root, smb, parameters)
                new_jumps = SsbLabelJump(new_root, new_labels[blueprint_op.label.id])
                new_jumps.markers = blueprint_op.markers.copy()
                out_ops.append(new_jumps)
            elif blueprint_op.op_code.name == OP_RETURN:
                # Process return: Exit the macro instead
                replacement_blueprint_op = SsbOperation(blueprint_op.offset, SsbOpCode(-1, OP_JUMP), [])
                out_ops.append(SsbLabelJump(
                    self._build_op(
                        op_idx_counter, replacement_blueprint_op, smb, parameters
                    ), end_label
                ))
            else:
                out_ops.append(self._build_op(op_idx_counter, blueprint_op, smb, parameters))
        for pos_mark in self.source_map.get_position_marks__direct():
            smb.add_macro_position_mark(self.included__relative_path, self.name, pos_mark)
        # Also add the sub-macro position marks to the map
        for m in self.source_map.get_position_marks__macros():
            smb.add_macro_position_mark(*m)

        out_ops.append(end_label)

        # Macro callstack: Pop our return address
        smb.macro_context__pop()

        return out_ops
示例#28
0
 def _check_sub_stmt(self):
     if self._sub_stmt is not None:
         raise SsbCompilerError(
             _("A with(){} block needs exactly one statement."))
示例#29
0
    def collect(self) -> SsbOperation:
        if self.value is None:
            raise SsbCompilerError(
                _("No dungeon id set for dungeon_mode switch condition."))

        return self._generate_operation(OP_SWITCH_DUNGEON_MODE, [self.value])
示例#30
0
    def compile(self,
                explorerscript_src: str,
                file_name: str,
                macros_only=False,
                original_base_file=None):
        """
        After compiling, the components are present in this object's attributes.

        file_name is the full path to the file that is being compiled.
        original_base_file is the full path to the file that originally started an import chain. If not given, file_name
        is used.
        If macros_only is True, then an exception is raised, if the script files contains any routines.

        :raises: ParseError: On parsing errors
        :raises: SsbCompilerError: On logical compiling errors
        :raises: ValueError: On misc. unexpected compilation errors
        """
        logger.debug(
            "<%d> Compiling ExplorerScript (-> %s)... - Macros only:%d, base:%s",
            id(self), file_name, macros_only, original_base_file)
        self.routine_infos = None
        self.routine_ops = None
        self.named_coroutines = None
        self.source_map = None
        self.imports = []
        self.macros = {}
        if original_base_file is None:
            original_base_file = file_name

        reader = ExplorerScriptReader(explorerscript_src)
        tree = reader.read()
        parser = reader.get_parser()

        # Collect imports
        logger.debug("<%d> Collecting imports...", id(self))
        self.imports = ImportVisitor().visit(tree)

        # Resolve imports and load macros in the imported files
        for subfile_path in self._resolve_imported_file(
                os.path.dirname(file_name)):
            logger.debug("<%d> Compiling sub-file %s...", id(self),
                         subfile_path)
            if subfile_path in self.recursion_check:
                raise SsbCompilerError(
                    f(
                        _("Infinite recursion detected while trying to load "
                          "an ExplorerScript file from {subfile_path}.\n"
                          "Tried loading from: {file_name}.")))
            subfile_compiler = self.__class__(
                self.performance_progress_list_var_name,
                self.lookup_paths,
                recursion_check=self.recursion_check + [file_name])
            with open_utf8(subfile_path, 'r') as file:
                subfile_compiler.compile(file.read(),
                                         subfile_path,
                                         macros_only=True,
                                         original_base_file=original_base_file)
            self.macros.update(
                self._macros_add_filenames(subfile_compiler.macros,
                                           original_base_file, subfile_path))

        # Sort the list of macros by how they are used
        logger.debug("<%d> Building macro resolution order...", id(self))
        self.macro_resolution_order = MacroResolutionOrderVisitor(
            self.macros).visit(tree)

        # Loads and compiles modules in base file
        # (we write our absolute path there only for now, if this is an inclusion, the outer compiler will update it).
        logger.debug("<%d> Compiling macros...", id(self))
        self.macros.update(
            self._macros_add_filenames(
                MacroVisitor(self.performance_progress_list_var_name,
                             self.macros,
                             self.macro_resolution_order).visit(tree), None,
                file_name))

        # Check if macros_only
        if macros_only:
            # Check if the file contains any routines
            if HasRoutinesVisitor().visit(parser.start()):
                # noinspection PyUnusedLocal
                fn = os.path.basename(file_name)
                raise SsbCompilerError(
                    f(_("{fn}: Macro scripts must not contain any routines.")))
            return self

        # Start Compiling
        try:
            try:
                logger.debug("<%d> Compiling routines...", id(self))
                compiler_visitor = RoutineVisitor(
                    self.performance_progress_list_var_name, self.macros)
                compiler_visitor.visit(tree)
            except Exception as ex:
                # due to the stack nature of the decompile visitor, we get many stack exceptions after raising
                # the first. Raise the last exception in the context chain.
                while ex.__context__ is not None:
                    ex = ex.__context__
                raise ex
        except AssertionError as e:
            raise ValueError(str(e)) from e

        assert routine_op_offsets_are_ordered(compiler_visitor.routine_ops)

        # Copy from listener / remove labels and label jumps
        label_finalizer = LabelFinalizer(
            strip_last_label(compiler_visitor.routine_ops))

        self.routine_ops = OpsLabelJumpToRemover(
            label_finalizer.routines, label_finalizer.label_offsets).routines
        self.routine_infos = compiler_visitor.routine_infos
        self.named_coroutines = compiler_visitor.named_coroutines
        self.source_map = compiler_visitor.source_map_builder.build()

        # Done!
        return self