%start Yang
%title "Yang grammar"
%comment "YANG grammar by `parol`"
%line_comment "//"
// KNOWN LIMITATION (parol 3.x / scnr): parol generates the matcher
// `/\*([^*]|\*[^/])*\*/` from this directive, which fails to match block
// comments ending in `**/` (an even number of trailing stars, e.g.
// `/*** text **/`). The end delimiter can't be widened (parol caps it at 2
// chars) and there is no user-defined skip-terminal escape hatch, so such
// comments are unsupported. Only one in-repo reference module hits this
// (yang/ietf-routing-types@2017-12-04.yang). See the Identifier note below
// for the related scnr regex-dialect difference.
%block_comment "/\*" "\*/"
// Scanner switching is token-based in parol 4.x (parser-based %push/%pop/%sc
// in productions was removed in 4.0). The transitions below replicate the old
// behavior: a statement keyword switches the scanner for its argument, and the
// terminator (`;` or `{`) switches back to INITIAL. These %on directives at
// top level apply to the INITIAL (default) scanner.
// Identifier/identifier-ref/key/refine args read in the Keyword scanner so
// keyword-like names lex as Identifier.
%on KwModule, KwSubmodule, KwRpc, KwExtension, KwArgument, KwFeature, KwTypedef, KwGrouping, KwIdentity, KwAnyxml, KwAnydata, KwCase, KwContainer, KwAction, KwNotification, KwLeafList, KwLeaf, KwList, KwBit, KwImport, KwInclude, KwPrefix, KwBelongsTo, KwBase, KwChoice, KwUses, KwType, KwRefine, KwKey, KwDeviation %enter Keyword
%on KwYangVersion %enter YVersion
%on KwStatus %enter Status
%on KwIfFeature %enter IfFeature
%on KwMandatory, KwConfig, KwRequireInstance %enter Mandatory
%on KwOrderedBy %enter Ordered
%on KwDefault %enter Default
%on KwFractionDigits %enter Fraction
%on KwLength, KwRange %enter Range
%on KwValue, KwPosition %enter Value
%on KwEnum %enter Enum
%on KwRevision, KwRevisionDate %enter Revision
%on KwNamespace %enter Uri
%on DoubleQuotation %push DQString
%on SingleQuotation %push SQString
%scanner DQString {
%auto_newline_off
%auto_ws_off
%on Escape %push Esc
%on DoubleQuotation %pop
}
%scanner SQString {
%auto_newline_off
%auto_ws_off
%on Escape %push Esc
%on SingleQuotation %pop
}
%scanner Esc {
%auto_newline_off
%auto_ws_off
%on Escape, DoubleQuotation, EscN, EscT, SQEscapeSeqChar %pop
}
%scanner Keyword {
%on Semicolon, LBrace %enter INITIAL
}
%scanner YVersion {
%on Semicolon %enter INITIAL
}
%scanner Range {
%on Semicolon %enter INITIAL
}
%scanner Enum {
%on Semicolon, LBrace %enter INITIAL
}
%scanner Default {
%on Semicolon %enter INITIAL
}
%scanner Revision {
%on Semicolon, LBrace %enter INITIAL
}
%scanner Mandatory {
%on Semicolon %enter INITIAL
}
%scanner IfFeature {
%on Semicolon %enter INITIAL
}
%scanner Status {
%on Semicolon %enter INITIAL
}
%scanner Value {
%on Semicolon %enter INITIAL
}
%scanner Fraction {
%on Semicolon %enter INITIAL
}
%scanner Ordered {
%on Semicolon %enter INITIAL
}
%scanner Uri {
%on Semicolon %enter INITIAL
}
%%
// --- Primary non-terminals for scanner transitions (parol 4.x) ---
// parol 4.x requires every token named in an %on transition to be a primary
// non-terminal (a non-terminal whose sole production is one terminal). The
// statement keywords below are therefore wrapped as KwX and used clipped
// (`KwX^`) so the AST is identical to the old inline `'kw'^` literals.
LBrace : <INITIAL, Keyword, Enum, Revision>'{';
EscN : <Esc>"n";
EscT : <Esc>"t";
KwModule : 'module';
KwSubmodule : 'submodule';
KwRpc : 'rpc';
KwExtension : 'extension';
KwArgument : 'argument';
KwFeature : 'feature';
KwTypedef : 'typedef';
KwGrouping : 'grouping';
KwIdentity : 'identity';
KwAnyxml : 'anyxml';
KwAnydata : 'anydata';
KwCase : 'case';
KwContainer : 'container';
KwAction : 'action';
KwNotification : 'notification';
KwLeafList : 'leaf-list';
KwLeaf : 'leaf';
KwList : 'list';
KwBit : 'bit';
KwImport : 'import';
KwInclude : 'include';
KwPrefix : 'prefix';
KwBelongsTo : 'belongs-to';
KwBase : 'base';
KwChoice : 'choice';
KwUses : 'uses';
KwType : 'type';
KwRefine : 'refine';
KwKey : 'key';
KwDeviation : 'deviation';
KwYangVersion : 'yang-version';
KwStatus : 'status';
KwIfFeature : 'if-feature';
KwMandatory : 'mandatory';
KwConfig : 'config';
KwRequireInstance : 'require-instance';
KwOrderedBy : 'ordered-by';
KwDefault : 'default';
KwFractionDigits : 'fraction-digits';
KwLength : 'length';
KwRange : 'range';
KwValue : 'value';
KwPosition : 'position';
KwEnum : 'enum';
KwRevision : 'revision';
KwRevisionDate : 'revision-date';
KwNamespace : 'namespace';
Yang
: ModuleStmt
| SubmoduleStmt;
ModuleStmt
: KwModule^ IdentifierArgStr
LBrace^
{ ModuleHeaderStmts }
{ LinkageStmts }
{ MetaStmts }
{ RevisionStmt }
{ BodyStmts }
'}'^;
SubmoduleStmt
: KwSubmodule^ IdentifierArgStr
LBrace^
{ SubmoduleHeaderStmts }
{ LinkageStmts }
{ MetaStmts }
{ RevisionStmt }
{ BodyStmts }
'}'^;
ModuleHeaderStmts
: YangVersionStmt
| NamespaceStmt
| PrefixStmt;
SubmoduleHeaderStmts
: YangVersionStmt
| BelongsToStmt;
MetaStmts
: OrganizationStmt
| ContactStmt
| DescriptionStmt
| ReferenceStmt;
LinkageStmts
: ImportStmt
| IncludeStmt;
BodyStmts
: ExtensionStmt
| FeatureStmt
| IdentityStmt
| TypedefStmt
| GroupingStmt
| DataDefStmt
| AugmentStmt
| RpcStmt
| NotificationStmt
| DeviationStmt
| UnknownStmt;
DataDefStmt
: ContainerStmt
| LeafStmt
| LeafListStmt
| ListStmt
| ChoiceStmt
| AnydataStmt
| AnyxmlStmt
| UsesStmt;
YangVersionStmt
: KwYangVersion^ YangVersionArgStr Semicolon^;
YangVersionArgStr
: YangVersionArg
| <YVersion>'"'^ YangVersionArg <YVersion>'"'^;
//
DeviationStmt
: KwDeviation^ AbsoluteSchemaNodeid Semicolon;
RpcStmt
: KwRpc^ IdentifierArgStr Semicolon
| KwRpc^ IdentifierArgStr
LBrace^
{ IfFeatureStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| TypedefStmt
| GroupingStmt
| InputStmt
| OutputStmt }
'}';
ExtensionStmt
: KwExtension^ IdentifierArgStr Semicolon^
| KwExtension^ IdentifierArgStr
LBrace^
{ ArgumentStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
ArgumentStmt
: KwArgument^ IdentifierArgStr Semicolon^;
FeatureStmt
: KwFeature^ IdentifierArgStr Semicolon^
| KwFeature^ IdentifierArgStr
LBrace^
{ IfFeatureStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt }
'}';
TypedefStmt
: KwTypedef^ IdentifierArgStr
LBrace^
{ TypeStmt
| UnitsStmt
| DefaultStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
AugmentStmt
: 'augment'^ AugmentArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| DataDefStmt
| CaseStmt
| ActionStmt
| NotificationStmt }
'}'^;
// AugmentArgStr parses the augment target path. YANG 1.1 §7.17
// allows absolute or descendant schema-node identifiers which are
// naturally multi-segment (e.g. "/a:root/a:inner/b:leaf"). The
// previous grammar rule accepted only a single `/IdentifierRef` and
// dropped everything else. Replace with a plain Ystring so the full
// path comes through as one quoted token; the schema walker in
// store/entry.rs splits the segments and resolves prefixes against
// the augmenting module's imports.
AugmentArgStr
: Ystring;
WhenStmt
: 'when'^ Ystring Semicolon^
| 'when'^ Ystring
LBrace^
{ DescriptionStmt
| ReferenceStmt }
'}';
GroupingStmt
: KwGrouping^ IdentifierArgStr
LBrace^
{ StatusStmt
| DescriptionStmt
| ReferenceStmt
| TypedefStmt
| GroupingStmt
| DataDefStmt
| ActionStmt
| NotificationStmt
| UnknownStmt }
'}'^;
IdentityStmt
: KwIdentity^ IdentifierArgStr
LBrace^
{ IfFeatureStmt
| BaseStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
BaseStmt
: KwBase^ IdentifierRefArgStr Semicolon^;
AnyxmlStmt
: KwAnyxml^ IdentifierArgStr Semicolon^
| KwAnyxml^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| MustStmt
| ConfigStmt
| MandatoryStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
ChoiceStmt
: KwChoice^ IdentifierRefArgStr Semicolon^
| KwChoice^ IdentifierRefArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| DefaultStmt
| ConfigStmt
| MandatoryStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| ShortCaseStmt
| CaseStmt }
'}';
ShortCaseStmt
: ChoiceStmt
| ContainerStmt
| LeafStmt
| LeafListStmt
| ListStmt
| AnydataStmt
| AnyxmlStmt;
AnydataStmt
: KwAnydata^ IdentifierArgStr Semicolon^
| KwAnydata^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| MustStmt
| ConfigStmt
| MandatoryStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
CaseStmt
: KwCase^ IdentifierArgStr Semicolon^
| KwCase^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| DataDefStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
StatusStmt
: KwStatus^ StatusArgStr Semicolon^;
StatusArgStr
: StatusArg
| <Status>'"'^ StatusArg <Status>'"'^;
StatusArg
: <Status>/current|obsolete|deprecated/;
ContainerStmt
: KwContainer^ IdentifierArgStr Semicolon^
| KwContainer^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| MustStmt
| PresenceStmt
| ConfigStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| DataDefStmt
| ActionStmt
| NotificationStmt
| UnknownStmt }
'}'^;
ActionStmt
: KwAction^ IdentifierArgStr Semicolon
| KwAction^ IdentifierArgStr
LBrace^
{ IfFeatureStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| InputStmt
| OutputStmt }
'}';
InputStmt
: 'input'
LBrace^
{ DataDefStmt }
'}';
OutputStmt
: 'output'
LBrace^
{ DataDefStmt }
'}';
NotificationStmt
: KwNotification^ IdentifierArgStr
LBrace^
{ IfFeatureStmt
| MustStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| TypedefStmt
| GroupingStmt
| DataDefStmt }
'}';
IfFeatureStmt
: KwIfFeature^ IfFeatureExprStr Semicolon^;
IfFeatureExprStr
: IfFeatureExpr;
IfFeatureExpr
: IfFeatureTerm [ <IfFeature>'or' IfFeatureExpr ];
IfFeatureTerm
: IfFeatureFactor [ <IfFeature>'and' IfFeatureTerm ];
// A feature reference is read directly in the IfFeature scanner so the
// context stays active across the whole expression. Routing through
// IdentifierRefArgStr (as before) reset the scanner to INITIAL after the
// first name, which left a following `and`/`or`/`not` lexed as a plain
// identifier and broke every compound if-feature expression.
IfFeatureFactor
: <IfFeature>'not' IfFeatureFactor
| <IfFeature>'(' IfFeatureExpr <IfFeature>')'
| Identifier
| <IfFeature>'"' Identifier <IfFeature>'"';
PresenceStmt
: 'presence'^ Ystring Semicolon^;
UsesStmt
: KwUses^ IdentifierRefArgStr Semicolon^
| KwUses^ IdentifierRefArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| RefineStmt
| AugmentStmt }
'}'^;
RefineStmt
: KwRefine^ RefineArgStr Semicolon^
| KwRefine^ RefineArgStr
LBrace^
{ IfFeatureStmt
| MustStmt
| PresenceStmt
| DefaultStmt
| ConfigStmt
| MandatoryStmt
| MinElementsStmt
| MaxElementsStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
RefineArgStr
: RefineArg
| <Keyword>'"' RefineArg <Keyword>'"';
RefineArg
: DescendantSchemaNodeid;
DescendantSchemaNodeid
: IdentifierRef
| AbsoluteSchemaNodeid;
UnknownStmt
: IdentifierRef Ystring Semicolon^
| IdentifierRef Ystring
LBrace^
{ TypeStmt
| DescriptionStmt }
'}'^;
LeafListStmt
: KwLeafList^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| TypeStmt
| UnitsStmt
| MustStmt
| DefaultStmt
| ConfigStmt
| MinElementsStmt
| MaxElementsStmt
| OrderedByStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| UnknownStmt }
'}'^;
LeafStmt
: KwLeaf^ IdentifierArgStr
LBrace^
{ WhenStmt
| StatusStmt
| IfFeatureStmt
| TypeStmt
| UnitsStmt
| MustStmt
| DefaultStmt
| ConfigStmt
| MandatoryStmt
| DescriptionStmt
| ReferenceStmt
| UnknownStmt }
'}'^;
MustStmt
: 'must'^ Ystring Semicolon^
| 'must'^ Ystring
LBrace^
{ ErrorMessage
| DescriptionStmt }
'}'^;
ErrorMessage
: 'error-message' Ystring Semicolon;
UnitsStmt
: 'units' Ystring Semicolon;
ConfigStmt
: KwConfig^ MandatoryArgStr Semicolon;
MandatoryStmt
: KwMandatory^ MandatoryArgStr Semicolon;
MandatoryArgStr
: MandatoryArg
| <Mandatory>'"' MandatoryArg <Mandatory>'"';
MandatoryArg
: <Mandatory>/true|false/;
ListStmt
: KwList^ IdentifierArgStr
LBrace^
{ WhenStmt
| IfFeatureStmt
| MustStmt
| KeyStmt
| ConfigStmt
| MinElementsStmt
| MaxElementsStmt
| OrderedByStmt
| StatusStmt
| DescriptionStmt
| ReferenceStmt
| DataDefStmt
| ActionStmt
| NotificationStmt
| UnknownStmt }
'}'^;
OrderedByStmt
: KwOrderedBy^ OrderedByArgStr Semicolon^;
OrderedByArgStr
: OrderedByArg
| <Ordered>'"' OrderedByArg <Ordered>'"';
OrderedByArg
: <Ordered>/user|system/;
DefaultStmt
: KwDefault^ AsciiNoSemicolon Semicolon^
| KwDefault^ <Default>'"' AsciiNoSemicolon <Default>'"' Semicolon^;
MaxElementsStmt
: 'max-elements'^ /[1-9][0-9]*/ Semicolon^;
MinElementsStmt
: 'min-elements'^ /[1-9][0-9]*/ Semicolon^;
TypeStmt
: KwType^ IdentifierRefArgStr Semicolon^
| KwType^ IdentifierRefArgStr
LBrace^
{ FractionDigitsStmt
| EnumStmt
| BaseStmt
| LeafrefSpecification
| StringRestrictions
| RangeStmt
| BitStmt
| TypeStmt }
<INITIAL, Keyword>'}'^;
FractionDigitsStmt
: KwFractionDigits^ FractionDigitsArg Semicolon^;
FractionDigitsArg
: <Fraction>/1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|/;
BitStmt
: KwBit^ IdentifierArgStr Semicolon^
| KwBit^ IdentifierArgStr
LBrace^
{ PositionStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
PositionStmt
: KwPosition^ IntegerValueStr Semicolon^;
LeafrefSpecification
: PathStmt
| RequireInstanceStmt;
RequireInstanceStmt
: KwRequireInstance^ RequireInstanceArgStr Semicolon;
RequireInstanceArgStr
: MandatoryArg
| <Mandatory>'"' MandatoryArg <Mandatory>'"';
PathStmt
: 'path'^ Ystring Semicolon^;
StringRestrictions
: LengthStmt
| PatternStmt;
PatternStmt
: 'pattern' Ystring Semicolon;
LengthStmt
: KwLength^ RangeArgStr Semicolon^;
EnumStmt
: KwEnum^ EnumArgStr Semicolon^
| KwEnum^ EnumArgStr
LBrace^
{ IfFeatureStmt
| DescriptionStmt
| ValueStmt
| ReferenceStmt }
'}'^;
ValueStmt
: KwValue^ IntegerValueStr Semicolon^;
IntegerValueStr
: IntegerValue
| <Value>'"' IntegerValue <Value>'"';
IntegerValue
: <Value>/[0-9]+/;
EnumArgStr
: AsciiNoBrace
| <Enum>'"'^ AsciiNoBrace <Enum>'"'^;
RangeStmt
: KwRange^ RangeArgStr Semicolon^;
RangeArgStr
: RangeArg
| <Range>'"'^ RangeArg <Range>'"'^;
RangeArg
: RangePart [ <Range>'|' RangeArg ];
RangePart
: RangeBoundary [ <Range>'..' RangeBoundary ];
RangeBoundary
: <Range>'min'
| <Range>'max'
| <Range>"-?[0-9]+";
IdentifierRefArgStr
: IdentifierRef
| <Keyword, IfFeature>'"' IdentifierRef <Keyword, IfFeature>'"';
IdentifierRef
: [ Identifier <Keyword, IfFeature>':' ] Identifier;
KeyStmt
: KwKey^ KeyArgStr Semicolon^;
KeyArgStr
: KeyArg
| <Keyword>'"'^ KeyArg <Keyword>'"'^;
Semicolon
: <INITIAL, Keyword, Range, YVersion, Enum, Revision, Mandatory, IfFeature, Status, Ordered, Default, Fraction, Value, Uri>';';
KeyArg
: IdentifierRef [ KeyArg ];
AbsoluteSchemaNodeid
: <Keyword, IfFeature>'/' IdentifierRef;
ImportStmt
: KwImport^ IdentifierArgStr
LBrace^
{ PrefixStmt
| RevisionDateStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
IncludeStmt
: KwInclude^ IdentifierArgStr Semicolon^
| KwInclude^ IdentifierArgStr
LBrace^
{ RevisionDateStmt
| DescriptionStmt
| ReferenceStmt }
'}'^;
RevisionDateStmt
: KwRevisionDate^ DateArgStr Semicolon^;
NamespaceStmt
: KwNamespace^ UriStr Semicolon^;
UriStr
: UriArg
| <Uri>'"'^ UriArg <Uri>'"'^;
UriArg
: <Uri>"urn:[a-zA-Z0-9\-\.:]+";
PrefixStmt
: KwPrefix^ IdentifierArgStr Semicolon^;
BelongsToStmt
: KwBelongsTo^ IdentifierArgStr
LBrace^
PrefixStmt
'}'^;
OrganizationStmt
: 'organization'^ Ystring Semicolon;
ContactStmt
: 'contact'^ Ystring Semicolon;
DescriptionStmt
: 'description'^ Ystring Semicolon^;
ReferenceStmt
: 'reference'^ Ystring Semicolon^;
RevisionStmt
: KwRevision^ DateArgStr
LBrace^
{ DescriptionStmt
| ReferenceStmt }
'}'^;
DateArgStr
: DateArg
| <Revision>'"'^ DateArg <Revision>'"'^;
DateArg
: <Revision>/\d{4}-\d{2}-\d{2}/;
// Ystring definition starts here.
// RFC 7950 §6.1.3 allows unquoted strings as YANG statement arguments
// (any token not containing whitespace, quotes, ';', LBrace^, '}', or a
// comment-introducer). For the common case the existing `Identifier`
// token covers it: `units milliseconds;`, `description foo;`, etc.
// The `+` concatenation form only applies to quoted strings, so the
// unquoted alternative is a plain `Identifier`.
Ystring
: BasicString [ '+' Ystring ]
| Identifier;
BasicString
: DQString
| SQString;
DoubleQuotation
: <INITIAL, DQString, Esc>'"';
DQString
: DoubleQuotation^ { DQChar } DoubleQuotation^;
DQChar
: DQUnescaped
| DQEscaped;
DQUnescaped
: DQNoEscape
| NonAscii;
DQEscaped
: Escape DQEscapeSeqChar;
DQEscapeSeqChar
: Escape
| DoubleQuotation
| EscN
| EscT;
Escape
: <DQString, SQString, Esc>"\u{5C}";
NonAscii
: <DQString, SQString>"[\n\r\u{80}-\u{D7FF}\u{E000}-\u{10FFFF}]";
DQNoEscape
: <DQString>"[ \n\r\t\u{21}\u{23}-\u{5B}\u{5D}-\u{7E}]+";
SQString
: SingleQuotation { SQChar } SingleQuotation;
SQChar
: SQUnescaped
| SQEscaped;
SQEscaped
: Escape SQEscapeSeqChar;
SQEscapeSeqChar
: <Esc>".";
SQUnescaped
: SQNoEscape
| NonAscii;
SQNoEscape
: <SQString>"[ \t\u{21}-\u{26}\u{28}-\u{5B}\u{5D}-\u{7E}]+";
SingleQuotation
: <INITIAL, SQString>"\u{27}";
IdentifierArgStr
: Identifier
| <Keyword>'"'^ Identifier <Keyword>'"'^;
// The `.` in this character class is escaped (`\.`). parol's scanner since
// 2.0 is `scnr`, whose regex dialect treats an UNESCAPED `.` inside a class
// as "any character" (the previous regex-automata backend treated it as a
// literal dot). Left unescaped, this class matches anything and longest-match
// makes Identifier swallow whole lines, breaking all parsing.
Identifier
: <INITIAL, Keyword, IfFeature>/[a-zA-Z_][a-zA-Z0-9_\-\/\.:]*/;
AsciiNoSemicolon
: <Default>/[ \t\u{21}\u{23}-\u{3a}\u{3c}-\u{5b}\u{5d}-\u{7e}]+/;
AsciiNoBrace
: <Enum>/[\t\u{21}\u{23}-\u{3a}\u{3c}-\u{5b}\u{5d}-\u{7a}\u{7c}\u{7e}]+/;
YangVersionArg
: <YVersion>"1.1|1";