Skip to content

Commit

Permalink
Derive macros for simple enums
Browse files Browse the repository at this point in the history
Cleans up the generated code a bit and makes it easier to define your
own macros. We still need something for option sets, probably a custom
function-like macro.
  • Loading branch information
einarmo committed Dec 31, 2024
1 parent c55f08a commit 2407e08
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 206 deletions.
190 changes: 29 additions & 161 deletions opcua-codegen/src/types/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,22 +425,33 @@ impl CodeGenerator {
});
}
attrs.push(parse_quote! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq,
opcua::types::UaEnum, opcua::types::BinaryEncodable,
opcua::types::BinaryDecodable)]
});
attrs.push(parse_quote! {
#[cfg_attr(
feature = "json",
derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable)
)]
});
attrs.push(parse_quote! {
#[cfg_attr(feature = "xml", derive(opcua::types::FromXml))]
});
let ty: Type = syn::parse_str(&item.typ.to_string())?;
attrs.push(parse_quote! {
#[repr(#ty)]
});

let mut try_from_arms = quote! {};
let mut default_ident = None;

for field in &item.values {
let (name, _) = safe_ident(&field.name);
let value = field.value;
if value == 0 {
default_ident = Some(name.clone());
}
let is_default = if let Some(default_name) = &item.default_value {
&name.to_string() == default_name
} else {
value == 0
};

let value_token = match item.typ {
EnumReprType::u8 => {
let value: u8 = value.try_into().map_err(|_| {
Expand Down Expand Up @@ -474,163 +485,20 @@ impl CodeGenerator {
}
};

try_from_arms = quote! {
#try_from_arms
#value_token => Self::#name,
};

variants.push(parse_quote! {
#name = #value_token
})
}

if item.values.iter().any(|f| f.name == "Invalid") {
let invalid_msg = format!(
"Got unexpected value for enum {}: {{}}. Falling back on Invalid",
item.name
);
try_from_arms = quote! {
#try_from_arms
r => {
log::warn!(#invalid_msg, r);
Self::Invalid
},
};
} else {
let invalid_msg = format!("Got unexpected value for enum {}: {{}}", item.name);
try_from_arms = quote! {
#try_from_arms
r => {
return Err(opcua::types::Error::decoding(format!(#invalid_msg, r)))
}
};
if is_default {
variants.push(parse_quote! {
#[opcua(default)]
#name = #value_token
})
} else {
variants.push(parse_quote! {
#name = #value_token
})
}
}

let mut impls = Vec::new();
let (enum_ident, _) = safe_ident(&item.name);

if let Some(default_name) = item.default_value {
let (default_ident, _) = safe_ident(&default_name);
impls.push(parse_quote! {
impl Default for #enum_ident {
fn default() -> Self {
Self::#default_ident
}
}
});
} else if let Some(default_ident) = default_ident {
impls.push(parse_quote! {
impl Default for #enum_ident {
fn default() -> Self {
Self::#default_ident
}
}
});
}

// TryFrom impl
impls.push(parse_quote! {
impl TryFrom<#ty> for #enum_ident {
type Error = opcua::types::Error;

fn try_from(value: #ty) -> Result<Self, <Self as TryFrom<#ty>>::Error> {
Ok(match value {
#try_from_arms
})
}
}
});
// Xml impl
let fail_xml_msg = format!("Got unexpected value for enum {}: {{}}", item.name);
impls.push(parse_quote! {
#[cfg(feature = "xml")]
impl opcua::types::xml::FromXml for #enum_ident {
fn from_xml(
element: &opcua::types::xml::XmlElement,
ctx: &opcua::types::xml::XmlContext<'_>
) -> Result<Self, opcua::types::xml::FromXmlError> {
let val = #ty::from_xml(element, ctx)?;
Ok(Self::try_from(val).map_err(|e| format!(#fail_xml_msg, e))?)
}
}
});

impls.push(parse_quote! {
impl From<#enum_ident> for #ty {
fn from(value: #enum_ident) -> Self {
value as #ty
}
}
});
impls.push(parse_quote! {
impl opcua::types::IntoVariant for #enum_ident {
fn into_variant(self) -> opcua::types::Variant {
(self as #ty).into_variant()
}
}
});

let typ_name_str = item.typ.to_string();
let failure_str = format!("Failed to deserialize {}: {{:?}}", typ_name_str);
impls.push(parse_quote! {
#[cfg(feature = "json")]
impl opcua::types::json::JsonDecodable for #enum_ident {
fn decode(
stream: &mut opcua::types::json::JsonStreamReader<&mut dyn std::io::Read>,
_ctx: &opcua::types::Context<'_>,
) -> opcua::types::EncodingResult<Self> {
use opcua::types::json::JsonReader;
let value: #ty = stream.next_number()??;
Self::try_from(value).map_err(|e| {
opcua::types::Error::decoding(format!(#failure_str, e))
})
}
}
});

impls.push(parse_quote! {
#[cfg(feature = "json")]
impl opcua::types::json::JsonEncodable for #enum_ident {
fn encode(
&self,
stream: &mut opcua::types::json::JsonStreamWriter<&mut dyn std::io::Write>,
_ctx: &opcua::types::Context<'_>,
) -> opcua::types::EncodingResult<()> {
use opcua::types::json::JsonWriter;
stream.number_value(*self as #ty)?;
Ok(())
}
}
});

// BinaryEncodable impl
let size: usize = item.size.try_into().map_err(|_| {
CodeGenError::Other(format!("Value {} does not fit in a usize", item.size))
})?;
let write_method = Ident::new(&format!("write_{}", item.typ), Span::call_site());
let read_method = Ident::new(&format!("read_{}", item.typ), Span::call_site());

impls.push(parse_quote! {
impl opcua::types::BinaryEncodable for #enum_ident {
fn byte_len(&self, _ctx: &opcua::types::Context<'_>) -> usize {
#size
}

fn encode<S: std::io::Write + ?Sized>(&self, stream: &mut S, _ctx: &opcua::types::Context<'_>) -> opcua::types::EncodingResult<()> {
opcua::types::#write_method(stream, *self as #ty)
}
}
});

impls.push(parse_quote! {
impl opcua::types::BinaryDecodable for #enum_ident {
fn decode<S: std::io::Read + ?Sized>(stream: &mut S, _ctx: &opcua::types::Context<'_>) -> opcua::types::EncodingResult<Self> {
let value = opcua::types::#read_method(stream)?;
Self::try_from(value)
}
}
});

let res = ItemEnum {
attrs,
vis: Visibility::Public(Token![pub](Span::call_site())),
Expand All @@ -643,7 +511,7 @@ impl CodeGenerator {

Ok(GeneratedItem {
item: ItemDefinition::Enum(res),
impls,
impls: Vec::new(),
module: if self.config.enums_single_file {
"enums".to_owned()
} else {
Expand Down
53 changes: 49 additions & 4 deletions opcua-macros/src/encoding/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use crate::utils::ItemAttr;
pub(crate) struct EncodingFieldAttribute {
pub rename: Option<String>,
pub ignore: bool,
pub required: bool,
pub no_default: bool,
#[allow(dead_code)]
pub optional: bool,
}

impl Parse for EncodingFieldAttribute {
Expand All @@ -24,8 +26,11 @@ impl Parse for EncodingFieldAttribute {
"ignore" => {
slf.ignore = true;
}
"required" => {
slf.required = true;
"no_default" => {
slf.no_default = true;
}
"optional" => {
slf.optional = true;
}
_ => return Err(syn::Error::new_spanned(ident, "Unknown attribute value")),
}
Expand All @@ -42,6 +47,46 @@ impl ItemAttr for EncodingFieldAttribute {
fn combine(&mut self, other: Self) {
self.rename = other.rename;
self.ignore |= other.ignore;
self.required |= other.required;
self.no_default |= other.no_default;
self.optional |= other.optional;
}
}

#[derive(Debug, Default)]
pub(crate) struct EncodingVariantAttribute {
pub rename: Option<String>,
pub default: bool,
}

impl ItemAttr for EncodingVariantAttribute {
fn combine(&mut self, other: Self) {
self.rename = other.rename;
self.default |= other.default;
}
}

impl Parse for EncodingVariantAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut slf = Self::default();

loop {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"rename" => {
input.parse::<Token![=]>()?;
let val: LitStr = input.parse()?;
slf.rename = Some(val.value());
}
"default" => {
slf.default = true;
}
_ => return Err(syn::Error::new_spanned(ident, "Unknown attribute value")),
}
if !input.peek(Token![,]) {
break;
}
input.parse::<Token![,]>()?;
}
Ok(slf)
}
}
39 changes: 38 additions & 1 deletion opcua-macros/src/encoding/binary.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::quote;

use super::EncodingStruct;
use super::{enums::SimpleEnum, EncodingStruct};

pub fn generate_binary_encode_impl(strct: EncodingStruct) -> syn::Result<TokenStream> {
let mut byte_len_body = quote! {};
Expand Down Expand Up @@ -99,3 +99,40 @@ pub fn generate_binary_decode_impl(strct: EncodingStruct) -> syn::Result<TokenSt
}
})
}

pub fn generate_simple_enum_binary_decode_impl(en: SimpleEnum) -> syn::Result<TokenStream> {
let ident = en.ident;
let repr = en.repr;

Ok(quote! {
impl opcua::types::BinaryDecodable for #ident {
#[allow(unused_variables)]
fn decode<S: std::io::Read + ?Sized>(stream: &mut S, ctx: &opcua::types::Context<'_>) -> opcua::types::EncodingResult<Self> {
let val = #repr::decode(stream, ctx)?;
Self::try_from(val)
}
}
})
}

pub fn generate_simple_enum_binary_encode_impl(en: SimpleEnum) -> syn::Result<TokenStream> {
let ident = en.ident;
let repr = en.repr;

Ok(quote! {
impl opcua::types::BinaryEncodable for #ident {
#[allow(unused)]
fn byte_len(&self, ctx: &opcua::types::Context<'_>) -> usize {
(*self as #repr).byte_len(ctx)
}
#[allow(unused)]
fn encode<S: std::io::Write + ?Sized>(
&self,
stream: &mut S,
ctx: &opcua::types::Context<'_>,
) -> opcua::types::EncodingResult<()> {
(*self as #repr).encode(stream, ctx)
}
}
})
}
Loading

0 comments on commit 2407e08

Please sign in to comment.