Skip to main content

mib_rs/
syntax.rs

1//! Syntax kinds and lexical spelling metadata.
2//!
3//! [`SyntaxKind`] is the shared kind vocabulary for lexer tokens and future
4//! lossless syntax-tree nodes. Its inventory, spellings, keyword aliases,
5//! categories, display names, and libsmi names are declared together so lexer,
6//! parser, and tooling APIs cannot drift apart.
7//!
8//! [`SyntaxKind::Whitespace`], [`SyntaxKind::OpaqueText`],
9//! [`SyntaxKind::SourceFile`], and [`SyntaxKind::Error`] form the first
10//! lossless-CST vocabulary. The lossless lexer emits the token kinds; tree
11//! construction is a separate stage.
12
13use std::fmt;
14
15/// Broad classification of a [`SyntaxKind`].
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
17pub enum SyntaxCategory {
18    /// Lexer control or recovery token.
19    Special,
20    /// Whitespace or comment trivia.
21    Trivia,
22    /// Uppercase or lowercase identifier.
23    Identifier,
24    /// Numeric or string literal.
25    Literal,
26    /// Fixed punctuation or operator.
27    Punctuation,
28    /// Recognized SMI or ASN.1 keyword.
29    Keyword,
30    /// Lossless syntax-tree node.
31    Node,
32}
33
34/// More specific classification for keyword kinds.
35#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
36pub enum KeywordCategory {
37    /// Keywords framing modules and ASN.1 structures.
38    Structural,
39    /// Keywords introducing macro clauses.
40    Clause,
41    /// SMI macro invocation keywords.
42    Macro,
43    /// Built-in SMI type keywords.
44    Type,
45    /// ASN.1 tag keywords.
46    Tag,
47    /// Status and access value keywords.
48    StatusAccess,
49}
50
51macro_rules! define_syntax_kinds {
52    (
53        special { $( $special:ident => ($special_libsmi:literal, $special_display:literal); )* }
54        trivia { $( $trivia:ident => ($trivia_libsmi:literal, $trivia_display:literal); )* }
55        identifiers { $( $identifier:ident => ($identifier_libsmi:literal, $identifier_display:literal); )* }
56        literals { $( $literal:ident => ($literal_libsmi:literal, $literal_display:literal); )* }
57        punctuation { $( $punctuation:ident => ($byte:literal, $spelling:literal, $punctuation_libsmi:literal, $punctuation_display:literal); )* }
58        operators { $( $operator:ident => ($operator_spelling:literal, $operator_libsmi:literal, $operator_display:literal); )* }
59        keywords { $( $keyword:ident => ($keyword_category:ident, $canonical:literal, [$($alias:literal),* $(,)?], $keyword_libsmi:literal); )* }
60        nodes { $( $node:ident => ($node_libsmi:literal, $node_display:literal); )* }
61        forbidden { $( $forbidden:literal ),* $(,)? }
62    ) => {
63        /// Kind of a lexical token or lossless syntax-tree node.
64        ///
65        /// Values are stable within a crate release and use a 16-bit
66        /// representation so the vocabulary can grow with CST node kinds.
67        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68        #[repr(u16)]
69        pub enum SyntaxKind {
70            $( #[doc = concat!("`", $special_display, "`")] $special, )*
71            $( #[doc = $trivia_display] $trivia, )*
72            $( #[doc = $identifier_display] $identifier, )*
73            $( #[doc = $literal_display] $literal, )*
74            $( #[doc = $punctuation_display] $punctuation, )*
75            $( #[doc = $operator_display] $operator, )*
76            $( #[doc = $canonical] $keyword, )*
77            $( #[doc = $node_display] $node, )*
78        }
79
80        impl SyntaxKind {
81            /// Every declared token and node kind in discriminant order.
82            pub const ALL: &'static [Self] = &[
83                $( Self::$special, )*
84                $( Self::$trivia, )*
85                $( Self::$identifier, )*
86                $( Self::$literal, )*
87                $( Self::$punctuation, )*
88                $( Self::$operator, )*
89                $( Self::$keyword, )*
90                $( Self::$node, )*
91            ];
92
93            /// Return the kind for a raw discriminant, if it is declared.
94            pub const fn from_raw(raw: u16) -> Option<Self> {
95                match raw {
96                    $( value if value == Self::$special as u16 => Some(Self::$special), )*
97                    $( value if value == Self::$trivia as u16 => Some(Self::$trivia), )*
98                    $( value if value == Self::$identifier as u16 => Some(Self::$identifier), )*
99                    $( value if value == Self::$literal as u16 => Some(Self::$literal), )*
100                    $( value if value == Self::$punctuation as u16 => Some(Self::$punctuation), )*
101                    $( value if value == Self::$operator as u16 => Some(Self::$operator), )*
102                    $( value if value == Self::$keyword as u16 => Some(Self::$keyword), )*
103                    $( value if value == Self::$node as u16 => Some(Self::$node), )*
104                    _ => None,
105                }
106            }
107
108            /// Return the 16-bit discriminant for this kind.
109            pub const fn to_raw(self) -> u16 {
110                self as u16
111            }
112
113            /// Return this kind's broad syntax category.
114            pub const fn category(self) -> SyntaxCategory {
115                match self {
116                    $( Self::$special => SyntaxCategory::Special, )*
117                    $( Self::$trivia => SyntaxCategory::Trivia, )*
118                    $( Self::$identifier => SyntaxCategory::Identifier, )*
119                    $( Self::$literal => SyntaxCategory::Literal, )*
120                    $( Self::$punctuation => SyntaxCategory::Punctuation, )*
121                    $( Self::$operator => SyntaxCategory::Punctuation, )*
122                    $( Self::$keyword => SyntaxCategory::Keyword, )*
123                    $( Self::$node => SyntaxCategory::Node, )*
124                }
125            }
126
127            /// Return this kind's keyword category, or `None` for non-keywords.
128            pub const fn keyword_category(self) -> Option<KeywordCategory> {
129                match self {
130                    $( Self::$keyword => Some(KeywordCategory::$keyword_category), )*
131                    _ => None,
132                }
133            }
134
135            /// Return whether this kind is emitted by the lexer.
136            pub const fn is_token(self) -> bool {
137                !self.is_node()
138            }
139
140            /// Return whether this kind represents a syntax-tree node.
141            pub const fn is_node(self) -> bool {
142                matches!(self.category(), SyntaxCategory::Node)
143            }
144
145            /// Return whether this kind is whitespace or comment trivia.
146            pub const fn is_trivia(self) -> bool {
147                matches!(self.category(), SyntaxCategory::Trivia)
148            }
149
150            /// Return whether this kind is an uppercase or lowercase identifier.
151            pub const fn is_identifier(self) -> bool {
152                matches!(self.category(), SyntaxCategory::Identifier)
153            }
154
155            /// Return whether this kind is a numeric or string literal.
156            pub const fn is_literal(self) -> bool {
157                matches!(self.category(), SyntaxCategory::Literal)
158            }
159
160            /// Return whether this kind is fixed punctuation or an operator.
161            pub const fn is_punctuation(self) -> bool {
162                matches!(self.category(), SyntaxCategory::Punctuation)
163            }
164
165            /// Return whether this kind is any recognized keyword.
166            pub const fn is_keyword(self) -> bool {
167                matches!(self.category(), SyntaxCategory::Keyword)
168            }
169
170            /// Return whether this is a structural keyword.
171            pub const fn is_structural_keyword(self) -> bool {
172                matches!(self.keyword_category(), Some(KeywordCategory::Structural))
173            }
174
175            /// Return whether this is a clause keyword.
176            pub const fn is_clause_keyword(self) -> bool {
177                matches!(self.keyword_category(), Some(KeywordCategory::Clause))
178            }
179
180            /// Return whether this is an SMI macro invocation keyword.
181            pub const fn is_macro_keyword(self) -> bool {
182                matches!(self.keyword_category(), Some(KeywordCategory::Macro))
183            }
184
185            /// Return whether this is a built-in SMI type keyword.
186            pub const fn is_type_keyword(self) -> bool {
187                matches!(self.keyword_category(), Some(KeywordCategory::Type))
188            }
189
190            /// Return whether this is an ASN.1 tag keyword.
191            pub const fn is_tag_keyword(self) -> bool {
192                matches!(self.keyword_category(), Some(KeywordCategory::Tag))
193            }
194
195            /// Return whether this is a status or access value keyword.
196            pub const fn is_status_access_keyword(self) -> bool {
197                matches!(self.keyword_category(), Some(KeywordCategory::StatusAccess))
198            }
199
200            /// Return fixed source text for punctuation and canonical keywords.
201            pub const fn fixed_text(self) -> Option<&'static str> {
202                match self {
203                    $( Self::$punctuation => Some($spelling), )*
204                    $( Self::$operator => Some($operator_spelling), )*
205                    $( Self::$keyword => Some($canonical), )*
206                    _ => None,
207                }
208            }
209
210            /// Return every accepted spelling for a keyword kind.
211            pub const fn keyword_spellings(self) -> &'static [&'static str] {
212                match self {
213                    $( Self::$keyword => &[$canonical, $($alias),*], )*
214                    _ => &[],
215                }
216            }
217
218            /// Look up a recognized keyword using case-sensitive source spelling.
219            pub fn from_keyword(text: &str) -> Option<Self> {
220                match text {
221                    $( $canonical $(| $alias)* => Some(Self::$keyword), )*
222                    _ => None,
223                }
224            }
225
226            /// Look up fixed punctuation or a canonical keyword spelling.
227            pub fn from_fixed_text(text: &str) -> Option<Self> {
228                match text {
229                    $( $spelling => Some(Self::$punctuation), )*
230                    $( $operator_spelling => Some(Self::$operator), )*
231                    $( $canonical => Some(Self::$keyword), )*
232                    _ => None,
233                }
234            }
235
236            /// Look up a single-byte punctuation kind.
237            pub const fn from_punctuation_byte(byte: u8) -> Option<Self> {
238                match byte {
239                    $( $byte => Some(Self::$punctuation), )*
240                    _ => None,
241                }
242            }
243
244            /// Return a human-readable name suitable for parser diagnostics.
245            pub const fn display_name(self) -> &'static str {
246                match self {
247                    $( Self::$special => $special_display, )*
248                    $( Self::$trivia => $trivia_display, )*
249                    $( Self::$identifier => $identifier_display, )*
250                    $( Self::$literal => $literal_display, )*
251                    $( Self::$punctuation => $punctuation_display, )*
252                    $( Self::$operator => $operator_display, )*
253                    $( Self::$keyword => $keyword_libsmi, )*
254                    $( Self::$node => $node_display, )*
255                }
256            }
257
258            /// Return the libsmi-compatible uppercase kind name.
259            pub const fn libsmi_name(self) -> &'static str {
260                match self {
261                    $( Self::$special => $special_libsmi, )*
262                    $( Self::$trivia => $trivia_libsmi, )*
263                    $( Self::$identifier => $identifier_libsmi, )*
264                    $( Self::$literal => $literal_libsmi, )*
265                    $( Self::$punctuation => $punctuation_libsmi, )*
266                    $( Self::$operator => $operator_libsmi, )*
267                    $( Self::$keyword => $keyword_libsmi, )*
268                    $( Self::$node => $node_libsmi, )*
269                }
270            }
271        }
272
273        /// Reserved ASN.1 words rejected when used as MIB identifiers.
274        pub const FORBIDDEN_KEYWORDS: &[&str] = &[$($forbidden),*];
275
276        /// Look up a recognized keyword using case-sensitive source spelling.
277        pub fn lookup_keyword(text: &str) -> Option<SyntaxKind> {
278            SyntaxKind::from_keyword(text)
279        }
280
281        /// Return whether text is a reserved ASN.1 keyword forbidden as a MIB identifier.
282        pub fn is_forbidden_keyword(text: &str) -> bool {
283            matches!(text, $($forbidden)|*)
284        }
285    };
286}
287
288define_syntax_kinds! {
289    special {
290        ErrorToken => ("ERROR", "<error>");
291        EofToken => ("EOF", "end of file");
292        ForbiddenKeyword => ("FORBIDDEN_KEYWORD", "reserved keyword");
293        OpaqueText => ("OPAQUE_TEXT", "opaque text");
294    }
295    trivia {
296        Whitespace => ("WHITESPACE", "whitespace");
297        Comment => ("COMMENT", "comment");
298    }
299    identifiers {
300        UppercaseIdent => ("UPPERCASE_IDENTIFIER", "identifier");
301        LowercaseIdent => ("LOWERCASE_IDENTIFIER", "identifier");
302    }
303    literals {
304        Number => ("NUMBER", "number");
305        NegativeNumber => ("NEGATIVENUMBER", "negative number");
306        QuotedString => ("QUOTED_STRING", "quoted string");
307        HexString => ("HEX_STRING", "hex string");
308        BinString => ("BIN_STRING", "binary string");
309    }
310    punctuation {
311        LBracket => (b'[', "[", "LBRACKET", "'['");
312        RBracket => (b']', "]", "RBRACKET", "']'");
313        LBrace => (b'{', "{", "LBRACE", "'{'");
314        RBrace => (b'}', "}", "RBRACE", "'}'");
315        LParen => (b'(', "(", "LPAREN", "'('");
316        RParen => (b')', ")", "RPAREN", "')'");
317        Colon => (b':', ":", "COLON", "':'");
318        Semicolon => (b';', ";", "SEMICOLON", "';'");
319        Comma => (b',', ",", "COMMA", "','");
320        Dot => (b'.', ".", "DOT", "'.'");
321        Pipe => (b'|', "|", "PIPE", "'|'");
322        Minus => (b'-', "-", "MINUS", "'-'");
323    }
324    operators {
325        DotDot => ("..", "DOT_DOT", "'..'");
326        ColonColonEqual => ("::=", "COLON_COLON_EQUAL", "'::='");
327    }
328    keywords {
329        KwDefinitions => (Structural, "DEFINITIONS", [], "DEFINITIONS");
330        KwBegin => (Structural, "BEGIN", [], "BEGIN");
331        KwEnd => (Structural, "END", [], "END");
332        KwImports => (Structural, "IMPORTS", [], "IMPORTS");
333        KwExports => (Structural, "EXPORTS", [], "EXPORTS");
334        KwFrom => (Structural, "FROM", [], "FROM");
335        KwObject => (Structural, "OBJECT", [], "OBJECT");
336        KwIdentifier => (Structural, "IDENTIFIER", [], "IDENTIFIER");
337        KwSequence => (Structural, "SEQUENCE", [], "SEQUENCE");
338        KwOf => (Structural, "OF", [], "OF");
339        KwChoice => (Structural, "CHOICE", [], "CHOICE");
340        KwMacro => (Structural, "MACRO", [], "MACRO");
341
342        KwSyntax => (Clause, "SYNTAX", [], "SYNTAX");
343        KwMaxAccess => (Clause, "MAX-ACCESS", [], "MAX_ACCESS");
344        KwMinAccess => (Clause, "MIN-ACCESS", [], "MIN_ACCESS");
345        KwAccess => (Clause, "ACCESS", [], "ACCESS");
346        KwStatus => (Clause, "STATUS", [], "STATUS");
347        KwDescription => (Clause, "DESCRIPTION", [], "DESCRIPTION");
348        KwReference => (Clause, "REFERENCE", [], "REFERENCE");
349        KwIndex => (Clause, "INDEX", [], "INDEX");
350        KwDefval => (Clause, "DEFVAL", [], "DEFVAL");
351        KwAugments => (Clause, "AUGMENTS", [], "AUGMENTS");
352        KwUnits => (Clause, "UNITS", [], "UNITS");
353        KwDisplayHint => (Clause, "DISPLAY-HINT", [], "DISPLAY_HINT");
354        KwObjects => (Clause, "OBJECTS", [], "OBJECTS");
355        KwNotifications => (Clause, "NOTIFICATIONS", [], "NOTIFICATIONS");
356        KwModule => (Clause, "MODULE", [], "MODULE");
357        KwMandatoryGroups => (Clause, "MANDATORY-GROUPS", [], "MANDATORY_GROUPS");
358        KwGroup => (Clause, "GROUP", [], "GROUP");
359        KwWriteSyntax => (Clause, "WRITE-SYNTAX", [], "WRITE_SYNTAX");
360        KwProductRelease => (Clause, "PRODUCT-RELEASE", [], "PRODUCT_RELEASE");
361        KwSupports => (Clause, "SUPPORTS", [], "SUPPORTS");
362        KwIncludes => (Clause, "INCLUDES", [], "INCLUDES");
363        KwVariation => (Clause, "VARIATION", [], "VARIATION");
364        KwCreationRequires => (Clause, "CREATION-REQUIRES", [], "CREATION_REQUIRES");
365        KwRevision => (Clause, "REVISION", [], "REVISION");
366        KwLastUpdated => (Clause, "LAST-UPDATED", [], "LAST_UPDATED");
367        KwOrganization => (Clause, "ORGANIZATION", [], "ORGANIZATION");
368        KwContactInfo => (Clause, "CONTACT-INFO", [], "CONTACT_INFO");
369        KwImplied => (Clause, "IMPLIED", [], "IMPLIED");
370        KwSize => (Clause, "SIZE", [], "SIZE");
371        KwEnterprise => (Clause, "ENTERPRISE", [], "ENTERPRISE");
372        KwVariables => (Clause, "VARIABLES", [], "VARIABLES");
373
374        KwModuleIdentity => (Macro, "MODULE-IDENTITY", [], "MODULE_IDENTITY");
375        KwModuleCompliance => (Macro, "MODULE-COMPLIANCE", [], "MODULE_COMPLIANCE");
376        KwObjectGroup => (Macro, "OBJECT-GROUP", [], "OBJECT_GROUP");
377        KwNotificationGroup => (Macro, "NOTIFICATION-GROUP", [], "NOTIFICATION_GROUP");
378        KwAgentCapabilities => (Macro, "AGENT-CAPABILITIES", [], "AGENT_CAPABILITIES");
379        KwObjectType => (Macro, "OBJECT-TYPE", [], "OBJECT_TYPE");
380        KwObjectIdentity => (Macro, "OBJECT-IDENTITY", [], "OBJECT_IDENTITY");
381        KwNotificationType => (Macro, "NOTIFICATION-TYPE", [], "NOTIFICATION_TYPE");
382        KwTextualConvention => (Macro, "TEXTUAL-CONVENTION", [], "TEXTUAL_CONVENTION");
383        KwTrapType => (Macro, "TRAP-TYPE", [], "TRAP_TYPE");
384
385        KwInteger => (Type, "INTEGER", ["Integer"], "INTEGER");
386        KwUnsigned32 => (Type, "Unsigned32", [], "UNSIGNED32");
387        KwCounter32 => (Type, "Counter32", [], "COUNTER32");
388        KwCounter64 => (Type, "Counter64", [], "COUNTER64");
389        KwGauge32 => (Type, "Gauge32", [], "GAUGE32");
390        KwIpAddress => (Type, "IpAddress", [], "IPADDRESS");
391        KwOpaque => (Type, "Opaque", [], "OPAQUE");
392        KwTimeTicks => (Type, "TimeTicks", [], "TIMETICKS");
393        KwBits => (Type, "BITS", [], "BITS");
394        KwOctet => (Type, "OCTET", [], "OCTET");
395        KwString => (Type, "STRING", [], "STRING");
396        KwCounter => (Type, "Counter", [], "COUNTER");
397        KwGauge => (Type, "Gauge", [], "GAUGE");
398        KwNetworkAddress => (Type, "NetworkAddress", [], "NETWORKADDRESS");
399
400        KwApplication => (Tag, "APPLICATION", [], "APPLICATION");
401        KwImplicit => (Tag, "IMPLICIT", [], "IMPLICIT");
402        KwUniversal => (Tag, "UNIVERSAL", [], "UNIVERSAL");
403
404        KwCurrent => (StatusAccess, "current", [], "CURRENT");
405        KwDeprecated => (StatusAccess, "deprecated", [], "DEPRECATED");
406        KwObsolete => (StatusAccess, "obsolete", [], "OBSOLETE");
407        KwMandatory => (StatusAccess, "mandatory", [], "MANDATORY");
408        KwOptional => (StatusAccess, "optional", [], "OPTIONAL");
409        KwReadOnly => (StatusAccess, "read-only", [], "READ_ONLY");
410        KwReadWrite => (StatusAccess, "read-write", [], "READ_WRITE");
411        KwReadCreate => (StatusAccess, "read-create", [], "READ_CREATE");
412        KwWriteOnly => (StatusAccess, "write-only", [], "WRITE_ONLY");
413        KwNotAccessible => (StatusAccess, "not-accessible", [], "NOT_ACCESSIBLE");
414        KwAccessibleForNotify => (StatusAccess, "accessible-for-notify", [], "ACCESSIBLE_FOR_NOTIFY");
415        KwNotImplemented => (StatusAccess, "not-implemented", [], "NOT_IMPLEMENTED");
416    }
417    nodes {
418        SourceFile => ("SOURCE_FILE", "source file");
419        Module => ("MODULE", "module");
420        ModuleHeader => ("MODULE_HEADER", "module header");
421        Imports => ("IMPORTS_NODE", "imports");
422        ImportGroup => ("IMPORT_GROUP", "import group");
423        UnparsedRegion => ("UNPARSED_REGION", "unparsed region");
424
425        ValueAssignment => ("VALUE_ASSIGNMENT", "value assignment");
426        TypeAssignment => ("TYPE_ASSIGNMENT", "type assignment");
427        TextualConventionDefinition => ("TEXTUAL_CONVENTION_DEFINITION", "TEXTUAL-CONVENTION definition");
428        ObjectTypeDefinition => ("OBJECT_TYPE_DEFINITION", "OBJECT-TYPE definition");
429        ModuleIdentityDefinition => ("MODULE_IDENTITY_DEFINITION", "MODULE-IDENTITY definition");
430        ObjectIdentityDefinition => ("OBJECT_IDENTITY_DEFINITION", "OBJECT-IDENTITY definition");
431        NotificationTypeDefinition => ("NOTIFICATION_TYPE_DEFINITION", "NOTIFICATION-TYPE definition");
432        TrapTypeDefinition => ("TRAP_TYPE_DEFINITION", "TRAP-TYPE definition");
433        MacroDefinition => ("MACRO_DEFINITION", "MACRO definition");
434        ObjectGroupDefinition => ("OBJECT_GROUP_DEFINITION", "OBJECT-GROUP definition");
435        NotificationGroupDefinition => ("NOTIFICATION_GROUP_DEFINITION", "NOTIFICATION-GROUP definition");
436        ModuleComplianceDefinition => ("MODULE_COMPLIANCE_DEFINITION", "MODULE-COMPLIANCE definition");
437        AgentCapabilitiesDefinition => ("AGENT_CAPABILITIES_DEFINITION", "AGENT-CAPABILITIES definition");
438
439        ComplianceModule => ("COMPLIANCE_MODULE", "MODULE compliance section");
440        MandatoryGroupsClause => ("MANDATORY_GROUPS_CLAUSE", "MANDATORY-GROUPS clause");
441        ComplianceGroup => ("COMPLIANCE_GROUP", "GROUP compliance refinement");
442        ComplianceObject => ("COMPLIANCE_OBJECT", "OBJECT compliance refinement");
443        WriteSyntaxClause => ("WRITE_SYNTAX_CLAUSE", "WRITE-SYNTAX clause");
444        SupportsModule => ("SUPPORTS_MODULE", "SUPPORTS capability section");
445        IncludesClause => ("INCLUDES_CLAUSE", "INCLUDES clause");
446        VariationClause => ("VARIATION_CLAUSE", "VARIATION clause");
447        CreationRequiresClause => ("CREATION_REQUIRES_CLAUSE", "CREATION-REQUIRES clause");
448
449        SyntaxClause => ("SYNTAX_CLAUSE", "SYNTAX clause");
450        AccessClause => ("ACCESS_CLAUSE", "access clause");
451        StatusClause => ("STATUS_CLAUSE", "STATUS clause");
452        DescriptionClause => ("DESCRIPTION_CLAUSE", "DESCRIPTION clause");
453        ReferenceClause => ("REFERENCE_CLAUSE", "REFERENCE clause");
454        UnitsClause => ("UNITS_CLAUSE", "UNITS clause");
455        DisplayHintClause => ("DISPLAY_HINT_CLAUSE", "DISPLAY-HINT clause");
456        IndexClause => ("INDEX_CLAUSE", "INDEX clause");
457        IndexItem => ("INDEX_ITEM", "index item");
458        AugmentsClause => ("AUGMENTS_CLAUSE", "AUGMENTS clause");
459        DefvalClause => ("DEFVAL_CLAUSE", "DEFVAL clause");
460        DefvalContent => ("DEFVAL_CONTENT", "DEFVAL content");
461        ObjectsClause => ("OBJECTS_CLAUSE", "OBJECTS clause");
462        NotificationsClause => ("NOTIFICATIONS_CLAUSE", "NOTIFICATIONS clause");
463        RevisionClause => ("REVISION_CLAUSE", "REVISION clause");
464        LastUpdatedClause => ("LAST_UPDATED_CLAUSE", "LAST-UPDATED clause");
465        OrganizationClause => ("ORGANIZATION_CLAUSE", "ORGANIZATION clause");
466        ContactInfoClause => ("CONTACT_INFO_CLAUSE", "CONTACT-INFO clause");
467        EnterpriseClause => ("ENTERPRISE_CLAUSE", "ENTERPRISE clause");
468        VariablesClause => ("VARIABLES_CLAUSE", "VARIABLES clause");
469        ProductReleaseClause => ("PRODUCT_RELEASE_CLAUSE", "PRODUCT-RELEASE clause");
470
471        OidAssignment => ("OID_ASSIGNMENT", "OID assignment");
472        OidComponent => ("OID_COMPONENT", "OID component");
473
474        TypeRefSyntax => ("TYPE_REF_SYNTAX", "type reference syntax");
475        IntegerEnumSyntax => ("INTEGER_ENUM_SYNTAX", "integer enumeration syntax");
476        BitsSyntax => ("BITS_SYNTAX", "BITS syntax");
477        ConstrainedSyntax => ("CONSTRAINED_SYNTAX", "constrained type syntax");
478        Constraint => ("CONSTRAINT", "constraint");
479        Range => ("RANGE", "constraint range");
480        NamedNumber => ("NAMED_NUMBER", "named number");
481        SequenceOfSyntax => ("SEQUENCE_OF_SYNTAX", "SEQUENCE OF syntax");
482        SequenceSyntax => ("SEQUENCE_SYNTAX", "SEQUENCE syntax");
483        SequenceField => ("SEQUENCE_FIELD", "SEQUENCE or CHOICE field");
484        ChoiceSyntax => ("CHOICE_SYNTAX", "CHOICE syntax");
485        TaggedSyntax => ("TAGGED_SYNTAX", "tagged syntax");
486        OctetStringSyntax => ("OCTET_STRING_SYNTAX", "OCTET STRING syntax");
487        ObjectIdentifierSyntax => ("OBJECT_IDENTIFIER_SYNTAX", "OBJECT IDENTIFIER syntax");
488        Error => ("ERROR_NODE", "error node");
489    }
490    forbidden {
491        "ABSENT", "ANY", "BIT", "BOOLEAN", "BY", "COMPONENT", "COMPONENTS",
492        "DEFAULT", "DEFINED", "ENUMERATED", "EXPLICIT", "EXTERNAL", "FALSE",
493        "MAX", "MIN", "MINUS-INFINITY", "NULL", "OPTIONAL", "PLUS-INFINITY",
494        "PRESENT", "PRIVATE", "REAL", "SET", "TAGS", "TRUE", "WITH",
495    }
496}
497
498impl fmt::Display for SyntaxKind {
499    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500        f.write_str(self.libsmi_name())
501    }
502}
503
504#[cfg(test)]
505mod tests {
506    use std::collections::{HashMap, HashSet};
507
508    use super::*;
509
510    #[test]
511    fn raw_discriminants_round_trip_exhaustively() {
512        for (raw, kind) in SyntaxKind::ALL.iter().copied().enumerate() {
513            assert_eq!(usize::from(kind.to_raw()), raw);
514            assert_eq!(SyntaxKind::from_raw(kind.to_raw()), Some(kind));
515        }
516        assert_eq!(
517            SyntaxKind::from_raw(u16::try_from(SyntaxKind::ALL.len()).unwrap()),
518            None
519        );
520        assert_eq!(SyntaxKind::from_raw(u16::MAX), None);
521    }
522
523    #[test]
524    fn inventory_and_categories_are_exhaustive() {
525        assert_eq!(SyntaxKind::ALL.len(), 175);
526        assert_eq!(
527            SyntaxKind::ALL.iter().filter(|kind| kind.is_node()).count(),
528            66
529        );
530        assert_eq!(
531            SyntaxKind::ALL
532                .iter()
533                .filter(|kind| kind.is_token())
534                .count(),
535            109
536        );
537        assert_eq!(
538            SyntaxKind::ALL
539                .iter()
540                .filter(|kind| kind.is_trivia())
541                .count(),
542            2
543        );
544        assert_eq!(
545            SyntaxKind::ALL
546                .iter()
547                .filter(|kind| kind.is_identifier())
548                .count(),
549            2
550        );
551        assert_eq!(
552            SyntaxKind::ALL
553                .iter()
554                .filter(|kind| kind.is_literal())
555                .count(),
556            5
557        );
558        assert_eq!(
559            SyntaxKind::ALL
560                .iter()
561                .filter(|kind| kind.is_punctuation())
562                .count(),
563            14
564        );
565        assert_eq!(
566            SyntaxKind::ALL
567                .iter()
568                .filter(|kind| kind.is_keyword())
569                .count(),
570            82
571        );
572        assert_eq!(SyntaxKind::SourceFile.category(), SyntaxCategory::Node);
573        assert_eq!(SyntaxKind::Error.category(), SyntaxCategory::Node);
574        assert!(SyntaxKind::Whitespace.is_trivia());
575        assert!(SyntaxKind::Comment.is_trivia());
576        assert!(!SyntaxKind::OpaqueText.is_trivia());
577    }
578
579    #[test]
580    fn keyword_and_fixed_spelling_round_trips_are_exhaustive() {
581        for kind in SyntaxKind::ALL.iter().copied() {
582            for spelling in kind.keyword_spellings() {
583                assert_eq!(SyntaxKind::from_keyword(spelling), Some(kind));
584            }
585            if let Some(text) = kind.fixed_text() {
586                assert_eq!(SyntaxKind::from_fixed_text(text), Some(kind));
587            }
588        }
589        assert_eq!(SyntaxKind::from_keyword("integer"), None);
590        assert_eq!(SyntaxKind::from_fixed_text("Integer"), None);
591    }
592
593    #[test]
594    fn single_byte_punctuation_round_trips_exhaustively() {
595        for kind in SyntaxKind::ALL
596            .iter()
597            .copied()
598            .filter(|kind| kind.is_punctuation())
599        {
600            let spelling = kind.fixed_text().unwrap();
601            if let [byte] = spelling.as_bytes() {
602                assert_eq!(SyntaxKind::from_punctuation_byte(*byte), Some(kind));
603            } else {
604                assert!(matches!(
605                    kind,
606                    SyntaxKind::DotDot | SyntaxKind::ColonColonEqual
607                ));
608            }
609        }
610    }
611
612    #[test]
613    fn declared_spellings_are_unique() {
614        let mut keywords = HashMap::new();
615        let mut fixed = HashMap::new();
616        for kind in SyntaxKind::ALL.iter().copied() {
617            for spelling in kind.keyword_spellings() {
618                assert_eq!(
619                    keywords.insert(*spelling, kind),
620                    None,
621                    "duplicate {spelling}"
622                );
623            }
624            if let Some(spelling) = kind.fixed_text() {
625                assert_eq!(fixed.insert(spelling, kind), None, "duplicate {spelling}");
626            }
627        }
628        let forbidden: HashSet<_> = FORBIDDEN_KEYWORDS.iter().copied().collect();
629        assert_eq!(forbidden.len(), FORBIDDEN_KEYWORDS.len());
630        assert!(forbidden.is_disjoint(&keywords.keys().copied().collect()));
631    }
632
633    #[test]
634    fn forbidden_keyword_lookup_matches_the_declared_inventory() {
635        for keyword in FORBIDDEN_KEYWORDS {
636            assert!(is_forbidden_keyword(keyword));
637            assert_eq!(SyntaxKind::from_keyword(keyword), None);
638        }
639        assert!(!is_forbidden_keyword("optional"));
640        assert_eq!(
641            SyntaxKind::from_keyword("optional"),
642            Some(SyntaxKind::KwOptional)
643        );
644    }
645
646    #[test]
647    fn keyword_subcategories_cover_exactly_all_keywords() {
648        let counts = SyntaxKind::ALL.iter().copied().fold(
649            HashMap::<KeywordCategory, usize>::new(),
650            |mut counts, kind| {
651                if let Some(category) = kind.keyword_category() {
652                    *counts.entry(category).or_default() += 1;
653                }
654                counts
655            },
656        );
657        assert_eq!(counts.values().sum::<usize>(), 82);
658        assert_eq!(counts[&KeywordCategory::Structural], 12);
659        assert_eq!(counts[&KeywordCategory::Clause], 31);
660        assert_eq!(counts[&KeywordCategory::Macro], 10);
661        assert_eq!(counts[&KeywordCategory::Type], 14);
662        assert_eq!(counts[&KeywordCategory::Tag], 3);
663        assert_eq!(counts[&KeywordCategory::StatusAccess], 12);
664    }
665
666    #[test]
667    fn legacy_display_and_libsmi_names_are_preserved() {
668        assert_eq!(SyntaxKind::EofToken.display_name(), "end of file");
669        assert_eq!(SyntaxKind::LBrace.display_name(), "'{'");
670        assert_eq!(SyntaxKind::KwObjectType.display_name(), "OBJECT_TYPE");
671        assert_eq!(SyntaxKind::KwObjectType.libsmi_name(), "OBJECT_TYPE");
672        assert_eq!(SyntaxKind::NegativeNumber.libsmi_name(), "NEGATIVENUMBER");
673        assert_eq!(SyntaxKind::ColonColonEqual.fixed_text(), Some("::="));
674    }
675}