Coverage for src/renaissance/common/rewriter.py: 72%
39 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-15 10:34 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-15 10:34 +0000
1# Copyright (C) 2024-2026 TNO
2#
3# Licensed under the terms specified in the LICENSE file in the root directory
4# of this repository, also available at:
5# https://github.com/TNO/Renaissance.Py/blob/main/LICENSE
7"""AI: Byte-level content rewriter that accumulates and applies pending replacements."""
9import sys
12class Rewrite:
13 """AI: Represent one pending replacement of a byte range with new content."""
15 def __init__(self, start: int, end: int, replacement: bytes) -> None:
16 """AI: Record a pending replacement of a byte range with new content."""
17 self.start = start
18 self.end = end
19 self.replacement = replacement
22class Rewriter:
23 """A class that allows for modifications to a byte sequence."""
25 def __init__(self, content: bytes) -> None:
26 """AI: Prepare a rewriter that accumulates edits to apply to the given content."""
27 self.__content = content
28 self.__rewrites: list[Rewrite] = []
30 def replace(self, start: int, end: int, new_content: bytes) -> None:
31 """Replace a portion of the content with new content.
33 This method will replace the content between the specified start and end
34 indices with the provided new_content. If there is an existing rewrite
35 that partially overlaps with the specified range, the new content will be
36 appended to the existing replacement, and the range will be adjusted to
37 encompass both the old and new content. If the start or end indices are out
38 of bounds, then new content will be inserted at the end of the byte sequence.
40 Args:
41 start (int): The starting index of the content to be replaced.
42 end (int): The ending index of the content to be replaced.
43 new_content (bytes): The new content to insert in place of the old content.
45 """
46 for r in self.__rewrites:
47 # if r partially overlaps with start and end then append the new content to the existing replacement
48 if r.start <= start <= r.end:
49 r.replacement += new_content
50 r.start = min(r.start, start)
51 r.end = max(r.end, end)
52 return
53 real_start = len(self.__content) if start > len(self.__content) or start < 0 else start
54 real_end = len(self.__content) if end > len(self.__content) or end < 0 else end
55 self.__rewrites.append(Rewrite(real_start, real_end, new_content))
57 def apply(self) -> bytes:
58 """Apply the rewrites to a copied byte sequence.
60 This method reverses the order of the rewrites to ensure that insertions
61 are performed correctly. It then sorts the rewrites by their start position
62 in descending order and applies each rewrite to the byte sequence.
64 Returns:
65 bytes: The modified byte sequence after all rewrites have been applied.
67 """
68 result = bytearray(self.__content[:])
69 for rewrite in sorted(self.__rewrites, key=lambda x: x.start, reverse=True):
70 result[rewrite.start : rewrite.end] = rewrite.replacement
71 return result
73 @property
74 def content(self) -> bytes:
75 """AI: Return the original, unmodified content this rewriter was constructed with."""
76 return self.__content
79if __name__ == "__main__":
80 # create a byte array a random bytes of len 20
82 my_bytes = bytearray(20)
83 for i in range(20):
84 my_bytes[i] = ord("a") + i
85 rewriter = Rewriter(my_bytes)
86 rewriter.replace(5, 10, b"hellooo")
87 rewriter.replace(5, 10, b" world")
88 rewriter.replace(0, 0, b"BEGIN")
89 s = rewriter.apply().decode(sys.getfilesystemencoding())
90 print(len(s))
91 print(s)