Beispiel #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}).")))
Beispiel #2
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 _parse_param(self, param: SsbOpParam, built_strings: Dict[str, List[str]], built_constants: List[str]) -> int:
        if isinstance(param, int):
            return param

        if isinstance(param, SsbOpParamConstant):
            try:
                return SsbConstant(param.name, self.rom_data.script_data).value.id
            except ValueError as err:
                raise SsbCompilerError(str(err)) from err

        if isinstance(param, SsbOpParamConstString):
            i = len(built_constants)
            built_constants.append(param.name)
            return i

        if isinstance(param, SsbOpParamLanguageString):
            i = len(built_strings[next(iter(built_strings.keys()))])
            if len(param.strings.keys()) == 1:
                # Single language convenience mode, apply this to all languages.
                only_value = param.strings[next(iter(param.strings.keys()))]
                for lang in built_strings.keys():
                    built_strings[lang].append(only_value)
            else:
                # Multi language regular case. All languages must be known.
                for lang, string in param.strings.items():
                    if lang not in built_strings:
                        raise SsbCompilerError(f(_("Unknown language for string: {lang}")))
                    built_strings[lang].append(string)
            return StringIndexPlaceholder(i)

        raise SsbCompilerError(f(_("Invalid parameter supplied for an operation: {param}")))
Beispiel #4
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
 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']}').")))
Beispiel #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 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])
        ]
Beispiel #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
Beispiel #9
0
 def collect(self) -> SsbOperator:
     self.ctx: ExplorerScriptParser.Conditional_operatorContext
     if self.ctx.OP_FALSE():
         return SsbOperator.FALSE
     if self.ctx.OP_TRUE():
         return SsbOperator.TRUE
     if self.ctx.OP_EQ():
         return SsbOperator.EQ
     if self.ctx.OP_GE():
         return SsbOperator.GE
     if self.ctx.OP_LE():
         return SsbOperator.LE
     if self.ctx.CLOSE_SHARP():
         return SsbOperator.GT
     if self.ctx.OPEN_SHARP():
         return SsbOperator.LT
     if self.ctx.OP_NEQ():
         return SsbOperator.NOT
     if self.ctx.OP_AND():
         return SsbOperator.AND
     if self.ctx.OP_XOR():
         return SsbOperator.XOR
     if self.ctx.OP_BICH():
         return SsbOperator.BIT_SET
     raise SsbCompilerError("Unknown conditional operator.")
Beispiel #10
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])]
 def collect(self) -> Union[int, SsbOpParamConstant]:
     self.ctx: ExplorerScriptParser.Integer_likeContext
     if self.ctx.INTEGER():
         return exps_int(str(self.ctx.INTEGER()))
     if self.ctx.IDENTIFIER():
         return SsbOpParamConstant(str(self.ctx.IDENTIFIER()))
     if self.ctx.VARIABLE():
         return SsbOpParamConstant(str(self.ctx.VARIABLE()))
     raise SsbCompilerError("Unknown 'integer like'.")
Beispiel #12
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])
    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
Beispiel #14
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])
        ]
    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
Beispiel #16
0
 def collect(
         self) -> Union[SsbOpParamLanguageString, SsbOpParamConstString]:
     self.ctx: ExplorerScriptParser.StringContext
     if self.ctx.STRING_LITERAL():
         return SsbOpParamConstString(
             string_literal(self.ctx.STRING_LITERAL()))
     if self.lang_string_handler:
         return self.lang_string_handler.collect()
     raise SsbCompilerError(
         "Invalid string, neither literal nor language string")
    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)))
            ])
        ]
 def collect(self) -> SsbCalcOperator:
     self.ctx: ExplorerScriptParser.Assign_operatorContext
     if self.ctx.OP_MINUS():
         return SsbCalcOperator.MINUS
     if self.ctx.OP_PLUS():
         return SsbCalcOperator.PLUS
     if self.ctx.OP_MULTIPLY():
         return SsbCalcOperator.MULTIPLY
     if self.ctx.OP_DIVIDE():
         return SsbCalcOperator.DIVIDE
     if self.ctx.ASSIGN():
         return SsbCalcOperator.ASSIGN
     raise SsbCompilerError("Unknown conditional operator.")
Beispiel #19
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])
Beispiel #20
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])
Beispiel #21
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])
    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."))
Beispiel #23
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."))
 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)
Beispiel #25
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)
Beispiel #26
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)
Beispiel #27
0
    def add(self, obj: any):
        if isinstance(obj, IntegerLikeCompileHandler):
            if self.var_target is None:
                # integer_like[0] -> variable
                self.var_target = obj.collect()
                return
            if self.value is None:
                # (integer_like[1] | value_of) -> var to set to
                self.value = obj.collect()
                self.value_is_a_variable = False
                return
            raise SsbCompilerError("Assignment: unexpected INTEGER_LIKE.")
        if isinstance(obj, ConditionalOperatorCompileHandler):
            self.operator = obj.collect()
            return
        if isinstance(obj, ValueOfCompileHandler):
            self.value = obj.collect()
            self.value_is_a_variable = True
            return

        self._raise_add_error(obj)
Beispiel #28
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()
Beispiel #29
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 compile_structured(
            self,
            routine_infos: List[SsbRoutineInfo],
            routine_ops: List[List[SsbOperation]],
            named_coroutines: List[str],
            original_source_map: SourceMap
    ) -> Tuple[Ssb, SourceMap]:
        """Compile the structured data from a base compiler for SsbScript or ExplorerScript into an SSB model."""
        logger.debug("Assembling SSB model...")

        model = Ssb.create_empty(self.rom_data.script_data)
        if len(routine_ops) != len(routine_ops) != len(named_coroutines):
            raise SsbCompilerError(_("The routine data lists for the decompiler must have the same lengths."))

        # Build routines and opcodes.
        if len(routine_ops) > 0:
            header_class = SsbHeaderUs
            if self.rom_data.game_region == GAME_REGION_EU:
                header_class = SsbHeaderEu

            built_strings: Dict[str, List[str]] = {lang: [] for lang in header_class.supported_langs()}
            built_constants: List[str] = []

            for i, r in enumerate(routine_infos):
                if r is None:
                    raise SsbCompilerError(f(_("Routine {i} not found.")))

            input_routine_structure: List[
                Tuple[SsbRoutineInfo, str, List[SsbOperation]]
            ] = list(zip(routine_infos, named_coroutines, routine_ops))

            # The cursor position of the written routine opcodes.
            # The opcodes start after the routine info, which has a fixed length, based on the number of routines.
            opcode_cursor = SSB_LEN_ROUTINE_INFO_ENTRY * len(input_routine_structure) + SSB_PADDING_BEFORE_ROUTINE_INFO
            # If it has any coroutines, they all have to be.
            has_coroutines = routine_infos[0].type == SsbRoutineType.COROUTINE

            # Run coroutine checks and sortings.
            if has_coroutines:
                # Assert, that the data contains all coroutines from the ROM schema and sort all three lists by this
                if len(input_routine_structure) != len(self.rom_data.script_data.common_routine_info):
                    raise SsbCompilerError(
                        f(_("The script must contain exactly {len(self.rom_data.script_data.common_routine_info)} coroutines."))
                    )
                if len(routine_infos) != len(set(named_coroutines)):
                    raise SsbCompilerError(f(_("The script must not contain any duplicate coroutines.")))
                try:
                    input_routine_structure = sorted(
                        input_routine_structure, key=lambda k: self.rom_data.script_data.common_routine_info__by_name[k[1]].id
                    )
                except KeyError as err:
                    raise SsbCompilerError(f(_("Unknown coroutine {err}"))) from err

            # Build Routine Infos
            built_routine_info_with_offset: List[Tuple[int, SsbRoutineInfo]] = []
            built_routine_ops: List[List[SsbOperation]] = []
            # A list of lists for ALL opcodes that maps all opcode indices to their memory address.
            opcode_index_mem_offset_mapping: Dict[int, int] = {}
            bytes_written_last_rtn = 0

            for i, (input_info, __, input_ops) in enumerate(input_routine_structure):
                if (
                        has_coroutines and input_info.type != SsbRoutineType.COROUTINE
                ) or (
                        not has_coroutines and input_info.type == SsbRoutineType.COROUTINE
                ):
                    raise SsbCompilerError(f(_("Coroutines and regular routines can not be mixed in a script file.")))

                routine_start_cursor = opcode_cursor
                # Build OPs
                built_ops: List[SkyTempleSsbOperation] = []
                if len(input_ops) == 0:
                    # ALIAS ROUTINE. This alias the PREVIOUS routine
                    routine_start_cursor = opcode_cursor - bytes_written_last_rtn
                else:
                    bytes_written_last_rtn = 0
                    for in_op in input_ops:
                        if in_op.op_code.name not in self.rom_data.script_data.op_codes__by_name:
                            raise SsbCompilerError(f(_("Unknown operation {in_op.op_code.name}.")))
                        op_codes: List[Pmd2ScriptOpCode] = self.rom_data.script_data.op_codes__by_name[in_op.op_code.name]
                        if len(op_codes) > 1:
                            # Can be either a variable length opcode or the "normal" variant.
                            var_len_op_code = next(o for o in op_codes if o.params == -1)
                            normal_op_code = next(o for o in op_codes if o.params != -1)
                            if self._correct_param_list_len(in_op.params) == normal_op_code.params:
                                op_code = normal_op_code
                            elif self._correct_param_list_len(in_op.params) > normal_op_code.params:
                                op_code = var_len_op_code
                            else:
                                raise SsbCompilerError(f(_("The number of parameters for {normal_op_code.name} "
                                                          "must be at least {normal_op_code.params}, is {self._correct_param_list_len(in_op.params)}.")))
                        else:
                            op_code = op_codes[0]
                        new_params: List[int] = []
                        op_len = 2
                        if op_code.params == -1:
                            # Handle variable length opcode by inserting the number of opcodes as the first argument.
                            # ... nothing to do here! Writing the first "meta-argument" for the number of arguments
                            # is the job of the writer later!
                            op_len += 2
                            pass
                        elif self._correct_param_list_len(in_op.params) != op_code.params:
                            # TODO: This might be a confusing count for end users in the case of position markers.
                            raise SsbCompilerError(f(_("The number of parameters for {op_code.name} "
                                                       "must be {op_code.params}, is {self._correct_param_list_len(in_op.params)}.")))
                        for param in in_op.params:
                            if isinstance(param, SsbOpParamPositionMarker):
                                # Handle multi-argument case position markers
                                new_params.append(param.x_offset)
                                new_params.append(param.y_offset)
                                new_params.append(param.x_relative)
                                new_params.append(param.y_relative)
                                op_len += 8
                            else:
                                # Handle the rest
                                new_params.append(self._parse_param(param, built_strings, built_constants))
                                op_len += 2
                        built_ops.append(SkyTempleSsbOperation(opcode_cursor, op_code, new_params))

                        # Create actual offset mapping for this opcode and update source map
                        opcode_index_mem_offset_mapping[in_op.offset] = int(opcode_cursor / 2)

                        bytes_written_last_rtn += op_len
                        opcode_cursor += op_len

                # Find out the target for this routine if it's specified by name
                if input_info.linked_to == -1:
                    input_info.linked_to = SsbConstant(input_info.linked_to_name, self.rom_data.script_data).value.id

                built_routine_info_with_offset.append((routine_start_cursor, input_info))
                built_routine_ops.append(built_ops)

            # Second pass: Update all jumps to their correct place and update string index positions
            for built_routine in built_routine_ops:
                for op in built_routine:
                    if op.op_code.name in OPS_WITH_JUMP_TO_MEM_OFFSET:
                        param_id = OPS_WITH_JUMP_TO_MEM_OFFSET[op.op_code.name]
                        index_to_jump_to = op.params[param_id]
                        op.params[param_id] = opcode_index_mem_offset_mapping[index_to_jump_to]
                    for i, param in enumerate(op.params):
                        if isinstance(param, StringIndexPlaceholder):
                            # If the parameter is a reference to a language string, the length of the constants
                            # has to be added, because the language strings are after the const strings.
                            op.params[i] = len(built_constants) + int(param)

            # Fill the model
            model.routine_info = built_routine_info_with_offset
            model.routine_ops = built_routine_ops
            model.constants = built_constants
            model.strings = built_strings

            # Update the source map
            original_source_map.rewrite_offsets(opcode_index_mem_offset_mapping)

        return model, original_source_map