Skip to content

Commit

Permalink
Migrate ast annotations to an external map
Browse files Browse the repository at this point in the history
Annotations make it cumbersome to add new information
or operate ast transformations. Instead each node is
given a unique key identifier, which can be used
to reference the node in contextes.
  • Loading branch information
hchataing committed Mar 26, 2024
1 parent c847947 commit 69239da
Show file tree
Hide file tree
Showing 14 changed files with 525 additions and 642 deletions.
641 changes: 287 additions & 354 deletions pdl-compiler/src/analyzer.rs

Large diffs are not rendered by default.

115 changes: 34 additions & 81 deletions pdl-compiler/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ pub struct SourceRange {
pub end: SourceLocation,
}

pub trait Annotation: fmt::Debug + Serialize {
type FieldAnnotation: Default + fmt::Debug + Clone;
type DeclAnnotation: Default + fmt::Debug + Clone;
}

#[derive(Debug, Serialize, Clone)]
#[serde(tag = "kind", rename = "comment")]
pub struct Comment {
Expand Down Expand Up @@ -154,10 +149,12 @@ pub enum FieldDesc {
}

#[derive(Debug, Serialize, Clone)]
pub struct Field<A: Annotation> {
pub struct Field {
pub loc: SourceRange,
/// Unique identifier used to refer to the AST node in
/// compilation environments.
#[serde(skip_serializing)]
pub annot: A::FieldAnnotation,
pub key: usize,
#[serde(flatten)]
pub desc: FieldDesc,
pub cond: Option<Constraint>,
Expand All @@ -172,7 +169,7 @@ pub struct TestCase {

#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum DeclDesc<A: Annotation> {
pub enum DeclDesc {
#[serde(rename = "checksum_declaration")]
Checksum { id: String, function: String, width: usize },
#[serde(rename = "custom_field_declaration")]
Expand All @@ -183,38 +180,42 @@ pub enum DeclDesc<A: Annotation> {
Packet {
id: String,
constraints: Vec<Constraint>,
fields: Vec<Field<A>>,
fields: Vec<Field>,
parent_id: Option<String>,
},
#[serde(rename = "struct_declaration")]
Struct {
id: String,
constraints: Vec<Constraint>,
fields: Vec<Field<A>>,
fields: Vec<Field>,
parent_id: Option<String>,
},
#[serde(rename = "group_declaration")]
Group { id: String, fields: Vec<Field<A>> },
Group { id: String, fields: Vec<Field> },
#[serde(rename = "test_declaration")]
Test { type_id: String, test_cases: Vec<TestCase> },
}

#[derive(Debug, Serialize, Clone)]
pub struct Decl<A: Annotation> {
pub struct Decl {
pub loc: SourceRange,
/// Unique identifier used to refer to the AST node in
/// compilation environments.
#[serde(skip_serializing)]
pub annot: A::DeclAnnotation,
pub key: usize,
#[serde(flatten)]
pub desc: DeclDesc<A>,
pub desc: DeclDesc,
}

#[derive(Debug, Serialize, Clone)]
pub struct File<A: Annotation> {
pub struct File {
pub version: String,
pub file: FileId,
pub comments: Vec<Comment>,
pub endianness: Endianness,
pub declarations: Vec<Decl<A>>,
pub declarations: Vec<Decl>,
#[serde(skip_serializing)]
pub max_key: usize,
}

impl SourceLocation {
Expand Down Expand Up @@ -350,17 +351,17 @@ impl PartialEq for TestCase {
}
}

impl<A: Annotation + PartialEq> Eq for File<A> {}
impl<A: Annotation + PartialEq> PartialEq for File<A> {
impl Eq for File {}
impl PartialEq for File {
fn eq(&self, other: &Self) -> bool {
// Implement structural equality, leave out comments and PDL
// version information.
self.endianness == other.endianness && self.declarations == other.declarations
}
}

impl<A: Annotation> File<A> {
pub fn new(file: FileId) -> File<A> {
impl File {
pub fn new(file: FileId) -> File {
File {
version: "1,0".to_owned(),
comments: vec![],
Expand All @@ -372,71 +373,27 @@ impl<A: Annotation> File<A> {
},
declarations: vec![],
file,
max_key: 0,
}
}

/// 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>> {
pub fn iter_children<'d>(&'d self, decl: &'d Decl) -> impl Iterator<Item = &'d Decl> {
self.declarations.iter().filter(|other_decl| other_decl.parent_id() == decl.id())
}
}

impl<A: Annotation + PartialEq> Eq for Decl<A> {}
impl<A: Annotation + PartialEq> PartialEq for Decl<A> {
impl Eq for Decl {}
impl PartialEq for Decl {
fn eq(&self, other: &Self) -> bool {
// Implement structural equality, leave out loc and annot.
// Implement structural equality, leave out loc and key.
self.desc == other.desc
}
}

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 }
}

impl Decl {
pub fn id(&self) -> Option<&str> {
match &self.desc {
DeclDesc::Test { .. } => None,
Expand Down Expand Up @@ -467,7 +424,7 @@ impl<A: Annotation> Decl<A> {
}
}

pub fn fields(&self) -> std::slice::Iter<'_, Field<A>> {
pub fn fields(&self) -> std::slice::Iter<'_, Field> {
match &self.desc {
DeclDesc::Packet { fields, .. }
| DeclDesc::Struct { fields, .. }
Expand All @@ -478,14 +435,14 @@ impl<A: Annotation> Decl<A> {

/// Return the reference to the payload or body field in a declaration,
/// if present.
pub fn payload(&self) -> Option<&Field<A>> {
pub fn payload(&self) -> Option<&Field> {
self.fields()
.find(|field| matches!(&field.desc, FieldDesc::Payload { .. } | FieldDesc::Body { .. }))
}

/// Return the reference to the payload or body size field in a declaration,
/// if present.
pub fn payload_size(&self) -> Option<&Field<A>> {
pub fn payload_size(&self) -> Option<&Field> {
self.fields().find(|field| match &field.desc {
FieldDesc::Size { field_id, .. } => field_id == "_payload_" || field_id == "_body_",
_ => false,
Expand All @@ -494,7 +451,7 @@ impl<A: Annotation> Decl<A> {

/// Return the reference to the array size or count field in a declaration,
/// if present.
pub fn array_size(&self, id: &str) -> Option<&Field<A>> {
pub fn array_size(&self, id: &str) -> Option<&Field> {
self.fields().find(|field| match &field.desc {
FieldDesc::Size { field_id, .. } | FieldDesc::Count { field_id, .. } => field_id == id,
_ => false,
Expand All @@ -514,19 +471,15 @@ impl<A: Annotation> Decl<A> {
}
}

impl<A: Annotation> Eq for Field<A> {}
impl<A: Annotation> PartialEq for Field<A> {
impl Eq for Field {}
impl PartialEq for Field {
fn eq(&self, other: &Self) -> bool {
// Implement structural equality, leave out loc and annot.
self.desc == other.desc
}
}

impl<A: Annotation> Field<A> {
pub fn annotate<B: Annotation>(&self, annot: B::FieldAnnotation) -> Field<B> {
Field { loc: self.loc, annot, cond: self.cond.clone(), desc: self.desc.clone() }
}

impl Field {
pub fn id(&self) -> Option<&str> {
match &self.desc {
FieldDesc::Checksum { .. }
Expand Down
16 changes: 4 additions & 12 deletions pdl-compiler/src/backends/intermediate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use std::collections::{btree_map::Entry, BTreeMap, HashMap};

use crate::ast;
use crate::parser;

pub struct Schema<'a> {
pub packets_and_structs: HashMap<&'a str, PacketOrStruct<'a>>,
Expand Down Expand Up @@ -98,7 +97,7 @@ pub enum ComputedOffset<'a> {
Alias(ComputedOffsetId<'a>),
}

pub fn generate(file: &parser::ast::File) -> Result<Schema, String> {
pub fn generate(file: &ast::File) -> Result<Schema, String> {
let mut schema = Schema { packets_and_structs: HashMap::new(), enums: HashMap::new() };
match file.endianness.value {
ast::EndiannessValue::LittleEndian => {}
Expand All @@ -112,7 +111,7 @@ pub fn generate(file: &parser::ast::File) -> Result<Schema, String> {
Ok(schema)
}

fn process_decl<'a>(schema: &mut Schema<'a>, decl: &'a parser::ast::Decl) {
fn process_decl<'a>(schema: &mut Schema<'a>, decl: &'a ast::Decl) {
match &decl.desc {
ast::DeclDesc::Enum { id, tags, width, .. } => process_enum(schema, id, tags, *width),
ast::DeclDesc::Packet { id, fields, .. } | ast::DeclDesc::Struct { id, fields, .. } => {
Expand All @@ -135,18 +134,11 @@ fn process_enum<'a>(schema: &mut Schema<'a>, id: &'a str, tags: &'a [ast::Tag],
);
}

fn process_packet_or_struct<'a>(
schema: &mut Schema<'a>,
id: &'a str,
fields: &'a [parser::ast::Field],
) {
fn process_packet_or_struct<'a>(schema: &mut Schema<'a>, id: &'a str, fields: &'a [ast::Field]) {
schema.packets_and_structs.insert(id, compute_getters(schema, fields));
}

fn compute_getters<'a>(
schema: &Schema<'a>,
fields: &'a [parser::ast::Field],
) -> PacketOrStruct<'a> {
fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketOrStruct<'a> {
let mut prev_pos_id = None;
let mut curr_pos_id = ComputedOffsetId::HeaderStart;
let mut computed_values = BTreeMap::new();
Expand Down
4 changes: 2 additions & 2 deletions pdl-compiler/src/backends/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

//! Json compiler backend.
use crate::parser;
use crate::ast;

/// Turn the AST into a JSON representation.
pub fn generate(file: &parser::ast::File) -> Result<String, String> {
pub fn generate(file: &ast::File) -> Result<String, String> {
serde_json::to_string_pretty(&file)
.map_err(|err| format!("could not JSON serialize grammar: {err}"))
}
Loading

0 comments on commit 69239da

Please sign in to comment.