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::collections::HashMap;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::hash::Hash; use std::hash::Hash;
use std::mem;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -141,25 +142,26 @@ impl Context {
let source = self.sources.get(id); let source = self.sources.get(id);
let ast = source.ast()?; let ast = source.ast()?;
let std = self.std.clone(); // Save the old context.
let mut scp = Scopes::new(Some(&std)); 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. // Evaluate the module.
let prev = std::mem::replace(&mut self.deps, vec![(id, source.rev())]); let std = self.std.clone();
self.route.push(id); let mut scp = Scopes::new(Some(&std));
let content = ast.eval(self, &mut scp); let result = ast.eval(self, &mut scp);
// Restore the old context and handle control flow.
self.route.pop().unwrap(); self.route.pop().unwrap();
let deps = std::mem::replace(&mut self.deps, prev); let deps = mem::replace(&mut self.deps, prev_deps);
let flow = self.flow.take(); if let Some(flow) = mem::replace(&mut self.flow, prev_flow) {
// Assemble the module.
let module = Module { scope: scp.top, content: content?, deps };
// Handle unhandled flow.
if let Some(flow) = flow {
return Err(flow.forbidden()); return Err(flow.forbidden());
} }
// Assemble the module.
let module = Module { scope: scp.top, content: result?, deps };
// Save the evaluated module. // Save the evaluated module.
self.modules.insert(id, module.clone()); self.modules.insert(id, module.clone());

View File

@ -73,6 +73,7 @@ pub fn new() -> Scope {
// Utility. // Utility.
std.def_fn("type", utility::type_); std.def_fn("type", utility::type_);
std.def_fn("assert", utility::assert); std.def_fn("assert", utility::assert);
std.def_fn("eval", utility::eval);
std.def_fn("int", utility::int); std.def_fn("int", utility::int);
std.def_fn("float", utility::float); std.def_fn("float", utility::float);
std.def_fn("abs", utility::abs); std.def_fn("abs", utility::abs);

View File

@ -8,7 +8,11 @@ pub use color::*;
pub use math::*; pub use math::*;
pub use string::*; pub use string::*;
use std::mem;
use crate::eval::{Eval, Scopes};
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::source::SourceFile;
/// The name of a value's type. /// The name of a value's type.
pub fn type_(_: &mut Context, args: &mut Args) -> TypResult<Value> { 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) 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 { pub trait Key<'a>: Copy + 'static {
/// The unfolded type which this property is stored as in a style map. For /// The unfolded type which this property is stored as in a style map. For
/// example, this is [`Toggle`](crate::geom::Length) for the /// 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; type Value: Debug + Clone + Hash + Sync + Send + 'static;
/// The folded type of value that is returned when reading this property /// The folded type of value that is returned when reading this property
/// from a style chain. For example, this is [`bool`] for the /// from a style chain. For example, this is [`bool`] for the
/// [`STRONG`](TextNode::STRONG) property. For non-copy, non-folding /// [`STRONG`](crate::library::text::TextNode::STRONG) property. For
/// properties this is a reference type. /// non-copy, non-folding properties this is a reference type.
type Output; type Output;
/// The name of the property, used for debug printing. /// 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. /// A scoped property barrier.
/// ///
/// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style /// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
/// can still be read through a single barrier (the one of the node it /// 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. /// _should_ apply to), but a second barrier will make it invisible.
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Barrier(NodeId); pub struct Barrier(NodeId);

View File

@ -103,7 +103,8 @@ impl StyleMap {
/// Mark all contained properties as _scoped_. This means that they only /// Mark all contained properties as _scoped_. This means that they only
/// apply to the first descendant node (of their type) in the hierarchy and /// 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 { pub fn scoped(mut self) -> Self {
for entry in &mut self.0 { for entry in &mut self.0 {
if let StyleEntry::Property(property) = entry { if let StyleEntry::Property(property) = entry {

View File

@ -12,7 +12,7 @@ use crate::diag::TypResult;
use crate::loading::{FileHash, Loader}; use crate::loading::{FileHash, Loader};
use crate::parse::{is_newline, parse, reparse}; use crate::parse::{is_newline, parse, reparse};
use crate::syntax::ast::Markup; 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}; use crate::util::{PathExt, StrExt};
#[cfg(feature = "codespan-reporting")] #[cfg(feature = "codespan-reporting")]
@ -23,6 +23,11 @@ use codespan_reporting::files::{self, Files};
pub struct SourceId(u32); pub struct SourceId(u32);
impl SourceId { 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. /// Create a source id from the raw underlying value.
/// ///
/// This should only be called with values returned by /// 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. /// Create a source file without a real id and path, usually for testing.
pub fn detached(src: impl Into<String>) -> Self { 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. /// The root node of the file's untyped green tree.

View File

@ -81,6 +81,14 @@ impl Green {
Self::Token(data) => data.kind = kind, 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 { impl Default for Green {
@ -151,6 +159,14 @@ impl GreenNode {
self.data().len() 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. /// The node's children, mutably.
pub(crate) fn children_mut(&mut self) -> &mut [Green] { pub(crate) fn children_mut(&mut self) -> &mut [Green] {
&mut self.children &mut self.children
@ -214,12 +230,14 @@ pub struct GreenData {
kind: NodeKind, kind: NodeKind,
/// The byte length of the node in the source. /// The byte length of the node in the source.
len: usize, len: usize,
/// A synthetic span for the node, usually this is `None`.
span: Option<Arc<Span>>,
} }
impl GreenData { impl GreenData {
/// Create new node metadata. /// Create new node metadata.
pub fn new(kind: NodeKind, len: usize) -> Self { pub fn new(kind: NodeKind, len: usize) -> Self {
Self { len, kind } Self { len, kind, span: None }
} }
/// The type of the node. /// The type of the node.
@ -231,6 +249,11 @@ impl GreenData {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len 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 { 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. /// The type of the node.
pub fn kind(&self) -> &NodeKind { pub fn kind(&self) -> &NodeKind {
self.as_ref().kind() 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. /// The type of the node.
pub fn kind(self) -> &'a NodeKind { pub fn kind(self) -> &'a NodeKind {
self.green.kind() self.green.kind()
@ -352,7 +385,10 @@ impl<'a> RedRef<'a> {
/// The span of the node. /// The span of the node.
pub fn span(self) -> Span { 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. /// Whether the node is a leaf node.
@ -368,11 +404,14 @@ impl<'a> RedRef<'a> {
match self.kind() { match self.kind() {
NodeKind::Error(pos, msg) => { NodeKind::Error(pos, msg) => {
let span = match pos { let mut span = self.span();
ErrorPos::Start => self.span().at_start(), if self.data().span.is_none() {
ErrorPos::Full => self.span(), span = match pos {
ErrorPos::End => self.span().at_end(), ErrorPos::Start => span.at_start(),
ErrorPos::Full => span,
ErrorPos::End => span.at_end(),
}; };
}
vec![Error::new(span, msg.to_string())] 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)}")