#[doc(inline)]
pub use typst_macros::func;
use typst_syntax::ast::AstNode;
use std::fmt::{self, Debug, Formatter};
use std::sync::{Arc, LazyLock};
use comemo::{Tracked, TrackedMut};
use ecow::{EcoString, eco_format};
use either::Either;
use typst_syntax::{Span, Spanned, SyntaxNode, ast};
use typst_utils::{DefSite, LazyHash, Static, singleton};
use crate::diag::{At, SourceResult, StrResult, WarningSink, bail};
use crate::engine::Engine;
use crate::foundations::{
Args, Bytes, CastInfo, Content, Context, Element, IntoArgs, PluginFunc, Repr, Scope,
Selector, Type, Value, cast, scope, ty,
};
#[ty(scope, cast, name = "function")]
#[derive(Clone, Hash)]
pub struct Func {
inner: FuncInner,
span: Span,
}
#[derive(Clone, PartialEq, Hash)]
enum FuncInner {
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.inner {
FuncInner::Native(native) => Some(native.name),
FuncInner::Element(elem) => Some(elem.name()),
FuncInner::Closure(closure) => closure.name(),
FuncInner::Plugin(func) => Some(func.name()),
FuncInner::With(with) => with.0.name(),
}
}
pub fn title(&self) -> Option<&'static str> {
match &self.inner {
FuncInner::Native(native) => Some(native.title),
FuncInner::Element(elem) => Some(elem.title()),
FuncInner::Closure(_) => None,
FuncInner::Plugin(_) => None,
FuncInner::With(with) => with.0.title(),
}
}
pub fn docs(&self) -> Option<&'static str> {
match &self.inner {
FuncInner::Native(native) => Some(native.docs),
FuncInner::Element(elem) => Some(elem.docs()),
FuncInner::Closure(_) => None,
FuncInner::Plugin(_) => None,
FuncInner::With(with) => with.0.docs(),
}
}
pub fn contextual(&self) -> Option<bool> {
match &self.inner {
FuncInner::Native(native) => Some(native.contextual),
_ => None,
}
}
pub fn params(&self) -> impl Iterator<Item = ParamInfo> {
match &self.inner {
FuncInner::Native(native) => {
Either::Left(native.0.params.iter().map(ParamInfo::Native))
}
FuncInner::Element(elem) => {
Either::Left(elem.params().iter().map(ParamInfo::Native))
}
FuncInner::Closure(closure) => {
Either::Right(Either::Left(closure.params().map(ParamInfo::Closure)))
}
FuncInner::Plugin(_) => {
Either::Right(Either::Right([ParamInfo::Plugin].into_iter()))
}
FuncInner::With(with) => with.0.params(),
}
}
pub fn param(&self, name: &str) -> Option<ParamInfo> {
self.params().find(|param| param.name() == Some(name))
}
pub fn returns(&self) -> Option<&'static CastInfo> {
match &self.inner {
FuncInner::Native(native) => Some(&native.0.returns),
FuncInner::Element(_) => {
Some(singleton!(CastInfo, CastInfo::Type(Type::of::<Content>())))
}
FuncInner::Closure(_) => None,
FuncInner::Plugin(_) => None,
FuncInner::With(with) => with.0.returns(),
}
}
pub fn keywords(&self) -> &'static [&'static str] {
match &self.inner {
FuncInner::Native(native) => native.keywords,
FuncInner::Element(elem) => elem.keywords(),
FuncInner::Closure(_) => &[],
FuncInner::Plugin(_) => &[],
FuncInner::With(with) => with.0.keywords(),
}
}
pub fn def_site(&self) -> Option<DefSite> {
match &self.inner {
FuncInner::Native(native) => native.def_site,
FuncInner::Element(elem) => Some(elem.def_site()),
_ => None,
}
}
pub fn scope(&self) -> Option<&'static Scope> {
match &self.inner {
FuncInner::Native(native) => Some(&native.0.scope),
FuncInner::Element(elem) => Some(elem.scope()),
FuncInner::Closure(_) => None,
FuncInner::Plugin(_) => None,
FuncInner::With(with) => with.0.scope(),
}
}
pub fn field(
&self,
field: &str,
sink: impl WarningSink,
) -> 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 to_element(&self) -> Option<Element> {
match self.inner {
FuncInner::Element(func) => Some(func),
_ => None,
}
}
pub fn to_plugin(&self) -> Option<&PluginFunc> {
match &self.inner {
FuncInner::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.inner {
FuncInner::Native(native) => {
let value = (native.function.0)(engine, context, &mut args)?;
args.finish()?;
Ok(value)
}
FuncInner::Element(func) => {
let value = func.construct(engine, &mut args)?;
args.finish()?;
Ok(Value::Content(value))
}
FuncInner::Closure(closure) => (engine.library.routines.eval_closure)(
self,
closure,
engine.world,
engine.library,
engine.introspector.into_raw(),
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
context,
args,
),
FuncInner::Plugin(func) => {
let inputs = args.all::<Bytes>()?;
let output = func.call(inputs).at(args.span)?;
args.finish()?;
Ok(Value::Bytes(output))
}
FuncInner::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 {
inner: FuncInner::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
.to_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 for Func {
fn repr(&self) -> EcoString {
const DEFAULT: &str = "(..) => ..";
match &self.inner {
FuncInner::Native(native) => native.name.into(),
FuncInner::Element(elem) => elem.name().into(),
FuncInner::Closure(closure) => closure.name().unwrap_or(DEFAULT).into(),
FuncInner::Plugin(func) => func.name().clone(),
FuncInner::With(_) => DEFAULT.into(),
}
}
}
impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl PartialEq<&'static NativeFuncData> for Func {
fn eq(&self, other: &&'static NativeFuncData) -> bool {
match &self.inner {
FuncInner::Native(native) => *native == Static(*other),
_ => false,
}
}
}
impl PartialEq<Element> for Func {
fn eq(&self, other: &Element) -> bool {
match &self.inner {
FuncInner::Element(elem) => elem == other,
_ => false,
}
}
}
impl From<FuncInner> for Func {
fn from(inner: FuncInner) -> Self {
Self { inner, span: Span::detached() }
}
}
impl From<&'static NativeFuncData> for Func {
fn from(data: &'static NativeFuncData) -> Self {
FuncInner::Native(Static(data)).into()
}
}
impl From<Element> for Func {
fn from(func: Element) -> Self {
FuncInner::Element(func).into()
}
}
impl From<Closure> for Func {
fn from(closure: Closure) -> Self {
FuncInner::Closure(Arc::new(LazyHash::new(closure))).into()
}
}
impl From<PluginFunc> for Func {
fn from(func: PluginFunc) -> Self {
FuncInner::Plugin(Arc::new(func)).into()
}
}
#[derive(Debug, Clone)]
pub enum ParamInfo {
Native(&'static NativeParamInfo),
Closure(Spanned<ClosureParamInfo>),
Plugin,
}
impl ParamInfo {
pub fn to_native(&self) -> Option<&'static NativeParamInfo> {
match self {
Self::Native(native) => Some(native),
_ => None,
}
}
pub fn name(&self) -> Option<&str> {
match self {
Self::Native(info) => Some(info.name),
Self::Closure(info) => match &info.v {
ClosureParamInfo::Pos { name } => name.as_deref(),
ClosureParamInfo::Sink { name } => name.as_deref(),
ClosureParamInfo::Named { name, .. } => Some(name),
},
Self::Plugin => None,
}
}
pub fn default(&self) -> Option<Value> {
match self {
Self::Native(info) => info.default.map(|f| f()),
Self::Closure(info) => match &info.v {
ClosureParamInfo::Named { default, .. } => Some(default.clone()),
_ => None,
},
Self::Plugin => None,
}
}
pub fn positional(&self) -> bool {
match self {
Self::Native(info) => info.positional,
Self::Closure(info) => matches!(
&info.v,
ClosureParamInfo::Pos { .. } | ClosureParamInfo::Sink { .. }
),
Self::Plugin => true,
}
}
pub fn named(&self) -> bool {
match self {
Self::Native(info) => info.named,
Self::Closure(info) => matches!(&info.v, ClosureParamInfo::Named { .. }),
Self::Plugin => false,
}
}
pub fn variadic(&self) -> bool {
match self {
Self::Native(info) => info.variadic,
Self::Closure(info) => matches!(&info.v, ClosureParamInfo::Sink { .. }),
Self::Plugin => true,
}
}
pub fn required(&self) -> bool {
match self {
Self::Native(info) => info.required,
Self::Closure(info) => matches!(&info.v, ClosureParamInfo::Pos { .. }),
Self::Plugin => false,
}
}
pub fn settable(&self) -> bool {
match self {
Self::Native(info) => info.settable,
Self::Closure(_) => false,
Self::Plugin => false,
}
}
}
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 def_site: Option<DefSite>,
pub keywords: &'static [&'static str],
pub contextual: bool,
pub scope: DynLazyLock<Scope>,
pub params: DynLazyLock<Vec<NativeParamInfo>>,
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 NativeParamInfo {
pub name: &'static str,
pub docs: &'static str,
pub def_site: Option<DefSite>,
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,
}
}
pub fn params(&self) -> impl Iterator<Item = Spanned<ClosureParamInfo>> {
let params = match self.node {
ClosureNode::Closure(ref node) => Some(
node.cast::<ast::Closure>()
.expect("node to be an `ast::Closure`")
.params()
.children(),
),
ClosureNode::Context(_) => None,
};
let mut defaults = self.defaults.iter();
params.into_iter().flatten().map(move |param| {
let info = match param {
ast::Param::Pos(pattern) => ClosureParamInfo::Pos {
name: match pattern {
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
Some(ident.get().clone())
}
_ => None,
},
},
ast::Param::Spread(spread) => ClosureParamInfo::Sink {
name: spread.sink_ident().map(|ident| ident.get().clone()),
},
ast::Param::Named(named) => ClosureParamInfo::Named {
name: named.name().get().clone(),
default: defaults.next().unwrap().clone(),
},
};
Spanned::new(info, param.span())
})
}
}
cast! {
Closure,
self => Value::Func(self.into()),
}
#[derive(Debug, Clone)]
pub enum ClosureParamInfo {
Pos { name: Option<EcoString> },
Sink { name: Option<EcoString> },
Named { name: EcoString, default: Value },
}