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
122 Ok(model)
123 }
124
125 pub(super) fn scope<T>(
127 &mut self,
128 stack_frame: StackFrame,
129 f: impl FnOnce(&mut EvalContext) -> T,
130 ) -> T {
131 self.open(stack_frame);
132 let result = f(self);
133 let mut unused: Vec<_> = if let Some(frame) = &self.stack.current_frame() {
134 if let Some(locals) = frame.locals() {
135 locals
136 .iter()
137 .filter(|(_, symbol)| !symbol.is_used())
138 .filter(|(id, _)| !id.ignore())
139 .filter(|(_, symbol)| !symbol.src_ref().is_none())
140 .map(|(id, _)| id.clone())
141 .collect()
142 } else {
143 vec![]
144 }
145 } else {
146 vec![]
147 };
148 unused.sort();
149
150 unused
151 .iter()
152 .try_for_each(|id| self.warning(id, EvalError::UnusedLocal(id.clone())))
153 .expect("diag error");
154
155 self.close();
156 result
157 }
158
159 pub fn exporters(&self) -> &ExporterRegistry {
161 &self.exporters
162 }
163
164 pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
166 self.sources.search_paths()
167 }
168
169 pub(super) fn get_property(&self, id: &Identifier) -> EvalResult<Value> {
171 match self.get_model() {
172 Ok(model) => {
173 if let Some(value) = model.get_property(id) {
174 Ok(value.clone())
175 } else {
176 Err(EvalError::PropertyNotFound(id.clone()).into())
177 }
178 }
179 Err(err) => Err(err),
180 }
181 }
182
183 pub(super) fn init_property(&self, id: Identifier, value: Value) -> EvalResult<()> {
187 match self.get_model() {
188 Ok(model) => {
189 if let Some(previous_value) = model.borrow_mut().set_property(id.clone(), value) {
190 if !previous_value.is_invalid() {
191 return Err(EvalError::ValueAlreadyDefined {
192 location: id.src_ref(),
193 name: id.clone(),
194 value: previous_value.to_string(),
195 previous_location: id.src_ref(),
196 }
197 .into());
198 }
199 }
200 Ok(())
201 }
202 Err(err) => Err(err),
203 }
204 }
205
206 pub(super) fn is_init(&mut self) -> bool {
208 matches!(self.stack.current_frame(), Some(StackFrame::Init(_)))
209 }
210
211 fn lookup_property(&self, name: &ir::QualifiedName) -> EvalResult<Symbol> {
213 log::trace!(
214 "{lookup} for property {name:?}",
215 lookup = microcad_lang_base::mark!(LOOKUP)
216 );
217
218 if self.stack.current_call_name().is_some() {
219 if let Some(id) = name.single_identifier() {
220 match self.get_property(id) {
221 Ok(value) => {
222 log::trace!(
223 "{found} property '{name:?}'",
224 found = microcad_lang_base::mark!(FOUND)
225 );
226 return Ok(Symbol::new(SymbolDef::Value(id.clone(), value), None));
227 }
228 Err(err) => return Err(err),
229 }
230 }
231 }
232 log::trace!(
233 "{not_found} Property '{name:?}'",
234 not_found = microcad_lang_base::mark!(NOT_FOUND)
235 );
236 Err(EvalError::NoPropertyId(name.clone()).into())
237 }
238
239 fn lookup_workbench(
240 &self,
241 name: &ir::QualifiedName,
242 target: LookupTarget,
243 ) -> ResolveResult<Symbol> {
244 if let Some(workbench) = &self.stack.current_call_name() {
245 log::trace!(
246 "{lookup} for symbol '{name:?}' in current workbench '{workbench:?}'",
247 lookup = microcad_lang_base::mark!(LOOKUP)
248 );
249 match self.root.lookup_within_name(name, workbench, target) {
250 Ok(symbol) => {
251 log::trace!(
252 "{found} symbol in current module: {symbol:?}",
253 found = microcad_lang_base::mark!(FOUND),
254 );
255 Ok(symbol)
256 }
257 Err(err) => {
258 log::trace!(
259 "{not_found} symbol '{name:?}': {err}",
260 not_found = microcad_lang_base::mark!(NOT_FOUND)
261 );
262 Err(err)
263 }
264 }
265 } else {
266 log::trace!(
267 "{not_found} No current workbench",
268 not_found = microcad_lang_base::mark!(NOT_FOUND)
269 );
270 Err(ResolveError::SymbolNotFound(name.clone()))
271 }
272 }
273
274 fn lookup_within(
275 &self,
276 name: &ir::QualifiedName,
277 target: LookupTarget,
278 ) -> ResolveResult<Symbol> {
279 self.root.lookup_within(
280 name,
281 &self.root.search(&self.stack.current_module_name(), false)?,
282 target,
283 )
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) -> ir::QualifiedName {
313 self.stack.current_name()
314 }
315}
316
317impl Lookup<Box<EvalError>> for EvalContext {
318 fn lookup(&self, name: &ir::QualifiedName, target: LookupTarget) -> EvalResult<Symbol> {
319 log::debug!("Lookup {target} '{name:?}' (at line {:?}):", name.src_ref());
320
321 log::trace!("- lookups -------------------------------------------------------");
322 let results = [
324 ("local", { self.stack.lookup(name, target) }),
325 ("global", {
326 self.lookup_within(name, target).map_err(|err| err.into())
327 }),
328 ("property", { self.lookup_property(name) }),
329 ("workbench", {
330 self.lookup_workbench(name, target)
331 .map_err(|err| err.into())
332 }),
333 ]
334 .into_iter();
335
336 log::trace!("- lookup results ------------------------------------------------");
337 let results = results.inspect(|(from, result)| log::trace!("{from}: {:?}", result));
338
339 let (found, mut ambiguities, mut errors) = results.fold(
341 (vec![], vec![], vec![]),
342 |(mut oks, mut ambiguities, mut errors), (origin, result)| {
343 match result {
344 Ok(symbol) => oks.push((origin, symbol)),
345 Err(err) => match *err {
346 EvalError::AmbiguousSymbol(ambiguous, others) => {
347 ambiguities.push((origin, EvalError::AmbiguousSymbol ( ambiguous, others )));
348 }
349 EvalError::SymbolNotFound(_)
351 | EvalError::LocalNotFound(_)
353 | EvalError::NoModelInWorkbench
355 | EvalError::PropertyNotFound(_)
356 | EvalError::NoPropertyId(_)
357 | EvalError::ResolveError(ResolveError::SymbolNotFound(_))
359 | EvalError::ResolveError(ResolveError::SymbolIsPrivate(_))
360 | EvalError::ResolveError(ResolveError::NulHash)
361 | EvalError::ResolveError(ResolveError::WrongTarget) => {},
362 err => errors.push((origin, err)),
363 }
364 }
365 (oks, ambiguities, errors)
366 },
367 );
368
369 if !errors.is_empty() {
371 log::error!("Unexpected errors while lookup symbol '{name:?}':");
372 errors
373 .iter()
374 .for_each(|(origin, err)| log::error!("Lookup ({origin}) error: {err}"));
375
376 return Err(errors.remove(0).1.into());
377 }
378
379 if !ambiguities.is_empty() {
381 log::debug!(
382 "{ambiguous} Symbol '{name:?}':\n{}",
383 ambiguities
384 .iter()
385 .map(|(origin, err)| format!("{origin}: {err}"))
386 .collect::<Vec<_>>()
387 .join("\n"),
388 ambiguous = microcad_lang_base::mark!(AMBIGUOUS)
389 );
390 return Err(ambiguities.remove(0).1.into());
391 }
392
393 let found: Vec<_> = found
395 .iter()
396 .filter(|(_, symbol)| target.matches(symbol))
397 .collect();
398
399 match found.first() {
401 Some((origin, symbol)) => {
402 if found.iter().all(|(_, x)| x == symbol) {
404 log::debug!(
405 "{found} symbol '{name:?}' in {origin}",
406 found = microcad_lang_base::mark!(FOUND!)
407 );
408 symbol.set_used();
409 Ok(symbol.clone())
410 } else {
411 let others: ir::QualifiedNames =
412 found.iter().map(|(_, symbol)| symbol.full_name()).collect();
413 log::debug!(
414 "{ambiguous} symbol '{name:?}' in {others:?}:\n{self:?}",
415 ambiguous = microcad_lang_base::mark!(AMBIGUOUS),
416 );
417 Err(EvalError::AmbiguousSymbol(name.clone(), others).into())
418 }
419 }
420 None => {
421 log::debug!(
422 "{not_found} Symbol '{name:?}'",
423 not_found = microcad_lang_base::mark!(NOT_FOUND!)
424 );
425 Err(EvalError::SymbolNotFound(name.clone()).into())
426 }
427 }
428 }
429
430 fn ambiguity_error(ambiguous: ir::QualifiedName, others: ir::QualifiedNames) -> Box<EvalError> {
431 EvalError::AmbiguousSymbol(ambiguous, others).into()
432 }
433}
434
435impl microcad_lang_base::Diag for EvalContext {
436 fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
437 self.diag.pretty_print(f, self)
438 }
439
440 fn warning_count(&self) -> u32 {
441 self.diag.warning_count()
442 }
443
444 fn error_count(&self) -> u32 {
445 self.diag.error_count()
446 }
447
448 fn error_lines(&self) -> HashSet<u32> {
449 self.diag.error_lines()
450 }
451
452 fn warning_lines(&self) -> HashSet<u32> {
453 self.diag.warning_lines()
454 }
455}
456
457impl PushDiag for EvalContext {
458 fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
459 self.diag.push_diag(diag)
460 }
461}
462
463impl GetSourceByHash for EvalContext {
464 fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<ir::Source>> {
465 self.sources.get_by_hash(hash)
466 }
467}
468
469impl GetSourceLocInfoByHash for EvalContext {
470 fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>> {
471 self.sources.get_source_loc_info_by_hash(hash)
472 }
473}
474
475impl std::fmt::Debug for EvalContext {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 if let Ok(model) = self.get_model() {
478 write!(f, "\nModel:\n")?;
479 model.tree_print(f, TreeState::new_debug(4))?;
480 }
481 writeln!(f, "\nCurrent: {:?}", self.stack.current_name())?;
482 writeln!(f, "\nModule: {:?}", self.stack.current_module_name())?;
483 write!(f, "\nLocals Stack:\n{:?}", self.stack)?;
484 writeln!(f, "\nCall Stack:")?;
485 self.stack.pretty_print_call_trace(f, &self.sources)?;
486
487 writeln!(f, "\nSources:\n")?;
488 write!(f, "{:?}", &self.sources)?;
489
490 write!(f, "\nSymbol Table:\n")?;
491 self.root.tree_print(f, TreeState::new_debug(0))?;
492
493 match self.error_count() {
494 0 => write!(f, "No errors")?,
495 1 => write!(f, "1 error")?,
496 _ => write!(f, "{} errors", self.error_count())?,
497 };
498 match self.warning_count() {
499 0 => writeln!(
500 f,
501 ", no warnings{}",
502 if self.error_count() > 0 { ":" } else { "." }
503 )?,
504 1 => writeln!(f, ", 1 warning:")?,
505 _ => writeln!(f, ", {} warnings:", self.warning_count())?,
506 };
507 self.fmt_diagnosis(f)?;
508 Ok(())
509 }
510}
511
512impl ImporterRegistryAccess for EvalContext {
513 type Error = Box<EvalError>;
514
515 fn import(
516 &mut self,
517 arg_map: &Tuple,
518 search_paths: &[std::path::PathBuf],
519 ) -> Result<Value, Self::Error> {
520 match self.importers.import(arg_map, search_paths) {
521 Ok(value) => Ok(value),
522 Err(err) => {
523 self.error(arg_map, err)?;
524 Ok(Value::None)
525 }
526 }
527 }
528}
529
530impl ExporterAccess for EvalContext {
531 fn exporter_by_id(&self, id: &crate::Id) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
532 self.exporters.exporter_by_id(id)
533 }
534
535 fn exporter_by_filename(
536 &self,
537 filename: &std::path::Path,
538 ) -> Result<std::rc::Rc<dyn Exporter>, ExportError> {
539 self.exporters.exporter_by_filename(filename)
540 }
541}