summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs26
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/utility/mod.rs31
-rw-r--r--src/model/property.rs10
-rw-r--r--src/model/styles.rs3
-rw-r--r--src/source.rs14
-rw-r--r--src/syntax/mod.rs53
-rw-r--r--tests/ref/utility/eval.pngbin0 -> 5429 bytes
-rw-r--r--tests/typ/utility/eval.typ52
9 files changed, 163 insertions, 27 deletions
diff --git a/src/lib.rs b/src/lib.rs
index eb6e8f72..06324c11 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -54,6 +54,7 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::hash::Hash;
+use std::mem;
use std::path::PathBuf;
use std::sync::Arc;
@@ -141,25 +142,26 @@ impl Context {
let source = self.sources.get(id);
let ast = source.ast()?;
+ // Save the old context.
+ let prev_flow = self.flow.take();
+ let prev_deps = mem::replace(&mut self.deps, vec![(id, source.rev())]);
+ self.route.push(id);
+
+ // Evaluate the module.
let std = self.std.clone();
let mut scp = Scopes::new(Some(&std));
+ let result = ast.eval(self, &mut scp);
- // Evaluate the module.
- let prev = std::mem::replace(&mut self.deps, vec![(id, source.rev())]);
- self.route.push(id);
- let content = ast.eval(self, &mut scp);
+ // Restore the old context and handle control flow.
self.route.pop().unwrap();
- let deps = std::mem::replace(&mut self.deps, prev);
- let flow = self.flow.take();
-
- // Assemble the module.
- let module = Module { scope: scp.top, content: content?, deps };
-
- // Handle unhandled flow.
- if let Some(flow) = flow {
+ let deps = mem::replace(&mut self.deps, prev_deps);
+ if let Some(flow) = mem::replace(&mut self.flow, prev_flow) {
return Err(flow.forbidden());
}
+ // Assemble the module.
+ let module = Module { scope: scp.top, content: result?, deps };
+
// Save the evaluated module.
self.modules.insert(id, module.clone());
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 6a30badf..ac0cbb92 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -73,6 +73,7 @@ pub fn new() -> Scope {
// Utility.
std.def_fn("type", utility::type_);
std.def_fn("assert", utility::assert);
+ std.def_fn("eval", utility::eval);
std.def_fn("int", utility::int);
std.def_fn("float", utility::float);
std.def_fn("abs", utility::abs);
diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs
index 13220242..355315e4 100644
--- a/src/library/utility/mod.rs
+++ b/src/library/utility/mod.rs
@@ -8,7 +8,11 @@ pub use color::*;
pub use math::*;
pub use string::*;
+use std::mem;
+
+use crate::eval::{Eval, Scopes};
use crate::library::prelude::*;
+use crate::source::SourceFile;
/// The name of a value's type.
pub fn type_(_: &mut Context, args: &mut Args) -> TypResult<Value> {
@@ -23,3 +27,30 @@ pub fn assert(_: &mut Context, args: &mut Args) -> TypResult<Value> {
}
Ok(Value::None)
}
+
+/// Evaluate a string as Typst markup.
+pub fn eval(ctx: &mut Context, args: &mut Args) -> TypResult<Value> {
+ let Spanned { v: src, span } = args.expect::<Spanned<String>>("source")?;
+
+ // Parse the source and set a synthetic span for all nodes.
+ let mut source = SourceFile::detached(src);
+ source.synthesize(span);
+ let ast = source.ast()?;
+
+ // Save the old context, then detach it.
+ let prev_flow = ctx.flow.take();
+ let prev_route = mem::take(&mut ctx.route);
+
+ // Evaluate the source.
+ let std = ctx.std.clone();
+ let mut scp = Scopes::new(Some(&std));
+ let result = ast.eval(ctx, &mut scp);
+
+ // Restore the old context and handle control flow.
+ ctx.route = prev_route;
+ if let Some(flow) = mem::replace(&mut ctx.flow, prev_flow) {
+ return Err(flow.forbidden());
+ }
+
+ Ok(Value::Content(result?))
+}
diff --git a/src/model/property.rs b/src/model/property.rs
index b35ffba9..ff686970 100644
--- a/src/model/property.rs
+++ b/src/model/property.rs
@@ -131,13 +131,13 @@ impl Debug for KeyId {
pub trait Key<'a>: Copy + 'static {
/// The unfolded type which this property is stored as in a style map. For
/// example, this is [`Toggle`](crate::geom::Length) for the
- /// [`STRONG`](TextNode::STRONG) property.
+ /// [`STRONG`](crate::library::text::TextNode::STRONG) property.
type Value: Debug + Clone + Hash + Sync + Send + 'static;
/// The folded type of value that is returned when reading this property
/// from a style chain. For example, this is [`bool`] for the
- /// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding
- /// properties this is a reference type.
+ /// [`STRONG`](crate::library::text::TextNode::STRONG) property. For
+ /// non-copy, non-folding properties this is a reference type.
type Output;
/// The name of the property, used for debug printing.
@@ -274,8 +274,8 @@ impl Fold for Sides<Option<Smart<Relative<RawLength>>>> {
/// A scoped property barrier.
///
-/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style
-/// can still be read through a single barrier (the one of the node it
+/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
+/// style can still be read through a single barrier (the one of the node it
/// _should_ apply to), but a second barrier will make it invisible.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Barrier(NodeId);
diff --git a/src/model/styles.rs b/src/model/styles.rs
index 928133c7..5c36861a 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -103,7 +103,8 @@ impl StyleMap {
/// Mark all contained properties as _scoped_. This means that they only
/// apply to the first descendant node (of their type) in the hierarchy and
- /// not its children, too. This is used by [constructors](Node::construct).
+ /// not its children, too. This is used by
+ /// [constructors](crate::eval::Node::construct).
pub fn scoped(mut self) -> Self {
for entry in &mut self.0 {
if let StyleEntry::Property(property) = entry {
diff --git a/src/source.rs b/src/source.rs
index 780e12a8..deeaef4b 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -12,7 +12,7 @@ use crate::diag::TypResult;
use crate::loading::{FileHash, Loader};
use crate::parse::{is_newline, parse, reparse};
use crate::syntax::ast::Markup;
-use crate::syntax::{self, Category, GreenNode, RedNode};
+use crate::syntax::{self, Category, GreenNode, RedNode, Span};
use crate::util::{PathExt, StrExt};
#[cfg(feature = "codespan-reporting")]
@@ -23,6 +23,11 @@ use codespan_reporting::files::{self, Files};
pub struct SourceId(u32);
impl SourceId {
+ /// Create a new source id for a file that is not part of a store.
+ pub const fn detached() -> Self {
+ Self(u32::MAX)
+ }
+
/// Create a source id from the raw underlying value.
///
/// This should only be called with values returned by
@@ -165,7 +170,12 @@ impl SourceFile {
/// Create a source file without a real id and path, usually for testing.
pub fn detached(src: impl Into<String>) -> Self {
- Self::new(SourceId(0), Path::new(""), src.into())
+ Self::new(SourceId::detached(), Path::new(""), src.into())
+ }
+
+ /// Set a synthetic span for all nodes in this file.
+ pub fn synthesize(&mut self, span: Span) {
+ Arc::make_mut(&mut self.root).synthesize(Arc::new(span));
}
/// The root node of the file's untyped green tree.
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index abe541b8..29e2718b 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -81,6 +81,14 @@ impl Green {
Self::Token(data) => data.kind = kind,
}
}
+
+ /// Set a synthetic span for the node and all its children.
+ pub fn synthesize(&mut self, span: Arc<Span>) {
+ match self {
+ Green::Node(n) => Arc::make_mut(n).synthesize(span),
+ Green::Token(t) => t.synthesize(span),
+ }
+ }
}
impl Default for Green {
@@ -151,6 +159,14 @@ impl GreenNode {
self.data().len()
}
+ /// Set a synthetic span for the node and all its children.
+ pub fn synthesize(&mut self, span: Arc<Span>) {
+ self.data.synthesize(span.clone());
+ for child in &mut self.children {
+ child.synthesize(span.clone());
+ }
+ }
+
/// The node's children, mutably.
pub(crate) fn children_mut(&mut self) -> &mut [Green] {
&mut self.children
@@ -214,12 +230,14 @@ pub struct GreenData {
kind: NodeKind,
/// The byte length of the node in the source.
len: usize,
+ /// A synthetic span for the node, usually this is `None`.
+ span: Option<Arc<Span>>,
}
impl GreenData {
/// Create new node metadata.
pub fn new(kind: NodeKind, len: usize) -> Self {
- Self { len, kind }
+ Self { len, kind, span: None }
}
/// The type of the node.
@@ -231,6 +249,11 @@ impl GreenData {
pub fn len(&self) -> usize {
self.len
}
+
+ /// Set a synthetic span for the node.
+ pub fn synthesize(&mut self, span: Arc<Span>) {
+ self.span = Some(span)
+ }
}
impl From<GreenData> for Green {
@@ -270,6 +293,11 @@ impl RedNode {
}
}
+ /// The node's metadata.
+ pub fn data(&self) -> &GreenData {
+ self.as_ref().data()
+ }
+
/// The type of the node.
pub fn kind(&self) -> &NodeKind {
self.as_ref().kind()
@@ -340,6 +368,11 @@ impl<'a> RedRef<'a> {
}
}
+ /// The node's metadata.
+ pub fn data(self) -> &'a GreenData {
+ self.green.data()
+ }
+
/// The type of the node.
pub fn kind(self) -> &'a NodeKind {
self.green.kind()
@@ -352,7 +385,10 @@ impl<'a> RedRef<'a> {
/// The span of the node.
pub fn span(self) -> Span {
- Span::new(self.id, self.offset, self.offset + self.green.len())
+ match self.data().span.as_deref() {
+ Some(&span) => span,
+ None => Span::new(self.id, self.offset, self.offset + self.len()),
+ }
}
/// Whether the node is a leaf node.
@@ -368,11 +404,14 @@ impl<'a> RedRef<'a> {
match self.kind() {
NodeKind::Error(pos, msg) => {
- let span = match pos {
- ErrorPos::Start => self.span().at_start(),
- ErrorPos::Full => self.span(),
- ErrorPos::End => self.span().at_end(),
- };
+ let mut span = self.span();
+ if self.data().span.is_none() {
+ span = match pos {
+ ErrorPos::Start => span.at_start(),
+ ErrorPos::Full => span,
+ ErrorPos::End => span.at_end(),
+ };
+ }
vec![Error::new(span, msg.to_string())]
}
diff --git a/tests/ref/utility/eval.png b/tests/ref/utility/eval.png
new file mode 100644
index 00000000..38c1d64e
--- /dev/null
+++ b/tests/ref/utility/eval.png
Binary files differ
diff --git a/tests/typ/utility/eval.typ b/tests/typ/utility/eval.typ
new file mode 100644
index 00000000..86b1f0c4
--- /dev/null
+++ b/tests/typ/utility/eval.typ
@@ -0,0 +1,52 @@
+// Test the `eval` function.
+
+---
+#eval("_Hello" + " World!_")
+
+---
+// Error: 7-13 expected identifier
+#eval("#let")
+
+---
+#set raw(around: none)
+#show it: raw as text("IBM Plex Sans", eval(it.text))
+
+Interacting
+```
+#set text(blue)
+Blue #move(dy: -0.15em)[🌊]
+```
+
+---
+// Error: 7-19 cannot continue outside of loop
+#eval("{continue}")
+
+---
+// Error: 7-33 cannot access file system from here
+#eval("#include \"../coma.typ\"")
+
+---
+// Error: 7-35 cannot access file system from here
+#eval("#image(\"/res/tiger.jpg\")")
+
+---
+// Error: 23-30 cannot access file system from here
+#show it: raw as eval(it.text)
+
+```
+#show strong as image("/res/tiger.jpg")
+*No absolute tiger!*
+```
+
+---
+// Error: 23-30 cannot access file system from here
+#show it: raw as eval(it.text)
+
+```
+#show emph as image("../../res/giraffe.jpg")
+_No relative giraffe!_
+```
+
+---
+// Error: 7-16 expected comma
+#eval("{(1 2)}")