From 534dc53ac2fe2bf00ac9178f9c7ac3fe6b91d619 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sat, 6 Jan 2018 02:28:09 +0500 Subject: [PATCH] Optimize redundant fail checks (#400) eliminate unnecessary checks for the result of parse, when it can be statically determined --- lib/compiler/index.js | 2 + lib/compiler/passes/generate-bytecode.js | 41 +- lib/compiler/passes/inference-match-result.js | 173 ++++ lib/parser.js | 810 +++++++----------- lib/typings/api.d.ts | 4 + .../passes/inference-match-result.spec.js | 164 ++++ 6 files changed, 663 insertions(+), 531 deletions(-) create mode 100644 lib/compiler/passes/inference-match-result.js create mode 100644 test/spec/unit/compiler/passes/inference-match-result.spec.js diff --git a/lib/compiler/index.js b/lib/compiler/index.js index d5375c5..f7ee24e 100644 --- a/lib/compiler/index.js +++ b/lib/compiler/index.js @@ -9,6 +9,7 @@ const reportDuplicateRules = require( "./passes/report-duplicate-rules" ); 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 visitor = require( "./visitor" ); function processOptions( options, defaults ) { @@ -58,6 +59,7 @@ const compiler = { }, generate: { calcReportFailures: calcReportFailures, + inferenceMatchResult: inferenceMatchResult, generateBytecode: generateBytecode, generateJS: generateJS } diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js index 9d91982..d5dda23 100644 --- a/lib/compiler/passes/generate-bytecode.js +++ b/lib/compiler/passes/generate-bytecode.js @@ -238,7 +238,10 @@ function generateBytecode( ast ) { } - function buildCondition( condCode, thenCode, elseCode ) { + function buildCondition( match, condCode, thenCode, elseCode ) { + + if ( match > 0 ) return thenCode; + if ( match < 0 ) return elseCode; return condCode.concat( [ thenCode.length, elseCode.length ], @@ -263,6 +266,7 @@ function generateBytecode( ast ) { function buildSimplePredicate( expression, negative, context ) { + const match = expression.match|0; return buildSequence( [ op.PUSH_CURR_POS ], [ op.EXPECT_NS_BEGIN ], @@ -274,6 +278,7 @@ function generateBytecode( ast ) { } ), [ op.EXPECT_NS_END, negative ? 1 : 0 ], buildCondition( + negative ? -match : match, [ negative ? op.IF_ERROR : op.IF_NOT_ERROR ], buildSequence( [ op.POP ], @@ -290,14 +295,15 @@ function generateBytecode( ast ) { } - function buildSemanticPredicate( code, negative, context ) { + function buildSemanticPredicate( node, negative, context ) { - const functionIndex = addFunctionConst( Object.keys( context.env ), code ); + const functionIndex = addFunctionConst( Object.keys( context.env ), node.code ); return buildSequence( [ op.UPDATE_SAVED_POS ], buildCall( functionIndex, 0, context.env, context.sp ), buildCondition( + node.match|0, [ op.IF ], buildSequence( [ op.POP ], negative ? [ op.PUSH_FAILED ] : [ op.PUSH_UNDEFINED ] ), buildSequence( [ op.POP ], negative ? [ op.PUSH_UNDEFINED ] : [ op.PUSH_FAILED ] ) @@ -371,6 +377,8 @@ function generateBytecode( ast ) { alternatives.length < 2 ? [] : buildCondition( + // If alternative always match no need generate code for next alternatives + -( alternatives[ 0 ].match|0 ), [ op.IF_ERROR ], buildSequence( [ op.POP ], @@ -396,7 +404,10 @@ function generateBytecode( ast ) { action: node, reportFailures: context.reportFailures } ); - const functionIndex = addFunctionConst( Object.keys( env ), node.code ); + const match = node.expression.match|0; + const functionIndex = emitCall && match >= 0 + ? addFunctionConst( Object.keys( env ), node.code ) + : null; return emitCall === false ? expressionCode @@ -404,6 +415,7 @@ function generateBytecode( ast ) { [ op.PUSH_CURR_POS ], expressionCode, buildCondition( + match, [ op.IF_NOT_ERROR ], buildSequence( [ op.LOAD_SAVED_POS, 1 ], @@ -432,6 +444,7 @@ function generateBytecode( ast ) { reportFailures: context.reportFailures } ), buildCondition( + elements[ 0 ].match|0, [ op.IF_NOT_ERROR ], buildElementsCode( elements.slice( 1 ), { sp: context.sp + 1, @@ -507,6 +520,7 @@ function generateBytecode( ast ) { reportFailures: context.reportFailures } ), buildCondition( + node.expression.match|0, [ op.IF_NOT_ERROR ], buildSequence( [ op.POP ], [ op.TEXT ] ), [ op.NIP ] @@ -537,6 +551,8 @@ function generateBytecode( ast ) { reportFailures: context.reportFailures } ), buildCondition( + // If expression always match no need replace FAILED to NULL + -( node.expression.match|0 ), [ op.IF_ERROR ], buildSequence( [ op.POP ], [ op.PUSH_NULL ] ), [] @@ -576,6 +592,7 @@ function generateBytecode( ast ) { [ op.PUSH_EMPTY_ARRAY ], expressionCode, buildCondition( + node.expression.match|0, [ op.IF_NOT_ERROR ], buildSequence( buildAppendLoop( expressionCode ), [ op.POP ] ), buildSequence( [ op.POP ], [ op.POP ], [ op.PUSH_FAILED ] ) @@ -597,13 +614,13 @@ function generateBytecode( ast ) { semantic_and( node, context ) { - return buildSemanticPredicate( node.code, false, context ); + return buildSemanticPredicate( node, false, context ); }, semantic_not( node, context ) { - return buildSemanticPredicate( node.code, true, context ); + return buildSemanticPredicate( node, true, context ); }, @@ -617,9 +634,11 @@ function generateBytecode( ast ) { if ( node.value.length > 0 ) { - const stringIndex = addConst( `"${ js.stringEscape( + const match = node.match|0; + const needConst = match === 0 || ( match > 0 && ! node.ignoreCase ); + const stringIndex = needConst ? addConst( `"${ js.stringEscape( node.ignoreCase ? node.value.toLowerCase() : node.value - ) }"` ); + ) }"` ) : null; // Do not generate unused constant, if no need it const expectedIndex = context.reportFailures ? addConst( "peg$literalExpectation(" @@ -634,6 +653,7 @@ function generateBytecode( ast ) { return buildSequence( context.reportFailures ? [ op.EXPECT, expectedIndex ] : [], buildCondition( + match, node.ignoreCase ? [ op.MATCH_STRING_IC, stringIndex ] : [ op.MATCH_STRING, stringIndex ], @@ -677,7 +697,8 @@ function generateBytecode( ast ) { .join( ", " ) + "]"; - const regexpIndex = addConst( regexp ); + const match = node.match|0; + const regexpIndex = match === 0 ? addConst( regexp ) : null; // Do not generate unused constant, if no need it const expectedIndex = context.reportFailures ? addConst( "peg$classExpectation(" @@ -690,6 +711,7 @@ function generateBytecode( ast ) { return buildSequence( context.reportFailures ? [ op.EXPECT, expectedIndex ] : [], buildCondition( + match, [ op.MATCH_REGEXP, regexpIndex ], [ op.ACCEPT_N, 1 ], [ op.PUSH_FAILED ] @@ -708,6 +730,7 @@ function generateBytecode( ast ) { return buildSequence( context.reportFailures ? [ op.EXPECT, expectedIndex ] : [], buildCondition( + node.match|0, [ op.MATCH_ANY ], [ op.ACCEPT_N, 1 ], [ op.PUSH_FAILED ] diff --git a/lib/compiler/passes/inference-match-result.js b/lib/compiler/passes/inference-match-result.js new file mode 100644 index 0000000..6e84f82 --- /dev/null +++ b/lib/compiler/passes/inference-match-result.js @@ -0,0 +1,173 @@ +"use strict"; + +const visitor = require( "../visitor" ); +const asts = require( "../asts" ); +const GrammarError = require( "../../grammar-error" ); + +// Inference match result of the rule. Can be: +// -1: negative result, always fails +// 0: neutral result, may be fail, may be match +// 1: positive result, always match +function inferenceMatchResult( ast ) { + + let inference; + function sometimesMatch( node ) { + + node.match = 0; + + return node.match; + + } + function alwaysMatch( node ) { + + inference( node.expression ); + + node.match = 1; + + return node.match; + + } + + function inferenceExpression( node ) { + + node.match = inference( node.expression ); + + return node.match; + + } + function inferenceElements( elements, forChoice ) { + + const length = elements.length; + let always = 0; + let never = 0; + + for ( let i = 0; i < length; ++i ) { + + const result = inference( elements[ i ] ); + + if ( result > 0 ) { + + ++always; + + } + if ( result < 0 ) { + + ++never; + + } + + } + + if ( always === length ) { + + return 1; + + } + if ( forChoice ) { + + return never === length ? -1 : 0; + + } + + return never > 0 ? -1 : 0; + + } + + inference = visitor.build( { + rule( node ) { + + let oldResult; + let count = 0; + + if ( typeof node.match === "undefined" ) { + + node.match = 0; + do { + + oldResult = node.match; + node.match = inference( node.expression ); + // 6 == 3! -- permutations count for all transitions from one match + // state to another. + // After 6 iterations the cycle with guarantee begins + // istanbul ignore next + if ( ++count > 6 ) { + + throw new GrammarError( + "Infinity cycle detected when trying evaluate node match result", + node.location + ); + + } + + } while ( oldResult !== node.match ); + + } + + return node.match; + + }, + named: inferenceExpression, + choice( node ) { + + node.match = inferenceElements( node.alternatives, true ); + + return node.match; + + }, + action: inferenceExpression, + sequence( node ) { + + node.match = inferenceElements( node.elements, false ); + + return node.match; + + }, + labeled: inferenceExpression, + text: inferenceExpression, + simple_and: inferenceExpression, + simple_not( node ) { + + node.match = -inference( node.expression ); + + return node.match; + + }, + optional: alwaysMatch, + zero_or_more: alwaysMatch, + one_or_more: inferenceExpression, + group: inferenceExpression, + semantic_and: sometimesMatch, + semantic_not: sometimesMatch, + rule_ref( node ) { + + const rule = asts.findRule( ast, node.name ); + node.match = inference( rule ); + + return node.match; + + }, + literal( node ) { + + // Empty literal always match on any input + node.match = node.value.length === 0 ? 1 : 0; + + return node.match; + + }, + class( node ) { + + // Empty character class never match on any input + node.match = node.parts.length === 0 ? -1 : 0; + + return node.match; + + }, + // |any| not match on empty input + any: sometimesMatch + } ); + + inference( ast ); + +} + +module.exports = inferenceMatchResult; diff --git a/lib/parser.js b/lib/parser.js index b81d9b3..4acb69a 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -645,75 +645,50 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parse__(); - if (s1 !== peg$FAILED) { - s2 = peg$currPos; - s3 = peg$parseInitializer(); - if (s3 !== peg$FAILED) { - s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - s3 = [s3, s4]; - s2 = s3; - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } - if (s2 === peg$FAILED) { - s2 = null; - } - if (s2 !== peg$FAILED) { - s3 = []; + s2 = peg$currPos; + s3 = peg$parseInitializer(); + if (s3 !== peg$FAILED) { + s4 = peg$parse__(); + s3 = [s3, s4]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = []; + s4 = peg$currPos; + s5 = peg$parseRule(); + if (s5 !== peg$FAILED) { + s6 = peg$parse__(); + s5 = [s5, s6]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); s4 = peg$currPos; s5 = peg$parseRule(); if (s5 !== peg$FAILED) { s6 = peg$parse__(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } + s5 = [s5, s6]; + s4 = s5; } else { peg$currPos = s4; s4 = peg$FAILED; } - if (s4 !== peg$FAILED) { - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$currPos; - s5 = peg$parseRule(); - if (s5 !== peg$FAILED) { - s6 = peg$parse__(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c0(s2, s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c0(s2, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -751,54 +726,34 @@ function peg$parse(input, options) { s1 = peg$parseIdentifierName(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - s3 = peg$currPos; - s4 = peg$parseStringLiteral(); - if (s4 !== peg$FAILED) { - s5 = peg$parse__(); - if (s5 !== peg$FAILED) { - s4 = [s4, s5]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - if (s3 === peg$FAILED) { - s3 = null; - } - if (s3 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c3); } - if (input.charCodeAt(peg$currPos) === 61) { - s4 = peg$c2; - peg$currPos++; - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - s5 = peg$parse__(); - if (s5 !== peg$FAILED) { - s6 = peg$parseChoiceExpression(); - if (s6 !== peg$FAILED) { - s7 = peg$parseEOS(); - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c4(s1, s3, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s3 = peg$currPos; + s4 = peg$parseStringLiteral(); + if (s4 !== peg$FAILED) { + s5 = peg$parse__(); + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 === peg$FAILED) { + s3 = null; + } + if (peg$silentFails === 0) { peg$expect(peg$c3); } + if (input.charCodeAt(peg$currPos) === 61) { + s4 = peg$c2; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$parse__(); + s6 = peg$parseChoiceExpression(); + if (s6 !== peg$FAILED) { + s7 = peg$parseEOS(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c4(s1, s3, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -828,29 +783,19 @@ function peg$parse(input, options) { s2 = []; s3 = peg$currPos; s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c6); } - if (input.charCodeAt(peg$currPos) === 47) { - s5 = peg$c5; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - s6 = peg$parse__(); - if (s6 !== peg$FAILED) { - s7 = peg$parseActionExpression(); - if (s7 !== peg$FAILED) { - s4 = [s4, s5, s6, s7]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } + if (peg$silentFails === 0) { peg$expect(peg$c6); } + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c5; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$parse__(); + s7 = peg$parseActionExpression(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; @@ -863,29 +808,19 @@ function peg$parse(input, options) { s2.push(s3); s3 = peg$currPos; s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c6); } - if (input.charCodeAt(peg$currPos) === 47) { - s5 = peg$c5; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - s6 = peg$parse__(); - if (s6 !== peg$FAILED) { - s7 = peg$parseActionExpression(); - if (s7 !== peg$FAILED) { - s4 = [s4, s5, s6, s7]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } + if (peg$silentFails === 0) { peg$expect(peg$c6); } + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c5; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s6 = peg$parse__(); + s7 = peg$parseActionExpression(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; @@ -895,13 +830,8 @@ function peg$parse(input, options) { s3 = peg$FAILED; } } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c7(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$c7(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -918,15 +848,10 @@ function peg$parse(input, options) { if (s1 !== peg$FAILED) { s2 = peg$currPos; s3 = peg$parse__(); - if (s3 !== peg$FAILED) { - s4 = peg$parseCodeBlock(); - if (s4 !== peg$FAILED) { - s3 = [s3, s4]; - s2 = s3; - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } + s4 = peg$parseCodeBlock(); + if (s4 !== peg$FAILED) { + s3 = [s3, s4]; + s2 = s3; } else { peg$currPos = s2; s2 = peg$FAILED; @@ -934,13 +859,8 @@ function peg$parse(input, options) { if (s2 === peg$FAILED) { s2 = null; } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c8(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$c8(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -958,15 +878,10 @@ function peg$parse(input, options) { s2 = []; s3 = peg$currPos; s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - s5 = peg$parseLabeledExpression(); - if (s5 !== peg$FAILED) { - s4 = [s4, s5]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } + s5 = peg$parseLabeledExpression(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; @@ -975,27 +890,17 @@ function peg$parse(input, options) { s2.push(s3); s3 = peg$currPos; s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - s5 = peg$parseLabeledExpression(); - if (s5 !== peg$FAILED) { - s4 = [s4, s5]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } + s5 = peg$parseLabeledExpression(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; } } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c9(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$c9(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1011,29 +916,19 @@ function peg$parse(input, options) { s1 = peg$parseIdentifier(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c11); } - if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c10; - peg$currPos++; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - s5 = peg$parsePrefixedExpression(); - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c12(s1, s5); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (peg$silentFails === 0) { peg$expect(peg$c11); } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c10; + 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$c12(s1, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1060,15 +955,10 @@ function peg$parse(input, options) { s1 = peg$parsePrefixedOperator(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - s3 = peg$parseSuffixedExpression(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c13(s1, s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s3 = peg$parseSuffixedExpression(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c13(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1123,15 +1013,10 @@ function peg$parse(input, options) { s1 = peg$parsePrimaryExpression(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - s3 = peg$parseSuffixedOperator(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c20(s1, s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s3 = peg$parseSuffixedOperator(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c20(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1202,29 +1087,19 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - s3 = peg$parseChoiceExpression(); - if (s3 !== peg$FAILED) { - s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c30); } - if (input.charCodeAt(peg$currPos) === 41) { - s5 = peg$c29; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c31(s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s3 = peg$parseChoiceExpression(); + if (s3 !== peg$FAILED) { + s4 = peg$parse__(); + if (peg$silentFails === 0) { peg$expect(peg$c30); } + if (input.charCodeAt(peg$currPos) === 41) { + s5 = peg$c29; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c31(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1253,47 +1128,32 @@ function peg$parse(input, options) { s1 = peg$parseIdentifierName(); if (s1 !== peg$FAILED) { s2 = peg$currPos; - peg$begin(); - s3 = peg$currPos; - s4 = peg$parse__(); - if (s4 !== peg$FAILED) { - s5 = peg$currPos; - s6 = peg$parseStringLiteral(); - if (s6 !== peg$FAILED) { - s7 = peg$parse__(); - if (s7 !== peg$FAILED) { - s6 = [s6, s7]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - if (s5 === peg$FAILED) { - s5 = null; - } - if (s5 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c3); } - if (input.charCodeAt(peg$currPos) === 61) { - s6 = peg$c2; - peg$currPos++; - } else { - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - s4 = [s4, s5, s6]; - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } + peg$begin(); + s3 = peg$currPos; + s4 = peg$parse__(); + s5 = peg$currPos; + s6 = peg$parseStringLiteral(); + if (s6 !== peg$FAILED) { + s7 = peg$parse__(); + s6 = [s6, s7]; + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + if (s5 === peg$FAILED) { + s5 = null; + } + if (peg$silentFails === 0) { peg$expect(peg$c3); } + if (input.charCodeAt(peg$currPos) === 61) { + s6 = peg$c2; + peg$currPos++; + } else { + s6 = peg$FAILED; + } + if (s6 !== peg$FAILED) { + s4 = [s4, s5, s6]; + s3 = s4; } else { peg$currPos = s3; s3 = peg$FAILED; @@ -1327,15 +1187,10 @@ function peg$parse(input, options) { s1 = peg$parseSemanticPredicateOperator(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (s2 !== peg$FAILED) { - s3 = peg$parseCodeBlock(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c33(s1, s3); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s3 = peg$parseCodeBlock(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c33(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1593,20 +1448,15 @@ function peg$parse(input, options) { s3 = peg$FAILED; } } - if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c52) { - s3 = peg$c52; - peg$currPos += 2; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (input.substr(peg$currPos, 2) === peg$c52) { + s3 = peg$c52; + peg$currPos += 2; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1701,21 +1551,16 @@ function peg$parse(input, options) { s3 = peg$FAILED; } } - if (s2 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c54); } - if (input.substr(peg$currPos, 2) === peg$c52) { - s3 = peg$c52; - peg$currPos += 2; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (peg$silentFails === 0) { peg$expect(peg$c54); } + if (input.substr(peg$currPos, 2) === peg$c52) { + s3 = peg$c52; + peg$currPos += 2; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1792,13 +1637,8 @@ function peg$parse(input, options) { s3 = peg$FAILED; } } - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s1 = [s1, s2]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1852,13 +1692,8 @@ function peg$parse(input, options) { s2.push(s3); s3 = peg$parseIdentifierPart(); } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c59(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$c59(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2140,13 +1975,8 @@ function peg$parse(input, options) { if (s2 === peg$FAILED) { s2 = null; } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c71(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$c71(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2175,20 +2005,15 @@ function peg$parse(input, options) { s2.push(s3); s3 = peg$parseDoubleStringCharacter(); } - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c73; - peg$currPos++; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c74(s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (input.charCodeAt(peg$currPos) === 34) { + s3 = peg$c73; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c74(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2212,20 +2037,15 @@ function peg$parse(input, options) { s2.push(s3); s3 = peg$parseSingleStringCharacter(); } - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 39) { - s3 = peg$c75; - peg$currPos++; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c74(s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (input.charCodeAt(peg$currPos) === 39) { + s3 = peg$c75; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c74(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2406,51 +2226,36 @@ function peg$parse(input, options) { if (s2 === peg$FAILED) { s2 = null; } - if (s2 !== peg$FAILED) { - s3 = []; + s3 = []; + s4 = peg$parseClassCharacterRange(); + if (s4 === peg$FAILED) { + s4 = peg$parseClassCharacter(); + } + while (s4 !== peg$FAILED) { + s3.push(s4); s4 = peg$parseClassCharacterRange(); if (s4 === peg$FAILED) { s4 = peg$parseClassCharacter(); } - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseClassCharacterRange(); - if (s4 === peg$FAILED) { - s4 = peg$parseClassCharacter(); - } - } - if (s3 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 93) { - s4 = peg$c80; - peg$currPos++; - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 105) { - s5 = peg$c70; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 === peg$FAILED) { - s5 = null; - } - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c81(s2, s3, s5); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + } + if (input.charCodeAt(peg$currPos) === 93) { + s4 = peg$c80; + peg$currPos++; + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 105) { + s5 = peg$c70; + peg$currPos++; } else { - peg$currPos = s0; - s0 = peg$FAILED; + s5 = peg$FAILED; } + if (s5 === peg$FAILED) { + s5 = null; + } + peg$savedPos = s0; + s0 = peg$c81(s2, s3, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2994,20 +2799,15 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { s2 = peg$parseCode(); - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 125) { - s3 = peg$c111; - peg$currPos++; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$c112(s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c111; + peg$currPos++; + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$c112(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -3116,20 +2916,15 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { s4 = peg$parseCode(); - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c111; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - s3 = [s3, s4, s5]; - s2 = s3; - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } + if (input.charCodeAt(peg$currPos) === 125) { + s5 = peg$c111; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s3 = [s3, s4, s5]; + s2 = s3; } else { peg$currPos = s2; s2 = peg$FAILED; @@ -3217,20 +3012,15 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { s4 = peg$parseCode(); - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c111; - peg$currPos++; - } else { - s5 = peg$FAILED; - } - if (s5 !== peg$FAILED) { - s3 = [s3, s4, s5]; - s2 = s3; - } else { - peg$currPos = s2; - s2 = peg$FAILED; - } + if (input.charCodeAt(peg$currPos) === 125) { + s5 = peg$c111; + peg$currPos++; + } else { + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + s3 = [s3, s4, s5]; + s2 = s3; } else { peg$currPos = s2; s2 = peg$FAILED; @@ -3241,11 +3031,7 @@ function peg$parse(input, options) { } } } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } + s0 = input.substring(s0, peg$currPos); return s0; } @@ -4784,21 +4570,16 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parse__(); - if (s1 !== peg$FAILED) { - if (peg$silentFails === 0) { peg$expect(peg$c209); } - if (input.charCodeAt(peg$currPos) === 59) { - s2 = peg$c208; - peg$currPos++; - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + if (peg$silentFails === 0) { peg$expect(peg$c209); } + if (input.charCodeAt(peg$currPos) === 59) { + s2 = peg$c208; + peg$currPos++; + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; @@ -4806,24 +4587,14 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = peg$parseSingleLineComment(); - if (s2 === peg$FAILED) { - s2 = null; - } - if (s2 !== peg$FAILED) { - s3 = peg$parseLineTerminatorSequence(); - if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s2 = peg$parseSingleLineComment(); + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = peg$parseLineTerminatorSequence(); + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; @@ -4831,15 +4602,10 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; s1 = peg$parse__(); - if (s1 !== peg$FAILED) { - s2 = peg$parseEOF(); - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s2 = peg$parseEOF(); + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; diff --git a/lib/typings/api.d.ts b/lib/typings/api.d.ts index 7f9a484..fd8a855 100644 --- a/lib/typings/api.d.ts +++ b/lib/typings/api.d.ts @@ -98,6 +98,10 @@ declare namespace peg { reportFailures?: boolean; + // Added by inference-match-result pass + + match?: number; + } interface Named extends INode { diff --git a/test/spec/unit/compiler/passes/inference-match-result.spec.js b/test/spec/unit/compiler/passes/inference-match-result.spec.js new file mode 100644 index 0000000..5e2f3de --- /dev/null +++ b/test/spec/unit/compiler/passes/inference-match-result.spec.js @@ -0,0 +1,164 @@ +"use strict"; + +const chai = require( "chai" ); +const helpers = require( "./helpers" ); +const pass = require( "pegjs-dev" ).compiler.passes.generate.inferenceMatchResult; + +chai.use( helpers ); + +const expect = chai.expect; + +describe( "compiler pass |inferenceMatchResult|", function () { + + it( "calculate |match| property for |any| correctly", function () { + + expect( pass ).to.changeAST( "start = . ", { rules: [ { match: 0 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |literal| correctly", function () { + + expect( pass ).to.changeAST( "start = '' ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = ''i ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = 'a' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = 'a'i ", { rules: [ { match: 0 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |class| correctly", function () { + + expect( pass ).to.changeAST( "start = [] ", { rules: [ { match: -1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = []i ", { rules: [ { match: -1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [a] ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [a]i ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [a-b] ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [a-b]i ", { rules: [ { match: 0 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |sequence| correctly", function () { + + expect( pass ).to.changeAST( "start = 'a' 'b' ", { rules: [ { match: 0 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = 'a' '' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' 'b' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' '' ", { rules: [ { match: 1 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = 'a' [] ", { rules: [ { match: -1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] 'b' ", { rules: [ { match: -1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] [] ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |choice| correctly", function () { + + expect( pass ).to.changeAST( "start = 'a' / 'b' ", { rules: [ { match: 0 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = 'a' / '' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' / 'b' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' / '' ", { rules: [ { match: 1 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = 'a' / [] ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] / 'b' ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] / [] ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for predicates correctly", function () { + + expect( pass ).to.changeAST( "start = &. ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = &'' ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = &[] ", { rules: [ { match: -1 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = !. ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = !'' ", { rules: [ { match: -1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = ![] ", { rules: [ { match: 1 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = &{ code } ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = !{ code } ", { rules: [ { match: 0 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |text| correctly", function () { + + expect( pass ).to.changeAST( "start = $. ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = $'' ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = $[] ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |action| correctly", function () { + + expect( pass ).to.changeAST( "start = . { code }", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' { code }", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] { code }", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |labeled| correctly", function () { + + expect( pass ).to.changeAST( "start = a:. ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = a:'' ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = a:[] ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |named| correctly", function () { + + expect( pass ).to.changeAST( "start 'start' = . ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start 'start' = '' ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start 'start' = [] ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |optional| correctly", function () { + + expect( pass ).to.changeAST( "start = .? ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = ''? ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = []? ", { rules: [ { match: 1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |zero_or_more| correctly", function () { + + expect( pass ).to.changeAST( "start = .* ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = ''* ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = []* ", { rules: [ { match: 1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |one_or_more| correctly", function () { + + expect( pass ).to.changeAST( "start = .+ ", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = ''+ ", { rules: [ { match: 1 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = []+ ", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + + it( "calculate |match| property for |rule_ref| correctly", function () { + + expect( pass ).to.changeAST( + [ "start = end", "end = . " ].join( "\n" ), + { rules: [ { match: 0 }, { match: 0 } ] }, + {}, {} + ); + expect( pass ).to.changeAST( + [ "start = end", "end = ''" ].join( "\n" ), + { rules: [ { match: 1 }, { match: 1 } ] }, + {}, {} + ); + expect( pass ).to.changeAST( + [ "start = end", "end = []" ].join( "\n" ), + { rules: [ { match: -1 }, { match: -1 } ] }, + {}, {} + ); + + expect( pass ).to.changeAST( "start = . start", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = '' start", { rules: [ { match: 0 } ] }, {}, {} ); + expect( pass ).to.changeAST( "start = [] start", { rules: [ { match: -1 } ] }, {}, {} ); + + expect( pass ).to.changeAST( "start = . start []", { rules: [ { match: -1 } ] }, {}, {} ); + + } ); + +} );