mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Memoize closure calls
This commit is contained in:
parent
c1637054a4
commit
090831c9cb
@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut};
|
use comemo::{Prehashed, Track, Tracked, TrackedMut};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
|
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
|
||||||
@ -11,12 +11,13 @@ use super::{
|
|||||||
use crate::diag::{bail, SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::syntax::ast::{self, AstNode, Expr};
|
use crate::syntax::ast::{self, AstNode, Expr};
|
||||||
use crate::syntax::{SourceId, Span, SyntaxNode};
|
use crate::syntax::{SourceId, Span, SyntaxNode};
|
||||||
|
use crate::util::hash128;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// An evaluatable function.
|
/// An evaluatable function.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Func(Arc<Repr>, Span);
|
pub struct Func(Arc<Prehashed<Repr>>, Span);
|
||||||
|
|
||||||
/// The different kinds of function representations.
|
/// The different kinds of function representations.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
@ -41,7 +42,12 @@ impl Func {
|
|||||||
info: FuncInfo,
|
info: FuncInfo,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(
|
Self(
|
||||||
Arc::new(Repr::Native(Native { func, set: None, node: None, info })),
|
Arc::new(Prehashed::new(Repr::Native(Native {
|
||||||
|
func,
|
||||||
|
set: None,
|
||||||
|
node: None,
|
||||||
|
info,
|
||||||
|
}))),
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -50,7 +56,7 @@ impl Func {
|
|||||||
pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
|
pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
|
||||||
info.params.extend(T::properties());
|
info.params.extend(T::properties());
|
||||||
Self(
|
Self(
|
||||||
Arc::new(Repr::Native(Native {
|
Arc::new(Prehashed::new(Repr::Native(Native {
|
||||||
func: |ctx, args| {
|
func: |ctx, args| {
|
||||||
let styles = T::set(args, true)?;
|
let styles = T::set(args, true)?;
|
||||||
let content = T::construct(ctx, args)?;
|
let content = T::construct(ctx, args)?;
|
||||||
@ -59,19 +65,19 @@ impl Func {
|
|||||||
set: Some(|args| T::set(args, false)),
|
set: Some(|args| T::set(args, false)),
|
||||||
node: Some(NodeId::of::<T>()),
|
node: Some(NodeId::of::<T>()),
|
||||||
info,
|
info,
|
||||||
})),
|
}))),
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new function from a closure.
|
/// Create a new function from a closure.
|
||||||
pub(super) fn from_closure(closure: Closure, span: Span) -> Self {
|
pub(super) fn from_closure(closure: Closure, span: Span) -> Self {
|
||||||
Self(Arc::new(Repr::Closure(closure)), span)
|
Self(Arc::new(Prehashed::new(Repr::Closure(closure))), span)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the function.
|
/// The name of the function.
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
match self.0.as_ref() {
|
match &**self.0 {
|
||||||
Repr::Native(native) => Some(native.info.name),
|
Repr::Native(native) => Some(native.info.name),
|
||||||
Repr::Closure(closure) => closure.name.as_deref(),
|
Repr::Closure(closure) => closure.name.as_deref(),
|
||||||
Repr::With(func, _) => func.name(),
|
Repr::With(func, _) => func.name(),
|
||||||
@ -80,7 +86,7 @@ impl Func {
|
|||||||
|
|
||||||
/// Extract details the function.
|
/// Extract details the function.
|
||||||
pub fn info(&self) -> Option<&FuncInfo> {
|
pub fn info(&self) -> Option<&FuncInfo> {
|
||||||
match self.0.as_ref() {
|
match &**self.0 {
|
||||||
Repr::Native(native) => Some(&native.info),
|
Repr::Native(native) => Some(&native.info),
|
||||||
Repr::With(func, _) => func.info(),
|
Repr::With(func, _) => func.info(),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -100,7 +106,7 @@ impl Func {
|
|||||||
|
|
||||||
/// The number of positional arguments this function takes, if known.
|
/// The number of positional arguments this function takes, if known.
|
||||||
pub fn argc(&self) -> Option<usize> {
|
pub fn argc(&self) -> Option<usize> {
|
||||||
match self.0.as_ref() {
|
match &**self.0 {
|
||||||
Repr::Closure(closure) => closure.argc(),
|
Repr::Closure(closure) => closure.argc(),
|
||||||
Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
|
Repr::With(wrapped, applied) => Some(wrapped.argc()?.saturating_sub(
|
||||||
applied.items.iter().filter(|arg| arg.name.is_none()).count(),
|
applied.items.iter().filter(|arg| arg.name.is_none()).count(),
|
||||||
@ -111,16 +117,32 @@ impl Func {
|
|||||||
|
|
||||||
/// Call the function with the given arguments.
|
/// Call the function with the given arguments.
|
||||||
pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> {
|
pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> {
|
||||||
let value = match self.0.as_ref() {
|
match &**self.0 {
|
||||||
Repr::Native(native) => (native.func)(vm, &mut args)?,
|
Repr::Native(native) => {
|
||||||
Repr::Closure(closure) => closure.call(vm, self, &mut args)?,
|
let value = (native.func)(vm, &mut args)?;
|
||||||
|
args.finish()?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
Repr::Closure(closure) => {
|
||||||
|
// Determine the route inside the closure.
|
||||||
|
let fresh = Route::new(closure.location);
|
||||||
|
let route =
|
||||||
|
if vm.location.is_detached() { fresh.track() } else { vm.route };
|
||||||
|
|
||||||
|
Closure::call(
|
||||||
|
self,
|
||||||
|
vm.world,
|
||||||
|
route,
|
||||||
|
TrackedMut::reborrow_mut(&mut vm.tracer),
|
||||||
|
vm.depth + 1,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
}
|
||||||
Repr::With(wrapped, applied) => {
|
Repr::With(wrapped, applied) => {
|
||||||
args.items.splice(..0, applied.items.iter().cloned());
|
args.items.splice(..0, applied.items.iter().cloned());
|
||||||
return wrapped.call(vm, args);
|
wrapped.call(vm, args)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
args.finish()?;
|
|
||||||
Ok(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the function without an existing virtual machine.
|
/// Call the function without an existing virtual machine.
|
||||||
@ -140,7 +162,7 @@ impl Func {
|
|||||||
/// Apply the given arguments to the function.
|
/// Apply the given arguments to the function.
|
||||||
pub fn with(self, args: Args) -> Self {
|
pub fn with(self, args: Args) -> Self {
|
||||||
let span = self.1;
|
let span = self.1;
|
||||||
Self(Arc::new(Repr::With(self, args)), span)
|
Self(Arc::new(Prehashed::new(Repr::With(self, args))), span)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a selector for this function's node type, filtering by node's
|
/// Create a selector for this function's node type, filtering by node's
|
||||||
@ -153,7 +175,7 @@ impl Func {
|
|||||||
|
|
||||||
/// Execute the function's set rule and return the resulting style map.
|
/// Execute the function's set rule and return the resulting style map.
|
||||||
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> {
|
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> {
|
||||||
Ok(match self.0.as_ref() {
|
Ok(match &**self.0 {
|
||||||
Repr::Native(Native { set: Some(set), .. }) => {
|
Repr::Native(Native { set: Some(set), .. }) => {
|
||||||
let styles = set(&mut args)?;
|
let styles = set(&mut args)?;
|
||||||
args.finish()?;
|
args.finish()?;
|
||||||
@ -165,8 +187,8 @@ impl Func {
|
|||||||
|
|
||||||
/// Create a selector for this function's node type.
|
/// Create a selector for this function's node type.
|
||||||
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
|
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
|
||||||
match self.0.as_ref() {
|
match **self.0 {
|
||||||
&Repr::Native(Native { node: Some(id), .. }) => {
|
Repr::Native(Native { node: Some(id), .. }) => {
|
||||||
if id == item!(text_id) {
|
if id == item!(text_id) {
|
||||||
Err("to select text, please use a string or regex instead")?;
|
Err("to select text, please use a string or regex instead")?;
|
||||||
}
|
}
|
||||||
@ -189,7 +211,7 @@ impl Debug for Func {
|
|||||||
|
|
||||||
impl PartialEq for Func {
|
impl PartialEq for Func {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
Arc::ptr_eq(&self.0, &other.0)
|
hash128(&self.0) == hash128(&other.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,19 +309,32 @@ pub(super) struct Closure {
|
|||||||
|
|
||||||
impl Closure {
|
impl Closure {
|
||||||
/// Call the function in the context with the arguments.
|
/// Call the function in the context with the arguments.
|
||||||
fn call(&self, vm: &mut Vm, this: &Func, args: &mut Args) -> SourceResult<Value> {
|
#[comemo::memoize]
|
||||||
|
fn call(
|
||||||
|
this: &Func,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
route: Tracked<Route>,
|
||||||
|
tracer: TrackedMut<Tracer>,
|
||||||
|
depth: usize,
|
||||||
|
mut args: Args,
|
||||||
|
) -> SourceResult<Value> {
|
||||||
|
let closure = match &**this.0 {
|
||||||
|
Repr::Closure(closure) => closure,
|
||||||
|
_ => panic!("`this` must be a closure"),
|
||||||
|
};
|
||||||
|
|
||||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||||
// of captured variables we collected earlier.
|
// of captured variables we collected earlier.
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
scopes.top = self.captured.clone();
|
scopes.top = closure.captured.clone();
|
||||||
|
|
||||||
// Provide the closure itself for recursive calls.
|
// Provide the closure itself for recursive calls.
|
||||||
if let Some(name) = &self.name {
|
if let Some(name) = &closure.name {
|
||||||
scopes.top.define(name.clone(), Value::Func(this.clone()));
|
scopes.top.define(name.clone(), Value::Func(this.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the arguments according to the parameter list.
|
// Parse the arguments according to the parameter list.
|
||||||
for (param, default) in &self.params {
|
for (param, default) in &closure.params {
|
||||||
scopes.top.define(
|
scopes.top.define(
|
||||||
param.clone(),
|
param.clone(),
|
||||||
match default {
|
match default {
|
||||||
@ -312,25 +347,16 @@ impl Closure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Put the remaining arguments into the sink.
|
// Put the remaining arguments into the sink.
|
||||||
if let Some(sink) = &self.sink {
|
if let Some(sink) = &closure.sink {
|
||||||
scopes.top.define(sink.clone(), args.take());
|
scopes.top.define(sink.clone(), args.take());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the route inside the closure.
|
// Ensure all arguments have been used.
|
||||||
let detached = vm.location.is_detached();
|
args.finish()?;
|
||||||
let fresh = Route::new(self.location);
|
|
||||||
let route = if detached { fresh.track() } else { vm.route };
|
|
||||||
|
|
||||||
// Evaluate the body.
|
// Evaluate the body.
|
||||||
let mut sub = Vm::new(
|
let mut sub = Vm::new(world, route, tracer, closure.location, scopes, depth);
|
||||||
vm.world,
|
let result = closure.body.eval(&mut sub);
|
||||||
route,
|
|
||||||
TrackedMut::reborrow_mut(&mut vm.tracer),
|
|
||||||
self.location,
|
|
||||||
scopes,
|
|
||||||
vm.depth + 1,
|
|
||||||
);
|
|
||||||
let result = self.body.eval(&mut sub);
|
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
match sub.flow {
|
match sub.flow {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user