aboutsummaryrefslogtreecommitdiff
path: root/src/analyzer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/analyzer.rs')
-rw-r--r--src/analyzer.rs425
1 files changed, 239 insertions, 186 deletions
diff --git a/src/analyzer.rs b/src/analyzer.rs
index c89d3d0..d1cf84e 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -20,7 +20,6 @@ use std::collections::HashMap;
use crate::ast::*;
use crate::parser::ast as parser_ast;
-use crate::utils;
pub mod ast {
use serde::Serialize;
@@ -845,184 +844,176 @@ fn check_enum_declarations(file: &parser_ast::File) -> Result<(), Diagnostics> {
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,
+/// Helper function for validating one constraint.
+fn check_constraint(
+ constraint: &Constraint,
+ decl: &parser_ast::Decl,
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 {
+ 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::E17)
+ .with_code(ErrorCode::E19)
.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) => 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(_) => (),
- },
- }
- }
- 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()
+ "`{}` is declared here as typedef field",
+ constraint.id
)),
])
.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(
+ 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(_) => (),
+ },
+ },
+ Some(decl) => diagnostics.push(
Diagnostic::error()
- .with_code(ErrorCode::DuplicateConstraintIdentifier)
+ .with_code(ErrorCode::E21)
.with_message(format!(
- "duplicate constraint identifier `{}`",
- constraint.id
+ "invalid constraint identifier `{}`",
+ constraint.value.unwrap()
))
.with_labels(vec![
constraint.loc.primary(),
- prev.loc
- .secondary()
- .with_message(format!("`{}` is first constrained here", prev.id)),
- ]),
- )
+ 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!(),
}
+}
+/// Helper function for validating a list of constraints.
+fn check_constraints_list<'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)),
+ ]),
+ )
+ }
+ }
+}
+
+/// 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_decl_constraints(
+ file: &parser_ast::File,
+ scope: &Scope<parser_ast::Annotation>,
+) -> Result<(), Diagnostics> {
let mut diagnostics: Diagnostics = Default::default();
for decl in &file.declarations {
// Check constraints for packet inheritance.
@@ -1030,7 +1021,7 @@ fn check_constraints(
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(
+ check_constraints_list(
constraints,
parent_decl,
scope,
@@ -1047,12 +1038,37 @@ fn check_constraints(
}
_ => (),
}
+ }
+ 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_group_constraints(
+ file: &parser_ast::File,
+ scope: &Scope<parser_ast::Annotation>,
+) -> Result<(), Diagnostics> {
+ let mut diagnostics: Diagnostics = Default::default();
+ for decl in &file.declarations {
// 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)
+ check_constraints_list(
+ constraints,
+ group_decl,
+ scope,
+ HashMap::new(),
+ &mut diagnostics,
+ )
}
}
}
@@ -1542,12 +1558,12 @@ fn compute_field_sizes(file: &parser_ast::File) -> ast::File {
}
/// Inline group fields and remove group declarations.
-fn inline_groups(file: &mut ast::File) -> Result<(), Diagnostics> {
+fn inline_groups(file: &parser_ast::File) -> Result<parser_ast::File, Diagnostics> {
fn inline_fields<'a>(
- fields: impl Iterator<Item = &'a ast::Field>,
- groups: &HashMap<String, ast::Decl>,
+ fields: impl Iterator<Item = &'a parser_ast::Field>,
+ groups: &HashMap<String, &parser_ast::Decl>,
constraints: &HashMap<String, Constraint>,
- ) -> Vec<ast::Field> {
+ ) -> Vec<parser_ast::Field> {
fields
.flat_map(|field| match &field.desc {
FieldDesc::Group { group_id, constraints: group_constraints } => {
@@ -1560,17 +1576,17 @@ fn inline_groups(file: &mut ast::File) -> Result<(), Diagnostics> {
inline_fields(groups.get(group_id).unwrap().fields(), groups, &constraints)
}
FieldDesc::Scalar { id, width } if constraints.contains_key(id) => {
- vec![ast::Field {
+ vec![parser_ast::Field {
desc: FieldDesc::FixedScalar {
width: *width,
value: constraints.get(id).unwrap().value.unwrap(),
},
loc: field.loc,
- annot: field.annot.clone(),
+ annot: field.annot,
}]
}
FieldDesc::Typedef { id, type_id, .. } if constraints.contains_key(id) => {
- vec![ast::Field {
+ vec![parser_ast::Field {
desc: FieldDesc::FixedEnum {
enum_id: type_id.clone(),
tag_id: constraints
@@ -1579,7 +1595,7 @@ fn inline_groups(file: &mut ast::File) -> Result<(), Diagnostics> {
.unwrap(),
},
loc: field.loc,
- annot: field.annot.clone(),
+ annot: field.annot,
}]
}
_ => vec![field.clone()],
@@ -1587,23 +1603,50 @@ fn inline_groups(file: &mut ast::File) -> Result<(), Diagnostics> {
.collect()
}
- let groups = utils::drain_filter(&mut file.declarations, |decl| {
- matches!(&decl.desc, DeclDesc::Group { .. })
- })
- .into_iter()
- .map(|decl| (decl.id().unwrap().to_owned(), decl))
- .collect::<HashMap<String, _>>();
-
- for decl in file.declarations.iter_mut() {
- match &mut decl.desc {
- DeclDesc::Packet { fields, .. } | DeclDesc::Struct { fields, .. } => {
- *fields = inline_fields(fields.iter(), &groups, &HashMap::new())
- }
- _ => (),
- }
- }
+ let groups = file
+ .declarations
+ .iter()
+ .filter(|decl| matches!(&decl.desc, DeclDesc::Group { .. }))
+ .map(|decl| (decl.id().unwrap().to_owned(), decl))
+ .collect::<HashMap<String, _>>();
+
+ let declarations = file
+ .declarations
+ .iter()
+ .filter_map(|decl| match &decl.desc {
+ DeclDesc::Packet { fields, id, parent_id, constraints } => Some(parser_ast::Decl {
+ desc: DeclDesc::Packet {
+ fields: inline_fields(fields.iter(), &groups, &HashMap::new()),
+ id: id.clone(),
+ parent_id: parent_id.clone(),
+ constraints: constraints.clone(),
+ },
+ loc: decl.loc,
+ annot: decl.annot,
+ }),
+ DeclDesc::Struct { fields, id, parent_id, constraints } => Some(parser_ast::Decl {
+ desc: DeclDesc::Struct {
+ fields: inline_fields(fields.iter(), &groups, &HashMap::new()),
+ id: id.clone(),
+ parent_id: parent_id.clone(),
+ constraints: constraints.clone(),
+ },
+ loc: decl.loc,
+ annot: decl.annot,
+ }),
+ DeclDesc::Group { .. } => None,
+ _ => Some(decl.clone()),
+ })
+ .collect();
- Ok(())
+ Ok(File {
+ declarations,
+
+ version: file.version.clone(),
+ file: file.file,
+ comments: file.comments.clone(),
+ endianness: file.endianness,
+ })
}
/// Analyzer entry point, produces a new AST with annotations resulting
@@ -1613,16 +1656,17 @@ pub fn analyze(file: &parser_ast::File) -> Result<ast::File, Diagnostics> {
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)?;
check_padding_fields(file)?;
check_checksum_fields(file, &scope)?;
- let mut file = compute_field_sizes(file);
- inline_groups(&mut file)?;
- Ok(file)
+ check_group_constraints(file, &scope)?;
+ let file = inline_groups(file)?;
+ let scope = Scope::new(&file)?;
+ check_decl_constraints(&file, &scope)?;
+ Ok(compute_field_sizes(&file))
}
#[cfg(test)]
@@ -2020,6 +2064,15 @@ mod test {
}
"#
);
+
+ valid!(
+ r#"
+ little_endian_packets
+ group A { x : 8 }
+ packet B { A }
+ packet C : B (x = 1) { }
+ "#
+ );
}
#[test]