use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use comemo::{Tracked, TrackedMut};
use ecow::{eco_format, EcoString};
use once_cell::sync::Lazy;
use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, repr, scope, ty, Args, CastInfo, Content, Context, Element, IntoArgs, Scope,
Selector, Type, Value,
};
use crate::syntax::{ast, Span, SyntaxNode};
use crate::utils::{singleton, LazyHash, Static};
#[doc(inline)]
pub use typst_macros::func;
#[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>>),
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::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::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::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::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::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::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::With(with) => with.0.scope(),
}
}
pub fn field(&self, field: &str) -> StrResult<&'static Value> {
let scope =
self.scope().ok_or("cannot access fields on user-defined functions")?;
match scope.get(field) {
Some(field) => Ok(field),
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 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)(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) => crate::eval::call_closure(
self,
closure,
engine.world,
engine.introspector,
engine.traced,
TrackedMut::reborrow_mut(&mut engine.sink),
engine.route.track(),
context,
args,
),
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 {
match self.name() {
Some(name) => name.into(),
None => "(..) => ..".into(),
}
}
}
impl PartialEq for Func {
fn eq(&self, other: &Self) -> bool {
self.repr == other.repr
}
}
impl PartialEq<&NativeFuncData> for Func {
fn eq(&self, other: &&NativeFuncData) -> bool {
match &self.repr {
Repr::Native(native) => native.function == other.function,
_ => false,
}
}
}
impl From<Repr> for Func {
fn from(repr: Repr) -> Self {
Self { repr, span: Span::detached() }
}
}
impl From<Element> for Func {
fn from(func: Element) -> Self {
Repr::Element(func).into()
}
}
pub trait NativeFunc {
fn func() -> Func {
Func::from(Self::data())
}
fn data() -> &'static NativeFuncData;
}
#[derive(Debug)]
pub struct NativeFuncData {
pub function: fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value>,
pub name: &'static str,
pub title: &'static str,
pub docs: &'static str,
pub keywords: &'static [&'static str],
pub contextual: bool,
pub scope: Lazy<Scope>,
pub params: Lazy<Vec<ParamInfo>>,
pub returns: Lazy<CastInfo>,
}
impl From<&'static NativeFuncData> for Func {
fn from(data: &'static NativeFuncData) -> Self {
Repr::Native(Static(data)).into()
}
}
cast! {
&'static NativeFuncData,
self => Func::from(self).into_value(),
}
#[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 struct Closure {
pub node: SyntaxNode,
pub defaults: Vec<Value>,
pub captured: Scope,
pub num_pos_params: usize,
}
impl Closure {
pub fn name(&self) -> Option<&str> {
self.node.cast::<ast::Closure>()?.name().map(|ident| ident.as_str())
}
}
impl From<Closure> for Func {
fn from(closure: Closure) -> Self {
Repr::Closure(Arc::new(LazyHash::new(closure))).into()
}
}
cast! {
Closure,
self => Value::Func(self.into()),
}