_diffctx/edges/semantic/
julia.rs1use std::path::{Path, PathBuf};
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5use rustc_hash::{FxHashMap, FxHashSet};
6
7use crate::config::weights::EDGE_WEIGHTS;
8use crate::types::Fragment;
9
10use super::super::EdgeDict;
11use super::super::base::{self, EdgeBuilder, add_edge, discover_files_by_refs};
12
13fn is_julia_file(path: &Path) -> bool {
14 base::file_ext(path) == ".jl"
15}
16
17static USING_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*using\s+([\w.,\s]+)").unwrap());
18static IMPORT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*import\s+([\w.,:\s]+)").unwrap());
19static INCLUDE_RE: Lazy<Regex> =
20 Lazy::new(|| Regex::new(r#"(?m)^\s*include\s*\(\s*['"]([^'"]+)['"]"#).unwrap());
21static STRUCT_RE: Lazy<Regex> =
22 Lazy::new(|| Regex::new(r"(?m)^\s*(?:mutable\s+)?struct\s+(\w+)").unwrap());
23static ABSTRACT_RE: Lazy<Regex> =
24 Lazy::new(|| Regex::new(r"(?m)^\s*abstract\s+type\s+(\w+)").unwrap());
25static FUNC_RE: Lazy<Regex> =
26 Lazy::new(|| Regex::new(r"(?m)^\s*(?:function|macro)\s+(\w+)").unwrap());
27static SHORT_FUNC_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^(\w+)\s*\(.*\)\s*=").unwrap());
28static TYPE_REF_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\b([A-Z]\w+)\b").unwrap());
29
30fn extract_imports(content: &str) -> FxHashSet<String> {
31 let mut refs = FxHashSet::default();
32 for cap in USING_RE.captures_iter(content) {
33 for part in cap[1].split(',') {
34 let name = part.split(':').next().unwrap_or("").trim();
35 if !name.is_empty() {
36 refs.insert(name.to_string());
37 }
38 }
39 }
40 for cap in IMPORT_RE.captures_iter(content) {
41 for part in cap[1].split(',') {
42 let name = part.split(':').next().unwrap_or("").trim();
43 if !name.is_empty() {
44 refs.insert(name.to_string());
45 }
46 }
47 }
48 refs.extend(INCLUDE_RE.captures_iter(content).map(|c| c[1].to_string()));
49 refs
50}
51
52fn extract_defs(content: &str) -> FxHashSet<String> {
53 let mut defs: FxHashSet<String> = STRUCT_RE
54 .captures_iter(content)
55 .map(|c| c[1].to_string())
56 .collect();
57 defs.extend(ABSTRACT_RE.captures_iter(content).map(|c| c[1].to_string()));
58 defs.extend(FUNC_RE.captures_iter(content).map(|c| c[1].to_string()));
59 defs.extend(
60 SHORT_FUNC_RE
61 .captures_iter(content)
62 .map(|c| c[1].to_string()),
63 );
64 defs
65}
66
67pub struct JuliaEdgeBuilder;
68
69impl EdgeBuilder for JuliaEdgeBuilder {
70 fn build(&self, fragments: &[Fragment], repo_root: Option<&Path>) -> EdgeDict {
71 let frags: Vec<&Fragment> = fragments
72 .iter()
73 .filter(|f| is_julia_file(Path::new(f.path())))
74 .collect();
75 if frags.is_empty() {
76 return FxHashMap::default();
77 }
78
79 let using_w = EDGE_WEIGHTS["julia_using"].forward;
80 let include_w = EDGE_WEIGHTS["julia_include"].forward;
81 let type_w = EDGE_WEIGHTS["julia_type"].forward;
82 let fn_w = EDGE_WEIGHTS["julia_fn"].forward;
83 let reverse_factor = EDGE_WEIGHTS["julia_using"].reverse_factor;
84
85 let idx = base::FragmentIndex::new(fragments, repo_root);
86 let mut name_to_defs: FxHashMap<String, Vec<_>> = FxHashMap::default();
87 for f in &frags {
88 for name in extract_defs(&f.content) {
89 name_to_defs
90 .entry(name.to_lowercase())
91 .or_default()
92 .push(f.id.clone());
93 }
94 }
95
96 let mut edges: EdgeDict = FxHashMap::default();
97
98 for f in &frags {
99 let self_defs = extract_defs(&f.content);
100 for imp in extract_imports(&f.content) {
101 let w = if INCLUDE_RE.is_match(&format!("include(\"{}\")", imp)) {
102 include_w
103 } else {
104 using_w
105 };
106 base::link_by_name(&f.id, &imp, &idx, &mut edges, w, reverse_factor);
107 }
108 for cap in TYPE_REF_RE.captures_iter(&f.content) {
109 let name = &cap[1];
110 if self_defs.contains(name) {
111 continue;
112 }
113 if let Some(targets) = name_to_defs.get(&name.to_lowercase()) {
114 for t in targets {
115 if t != &f.id {
116 add_edge(&mut edges, &f.id, t, type_w, reverse_factor);
117 }
118 }
119 }
120 }
121 for id in &f.identifiers {
122 if self_defs.contains(id) {
123 continue;
124 }
125 if let Some(targets) = name_to_defs.get(&id.to_lowercase()) {
126 for t in targets {
127 if t != &f.id {
128 add_edge(&mut edges, &f.id, t, fn_w, reverse_factor);
129 }
130 }
131 }
132 }
133 }
134 edges
135 }
136
137 fn discover_related_files(
138 &self,
139 changed: &[PathBuf],
140 candidates: &[PathBuf],
141 repo_root: Option<&Path>,
142 file_cache: Option<&FxHashMap<PathBuf, String>>,
143 ) -> Vec<PathBuf> {
144 let jl_changed: Vec<&PathBuf> = changed.iter().filter(|f| is_julia_file(f)).collect();
145 if jl_changed.is_empty() {
146 return vec![];
147 }
148 let mut refs = FxHashSet::default();
149 for f in &jl_changed {
150 if let Some(content) = base::read_file_cached(f, file_cache) {
151 refs.extend(extract_imports(&content));
152 }
153 }
154 discover_files_by_refs(&refs, changed, candidates, repo_root)
155 }
156}