pdl_compiler/
ast.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use codespan_reporting::diagnostic;
16use codespan_reporting::files;
17use serde::Serialize;
18use std::fmt;
19use std::ops;
20
21/// File identifier.
22/// References a source file in the source database.
23pub type FileId = usize;
24
25/// Source database.
26/// Stores the source file contents for reference.
27pub type SourceDatabase = files::SimpleFiles<String, String>;
28
29#[derive(Debug, Default, Copy, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
30pub struct SourceLocation {
31    /// Byte offset into the file (counted from zero).
32    pub offset: usize,
33    /// Line number (counted from zero).
34    pub line: usize,
35    /// Column number (counted from zero)
36    pub column: usize,
37}
38
39#[derive(Default, Copy, Clone, PartialEq, Eq, Serialize)]
40pub struct SourceRange {
41    pub file: FileId,
42    pub start: SourceLocation,
43    pub end: SourceLocation,
44}
45
46#[derive(Debug, Serialize, Clone)]
47#[serde(tag = "kind", rename = "comment")]
48pub struct Comment {
49    pub loc: SourceRange,
50    pub text: String,
51}
52
53#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum EndiannessValue {
56    LittleEndian,
57    BigEndian,
58}
59
60#[derive(Debug, Copy, Clone, Serialize)]
61#[serde(tag = "kind", rename = "endianness_declaration")]
62pub struct Endianness {
63    pub loc: SourceRange,
64    pub value: EndiannessValue,
65}
66
67#[derive(Debug, Clone, Serialize)]
68#[serde(tag = "kind", rename = "tag")]
69pub struct TagValue {
70    pub id: String,
71    pub loc: SourceRange,
72    pub value: usize,
73}
74
75#[derive(Debug, Clone, Serialize)]
76#[serde(tag = "kind", rename = "tag")]
77pub struct TagRange {
78    pub id: String,
79    pub loc: SourceRange,
80    pub range: ops::RangeInclusive<usize>,
81    pub tags: Vec<TagValue>,
82}
83
84#[derive(Debug, Clone, Serialize)]
85#[serde(tag = "kind", rename = "tag")]
86pub struct TagOther {
87    pub id: String,
88    pub loc: SourceRange,
89}
90
91#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
92#[serde(untagged)]
93pub enum Tag {
94    Value(TagValue),
95    Range(TagRange),
96    Other(TagOther),
97}
98
99#[derive(Debug, Serialize, Clone)]
100#[serde(tag = "kind", rename = "constraint")]
101pub struct Constraint {
102    pub id: String,
103    pub loc: SourceRange,
104    pub value: Option<usize>,
105    pub tag_id: Option<String>,
106}
107
108#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
109pub struct FieldKey(pub usize);
110
111#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
112#[serde(tag = "kind")]
113pub enum FieldDesc {
114    #[serde(rename = "checksum_field")]
115    Checksum { field_id: String },
116    #[serde(rename = "padding_field")]
117    Padding { size: usize },
118    #[serde(rename = "size_field")]
119    Size { field_id: String, width: usize },
120    #[serde(rename = "count_field")]
121    Count { field_id: String, width: usize },
122    #[serde(rename = "elementsize_field")]
123    ElementSize { field_id: String, width: usize },
124    #[serde(rename = "body_field")]
125    Body,
126    #[serde(rename = "payload_field")]
127    Payload { size_modifier: Option<String> },
128    #[serde(rename = "fixed_field")]
129    FixedScalar { width: usize, value: usize },
130    #[serde(rename = "fixed_field")]
131    FixedEnum { enum_id: String, tag_id: String },
132    #[serde(rename = "reserved_field")]
133    Reserved { width: usize },
134    #[serde(rename = "array_field")]
135    Array {
136        id: String,
137        width: Option<usize>,
138        type_id: Option<String>,
139        size_modifier: Option<String>,
140        size: Option<usize>,
141    },
142    #[serde(rename = "scalar_field")]
143    Scalar { id: String, width: usize },
144    /// Special case of Scalar for fields used as condition for
145    /// optional fields. The width is always 1.
146    #[serde(rename = "flag_field")]
147    Flag { id: String, optional_field_ids: Vec<(String, usize)> },
148    #[serde(rename = "typedef_field")]
149    Typedef { id: String, type_id: String },
150    #[serde(rename = "group_field")]
151    Group { group_id: String, constraints: Vec<Constraint> },
152}
153
154#[derive(Debug, Serialize, Clone)]
155pub struct Field {
156    pub loc: SourceRange,
157    /// Unique identifier used to refer to the AST node in
158    /// compilation environments.
159    #[serde(skip_serializing)]
160    pub key: FieldKey,
161    #[serde(flatten)]
162    pub desc: FieldDesc,
163    pub cond: Option<Constraint>,
164}
165
166#[derive(Debug, Serialize, Clone)]
167#[serde(tag = "kind", rename = "test_case")]
168pub struct TestCase {
169    pub loc: SourceRange,
170    pub input: String,
171}
172
173#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
174pub struct DeclKey(pub usize);
175
176#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
177#[serde(tag = "kind")]
178pub enum DeclDesc {
179    #[serde(rename = "checksum_declaration")]
180    Checksum { id: String, function: String, width: usize },
181    #[serde(rename = "custom_field_declaration")]
182    CustomField { id: String, width: Option<usize>, function: String },
183    #[serde(rename = "enum_declaration")]
184    Enum { id: String, tags: Vec<Tag>, width: usize },
185    #[serde(rename = "packet_declaration")]
186    Packet {
187        id: String,
188        constraints: Vec<Constraint>,
189        fields: Vec<Field>,
190        parent_id: Option<String>,
191    },
192    #[serde(rename = "struct_declaration")]
193    Struct {
194        id: String,
195        constraints: Vec<Constraint>,
196        fields: Vec<Field>,
197        parent_id: Option<String>,
198    },
199    #[serde(rename = "group_declaration")]
200    Group { id: String, fields: Vec<Field> },
201    #[serde(rename = "test_declaration")]
202    Test { type_id: String, test_cases: Vec<TestCase> },
203}
204
205#[derive(Debug, Serialize, Clone)]
206pub struct Decl {
207    pub loc: SourceRange,
208    /// Unique identifier used to refer to the AST node in
209    /// compilation environments.
210    #[serde(skip_serializing)]
211    pub key: DeclKey,
212    #[serde(flatten)]
213    pub desc: DeclDesc,
214}
215
216#[derive(Debug, Serialize, Clone)]
217pub struct File {
218    pub version: String,
219    pub file: FileId,
220    pub comments: Vec<Comment>,
221    pub endianness: Endianness,
222    pub declarations: Vec<Decl>,
223    #[serde(skip_serializing)]
224    pub max_key: usize,
225}
226
227impl SourceLocation {
228    /// Construct a new source location.
229    ///
230    /// The `line_starts` indicates the byte offsets where new lines
231    /// start in the file. The first element should thus be `0` since
232    /// every file has at least one line starting at offset `0`.
233    pub fn new(offset: usize, line_starts: &[usize]) -> SourceLocation {
234        let mut loc = SourceLocation { offset, line: 0, column: offset };
235        for (line, start) in line_starts.iter().enumerate() {
236            if *start > offset {
237                break;
238            }
239            loc = SourceLocation { offset, line, column: offset - start };
240        }
241        loc
242    }
243}
244
245impl SourceRange {
246    pub fn primary(&self) -> diagnostic::Label<FileId> {
247        diagnostic::Label::primary(self.file, self.start.offset..self.end.offset)
248    }
249    pub fn secondary(&self) -> diagnostic::Label<FileId> {
250        diagnostic::Label::secondary(self.file, self.start.offset..self.end.offset)
251    }
252}
253
254impl fmt::Display for SourceRange {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        if self.start.line == self.end.line {
257            write!(f, "{}:{}-{}", self.start.line, self.start.column, self.end.column)
258        } else {
259            write!(
260                f,
261                "{}:{}-{}:{}",
262                self.start.line, self.start.column, self.end.line, self.end.column
263            )
264        }
265    }
266}
267
268impl fmt::Debug for SourceRange {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        f.debug_struct("SourceRange").finish_non_exhaustive()
271    }
272}
273
274impl ops::Add<SourceRange> for SourceRange {
275    type Output = SourceRange;
276
277    fn add(self, rhs: SourceRange) -> SourceRange {
278        assert_eq!(self.file, rhs.file);
279        SourceRange {
280            file: self.file,
281            start: self.start.min(rhs.start),
282            end: self.end.max(rhs.end),
283        }
284    }
285}
286
287impl Eq for Endianness {}
288impl PartialEq for Endianness {
289    fn eq(&self, other: &Self) -> bool {
290        // Implement structural equality, leave out loc.
291        self.value == other.value
292    }
293}
294
295impl Eq for TagValue {}
296impl PartialEq for TagValue {
297    fn eq(&self, other: &Self) -> bool {
298        // Implement structural equality, leave out loc.
299        self.id == other.id && self.value == other.value
300    }
301}
302
303impl Eq for TagRange {}
304impl PartialEq for TagRange {
305    fn eq(&self, other: &Self) -> bool {
306        // Implement structural equality, leave out loc.
307        self.id == other.id && self.range == other.range && self.tags == other.tags
308    }
309}
310
311impl Eq for TagOther {}
312impl PartialEq for TagOther {
313    fn eq(&self, other: &Self) -> bool {
314        // Implement structual equality, leave out loc.
315        self.id == other.id
316    }
317}
318
319impl Tag {
320    pub fn id(&self) -> &str {
321        match self {
322            Tag::Value(TagValue { id, .. })
323            | Tag::Range(TagRange { id, .. })
324            | Tag::Other(TagOther { id, .. }) => id,
325        }
326    }
327
328    pub fn loc(&self) -> &SourceRange {
329        match self {
330            Tag::Value(TagValue { loc, .. })
331            | Tag::Range(TagRange { loc, .. })
332            | Tag::Other(TagOther { loc, .. }) => loc,
333        }
334    }
335
336    pub fn value(&self) -> Option<usize> {
337        match self {
338            Tag::Value(TagValue { value, .. }) => Some(*value),
339            Tag::Range(_) | Tag::Other(_) => None,
340        }
341    }
342}
343
344impl Eq for Constraint {}
345impl PartialEq for Constraint {
346    fn eq(&self, other: &Self) -> bool {
347        // Implement structural equality, leave out loc.
348        self.id == other.id && self.value == other.value && self.tag_id == other.tag_id
349    }
350}
351
352impl Eq for TestCase {}
353impl PartialEq for TestCase {
354    fn eq(&self, other: &Self) -> bool {
355        // Implement structural equality, leave out loc.
356        self.input == other.input
357    }
358}
359
360impl Eq for File {}
361impl PartialEq for File {
362    fn eq(&self, other: &Self) -> bool {
363        // Implement structural equality, leave out comments and PDL
364        // version information.
365        self.endianness == other.endianness && self.declarations == other.declarations
366    }
367}
368
369impl File {
370    pub fn new(file: FileId) -> File {
371        File {
372            version: "1,0".to_owned(),
373            comments: vec![],
374            // The endianness is mandatory, so this default value will
375            // be updated while parsing.
376            endianness: Endianness {
377                loc: SourceRange::default(),
378                value: EndiannessValue::LittleEndian,
379            },
380            declarations: vec![],
381            file,
382            max_key: 0,
383        }
384    }
385
386    /// Iterate over the children of the selected declaration.
387    /// /!\ This method is unsafe to use if the file contains cyclic
388    /// declarations, use with caution.
389    pub fn iter_children<'d>(&'d self, decl: &'d Decl) -> impl Iterator<Item = &'d Decl> {
390        self.declarations.iter().filter(|other_decl| other_decl.parent_id() == decl.id())
391    }
392}
393
394impl Eq for Decl {}
395impl PartialEq for Decl {
396    fn eq(&self, other: &Self) -> bool {
397        // Implement structural equality, leave out loc and key.
398        self.desc == other.desc
399    }
400}
401
402impl Decl {
403    pub fn id(&self) -> Option<&str> {
404        match &self.desc {
405            DeclDesc::Test { .. } => None,
406            DeclDesc::Checksum { id, .. }
407            | DeclDesc::CustomField { id, .. }
408            | DeclDesc::Enum { id, .. }
409            | DeclDesc::Packet { id, .. }
410            | DeclDesc::Struct { id, .. }
411            | DeclDesc::Group { id, .. } => Some(id),
412        }
413    }
414
415    pub fn parent_id(&self) -> Option<&str> {
416        match &self.desc {
417            DeclDesc::Packet { parent_id, .. } | DeclDesc::Struct { parent_id, .. } => {
418                parent_id.as_deref()
419            }
420            _ => None,
421        }
422    }
423
424    pub fn constraints(&self) -> std::slice::Iter<'_, Constraint> {
425        match &self.desc {
426            DeclDesc::Packet { constraints, .. } | DeclDesc::Struct { constraints, .. } => {
427                constraints.iter()
428            }
429            _ => [].iter(),
430        }
431    }
432
433    pub fn fields(&self) -> std::slice::Iter<'_, Field> {
434        match &self.desc {
435            DeclDesc::Packet { fields, .. }
436            | DeclDesc::Struct { fields, .. }
437            | DeclDesc::Group { fields, .. } => fields.iter(),
438            _ => [].iter(),
439        }
440    }
441
442    /// Return the reference to the payload or body field in a declaration,
443    /// if present.
444    pub fn payload(&self) -> Option<&Field> {
445        self.fields()
446            .find(|field| matches!(&field.desc, FieldDesc::Payload { .. } | FieldDesc::Body))
447    }
448
449    /// Return the reference to the payload or body size field in a declaration,
450    /// if present.
451    pub fn payload_size(&self) -> Option<&Field> {
452        self.fields().find(|field| match &field.desc {
453            FieldDesc::Size { field_id, .. } => field_id == "_payload_" || field_id == "_body_",
454            _ => false,
455        })
456    }
457
458    /// Return the reference to the array size or count field in a declaration,
459    /// if present.
460    pub fn array_size(&self, id: &str) -> Option<&Field> {
461        self.fields().find(|field| match &field.desc {
462            FieldDesc::Size { field_id, .. } | FieldDesc::Count { field_id, .. } => field_id == id,
463            _ => false,
464        })
465    }
466
467    pub fn kind(&self) -> &str {
468        match &self.desc {
469            DeclDesc::Checksum { .. } => "checksum",
470            DeclDesc::CustomField { .. } => "custom field",
471            DeclDesc::Enum { .. } => "enum",
472            DeclDesc::Packet { .. } => "packet",
473            DeclDesc::Struct { .. } => "struct",
474            DeclDesc::Group { .. } => "group",
475            DeclDesc::Test { .. } => "test",
476        }
477    }
478}
479
480impl Eq for Field {}
481impl PartialEq for Field {
482    fn eq(&self, other: &Self) -> bool {
483        // Implement structural equality, leave out loc and annot.
484        self.desc == other.desc
485    }
486}
487
488impl Field {
489    pub fn id(&self) -> Option<&str> {
490        match &self.desc {
491            FieldDesc::Checksum { .. }
492            | FieldDesc::Padding { .. }
493            | FieldDesc::Size { .. }
494            | FieldDesc::Count { .. }
495            | FieldDesc::ElementSize { .. }
496            | FieldDesc::Body
497            | FieldDesc::Payload { .. }
498            | FieldDesc::FixedScalar { .. }
499            | FieldDesc::FixedEnum { .. }
500            | FieldDesc::Reserved { .. }
501            | FieldDesc::Group { .. } => None,
502            FieldDesc::Array { id, .. }
503            | FieldDesc::Scalar { id, .. }
504            | FieldDesc::Flag { id, .. }
505            | FieldDesc::Typedef { id, .. } => Some(id),
506        }
507    }
508
509    pub fn kind(&self) -> &str {
510        match &self.desc {
511            FieldDesc::Checksum { .. } => "payload",
512            FieldDesc::Padding { .. } => "padding",
513            FieldDesc::Size { .. } => "size",
514            FieldDesc::Count { .. } => "count",
515            FieldDesc::ElementSize { .. } => "elementsize",
516            FieldDesc::Body => "body",
517            FieldDesc::Payload { .. } => "payload",
518            FieldDesc::FixedScalar { .. } | FieldDesc::FixedEnum { .. } => "fixed",
519            FieldDesc::Reserved { .. } => "reserved",
520            FieldDesc::Group { .. } => "group",
521            FieldDesc::Array { .. } => "array",
522            FieldDesc::Scalar { .. } => "scalar",
523            FieldDesc::Flag { .. } => "scalar",
524            FieldDesc::Typedef { .. } => "typedef",
525        }
526    }
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    #[test]
534    fn source_location_new() {
535        let line_starts = &[0, 20, 80, 120, 150];
536        assert_eq!(
537            SourceLocation::new(0, line_starts),
538            SourceLocation { offset: 0, line: 0, column: 0 }
539        );
540        assert_eq!(
541            SourceLocation::new(10, line_starts),
542            SourceLocation { offset: 10, line: 0, column: 10 }
543        );
544        assert_eq!(
545            SourceLocation::new(50, line_starts),
546            SourceLocation { offset: 50, line: 1, column: 30 }
547        );
548        assert_eq!(
549            SourceLocation::new(100, line_starts),
550            SourceLocation { offset: 100, line: 2, column: 20 }
551        );
552        assert_eq!(
553            SourceLocation::new(1000, line_starts),
554            SourceLocation { offset: 1000, line: 4, column: 850 }
555        );
556    }
557
558    #[test]
559    fn source_location_new_no_crash_with_empty_line_starts() {
560        let loc = SourceLocation::new(100, &[]);
561        assert_eq!(loc, SourceLocation { offset: 100, line: 0, column: 100 });
562    }
563}