swamp_script_dep_loader/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/script
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 std::collections::HashSet;
9use std::io::ErrorKind;
10use std::path::{Path, PathBuf};
11use std::time::Instant;
12use std::{env, io};
13use swamp_script_ast::Function;
14use swamp_script_ast::prelude::*;
15use swamp_script_parser::{AstParser, SpecificError};
16use swamp_script_source_map::{FileId, SourceMap};
17use time_dilation::ScopedTimer;
18use tracing::debug;
19
20pub struct ParseRoot;
21
22#[derive(Debug)]
23pub enum ParseRootError {
24    ParserError(ParserError),
25}
26
27#[derive(Debug)]
28pub struct ParsedAstModule {
29    pub ast_module: swamp_script_ast::Module,
30    pub file_id: FileId,
31}
32
33impl ParsedAstModule {
34    // TODO: HACK: declare_external_function() should be removed
35    pub fn declare_external_function(
36        &mut self,
37        parameters: Vec<Parameter>,
38        return_type: Option<Type>,
39    ) {
40        let fake_identifier = Node::default();
41
42        let signature = FunctionDeclaration {
43            name: fake_identifier.clone(),
44            params: parameters,
45            self_parameter: None,
46            return_type,
47            generic_variables: vec![],
48        };
49        let external_signature = Function::External(signature);
50
51        self.ast_module.definitions.insert(
52            0, // add it first
53            Definition::FunctionDef(external_signature),
54        );
55    }
56}
57
58#[derive(Debug)]
59pub struct RelativePath(pub String);
60
61#[derive(Debug)]
62pub struct ParserError {
63    pub node: Node,
64    pub specific: SpecificError,
65    pub file_id: FileId,
66}
67
68impl ParseRoot {
69    pub fn new() -> Self {
70        Self {}
71    }
72
73    pub fn parse(
74        &self,
75        contents: String,
76        file_id: FileId,
77    ) -> Result<ParsedAstModule, ParseRootError> {
78        let before = Instant::now();
79        let ast_program = AstParser.parse_module(&contents).map_err(|err| {
80            let new_err = ParserError {
81                node: Node { span: err.span },
82                specific: err.specific,
83                file_id,
84            };
85            ParseRootError::ParserError(new_err)
86        })?;
87        let after = Instant::now();
88
89        Ok(ParsedAstModule {
90            ast_module: ast_program,
91            file_id,
92        })
93    }
94}
95
96#[derive(Clone)]
97#[allow(unused)]
98pub struct ModuleInfo {
99    path: Vec<String>,
100    imports: Vec<Vec<String>>,
101    uses: Vec<Vec<String>>,
102    parsed: bool,
103    analyzed: bool,
104}
105
106pub struct DependencyParser {
107    pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
108    already_parsed_modules: SeqMap<Vec<String>, ParsedAstModule>,
109    pub already_resolved_modules: HashSet<Vec<String>>,
110}
111
112impl Default for DependencyParser {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl DependencyParser {
119    pub fn new() -> Self {
120        Self {
121            import_scanned_modules: SeqMap::new(),
122            already_parsed_modules: SeqMap::new(),
123            already_resolved_modules: HashSet::new(),
124        }
125    }
126
127    pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
128        self.already_resolved_modules.insert(module_path);
129    }
130
131    pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParsedAstModule) {
132        debug!(
133            "Adding ast module parsed outside of graph resolver {:?}",
134            module_path
135        );
136        self.already_parsed_modules
137            .insert(module_path, parsed_module)
138            .expect("can not add parsed module")
139    }
140}
141
142#[derive(Debug)]
143pub enum DependencyError {
144    CircularDependency(Vec<String>),
145    ParseRootError(ParseRootError),
146    ReadFileError(io::Error),
147}
148
149impl From<ParseRootError> for DependencyError {
150    fn from(err: ParseRootError) -> Self {
151        Self::ParseRootError(err)
152    }
153}
154
155pub const LOCAL_ROOT_PACKAGE_PATH: &str = "crate";
156
157pub fn get_all_local_paths(
158    source_map: &SourceMap,
159    parsed_module: &ParsedAstModule,
160) -> (Vec<Vec<String>>, Vec<Vec<String>>) {
161    let mut imports = vec![];
162    let mut uses = vec![];
163
164    for def in parsed_module.ast_module.definitions() {
165        match def {
166            Definition::Mod(import) => {
167                let mut sections = Vec::new();
168                sections.push(LOCAL_ROOT_PACKAGE_PATH.to_string());
169                for section_node in &import.module_path.0 {
170                    let import_path = source_map
171                        .get_span_source(
172                            parsed_module.file_id,
173                            section_node.span.offset as usize,
174                            section_node.span.length.into(),
175                        )
176                        .to_string();
177                    sections.push(import_path);
178                }
179
180                imports.push(sections);
181            }
182
183            Definition::Use(import) => {
184                let mut sections = Vec::new();
185                for section_node in &import.module_path.0 {
186                    let import_path = source_map
187                        .get_span_source(
188                            parsed_module.file_id,
189                            section_node.span.offset as usize,
190                            section_node.span.length.into(),
191                        )
192                        .to_string();
193                    sections.push(import_path);
194                }
195                uses.push(sections);
196            }
197            _ => continue,
198        }
199    }
200
201    (imports, uses)
202}
203
204pub fn module_path_to_relative_swamp_file(module_path_vec: &[String]) -> PathBuf {
205    let mut path_buf = PathBuf::new();
206
207    let orig_len = module_path_vec.len();
208
209    let converted_path = if module_path_vec[0] == "crate" {
210        &module_path_vec[1..]
211    } else {
212        module_path_vec
213    };
214
215    path_buf.push(converted_path.join("/"));
216    if orig_len == 1 {
217        path_buf.push("lib"); // lib is default if the path only contains the package root
218    }
219
220    path_buf.set_extension("swamp");
221
222    path_buf
223}
224
225pub fn module_path_to_relative_swamp_file_string(module_path_vec: &[String]) -> String {
226    module_path_to_relative_swamp_file(module_path_vec)
227        .to_str()
228        .unwrap()
229        .into()
230}
231
232pub fn mount_name_from_path(path: &[String]) -> &str {
233    if path[0] == "crate" {
234        "crate"
235    } else {
236        "registry"
237    }
238}
239
240/// Parses just a single module. Any `mod` keywords in this file will be ignored. So this
241/// is mainly for internal use.
242pub fn parse_single_module(
243    source_map: &mut SourceMap,
244    module_path: &[String],
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    let (file_id, script) = source_map
252        .read_file_relative(
253            mount_name,
254            &module_path_to_relative_swamp_file_string(module_path),
255        )
256        .map_err(DependencyError::ReadFileError)?;
257
258    let parse_module = ParseRoot.parse(script, 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"] {
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("TODO: panic message");
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("TODO: panic message");
319
320            to_parse.extend(filtered_imports.clone());
321
322            to_parse.extend(filtered_uses.clone());
323        }
324        Ok(())
325    }
326
327    pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParsedAstModule> {
328        self.already_parsed_modules.get(&path.to_vec())
329    }
330
331    pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParsedAstModule> {
332        self.already_parsed_modules.get_mut(&path.to_vec())
333    }
334
335    pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
336        let mut order = Vec::new();
337        let mut visited = HashSet::new();
338        let mut temp_visited = HashSet::new();
339
340        fn visit(
341            graph: &DependencyParser,
342            path: &[String],
343            visited: &mut HashSet<Vec<String>>,
344            temp_visited: &mut HashSet<Vec<String>>,
345            order: &mut Vec<Vec<String>>,
346        ) -> Result<(), DependencyError> {
347            if temp_visited.contains(path) {
348                return Err(DependencyError::CircularDependency(Vec::from(path)));
349            }
350
351            if visited.contains(path) {
352                return Ok(());
353            }
354
355            temp_visited.insert(Vec::from(path));
356
357            if let Some(module) = graph.import_scanned_modules.get(&path.to_vec()) {
358                for import in &module.uses {
359                    visit(graph, import, visited, temp_visited, order)?;
360                }
361                for import in &module.imports {
362                    visit(graph, import, visited, temp_visited, order)?;
363                }
364            }
365
366            order.push(Vec::from(path));
367            visited.insert(Vec::from(path));
368
369            temp_visited.remove(path);
370
371            Ok(())
372        }
373
374        for path in self.import_scanned_modules.keys() {
375            if !visited.contains(path) {
376                visit(self, path, &mut visited, &mut temp_visited, &mut order)?;
377            }
378        }
379
380        Ok(order)
381    }
382}
383
384fn get_current_dir() -> Result<PathBuf, std::io::Error> {
385    let path = env::current_dir()?;
386
387    //let cargo_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
388
389    Ok(path)
390}
391
392#[derive(Debug)]
393pub enum DepLoaderError {
394    DependencyError(DependencyError),
395}
396
397impl From<DependencyError> for DepLoaderError {
398    fn from(e: DependencyError) -> Self {
399        Self::DependencyError(e)
400    }
401}
402
403/// # Errors
404///
405pub fn os_home_relative_path(project_name: &str) -> io::Result<PathBuf> {
406    home_dir().map_or_else(
407        || {
408            Err(io::Error::new(
409                io::ErrorKind::Other,
410                "Could not determine home directory",
411            ))
412        },
413        |home_path| {
414            let mut path = home_path;
415            path.push(format!(".{project_name}"));
416            Ok(path)
417        },
418    )
419}
420
421pub fn path_from_environment_variable() -> io::Result<PathBuf> {
422    if let Ok(string_value) = &env::var("SWAMP_HOME") {
423        Ok(Path::new(string_value).to_path_buf())
424    } else {
425        Err(io::Error::new(ErrorKind::InvalidData, "missing SWAMP_HOME"))
426    }
427}
428pub fn swamp_home() -> io::Result<PathBuf> {
429    if let Ok(found_path) = path_from_environment_variable() {
430        Ok(found_path)
431    } else {
432        os_home_relative_path("swamp")
433    }
434}
435
436/// # Errors
437///
438pub fn swamp_registry_path() -> io::Result<PathBuf> {
439    let mut swamp_home = swamp_home()?;
440    swamp_home.push("packages");
441    Ok(swamp_home)
442}
443
444pub fn parse_local_modules_and_get_order(
445    module_path: Vec<String>,
446    dependency_parser: &mut DependencyParser,
447    source_map: &mut SourceMap,
448) -> Result<Vec<Vec<String>>, DepLoaderError> {
449    debug!(current_directory=?get_current_dir().expect("failed to get current directory"), "current directory");
450    dependency_parser.parse_local_modules(&module_path, source_map)?;
451
452    let module_paths_in_order = dependency_parser.get_analysis_order()?;
453
454    Ok(module_paths_in_order)
455}