Update src/parser.pegjs
- use value plucking - remove helpers not needed now - types in OPS_* are now returned by *Operator - RESERVED_WORDS is now a `Object<Identifier,true>` - use ES2015+ JavaScript - cleanup source code
This commit is contained in:
parent
4964b9af7e
commit
e64118f3b7
File diff suppressed because it is too large
Load diff
356
src/parser.pegjs
356
src/parser.pegjs
|
@ -22,184 +22,175 @@
|
||||||
// [1] http://www.ecma-international.org/publications/standards/Ecma-262.htm
|
// [1] http://www.ecma-international.org/publications/standards/Ecma-262.htm
|
||||||
|
|
||||||
{
|
{
|
||||||
const OPS_TO_PREFIXED_TYPES = {
|
|
||||||
"$": "text",
|
|
||||||
"&": "simple_and",
|
|
||||||
"!": "simple_not"
|
|
||||||
};
|
|
||||||
|
|
||||||
const OPS_TO_SUFFIXED_TYPES = {
|
// Used as a shorthand property name for `LabeledExpression`
|
||||||
"?": "optional",
|
const pick = true;
|
||||||
"*": "zero_or_more",
|
|
||||||
"+": "one_or_more"
|
|
||||||
};
|
|
||||||
|
|
||||||
const OPS_TO_SEMANTIC_PREDICATE_TYPES = {
|
// Used by `LabelIdentifier` to disallow the use of certain words as labels
|
||||||
"&": "semantic_and",
|
const RESERVED_WORDS = {};
|
||||||
"!": "semantic_not"
|
|
||||||
};
|
|
||||||
|
|
||||||
let RESERVED_WORDS = options.reservedWords || util.reservedWords;
|
// Populate `RESERVED_WORDS` using the optional option `reservedWords`
|
||||||
if ( !Array.isArray(RESERVED_WORDS) ) RESERVED_WORDS = [];
|
const reservedWords = options.reservedWords || util.reservedWords;
|
||||||
|
if ( Array.isArray( reservedWords ) ) {
|
||||||
|
|
||||||
function extractOptional(optional, index) {
|
for ( const word of reservedWords ) RESERVED_WORDS[ word ] = true;
|
||||||
return optional ? optional[index] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractList(list, index) {
|
|
||||||
return list.map(element => element[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildList(head, tail, index) {
|
|
||||||
return [head].concat(extractList(tail, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNode( type, details ) {
|
|
||||||
const node = new ast.Node( type, location() );
|
|
||||||
util.extend( node, details );
|
|
||||||
return util.enforceFastProperties( node );
|
|
||||||
}
|
|
||||||
|
|
||||||
let comments = options.extractComments ? {} : null;
|
|
||||||
function addComment(comment, multiline) {
|
|
||||||
if (options.extractComments) {
|
|
||||||
let loc = location();
|
|
||||||
comment = {
|
|
||||||
text: comment,
|
|
||||||
multiline: multiline,
|
|
||||||
location: loc
|
|
||||||
};
|
|
||||||
comments[loc.start.offset] = comment;
|
|
||||||
return comment;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Helper to construct a new AST Node
|
||||||
|
function createNode( type, details ) {
|
||||||
|
|
||||||
|
const node = new ast.Node( type, location() );
|
||||||
|
if ( details === null ) return node;
|
||||||
|
|
||||||
|
util.extend( node, details );
|
||||||
|
return util.enforceFastProperties( node );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by `addComment` to store comments for the Grammar AST
|
||||||
|
const comments = options.extractComments ? {} : null;
|
||||||
|
|
||||||
|
// Helper that collects all the comments to pass to the Grammar AST
|
||||||
|
function addComment( text, multiline ) {
|
||||||
|
|
||||||
|
if ( options.extractComments ) {
|
||||||
|
|
||||||
|
const loc = location();
|
||||||
|
|
||||||
|
comments[ loc.start.offset ] = {
|
||||||
|
text: text,
|
||||||
|
multiline: multiline,
|
||||||
|
location: loc,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Syntactic Grammar -----
|
// ---- Syntactic Grammar -----
|
||||||
|
|
||||||
Grammar
|
Grammar
|
||||||
= __ initializer:(Initializer __)? rules:(Rule __)+ {
|
= __ initializer:(@Initializer __)? rules:(@Rule __)+ {
|
||||||
return new ast.Grammar(
|
|
||||||
extractOptional(initializer, 0),
|
return new ast.Grammar( initializer, rules, comments, location() );
|
||||||
extractList(rules, 0),
|
|
||||||
comments,
|
|
||||||
location()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Initializer
|
Initializer
|
||||||
= code:CodeBlock EOS {
|
= code:CodeBlock EOS {
|
||||||
return createNode( "initializer", { code: code } );
|
|
||||||
|
return createNode( "initializer", { code } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rule
|
Rule
|
||||||
= name:Identifier __
|
= name:Identifier __ displayName:(@StringLiteral __)? "=" __ expression:Expression EOS {
|
||||||
displayName:(StringLiteral __)?
|
|
||||||
"=" __
|
if ( displayName )
|
||||||
expression:Expression EOS
|
|
||||||
{
|
expression = createNode( "named", {
|
||||||
return createNode( "rule", {
|
name: displayName,
|
||||||
name: name,
|
expression: expression,
|
||||||
expression: displayName !== null
|
} );
|
||||||
? createNode( "named", {
|
|
||||||
name: displayName[0],
|
return createNode( "rule", { name, expression } );
|
||||||
expression: expression,
|
|
||||||
} )
|
|
||||||
: expression,
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression
|
Expression
|
||||||
= ChoiceExpression
|
= ChoiceExpression
|
||||||
|
|
||||||
ChoiceExpression
|
ChoiceExpression
|
||||||
= head:ActionExpression tail:(__ "/" __ ActionExpression)* {
|
= head:ActionExpression tail:(__ "/" __ @ActionExpression)* {
|
||||||
return tail.length > 0
|
|
||||||
? createNode( "choice", {
|
if ( tail.length === 0 ) return head;
|
||||||
alternatives: buildList(head, tail, 3),
|
|
||||||
} )
|
return createNode( "choice", {
|
||||||
: head;
|
alternatives: [ head ].concat( tail ),
|
||||||
|
} );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionExpression
|
ActionExpression
|
||||||
= expression:SequenceExpression code:(__ CodeBlock)? {
|
= expression:SequenceExpression code:(__ @CodeBlock)? {
|
||||||
return code !== null
|
|
||||||
? createNode( "action", {
|
if ( code === null ) return expression;
|
||||||
expression: expression,
|
|
||||||
code: code[1],
|
return createNode( "action", { expression, code } );
|
||||||
} )
|
|
||||||
: expression;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SequenceExpression
|
SequenceExpression
|
||||||
= head:LabeledExpression tail:(__ LabeledExpression)* {
|
= head:LabeledExpression tail:(__ @LabeledExpression)* {
|
||||||
if ( tail.length < 1 )
|
|
||||||
|
|
||||||
return head.type === "labeled" && head.pick
|
let elements = [ head ];
|
||||||
? createNode( "sequence", { elements: [ head ] } )
|
|
||||||
: head;
|
|
||||||
|
|
||||||
return createNode( "sequence", {
|
if ( tail.length === 0 ) {
|
||||||
|
|
||||||
elements: buildList( head, tail, 1 ),
|
if ( head.type !== "labeled" || ! head.pick ) return head;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
elements = elements.concat( tail );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return createNode( "sequence", { elements } );
|
||||||
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LabeledExpression
|
LabeledExpression
|
||||||
= "@" label:(IdentifierName __ ":")? __ expression:PrefixedExpression {
|
= "@" label:LabelIdentifier? __ expression:PrefixedExpression {
|
||||||
const [ name, location ] = extractOptional(label, 0) || [];
|
|
||||||
|
|
||||||
if (name && RESERVED_WORDS.indexOf(name) >= 0) {
|
return createNode( "labeled", { pick, label, expression } );
|
||||||
error(`Label can't be a reserved word "${name}".`, location);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createNode( "labeled", {
|
|
||||||
pick: true,
|
|
||||||
label: name,
|
|
||||||
expression: expression,
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
/ label:IdentifierName __ ":" __ expression:PrefixedExpression {
|
/ label:LabelIdentifier __ expression:PrefixedExpression {
|
||||||
if (RESERVED_WORDS.indexOf(label[0]) >= 0) {
|
|
||||||
error(`Label can't be a reserved word "${label[0]}".`, label[1]);
|
return createNode( "labeled", { label, expression } );
|
||||||
}
|
|
||||||
|
|
||||||
return createNode( "labeled", {
|
|
||||||
label: label[0],
|
|
||||||
expression: expression,
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
/ PrefixedExpression
|
/ PrefixedExpression
|
||||||
|
|
||||||
IdentifierName
|
LabelIdentifier
|
||||||
= name:Identifier { return [name, location()]; }
|
= name:Identifier __ ":" {
|
||||||
|
|
||||||
|
if ( RESERVED_WORDS[ name ] !== true ) return name;
|
||||||
|
|
||||||
|
error( `Label can't be a reserved word "${ name }".`, location() );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
PrefixedExpression
|
PrefixedExpression
|
||||||
= operator:PrefixedOperator __ expression:SuffixedExpression {
|
= operator:PrefixedOperator __ expression:SuffixedExpression {
|
||||||
return createNode( OPS_TO_PREFIXED_TYPES[operator], {
|
|
||||||
expression: expression,
|
return createNode( operator, { expression } );
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
/ SuffixedExpression
|
/ SuffixedExpression
|
||||||
|
|
||||||
PrefixedOperator
|
PrefixedOperator
|
||||||
= "$"
|
= "$" { return "text"; }
|
||||||
/ "&"
|
/ "&" { return "simple_and"; }
|
||||||
/ "!"
|
/ "!" { return "simple_not"; }
|
||||||
|
|
||||||
SuffixedExpression
|
SuffixedExpression
|
||||||
= expression:PrimaryExpression __ operator:SuffixedOperator {
|
= expression:PrimaryExpression __ operator:SuffixedOperator {
|
||||||
return createNode( OPS_TO_SUFFIXED_TYPES[operator], {
|
|
||||||
expression: expression,
|
return createNode( operator, { expression } );
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
/ PrimaryExpression
|
/ PrimaryExpression
|
||||||
|
|
||||||
SuffixedOperator
|
SuffixedOperator
|
||||||
= "?"
|
= "?" { return "optional"; }
|
||||||
/ "*"
|
/ "*" { return "zero_or_more"; }
|
||||||
/ "+"
|
/ "+" { return "one_or_more"; }
|
||||||
|
|
||||||
PrimaryExpression
|
PrimaryExpression
|
||||||
= LiteralMatcher
|
= LiteralMatcher
|
||||||
|
@ -207,29 +198,35 @@ PrimaryExpression
|
||||||
/ AnyMatcher
|
/ AnyMatcher
|
||||||
/ RuleReferenceExpression
|
/ RuleReferenceExpression
|
||||||
/ SemanticPredicateExpression
|
/ SemanticPredicateExpression
|
||||||
/ "(" __ expression:Expression __ ")" {
|
/ "(" __ e:Expression __ ")" {
|
||||||
// The purpose of the "group" AST node is just to isolate label scope. We
|
|
||||||
// don't need to put it around nodes that can't contain any labels or
|
// The purpose of the "group" AST node is just to isolate label scope. We
|
||||||
// nodes that already isolate label scope themselves. This leaves us with
|
// don't need to put it around nodes that can't contain any labels or
|
||||||
// "labeled" and "sequence".
|
// nodes that already isolate label scope themselves.
|
||||||
return expression.type === "labeled" || expression.type === "sequence"
|
if ( e.type !== "labeled" && e.type !== "sequence" ) return e;
|
||||||
? createNode( "group", { expression: expression } )
|
|
||||||
: expression;
|
// This leaves us with "labeled" and "sequence".
|
||||||
|
return createNode( "group", { expression: e } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RuleReferenceExpression
|
RuleReferenceExpression
|
||||||
= name:Identifier !(__ (StringLiteral __)? "=") {
|
= name:Identifier !(__ (StringLiteral __)? "=") {
|
||||||
return createNode( "rule_ref", { name: name } );
|
|
||||||
|
return createNode( "rule_ref", { name } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SemanticPredicateExpression
|
SemanticPredicateExpression
|
||||||
= operator:SemanticPredicateOperator __ code:CodeBlock {
|
= operator:SemanticPredicateOperator __ code:CodeBlock {
|
||||||
return createNode( OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], { code: code } );
|
|
||||||
|
return createNode( operator, { code } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SemanticPredicateOperator
|
SemanticPredicateOperator
|
||||||
= "&"
|
= "&" { return "semantic_and"; }
|
||||||
/ "!"
|
/ "!" { return "semantic_not"; }
|
||||||
|
|
||||||
// ---- Lexical Grammar -----
|
// ---- Lexical Grammar -----
|
||||||
|
|
||||||
|
@ -261,27 +258,37 @@ Comment "comment"
|
||||||
|
|
||||||
MultiLineComment
|
MultiLineComment
|
||||||
= "/*" comment:$(!"*/" SourceCharacter)* "*/" {
|
= "/*" comment:$(!"*/" SourceCharacter)* "*/" {
|
||||||
return addComment(comment, true);
|
|
||||||
|
return addComment( comment, true );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiLineCommentNoLineTerminator
|
MultiLineCommentNoLineTerminator
|
||||||
= "/*" comment:$(!("*/" / LineTerminator) SourceCharacter)* "*/" {
|
= "/*" comment:$(!("*/" / LineTerminator) SourceCharacter)* "*/" {
|
||||||
return addComment(comment, true);
|
|
||||||
|
return addComment( comment, true );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleLineComment
|
SingleLineComment
|
||||||
= "//" comment:$(!LineTerminator SourceCharacter)* {
|
= "//" comment:$(!LineTerminator SourceCharacter)* {
|
||||||
return addComment(comment, false);
|
|
||||||
|
return addComment( comment, false );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Identifier "identifier"
|
Identifier "identifier"
|
||||||
= head:IdentifierStart tail:IdentifierPart* { return head + tail.join(""); }
|
= head:IdentifierStart tail:IdentifierPart* {
|
||||||
|
|
||||||
|
return head + tail.join("");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
IdentifierStart
|
IdentifierStart
|
||||||
= UnicodeLetter
|
= UnicodeLetter
|
||||||
/ "$"
|
/ "$"
|
||||||
/ "_"
|
/ "_"
|
||||||
/ "\\" sequence:UnicodeEscapeSequence { return sequence; }
|
/ "\\" @UnicodeEscapeSequence
|
||||||
|
|
||||||
IdentifierPart
|
IdentifierPart
|
||||||
= IdentifierStart
|
= IdentifierStart
|
||||||
|
@ -311,10 +318,12 @@ UnicodeConnectorPunctuation
|
||||||
|
|
||||||
LiteralMatcher "literal"
|
LiteralMatcher "literal"
|
||||||
= value:StringLiteral ignoreCase:"i"? {
|
= value:StringLiteral ignoreCase:"i"? {
|
||||||
return createNode( "literal", {
|
|
||||||
value: value,
|
return createNode( "literal", {
|
||||||
ignoreCase: ignoreCase !== null,
|
value: value,
|
||||||
} );
|
ignoreCase: ignoreCase !== null,
|
||||||
|
} );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringLiteral "string"
|
StringLiteral "string"
|
||||||
|
@ -322,43 +331,44 @@ StringLiteral "string"
|
||||||
/ "'" chars:SingleStringCharacter* "'" { return chars.join(""); }
|
/ "'" chars:SingleStringCharacter* "'" { return chars.join(""); }
|
||||||
|
|
||||||
DoubleStringCharacter
|
DoubleStringCharacter
|
||||||
= !('"' / "\\" / LineTerminator) SourceCharacter { return text(); }
|
= !('"' / "\\" / LineTerminator) @SourceCharacter
|
||||||
/ "\\" sequence:EscapeSequence { return sequence; }
|
/ "\\" @EscapeSequence
|
||||||
/ LineContinuation
|
/ LineContinuation
|
||||||
|
|
||||||
SingleStringCharacter
|
SingleStringCharacter
|
||||||
= !("'" / "\\" / LineTerminator) SourceCharacter { return text(); }
|
= !("'" / "\\" / LineTerminator) @SourceCharacter
|
||||||
/ "\\" sequence:EscapeSequence { return sequence; }
|
/ "\\" @EscapeSequence
|
||||||
/ LineContinuation
|
/ LineContinuation
|
||||||
|
|
||||||
CharacterClassMatcher "character class"
|
CharacterClassMatcher "character class"
|
||||||
= "["
|
= "[" inverted:"^"? parts:CharacterPart* "]" ignoreCase:"i"? {
|
||||||
inverted:"^"?
|
|
||||||
parts:(ClassCharacterRange / ClassCharacter)*
|
return createNode( "class", {
|
||||||
"]"
|
parts: parts.filter( part => part !== "" ),
|
||||||
ignoreCase:"i"?
|
inverted: inverted !== null,
|
||||||
{
|
ignoreCase: ignoreCase !== null,
|
||||||
return createNode( "class", {
|
} );
|
||||||
parts: parts.filter(part => part !== ""),
|
|
||||||
inverted: inverted !== null,
|
|
||||||
ignoreCase: ignoreCase !== null,
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CharacterPart
|
||||||
|
= ClassCharacterRange
|
||||||
|
/ ClassCharacter
|
||||||
|
|
||||||
ClassCharacterRange
|
ClassCharacterRange
|
||||||
= begin:ClassCharacter "-" end:ClassCharacter {
|
= begin:ClassCharacter "-" end:ClassCharacter {
|
||||||
if (begin.charCodeAt(0) > end.charCodeAt(0)) {
|
|
||||||
error(
|
|
||||||
"Invalid character range: " + text() + "."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [begin, end];
|
if ( begin.charCodeAt( 0 ) > end.charCodeAt( 0 ) )
|
||||||
|
|
||||||
|
error( "Invalid character range: " + text() + "." );
|
||||||
|
|
||||||
|
return [ begin, end ];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassCharacter
|
ClassCharacter
|
||||||
= !("]" / "\\" / LineTerminator) SourceCharacter { return text(); }
|
= !("]" / "\\" / LineTerminator) @SourceCharacter
|
||||||
/ "\\" sequence:EscapeSequence { return sequence; }
|
/ "\\" @EscapeSequence
|
||||||
/ LineContinuation
|
/ LineContinuation
|
||||||
|
|
||||||
LineContinuation
|
LineContinuation
|
||||||
|
@ -386,7 +396,7 @@ SingleEscapeCharacter
|
||||||
/ "v" { return "\v"; }
|
/ "v" { return "\v"; }
|
||||||
|
|
||||||
NonEscapeCharacter
|
NonEscapeCharacter
|
||||||
= !(EscapeCharacter / LineTerminator) SourceCharacter { return text(); }
|
= !(EscapeCharacter / LineTerminator) @SourceCharacter
|
||||||
|
|
||||||
EscapeCharacter
|
EscapeCharacter
|
||||||
= SingleEscapeCharacter
|
= SingleEscapeCharacter
|
||||||
|
@ -396,12 +406,16 @@ EscapeCharacter
|
||||||
|
|
||||||
HexEscapeSequence
|
HexEscapeSequence
|
||||||
= "x" digits:$(HexDigit HexDigit) {
|
= "x" digits:$(HexDigit HexDigit) {
|
||||||
return String.fromCharCode(parseInt(digits, 16));
|
|
||||||
|
return String.fromCharCode( parseInt( digits, 16 ) );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UnicodeEscapeSequence
|
UnicodeEscapeSequence
|
||||||
= "u" digits:$(HexDigit HexDigit HexDigit HexDigit) {
|
= "u" digits:$(HexDigit HexDigit HexDigit HexDigit) {
|
||||||
return String.fromCharCode(parseInt(digits, 16));
|
|
||||||
|
return String.fromCharCode( parseInt( digits, 16 ) );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DecimalDigit
|
DecimalDigit
|
||||||
|
@ -411,10 +425,14 @@ HexDigit
|
||||||
= [0-9a-f]i
|
= [0-9a-f]i
|
||||||
|
|
||||||
AnyMatcher
|
AnyMatcher
|
||||||
= "." { return createNode( "any", {} ); }
|
= "." {
|
||||||
|
|
||||||
|
return createNode( "any" );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
CodeBlock "code block"
|
CodeBlock "code block"
|
||||||
= "{" code:Code "}" { return code; }
|
= "{" @Code "}"
|
||||||
/ "{" { error("Unbalanced brace."); }
|
/ "{" { error("Unbalanced brace."); }
|
||||||
|
|
||||||
Code
|
Code
|
||||||
|
|
|
@ -441,7 +441,7 @@ describe( "PEG.js grammar parser", function () {
|
||||||
return {
|
return {
|
||||||
type: "labeled",
|
type: "labeled",
|
||||||
pick: true,
|
pick: true,
|
||||||
label: label || void 0,
|
label: label,
|
||||||
expression: expression
|
expression: expression
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue