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