mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-26 19:03:27 +00:00
Add crate int-to-c-enum
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
aec46295c4
commit
6ecc7dc557
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -758,6 +758,22 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-enum"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"value-enum-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-enum-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -35,6 +35,7 @@ members = [
|
|||||||
"services/libs/typeflags-util",
|
"services/libs/typeflags-util",
|
||||||
"services/libs/jinux-util",
|
"services/libs/jinux-util",
|
||||||
"services/libs/cpio-decoder",
|
"services/libs/cpio-decoder",
|
||||||
|
"services/libs/int-to-c-enum",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["services/libs/comp-sys/controlled", "services/libs/comp-sys/cargo-component"]
|
exclude = ["services/libs/comp-sys/controlled", "services/libs/comp-sys/cargo-component"]
|
||||||
|
2
services/libs/int-to-c-enum/.gitignore
vendored
Normal file
2
services/libs/int-to-c-enum/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/Cargo.lock
|
13
services/libs/int-to-c-enum/Cargo.toml
Normal file
13
services/libs/int-to-c-enum/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "int-to-c-enum"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
int-to-c-enum-derive = { path = "derive", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["derive"]
|
||||||
|
derive = ["int-to-c-enum-derive"]
|
35
services/libs/int-to-c-enum/README.md
Normal file
35
services/libs/int-to-c-enum/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# TryFromInt - A convenient derive macro for converting an integer to an enum
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
To use this crate, first add this crate to your `Cargo.toml`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
int-to-c-enum = "0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use this macro for a [C-like enum](https://doc.rust-lang.org/stable/rust-by-example/custom_types/enum/c_like.html).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use int_to_c_enum::TryFromInt;
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(TryFromInt, Debug)]
|
||||||
|
pub enum Color {
|
||||||
|
Red = 1,
|
||||||
|
Blue = 2,
|
||||||
|
Green = 3,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can use `try_from` function for this enum.
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let color = Color::try_from(1).unwrap();
|
||||||
|
println!("color = {color:?}"); // color = Red;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
This crate provides a derive procedural macro named `TryFromInt`. This macro will automatically implement [TryFrom](https://doc.rust-lang.org/core/convert/trait.TryFrom.html) trait for enums that meet the following requirements:
|
||||||
|
1. The enum must have a primitive repr, i.e., the enum should have attribute like #[repr(u8)], #[repr(u32)], etc. The type parameter of TryFrom will be the repr, e.g., in the `QuickStart` example, the macro will implment `TryFrom<u8>` for `Color`.
|
||||||
|
2. The enum must consist solely of unit variants, which is called [units only enum](https://doc.rust-lang.org/reference/items/enumerations.html#unit-only-enum). Each field should have an **explicit discriminant**.
|
14
services/libs/int-to-c-enum/derive/Cargo.toml
Normal file
14
services/libs/int-to-c-enum/derive/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "int-to-c-enum-derive"
|
||||||
|
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 = "2.0.15", features = ["parsing"] }
|
106
services/libs/int-to-c-enum/derive/src/lib.rs
Normal file
106
services/libs/int-to-c-enum/derive/src/lib.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
use quote::{format_ident, quote, TokenStreamExt};
|
||||||
|
use syn::{parse_macro_input, Attribute, Data, DataEnum, DeriveInput, Generics};
|
||||||
|
|
||||||
|
const ALLOWED_REPRS: &[&str] = &[
|
||||||
|
"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize",
|
||||||
|
];
|
||||||
|
const VALUE_NAME: &str = "value";
|
||||||
|
const REPR_PATH: &str = "repr";
|
||||||
|
|
||||||
|
#[proc_macro_derive(TryFromInt)]
|
||||||
|
pub fn derive_try_from_num(input_token: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = parse_macro_input!(input_token as DeriveInput);
|
||||||
|
expand_derive_try_from_num(input).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_derive_try_from_num(input: DeriveInput) -> TokenStream {
|
||||||
|
let attrs = input.attrs;
|
||||||
|
let ident = input.ident;
|
||||||
|
let generics = input.generics;
|
||||||
|
if let Data::Enum(data_enum) = input.data {
|
||||||
|
impl_try_from(data_enum, attrs, generics, ident)
|
||||||
|
} else {
|
||||||
|
panic!("cannot derive TryFromInt for structs or union.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_try_from(
|
||||||
|
data_enum: DataEnum,
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
generics: Generics,
|
||||||
|
ident: Ident,
|
||||||
|
) -> TokenStream {
|
||||||
|
let valid_repr = if let Some(valid_repr) = has_valid_repr(attrs) {
|
||||||
|
format_ident!("{}", valid_repr)
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"{} does not have invalid repr to implement TryFromInt.",
|
||||||
|
ident.to_string()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for variant in &data_enum.variants {
|
||||||
|
if variant.discriminant.is_none() {
|
||||||
|
panic!("Enum can only have fields like Variant=1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||||
|
let fn_body = fn_body_tokens(VALUE_NAME, &data_enum, ident.clone());
|
||||||
|
let param = format_ident!("{}", VALUE_NAME);
|
||||||
|
quote!(
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics ::core::convert::TryFrom<#valid_repr> #type_generics for #ident #where_clause {
|
||||||
|
type Error = ::int_to_c_enum::TryFromIntError;
|
||||||
|
fn try_from(#param: #valid_repr) -> ::core::result::Result<Self, Self::Error> {
|
||||||
|
#fn_body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fn_body_tokens(value_name: &str, data_enum: &DataEnum, ident: Ident) -> TokenStream {
|
||||||
|
let mut match_bodys = quote!();
|
||||||
|
for variant in &data_enum.variants {
|
||||||
|
let (_, value) = variant
|
||||||
|
.discriminant
|
||||||
|
.as_ref()
|
||||||
|
.expect("Each field must be assigned a discriminant value explicitly");
|
||||||
|
let vairant_ident = &variant.ident;
|
||||||
|
let statement = quote!(#value => ::core::result::Result::Ok(#ident::#vairant_ident),);
|
||||||
|
match_bodys.append_all(statement);
|
||||||
|
}
|
||||||
|
match_bodys.append_all(quote!(_ => core::result::Result::Err(::int_to_c_enum::TryFromIntError::InvalidValue),));
|
||||||
|
let param = format_ident!("{}", value_name);
|
||||||
|
quote!(match #param {
|
||||||
|
#match_bodys
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_valid_repr(attrs: Vec<Attribute>) -> Option<&'static str> {
|
||||||
|
for attr in attrs {
|
||||||
|
if let Some(repr) = parse_repr(attr) {
|
||||||
|
return Some(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_repr(attr: Attribute) -> Option<&'static str> {
|
||||||
|
if !attr.path().is_ident(REPR_PATH) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut repr = None;
|
||||||
|
attr.parse_nested_meta(|meta| {
|
||||||
|
for allowed_repr in ALLOWED_REPRS {
|
||||||
|
if meta.path.is_ident(*allowed_repr) {
|
||||||
|
repr = Some(*allowed_repr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
repr
|
||||||
|
}
|
47
services/libs/int-to-c-enum/src/lib.rs
Normal file
47
services/libs/int-to-c-enum/src/lib.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//! This crate provides a derive macro named TryFromInt. This macro can be used to automatically implement TryFrom trait
|
||||||
|
//! for [C-like enums](https://doc.rust-lang.org/stable/rust-by-example/custom_types/enum/c_like.html).
|
||||||
|
//!
|
||||||
|
//! Currently, this macro only supports enums with [explicit discriminants](https://doc.rust-lang.org/reference/items/enumerations.html#explicit-discriminants).
|
||||||
|
//!
|
||||||
|
//! Below is a simple example. We derive macro `TryFromInt` for an enum `Color`.
|
||||||
|
//! ```rust
|
||||||
|
//! use int_to_c_enum::TryFromInt;
|
||||||
|
//! #[repr(u8)]
|
||||||
|
//! #[derive(TryFromInt, Eq, PartialEq)]
|
||||||
|
//! pub enum Color {
|
||||||
|
//! Red = 1,
|
||||||
|
//! Yellow = 2,
|
||||||
|
//! Blue = 3,
|
||||||
|
//! }
|
||||||
|
//! // Then, we can use method `try_from` for `Color`.
|
||||||
|
//! let color = Color::try_from(1).unwrap();
|
||||||
|
//! assert!(color == Color::Red);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The `TryFromInt` macro will automatically implement trait `TryFrom<u8>` for `Color`.
|
||||||
|
//! After macro expansion, the generated code looks like as follows:
|
||||||
|
//! ```ignore
|
||||||
|
//! impl TryFrom<u8> for Color {
|
||||||
|
//! type Error = TryFromIntError;
|
||||||
|
//! fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
//! match value {
|
||||||
|
//! 1 => Ok(Color::Red),
|
||||||
|
//! 2 => Ok(Color::Yellow),
|
||||||
|
//! 3 => Ok(Color::Blue),
|
||||||
|
//! _ => Err(TryFromIntError::InvalidValue),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
/// Error type for TryFromInt derive macro
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum TryFromIntError {
|
||||||
|
InvalidValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
|
pub use int_to_c_enum_derive::TryFromInt;
|
22
services/libs/int-to-c-enum/tests/regression.rs
Normal file
22
services/libs/int-to-c-enum/tests/regression.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use int_to_c_enum::TryFromInt;
|
||||||
|
|
||||||
|
#[derive(TryFromInt, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Color {
|
||||||
|
Red = 1,
|
||||||
|
Blue = 2,
|
||||||
|
Green = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conversion() {
|
||||||
|
let color = Color::try_from(1).unwrap();
|
||||||
|
println!("color = {color:?}");
|
||||||
|
assert!(color == Color::Red);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_value() {
|
||||||
|
let color = Color::try_from(4);
|
||||||
|
assert!(color.is_err());
|
||||||
|
}
|
Reference in New Issue
Block a user