Optimize redundant fail checks (#400)
eliminate unnecessary checks for the result of parse, when it can be statically determinedmaster
parent
f5699d63fb
commit
534dc53ac2
@ -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;
|
File diff suppressed because it is too large
Load Diff
@ -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 } ] }, {}, {} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
Loading…
Reference in New Issue