Skip to main content

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, hint_if_shadowed_std};
21
22use self::access::*;
23use self::binding::*;
24use self::methods::*;
25
26use comemo::{Track, Tracked, TrackedMut};
27use typst_library::diag::{At, SourceResult, bail};
28use typst_library::engine::{Engine, Route, Sink, Traced};
29use typst_library::foundations::{Context, Module, NativeElement, Scope, Scopes, Value};
30use typst_library::introspection::{EmptyIntrospector, Introspector};
31use typst_library::math::EquationElem;
32use typst_library::routines::SpanMode;
33use typst_library::{Library, World};
34use typst_syntax::{Source, SyntaxMode, ast, parse, parse_code, parse_math};
35use typst_utils::{LazyHash, Protected};
36
37/// Evaluate a source file and return the resulting module.
38#[comemo::memoize]
39#[typst_macros::time(name = "eval", span = source.root().span())]
40pub fn eval(
41    world: Tracked<dyn World + '_>,
42    library: &LazyHash<Library>,
43    traced: Tracked<Traced>,
44    sink: TrackedMut<Sink>,
45    route: Tracked<Route>,
46    source: &Source,
47) -> SourceResult<Module> {
48    // Prevent cyclic evaluation.
49    let id = source.id();
50    if route.contains(id) {
51        panic!("Tried to cyclicly evaluate {:?}", id.vpath());
52    }
53
54    // Prepare the engine.
55    let introspector = EmptyIntrospector;
56    let engine = Engine {
57        library,
58        world,
59        introspector: Protected::new(introspector.track()),
60        traced,
61        sink,
62        route: Route::extend(route).with_id(id),
63    };
64
65    // Prepare VM.
66    let context = Context::none();
67    let scopes = Scopes::new(Some(library));
68    let root = source.root();
69    let mut vm = Vm::new(engine, context.track(), scopes, root.span());
70
71    // Check for errors or warnings in the syntax tree before evaluating it.
72    // However, if we're inspecting a span, we keep going with evaluation
73    // regardless of syntax errors.
74    let (errors, warnings) = root.errors_and_warnings();
75    for warning in warnings {
76        vm.engine.sink.warn(warning.into());
77    }
78    if !errors.is_empty() && vm.inspected.is_none() {
79        // We _could_ also return the warnings here with the errors, but we want
80        // to only use the sink for warnings for consistency.
81        return Err(errors.into_iter().map(Into::into).collect());
82    }
83
84    // Evaluate the module.
85    let markup = root.cast::<ast::Markup>().unwrap();
86    let output = markup.eval(&mut vm)?;
87
88    // Handle control flow.
89    if let Some(flow) = vm.flow {
90        bail!(flow.forbidden());
91    }
92
93    // Assemble the module.
94    let name = id.vpath().file_stem().unwrap_or_default();
95
96    Ok(Module::new(name, vm.scopes.top).with_content(output).with_file_id(id))
97}
98
99/// Evaluates a string in the given syntax `mode` and returns the resulting
100/// value.
101#[comemo::memoize]
102#[allow(clippy::too_many_arguments)]
103pub fn eval_string(
104    world: Tracked<dyn World + '_>,
105    library: &LazyHash<Library>,
106    mut sink: TrackedMut<Sink>,
107    introspector: Tracked<dyn Introspector + '_>,
108    context: Tracked<Context>,
109    string: &str,
110    spans: SpanMode,
111    mode: SyntaxMode,
112    scope: Scope,
113) -> SourceResult<Value> {
114    let mut root = match mode {
115        SyntaxMode::Code => parse_code(string),
116        SyntaxMode::Markup => parse(string),
117        SyntaxMode::Math => parse_math(string),
118    };
119
120    match spans {
121        SpanMode::Uniform(span) if span.is_detached() => {}
122        SpanMode::Uniform(span) => root.synthesize(span),
123        SpanMode::Mapped { id, mapper, mapper_error_span } => {
124            root.synthesize_mapped(id, mapper).at(mapper_error_span)?;
125        }
126    }
127
128    // Check for errors or warnings in the syntax tree before evaluating it.
129    let (errors, warnings) = root.errors_and_warnings();
130    for warning in warnings {
131        sink.warn(warning.into());
132    }
133    if !errors.is_empty() {
134        // We _could_ also return the warnings here with the errors, but we want
135        // to only use the sink for warnings for consistency.
136        return Err(errors.into_iter().map(Into::into).collect());
137    }
138
139    // Prepare the engine.
140    let traced = Traced::default();
141    let engine = Engine {
142        library,
143        world,
144        introspector: Protected::new(introspector),
145        traced: traced.track(),
146        sink,
147        route: Route::default(),
148    };
149
150    // Prepare VM.
151    let scopes = Scopes::new(Some(library));
152    let mut vm = Vm::new(engine, context, scopes, root.span());
153    vm.scopes.scopes.push(scope);
154
155    // Evaluate the code.
156    let output = match mode {
157        SyntaxMode::Code => root.cast::<ast::Code>().unwrap().eval(&mut vm)?,
158        SyntaxMode::Markup => {
159            Value::Content(root.cast::<ast::Markup>().unwrap().eval(&mut vm)?)
160        }
161        SyntaxMode::Math => Value::Content(
162            EquationElem::new(root.cast::<ast::Math>().unwrap().eval(&mut vm)?)
163                .with_block(false)
164                .pack()
165                .spanned(root.span()),
166        ),
167    };
168
169    // Handle control flow.
170    if let Some(flow) = vm.flow {
171        bail!(flow.forbidden());
172    }
173
174    Ok(output)
175}
176
177/// Evaluate an expression.
178pub trait Eval {
179    /// The output of evaluating the expression.
180    type Output;
181
182    /// Evaluate the expression to the output value.
183    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output>;
184}