use crate::modules::IntoModuleCodeString;
use crate::modules::ModuleCodeString;
use crate::ops::OpMetadata;
use crate::runtime::bindings;
use crate::FastStaticString;
use crate::OpState;
use anyhow::Context as _;
use anyhow::Error;
use std::borrow::Cow;
use std::marker::PhantomData;
use std::sync::Arc;
use v8::fast_api::CFunction;
use v8::fast_api::CFunctionInfo;
use v8::fast_api::Int64Representation;
use v8::fast_api::Type;
use v8::MapFnTo;
#[derive(Clone)]
pub enum ExtensionFileSourceCode {
#[deprecated = "Use ExtensionFileSource::new"]
IncludedInBinary(FastStaticString),
LoadedFromFsDuringSnapshot(&'static str),
LoadedFromMemoryDuringSnapshot(FastStaticString),
Computed(Arc<str>),
}
#[allow(deprecated)]
impl std::fmt::Debug for ExtensionFileSourceCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::IncludedInBinary(..) => write!(f, "IncludedInBinary(..)"),
Self::LoadedFromFsDuringSnapshot(path) => {
write!(f, "LoadedFromFsDuringSnapshot({path})")
}
Self::LoadedFromMemoryDuringSnapshot(..) => {
write!(f, "LoadedFromMemoryDuringSnapshot(..)")
}
Self::Computed(..) => write!(f, "Computed(..)"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExtensionSourceType {
LazyEsm,
Js,
Esm,
}
#[derive(Clone, Debug)]
pub struct ExtensionFileSource {
pub specifier: &'static str,
pub code: ExtensionFileSourceCode,
_unconstructable_use_new: PhantomData<()>,
}
impl ExtensionFileSource {
pub const fn new(specifier: &'static str, code: FastStaticString) -> Self {
#[allow(deprecated)]
Self {
specifier,
code: ExtensionFileSourceCode::IncludedInBinary(code),
_unconstructable_use_new: PhantomData,
}
}
pub const fn new_computed(specifier: &'static str, code: Arc<str>) -> Self {
#[allow(deprecated)]
Self {
specifier,
code: ExtensionFileSourceCode::Computed(code),
_unconstructable_use_new: PhantomData,
}
}
pub const fn loaded_during_snapshot(
specifier: &'static str,
path: &'static str,
) -> Self {
#[allow(deprecated)]
Self {
specifier,
code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path),
_unconstructable_use_new: PhantomData,
}
}
pub const fn loaded_from_memory_during_snapshot(
specifier: &'static str,
code: FastStaticString,
) -> Self {
#[allow(deprecated)]
Self {
specifier,
code: ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code),
_unconstructable_use_new: PhantomData,
}
}
fn find_non_ascii(s: &str) -> String {
s.chars().filter(|c| !c.is_ascii()).collect::<String>()
}
#[allow(deprecated)]
pub fn load(&self) -> Result<ModuleCodeString, Error> {
match &self.code {
ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code)
| ExtensionFileSourceCode::IncludedInBinary(code) => {
debug_assert!(
code.is_ascii(),
"Extension code must be 7-bit ASCII: {} (found {})",
self.specifier,
Self::find_non_ascii(code)
);
Ok(IntoModuleCodeString::into_module_code(*code))
}
ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => {
let msg = || format!("Failed to read \"{}\"", path);
let s = std::fs::read_to_string(path).with_context(msg)?;
debug_assert!(
s.is_ascii(),
"Extension code must be 7-bit ASCII: {} (found {})",
self.specifier,
Self::find_non_ascii(&s)
);
Ok(s.into())
}
ExtensionFileSourceCode::Computed(code) => {
debug_assert!(
code.is_ascii(),
"Extension code must be 7-bit ASCII: {} (found {})",
self.specifier,
Self::find_non_ascii(code)
);
Ok(ModuleCodeString::from(code.clone()))
}
}
}
}
pub type OpFnRef = v8::FunctionCallback;
pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl;
pub type OpStateFn = dyn FnOnce(&mut OpState);
pub trait Op {
const NAME: &'static str;
const DECL: OpDecl;
}
pub type GlobalTemplateMiddlewareFn =
for<'s> fn(
&mut v8::HandleScope<'s, ()>,
v8::Local<'s, v8::ObjectTemplate>,
) -> v8::Local<'s, v8::ObjectTemplate>;
pub type GlobalObjectMiddlewareFn =
for<'s> fn(&mut v8::HandleScope<'s>, v8::Local<'s, v8::Object>);
extern "C" fn noop() {}
const NOOP_FN: CFunction = CFunction::new(
noop as _,
&CFunctionInfo::new(Type::Void.scalar(), &[], Int64Representation::Number),
);
#[derive(Clone, Copy)]
pub struct OpDecl {
pub name: &'static str,
pub(crate) name_fast: FastStaticString,
pub is_async: bool,
pub is_reentrant: bool,
pub arg_count: u8,
pub no_side_effects: bool,
pub(crate) slow_fn: OpFnRef,
pub(crate) slow_fn_with_metrics: OpFnRef,
pub(crate) fast_fn: Option<CFunction>,
pub(crate) fast_fn_with_metrics: Option<CFunction>,
pub metadata: OpMetadata,
}
impl OpDecl {
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub const fn new_internal_op2(
name: (&'static str, FastStaticString),
is_async: bool,
is_reentrant: bool,
arg_count: u8,
no_side_effects: bool,
slow_fn: OpFnRef,
slow_fn_with_metrics: OpFnRef,
fast_fn: Option<CFunction>,
fast_fn_with_metrics: Option<CFunction>,
metadata: OpMetadata,
) -> Self {
#[allow(deprecated)]
Self {
name: name.0,
name_fast: name.1,
is_async,
is_reentrant,
arg_count,
no_side_effects,
slow_fn,
slow_fn_with_metrics,
fast_fn,
fast_fn_with_metrics,
metadata,
}
}
pub fn disable(self) -> Self {
Self {
slow_fn: bindings::op_disabled_fn.map_fn_to(),
slow_fn_with_metrics: bindings::op_disabled_fn.map_fn_to(),
fast_fn: self.fast_fn.map(|_| NOOP_FN),
fast_fn_with_metrics: self.fast_fn_with_metrics.map(|_| NOOP_FN),
..self
}
}
pub const fn with_implementation_from(mut self, from: &Self) -> Self {
self.slow_fn = from.slow_fn;
self.slow_fn_with_metrics = from.slow_fn_with_metrics;
self.fast_fn = from.fast_fn;
self.fast_fn_with_metrics = from.fast_fn_with_metrics;
self
}
#[doc(hidden)]
pub const fn fast_fn(&self) -> CFunction {
let Some(f) = self.fast_fn else {
panic!("Not a fast function");
};
f
}
#[doc(hidden)]
pub const fn fast_fn_with_metrics(&self) -> CFunction {
let Some(f) = self.fast_fn_with_metrics else {
panic!("Not a fast function");
};
f
}
}
#[macro_export]
macro_rules! ops {
($name:ident, parameters = [ $( $param:ident : $type:ident ),+ ], ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )? ),+ $(,)? ]) => {
pub(crate) fn $name < $( $param : $type + 'static ),+ > () -> ::std::vec::Vec<$crate::OpDecl> {
vec![
$(
$( #[ $m ] )*
$( $op )+ $( :: <$op_param> )? () ,
)+
]
}
};
($name:ident, [ $( $(#[$m:meta])* $( $op:ident )::+ ),+ $(,)? ] ) => {
pub(crate) fn $name() -> ::std::Vec<$crate::OpDecl> {
use $crate::Op;
vec![
$( $( #[ $m ] )* $( $op )+() , )+
]
}
}
}
#[macro_export]
macro_rules! or {
($e:expr, $fallback:expr) => {
$e
};
(, $fallback:expr) => {
$fallback
};
}
#[macro_export]
macro_rules! extension {
(
$name:ident
$(, deps = [ $( $dep:ident ),* ] )?
$(, parameters = [ $( $param:ident : $type:ident ),+ ] )?
$(, bounds = [ $( $bound:path : $bound_type:ident ),+ ] )?
$(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )?
$(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )?
$(, esm_entry_point = $esm_entry_point:expr )?
$(, esm = [ $($esm:tt)* ] )?
$(, lazy_loaded_esm = [ $($lazy_loaded_esm:tt)* ] )?
$(, js = [ $($js:tt)* ] )?
$(, options = { $( $options_id:ident : $options_type:ty ),* $(,)? } )?
$(, middleware = $middleware_fn:expr )?
$(, state = $state_fn:expr )?
$(, global_template_middleware = $global_template_middleware_fn:expr )?
$(, global_object_middleware = $global_object_middleware_fn:expr )?
$(, external_references = [ $( $external_reference:expr ),* $(,)? ] )?
$(, customizer = $customizer_fn:expr )?
$(, docs = $($docblocks:expr),+)?
$(,)?
) => {
$( $(#[doc = $docblocks])+ )?
#[doc = concat!("let mut extensions = vec![", stringify!($name), "::init_ops_and_esm()];")]
#[allow(non_camel_case_types)]
pub struct $name {
}
impl $name {
fn ext $( < $( $param : $type + 'static ),+ > )?() -> $crate::Extension {
#[allow(unused_imports)]
use $crate::Op;
$crate::Extension {
name: ::std::stringify!($name),
deps: &[ $( $( ::std::stringify!($dep) ),* )? ],
js_files: {
const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($js)*)? );
::std::borrow::Cow::Borrowed(JS)
},
esm_files: {
const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($esm)*)? );
::std::borrow::Cow::Borrowed(JS)
},
lazy_loaded_esm_files: {
const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_lazy_loaded_js_files!( $name $($($lazy_loaded_esm)*)? );
::std::borrow::Cow::Borrowed(JS)
},
esm_entry_point: {
const V: ::std::option::Option<&'static ::std::primitive::str> = $crate::or!($(::std::option::Option::Some($esm_entry_point))?, ::std::option::Option::None);
V
},
ops: ::std::borrow::Cow::Owned(vec![$($({
$( #[ $m ] )*
$( $op )::+ $( :: < $($op_param),* > )? ()
}),+)?]),
external_references: ::std::borrow::Cow::Borrowed(&[ $( $external_reference ),* ]),
global_template_middleware: ::std::option::Option::None,
global_object_middleware: ::std::option::Option::None,
op_state_fn: ::std::option::Option::None,
middleware_fn: ::std::option::Option::None,
enabled: true,
}
}
#[inline(always)]
#[allow(unused_variables)]
fn with_ops_fn $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension)
$( where $( $bound : $bound_type ),+ )?
{
$crate::extension!(! __ops__ ext $( $ops_symbol $( < $ops_param > )? )? __eot__);
}
#[inline(always)]
#[allow(unused_variables)]
fn with_state_and_middleware$( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension, $( $( $options_id : $options_type ),* )? )
$( where $( $bound : $bound_type ),+ )?
{
$crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? );
$(
ext.global_template_middleware = ::std::option::Option::Some($global_template_middleware_fn);
)?
$(
ext.global_object_middleware = ::std::option::Option::Some($global_object_middleware_fn);
)?
$(
ext.middleware_fn = ::std::option::Option::Some(::std::boxed::Box::new($middleware_fn));
)?
}
#[inline(always)]
#[allow(unused_variables)]
#[allow(clippy::redundant_closure_call)]
fn with_customizer(ext: &mut $crate::Extension) {
$( ($customizer_fn)(ext); )?
}
#[allow(dead_code)]
pub fn init_ops_and_esm $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
$( where $( $bound : $bound_type ),+ )?
{
let mut ext = Self::ext $( ::< $( $param ),+ > )?();
Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext);
Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
Self::with_customizer(&mut ext);
ext
}
#[allow(dead_code)]
pub fn init_ops $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
$( where $( $bound : $bound_type ),+ )?
{
let mut ext = Self::ext $( ::< $( $param ),+ > )?();
Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext);
Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
Self::with_customizer(&mut ext);
ext.js_files = ::std::borrow::Cow::Borrowed(&[]);
ext.esm_files = ::std::borrow::Cow::Borrowed(&[]);
ext.esm_entry_point = ::std::option::Option::None;
ext
}
}
};
(! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? config = { $( $options_id:ident : $options_type:ty ),* } $( state_fn = $state_fn:expr )? ) => {
{
#[doc(hidden)]
struct Config $( < $( $param : $type + 'static ),+ > )? {
$( pub $options_id : $options_type , )*
$( __phantom_data: ::std::marker::PhantomData<($( $param ),+)>, )?
}
let config = Config {
$( $options_id , )*
$( __phantom_data: ::std::marker::PhantomData::<($( $param ),+)>::default() )?
};
let state_fn: fn(&mut $crate::OpState, Config $( < $( $param ),+ > )? ) = $( $state_fn )?;
$ext.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new(move |state: &mut $crate::OpState| {
state_fn(state, config);
}));
}
};
(! __config__ $ext:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? $( state_fn = $state_fn:expr )? ) => {
$( $ext.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new($state_fn)); )?
};
(! __ops__ $ext:ident __eot__) => {
};
(! __ops__ $ext:ident $ops_symbol:ident __eot__) => {
$ext.ops.to_mut().extend($ops_symbol())
};
(! __ops__ $ext:ident $ops_symbol:ident < $ops_param:ident > __eot__) => {
$ext.ops.to_mut().extend($ops_symbol::<$ops_param>())
};
}
pub struct Extension {
pub name: &'static str,
pub deps: &'static [&'static str],
pub js_files: Cow<'static, [ExtensionFileSource]>,
pub esm_files: Cow<'static, [ExtensionFileSource]>,
pub lazy_loaded_esm_files: Cow<'static, [ExtensionFileSource]>,
pub esm_entry_point: Option<&'static str>,
pub ops: Cow<'static, [OpDecl]>,
pub external_references: Cow<'static, [v8::ExternalReference<'static>]>,
pub global_template_middleware: Option<GlobalTemplateMiddlewareFn>,
pub global_object_middleware: Option<GlobalObjectMiddlewareFn>,
pub op_state_fn: Option<Box<OpStateFn>>,
pub middleware_fn: Option<Box<OpMiddlewareFn>>,
pub enabled: bool,
}
impl Extension {
pub(crate) fn for_warmup(&self) -> Extension {
Self {
op_state_fn: None,
middleware_fn: None,
name: self.name,
deps: self.deps,
js_files: Cow::Borrowed(&[]),
esm_files: Cow::Borrowed(&[]),
lazy_loaded_esm_files: Cow::Borrowed(&[]),
esm_entry_point: None,
ops: self.ops.clone(),
external_references: self.external_references.clone(),
global_template_middleware: self.global_template_middleware,
global_object_middleware: self.global_object_middleware,
enabled: self.enabled,
}
}
}
impl Default for Extension {
fn default() -> Self {
Self {
name: "DEFAULT",
deps: &[],
js_files: Cow::Borrowed(&[]),
esm_files: Cow::Borrowed(&[]),
lazy_loaded_esm_files: Cow::Borrowed(&[]),
esm_entry_point: None,
ops: Cow::Borrowed(&[]),
external_references: Cow::Borrowed(&[]),
global_template_middleware: None,
global_object_middleware: None,
op_state_fn: None,
middleware_fn: None,
enabled: true,
}
}
}
impl Extension {
pub fn check_dependencies(&self, previous_exts: &[Extension]) {
'dep_loop: for dep in self.deps {
if dep == &self.name {
panic!("Extension '{}' is either depending on itself or there is another extension with the same name", self.name);
}
for ext in previous_exts {
if dep == &ext.name {
continue 'dep_loop;
}
}
panic!("Extension '{}' is missing dependency '{dep}'", self.name);
}
}
pub fn get_js_sources(&self) -> &[ExtensionFileSource] {
&self.js_files
}
pub fn get_esm_sources(&self) -> &[ExtensionFileSource] {
&self.esm_files
}
pub fn get_lazy_loaded_esm_sources(&self) -> &[ExtensionFileSource] {
&self.lazy_loaded_esm_files
}
pub fn get_esm_entry_point(&self) -> Option<&'static str> {
self.esm_entry_point
}
pub fn op_count(&self) -> usize {
self.ops.len()
}
pub fn init_ops(&mut self) -> &[OpDecl] {
if !self.enabled {
for op in self.ops.to_mut() {
op.disable();
}
}
self.ops.as_ref()
}
pub fn take_state(&mut self, state: &mut OpState) {
if let Some(op_fn) = self.op_state_fn.take() {
op_fn(state);
}
}
pub fn take_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> {
self.middleware_fn.take()
}
pub fn get_global_template_middleware(
&mut self,
) -> Option<GlobalTemplateMiddlewareFn> {
self.global_template_middleware
}
pub fn get_global_object_middleware(
&mut self,
) -> Option<GlobalObjectMiddlewareFn> {
self.global_object_middleware
}
pub fn get_external_references(
&mut self,
) -> &[v8::ExternalReference<'static>] {
self.external_references.as_ref()
}
pub fn enabled(self, enabled: bool) -> Self {
Self { enabled, ..self }
}
pub fn disable(self) -> Self {
self.enabled(false)
}
}
#[macro_export]
macro_rules! include_js_files {
($name:ident $( dir $dir:literal, )? $(
$s1:literal
$(with_specifier $s2:literal)?
$(= $config:tt)?
),* $(,)?) => {
$crate::__extension_include_js_files_detect!(name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
// These entries will be parsed in __extension_include_js_files_inner
$s1 $(with_specifier $s2)? $(= $config)?
]),*)
};
}
#[macro_export]
macro_rules! include_lazy_loaded_js_files {
($name:ident $( dir $dir:literal, )? $(
$s1:literal
$(with_specifier $s2:literal)?
$(= $config:tt)?
),* $(,)?) => {
$crate::__extension_include_js_files_inner!(mode=included, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
// These entries will be parsed in __extension_include_js_files_inner
$s1 $(with_specifier $s2)? $(= $config)?
]),*)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! include_js_files_doctest {
($name:ident $( dir $dir:literal, )? $(
$s1:literal
$(with_specifier $s2:literal)?
$(= $config:tt)?
),* $(,)?) => {
$crate::__extension_include_js_files_inner!(mode=loaded, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([
$s1 $(with_specifier $s2)? $(= $config)?
]),*)
};
}
#[cfg(not(feature = "include_js_files_for_snapshotting"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __extension_include_js_files_detect {
($($rest:tt)*) => { $crate::__extension_include_js_files_inner!(mode=included, $($rest)*) };
}
#[cfg(feature = "include_js_files_for_snapshotting")]
#[doc(hidden)]
#[macro_export]
macro_rules! __extension_include_js_files_detect {
($($rest:tt)*) => { $crate::__extension_include_js_files_inner!(mode=loaded, $($rest)*) };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __extension_include_js_files_inner {
(mode=$mode:ident, name=$name:ident, dir=$dir:expr, $([
$s1:literal
$(with_specifier $s2:literal)?
$(= $config:tt)?
]),*) => {
[
$(
$crate::__extension_include_js_files_inner!(
@parse_item
mode=$mode,
name=$name,
dir=$dir,
$s1 $(with_specifier $s2)? $(= $config)?
)
),*
]
};
(@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $file:literal) => {
$crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=concat!("ext:", stringify!($name), "/", $file), file=$file)
};
(@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $file:literal with_specifier $specifier:literal) => {
{
#[deprecated="When including JS files 'file with_specifier specifier' is deprecated: use 'specifier = file' instead"]
struct WithSpecifierIsDeprecated {}
_ = WithSpecifierIsDeprecated {};
$crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file)
}
};
(@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $specifier:literal = $file:literal) => {
$crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file)
};
(@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr, $specifier:literal = { source = $source:literal }) => {
$crate::__extension_include_js_files_inner!(@item mode=$mode, specifier=$specifier, source=$source)
};
(@item mode=loaded, specifier=$specifier:expr, source=$source:expr) => {
$crate::ExtensionFileSource::loaded_from_memory_during_snapshot($specifier, $crate::ascii_str!($source))
};
(@item mode=loaded, dir=$dir:expr, specifier=$specifier:expr, file=$file:literal) => {
$crate::ExtensionFileSource::loaded_during_snapshot($specifier, concat!($dir, "/", $file))
};
(@item mode=included, specifier=$specifier:expr, source=$source:expr) => {
$crate::ExtensionFileSource::new($specifier, $crate::ascii_str!($source))
};
(@item mode=included, dir=$dir:expr, specifier=$specifier:expr, file=$file:literal) => {
$crate::ExtensionFileSource::new($specifier, $crate::ascii_str_include!(concat!($dir, "/", $file)))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __extension_root_dir {
() => {
env!("CARGO_MANIFEST_DIR")
};
($dir:expr) => {
concat!(env!("CARGO_MANIFEST_DIR"), "/", $dir)
};
}
#[cfg(test)]
mod tests {
#[test]
fn test_include_js() {
let files = include_js_files!(prefix "00_infra.js", "01_core.js",);
assert_eq!("ext:prefix/00_infra.js", files[0].specifier);
assert_eq!("ext:prefix/01_core.js", files[1].specifier);
let files = include_js_files!(prefix dir ".", "00_infra.js", "01_core.js",);
assert_eq!("ext:prefix/00_infra.js", files[0].specifier);
assert_eq!("ext:prefix/01_core.js", files[1].specifier);
let files = include_js_files!(prefix
"a" = { source = "b" }
);
assert_eq!("a", files[0].specifier);
}
}