1use crate::{
5 builtin::*, diag::*, eval::*, model::*, rc::*, resolve::*, syntax::*, tree_display::*,
6};
7
8pub struct EvalContext {
12 symbol_table: SymbolTable,
14 sources: Sources,
16 pub(super) stack: Stack,
18 output: Box<dyn Output>,
20 exporters: ExporterRegistry,
22 importers: ImporterRegistry,
24 pub diag: DiagHandler,
26}
27
28impl EvalContext {
29 pub fn new(
31 resolve_context: ResolveContext,
32 output: Box<dyn Output>,
33 exporters: ExporterRegistry,
34 importers: ImporterRegistry,
35 ) -> Self {
36 log::debug!("Creating evaluation context");
37
38 Self {
39 symbol_table: resolve_context.symbol_table,
40 sources: resolve_context.sources,
41 diag: resolve_context.diag,
42 output,
43 exporters,
44 importers,
45 ..Default::default()
46 }
47 }
48
49 pub(crate) fn current_symbol(&self) -> Symbol {
51 self.stack.current_symbol().expect("Some symbol")
52 }
53
54 pub fn from_source(
56 root: Rc<SourceFile>,
57 builtin: Option<Symbol>,
58 search_paths: &[impl AsRef<std::path::Path>],
59 output: Box<dyn Output>,
60 exporters: ExporterRegistry,
61 importers: ImporterRegistry,
62 line_offset: usize,
63 ) -> EvalResult<Self> {
64 Ok(Self::new(
65 ResolveContext::create(root, search_paths, builtin, DiagHandler::new(line_offset))?,
66 output,
67 exporters,
68 importers,
69 ))
70 }
71
72 pub fn output(&self) -> Option<String> {
74 self.output.output()
75 }
76
77 pub fn print(&mut self, what: String) {
79 self.output.print(what).expect("could not write to output");
80 }
81
82 pub fn eval(&mut self) -> EvalResult<Option<Model>> {
84 if self.diag.error_count() > 0 {
85 log::error!("Aborting evaluation because of prior resolve errors!");
86 return Err(EvalError::ResolveFailed);
87 }
88 let model: Model = self.sources.root().eval(self)?;
89 log::trace!("Post-evaluation context:\n{self:?}");
90 log::trace!("Evaluated Model:\n{}", FormatTree(&model));
91
92 let unused = self
93 .symbol_table
94 .unused_private()
95 .iter()
96 .map(|symbol| {
97 (
98 match self.sources.get_code(&symbol) {
99 Ok(id) => id,
100 Err(_) => symbol.id().to_string(),
101 },
102 symbol.src_ref(),
103 )
104 })
105 .collect::<indexmap::IndexMap<_, _>>();
107
108 unused.into_iter().try_for_each(|(id, src_ref)| {
109 self.warning(&src_ref, EvalError::UnusedGlobalSymbol(id))
110 })?;
111
112 if model.has_no_output() {
113 Ok(None)
115 } else {
116 Ok(Some(model))
117 }
118 }
119
120 pub(super) fn scope<T>(
122 &mut self,
123 stack_frame: StackFrame,
124 f: impl FnOnce(&mut EvalContext) -> T,
125 ) -> T {
126 self.open(stack_frame);
127 let result = f(self);
128 let mut unused: Vec<_> = if let Some(frame) = &self.stack.current_frame() {
129 if let Some(locals) = frame.locals() {
130 locals
131 .iter()
132 .filter(|(_, symbol)| !symbol.is_used())
133 .filter(|(id, _)| !id.ignore())
134 .filter(|(_, symbol)| !symbol.src_ref().is_none())
135 .map(|(id, _)| id.clone())
136 .collect()
137 } else {
138 vec![]
139 }
140 } else {
141 vec![]
142 };
143 unused.sort();
144
145 unused
146 .iter()
147 .try_for_each(|id| self.warning(id, EvalError::UnusedLocal(id.clone())))
148 .expect("diag error");
149
150 self.close();
151 result
152 }
153
154 pub fn exporters(&self) -> &ExporterRegistry {
156 &self.exporters
157 }
158
159 pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
161 self.sources.search_paths()
162 }
163
164 pub(super) fn get_property(&self, id: &Identifier) -> EvalResult<Value> {
166 match self.get_model() {
167 Ok(model) => {
168 if let Some(value) = model.get_property(id) {
169 Ok(value.clone())
170 } else {
171 Err(EvalError::PropertyNotFound(id.clone()))
172 }
173 }
174 Err(err) => Err(err),
175 }
176 }
177
178 pub(super) fn init_property(&self, id: Identifier, value: Value) -> EvalResult<()> {
182 match self.get_model() {
183 Ok(model) => {
184 if let Some(previous_value) = model.borrow_mut().set_property(id.clone(), value) {
185 if !previous_value.is_invalid() {
186 return Err(EvalError::ValueAlreadyDefined {
187 location: id.src_ref(),
188 name: id.clone(),
189 value: previous_value.to_string(),
190 previous_location: id.src_ref(),
191 });
192 }
193 }
194 Ok(())
195 }
196 Err(err) => Err(err),
197 }
198 }
199
200 pub(super) fn is_init(&mut self) -> bool {
202 matches!(self.stack.current_frame(), Some(StackFrame::Init(_)))
203 }
204
205 pub(super) fn is_within_function(&self) -> bool {
206 self.stack.is_within_function()
207 }
208
209 fn lookup_property(&self, name: &QualifiedName) -> EvalResult<Symbol> {
211 log::trace!(
212 "{lookup} for property {name:?}",
213 lookup = crate::mark!(LOOKUP)
214 );
215 self.symbol_table.deny_super(name)?;
216
217 if self.stack.current_workbench_name().is_some() {
218 if let Some(id) = name.single_identifier() {
219 match self.get_property(id) {
220 Ok(value) => {
221 log::trace!(
222 "{found} property '{name:?}'",
223 found = crate::mark!(FOUND_INTERIM)
224 );
225 return Ok(Symbol::new(
226 SymbolDef::Constant(Visibility::Public, id.clone(), value),
227 None,
228 ));
229 }
230 Err(err) => return Err(err),
231 }
232 }
233 }
234 log::trace!(
235 "{not_found} Property '{name:?}'",
236 not_found = crate::mark!(NOT_FOUND_INTERIM)
237 );
238 Err(EvalError::NoPropertyId(name.clone()))
239 }
240
241 fn lookup_workbench(
242 &self,
243 name: &QualifiedName,
244 target: LookupTarget,
245 ) -> ResolveResult<Symbol> {
246 if let Some(workbench) = &self.stack.current_workbench_name() {
247 log::trace!(
248 "{lookup} for symbol '{name:?}' in current workbench '{workbench:?}'",
249 lookup = crate::mark!(LOOKUP)
250 );
251 self.deny_super(name)?;
252 match self
253 .symbol_table
254 .lookup_within_name(name, workbench, target)
255 {
256 Ok(symbol) => {
257 log::trace!(
258 "{found} symbol in current module: {symbol:?}",
259 found = crate::mark!(FOUND_INTERIM),
260 );
261 Ok(symbol)
262 }
263 Err(err) => {
264 log::trace!(
265 "{not_found} symbol '{name:?}': {err}",
266 not_found = crate::mark!(NOT_FOUND_INTERIM)
267 );
268 Err(err)
269 }
270 }
271 } else {
272 log::trace!(
273 "{not_found} No current workbench",
274 not_found = crate::mark!(NOT_FOUND_INTERIM)
275 );
276 Err(ResolveError::SymbolNotFound(name.clone()))
277 }
278 }
279
280 fn is_code(&self) -> bool {
282 !matches!(self.stack.current_frame(), Some(StackFrame::Module(..)))
283 }
284
285 pub(crate) fn is_module(&self) -> bool {
287 matches!(
288 self.stack.current_frame(),
289 Some(StackFrame::Module(..) | StackFrame::Source(..))
290 )
291 }
292
293 fn lookup_within(&self, name: &QualifiedName, target: LookupTarget) -> ResolveResult<Symbol> {
294 self.symbol_table.lookup_within(
295 name,
296 &self
297 .symbol_table
298 .search(&self.stack.current_module_name(), false)?,
299 target,
300 )
301 }
302
303 pub fn symbol_table(&self) -> &SymbolTable {
305 &self.symbol_table
306 }
307}
308
309impl UseLocally for EvalContext {
310 fn use_symbol(&mut self, name: &QualifiedName, id: Option<Identifier>) -> EvalResult<Symbol> {
311 log::debug!("Using symbol {name:?}");
312
313 let symbol = self.lookup(name, LookupTarget::Any)?;
314 if self.is_code() {
315 self.stack.put_local(id, symbol.clone())?;
316 log::trace!("Local Stack:\n{:?}", self.stack);
317 }
318
319 Ok(symbol)
320 }
321
322 fn use_symbols_of(&mut self, name: &QualifiedName) -> EvalResult<Symbol> {
323 log::debug!("Using all symbols in {name:?}");
324
325 let symbol = self.lookup(name, LookupTarget::Any)?;
326 if symbol.is_empty() {
327 Err(EvalError::NoSymbolsToUse(symbol.full_name()))
328 } else {
329 if self.is_code() {
330 symbol.try_children(|(id, symbol)| {
331 self.stack.put_local(Some(id.clone()), symbol.clone())
332 })?;
333 log::trace!("Local Stack:\n{:?}", self.stack);
334 }
335 Ok(symbol)
336 }
337 }
338}
339
340impl Locals for EvalContext {
341 fn set_local_value(&mut self, id: Identifier, value: Value) -> EvalResult<()> {
342 self.stack.set_local_value(id, value)
343 }
344
345 fn get_local_value(&self, id: &Identifier) -> EvalResult<Value> {
346 self.stack.get_local_value(id)
347 }
348
349 fn open(&mut self, frame: StackFrame) {
350 self.stack.open(frame);
351 }
352
353 fn close(&mut self) -> StackFrame {
354 self.stack.close()
355 }
356
357 fn fetch_symbol(&self, id: &Identifier) -> EvalResult<Symbol> {
358 self.stack.fetch_symbol(id)
359 }
360
361 fn get_model(&self) -> EvalResult<Model> {
362 self.stack.get_model()
363 }
364
365 fn current_name(&self) -> QualifiedName {
366 self.stack.current_name()
367 }
368}
369
370impl Default for EvalContext {
371 fn default() -> Self {
372 Self {
373 symbol_table: Default::default(),
374 sources: Default::default(),
375 stack: Default::default(),
376 output: Stdout::new(),
377 exporters: Default::default(),
378 importers: Default::default(),
379 diag: Default::default(),
380 }
381 }
382}
383
384impl Lookup<EvalError> for EvalContext {
385 fn lookup(&self, name: &QualifiedName, target: LookupTarget) -> EvalResult<Symbol> {
386 log::debug!("Lookup {target} '{name:?}' (at line {:?}):", name.src_ref());
387
388 log::trace!("- lookups -------------------------------------------------------");
389 let results = [
391 ("local", { self.stack.lookup(name, target) }),
392 ("global", {
393 self.lookup_within(name, target).map_err(|err| err.into())
394 }),
395 ("property", { self.lookup_property(name) }),
396 ("workbench", {
397 self.lookup_workbench(name, target)
398 .map_err(|err| err.into())
399 }),
400 ]
401 .into_iter();
402
403 log::trace!("- lookup results ------------------------------------------------");
404 let results = results.inspect(|(from, result)| log::trace!("{from}: {:?}", result));
405
406 let (found, mut ambiguities, mut errors) = results.fold(
408 (vec![], vec![], vec![]),
409 |(mut oks, mut ambiguities, mut errors), (origin, result)| {
410 match result {
411 Ok(symbol) => oks.push((origin, symbol)),
412 Err(EvalError::AmbiguousSymbol( ambiguous, others)) => {
413 ambiguities.push((origin, EvalError::AmbiguousSymbol ( ambiguous, others )))
414 }
415 Err(
416 EvalError::SymbolNotFound(_)
418 | EvalError::LocalNotFound(_)
420 | EvalError::NoModelInWorkbench
422 | EvalError::PropertyNotFound(_)
423 | EvalError::NoPropertyId(_)
424 | EvalError::ResolveError(ResolveError::SymbolNotFound(_))
426 | EvalError::ResolveError(ResolveError::ExternalPathNotFound(_))
427 | EvalError::ResolveError(ResolveError::SymbolIsPrivate(_))
428 | EvalError::ResolveError(ResolveError::NulHash)
429 | EvalError::ResolveError(ResolveError::WrongTarget),
430 ) => (),
431 Err(err) => errors.push((origin, err)),
432 }
433 (oks, ambiguities, errors)
434 },
435 );
436
437 if !errors.is_empty() {
439 log::error!("Unexpected errors while lookup symbol '{name:?}':");
440 errors
441 .iter()
442 .for_each(|(origin, err)| log::error!("Lookup ({origin}) error: {err}"));
443
444 return Err(errors.remove(0).1);
445 }
446
447 if !ambiguities.is_empty() {
449 log::debug!(
450 "{ambiguous} Symbol '{name:?}':\n{}",
451 ambiguities
452 .iter()
453 .map(|(origin, err)| format!("{origin}: {err}"))
454 .collect::<Vec<_>>()
455 .join("\n"),
456 ambiguous = crate::mark!(AMBIGUOUS)
457 );
458 return Err(ambiguities.remove(0).1);
459 }
460
461 let found: Vec<_> = found
463 .iter()
464 .filter(|(_, symbol)| target.matches(symbol))
465 .collect();
466
467 match found.first() {
469 Some((origin, symbol)) => {
470 if found.iter().all(|(_, x)| x == symbol) {
472 log::debug!(
473 "{found} symbol '{name:?}' in {origin}",
474 found = crate::mark!(FOUND)
475 );
476 symbol.set_used();
477 Ok(symbol.clone())
478 } else {
479 let others: QualifiedNames =
480 found.iter().map(|(_, symbol)| symbol.full_name()).collect();
481 log::debug!(
482 "{ambiguous} symbol '{name:?}' in {others:?}:\n{self:?}",
483 ambiguous = crate::mark!(AMBIGUOUS),
484 );
485 Err(EvalError::AmbiguousSymbol(name.clone(), others))
486 }
487 }
488 None => {
489 log::debug!(
490 "{not_found} Symbol '{name:?}'",
491 not_found = crate::mark!(NOT_FOUND)
492 );
493 Err(EvalError::SymbolNotFound(name.clone()))
494 }
495 }
496 }
497
498 fn ambiguity_error(ambiguous: QualifiedName, others: QualifiedNames) -> EvalError {
499 EvalError::AmbiguousSymbol(ambiguous, others)
500 }
501}
502
503impl Diag for EvalContext {
504 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
505 self.diag.pretty_print(f, self)
506 }
507
508 fn warning_count(&self) -> u32 {
509 self.diag.warning_count()
510 }
511
512 fn error_count(&self) -> u32 {
513 self.diag.error_count()
514 }
515
516 fn error_lines(&self) -> std::collections::HashSet<usize> {
517 self.diag.error_lines()
518 }
519
520 fn warning_lines(&self) -> std::collections::HashSet<usize> {
521 self.diag.warning_lines()
522 }
523}
524
525impl PushDiag for EvalContext {
526 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
527 let result = self.diag.push_diag(diag);
528 log::trace!("Error Context:\n{self:?}");
529 #[cfg(debug_assertions)]
530 if std::env::var("MICROCAD_ERROR_PANIC").is_ok() {
531 eprintln!("{}", self.diagnosis());
532 panic!("MICROCAD_ERROR_PANIC")
533 }
534 result
535 }
536}
537
538impl GetSourceByHash for EvalContext {
539 fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<SourceFile>> {
540 self.sources.get_by_hash(hash)
541 }
542}
543
544impl std::fmt::Debug for EvalContext {
545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546 if let Ok(model) = self.get_model() {
547 write!(f, "\nModel:\n")?;
548 model.tree_print(f, TreeState::new_debug(4))?;
549 }
550 writeln!(f, "\nCurrent: {:?}", self.stack.current_name())?;
551 writeln!(f, "\nModule: {:?}", self.stack.current_module_name())?;
552 write!(f, "\nLocals Stack:\n{:?}", self.stack)?;
553 writeln!(f, "\nCall Stack:")?;
554 self.stack.pretty_print_call_trace(f, &self.sources)?;
555
556 writeln!(f, "\nSources:\n")?;
557 write!(f, "{:?}", &self.sources)?;
558
559 write!(f, "\nSymbol Table:\n{:?}", self.symbol_table)?;
560 match self.error_count() {
561 0 => write!(f, "No errors")?,
562 1 => write!(f, "1 error")?,
563 _ => write!(f, "{} errors", self.error_count())?,
564 };
565 match self.warning_count() {
566 0 => writeln!(
567 f,
568 ", no warnings{}",
569 if self.error_count() > 0 { ":" } else { "." }
570 )?,
571 1 => writeln!(f, ", 1 warning:")?,
572 _ => writeln!(f, ", {} warnings:", self.warning_count())?,
573 };
574 self.fmt_diagnosis(f)?;
575 Ok(())
576 }
577}
578
579impl ImporterRegistryAccess for EvalContext {
580 type Error = EvalError;
581
582 fn import(
583 &mut self,
584 arg_map: &Tuple,
585 search_paths: &[std::path::PathBuf],
586 ) -> Result<Value, Self::Error> {
587 match self.importers.import(arg_map, search_paths) {
588 Ok(value) => Ok(value),
589 Err(err) => {
590 self.error(arg_map, err)?;
591 Ok(Value::None)
592 }
593 }
594 }
595}
596
597impl ExporterAccess for EvalContext {
598 fn exporter_by_id(&self, id: &crate::Id) -> Result<Rc<dyn Exporter>, ExportError> {
599 self.exporters.exporter_by_id(id)
600 }
601
602 fn exporter_by_filename(
603 &self,
604 filename: &std::path::Path,
605 ) -> Result<Rc<dyn Exporter>, ExportError> {
606 self.exporters.exporter_by_filename(filename)
607 }
608}