feat: WIP

This commit is contained in:
2025-08-22 21:40:52 +08:00
parent 3c7722a3d2
commit 65bcfcb47b
9 changed files with 312 additions and 154 deletions

View File

@@ -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