pdl: Refactor analyzer
Modify the analyzer to process the input AST to:
- annotate fields and types with the size in bits
- inline group fields
Test: cargo test analyzer::test
Change-Id: I0e9165c32aa0220faa2ca6bd29dec7d4096ebc51
diff --git a/tools/pdl/src/analyzer.rs b/tools/pdl/src/analyzer.rs
new file mode 100644
index 0000000..698e0cd
--- /dev/null
+++ b/tools/pdl/src/analyzer.rs
@@ -0,0 +1,1972 @@
+use codespan_reporting::diagnostic::Diagnostic;
+use codespan_reporting::files;
+use codespan_reporting::term;
+use codespan_reporting::term::termcolor;
+use std::collections::HashMap;
+
+use crate::ast::*;
+use crate::parser::ast as parser_ast;
+
+pub mod ast {
+ use serde::Serialize;
+
+ /// Field and declaration size information.
+ #[derive(Default, Debug, Clone, Copy)]
+ #[allow(unused)]
+ pub enum Size {
+ /// Constant size in bits.
+ Static(usize),
+ /// Size indicated at packet parsing by a size or count field.
+ /// The parameter is the static part of the size.
+ Dynamic,
+ /// The size cannot be determined statically or at runtime.
+ /// The packet assumes the largest possible size.
+ #[default]
+ Unknown,
+ }
+
+ #[derive(Debug, Serialize, Default)]
+ pub struct Annotation;
+
+ #[derive(Default, Debug, Clone)]
+ pub struct FieldAnnotation {
+ // Size of field.
+ pub size: Size,
+ }
+
+ #[derive(Default, Debug, Clone)]
+ pub struct DeclAnnotation {
+ // Size computed excluding the payload.
+ pub size: Size,
+ // Payload size, or Static(0) if the declaration does not
+ // have a payload.
+ pub payload_size: Size,
+ }
+
+ impl std::ops::Add for Size {
+ type Output = Size;
+ fn add(self, rhs: Size) -> Self::Output {
+ match (self, rhs) {
+ (Size::Unknown, _) | (_, Size::Unknown) => Size::Unknown,
+ (Size::Dynamic, _) | (_, Size::Dynamic) => Size::Dynamic,
+ (Size::Static(lhs), Size::Static(rhs)) => Size::Static(lhs + rhs),
+ }
+ }
+ }
+
+ impl std::ops::Mul for Size {
+ type Output = Size;
+ fn mul(self, rhs: Size) -> Self::Output {
+ match (self, rhs) {
+ (Size::Unknown, _) | (_, Size::Unknown) => Size::Unknown,
+ (Size::Dynamic, _) | (_, Size::Dynamic) => Size::Dynamic,
+ (Size::Static(lhs), Size::Static(rhs)) => Size::Static(lhs * rhs),
+ }
+ }
+ }
+
+ impl std::ops::Mul<usize> for Size {
+ type Output = Size;
+ fn mul(self, rhs: usize) -> Self::Output {
+ match self {
+ Size::Unknown => Size::Unknown,
+ Size::Dynamic => Size::Dynamic,
+ Size::Static(lhs) => Size::Static(lhs * rhs),
+ }
+ }
+ }
+
+ impl crate::ast::Annotation for Annotation {
+ type FieldAnnotation = FieldAnnotation;
+ type DeclAnnotation = DeclAnnotation;
+ }
+
+ #[allow(unused)]
+ pub type Field = crate::ast::Field<Annotation>;
+ #[allow(unused)]
+ pub type Decl = crate::ast::Decl<Annotation>;
+ #[allow(unused)]
+ pub type File = crate::ast::File<Annotation>;
+}
+
+/// List of unique errors reported as analyzer diagnostics.
+#[repr(u16)]
+pub enum ErrorCode {
+ DuplicateDeclIdentifier = 1,
+ RecursiveDecl = 2,
+ UndeclaredGroupIdentifier = 3,
+ InvalidGroupIdentifier = 4,
+ UndeclaredTypeIdentifier = 5,
+ InvalidTypeIdentifier = 6,
+ UndeclaredParentIdentifier = 7,
+ InvalidParentIdentifier = 8,
+ UndeclaredTestIdentifier = 9,
+ InvalidTestIdentifier = 10,
+ DuplicateFieldIdentifier = 11,
+ DuplicateTagIdentifier = 12,
+ DuplicateTagValue = 13,
+ InvalidTagValue = 14,
+ UndeclaredConstraintIdentifier = 15,
+ InvalidConstraintIdentifier = 16,
+ E17 = 17,
+ ConstraintValueOutOfRange = 18,
+ E19 = 19,
+ E20 = 20,
+ E21 = 21,
+ DuplicateConstraintIdentifier = 22,
+ DuplicateSizeField = 23,
+ UndeclaredSizeIdentifier = 24,
+ InvalidSizeIdentifier = 25,
+ DuplicateCountField = 26,
+ UndeclaredCountIdentifier = 27,
+ InvalidCountIdentifier = 28,
+ DuplicateElementSizeField = 29,
+ UndeclaredElementSizeIdentifier = 30,
+ InvalidElementSizeIdentifier = 31,
+ FixedValueOutOfRange = 32,
+ E33 = 33,
+ E34 = 34,
+ E35 = 35,
+ DuplicatePayloadField = 36,
+ MissingPayloadField = 37,
+ RedundantArraySize = 38,
+}
+
+impl From<ErrorCode> for String {
+ fn from(code: ErrorCode) -> Self {
+ format!("E{}", code as u16)
+ }
+}
+
+/// Aggregate analyzer diagnostics.
+#[derive(Debug, Default)]
+pub struct Diagnostics {
+ pub diagnostics: Vec<Diagnostic<FileId>>,
+}
+
+/// Gather information about the full AST.
+#[derive(Debug, Default)]
+pub struct Scope<'d, A: Annotation> {
+ /// Collection of Group, Packet, Enum, Struct, Checksum, and CustomField
+ /// declarations.
+ pub typedef: HashMap<String, &'d crate::ast::Decl<A>>,
+}
+
+impl Diagnostics {
+ fn is_empty(&self) -> bool {
+ self.diagnostics.is_empty()
+ }
+
+ fn push(&mut self, diagnostic: Diagnostic<FileId>) {
+ self.diagnostics.push(diagnostic)
+ }
+
+ fn err_or<T>(self, value: T) -> Result<T, Diagnostics> {
+ if self.is_empty() {
+ Ok(value)
+ } else {
+ Err(self)
+ }
+ }
+
+ pub fn emit(
+ &self,
+ sources: &SourceDatabase,
+ writer: &mut dyn termcolor::WriteColor,
+ ) -> Result<(), files::Error> {
+ let config = term::Config::default();
+ for d in self.diagnostics.iter() {
+ term::emit(writer, &config, sources, d)?;
+ }
+ Ok(())
+ }
+}
+
+impl<'d, A: Annotation + Default> Scope<'d, A> {
+ pub fn new(file: &'d crate::ast::File<A>) -> Result<Scope<'d, A>, Diagnostics> {
+ // Gather top-level declarations.
+ let mut scope: Scope<A> = Default::default();
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ if let Some(id) = decl.id() {
+ if let Some(prev) = scope.typedef.insert(id.to_string(), decl) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicateDeclIdentifier)
+ .with_message(format!(
+ "redeclaration of {} identifier `{}`",
+ decl.kind(),
+ id
+ ))
+ .with_labels(vec![
+ decl.loc.primary(),
+ prev.loc
+ .secondary()
+ .with_message(format!("`{}` is first declared here", id)),
+ ]),
+ )
+ }
+ }
+ }
+
+ // Return failure if any diagnostic is raised.
+ if diagnostics.is_empty() {
+ Ok(scope)
+ } else {
+ Err(diagnostics)
+ }
+ }
+
+ /// Return the parent declaration of the selected declaration,
+ /// if it has one.
+ pub fn get_parent(&self, decl: &crate::ast::Decl<A>) -> Option<&'d crate::ast::Decl<A>> {
+ decl.parent_id().and_then(|parent_id| self.typedef.get(parent_id).cloned())
+ }
+
+ /// Iterate over the parent declarations of the selected declaration.
+ pub fn iter_parents<'s>(
+ &'s self,
+ decl: &'d crate::ast::Decl<A>,
+ ) -> impl Iterator<Item = &'d Decl<A>> + 's {
+ std::iter::successors(self.get_parent(decl), |decl| self.get_parent(decl))
+ }
+
+ /// Iterate over the declaration and its parent's fields.
+ pub fn iter_fields<'s>(
+ &'s self,
+ decl: &'d crate::ast::Decl<A>,
+ ) -> impl Iterator<Item = &'d Field<A>> + 's {
+ std::iter::successors(Some(decl), |decl| self.get_parent(decl)).flat_map(Decl::fields)
+ }
+
+ /// Return the type declaration for the selected field, if applicable.
+ #[allow(dead_code)]
+ pub fn get_declaration(
+ &self,
+ field: &'d crate::ast::Field<A>,
+ ) -> Option<&'d crate::ast::Decl<A>> {
+ match &field.desc {
+ FieldDesc::Checksum { .. }
+ | FieldDesc::Padding { .. }
+ | FieldDesc::Size { .. }
+ | FieldDesc::Count { .. }
+ | FieldDesc::ElementSize { .. }
+ | FieldDesc::Body
+ | FieldDesc::Payload { .. }
+ | FieldDesc::FixedScalar { .. }
+ | FieldDesc::Reserved { .. }
+ | FieldDesc::Group { .. }
+ | FieldDesc::Scalar { .. }
+ | FieldDesc::Array { type_id: None, .. } => None,
+ FieldDesc::FixedEnum { enum_id: type_id, .. }
+ | FieldDesc::Array { type_id: Some(type_id), .. }
+ | FieldDesc::Typedef { type_id, .. } => self.typedef.get(type_id).cloned(),
+ }
+ }
+}
+
+/// Return the bit-width of a scalar value.
+fn bit_width(value: usize) -> usize {
+ usize::BITS as usize - value.leading_zeros() as usize
+}
+
+/// Check declaration identifiers.
+/// Raises error diagnostics for the following cases:
+/// - undeclared parent identifier
+/// - invalid parent identifier
+/// - undeclared group identifier
+/// - invalid group identifier
+/// - undeclared typedef identifier
+/// - invalid typedef identifier
+/// - undeclared test identifier
+/// - invalid test identifier
+/// - recursive declaration
+fn check_decl_identifiers(
+ file: &parser_ast::File,
+ scope: &Scope<parser_ast::Annotation>,
+) -> Result<(), Diagnostics> {
+ enum Mark {
+ Temporary,
+ Permanent,
+ }
+ #[derive(Default)]
+ struct Context<'d> {
+ visited: HashMap<&'d str, Mark>,
+ }
+
+ fn bfs<'d>(
+ decl: &'d parser_ast::Decl,
+ context: &mut Context<'d>,
+ scope: &Scope<'d, parser_ast::Annotation>,
+ diagnostics: &mut Diagnostics,
+ ) {
+ let decl_id = decl.id().unwrap();
+ match context.visited.get(decl_id) {
+ Some(Mark::Permanent) => return,
+ Some(Mark::Temporary) => {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::RecursiveDecl)
+ .with_message(format!(
+ "recursive declaration of {} `{}`",
+ decl.kind(),
+ decl_id
+ ))
+ .with_labels(vec![decl.loc.primary()]),
+ );
+ return;
+ }
+ _ => (),
+ }
+
+ // Start visiting current declaration.
+ context.visited.insert(decl_id, Mark::Temporary);
+
+ // Iterate over Struct and Group fields.
+ for field in decl.fields() {
+ match &field.desc {
+ // Validate that the group field has a valid identifier.
+ // If the type is a group recurse the group definition.
+ FieldDesc::Group { group_id, .. } => match scope.typedef.get(group_id) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::UndeclaredGroupIdentifier)
+ .with_message(format!("undeclared group identifier `{}`", group_id))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected group identifier".to_owned()]),
+ ),
+ Some(group_decl @ Decl { desc: DeclDesc::Group { .. }, .. }) => {
+ bfs(group_decl, context, scope, diagnostics)
+ }
+ Some(_) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidGroupIdentifier)
+ .with_message(format!("invalid group identifier `{}`", group_id))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected group identifier".to_owned()]),
+ ),
+ },
+ // Validate that the typedef field has a valid identifier.
+ // If the type is a struct recurse the struct definition.
+ // Append the field to the packet re-definition.
+ FieldDesc::Typedef { type_id, .. }
+ | FieldDesc::Array { type_id: Some(type_id), .. } => {
+ match scope.typedef.get(type_id) {
+ None => diagnostics.push(
+ Diagnostic::error().with_code(ErrorCode::UndeclaredTypeIdentifier)
+ .with_message(format!(
+ "undeclared {} identifier `{}`",
+ field.kind(),
+ type_id
+ ))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected enum, struct, custom_field, or checksum identifier".to_owned()]),
+ ),
+ Some(Decl { desc: DeclDesc::Packet { .. }, .. }) => diagnostics.push(
+ Diagnostic::error().with_code(ErrorCode::InvalidTypeIdentifier)
+ .with_message(format!(
+ "invalid {} identifier `{}`",
+ field.kind(),
+ type_id
+ ))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected enum, struct, custom_field, or checksum identifier".to_owned()]),
+ ),
+ Some(typedef_decl) =>
+ // Not recursing on array type since it is allowed to
+ // have recursive structures, e.g. nested TLV types.
+ if matches!(&field.desc, FieldDesc::Typedef { .. }) ||
+ matches!(&field.desc, FieldDesc::Array { size: Some(_), .. }) {
+ bfs(typedef_decl, context, scope, diagnostics)
+ }
+ }
+ }
+ // Ignore other fields.
+ _ => (),
+ }
+ }
+
+ // Iterate over parent declaration.
+ if let Some(parent_id) = decl.parent_id() {
+ let parent_decl = scope.typedef.get(parent_id);
+ match (&decl.desc, parent_decl) {
+ (DeclDesc::Packet { .. }, None) | (DeclDesc::Struct { .. }, None) => diagnostics
+ .push(
+ Diagnostic::error()
+ .with_code(ErrorCode::UndeclaredParentIdentifier)
+ .with_message(format!("undeclared parent identifier `{}`", parent_id))
+ .with_labels(vec![decl.loc.primary()])
+ .with_notes(vec![format!("hint: expected {} identifier", decl.kind())]),
+ ),
+ (
+ DeclDesc::Packet { .. },
+ Some(parent_decl @ Decl { desc: DeclDesc::Packet { .. }, .. }),
+ )
+ | (
+ DeclDesc::Struct { .. },
+ Some(parent_decl @ Decl { desc: DeclDesc::Struct { .. }, .. }),
+ ) => bfs(parent_decl, context, scope, diagnostics),
+ (_, Some(_)) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidParentIdentifier)
+ .with_message(format!("invalid parent identifier `{}`", parent_id))
+ .with_labels(vec![decl.loc.primary()])
+ .with_notes(vec![format!("hint: expected {} identifier", decl.kind())]),
+ ),
+ _ => unreachable!(),
+ }
+ }
+
+ // Done visiting current declaration.
+ context.visited.insert(decl_id, Mark::Permanent);
+ }
+
+ // Start bfs.
+ let mut diagnostics = Default::default();
+ let mut context = Default::default();
+ for decl in &file.declarations {
+ match &decl.desc {
+ DeclDesc::Checksum { .. } | DeclDesc::CustomField { .. } | DeclDesc::Enum { .. } => (),
+ DeclDesc::Packet { .. } | DeclDesc::Struct { .. } | DeclDesc::Group { .. } => {
+ bfs(decl, &mut context, scope, &mut diagnostics)
+ }
+ DeclDesc::Test { type_id, .. } => match scope.typedef.get(type_id) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::UndeclaredTestIdentifier)
+ .with_message(format!("undeclared test identifier `{}`", type_id))
+ .with_labels(vec![decl.loc.primary()])
+ .with_notes(vec!["hint: expected packet identifier".to_owned()]),
+ ),
+ Some(Decl { desc: DeclDesc::Packet { .. }, .. }) => (),
+ Some(_) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidTestIdentifier)
+ .with_message(format!("invalid test identifier `{}`", type_id))
+ .with_labels(vec![decl.loc.primary()])
+ .with_notes(vec!["hint: expected packet identifier".to_owned()]),
+ ),
+ },
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check field identifiers.
+/// Raises error diagnostics for the following cases:
+/// - duplicate field identifier
+fn check_field_identifiers(file: &parser_ast::File) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ let mut local_scope = HashMap::new();
+ for field in decl.fields() {
+ if let Some(id) = field.id() {
+ if let Some(prev) = local_scope.insert(id.to_string(), field) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicateFieldIdentifier)
+ .with_message(format!(
+ "redeclaration of {} field identifier `{}`",
+ field.kind(),
+ id
+ ))
+ .with_labels(vec![
+ field.loc.primary(),
+ prev.loc
+ .secondary()
+ .with_message(format!("`{}` is first declared here", id)),
+ ]),
+ )
+ }
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check enum declarations.
+/// Raises error diagnostics for the following cases:
+/// - duplicate tag identifier
+/// - duplicate tag value
+fn check_enum_declarations(file: &parser_ast::File) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ if let DeclDesc::Enum { tags, width, .. } = &decl.desc {
+ let mut tags_by_id = HashMap::new();
+ let mut tags_by_value = HashMap::new();
+
+ for tag in tags {
+ if let Some(prev) = tags_by_id.insert(&tag.id, tag) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicateTagIdentifier)
+ .with_message(format!("duplicate tag identifier `{}`", tag.id))
+ .with_labels(vec![
+ tag.loc.primary(),
+ prev.loc
+ .secondary()
+ .with_message(format!("`{}` is first declared here", tag.id)),
+ ]),
+ )
+ }
+ if let Some(prev) = tags_by_value.insert(&tag.value, tag) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicateTagValue)
+ .with_message(format!("duplicate tag value `{}`", tag.value))
+ .with_labels(vec![
+ tag.loc.primary(),
+ prev.loc.secondary().with_message(format!(
+ "`{}` is first declared here",
+ tag.value
+ )),
+ ]),
+ )
+ }
+
+ if bit_width(tag.value) > *width {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidTagValue)
+ .with_message(format!(
+ "tag value `{}` is larger than the maximum value",
+ tag.value
+ ))
+ .with_labels(vec![tag.loc.primary()]),
+ )
+ }
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check constraints.
+/// Raises error diagnostics for the following cases:
+/// - undeclared constraint identifier
+/// - invalid constraint identifier
+/// - invalid constraint scalar value (bad type)
+/// - invalid constraint scalar value (overflow)
+/// - invalid constraint enum value (bad type)
+/// - invalid constraint enum value (undeclared tag)
+/// - duplicate constraint
+fn check_constraints(
+ file: &parser_ast::File,
+ scope: &Scope<parser_ast::Annotation>,
+) -> Result<(), Diagnostics> {
+ fn check_constraint(
+ constraint: &Constraint,
+ decl: &parser_ast::Decl,
+ scope: &Scope<parser_ast::Annotation>,
+ diagnostics: &mut Diagnostics,
+ ) {
+ match scope.iter_fields(decl).find(|field| field.id() == Some(&constraint.id)) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::UndeclaredConstraintIdentifier)
+ .with_message(format!("undeclared constraint identifier `{}`", constraint.id))
+ .with_labels(vec![constraint.loc.primary()])
+ .with_notes(vec!["hint: expected scalar or typedef identifier".to_owned()]),
+ ),
+ Some(field @ Field { desc: FieldDesc::Array { .. }, .. }) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidConstraintIdentifier)
+ .with_message(format!("invalid constraint identifier `{}`", constraint.id))
+ .with_labels(vec![
+ constraint.loc.primary(),
+ field.loc.secondary().with_message(format!(
+ "`{}` is declared here as array field",
+ constraint.id
+ )),
+ ])
+ .with_notes(vec!["hint: expected scalar or typedef identifier".to_owned()]),
+ ),
+ Some(field @ Field { desc: FieldDesc::Scalar { width, .. }, .. }) => {
+ match constraint.value {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::E17)
+ .with_message(format!(
+ "invalid constraint value `{}`",
+ constraint.tag_id.as_ref().unwrap()
+ ))
+ .with_labels(vec![
+ constraint.loc.primary(),
+ field.loc.secondary().with_message(format!(
+ "`{}` is declared here as scalar field",
+ constraint.id
+ )),
+ ])
+ .with_notes(vec!["hint: expected scalar value".to_owned()]),
+ ),
+ Some(value) if bit_width(value) > *width => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::ConstraintValueOutOfRange)
+ .with_message(format!(
+ "constraint value `{}` is larger than maximum value",
+ value
+ ))
+ .with_labels(vec![constraint.loc.primary(), field.loc.secondary()]),
+ ),
+ _ => (),
+ }
+ }
+ Some(field @ Field { desc: FieldDesc::Typedef { type_id, .. }, .. }) => {
+ match scope.typedef.get(type_id) {
+ None => (),
+ 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) => {
+ if !tags.iter().any(|tag| &tag.id == tag_id) {
+ 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(decl) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::E21)
+ .with_message(format!(
+ "invalid constraint identifier `{}`",
+ constraint.value.unwrap()
+ ))
+ .with_labels(vec![
+ constraint.loc.primary(),
+ field.loc.secondary().with_message(format!(
+ "`{}` is declared here as {} typedef field",
+ constraint.id,
+ decl.kind()
+ )),
+ ])
+ .with_notes(vec!["hint: expected enum value".to_owned()]),
+ ),
+ }
+ }
+ Some(_) => unreachable!(),
+ }
+ }
+
+ fn check_constraints<'d>(
+ constraints: &'d [Constraint],
+ parent_decl: &parser_ast::Decl,
+ scope: &Scope<parser_ast::Annotation>,
+ mut constraints_by_id: HashMap<String, &'d Constraint>,
+ diagnostics: &mut Diagnostics,
+ ) {
+ for constraint in constraints {
+ check_constraint(constraint, parent_decl, scope, diagnostics);
+ if let Some(prev) = constraints_by_id.insert(constraint.id.to_string(), constraint) {
+ // Constraint appears twice in current set.
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicateConstraintIdentifier)
+ .with_message(format!(
+ "duplicate constraint identifier `{}`",
+ constraint.id
+ ))
+ .with_labels(vec![
+ constraint.loc.primary(),
+ prev.loc
+ .secondary()
+ .with_message(format!("`{}` is first constrained here", prev.id)),
+ ]),
+ )
+ }
+ }
+ }
+
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ // Check constraints for packet inheritance.
+ match &decl.desc {
+ DeclDesc::Packet { constraints, parent_id: Some(parent_id), .. }
+ | DeclDesc::Struct { constraints, parent_id: Some(parent_id), .. } => {
+ let parent_decl = scope.typedef.get(parent_id).unwrap();
+ check_constraints(
+ constraints,
+ parent_decl,
+ scope,
+ // Include constraints declared in parent declarations
+ // for duplicate check.
+ scope.iter_parents(decl).fold(HashMap::new(), |acc, decl| {
+ decl.constraints().fold(acc, |mut acc, constraint| {
+ let _ = acc.insert(constraint.id.to_string(), constraint);
+ acc
+ })
+ }),
+ &mut diagnostics,
+ )
+ }
+ _ => (),
+ }
+
+ // Check constraints for group inlining.
+ for field in decl.fields() {
+ if let FieldDesc::Group { group_id, constraints } = &field.desc {
+ let group_decl = scope.typedef.get(group_id).unwrap();
+ check_constraints(constraints, group_decl, scope, HashMap::new(), &mut diagnostics)
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check size fields.
+/// Raises error diagnostics for the following cases:
+/// - undeclared size identifier
+/// - invalid size identifier
+/// - duplicate size field
+/// - undeclared count identifier
+/// - invalid count identifier
+/// - duplicate count field
+/// - undeclared elementsize identifier
+/// - invalid elementsize identifier
+/// - duplicate elementsize field
+fn check_size_fields(file: &parser_ast::File) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ let mut size_for_id = HashMap::new();
+ let mut element_size_for_id = HashMap::new();
+ for field in decl.fields() {
+ // Check for duplicate size, count, or element size fields.
+ if let Some((reverse_map, field_id, err)) = match &field.desc {
+ FieldDesc::Size { field_id, .. } => {
+ Some((&mut size_for_id, field_id, ErrorCode::DuplicateSizeField))
+ }
+ FieldDesc::Count { field_id, .. } => {
+ Some((&mut size_for_id, field_id, ErrorCode::DuplicateCountField))
+ }
+ FieldDesc::ElementSize { field_id, .. } => {
+ Some((&mut element_size_for_id, field_id, ErrorCode::DuplicateElementSizeField))
+ }
+ _ => None,
+ } {
+ if let Some(prev) = reverse_map.insert(field_id, field) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(err)
+ .with_message(format!("duplicate {} field", field.kind()))
+ .with_labels(vec![
+ field.loc.primary(),
+ prev.loc.secondary().with_message(format!(
+ "{} is first declared here",
+ prev.kind()
+ )),
+ ]),
+ )
+ }
+ }
+
+ // Check for invalid size, count, or element size field identifiers.
+ match &field.desc {
+ FieldDesc::Size { field_id, .. } => {
+ match decl.fields().find(|field| match &field.desc {
+ FieldDesc::Payload { .. } => field_id == "_payload_",
+ FieldDesc::Body { .. } => field_id == "_body_",
+ _ => field.id() == Some(field_id),
+ }) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::UndeclaredSizeIdentifier)
+ .with_message(format!(
+ "undeclared {} identifier `{}`",
+ field.kind(),
+ field_id
+ ))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec![
+ "hint: expected payload, body, or array identifier".to_owned(),
+ ]),
+ ),
+ Some(Field { desc: FieldDesc::Body { .. }, .. })
+ | Some(Field { desc: FieldDesc::Payload { .. }, .. })
+ | Some(Field { desc: FieldDesc::Array { .. }, .. }) => (),
+ Some(Field { loc, .. }) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::InvalidSizeIdentifier)
+ .with_message(format!(
+ "invalid {} identifier `{}`",
+ field.kind(),
+ field_id
+ ))
+ .with_labels(vec![field.loc.primary(), loc.secondary()])
+ .with_notes(vec![
+ "hint: expected payload, body, or array identifier".to_owned(),
+ ]),
+ ),
+ }
+ }
+
+ FieldDesc::Count { field_id, .. } | FieldDesc::ElementSize { field_id, .. } => {
+ let (undeclared_err, invalid_err) =
+ if matches!(&field.desc, FieldDesc::Count { .. }) {
+ (
+ ErrorCode::UndeclaredCountIdentifier,
+ ErrorCode::InvalidCountIdentifier,
+ )
+ } else {
+ (
+ ErrorCode::UndeclaredElementSizeIdentifier,
+ ErrorCode::InvalidElementSizeIdentifier,
+ )
+ };
+ match decl.fields().find(|field| field.id() == Some(field_id)) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(undeclared_err)
+ .with_message(format!(
+ "undeclared {} identifier `{}`",
+ field.kind(),
+ field_id
+ ))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected array identifier".to_owned()]),
+ ),
+ Some(Field { desc: FieldDesc::Array { .. }, .. }) => (),
+ Some(Field { loc, .. }) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(invalid_err)
+ .with_message(format!(
+ "invalid {} identifier `{}`",
+ field.kind(),
+ field_id
+ ))
+ .with_labels(vec![field.loc.primary(), loc.secondary()])
+ .with_notes(vec!["hint: expected array identifier".to_owned()]),
+ ),
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check fixed fields.
+/// Raises error diagnostics for the following cases:
+/// - invalid scalar value
+/// - undeclared enum identifier
+/// - invalid enum identifier
+/// - undeclared tag identifier
+fn check_fixed_fields(
+ file: &parser_ast::File,
+ scope: &Scope<parser_ast::Annotation>,
+) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ for field in decl.fields() {
+ match &field.desc {
+ FieldDesc::FixedScalar { value, width } if bit_width(*value) > *width => {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::FixedValueOutOfRange)
+ .with_message(format!(
+ "fixed value `{}` is larger than maximum value",
+ value
+ ))
+ .with_labels(vec![field.loc.primary()]),
+ )
+ }
+ FieldDesc::FixedEnum { tag_id, enum_id } => match scope.typedef.get(enum_id) {
+ None => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::E33)
+ .with_message(format!("undeclared type identifier `{}`", enum_id))
+ .with_labels(vec![field.loc.primary()])
+ .with_notes(vec!["hint: expected enum identifier".to_owned()]),
+ ),
+ Some(enum_decl @ Decl { desc: DeclDesc::Enum { tags, .. }, .. }) => {
+ if !tags.iter().any(|tag| &tag.id == tag_id) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::E34)
+ .with_message(format!("undeclared tag identifier `{}`", tag_id))
+ .with_labels(vec![
+ field.loc.primary(),
+ enum_decl.loc.secondary(),
+ ]),
+ )
+ }
+ }
+ Some(decl) => diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::E35)
+ .with_message(format!("invalid type identifier `{}`", enum_id))
+ .with_labels(vec![
+ field.loc.primary(),
+ decl.loc
+ .secondary()
+ .with_message(format!("`{}` is declared here", enum_id)),
+ ])
+ .with_notes(vec!["hint: expected enum identifier".to_owned()]),
+ ),
+ },
+ _ => (),
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check payload fields.
+/// Raises error diagnostics for the following cases:
+/// - duplicate payload field
+/// - duplicate payload field size
+/// - duplicate body field
+/// - duplicate body field size
+/// - missing payload field
+fn check_payload_fields(file: &parser_ast::File) -> Result<(), Diagnostics> {
+ // Check whether the declaration requires a payload field.
+ // The payload is required if any child packets declares fields.
+ fn requires_payload(file: &parser_ast::File, decl: &parser_ast::Decl) -> bool {
+ file.iter_children(decl).any(|child| child.fields().next().is_some())
+ }
+
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ let mut payload: Option<&parser_ast::Field> = None;
+ for field in decl.fields() {
+ match &field.desc {
+ FieldDesc::Payload { .. } | FieldDesc::Body { .. } => {
+ if let Some(prev) = payload {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::DuplicatePayloadField)
+ .with_message(format!("duplicate {} field", field.kind()))
+ .with_labels(vec![
+ field.loc.primary(),
+ prev.loc.secondary().with_message(format!(
+ "{} is first declared here",
+ prev.kind()
+ )),
+ ]),
+ )
+ } else {
+ payload = Some(field);
+ }
+ }
+ _ => (),
+ }
+ }
+
+ if payload.is_none() && requires_payload(file, decl) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::MissingPayloadField)
+ .with_message("missing payload field".to_owned())
+ .with_labels(vec![decl.loc.primary()])
+ .with_notes(vec![format!(
+ "hint: one child packet is extending `{}`",
+ decl.id().unwrap()
+ )]),
+ )
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check array fields.
+/// Raises error diagnostics for the following cases:
+/// - redundant array field size
+fn check_array_fields(file: &parser_ast::File) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
+ for field in decl.fields() {
+ if let FieldDesc::Array { id, size: Some(size), .. } = &field.desc {
+ if let Some(size_field) = decl.fields().find(|field| match &field.desc {
+ FieldDesc::Size { field_id, .. } | FieldDesc::Count { field_id, .. } => {
+ field_id == id
+ }
+ _ => false,
+ }) {
+ diagnostics.push(
+ Diagnostic::error()
+ .with_code(ErrorCode::RedundantArraySize)
+ .with_message(format!("redundant array {} field", size_field.kind()))
+ .with_labels(vec![
+ size_field.loc.primary(),
+ field
+ .loc
+ .secondary()
+ .with_message(format!("`{}` has constant size {}", id, size)),
+ ]),
+ )
+ }
+ }
+ }
+ }
+
+ diagnostics.err_or(())
+}
+
+/// Check correct definition of packet sizes.
+/// Annotate fields and declarations with the size in bits.
+fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
+ fn annotate_decl(
+ decl: &parser_ast::Decl,
+ scope: &HashMap<String, ast::DeclAnnotation>,
+ ) -> ast::Decl {
+ // Annotate the declaration fields.
+ let mut decl = decl.annotate(Default::default(), |fields| {
+ fields.iter().map(|field| annotate_field(decl, field, scope)).collect()
+ });
+
+ // Compute the declaration annotation.
+ decl.annot = match &decl.desc {
+ DeclDesc::Packet { fields, .. }
+ | DeclDesc::Struct { fields, .. }
+ | DeclDesc::Group { fields, .. } => {
+ let mut size = decl
+ .parent_id()
+ .and_then(|parent_id| scope.get(parent_id))
+ .map(|annot| annot.size)
+ .unwrap_or(ast::Size::Static(0));
+ let mut payload_size = ast::Size::Static(0);
+ for field in fields {
+ match &field.desc {
+ FieldDesc::Payload { .. } | FieldDesc::Body { .. } => {
+ payload_size = field.annot.size
+ }
+ _ => size = size + field.annot.size,
+ }
+ }
+ ast::DeclAnnotation { size, payload_size }
+ }
+ DeclDesc::Enum { width, .. }
+ | DeclDesc::Checksum { width, .. }
+ | DeclDesc::CustomField { width: Some(width), .. } => {
+ ast::DeclAnnotation { size: ast::Size::Static(*width), ..decl.annot }
+ }
+ DeclDesc::CustomField { width: None, .. } => {
+ ast::DeclAnnotation { size: ast::Size::Dynamic, ..decl.annot }
+ }
+ DeclDesc::Test { .. } => {
+ ast::DeclAnnotation { size: ast::Size::Static(0), ..decl.annot }
+ }
+ };
+ decl
+ }
+
+ fn annotate_field(
+ decl: &parser_ast::Decl,
+ field: &parser_ast::Field,
+ scope: &HashMap<String, ast::DeclAnnotation>,
+ ) -> ast::Field {
+ field.annotate(match &field.desc {
+ FieldDesc::Checksum { .. } | FieldDesc::Padding { .. } => {
+ ast::FieldAnnotation { size: ast::Size::Static(0) }
+ }
+ FieldDesc::Size { width, .. }
+ | FieldDesc::Count { width, .. }
+ | FieldDesc::ElementSize { width, .. }
+ | FieldDesc::FixedScalar { width, .. }
+ | FieldDesc::Reserved { width }
+ | FieldDesc::Scalar { width, .. } => {
+ ast::FieldAnnotation { size: ast::Size::Static(*width) }
+ }
+ FieldDesc::Body | FieldDesc::Payload { .. } => {
+ let has_payload_size = decl.fields().any(|field| match &field.desc {
+ FieldDesc::Size { field_id, .. } => {
+ field_id == "_body_" || field_id == "_payload_"
+ }
+ _ => false,
+ });
+ ast::FieldAnnotation {
+ size: if has_payload_size { ast::Size::Dynamic } else { ast::Size::Unknown },
+ }
+ }
+ FieldDesc::Typedef { type_id, .. }
+ | FieldDesc::FixedEnum { enum_id: type_id, .. }
+ | FieldDesc::Group { group_id: type_id, .. } => {
+ let type_annot = scope.get(type_id).unwrap();
+ ast::FieldAnnotation { size: type_annot.size + type_annot.payload_size }
+ }
+ FieldDesc::Array { width: Some(width), size: Some(size), .. } => {
+ ast::FieldAnnotation { size: ast::Size::Static(*size * *width) }
+ }
+ FieldDesc::Array { width: None, size: Some(size), type_id: Some(type_id), .. } => {
+ let type_annot = scope.get(type_id).unwrap();
+ ast::FieldAnnotation { size: (type_annot.size + type_annot.payload_size) * *size }
+ }
+ FieldDesc::Array { id, size: None, .. } => {
+ // The element does not matter when the size of the array is
+ // not static. The array size depends on there being a count
+ // or size field or not.
+ let has_array_size = decl.fields().any(|field| match &field.desc {
+ FieldDesc::Size { field_id, .. } | FieldDesc::Count { field_id, .. } => {
+ field_id == id
+ }
+ _ => false,
+ });
+ ast::FieldAnnotation {
+ size: if has_array_size { ast::Size::Dynamic } else { ast::Size::Unknown },
+ }
+ }
+ FieldDesc::Array { .. } => unreachable!(),
+ })
+ }
+
+ // Construct a scope mapping typedef identifiers to decl annotations.
+ let mut scope = HashMap::new();
+
+ // Annotate declarations.
+ let mut declarations = Vec::new();
+ for decl in file.declarations.iter() {
+ let decl = annotate_decl(decl, &scope);
+ if let Some(id) = decl.id() {
+ scope.insert(id.to_string(), decl.annot.clone());
+ }
+ declarations.push(decl);
+ }
+
+ File {
+ version: file.version.clone(),
+ file: file.file,
+ comments: file.comments.clone(),
+ endianness: file.endianness,
+ declarations,
+ }
+}
+
+/// Inline group fields and remove group declarations.
+fn inline_groups(_file: &mut ast::File) -> Result<(), Diagnostics> {
+ // TODO
+ Ok(())
+}
+
+/// Analyzer entry point, produces a new AST with annotations resulting
+/// from the analysis.
+pub fn analyze(file: &parser_ast::File) -> Result<ast::File, Diagnostics> {
+ let scope = Scope::new(file)?;
+ check_decl_identifiers(file, &scope)?;
+ check_field_identifiers(file)?;
+ check_enum_declarations(file)?;
+ check_constraints(file, &scope)?;
+ check_size_fields(file)?;
+ check_fixed_fields(file, &scope)?;
+ check_payload_fields(file)?;
+ check_array_fields(file)?;
+ // TODO check_checksum_fields(file, &scope)?;
+ // TODO check_padding_fields(file, &scope)?;
+ let mut file = compute_field_sizes(file);
+ inline_groups(&mut file)?;
+ Ok(file)
+}
+
+#[cfg(test)]
+mod test {
+ use crate::analyzer;
+ use crate::ast::*;
+ use crate::parser::parse_inline;
+ use codespan_reporting::term::termcolor;
+
+ macro_rules! raises {
+ ($code:ident, $text:literal) => {{
+ let mut db = SourceDatabase::new();
+ let file = parse_inline(&mut db, "stdin".to_owned(), $text.to_owned())
+ .expect("parsing failure");
+ let result = analyzer::analyze(&file);
+ assert!(matches!(result, Err(_)));
+ let diagnostics = result.err().unwrap();
+ let mut buffer = termcolor::Buffer::no_color();
+ let _ = diagnostics.emit(&db, &mut buffer);
+ println!("{}", std::str::from_utf8(buffer.as_slice()).unwrap());
+ assert_eq!(diagnostics.diagnostics.len(), 1);
+ assert_eq!(diagnostics.diagnostics[0].code, Some(analyzer::ErrorCode::$code.into()));
+ }};
+ }
+
+ #[test]
+ fn test_e1() {
+ raises!(
+ DuplicateDeclIdentifier,
+ r#"
+ little_endian_packets
+ struct A { }
+ packet A { }
+ "#
+ );
+
+ raises!(
+ DuplicateDeclIdentifier,
+ r#"
+ little_endian_packets
+ struct A { }
+ enum A : 8 { X = 0, Y = 1 }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e2() {
+ raises!(
+ RecursiveDecl,
+ r#"
+ little_endian_packets
+ packet A : A { }
+ "#
+ );
+
+ raises!(
+ RecursiveDecl,
+ r#"
+ little_endian_packets
+ packet A : B { }
+ packet B : A { }
+ "#
+ );
+
+ raises!(
+ RecursiveDecl,
+ r#"
+ little_endian_packets
+ struct B { x : B }
+ "#
+ );
+
+ raises!(
+ RecursiveDecl,
+ r#"
+ little_endian_packets
+ struct B { x : B[8] }
+ "#
+ );
+
+ raises!(
+ RecursiveDecl,
+ r#"
+ little_endian_packets
+ group C { C { x = 1 } }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e3() {
+ raises!(
+ UndeclaredGroupIdentifier,
+ r#"
+ little_endian_packets
+ packet A { C { x = 1 } }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e4() {
+ raises!(
+ InvalidGroupIdentifier,
+ r#"
+ little_endian_packets
+ struct C { x : 8 }
+ packet A { C { x = 1 } }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e5() {
+ raises!(
+ UndeclaredTypeIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x : B }
+ "#
+ );
+
+ raises!(
+ UndeclaredTypeIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x : B[] }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e6() {
+ raises!(
+ InvalidTypeIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B { x : A }
+ "#
+ );
+
+ raises!(
+ InvalidTypeIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B { x : A[] }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e7() {
+ raises!(
+ UndeclaredParentIdentifier,
+ r#"
+ little_endian_packets
+ packet A : B { }
+ "#
+ );
+
+ raises!(
+ UndeclaredParentIdentifier,
+ r#"
+ little_endian_packets
+ struct A : B { }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e8() {
+ raises!(
+ InvalidParentIdentifier,
+ r#"
+ little_endian_packets
+ struct A { }
+ packet B : A { }
+ "#
+ );
+
+ raises!(
+ InvalidParentIdentifier,
+ r#"
+ little_endian_packets
+ packet A { }
+ struct B : A { }
+ "#
+ );
+
+ raises!(
+ InvalidParentIdentifier,
+ r#"
+ little_endian_packets
+ group A { x : 1 }
+ struct B : A { }
+ "#
+ );
+ }
+
+ #[ignore]
+ #[test]
+ fn test_e9() {
+ raises!(
+ UndeclaredTestIdentifier,
+ r#"
+ little_endian_packets
+ test A { "aaa" }
+ "#
+ );
+ }
+
+ #[ignore]
+ #[test]
+ fn test_e10() {
+ raises!(
+ InvalidTestIdentifier,
+ r#"
+ little_endian_packets
+ struct A { }
+ test A { "aaa" }
+ "#
+ );
+
+ raises!(
+ InvalidTestIdentifier,
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ test A { "aaa" }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e11() {
+ raises!(
+ DuplicateFieldIdentifier,
+ r#"
+ little_endian_packets
+ enum A : 8 { X = 0 }
+ struct B {
+ x : 8,
+ x : A
+ }
+ "#
+ );
+
+ raises!(
+ DuplicateFieldIdentifier,
+ r#"
+ little_endian_packets
+ enum A : 8 { X = 0 }
+ packet B {
+ x : 8,
+ x : A[]
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e12() {
+ raises!(
+ DuplicateTagIdentifier,
+ r#"
+ little_endian_packets
+ enum A : 8 {
+ X = 0,
+ X = 1,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e13() {
+ raises!(
+ DuplicateTagValue,
+ r#"
+ little_endian_packets
+ enum A : 8 {
+ X = 0,
+ Y = 0,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e14() {
+ raises!(
+ InvalidTagValue,
+ r#"
+ little_endian_packets
+ enum A : 8 {
+ X = 256,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e15() {
+ raises!(
+ UndeclaredConstraintIdentifier,
+ r#"
+ little_endian_packets
+ packet A { }
+ packet B : A (x = 1) { }
+ "#
+ );
+
+ raises!(
+ UndeclaredConstraintIdentifier,
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ packet B {
+ A { y = 1 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e16() {
+ raises!(
+ InvalidConstraintIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x : 8[] }
+ packet B : A (x = 1) { }
+ "#
+ );
+
+ raises!(
+ InvalidConstraintIdentifier,
+ r#"
+ little_endian_packets
+ group A { x : 8[] }
+ packet B {
+ A { x = 1 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e17() {
+ raises!(
+ E17,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B : A (x = X) { }
+ "#
+ );
+
+ raises!(
+ E17,
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ packet B {
+ A { x = X }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e18() {
+ raises!(
+ ConstraintValueOutOfRange,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B : A (x = 256) { }
+ "#
+ );
+
+ raises!(
+ ConstraintValueOutOfRange,
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ packet B {
+ A { x = 256 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e19() {
+ raises!(
+ E19,
+ r#"
+ little_endian_packets
+ enum C : 8 { X = 0 }
+ packet A { x : C }
+ packet B : A (x = 0) { }
+ "#
+ );
+
+ raises!(
+ E19,
+ r#"
+ little_endian_packets
+ enum C : 8 { X = 0 }
+ group A { x : C }
+ packet B {
+ A { x = 0 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e20() {
+ raises!(
+ E20,
+ r#"
+ little_endian_packets
+ enum C : 8 { X = 0 }
+ packet A { x : C }
+ packet B : A (x = Y) { }
+ "#
+ );
+
+ raises!(
+ E20,
+ r#"
+ little_endian_packets
+ enum C : 8 { X = 0 }
+ group A { x : C }
+ packet B {
+ A { x = Y }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e21() {
+ raises!(
+ E21,
+ r#"
+ little_endian_packets
+ struct C { }
+ packet A { x : C }
+ packet B : A (x = 0) { }
+ "#
+ );
+
+ raises!(
+ E21,
+ r#"
+ little_endian_packets
+ struct C { }
+ group A { x : C }
+ packet B {
+ A { x = 0 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e22() {
+ raises!(
+ DuplicateConstraintIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x: 8 }
+ packet B : A (x = 0, x = 1) { }
+ "#
+ );
+
+ raises!(
+ DuplicateConstraintIdentifier,
+ r#"
+ little_endian_packets
+ packet A { x: 8 }
+ packet B : A (x = 0) { }
+ packet C : B (x = 1) { }
+ "#
+ );
+
+ raises!(
+ DuplicateConstraintIdentifier,
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ packet B {
+ A { x = 0, x = 1 }
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e23() {
+ raises!(
+ DuplicateSizeField,
+ r#"
+ little_endian_packets
+ struct A {
+ _size_ (_payload_) : 8,
+ _size_ (_payload_) : 8,
+ _payload_,
+ }
+ "#
+ );
+
+ raises!(
+ DuplicateSizeField,
+ r#"
+ little_endian_packets
+ struct A {
+ _count_ (x) : 8,
+ _size_ (x) : 8,
+ x: 8[],
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e24() {
+ raises!(
+ UndeclaredSizeIdentifier,
+ r#"
+ little_endian_packets
+ struct A {
+ _size_ (x) : 8,
+ }
+ "#
+ );
+
+ raises!(
+ UndeclaredSizeIdentifier,
+ r#"
+ little_endian_packets
+ struct A {
+ _size_ (_payload_) : 8,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e25() {
+ raises!(
+ InvalidSizeIdentifier,
+ r#"
+ little_endian_packets
+ enum B : 8 { X = 0 }
+ struct A {
+ _size_ (x) : 8,
+ x : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e26() {
+ raises!(
+ DuplicateCountField,
+ r#"
+ little_endian_packets
+ struct A {
+ _size_ (x) : 8,
+ _count_ (x) : 8,
+ x: 8[],
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e27() {
+ raises!(
+ UndeclaredCountIdentifier,
+ r#"
+ little_endian_packets
+ struct A {
+ _count_ (x) : 8,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e28() {
+ raises!(
+ InvalidCountIdentifier,
+ r#"
+ little_endian_packets
+ enum B : 8 { X = 0 }
+ struct A {
+ _count_ (x) : 8,
+ x : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e29() {
+ raises!(
+ DuplicateElementSizeField,
+ r#"
+ little_endian_packets
+ struct A {
+ _elementsize_ (x) : 8,
+ _elementsize_ (x) : 8,
+ x: 8[],
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e30() {
+ raises!(
+ UndeclaredElementSizeIdentifier,
+ r#"
+ little_endian_packets
+ struct A {
+ _elementsize_ (x) : 8,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e31() {
+ raises!(
+ InvalidElementSizeIdentifier,
+ r#"
+ little_endian_packets
+ enum B : 8 { X = 0 }
+ struct A {
+ _elementsize_ (x) : 8,
+ x : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e32() {
+ raises!(
+ FixedValueOutOfRange,
+ r#"
+ little_endian_packets
+ struct A {
+ _fixed_ = 256 : 8,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e33() {
+ raises!(
+ E33,
+ r#"
+ little_endian_packets
+ struct A {
+ _fixed_ = X : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e34() {
+ raises!(
+ E34,
+ r#"
+ little_endian_packets
+ enum B : 8 { X = 0 }
+ struct A {
+ _fixed_ = Y : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e35() {
+ raises!(
+ E35,
+ r#"
+ little_endian_packets
+ struct B { }
+ struct A {
+ _fixed_ = X : B,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e36() {
+ raises!(
+ DuplicatePayloadField,
+ r#"
+ little_endian_packets
+ packet A {
+ _payload_,
+ _body_,
+ }
+ "#
+ );
+
+ raises!(
+ DuplicatePayloadField,
+ r#"
+ little_endian_packets
+ packet A {
+ _body_,
+ _payload_,
+ }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e37() {
+ raises!(
+ MissingPayloadField,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B : A { y : 8 }
+ "#
+ );
+
+ raises!(
+ MissingPayloadField,
+ r#"
+ little_endian_packets
+ packet A { x : 8 }
+ packet B : A (x = 0) { }
+ packet C : B { y : 8 }
+ "#
+ );
+ }
+
+ #[test]
+ fn test_e38() {
+ raises!(
+ RedundantArraySize,
+ r#"
+ little_endian_packets
+ packet A {
+ _size_ (x) : 8,
+ x : 8[8]
+ }
+ "#
+ );
+
+ raises!(
+ RedundantArraySize,
+ r#"
+ little_endian_packets
+ packet A {
+ _count_ (x) : 8,
+ x : 8[8]
+ }
+ "#
+ );
+ }
+}
diff --git a/tools/pdl/src/ast.rs b/tools/pdl/src/ast.rs
index 28fd098..c094da4 100644
--- a/tools/pdl/src/ast.rs
+++ b/tools/pdl/src/ast.rs
@@ -31,11 +31,11 @@
}
pub trait Annotation: fmt::Debug + Serialize {
- type FieldAnnotation: Default + fmt::Debug;
+ type FieldAnnotation: Default + fmt::Debug + Clone;
type DeclAnnotation: Default + fmt::Debug;
}
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, Clone)]
#[serde(tag = "kind", rename = "comment")]
pub struct Comment {
pub loc: SourceRange,
@@ -56,7 +56,7 @@
pub value: EndiannessValue,
}
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, Clone)]
#[serde(tag = "kind", rename = "tag")]
pub struct Tag {
pub id: String,
@@ -121,7 +121,7 @@
pub desc: FieldDesc,
}
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, Clone)]
#[serde(tag = "kind", rename = "test_case")]
pub struct TestCase {
pub loc: SourceRange,
@@ -250,9 +250,61 @@
file,
}
}
+
+ /// Iterate over the children of the selected declaration.
+ /// /!\ This method is unsafe to use if the file contains cyclic
+ /// declarations, use with caution.
+ pub fn iter_children<'d>(&'d self, decl: &'d Decl<A>) -> impl Iterator<Item = &'d Decl<A>> {
+ self.declarations.iter().filter(|other_decl| other_decl.parent_id() == decl.id())
+ }
}
impl<A: Annotation> Decl<A> {
+ pub fn new(loc: SourceRange, desc: DeclDesc<A>) -> Decl<A> {
+ Decl { loc, annot: Default::default(), desc }
+ }
+
+ pub fn annotate<F, B: Annotation>(
+ &self,
+ annot: B::DeclAnnotation,
+ annotate_fields: F,
+ ) -> Decl<B>
+ where
+ F: FnOnce(&[Field<A>]) -> Vec<Field<B>>,
+ {
+ let desc = match &self.desc {
+ DeclDesc::Checksum { id, function, width } => {
+ DeclDesc::Checksum { id: id.clone(), function: function.clone(), width: *width }
+ }
+ DeclDesc::CustomField { id, width, function } => {
+ DeclDesc::CustomField { id: id.clone(), width: *width, function: function.clone() }
+ }
+ DeclDesc::Enum { id, tags, width } => {
+ DeclDesc::Enum { id: id.clone(), tags: tags.clone(), width: *width }
+ }
+
+ DeclDesc::Test { type_id, test_cases } => {
+ DeclDesc::Test { type_id: type_id.clone(), test_cases: test_cases.clone() }
+ }
+ DeclDesc::Packet { id, constraints, parent_id, fields } => DeclDesc::Packet {
+ id: id.clone(),
+ constraints: constraints.clone(),
+ parent_id: parent_id.clone(),
+ fields: annotate_fields(fields),
+ },
+ DeclDesc::Struct { id, constraints, parent_id, fields } => DeclDesc::Struct {
+ id: id.clone(),
+ constraints: constraints.clone(),
+ parent_id: parent_id.clone(),
+ fields: annotate_fields(fields),
+ },
+ DeclDesc::Group { id, fields } => {
+ DeclDesc::Group { id: id.clone(), fields: annotate_fields(fields) }
+ }
+ };
+ Decl { loc: self.loc, desc, annot }
+ }
+
pub fn id(&self) -> Option<&str> {
match &self.desc {
DeclDesc::Test { .. } => None,
@@ -265,6 +317,24 @@
}
}
+ pub fn parent_id(&self) -> Option<&str> {
+ match &self.desc {
+ DeclDesc::Packet { parent_id, .. } | DeclDesc::Struct { parent_id, .. } => {
+ parent_id.as_deref()
+ }
+ _ => None,
+ }
+ }
+
+ pub fn constraints(&self) -> std::slice::Iter<'_, Constraint> {
+ match &self.desc {
+ DeclDesc::Packet { constraints, .. } | DeclDesc::Struct { constraints, .. } => {
+ constraints.iter()
+ }
+ _ => [].iter(),
+ }
+ }
+
/// Determine the size of a declaration type in bits, if possible.
///
/// If the type is dynamically sized (e.g. contains an array or
@@ -290,12 +360,33 @@
}
}
- pub fn new(loc: SourceRange, desc: DeclDesc<A>) -> Decl<A> {
- Decl { loc, annot: Default::default(), desc }
+ pub fn fields(&self) -> std::slice::Iter<'_, Field<A>> {
+ match &self.desc {
+ DeclDesc::Packet { fields, .. }
+ | DeclDesc::Struct { fields, .. }
+ | DeclDesc::Group { fields, .. } => fields.iter(),
+ _ => [].iter(),
+ }
+ }
+
+ pub fn kind(&self) -> &str {
+ match &self.desc {
+ DeclDesc::Checksum { .. } => "checksum",
+ DeclDesc::CustomField { .. } => "custom field",
+ DeclDesc::Enum { .. } => "enum",
+ DeclDesc::Packet { .. } => "packet",
+ DeclDesc::Struct { .. } => "struct",
+ DeclDesc::Group { .. } => "group",
+ DeclDesc::Test { .. } => "test",
+ }
}
}
impl<A: Annotation> Field<A> {
+ pub fn annotate<B: Annotation>(&self, annot: B::FieldAnnotation) -> Field<B> {
+ Field { loc: self.loc, annot, desc: self.desc.clone() }
+ }
+
pub fn id(&self) -> Option<&str> {
match &self.desc {
FieldDesc::Checksum { .. }
@@ -370,6 +461,24 @@
_ => None,
}
}
+
+ pub fn kind(&self) -> &str {
+ match &self.desc {
+ FieldDesc::Checksum { .. } => "payload",
+ FieldDesc::Padding { .. } => "padding",
+ FieldDesc::Size { .. } => "size",
+ FieldDesc::Count { .. } => "count",
+ FieldDesc::ElementSize { .. } => "elementsize",
+ FieldDesc::Body { .. } => "body",
+ FieldDesc::Payload { .. } => "payload",
+ FieldDesc::FixedScalar { .. } | FieldDesc::FixedEnum { .. } => "fixed",
+ FieldDesc::Reserved { .. } => "reserved",
+ FieldDesc::Group { .. } => "group",
+ FieldDesc::Array { .. } => "array",
+ FieldDesc::Scalar { .. } => "scalar",
+ FieldDesc::Typedef { .. } => "typedef",
+ }
+ }
}
#[cfg(test)]
diff --git a/tools/pdl/src/lint.rs b/tools/pdl/src/lint.rs
index 53e66ae..3466993 100644
--- a/tools/pdl/src/lint.rs
+++ b/tools/pdl/src/lint.rs
@@ -11,7 +11,7 @@
use serde::Serialize;
// Field and declaration size information.
- #[derive(Default, Debug)]
+ #[derive(Default, Debug, Clone)]
#[allow(unused)]
pub enum Size {
// Constant size in bits.
@@ -682,26 +682,6 @@
}
}
-impl parser::ast::Field {
- fn kind(&self) -> &str {
- match &self.desc {
- FieldDesc::Checksum { .. } => "payload",
- FieldDesc::Padding { .. } => "padding",
- FieldDesc::Size { .. } => "size",
- FieldDesc::Count { .. } => "count",
- FieldDesc::ElementSize { .. } => "elementsize",
- FieldDesc::Body { .. } => "body",
- FieldDesc::Payload { .. } => "payload",
- FieldDesc::FixedScalar { .. } | FieldDesc::FixedEnum { .. } => "fixed",
- FieldDesc::Reserved { .. } => "reserved",
- FieldDesc::Group { .. } => "group",
- FieldDesc::Array { .. } => "array",
- FieldDesc::Scalar { .. } => "scalar",
- FieldDesc::Typedef { .. } => "typedef",
- }
- }
-}
-
// Helper for linting an enum declaration.
fn lint_enum(tags: &[Tag], width: usize, result: &mut LintDiagnostics) {
let mut local_scope = HashMap::new();
@@ -1211,17 +1191,6 @@
}
impl parser::ast::Decl {
- fn constraints(&self) -> impl Iterator<Item = &Constraint> {
- match &self.desc {
- DeclDesc::Packet { constraints, .. } | DeclDesc::Struct { constraints, .. } => {
- Some(constraints.iter())
- }
- _ => None,
- }
- .into_iter()
- .flatten()
- }
-
fn scope<'d>(&'d self, result: &mut LintDiagnostics) -> Option<PacketScope<'d>> {
match &self.desc {
DeclDesc::Packet { fields, .. }
@@ -1264,18 +1233,6 @@
DeclDesc::Test { .. } => (),
}
}
-
- fn kind(&self) -> &str {
- match &self.desc {
- DeclDesc::Checksum { .. } => "checksum",
- DeclDesc::CustomField { .. } => "custom field",
- DeclDesc::Enum { .. } => "enum",
- DeclDesc::Packet { .. } => "packet",
- DeclDesc::Struct { .. } => "struct",
- DeclDesc::Group { .. } => "group",
- DeclDesc::Test { .. } => "test",
- }
- }
}
impl parser::ast::File {
diff --git a/tools/pdl/src/main.rs b/tools/pdl/src/main.rs
index b6fc62c..428a4f5 100644
--- a/tools/pdl/src/main.rs
+++ b/tools/pdl/src/main.rs
@@ -1,8 +1,9 @@
-//! PDL parser and linter.
+//! PDL parser and analyzer.
use clap::Parser;
use codespan_reporting::term::{self, termcolor};
+mod analyzer;
mod ast;
mod backends;
mod lint;
@@ -10,8 +11,6 @@
#[cfg(test)]
mod test_utils;
-use crate::lint::Lintable;
-
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum OutputFormat {
JSON,
@@ -62,12 +61,19 @@
let mut sources = ast::SourceDatabase::new();
match parser::parse_file(&mut sources, opt.input_file) {
Ok(file) => {
- let lint = file.lint();
- if !lint.diagnostics.is_empty() {
- lint.print(&sources, termcolor::ColorChoice::Always)
- .expect("Could not print lint diagnostics");
- return std::process::ExitCode::FAILURE;
- }
+ let _analyzed_file = match analyzer::analyze(&file) {
+ Ok(file) => file,
+ Err(diagnostics) => {
+ diagnostics
+ .emit(
+ &sources,
+ &mut termcolor::StandardStream::stderr(termcolor::ColorChoice::Always)
+ .lock(),
+ )
+ .expect("Could not print analyzer diagnostics");
+ return std::process::ExitCode::FAILURE;
+ }
+ };
match opt.output_format {
OutputFormat::JSON => {
@@ -89,6 +95,7 @@
}
std::process::ExitCode::SUCCESS
}
+
Err(err) => {
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Always);
let config = term::Config::default();
diff --git a/tools/pdl/src/parser.rs b/tools/pdl/src/parser.rs
index 4b00912..4a68a65 100644
--- a/tools/pdl/src/parser.rs
+++ b/tools/pdl/src/parser.rs
@@ -7,7 +7,7 @@
pub mod ast {
use serde::Serialize;
- #[derive(Debug, Serialize, PartialEq, Eq)]
+ #[derive(Debug, Serialize, Default, PartialEq, Eq)]
pub struct Annotation;
impl crate::ast::Annotation for Annotation {