typst_eval/
vm.rs

1use comemo::Tracked;
2use typst_library::World;
3use typst_library::diag::warning;
4use typst_library::engine::Engine;
5use typst_library::foundations::{Binding, Context, IntoValue, Scopes, Value};
6use typst_syntax::Span;
7use typst_syntax::ast::{self, AstNode};
8
9use crate::FlowEvent;
10
11/// A virtual machine.
12///
13/// Holds the state needed to [evaluate](crate::eval()) Typst sources. A
14/// new virtual machine is created for each module evaluation and function call.
15pub struct Vm<'a> {
16    /// The underlying virtual typesetter.
17    pub engine: Engine<'a>,
18    /// A control flow event that is currently happening.
19    pub flow: Option<FlowEvent>,
20    /// The stack of scopes.
21    pub scopes: Scopes<'a>,
22    /// A span that is currently under inspection.
23    pub inspected: Option<Span>,
24    /// Data that is contextually made accessible to code behind the scenes.
25    pub context: Tracked<'a, Context<'a>>,
26}
27
28impl<'a> Vm<'a> {
29    /// Create a new virtual machine.
30    pub fn new(
31        engine: Engine<'a>,
32        context: Tracked<'a, Context<'a>>,
33        scopes: Scopes<'a>,
34        target: Span,
35    ) -> Self {
36        let inspected = target.id().and_then(|id| engine.traced.get(id));
37        Self { engine, context, flow: None, scopes, inspected }
38    }
39
40    /// Access the underlying world.
41    pub fn world(&self) -> Tracked<'a, dyn World + 'a> {
42        self.engine.world
43    }
44
45    /// Bind a value to an identifier.
46    ///
47    /// This will create a [`Binding`] with the value and the identifier's span.
48    pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
49        self.bind(var, Binding::new(value, var.span()));
50    }
51
52    /// Insert a binding into the current scope.
53    ///
54    /// This will insert the value into the top-most scope and make it available
55    /// for dynamic tracing, assisting IDE functionality.
56    pub fn bind(&mut self, var: ast::Ident, binding: Binding) {
57        if self.inspected == Some(var.span()) {
58            self.trace(binding.read().clone());
59        }
60
61        // This will become an error in the parser if `is` becomes a keyword.
62        if var.get() == "is" {
63            self.engine.sink.warn(warning!(
64                var.span(),
65                "`is` will likely become a keyword in future versions and will \
66                not be allowed as an identifier";
67                hint: "rename this variable to avoid future errors";
68                hint: "try `is_` instead"
69            ));
70        }
71
72        self.scopes.top.bind(var.get().clone(), binding);
73    }
74
75    /// Trace a value.
76    #[cold]
77    pub fn trace(&mut self, value: Value) {
78        self.engine
79            .sink
80            .value(value.clone(), self.context.styles().ok().map(|s| s.to_map()));
81    }
82}