brokk_bifrost_python/
adapter.rs1use brokk_bifrost_core::analyzer::cognitive_complexity;
9use std::sync::LazyLock;
10
11use crate::declarations::python_is_decorated_function_boundary;
12
13pub const PYTHON_FILE_EXTENSION: &str = "py";
14
15pub static PYTHON_COGNITIVE_CONFIG: LazyLock<cognitive_complexity::Config> =
18 LazyLock::new(|| cognitive_complexity::Config {
19 if_types: &["if_statement"],
20 alternate_if_types: &["elif_clause"],
21 loop_types: &["for_statement", "while_statement"],
22 catch_types: &["except_clause"],
23 conditional_types: &["conditional_expression"],
24 case_types: &["case_clause"],
25 binary_types: &["boolean_operator"],
26 logical_operators: &["and", "or"],
27 named_function_boundary_types: &["function_definition"],
28 anonymous_function_types: &["lambda"],
29 named_function_boundary_predicate: Some(python_is_decorated_function_boundary),
30 ..cognitive_complexity::Config::empty()
31 });
32
33pub fn python_extract_call_receiver(reference: &str) -> Option<String> {
34 brokk_bifrost_core::analyzer::common::extract_call_reference_parts(reference, &["."])
35 .map(|(receiver, _)| receiver.to_string())
36}
37
38pub fn is_string_line_data(
42 node: tree_sitter::Node<'_>,
43 should_stop: &mut dyn FnMut() -> bool,
44) -> std::ops::ControlFlow<(), bool> {
45 use brokk_bifrost_core::analyzer::tree_walk::{WalkControl, walk_tree_preorder};
46 use std::ops::ControlFlow;
47 if !matches!(node.kind(), "string" | "concatenated_string") {
48 return ControlFlow::Continue(false);
49 }
50 let mut result = ControlFlow::Continue(true);
51 walk_tree_preorder(node, true, |current| {
52 if should_stop() {
53 result = ControlFlow::Break(());
54 WalkControl::Break
55 } else if current.is_error() || current.is_missing() || current.kind() == "interpolation" {
56 result = ControlFlow::Continue(false);
57 WalkControl::Break
58 } else {
59 WalkControl::Continue
60 }
61 });
62 result
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::ops::ControlFlow;
69
70 #[test]
71 fn qs6_string_data_grammar_and_cancellation() {
72 for (literal, kind, interpolations, data) in [
73 ("u'caf\u{e9}'", "string", 0, true),
74 ("b'bytes'", "string", 0, true),
75 ("'one' 'two'", "concatenated_string", 0, true),
76 ("f'plain {{braces}}'", "string", 0, true),
77 ("f'{value:{width}}'", "string", 1, false),
78 ("'prefix' f'{value}'", "concatenated_string", 1, false),
79 ] {
80 let source = format!("value = {literal}\n");
81 let tree = crate::declarations::parse_python_tree(&source).unwrap();
82 let root = tree.root_node();
83 assert!(!root.has_error(), "{}", root.to_sexp());
84 let value = root
85 .named_child(0)
86 .unwrap()
87 .named_child(0)
88 .unwrap()
89 .child_by_field_name("right")
90 .unwrap();
91 assert_eq!(value.kind(), kind);
92 let mut stack = vec![value];
93 let mut count = 0;
94 while let Some(node) = stack.pop() {
95 assert!(!node.is_error() && !node.is_missing());
96 count += usize::from(node.kind() == "interpolation");
97 let mut cursor = node.walk();
98 stack.extend(node.named_children(&mut cursor));
99 }
100 assert_eq!(count, interpolations, "{}", value.to_sexp());
101 assert_eq!(
102 is_string_line_data(value, &mut || false),
103 ControlFlow::Continue(data)
104 );
105 let mut stops = 0;
106 assert_eq!(
107 is_string_line_data(value, &mut || {
108 stops += 1;
109 true
110 }),
111 ControlFlow::Break(())
112 );
113 assert_eq!(stops, 1);
114 }
115 }
116}