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
master
Futago-za Ryuu 6 years ago
parent 4964b9af7e
commit e64118f3b7

File diff suppressed because it is too large Load Diff

@ -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 = {
"?": "optional",
"*": "zero_or_more",
"+": "one_or_more"
};
const OPS_TO_SEMANTIC_PREDICATE_TYPES = {
"&": "semantic_and",
"!": "semantic_not"
};
let RESERVED_WORDS = options.reservedWords || util.reservedWords;
if ( !Array.isArray(RESERVED_WORDS) ) RESERVED_WORDS = [];
function extractOptional(optional, index) {
return optional ? optional[index] : null;
}
function extractList(list, index) { // Used as a shorthand property name for `LabeledExpression`
return list.map(element => element[index]); const pick = true;
}
function buildList(head, tail, index) { // Used by `LabelIdentifier` to disallow the use of certain words as labels
return [head].concat(extractList(tail, index)); const RESERVED_WORDS = {};
}
function createNode( type, details ) { // Populate `RESERVED_WORDS` using the optional option `reservedWords`
const node = new ast.Node( type, location() ); const reservedWords = options.reservedWords || util.reservedWords;
util.extend( node, details ); if ( Array.isArray( reservedWords ) ) {
return util.enforceFastProperties( node );
} for ( const word of reservedWords ) RESERVED_WORDS[ word ] = true;
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 )
let elements = [ head ];
return head.type === "labeled" && head.pick if ( tail.length === 0 ) {
? createNode( "sequence", { elements: [ head ] } )
: head;
return createNode( "sequence", { if ( head.type !== "labeled" || ! head.pick ) return head;
elements: buildList( head, tail, 1 ), } 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) || [];
return createNode( "labeled", { pick, label, expression } );
if (name && RESERVED_WORDS.indexOf(name) >= 0) {
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…
Cancel
Save