Skip to content

Commit

Permalink
Binary encoding macros (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
einarmo committed Dec 15, 2024
1 parent 716f10d commit d511dd1
Show file tree
Hide file tree
Showing 291 changed files with 483 additions and 11,367 deletions.
105 changes: 1 addition & 104 deletions opcua-codegen/src/types/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ impl CodeGenerator {
});
}
attrs.push(parse_quote! {
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, opcua::types::BinaryEncodable, opcua::types::BinaryDecodable)]
});
attrs.push(parse_quote! {
#[cfg_attr(feature = "json", derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable))]
Expand Down Expand Up @@ -792,109 +792,6 @@ impl CodeGenerator {
encoding_ids = Some(EncodingIds::new(&item.name));
}

let mut len_impl;
let mut encode_impl;
let mut decode_impl = quote! {};
let mut decode_build = quote! {};

let mut has_context = false;
// Special case an empty struct
if item.fields.is_empty() {
len_impl = quote! { 0usize };
encode_impl = quote! { Ok(0) };
decode_build = quote! { Ok(Self {}) };
} else {
len_impl = quote! {
let mut size = 0usize;
};
encode_impl = quote! {
let mut size = 0usize;
};
for field in item.visible_fields() {
let (ident, _) = safe_ident(&field.name);

let ty: Type = match &field.typ {
crate::StructureFieldType::Field(f) => syn::parse_str(&self.get_type_path(f))?,
crate::StructureFieldType::Array(f) => {
let path: Path = syn::parse_str(&self.get_type_path(f))?;
parse_quote! {
Option<Vec<#path>>
}
}
};

len_impl.extend(quote! {
size += self.#ident.byte_len(ctx);
});
encode_impl.extend(quote! {
size += self.#ident.encode(stream, ctx)?;
});
if field.name == "request_header" {
decode_impl.extend(quote! {
let request_header: #ty = opcua::types::BinaryDecodable::decode(stream, ctx)?;
let __request_handle = request_header.request_handle;
});
decode_build.extend(quote! {
request_header,
});
has_context = true;
} else if field.name == "response_header" {
decode_impl.extend(quote! {
let response_header: #ty = opcua::types::BinaryDecodable::decode(stream, ctx)?;
let __request_handle = response_header.request_handle;
});
decode_build.extend(quote! {
response_header,
});
has_context = true;
} else if has_context {
decode_build.extend(quote! {
#ident: opcua::types::BinaryDecodable::decode(stream, ctx)
.map_err(|e| e.with_request_handle(__request_handle))?,
});
} else {
decode_build.extend(quote! {
#ident: opcua::types::BinaryDecodable::decode(stream, ctx)?,
});
}
}
len_impl.extend(quote! {
size
});
encode_impl.extend(quote! {
Ok(size)
});
decode_build = quote! {
Ok(Self {
#decode_build
})
};
}

impls.push(parse_quote! {
impl opcua::types::BinaryEncodable for #struct_ident {
#[allow(unused_variables)]
fn byte_len(&self, ctx: &opcua::types::Context<'_>) -> usize {
#len_impl
}

#[allow(unused_variables)]
fn encode<S: std::io::Write + ?Sized>(&self, stream: &mut S, ctx: &opcua::types::Context<'_>) -> opcua::types::EncodingResult<usize> {
#encode_impl
}
}
});

impls.push(parse_quote! {
impl opcua::types::BinaryDecodable for #struct_ident {
#[allow(unused_variables)]
fn decode<S: std::io::Read + ?Sized>(stream: &mut S, ctx: &opcua::types::Context<'_>) -> opcua::types::EncodingResult<Self> {
#decode_impl
#decode_build
}
}
});

let res = ItemStruct {
attrs,
vis: Visibility::Public(Token![pub](Span::call_site())),
Expand Down
109 changes: 109 additions & 0 deletions opcua-macros/src/binary/gen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

use crate::utils::{EmptyAttribute, EncodingFieldAttribute, StructItem};

pub type BinaryStruct = StructItem<EncodingFieldAttribute, EmptyAttribute>;

pub fn parse_binary_struct(input: DeriveInput) -> syn::Result<BinaryStruct> {
BinaryStruct::from_input(input)
}

pub fn generate_binary_encode_impl(strct: BinaryStruct) -> syn::Result<TokenStream> {
let mut byte_len_body = quote! {};
let mut encode_body = quote! {};

for field in strct.fields {
if field.attr.ignore {
continue;
}

let ident = field.ident;
byte_len_body.extend(quote! {
size += self.#ident.byte_len(ctx);
});
encode_body.extend(quote! {
size += self.#ident.encode(stream, ctx)?;
});
}
let ident = strct.ident;

Ok(quote! {
impl opcua::types::BinaryEncodable for #ident {
#[allow(unused)]
fn byte_len(&self, ctx: &opcua::types::Context<'_>) -> usize {
let mut size = 0usize;
#byte_len_body
size
}
#[allow(unused)]
fn encode<S: std::io::Write + ?Sized>(
&self,
stream: &mut S,
ctx: &opcua::types::Context<'_>,
) -> opcua::types::EncodingResult<usize> {
let mut size = 0usize;
#encode_body
Ok(size)
}
}
})
}

pub fn generate_binary_decode_impl(strct: BinaryStruct) -> syn::Result<TokenStream> {
let mut decode_impl = quote! {};
let mut decode_build = quote! {};

let mut has_context = false;
for field in strct.fields {
if field.attr.ignore {
continue;
}

let ident = field.ident;
let ident_string = ident.to_string();
if ident_string == "request_header" {
decode_impl.extend(quote! {
let request_header: opcua::types::RequestHeader = opcua::types::BinaryDecodable::decode(stream, ctx)?;
let __request_handle = request_header.request_handle;
});
decode_build.extend(quote! {
request_header,
});
has_context = true;
} else if ident_string == "response_header" {
decode_impl.extend(quote! {
let response_header: opcua::types::ResponseHeader = opcua::types::BinaryDecodable::decode(stream, ctx)?;
let __request_handle = response_header.request_handle;
});
decode_build.extend(quote! {
response_header,
});
has_context = true;
} else if has_context {
decode_build.extend(quote! {
#ident: opcua::types::BinaryDecodable::decode(stream, ctx)
.map_err(|e| e.with_request_handle(__request_handle))?,
});
} else {
decode_build.extend(quote! {
#ident: opcua::types::BinaryDecodable::decode(stream, ctx)?,
});
}
}

let ident = strct.ident;

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> {
#decode_impl
Ok(Self {
#decode_build
})
}
}
})
}
14 changes: 14 additions & 0 deletions opcua-macros/src/binary/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use gen::{generate_binary_decode_impl, generate_binary_encode_impl, parse_binary_struct};
use proc_macro2::TokenStream;
use syn::DeriveInput;

mod gen;
pub fn derive_binary_encode_inner(input: DeriveInput) -> syn::Result<TokenStream> {
let struct_data = parse_binary_struct(input)?;
generate_binary_encode_impl(struct_data)
}

pub fn derive_binary_decode_inner(input: DeriveInput) -> syn::Result<TokenStream> {
let struct_data = parse_binary_struct(input)?;
generate_binary_decode_impl(struct_data)
}
50 changes: 3 additions & 47 deletions opcua-macros/src/json/gen.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,12 @@
use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream};
use syn::{parse::Parse, DeriveInput, Ident, LitStr, Token};
use syn::{DeriveInput, Ident};

use crate::utils::{EmptyAttribute, ItemAttr, StructItem};
use crate::utils::{EmptyAttribute, EncodingFieldAttribute, StructItem};

use quote::quote;

#[derive(Debug, Default)]
pub(super) struct JsonFieldAttribute {
pub rename: Option<String>,
pub ignore: bool,
pub required: bool,
}

impl Parse for JsonFieldAttribute {
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());
}
"ignore" => {
slf.ignore = true;
}
"required" => {
slf.required = true;
}
_ => return Err(syn::Error::new_spanned(ident, "Unknown attribute value")),
}
if !input.peek(Token![,]) {
break;
}
input.parse::<Token![,]>()?;
}
Ok(slf)
}
}

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

pub type JsonStruct = StructItem<JsonFieldAttribute, EmptyAttribute>;
pub type JsonStruct = StructItem<EncodingFieldAttribute, EmptyAttribute>;

pub fn parse_json_struct(input: DeriveInput) -> syn::Result<JsonStruct> {
JsonStruct::from_input(input)
Expand Down
18 changes: 18 additions & 0 deletions opcua-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod binary;
mod events;
#[cfg(feature = "json")]
mod json;
mod utils;
#[cfg(feature = "xml")]
mod xml;

use binary::{derive_binary_decode_inner, derive_binary_encode_inner};
use events::{derive_event_field_inner, derive_event_inner};
use proc_macro::TokenStream;
use syn::parse_macro_input;
Expand Down Expand Up @@ -57,3 +59,19 @@ pub fn derive_json_decodable(item: TokenStream) -> TokenStream {
Err(e) => e.to_compile_error().into(),
}
}

#[proc_macro_derive(BinaryEncodable, attributes(opcua))]
pub fn derive_binary_encodable(item: TokenStream) -> TokenStream {
match derive_binary_encode_inner(parse_macro_input!(item)) {
Ok(r) => r.into(),
Err(e) => e.to_compile_error().into(),
}
}

#[proc_macro_derive(BinaryDecodable, attributes(opcua))]
pub fn derive_binary_decodable(item: TokenStream) -> TokenStream {
match derive_binary_decode_inner(parse_macro_input!(item)) {
Ok(r) => r.into(),
Err(e) => e.to_compile_error().into(),
}
}
46 changes: 45 additions & 1 deletion opcua-macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use syn::{parse::Parse, DeriveInput, Field, Ident, Type};
use syn::{parse::Parse, DeriveInput, Field, Ident, LitStr, Token, Type};

#[derive(Debug, Default)]
pub struct EmptyAttribute;
Expand Down Expand Up @@ -32,6 +32,50 @@ pub struct StructItem<TFieldAttr, TAttr> {
pub attribute: TAttr,
}

#[derive(Debug, Default)]
pub(super) struct EncodingFieldAttribute {
pub rename: Option<String>,
pub ignore: bool,
pub required: bool,
}

impl Parse for EncodingFieldAttribute {
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());
}
"ignore" => {
slf.ignore = true;
}
"required" => {
slf.required = true;
}
_ => return Err(syn::Error::new_spanned(ident, "Unknown attribute value")),
}
if !input.peek(Token![,]) {
break;
}
input.parse::<Token![,]>()?;
}
Ok(slf)
}
}

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

impl<TFieldAttr: Parse + ItemAttr + Default, TAttr: Parse + ItemAttr + Default>
StructItem<TFieldAttr, TAttr>
{
Expand Down
Loading

0 comments on commit d511dd1

Please sign in to comment.