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