#[doc(inline)]
pub use typst_macros::func;
use std::fmt::{self, Debug, Formatter};
use std::sync::{Arc, LazyLock};
use comemo::{Tracked, TrackedMut};
use ecow::{EcoString, eco_format};
use typst_syntax::{Span, SyntaxNode, ast};
use typst_utils::{LazyHash, Static, singleton};
use crate::diag::{At, DeprecationSink, SourceResult, StrResult, bail};
use crate::engine::Engine;
use crate::foundations::{
Args, Bytes, CastInfo, Content, Context, Element, IntoArgs, PluginFunc, Scope,
Selector, Type, Value, cast, repr, scope, ty,
};
#[ty(scope, cast, name = "function")]
#[derive(Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct Func {
repr: Repr,
span: Span,
}
#[derive(Clone, PartialEq, Hash)]
enum Repr {
Native(Static<NativeFuncData>),
Element(Element),
Closure(Arc<LazyHash<Closure>>),
Plugin(Arc<PluginFunc>),
With(Arc<(Func, Args)>),
}
impl Func {
pub fn name(&self) -> Option<&str> {
match &self.repr {
Repr::Native(native) => Some(native.name),
Repr::Element(elem) => Some(elem.name()),
Repr::Closure(closure) => closure.name(),
Repr::Plugin(func) => Some(func.name()),
Repr::With(with) => with.0.name(),
}
}
pub fn title(&self) -> Option<&'static str> {
match &self.repr {
Repr::Native(native) => Some(native.title),
Repr::Element(elem) => Some(elem.title()),
Repr::Closure(_) => None,
Repr::Plugin(_) => None,
Repr::With(with) => with.0.title(),
}
}
pub fn docs(&self) -> Option<&'static str> {
match &self.repr {
Repr::Native(native) => Some(native.docs),
Repr::Element(elem) => Some(elem.docs()),
Repr::Closure(_) => None,
Repr::Plugin(_) => None,
Repr::With(with) => with.0.docs(),
}
}
pub fn contextual(&self) -> Option<bool> {
match &self.repr {
Repr::Native(native) => Some(native.contextual),
_ => None,
}
}
pub fn params(&self) -> Option<&'static [ParamInfo]> {
match &self.repr {
Repr::Native(native) => Some(&native.0.params),
Repr::Element(elem) => Some(elem.params()),
Repr::Closure(_) => None,
Repr::Plugin(_) => None,
Repr::With(with) => with.0.params(),
}
}
pub fn param(&self, name: &str) -> Option<&'static ParamInfo> {
self.params()?.iter().find(|param| param.name == name)
}
pub fn returns(&self) -> Option<&'static CastInfo> {
match &self.repr {
Repr::Native(native) => Some(&native.0.returns),
Repr::Element(_) => {
Some(singleton!(CastInfo, CastInfo::Type(Type::of::<Content>())))
}
Repr::Closure(_) => None,
Repr::Plugin(_) => None,
Repr::With(with) => with.0.returns(),
}
}
pub fn keywords(&self) -> &'static [&'static str] {
match &self.repr {
Repr::Native(native) => native.keywords,
Repr::Element(elem) => elem.keywords(),
Repr::Closure(_) => &[],
Repr::Plugin(_) => &[],
Repr::With(with) => with.0.keywords(),
}
}
pub fn scope(&self) -> Option<&'static Scope> {
match &self.repr {
Repr::Native(native) => Some(&native.0.scope),
Repr::Element(elem) => Some(elem.scope()),
Repr::Closure(_) => None,
Repr::Plugin(_) => None,
Repr::With(with) => with.0.scope(),
}
}
pub fn field(
&self,
field: &str,
sink: impl DeprecationSink,
) -> StrResult<&'static Value> {
let scope =
self.scope().ok_or("cannot access fields on user-defined functions")?;
match scope.get(field) {
Some(binding) => Ok(binding.read_checked(sink)),
None => match self.name() {
Some(name) => bail!("function `{name}` does not contain field `{field}`"),
None => bail!("function does not contain field `{field}`"),
},
}
}
pub fn element(&self) -> Option<Element> {
match self.repr {
Repr::Element(func) => Some(func),
_ => None,
}
}
pub fn to_plugin(&self) -> Option<&PluginFunc> {
match &self.repr {
Repr::Plugin(func) => Some(func),
_ => None,
}
}
pub fn call<A: IntoArgs>(
&self,
engine: &mut Engine,
context: Tracked<Context>,
args: A,
) -> SourceResult<Value> {
self.call_impl(engine, context, args.into_args(self.span))
}
#[typst_macros::time(name = "func call", span = self.span())]
fn call_impl(
&self,
engine: &mut Engine,
context: Tracked<Context>,
mut args: Args,
) -> SourceResult<Value> {
match &self.repr {
Repr::Native(native) => {
let value = (native.function.0)(engine, context, &mut args)?;
args.finish()?;
Ok(value)
}
Repr::Element(func) => {
let value = func.construct(engine, &mut args)?;
args.finish()?;
Ok(Value::Content(value))
}
Repr::Closure(closure) => (engine.routines.eval_closure)(
self,
closure,
engine.routines,
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
context,
args,
),
Repr::Plugin(func) => {
let inputs = args.all::<Bytes>()?;
let output = func.call(inputs).at(args.span)?;
args.finish()?;
Ok(Value::Bytes(output))
}
Repr::With(with) => {
args.items = with.1.items.iter().cloned().chain(args.items).collect();
with.0.call(engine, context, args)
}
}
}
pub fn span(&self) -> Span {
self.span
}
pub fn spanned(mut self, span: Span) -> Self {
if self.span.is_detached() {
self.span = span;
}
self
}
}
#[scope]
impl Func {
#[func]
pub fn with(
self,
args: &mut Args,
#[external]
#[variadic]
arguments: Vec<Value>,
) -> Func {
let span = self.span;
Self {
repr: Repr::With(Arc::new((self, args.take()))),
span,
}
}
#[func]
pub fn where_(
self,
args: &mut Args,
#[variadic]
#[external]
fields: Vec<Value>,
) -> StrResult<Selector> {
let fields = args.to_named();
args.items.retain(|arg| arg.name.is_none());
let element = self
.element()
.ok_or("`where()` can only be called on element functions")?;
let fields = fields
.into_iter()
.map(|(key, value)| {
element.field_id(&key).map(|id| (id, value)).ok_or_else(|| {
eco_format!(
"element `{}` does not have field `{}`",
element.name(),
key
)
})
})
.collect::<StrResult<smallvec::SmallVec<_>>>()?;
Ok(element.where_(fields))
}
}
impl Debug for Func {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Func({})", self.name().unwrap_or(".."))
}
}
impl repr::Repr for Func {
fn repr(&self) -> EcoString {
const DEFAULT: &str = "(..) => ..";
match &self.repr {
Repr::Native(native) => native.name.into(),
Repr::Element(elem) => elem.name().into(),
Repr::Closure(closure) => closure.name().unwrap_or(DEFAULT).into(),
Repr::Plugin(func) => func.name().clone(),
Repr::With(_) => DEFAULT.into(),
}
}
}
impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool {
self.repr == other.repr
}
}
impl PartialEq<&'static NativeFuncData> for Func {
fn eq(&self, other: &&'static NativeFuncData) -> bool {
match &self.repr {
Repr::Native(native) => *native == Static(*other),
_ => false,
}
}
}
impl PartialEq<Element> for Func {
fn eq(&self, other: &Element) -> bool {
match &self.repr {
Repr::Element(elem) => elem == other,
_ => false,
}
}
}
impl From<Repr> for Func {
fn from(repr: Repr) -> Self {
Self { repr, span: Span::detached() }
}
}
impl From<&'static NativeFuncData> for Func {
fn from(data: &'static NativeFuncData) -> Self {
Repr::Native(Static(data)).into()
}
}
impl From<Element> for Func {
fn from(func: Element) -> Self {
Repr::Element(func).into()
}
}
impl From<Closure> for Func {
fn from(closure: Closure) -> Self {
Repr::Closure(Arc::new(LazyHash::new(closure))).into()
}
}
impl From<PluginFunc> for Func {
fn from(func: PluginFunc) -> Self {
Repr::Plugin(Arc::new(func)).into()
}
}
pub trait NativeFunc {
fn func() -> Func {
Func::from(Self::data())
}
fn data() -> &'static NativeFuncData;
}
#[derive(Debug)]
pub struct NativeFuncData {
pub function: NativeFuncPtr,
pub name: &'static str,
pub title: &'static str,
pub docs: &'static str,
pub keywords: &'static [&'static str],
pub contextual: bool,
pub scope: DynLazyLock<Scope>,
pub params: DynLazyLock<Vec<ParamInfo>>,
pub returns: DynLazyLock<CastInfo>,
}
cast! {
&'static NativeFuncData,
self => Func::from(self).into_value(),
}
pub struct NativeFuncPtr(pub &'static NativeFuncSignature);
type NativeFuncSignature =
dyn Fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value> + Send + Sync;
impl Debug for NativeFuncPtr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad("NativeFuncPtr(..)")
}
}
type DynLazyLock<T> = LazyLock<T, &'static (dyn Fn() -> T + Send + Sync)>;
#[derive(Debug, Clone)]
pub struct ParamInfo {
pub name: &'static str,
pub docs: &'static str,
pub input: CastInfo,
pub default: Option<fn() -> Value>,
pub positional: bool,
pub named: bool,
pub variadic: bool,
pub required: bool,
pub settable: bool,
}
#[derive(Debug, Hash)]
pub enum ClosureNode {
Closure(SyntaxNode),
Context(SyntaxNode),
}
#[derive(Debug, Hash)]
pub struct Closure {
pub node: ClosureNode,
pub defaults: Vec<Value>,
pub captured: Scope,
pub num_pos_params: usize,
}
impl Closure {
pub fn name(&self) -> Option<&str> {
match self.node {
ClosureNode::Closure(ref node) => {
node.cast::<ast::Closure>()?.name().map(|ident| ident.as_str())
}
_ => None,
}
}
}
cast! {
Closure,
self => Value::Func(self.into()),
}