diff --git a/pdl-compiler/src/analyzer.rs b/pdl-compiler/src/analyzer.rs index a088023..f9de973 100644 --- a/pdl-compiler/src/analyzer.rs +++ b/pdl-compiler/src/analyzer.rs @@ -303,6 +303,7 @@ impl<'d> Scope<'d> { | FieldDesc::Scalar { .. } | FieldDesc::Array { type_id: None, .. } => None, FieldDesc::FixedEnum { enum_id: type_id, .. } + | FieldDesc::Enum { enum_id: type_id, .. } | FieldDesc::Array { type_id: Some(type_id), .. } | FieldDesc::Typedef { type_id, .. } => self.typedef.get(type_id).cloned(), } @@ -318,12 +319,18 @@ impl<'d> Scope<'d> { | FieldDesc::FixedEnum { .. } | FieldDesc::Reserved { .. } | FieldDesc::Flag { .. } - | FieldDesc::Scalar { .. } => true, + | FieldDesc::Scalar { .. } + | FieldDesc::Enum { .. } => true, FieldDesc::Typedef { type_id, .. } => { let field = self.typedef.get(type_id.as_str()); matches!(field, Some(Decl { desc: DeclDesc::Enum { .. }, .. })) } - _ => false, + FieldDesc::Checksum { .. } + | FieldDesc::Padding { .. } + | FieldDesc::Array { .. } + | FieldDesc::Group { .. } + | FieldDesc::Body { .. } + | FieldDesc::Payload { .. } => false, } } } @@ -402,7 +409,8 @@ impl Schema { | FieldDesc::ElementSize { width, .. } | FieldDesc::FixedScalar { width, .. } | FieldDesc::Reserved { width } - | FieldDesc::Scalar { width, .. } => Size::Static(*width), + | FieldDesc::Scalar { width, .. } + | FieldDesc::Enum { width, .. } => Size::Static(*width), FieldDesc::Flag { .. } => Size::Static(1), FieldDesc::Body | FieldDesc::Payload { .. } => { let has_payload_size = decl.fields().any(|field| match &field.desc { @@ -1022,6 +1030,57 @@ fn check_constraint( _ => (), } } + Some(field @ Field { desc: FieldDesc::Enum { enum_id, .. }, .. }) => { + match scope.typedef.get(enum_id) { + Some(Decl { desc: DeclDesc::Enum { tags, .. }, .. }) => match &constraint.tag_id { + None => diagnostics.push( + Diagnostic::error() + .with_code(ErrorCode::E19) + .with_message(format!( + "invalid constraint value `{}`", + constraint.value.unwrap() + )) + .with_labels(vec![ + constraint.loc.primary(), + field.loc.secondary().with_message(format!( + "`{}` is declared here as typedef field", + constraint.id + )), + ]) + .with_notes(vec!["hint: expected enum value".to_owned()]), + ), + Some(tag_id) => match tags.iter().find(|tag| tag.id() == tag_id) { + None => diagnostics.push( + Diagnostic::error() + .with_code(ErrorCode::E20) + .with_message(format!("undeclared enum tag `{}`", tag_id)) + .with_labels(vec![ + constraint.loc.primary(), + field.loc.secondary().with_message(format!( + "`{}` is declared here", + constraint.id + )), + ]), + ), + Some(Tag::Range { .. }) => diagnostics.push( + Diagnostic::error() + .with_code(ErrorCode::E42) + .with_message(format!("enum tag `{}` defines a range", tag_id)) + .with_labels(vec![ + constraint.loc.primary(), + field.loc.secondary().with_message(format!( + "`{}` is declared here", + constraint.id + )), + ]) + .with_notes(vec!["hint: expected enum tag with value".to_owned()]), + ), + Some(_) => (), + }, + }, + _ => unreachable!(), + } + } Some(field @ Field { desc: FieldDesc::Typedef { type_id, .. }, .. }) => { match scope.typedef.get(type_id) { None => (), @@ -1650,6 +1709,7 @@ fn check_field_offsets(file: &File, scope: &Scope, schema: &Schema) -> Result<() | FieldDesc::FixedEnum { .. } | FieldDesc::FixedScalar { .. } | FieldDesc::Group { .. } + | FieldDesc::Enum { .. } | FieldDesc::Flag { .. } | FieldDesc::Reserved { .. } | FieldDesc::Scalar { .. } => (), @@ -1833,6 +1893,45 @@ fn desugar_flags(file: &mut File) { } } +/// Replace Typedef fields with enum types by the more specific Enum construct. +fn desugar_enums(file: &mut File) { + // Gather information about enum declarations. + let mut enum_declarations = HashMap::new(); + for decl in &file.declarations { + if let DeclDesc::Enum { id, width, .. } = &decl.desc { + enum_declarations.insert(id.to_string(), *width); + } + } + + fn to_enum_field( + desc: &FieldDesc, + enum_declarations: &HashMap, + ) -> Option { + match desc { + FieldDesc::Typedef { id, type_id, .. } => enum_declarations.get(type_id).map(|width| { + FieldDesc::Enum { id: id.to_owned(), enum_id: type_id.to_owned(), width: *width } + }), + _ => None, + } + } + + for decl in &mut file.declarations { + match &mut decl.desc { + DeclDesc::Packet { fields, .. } + | DeclDesc::Struct { fields, .. } + | DeclDesc::Group { fields, .. } => { + // Replace enum Typedef fields in other declarations. + for field in fields.iter_mut() { + if let Some(desc) = to_enum_field(&field.desc, &enum_declarations) { + field.desc = desc + } + } + } + _ => (), + } + } +} + /// Analyzer entry point, produces a new AST with annotations resulting /// from the analysis. pub fn analyze(file: &File) -> Result { @@ -1850,6 +1949,7 @@ pub fn analyze(file: &File) -> Result { check_group_constraints(file, &scope)?; let mut file = inline_groups(file)?; desugar_flags(&mut file); + desugar_enums(&mut file); let scope = Scope::new(&file)?; check_decl_constraints(&file, &scope)?; let schema = Schema::new(&file); diff --git a/pdl-compiler/src/ast.rs b/pdl-compiler/src/ast.rs index 7fac78d..28e7b2b 100644 --- a/pdl-compiler/src/ast.rs +++ b/pdl-compiler/src/ast.rs @@ -141,10 +141,13 @@ pub enum FieldDesc { }, #[serde(rename = "scalar_field")] Scalar { id: String, width: usize }, + /// Special case of Typedef for enum types. + #[serde(rename = "enum_field")] + Enum { id: String, width: usize, enum_id: String }, /// Special case of Scalar for fields used as condition for /// optional fields. The width is always 1. #[serde(rename = "flag_field")] - Flag { id: String, optional_field_ids: Vec<(String, usize)> }, + Flag { id: String, optional_field_ids: Vec, set_value: usize }, #[serde(rename = "typedef_field")] Typedef { id: String, type_id: String }, #[serde(rename = "group_field")] @@ -501,6 +504,7 @@ impl Field { | FieldDesc::Group { .. } => None, FieldDesc::Array { id, .. } | FieldDesc::Scalar { id, .. } + | FieldDesc::Enum { id, .. } | FieldDesc::Flag { id, .. } | FieldDesc::Typedef { id, .. } => Some(id), } @@ -520,6 +524,7 @@ impl Field { FieldDesc::Group { .. } => "group", FieldDesc::Array { .. } => "array", FieldDesc::Scalar { .. } => "scalar", + FieldDesc::Enum { .. } => "enum", FieldDesc::Flag { .. } => "scalar", FieldDesc::Typedef { .. } => "typedef", } diff --git a/pdl-compiler/src/backends/rust/decoder.rs b/pdl-compiler/src/backends/rust/decoder.rs index ce2725c..b2c445b 100644 --- a/pdl-compiler/src/backends/rust/decoder.rs +++ b/pdl-compiler/src/backends/rust/decoder.rs @@ -99,28 +99,28 @@ impl<'a> FieldParser<'a> { let #id = (#cond_id == #cond_value).then(|| #value); } } - ast::FieldDesc::Typedef { id, type_id } => match &self.scope.typedef[type_id].desc { - ast::DeclDesc::Enum { width, .. } => { - let name = id; - let type_name = type_id; - let id = id.to_ident(); - let type_id = type_id.to_ident(); - let decl_id = &self.packet_name; - let value = types::get_uint(self.endianness, *width, self.span); - quote! { - let #id = (#cond_id == #cond_value) - .then(|| - #type_id::try_from(#value).map_err(|unknown_val| { - DecodeError::InvalidEnumValueError { - obj: #decl_id, - field: #name, - value: unknown_val as u64, - type_: #type_name, - } - })) - .transpose()?; - } + ast::FieldDesc::Enum { id, enum_id, width } => { + let name = id; + let type_name = enum_id; + let id = id.to_ident(); + let type_id = enum_id.to_ident(); + let decl_id = &self.packet_name; + let value = types::get_uint(self.endianness, *width, self.span); + quote! { + let #id = (#cond_id == #cond_value) + .then(|| + #type_id::try_from(#value).map_err(|unknown_val| { + DecodeError::InvalidEnumValueError { + obj: #decl_id, + field: #name, + value: unknown_val as u64, + type_: #type_name, + } + })) + .transpose()?; } + } + ast::FieldDesc::Typedef { id, type_id } => match &self.scope.typedef[type_id].desc { ast::DeclDesc::Struct { .. } => { let id = id.to_ident(); let type_id = type_id.to_ident(); @@ -226,12 +226,12 @@ impl<'a> FieldParser<'a> { } } } - ast::FieldDesc::Typedef { id, type_id } => { + ast::FieldDesc::Enum { id, enum_id, .. } => { let field_name = id; - let type_name = type_id; + let type_name = enum_id; let packet_name = &self.packet_name; let id = id.to_ident(); - let type_id = type_id.to_ident(); + let type_id = enum_id.to_ident(); quote! { let #id = #type_id::try_from(#v).map_err(|unknown_val| DecodeError::InvalidEnumValueError { obj: #packet_name, diff --git a/pdl-compiler/src/backends/rust/encoder.rs b/pdl-compiler/src/backends/rust/encoder.rs index bfb1881..792619c 100644 --- a/pdl-compiler/src/backends/rust/encoder.rs +++ b/pdl-compiler/src/backends/rust/encoder.rs @@ -167,35 +167,32 @@ impl Encoder { } } } - ast::FieldDesc::Typedef { id, type_id } => match &scope.typedef[type_id].desc { - ast::DeclDesc::Enum { width, .. } => { - let id = id.to_ident(); - let backing_type = types::Integer::new(*width); - let put_uint = types::put_uint( - self.endianness, - "e!(#backing_type::from(#id)), - *width, - &self.buf, - ); + ast::FieldDesc::Enum { id, width, .. } => { + let id = id.to_ident(); + let backing_type = types::Integer::new(*width); + let put_uint = types::put_uint( + self.endianness, + "e!(#backing_type::from(#id)), + *width, + &self.buf, + ); - quote! { - if let Some(#id) = &self.#id { - #put_uint; - } + quote! { + if let Some(#id) = &self.#id { + #put_uint; } } - ast::DeclDesc::Struct { .. } => { - let id = id.to_ident(); - let buf = &self.buf; + } + ast::FieldDesc::Typedef { id, .. } => { + let id = id.to_ident(); + let buf = &self.buf; - quote! { - if let Some(#id) = &self.#id { - #id.encode(#buf)?; - } + quote! { + if let Some(#id) = &self.#id { + #id.encode(#buf)?; } } - _ => unreachable!(), - }, + } _ => unreachable!(), }); @@ -320,7 +317,7 @@ impl Encoder { let value = proc_macro2::Literal::usize_unsuffixed(*value); self.bit_fields.push(BitField { value: quote!(#value), field_type, shift }); } - ast::FieldDesc::Typedef { id, .. } => { + ast::FieldDesc::Enum { id, .. } => { let id = id.to_ident(); let field_type = types::Integer::new(width); self.bit_fields.push(BitField { diff --git a/pdl-compiler/src/backends/rust/mod.rs b/pdl-compiler/src/backends/rust/mod.rs index d37eca7..742570b 100644 --- a/pdl-compiler/src/backends/rust/mod.rs +++ b/pdl-compiler/src/backends/rust/mod.rs @@ -174,8 +174,8 @@ fn constraint_value( let type_id = fields .iter() .filter_map(|f| match &f.desc { - ast::FieldDesc::Typedef { id, type_id } if id == &constraint.id => { - Some(type_id.to_ident()) + ast::FieldDesc::Enum { id, enum_id, .. } if id == &constraint.id => { + Some(enum_id.to_ident()) } _ => None, }) @@ -213,8 +213,9 @@ fn constraint_value_str(fields: &[&'_ ast::Field], constraint: &ast::Constraint) fn implements_copy(scope: &analyzer::Scope<'_>, field: &ast::Field) -> bool { match &field.desc { ast::FieldDesc::Scalar { .. } => true, + ast::FieldDesc::Enum { .. } => true, ast::FieldDesc::Typedef { type_id, .. } => match &scope.typedef[type_id].desc { - ast::DeclDesc::Enum { .. } | ast::DeclDesc::CustomField { .. } => true, + ast::DeclDesc::CustomField { .. } => true, ast::DeclDesc::Struct { .. } => false, desc => unreachable!("unexpected declaration: {desc:?}"), }, diff --git a/pdl-compiler/src/backends/rust/types.rs b/pdl-compiler/src/backends/rust/types.rs index 5c30d10..46e827d 100644 --- a/pdl-compiler/src/backends/rust/types.rs +++ b/pdl-compiler/src/backends/rust/types.rs @@ -58,6 +58,14 @@ pub fn rust_type(field: &ast::Field) -> proc_macro2::TokenStream { let field_type = Integer::new(*width); quote!(#field_type) } + ast::FieldDesc::Enum { enum_id, .. } if field.cond.is_some() => { + let field_type = enum_id.to_ident(); + quote!(Option<#field_type>) + } + ast::FieldDesc::Enum { enum_id, .. } => { + let field_type = enum_id.to_ident(); + quote!(#field_type) + } ast::FieldDesc::Typedef { type_id, .. } if field.cond.is_some() => { let field_type = type_id.to_ident(); quote!(Option<#field_type>)