Safe eval function

This commit is contained in:
Laurenz 2022-05-16 19:13:39 +02:00
parent a741bd6b83
commit 242b01549a
9 changed files with 164 additions and 28 deletions

View File

@ -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());

View File

@ -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);

View File

@ -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?))
}

View File

@ -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);

View File

@ -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 {

View File

@ -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.

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View 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)}")