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
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
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')
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
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]
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)]
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 )
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)]
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]
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]
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()