diff --git a/src/Cargo.lock b/src/Cargo.lock index 890bb973..6d3c457b 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -50,12 +50,27 @@ dependencies = [ "spin 0.7.1", ] +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "font8x8" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63201c624b8c8883921b1a1accc8916c4fa9dbfb15d122b26e4dde945b86bbf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "json" version = "0.12.4" @@ -117,6 +132,15 @@ dependencies = [ "spin 0.9.4", ] +[[package]] +name = "kxos-rights-proc" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "kxos-std" version = "0.1.0" @@ -131,6 +155,20 @@ dependencies = [ "xmas-elf", ] +[[package]] +name = "kxos-typeflags" +version = "0.1.0" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "kxos-typeflags-util" +version = "0.1.0" + [[package]] name = "kxos-util" version = "0.1.0" diff --git a/src/Cargo.toml b/src/Cargo.toml index b1e0c20a..1c252408 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -18,6 +18,9 @@ members = [ "kxos-virtio", "kxos-util", "kxos-frame-pod-derive", + "kxos-typeflags", + "kxos-typeflags-util", + "kxos-rights-proc", ] [package.metadata.bootloader] diff --git a/src/kxos-rights-proc/Cargo.toml b/src/kxos-rights-proc/Cargo.toml new file mode 100644 index 00000000..2ae1454c --- /dev/null +++ b/src/kxos-rights-proc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "kxos-rights-proc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = {version = "1.0.90", features = ["full", "fold"]} diff --git a/src/kxos-rights-proc/src/lib.rs b/src/kxos-rights-proc/src/lib.rs new file mode 100644 index 00000000..12cf3529 --- /dev/null +++ b/src/kxos-rights-proc/src/lib.rs @@ -0,0 +1,92 @@ +//!This crate defines the require procedural macros to implement capability for kxos. +//! When use this crate, kxos-typeflags and kxos-typeflags-util should also be added as dependency. +//! +//! The require macro are used to ensure that an object has the enough capability to call the function. +//! The **require** macro can accept constrait [SomeRightSet] > [SomeRight], +//! which means the SomeRightSet should **contain** the SomeRight. +//! The **require** macro can also accept constrait [SomeRightSet] > [AnotherRightSet], +//! which means the SomeRightSet should **include** the AnotherRightSet. In this case, AnotherRightSet should be a **generic parameter**. +//! i.e., AnotherRightSet should occur the the generic param list of the function. +//! +//! If there are multiple constraits, they can be seperated with `|`, which means all constraits should be satisfied. +//! +//! The require can also be used multiple times, which means each macro should be satisfied. +//! +//! Below is a simple example. +//! Suppose we have a channel that may have read and write capability. +//! +//! ```rust +//! /// A simple channel, only for demonstration. +//! struct Channel { +//! rights: PhantomData, +//! } +//! impl Channel { +//! pub fn new() -> Self { +//! Channel { rights: PhantomData } +//! } + +//! #[require(R > Read)] +//! pub fn read(&self) {} +//! +//! #[require(R > Write)] +//! pub fn write(&self) {} +//! +//! #[require(R > Read | Write)] +//! pub fn read_write(&self) {} + +//! #[require(R > R1)] +//! pub fn restrict(self) -> Channel where R1: RightSet { +//! Channel::new() +//! } +//! } +//! ``` +//! When we initialize channels with different rights, it can check whether the function +//! are wrongly used due to lack of capabilities at compilation time. +//! ```rust +//! let read_channel = Channel::::new(); +//! read_channel.read(); +//! // read_channel.write(); // compilation error! +//! // read_channel.read_write(); // compilation error! +//! let _ = read_channel.restrict::(); +//! +//! let write_channel = Channel::::new(); +//! write_channel.write(); +//! // write_channel.read(); // compilation error! +//! // write_channel.read_write(); // compilation error! +//! let _ = write_channel.restrict::(); +//! // let _ = write_channel.restrict::(); // compilation error! +//! +//! let rw_channel = Channel::::new(); +//! rw_channel.read(); +//! rw_channel.write(); +//! rw_channel.read_write(); +//! let rchannel = rw_channel.restrict::(); +//! rchannel.read(); +//! // rchannel.write(); // compilation error! +//! +//! let no_rw_channel = Channel::::new(); +//! // no_rw_channel.read(); // compilation error! +//! // no_rw_channel.write(); // compilation error! +//! // no_rw_channel.read_write(); // compilation error! +//! ``` + +#![feature(proc_macro_diagnostic)] +#![allow(dead_code)] + +use syn::parse_macro_input; +use syn::ItemFn; + +use crate::require_attr::expand_require; +use crate::require_attr::RequireAttr; + +mod require_attr; + +#[proc_macro_attribute] +pub fn require( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let item_fn = parse_macro_input!(item as ItemFn); + let require_attr = parse_macro_input!(attr as RequireAttr); + expand_require(item_fn, require_attr).into() +} diff --git a/src/kxos-rights-proc/src/require_attr.rs b/src/kxos-rights-proc/src/require_attr.rs new file mode 100644 index 00000000..24f50e6f --- /dev/null +++ b/src/kxos-rights-proc/src/require_attr.rs @@ -0,0 +1,194 @@ +//! expand the require macro + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + fold::Fold, parse::Parse, parse_quote, punctuated::Punctuated, token::Comma, GenericParam, + Generics, ItemFn, Token, Type, WhereClause, +}; + +pub struct RequireAttr { + type_set: Type, + required_types: Punctuated, +} + +impl Parse for RequireAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let type_set: Type = input.parse()?; + let _: Token![>] = input.parse()?; + let required_types = input.parse_terminated(Ident::parse)?; + Ok(RequireAttr { + type_set, + required_types, + }) + } +} + +impl Fold for RequireAttr { + fn fold_generics(&mut self, i: Generics) -> Generics { + let Generics { + lt_token, + params, + gt_token, + where_clause, + } = i; + + let mut new_where_clause = where_clause; + for required_type in self.required_types.iter() { + // If required type is a generic param, the required type should be a type set, we use SetInclude. + // Otherwise, we use SetContain. + if is_generic_param(required_type.clone(), ¶ms) { + new_where_clause = Some(set_include_where_clause( + self, + required_type.clone(), + new_where_clause, + )); + } else { + new_where_clause = Some(set_contain_where_clause( + self, + required_type.clone(), + new_where_clause, + )); + } + } + + Generics { + lt_token, + params, + gt_token, + where_clause: new_where_clause, + } + } +} + +pub fn expand_require(item_fn: ItemFn, mut require_attr: RequireAttr) -> TokenStream { + let new_item_fn = require_attr.fold_item_fn(item_fn); + quote!( + #new_item_fn + ) +} + +fn is_generic_param(ident: Ident, generic_params: &Punctuated) -> bool { + for generic_param in generic_params { + match generic_param { + GenericParam::Type(type_param) => { + let type_param_ident = type_param.ident.clone(); + if ident.to_string() == type_param_ident.to_string() { + return true; + } + } + GenericParam::Const(const_param) => { + let const_param_ident = const_param.ident.clone(); + if ident.to_string() == const_param_ident.to_string() { + return true; + } + } + _ => {} + } + } + + false +} + +fn set_contain_where_clause( + require_attr: &RequireAttr, + required_type: Ident, + old_where_clause: Option, +) -> WhereClause { + let type_set = require_attr.type_set.clone(); + let mut where_predicates = Vec::new(); + where_predicates.push(parse_quote!( + #type_set: ::kxos_typeflags_util::SetContain<#required_type> + )); + where_predicates.push(parse_quote!( + ::kxos_typeflags_util::SetContainOp<#type_set, #required_type>: ::kxos_typeflags_util::IsTrue + )); + + let comma = parse_quote!(,); + match old_where_clause { + None => { + let where_token = parse_quote!(where); + let mut predicates = Punctuated::new(); + for predicate in where_predicates { + if !predicates.empty_or_trailing() { + predicates.push_punct(comma); + } + predicates.push_value(predicate); + } + WhereClause { + where_token, + predicates, + } + } + Some(old_where_clause) => { + let WhereClause { + where_token, + mut predicates, + } = old_where_clause; + + for predicate in where_predicates { + if !predicates.empty_or_trailing() { + predicates.push_punct(comma); + } + predicates.push_value(predicate); + } + WhereClause { + where_token, + predicates, + } + } + } +} + +fn set_include_where_clause( + require_attr: &RequireAttr, + required_type_set: Ident, + old_where_clause: Option, +) -> WhereClause { + let type_set = require_attr.type_set.clone(); + let comma = parse_quote!(,); + + let mut additional_where_prediates = Vec::new(); + additional_where_prediates.push(parse_quote!( + #required_type_set: ::kxos_typeflags_util::Set + )); + additional_where_prediates.push(parse_quote!( + #type_set: ::kxos_typeflags_util::SetInclude<#required_type_set> + )); + additional_where_prediates.push(parse_quote!( + ::kxos_typeflags_util::SetIncludeOp<#type_set, #required_type_set>: ::kxos_typeflags_util::IsTrue + )); + + match old_where_clause { + None => { + let where_token = parse_quote!(where); + let mut predicates = Punctuated::new(); + for predicate in additional_where_prediates.into_iter() { + if !predicates.empty_or_trailing() { + predicates.push_punct(comma); + } + predicates.push_value(predicate); + } + WhereClause { + where_token, + predicates, + } + } + Some(old_where_clause) => { + let WhereClause { + where_token, + mut predicates, + } = old_where_clause; + for predicate in additional_where_prediates.into_iter() { + if !predicates.empty_or_trailing() { + predicates.push_punct(comma); + } + predicates.push_value(predicate); + } + WhereClause { + where_token, + predicates, + } + } + } +} diff --git a/src/kxos-typeflags-util/Cargo.toml b/src/kxos-typeflags-util/Cargo.toml new file mode 100644 index 00000000..f47df814 --- /dev/null +++ b/src/kxos-typeflags-util/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "kxos-typeflags-util" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/kxos-typeflags-util/src/assert.rs b/src/kxos-typeflags-util/src/assert.rs new file mode 100644 index 00000000..f4458a3e --- /dev/null +++ b/src/kxos-typeflags-util/src/assert.rs @@ -0,0 +1,21 @@ +//! define macro assert_type_same + +use crate::same::SameAs; + +pub type AssertTypeSame = >::Output; + +#[macro_export] +macro_rules! assert_type_same { + ($lhs:ty, $rhs:ty) => { + const _: $crate::assert::AssertTypeSame<$lhs, $rhs> = crate::True; + }; +} + +#[cfg(test)] +mod test { + #[test] + fn test() { + assert_type_same!(u16, u16); + assert_type_same!((), ()); + } +} diff --git a/src/kxos-typeflags-util/src/bool.rs b/src/kxos-typeflags-util/src/bool.rs new file mode 100644 index 00000000..d685a766 --- /dev/null +++ b/src/kxos-typeflags-util/src/bool.rs @@ -0,0 +1,78 @@ +//! Type level bools + +pub use std::ops::BitAnd as And; +pub use std::ops::BitOr as Or; +pub use std::ops::Not; + +pub trait Bool {} + +/// Type-level "true". +pub struct True; + +/// Type-level "false". +pub struct False; + +impl Bool for True {} +impl Bool for False {} + +impl Not for True { + type Output = False; + + fn not(self) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +impl Not for False { + type Output = True; + + fn not(self) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +impl And for True { + type Output = B; + + fn bitand(self, _rhs: B) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +impl And for False { + type Output = False; + + fn bitand(self, _rhs: B) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +impl Or for True { + type Output = True; + + fn bitor(self, _rhs: B) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +impl Or for False { + type Output = B; + + fn bitor(self, _rhs: B) -> Self::Output { + unimplemented!("not supposed to be used") + } +} + +pub type NotOp = ::Output; +pub type AndOp = >::Output; +pub type OrOp = >::Output; + +// In where clause, we can only use trait bounds, but not equal bounds. +// For certain situation, we need to do some comparison in where clause. +// e.g., we need to determine the result type of `SetContainOp` is `True` or `False`. +// Since Sometype == True is not allowed, We can use SomeType: IsTrue to determine the result. +pub trait IsTrue {} +pub trait IsFalse {} + +impl IsTrue for True {} +impl IsFalse for False {} diff --git a/src/kxos-typeflags-util/src/if_.rs b/src/kxos-typeflags-util/src/if_.rs new file mode 100644 index 00000000..4b144551 --- /dev/null +++ b/src/kxos-typeflags-util/src/if_.rs @@ -0,0 +1,29 @@ +//! Type Level If + +use crate::bool::{False, True}; + +pub trait If { + type Output; +} + +impl If for True { + type Output = B1; +} + +impl If for False { + type Output = B2; +} + +pub type IfOp = >::Output; + +#[cfg(test)] +mod test { + use super::*; + use crate::*; + + #[test] + fn test() { + assert_type_same!(IfOp, u32); + assert_type_same!(IfOp, usize); + } +} diff --git a/src/kxos-typeflags-util/src/lib.rs b/src/kxos-typeflags-util/src/lib.rs new file mode 100644 index 00000000..a55c8af6 --- /dev/null +++ b/src/kxos-typeflags-util/src/lib.rs @@ -0,0 +1,15 @@ +//! The content of this crate is from another project CapComp. +//! This crate defines common type level operations, like SameAsOp, and Bool type operations. +//! Besides, this crate defines operations to deal with type sets, like SetContain and SetInclude. +//! When use kxos-typeflags or kxos-rights-poc, this crate should also be added as a dependency. + +pub mod assert; +pub mod bool; +pub mod if_; +pub mod same; +pub mod set; + +pub use crate::bool::{And, AndOp, False, Not, NotOp, Or, OrOp, True, IsFalse, IsTrue}; +pub use crate::same::{SameAs, SameAsOp}; +pub use crate::set::{Cons, Nil, Set, SetContain, SetContainOp, SetInclude, SetIncludeOp}; +pub use assert::AssertTypeSame; diff --git a/src/kxos-typeflags-util/src/same.rs b/src/kxos-typeflags-util/src/same.rs new file mode 100644 index 00000000..66b19330 --- /dev/null +++ b/src/kxos-typeflags-util/src/same.rs @@ -0,0 +1,28 @@ +//! Traits used to check if two types are the same, returning a Bool. +//! This check happens at compile time + +use crate::bool::{Bool, False, True}; + +pub trait SameAs { + type Output: Bool; +} + +// A type is always same as itself +impl SameAs for T { + type Output = True; +} + +impl SameAs for True { + type Output = False; +} + +impl SameAs for False { + type Output = False; +} + +// How to implement self reflection? +// impl SameAs for U where T: SameAs, { +// type Output = >::Output; +// } + +pub type SameAsOp = >::Output; diff --git a/src/kxos-typeflags-util/src/set.rs b/src/kxos-typeflags-util/src/set.rs new file mode 100644 index 00000000..23cac701 --- /dev/null +++ b/src/kxos-typeflags-util/src/set.rs @@ -0,0 +1,107 @@ +//! Common types and traits to deal with type-level sets + +use std::marker::PhantomData; + +use crate::{ + False, OrOp, True, + SameAs, SameAsOp, + And, AndOp, + if_::{If, IfOp}, +}; + +use std::ops::BitOr as Or; + +/// A marker trait for type-level sets. +pub trait Set {} + +/// An non-empty type-level set. +pub struct Cons(PhantomData<(T, S)>); + +impl Cons { + pub fn new() -> Self { + Cons(PhantomData) + } +} + +/// An empty type-level set. +pub struct Nil; + +impl Set for Cons {} +impl Set for Nil {} + +/// A trait operator to check if `T` is a member of a type set; +pub trait SetContain { + type Output; +} + +pub type SetContainOp = >::Output; + +impl SetContain for Nil { + type Output = False; +} + +impl SetContain for Cons +where + S: Set + SetContain, + U: SameAs, + SameAsOp: Or>, +{ + type Output = OrOp, SetContainOp>; +} + +/// A trait operator to check if a set A includes a set B, i.e., A is a superset of B. +pub trait SetInclude { + type Output; +} + +pub type SetIncludeOp = >::Output; + +impl SetInclude for Nil { + type Output = True; +} + +impl SetInclude> for Nil { + type Output = False; +} + +impl SetInclude for Cons { + type Output = True; +} + +impl SetInclude> for Cons +where + SubS: Set, + SuperS: Set + SetInclude + SetContain, + SuperT: SameAs, + SetContainOp: And>, + SameAsOp: If< + SetIncludeOp, + AndOp, SetIncludeOp>, + >, +{ + type Output = IfOp< + SameAsOp, + SetIncludeOp, + AndOp, SetIncludeOp>, + >; +} + +#[cfg(test)] +mod test { + use crate::*; + + #[test] + fn test() { + assert_type_same!(SetContainOp, False>, False); + assert_type_same!(SetContainOp, True>, True); + + assert_type_same!( + SetIncludeOp>, Cons>, + True + ); + assert_type_same!( + SetIncludeOp>, Cons>, + False + ); + } +} diff --git a/src/kxos-typeflags/Cargo.toml b/src/kxos-typeflags/Cargo.toml new file mode 100644 index 00000000..03d2c301 --- /dev/null +++ b/src/kxos-typeflags/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "kxos-typeflags" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = {version = "1.0.90"} +itertools = "0.10.5" diff --git a/src/kxos-typeflags/src/flag_set.rs b/src/kxos-typeflags/src/flag_set.rs new file mode 100644 index 00000000..3a7c181d --- /dev/null +++ b/src/kxos-typeflags/src/flag_set.rs @@ -0,0 +1,163 @@ +use itertools::Itertools; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, TokenStreamExt}; +use syn::Expr; + +use crate::type_flag::TypeFlagDef; + +const EMPTY_SET_NAME: &'static str = "::kxos_typeflags_util::Nil"; +const SET_NAME: &'static str = "::kxos_typeflags_util::Cons"; + +/// A flagSet represent the combination of differnt flag item. +/// e.g. [Read, Write], [Read], [] are all flag sets. +/// The order of flagItem does not matters. So flag sets with same sets of items should be viewed as the same set. +pub struct FlagSet { + items: Vec, +} + +impl FlagSet { + /// create a new empty flag set + pub fn new() -> Self { + FlagSet { items: Vec::new() } + } + + /// add a flag item + pub fn push_item(&mut self, flag_item: FlagItem) { + self.items.push(flag_item); + } + + /// the flag set string. debug use. + pub fn to_string(&self) -> String { + if self.items.len() == 0 { + return EMPTY_SET_NAME.to_string(); + } + + let mut res = EMPTY_SET_NAME.to_string(); + + for item in self.items.iter() { + let replace_set = format!( + "{}<{}, {}>", + SET_NAME, + item.ident.to_string(), + EMPTY_SET_NAME + ); + res = res.replace(EMPTY_SET_NAME, &replace_set); + } + res + } + + /// the tokens represents the flag set type name + pub fn type_name_tokens(&self) -> TokenStream { + let mut res = quote!(::kxos_typeflags_util::Nil); + + for item in self.items.iter() { + let ident = item.ident.clone(); + + // insert new item as head + let new_res = quote! { + ::kxos_typeflags_util::Cons<#ident, #res> + }; + res = new_res; + } + + res + } + + /// the number of items + pub fn len(&self) -> usize { + self.items.len() + } + + /// the tokens to impl main trait for the current flagset + pub fn impl_main_trait_tokens(&self, type_flags_def: &TypeFlagDef) -> TokenStream { + let trait_ident = type_flags_def.trait_ident(); + let name = self.type_name_tokens(); + let mut all_tokens = quote! ( + impl #trait_ident for #name + ); + all_tokens.append_all(self.inner_tokens(type_flags_def)); + all_tokens + } + + /// the impl main trait inner content + fn inner_tokens(&self, type_flags_def: &TypeFlagDef) -> TokenStream { + let ty = type_flags_def.val_type(); + let item_vals = self.items.iter().map(|item| item.val.clone()); + + // quote seems unable to use string for types. + // So we hardcode all types here. + if item_vals.len() == 0 { + quote!( + { + const BITS: #ty = 0 ; + fn new() -> Self { + ::kxos_typeflags_util::Nil + } + } + ) + } else { + quote!( + { + const BITS: #ty = #(#item_vals)|* ; + fn new() -> Self { + ::kxos_typeflags_util::Cons::new() + } + } + ) + } + } + + /// The token stream inside macro definition. We will generate a token stream for each permutation of items + /// since the user may use arbitrary order of items in macro. + pub fn macro_item_tokens(&self) -> Vec { + let res_type = self.type_name_tokens(); + // We first calculate the full permutations, + let item_permutations = self.items.iter().permutations(self.items.len()); + item_permutations + .map(|flag_items| { + let item_names = flag_items + .into_iter() + .map(|flag_item| flag_item.ident.clone()) + .collect::>(); + quote! ( + (#(#item_names),*) => { #res_type } + ) + }) + .collect() + } +} + +#[derive(Clone)] +pub struct FlagItem { + /// the user provided name + ident: Ident, + /// the user-provided val + val: Expr, +} + +/// generate all possible flag sets +pub fn generate_flag_sets(type_flag_def: &TypeFlagDef) -> Vec { + let flag_items = type_flag_def + .items_iter() + .map(|type_flag_item| { + let ident = type_flag_item.ident(); + let val = type_flag_item.val(); + FlagItem { ident, val } + }) + .collect::>(); + let flag_item_num = flag_items.len(); + let limit = 1 << flag_items.len(); + let mut res = Vec::with_capacity(limit); + + for i in 0..limit { + let mut flag_set = FlagSet::new(); + for j in 0..flag_item_num { + if (i >> j) & 0x1 == 1usize { + flag_set.push_item(flag_items[j].clone()); + } + } + res.push(flag_set); + } + + res +} diff --git a/src/kxos-typeflags/src/lib.rs b/src/kxos-typeflags/src/lib.rs new file mode 100644 index 00000000..9484ec9e --- /dev/null +++ b/src/kxos-typeflags/src/lib.rs @@ -0,0 +1,63 @@ +//!This crate defines the procedural macro typeflags to implement capability for kxos. +//! When using this crate, kxos-typeflags-util should also be added as dependency. +//! This is due to kxos-typeflgas is a proc-macro crate, which is only allowed to export proc-macro interfaces. +//! So we leave the common type-level operations and structures defined in kxos-typeflags-util. +//! +//! type_flag is used to define another declarive macro to define type set. +//! It can be used as the following example. +//! ```rust +//! type_flags! { +//! pub trait RightSet: u32 { +//! struct Read = 1 << 1; +//! struct Write = 1 << 2; +//! } +//! } +//! ``` +//! The code will generate a macro with the name as RightSet, we can use this macro to define typesets with different types. +//! Usage example: +//! ```rust +//! type O = RightSet![]; // Nil +//! type R = RightSet![Read]; // Cons +//! type W = RightSet![Write]; // Cons +//! type RW = RightSet![Read, Write]; // Cons> +//! type WR = RightSet![Write, Read]; // Cons> +//! ``` +//! +//! Test Example +//! ```rust +//! use kxos_typeflags_util::*; +//! assert_eq!(O::BITS, 0); +//! assert_eq!(R::BITS, 2); +//! assert_eq!(W::BITS, 4); +//! assert_eq!(RW::BITS, 6); +//! assert_eq!(WR::BITS, 6); +//! assert_type_same!(SameAsOp, False); +//! assert_type_same!(SameAsOp, True); +//! assert_type_same!(SameAsOp, True); +//! assert_type_same!(SameAsOp, True); +//! assert_type_same!(SetContainOp, False); +//! assert_type_same!(SetContainOp, True); +//! assert_type_same!(SetContainOp, False); +//! assert_type_same!(SetContainOp, True); +//! assert_type_same!(SetIncludeOp, True); +//! assert_type_same!(SetIncludeOp, False); +//! assert_type_same!(SetIncludeOp, True); +//! assert_type_same!(SetIncludeOp, False); +//! ``` + +#![feature(proc_macro_diagnostic)] +#![allow(dead_code)] + +use syn::parse_macro_input; + +use crate::{type_flag::TypeFlagDef, util::expand_type_flag}; + +mod flag_set; +mod type_flag; +mod util; + +#[proc_macro] +pub fn type_flags(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let type_flags_def = parse_macro_input!(input as TypeFlagDef); + expand_type_flag(&type_flags_def).into() +} diff --git a/src/kxos-typeflags/src/type_flag.rs b/src/kxos-typeflags/src/type_flag.rs new file mode 100644 index 00000000..b9a0d321 --- /dev/null +++ b/src/kxos-typeflags/src/type_flag.rs @@ -0,0 +1,139 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + braced, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Expr, Ident, Token, Type, Visibility, +}; + +/// The content inside typeflag macro +pub struct TypeFlagDef { + ident: Ident, + vis: Visibility, + type_: Type, + items: Punctuated, +} + +/// struct item inside typeflag macro +#[derive(Clone)] +pub struct TypeFlagItem { + vis: Visibility, + ident: Ident, + value: Expr, +} + +impl Parse for TypeFlagDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let vis: Visibility = input.parse()?; + let _: Token![trait] = input.parse()?; + let ident: Ident = input.parse()?; + let _: Token![:] = input.parse()?; + let type_: Type = input.parse()?; + // read content inside brace + let content; + let _ = braced!(content in input); + let items = content.parse_terminated(TypeFlagItem::parse)?; + + let res = TypeFlagDef { + ident, + vis, + type_, + items, + }; + + Ok(res) + } +} + +impl Parse for TypeFlagItem { + fn parse(input: ParseStream) -> syn::Result { + let vis: Visibility = input.parse()?; + let _: Token![struct] = input.parse()?; + let ident: Ident = input.parse()?; + let _: Token![=] = input.parse()?; + let value: Expr = input.parse()?; + let res = TypeFlagItem { vis, ident, value }; + Ok(res) + } +} + +impl TypeFlagDef { + /// tokens to define the trait + pub fn trait_def_tokens(&self) -> TokenStream { + let vis = self.vis.clone(); + let ident = self.ident.clone(); + let type_ = self.type_.clone(); + quote!( + #vis trait #ident { + const BITS: #type_; + + fn new() -> Self; + } + ) + } + + /// tokens to define all structs + pub fn items_def_tokens(&self) -> Vec { + self.items + .iter() + .map(|type_flag_item| type_flag_item.item_def_tokens()) + .collect() + } + + /// return the items iter + pub fn items_iter(&self) -> syn::punctuated::Iter { + self.items.iter() + } + + /// the number of items + pub fn item_num(&self) -> usize { + self.items.len() + } + + /// get item at specific position + pub fn get_item(&self, index: usize) -> Option { + self.items.iter().nth(index).map(|item| item.clone()) + } + + /// the trait ident + pub fn trait_ident(&self) -> Ident { + self.ident.clone() + } + + /// the val type + pub fn val_type(&self) -> Type { + self.type_.clone() + } +} + +impl TypeFlagItem { + /// the token stream to define such item + fn item_def_tokens(&self) -> TokenStream { + let vis = self.vis.clone(); + let ident = self.ident.clone(); + quote!( + #vis struct #ident {} + ) + } + + /// the item's ident + pub fn ident(&self) -> Ident { + self.ident.clone() + } + + /// the expression of the items's value + pub fn val(&self) -> Expr { + self.value.clone() + } +} + +impl TypeFlagDef { + /// Debug use. Print all item's name. + pub fn debug(&self) { + println!("{}", self.ident.to_string()); + for type_flag_item in &self.items { + println!("{}", type_flag_item.ident.to_string()); + } + } +} diff --git a/src/kxos-typeflags/src/util.rs b/src/kxos-typeflags/src/util.rs new file mode 100644 index 00000000..bea77b2a --- /dev/null +++ b/src/kxos-typeflags/src/util.rs @@ -0,0 +1,95 @@ +use proc_macro2::TokenStream; +use quote::{quote, TokenStreamExt}; + +use crate::{ + flag_set::{generate_flag_sets, FlagSet}, + type_flag::TypeFlagDef, +}; + +pub fn expand_type_flag(type_flags_def: &TypeFlagDef) -> TokenStream { + let mut all_tokens = TokenStream::new(); + let import_util_tokens = import_util(); + all_tokens.append_all(import_util_tokens); + + let trait_and_items_def_tokens = trait_and_items_def(type_flags_def); + all_tokens.append_all(trait_and_items_def_tokens); + + let impl_same_as_tokens = impl_same_as(type_flags_def); + all_tokens.append_all(impl_same_as_tokens); + + let flag_sets = generate_flag_sets(&type_flags_def); + flag_sets.iter().for_each(|flag_set| { + let impl_main_trait_tokens = flag_set.impl_main_trait_tokens(type_flags_def); + all_tokens.append_all(impl_main_trait_tokens); + }); + + let export_declarive_macro_tokens = export_declarive_macro(type_flags_def, &flag_sets); + all_tokens.append_all(export_declarive_macro_tokens); + + all_tokens +} + +/// import crate kxos_typeflags_util +pub fn import_util() -> TokenStream { + quote!( + #[macro_use] + use ::kxos_typeflags_util::*; + ) +} + +/// define the main trait and all items +pub fn trait_and_items_def(type_flags_def: &TypeFlagDef) -> TokenStream { + let mut tokens = TokenStream::new(); + tokens.append_all(type_flags_def.trait_def_tokens()); + for item_def in type_flags_def.items_def_tokens() { + tokens.append_all(item_def); + } + tokens +} + +/// impl SameAs trait for each struct +pub fn impl_same_as(type_flags_def: &TypeFlagDef) -> TokenStream { + let mut all_tokens = TokenStream::new(); + let items_num = type_flags_def.item_num(); + + for i in 0..items_num { + let item1 = type_flags_def.get_item(i).unwrap(); + for j in 0..items_num { + if i == j { + // We don't need to impl SameAs for the same type + continue; + } + let item2 = type_flags_def.get_item(j).unwrap(); + let ident1 = item1.ident(); + let ident2 = item2.ident(); + let tokens = quote!( + impl ::kxos_typeflags_util::SameAs<#ident1> for #ident2 { + type Output = ::kxos_typeflags_util::False; + } + ); + all_tokens.append_all(tokens); + } + } + all_tokens +} + +/// export the declarive macro +pub fn export_declarive_macro(type_flags_def: &TypeFlagDef, flag_sets: &[FlagSet]) -> TokenStream { + let macro_ident = type_flags_def.trait_ident(); + let macro_item_tokens = flag_sets + .iter() + .map(|flag_set| flag_set.macro_item_tokens()) + .fold(Vec::new(), |mut left, mut new_item| { + left.append(&mut new_item); + left + }); + + let tokens = quote!( + #[macro_export] + macro_rules! #macro_ident { + #(#macro_item_tokens);* + } + ); + + tokens +}