1pub 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"); }
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
218pub 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 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
399fn 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 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#[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#[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}