From ba1ed5644daa3bac036ed4ca474efe63d17693f4 Mon Sep 17 00:00:00 2001 From: Zane Fink Date: Wed, 5 Mar 2025 16:49:12 -0600 Subject: [PATCH 1/2] add offsets to bytecode (#1) --- src/bytecode/cfg.py | 1 + src/bytecode/concrete.py | 14 ++++++++------ src/bytecode/instr.py | 15 ++++++++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/bytecode/cfg.py b/src/bytecode/cfg.py index 64f85c79..9c2a8818 100644 --- a/src/bytecode/cfg.py +++ b/src/bytecode/cfg.py @@ -621,6 +621,7 @@ def _get_instructions( instr._name, self.get_block_index(target_block), location=instr.location, + offset=instr.offset ) instructions.append(c_instr) else: diff --git a/src/bytecode/concrete.py b/src/bytecode/concrete.py index abfd2d08..76da39f9 100644 --- a/src/bytecode/concrete.py +++ b/src/bytecode/concrete.py @@ -105,13 +105,13 @@ def __init__( lineno: Union[int, None, _UNSET] = UNSET, location: Optional[InstrLocation] = None, extended_args: Optional[int] = None, + offset: Optional[int] = None, ): # Allow to remember a potentially meaningless EXTENDED_ARG emitted by # Python to properly compute the size and avoid messing up the jump # targets self._extended_args = extended_args - super().__init__(name, arg, lineno=lineno, location=location) - + super().__init__(name, arg, lineno=lineno, location=location, offset=offset) def _check_arg(self, name: str, opcode: int, arg: int) -> None: if opcode_has_argument(opcode): if arg is UNSET: @@ -336,6 +336,7 @@ def from_code( instructions: MutableSequence[Union[SetLineno, ConcreteInstr]] = [] for i in dis.get_instructions(code, show_caches=True): loc = InstrLocation.from_positions(i.positions) if i.positions else None + offset = i.offset # dis.get_instructions automatically handle extended arg which # we do not want, so we fold back arguments to be between 0 and 255 instructions.append( @@ -343,6 +344,7 @@ def from_code( i.opname, i.arg % 256 if i.arg is not None else UNSET, location=loc, + offset=offset, ) ) # cache_info only exist on 3.13+ @@ -935,7 +937,7 @@ def to_bytecode( instr_index = len(instructions) jumps.append((instr_index, jump_target)) - instructions.append(Instr(c_instr.name, arg, location=location)) + instructions.append(Instr(c_instr.name, arg, location=location, offset=c_instr.offset)) # We now insert the TryEnd entries if current_instr_offset in ex_end: @@ -1063,7 +1065,7 @@ def concrete_instructions(self) -> None: self.instructions.extend( [ ConcreteInstr( - "CACHE", 0, location=self.instructions[-1].location + "CACHE", 0, location=self.instructions[-1].location, offset=self.instructions[-1].offset ) ] * self.required_caches @@ -1131,7 +1133,7 @@ def concrete_instructions(self) -> None: arg2_index = self.add(self.varnames, _arg2[1]) if arg1_index > 16 or arg2_index > 16: n1, n2 = DUAL_ARG_OPCODES_SINGLE_OPS[opcode] - c_instr = ConcreteInstr(n1, arg1_index, location=location) + c_instr = ConcreteInstr(n1, arg1_index, location=location, offset=len(self.instructions)) self.instructions.append(c_instr) instr_name = n2 c_arg = arg2_index @@ -1199,7 +1201,7 @@ def concrete_instructions(self) -> None: c_arg = arg # The above should have performed all the necessary conversion - c_instr = ConcreteInstr(instr_name, c_arg, location=location) + c_instr = ConcreteInstr(instr_name, c_arg, location=location, offset=len(self.instructions)) if is_jump: self.jumps.append((len(self.instructions), label, c_instr)) diff --git a/src/bytecode/instr.py b/src/bytecode/instr.py index 9910f0ec..ce647bfd 100644 --- a/src/bytecode/instr.py +++ b/src/bytecode/instr.py @@ -675,7 +675,7 @@ def copy(self) -> "TryEnd": class BaseInstr(Generic[A]): """Abstract instruction.""" - __slots__ = ("_arg", "_location", "_name", "_opcode") + __slots__ = ("_arg", "_location", "_name", "_opcode", "_offset") # Work around an issue with the default value of arg def __init__( @@ -685,6 +685,7 @@ def __init__( *, lineno: int | None | _UNSET = UNSET, location: Optional[InstrLocation] = None, + offset: Optional[int] = None, ) -> None: self._set(name, arg) if location: @@ -693,7 +694,7 @@ def __init__( self._location = None else: self._location = InstrLocation(lineno, None, None, None) - + self._offset = offset # Work around an issue with the default value of arg def set(self, name: str, arg: A = UNSET) -> None: # type: ignore """Modify the instruction in-place. @@ -741,6 +742,14 @@ def arg(self) -> A: def arg(self, arg: A): self._set(self._name, arg) + @property + def offset(self): + return self._offset + + @offset.setter + def offset(self, offset: int): + self._offset = offset + @property def lineno(self) -> int | _UNSET | None: return self._location.lineno if self._location is not None else UNSET @@ -810,7 +819,7 @@ def pre_and_post_stack_effect(self, jump: Optional[bool] = None) -> tuple[int, i return (_effect, 0) def copy(self: T) -> T: - return self.__class__(self._name, self._arg, location=self._location) + return self.__class__(self._name, self._arg, location=self._location, offset=self._offset) def has_jump(self) -> bool: return self._has_jump(self._opcode) From aca1f74dd8bb79d96f97bc3ccb5af47b36a3e6f8 Mon Sep 17 00:00:00 2001 From: Zane Fink Date: Wed, 5 Mar 2025 16:55:33 -0600 Subject: [PATCH 2/2] fix version parsing of dev version --- pyproject.toml | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dd61b4df..518f5355 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,15 +21,11 @@ ] dependencies = ["typing_extensions;python_version<'3.10'"] dynamic = ["version"] - - [project.urls] homepage = "https://github.com/MatthieuDartiailh/bytecode" documentation = "https://bytecode.readthedocs.io/en/latest/" repository = "https://github.com/MatthieuDartiailh/bytecode" changelog = "https://github.com/MatthieuDartiailh/bytecode/blob/main/doc/changelog.rst" - - [build-system] requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"] build-backend = "setuptools.build_meta" @@ -50,45 +46,57 @@ write_to = "src/bytecode/version.py" write_to_template = """ # This file is auto-generated by setuptools-scm do NOT edit it. - from collections import namedtuple +import re #: A namedtuple of the version info for the current release. -_version_info = namedtuple("_version_info", "major minor micro status") - -parts = "{version}".split(".", 3) -version_info = _version_info( +version_info = namedtuple("version_info", "major minor micro status") + +# Parse the version string +parts = "{version}".split(".", 2) + +# Extract the micro version and any status +if len(parts) >= 3: + # Handle cases like "0.1.2" or "0.1.dev123+gabcdef" + micro_match = re.match(r'(\\d+)([^\\d].+)?', parts[2]) + if micro_match: + micro = int(micro_match.group(1)) + status = micro_match.group(2) or "" + else: + # Handle development versions like "dev123+gabcdef" + micro = 0 + status = parts[2] +else: + micro = 0 + status = "" + +version_info = version_info( int(parts[0]), int(parts[1]), - int(parts[2]), - parts[3] if len(parts) == 4 else "", + micro, + status ) # Remove everything but the 'version_info' from this module. -del namedtuple, _version_info, parts +del namedtuple, re, parts, micro_match __version__ = "{version}" """ - [tool.ruff] src = ["src"] extend-exclude = ["tests/instruments/hardware/nifpga/scope_based"] line-length = 88 - [tool.ruff.lint] select = ["B", "C", "E", "F", "W", "B9", "I", "C90", "RUF"] extend-ignore = ["E203", "E266", "E501", "F403", "F401", "RUF012"] - [tool.ruff.lint.isort] combine-as-imports = true extra-standard-library = ["opcode"] - [tool.ruff.lint.mccabe] max-complexity = 43 [tool.mypy] follow_imports = "normal" strict_optional = true - [tool.pytest.ini_options] minversion = "6.0"