microcad_lang/eval/
context.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{
5    builtin::*, diag::*, eval::*, model::*, rc::*, resolve::*, syntax::*, tree_display::*,
6};
7
8/// Grant statements depending on context
9pub trait Grant<T> {
10    /// Check if given statement `T` is granted within the current context
11    fn grant(&mut self, t: &T) -> EvalResult<()>;
12}
13
14/// *Context* for *evaluation* of a resolved µcad file.
15///
16/// The context is used to store the current state of the evaluation.
17///
18/// A context consists of the following members:
19/// - A *symbol table* ([`SymbolTable`]) with symbols stored by [`QualifiedName`] and a [`Stack`].
20/// - A *diagnostic handler* ([`DiagHandler`]) that accumulates *evaluation errors* for later output.
21/// - One *output channel* ([`Output`]) where `__builtin::print` writes it's output to while evaluation.
22///
23/// All these internal structures can be accessed by several implemented traits.
24pub struct Context {
25    /// Symbol table
26    symbol_table: SymbolTable,
27    /// Source file diagnostics.
28    diag_handler: DiagHandler,
29    /// Output channel for [__builtin::print].
30    output: Box<dyn Output>,
31    /// Exporter registry.
32    exporters: ExporterRegistry,
33    /// Importer registry.
34    importers: ImporterRegistry,
35}
36
37impl Context {
38    /// Create a new context from a source file.
39    ///
40    /// # Arguments
41    /// - `root`: Root symbol.
42    /// - `builtin`: The builtin library.
43    /// - `search_paths`: Paths to search for external libraries (e.g. the standard library).
44    /// - `output`: Output channel to use.
45    pub fn new(
46        root: Identifier,
47        symbols: SymbolMap,
48        sources: Sources,
49        output: Box<dyn Output>,
50    ) -> Self {
51        log::debug!("Creating Context");
52
53        // put all together
54        Self {
55            symbol_table: SymbolTable::new(root, symbols, sources).expect("unknown root id"),
56            output,
57            diag_handler: Default::default(),
58            exporters: ExporterRegistry::default(),
59            importers: ImporterRegistry::default(),
60        }
61    }
62
63    /// Current symbol, panics if there no current symbol.
64    pub fn current_symbol(&self) -> Symbol {
65        self.symbol_table
66            .stack
67            .current_symbol()
68            .expect("Some symbol")
69    }
70
71    /// Create a new context from a source file.
72    ///
73    /// # Arguments
74    /// - `root`: Path to the root file to load.
75    /// - `builtin`: The builtin library.
76    /// - `search_paths`: Paths to search for external libraries (e.g. the standard library).
77    pub fn from_source(
78        root: impl AsRef<std::path::Path> + std::fmt::Debug,
79        builtin: Symbol,
80        search_paths: &[std::path::PathBuf],
81    ) -> EvalResult<Self> {
82        let root = SourceFile::load(root)?;
83        let root_id = root.id();
84        let sources = Sources::load(root, search_paths)?;
85        let mut symbols = sources.resolve()?;
86        symbols.insert(Identifier::no_ref("__builtin"), builtin);
87        Ok(Self::new(root_id, symbols, sources, Box::new(Stdout)))
88    }
89
90    /// Access captured output.
91    pub fn output(&self) -> Option<String> {
92        self.output.output()
93    }
94
95    /// Print for `__builtin::print`.
96    pub fn print(&mut self, what: String) {
97        self.output.print(what).expect("could not write to output");
98    }
99
100    /// Get the source code location of the given referrer as string (e.g. `/path/to/file.µcad:52:1`).
101    pub fn locate(&self, referrer: &impl SrcReferrer) -> EvalResult<String> {
102        Ok(format!(
103            "{}:{}",
104            self.get_by_hash(referrer.src_ref().source_hash())?
105                .filename_as_str(),
106            referrer.src_ref()
107        ))
108    }
109
110    /// Get the original source code of the given referrer.
111    pub fn source_code(&self, referrer: &impl SrcReferrer) -> EvalResult<String> {
112        Ok(referrer
113            .src_ref()
114            .source_slice(&self.get_by_hash(referrer.src_ref().source_hash())?.source)
115            .to_string())
116    }
117
118    /// Evaluate context into a value.
119    pub fn eval(&mut self) -> EvalResult<Model> {
120        let source_file = match &self.symbol_table.root.borrow().def {
121            SymbolDefinition::SourceFile(source_file) => source_file.clone(),
122            _ => todo!(),
123        };
124        source_file.eval(self)
125    }
126
127    /// Peek into root node for testing.
128    pub fn root(&self) -> &Symbol {
129        &self.symbol_table.root
130    }
131
132    /// Run the closure `f` within the given `stack_frame`.
133    pub fn scope<T>(&mut self, stack_frame: StackFrame, f: impl FnOnce(&mut Context) -> T) -> T {
134        self.open(stack_frame);
135        let result = f(self);
136        self.close();
137        result
138    }
139
140    /// Set importers.
141    pub fn set_importers(&mut self, importers: ImporterRegistry) {
142        self.importers = importers;
143    }
144
145    /// All registered exporters.
146    pub fn exporters(&self) -> &ExporterRegistry {
147        &self.exporters
148    }
149
150    /// Set exporters.
151    pub fn set_exporters(&mut self, exporters: ExporterRegistry) {
152        self.exporters = exporters;
153    }
154
155    /// Return search paths of this context.
156    pub fn search_paths(&self) -> &Vec<std::path::PathBuf> {
157        self.symbol_table.search_paths()
158    }
159
160    /// Get property from current model.
161    pub fn get_property(&self, id: &Identifier) -> EvalResult<Value> {
162        match self.get_model() {
163            Ok(model) => {
164                if let Some(value) = model.get_property(id) {
165                    Ok(value.clone())
166                } else {
167                    Err(EvalError::PropertyNotFound(id.clone()))
168                }
169            }
170            Err(err) => Err(err),
171        }
172    }
173
174    /// Initialize a property.
175    ///
176    /// Returns error if there is no model or the property has been initialized before.
177    pub fn init_property(&self, id: Identifier, value: Value) -> EvalResult<()> {
178        match self.get_model() {
179            Ok(model) => {
180                if let Some(previous_value) = model.borrow_mut().set_property(id.clone(), value) {
181                    if !previous_value.is_invalid() {
182                        return Err(EvalError::ValueAlreadyInitialized(
183                            id.clone(),
184                            previous_value,
185                            id.src_ref(),
186                        ));
187                    }
188                }
189                Ok(())
190            }
191            Err(err) => Err(err),
192        }
193    }
194
195    /// Return if the current frame is an init frame.
196    pub fn is_init(&mut self) -> bool {
197        matches!(
198            self.symbol_table.stack.current_frame(),
199            Some(StackFrame::Init(_))
200        )
201    }
202
203    /// Lookup a property by qualified name.
204    fn lookup_property(&self, name: &QualifiedName) -> EvalResult<Symbol> {
205        match name.single_identifier() {
206            Some(id) => match self.get_property(id) {
207                Ok(value) => {
208                    log::debug!(
209                        "{found} property '{name:?}'",
210                        found = crate::mark!(FOUND_INTERIM)
211                    );
212                    Ok(Symbol::new(
213                        SymbolDefinition::Constant(Visibility::Public, id.clone(), value),
214                        None,
215                    ))
216                }
217                Err(err) => {
218                    log::warn!(
219                        "{not_found} Property '{name:?}'",
220                        not_found = crate::mark!(NOT_FOUND_INTERIM)
221                    );
222                    Err(err)
223                }
224            },
225            None => {
226                log::debug!(
227                    "{not_found} Property '{name:?}'",
228                    not_found = crate::mark!(NOT_FOUND_INTERIM)
229                );
230                Err(EvalError::SymbolNotFound(name.clone()))
231            }
232        }
233    }
234}
235
236impl Locals for Context {
237    fn set_local_value(&mut self, id: Identifier, value: Value) -> EvalResult<()> {
238        self.symbol_table.set_local_value(id, value)
239    }
240
241    fn get_local_value(&self, id: &Identifier) -> EvalResult<Value> {
242        self.symbol_table.get_local_value(id)
243    }
244
245    fn open(&mut self, frame: StackFrame) {
246        self.symbol_table.open(frame);
247    }
248
249    fn close(&mut self) {
250        self.symbol_table.close();
251    }
252
253    fn fetch(&self, id: &Identifier) -> EvalResult<Symbol> {
254        self.symbol_table.fetch(id)
255    }
256
257    fn get_model(&self) -> EvalResult<Model> {
258        self.symbol_table.get_model()
259    }
260
261    fn current_name(&self) -> QualifiedName {
262        self.symbol_table.current_name()
263    }
264}
265
266impl Default for Context {
267    fn default() -> Self {
268        Self {
269            symbol_table: Default::default(),
270            diag_handler: Default::default(),
271            output: Box::new(Stdout),
272            exporters: Default::default(),
273            importers: Default::default(),
274        }
275    }
276}
277
278impl Lookup for Context {
279    fn lookup(&mut self, name: &QualifiedName) -> EvalResult<Symbol> {
280        log::debug!("Lookup symbol or property '{name}'");
281        let symbol = self.symbol_table.lookup(name);
282        let property = self.lookup_property(name);
283
284        match (&symbol, &property) {
285            (Ok(_), Err(_)) => {
286                log::debug!(
287                    "{found} symbol '{name:?}'",
288                    found = crate::mark!(FOUND_FINAL)
289                );
290                symbol
291            }
292            (Err(_), Ok(_)) => {
293                log::debug!(
294                    "{found} property '{name:?}'",
295                    found = crate::mark!(FOUND_FINAL)
296                );
297                property
298            }
299            (Ok(symbol), Ok(property)) => {
300                log::debug!(
301                    "{ambiguous} symbol '{name:?}' in {symbol} and {property}:\n{self}",
302                    ambiguous = crate::mark!(AMBIGUOUS),
303                );
304                Err(EvalError::AmbiguousProperty(
305                    symbol.full_name(),
306                    property.id(),
307                ))
308            }
309            // throw error from lookup on any error
310            (Err(_), Err(_)) => {
311                log::debug!(
312                    "{not_found} symbol or property '{name:?}'",
313                    not_found = crate::mark!(NOT_FOUND)
314                );
315                symbol
316            }
317        }
318    }
319}
320
321impl Diag for Context {
322    fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
323        self.diag_handler.pretty_print(f, &self.symbol_table)
324    }
325
326    fn error_count(&self) -> u32 {
327        self.diag_handler.error_count()
328    }
329
330    fn error_lines(&self) -> std::collections::HashSet<usize> {
331        self.diag_handler.error_lines()
332    }
333
334    fn warning_lines(&self) -> std::collections::HashSet<usize> {
335        self.diag_handler.warning_lines()
336    }
337}
338
339impl Context {
340    /// use symbol in context
341    pub fn use_symbol(
342        &mut self,
343        visibility: Visibility,
344        name: &QualifiedName,
345        id: Option<Identifier>,
346    ) -> EvalResult<Symbol> {
347        self.symbol_table
348            .use_symbol(visibility, name, id, &self.current_name())
349    }
350
351    /// use all symbols of given module in context
352    pub fn use_symbols_of(
353        &mut self,
354        visibility: Visibility,
355        name: &QualifiedName,
356    ) -> EvalResult<Symbol> {
357        self.symbol_table
358            .use_symbols_of(visibility, name, &self.current_name())
359    }
360}
361
362impl PushDiag for Context {
363    fn push_diag(&mut self, diag: Diagnostic) -> EvalResult<()> {
364        let result = self.diag_handler.push_diag(diag);
365        log::trace!("Error Context:\n{self}");
366        result
367    }
368}
369
370impl GetSourceByHash for Context {
371    fn get_by_hash(&self, hash: u64) -> ResolveResult<Rc<SourceFile>> {
372        self.symbol_table.get_by_hash(hash)
373    }
374}
375
376impl std::fmt::Display for Context {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        if let Ok(model) = self.get_model() {
379            write!(f, "\nModel:\n")?;
380            model.tree_print(f, 4.into())?;
381        }
382        if self.has_errors() {
383            writeln!(f, "{}\nErrors:", self.symbol_table)?;
384            self.diag_handler.pretty_print(f, &self.symbol_table)?;
385        } else {
386            write!(f, "{}", self.symbol_table)?;
387        }
388        Ok(())
389    }
390}
391
392impl ImporterRegistryAccess for Context {
393    type Error = EvalError;
394
395    fn import(
396        &mut self,
397        arg_map: &Tuple,
398        search_paths: &[std::path::PathBuf],
399    ) -> Result<Value, Self::Error> {
400        match self.importers.import(arg_map, search_paths) {
401            Ok(value) => Ok(value),
402            Err(err) => {
403                self.error(arg_map, err)?;
404                Ok(Value::None)
405            }
406        }
407    }
408}
409
410impl ExporterAccess for Context {
411    fn exporter_by_id(&self, id: &crate::Id) -> Result<Rc<dyn Exporter>, ExportError> {
412        self.exporters.exporter_by_id(id)
413    }
414
415    fn exporter_by_filename(
416        &self,
417        filename: &std::path::Path,
418    ) -> Result<Rc<dyn Exporter>, ExportError> {
419        self.exporters.exporter_by_filename(filename)
420    }
421}
422
423impl Grant<WorkbenchDefinition> for Context {
424    fn grant(&mut self, statement: &WorkbenchDefinition) -> EvalResult<()> {
425        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
426            matches!(
427                stack_frame,
428                StackFrame::Source(_, _) | StackFrame::Module(_, _)
429            )
430        } else {
431            false
432        };
433        if granted {
434            Ok(())
435        } else {
436            self.error(
437                statement,
438                EvalError::StatementNotSupported(statement.kind.as_str()),
439            )
440        }
441    }
442}
443
444impl Grant<ModuleDefinition> for Context {
445    fn grant(&mut self, statement: &ModuleDefinition) -> EvalResult<()> {
446        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
447            matches!(
448                stack_frame,
449                StackFrame::Source(_, _) | StackFrame::Module(_, _)
450            )
451        } else {
452            false
453        };
454        if granted {
455            Ok(())
456        } else {
457            self.error(statement, EvalError::StatementNotSupported("Module"))
458        }
459    }
460}
461
462impl Grant<FunctionDefinition> for Context {
463    fn grant(&mut self, statement: &FunctionDefinition) -> EvalResult<()> {
464        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
465            match stack_frame {
466                // TODO: check if expression generates models (see test `source_expression``)
467                StackFrame::Source(..) | StackFrame::Module(..) => true,
468                StackFrame::Workbench(..) => statement.visibility == Visibility::Private,
469                _ => false,
470            }
471        } else {
472            false
473        };
474        if granted {
475            Ok(())
476        } else {
477            self.error(statement, EvalError::StatementNotSupported("Function"))
478        }
479    }
480}
481impl Grant<InitDefinition> for Context {
482    fn grant(&mut self, statement: &InitDefinition) -> EvalResult<()> {
483        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
484            matches!(stack_frame, StackFrame::Workbench(..))
485        } else {
486            false
487        };
488        if granted {
489            Ok(())
490        } else {
491            self.error(statement, EvalError::StatementNotSupported("Init"))
492        }
493    }
494}
495
496impl Grant<UseStatement> for Context {
497    fn grant(&mut self, statement: &UseStatement) -> EvalResult<()> {
498        match (
499            &statement.visibility,
500            self.symbol_table.stack.current_frame(),
501        ) {
502            (Visibility::Private, _) => Ok(()),
503            (Visibility::Public, Some(StackFrame::Source(..) | StackFrame::Module(..))) => Ok(()),
504            _ => self.error(statement, EvalError::StatementNotSupported("Use")),
505        }
506    }
507}
508
509impl Grant<ReturnStatement> for Context {
510    fn grant(&mut self, statement: &ReturnStatement) -> EvalResult<()> {
511        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
512            matches!(stack_frame, StackFrame::Function(_))
513        } else {
514            false
515        };
516        if granted {
517            Ok(())
518        } else {
519            self.error(statement, EvalError::StatementNotSupported("Return"))
520        }
521    }
522}
523
524impl Grant<IfStatement> for Context {
525    fn grant(&mut self, statement: &IfStatement) -> EvalResult<()> {
526        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
527            matches!(
528                stack_frame,
529                StackFrame::Source(_, _)
530                    | StackFrame::Workbench(_, _, _)
531                    | StackFrame::Body(_)
532                    | StackFrame::Function(_)
533            )
534        } else {
535            false
536        };
537        if granted {
538            Ok(())
539        } else {
540            self.error(statement, EvalError::StatementNotSupported("If"))
541        }
542    }
543}
544
545impl Grant<AssignmentStatement> for Context {
546    fn grant(&mut self, statement: &AssignmentStatement) -> EvalResult<()> {
547        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
548            match statement.assignment.qualifier {
549                Qualifier::Const => {
550                    matches!(stack_frame, StackFrame::Source(..) | StackFrame::Module(..))
551                }
552                Qualifier::Value => {
553                    matches!(
554                        stack_frame,
555                        StackFrame::Source(..)
556                            | StackFrame::Module(..)
557                            | StackFrame::Body(_)
558                            | StackFrame::Workbench(..)
559                            | StackFrame::Init(_)
560                            | StackFrame::Function(_)
561                    )
562                }
563                Qualifier::Prop => matches!(stack_frame, StackFrame::Workbench(..)),
564            }
565        } else {
566            false
567        };
568        if granted {
569            Ok(())
570        } else {
571            self.error(statement, EvalError::StatementNotSupported("Assignment"))
572        }
573    }
574}
575
576impl Grant<ExpressionStatement> for Context {
577    fn grant(&mut self, statement: &ExpressionStatement) -> EvalResult<()> {
578        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
579            matches!(
580                stack_frame,
581                StackFrame::Source(_, _)
582                    | StackFrame::Body(_)
583                    | StackFrame::Workbench(_, _, _)
584                    | StackFrame::Function(_)
585            )
586        } else {
587            false
588        };
589        if granted {
590            Ok(())
591        } else {
592            self.error(statement, EvalError::StatementNotSupported("Expression"))
593        }
594    }
595}
596
597impl Grant<Marker> for Context {
598    fn grant(&mut self, statement: &Marker) -> EvalResult<()> {
599        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
600            matches!(stack_frame, StackFrame::Workbench(_, _, _))
601        } else {
602            false
603        };
604        if granted {
605            Ok(())
606        } else {
607            self.error(statement, EvalError::StatementNotSupported("Expression"))
608        }
609    }
610}
611
612impl Grant<crate::syntax::Attribute> for Context {
613    fn grant(&mut self, statement: &crate::syntax::Attribute) -> EvalResult<()> {
614        let granted = if let Some(stack_frame) = self.symbol_table.stack.current_frame() {
615            matches!(
616                stack_frame,
617                StackFrame::Source(_, _) | StackFrame::Body(_) | StackFrame::Workbench(_, _, _)
618            )
619        } else {
620            false
621        };
622        if granted {
623            Ok(())
624        } else {
625            self.error(
626                statement,
627                EvalError::StatementNotSupported("InnerAttribute"),
628            )
629        }
630    }
631}