|
|
|
"use strict";
|
|
|
|
|
|
|
|
const GrammarError = require( "../../grammar-error" );
|
|
|
|
const asts = require( "../asts" );
|
|
|
|
const visitor = require( "../visitor" );
|
|
|
|
|
|
|
|
// Reports left recursion in the grammar, which prevents infinite recursion in
|
|
|
|
// the generated parser.
|
|
|
|
//
|
|
|
|
// Both direct and indirect recursion is detected. The pass also correctly
|
|
|
|
// reports cases like this:
|
|
|
|
//
|
|
|
|
// start = "a"? start
|
|
|
|
//
|
|
|
|
// In general, if a rule reference can be reached without consuming any input,
|
|
|
|
// it can lead to left recursion.
|
|
|
|
function reportInfiniteRecursion( ast ) {
|
|
|
|
|
|
|
|
const visitedRules = [];
|
|
|
|
|
|
|
|
const check = visitor.build( {
|
|
|
|
rule( node ) {
|
|
|
|
|
|
|
|
visitedRules.push( node.name );
|
|
|
|
check( node.expression );
|
|
|
|
visitedRules.pop( node.name );
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
sequence( node ) {
|
|
|
|
|
|
|
|
node.elements.every( element => {
|
|
|
|
|
|
|
|
check( element );
|
|
|
|
|
|
|
|
return ! asts.alwaysConsumesOnSuccess( ast, element );
|
|
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
rule_ref( node ) {
|
|
|
|
|
|
|
|
if ( visitedRules.indexOf( node.name ) !== -1 ) {
|
|
|
|
|
|
|
|
visitedRules.push( node.name );
|
|
|
|
const rulePath = visitedRules.join( " -> " );
|
|
|
|
|
|
|
|
throw new GrammarError(
|
|
|
|
`Possible infinite loop when parsing (left recursion: ${ rulePath }).`,
|
|
|
|
node.location
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
check( asts.findRule( ast, node.name ) );
|
|
|
|
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
check( ast );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = reportInfiniteRecursion;
|