1use std::collections::HashMap;
6
7use super::lockfile::{integrity_of, LockEntry, Lockfile};
8use super::{parse_version, version_satisfies, FileResolver, ImportSpec, ModuleId};
9use crate::error::Diagnostic;
10use crate::eval::value::Value;
11use crate::eval::Interpreter;
12use crate::lexer::Lexer;
13use crate::parser::ast::ImportSource;
14use crate::parser::Parser;
15use crate::source::SourceCache;
16use crate::span::Span;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19enum LoadState {
20 Loading,
21 Loaded,
22}
23
24pub struct Loader<'a, 'r> {
25 pub cache: &'a SourceCache,
26 pub resolver: &'r dyn FileResolver,
27 pub lock: Lockfile,
28 pub frozen: bool,
30 pub hermetic: bool,
33 pub diags: Vec<Diagnostic>,
36 state: HashMap<ModuleId, LoadState>,
37 values: HashMap<ModuleId, Value<'a>>,
38 chain: Vec<ModuleId>,
39}
40
41fn rt(code: &'static str, msg: impl Into<String>, span: Span) -> Diagnostic {
42 Diagnostic::error(code, msg, span, "while loading modules")
43}
44
45impl<'a, 'r> Loader<'a, 'r> {
46 pub fn new(cache: &'a SourceCache, resolver: &'r dyn FileResolver) -> Self {
47 Loader {
48 cache,
49 resolver,
50 lock: Lockfile::default(),
51 frozen: false,
52 hermetic: false,
53 diags: Vec::new(),
54 state: HashMap::new(),
55 values: HashMap::new(),
56 chain: Vec::new(),
57 }
58 }
59
60 pub fn eval_entry(
62 &mut self,
63 interp: &mut Interpreter<'a>,
64 spec: &ImportSpec<'_>,
65 ) -> Result<Value<'a>, Diagnostic> {
66 let dummy = Span::new(0, 0, 0);
67 let id = self
68 .resolver
69 .resolve(spec, None)
70 .map_err(|e| rt("E0404", e, dummy))?;
71 self.eval_module_rec(interp, id, dummy, true)
72 }
73
74 fn eval_module_rec(
75 &mut self,
76 interp: &mut Interpreter<'a>,
77 id: ModuleId,
78 err_span: Span,
79 is_root: bool,
80 ) -> Result<Value<'a>, Diagnostic> {
81 match self.state.get(&id) {
82 Some(LoadState::Loaded) => return Ok(self.values[&id].clone()),
83 Some(LoadState::Loading) => {
84 let mut chain: Vec<String> = self.chain.iter().map(ModuleId::to_string).collect();
85 chain.push(id.to_string());
86 return Err(rt(
87 "E0401",
88 format!("cyclic import: {}", chain.join(" -> ")),
89 err_span,
90 ));
91 }
92 None => {}
93 }
94 self.state.insert(id.clone(), LoadState::Loading);
95 self.chain.push(id.clone());
96
97 let result = self.eval_module_inner(interp, &id, err_span, is_root);
98
99 self.chain.pop();
100 match &result {
101 Ok(v) => {
102 self.state.insert(id.clone(), LoadState::Loaded);
103 self.values.insert(id, v.clone());
104 }
105 Err(_) => {
106 self.state.remove(&id);
107 }
108 }
109 result
110 }
111
112 fn eval_module_inner(
113 &mut self,
114 interp: &mut Interpreter<'a>,
115 id: &ModuleId,
116 err_span: Span,
117 is_root: bool,
118 ) -> Result<Value<'a>, Diagnostic> {
119 let text = self
120 .resolver
121 .load(id)
122 .map_err(|e| rt("E0404", format!("cannot load module '{id}': {e}"), err_span))?;
123 self.verify_lock(id, &text, err_span)?;
124
125 let (source_id, src) = self.cache.add(id.to_string(), text);
126 let toks = Lexer::new(src, source_id).tokenize()?;
127 let module = match Parser::new(toks).parse_module() {
128 Ok(m) => m,
129 Err(mut ds) => {
130 let first = ds.remove(0);
131 self.diags.extend(ds);
132 return Err(first);
133 }
134 };
135
136 let mut analysis = crate::analysis::analyze_with(&module, is_root, self.hermetic);
140 if let Some(pos) = analysis
141 .iter()
142 .position(|d| d.severity == crate::error::Severity::Error)
143 {
144 let first = analysis.remove(pos);
145 self.diags.extend(analysis);
146 return Err(first);
147 }
148 self.diags.extend(analysis);
149
150 let mut resolved: Vec<(&str, Value<'a>)> = Vec::with_capacity(module.imports.len());
153 for imp in &module.imports {
154 let spec = match &imp.source {
155 ImportSource::File(p) => ImportSpec::File(p),
156 ImportSource::Registry { path, version } => ImportSpec::Registry { path, version },
157 };
158 let child_id = self.resolve_with_lock(&spec, Some(id), imp.span)?;
159 let value = self.eval_module_rec(interp, child_id, imp.span, false)?;
160 resolved.push((imp.alias, value));
161 }
162 for (alias, value) in resolved {
163 interp.provide_module(alias, value);
164 }
165
166 let saved_root = interp.current_root;
168 interp.current_root = is_root;
169 let result = interp.eval_module(&module);
170 interp.current_root = saved_root;
171 result
172 }
173
174 fn resolve_with_lock(
177 &mut self,
178 spec: &ImportSpec<'_>,
179 importer: Option<&ModuleId>,
180 span: Span,
181 ) -> Result<ModuleId, Diagnostic> {
182 if let ImportSpec::Registry { path, version } = spec {
183 let request = parse_version(version)
184 .ok_or_else(|| rt("E0404", format!("malformed version '{version}'"), span))?;
185 if let Some(entry) = self.lock.entries.get(*path) {
186 if parse_version(&entry.version)
187 .is_some_and(|locked| version_satisfies(&request, &locked))
188 {
189 return Ok(ModuleId::Registry {
190 path: path.to_string(),
191 version: entry.version.clone(),
192 });
193 }
194 }
195 if self.frozen {
196 return Err(rt(
197 "E0403",
198 format!("--frozen: aura.lock has no entry satisfying '{path}@{version}'"),
199 span,
200 ));
201 }
202 }
203 self.resolver
204 .resolve(spec, importer)
205 .map_err(|e| rt("E0404", e, span))
206 }
207
208 fn verify_lock(&mut self, id: &ModuleId, text: &str, span: Span) -> Result<(), Diagnostic> {
210 let ModuleId::Registry { path, version } = id else {
211 return Ok(());
212 };
213 let hash = integrity_of(text);
214 match self.lock.entries.get(path) {
215 Some(entry) if entry.version == *version => {
216 let expected = crate::vfs::lockfile::recompute_like(&entry.integrity, text);
221 if entry.integrity != expected {
222 return Err(rt(
223 "E0402",
224 format!(
225 "integrity mismatch for '{path}@v{version}': lock has {}, got {expected}",
226 entry.integrity
227 ),
228 span,
229 ));
230 }
231 if expected != hash && !self.frozen {
235 self.lock.entries.insert(
236 path.clone(),
237 LockEntry {
238 version: version.clone(),
239 integrity: hash,
240 },
241 );
242 self.lock.dirty = true;
243 }
244 }
245 _ => {
246 if self.frozen {
247 return Err(rt(
248 "E0403",
249 format!("--frozen: '{path}@v{version}' is not in aura.lock"),
250 span,
251 ));
252 }
253 self.lock.entries.insert(
254 path.clone(),
255 LockEntry {
256 version: version.clone(),
257 integrity: hash,
258 },
259 );
260 self.lock.dirty = true;
261 }
262 }
263 Ok(())
264 }
265}