diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 33a90ae42..82270dd31 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -1,9 +1,5 @@ use crate::prelude::*; -use comemo::Track; -use typst::model; -use typst::syntax::Source; - /// # Type /// Determine a value's type. /// @@ -92,32 +88,29 @@ pub fn assert(args: &mut Args) -> SourceResult { } /// # Evaluate -/// Evaluate a string as Typst markup. +/// Evaluate a string as Typst code. /// -/// You shouldn't typically need this function, but it is there if you do. +/// This function should only be used as a last resort. /// /// ## Example /// ```example -/// #let markup = "= Heading\n _Emphasis_" -/// #eval(markup) +/// #eval("1 + 2") \ +/// #eval("[*Strong text*]") \ +/// #eval("(1, 2, 3)").len() /// ``` /// /// ## Parameters /// - source: `String` (positional, required) -/// A string of Typst markup to evaluate. +/// A string of Typst code to evaluate. /// -/// The markup and code in the string cannot interact with the file system. +/// The code in the string cannot interact with the file system. /// -/// - returns: content +/// - returns: any /// /// ## Category /// foundations #[func] pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: text, span } = args.expect::>("source")?; - let source = Source::synthesized(text, span); - let route = model::Route::default(); - let mut tracer = model::Tracer::default(); - let module = model::eval(vm.world(), route.track(), tracer.track_mut(), &source)?; - Ok(Value::Content(module.content())) + typst::model::eval_code_str(vm.world(), &text, span) } diff --git a/src/model/eval.rs b/src/model/eval.rs index 1fbf41254..6e118f8a9 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -16,7 +16,9 @@ use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; use crate::syntax::ast::AstNode; -use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode}; +use crate::syntax::{ + ast, parse_code, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, +}; use crate::util::PathExt; use crate::World; @@ -63,6 +65,40 @@ pub fn eval( Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?)) } +/// 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_code_str( + world: Tracked, + text: &str, + span: Span, +) -> SourceResult { + let mut root = parse_code(text); + root.synthesize(span); + + let errors = root.errors(); + if !errors.is_empty() { + return Err(Box::new(errors)); + } + + let id = SourceId::detached(); + let library = world.library(); + let scopes = Scopes::new(Some(library)); + let route = Route::default(); + let mut tracer = Tracer::default(); + let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0); + let code = root.cast::().unwrap(); + let result = code.eval(&mut vm); + + // Handle control flow. + if let Some(flow) = vm.flow { + bail!(flow.forbidden()); + } + + result +} + /// A virtual machine. /// /// Holds the state needed to [evaluate](eval) Typst sources. A new diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 1fdb0a838..e153b0bf2 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -175,7 +175,7 @@ impl SyntaxNode { } /// Set a synthetic span for the node and all its descendants. - pub(super) fn synthesize(&mut self, span: Span) { + pub(crate) fn synthesize(&mut self, span: Span) { match &mut self.0 { Repr::Leaf(leaf) => leaf.span = span, Repr::Inner(inner) => Arc::make_mut(inner).synthesize(span), diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ index 602bc22d5..83cda65fb 100644 --- a/tests/typ/compute/foundations.typ +++ b/tests/typ/compute/foundations.typ @@ -27,14 +27,14 @@ #test(type(10 / 3), "float") --- -#eval("_Hello" + " World!_") +#eval("[_Hello" + " World!_]") --- -// Error: 7-13 expected identifier -#eval("#let") +// Error: 7-12 expected identifier +#eval("let") --- -#show raw: it => text("IBM Plex Sans", eval(it.text)) +#show raw: it => text("IBM Plex Sans", eval("[" + it.text + "]")) Interacting ``` @@ -43,28 +43,28 @@ Blue #move(dy: -0.15em)[🌊] ``` --- -// Error: 7-18 cannot continue outside of loop -#eval("#continue") +// Error: 7-17 cannot continue outside of loop +#eval("continue") --- -// Error: 7-33 cannot access file system from here -#eval("#include \"../coma.typ\"") +// Error: 7-32 cannot access file system from here +#eval("include \"../coma.typ\"") --- -// Error: 7-31 cannot access file system from here -#eval("#image(\"/tiger.jpg\")") +// Error: 7-30 cannot access file system from here +#eval("image(\"/tiger.jpg\")") --- // Error: 23-30 cannot access file system from here #show raw: it => eval(it.text) ``` -#image("/tiger.jpg") +image("/tiger.jpg") ``` --- -// Error: 23-30 cannot access file system from here -#show raw: it => eval(it.text) +// Error: 23-42 cannot access file system from here +#show raw: it => eval("[" + it.text + "]") ``` #show emph: _ => image("/giraffe.jpg") @@ -72,5 +72,5 @@ _No relative giraffe!_ ``` --- -// Error: 7-15 expected comma -#eval("#(1 2)") +// Error: 7-12 expected semicolon or line break +#eval("1 2")