1use crate::ast::*;
5use crate::engine::{Engine, RootSrc};
6use crate::package::Resolver;
7use crate::parse::parse_source;
8use crate::semantics::*;
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12use std::rc::Rc;
13
14pub struct Module {
15 pub path: PathBuf,
16 pub decls: Vec<Decl>,
17 pub env: Rc<Env>,
18 pub exports: Rc<RefCell<HashMap<String, Export>>>,
19}
20
21pub struct LoadResult {
22 pub modules: Vec<Rc<Module>>,
23 pub entry: Option<Rc<Module>>,
24 pub diags: Vec<Diag>,
25}
26
27struct Loader {
28 modules: HashMap<PathBuf, Rc<Module>>,
29 order: Vec<Rc<Module>>,
30 visiting: Vec<PathBuf>,
31 diags: Vec<Diag>,
32 resolver: Option<Resolver>,
33 overrides: HashMap<PathBuf, String>,
34}
35
36impl Loader {
37 fn report(&mut self, code: &str, message: String) {
38 self.diags
39 .push(Diag::error(message, String::new(), Some(code)));
40 }
41 fn resolve_spec(&mut self, spec: &str, from_dir: &Path) -> Option<PathBuf> {
42 if spec.starts_with("./") || spec.starts_with("../") {
43 return Some(normalize(&from_dir.join(spec)));
44 }
45 let Some(resolver) = self.resolver.clone() else {
46 self.report(
47 "E3010",
48 format!("package import \"{spec}\" outside a package (no manifest)"),
49 );
50 return None;
51 };
52 match resolver(spec, from_dir) {
53 Ok(p) => Some(p),
54 Err((code, message)) => {
55 self.report(&code, message);
56 None
57 }
58 }
59 }
60 fn load(&mut self, path: &Path) -> Option<Rc<Module>> {
61 let abs = normalize(&std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf()));
62 if let Some(m) = self.modules.get(&abs) {
63 return Some(m.clone());
64 }
65 if let Some(ci) = self.visiting.iter().position(|p| *p == abs) {
66 let cycle: Vec<String> = self.visiting[ci..]
67 .iter()
68 .chain(std::iter::once(&abs))
69 .map(|p| p.display().to_string())
70 .collect();
71 self.report(
72 "E3007",
73 format!("module import cycle: {}", cycle.join(" -> ")),
74 );
75 return None;
76 }
77 let src = match self.overrides.get(&abs) {
78 Some(s) => s.clone(),
79 None => match std::fs::read_to_string(&abs) {
80 Ok(s) => s,
81 Err(_) => {
82 self.report("E3004", format!("module not found: {}", abs.display()));
83 return None;
84 }
85 },
86 };
87 let parsed = parse_source(&src);
88 if !parsed.errors.is_empty() {
89 self.report(
90 "E2001",
91 format!("{}: {} parse error(s)", abs.display(), parsed.errors.len()),
92 );
93 return None;
94 }
95 let env = Env::new();
96 env.load(&parsed.decls);
97 for n in env.duplicates.borrow().iter() {
98 self.diags.push(Diag::error(
99 format!("duplicate name {n} in {}", abs.display()),
100 String::new(),
101 Some("E3001"),
102 ));
103 }
104 let m = Rc::new(Module {
105 path: abs.clone(),
106 decls: parsed.decls,
107 env,
108 exports: Rc::new(RefCell::new(HashMap::new())),
109 });
110 self.visiting.push(abs.clone());
111 let mut targets: HashMap<String, Rc<Module>> = HashMap::new();
112 let from_dir = abs.parent().map(|p| p.to_path_buf()).unwrap_or_default();
113 for d in &m.decls {
114 let from = match &d.body {
115 DeclBody::Import { from, .. } | DeclBody::ReExport { from, .. } => from.clone(),
116 _ => continue,
117 };
118 if let Some(t) = self.resolve_spec(&from, &from_dir) {
119 if let Some(tm) = self.load(&t) {
120 targets.insert(from, tm);
121 }
122 }
123 }
124 self.visiting.pop();
125 self.modules.insert(abs.clone(), m.clone());
126
127 let taken = |n: &str| {
128 let e = &m.env;
129 e.type_asts.borrow().contains_key(n)
130 || e.consts.borrow().contains_key(n)
131 || e.funcs.borrow().contains_key(n)
132 || e.diags.borrow().contains_key(n)
133 || e.inputs.borrow().contains_key(n)
134 || e.outputs.borrow().iter().any(|(o, _, _)| o == n)
135 || e.imports.borrow().contains_key(n)
136 || e.namespaces.borrow().contains_key(n)
137 };
138 for d in &m.decls {
139 match &d.body {
140 DeclBody::Import { from, names, ns } => {
141 let Some(tm) = targets.get(from) else {
142 continue;
143 };
144 if let Some(ns) = ns {
145 if taken(ns) {
146 self.report(
147 "E3006",
148 format!(
149 "import {ns} collides with an existing binding in {}",
150 abs.display()
151 ),
152 );
153 continue;
154 }
155 m.env
156 .namespaces
157 .borrow_mut()
158 .insert(ns.clone(), (tm.env.clone(), tm.exports.clone()));
159 continue;
160 }
161 for it in names.iter().flatten() {
162 let local = it.alias.clone().unwrap_or_else(|| it.name.clone());
163 let ex = tm.exports.borrow().get(&it.name).cloned();
164 let Some(ex) = ex else {
165 self.report(
166 "E3005",
167 format!("{} does not export {}", tm.path.display(), it.name),
168 );
169 continue;
170 };
171 if taken(&local) {
172 self.report(
173 "E3006",
174 format!(
175 "import {local} collides with an existing binding in {}",
176 abs.display()
177 ),
178 );
179 continue;
180 }
181 m.env.imports.borrow_mut().insert(local, ex);
182 }
183 }
184 DeclBody::ReExport { from, names } => {
185 let Some(tm) = targets.get(from) else {
186 continue;
187 };
188 for it in names {
189 let ex = tm.exports.borrow().get(&it.name).cloned();
190 match ex {
191 Some(ex) => {
192 m.exports.borrow_mut().insert(
193 it.alias.clone().unwrap_or_else(|| it.name.clone()),
194 ex,
195 );
196 }
197 None => self.report(
198 "E3005",
199 format!("{} does not export {}", tm.path.display(), it.name),
200 ),
201 }
202 }
203 }
204 _ => {}
205 }
206 }
207 for d in &m.decls {
208 if !d.exported {
209 continue;
210 }
211 if matches!(
212 d.body,
213 DeclBody::Unit { .. }
214 | DeclBody::Dimension { .. }
215 | DeclBody::Import { .. }
216 | DeclBody::ReExport { .. }
217 ) {
218 continue;
219 }
220 if let Some(n) = d.name() {
221 m.exports.borrow_mut().insert(
222 n.to_string(),
223 Export {
224 env: m.env.clone(),
225 name: n.to_string(),
226 },
227 );
228 }
229 }
230 self.order.push(m.clone());
231 Some(m)
232 }
233}
234
235fn normalize(p: &Path) -> PathBuf {
236 let mut out = PathBuf::new();
237 for c in p.components() {
238 match c {
239 std::path::Component::ParentDir => {
240 out.pop();
241 }
242 std::path::Component::CurDir => {}
243 other => out.push(other),
244 }
245 }
246 out
247}
248
249pub fn load_modules(
252 entry: &Path,
253 resolver: Option<&Resolver>,
254 overrides: Option<&HashMap<PathBuf, String>>,
255) -> LoadResult {
256 let mut ld = Loader {
257 modules: HashMap::new(),
258 order: vec![],
259 visiting: vec![],
260 diags: vec![],
261 resolver: resolver.cloned(),
262 overrides: overrides.cloned().unwrap_or_default(),
263 };
264 let entry_m = ld.load(entry);
265 if let Some(e) = &entry_m {
266 link_universe(&ld.order, e, &mut ld.diags);
267 }
268 LoadResult {
269 modules: ld.order,
270 entry: entry_m,
271 diags: ld.diags,
272 }
273}
274
275fn link_universe(mods: &[Rc<Module>], entry: &Rc<Module>, diags: &mut Vec<Diag>) {
276 let mut owners: HashMap<String, PathBuf> = HashMap::new();
277 for m in mods {
278 for d in &m.decls {
279 if let DeclBody::Output { name, .. } | DeclBody::Input { name, .. } = &d.body {
280 if let Some(prev) = owners.get(name) {
281 if *prev != m.path {
282 diags.push(Diag::error(
283 format!(
284 "root {name} declared in both {} and {}",
285 prev.display(),
286 m.path.display()
287 ),
288 String::new(),
289 Some("E3018"),
290 ));
291 }
292 }
293 owners.insert(name.clone(), m.path.clone());
294 }
295 }
296 }
297 for m in mods {
298 for d in &m.decls {
299 if !d.exported {
300 continue;
301 }
302 match &d.body {
303 DeclBody::Dimension { name, terms } => {
304 for m2 in mods {
305 if Rc::ptr_eq(m2, m) {
306 continue;
307 }
308 let own = m2.decls.iter().any(
309 |x| matches!(&x.body, DeclBody::Dimension { name: n, .. } if n == name),
310 );
311 let has = m2.env.dim_decls.borrow().contains_key(name);
312 if has && !own {
313 continue;
314 }
315 if has {
316 diags.push(Diag::error(
317 format!("dimension {name} redeclared across modules"),
318 String::new(),
319 Some("E3001"),
320 ));
321 } else {
322 m2.env
323 .dim_decls
324 .borrow_mut()
325 .insert(name.clone(), terms.clone());
326 }
327 }
328 }
329 DeclBody::Unit {
330 name,
331 dim,
332 factor,
333 base,
334 } => {
335 for m2 in mods {
336 if Rc::ptr_eq(m2, m) {
337 continue;
338 }
339 let own = m2.decls.iter().any(
340 |x| matches!(&x.body, DeclBody::Unit { name: n, .. } if n == name),
341 );
342 let has = m2.env.unit_decls.borrow().contains_key(name);
343 if has && !own {
344 continue;
345 }
346 if has {
347 diags.push(Diag::error(
348 format!("unit {name} redeclared across modules"),
349 String::new(),
350 Some("E4073"),
351 ));
352 } else {
353 m2.env.unit_decls.borrow_mut().insert(
354 name.clone(),
355 UnitDecl {
356 dim: dim.clone(),
357 factor: factor.clone(),
358 base: base.clone(),
359 },
360 );
361 }
362 }
363 }
364 _ => {}
365 }
366 }
367 }
368 for m in mods {
369 if Rc::ptr_eq(m, entry) {
370 continue;
371 }
372 *m.env.registry.borrow_mut() = entry.env.registry.borrow().clone();
373 *m.env.roots.borrow_mut() = entry.env.roots.borrow().clone();
374 *m.env.diagnostics.borrow_mut() = entry.env.diagnostics.borrow().clone();
375 }
376}
377
378pub struct Bind {
381 pub module: Option<Rc<Module>>,
382 pub input: String,
383 pub raw: Value,
384}
385
386pub fn run_universe(
387 mods: &[Rc<Module>],
388 entry: &Rc<Module>,
389 binds: Vec<Bind>,
390) -> (Rc<Engine>, Vec<Diag>) {
391 let eng = Engine::new(entry.env.clone());
392 for m in mods {
393 eng.install_hooks(&m.env, true);
394 }
395 for b in binds {
399 let m = b.module.clone().unwrap_or_else(|| entry.clone());
400 let decl = m.env.inputs.borrow().get(&b.input).cloned();
401 let Some((ty_ast, _)) = decl else { continue };
402 let sc = Scope::new(&b.input, Some(m.env.clone()));
403 match m.env.resolve(&ty_ast, None) {
404 Ok(rt) => eng.bind_root(&b.input, RootSrc::Doc(b.raw), &rt, &sc),
405 Err(e) => entry.env.report(Diag::error(e, b.input.clone(), None)),
406 }
407 }
408 for m in mods {
409 let outs = m.env.outputs.borrow().clone();
410 for (name, ty_ast, expr) in outs {
411 let sc = Scope::new(&name, Some(m.env.clone()));
412 match m.env.resolve(&ty_ast, None) {
413 Ok(rt) => eng.bind_root(&name, RootSrc::Expr(&expr), &rt, &sc),
414 Err(e) => entry.env.report(Diag::error(e, name.clone(), None)),
415 }
416 }
417 }
418 eng.drive(&entry.env);
419 let diags = sort_diags(entry.env.diagnostics_vec());
421 entry.env.diag_set(diags.clone());
422 (eng, diags)
423}