pax_global_header00006660000000000000000000000064144016121750014513gustar00rootroot0000000000000052 comment=f51b26b18abe5e6d84f1c8282e7fbe58a629b3df amol--linetable-f51b26b/000077500000000000000000000000001440161217500150665ustar00rootroot00000000000000amol--linetable-f51b26b/.github/000077500000000000000000000000001440161217500164265ustar00rootroot00000000000000amol--linetable-f51b26b/.github/workflows/000077500000000000000000000000001440161217500204635ustar00rootroot00000000000000amol--linetable-f51b26b/.github/workflows/run-tests.yml000066400000000000000000000016301440161217500231520ustar00rootroot00000000000000name: run-tests on: [push, pull_request, workflow_dispatch] jobs: build: name: Run tests strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.11"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -U --upgrade-strategy=eager --pre -e .[testing] pip install pytest-cov - name: Run Tests run: | pytest --cov=linetable - name: Publish to coveralls.io env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | pip install 'coveralls' coveralls --service=github amol--linetable-f51b26b/.gitignore000066400000000000000000000034071440161217500170620ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ amol--linetable-f51b26b/LICENSE000066400000000000000000000020621440161217500160730ustar00rootroot00000000000000MIT License Copyright (c) 2022 Alessandro Molina 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. amol--linetable-f51b26b/MANIFEST.in000066400000000000000000000000261440161217500166220ustar00rootroot00000000000000global-exclude *.pyc amol--linetable-f51b26b/README.rst000066400000000000000000000053341440161217500165620ustar00rootroot00000000000000About linetable --------------- .. image:: https://github.com/amol-/linetable/actions/workflows/run-tests.yml/badge.svg :target: https://github.com/amol-/linetable/actions/workflows/run-tests.yml .. image:: https://coveralls.io/repos/amol-/linetable/badge.svg :target: https://coveralls.io/r/amol-/linetable .. image:: https://img.shields.io/pypi/v/linetable.svg :target: https://pypi.python.org/pypi/linetable .. image:: https://img.shields.io/pypi/pyversions/linetable.svg :target: https://pypi.python.org/pypi/linetable .. image:: https://img.shields.io/pypi/l/linetable.svg :target: https://pypi.python.org/pypi/linetable linetable is a library parse and generate co_linetable attributes in Python code objects. Based on https://github.com/python/cpython/blob/main/Objects/locations.md Installing ---------- linetable can be installed from pypi:: pip install linetable should just work for most of the users Usage ----- Existing linetable can be parsed using ``linetable.parse_linetable``:: >>> def testfunc(): ... x = 3 ... y = x + 1 ... return y >>> list(linetable.parse_linetable(testfunc.__code__.co_linetable)) [ (1, 1, 1, 0, 0), (1, 2, 2, 6, 7), (1, 2, 2, 2, 3), (1, 3, 3, 6, 7), (1, 3, 3, 10, 11), (2, 3, 3, 6, 11), (1, 3, 3, 2, 3), (1, 4, 4, 9, 10), (1, 4, 4, 2, 10), ] If you prefer the output in the format of ``dis.Positions`` objects, you can create them from the yielded values:: >>> [dis.Positions(*e[1:]) for e in linetable.parse_linetable(testfunc.__code__.co_linetable)] [Positions(lineno=1, end_lineno=1, col_offset=0, end_col_offset=0), Positions(lineno=2, end_lineno=2, col_offset=8, end_col_offset=9), Positions(lineno=2, end_lineno=2, col_offset=4, end_col_offset=5), Positions(lineno=3, end_lineno=3, col_offset=8, end_col_offset=9), Positions(lineno=3, end_lineno=3, col_offset=12, end_col_offset=13), Positions(lineno=3, end_lineno=3, col_offset=8, end_col_offset=13), Positions(lineno=3, end_lineno=3, col_offset=4, end_col_offset=5), Positions(lineno=4, end_lineno=4, col_offset=11, end_col_offset=12), Positions(lineno=4, end_lineno=4, col_offset=4, end_col_offset=12)] If you have the linetable, you can generate back the binary encoded version using ``linetable.generate_linetable``:: >>> lt = [ ... (1, 1, 1, 0, 0), ... (1, 2, 2, 6, 7), ... (1, 2, 2, 2, 3), ... (1, 3, 3, 6, 7), ... (1, 3, 3, 10, 11), ... (2, 3, 3, 6, 11), ... (1, 3, 3, 2, 3), ... (1, 4, 4, 9, 10), ... (1, 4, 4, 2, 10), ... ] >>> linetable.generate_linetable(lt) b"\x80\x00\xd8\x06\x07\x80!\xd8\x06\x07\x88!\x81e\x80!\xd8\t\n\x80(" amol--linetable-f51b26b/linetable/000077500000000000000000000000001440161217500170255ustar00rootroot00000000000000amol--linetable-f51b26b/linetable/__init__.py000066400000000000000000000000751440161217500211400ustar00rootroot00000000000000# from .linetable import generate_linetable, parse_linetable amol--linetable-f51b26b/linetable/linetable.py000066400000000000000000000102301440161217500213320ustar00rootroot00000000000000from .varint import read_signed_varint, read_varint, generate_signed_varint def generate_linetable(pairs, firstlineno=1, use_bytecode_offset=False): return b"".join(_generate_linetable(pairs, firstlineno, use_bytecode_offset)) def _generate_linetable(pairs, firstlineno, use_bytecode_offset): pairs = iter(pairs) # will do nothing if it's already an iterator. cur_line = firstlineno cur_entry = next(pairs) while cur_entry: try: next_entry = next(pairs) except StopIteration: next_entry = None length, start_line, *more = cur_entry if more: end_line, start_col, end_col = more else: end_line, start_col, end_col = start_line, None, None if use_bytecode_offset: # We don't have the length, # but we have the byte code offsets from dis.findlinestarts() length = _linetable_length(length, next_entry) if start_line is not None: line_delta = start_line - cur_line cur_line = end_line if start_line is None: code = 15 yield _new_linetable_entry(code, length).to_bytes(1, byteorder="little") elif start_col is None: code = 13 yield _new_linetable_entry(code, length).to_bytes(1, byteorder="little") for b in generate_signed_varint(line_delta): yield b.to_bytes(1, byteorder="little") elif line_delta == 0 and (end_col - start_col) < 15: # short form, same line as before and near columns. code = start_col // 8 yield _new_linetable_entry(code, length).to_bytes(1, byteorder="little") yield (((start_col % 8) << 4) | (end_col - start_col)).to_bytes(1, byteorder="little") elif line_delta <= 2 and start_col <= 255 and end_col <= 255: # New line form code = 10 + line_delta yield _new_linetable_entry(code, length).to_bytes(1, byteorder="little") yield start_col.to_bytes(1, byteorder="little") yield end_col.to_bytes(1, byteorder="little") else: raise NotImplementedError() cur_entry = next_entry def _new_linetable_entry(code, length): # 8 bits entry made of: # ----------------- # 7 | 6 - 3 | 2 - 0 # 1 | code | length-1 return (1 << 7) | (code << 3) | (length - 1) def _linetable_length(bc_start, next_entry): length = 1 if next_entry: # Each bytecode entry is 2 bytes, # so compute the offset in bytes between two bytecode entries # and then divide by 2 to get the number of entries. length = (next_entry[0] - bc_start) // 2 return length def parse_linetable(linetable, firstlineno=1): line = firstlineno it = iter(linetable) while True: try: first_byte = next(it) except StopIteration: return code = (first_byte >> 3) & 15 length = (first_byte & 7) + 1 if code == 15: yield (length, None, None, None, None) elif code == 14: line_delta = read_signed_varint(it) line += line_delta end_line = line + read_varint(it) col = read_varint(it) if col == 0: col = None else: col -= 1 end_col = read_varint(it) if end_col == 0: end_col = None else: end_col -= 1 yield (length, line, end_line, col, end_col) elif code == 13: # No column line_delta = read_signed_varint(it) line += line_delta yield (length, line, line, None, None) elif code in (10, 11, 12): # new line line_delta = code - 10 line += line_delta column = next(it) end_column = next(it) yield (length, line, line, column, end_column) elif 0 <= code < 10: # short form second_byte = next(it) column = code << 3 | (second_byte >> 4) yield (length, line, line, column, column + (second_byte & 15)) else: raise NotImplementedError() amol--linetable-f51b26b/linetable/varint.py000066400000000000000000000011711440161217500207020ustar00rootroot00000000000000def read_varint(it): b = next(it) val = b & 63 shift = 0 while b & 64: b = next(it) shift += 6 val |= (b & 63) << shift return val def read_signed_varint(it): uval = read_varint(it) if uval & 1: return -(uval >> 1) else: return uval >> 1 def generate_varint(n): if n == 0: yield 0 while n: if n > 63: yield n & 63 | 64 else: yield n & 63 n = n >> 6 def generate_signed_varint(s): if s < 0: return generate_varint(((-s) << 1) | 1) else: return generate_varint(s << 1) amol--linetable-f51b26b/setup.cfg000066400000000000000000000000001440161217500166750ustar00rootroot00000000000000amol--linetable-f51b26b/setup.py000066400000000000000000000016641440161217500166070ustar00rootroot00000000000000from setuptools import setup, find_packages import sys, os here = os.path.abspath(os.path.dirname(__file__)) try: README = open(os.path.join(here, "README.rst")).read() except IOError: README = "" version = "0.0.3" setup( name="linetable", version=version, description="library to manage Python Locations Table (co_linetable)", long_description=README, classifiers=[ 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3.11', ], keywords="", author="Alessandro Molina", author_email="amol@turbogears.org", url="https://github.com/amol-/linetable", license="MIT", packages=find_packages(exclude=["ez_setup", "examples", "tests"]), include_package_data=True, zip_safe=False, install_requires=[ # -*- Extra requirements: -*- ], entry_points=""" # -*- Entry points: -*- """, ) amol--linetable-f51b26b/tests/000077500000000000000000000000001440161217500162305ustar00rootroot00000000000000amol--linetable-f51b26b/tests/test_linetable.py000066400000000000000000000055121440161217500216030ustar00rootroot00000000000000import sys import pytest from linetable import generate_linetable, parse_linetable def test_parse_linetable(): linetable = b"\x80\x00\xd8\x06\x07\x80!\xd8\x06\x07\x88!\x81e\x80!\xd8\t\n\x80(" parsed = list(parse_linetable(linetable)) assert parsed == [ (1, 1, 1, 0, 0), (1, 2, 2, 6, 7), (1, 2, 2, 2, 3), (1, 3, 3, 6, 7), (1, 3, 3, 10, 11), (2, 3, 3, 6, 11), (1, 3, 3, 2, 3), (1, 4, 4, 9, 10), (1, 4, 4, 2, 10), ] def test_parse_linetable_nocolumns(): linetable = ( b"\xe8\x00\xe8\x02\xe8\x00\xe8\x02\xe8\x00\xe9\x00\xe8\x00\xe8\x02\xe8\x00" ) parsed = list(parse_linetable(linetable)) assert parsed == [ (1, 1, 1, None, None), (1, 2, 2, None, None), (1, 2, 2, None, None), (1, 3, 3, None, None), (1, 3, 3, None, None), (2, 3, 3, None, None), (1, 3, 3, None, None), (1, 4, 4, None, None), (1, 4, 4, None, None), ] def test_linetable(): pairs = [ # length, start_line, end_line, start_col, end_col (1, 1, 1, 0, 0), (1, 2, 2, 6, 7), (1, 2, 2, 2, 3), (1, 3, 3, 6, 7), (1, 3, 3, 10, 11), (2, 3, 3, 6, 11), (1, 3, 3, 2, 3), (1, 4, 4, 9, 10), (1, 4, 4, 2, 10), ] expected = b"\x80\x00\xd8\x06\x07\x80!\xd8\x06\x07\x88!\x81e\x80!\xd8\t\n\x80(" assert generate_linetable(pairs, use_bytecode_offset=False) == expected def test_linetable_offsets(): pairs = [ # bytecode_offset, start_line, end_line, start_col, end_col (0, 1, 1, 0, 0), (2, 2, 2, 6, 7), (4, 2, 2, 2, 3), (6, 3, 3, 6, 7), (8, 3, 3, 10, 11), (10, 3, 3, 6, 11), (14, 3, 3, 2, 3), (16, 4, 4, 9, 10), (18, 4, 4, 2, 10), ] expected = b"\x80\x00\xd8\x06\x07\x80!\xd8\x06\x07\x88!\x81e\x80!\xd8\t\n\x80(" assert generate_linetable(pairs, use_bytecode_offset=True) == expected def test_linetable_offsets_short(): pairs = [ # bytecode_offset, linenum (0, 1), (2, 2), (4, 2), (6, 3), (8, 3), (10, 3), (14, 3), (16, 4), (18, 4), ] expected = ( b"\xe8\x00\xe8\x02\xe8\x00\xe8\x02\xe8\x00\xe9\x00\xe8\x00\xe8\x02\xe8\x00" ) assert generate_linetable(pairs, use_bytecode_offset=True) == expected @pytest.mark.skipif(sys.version_info < (3,11), reason="requires python3.11+") def test_roundtrip(): def _test_function(): x = 13 y = x * 2 + 7 + 8 + 9 - 3 - 1 - 5 z = y**2 return z co_linetable = _test_function.__code__.co_linetable assert ( generate_linetable( parse_linetable(generate_linetable(parse_linetable(co_linetable))) ) == co_linetable )