Пример #1
0
    def _process_block(self,
                       insert_the_jump_if_needed=True) -> List[SsbOperation]:
        """
        This processes the sub-block and returns the generated sub-block opcodes.
        - Opcodes are collected (warning: opcode indexing! Make sure you allocated index numbers for the header ops!)
        - From the header jump blueprints, jump opcodes are created [only applies if there are some!]
            - If the sub-block only has one single label jump, it's removed and the target
              of the headers is changed to the target of that jump
            - Otherwise the jump is set to the offset id of the first opcode in the sub-block.
        - If insert_the_jump_if_needed:
            - If the last op of a block does not end the control flow, a label jump is inserted.
                - The label is set to None and is expected to be replaced with the real end label of
                  the if (see _update_last_jump_to_end_label).

        After this the generated header opcodes can be retrieved using get_processed_header_jumps()

        The returned list is guaranteed to have one entry: the end label (next opcode outside this block).
        It usually also has a start label (if the only-one-jump case didn't happen).
        """
        ops: List[SsbOperation] = []

        for h in self._added_handlers:
            ops += h.collect()

        self.end_label = SsbLabel(
            self.compiler_ctx.counter_labels(), -1,
            f'{self.__class__.__name__} block end label')

        if len(self._header_jump_blueprints) > 0 and len(ops) == 1 \
                and isinstance(ops[0], SsbLabelJump) and ops[0].root.op_code.name == OP_JUMP:
            # Just has a jump, insert that into the headers instead
            for hjb in self._header_jump_blueprints:
                self.processed_header_jumps.append(hjb.build_for(ops[0].label))
            self.start_label = ops[0].label
            return [
                self.end_label
            ]  # in this case we don't actually have any operations to write

        if insert_the_jump_if_needed and (
                len(ops) < 1 or not does_op_end_control_flow(
                    ops[-1], ops[-2] if len(ops) > 1 else None)):
            # insert the end label jump
            ops.append(self._generate_empty_jump())

        # Generate the start and end label for this block
        self.start_label = SsbLabel(
            self.compiler_ctx.counter_labels(), -1,
            f'{self.__class__.__name__} block start label')
        ops.insert(0, self.start_label)
        ops.append(self.end_label)
        # Update headers to jump to/over the block
        for hjb in self._header_jump_blueprints:
            if hjb.jump_is_positive:
                self.processed_header_jumps.append(
                    hjb.build_for(self.start_label))
            else:
                self.processed_header_jumps.append(
                    hjb.build_for(self.end_label))

        return ops
Пример #2
0
    def collect(self) -> List[SsbOperation]:
        self.compiler_ctx.add_loop(self)
        is_positive = self.ctx.NOT() is None

        if is_positive:
            check_label = SsbLabel(self.compiler_ctx.counter_labels(), -1,
                                   f'{self.__class__.__name__} check label')
            block_label = SsbLabel(self.compiler_ctx.counter_labels(), -1,
                                   f'{self.__class__.__name__} block label')
            retval = [
                self._start_label,
                self._generate_jump_operation(OP_JUMP, [], check_label),
                block_label
            ] + self._process_block(False) + [
                check_label,
                self._branch_blueprint.build_for(block_label), self._end_label
            ]
        else:
            retval = [
                self._start_label,
                self._branch_blueprint.build_for(self._end_label),
            ] + self._process_block(False) + [
                self._generate_jump_operation(OP_JUMP, [], self._start_label),
                self._end_label
            ]
        self.compiler_ctx.remove_loop()
        return retval
Пример #3
0
 def __init__(self, ctx, compiler_ctx: CompilerCtx):
     super().__init__(ctx, compiler_ctx)
     self._start_label = SsbLabel(
         self.compiler_ctx.counter_labels(), -1,
         f'{self.__class__.__name__} outer start label')
     self._end_label = SsbLabel(
         self.compiler_ctx.counter_labels(), -1,
         f'{self.__class__.__name__} outer end label')
Пример #4
0
 def __init__(self, ctx, compiler_ctx: CompilerCtx):
     super().__init__(ctx, compiler_ctx)
     self._block_label = SsbLabel(
         self.compiler_ctx.counter_labels(), -1, f'{self.__class__.__name__} block label'
     )
     self._new_run_label = SsbLabel(
         self.compiler_ctx.counter_labels(), -1, f'{self.__class__.__name__} new run label'
     )
     self._initial_label = SsbLabel(
         self.compiler_ctx.counter_labels(), -1, f'{self.__class__.__name__} initial label'
     )
     self._branch_blueprint: Optional[SsbLabelJumpBlueprint] = None
     self._init_statement_handler: Optional[AbstractStatementCompileHandler] = None
     self._end_statement_handler: Optional[AbstractStatementCompileHandler] = None
Пример #5
0
    def collect(self) -> List[SsbLabel]:
        label_name = str(self.ctx.IDENTIFIER())

        if label_name in self.compiler_ctx.collected_labels:
            label = self.compiler_ctx.collected_labels[label_name]
        else:
            label = SsbLabel(
                self.compiler_ctx.counter_labels(), -1, f'proper label, named {label_name}'
            )
            self.compiler_ctx.collected_labels[label_name] = label

        return [label]
Пример #6
0
 def collect(self) -> List[SsbOperation]:
     """
     We generate a label jump (using call) now.
     """
     self.ctx: ExplorerScriptParser.CallContext
     label_name = str(self.ctx.IDENTIFIER())
     if label_name in self.compiler_ctx.collected_labels:
         label = self.compiler_ctx.collected_labels[label_name]
     else:
         label = SsbLabel(self.compiler_ctx.counter_labels(), -1,
                          f'proper label, named {label_name}')
         self.compiler_ctx.collected_labels[label_name] = label
     return [self._generate_jump_operation(OP_CALL, [], label)]
Пример #7
0
 def _copy_blueprint_label(self, lbl_idx_counter: Counter, blueprint_op: SsbLabel):
     if isinstance(blueprint_op, MacroStartSsbLabel):
         return MacroStartSsbLabel(
             lbl_idx_counter(), -1, blueprint_op.length_of_macro,
             blueprint_op.parameter_mapping,
             blueprint_op.debugging_note
         )
     elif isinstance(blueprint_op, MacroEndSsbLabel):
         return MacroEndSsbLabel(
             lbl_idx_counter(), -1, blueprint_op.debugging_note
         )
     else:
         return SsbLabel(
             lbl_idx_counter(), -1, blueprint_op.debugging_note
         )
Пример #8
0
 def collect(self) -> List[SsbOperation]:
     """
     We generate a label jump now. This might be removed & merged with the if/switch-case
     before it (if applicable and only operation in block), in the appropriate parent handler.
     """
     self.ctx: ExplorerScriptParser.JumpContext
     label_name = str(self.ctx.IDENTIFIER())
     if label_name in self.compiler_ctx.collected_labels:
         label = self.compiler_ctx.collected_labels[label_name]
     else:
         label = SsbLabel(
             self.compiler_ctx.counter_labels(), -1, f'proper label, named {label_name}'
         )
         self.compiler_ctx.collected_labels[label_name] = label
     return [self._generate_jump_operation(OP_JUMP, [], label)]
Пример #9
0
    def collect(self) -> List[SsbOperation]:
        self.ctx: ExplorerScriptParser.If_blockContext
        if_header__allocations: List[int] = []  # list index in ops!
        if_block__was_output = False
        elseif_header__allocations: List[List[int]] = []  # list index in ops!
        elseif_block__was_output: List[bool] = []
        # 0. Prepare the end label to insert.
        end_label = SsbLabel(
            self.compiler_ctx.counter_labels(), -1, 'entire if-block end label'
        )
        is_positive = self.ctx.NOT() is None

        ops: List[Optional[SsbOperation]] = []

        # 1. Go over all if header ops:
        for h in self._if_header_handlers:
            ops.append(None)
            h.set_positive(is_positive)
            jmpb = h.collect()
            self._header_jump_blueprints.append(jmpb)
            jmpb.set_index_number(self.compiler_ctx.counter_ops.allocate(1))
            # Allocate 1 for it's branch op
            if_header__allocations.append(len(ops) - 1)
        if not self._header_jump_blueprints[0].jump_is_positive:
            # If all the header jumps are negative (if one is all are!) output the block now, else do it later
            if_block__was_output = True
            ops += self._process_block()
        # 2. For each else if: Go over all if header ops:
        for else_if_h in self._else_if_handlers:
            jmp_blueprints = else_if_h.create_header_jump_templates()
            this_elseif__allocations = []
            this_elseif__was_output = False
            for jmpb in jmp_blueprints:
                ops.append(None)
                jmpb.set_index_number(self.compiler_ctx.counter_ops.allocate(1))
                # Allocate 1 for it's branch op
                this_elseif__allocations.append(len(ops) - 1)
            if not jmp_blueprints[0].jump_is_positive:
                # If all the header jumps are negative (if one is all are!) output the block now, else do it later
                this_elseif__was_output = True
                ops += else_if_h.collect()
            elseif_header__allocations.append(this_elseif__allocations)
            elseif_block__was_output.append(this_elseif__was_output)
        # 3. Collect else sub block ops
        if self._else_handler:
            ops += self._else_handler.collect()
        else:
            # 3b. If no else: Create an else block with just one jump without target
            ops.append(self._generate_empty_jump())
        # 4. Collect if sub block ops, if not already done
        if not if_block__was_output:
            if_block__was_output = True
            ops += self._process_block()
        # 5. Collect remaining elseif sub block ops
        for i, else_if_h in enumerate(self._else_if_handlers):
            if not elseif_block__was_output[i]:
                elseif_block__was_output[i] = True
                ops += else_if_h.collect()
        # 6. Go through all blocks and check, if the last op is a jump without target
        #    if so: insert jump to the end label
        for op in ops:
            if isinstance(op, SsbLabelJump) and op.root.op_code.name == OP_JUMP and op.label is None:
                op.label = end_label
        # 7. Process header ops
        for i, jmp in enumerate(self.get_processed_header_jumps()):
            allocation = if_header__allocations[i]
            ops[allocation] = jmp
        for i, else_if_h in enumerate(self._else_if_handlers):
            for j, jmp in enumerate(else_if_h.get_processed_header_jumps()):
                allocation = elseif_header__allocations[i][j]
                ops[allocation] = jmp

        return ops + [end_label]
Пример #10
0
    def collect(self) -> List[SsbOperation]:
        self.ctx: ExplorerScriptParser.Switch_blockContext
        # 0. Prepare labels to insert
        default_start_label = SsbLabel(self.compiler_ctx.counter_labels(), -1,
                                       'switch default start label')
        end_label = SsbLabel(self.compiler_ctx.counter_labels(), -1,
                             'entire switch-block end label')
        default_jmp_to_case_block: Optional[SsbLabelJumpBlueprint] = None
        case_ops: List[SsbOperation] = []
        default_ops: List[SsbOperation]

        # 0b. Switch op
        switch_op = self._switch_header_handler.collect()

        # If there is no default and also no cases... we really don't need anything.
        if self._default_handler is None and len(self._case_handlers) == 0:
            return [switch_op]

        # 1. For each case: Generate and allocate case header op templates
        for h in self._case_handlers:
            h.set_end_label(end_label)
            if h.is_message_case:
                raise SsbCompilerError(
                    f(
                        _("A switch case must contain a list of statements "
                          "(line {self.ctx.start.line}).")))
            jmp_blueprint = h.get_header_jump_template()
            first = self.compiler_ctx.counter_ops.allocate(1)
            jmp_blueprint.set_index_number(first)
            # A little special case: If the switch header op is SwitchScenario and the case is CaseValue, change it to
            # CaseScenario, just to be more consistent with how the game odes it.
            if switch_op.op_code.name == OP_SWITCH_SCENARIO and jmp_blueprint.op_code_name == OP_CASE_VALUE:
                jmp_blueprint.op_code_name = OP_CASE_SCENARIO

        # 2. Default block (first because default is after no case op branched)
        if self._default_handler:
            self._default_handler.set_end_label(end_label)
            default_ops = []
            # Insert a jump blueprint for now, note it, and if it comes up later during 3b,
            # process it like explained there.
            default_jmp_to_case_block = SsbLabelJumpBlueprint(
                self.compiler_ctx, self.ctx, OP_JUMP, [])
            self._case_handlers.insert(self._default_handler_index,
                                       self._default_handler)
        else:
            # 2c. If no default: Create a default block with just one jump to end label
            default_ops = [
                self._generate_jump_operation(OP_JUMP, [], end_label)
            ]
        # 3. For each case:
        cases_waiting_for_a_block = []
        for i, h in enumerate(self._case_handlers):
            if not h.has_sub_block_handlers():
                # 3b. Else: Jump to the block of the next case which has a block.
                cases_waiting_for_a_block.append(h)
            else:
                # 3a. If the case has operations: Collect case sub-block ops
                ops = h.collect()
                if isinstance(h, DefaultCaseBlockCompileHandler):
                    default_ops = [
                        default_jmp_to_case_block.build_for(
                            h.get_start_label())
                    ]
                for h_waiting in cases_waiting_for_a_block:
                    if isinstance(h_waiting, DefaultCaseBlockCompileHandler):
                        default_ops = [
                            default_jmp_to_case_block.build_for(
                                h.get_start_label())
                        ]
                    else:
                        h_waiting.set_processed_header_jumps([
                            h_waiting.get_header_jump_template().build_for(
                                h.get_start_label())
                        ])
                cases_waiting_for_a_block = []
                case_ops += ops
        # 3c. Edge case: We expected a next case with ops, but got end of switch instead. Invalid!
        if len(cases_waiting_for_a_block) > 0:
            raise SsbCompilerError(
                f(_("Unexpected switch end (line {self.ctx.start.line})")))
        # 4. Build ops list
        header_ops = [switch_op]
        for h in self._case_handlers:
            header_ops += h.get_processed_header_jumps()
        return header_ops + [default_start_label
                             ] + default_ops + case_ops + [end_label]
Пример #11
0
    def run(self, status: Status):
        status.step('Unlocking dungeons...')

        new_ops = []
        coro_id = self.static_data.script_data.common_routine_info__by_name['EVENT_DIVIDE'].id
        ops = self.static_data.script_data.op_codes__by_name

        # DECOMPILE
        ssb: Ssb = get_script(
            'SCRIPT/COMMON/unionall.ssb', self.rom, static_data=self.static_data
        )
        routine_ops = list(OpsLabelJumpToResolver(ssb.get_filled_routine_ops()))

        # CREATE NEW OPS
        off = Counter()
        off.count = -10000
        for dungeon_id, dungeon in self.config['dungeons']['settings'].items():
            if dungeon['unlock']:
                if len(new_ops) < 1:
                    new_ops.append(SkyTempleSsbOperation(
                        off(), ops['debug_Print'][0], [SsbOpParamConstString('SkyTemple Randomizer: Dungeon Unlock...')]
                    ))
                label_closed = SsbLabel(9000 + dungeon_id, coro_id)
                label_request = SsbLabel(9200 + dungeon_id, coro_id)
                label_else = SsbLabel(9400 + dungeon_id, coro_id)
                new_ops.append(SkyTempleSsbOperation(
                    off(), ops['SwitchDungeonMode'][0], [dungeon_id]
                ))
                new_ops.append(SsbLabelJump(SkyTempleSsbOperation(
                    off(), ops['Case'][0], [0]
                ), label_closed))
                new_ops.append(SsbLabelJump(SkyTempleSsbOperation(
                    off(), ops['Case'][0], [2]
                ), label_request))
                new_ops.append(SsbLabelJump(SkyTempleSsbOperation(
                    off(), ops['Jump'][0], []
                ), label_else))
                new_ops.append(label_closed)
                new_ops.append(SkyTempleSsbOperation(
                    off(), ops['flag_SetDungeonMode'][0], [dungeon_id, 1]
                ))
                new_ops.append(SsbLabelJump(SkyTempleSsbOperation(
                    off(), ops['Jump'][0], []
                ), label_else))
                new_ops.append(label_request)
                new_ops.append(SkyTempleSsbOperation(
                    off(), ops['flag_SetDungeonMode'][0], [dungeon_id, 3]
                ))
                new_ops.append(label_else)

        routine_ops[coro_id] = new_ops + routine_ops[coro_id]
        # COMPILE
        label_finalizer = LabelFinalizer(strip_last_label(routine_ops))
        routine_ops = OpsLabelJumpToRemover(routine_ops, label_finalizer.label_offsets).routines
        new_ssb, _ = ScriptCompiler(self.static_data).compile_structured(
            [b for a, b in ssb.routine_info],
            routine_ops,
            [x.name for x in self.static_data.script_data.common_routine_info__by_id],
            SourceMap.create_empty()
        )

        self.rom.setFileByName('SCRIPT/COMMON/unionall.ssb', FileType.SSB.serialize(
            new_ssb, static_data=self.static_data
        ))
        clear_script_cache_for('SCRIPT/COMMON/unionall.ssb')

        status.done()