Skip to main content

cl_parser/
inliner.rs

1//! The [ModuleInliner] reads files described in the module structure of the
2
3use crate::{ParseError, Parser};
4use cl_ast::{
5    fold::{Fold, Foldable},
6    types::{Literal, Path as AstPath, Symbol},
7    *,
8};
9use cl_lexer::Lexer;
10use cl_structures::span::Span;
11use std::{
12    convert::Infallible,
13    path::{Path, PathBuf},
14};
15
16pub type IoErrs = Vec<(PathBuf, std::io::Error)>;
17pub type ParseErrs = Vec<(PathBuf, ParseError)>;
18
19pub struct ModuleInliner {
20    path: PathBuf,
21    io_errs: IoErrs,
22    parse_errs: ParseErrs,
23}
24
25impl ModuleInliner {
26    /// Creates a new [ModuleInliner]
27    pub fn new(root: impl AsRef<Path>) -> Self {
28        Self {
29            path: root.as_ref().to_path_buf(),
30            io_errs: Default::default(),
31            parse_errs: Default::default(),
32        }
33    }
34
35    pub fn fork(&self, path: impl AsRef<Path>) -> Self {
36        Self::new(path.as_ref())
37    }
38
39    pub fn join(&mut self, other: ModuleInliner) -> &mut Self {
40        let ModuleInliner { path: _, io_errs, parse_errs } = other;
41        self.io_errs.extend(io_errs);
42        self.parse_errs.extend(parse_errs);
43        self
44    }
45
46    pub fn main_path(&self) -> PathBuf {
47        self.path.with_extension("cl")
48    }
49
50    pub fn fallback_path(&self) -> Option<PathBuf> {
51        let basename = self.path.file_name()?;
52        Some(
53            self.path
54                .parent()?
55                .parent()?
56                .join(basename)
57                .with_added_extension("cl"),
58        )
59    }
60
61    /// Returns true when the [ModuleInliner] has errors to report
62    pub fn has_errors(&self) -> bool {
63        !(self.io_errs.is_empty() && self.parse_errs.is_empty())
64    }
65
66    /// Returns the [IO Errors](IoErrs) and [parse Errors](ParseErrs)
67    pub fn into_errs(self) -> Option<(IoErrs, ParseErrs)> {
68        self.has_errors().then_some((self.io_errs, self.parse_errs))
69    }
70
71    /// Traverses an [Expr], attempting to inline all submodules.
72    ///
73    /// This is a simple wrapper around [ModuleInliner::fold_expr()] and
74    /// [ModuleInliner::into_errs()]
75    pub fn inline(mut self, expr: Expr) -> Result<Expr, (Expr, IoErrs, ParseErrs)> {
76        let Ok(file) = self.fold_expr(expr);
77
78        match self.into_errs() {
79            Some((io, parse)) => Err((file, io, parse)),
80            None => Ok(file),
81        }
82    }
83
84    /// Records an [I/O error](std::io::Error) for later
85    fn handle_io_error<T>(&mut self, path: PathBuf, error: std::io::Error) -> Option<T> {
86        self.io_errs.push((path, error));
87        None
88    }
89
90    /// Records a [ParseError] for later
91    fn handle_parse_error<T>(&mut self, path: PathBuf, error: ParseError) -> Option<T> {
92        self.parse_errs.push((path, error));
93        None
94    }
95}
96
97impl Fold<DefaultTypes> for ModuleInliner {
98    type Error = Infallible;
99
100    /// Traverses down the module tree, entering ever nested directories
101    fn fold_bind(&mut self, bind: Bind<DefaultTypes>) -> Result<Bind<DefaultTypes>, Self::Error> {
102        let Bind(BindOp::Mod, ts, pat, exprs) = bind else {
103            return bind.children(self);
104        };
105
106        let name = if let Pat::Name(name) = pat {
107            name
108        } else if let Pat::Value(expr) = &pat
109            && let Expr::Lit(Literal::Str(path)) = &expr.as_ref().0
110            && let Some(Ok(At(out, span))) = self.inline_file_at(path)
111            && let [At(Expr::Omitted, _)] = exprs.as_slice()
112        {
113            let sym = Path::new(path).with_extension("");
114            let sym = sym.file_name().expect("should have filename after load");
115            let sym = sym
116                .to_str()
117                .unwrap_or(path)
118                .replace([',', '.', '-', ' '], "_")
119                .to_lowercase()
120                .as_str()
121                .into();
122            let expr = Expr::Op(Op::Block, vec![out.at(span)]).at(span);
123            return Ok(Bind(BindOp::Mod, ts, Pat::Name(sym), vec![expr]));
124        } else {
125            return Ok(Bind(BindOp::Mod, ts, pat, exprs));
126        };
127
128        self.path.push(name.0); // cd ./name
129        let out = if let [At(Expr::Omitted, _)] = exprs.as_slice()
130            && let Some(Ok(At(out, span))) = self.inline_file()
131        {
132            let expr = Expr::Op(Op::Block, vec![out.at(span)]).at(span);
133            Ok(Bind(BindOp::Mod, ts, pat, vec![expr]))
134        } else {
135            Bind(BindOp::Mod, ts, pat, exprs).children(self)
136        };
137
138        self.path.pop(); // cd ..
139        out
140    }
141
142    fn fold_annotation(&mut self, span: Span) -> Result<Span, Self::Error> {
143        Ok(span)
144    }
145
146    fn fold_macro_id(&mut self, name: Symbol) -> Result<Symbol, Self::Error> {
147        Ok(name)
148    }
149
150    fn fold_symbol(&mut self, name: Symbol) -> Result<Symbol, Self::Error> {
151        Ok(name)
152    }
153
154    fn fold_path(&mut self, path: AstPath) -> Result<AstPath, Self::Error> {
155        Ok(path)
156    }
157
158    fn fold_literal(&mut self, lit: Literal) -> Result<Literal, Self::Error> {
159        Ok(lit)
160    }
161}
162
163impl ModuleInliner {
164    fn inline_file(&mut self) -> Option<Result<At<Expr>, Infallible>> {
165        // cd path/mod.cl
166        let path = self.main_path();
167        let used_path = if path.exists() {
168            path
169        } else {
170            self.fallback_path().unwrap_or(path)
171        };
172
173        let file = match std::fs::read_to_string(&used_path) {
174            Err(e) => return self.handle_io_error(used_path, e),
175            Ok(file) => file,
176        };
177
178        let path = used_path.display().to_string().as_str().into();
179
180        match Parser::new(Lexer::new(path, &file)).parse(0) {
181            Err(e) => self.handle_parse_error(used_path, e),
182            Ok(file) => {
183                // The newly loaded module may need further inlining
184                Some(self.fold_at_expr(file))
185            }
186        }
187    }
188
189    /// Inlines a file at the given `path`,
190    fn inline_file_at(&mut self, path: &str) -> Option<Result<At<Expr>, Infallible>> {
191        let mut full_path = self.path.clone();
192        full_path.push(path);
193        let file = match std::fs::read_to_string(&full_path) {
194            Err(error) => return self.handle_io_error(full_path, error),
195            Ok(file) => file,
196        };
197
198        match Parser::new(Lexer::new(path.into(), &file)).parse_entire(0) {
199            Err(e) => self.handle_parse_error(full_path, e),
200            Ok(file) => {
201                let full_path = full_path.with_extension("");
202                // The newly loaded module may need further inlining
203                let mut mi = self.fork(full_path);
204                let out = mi.fold_at_expr(file);
205                self.join(mi);
206                Some(out)
207            }
208        }
209    }
210}