swamp_dep_loader/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod prelude;
6use dirs::home_dir;
7use seq_map::SeqMap;
8use source_map_cache::{FileId, SourceMap};
9use std::collections::HashSet;
10use std::path::{Path, PathBuf};
11use std::{env, io};
12use swamp_ast::prelude::*;
13use swamp_parser::{AstParser, SpecificError};
14use time_dilation::ScopedTimer;
15use tracing::error;
16
17pub struct ParseRoot;
18
19#[derive(Debug)]
20pub enum ParseRootError {
21    ParserError(ParserError),
22}
23
24#[derive(Debug)]
25pub struct ParsedAstModule {
26    pub ast_module: swamp_ast::Module,
27    pub file_id: FileId,
28}
29
30#[derive(Debug)]
31pub struct RelativePath(pub String);
32
33#[derive(Debug)]
34pub struct ParserError {
35    pub node: Node,
36    pub specific: SpecificError,
37    pub file_id: FileId,
38}
39
40impl Default for ParseRoot {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl ParseRoot {
47    #[must_use]
48    pub const fn new() -> Self {
49        Self {}
50    }
51
52    pub fn parse(
53        &self,
54        contents: String,
55        file_id: FileId,
56    ) -> Result<ParsedAstModule, ParseRootError> {
57        let ast_program = AstParser.parse_module(&contents).map_err(|err| {
58            let new_err = ParserError {
59                node: Node { span: err.span },
60                specific: err.specific,
61                file_id,
62            };
63            ParseRootError::ParserError(new_err)
64        })?;
65
66        Ok(ParsedAstModule {
67            ast_module: ast_program,
68            file_id,
69        })
70    }
71}
72
73#[derive(Clone)]
74#[allow(unused)]
75pub struct ModuleInfo {
76    path: Vec<String>,
77    imports: Vec<Vec<String>>,
78    uses: Vec<Vec<String>>,
79    parsed: bool,
80    analyzed: bool,
81}
82
83pub struct DependencyParser {
84    pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
85    already_parsed_modules: SeqMap<Vec<String>, ParsedAstModule>,
86    pub already_resolved_modules: HashSet<Vec<String>>,
87}
88
89impl Default for DependencyParser {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl DependencyParser {
96    #[must_use]
97    pub fn new() -> Self {
98        Self {
99            import_scanned_modules: SeqMap::new(),
100            already_parsed_modules: SeqMap::new(),
101            already_resolved_modules: HashSet::new(),
102        }
103    }
104
105    pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
106        self.already_resolved_modules.insert(module_path);
107    }
108
109    pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParsedAstModule) {
110        self.already_parsed_modules
111            .insert(module_path, parsed_module)
112            .expect("insert");
113    }
114}
115
116#[derive(Debug)]
117pub enum DependencyError {
118    CircularDependency(Vec<String>),
119    ParseRootError(ParseRootError),
120    ReadFileError(io::Error),
121}
122
123impl From<ParseRootError> for DependencyError {
124    fn from(err: ParseRootError) -> Self {
125        Self::ParseRootError(err)
126    }
127}
128
129pub const LOCAL_ROOT_PACKAGE_PATH: &str = "crate";
130
131#[must_use]
132pub fn get_all_local_paths(
133    source_map: &SourceMap,
134    parsed_module: &ParsedAstModule,
135) -> (Vec<Vec<String>>, Vec<Vec<String>>) {
136    let mut imports = vec![];
137    let mut uses = vec![];
138
139    for def in parsed_module.ast_module.definitions() {
140        match &def.kind {
141            DefinitionKind::Mod(import) => {
142                let mut sections = Vec::new();
143                sections.push(LOCAL_ROOT_PACKAGE_PATH.to_string());
144                for section_node in &import.module_path.0 {
145                    let import_path = source_map
146                        .get_span_source(
147                            parsed_module.file_id,
148                            section_node.span.offset as usize,
149                            section_node.span.length.into(),
150                        )
151                        .to_string();
152                    sections.push(import_path);
153                }
154
155                imports.push(sections);
156            }
157
158            DefinitionKind::Use(import) => {
159                let mut sections = Vec::new();
160                for section_node in &import.module_path.0 {
161                    let import_path = source_map
162                        .get_span_source(
163                            parsed_module.file_id,
164                            section_node.span.offset as usize,
165                            section_node.span.length.into(),
166                        )
167                        .to_string();
168                    sections.push(import_path);
169                }
170                uses.push(sections);
171            }
172            _ => continue,
173        }
174    }
175
176    (imports, uses)
177}
178
179#[must_use]
180pub fn module_path_to_relative_swamp_file(module_path_vec: &[String]) -> PathBuf {
181    let mut path_buf = PathBuf::new();
182
183    let orig_len = module_path_vec.len();
184
185    let converted_path = if module_path_vec[0] == "crate" {
186        &module_path_vec[1..]
187    } else {
188        module_path_vec
189    };
190
191    path_buf.push(converted_path.join("/"));
192    if orig_len == 1 {
193        path_buf.push("lib"); // lib is default if the path only contains the package root
194    }
195
196    path_buf.set_extension("swamp");
197
198    path_buf
199}
200
201#[must_use]
202pub fn module_path_to_relative_swamp_file_string(module_path_vec: &[String]) -> String {
203    module_path_to_relative_swamp_file(module_path_vec)
204        .to_str()
205        .unwrap()
206        .into()
207}
208
209#[must_use]
210pub fn mount_name_from_path(path: &[String]) -> &str {
211    if path[0] == "crate" {
212        "crate"
213    } else {
214        "registry"
215    }
216}
217
218/// Parses just a single module. Any `mod` keywords in this file will be ignored. So this
219/// is mainly for internal use.
220pub fn parse_single_module(
221    source_map: &mut SourceMap,
222    module_path: &[String],
223) -> Result<ParsedAstModule, DependencyError> {
224    let debug = format!("parse module {module_path:?}");
225    let _parse_module_timer = ScopedTimer::new(&debug);
226
227    let mount_name = mount_name_from_path(module_path);
228
229    let (file_id, script) = source_map
230        .read_file_relative(
231            mount_name,
232            &module_path_to_relative_swamp_file_string(module_path),
233        )
234        .map_err(DependencyError::ReadFileError)?;
235
236    let parse_module = ParseRoot.parse(script, file_id)?;
237
238    Ok(parse_module)
239}
240
241pub fn parse_single_module_from_text(
242    source_map: &mut SourceMap,
243    module_path: &[String],
244    script: &str,
245) -> Result<ParsedAstModule, DependencyError> {
246    let debug = format!("parse module {module_path:?}");
247    let _parse_module_timer = ScopedTimer::new(&debug);
248
249    let mount_name = mount_name_from_path(module_path);
250
251
252    let file_id = source_map.set(
253        mount_name,
254        module_path_to_relative_swamp_file_string(module_path).as_ref(),
255        script,
256    );
257
258    let parse_module = ParseRoot.parse(script.to_string(), file_id)?;
259
260    Ok(parse_module)
261}
262
263impl DependencyParser {
264    pub fn parse_local_modules(
265        &mut self,
266        module_path: &[String],
267        source_map: &mut SourceMap,
268    ) -> Result<(), DependencyError> {
269        let mut to_parse = vec![module_path.to_vec()];
270
271        while let Some(path) = to_parse.pop() {
272            let module_path_vec = &path.clone();
273            if self.import_scanned_modules.contains_key(module_path_vec) {
274                continue;
275            }
276
277            let parsed_module_to_scan =
278                if let Some(parsed_module) = self.already_parsed_modules.get(module_path_vec) {
279                    parsed_module
280                } else if self.already_resolved_modules.contains(module_path_vec) {
281                    continue;
282                } else if path == ["core"] || path == ["std"] {
283                    continue;
284                } else {
285                    let parsed_ast_module = parse_single_module(source_map, &path)?;
286
287                    self.already_parsed_modules
288                        .insert(path.clone(), parsed_ast_module)
289                        .expect("insert");
290
291                    self.already_parsed_modules
292                        .get(&path.clone())
293                        .expect("we just inserted it")
294                };
295
296            let (imports, uses) = get_all_local_paths(source_map, parsed_module_to_scan);
297            let filtered_imports: Vec<Vec<String>> = imports
298                .into_iter()
299                .filter(|import| !self.already_resolved_modules.contains(import))
300                .collect();
301
302            let filtered_uses: Vec<Vec<String>> = uses
303                .into_iter()
304                .filter(|import| !self.already_resolved_modules.contains(import))
305                .collect();
306
307            self.import_scanned_modules
308                .insert(
309                    path.clone(),
310                    ModuleInfo {
311                        path: path.clone(),
312                        imports: filtered_imports.clone(),
313                        uses: filtered_uses.clone(),
314                        parsed: false,
315                        analyzed: false,
316                    },
317                )
318                .expect("insert");
319
320            to_parse.extend(filtered_imports.clone());
321
322            to_parse.extend(filtered_uses.clone());
323        }
324        Ok(())
325    }
326
327    #[must_use]
328    pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParsedAstModule> {
329        self.already_parsed_modules.get(&path.to_vec())
330    }
331
332    pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParsedAstModule> {
333        self.already_parsed_modules.get_mut(&path.to_vec())
334    }
335
336    pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
337        let mut order = Vec::new();
338        let mut visited = HashSet::new();
339        let mut temp_visited = HashSet::new();
340
341        fn visit(
342            graph: &DependencyParser,
343            path: &[String],
344            visited: &mut HashSet<Vec<String>>,
345            temp_visited: &mut HashSet<Vec<String>>,
346            order: &mut Vec<Vec<String>>,
347            depth: usize,
348        ) -> Result<(), DependencyError> {
349            if temp_visited.contains(path) {
350                error!(?path, depth, "already visited this path");
351                return Err(DependencyError::CircularDependency(Vec::from(path)));
352            }
353
354            if visited.contains(path) {
355                return Ok(());
356            }
357
358            //trace!(?path, depth, "visit path");
359            temp_visited.insert(Vec::from(path));
360
361            if let Some(module) = graph.import_scanned_modules.get(&path.to_vec()) {
362                for import in &module.uses {
363                    visit(graph, import, visited, temp_visited, order, depth + 1)?;
364                }
365                for import in &module.imports {
366                    visit(graph, import, visited, temp_visited, order, depth + 1)?;
367                }
368            }
369
370            order.push(Vec::from(path));
371            visited.insert(Vec::from(path));
372
373            temp_visited.remove(path);
374
375            Ok(())
376        }
377
378        for path in self.import_scanned_modules.keys() {
379            if !visited.contains(path) {
380                visit(self, path, &mut visited, &mut temp_visited, &mut order, 0)?;
381            }
382        }
383
384        Ok(order)
385    }
386}
387
388#[derive(Debug)]
389pub enum DepLoaderError {
390    DependencyError(DependencyError),
391}
392
393impl From<DependencyError> for DepLoaderError {
394    fn from(e: DependencyError) -> Self {
395        Self::DependencyError(e)
396    }
397}
398
399/// # Errors
400///
401fn os_home_relative_path(project_name: &str) -> Option<PathBuf> {
402    home_dir().map(|home_path| home_path.join(format!(".{project_name}")))
403}
404
405fn current_directory_relative_path(project_name: &str) -> PathBuf {
406    Path::new(".").join(format!(".{project_name}"))
407}
408
409#[must_use]
410pub fn path_from_environment_variable() -> Option<PathBuf> {
411    env::var("SWAMP_HOME")
412        .map(|string_value| Path::new(&string_value).to_path_buf())
413        .ok()
414}
415
416#[must_use]
417pub fn swamp_home(run_mode: &RunMode) -> Option<PathBuf> {
418    // First try environment variable
419    match run_mode {
420        RunMode::Development => {
421            path_from_environment_variable().or_else(|| os_home_relative_path("swamp"))
422        }
423        RunMode::Deployed => Some(Path::new(".").to_path_buf()),
424    }
425}
426
427// Verifies the structure of swamp_home to make sure if we can use it
428#[must_use]
429pub fn verify_if_swamp_home_seems_correct(swamp_home: &Path) -> bool {
430    let mut swamp_packages_dir = swamp_home.to_path_buf();
431
432    swamp_packages_dir.push("packages");
433
434    swamp_packages_dir.exists() && swamp_packages_dir.is_dir()
435}
436
437pub enum RunMode {
438    Development,
439    Deployed,
440}
441
442/// # Errors
443///
444#[must_use]
445pub fn swamp_registry_path(run_mode: &RunMode) -> Option<PathBuf> {
446    let swamp_home = swamp_home(run_mode)?;
447
448    if verify_if_swamp_home_seems_correct(&swamp_home) {
449        let mut packages_path = swamp_home;
450        packages_path.push("packages");
451
452        Some(packages_path)
453    } else {
454        None
455    }
456}
457
458pub fn parse_local_modules_and_get_order(
459    module_path: &[String],
460    dependency_parser: &mut DependencyParser,
461    source_map: &mut SourceMap,
462) -> Result<Vec<Vec<String>>, DepLoaderError> {
463    dependency_parser.parse_local_modules(module_path, source_map)?;
464
465    let module_paths_in_order = dependency_parser.get_analysis_order()?;
466
467    Ok(module_paths_in_order)
468}