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;
6
7use seq_map::SeqMap;
8use std::collections::HashSet;
9use std::path::PathBuf;
10use std::{env, io};
11use swamp_script_ast::prelude::*;
12use swamp_script_ast::Function;
13use swamp_script_parser::{AstParser, ParseError};
14use swamp_script_source_map::{FileId, SourceMap};
15use tracing::{debug, trace};
16
17pub struct ParseRoot {
18    pub base_path: PathBuf,
19}
20
21#[derive(Debug)]
22pub enum ParseRootError {
23    IoError(std::io::Error),
24    ParseError(ParseError),
25}
26
27impl From<std::io::Error> for ParseRootError {
28    fn from(err: std::io::Error) -> Self {
29        Self::IoError(err)
30    }
31}
32
33impl From<ParseError> for ParseRootError {
34    fn from(value: ParseError) -> Self {
35        Self::ParseError(value)
36    }
37}
38
39#[derive(Debug)]
40pub struct ParseModule {
41    pub ast_module: swamp_script_ast::Module,
42    pub file_id: FileId,
43}
44
45impl ParseModule {
46    // TODO: HACK: declare_external_function() should be removed
47    pub fn declare_external_function(
48        &mut self,
49        parameters: Vec<Parameter>,
50        return_type: Option<Type>,
51    ) {
52        let fake_identifier = Node::default();
53
54        let signature = FunctionDeclaration {
55            name: fake_identifier.clone(),
56            params: parameters,
57            self_parameter: None,
58            return_type,
59        };
60        let external_signature = Function::External(signature);
61
62        self.ast_module.definitions.insert(
63            0, // add it first
64            Definition::FunctionDef(external_signature),
65        );
66    }
67}
68
69#[derive(Debug)]
70pub struct RelativePath(pub String);
71
72impl ParseRoot {
73    pub fn new(base_path: PathBuf) -> Self {
74        Self { base_path }
75    }
76
77    pub fn parse(&self, contents: String, file_id: FileId) -> Result<ParseModule, ParseRootError> {
78        let parser = AstParser {};
79
80        let ast_program = parser.parse_module(&*contents)?;
81
82        Ok(ParseModule {
83            ast_module: ast_program,
84            file_id,
85        })
86    }
87}
88
89#[derive(Clone)]
90#[allow(unused)]
91pub struct ModuleInfo {
92    path: Vec<String>,
93    imports: Vec<Vec<String>>,
94    parsed: bool,
95    analyzed: bool,
96}
97
98pub struct DependencyParser {
99    pub import_scanned_modules: SeqMap<Vec<String>, ModuleInfo>,
100    already_parsed_modules: SeqMap<Vec<String>, ParseModule>,
101    pub already_resolved_modules: HashSet<Vec<String>>,
102}
103
104impl Default for DependencyParser {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl DependencyParser {
111    pub fn new() -> Self {
112        Self {
113            import_scanned_modules: SeqMap::new(),
114            already_parsed_modules: SeqMap::new(),
115            already_resolved_modules: HashSet::new(),
116        }
117    }
118
119    pub fn add_resolved_module(&mut self, module_path: Vec<String>) {
120        self.already_resolved_modules.insert(module_path);
121    }
122
123    pub fn add_ast_module(&mut self, module_path: Vec<String>, parsed_module: ParseModule) {
124        debug!(
125            "Adding ast module parsed outside of graph resolver {:?}",
126            module_path
127        );
128        self.already_parsed_modules
129            .insert(module_path, parsed_module)
130            .expect("can not add parsed module")
131    }
132}
133
134#[derive(Debug)]
135pub enum DependencyError {
136    CircularDependency(Vec<String>),
137    ParseRootError(ParseRootError),
138    IoError(io::Error),
139}
140
141impl From<ParseRootError> for DependencyError {
142    fn from(err: ParseRootError) -> Self {
143        Self::ParseRootError(err)
144    }
145}
146
147impl From<io::Error> for DependencyError {
148    fn from(value: io::Error) -> Self {
149        Self::IoError(value)
150    }
151}
152
153fn get_all_import_paths(parsed_module: &ParseModule) -> Vec<Vec<String>> {
154    let mut imports = vec![];
155
156    for def in parsed_module.ast_module.definitions() {
157        match def {
158            Definition::Use(import) => imports.push(import.assigned_path.clone()),
159            _ => continue,
160        }
161    }
162
163    imports
164}
165
166impl DependencyParser {
167    pub fn parse_all_dependant_modules(
168        &mut self,
169        parse_root: ParseRoot,
170        module_path: &[String],
171        source_map: &mut SourceMap,
172    ) -> Result<(), DependencyError> {
173        let mut to_parse = vec![module_path.to_vec()];
174
175        while let Some(path) = to_parse.pop() {
176            let module_path_vec = &path.clone();
177            if self.import_scanned_modules.contains_key(module_path_vec) {
178                continue;
179            }
180
181            let parsed_module_to_scan =
182                if let Some(parsed_module) = self.already_parsed_modules.get(module_path_vec) {
183                    parsed_module
184                } else {
185                    if self.already_resolved_modules.contains(module_path_vec) {
186                        continue;
187                    } else {
188                        let (file_id, script) =
189                            source_map.read_file_relative(module_path_vec.join("/").as_ref())?;
190                        let parse_module = parse_root.parse(script, file_id)?;
191
192                        self.already_parsed_modules
193                            .insert(path.clone(), parse_module)
194                            .expect("TODO: panic message");
195
196                        self.already_parsed_modules
197                            .get(&path.clone())
198                            .expect("we just inserted it")
199                    }
200                };
201
202            let imports = get_all_import_paths(parsed_module_to_scan);
203
204            let filtered_imports: Vec<Vec<String>> = imports
205                .into_iter()
206                .filter(|import| !self.already_resolved_modules.contains(import))
207                .collect();
208
209            self.import_scanned_modules
210                .insert(
211                    path.clone(),
212                    ModuleInfo {
213                        path: path.clone(),
214                        imports: filtered_imports.clone(),
215                        parsed: false,
216                        analyzed: false,
217                    },
218                )
219                .expect("TODO: panic message");
220
221            to_parse.extend(filtered_imports.clone());
222        }
223        Ok(())
224    }
225
226    pub fn get_parsed_module(&self, path: &[String]) -> Option<&ParseModule> {
227        self.already_parsed_modules.get(&path.to_vec())
228    }
229
230    pub fn get_parsed_module_mut(&mut self, path: &[String]) -> Option<&mut ParseModule> {
231        self.already_parsed_modules.get_mut(&path.to_vec())
232    }
233
234    pub(crate) fn get_analysis_order(&self) -> Result<Vec<Vec<String>>, DependencyError> {
235        let mut order = Vec::new();
236        let mut visited = HashSet::new();
237        let mut temp_visited = HashSet::new();
238
239        fn visit(
240            graph: &DependencyParser,
241            path: &[String],
242            visited: &mut HashSet<Vec<String>>,
243            temp_visited: &mut HashSet<Vec<String>>,
244            order: &mut Vec<Vec<String>>,
245        ) -> Result<(), DependencyError> {
246            if temp_visited.contains(path) {
247                return Err(DependencyError::CircularDependency(Vec::from(path)));
248            }
249
250            if visited.contains(path) {
251                return Ok(());
252            }
253
254            temp_visited.insert(Vec::from(path));
255
256            if let Some(module) = graph.import_scanned_modules.get(&path.to_vec()) {
257                for import in &module.imports {
258                    visit(graph, import, visited, temp_visited, order)?;
259                }
260            }
261
262            order.push(Vec::from(path));
263            visited.insert(Vec::from(path));
264
265            temp_visited.remove(path);
266
267            Ok(())
268        }
269
270        for path in self.import_scanned_modules.keys() {
271            if !visited.contains(path) {
272                visit(self, path, &mut visited, &mut temp_visited, &mut order)?;
273            }
274        }
275
276        Ok(order)
277    }
278}
279
280fn get_current_dir() -> Result<PathBuf, std::io::Error> {
281    let path = env::current_dir()?;
282
283    //let cargo_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
284
285    Ok(path)
286}
287
288#[derive(Debug)]
289pub enum DepLoaderError {
290    DependencyError(DependencyError),
291}
292
293impl From<DependencyError> for DepLoaderError {
294    fn from(e: DependencyError) -> Self {
295        Self::DependencyError(e)
296    }
297}
298
299pub fn parse_dependant_modules_and_resolve(
300    base_path: PathBuf,
301    module_path: Vec<String>,
302    dependency_parser: &mut DependencyParser,
303    source_map: &mut SourceMap,
304) -> Result<Vec<Vec<String>>, DepLoaderError> {
305    debug!(current_directory=?get_current_dir().expect("failed to get current directory"), "current directory");
306    let parse_root = ParseRoot::new(base_path);
307
308    dependency_parser.parse_all_dependant_modules(parse_root, &module_path, source_map)?;
309
310    let module_paths_in_order = dependency_parser.get_analysis_order()?;
311
312    Ok(module_paths_in_order)
313}
314
315pub fn create_parsed_modules(
316    script: &str,
317    source_map: &mut SourceMap,
318    root_path: PathBuf,
319) -> Result<DependencyParser, ParseError> {
320    let parser = AstParser {};
321
322    let file_id = source_map.add_manual_no_id(&*root_path, script);
323    let ast_module_result = parser.parse_module(script);
324    if let Err(some) = ast_module_result {
325        return Err(some);
326    }
327    let ast_module = ast_module_result.unwrap();
328    trace!("ast_module:\n{:?}", ast_module);
329
330    let parse_module = ParseModule {
331        ast_module,
332        file_id,
333    };
334
335    let mut graph = DependencyParser::new();
336    let root = vec!["test".to_string()];
337    graph.add_ast_module(root, parse_module);
338
339    debug!("root path is {root_path:?}");
340
341    Ok(graph)
342}