summaryrefslogtreecommitdiff
path: root/crates/typst-eval/src/vm.rs
blob: 52cfb4b5b3445f90ab48a2bc770a1b457c38ab7e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use comemo::Tracked;
use typst_library::diag::warning;
use typst_library::engine::Engine;
use typst_library::foundations::{Binding, Context, IntoValue, Scopes, Value};
use typst_library::World;
use typst_syntax::ast::{self, AstNode};
use typst_syntax::Span;

use crate::FlowEvent;

/// A virtual machine.
///
/// Holds the state needed to [evaluate](crate::eval()) Typst sources. A
/// new virtual machine is created for each module evaluation and function call.
pub struct Vm<'a> {
    /// The underlying virtual typesetter.
    pub engine: Engine<'a>,
    /// A control flow event that is currently happening.
    pub flow: Option<FlowEvent>,
    /// The stack of scopes.
    pub scopes: Scopes<'a>,
    /// A span that is currently under inspection.
    pub inspected: Option<Span>,
    /// Data that is contextually made accessible to code behind the scenes.
    pub context: Tracked<'a, Context<'a>>,
}

impl<'a> Vm<'a> {
    /// Create a new virtual machine.
    pub fn new(
        engine: Engine<'a>,
        context: Tracked<'a, Context<'a>>,
        scopes: Scopes<'a>,
        target: Span,
    ) -> Self {
        let inspected = target.id().and_then(|id| engine.traced.get(id));
        Self { engine, context, flow: None, scopes, inspected }
    }

    /// Access the underlying world.
    pub fn world(&self) -> Tracked<'a, dyn World + 'a> {
        self.engine.world
    }

    /// Bind a value to an identifier.
    ///
    /// This will create a [`Binding`] with the value and the identifier's span.
    pub fn define(&mut self, var: ast::Ident, value: impl IntoValue) {
        self.bind(var, Binding::new(value, var.span()));
    }

    /// Insert a binding into the current scope.
    ///
    /// This will insert the value into the top-most scope and make it available
    /// for dynamic tracing, assisting IDE functionality.
    pub fn bind(&mut self, var: ast::Ident, binding: Binding) {
        if self.inspected == Some(var.span()) {
            self.trace(binding.read().clone());
        }

        // This will become an error in the parser if `is` becomes a keyword.
        if var.get() == "is" {
            self.engine.sink.warn(warning!(
                var.span(),
                "`is` will likely become a keyword in future versions and will \
                not be allowed as an identifier";
                hint: "rename this variable to avoid future errors";
                hint: "try `is_` instead"
            ));
        }

        self.scopes.top.bind(var.get().clone(), binding);
    }

    /// Trace a value.
    #[cold]
    pub fn trace(&mut self, value: Value) {
        self.engine
            .sink
            .value(value.clone(), self.context.styles().ok().map(|s| s.to_map()));
    }
}