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"
|
||||
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]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -35,6 +35,7 @@ members = [
|
||||
"services/libs/typeflags-util",
|
||||
"services/libs/jinux-util",
|
||||
"services/libs/cpio-decoder",
|
||||
"services/libs/int-to-c-enum",
|
||||
]
|
||||
|
||||
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