typst_eval/
lib.rs

1//! Typst's code interpreter.
2
3pub(crate) mod ops;
4
5mod access;
6mod binding;
7mod call;
8mod code;
9mod flow;
10mod import;
11mod markup;
12mod math;
13mod methods;
14mod rules;
15mod vm;
16
17pub use self::call::{CapturesVisitor, eval_closure};
18pub use self::flow::FlowEvent;
19pub use self::import::import;
20pub use self::vm::Vm;
21
22use self::access::*;
23use self::binding::*;
24use self::methods::*;
25
26use comemo::{Track, Tracked, TrackedMut};
27use typst_library::World;
28use typst_library::diag::{SourceResult, bail};
29use typst_library::engine::{Engine, Route, Sink, Traced};
30use typst_library::foundations::{Context, Module, NativeElement, Scope, Scopes, Value};
31use typst_library::introspection::Introspector;
32use typst_library::math::EquationElem;
33use typst_library::routines::Routines;
34use typst_syntax::{Source, Span, SyntaxMode, ast, parse, parse_code, parse_math};
35
36/// Evaluate a source file and return the resulting module.
37#[comemo::memoize]
38#[typst_macros::time(name = "eval", span = source.root().span())]
39pub fn eval(
40    routines: &Routines,
41    world: Tracked<dyn World + '_>,
42    traced: Tracked<Traced>,
43    sink: TrackedMut<Sink>,
44    route: Tracked<Route>,
45    source: &Source,
46) -> SourceResult<Module> {
47    // Prevent cyclic evaluation.
48    let id = source.id();
49    if route.contains(id) {
50        panic!("Tried to cyclicly evaluate {:?}", id.vpath());
51    }
52
53    // Prepare the engine.
54    let introspector = Introspector::default();
55    let engine = Engine {
56        routines,
57        world,
58        introspector: introspector.track(),
59        traced,
60        sink,
61        route: Route::extend(route).with_id(id),
62    };
63
64    // Prepare VM.
65    let context = Context::none();
66    let scopes = Scopes::new(Some(world.library()));
67    let root = source.root();
68    let mut vm = Vm::new(engine, context.track(), scopes, root.span());
69
70    // Check for well-formedness unless we are in trace mode.
71    let errors = root.errors();
72    if !errors.is_empty() && vm.inspected.is_none() {
73        return Err(errors.into_iter().map(Into::into).collect());
74    }
75
76    // Evaluate the module.
77    let markup = root.cast::<ast::Markup>().unwrap();
78    let output = markup.eval(&mut vm)?;
79
80    // Handle control flow.
81    if let Some(flow) = vm.flow {
82        bail!(flow.forbidden());
83    }
84
85    // Assemble the module.
86    let name = id
87        .vpath()
88        .as_rootless_path()
89        .file_stem()
90        .unwrap_or_default()
91        .to_string_lossy();
92
93    Ok(Module::new(name, vm.scopes.top).with_content(output).with_file_id(id))
94}
95
96/// Evaluate a string as code and return the resulting value.
97///
98/// Everything in the output is associated with the given `span`.
99#[comemo::memoize]
100pub fn eval_string(
101    routines: &Routines,
102    world: Tracked<dyn World + '_>,
103    sink: TrackedMut<Sink>,
104    string: &str,
105    span: Span,
106    mode: SyntaxMode,
107    scope: Scope,
108) -> SourceResult<Value> {
109    let mut root = match mode {
110        SyntaxMode::Code => parse_code(string),
111        SyntaxMode::Markup => parse(string),
112        SyntaxMode::Math => parse_math(string),
113    };
114
115    root.synthesize(span);
116
117    // Check for well-formedness.
118    let errors = root.errors();
119    if !errors.is_empty() {
120        return Err(errors.into_iter().map(Into::into).collect());
121    }
122
123    // Prepare the engine.
124    let introspector = Introspector::default();
125    let traced = Traced::default();
126    let engine = Engine {
127        routines,
128        world,
129        introspector: introspector.track(),
130        traced: traced.track(),
131        sink,
132        route: Route::default(),
133    };
134
135    // Prepare VM.
136    let context = Context::none();
137    let scopes = Scopes::new(Some(world.library()));
138    let mut vm = Vm::new(engine, context.track(), scopes, root.span());
139    vm.scopes.scopes.push(scope);
140
141    // Evaluate the code.
142    let output = match mode {
143        SyntaxMode::Code => root.cast::<ast::Code>().unwrap().eval(&mut vm)?,
144        SyntaxMode::Markup => {
145            Value::Content(root.cast::<ast::Markup>().unwrap().eval(&mut vm)?)
146        }
147        SyntaxMode::Math => Value::Content(
148            EquationElem::new(root.cast::<ast::Math>().unwrap().eval(&mut vm)?)
149                .with_block(false)
150                .pack()
151                .spanned(span),
152        ),
153    };
154
155    // Handle control flow.
156    if let Some(flow) = vm.flow {
157        bail!(flow.forbidden());
158    }
159
160    Ok(output)
161}
162
163/// Evaluate an expression.
164pub trait Eval {
165    /// The output of evaluating the expression.
166    type Output;
167
168    /// Evaluate the expression to the output value.
169    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output>;
170}