Coverage for src/renaissance/integrations/tree_sitter/lst.py: 91%

67 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-02 13:12 +0000

1"""AI: Lossless syntax tree (LST) node implementation backed by tree-sitter.""" 

2 

3import sys 

4from typing import Any, Self, cast 

5 

6from renaissance.integrations.tree_sitter.kinds import TREE_SITTER_KIND_MAP 

7from renaissance.syntax_tree.semantic_kind import SemanticKind 

8from renaissance.utils.ast_utils import format_node, match_children, match_props, next_sibling, preceding_sibling 

9 

10IRRELEVANT_PROPS = {"source_code", "end_point", "start_point", "location", "type"} 

11IRRELEVANT_NODE = {"comment"} 

12 

13 

14class LSTNode: 

15 """AI: Lossless syntax tree (LST) node implementation backed by tree-sitter.""" 

16 

17 def __init__( 

18 self, 

19 node_type: str, 

20 properties: dict[str, Any], 

21 signature: str, 

22 offset: int = 0, 

23 children: list[Self] | None = None, 

24 parent: Self | None = None, 

25 root: Self | None = None, 

26 ): 

27 """AI: Wrap a tree-sitter node as a language-syntax-tree node with derived semantic kind.""" 

28 self.root = root or self 

29 self.parent = parent 

30 self.children = [] if children is None else children 

31 self.properties = properties 

32 if node_type == "string" and signature.startswith("f"): 

33 node_type = "FormattedString" 

34 

35 self.parser_kind = node_type 

36 self.semantic_kind = TREE_SITTER_KIND_MAP.get(node_type, SemanticKind.NODE) 

37 

38 self.is_implicit = True 

39 self.show_props = False 

40 self.indent = "" 

41 

42 self.is_statement = node_type == "Expr" 

43 self.referenced_by = [] 

44 self.references = [] 

45 

46 self.signature = signature 

47 self.text = signature 

48 self.filename = "unknown" 

49 self.length = len(signature) 

50 self.offset = offset 

51 self.end_offset = self.offset + self.length 

52 self.extended_end_offset = self.end_offset 

53 

54 @property 

55 def kind_key(self) -> SemanticKind | str: 

56 """AI: Return the semantic kind, or the raw parser kind when no semantic kind applies.""" 

57 return self.semantic_kind if self.semantic_kind is not SemanticKind.NODE else self.parser_kind 

58 

59 def __eq__(self, other): 

60 """AI: Return whether this node is structurally equal to `other`, ignoring irrelevant properties/children.""" 

61 return ( 

62 isinstance(other, type(self)) 

63 and self.kind_key == other.kind_key 

64 and match_props(self.properties, other.properties, IRRELEVANT_PROPS) 

65 and match_children(self.children, other.children, IRRELEVANT_NODE) 

66 ) 

67 

68 def __hash__(self): 

69 """AI: Return a hash based on the node's kind key, properties, and children.""" 

70 return hash((self.kind_key, frozenset(self.properties.items()), tuple(self.children))) 

71 

72 def match_props(self, properties) -> bool: 

73 """AI: Return whether this node's properties match the given properties, ignoring irrelevant ones.""" 

74 all_keys = (self.properties.keys() | properties.keys()) - IRRELEVANT_PROPS 

75 return all(self.properties.get(n) == properties.get(n) for n in all_keys) 

76 

77 def match_children(self, children): 

78 """AI: Return whether this node's children match the given children at each corresponding index.""" 

79 return all(i < len(self.children) and self.children[i] == child for i, child in enumerate(children)) 

80 

81 def add_child(self, child): # LSTNode): 

82 """AI: Append child to this node's children and set its parent to this node.""" 

83 self.children.append(child) 

84 child.parent = self 

85 

86 @property 

87 def preceding_sibling(self) -> Self | None: 

88 """AI: Return the sibling node immediately preceding this one, or None.""" 

89 return preceding_sibling(self) 

90 

91 @property 

92 def next_sibling(self) -> Self | None: 

93 """AI: Return the sibling node immediately following this one, or None.""" 

94 return next_sibling(self) 

95 

96 @property 

97 def name(self) -> str: 

98 """AI: Return this node's "name" property, or an empty string if absent.""" 

99 return self.properties.get("name", "") 

100 

101 def binary_file_content(self): 

102 """AI: Return this node's source code encoded as bytes using the filesystem encoding.""" 

103 src = cast("str", self.properties.get("source_code")) 

104 return src.encode(sys.getfilesystemencoding()) 

105 

106 @property 

107 def node(self): 

108 """AI: Return this node itself.""" 

109 return self 

110 

111 def __repr__(self): 

112 """AI: Return the formatted node representation.""" 

113 return format_node(self) 

114 # raw_lines = self.signature.splitlines() 

115 # properties_text = "" if not self.show_props else self.properties 

116 # prefix = " " if len(raw_lines) < 2 else f"\n {self.indent}" 

117 # formatted_lines = [f"{prefix}|{line}|" for line in raw_lines] 

118 # return ( 

119 # f"{self.indent}({self.kind}, {self.name}," 

120 # f" {self.filename}[{self.offset}:{self.offset + self.length}])" 

121 # f"{properties_text}:{''.join(formatted_lines)}\n" 

122 # ) 

123 

124 def is_part_of_translation_unit(self): 

125 """AI: Return whether this node belongs to a translation unit (has a root).""" 

126 return self.root is not None 

127 

128 

129class LST: 

130 """AI: Hold the root node of a language-syntax tree.""" 

131 

132 def __init__(self, root: LSTNode): 

133 """AI: Hold the root node of a language-syntax tree.""" 

134 self.root = root