cl_parser/
inliner.rs

1//! The [ModuleInliner] reads files described in the module structure of the
2
3use crate::Parser;
4use cl_ast::{ast_visitor::Fold, *};
5use cl_lexer::Lexer;
6use std::path::{Path, PathBuf};
7
8pub type IoErrs = Vec<(PathBuf, std::io::Error)>;
9pub type ParseErrs = Vec<(PathBuf, crate::error::Error)>;
10
11pub struct ModuleInliner {
12    path: PathBuf,
13    io_errs: IoErrs,
14    parse_errs: ParseErrs,
15}
16
17impl ModuleInliner {
18    /// Creates a new [ModuleInliner]
19    pub fn new(root: impl AsRef<Path>) -> Self {
20        Self {
21            path: root.as_ref().to_path_buf(),
22            io_errs: Default::default(),
23            parse_errs: Default::default(),
24        }
25    }
26
27    /// Returns true when the [ModuleInliner] has errors to report
28    pub fn has_errors(&self) -> bool {
29        !(self.io_errs.is_empty() && self.parse_errs.is_empty())
30    }
31
32    /// Returns the [IO Errors](IoErrs) and [parse Errors](ParseErrs)
33    pub fn into_errs(self) -> Option<(IoErrs, ParseErrs)> {
34        self.has_errors().then_some((self.io_errs, self.parse_errs))
35    }
36
37    /// Traverses a [File], attempting to inline all submodules.
38    ///
39    /// This is a simple wrapper around [ModuleInliner::fold_file()] and
40    /// [ModuleInliner::into_errs()]
41    pub fn inline(mut self, file: File) -> Result<File, (File, IoErrs, ParseErrs)> {
42        let file = self.fold_file(file);
43
44        match self.into_errs() {
45            Some((io, parse)) => Err((file, io, parse)),
46            None => Ok(file),
47        }
48    }
49
50    /// Records an [I/O error](std::io::Error) for later
51    fn handle_io_error(&mut self, error: std::io::Error) -> Option<File> {
52        self.io_errs.push((self.path.clone(), error));
53        None
54    }
55
56    /// Records a [parse error](crate::error::Error) for later
57    fn handle_parse_error(&mut self, error: crate::error::Error) -> Option<File> {
58        self.parse_errs.push((self.path.clone(), error));
59        None
60    }
61}
62
63impl Fold for ModuleInliner {
64    /// Traverses down the module tree, entering ever nested directories
65    fn fold_module(&mut self, m: Module) -> Module {
66        let Module { name, file } = m;
67        self.path.push(&*name); // cd ./name
68
69        let file = self.fold_module_kind(file);
70
71        self.path.pop(); // cd ..
72        Module { name, file }
73    }
74}
75
76impl ModuleInliner {
77    /// Attempts to read and parse a file for every module in the tree
78    fn fold_module_kind(&mut self, m: Option<File>) -> Option<File> {
79        use std::borrow::Cow;
80        if let Some(f) = m {
81            return Some(self.fold_file(f));
82        }
83
84        // cd path/mod.cl
85        self.path.set_extension("cl");
86        let mut used_path: Cow<Path> = Cow::Borrowed(&self.path);
87
88        let file = match std::fs::read_to_string(&self.path) {
89            Err(error) => {
90                let Some(basename) = self.path.file_name() else {
91                    return self.handle_io_error(error);
92                };
93                used_path = Cow::Owned(
94                    self.path
95                        .parent()
96                        .and_then(Path::parent)
97                        .map(|path| path.join(basename))
98                        .unwrap_or_default(),
99                );
100
101                match std::fs::read_to_string(&used_path) {
102                    Err(error) => return self.handle_io_error(error),
103                    Ok(file) => file,
104                }
105            }
106            Ok(file) => file,
107        };
108
109        match Parser::new(used_path.display().to_string(), Lexer::new(&file)).parse() {
110            Err(e) => self.handle_parse_error(e),
111            Ok(file) => {
112                self.path.set_extension("");
113                // The newly loaded module may need further inlining
114                Some(self.fold_file(file))
115            }
116        }
117    }
118}