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