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::{eval_closure, CapturesVisitor};
18pub use self::flow::FlowEvent;
19pub use self::import::import;
20pub use self::vm::Vm;
21pub use typst_library::routines::EvalMode;
22
23use self::access::*;
24use self::binding::*;
25use self::methods::*;
26
27use comemo::{Track, Tracked, TrackedMut};
28use typst_library::diag::{bail, SourceResult};
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_library::World;
35use typst_syntax::{ast, parse, parse_code, parse_math, Source, Span};
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    routines: &Routines,
42    world: Tracked<dyn World + '_>,
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 = Introspector::default();
56    let engine = Engine {
57        routines,
58        world,
59        introspector: 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(world.library()));
68    let root = source.root();
69    let mut vm = Vm::new(engine, context.track(), scopes, root.span());
70
71    // Check for well-formedness unless we are in trace mode.
72    let errors = root.errors();
73    if !errors.is_empty() && vm.inspected.is_none() {
74        return Err(errors.into_iter().map(Into::into).collect());
75    }
76
77    // Evaluate the module.
78    let markup = root.cast::<ast::Markup>().unwrap();
79    let output = markup.eval(&mut vm)?;
80
81    // Handle control flow.
82    if let Some(flow) = vm.flow {
83        bail!(flow.forbidden());
84    }
85
86    // Assemble the module.
87    let name = id
88        .vpath()
89        .as_rootless_path()
90        .file_stem()
91        .unwrap_or_default()
92        .to_string_lossy();
93
94    Ok(Module::new(name, vm.scopes.top).with_content(output).with_file_id(id))
95}
96
97/// Evaluate a string as code and return the resulting value.
98///
99/// Everything in the output is associated with the given `span`.
100#[comemo::memoize]
101pub fn eval_string(
102    routines: &Routines,
103    world: Tracked<dyn World + '_>,
104    string: &str,
105    span: Span,
106    mode: EvalMode,
107    scope: Scope,
108) -> SourceResult<Value> {
109    let mut root = match mode {
110        EvalMode::Code => parse_code(string),
111        EvalMode::Markup => parse(string),
112        EvalMode::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 mut sink = Sink::new();
125    let introspector = Introspector::default();
126    let traced = Traced::default();
127    let engine = Engine {
128        routines,
129        world,
130        introspector: introspector.track(),
131        traced: traced.track(),
132        sink: sink.track_mut(),
133        route: Route::default(),
134    };
135
136    // Prepare VM.
137    let context = Context::none();
138    let scopes = Scopes::new(Some(world.library()));
139    let mut vm = Vm::new(engine, context.track(), scopes, root.span());
140    vm.scopes.scopes.push(scope);
141
142    // Evaluate the code.
143    let output = match mode {
144        EvalMode::Code => root.cast::<ast::Code>().unwrap().eval(&mut vm)?,
145        EvalMode::Markup => {
146            Value::Content(root.cast::<ast::Markup>().unwrap().eval(&mut vm)?)
147        }
148        EvalMode::Math => Value::Content(
149            EquationElem::new(root.cast::<ast::Math>().unwrap().eval(&mut vm)?)
150                .with_block(false)
151                .pack()
152                .spanned(span),
153        ),
154    };
155
156    // Handle control flow.
157    if let Some(flow) = vm.flow {
158        bail!(flow.forbidden());
159    }
160
161    Ok(output)
162}
163
164/// Evaluate an expression.
165pub trait Eval {
166    /// The output of evaluating the expression.
167    type Output;
168
169    /// Evaluate the expression to the output value.
170    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output>;
171}