use crate::function_expand::{expr_binding_tokens, expr_result_wrap_tokens};
use crate::native_class_expand::{native_class_call_dispatch, native_class_validation_error};
use crate::native_macro_expand::native_macro_call_dispatch;
use crate::native_number_expand::native_number_domain_call_dispatch;
use crate::parse::{ParsedCodec, ParsedFunctionKind, ParsedModule, ParsedSite, ParsedType};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{Ident, LitStr};
impl ParsedModule {
pub(super) fn native_export_impl(
&self,
lib_ident: &Ident,
enabled: bool,
) -> syn::Result<TokenStream2> {
if !enabled {
return Ok(TokenStream2::new());
}
if let Some(message) = self.native_export_validation_error() {
let message = LitStr::new(&message, Span::call_site());
return Ok(quote!(compile_error!(#message);));
}
let native_class_call_dispatch = native_class_call_dispatch(&self.classes, &self.functions);
let native_macro_call_dispatch = native_macro_call_dispatch(&self.macros);
let native_call_dispatch = self.native_call_dispatch();
let native_codec_call_dispatch = self.native_codec_call_dispatch();
let native_number_domain_call_dispatch =
native_number_domain_call_dispatch(&self.number_domains);
let native_site_call_dispatch = self.native_site_call_dispatch();
Ok(quote! {
mod native_abi_export {
use super::#lib_ident;
struct NativeLibInstance(Box<dyn ::sim::kernel::Lib>);
#[allow(unsafe_code)]
unsafe extern "C" fn instantiate() -> *mut ::std::ffi::c_void {
let instance = Box::new(NativeLibInstance(Box::new(#lib_ident)));
Box::into_raw(instance).cast::<::std::ffi::c_void>()
}
#[allow(unsafe_code)]
unsafe extern "C" fn destroy_instance(instance: *mut ::std::ffi::c_void) {
if instance.is_null() {
return;
}
drop(Box::from_raw(instance.cast::<NativeLibInstance>()));
}
#[allow(unsafe_code)]
unsafe extern "C" fn manifest(
instance: *mut ::std::ffi::c_void,
) -> ::sim::kernel::NativeAbiCallResponse {
let lib = &(*instance.cast::<NativeLibInstance>()).0;
match ::sim::loaders::encode_native_manifest_response(&lib.manifest()) {
Ok(response) => response,
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn call(
instance: *mut ::std::ffi::c_void,
function: *const ::std::ffi::c_char,
args: ::sim::kernel::NativeAbiBorrowedBytes,
) -> ::sim::kernel::NativeAbiCallResponse {
let _lib = &(*instance.cast::<NativeLibInstance>()).0;
let function = if function.is_null() {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed("native ABI call received a null function symbol"),
);
} else {
::std::ffi::CStr::from_ptr(function).to_string_lossy().into_owned()
};
let arg_bytes = if args.ptr.is_null() && args.len == 0 {
&[][..]
} else {
::std::slice::from_raw_parts(args.ptr, args.len)
};
let expr = match ::sim::codec_binary::decode_frame(::sim::kernel::CodecId(0), arg_bytes) {
Ok((_, expr)) => expr,
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
};
match native_class_call(function.as_str(), &expr) {
Ok(Some(expr)) => {
return match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
};
}
Ok(None) => {}
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
}
match native_macro_call(function.as_str(), &expr) {
Ok(Some(expr)) => {
return match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
};
}
Ok(None) => {}
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
}
match native_number_domain_call(function.as_str(), &expr) {
Ok(Some(expr)) => {
return match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
};
}
Ok(None) => {}
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
}
match native_codec_call(function.as_str(), &expr) {
Ok(Some(expr)) => {
return match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
};
}
Ok(None) => {}
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
}
match native_site_call(function.as_str(), &expr) {
Ok(Some(expr)) => {
return match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
};
}
Ok(None) => {}
Err(err) => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
);
}
}
let args = match expr {
::sim::kernel::Expr::List(items) => items,
_ => {
return ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed("native ABI expected argument frame to decode to an expr list"),
);
}
};
match native_call(function.as_str(), args) {
Ok(expr) => match ::sim::codec_binary::encode_frame(&expr) {
Ok(frame) => ::sim::kernel::NativeAbiCallResponse::success(
::sim::kernel::native_abi_owned_bytes(frame.0),
),
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
},
Err(err) => ::sim::kernel::NativeAbiCallResponse::failure(
::sim::kernel::NativeAbiError::boxed(err.to_string()),
),
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn destroy_bytes(bytes: ::sim::kernel::NativeAbiOwnedBytes) {
if !bytes.ptr.is_null() {
drop(Vec::from_raw_parts(bytes.ptr, bytes.len, bytes.cap));
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn destroy_error(error: *mut ::sim::kernel::NativeAbiError) {
if error.is_null() {
return;
}
let error = Box::from_raw(error);
if !error.message.is_null() {
drop(::std::ffi::CString::from_raw(error.message));
}
}
#native_class_call_dispatch
#native_macro_call_dispatch
#native_call_dispatch
#native_codec_call_dispatch
#native_number_domain_call_dispatch
#native_site_call_dispatch
static ABI: ::sim::kernel::NativeLibAbiV1 =
::sim::kernel::NativeLibAbiV1::new(
instantiate,
destroy_instance,
manifest,
call,
destroy_bytes,
destroy_error,
);
#[unsafe(no_mangle)]
pub extern "C" fn sim_native_abi_v1() -> *const ::sim::kernel::NativeLibAbiV1 {
&ABI
}
}
})
}
fn native_export_validation_error(&self) -> Option<String> {
if let Some(message) = native_class_validation_error(&self.classes, &self.functions) {
return Some(message);
}
for function in &self.functions {
let ParsedFunctionKind::Function { .. } = function.kind else {
continue;
};
for (_, ty) in &function.inputs {
if matches!(ty, ParsedType::RefClass(_) | ParsedType::OwnedClass(_)) {
return Some(
"native_export = true only supports f64, bool, String, Symbol, and Expr arguments".to_owned(),
);
}
}
if matches!(
function.output,
Some(ParsedType::RefClass(_)) | Some(ParsedType::OwnedClass(_))
) {
return Some(
"native_export = true only supports f64, bool, String, Symbol, Expr, or nil returns".to_owned(),
);
}
}
None
}
fn native_codec_call_dispatch(&self) -> TokenStream2 {
let arms = self.codecs.iter().flat_map(codec_dispatch_arms);
let result_adapter = if self.codecs.is_empty() {
TokenStream2::new()
} else {
quote! {
trait NativeCodecResult<T> {
fn into_native_codec_result(self) -> ::sim::kernel::Result<T>;
}
impl NativeCodecResult<::sim::kernel::Expr> for ::sim::kernel::Expr {
fn into_native_codec_result(self) -> ::sim::kernel::Result<::sim::kernel::Expr> {
Ok(self)
}
}
impl NativeCodecResult<::sim::kernel::Expr>
for ::sim::kernel::Result<::sim::kernel::Expr>
{
fn into_native_codec_result(self) -> ::sim::kernel::Result<::sim::kernel::Expr> {
self
}
}
impl NativeCodecResult<String> for String {
fn into_native_codec_result(self) -> ::sim::kernel::Result<String> {
Ok(self)
}
}
impl NativeCodecResult<String> for ::sim::kernel::Result<String> {
fn into_native_codec_result(self) -> ::sim::kernel::Result<String> {
self
}
}
}
};
quote! {
#result_adapter
fn native_codec_call(
function: &str,
expr: &::sim::kernel::Expr,
) -> ::sim::kernel::Result<Option<::sim::kernel::Expr>> {
match function {
#(#arms,)*
_ => Ok(None),
}
}
}
}
fn native_site_call_dispatch(&self) -> TokenStream2 {
let arms = self.sites.iter().map(site_dispatch_arm);
let result_adapter = if self.sites.is_empty() {
TokenStream2::new()
} else {
quote! {
trait NativeSiteResult {
fn into_native_site_result(self) -> ::sim::kernel::Result<::sim::kernel::Expr>;
}
impl NativeSiteResult for ::sim::kernel::Expr {
fn into_native_site_result(self) -> ::sim::kernel::Result<::sim::kernel::Expr> {
Ok(self)
}
}
impl NativeSiteResult for ::sim::kernel::Result<::sim::kernel::Expr> {
fn into_native_site_result(self) -> ::sim::kernel::Result<::sim::kernel::Expr> {
self
}
}
}
};
quote! {
#result_adapter
fn native_site_call(
function: &str,
expr: &::sim::kernel::Expr,
) -> ::sim::kernel::Result<Option<::sim::kernel::Expr>> {
match function {
#(#arms,)*
_ => Ok(None),
}
}
}
}
fn native_call_dispatch(&self) -> TokenStream2 {
let arms = self
.functions
.iter()
.filter_map(|function| match function.kind {
ParsedFunctionKind::Function { .. } => {
let rust_ident = &function.rust_ident;
let symbol_name = match &function.kind {
ParsedFunctionKind::Function { lisp_name } => lisp_name.clone(),
ParsedFunctionKind::Constructor { .. } => unreachable!(),
};
let symbol_lit = LitStr::new(&symbol_name, Span::call_site());
let expected_len = function.inputs.len();
let arg_bindings = function
.inputs
.iter()
.enumerate()
.map(|(index, (name, ty))| expr_binding_tokens(index, name, ty));
let call_args = function.inputs.iter().map(|(name, _)| quote!(#name));
let result_wrap =
expr_result_wrap_tokens(function.output.as_ref(), quote!(result));
Some(quote! {
#symbol_lit => {
if args.len() != #expected_len {
return Err(::sim::kernel::Error::Eval(format!(
"{} expects {} args, got {}",
#symbol_lit,
#expected_len,
args.len(),
)));
}
#(#arg_bindings)*
let result = super::#rust_ident(#(#call_args),*);
Ok(#result_wrap)
}
})
}
ParsedFunctionKind::Constructor { .. } => None,
});
quote! {
fn native_call(
function: &str,
args: Vec<::sim::kernel::Expr>,
) -> ::sim::kernel::Result<::sim::kernel::Expr> {
match function {
#(#arms,)*
_ => Err(::sim::kernel::Error::UnknownFunction {
function: ::sim::kernel::Symbol::new(function),
}),
}
}
}
}
}
fn site_dispatch_arm(site: &ParsedSite) -> TokenStream2 {
let op = LitStr::new(&format!("{}/realize", site.symbol), Span::call_site());
let realize_ident = &site.realize_ident;
quote! {
#op => {
let args = match expr {
::sim::kernel::Expr::List(items) => items.clone(),
other => {
return Err(::sim::kernel::Error::HostError(format!(
"{} expected realize args list, got {:?}",
#op,
other,
)));
}
};
super::#realize_ident(args)
.into_native_site_result()
.map(Some)
}
}
}
fn codec_dispatch_arms(codec: &ParsedCodec) -> [TokenStream2; 2] {
let decode_op = LitStr::new(&format!("{}/decode", codec.symbol), Span::call_site());
let encode_op = LitStr::new(&format!("{}/encode", codec.symbol), Span::call_site());
let decode_ident = &codec.decode_ident;
let encode_ident = &codec.encode_ident;
[
quote! {
#decode_op => {
let text = match expr {
::sim::kernel::Expr::String(text) => text.clone(),
other => {
return Err(::sim::kernel::Error::HostError(format!(
"{} expected decode input text, got {:?}",
#decode_op,
other,
)));
}
};
let decoded: ::sim::kernel::Expr =
NativeCodecResult::into_native_codec_result(super::#decode_ident(text))?;
Ok(Some(decoded))
}
},
quote! {
#encode_op => {
let rendered: String =
NativeCodecResult::into_native_codec_result(super::#encode_ident(expr.clone()))?;
Ok(Some(::sim::kernel::Expr::String(rendered)))
}
},
]
}