1pub mod prelude;
6use seq_map::SeqMap;
7use std::collections::HashSet;
8use std::path::PathBuf;
9use std::{env, io};
10use swamp_script_ast::Function;
11use swamp_script_ast::prelude::*;
12use swamp_script_parser::{AstParser, SpecificError};
13use swamp_script_source_map::{FileId, SourceMap};
14use tracing::debug;
15pub struct ParseRoot;
16
17#[derive(Debug)]
18pub enum ParseRootError {
19 IoError(std::io::Error),
20
21 ParserError(ParserError),
22}
23
24impl From<std::io::Error> for ParseRootError {
25 fn from(err: std::io::Error) -> Self {
26 Self::IoError(err)
27 }
28}
29
30#[derive(Debug)]
31pub struct ParsedAstModule {
32 pub ast_module: swamp_script_ast::Module,
33 pub file_id: FileId,
34}
35
36impl ParsedAstModule {
37 pub fn declare_external_function(
39 &mut self,
40 parameters: Vec<Parameter>,
41 return_type: Option<Type>,
42 ) {
43 let fake_identifier = Node::default();
44
45 let signature = FunctionDeclaration {
46 name: fake_identifier.clone(),
47 params: parameters,
48 self_parameter: None,
49 return_type,
50 };
51 let external_signature = Function::External(signature);
52
53 self.ast_module.definitions.insert(
54 0, Definition::FunctionDef(external_signature),
56 );
57 }
58}
59
60#[derive(Debug)]
61pub struct RelativePath(pub String);
62
63#[derive(Debug)]
64pub struct ParserError {
65 pub node: Node,
66 pub specific: SpecificError,
67 pub file_id: FileId,
68}
69
70impl ParseRoot {
71 pub fn new() -> Self {
72 Self {}
73 }
74
75 pub fn parse(
76 &self,
77 contents: String,
78 file_id: FileId,
79 ) -> Result<ParsedAstModule, ParseRootError> {
80 let parser = AstParser {};
81
82 let ast_program = parser.parse_module(&contents).map_err(|err| {
83 let new_err = ParserError {
84 node: Node { span: err.span },
85 specific: err.specific,
86 file_id,
87 };
88 ParseRootError::ParserError(new_err)
89 })?;
90
91 Ok(ParsedAstModule {
92 ast_module: ast_program,
93 file_id,
94 })
95 }
96}
97
98#[derive(Clone)]
99#[allow(unused)]
100pub struct ModuleInfo {
101 path: Vec<String>,
102 imports: Vec<Vec<String>>,
103 parsed: bool,
104 analyzed: bool,
105}
106
107pub struct DependencyParser {
108 pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
109 already_parsed_modules: SeqMap<Vec<String>, ParsedAstModule>,
110 pub already_resolved_modules: HashSet<Vec<String>>,
111}
112
113impl Default for DependencyParser {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl DependencyParser {
120 pub fn new() -> Self {
121 Self {
122 import_scanned_modules: SeqMap::new(),
123 already_parsed_modules: SeqMap::new(),
124 already_resolved_modules: HashSet::new(),
125 }
126 }
127
128 pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
129 self.already_resolved_modules.insert(module_path);
130 }
131
132 pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParsedAstModule) {
133 debug!(
134 "Adding ast module parsed outside of graph resolver {:?}",
135 module_path
136 );
137 self.already_parsed_modules
138 .insert(module_path, parsed_module)
139 .expect("can not add parsed module")
140 }
141}
142
143#[derive(Debug)]
144pub enum DependencyError {
145 CircularDependency(Vec<String>),
146 ParseRootError(ParseRootError),
147 IoError(io::Error),
148}
149
150impl From<ParseRootError> for DependencyError {
151 fn from(err: ParseRootError) -> Self {
152 Self::ParseRootError(err)
153 }
154}
155
156impl From<io::Error> for DependencyError {
157 fn from(value: io::Error) -> Self {
158 Self::IoError(value)
159 }
160}
161
162pub const LOCAL_ROOT_PACKAGE_PATH: &str = "crate";
163
164pub fn get_all_local_paths(
165 source_map: &SourceMap,
166 parsed_module: &ParsedAstModule,
167) -> Vec<Vec<String>> {
168 let mut imports = vec![];
169
170 for def in parsed_module.ast_module.definitions() {
171 match def {
172 Definition::Mod(import) => {
173 let mut sections = Vec::new();
174 sections.push(LOCAL_ROOT_PACKAGE_PATH.to_string());
175 for section_node in &import.module_path.0 {
176 let import_path = source_map
177 .get_span_source(
178 parsed_module.file_id,
179 section_node.span.offset as usize,
180 section_node.span.length.into(),
181 )
182 .to_string();
183 sections.push(import_path);
184 }
185
186 imports.push(sections);
187 }
188 _ => continue,
189 }
190 }
191
192 imports
193}
194
195pub fn module_path_to_relative_swamp_file(module_path_vec: &[String]) -> PathBuf {
196 let mut path_buf = PathBuf::new();
197
198 let orig_len = module_path_vec.len();
199
200 let converted_path = if module_path_vec[0] == "crate" {
201 &module_path_vec[1..]
202 } else {
203 module_path_vec
204 };
205
206 path_buf.push(converted_path.join("/"));
207 if orig_len == 1 {
208 path_buf.push("lib"); }
210
211 path_buf.set_extension("swamp");
212
213 path_buf
214}
215
216pub fn module_path_to_relative_swamp_file_string(module_path_vec: &[String]) -> String {
217 module_path_to_relative_swamp_file(module_path_vec)
218 .to_str()
219 .unwrap()
220 .into()
221}
222
223pub fn mount_name_from_path(path: &[String]) -> &str {
224 if path[0] == "crate" {
225 "crate"
226 } else {
227 "registry"
228 }
229}
230
231pub fn parse_single_module(
234 source_map: &mut SourceMap,
235 module_path: &[String],
236) -> Result<ParsedAstModule, DependencyError> {
237 let mount_name = mount_name_from_path(&module_path);
238
239 let (file_id, script) = source_map.read_file_relative(
240 mount_name,
241 &module_path_to_relative_swamp_file_string(module_path),
242 )?;
243
244 let parse_module = ParseRoot.parse(script, file_id)?;
245
246 Ok(parse_module)
247}
248
249impl DependencyParser {
250 pub fn parse_local_modules(
251 &mut self,
252 module_path: &[String],
253 source_map: &mut SourceMap,
254 ) -> Result<(), DependencyError> {
255 let mut to_parse = vec![module_path.to_vec()];
256
257 while let Some(path) = to_parse.pop() {
258 let module_path_vec = &path.clone();
259 if self.import_scanned_modules.contains_key(module_path_vec) {
260 continue;
261 }
262
263 let parsed_module_to_scan =
264 if let Some(parsed_module) = self.already_parsed_modules.get(module_path_vec) {
265 parsed_module
266 } else if self.already_resolved_modules.contains(module_path_vec) {
267 continue;
268 } else {
269 let parsed_ast_module = parse_single_module(source_map, &path)?;
270
271 self.already_parsed_modules
272 .insert(path.clone(), parsed_ast_module)
273 .expect("TODO: panic message");
274
275 self.already_parsed_modules
276 .get(&path.clone())
277 .expect("we just inserted it")
278 };
279
280 let imports = get_all_local_paths(source_map, parsed_module_to_scan);
281 let filtered_imports: Vec<Vec<String>> = imports
282 .into_iter()
283 .filter(|import| !self.already_resolved_modules.contains(import))
284 .collect();
285
286 self.import_scanned_modules
287 .insert(
288 path.clone(),
289 ModuleInfo {
290 path: path.clone(),
291 imports: filtered_imports.clone(),
292 parsed: false,
293 analyzed: false,
294 },
295 )
296 .expect("TODO: panic message");
297
298 to_parse.extend(filtered_imports.clone());
299 }
300 Ok(())
301 }
302
303 pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParsedAstModule> {
304 self.already_parsed_modules.get(&path.to_vec())
305 }
306
307 pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParsedAstModule> {
308 self.already_parsed_modules.get_mut(&path.to_vec())
309 }
310
311 pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
312 let mut order = Vec::new();
313 let mut visited = HashSet::new();
314 let mut temp_visited = HashSet::new();
315
316 fn visit(
317 graph: &DependencyParser,
318 path: &[String],
319 visited: &mut HashSet<Vec<String>>,
320 temp_visited: &mut HashSet<Vec<String>>,
321 order: &mut Vec<Vec<String>>,
322 ) -> Result<(), DependencyError> {
323 if temp_visited.contains(path) {
324 return Err(DependencyError::CircularDependency(Vec::from(path)));
325 }
326
327 if visited.contains(path) {
328 return Ok(());
329 }
330
331 temp_visited.insert(Vec::from(path));
332
333 if let Some(module) = graph.import_scanned_modules.get(&path.to_vec()) {
334 for import in &module.imports {
335 visit(graph, import, visited, temp_visited, order)?;
336 }
337 }
338
339 order.push(Vec::from(path));
340 visited.insert(Vec::from(path));
341
342 temp_visited.remove(path);
343
344 Ok(())
345 }
346
347 for path in self.import_scanned_modules.keys() {
348 if !visited.contains(path) {
349 visit(self, path, &mut visited, &mut temp_visited, &mut order)?;
350 }
351 }
352
353 Ok(order)
354 }
355}
356
357fn get_current_dir() -> Result<PathBuf, std::io::Error> {
358 let path = env::current_dir()?;
359
360 Ok(path)
363}
364
365#[derive(Debug)]
366pub enum DepLoaderError {
367 DependencyError(DependencyError),
368}
369
370impl From<DependencyError> for DepLoaderError {
371 fn from(e: DependencyError) -> Self {
372 Self::DependencyError(e)
373 }
374}
375
376pub fn os_cache_path(project_name: &str, directory_name: &str) -> io::Result<PathBuf> {
377 if cfg!(target_os = "windows") {
378 match env::var("LOCALAPPDATA") {
379 Ok(app_data) => {
380 let mut path = PathBuf::from(app_data);
381 path.push(project_name);
382 path.push(directory_name);
383 Ok(path)
384 }
385 Err(err) => {
386 eprintln!("Error: LOCALAPPDATA environment variable not found.");
387 Err(io::Error::new(io::ErrorKind::Other, err.to_string()))
388 }
389 }
390 } else if cfg!(target_os = "macos") {
391 match env::var("HOME") {
392 Ok(home_dir) => {
393 let mut path = PathBuf::from(home_dir);
394 path.push("Library");
395 path.push("Caches");
396 path.push(project_name);
397 path.push(directory_name);
398 Ok(path)
399 }
400 Err(err) => {
401 eprintln!("Error: HOME environment variable not found.");
402 Err(io::Error::new(io::ErrorKind::Other, err.to_string()))
403 }
404 }
405 } else if cfg!(target_os = "linux") {
406 let xdg_cache_home = env::var("XDG_CACHE_HOME").ok();
407 let cache_dir = xdg_cache_home.as_deref().unwrap_or("$HOME/.cache");
408
409 match env::var("HOME") {
410 Ok(home_dir) => {
411 let mut path = PathBuf::from(cache_dir.replace("$HOME", &home_dir));
412 path.push(project_name);
413 path.push(directory_name);
414 Ok(path)
415 }
416 Err(err) => {
417 eprintln!("Error: HOME environment variable not found.");
418 Err(io::Error::new(io::ErrorKind::Other, err.to_string()))
419 }
420 }
421 } else {
422 Err(io::Error::new(
423 io::ErrorKind::Other,
424 "unknown operating system",
425 ))
426 }
427}
428
429pub fn swamp_registry_path() -> io::Result<PathBuf> {
430 os_cache_path("swamp", "registry")
431}
432
433pub fn parse_local_modules_and_get_order(
434 module_path: Vec<String>,
435 dependency_parser: &mut DependencyParser,
436 source_map: &mut SourceMap,
437) -> Result<Vec<Vec<String>>, DepLoaderError> {
438 debug!(current_directory=?get_current_dir().expect("failed to get current directory"), "current directory");
439 dependency_parser.parse_local_modules(&module_path, source_map)?;
440
441 let module_paths_in_order = dependency_parser.get_analysis_order()?;
442
443 Ok(module_paths_in_order)
444}
445
446