1use 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 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 pub fn has_errors(&self) -> bool {
63 !(self.io_errs.is_empty() && self.parse_errs.is_empty())
64 }
65
66 pub fn into_errs(self) -> Option<(IoErrs, ParseErrs)> {
68 self.has_errors().then_some((self.io_errs, self.parse_errs))
69 }
70
71 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 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 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 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); 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(); 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 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 Some(self.fold_at_expr(file))
185 }
186 }
187 }
188
189 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 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}