PK!8~~nibbler/__init__.pyfrom __future__ import annotations import builtins from copy import deepcopy from enum import Enum from sys import modules from types import FunctionType from typing import _eval_type from typing import Any from typing import Callable from typing import Dict from typing import List from .annotations import Constant from .config import DEFAULT_CONFIG from .constants import CODE_TYPE from .containers import Context CONSTANT_TYPE = type(Constant) MODULE_TYPE = type(modules[list(modules.keys())[0]]) __all__ = ["Constant", "Nibbler"] class FunctionKind(Enum): CONSTANT = 1 INLINE = 2 class Nibbler: def __init__( self, module_namespace: Dict[str, Any], deep_copy: bool = True, config: List[Callable[CODE_TYPE, Context]] = DEFAULT_CONFIG, debug: bool = False, ): if "__name__" not in module_namespace: # To-Do: Subclass ValueError raise ValueError("provided namespace is not a module") self._module_namespace: Dict[str, Any] = module_namespace self._config: List[Callable[CODE_TYPE, Context]] = config self._debug: bool = debug self._module_name: str = module_namespace["__name__"] self._module: MODULE_TYPE = modules[self._module_name] self._evaluated_annotations: Dict[str, Any] = { name: _eval_type(annotation, module_namespace, {}) for name, annotation in ( module_namespace["__annotations__"] if "__annotations__" in module_namespace else {} ).items() } self._constant_variables: Dict[str, Any] = { name: deepcopy(value) if deep_copy else value for name, value in module_namespace.items() if name in self._evaluated_annotations # Marked Constant or Constant[SubType] and ( type(self._evaluated_annotations[name]) is CONSTANT_TYPE or ( hasattr(self._evaluated_annotations[name], "__origin__") and type(self._evaluated_annotations[name].__origin__) is CONSTANT_TYPE ) ) } # Mark builtins as constant functions self._constant_functions: Dict[str, Callable] = { name: func for name, func in ( (name, getattr(builtins, name)) for name in dir(builtins) if not name.startswith("_") and name[0].lower() == name[0] ) if callable(func) } self._inline_functions: Dict[str, Callable] = {} self._function_mapping: Dict[FunctionKind, Dict[str, Callable]] = { FunctionKind.CONSTANT: self._constant_functions, FunctionKind.INLINE: self._inline_functions, } @property def context(self) -> Context: return Context( self._module_namespace, {**self._constant_variables, **self._constant_functions}, self._inline_functions, self._debug, ) def _ensure_matching_module(self, callable_: Callable): if callable_.__module__ != self._module_name: raise ValueError( f"function not part of provided module '{self._module_name}'" ) def _decorator(self, callable_: Callable, function_type: FunctionKind) -> Callable: if not hasattr(callable_, "__name__"): # To-Do: Subclass ValueError raise ValueError(f"anonymous functions not yet supported") self._ensure_matching_module(callable_) try: function_type_dict = self._function_mapping[function_type] except KeyError: # To-Do: Subclass ValueError raise ValueError(f"unsupported function type ('{function_type}')") for function_type_ in self._function_mapping: if callable_ in self._function_mapping[function_type_].values(): # To-Do: Subclass ValueError raise ValueError(f"function already marked as '{function_type.name}'") function_type_dict[callable_.__name__] = callable_ return callable_ def constant(self, callable_: Callable) -> Callable: return self._decorator(callable_, FunctionKind.CONSTANT) def inline(self, callable_: Callable) -> Callable: return self._decorator(callable_, FunctionKind.INLINE) def nibble(self, callable_: Callable) -> Callable: self._ensure_matching_module(callable_) context = self.context code = callable_.__code__ for extension in self._config: code = extension(code, context) return FunctionType(code, callable_.__globals__, code.co_name) PK!HIInibbler/annotations.pyfrom typing import _Final from typing import _GenericAlias from typing import _Immutable from typing import _SpecialForm from typing import _tp_cache from typing import _type_check __all__ = ["Constant"] class SubscriptableAlias(_Final, _Immutable, _root=True): __slots__ = ("_name", "_doc") def __new__(cls, *args, **kwds): return super().__new__(cls) def __init__(self, name, doc): self._name = name self._doc = doc def __eq__(self, other): if not isinstance(other, _SpecialForm): return NotImplemented return self._name == other._name def __hash__(self): return hash((self._name,)) def __repr__(self): return "nibbler." + self._name def __reduce__(self): return self._name def __call__(self, *args, **kwds): raise TypeError(f"Cannot instantiate {self!r}") def __instancecheck__(self, obj): raise TypeError(f"{self} cannot be used with isinstance()") def __subclasscheck__(self, cls): raise TypeError(f"{self} cannot be used with issubclass()") @_tp_cache def __getitem__(self, parameters): item = _type_check(parameters, "Static accepts only single type.") return _GenericAlias(self, (item,)) Constant = SubscriptableAlias("Constant", "Constant marker used by nibbler.") PK!(pnibbler/config.pyfrom .extension import constantize_globals from .extension import debug # noqa: F401 from .extension import global_to_fast from .extension import inline from .extension import integrity_check from .extension import map_source from .extension import peephole from .extension import precompute_conditionals __all__ = ["DEFAULT_CONFIG"] DEFAULT_CONFIG = [ extension.EXTENSION for extension in [ inline, constantize_globals, precompute_conditionals, global_to_fast, integrity_check, # Provide well-formed lnotab to peephole optimizer map_source, peephole, map_source, ] ] PK!ߕnibbler/constants.pyfrom opcode import opmap __all__ = [ "REVERSE_OPMAP", "CALL_FUNCTION", "EXTENDED_ARG", "LOAD_CONST", "LOAD_FAST", "LOAD_GLOBAL", "POP_JUMP_IF_FALSE", "POP_JUMP_IF_TRUE", "POP_TOP", "RETURN_VALUE", "STORE_FAST", "LOAD_INSTRUCTIONS", "INSTRUCTION_FORMAT", "STEP", "CODE_TYPE", "FUNCTION_TYPE", ] REVERSE_OPMAP = {value: key for key, value in opmap.items()} CALL_FUNCTION = opmap["CALL_FUNCTION"] EXTENDED_ARG = opmap["EXTENDED_ARG"] LOAD_CONST = opmap["LOAD_CONST"] LOAD_FAST = opmap["LOAD_FAST"] LOAD_GLOBAL = opmap["LOAD_GLOBAL"] LOAD_DEREF = opmap["LOAD_DEREF"] POP_JUMP_IF_FALSE = opmap["POP_JUMP_IF_FALSE"] POP_JUMP_IF_TRUE = opmap["POP_JUMP_IF_TRUE"] POP_TOP = opmap["POP_TOP"] RETURN_VALUE = opmap["RETURN_VALUE"] STORE_FAST = opmap["STORE_FAST"] LOAD_INSTRUCTIONS = [opmap[op] for op in opmap if op.startswith("LOAD_")] INSTRUCTION_FORMAT = "BB" STEP = 2 CODE_TYPE = type((lambda: None).__code__) FUNCTION_TYPE = type(lambda: None) OP = 0 ARG = 1 PK!X$nibbler/containers.pyfrom dataclasses import dataclass from dataclasses import field from typing import Any from typing import Callable from typing import Dict __all__ = ["Context"] @dataclass class Context: module_namespace: Dict[str, Any] constants: Dict[str, Any] = field(default_factory=dict) inline_functions: Dict[str, Callable] = field(default_factory=dict) debug: bool = False PK!nibbler/extension/__init__.pyPK!_nɼ(nibbler/extension/constantize_globals.pyfrom types import CodeType from ..constants import ARG from ..constants import EXTENDED_ARG from ..constants import LOAD_CONST from ..constants import LOAD_GLOBAL from ..constants import OP from ..constants import STEP from ..containers import Context from ..util import Offset from ..util import pack_op from ..util import unpack_op __all__ = ["EXTENSION"] def constantize_globals(code: CodeType, context: Context) -> CodeType: consts = list(code.co_consts) const_map = {} ops = [] offset = Offset() pos = 0 while pos < len(code.co_code): op, unpacked_op = unpack_op(code.co_code, pos) if unpacked_op[OP] == LOAD_GLOBAL: name = code.co_names[unpacked_op[ARG]] if name in context.constants: if name not in const_map: value = context.constants[name] consts.append(value) const_map[name] = len(consts) - 1 ops += offset.swap( pos, unpacked_op, pack_op(LOAD_CONST, const_map[name]) ) else: ops += pack_op(unpacked_op[OP], unpacked_op[ARG]) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(unpacked_op[OP], unpacked_op[ARG]) pos += STEP return CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, offset.fix_abs_jumps(b"".join(ops)), tuple(consts), code.co_names, code.co_varnames, "", code.co_name, 1, b"", code.co_freevars, code.co_cellvars, ) EXTENSION = constantize_globals PK!P-nibbler/extension/debug.pyfrom dis import dis from types import CodeType from ..containers import Context def debug(code: CodeType, context: Context) -> CodeType: dis(code) return code EXTENSION = debug PK!>s#nibbler/extension/global_to_fast.pyfrom types import CodeType from ..constants import ARG from ..constants import EXTENDED_ARG from ..constants import LOAD_FAST from ..constants import LOAD_GLOBAL from ..constants import OP from ..constants import STEP from ..containers import Context from ..util import Offset from ..util import pack_op from ..util import unpack_op __all__ = ["EXTENSION"] def global_to_fast(code: CodeType, context: Context) -> CodeType: ops = [] offset = Offset() pos = 0 while pos < len(code.co_code): op, unpacked_op = unpack_op(code.co_code, pos) if ( unpacked_op[OP] == LOAD_GLOBAL and code.co_names[unpacked_op[ARG]] in code.co_varnames ): ops += offset.swap( pos, unpacked_op, pack_op( LOAD_FAST, code.co_varnames.index(code.co_names[unpacked_op[ARG]]) ), ) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(unpacked_op[OP], unpacked_op[ARG]) pos += STEP return CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, b"".join(ops), code.co_consts, code.co_names, code.co_varnames, "", code.co_name, 1, b"", code.co_freevars, code.co_cellvars, ) EXTENSION = global_to_fast PK!nibbler/extension/inline.pyfrom types import CodeType from typing import Any from typing import Dict from typing import List from ..constants import ARG from ..constants import CALL_FUNCTION from ..constants import EXTENDED_ARG from ..constants import LOAD_CONST from ..constants import LOAD_FAST from ..constants import LOAD_GLOBAL from ..constants import OP from ..constants import STEP from ..constants import STORE_FAST from ..containers import Context from ..util import needs_extended from ..util import Offset from ..util import pack_op from ..util import pos_excluding_last_return from ..util import previous_op from ..util import unpack_op __all__ = ["EXTENSION"] def rewrite_inlined( code: CodeType, consts: Dict[str, Any], names: Dict[str, str], varnames: Dict[str, str], ) -> List[bytes]: consts_index = len(consts) names_index = len(names) varsnames_index = len(varnames) offset = Offset() ops = [] pos = 0 while pos < pos_excluding_last_return(code.co_code): # Fetch and decode operand op, unpacked_op = unpack_op(code.co_code, pos) # Remap load indices if unpacked_op[OP] == LOAD_CONST: const = code.co_consts[unpacked_op[ARG]] if const in consts: index = consts.index(const) else: consts.append(code.co_consts[unpacked_op[ARG]]) index = consts_index consts_index += 1 ops += offset.swap(pos, unpacked_op, pack_op(LOAD_CONST, index)) elif unpacked_op[OP] == STORE_FAST: varname = code.co_varnames[unpacked_op[ARG]] if varname in varnames: index = varnames.index(varname) else: varnames.append(code.co_varnames[unpacked_op[ARG]]) index = varsnames_index varsnames_index += 1 ops += offset.swap(pos, unpacked_op, pack_op(STORE_FAST, index)) elif unpacked_op[OP] == LOAD_GLOBAL: name = code.co_names[unpacked_op[ARG]] if name in names: index = names.index(name) else: names.append(code.co_names[unpacked_op[ARG]]) index = names_index names_index += 1 ops += offset.swap(pos, unpacked_op, pack_op(LOAD_GLOBAL, index)) elif unpacked_op[OP] == LOAD_FAST: varname = code.co_varnames[unpacked_op[ARG]] if varname in varnames: index = varnames.index(varname) else: varnames.append(varname) index = varsnames_index varsnames_index += 1 ops += offset.swap(pos, unpacked_op, pack_op(LOAD_FAST, index)) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(*unpacked_op) pos += STEP return offset.fix_abs_jumps(b"".join(ops)) def inline(code: CodeType, context: Context) -> CodeType: consts = list(code.co_consts) names = list(code.co_names) varnames = list(code.co_varnames) ops = [] offset = Offset() pos = 0 while pos < len(code.co_code): op, unpacked_op = unpack_op(code.co_code, pos) # Parameter-less call to inlined function if unpacked_op[OP] == CALL_FUNCTION and unpacked_op[ARG] == 0: # Fetch previous op (ignoring own EXTENDED_ARG) prev_pos, unpacked_prev_op = previous_op(pos, code.co_code) prev_pos = ( prev_pos - STEP if needs_extended(unpacked_prev_op[ARG]) else prev_pos ) if ( unpacked_prev_op[OP] == LOAD_GLOBAL and code.co_names[unpacked_prev_op[ARG]] in context.inline_functions ): # Remove all preceding instructions pertaining to the CALL_FUNCTION ops = ops[: -(pos - prev_pos) // STEP] # LOAD_, CALL_, POP_TOP offset.strip(prev_pos, pos + (STEP * 2)) inline_offset = Offset(prev_pos) inlined_co_code = inline_offset.fix_abs_jumps( rewrite_inlined( context.inline_functions[ code.co_names[unpacked_prev_op[ARG]] ].__code__, consts, names, varnames, ) ) inlined_ops = [ inlined_co_code[idx : idx + STEP] for idx in range(0, len(inlined_co_code) * STEP, STEP) ] offset.add(pos, len(inlined_ops) // 2) ops += inlined_ops # Skip POP_TOP pos += STEP * 2 continue else: ops += pack_op(*unpacked_op) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(*unpacked_op) pos += STEP return CodeType( code.co_argcount, code.co_kwonlyargcount, len(varnames), code.co_stacksize, code.co_flags, offset.fix_abs_jumps(b"".join(ops)), tuple(consts), tuple(names), tuple(varnames), "", code.co_name, 1, b"", code.co_freevars, code.co_cellvars, ) EXTENSION = inline PK!/  $nibbler/extension/integrity_check.pyimport builtins from types import CodeType from ..constants import ARG from ..constants import LOAD_CONST from ..constants import LOAD_DEREF from ..constants import LOAD_FAST from ..constants import LOAD_GLOBAL from ..constants import OP from ..constants import REVERSE_OPMAP from ..constants import STEP from ..containers import Context from ..util import unpack_op __all__ = ["EXTENSION"] def integrity_check(code: CodeType, context: Context) -> CodeType: CHECKED_INSTRUCTIONS = { LOAD_GLOBAL: ( lambda index: index <= len(code.co_names) and code.co_names[index] in context.module_namespace or getattr(builtins, code.co_names[index], None) is not None, lambda index: "out of bounds access" if index > len(code.co_names) else f"'{code.co_names[index]}' is not defined", ), # Constants do not have names, but we can still validate out of bounds access LOAD_CONST: (lambda index: index <= len(code.co_consts), None), # Local variables can only be accessed at runtime LOAD_FAST: (lambda index: index <= code.co_nlocals, None), # Cells are bound to the function type, not the code LOAD_DEREF: (lambda index: index <= len(code.co_freevars), None), } pos = 0 while pos < len(code.co_code): op, unpacked_op = unpack_op(code.co_code, pos) if unpacked_op[OP] in CHECKED_INSTRUCTIONS: if not CHECKED_INSTRUCTIONS[unpacked_op[OP]][0](unpacked_op[ARG]): error = ( CHECKED_INSTRUCTIONS[unpacked_op[OP]][1](unpacked_op[ARG]) if callable(CHECKED_INSTRUCTIONS[unpacked_op[OP]][1]) else None ) raise ValueError( f"invalid {REVERSE_OPMAP[unpacked_op[OP]]} argument" f"{f' ({error})'}" if error is not None else "" ) pos += STEP return code EXTENSION = integrity_check PK!a2 2 nibbler/extension/map_source.pyfrom io import StringIO as StringIo from tempfile import NamedTemporaryFile from types import CodeType from black import format_str from uncompyle6.main import decompile from ..containers import Context __all__ = ["EXTENSION"] INDENTATION_SIZE = 4 LINE_LENGTH = 80 def map_source(code: CodeType, context: Context) -> CodeType: # Trick uncompyle6 into generating parse-able code # for impossible constructs (e.g. constant funtions) class CallableMock: def __init__(self, name: str): self.name = name def __repr__(self): return self.name wrapped_const_code = CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, tuple( ( CallableMock( const.__name__ if not const.__name__.startswith("<") else f"anonymous_func_{abs(hash(const))}" ) if callable(const) else const for const in code.co_consts ) ), code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, ) fn_name = ( code.co_name if not code.co_name.startswith("<") else f"anonymous_func_{abs(hash(code))}" ) tmp_file = NamedTemporaryFile(prefix=f"{fn_name}__", suffix="__.py", delete=False) raw_buf = StringIo() decompile(None, wrapped_const_code, out=raw_buf) raw_buf.flush() buf = [] # There doesn't appear to be an easy way to silence startup prints. # Luckily, the are all prefixed with a '# '. other_than_pound = False for line in raw_buf.getvalue().split("\n"): if not line.startswith("# "): other_than_pound = True if other_than_pound: # Indent by one level buf.append(f"{' ' * INDENTATION_SIZE}{line}") # Define a dummy function fn_code = "\n".join(buf) # To-Do: Use real function signature wrapped_fn_code = format_str(f"def {fn_name}():\n{fn_code}", LINE_LENGTH) tmp_file.write(wrapped_fn_code.encode()) # Compile code to obtain line-to-op mapping (co_lnotab) tmp_code = compile(wrapped_fn_code, tmp_file.name, "exec").co_consts[0] return CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, tmp_file.name, code.co_name, 1, tmp_code.co_lnotab, code.co_freevars, code.co_cellvars, ) EXTENSION = map_source PK!&nibbler/extension/peephole.pyfrom ctypes import py_object from ctypes import pythonapi from types import CodeType from ..containers import Context _PyCode_Optimize = pythonapi.PyCode_Optimize _PyCode_Optimize.restype = py_object __all__ = ["EXTENSION"] def peephole(code: CodeType, context: Context) -> CodeType: co_consts = list(code.co_consts) co_code = _PyCode_Optimize( py_object(code.co_code), py_object(co_consts), py_object(code.co_names), py_object(code.co_lnotab), ) return CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, co_code, tuple(co_consts), code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, ) EXTENSION = peephole PK!=, ,nibbler/extension/precompute_conditionals.pyfrom types import CodeType from ..constants import ARG from ..constants import EXTENDED_ARG from ..constants import LOAD_CONST from ..constants import LOAD_GLOBAL from ..constants import OP from ..constants import POP_JUMP_IF_FALSE from ..constants import POP_JUMP_IF_TRUE from ..constants import STEP from ..containers import Context from ..util import needs_extended from ..util import Offset from ..util import pack_op from ..util import previous_op from ..util import unpack_op __all__ = ["EXTENSION"] def precompute_conditionals(code: CodeType, context: Context) -> CodeType: ops = [] offset = Offset() pos = 0 while pos < len(code.co_code): op, unpacked_op = unpack_op(code.co_code, pos) if unpacked_op[OP] in (POP_JUMP_IF_FALSE, POP_JUMP_IF_TRUE): # Fetch previous op (ignoring own EXTENDED_ARG) prev_pos, unpacked_prev_op = previous_op(pos, code.co_code) prev_pos = ( prev_pos - STEP if needs_extended(unpacked_prev_op[ARG]) else prev_pos ) # Only apply conditional precomputing for globals that were marked constant or # constants (which the peephole optimizer ignored) if ( ( unpacked_prev_op[OP] == LOAD_GLOBAL and code.co_names[unpacked_prev_op[ARG]] in context.constants ) or unpacked_prev_op[OP] == LOAD_CONST # To-Do: Support "negative" jumps and unpacked_op[ARG] > pos ): value = ( context.constants[code.co_names[unpacked_prev_op[ARG]]] if unpacked_prev_op[OP] == LOAD_GLOBAL else code.co_consts[unpacked_prev_op[ARG]] ) # Reuse same logic for IF_TRUE and IF_FALSE if unpacked_op[OP] == POP_JUMP_IF_FALSE: value = not value # Remove all preceding instructions pertaining to the CALL_FUNCTION ops = ops[: -(pos - prev_pos) // STEP] # Skip entire block if value: offset.strip(prev_pos, unpacked_op[ARG]) pos = unpacked_op[ARG] continue # Skip jump instruction else: offset.strip(prev_pos, pos + STEP) else: ops += pack_op(*unpacked_op) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(*unpacked_op) pos += STEP return CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, offset.fix_abs_jumps(b"".join(ops)), code.co_consts, code.co_names, code.co_varnames, "", code.co_name, 1, b"", code.co_freevars, code.co_cellvars, ) EXTENSION = precompute_conditionals PK!ob?**nibbler/util.pyfrom opcode import hasjabs from opcode import hasjrel from struct import pack from struct import unpack from typing import Dict from typing import List from typing import Optional from typing import Tuple from .constants import ARG from .constants import EXTENDED_ARG from .constants import INSTRUCTION_FORMAT from .constants import LOAD_INSTRUCTIONS from .constants import OP from .constants import STEP def unpack_op(co_code: bytes, pos: int) -> Tuple[int, int]: co_code_len = len(co_code) if pos % 2 or pos < 0 or pos >= co_code_len: raise ValueError(f"invalid pos value 0 <= pos < {co_code_len} ({pos})") op = co_code[pos : pos + STEP] unpacked_op = unpack(INSTRUCTION_FORMAT, op) extended_arg = 0 # Preceding / succeeding (if reverse) op might be an EXTENDED_ARG if pos >= 2: previous_op = unpack(INSTRUCTION_FORMAT, co_code[pos - STEP : pos]) if previous_op[0] == EXTENDED_ARG: extended_arg = previous_op[ARG] return (op, (unpacked_op[OP], (extended_arg << 8) + unpacked_op[ARG])) def needs_extended(arg: int) -> bool: return arg > 255 # 0b11111111 def pack_op(op: int, arg: int) -> List[bytes]: min_val = 0 max_val = 65536 if arg < min_val or arg >= max_val: raise ValueError(f"invalid arg value ({arg}) ({min_val} <= arg < {max_val})") ops = [] if needs_extended(arg): extended_arg = arg >> 8 ops.append(pack(INSTRUCTION_FORMAT, EXTENDED_ARG, extended_arg)) ops.append(pack(INSTRUCTION_FORMAT, op, arg & 255)) # 0b11111111 return ops class Offset: def __init__(self, offset: int = 0): self._offsets: Dict[int, int] = {0: offset} def strip(self, from_: int, to: int) -> None: is_extended = needs_extended(to) start_pos = from_ - STEP if is_extended else from_ offset = -(to - start_pos) self._offsets[start_pos + self.offset(start_pos)] = offset def add(self, pos: int, op_count: int) -> None: self._offsets[pos + self.offset(pos)] = op_count * STEP def swap( self, pos: int, unpacked_op: Tuple[int, int], new_op: List[bytes] ) -> List[bytes]: is_extended = needs_extended(unpacked_op[ARG]) offset = None if len(new_op) == 2 and not is_extended: offset = STEP elif len(new_op) == 1 and is_extended: offset = -STEP if offset is not None: self._offsets[pos] = offset return new_op def offset(self, pos: int) -> int: return sum((self._offsets[pos_] for pos_ in self._offsets if pos_ <= pos)) def rel_offset(self, pos: int, arg: int) -> int: return sum( ( self._offsets[pos_] for pos_ in self._offsets if pos_ >= pos and pos_ <= pos + arg ) ) def fix_abs_jumps(self, co_code: bytes) -> bytes: ops: List[bytes] = [] pos = 0 while pos < len(co_code): op, unpacked_op = unpack_op(co_code, pos) if unpacked_op[OP] in hasjabs: old_is_extended = needs_extended(unpacked_op[ARG]) new_is_extended = needs_extended( unpacked_op[ARG] + self.offset( unpacked_op[ARG] - STEP if unpacked_op[ARG] < pos else pos ) ) offset = None if not old_is_extended and new_is_extended: offset = STEP elif old_is_extended and not new_is_extended: offset = -STEP if offset is not None: self._offsets[pos] = offset ops += pack_op( unpacked_op[OP], unpacked_op[ARG] + self.offset( unpacked_op[ARG] - STEP if unpacked_op[ARG] < pos else pos ), ) elif unpacked_op[OP] in hasjrel: old_is_extended = needs_extended(unpacked_op[ARG]) new_is_extended = needs_extended( unpacked_op[ARG] + self.rel_offset(pos, unpacked_op[ARG]) ) offset = None if not old_is_extended and new_is_extended: offset = STEP elif old_is_extended and not new_is_extended: offset = -STEP if offset is not None: self._offsets[pos] = offset ops += pack_op( unpacked_op[OP], unpacked_op[ARG] + self.rel_offset(pos, unpacked_op[ARG]), ) elif unpacked_op[OP] != EXTENDED_ARG: ops += pack_op(*unpacked_op) pos += STEP return b"".join(ops) def previous_op(pos: int, co_code: bytes) -> Optional[bytes]: pos -= STEP while pos >= 0: op, unpacked_op = unpack_op(co_code, pos) if unpacked_op[OP] != EXTENDED_ARG: return pos, unpacked_op pos -= STEP return None def pos_excluding_last_return(co_code: bytes) -> Optional[int]: # Start from the bottom and exclude return (be it implicit or explicit) pos = len(co_code) - STEP * 2 while pos >= 0: # Fetch and decode operand op, unpacked_op = unpack_op(co_code, pos) # Stop trimming LOAD_* instructions as soon as a non LOAD_* instruction # is encountered if unpacked_op[OP] not in LOAD_INSTRUCTIONS: return (pos + STEP) if not needs_extended(unpacked_op[ARG]) else pos pos -= STEP PK!Y)//nibbler-0.1.3.dist-info/LICENSEMIT License Copyright (c) 2019 Philip Trauner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hu)GTUnibbler-0.1.3.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)$qzd&Y)r$UV&UrPK!H-tz nibbler-0.1.3.dist-info/METADATAXr\>֙IIeDg.jtj˹n$D(!@ɺN?(t%$sEbvX #X0vTYl6 hlmIԭd$7I(ْl#= 2&סeE@Ϗ0NI - bȗ2r94΂I?uЃi}nJ>صI٤vJOjn7j<9i՝(\y??jU*:#Ϗ:*3²l۶xh/d j,1Ҙ'r`-E,an!) 7cE1>8S4ًbNDR>c1eV%'dBnS'R0PG YT/p1P $0٨9Saʒ7e~)Hq dX{VEg ={o5dlE+R/dSoX:MR=[b0ܮh`1gt l=?k[T6#]F4G}A L ?YyvaZŕ٫6u@IUzv\jrSPDcwѺj9x*v&P;Z nÛɫS)*zxH C# *m[on46ViOAP=7..7pV1u}"zj?uNЁgexeoнd4| ݀NkZ/}K6gi ~zҽ:jތ&ݗ://z Fex#M(>]8vȋCگkMw<ۿޣUX[׿܎oz{k.r\gmV4Hy#p q30>[1gtӹ;7IA7Pm۵.wc.q6-jo#tb<ƩK5sjX4>SR]UP6w}1WK.꾳% tE0~$ 5C#VEu, _uFU&}J P%m䭿Q_Hn#wkޟ~|ݤnRGmd3T/W$ϩNj^翊ytq$?XgU؜n+ZPK!Hͯnibbler-0.1.3.dist-info/RECORDuɲH}= E/yDd @f*mG#8Mq DR55WP ˳ ,gqrr#駴hoAu4.=n_>kJ9);b.!(bWqQauX sO!*s)9"0Lk[9H])vP$XG_=O=p+YUԷ4oJY3>ilԈZ| _(|NyWTՄdI@by,k"|,c9PܠՒ@^y?(Nddn] +DgVQD%tf>=K:8jn8ۭ+z|/ݜ־WR '|!j޵L~+?6xGJK#ksFD 'Ȫ"gx Kb=  ^/m ][y2&S)s#6)nibbler/extension/global_to_fast.pyPK!,/nibbler/extension/inline.pyPK!/  $cDnibbler/extension/integrity_check.pyPK!a2 2 Lnibbler/extension/map_source.pyPK!&Xnibbler/extension/peephole.pyPK!=, ,[nibbler/extension/precompute_conditionals.pyPK!ob?**hnibbler/util.pyPK!Y)//_~nibbler-0.1.3.dist-info/LICENSEPK!Hu)GTU˂nibbler-0.1.3.dist-info/WHEELPK!H-tz Znibbler-0.1.3.dist-info/METADATAPK!Hͯ nibbler-0.1.3.dist-info/RECORDPKۏ