1use microcad_core::hash::HashSet;
5use microcad_lang_base::{
6 Diag, DiagHandler, DiagResult, Diagnostic, FormatTree, GetSourceLocInfoByHash, HashId, Output,
7 PushDiag, SourceLocInfo, SrcReferrer, TreeDisplay, TreeState,
8};
9
10use crate::{
11 builtin::*,
12 eval::*,
13 lower::{SingleIdentifier, ir},
14 model::*,
15 symbol::SymbolDef,
16};
17
18pub struct EvalContext {
22 pub root: Symbol,
24 sources: Sources,
26 pub(super) stack: Stack,
28 output: Box<dyn Output>,
30 exporters: ExporterRegistry,
32 importers: ImporterRegistry,
34 pub diag: DiagHandler,
36}
37
38impl EvalContext {
39 pub fn new(
41 resolve_context: ResolveContext,
42 output: Box<dyn Output>,
43 exporters: ExporterRegistry,
44 importers: ImporterRegistry,
45 ) -> Self {
46 log::debug!("Creating evaluation context");
47
48 Self {
49 root: resolve_context.root,
50 sources: resolve_context.sources,
51 diag: resolve_context.diag,
52 output,
53 exporters,
54 importers,
55 stack: Stack::default(),
56 }
57 }
58
59 pub(crate) fn current_symbol(&self) -> Option<Symbol> {
61 self.stack.current_symbol()
62 }
63
64 pub fn from_source(
66 root: std::rc::Rc<ir::Source>,
67 builtin: Option<Symbol>,
68 search_paths: Vec<std::path::PathBuf>,
69 output: Box<dyn Output>,
70 exporters: ExporterRegistry,
71 importers: ImporterRegistry,
72 ) -> EvalResult<Self> {
73 Ok(Self::new(
74 ResolveContext::create(root, search_paths, builtin, DiagHandler::default())?,
75 output,
76 exporters,
77 importers,
78 ))
79 }
80
81 pub fn output(&self) -> Option<String> {
83 self.output.output()
84 }
85
86 pub fn print(&mut self, what: String) {
88 self.output.print(what).expect("could not write to output");
89 }
90
91 pub fn eval(&mut self) -> EvalResult<Model> {
93 if self.diag.error_count() > 0 {
94 log::error!("Aborting evaluation because of prior resolve errors!");
95 return Err(EvalError::ResolveFailed.into());
96 }
97 let model: Model = self.sources.root().eval(self)?;
98 log::trace!("Post-evaluation context:\n{self:?}");
99 log::trace!("Evaluated Model:\n{}", FormatTree(&model));
100
101 let unused = self
102 .root
103 .unused_private()
104 .iter()
105 .map(|symbol| {
106 (
107 match self.sources.get_code(&symbol) {
108 Ok(id) => id,
109 Err(_) => symbol.id().to_string(),
110 },
111 symbol.src_ref(),
112 )
113 })
114 .collect::<indexmap::IndexMap<_, _>>();
116
117 unused.into_iter().try_for_each(|(id, src_ref)| {
118 self.warning(&src_ref, EvalError::UnusedGlobalSymbol(id))
119 })?;
120
121 Ok(model)
122 }
123
124 pub(super) fn scope<T>(
126 &mut self,
127 stack_frame: StackFrame,
128 f: impl FnOnce(&mut EvalContext) -> T,
129 ) -> T {
130 self.open(stack_frame);
131 let result = f(self);
132 let mut unused: Vec<_> = if let Some(frame) = &self.stack.current_frame() {
133 if let Some(locals) = frame.locals() {
134 locals
135 .iter()
136 .filter(|(_, symbol)| !symbol.is_used())
137 .filter(|(id, _)| !id.ignore())
138 .filter(|(_, symbol)| !symbol.src_ref().is_none())
139 .map(|(id, _)| id.clone())
140 .collect()
141 } else {
142 vec![]
143 }
144 } else {
145 vec![]
146 };
147 unused.sort();
148
149 unused
150 .iter()
151 .try_for_each(|id| self.warning(id, EvalError::UnusedLocal(id.clone())))
152 .expect("diag error");
153
154 self.close();
155 result
156 }
157
158 pub fn exporters(&self) -> &ExporterRegistry {
160 &self.exporters
161 }
162
163 pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
165 self.sources.search_paths()
166 }
167
168 pub(super) fn get_property(&self, id: &Identifier) -> EvalResult<Value> {
170 match self.get_model() {
171 Ok(model) => {
172 if let Some(value) = model.get_property(id) {
173 Ok(value.clone())
174 } else {
175 Err(EvalError::PropertyNotFound(id.clone()).into())
176 }
177 }
178 Err(err) => Err(err),
179 }
180 }
181
182 pub(super) fn init_property(&self, id: Identifier, value: Value) -> EvalResult<()> {
186 match self.get_model() {
187 Ok(model) => {
188 if let Some(previous_value) = model.borrow_mut().set_property(id.clone(), value) {
189 if !previous_value.is_invalid() {
190 return Err(EvalError::ValueAlreadyDefined {
191 location: id.src_ref(),
192 name: id.clone(),
193 value: previous_value.to_string(),
194 previous_location: id.src_ref(),
195 }
196 .into());
197 }
198 }
199 Ok(())
200 }
201 Err(err) => Err(err),
202 }
203 }
204
205 pub(super) fn is_init(&mut self) -> bool {
207 matches!(self.stack.current_frame(), Some(StackFrame::Init(_)))
208 }
209
210 fn lookup_property(&self, name: &ir::QualifiedName) -> EvalResult<Symbol> {
212 log::trace!(
213 "{lookup} for property {name:?}",
214 lookup = microcad_lang_base::mark!(LOOKUP)
215 );
216
217 if self.stack.current_call_name().is_some() {
218 if let Some(id) = name.single_identifier() {
219 let value = self.get_property(id)?;
220 log::trace!(
221 "{found} property '{name:?}'",
222 found = microcad_lang_base::mark!(FOUND)
223 );
224 return Ok(Symbol::new(SymbolDef::Value(id.clone(), value), None));
225 }
226 }
227 log::trace!(
228 "{not_found} Property '{name:?}'",
229 not_found = microcad_lang_base::mark!(NOT_FOUND)
230 );
231 Err(EvalError::NoPropertyId(name.clone()).into())
232 }
233
234 fn lookup_workbench(
235 &self,
236 name: &ir::QualifiedName,
237 target: LookupTarget,
238 ) -> ResolveResult<Symbol> {
239 if let Some(workbench) = &self.stack.current_call_name() {
240 log::trace!(
241 "{lookup} for symbol '{name:?}' in current workbench '{workbench:?}'",
242 lookup = microcad_lang_base::mark!(LOOKUP)
243 );
244 match self.root.lookup_within_name(name, workbench, target) {
245 Ok(symbol) => {
246 log::trace!(
247 "{found} symbol in current module: {symbol:?}",
248 found = microcad_lang_base::mark!(FOUND),
249 );
250 Ok(symbol)
251 }
252 Err(err) => {
253 log::trace!(
254 "{not_found} symbol '{name:?}': {err}",
255 not_found = microcad_lang_base::mark!(NOT_FOUND)
256 );
257 Err(err)
258 }
259 }
260 } else {
261 log::trace!(
262 "{not_found} No current workbench",
263 not_found = microcad_lang_base::mark!(NOT_FOUND)
264 );
265 Err(ResolveError::SymbolNotFound(name.clone()))
266 }
267 }
268
269 fn lookup_within(
270 &self,
271 name: &ir::QualifiedName,
272 target: LookupTarget,
273 ) -> ResolveResult<Symbol> {
274 self.root.lookup_within(
275 name,
276 &self.root.search(&self.stack.current_module_name(), false)?,
277 target,
278 )
279 }
280}
281
282impl Locals for EvalContext {
283 fn set_local_value(&mut self, id: Identifier, value: Value) -> EvalResult<()> {
284 self.stack.set_local_value(id, value)
285 }
286
287 fn get_local_value(&self, id: &Identifier) -> EvalResult<Value> {
288 self.stack.get_local_value(id)
289 }
290
291 fn open(&mut self, frame: StackFrame) {
292 self.stack.open(frame);
293 }
294
295 fn close(&mut self) -> StackFrame {
296 self.stack.close()
297 }
298
299 fn fetch_symbol(&self, id: &Identifier) -> EvalResult<Symbol> {
300 self.stack.fetch_symbol(id)
301 }
302
303 fn get_model(&self) -> EvalResult<Model> {
304 self.stack.get_model()
305 }
306
307 fn current_name(&self) -> ir::QualifiedName {
308 self.stack.current_name()
309 }
310}
311
312impl Lookup<Box<EvalError>> for EvalContext {
313 fn lookup(&self, name: &ir::QualifiedName, target: LookupTarget) -> EvalResult<Symbol> {
314 log::debug!("Lookup {target} '{name:?}' (at line {:?}):", name.src_ref());
315
316 log::trace!("- lookups -------------------------------------------------------");
317 let results = [
319 ("local", { self.stack.lookup(name, target) }),
320 ("global", {
321 self.lookup_within(name, target).map_err(|err| err.into())
322 }),
323 ("property", { self.lookup_property(name) }),
324 ("workbench", {
325 self.lookup_workbench(name, target)
326 .map_err(|err| err.into())
327 }),
328 ]
329 .into_iter();
330
331 log::trace!("- lookup results ------------------------------------------------");
332 let results = results.inspect(|(from, result)| log::trace!("{from}: {:?}", result));
333
334 let (found, mut ambiguities, mut errors) = results.fold(
336 (vec![], vec![], vec![]),
337 |(mut oks, mut ambiguities, mut errors), (origin, result)| {
338 match result {
339 Ok(symbol) => oks.push((origin, symbol)),
340 Err(err) => match *err {
341 EvalError::AmbiguousSymbol(ambiguous, others) => {
342 ambiguities.push((origin, EvalError::AmbiguousSymbol ( ambiguous, others )));
343 }
344 EvalError::SymbolNotFound(_)
346 | EvalError::LocalNotFound(_)
348 | EvalError::NoModelInWorkbench
350 | EvalError::PropertyNotFound(_)
351 | EvalError::NoPropertyId(_)
352 | EvalError::ResolveError(ResolveError::SymbolNotFound(_))
354 | EvalError::ResolveError(ResolveError::SymbolIsPrivate(_))
355 | EvalError::ResolveError(ResolveError::NulHash)
356 | EvalError::ResolveError(ResolveError::WrongTarget) => {},
357 err => errors.push((origin, err)),
358 }
359 }
360 (oks, ambiguities, errors)
361 },
362 );
363
364 if !errors.is_empty() {
366 log::error!("Unexpected errors while lookup symbol '{name:?}':");
367 errors
368 .iter()
369 .for_each(|(origin, err)| log::error!("Lookup ({origin}) error: {err}"));
370
371 return Err(errors.remove(0).1.into());
372 }
373
374 if !ambiguities.is_empty() {
376 log::debug!(
377 "{ambiguous} Symbol '{name:?}':\n{}",
378 ambiguities
379 .iter()
380 .map(|(origin, err)| format!("{origin}: {err}"))
381 .collect::<Vec<_>>()
382 .join("\n"),
383 ambiguous = microcad_lang_base::mark!(AMBIGUOUS)
384 );
385 return Err(ambiguities.remove(0).1.into());
386 }
387
388 let found: Vec<_> = found
390 .iter()
391 .filter(|(_, symbol)| target.matches(symbol))
392 .collect();
393
394 match found.first() {
396 Some((origin, symbol)) => {
397 if found.iter().all(|(_, x)| x == symbol) {
399 log::debug!(
400 "{found} symbol '{name:?}' in {origin}",
401 found = microcad_lang_base::mark!(FOUND!)
402 );
403 symbol.set_used();
404 Ok(symbol.clone())
405 } else {
406 let others: ir::QualifiedNames =
407 found.iter().map(|(_, symbol)| symbol.full_name()).collect();
408 log::debug!(
409 "{ambiguous} symbol '{name:?}' in {others:?}:\n{self:?}",
410 ambiguous = microcad_lang_base::mark!(AMBIGUOUS),
411 );
412 Err(EvalError::AmbiguousSymbol(name.clone(), others).into())
413 }
414 }
415 None => {
416 log::debug!(
417 "{not_found} Symbol '{name:?}'",
418 not_found = microcad_lang_base::mark!(NOT_FOUND!)
419 );
420 Err(EvalError::SymbolNotFound(name.clone()).into())
421 }
422 }
423 }
424
425 fn ambiguity_error(ambiguous: ir::QualifiedName, others: ir::QualifiedNames) -> Box<EvalError> {
426 EvalError::AmbiguousSymbol(ambiguous, others).into()
427 }
428}
429
430impl microcad_lang_base::Diag for EvalContext {
431 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
432 self.diag.pretty_print(f, self)
433 }
434
435 fn warning_count(&self) -> u32 {
436 self.diag.warning_count()
437 }
438
439 fn error_count(&self) -> u32 {
440 self.diag.error_count()
441 }
442
443 fn error_lines(&self) -> HashSet<u32> {
444 self.diag.error_lines()
445 }
446
447 fn warning_lines(&self) -> HashSet<u32> {
448 self.diag.warning_lines()
449 }
450}
451
452impl PushDiag for EvalContext {
453 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
454 self.diag.push_diag(diag)
455 }
456}
457
458impl GetSourceByHash for EvalContext {
459 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<ir::Source>> {
460 self.sources.get_by_hash(hash)
461 }
462}
463
464impl GetSourceLocInfoByHash for EvalContext {
465 fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>> {
466 self.sources.get_source_loc_info_by_hash(hash)
467 }
468}
469
470impl std::fmt::Debug for EvalContext {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 if let Ok(model) = self.get_model() {
473 write!(f, "\nModel:\n")?;
474 model.tree_print(f, TreeState::new_debug(4))?;
475 }
476 writeln!(f, "\nCurrent: {:?}", self.stack.current_name())?;
477 writeln!(f, "\nModule: {:?}", self.stack.current_module_name())?;
478 write!(f, "\nLocals Stack:\n{:?}", self.stack)?;
479 writeln!(f, "\nCall Stack:")?;
480 self.stack.pretty_print_call_trace(f, &self.sources)?;
481
482 writeln!(f, "\nSources:\n")?;
483 write!(f, "{:?}", self.sources)?;
484
485 write!(f, "\nSymbol Table:\n")?;
486 self.root.tree_print(f, TreeState::new_debug(0))?;
487
488 match self.error_count() {
489 0 => write!(f, "No errors")?,
490 1 => write!(f, "1 error")?,
491 _ => write!(f, "{} errors", self.error_count())?,
492 };
493 match self.warning_count() {
494 0 => writeln!(
495 f,
496 ", no warnings{}",
497 if self.error_count() > 0 { ":" } else { "." }
498 )?,
499 1 => writeln!(f, ", 1 warning:")?,
500 _ => writeln!(f, ", {} warnings:", self.warning_count())?,
501 };
502 self.fmt_diagnosis(f)?;
503 Ok(())
504 }
505}
506
507impl ImporterRegistryAccess for EvalContext {
508 type Error = Box<EvalError>;
509
510 fn import(
511 &mut self,
512 arg_map: &Tuple,
513 search_paths: &[std::path::PathBuf],
514 ) -> Result<Value, Self::Error> {
515 match self.importers.import(arg_map, search_paths) {
516 Ok(value) => Ok(value),
517 Err(err) => {
518 self.error(arg_map, err)?;
519 Ok(Value::None)
520 }
521 }
522 }
523}
524
525impl ExporterAccess for EvalContext {
526 fn exporter_by_id(&self, id: &crate::Id) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
527 self.exporters.exporter_by_id(id)
528 }
529
530 fn exporter_by_filename(
531 &self,
532 filename: &std::path::Path,
533 ) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
534 self.exporters.exporter_by_filename(filename)
535 }
536}