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