From 3602d06a155a0567fe2b2e75a4d5970578d0f14f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 19 Jun 2025 17:45:00 +0200 Subject: [PATCH] Support for generating native functions at runtime --- crates/typst-library/src/foundations/func.rs | 34 ++++++++++++++++---- crates/typst-macros/src/func.rs | 8 ++--- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs index 27eb34eac..9ef812890 100644 --- a/crates/typst-library/src/foundations/func.rs +++ b/crates/typst-library/src/foundations/func.rs @@ -307,7 +307,7 @@ impl Func { ) -> SourceResult { match &self.repr { Repr::Native(native) => { - let value = (native.function)(engine, context, &mut args)?; + let value = (native.function.0)(engine, context, &mut args)?; args.finish()?; Ok(value) } @@ -491,8 +491,8 @@ pub trait NativeFunc { /// Defines a native function. #[derive(Debug)] pub struct NativeFuncData { - /// Invokes the function from Typst. - pub function: fn(&mut Engine, Tracked, &mut Args) -> SourceResult, + /// The implementation of the function. + pub function: NativeFuncPtr, /// The function's normal name (e.g. `align`), as exposed to Typst. pub name: &'static str, /// The function's title case name (e.g. `Align`). @@ -504,11 +504,11 @@ pub struct NativeFuncData { /// Whether this function makes use of context. pub contextual: bool, /// Definitions in the scope of the function. - pub scope: LazyLock, + pub scope: DynLazyLock, /// A list of parameter information for each parameter. - pub params: LazyLock>, + pub params: DynLazyLock>, /// Information about the return value of this function. - pub returns: LazyLock, + pub returns: DynLazyLock, } cast! { @@ -516,6 +516,28 @@ cast! { self => Func::from(self).into_value(), } +/// A pointer to a native function's implementation. +pub struct NativeFuncPtr(pub &'static NativeFuncSignature); + +/// The signature of a native function's implementation. +type NativeFuncSignature = + dyn Fn(&mut Engine, Tracked, &mut Args) -> SourceResult + Send + Sync; + +impl Debug for NativeFuncPtr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad("NativeFuncPtr(..)") + } +} + +/// A `LazyLock` that uses a static closure for initialization instead of only +/// working with function pointers. +/// +/// Can be created from a normal function or closure by prepending with a `&`, +/// e.g. `LazyLock::new(&|| "hello")`. Can be created from a dynamic closure +/// by allocating and then leaking it. This is equivalent to having it +/// statically allocated, but allows for it to be generated at runtime. +type DynLazyLock = LazyLock T + Send + Sync)>; + /// Describes a function parameter. #[derive(Debug, Clone)] pub struct ParamInfo { diff --git a/crates/typst-macros/src/func.rs b/crates/typst-macros/src/func.rs index b8ab7a364..e953dc374 100644 --- a/crates/typst-macros/src/func.rs +++ b/crates/typst-macros/src/func.rs @@ -315,15 +315,15 @@ fn create_func_data(func: &Func) -> TokenStream { quote! { #foundations::NativeFuncData { - function: #closure, + function: #foundations::NativeFuncPtr(&#closure), name: #name, title: #title, docs: #docs, keywords: &[#(#keywords),*], contextual: #contextual, - scope: ::std::sync::LazyLock::new(|| #scope), - params: ::std::sync::LazyLock::new(|| ::std::vec![#(#params),*]), - returns: ::std::sync::LazyLock::new(|| <#returns as #foundations::Reflect>::output()), + scope: ::std::sync::LazyLock::new(&|| #scope), + params: ::std::sync::LazyLock::new(&|| ::std::vec![#(#params),*]), + returns: ::std::sync::LazyLock::new(&|| <#returns as #foundations::Reflect>::output()), } } }