diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-04-02 19:42:04 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-04-02 19:42:04 -0400 |
commit | 4910434d33d0928374bf966c00c07feda5b32d77 (patch) | |
tree | 7b8da0180a122d3b261ac19cffa4a6c6c1305507 /lab/lnotab.py | |
parent | 1fadd547372a721a9b0acb4dc710195f85138c59 (diff) | |
download | python-coveragepy-git-4910434d33d0928374bf966c00c07feda5b32d77.tar.gz |
A lab directory for experiments in progress.
Diffstat (limited to 'lab/lnotab.py')
-rw-r--r-- | lab/lnotab.py | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/lab/lnotab.py b/lab/lnotab.py new file mode 100644 index 00000000..230e42bb --- /dev/null +++ b/lab/lnotab.py @@ -0,0 +1,122 @@ +# Comment copied from Python/compile.c: +# +# All about a_lnotab. +# +# c_lnotab is an array of unsigned bytes disguised as a Python string. +# It is used to map bytecode offsets to source code line #s (when needed +# for tracebacks). +# +# The array is conceptually a list of +# (bytecode offset increment, line number increment) +# pairs. The details are important and delicate, best illustrated by example: +# +# byte code offset source code line number +# 0 1 +# 6 2 +# 50 7 +# 350 307 +# 361 308 +# +# The first trick is that these numbers aren't stored, only the increments +# from one row to the next (this doesn't really work, but it's a start): +# +# 0, 1, 6, 1, 44, 5, 300, 300, 11, 1 +# +# The second trick is that an unsigned byte can't hold negative values, or +# values larger than 255, so (a) there's a deep assumption that byte code +# offsets and their corresponding line #s both increase monotonically, and (b) +# if at least one column jumps by more than 255 from one row to the next, more +# than one pair is written to the table. In case #b, there's no way to know +# from looking at the table later how many were written. That's the delicate +# part. A user of c_lnotab desiring to find the source line number +# corresponding to a bytecode address A should do something like this +# +# lineno = addr = 0 +# for addr_incr, line_incr in c_lnotab: +# addr += addr_incr +# if addr > A: +# return lineno +# lineno += line_incr +# +# In order for this to work, when the addr field increments by more than 255, +# the line # increment in each pair generated must be 0 until the remaining addr +# increment is < 256. So, in the example above, assemble_lnotab (it used +# to be called com_set_lineno) should not (as was actually done until 2.2) +# expand 300, 300 to 255, 255, 45, 45, +# but to 255, 0, 45, 255, 0, 45. +# + +def lnotab(pairs, first_lineno=0): + """Yields byte integers representing the pairs of integers passed in.""" + assert first_lineno <= pairs[0][1] + cur_byte, cur_line = 0, first_lineno + for byte_off, line_off in pairs: + byte_delta = byte_off - cur_byte + line_delta = line_off - cur_line + assert byte_delta >= 0 + assert line_delta >= 0 + while byte_delta > 255: + yield 255 # byte + yield 0 # line + byte_delta -= 255 + yield byte_delta + while line_delta > 255: + yield 255 # line + yield 0 # byte + line_delta -= 255 + yield line_delta + cur_byte, cur_line = byte_off, line_off + +def lnotab_string(pairs, first_lineno=0): + return "".join(chr(b) for b in lnotab(pairs, first_lineno)) + +def byte_pairs(lnotab): + """Yield pairs of integers from a string.""" + for i in range(0, len(lnotab), 2): + yield ord(lnotab[i]), ord(lnotab[i+1]) + +def lnotab_numbers(lnotab, first_lineno=0): + """Yields the byte, line offset pairs from a packed lnotab string.""" + + last_line = None + cur_byte, cur_line = 0, first_lineno + for byte_delta, line_delta in byte_pairs(lnotab): + if byte_delta: + if cur_line != last_line: + yield cur_byte, cur_line + last_line = cur_line + cur_byte += byte_delta + cur_line += line_delta + if cur_line != last_line: + yield cur_byte, cur_line + + +## Tests + +def same_list(a, b): + a = list(a) + assert a == b + +def test_simple(): + same_list(lnotab([(0,1)]), [0, 1]) + same_list(lnotab([(0,1), (6, 2)]), [0, 1, 6, 1]) + +def test_starting_above_one(): + same_list(lnotab([(0,100), (6,101)]), [0, 100, 6, 1]) + same_list(lnotab([(0,100), (6,101)], 50), [0, 50, 6, 1]) + +def test_large_gaps(): + same_list(lnotab([(0,1), (300, 300)]), [0, 1, 255, 0, 45, 255, 0, 44]) + same_list(lnotab([(0,1), (255, 300)]), [0, 1, 255, 255, 0, 44]) + same_list(lnotab([(0,1), (255, 256)]), [0, 1, 255, 255]) + +def test_strings(): + assert lnotab_string([(0,1), (6, 2)]) == "\x00\x01\x06\x01" + assert lnotab_string([(0,1), (300, 300)]) == "\x00\x01\xff\x00\x2d\xff\x00\x2c" + +def test_numbers(): + same_list(lnotab_numbers("\x00\x01\x06\x01"), [(0,1), (6,2)]) + same_list(lnotab_numbers("\x00\x01\xff\x00\x2d\xff\x00\x2c"), [(0,1), (300, 300)]) + +def test_numbers_firstlineno(): + same_list(lnotab_numbers("\x00\x01\xff\x00\x2d\xff\x00\x2c", 10), [(0,11), (300, 310)]) |