brokk_bifrost_python/
clones.rs1use crate::declarations::parse_python_tree;
9use tree_sitter::Node;
10
11const PYTHON_CLONE_AST_IDENTIFIER_TYPES: &[&str] = &["identifier", "keyword_identifier"];
12const PYTHON_CLONE_AST_STRING_TYPES: &[&str] = &["string", "string_content", "interpolation"];
13const PYTHON_CLONE_AST_NUMBER_TYPES: &[&str] = &["integer", "float"];
14
15pub fn normalized_clone_tokens_python(source: &str) -> Vec<String> {
16 let Some(tree) = parse_python_tree(source) else {
17 return Vec::new();
18 };
19 let mut out = Vec::new();
20 collect_normalized_leaf_tokens_python(tree.root_node(), source, &mut out);
21 out
22}
23
24fn collect_normalized_leaf_tokens_python(node: Node<'_>, source: &str, out: &mut Vec<String>) {
25 if node.named_child_count() == 0 {
26 let token = normalize_python_clone_leaf_token(node, source);
27 if !token.is_empty() {
28 out.push(token);
29 }
30 }
31 let child_count = node.child_count();
32 for index in 0..child_count {
33 if let Some(child) = node.child(index) {
34 collect_normalized_leaf_tokens_python(child, source, out);
35 }
36 }
37}
38
39fn normalize_python_clone_leaf_token(node: Node<'_>, source: &str) -> String {
40 let kind = node.kind();
41 let token = source
42 .get(node.start_byte()..node.end_byte())
43 .unwrap_or("")
44 .trim();
45 if token.is_empty() || kind == "comment" {
46 return String::new();
47 }
48 if PYTHON_CLONE_AST_IDENTIFIER_TYPES.contains(&kind) {
49 return "ID".to_string();
50 }
51 if PYTHON_CLONE_AST_STRING_TYPES.contains(&kind) {
52 return "STR".to_string();
53 }
54 if PYTHON_CLONE_AST_NUMBER_TYPES.contains(&kind) {
55 return "NUM".to_string();
56 }
57 if kind == "true" || kind == "false" || token == "True" || token == "False" {
58 return "BOOL".to_string();
59 }
60 if token.chars().count() == 1 && token.chars().all(|ch| !ch.is_alphanumeric()) {
61 return format!("OP:{token}");
62 }
63 format!("T:{kind}")
64}
65
66pub fn build_python_clone_ast_signature(source: &str) -> String {
67 let Some(tree) = parse_python_tree(source) else {
68 return String::new();
69 };
70 let mut labels = Vec::new();
71 collect_python_clone_ast_labels(tree.root_node(), source, &mut labels);
72 labels.join("|")
73}
74
75fn collect_python_clone_ast_labels(node: Node<'_>, source: &str, out: &mut Vec<String>) {
76 out.push(normalize_python_clone_ast_label(node, source));
77 let child_count = node.child_count();
78 for index in 0..child_count {
79 if let Some(child) = node.child(index) {
80 collect_python_clone_ast_labels(child, source, out);
81 }
82 }
83}
84
85fn normalize_python_clone_ast_label(node: Node<'_>, source: &str) -> String {
86 let kind = node.kind();
87 let text = source
88 .get(node.start_byte()..node.end_byte())
89 .unwrap_or("")
90 .trim();
91 if PYTHON_CLONE_AST_IDENTIFIER_TYPES.contains(&kind) {
92 return "ID".to_string();
93 }
94 if PYTHON_CLONE_AST_STRING_TYPES.contains(&kind) {
95 return "STR".to_string();
96 }
97 if PYTHON_CLONE_AST_NUMBER_TYPES.contains(&kind) {
98 return "NUM".to_string();
99 }
100 if kind == "true" || kind == "false" || text == "True" || text == "False" {
101 return "BOOL".to_string();
102 }
103 format!("N:{kind}")
104}