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

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 

6 

7"""AI: Byte-level content rewriter that accumulates and applies pending replacements.""" 

8 

9import sys 

10 

11 

12class Rewrite: 

13 """AI: Represent one pending replacement of a byte range with new content.""" 

14 

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 

20 

21 

22class Rewriter: 

23 """A class that allows for modifications to a byte sequence.""" 

24 

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] = [] 

29 

30 def replace(self, start: int, end: int, new_content: bytes) -> None: 

31 """Replace a portion of the content with new content. 

32 

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. 

39 

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. 

44 

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)) 

56 

57 def apply(self) -> bytes: 

58 """Apply the rewrites to a copied byte sequence. 

59 

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. 

63 

64 Returns: 

65 bytes: The modified byte sequence after all rewrites have been applied. 

66 

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 

72 

73 @property 

74 def content(self) -> bytes: 

75 """AI: Return the original, unmodified content this rewriter was constructed with.""" 

76 return self.__content 

77 

78 

79if __name__ == "__main__": 

80 # create a byte array a random bytes of len 20 

81 

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)