hebi 0.4.0

A dynamic scripting language
Documentation
use std::any::TypeId;
use std::future;
use std::future::Future;
use std::marker::PhantomData;
use std::mem::transmute;
use std::string::String as StdString;
use std::sync::Arc;

use futures_util::{FutureExt, TryFutureExt};
use indexmap::IndexMap;

use crate::internal::error::Result;
use crate::internal::object::native::{
  AsyncCallback, NativeClassDescriptor, NativeClassInstance, NativeFieldDescriptor,
  NativeMethodDescriptor, SyncCallback,
};
use crate::internal::value::Value as OwnedValue;
use crate::internal::vm::thread::Args;
use crate::public::{FromValue, IntoValue, Scope, This, Unbind, Value};

#[derive(Clone)]
pub struct NativeModule {
  pub(crate) data: Arc<NativeModuleData>,
}

impl NativeModule {
  pub fn builder(name: impl ToString) -> NativeModuleBuilder {
    NativeModuleBuilder {
      data: NativeModuleData {
        name: name.to_string(),
        fns: IndexMap::new(),
        async_fns: IndexMap::new(),
        classes: IndexMap::new(),
      },
    }
  }
}

pub(crate) struct NativeModuleData {
  pub(crate) name: StdString,
  pub(crate) fns: IndexMap<StdString, SyncCallback>,
  pub(crate) async_fns: IndexMap<StdString, AsyncCallback>,
  pub(crate) classes: IndexMap<StdString, NativeClassDescriptor>,
}

pub struct NativeModuleBuilder {
  data: NativeModuleData,
}

impl NativeModuleBuilder {
  pub fn function<'cx, R>(
    mut self,
    name: impl ToString,
    f: impl Fn(Scope<'cx>) -> R + Send + Sync + 'static,
  ) -> Self
  where
    R: IntoValue<'cx> + 'static,
  {
    self.data.fns.insert(name.to_string(), wrap_fn(f));
    self
  }

  pub fn async_function<'cx, Fut, R>(
    mut self,
    name: impl ToString,
    f: impl Fn(Scope<'cx>) -> Fut + Send + Sync + 'static,
  ) -> Self
  where
    Fut: Future<Output = R> + 'static,
    R: IntoValue<'cx>,
  {
    self
      .data
      .async_fns
      .insert(name.to_string(), wrap_async_fn(f));
    self
  }

  pub fn class<T: Send + 'static>(
    mut self,
    name: impl ToString,
    f: impl Fn(NativeClassBuilder<false, T>) -> NativeClassDescriptor + Send + Sync + 'static,
  ) -> Self {
    let name = name.to_string();
    self
      .data
      .classes
      .insert(name.clone(), f(NativeClassBuilder::new(name)));
    self
  }

  pub fn finish(self) -> NativeModule {
    NativeModule {
      data: Arc::new(self.data),
    }
  }
}

pub struct NativeClassBuilder<const HAS_INIT: bool, T: Send> {
  descriptor: NativeClassDescriptor,
  ty: PhantomData<fn() -> T>,
}

impl<T: Send + 'static> NativeClassBuilder<false, T> {
  pub fn init(
    mut self,
    f: impl Fn(Scope<'_>) -> Result<T> + Send + Sync + 'static,
  ) -> NativeClassBuilder<true, T> {
    self.descriptor.init = Some(wrap_fn(move |scope| {
      let global = scope.global();
      global.new_instance(f(scope)?)
    }));
    NativeClassBuilder {
      descriptor: self.descriptor,
      ty: self.ty,
    }
  }
}

impl<const HAS_INIT: bool, T: Send + 'static> NativeClassBuilder<HAS_INIT, T> {
  pub fn new(name: StdString) -> Self {
    Self {
      descriptor: NativeClassDescriptor {
        name,
        type_id: TypeId::of::<T>(),
        init: None,
        fields: IndexMap::new(),
        methods: IndexMap::new(),
        static_methods: IndexMap::new(),
      },
      ty: PhantomData,
    }
  }

  pub fn finish(self) -> NativeClassDescriptor {
    self.descriptor
  }

  pub fn field<'cx, G, V>(mut self, name: impl ToString, get: G) -> Self
  where
    G: Fn(Scope<'cx>, This<'cx, T>) -> V + Send + Sync + 'static,
    V: IntoValue<'cx> + 'static,
  {
    self.descriptor.fields.insert(
      name.to_string(),
      NativeFieldDescriptor {
        get: wrap_getter(get),
        set: None,
      },
    );
    self
  }

  pub fn field_mut<'cx, G, S, V>(mut self, name: impl ToString, get: G, set: S) -> Self
  where
    G: Fn(Scope<'cx>, This<'cx, T>) -> V + Send + Sync + 'static,
    S: Fn(Scope<'cx>, This<'cx, T>, V) -> Result<()> + Send + Sync + 'static,
    V: IntoValue<'cx> + FromValue<'cx> + 'static,
  {
    self.descriptor.fields.insert(
      name.to_string(),
      NativeFieldDescriptor {
        get: wrap_getter(get),
        set: Some(wrap_setter(set)),
      },
    );
    self
  }

  pub fn method<'cx, R>(
    mut self,
    name: impl ToString,
    f: impl Fn(Scope<'cx>, This<'cx, T>) -> R + Send + Sync + 'static,
  ) -> Self
  where
    R: IntoValue<'cx>,
  {
    self.descriptor.methods.insert(
      name.to_string(),
      NativeMethodDescriptor::Sync(wrap_method(f)),
    );
    self
  }

  pub fn async_method<'cx, Fut, R>(
    mut self,
    name: impl ToString,
    f: impl Fn(Scope<'cx>, This<'cx, T>) -> Fut + Send + Sync + 'static,
  ) -> Self
  where
    Fut: Future<Output = R> + 'static,
    R: IntoValue<'cx>,
  {
    self.descriptor.methods.insert(
      name.to_string(),
      NativeMethodDescriptor::Async(wrap_async_method(f)),
    );
    self
  }

  pub fn static_method<'cx, R>(
    mut self,
    name: impl ToString,
    f: impl Fn(Scope<'cx>) -> R + Send + Sync + 'static,
  ) -> Self
  where
    R: IntoValue<'cx> + 'static,
  {
    self
      .descriptor
      .static_methods
      .insert(name.to_string(), NativeMethodDescriptor::Sync(wrap_fn(f)));
    self
  }
}

fn wrap_fn<'cx, R>(f: impl Fn(Scope<'cx>) -> R + Send + Sync + 'static) -> SyncCallback
where
  R: IntoValue<'cx> + 'static,
{
  Arc::new(move |scope| {
    let scope = unsafe { transmute::<_, Scope<'static>>(scope) };
    let global = scope.global();
    f(scope).into_value(global).map(|value| value.unbind())
  })
}

fn wrap_async_fn<'cx, Fut, R>(
  f: impl Fn(Scope<'cx>) -> Fut + Send + Sync + 'static,
) -> AsyncCallback
where
  Fut: Future<Output = R> + 'static,
  R: IntoValue<'cx>,
{
  Arc::new(move |scope| {
    let scope = unsafe { transmute::<_, Scope<'static>>(scope) };
    let global = scope.global();
    Box::pin(
      f(scope)
        .map(|value| value.into_value(global))
        .map_ok(|value| value.unbind()),
    )
  })
}

fn extract_this<T: Send + 'static>(scope: Scope<'_>) -> Result<(Scope<'_>, This<'_, T>)> {
  let this = scope
    .param::<Value>(0)?
    .unbind()
    .to_object::<NativeClassInstance>()
    .ok_or_else(|| {
      error!(
        "receiver is not an instance of {}",
        std::any::type_name::<T>()
      )
    })?;
  let this = This::new(this).ok_or_else(|| {
    error!(
      "receiver is not an instance of {}",
      std::any::type_name::<T>()
    )
  })?;
  let scope = Scope {
    thread: scope.thread,
    args: Args {
      start: scope.args.start + 1,
      count: scope.args.count - 1,
    },
    stack_base: scope.stack_base,
    lifetime: PhantomData,
  };
  Ok((scope, this))
}

fn wrap_method<'cx, T: Send + 'static, R>(
  f: impl Fn(Scope<'cx>, This<'cx, T>) -> R + Send + Sync + 'static,
) -> SyncCallback
where
  R: IntoValue<'cx>,
{
  Arc::new(move |scope| {
    let (scope, this) = extract_this::<T>(scope)?;
    let (scope, this) =
      unsafe { transmute::<_, (Scope<'static>, This<'static, T>)>((scope, this)) };
    let global = scope.global();
    f(scope, this)
      .into_value(global)
      .map(|value| value.unbind())
  })
}

fn wrap_async_method<'cx, T: Send + 'static, Fut, R>(
  f: impl Fn(Scope<'cx>, This<'cx, T>) -> Fut + Send + Sync + 'static,
) -> AsyncCallback
where
  Fut: Future<Output = R> + 'static,
  R: IntoValue<'cx>,
{
  Arc::new(move |scope| {
    let (scope, this) = match extract_this::<T>(scope) {
      Ok(v) => v,
      Err(e) => return Box::pin(future::ready(Err(e))),
    };
    let (scope, this) =
      unsafe { transmute::<_, (Scope<'static>, This<'static, T>)>((scope, this)) };
    let global = scope.global();
    Box::pin(
      f(scope, this)
        .map(|value| value.into_value(global))
        .map_ok(|value| value.unbind()),
    )
  })
}

fn wrap_getter<'cx, T: Send + 'static, R>(
  f: impl Fn(Scope<'cx>, This<'cx, T>) -> R + Send + Sync + 'static,
) -> SyncCallback
where
  R: IntoValue<'cx> + 'static,
{
  Arc::new(move |scope| {
    let (scope, this) = extract_this::<T>(scope)?;
    let (scope, this) =
      unsafe { transmute::<_, (Scope<'static>, This<'static, T>)>((scope, this)) };
    if scope.args.count > 0 {
      fail!("getter called with argument");
    }
    let global = scope.global();
    f(scope, this)
      .into_value(global)
      .map(|value| value.unbind())
  })
}

fn wrap_setter<'cx, T: Send + 'static, V>(
  f: impl Fn(Scope<'cx>, This<'cx, T>, V) -> Result<()> + Send + Sync + 'static,
) -> SyncCallback
where
  V: FromValue<'cx> + 'static,
{
  Arc::new(move |scope| {
    let (scope, this) = extract_this::<T>(scope)?;
    let (scope, this) =
      unsafe { transmute::<_, (Scope<'static>, This<'static, T>)>((scope, this)) };

    let value = scope.param::<V>(0)?;
    let mut scope = scope;
    scope.args.start += 1;
    scope.args.count -= 1;

    f(scope, this, value)?;
    Ok(OwnedValue::none())
  })
}