Skip to content

Commit

Permalink
Refactor modules
Browse files Browse the repository at this point in the history
  • Loading branch information
Rexagon committed Jul 29, 2023
1 parent 1da41f2 commit 07c4b78
Show file tree
Hide file tree
Showing 28 changed files with 1,672 additions and 1,095 deletions.
71 changes: 71 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ rust-version = "1.70"
include = ["src/**/*.rs", "LICENSE-*", "README.md"]
license = "MIT OR Apache-2.0"

[workspace]
members = ["proc"]

[dependencies]
num-bigint = "0.4"
num-traits = "0.2"
thiserror = "1.0"
hex = "0.4"
itertools = "0.10"
dyn-clone = "1.0"
unicode-segmentation = "1.0"

#everscale-types = "0.1.0-rc.0"
everscale-types = { git = "https://github.com/broxus/everscale-types.git" }

fift-proc = { path = "./proc" }
17 changes: 17 additions & 0 deletions proc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "fift-proc"
description = "Proc-macro helpers for fift"
repository = "https://github.com/broxus/fift"
version = "0.1.0"
edition = "2021"
include = ["src/**/*.rs", "../LICENSE-*", "../README.md"]
license = "MIT OR Apache-2.0"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["visit"] }
darling = "0.20"
173 changes: 173 additions & 0 deletions proc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use std::collections::HashMap;

use darling::{Error, FromMeta};
use proc_macro::TokenStream;
use quote::quote;
use syn::ItemImpl;

#[derive(Debug, FromMeta)]
struct FiftCmdArgs {
#[darling(default)]
tail: bool,
#[darling(default)]
active: bool,
#[darling(default)]
stack: bool,

#[darling(default)]
without_space: bool,

name: String,

#[darling(default)]
args: Option<HashMap<String, syn::Expr>>,
}

#[proc_macro_attribute]
pub fn fift_module(_: TokenStream, input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as ItemImpl);

let dict_arg = quote::format_ident!("__dict");

let mut functions = Vec::new();
let mut definitions = Vec::new();
let mut errors = Vec::new();

let mut init_functions = Vec::new();

for impl_item in input.items {
let syn::ImplItem::Fn(mut fun) = impl_item else {
continue;
};

let mut has_init = false;

let mut cmd_attrs = Vec::with_capacity(fun.attrs.len());
let mut remaining_attr = Vec::new();
for attr in fun.attrs.drain(..) {
if let Some(path) = attr.meta.path().get_ident() {
if path == "cmd" {
cmd_attrs.push(attr);
continue;
} else if path == "init" {
has_init = true;
continue;
}
}

remaining_attr.push(attr);
}
fun.attrs = remaining_attr;

if has_init {
fun.sig.ident = quote::format_ident!("__{}", fun.sig.ident);
init_functions.push(fun.sig.ident.clone());
} else {
for attr in cmd_attrs {
match process_cmd_definition(&fun, &dict_arg, attr) {
Ok(definition) => definitions.push(definition),
Err(e) => errors.push(e),
}
}
}

functions.push(fun);
}

if !errors.is_empty() {
return TokenStream::from(Error::multiple(errors).write_errors());
}

let ty = input.self_ty;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

quote! {
#[automatically_derived]
impl #impl_generics ::fift::core::Module for #ty #ty_generics #where_clause {
fn init(
&self,
#dict_arg: &mut ::fift::core::Dictionary,
) -> ::core::result::Result<(), ::fift::error::Error> {
#(#init_functions(#dict_arg)?;)*
#(#definitions?;)*
Ok(())
}
}

#(#functions)*
}
.into()
}

fn process_cmd_definition(
function: &syn::ImplItemFn,
dict_arg: &syn::Ident,
attr: syn::Attribute,
) -> Result<syn::Expr, Error> {
let cmd = FiftCmdArgs::from_meta(&attr.meta)?;

let reg_fn = match (cmd.tail, cmd.active, cmd.stack) {
(false, false, false) => quote! { define_context_word },
(true, false, false) => quote! { define_context_tail_word },
(false, true, false) => quote! { define_active_word },
(false, false, true) => quote! { define_stack_word },
_ => {
return Err(Error::custom(
"`tail`, `active` and `stack` cannot be used together",
));
}
};

let cmd_name = if cmd.without_space {
cmd.name.trim().to_owned()
} else {
format!("{} ", cmd.name.trim())
};

let function_name = function.sig.ident.clone();
let expr = match cmd.args {
None => {
quote! { #function_name }
}
Some(provided_args) => {
let ctx_arg = quote::format_ident!("__c");
let required_args = find_command_args(function)?;

let mut errors = Vec::new();
let mut closure_args = vec![quote! { #ctx_arg }];
for arg in required_args {
match provided_args.get(&arg) {
Some(value) => closure_args.push(quote! { #value }),
None => errors.push(Error::custom(format!(
"No value provided for the argument `{arg}`"
))),
}
}

quote! { |#ctx_arg| #function_name(#(#closure_args),*) }
}
};

Ok(syn::parse_quote! { #dict_arg.#reg_fn(#cmd_name, #expr) })
}

fn find_command_args(function: &syn::ImplItemFn) -> Result<Vec<String>, Error> {
let mut inputs = function.sig.inputs.iter();

if let Some(first) = inputs.next() {
if !matches!(first, syn::FnArg::Typed(_)) {
return Err(Error::custom("Command context argument not found").with_span(&function));
}
}

let mut args = Vec::new();
for input in inputs {
let syn::FnArg::Typed(input) = input else { continue };
let syn::Pat::Ident(pat) = &*input.pat else {
return Err(Error::custom("Unsupported argument binding").with_span(&input.pat));
};
args.push(pat.ident.to_string());
}

Ok(args)
}
Loading

0 comments on commit 07c4b78

Please sign in to comment.