summaryrefslogtreecommitdiff
path: root/crates/typst-eval/src/lib.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-10-27 19:04:55 +0100
committerGitHub <noreply@github.com>2024-10-27 18:04:55 +0000
commitbe7cfc85d08c545abfac08098b7b33b4bd71f37e (patch)
treef4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-eval/src/lib.rs
parentb8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff)
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-eval/src/lib.rs')
-rw-r--r--crates/typst-eval/src/lib.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/crates/typst-eval/src/lib.rs b/crates/typst-eval/src/lib.rs
new file mode 100644
index 00000000..a5c0c7e3
--- /dev/null
+++ b/crates/typst-eval/src/lib.rs
@@ -0,0 +1,170 @@
+//! Typst's code interpreter.
+
+pub(crate) mod ops;
+
+mod access;
+mod binding;
+mod call;
+mod code;
+mod flow;
+mod import;
+mod markup;
+mod math;
+mod methods;
+mod rules;
+mod vm;
+
+pub use self::call::*;
+pub use self::import::*;
+pub use self::vm::*;
+pub use typst_library::routines::EvalMode;
+
+use self::access::*;
+use self::binding::*;
+use self::flow::*;
+use self::methods::*;
+
+use comemo::{Track, Tracked, TrackedMut};
+use typst_library::diag::{bail, SourceResult};
+use typst_library::engine::{Engine, Route, Sink, Traced};
+use typst_library::foundations::{Context, Module, NativeElement, Scope, Scopes, Value};
+use typst_library::introspection::Introspector;
+use typst_library::math::EquationElem;
+use typst_library::routines::Routines;
+use typst_library::World;
+use typst_syntax::{ast, parse, parse_code, parse_math, Source, Span};
+
+/// Evaluate a source file and return the resulting module.
+#[comemo::memoize]
+#[typst_macros::time(name = "eval", span = source.root().span())]
+pub fn eval(
+ routines: &Routines,
+ world: Tracked<dyn World + '_>,
+ traced: Tracked<Traced>,
+ sink: TrackedMut<Sink>,
+ route: Tracked<Route>,
+ source: &Source,
+) -> SourceResult<Module> {
+ // Prevent cyclic evaluation.
+ let id = source.id();
+ if route.contains(id) {
+ panic!("Tried to cyclicly evaluate {:?}", id.vpath());
+ }
+
+ // Prepare the engine.
+ let introspector = Introspector::default();
+ let engine = Engine {
+ routines,
+ world,
+ introspector: introspector.track(),
+ traced,
+ sink,
+ route: Route::extend(route).with_id(id),
+ };
+
+ // Prepare VM.
+ let context = Context::none();
+ let scopes = Scopes::new(Some(world.library()));
+ let root = source.root();
+ let mut vm = Vm::new(engine, context.track(), scopes, root.span());
+
+ // Check for well-formedness unless we are in trace mode.
+ let errors = root.errors();
+ if !errors.is_empty() && vm.inspected.is_none() {
+ return Err(errors.into_iter().map(Into::into).collect());
+ }
+
+ // Evaluate the module.
+ let markup = root.cast::<ast::Markup>().unwrap();
+ let output = markup.eval(&mut vm)?;
+
+ // Handle control flow.
+ if let Some(flow) = vm.flow {
+ bail!(flow.forbidden());
+ }
+
+ // Assemble the module.
+ let name = id
+ .vpath()
+ .as_rootless_path()
+ .file_stem()
+ .unwrap_or_default()
+ .to_string_lossy();
+
+ Ok(Module::new(name, vm.scopes.top).with_content(output).with_file_id(id))
+}
+
+/// Evaluate a string as code and return the resulting value.
+///
+/// Everything in the output is associated with the given `span`.
+#[comemo::memoize]
+pub fn eval_string(
+ routines: &Routines,
+ world: Tracked<dyn World + '_>,
+ string: &str,
+ span: Span,
+ mode: EvalMode,
+ scope: Scope,
+) -> SourceResult<Value> {
+ let mut root = match mode {
+ EvalMode::Code => parse_code(string),
+ EvalMode::Markup => parse(string),
+ EvalMode::Math => parse_math(string),
+ };
+
+ root.synthesize(span);
+
+ // Check for well-formedness.
+ let errors = root.errors();
+ if !errors.is_empty() {
+ return Err(errors.into_iter().map(Into::into).collect());
+ }
+
+ // Prepare the engine.
+ let mut sink = Sink::new();
+ let introspector = Introspector::default();
+ let traced = Traced::default();
+ let engine = Engine {
+ routines,
+ world,
+ introspector: introspector.track(),
+ traced: traced.track(),
+ sink: sink.track_mut(),
+ route: Route::default(),
+ };
+
+ // Prepare VM.
+ let context = Context::none();
+ let scopes = Scopes::new(Some(world.library()));
+ let mut vm = Vm::new(engine, context.track(), scopes, root.span());
+ vm.scopes.scopes.push(scope);
+
+ // Evaluate the code.
+ let output = match mode {
+ EvalMode::Code => root.cast::<ast::Code>().unwrap().eval(&mut vm)?,
+ EvalMode::Markup => {
+ Value::Content(root.cast::<ast::Markup>().unwrap().eval(&mut vm)?)
+ }
+ EvalMode::Math => Value::Content(
+ EquationElem::new(root.cast::<ast::Math>().unwrap().eval(&mut vm)?)
+ .with_block(false)
+ .pack(),
+ ),
+ };
+
+ // Handle control flow.
+ if let Some(flow) = vm.flow {
+ bail!(flow.forbidden());
+ }
+
+ Ok(output)
+}
+
+/// Evaluate an expression.
+pub trait Eval {
+ /// The output of evaluating the expression.
+ type Output;
+
+ /// Evaluate the expression to the output value.
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output>;
+}