Safe eval
function
This commit is contained in:
parent
a741bd6b83
commit
242b01549a
28
src/lib.rs
28
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()?;
|
||||
|
||||
let std = self.std.clone();
|
||||
let mut scp = Scopes::new(Some(&std));
|
||||
// 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 prev = std::mem::replace(&mut self.deps, vec![(id, source.rev())]);
|
||||
self.route.push(id);
|
||||
let content = ast.eval(self, &mut scp);
|
||||
let std = self.std.clone();
|
||||
let mut scp = Scopes::new(Some(&std));
|
||||
let result = 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());
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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?))
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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())]
|
||||
}
|
||||
|
BIN
tests/ref/utility/eval.png
Normal file
BIN
tests/ref/utility/eval.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
52
tests/typ/utility/eval.typ
Normal file
52
tests/typ/utility/eval.typ
Normal file
@ -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)}")
|
Loading…
x
Reference in New Issue
Block a user