codesynapse_core/ts_extract/
fortran.rs1use super::make_file_node;
2use crate::error::{CodeSynapseError, Result};
3use crate::extract::{make_id, ImportNode, LanguageExtractor};
4use crate::types::{Edge, ExtractionFragment, Node};
5use std::collections::HashMap;
6use std::path::Path;
7use tree_sitter::{Node as TsNode, Parser};
8
9pub struct FortranExtractor;
10
11fn ft_text(source: &[u8], node: &TsNode<'_>) -> String {
12 std::str::from_utf8(&source[node.start_byte()..node.end_byte()])
13 .unwrap_or("")
14 .trim()
15 .to_lowercase()
16}
17
18fn ft_node(id: String, label: String, file_type: &str, path: &Path) -> Node {
19 Node {
20 id,
21 label,
22 file_type: file_type.to_string(),
23 source_file: path.to_string_lossy().to_string(),
24 source_location: None,
25 community: None,
26 rationale: None,
27 docstring: None,
28 metadata: HashMap::new(),
29 }
30}
31
32fn ft_edge(fragment: &mut ExtractionFragment, src: &str, tgt: &str, rel: &str, path: &Path) {
33 fragment.edges.push(Edge {
34 source: src.to_string(),
35 target: tgt.to_string(),
36 relation: rel.to_string(),
37 confidence: "EXTRACTED".to_string(),
38 source_file: Some(path.to_string_lossy().to_string()),
39 weight: 1.0,
40 context: None,
41 });
42}
43
44fn add_node_if_missing(fragment: &mut ExtractionFragment, node: Node) {
45 if !fragment.nodes.iter().any(|n| n.id == node.id) {
46 fragment.nodes.push(node);
47 }
48}
49
50fn fortran_name(source: &[u8], stmt: &TsNode<'_>) -> Option<String> {
54 for i in 0..stmt.child_count() {
55 if let Some(child) = stmt.child(i) {
56 match child.kind() {
57 "name" | "identifier" | "module_name" => {
58 let t = ft_text(source, &child);
59 if !t.is_empty() {
60 return Some(t);
61 }
62 }
63 _ => {}
64 }
65 }
66 }
67 None
68}
69
70fn walk_ft<'t>(
71 node: TsNode<'t>,
72 source: &[u8],
73 file_id: &str,
74 stem: &str,
75 path: &Path,
76 fragment: &mut ExtractionFragment,
77 scope_nid: String,
78) {
79 match node.kind() {
80 "program" => {
81 let stmt = (0..node.child_count())
82 .filter_map(|i| node.child(i))
83 .find(|c| c.kind() == "program_statement");
84 let name = stmt.and_then(|s| fortran_name(source, &s));
85 if let Some(name) = name {
86 let nid = make_id(&[stem, &name]);
87 fragment
88 .nodes
89 .push(ft_node(nid.clone(), name, "code", path));
90 ft_edge(fragment, file_id, &nid, "defines", path);
91 for i in 0..node.child_count() {
92 if let Some(child) = node.child(i) {
93 walk_ft(child, source, file_id, stem, path, fragment, nid.clone());
94 }
95 }
96 }
97 }
98 "module" => {
99 let stmt = (0..node.child_count())
100 .filter_map(|i| node.child(i))
101 .find(|c| c.kind() == "module_statement");
102 let name = stmt.and_then(|s| fortran_name(source, &s));
103 if let Some(name) = name {
104 let nid = make_id(&[stem, &name]);
105 fragment
106 .nodes
107 .push(ft_node(nid.clone(), name, "module", path));
108 ft_edge(fragment, file_id, &nid, "defines", path);
109 for i in 0..node.child_count() {
110 if let Some(child) = node.child(i) {
111 walk_ft(child, source, file_id, stem, path, fragment, nid.clone());
112 }
113 }
114 }
115 }
116 "internal_procedures" => {
117 for i in 0..node.child_count() {
118 if let Some(child) = node.child(i) {
119 walk_ft(
120 child,
121 source,
122 file_id,
123 stem,
124 path,
125 fragment,
126 scope_nid.clone(),
127 );
128 }
129 }
130 }
131 "subroutine" => {
132 let stmt = (0..node.child_count())
133 .filter_map(|i| node.child(i))
134 .find(|c| c.kind() == "subroutine_statement");
135 let name = stmt.and_then(|s| fortran_name(source, &s));
136 if let Some(name) = name {
137 let nid = make_id(&[stem, &name]);
138 fragment.nodes.push(ft_node(
139 nid.clone(),
140 format!("{}()", name),
141 "function",
142 path,
143 ));
144 ft_edge(fragment, &scope_nid, &nid, "defines", path);
145 for i in 0..node.child_count() {
146 if let Some(child) = node.child(i) {
147 walk_ft(child, source, file_id, stem, path, fragment, nid.clone());
148 }
149 }
150 }
151 }
152 "function" => {
153 let stmt = (0..node.child_count())
154 .filter_map(|i| node.child(i))
155 .find(|c| c.kind() == "function_statement");
156 let name = stmt.and_then(|s| fortran_name(source, &s));
157 if let Some(name) = name {
158 let nid = make_id(&[stem, &name]);
159 fragment.nodes.push(ft_node(
160 nid.clone(),
161 format!("{}()", name),
162 "function",
163 path,
164 ));
165 ft_edge(fragment, &scope_nid, &nid, "defines", path);
166 for i in 0..node.child_count() {
167 if let Some(child) = node.child(i) {
168 walk_ft(child, source, file_id, stem, path, fragment, nid.clone());
169 }
170 }
171 }
172 }
173 "use_statement" => {
174 let name = fortran_name(source, &node);
175 if let Some(mod_name) = name {
176 let imp_nid = make_id(&[&mod_name]);
177 let imp_node = ft_node(imp_nid.clone(), mod_name, "module", path);
178 add_node_if_missing(fragment, imp_node);
179 fragment.edges.push(Edge {
180 source: scope_nid.to_string(),
181 target: imp_nid,
182 relation: "imports".to_string(),
183 confidence: "EXTRACTED".to_string(),
184 source_file: Some(path.to_string_lossy().to_string()),
185 weight: 1.0,
186 context: Some("use".to_string()),
187 });
188 }
189 }
190 _ => {
191 for i in 0..node.child_count() {
192 if let Some(child) = node.child(i) {
193 walk_ft(
194 child,
195 source,
196 file_id,
197 stem,
198 path,
199 fragment,
200 scope_nid.clone(),
201 );
202 }
203 }
204 }
205 }
206}
207
208impl FortranExtractor {
209 pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
210 let (file_id, _, file_node) = make_file_node(path);
211 let stem = file_id.clone();
212 let mut fragment = ExtractionFragment {
213 nodes: vec![file_node],
214 edges: vec![],
215 };
216
217 let lang: tree_sitter::Language = tree_sitter_fortran::LANGUAGE.into();
218 let mut parser = Parser::new();
219 parser
220 .set_language(&lang)
221 .map_err(|e| CodeSynapseError::Parse(format!("fortran set_language: {e}")))?;
222 let tree = parser
223 .parse(source, None)
224 .ok_or_else(|| CodeSynapseError::Parse("fortran parse failed".to_string()))?;
225
226 let scope = file_id.clone();
227 walk_ft(
228 tree.root_node(),
229 source,
230 &file_id,
231 &stem,
232 path,
233 &mut fragment,
234 scope,
235 );
236
237 Ok(fragment)
238 }
239}
240
241impl LanguageExtractor for FortranExtractor {
242 fn file_extensions(&self) -> Vec<&'static str> {
243 vec![
244 "f", "f90", "f95", "f03", "f08", "F", "F90", "F95", "F03", "F08",
245 ]
246 }
247 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
248 Self::extract(source, path)
249 }
250 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
251 vec![]
252 }
253 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
254}