feat: WIP
This commit is contained in:
@@ -18,7 +18,7 @@ use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use syn::{
|
||||
FnArg, Item, ItemFn, ItemMod, Pat, PatIdent, PatType, Type, Visibility, parse_macro_input,
|
||||
parse_macro_input, FnArg, Item, ItemConst, ItemFn, ItemMod, Pat, PatIdent, PatType, Type, Visibility
|
||||
};
|
||||
|
||||
/// The implementation of the `#[builtins]` macro.
|
||||
@@ -40,7 +40,6 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
};
|
||||
|
||||
let mut pub_item_mod = Vec::new();
|
||||
let mut consts = Vec::new();
|
||||
let mut global = Vec::new();
|
||||
let mut scoped = Vec::new();
|
||||
let mut wrappers = Vec::new();
|
||||
@@ -49,20 +48,17 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
for item in &items {
|
||||
match item {
|
||||
Item::Const(item_const) => {
|
||||
// Handle `const` definitions. These are exposed as constants in Nix.
|
||||
let name_str = item_const
|
||||
.ident
|
||||
.to_string()
|
||||
.from_case(Case::UpperSnake)
|
||||
.to_case(Case::Camel);
|
||||
let const_name = &item_const.ident;
|
||||
consts.push(quote! { (#name_str, builtins::#const_name) });
|
||||
pub_item_mod.push(
|
||||
quote! {
|
||||
pub #item_const
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let (primop, wrapper) = match generate_const_wrapper(item_const) {
|
||||
Ok(result) => result,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
// Public functions are added to the global scope, private ones to a scoped set.
|
||||
if matches!(item_const.vis, Visibility::Public(_)) {
|
||||
global.push(primop);
|
||||
} else {
|
||||
scoped.push(primop);
|
||||
}
|
||||
wrappers.push(wrapper);
|
||||
}
|
||||
Item::Fn(item_fn) => {
|
||||
// Handle function definitions. These become primops.
|
||||
@@ -90,7 +86,6 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
let consts_len = consts.len();
|
||||
let global_len = global.len();
|
||||
let scoped_len = scoped.len();
|
||||
|
||||
@@ -100,15 +95,12 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
#visibility mod #mod_name {
|
||||
#(#pub_item_mod)*
|
||||
#(#wrappers)*
|
||||
pub const CONSTS_LEN: usize = #consts_len;
|
||||
pub const GLOBAL_LEN: usize = #global_len;
|
||||
pub const SCOPED_LEN: usize = #scoped_len;
|
||||
}
|
||||
|
||||
/// A struct containing all the built-in constants and functions.
|
||||
pub struct Builtins<Ctx: BuiltinsContext> {
|
||||
/// Constant values available in the global scope.
|
||||
pub consts: [(&'static str, ::nixjit_value::Const); #mod_name::CONSTS_LEN],
|
||||
/// Global functions available in the global scope.
|
||||
pub global: [(&'static str, usize, fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::GLOBAL_LEN],
|
||||
/// Scoped functions, typically available under the `builtins` attribute set.
|
||||
@@ -119,7 +111,6 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
/// Creates a new instance of the `Builtins` struct.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
consts: [#(#consts,)*],
|
||||
global: [#(#global,)*],
|
||||
scoped: [#(#scoped,)*],
|
||||
}
|
||||
@@ -130,6 +121,33 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn generate_const_wrapper(
|
||||
item_const: &ItemConst,
|
||||
) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
|
||||
let const_name = &item_const.ident;
|
||||
let const_val = &item_const.expr;
|
||||
let name_str = const_name
|
||||
.to_string()
|
||||
.from_case(Case::UpperSnake)
|
||||
.to_case(Case::Camel);
|
||||
let const_name = format_ident!("{name_str}");
|
||||
let wrapper_name = format_ident!("wrapper_{}", const_name);
|
||||
let mod_name = format_ident!("builtins");
|
||||
|
||||
let fn_type = quote! { fn(&mut Ctx, ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> };
|
||||
|
||||
// The primop metadata tuple: (name, arity, wrapper_function_pointer)
|
||||
let primop = quote! { (#name_str, 0, #mod_name::#wrapper_name as #fn_type) };
|
||||
|
||||
// The generated wrapper function.
|
||||
let wrapper = quote! {
|
||||
pub fn #wrapper_name<Ctx: BuiltinsContext>(ctx: &mut Ctx, mut args: ::nixjit_eval::Args) -> ::nixjit_error::Result<::nixjit_eval::Value> {
|
||||
Ok(#const_val.into())
|
||||
}
|
||||
};
|
||||
|
||||
Ok((primop, wrapper))
|
||||
}
|
||||
/// Generates the primop metadata and the wrapper function for a single user-defined function.
|
||||
fn generate_primop_wrapper(
|
||||
item_fn: &ItemFn,
|
||||
@@ -166,7 +184,7 @@ fn generate_primop_wrapper(
|
||||
};
|
||||
|
||||
// Collect the remaining arguments.
|
||||
let arg_pats: Vec<_> = user_args.rev().collect();
|
||||
let arg_pats: Vec<_> = user_args.collect();
|
||||
let arg_count = arg_pats.len();
|
||||
|
||||
let arg_unpacks = arg_pats.iter().enumerate().map(|(i, arg)| {
|
||||
@@ -177,7 +195,7 @@ fn generate_primop_wrapper(
|
||||
};
|
||||
|
||||
quote! {
|
||||
let #arg_name: #arg_ty = args.pop().ok_or_else(|| ::nixjit_error::Error::eval_error("Not enough arguments provided".to_string()))?
|
||||
let #arg_name: #arg_ty = args.next().ok_or_else(|| ::nixjit_error::Error::eval_error("Not enough arguments provided".to_string()))?
|
||||
.try_into().map_err(|e| ::nixjit_error::Error::eval_error(format!("Argument type conversion failed: {}", e)))?;
|
||||
}
|
||||
});
|
||||
@@ -192,7 +210,6 @@ fn generate_primop_wrapper(
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
// Construct the argument list for the final call.
|
||||
@@ -232,6 +249,8 @@ fn generate_primop_wrapper(
|
||||
if args.len() != #arg_count {
|
||||
return Err(::nixjit_error::Error::eval_error(format!("Function '{}' expects {} arguments, but received {}", #name_str, #arg_count, args.len())));
|
||||
}
|
||||
|
||||
let mut args = args.into_iter();
|
||||
#(#arg_unpacks)*
|
||||
|
||||
#call_expr
|
||||
|
||||
Reference in New Issue
Block a user