Skip to main content

typst_eval/
vm.rs

1use comemo::Tracked;
2use ecow::eco_format;
3use typst_library::World;
4use typst_library::diag::{HintedString, warning};
5use typst_library::engine::Engine;
6use typst_library::foundations::{Binding, Context, IntoValue, Scopes, Value};
7use typst_syntax::Span;
8use typst_syntax::ast::{self, AstNode};
9
10use crate::FlowEvent;
11
12/// A virtual machine.
13///
14/// Holds the state needed to [evaluate](crate::eval()) Typst sources. A
15/// new virtual machine is created for each module evaluation and function call.
16pub struct Vm<'a> {
17    /// The underlying virtual typesetter.
18    pub engine: Engine<'a>,
19    /// A control flow event that is currently happening.
20    pub flow: Option<FlowEvent>,
21    /// The stack of scopes.
22    pub scopes: Scopes<'a>,
23    /// A span that is currently under inspection. If this is `Some`, we're in
24    /// tracing mode, and will record every value the given span sees.
25    pub inspected: Option<Span>,
26    /// Data that is contextually made accessible to code behind the scenes.
27    pub context: Tracked<'a, Context<'a>>,
28}
29
30impl<'a> Vm<'a> {
31    /// Create a new virtual machine.
32    pub fn new(
33        engine: Engine<'a>,
34        context: Tracked<'a, Context<'a>>,
35        scopes: Scopes<'a>,
36        target: Span,
37    ) -> Self {
38        let inspected = target.id().and_then(|id| engine.traced.get(id));
39        Self { engine, context, flow: None, scopes, inspected }
40    }
41
42    /// Access the underlying world.
43    pub fn world(&self) -> Tracked<'a, dyn World + 'a> {
44        self.engine.world
45    }
46
47    /// Bind a value to an identifier.
48    ///
49    /// This will create a [`Binding`] with the value and the identifier's span.
50    pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
51        self.bind(var, Binding::new(value, var.span()));
52    }
53
54    /// Insert a binding into the current scope.
55    ///
56    /// This will insert the value into the top-most scope and make it available
57    /// for dynamic tracing, assisting IDE functionality.
58    pub fn bind(&mut self, var: ast::Ident, binding: Binding) {
59        self.trace_at(var.span(), binding.read());
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    /// Helper to only call [`Self::trace`] for a value if we're inspecting its
76    /// span. This method (or `trace`) should be called for every value produced
77    /// by an expression.
78    pub fn trace_at(&mut self, span: Span, value: &Value) {
79        if self.inspected == Some(span) {
80            self.trace(value.clone());
81        }
82    }
83
84    /// Trace a value. Tracing powers IDE tooltips and hover info. This method
85    /// should be called for every value produced by an expression.
86    #[cold]
87    pub fn trace(&mut self, value: Value) {
88        self.engine
89            .sink
90            .value(value, self.context.styles().ok().map(|s| s.to_map()));
91    }
92}
93
94/// Provide a hint if the callee is a shadowed standard library function.
95pub fn hint_if_shadowed_std(
96    vm: &mut Vm,
97    callee: &ast::Expr,
98    mut err: HintedString,
99) -> HintedString {
100    if let ast::Expr::Ident(ident) = callee {
101        let ident = ident.get();
102        if vm.scopes.check_std_shadowed(ident) {
103            err.hint(eco_format!(
104                "use `std.{ident}` to access the shadowed standard library function",
105            ));
106        }
107    }
108    err
109}