From 460f0cc5bc9e7b12e7830a13a9afa5026a5f20f4 Mon Sep 17 00:00:00 2001 From: Futago-za Ryuu Date: Mon, 17 Sep 2018 11:32:34 +0100 Subject: [PATCH] Implement value plucking Resolves #235, #427, #545 --- docs/grammar/parsing-expression-types.md | 25 +- packages/pegjs/lib/compiler/index.js | 4 +- packages/pegjs/lib/compiler/opcodes.js | 1 + .../lib/compiler/passes/generate-bytecode.js | 48 +- .../pegjs/lib/compiler/passes/generate-js.js | 38 +- .../passes/report-duplicate-labels.js | 5 +- .../passes/report-incorrect-plucking.js | 53 ++ packages/pegjs/lib/parser.js | 610 ++++++++++-------- src/parser.pegjs | 31 +- .../generated-parser-behavior.spec.js | 128 +++- .../compiler/passes/generate-bytecode.spec.js | 44 ++ test/spec/unit/compiler/passes/helpers.js | 2 + .../passes/report-incorrect-plucking.spec.js | 52 ++ test/spec/unit/parser.spec.js | 45 +- 14 files changed, 770 insertions(+), 316 deletions(-) create mode 100644 packages/pegjs/lib/compiler/passes/report-incorrect-plucking.js create mode 100644 test/spec/unit/compiler/passes/report-incorrect-plucking.spec.js diff --git a/docs/grammar/parsing-expression-types.md b/docs/grammar/parsing-expression-types.md index 6463b29..a99ca15 100644 --- a/docs/grammar/parsing-expression-types.md +++ b/docs/grammar/parsing-expression-types.md @@ -16,9 +16,10 @@ There are several types of parsing expressions, some of them containing subexpre * [! { predicate }](#--predicate--1) * [$ expression](#-expression-2) * [label : expression](#label--expression) - * [expression1 expression2 ... expressionN](#expression1-expression2---expressionn) + * [expression1 expression2 ... expressionn](#expression1-expression2---expressionn) * [expression { action }](#expression--action-) - * [expression1 / expression2 / ... / expressionN](#expression1--expression2----expressionn) + * [expression1 / expression2 / ... / expressionn](#expression1--expression2----expressionn) + * [expression1 @expression2 ... expressionn](#expression1--expression2---expressionn) #### "*literal*"
'*literal*' @@ -113,3 +114,23 @@ The action has access to all variables and functions in the [Action Execution En #### *expression1* / *expression2* / ... / *expressionn* Try to match the first expression, if it does not succeed, try the second one, etc. Return the match result of the first successfully matched expression. If no expression matches, consider the match failed. + +#### *expression1* @*expression2* ... *expressionn* + +Only returns the expression(s) following `@` + +> WARNING: You cannot use this on predicate's, and cannot use it alongside an action. + +```js +start = MultiPluck + / SinglePluck + +SinglePluck = "0"? @integer +MultiPluck = @integer "." @integer + +integer = $[0-9]+ +``` + +When `SinglePluck` finds `011`, it returns `"11"` + +When `MultiPluck` finds `0.11`, it returns `["0", "11"]` diff --git a/packages/pegjs/lib/compiler/index.js b/packages/pegjs/lib/compiler/index.js index a3cc962..a2b14e2 100644 --- a/packages/pegjs/lib/compiler/index.js +++ b/packages/pegjs/lib/compiler/index.js @@ -11,6 +11,7 @@ const reportInfiniteRecursion = require( "./passes/report-infinite-recursion" ); const reportInfiniteRepetition = require( "./passes/report-infinite-repetition" ); const reportUndefinedRules = require( "./passes/report-undefined-rules" ); const inferenceMatchResult = require( "./passes/inference-match-result" ); +const reportIncorrectPlucking = require( "./passes/report-incorrect-plucking" ); const Session = require( "./session" ); const util = require( "../util" ); @@ -30,7 +31,8 @@ const compiler = { reportUnusedRules: reportUnusedRules, reportDuplicateLabels: reportDuplicateLabels, reportInfiniteRecursion: reportInfiniteRecursion, - reportInfiniteRepetition: reportInfiniteRepetition + reportInfiniteRepetition: reportInfiniteRepetition, + reportIncorrectPlucking: reportIncorrectPlucking, }, transform: { removeProxyRules: removeProxyRules diff --git a/packages/pegjs/lib/compiler/opcodes.js b/packages/pegjs/lib/compiler/opcodes.js index 2151a3f..1402072 100644 --- a/packages/pegjs/lib/compiler/opcodes.js +++ b/packages/pegjs/lib/compiler/opcodes.js @@ -18,6 +18,7 @@ const opcodes = { APPEND: 10, // APPEND WRAP: 11, // WRAP n TEXT: 12, // TEXT + PLUCK: 41, // PLUCK n, k, p1, ..., pK // Conditions and Loops diff --git a/packages/pegjs/lib/compiler/passes/generate-bytecode.js b/packages/pegjs/lib/compiler/passes/generate-bytecode.js index 5ab1c80..fc6d713 100644 --- a/packages/pegjs/lib/compiler/passes/generate-bytecode.js +++ b/packages/pegjs/lib/compiler/passes/generate-bytecode.js @@ -441,16 +441,19 @@ function generateBytecode( ast, session ) { sequence( node, context ) { + const TOTAL_ELEMENTS = node.elements.length; + function buildElementsCode( elements, context ) { if ( elements.length > 0 ) { - const processedCount = node.elements.length - elements.slice( 1 ).length; + const processedCount = TOTAL_ELEMENTS - elements.slice( 1 ).length; return buildSequence( generate( elements[ 0 ], { sp: context.sp, env: context.env, + pluck: context.pluck, action: null, reportFailures: context.reportFailures } ), @@ -460,6 +463,7 @@ function generateBytecode( ast, session ) { buildElementsCode( elements.slice( 1 ), { sp: context.sp + 1, env: context.env, + pluck: context.pluck, action: context.action, reportFailures: context.reportFailures } ), @@ -471,26 +475,32 @@ function generateBytecode( ast, session ) { ) ); - } else if ( context.action ) { + } + + if ( context.pluck.length > 0 ) - const functionIndex = addFunctionConst( - false, - Object.keys( context.env ), - context.action.code + return buildSequence( + [ op.PLUCK, TOTAL_ELEMENTS + 1, context.pluck.length ], + context.pluck.map( eSP => context.sp - eSP ) ); + if ( context.action ) + return buildSequence( - [ op.LOAD_SAVED_POS, node.elements.length ], + [ op.LOAD_SAVED_POS, TOTAL_ELEMENTS ], buildCall( - functionIndex, - node.elements.length + 1, + addFunctionConst( // functionIndex + false, + Object.keys( context.env ), + context.action.code + ), + TOTAL_ELEMENTS + 1, context.env, context.sp ) ); - } - return buildSequence( [ op.WRAP, node.elements.length ], [ op.NIP ] ); + return buildSequence( [ op.WRAP, TOTAL_ELEMENTS ], [ op.NIP ] ); } @@ -499,6 +509,7 @@ function generateBytecode( ast, session ) { buildElementsCode( node.elements, { sp: context.sp + 1, env: context.env, + pluck: [], action: context.action, reportFailures: context.reportFailures } ) @@ -508,9 +519,20 @@ function generateBytecode( ast, session ) { labeled( node, context ) { - const env = util.clone( context.env ); + let env = context.env; + const label = node.label; + const sp = context.sp + 1; + + if ( label !== null ) { + + env = util.clone( context.env ); + context.env[ label ] = sp; + + } + + if ( context.pluck && node.pick ) - context.env[ node.label ] = context.sp + 1; + context.pluck.push( sp ); return generate( node.expression, { sp: context.sp, diff --git a/packages/pegjs/lib/compiler/passes/generate-js.js b/packages/pegjs/lib/compiler/passes/generate-js.js index e939e51..ffe5fc1 100644 --- a/packages/pegjs/lib/compiler/passes/generate-js.js +++ b/packages/pegjs/lib/compiler/passes/generate-js.js @@ -379,7 +379,7 @@ function generateJS( ast, session, options ) { " var ends = [];", " var stack = [];", " var startPos = peg$currPos;", - " var params;" + " var params, paramsLength, paramsN;" ].join( "\n" ) ); } else { @@ -391,7 +391,7 @@ function generateJS( ast, session, options ) { " var end = bc.length;", " var ends = [];", " var stack = [];", - " var params;" + " var params, paramsLength, paramsN;" ].join( "\n" ) ); } @@ -472,6 +472,24 @@ function generateJS( ast, session, options ) { " ip++;", " break;", "", + " case " + op.PLUCK + ":", // PLUCK n, k, p1, ..., pK + " paramsLength = bc[ip + 2];", + " paramsN = 3 + paramsLength", + "", + " params = bc.slice(ip + 3, ip + paramsN);", + " params = paramsLength === 1", + " ? stack[stack.length - 1 - params[ 0 ]]", + " : params.map(function(p) { return stack[stack.length - 1 - p]; });", + "", + " stack.splice(", + " stack.length - bc[ip + 1],", + " bc[ip + 1],", + " params", + " );", + "", + " ip += paramsN;", + " break;", + "", " case " + op.IF + ":", // IF t, f indent10( generateCondition( "stack[stack.length - 1]", 0 ) ), "", @@ -825,6 +843,22 @@ function generateJS( ast, session, options ) { ip++; break; + case op.PLUCK: // PLUCK n, k, p1, ..., pK + const baseLength = 3; + const paramsLength = bc[ ip + baseLength - 1 ]; + const n = baseLength + paramsLength; + value = bc.slice( ip + baseLength, ip + n ); + value = paramsLength === 1 + ? stack.index( value[ 0 ] ) + : `[ ${ + value.map( p => stack.index( p ) ) + .join( ", " ) + } ]`; + stack.pop( bc[ ip + 1 ] ); + parts.push( stack.push( value ) ); + ip += n; + break; + case op.IF: // IF t, f compileCondition( stack.top(), 0 ); break; diff --git a/packages/pegjs/lib/compiler/passes/report-duplicate-labels.js b/packages/pegjs/lib/compiler/passes/report-duplicate-labels.js index bc9e79b..c4811a0 100644 --- a/packages/pegjs/lib/compiler/passes/report-duplicate-labels.js +++ b/packages/pegjs/lib/compiler/passes/report-duplicate-labels.js @@ -37,7 +37,7 @@ function reportDuplicateLabels( ast, session ) { const label = node.label; - if ( __hasOwnProperty.call( env, label ) ) { + if ( label && __hasOwnProperty.call( env, label ) ) { const start = env[ label ].start; @@ -49,7 +49,8 @@ function reportDuplicateLabels( ast, session ) { } check( node.expression, env ); - env[ label ] = node.location; + + if ( label ) env[ label ] = node.location; }, diff --git a/packages/pegjs/lib/compiler/passes/report-incorrect-plucking.js b/packages/pegjs/lib/compiler/passes/report-incorrect-plucking.js new file mode 100644 index 0000000..15f2370 --- /dev/null +++ b/packages/pegjs/lib/compiler/passes/report-incorrect-plucking.js @@ -0,0 +1,53 @@ +"use strict"; + +// +// Check if the given element's expression is of type `semantic_*` +// +function isSemanticPredicate( element ) { + + const type = element.expression.type; + + if ( type === "semantic_and" ) return true; + if ( type === "semantic_not" ) return true; + + return false; + +} + +// +// Compiler pass to ensure the following are enforced: +// +// - plucking can not be done with an action block +// - cannot pluck a semantic predicate +// +function reportIncorrectPlucking( ast, session ) { + + session.buildVisitor( { + + action( node ) { + + this.visit( node.expression, true ); + + }, + + labeled( node, action ) { + + if ( node.pick !== true ) return void 0; + + if ( action === true ) + + session.error( `"@" cannot be used with an action block.`, node.location ); + + if ( isSemanticPredicate( node ) ) + + session.error( `"@" cannot be used on a semantic predicate.`, node.location ); + + this.visit( node.expression ); + + }, + + } )( ast ); + +} + +module.exports = reportIncorrectPlucking; diff --git a/packages/pegjs/lib/parser.js b/packages/pegjs/lib/parser.js index e090bba..9d3161f 100644 --- a/packages/pegjs/lib/parser.js +++ b/packages/pegjs/lib/parser.js @@ -142,53 +142,54 @@ function peg$parse(input, options) { var peg$c0 = "="; var peg$c1 = "/"; - var peg$c2 = ":"; - var peg$c3 = "$"; - var peg$c4 = "&"; - var peg$c5 = "!"; - var peg$c6 = "?"; - var peg$c7 = "*"; - var peg$c8 = "+"; - var peg$c9 = "("; - var peg$c10 = ")"; - var peg$c11 = "\t"; - var peg$c12 = "\v"; - var peg$c13 = "\f"; - var peg$c14 = " "; - var peg$c15 = "\xA0"; - var peg$c16 = "\uFEFF"; - var peg$c17 = "\n"; - var peg$c18 = "\r\n"; - var peg$c19 = "\r"; - var peg$c20 = "\u2028"; - var peg$c21 = "\u2029"; - var peg$c22 = "/*"; - var peg$c23 = "*/"; - var peg$c24 = "//"; - var peg$c25 = "_"; - var peg$c26 = "\\"; - var peg$c27 = "\u200C"; - var peg$c28 = "\u200D"; - var peg$c29 = "i"; - var peg$c30 = "\""; - var peg$c31 = "'"; - var peg$c32 = "["; - var peg$c33 = "^"; - var peg$c34 = "]"; - var peg$c35 = "-"; - var peg$c36 = "0"; - var peg$c37 = "b"; - var peg$c38 = "f"; - var peg$c39 = "n"; - var peg$c40 = "r"; - var peg$c41 = "t"; - var peg$c42 = "v"; - var peg$c43 = "x"; - var peg$c44 = "u"; - var peg$c45 = "."; - var peg$c46 = "{"; - var peg$c47 = "}"; - var peg$c48 = ";"; + var peg$c2 = "@"; + var peg$c3 = ":"; + var peg$c4 = "$"; + var peg$c5 = "&"; + var peg$c6 = "!"; + var peg$c7 = "?"; + var peg$c8 = "*"; + var peg$c9 = "+"; + var peg$c10 = "("; + var peg$c11 = ")"; + var peg$c12 = "\t"; + var peg$c13 = "\v"; + var peg$c14 = "\f"; + var peg$c15 = " "; + var peg$c16 = "\xA0"; + var peg$c17 = "\uFEFF"; + var peg$c18 = "\n"; + var peg$c19 = "\r\n"; + var peg$c20 = "\r"; + var peg$c21 = "\u2028"; + var peg$c22 = "\u2029"; + var peg$c23 = "/*"; + var peg$c24 = "*/"; + var peg$c25 = "//"; + var peg$c26 = "_"; + var peg$c27 = "\\"; + var peg$c28 = "\u200C"; + var peg$c29 = "\u200D"; + var peg$c30 = "i"; + var peg$c31 = "\""; + var peg$c32 = "'"; + var peg$c33 = "["; + var peg$c34 = "^"; + var peg$c35 = "]"; + var peg$c36 = "-"; + var peg$c37 = "0"; + var peg$c38 = "b"; + var peg$c39 = "f"; + var peg$c40 = "n"; + var peg$c41 = "r"; + var peg$c42 = "t"; + var peg$c43 = "v"; + var peg$c44 = "x"; + var peg$c45 = "u"; + var peg$c46 = "."; + var peg$c47 = "{"; + var peg$c48 = "}"; + var peg$c49 = ";"; var peg$r0 = /^[\n\r\u2028\u2029]/; var peg$r1 = /^[0-9]/; @@ -208,30 +209,31 @@ function peg$parse(input, options) { var peg$e0 = peg$literalExpectation("=", false); var peg$e1 = peg$literalExpectation("/", false); - var peg$e2 = peg$literalExpectation(":", false); - var peg$e3 = peg$literalExpectation("$", false); - var peg$e4 = peg$literalExpectation("&", false); - var peg$e5 = peg$literalExpectation("!", false); - var peg$e6 = peg$literalExpectation("?", false); - var peg$e7 = peg$literalExpectation("*", false); - var peg$e8 = peg$literalExpectation("+", false); - var peg$e9 = peg$literalExpectation("(", false); - var peg$e10 = peg$literalExpectation(")", false); - var peg$e11 = peg$anyExpectation(); - var peg$e12 = peg$otherExpectation("whitespace"); - var peg$e13 = peg$classExpectation(["\n", "\r", "\u2028", "\u2029"], false, false); - var peg$e14 = peg$otherExpectation("end of line"); - var peg$e15 = peg$otherExpectation("comment"); - var peg$e16 = peg$literalExpectation("/*", false); - var peg$e17 = peg$literalExpectation("*/", false); - var peg$e18 = peg$literalExpectation("//", false); - var peg$e19 = peg$otherExpectation("identifier"); - var peg$e20 = peg$otherExpectation("literal"); - var peg$e21 = peg$otherExpectation("string"); - var peg$e22 = peg$otherExpectation("character class"); - var peg$e23 = peg$literalExpectation(".", false); - var peg$e24 = peg$otherExpectation("code block"); - var peg$e25 = peg$literalExpectation(";", false); + var peg$e2 = peg$literalExpectation("@", false); + var peg$e3 = peg$literalExpectation(":", false); + var peg$e4 = peg$literalExpectation("$", false); + var peg$e5 = peg$literalExpectation("&", false); + var peg$e6 = peg$literalExpectation("!", false); + var peg$e7 = peg$literalExpectation("?", false); + var peg$e8 = peg$literalExpectation("*", false); + var peg$e9 = peg$literalExpectation("+", false); + var peg$e10 = peg$literalExpectation("(", false); + var peg$e11 = peg$literalExpectation(")", false); + var peg$e12 = peg$anyExpectation(); + var peg$e13 = peg$otherExpectation("whitespace"); + var peg$e14 = peg$classExpectation(["\n", "\r", "\u2028", "\u2029"], false, false); + var peg$e15 = peg$otherExpectation("end of line"); + var peg$e16 = peg$otherExpectation("comment"); + var peg$e17 = peg$literalExpectation("/*", false); + var peg$e18 = peg$literalExpectation("*/", false); + var peg$e19 = peg$literalExpectation("//", false); + var peg$e20 = peg$otherExpectation("identifier"); + var peg$e21 = peg$otherExpectation("literal"); + var peg$e22 = peg$otherExpectation("string"); + var peg$e23 = peg$otherExpectation("character class"); + var peg$e24 = peg$literalExpectation(".", false); + var peg$e25 = peg$otherExpectation("code block"); + var peg$e26 = peg$literalExpectation(";", false); var peg$f0 = function(initializer, rules) { return new ast.Grammar( @@ -271,13 +273,32 @@ function peg$parse(input, options) { : expression; }; var peg$f5 = function(head, tail) { - return tail.length > 0 - ? createNode( "sequence", { - elements: buildList(head, tail, 1), - } ) - : head; + if ( tail.length < 1 ) + + return head.type === "labeled" && head.pick + ? createNode( "sequence", { elements: [ head ] } ) + : head; + + return createNode( "sequence", { + + elements: buildList( head, tail, 1 ), + + } ); }; var peg$f6 = function(label, expression) { + const [ name, location ] = extractOptional(label, 0) || []; + + 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, + } ); + }; + var peg$f7 = function(label, expression) { if (RESERVED_WORDS.indexOf(label[0]) >= 0) { error(`Label can't be a reserved word "${label[0]}".`, label[1]); } @@ -287,18 +308,18 @@ function peg$parse(input, options) { expression: expression, } ); }; - var peg$f7 = function(name) { return [name, location()]; }; - var peg$f8 = function(operator, expression) { + var peg$f8 = function(name) { return [name, location()]; }; + var peg$f9 = function(operator, expression) { return createNode( OPS_TO_PREFIXED_TYPES[operator], { expression: expression, } ); }; - var peg$f9 = function(expression, operator) { + var peg$f10 = function(expression, operator) { return createNode( OPS_TO_SUFFIXED_TYPES[operator], { expression: expression, } ); }; - var peg$f10 = function(expression) { + var peg$f11 = function(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 // nodes that already isolate label scope themselves. This leaves us with @@ -307,36 +328,36 @@ function peg$parse(input, options) { ? createNode( "group", { expression: expression } ) : expression; }; - var peg$f11 = function(name) { + var peg$f12 = function(name) { return createNode( "rule_ref", { name: name } ); }; - var peg$f12 = function(operator, code) { + var peg$f13 = function(operator, code) { return createNode( OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], { code: code } ); }; - var peg$f13 = function(comment) { + var peg$f14 = function(comment) { return addComment(comment, true); }; - var peg$f14 = function(comment) { + var peg$f15 = function(comment) { return addComment(comment, false); }; - var peg$f15 = function(head, tail) { return head + tail.join(""); }; - var peg$f16 = function(sequence) { return sequence; }; - var peg$f17 = function(value, ignoreCase) { + var peg$f16 = function(head, tail) { return head + tail.join(""); }; + var peg$f17 = function(sequence) { return sequence; }; + var peg$f18 = function(value, ignoreCase) { return createNode( "literal", { value: value, ignoreCase: ignoreCase !== null, } ); }; - var peg$f18 = function(chars) { return chars.join(""); }; - var peg$f19 = function() { return text(); }; - var peg$f20 = function(inverted, parts, ignoreCase) { + var peg$f19 = function(chars) { return chars.join(""); }; + var peg$f20 = function() { return text(); }; + var peg$f21 = function(inverted, parts, ignoreCase) { return createNode( "class", { parts: parts.filter(part => part !== ""), inverted: inverted !== null, ignoreCase: ignoreCase !== null, } ); }; - var peg$f21 = function(begin, end) { + var peg$f22 = function(begin, end) { if (begin.charCodeAt(0) > end.charCodeAt(0)) { error( "Invalid character range: " + text() + "." @@ -345,20 +366,20 @@ function peg$parse(input, options) { return [begin, end]; }; - var peg$f22 = function() { return ""; }; - var peg$f23 = function() { return "\0"; }; - var peg$f24 = function() { return "\b"; }; - var peg$f25 = function() { return "\f"; }; - var peg$f26 = function() { return "\n"; }; - var peg$f27 = function() { return "\r"; }; - var peg$f28 = function() { return "\t"; }; - var peg$f29 = function() { return "\v"; }; - var peg$f30 = function(digits) { + var peg$f23 = function() { return ""; }; + var peg$f24 = function() { return "\0"; }; + var peg$f25 = function() { return "\b"; }; + var peg$f26 = function() { return "\f"; }; + var peg$f27 = function() { return "\n"; }; + var peg$f28 = function() { return "\r"; }; + var peg$f29 = function() { return "\t"; }; + var peg$f30 = function() { return "\v"; }; + var peg$f31 = function(digits) { return String.fromCharCode(parseInt(digits, 16)); }; - var peg$f31 = function() { return createNode( "any", {} ); }; - var peg$f32 = function(code) { return code; }; - var peg$f33 = function() { error("Unbalanced brace."); }; + var peg$f32 = function() { return createNode( "any", {} ); }; + var peg$f33 = function(code) { return code; }; + var peg$f34 = function() { error("Unbalanced brace."); }; var peg$currPos = 0; var peg$savedPos = 0; @@ -830,26 +851,44 @@ function peg$parse(input, options) { } s0 = peg$currPos; - s1 = peg$parseIdentifierName(); + rule$expects(peg$e2); + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c2; + peg$currPos++; + } else { + s1 = peg$FAILED; + } if (s1 !== peg$FAILED) { - s2 = peg$parse__(); - rule$expects(peg$e2); - if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c2; - peg$currPos++; - } else { - s3 = peg$FAILED; - } + s2 = peg$currPos; + s3 = peg$parseIdentifierName(); if (s3 !== peg$FAILED) { s4 = peg$parse__(); - s5 = peg$parsePrefixedExpression(); + rule$expects(peg$e3); + if (input.charCodeAt(peg$currPos) === 58) { + s5 = peg$c3; + peg$currPos++; + } else { + s5 = peg$FAILED; + } if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f6(s1, s5); + s3 = [s3, s4, s5]; + s2 = s3; } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s2; + s2 = peg$FAILED; } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = peg$parse__(); + s4 = peg$parsePrefixedExpression(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s2, s4); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -859,7 +898,38 @@ function peg$parse(input, options) { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - s0 = peg$parsePrefixedExpression(); + s0 = peg$currPos; + s1 = peg$parseIdentifierName(); + if (s1 !== peg$FAILED) { + s2 = peg$parse__(); + rule$expects(peg$e3); + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c3; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s4 = peg$parse__(); + s5 = peg$parsePrefixedExpression(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parsePrefixedExpression(); + } } return s0; @@ -876,7 +946,7 @@ function peg$parse(input, options) { s1 = peg$parseIdentifier(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f7(s1); + s1 = peg$f8(s1); } s0 = s1; @@ -897,7 +967,7 @@ function peg$parse(input, options) { s3 = peg$parseSuffixedExpression(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f8(s1, s3); + s0 = peg$f9(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -920,25 +990,25 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e3); + rule$expects(peg$e4); if (input.charCodeAt(peg$currPos) === 36) { - s0 = peg$c3; + s0 = peg$c4; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - rule$expects(peg$e4); + rule$expects(peg$e5); if (input.charCodeAt(peg$currPos) === 38) { - s0 = peg$c4; + s0 = peg$c5; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - rule$expects(peg$e5); + rule$expects(peg$e6); if (input.charCodeAt(peg$currPos) === 33) { - s0 = peg$c5; + s0 = peg$c6; peg$currPos++; } else { s0 = peg$FAILED; @@ -963,7 +1033,7 @@ function peg$parse(input, options) { s3 = peg$parseSuffixedOperator(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f9(s1, s3); + s0 = peg$f10(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -986,25 +1056,25 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e6); + rule$expects(peg$e7); if (input.charCodeAt(peg$currPos) === 63) { - s0 = peg$c6; + s0 = peg$c7; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - rule$expects(peg$e7); + rule$expects(peg$e8); if (input.charCodeAt(peg$currPos) === 42) { - s0 = peg$c7; + s0 = peg$c8; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - rule$expects(peg$e8); + rule$expects(peg$e9); if (input.charCodeAt(peg$currPos) === 43) { - s0 = peg$c8; + s0 = peg$c9; peg$currPos++; } else { s0 = peg$FAILED; @@ -1033,9 +1103,9 @@ function peg$parse(input, options) { s0 = peg$parseSemanticPredicateExpression(); if (s0 === peg$FAILED) { s0 = peg$currPos; - rule$expects(peg$e9); + rule$expects(peg$e10); if (input.charCodeAt(peg$currPos) === 40) { - s1 = peg$c9; + s1 = peg$c10; peg$currPos++; } else { s1 = peg$FAILED; @@ -1045,16 +1115,16 @@ function peg$parse(input, options) { s3 = peg$parseChoiceExpression(); if (s3 !== peg$FAILED) { s4 = peg$parse__(); - rule$expects(peg$e10); + rule$expects(peg$e11); if (input.charCodeAt(peg$currPos) === 41) { - s5 = peg$c10; + s5 = peg$c11; peg$currPos++; } else { s5 = peg$FAILED; } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f10(s3); + s0 = peg$f11(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1126,7 +1196,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f11(s1); + s0 = peg$f12(s1); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1153,7 +1223,7 @@ function peg$parse(input, options) { s3 = peg$parseCodeBlock(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f12(s1, s3); + s0 = peg$f13(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1173,17 +1243,17 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e4); + rule$expects(peg$e5); if (input.charCodeAt(peg$currPos) === 38) { - s0 = peg$c4; + s0 = peg$c5; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - rule$expects(peg$e5); + rule$expects(peg$e6); if (input.charCodeAt(peg$currPos) === 33) { - s0 = peg$c5; + s0 = peg$c6; peg$currPos++; } else { s0 = peg$FAILED; @@ -1200,7 +1270,7 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e11); + rule$expects(peg$e12); if (input.length > peg$currPos) { s0 = input.charAt(peg$currPos); peg$currPos++; @@ -1218,45 +1288,45 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e12); + rule$expects(peg$e13); peg$silentFails++; if (input.charCodeAt(peg$currPos) === 9) { - s0 = peg$c11; + s0 = peg$c12; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 11) { - s0 = peg$c12; + s0 = peg$c13; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 12) { - s0 = peg$c13; + s0 = peg$c14; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 32) { - s0 = peg$c14; + s0 = peg$c15; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 160) { - s0 = peg$c15; + s0 = peg$c16; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 65279) { - s0 = peg$c16; + s0 = peg$c17; peg$currPos++; } else { s0 = peg$FAILED; @@ -1281,7 +1351,7 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e13); + rule$expects(peg$e14); if (peg$r0.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; @@ -1299,38 +1369,38 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e14); + rule$expects(peg$e15); peg$silentFails++; if (input.charCodeAt(peg$currPos) === 10) { - s0 = peg$c17; + s0 = peg$c18; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c18) { - s0 = peg$c18; + if (input.substr(peg$currPos, 2) === peg$c19) { + s0 = peg$c19; peg$currPos += 2; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 13) { - s0 = peg$c19; + s0 = peg$c20; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 8232) { - s0 = peg$c20; + s0 = peg$c21; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 8233) { - s0 = peg$c21; + s0 = peg$c22; peg$currPos++; } else { s0 = peg$FAILED; @@ -1351,7 +1421,7 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e15); + rule$expects(peg$e16); peg$silentFails++; s0 = peg$parseMultiLineComment(); if (s0 === peg$FAILED) { @@ -1370,8 +1440,8 @@ function peg$parse(input, options) { } s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c22) { - s1 = peg$c22; + if (input.substr(peg$currPos, 2) === peg$c23) { + s1 = peg$c23; peg$currPos += 2; } else { s1 = peg$FAILED; @@ -1382,8 +1452,8 @@ function peg$parse(input, options) { s4 = peg$currPos; s5 = peg$currPos; peg$begin(); - if (input.substr(peg$currPos, 2) === peg$c23) { - s6 = peg$c23; + if (input.substr(peg$currPos, 2) === peg$c24) { + s6 = peg$c24; peg$currPos += 2; } else { s6 = peg$FAILED; @@ -1413,8 +1483,8 @@ function peg$parse(input, options) { s4 = peg$currPos; s5 = peg$currPos; peg$begin(); - if (input.substr(peg$currPos, 2) === peg$c23) { - s6 = peg$c23; + if (input.substr(peg$currPos, 2) === peg$c24) { + s6 = peg$c24; peg$currPos += 2; } else { s6 = peg$FAILED; @@ -1441,15 +1511,15 @@ function peg$parse(input, options) { } } s2 = input.substring(s2, peg$currPos); - if (input.substr(peg$currPos, 2) === peg$c23) { - s3 = peg$c23; + if (input.substr(peg$currPos, 2) === peg$c24) { + s3 = peg$c24; peg$currPos += 2; } else { s3 = peg$FAILED; } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f13(s2); + s0 = peg$f14(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1470,9 +1540,9 @@ function peg$parse(input, options) { } s0 = peg$currPos; - rule$expects(peg$e16); - if (input.substr(peg$currPos, 2) === peg$c22) { - s1 = peg$c22; + rule$expects(peg$e17); + if (input.substr(peg$currPos, 2) === peg$c23) { + s1 = peg$c23; peg$currPos += 2; } else { s1 = peg$FAILED; @@ -1483,9 +1553,9 @@ function peg$parse(input, options) { s4 = peg$currPos; s5 = peg$currPos; peg$begin(); - rule$expects(peg$e17); - if (input.substr(peg$currPos, 2) === peg$c23) { - s6 = peg$c23; + rule$expects(peg$e18); + if (input.substr(peg$currPos, 2) === peg$c24) { + s6 = peg$c24; peg$currPos += 2; } else { s6 = peg$FAILED; @@ -1518,9 +1588,9 @@ function peg$parse(input, options) { s4 = peg$currPos; s5 = peg$currPos; peg$begin(); - rule$expects(peg$e17); - if (input.substr(peg$currPos, 2) === peg$c23) { - s6 = peg$c23; + rule$expects(peg$e18); + if (input.substr(peg$currPos, 2) === peg$c24) { + s6 = peg$c24; peg$currPos += 2; } else { s6 = peg$FAILED; @@ -1550,16 +1620,16 @@ function peg$parse(input, options) { } } s2 = input.substring(s2, peg$currPos); - rule$expects(peg$e17); - if (input.substr(peg$currPos, 2) === peg$c23) { - s3 = peg$c23; + rule$expects(peg$e18); + if (input.substr(peg$currPos, 2) === peg$c24) { + s3 = peg$c24; peg$currPos += 2; } else { s3 = peg$FAILED; } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f13(s2); + s0 = peg$f14(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1580,9 +1650,9 @@ function peg$parse(input, options) { } s0 = peg$currPos; - rule$expects(peg$e18); - if (input.substr(peg$currPos, 2) === peg$c24) { - s1 = peg$c24; + rule$expects(peg$e19); + if (input.substr(peg$currPos, 2) === peg$c25) { + s1 = peg$c25; peg$currPos += 2; } else { s1 = peg$FAILED; @@ -1643,7 +1713,7 @@ function peg$parse(input, options) { } s2 = input.substring(s2, peg$currPos); peg$savedPos = s0; - s0 = peg$f14(s2); + s0 = peg$f15(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1659,7 +1729,7 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e19); + rule$expects(peg$e20); peg$silentFails++; s0 = peg$currPos; s1 = peg$parseIdentifierStart(); @@ -1671,7 +1741,7 @@ function peg$parse(input, options) { s3 = peg$parseIdentifierPart(); } peg$savedPos = s0; - s0 = peg$f15(s1, s2); + s0 = peg$f16(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1691,14 +1761,14 @@ function peg$parse(input, options) { s0 = peg$parseUnicodeLetter(); if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 36) { - s0 = peg$c3; + s0 = peg$c4; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 95) { - s0 = peg$c25; + s0 = peg$c26; peg$currPos++; } else { s0 = peg$FAILED; @@ -1706,7 +1776,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c26; + s1 = peg$c27; peg$currPos++; } else { s1 = peg$FAILED; @@ -1715,7 +1785,7 @@ function peg$parse(input, options) { s2 = peg$parseUnicodeEscapeSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f16(s2); + s0 = peg$f17(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1747,14 +1817,14 @@ function peg$parse(input, options) { s0 = peg$parsePc(); if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 8204) { - s0 = peg$c27; + s0 = peg$c28; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 8205) { - s0 = peg$c28; + s0 = peg$c29; peg$currPos++; } else { s0 = peg$FAILED; @@ -1817,13 +1887,13 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e20); + rule$expects(peg$e21); peg$silentFails++; s0 = peg$currPos; s1 = peg$parseStringLiteral(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 105) { - s2 = peg$c29; + s2 = peg$c30; peg$currPos++; } else { s2 = peg$FAILED; @@ -1832,7 +1902,7 @@ function peg$parse(input, options) { s2 = null; } peg$savedPos = s0; - s0 = peg$f17(s1, s2); + s0 = peg$f18(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1849,11 +1919,11 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e21); + rule$expects(peg$e22); peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c30; + s1 = peg$c31; peg$currPos++; } else { s1 = peg$FAILED; @@ -1866,14 +1936,14 @@ function peg$parse(input, options) { s3 = peg$parseDoubleStringCharacter(); } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c30; + s3 = peg$c31; peg$currPos++; } else { s3 = peg$FAILED; } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f18(s2); + s0 = peg$f19(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1885,7 +1955,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 39) { - s1 = peg$c31; + s1 = peg$c32; peg$currPos++; } else { s1 = peg$FAILED; @@ -1898,14 +1968,14 @@ function peg$parse(input, options) { s3 = peg$parseSingleStringCharacter(); } if (input.charCodeAt(peg$currPos) === 39) { - s3 = peg$c31; + s3 = peg$c32; peg$currPos++; } else { s3 = peg$FAILED; } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f18(s2); + s0 = peg$f19(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1931,14 +2001,14 @@ function peg$parse(input, options) { s1 = peg$currPos; peg$begin(); if (input.charCodeAt(peg$currPos) === 34) { - s2 = peg$c30; + s2 = peg$c31; peg$currPos++; } else { s2 = peg$FAILED; } if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s2 = peg$c26; + s2 = peg$c27; peg$currPos++; } else { s2 = peg$FAILED; @@ -1958,7 +2028,7 @@ function peg$parse(input, options) { s2 = peg$parseSourceCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(); + s0 = peg$f20(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1970,7 +2040,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c26; + s1 = peg$c27; peg$currPos++; } else { s1 = peg$FAILED; @@ -1979,7 +2049,7 @@ function peg$parse(input, options) { s2 = peg$parseEscapeSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f16(s2); + s0 = peg$f17(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2007,14 +2077,14 @@ function peg$parse(input, options) { s1 = peg$currPos; peg$begin(); if (input.charCodeAt(peg$currPos) === 39) { - s2 = peg$c31; + s2 = peg$c32; peg$currPos++; } else { s2 = peg$FAILED; } if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s2 = peg$c26; + s2 = peg$c27; peg$currPos++; } else { s2 = peg$FAILED; @@ -2034,7 +2104,7 @@ function peg$parse(input, options) { s2 = peg$parseSourceCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(); + s0 = peg$f20(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2046,7 +2116,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c26; + s1 = peg$c27; peg$currPos++; } else { s1 = peg$FAILED; @@ -2055,7 +2125,7 @@ function peg$parse(input, options) { s2 = peg$parseEscapeSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f16(s2); + s0 = peg$f17(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2079,18 +2149,18 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e22); + rule$expects(peg$e23); peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 91) { - s1 = peg$c32; + s1 = peg$c33; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 94) { - s2 = peg$c33; + s2 = peg$c34; peg$currPos++; } else { s2 = peg$FAILED; @@ -2111,14 +2181,14 @@ function peg$parse(input, options) { } } if (input.charCodeAt(peg$currPos) === 93) { - s4 = peg$c34; + s4 = peg$c35; peg$currPos++; } else { s4 = peg$FAILED; } if (s4 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 105) { - s5 = peg$c29; + s5 = peg$c30; peg$currPos++; } else { s5 = peg$FAILED; @@ -2127,7 +2197,7 @@ function peg$parse(input, options) { s5 = null; } peg$savedPos = s0; - s0 = peg$f20(s2, s3, s5); + s0 = peg$f21(s2, s3, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2152,7 +2222,7 @@ function peg$parse(input, options) { s1 = peg$parseClassCharacter(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 45) { - s2 = peg$c35; + s2 = peg$c36; peg$currPos++; } else { s2 = peg$FAILED; @@ -2161,7 +2231,7 @@ function peg$parse(input, options) { s3 = peg$parseClassCharacter(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f21(s1, s3); + s0 = peg$f22(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2189,14 +2259,14 @@ function peg$parse(input, options) { s1 = peg$currPos; peg$begin(); if (input.charCodeAt(peg$currPos) === 93) { - s2 = peg$c34; + s2 = peg$c35; peg$currPos++; } else { s2 = peg$FAILED; } if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s2 = peg$c26; + s2 = peg$c27; peg$currPos++; } else { s2 = peg$FAILED; @@ -2216,7 +2286,7 @@ function peg$parse(input, options) { s2 = peg$parseSourceCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(); + s0 = peg$f20(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2228,7 +2298,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c26; + s1 = peg$c27; peg$currPos++; } else { s1 = peg$FAILED; @@ -2237,7 +2307,7 @@ function peg$parse(input, options) { s2 = peg$parseEscapeSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f16(s2); + s0 = peg$f17(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2263,7 +2333,7 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c26; + s1 = peg$c27; peg$currPos++; } else { s1 = peg$FAILED; @@ -2272,7 +2342,7 @@ function peg$parse(input, options) { s2 = peg$parseLineTerminatorSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f22(); + s0 = peg$f23(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2296,7 +2366,7 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 48) { - s1 = peg$c36; + s1 = peg$c37; peg$currPos++; } else { s1 = peg$FAILED; @@ -2314,7 +2384,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f23(); + s0 = peg$f24(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2357,21 +2427,21 @@ function peg$parse(input, options) { } if (input.charCodeAt(peg$currPos) === 39) { - s0 = peg$c31; + s0 = peg$c32; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s0 = peg$c30; + s0 = peg$c31; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s0 = peg$c26; + s0 = peg$c27; peg$currPos++; } else { s0 = peg$FAILED; @@ -2379,79 +2449,79 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 98) { - s1 = peg$c37; + s1 = peg$c38; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f24(); + s1 = peg$f25(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 102) { - s1 = peg$c38; + s1 = peg$c39; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f25(); + s1 = peg$f26(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 110) { - s1 = peg$c39; + s1 = peg$c40; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f26(); + s1 = peg$f27(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 114) { - s1 = peg$c40; + s1 = peg$c41; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f27(); + s1 = peg$f28(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 116) { - s1 = peg$c41; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f28(); + s1 = peg$f29(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 118) { - s1 = peg$c42; + s1 = peg$c43; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f29(); + s1 = peg$f30(); } s0 = s1; } @@ -2491,7 +2561,7 @@ function peg$parse(input, options) { s2 = peg$parseSourceCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(); + s0 = peg$f20(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2516,14 +2586,14 @@ function peg$parse(input, options) { s0 = peg$parseDecimalDigit(); if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 120) { - s0 = peg$c43; + s0 = peg$c44; peg$currPos++; } else { s0 = peg$FAILED; } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 117) { - s0 = peg$c44; + s0 = peg$c45; peg$currPos++; } else { s0 = peg$FAILED; @@ -2544,7 +2614,7 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 120) { - s1 = peg$c43; + s1 = peg$c44; peg$currPos++; } else { s1 = peg$FAILED; @@ -2573,7 +2643,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f30(s2); + s0 = peg$f31(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2595,7 +2665,7 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 117) { - s1 = peg$c44; + s1 = peg$c45; peg$currPos++; } else { s1 = peg$FAILED; @@ -2636,7 +2706,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f30(s2); + s0 = peg$f31(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2691,16 +2761,16 @@ function peg$parse(input, options) { } s0 = peg$currPos; - rule$expects(peg$e23); + rule$expects(peg$e24); if (input.charCodeAt(peg$currPos) === 46) { - s1 = peg$c45; + s1 = peg$c46; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f31(); + s1 = peg$f32(); } s0 = s1; @@ -2714,11 +2784,11 @@ function peg$parse(input, options) { if (peg$silentFails === 0) peg$expect(expected); } - rule$expects(peg$e24); + rule$expects(peg$e25); peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { - s1 = peg$c46; + s1 = peg$c47; peg$currPos++; } else { s1 = peg$FAILED; @@ -2726,14 +2796,14 @@ function peg$parse(input, options) { if (s1 !== peg$FAILED) { s2 = peg$parseCode(); if (input.charCodeAt(peg$currPos) === 125) { - s3 = peg$c47; + s3 = peg$c48; peg$currPos++; } else { s3 = peg$FAILED; } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f32(s2); + s0 = peg$f33(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2745,14 +2815,14 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { - s1 = peg$c46; + s1 = peg$c47; peg$currPos++; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f33(); + s1 = peg$f34(); } s0 = s1; } @@ -2839,7 +2909,7 @@ function peg$parse(input, options) { if (s2 === peg$FAILED) { s2 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { - s3 = peg$c46; + s3 = peg$c47; peg$currPos++; } else { s3 = peg$FAILED; @@ -2847,7 +2917,7 @@ function peg$parse(input, options) { if (s3 !== peg$FAILED) { s4 = peg$parseCode(); if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c47; + s5 = peg$c48; peg$currPos++; } else { s5 = peg$FAILED; @@ -2935,7 +3005,7 @@ function peg$parse(input, options) { if (s2 === peg$FAILED) { s2 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { - s3 = peg$c46; + s3 = peg$c47; peg$currPos++; } else { s3 = peg$FAILED; @@ -2943,7 +3013,7 @@ function peg$parse(input, options) { if (s3 !== peg$FAILED) { s4 = peg$parseCode(); if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c47; + s5 = peg$c48; peg$currPos++; } else { s5 = peg$FAILED; @@ -3214,9 +3284,9 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parse__(); - rule$expects(peg$e25); + rule$expects(peg$e26); if (input.charCodeAt(peg$currPos) === 59) { - s2 = peg$c48; + s2 = peg$c49; peg$currPos++; } else { s2 = peg$FAILED; @@ -3269,7 +3339,7 @@ function peg$parse(input, options) { s0 = peg$currPos; peg$begin(); - rule$expects(peg$e11); + rule$expects(peg$e12); if (input.length > peg$currPos) { s1 = input.charAt(peg$currPos); peg$currPos++; diff --git a/src/parser.pegjs b/src/parser.pegjs index 6b9f27d..d7da133 100644 --- a/src/parser.pegjs +++ b/src/parser.pegjs @@ -133,15 +133,34 @@ ActionExpression SequenceExpression = head:LabeledExpression tail:(__ LabeledExpression)* { - return tail.length > 0 - ? createNode( "sequence", { - elements: buildList(head, tail, 1), - } ) - : head; + if ( tail.length < 1 ) + + return head.type === "labeled" && head.pick + ? createNode( "sequence", { elements: [ head ] } ) + : head; + + return createNode( "sequence", { + + elements: buildList( head, tail, 1 ), + + } ); } LabeledExpression - = label:IdentifierName __ ":" __ expression:PrefixedExpression { + = "@" label:(IdentifierName __ ":")? __ expression:PrefixedExpression { + const [ name, location ] = extractOptional(label, 0) || []; + + 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 { if (RESERVED_WORDS.indexOf(label[0]) >= 0) { error(`Label can't be a reserved word "${label[0]}".`, label[1]); } diff --git a/test/spec/behavior/generated-parser-behavior.spec.js b/test/spec/behavior/generated-parser-behavior.spec.js index b603467..6e5cde0 100644 --- a/test/spec/behavior/generated-parser-behavior.spec.js +++ b/test/spec/behavior/generated-parser-behavior.spec.js @@ -10,20 +10,6 @@ describe( "generated parser behavior", function () { function varyOptimizationOptions( block ) { - function clone( object ) { - - const result = {}; - - Object.keys( object ).forEach( key => { - - result[ key ] = object[ key ]; - - } ); - - return result; - - } - const optionsVariants = [ { cache: false, optimize: "speed", trace: false }, { cache: false, optimize: "speed", trace: true }, @@ -41,7 +27,7 @@ describe( "generated parser behavior", function () { "with options " + chai.util.inspect( variant ), function () { - block( clone( variant ) ); + block( peg.util.clone( variant ) ); } ); @@ -1332,13 +1318,117 @@ describe( "generated parser behavior", function () { describe( "when all expressions match", function () { - it( "returns an array of their match results", function () { + function parser( description, edgecases ) { - const parser = peg.generate( "start = 'a' 'b' 'c'", options ); + it( description, () => { - expect( parser ).to.parse( "abc", [ "a", "b", "c" ] ); + edgecases.forEach( ( { grammar, input, output } ) => { - } ); + const parser = peg.generate( grammar, options ); + expect( parser ).to.parse( input, output ); + + } ); + + } ); + + } + + parser( "returns an array of their match results", [ + { + grammar: "start = 'a' 'b' 'c'", + input: "abc", + output: [ "a", "b", "c" ] + }, + ] ); + + parser( "plucking a single value", [ + { + grammar: "start = @'a'", + input: "a", + output: "a" + }, + { + grammar: "start = @'a' / @'b'", + input: "a", + output: "a" + }, + { + grammar: "start = @'a' / @'b'", + input: "b", + output: "b" + }, + { + grammar: "start = 'a' @'b' 'c'", + input: "abc", + output: "b" + }, + { + grammar: "start = 'a' ( @'b' 'c' )", + input: "abc", + output: [ "a", "b" ] + }, + { + grammar: "start = 'a' @( 'b' @'c' 'd' )", + input: "abcd", + output: "c" + }, + { + grammar: "start = 'a' ( @'b' 'c' ) @'d'", + input: "abcd", + output: "d" + }, + { + grammar: "start = 'a' @'b' 'c' / 'd' 'e' @'f'", + input: "def", + output: "f" + }, + ] ); + + parser( "plucking multiple values", [ + { + grammar: "start = 'a' @'b' @'c'", + input: "abc", + output: [ "b", "c" ] + }, + { + grammar: "start = 'a' ( @'b' @'c' )", + input: "abc", + output: [ "a", [ "b", "c" ] ] + }, + { + grammar: "start = 'a' @( 'b' @'c' @'d' )", + input: "abcd", + output: [ "c", "d" ] + }, + { + grammar: "start = 'a' @( @'b' 'c' ) @'d' 'e'", + input: "abcde", + output: [ "b", "d" ] + }, + { + grammar: "start = 'a' @'b' 'c' / @'d' 'e' @'f'", + input: "def", + output: [ "d", "f" ] + }, + ] ); + + parser( "plucking a value if a predicate doesnt fail", [ + { + grammar: "start = @'a' &{ return true; }", + input: "a", + output: "a" + }, + { + grammar: "start = @'a' !{ return false; }", + input: "a", + output: "a" + }, + { + grammar: "start = @n:[0-9] &{ return n > 0; }", + input: "2", + output: "2" + }, + ] ); } ); diff --git a/test/spec/unit/compiler/passes/generate-bytecode.spec.js b/test/spec/unit/compiler/passes/generate-bytecode.spec.js index 58c7fa4..b027f13 100644 --- a/test/spec/unit/compiler/passes/generate-bytecode.spec.js +++ b/test/spec/unit/compiler/passes/generate-bytecode.spec.js @@ -342,6 +342,50 @@ describe( "compiler pass |generateBytecode|", function () { } ); + it( "generates correct plucking bytecode", function () { + + expect( pass ).to.changeAST( "start = 'a' @'b' 'c'", bytecodeDetails( [ + 5, // PUSH_CURR_POS + 23, 0, 18, 0, 2, 1, 22, 0, 3, // + 15, 36, 3, // IF_NOT_ERROR + 23, 1, 18, 1, 2, 1, 22, 1, 3, // * + 15, 20, 4, // IF_NOT_ERROR + 23, 2, 18, 2, 2, 1, 22, 2, 3, // * + 15, 4, 4, // IF_NOT_ERROR + 41, 4, 1, 1, // * PLUCK + 8, 3, // * POP_N + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 8, 2, // * POP_N + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ] ) ); + + expect( pass ).to.changeAST( "start = 'a' @'b' @'c'", bytecodeDetails( [ + 5, // PUSH_CURR_POS + 23, 0, 18, 0, 2, 1, 22, 0, 3, // + 15, 37, 3, // IF_NOT_ERROR + 23, 1, 18, 1, 2, 1, 22, 1, 3, // * + 15, 21, 4, // IF_NOT_ERROR + 23, 2, 18, 2, 2, 1, 22, 2, 3, // * + 15, 5, 4, // IF_NOT_ERROR + 41, 4, 2, 1, 0, // * PLUCK + 8, 3, // * POP_N + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 8, 2, // * POP_N + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ] ) ); + + } ); + } ); describe( "for labeled", function () { diff --git a/test/spec/unit/compiler/passes/helpers.js b/test/spec/unit/compiler/passes/helpers.js index 026bce3..b6b04fe 100644 --- a/test/spec/unit/compiler/passes/helpers.js +++ b/test/spec/unit/compiler/passes/helpers.js @@ -72,6 +72,8 @@ module.exports = function ( chai, utils ) { if ( ! passed && typeof props !== "undefined" ) { + if ( typeof props === "string" ) props = { message: props }; + Object.keys( props ).forEach( key => { new Assertion( result ) diff --git a/test/spec/unit/compiler/passes/report-incorrect-plucking.spec.js b/test/spec/unit/compiler/passes/report-incorrect-plucking.spec.js new file mode 100644 index 0000000..0901a6c --- /dev/null +++ b/test/spec/unit/compiler/passes/report-incorrect-plucking.spec.js @@ -0,0 +1,52 @@ +"use strict"; + +const { expect, use } = require( "chai" ); +const helpers = require( "./helpers" ); +const pass = require( "pegjs" ).compiler.passes.check.reportIncorrectPlucking; + +use( helpers ); + +describe( "compiler pass |reportIncorrectPlucking|", function () { + + function reports( error, edgecases ) { + + it( error.slice( 0, -1 ), () => { + + edgecases.forEach( grammar => expect( pass ).to.reportError( grammar, error ) ); + + } ); + + } + + reports( `"@" cannot be used with an action block.`, [ + + `start1 = 'a' @'b' 'c' { /* empty action block */ }`, + `start2 = 'a' @('b' @'c' { /* empty action block */ })` + + ] ); + + reports( `"@" cannot be used on a semantic predicate.`, [ + + `start1 = 'a' @&{ /* semantic_and */ } 'c'`, + `start2 = 'a' @!{ /* semantic_not */ } 'c'` + + ] ); + + it( "allows valid plucking", function () { + + expect( pass ).not.to.reportError( ` + + start1 = @'1' // return '1' + start2 = @'1' / @'2' // return '1' or '2' + start2 = '1' @'2' '3' // return '2' + start3 = '1' @b:'2' '3' // return '2', label "b" can be used in semantic predicates + start4 = a:'1' @b:'2' '3' // return '2', labels "a" and "b" can be used in semantic predicates + start5 = @'1' @'2' '3' // return ['1', '2'] + start6 = @'1' @b:'2' '3' // return ['1', '2'], label "b" can be used in semantic predicates + start7 = a:'1' @'2' &{} // return '2' if the semantic predicate doesnt fail + + ` ); + + } ); + +} ); diff --git a/test/spec/unit/parser.spec.js b/test/spec/unit/parser.spec.js index 84796f8..bbd5ab0 100644 --- a/test/spec/unit/parser.spec.js +++ b/test/spec/unit/parser.spec.js @@ -425,6 +425,49 @@ describe( "PEG.js grammar parser", function () { } ); + // Value Plucking + it( "parses `@` (value plucking)", function () { + + function $S( ...elements ) { + + return oneRuleGrammar( { + type: "sequence", + elements: elements + } ); + + } + function $P( label, expression ) { + + return { + type: "labeled", + pick: true, + label: label || void 0, + expression: expression + }; + + } + + expect( "start = @'abcd'" ).to.parseAs( + $S( $P( null, literalAbcd ) ) + ); + expect( "start = @a:'abcd'" ).to.parseAs( + $S( $P( "a", literalAbcd ) ) + ); + expect( "start = 'abcd' @'efgh'" ).to.parseAs( + $S( literalAbcd, $P( null, literalEfgh ) ) + ); + expect( "start = a:'abcd' @b:'efgh'" ).to.parseAs( + $S( labeledAbcd, $P( "b", literalEfgh ) ) + ); + expect( "start = @'abcd' b:'efgh'" ).to.parseAs( + $S( $P( null, literalAbcd ), labeledEfgh ) + ); + expect( "start = a:'abcd' @'efgh' 'ijkl' @d:'mnop'" ).to.parseAs( + $S( labeledAbcd, $P( null, literalEfgh ), literalIjkl, $P( "d", literalMnop ) ) + ); + + } ); + // Canonical LabeledExpression is "a:'abcd'". it( "parses LabeledExpression", function () { @@ -605,7 +648,7 @@ describe( "PEG.js grammar parser", function () { trivialGrammar, [ { offset: 7, text: "abc", multiline: false } ], options ), options ); - expect( "start =//\n@\n'abcd'" ).to.failToParse(); + expect( "start =//\n>\n'abcd'" ).to.failToParse(); } );