Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d9892bbd authored by Martin Geisler's avatar Martin Geisler
Browse files

pdl: Add support for enums

This adds support for parsing and serializing enum fields.

The canonical test vectors work.

Tests have been added to the canonical PDL files: they demonstrate
that we can generate valid Rust code. A test was added to show that we
need “#[repr(u64)]” for an enum with a maximum discriminant.

Test: atest pdl_tests pdl_rust_generator_tests_{le,be}
Change-Id: Id65bf831ddde8ade9780f89096842ef9516cf4c3
parent 302e8333
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -50,10 +50,16 @@ rust_test_host {
    data: [
        ":rustfmt",
        ":rustfmt.toml",
        "tests/generated/packet_decl_8bit_enum_big_endian.rs",
        "tests/generated/packet_decl_8bit_enum_little_endian.rs",
        "tests/generated/packet_decl_8bit_scalar_big_endian.rs",
        "tests/generated/packet_decl_8bit_scalar_little_endian.rs",
        "tests/generated/packet_decl_24bit_enum_big_endian.rs",
        "tests/generated/packet_decl_24bit_enum_little_endian.rs",
        "tests/generated/packet_decl_24bit_scalar_big_endian.rs",
        "tests/generated/packet_decl_24bit_scalar_little_endian.rs",
        "tests/generated/packet_decl_64bit_enum_big_endian.rs",
        "tests/generated/packet_decl_64bit_enum_little_endian.rs",
        "tests/generated/packet_decl_64bit_scalar_big_endian.rs",
        "tests/generated/packet_decl_64bit_scalar_little_endian.rs",
        "tests/generated/packet_decl_complex_scalars_big_endian.rs",
@@ -62,6 +68,8 @@ rust_test_host {
        "tests/generated/packet_decl_empty_little_endian.rs",
        "tests/generated/packet_decl_mask_scalar_value_big_endian.rs",
        "tests/generated/packet_decl_mask_scalar_value_little_endian.rs",
        "tests/generated/packet_decl_mixed_scalars_enums_big_endian.rs",
        "tests/generated/packet_decl_mixed_scalars_enums_little_endian.rs",
        "tests/generated/packet_decl_simple_scalars_big_endian.rs",
        "tests/generated/packet_decl_simple_scalars_little_endian.rs",
        "tests/generated/preamble.rs",
+5 −1
Original line number Diff line number Diff line
@@ -304,12 +304,16 @@ impl Field {
        }
    }

    pub fn width(&self) -> Option<usize> {
    pub fn width(&self, scope: &lint::Scope<'_>) -> Option<usize> {
        match self {
            Field::Scalar { width, .. }
            | Field::Size { width, .. }
            | Field::Count { width, .. }
            | Field::Reserved { width, .. } => Some(*width),
            Field::Typedef { type_id, .. } => match scope.typedef.get(type_id.as_str()) {
                Some(Decl::Enum { width, .. }) => Some(*width),
                _ => None,
            },
            // TODO(mgeisler): padding, arrays, etc.
            _ => None,
        }
+50 −7
Original line number Diff line number Diff line
@@ -71,16 +71,13 @@ fn generate_packet_decl(
    let id_builder = format_ident!("{id}Builder");

    let field_names =
        fields.iter().filter_map(|f| f.id()).map(|id| format_ident!("{id}")).collect::<Vec<_>>();
    let field_types = fields
        .iter()
        .filter_map(|f| f.width())
        .map(|w| format_ident!("u{}", types::Integer::new(w).width))
        .collect::<Vec<_>>();
        fields.iter().map(|f| format_ident!("{}", f.id().unwrap())).collect::<Vec<_>>();
    let field_types = fields.iter().map(types::rust_type).collect::<Vec<_>>();

    let getter_names = field_names.iter().map(|id| format_ident!("get_{id}"));

    let packet_size = syn::Index::from(fields.iter().filter_map(|f| f.width()).sum::<usize>() / 8);
    let packet_size =
        syn::Index::from(fields.iter().filter_map(|f| f.width(scope)).sum::<usize>() / 8);
    let conforms = if packet_size.index == 0 {
        quote! { true }
    } else {
@@ -175,6 +172,25 @@ fn generate_packet_decl(
    }
}

fn generate_enum_decl(id: &str, tags: &[ast::Tag]) -> proc_macro2::TokenStream {
    let variants = tags.iter().map(|t| {
        let variant = format_ident!("{}", t.id);
        let value = syn::parse_str::<syn::LitInt>(&format!("{:#x}", t.value)).unwrap();
        quote! {
            #variant = #value
        }
    });

    let name = format_ident!("{}", id);
    quote! {
        #[derive(FromPrimitive, ToPrimitive, Debug, Hash, Eq, PartialEq, Clone, Copy)]
        #[repr(u64)]
        pub enum #name {
            #(#variants),*
        }
    }
}

fn generate_decl(scope: &lint::Scope<'_>, file: &ast::File, decl: &ast::Decl) -> String {
    match decl {
        ast::Decl::Packet { id, constraints, fields, parent_id, .. } => generate_packet_decl(
@@ -186,6 +202,7 @@ fn generate_decl(scope: &lint::Scope<'_>, file: &ast::File, decl: &ast::Decl) ->
            parent_id.as_deref(),
        )
        .to_string(),
        ast::Decl::Enum { id, tags, .. } => generate_enum_decl(id, tags).to_string(),
        _ => todo!("unsupported Decl::{:?}", decl),
    }
}
@@ -306,4 +323,30 @@ mod tests {
          }
        "#,
    );

    test_pdl!(packet_decl_8bit_enum, " enum Foo :  8 { A = 1, B = 2 } packet Bar { x: Foo }");
    test_pdl!(packet_decl_24bit_enum, "enum Foo : 24 { A = 1, B = 2 } packet Bar { x: Foo }");
    test_pdl!(packet_decl_64bit_enum, "enum Foo : 64 { A = 1, B = 2 } packet Bar { x: Foo }");

    test_pdl!(
        packet_decl_mixed_scalars_enums,
        "
          enum Enum7 : 7 {
            A = 1,
            B = 2,
          }

          enum Enum9 : 9 {
            A = 1,
            B = 2,
          }

          packet Foo {
            x: Enum7,
            y: 5,
            z: Enum9,
            w: 3,
          }
        "
    );
}
+7 −0
Original line number Diff line number Diff line
@@ -20,6 +20,13 @@ impl FieldDeclarations {
                    #id: #field_type,
                }
            }
            ast::Field::Typedef { id, type_id, .. } => {
                let id = format_ident!("{id}");
                let field_type = format_ident!("{type_id}");
                quote! {
                    #id: #field_type,
                }
            }
            _ => todo!(),
        });
    }
+14 −3
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ impl<'a> FieldParser<'a> {
        }
    }

    fn endianness_suffix(&self, width: usize) -> &'static str {
    fn endianness_suffix(&'a self, width: usize) -> &'static str {
        if width > 8 && self.endianness == ast::EndiannessValue::LittleEndian {
            "_le"
        } else {
@@ -80,7 +80,7 @@ impl<'a> FieldParser<'a> {

    fn add_bit_field(&mut self, field: &'a ast::Field) {
        self.chunk.push(BitField { shift: self.shift, field });
        self.shift += field.width().unwrap();
        self.shift += field.width(self.scope).unwrap();
        if self.shift % 8 != 0 {
            return;
        }
@@ -130,7 +130,7 @@ impl<'a> FieldParser<'a> {
                v = quote! { (#v >> #shift) }
            }

            let width = field.width().unwrap();
            let width = field.width(self.scope).unwrap();
            let value_type = types::Integer::new(width);
            if !single_value && width < value_type.width {
                // Mask value if we grabbed more than `width` and if
@@ -150,6 +150,17 @@ impl<'a> FieldParser<'a> {
                        let #id = #v;
                    }
                }
                ast::Field::Typedef { id, type_id, .. } => {
                    let id = format_ident!("{id}");
                    let type_id = format_ident!("{type_id}");
                    let from_u = format_ident!("from_u{}", value_type.width);
                    // TODO(mgeisler): Remove the `unwrap` from the
                    // generated code and return the error to the
                    // caller.
                    quote! {
                        let #id = #type_id::#from_u(#v).unwrap();
                    }
                }
                _ => todo!(),
            });
        }
Loading