1- from .ast import parse_ast
1+ from code_tokenize .config import load_from_lang_config
2+ from code_tokenize .tokens import match_type
3+
4+ from .ast import parse_ast
5+ from .utils import cached_property
6+ from .sstubs import SStubPattern , classify_sstub
27
38
49# Main method --------------------------------------------------------
510
611def difference (source , target , lang = "guess" , ** kwargs ):
712
13+ config = load_from_lang_config (lang , ** kwargs )
814 source_ast = parse_ast (source , lang = lang , ** kwargs )
915 target_ast = parse_ast (target , lang = lang , ** kwargs )
1016
1117 # Concretize Diff
1218 source_ast , target_ast = diff_search (source_ast , target_ast )
1319
14- return ASTDiff (source_ast , target_ast )
20+ return ASTDiff (config , source_ast , target_ast )
1521
1622
1723# Diff Search --------------------------------------------------------
@@ -40,12 +46,92 @@ def diff_search(source_ast, target_ast):
4046 return (source_node , target_node )
4147
4248
43-
44-
4549# AST Difference --------------------------------------------------------
4650
4751class ASTDiff :
4852
49- def __init__ (self , source_ast , target_ast ):
53+ def __init__ (self , config , source_ast , target_ast ):
54+ self .config = config
5055 self .source_ast = source_ast
51- self .target_ast = target_ast
56+ self .target_ast = target_ast
57+
58+ @cached_property
59+ def is_single_statement (self ):
60+ return (is_single_statement (self .config .statement_types , self .source_ast )
61+ and is_single_statement (self .config .statement_types , self .target_ast ))
62+
63+ @cached_property
64+ def source_text (self ):
65+ return tokenize_tree (self .source_ast )
66+
67+ @cached_property
68+ def target_text (self ):
69+ return tokenize_tree (self .target_ast )
70+
71+ def statement_diff (self ):
72+ source_stmt = parent_statement (self .config .statement_types , self .source_ast )
73+ target_stmt = parent_statement (self .config .statement_types , self .target_ast )
74+
75+ if source_stmt is None or target_stmt is None :
76+ raise ValueError ("AST diff is not enclosed in a statement" )
77+
78+ return ASTDiff (self .config , source_stmt , target_stmt )
79+
80+ def sstub_pattern (self ):
81+ if not self .is_single_statement :
82+ return SStubPattern .MULTI_STMT
83+
84+ return classify_sstub (* diff_search (self .source_ast , self .target_ast ))
85+
86+ def edit_script (self ):
87+ pass
88+
89+ def __repr__ (self ):
90+ return "%s -> %s" % (self .source_text , self .target_text )
91+
92+
93+
94+
95+ # AST Utils -----------------------------------------------------------
96+
97+ def is_single_statement (statement_types , ast ):
98+
99+ if parent_statement (statement_types , ast ) is None : return False
100+
101+ def is_statement_type (node_type ):
102+ return any (match_type (r , node_type ) for r in statement_types )
103+
104+ # Test if any other statement as child
105+ queue = list (ast .children )
106+ while len (queue ) > 0 :
107+ node = queue .pop (0 )
108+ if is_statement_type (node .type ): return False
109+
110+ queue .extend (node .children )
111+
112+ return True
113+
114+
115+ def parent_statement (statement_types , ast ):
116+
117+ def is_statement_type (node_type ):
118+ return any (match_type (r , node_type ) for r in statement_types )
119+
120+ # Test if node in statement
121+ parent_node = ast
122+ while parent_node is not None and not is_statement_type (parent_node .type ):
123+ parent_node = parent_node .parent
124+
125+ return parent_node
126+
127+
128+ def tokenize_tree (ast ):
129+ tokens = []
130+
131+ # Test if any other statement as child
132+ if ast .text : tokens .append (ast .text )
133+
134+ for child in ast .children :
135+ tokens .append (tokenize_tree (child ))
136+
137+ return " " .join (tokens )
0 commit comments