Optimize silent fails: remove checks that always false (#399)

* Optimization: do not generate unreachable calls |peg$fail| and constants for it (local to the rule).
* Optimization: do not generate unreachable calls |peg$fail| and constants for it (non-local to rule).
master
Mingun 6 years ago committed by Futago-za Ryuu
parent a7a0a0d9ac
commit 42ec335d13

@ -1,5 +1,6 @@
"use strict"; "use strict";
const calcReportFailures = require( "./passes/calc-report-failures" );
const generateBytecode = require( "./passes/generate-bytecode" ); const generateBytecode = require( "./passes/generate-bytecode" );
const generateJS = require( "./passes/generate-js" ); const generateJS = require( "./passes/generate-js" );
const removeProxyRules = require( "./passes/remove-proxy-rules" ); const removeProxyRules = require( "./passes/remove-proxy-rules" );
@ -56,6 +57,7 @@ const compiler = {
removeProxyRules: removeProxyRules removeProxyRules: removeProxyRules
}, },
generate: { generate: {
calcReportFailures: calcReportFailures,
generateBytecode: generateBytecode, generateBytecode: generateBytecode,
generateJS: generateJS generateJS: generateJS
} }

@ -0,0 +1,68 @@
"use strict";
const asts = require( "../asts" );
const visitor = require( "../visitor" );
// Determines if rule always used in disabled report failure context,
// that means, that any failures, reported within it, are never will be
// visible, so the no need to report it.
function calcReportFailures( ast, options ) {
// By default, not report failures for rules...
ast.rules.forEach( rule => {
rule.reportFailures = false;
} );
// ...but report for start rules, because in that context report failures
// always enabled
const changedRules = options.allowedStartRules.map( name => {
const rule = asts.findRule( ast, name );
rule.reportFailures = true;
return rule;
} );
const calc = visitor.build( {
rule( node ) {
calc( node.expression );
},
// Because all rules already by default marked as not report any failures
// just break AST traversing when we need mark all referenced rules from
// this sub-AST as always not report anything (of course if it not be
// already marked as report failures).
named() {},
rule_ref( node ) {
const rule = asts.findRule( ast, node.name );
// This function only called when rule can report failures. If so, we
// need recalculate all rules that referenced from it. But do not do
// this twice - if rule is already marked, it was in `changedRules`.
if ( ! rule.reportFailures ) {
rule.reportFailures = true;
changedRules.push( rule );
}
}
} );
while ( changedRules.length > 0 ) {
calc( changedRules.pop() );
}
}
module.exports = calcReportFailures;

@ -269,7 +269,8 @@ function generateBytecode( ast ) {
generate( expression, { generate( expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ), } ),
[ op.EXPECT_NS_END, negative ? 1 : 0 ], [ op.EXPECT_NS_END, negative ? 1 : 0 ],
buildCondition( buildCondition(
@ -325,25 +326,34 @@ function generateBytecode( ast ) {
rule( node ) { rule( node ) {
node.bytecode = generate( node.expression, { node.bytecode = generate( node.expression, {
sp: -1, // stack pointer sp: -1, // stack pointer
env: { }, // mapping of label names to stack positions env: { }, // mapping of label names to stack positions
action: null // action nodes pass themselves to children here action: null, // action nodes pass themselves to children here
reportFailures: node.reportFailures // if `false`, suppress generation of EXPECT opcodes
} ); } );
}, },
named( node, context ) { named( node, context ) {
const nameIndex = addConst( // Do not generate unused constant, if no need it
const nameIndex = context.reportFailures ? addConst(
`peg$otherExpectation("${ js.stringEscape( node.name ) }")` `peg$otherExpectation("${ js.stringEscape( node.name ) }")`
); ) : null;
const expressionCode = generate( node.expression, {
sp: context.sp,
env: context.env,
action: context.action,
reportFailures: false
} );
return buildSequence( // No need to disable report failures if it already disabled
return context.reportFailures ? buildSequence(
[ op.EXPECT, nameIndex ], [ op.EXPECT, nameIndex ],
[ op.SILENT_FAILS_ON ], [ op.SILENT_FAILS_ON ],
generate( node.expression, context ), expressionCode,
[ op.SILENT_FAILS_OFF ] [ op.SILENT_FAILS_OFF ]
); ) : expressionCode;
}, },
@ -355,7 +365,8 @@ function generateBytecode( ast ) {
generate( alternatives[ 0 ], { generate( alternatives[ 0 ], {
sp: context.sp, sp: context.sp,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ), } ),
alternatives.length < 2 alternatives.length < 2
? [] ? []
@ -382,7 +393,8 @@ function generateBytecode( ast ) {
const expressionCode = generate( node.expression, { const expressionCode = generate( node.expression, {
sp: context.sp + ( emitCall ? 1 : 0 ), sp: context.sp + ( emitCall ? 1 : 0 ),
env: env, env: env,
action: node action: node,
reportFailures: context.reportFailures
} ); } );
const functionIndex = addFunctionConst( Object.keys( env ), node.code ); const functionIndex = addFunctionConst( Object.keys( env ), node.code );
@ -416,14 +428,16 @@ function generateBytecode( ast ) {
generate( elements[ 0 ], { generate( elements[ 0 ], {
sp: context.sp, sp: context.sp,
env: context.env, env: context.env,
action: null action: null,
reportFailures: context.reportFailures
} ), } ),
buildCondition( buildCondition(
[ op.IF_NOT_ERROR ], [ op.IF_NOT_ERROR ],
buildElementsCode( elements.slice( 1 ), { buildElementsCode( elements.slice( 1 ), {
sp: context.sp + 1, sp: context.sp + 1,
env: context.env, env: context.env,
action: context.action action: context.action,
reportFailures: context.reportFailures
} ), } ),
buildSequence( buildSequence(
processedCount > 1 ? [ op.POP_N, processedCount ] : [ op.POP ], processedCount > 1 ? [ op.POP_N, processedCount ] : [ op.POP ],
@ -460,7 +474,8 @@ function generateBytecode( ast ) {
buildElementsCode( node.elements, { buildElementsCode( node.elements, {
sp: context.sp + 1, sp: context.sp + 1,
env: context.env, env: context.env,
action: context.action action: context.action,
reportFailures: context.reportFailures
} ) } )
); );
@ -475,7 +490,8 @@ function generateBytecode( ast ) {
return generate( node.expression, { return generate( node.expression, {
sp: context.sp, sp: context.sp,
env: env, env: env,
action: null action: null,
reportFailures: context.reportFailures
} ); } );
}, },
@ -487,7 +503,8 @@ function generateBytecode( ast ) {
generate( node.expression, { generate( node.expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ), } ),
buildCondition( buildCondition(
[ op.IF_NOT_ERROR ], [ op.IF_NOT_ERROR ],
@ -516,7 +533,8 @@ function generateBytecode( ast ) {
generate( node.expression, { generate( node.expression, {
sp: context.sp, sp: context.sp,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ), } ),
buildCondition( buildCondition(
[ op.IF_ERROR ], [ op.IF_ERROR ],
@ -532,7 +550,8 @@ function generateBytecode( ast ) {
const expressionCode = generate( node.expression, { const expressionCode = generate( node.expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ); } );
return buildSequence( return buildSequence(
@ -549,7 +568,8 @@ function generateBytecode( ast ) {
const expressionCode = generate( node.expression, { const expressionCode = generate( node.expression, {
sp: context.sp + 1, sp: context.sp + 1,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ); } );
return buildSequence( return buildSequence(
@ -569,7 +589,8 @@ function generateBytecode( ast ) {
return generate( node.expression, { return generate( node.expression, {
sp: context.sp, sp: context.sp,
env: cloneEnv( context.env ), env: cloneEnv( context.env ),
action: null action: null,
reportFailures: context.reportFailures
} ); } );
}, },
@ -592,25 +613,26 @@ function generateBytecode( ast ) {
}, },
literal( node ) { literal( node, context ) {
if ( node.value.length > 0 ) { if ( node.value.length > 0 ) {
const stringIndex = addConst( `"${ js.stringEscape( const stringIndex = addConst( `"${ js.stringEscape(
node.ignoreCase ? node.value.toLowerCase() : node.value node.ignoreCase ? node.value.toLowerCase() : node.value
) }"` ); ) }"` );
const expectedIndex = addConst( // Do not generate unused constant, if no need it
const expectedIndex = context.reportFailures ? addConst(
"peg$literalExpectation(" "peg$literalExpectation("
+ `"${ js.stringEscape( node.value ) }", ` + `"${ js.stringEscape( node.value ) }", `
+ node.ignoreCase + node.ignoreCase
+ ")" + ")"
); ) : null;
// For case-sensitive strings the value must match the beginning of the // For case-sensitive strings the value must match the beginning of the
// remaining input exactly. As a result, we can use |ACCEPT_STRING| and // remaining input exactly. As a result, we can use |ACCEPT_STRING| and
// save one |substr| call that would be needed if we used |ACCEPT_N|. // save one |substr| call that would be needed if we used |ACCEPT_N|.
return buildSequence( return buildSequence(
[ op.EXPECT, expectedIndex ], context.reportFailures ? [ op.EXPECT, expectedIndex ] : [],
buildCondition( buildCondition(
node.ignoreCase node.ignoreCase
? [ op.MATCH_STRING_IC, stringIndex ] ? [ op.MATCH_STRING_IC, stringIndex ]
@ -629,7 +651,7 @@ function generateBytecode( ast ) {
}, },
class( node ) { class( node, context ) {
const regexp = "/^[" const regexp = "/^["
+ ( node.inverted ? "^" : "" ) + ( node.inverted ? "^" : "" )
@ -656,16 +678,17 @@ function generateBytecode( ast ) {
+ "]"; + "]";
const regexpIndex = addConst( regexp ); const regexpIndex = addConst( regexp );
const expectedIndex = addConst( // Do not generate unused constant, if no need it
const expectedIndex = context.reportFailures ? addConst(
"peg$classExpectation(" "peg$classExpectation("
+ parts + ", " + parts + ", "
+ node.inverted + ", " + node.inverted + ", "
+ node.ignoreCase + node.ignoreCase
+ ")" + ")"
); ) : null;
return buildSequence( return buildSequence(
[ op.EXPECT, expectedIndex ], context.reportFailures ? [ op.EXPECT, expectedIndex ] : [],
buildCondition( buildCondition(
[ op.MATCH_REGEXP, regexpIndex ], [ op.MATCH_REGEXP, regexpIndex ],
[ op.ACCEPT_N, 1 ], [ op.ACCEPT_N, 1 ],
@ -675,12 +698,15 @@ function generateBytecode( ast ) {
}, },
any() { any( node, context ) {
const expectedIndex = addConst( "peg$anyExpectation()" ); // Do not generate unused constant, if no need it
const expectedIndex = context.reportFailures
? addConst( "peg$anyExpectation()" )
: null;
return buildSequence( return buildSequence(
[ op.EXPECT, expectedIndex ], context.reportFailures ? [ op.EXPECT, expectedIndex ] : [],
buildCondition( buildCondition(
[ op.MATCH_ANY ], [ op.MATCH_ANY ],
[ op.ACCEPT_N, 1 ], [ op.ACCEPT_N, 1 ],

File diff suppressed because one or more lines are too long

@ -94,6 +94,10 @@ declare namespace peg {
bytecode?: number[]; bytecode?: number[];
// Added by calc-report-failures pass
reportFailures?: boolean;
} }
interface Named extends INode { interface Named extends INode {
@ -340,6 +344,7 @@ declare namespace peg {
namespace transform { namespace transform {
function removeProxyRules( ast: Grammar, options: ICompilerPassOptions ): void; function removeProxyRules( ast: Grammar, options: ICompilerPassOptions ): void;
function calcReportFailures( ast: Grammar, options: ICompilerPassOptions ): void;
} }

@ -75,26 +75,80 @@ describe( "compiler pass |generateBytecode|", function () {
describe( "for named", function () { describe( "for named", function () {
const grammar = "start 'start' = 'a'"; const grammar1 = "start 'start' = .";
const grammar2 = "start 'start' = 'a'";
const grammar3 = "start 'start' = [a]";
it( "generates correct bytecode", function () { describe( "when |reportFailures=true|", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ it( "generates correct bytecode", function () {
23, 0, // EXPECT <0>
28, // SILENT_FAILS_ON expect( pass ).to.changeAST( grammar1, bytecodeDetails( [
23, 2, 18, 1, 2, 1, 22, 1, 3, // <expression> 23, 0, // EXPECT <0>
29 // SILENT_FAILS_OFF 28, // SILENT_FAILS_ON
] ) ); 17, 2, 1, 21, 1, 3, // <expression>
29 // SILENT_FAILS_OFF
] ) );
expect( pass ).to.changeAST( grammar2, bytecodeDetails( [
23, 0, // EXPECT <0>
28, // SILENT_FAILS_ON
18, 1, 2, 1, 22, 1, 3, // <expression>
29 // SILENT_FAILS_OFF
] ) );
expect( pass ).to.changeAST( grammar3, bytecodeDetails( [
23, 0, // EXPECT <0>
28, // SILENT_FAILS_ON
20, 1, 2, 1, 21, 1, 3, // <expression>
29 // SILENT_FAILS_OFF
] ) );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar1, constsDetails( [
"peg$otherExpectation(\"start\")"
] ) );
expect( pass ).to.changeAST( grammar2, constsDetails( [
"peg$otherExpectation(\"start\")",
"\"a\""
] ) );
expect( pass ).to.changeAST( grammar3, constsDetails( [
"peg$otherExpectation(\"start\")",
"/^[a]/"
] ) );
} );
} ); } );
it( "defines correct constants", function () { describe( "when |reportFailures=false|", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ it( "generates correct bytecode", function () {
"peg$otherExpectation(\"start\")",
"\"a\"", expect( pass ).to.changeAST( grammar1, bytecodeDetails( [
"peg$literalExpectation(\"a\", false)" 17, 2, 1, 21, 1, 3, // <expression>
] ) ); ] ), {}, { reportFailures: false } );
expect( pass ).to.changeAST( grammar2, bytecodeDetails( [
18, 0, 2, 1, 22, 0, 3, // <expression>
] ), {}, { reportFailures: false } );
expect( pass ).to.changeAST( grammar3, bytecodeDetails( [
20, 0, 2, 1, 21, 1, 3, // <expression>
] ), {}, { reportFailures: false } );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar1, constsDetails( [] ), {}, { reportFailures: false } );
expect( pass ).to.changeAST( grammar2, constsDetails( [
"\"a\""
] ), {}, { reportFailures: false } );
expect( pass ).to.changeAST( grammar3, constsDetails( [
"/^[a]/"
] ), {}, { reportFailures: false } );
} );
} ); } );
@ -452,14 +506,25 @@ describe( "compiler pass |generateBytecode|", function () {
describe( "for group", function () { describe( "for group", function () {
const grammar = "start = ('a')";
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = ('a')", bytecodeDetails( [ expect( pass ).to.changeAST( grammar, bytecodeDetails( [
23, 1, 18, 0, 2, 1, 22, 0, 3 // <expression> 23, 1, 18, 0, 2, 1, 22, 0, 3 // <expression>
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"",
"peg$literalExpectation(\"a\", false)"
] ) );
} );
} ); } );
describe( "for semantic_and", function () { describe( "for semantic_and", function () {
@ -662,140 +727,282 @@ describe( "compiler pass |generateBytecode|", function () {
describe( "for literal", function () { describe( "for literal", function () {
describe( "empty", function () { describe( "when |reportFailures=true|", function () {
const grammar = "start = ''"; describe( "empty", function () {
it( "generates correct bytecode", function () { const grammar = "start = ''";
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ it( "generates correct bytecode", function () {
0, 0 // PUSH
] ) ); expect( pass ).to.changeAST( grammar, bytecodeDetails( [
0, 0 // PUSH
] ) );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ "\"\"" ] ) );
} );
} ); } );
it( "defines correct constants", function () { describe( "non-empty case-sensitive", function () {
const grammar = "start = 'a'";
it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [
23, 1, // EXPECT <1>
18, 0, 2, 1, // MATCH_STRING <0>
22, 0, // * ACCEPT_STRING
3 // * PUSH_FAILED
] ) );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"",
"peg$literalExpectation(\"a\", false)"
] ) );
} );
} );
describe( "non-empty case-insensitive", function () {
const grammar = "start = 'A'i";
it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [
23, 1, // EXPECT <1>
19, 0, 2, 1, // MATCH_STRING_IC <0>
21, 1, // * ACCEPT_N
3 // * PUSH_FAILED
] ) );
} );
expect( pass ).to.changeAST( grammar, constsDetails( [ "\"\"" ] ) ); it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\"",
"peg$literalExpectation(\"A\", true)"
] ) );
} );
} ); } );
} ); } );
describe( "non-empty case-sensitive", function () { describe( "when |reportFailures=false|", function () {
const grammar = "start = 'a'"; describe( "empty", function () {
it( "generates correct bytecode", function () { const grammar = "start = ''";
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ it( "generates correct bytecode", function () {
23, 1, // EXPECT <1>
18, 0, 2, 1, // MATCH_STRING <0> expect( pass ).to.changeAST( grammar, bytecodeDetails( [
22, 0, // * ACCEPT_STRING 0, 0 // PUSH
3 // * PUSH_FAILED ] ), {}, { reportFailures: false } );
] ) );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ "\"\"" ] ), {}, { reportFailures: false } );
} );
} ); } );
it( "defines correct constants", function () { describe( "non-empty case-sensitive", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ const grammar = "start = 'a'";
"\"a\"",
"peg$literalExpectation(\"a\", false)" it( "generates correct bytecode", function () {
] ) );
expect( pass ).to.changeAST( grammar, bytecodeDetails( [
18, 0, 2, 1, // MATCH_STRING <0>
22, 0, // * ACCEPT_STRING
3 // * PUSH_FAILED
] ), {}, { reportFailures: false } );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\""
] ), {}, { reportFailures: false } );
} );
} );
describe( "non-empty case-insensitive", function () {
const grammar = "start = 'A'i";
it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [
19, 0, 2, 1, // MATCH_STRING_IC <0>
21, 1, // * ACCEPT_N
3 // * PUSH_FAILED
] ), {}, { reportFailures: false } );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [
"\"a\""
] ), {}, { reportFailures: false } );
} );
} ); } );
} ); } );
describe( "non-empty case-insensitive", function () { } );
describe( "for class", function () {
const grammar = "start = 'A'i"; describe( "when |reportFailures=true|", function () {
it( "generates correct bytecode", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ expect( pass ).to.changeAST( "start = [a]", bytecodeDetails( [
23, 1, // EXPECT <1> 23, 1, // EXPECT <1>
19, 0, 2, 1, // MATCH_STRING_IC <0> 20, 0, 2, 1, // MATCH_REGEXP <0>
21, 1, // * ACCEPT_N 21, 1, // * ACCEPT_N
3 // * PUSH_FAILED 3 // * PUSH_FAILED
] ) ); ] ) );
} ); } );
it( "defines correct constants", function () { describe( "non-inverted case-sensitive", function () {
expect( pass ).to.changeAST( grammar, constsDetails( [ it( "defines correct constants", function () {
"\"a\"",
"peg$literalExpectation(\"A\", true)" expect( pass ).to.changeAST( "start = [a]", constsDetails( [
] ) ); "/^[a]/",
"peg$classExpectation([\"a\"], false, false)"
] ) );
} );
} ); } );
} ); describe( "inverted case-sensitive", function () {
} ); it( "defines correct constants", function () {
describe( "for class", function () { expect( pass ).to.changeAST( "start = [^a]", constsDetails( [
"/^[^a]/",
"peg$classExpectation([\"a\"], true, false)"
] ) );
it( "generates correct bytecode", function () { } );
expect( pass ).to.changeAST( "start = [a]", bytecodeDetails( [ } );
23, 1, // EXPECT <1>
20, 0, 2, 1, // MATCH_REGEXP <0>
21, 1, // * ACCEPT_N
3 // * PUSH_FAILED
] ) );
} ); describe( "non-inverted case-insensitive", function () {
describe( "non-inverted case-sensitive", function () { it( "defines correct constants", function () {
it( "defines correct constants", function () { expect( pass ).to.changeAST( "start = [a]i", constsDetails( [
"/^[a]/i",
"peg$classExpectation([\"a\"], false, true)"
] ) );
expect( pass ).to.changeAST( "start = [a]", constsDetails( [ } );
"/^[a]/",
"peg$classExpectation([\"a\"], false, false)"
] ) );
} ); } );
} ); describe( "complex", function () {
describe( "inverted case-sensitive", function () { it( "defines correct constants", function () {
it( "defines correct constants", function () { expect( pass ).to.changeAST( "start = [ab-def-hij-l]", constsDetails( [
"/^[ab-def-hij-l]/",
"peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)"
] ) );
expect( pass ).to.changeAST( "start = [^a]", constsDetails( [ } );
"/^[^a]/",
"peg$classExpectation([\"a\"], true, false)"
] ) );
} ); } );
} ); } );
describe( "non-inverted case-insensitive", function () { describe( "when |reportFailures=false|", function () {
it( "defines correct constants", function () { it( "generates correct bytecode", function () {
expect( pass ).to.changeAST( "start = [a]i", constsDetails( [ expect( pass ).to.changeAST( "start = [a]", bytecodeDetails( [
"/^[a]/i", 20, 0, 2, 1, // MATCH_REGEXP <0>
"peg$classExpectation([\"a\"], false, true)" 21, 1, // * ACCEPT_N
] ) ); 3 // * PUSH_FAILED
] ), {}, { reportFailures: false } );
} ); } );
} ); describe( "non-inverted case-sensitive", function () {
describe( "complex", function () { it( "defines correct constants", function () {
it( "defines correct constants", function () { expect( pass ).to.changeAST( "start = [a]", constsDetails( [
"/^[a]/"
] ), {}, { reportFailures: false } );
expect( pass ).to.changeAST( "start = [ab-def-hij-l]", constsDetails( [ } );
"/^[ab-def-hij-l]/",
"peg$classExpectation([\"a\", [\"b\", \"d\"], \"e\", [\"f\", \"h\"], \"i\", [\"j\", \"l\"]], false, false)" } );
] ) );
describe( "inverted case-sensitive", function () {
it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [^a]", constsDetails( [
"/^[^a]/"
] ), {}, { reportFailures: false } );
} );
} );
describe( "non-inverted case-insensitive", function () {
it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [a]i", constsDetails( [
"/^[a]/i"
] ), {}, { reportFailures: false } );
} );
} );
describe( "complex", function () {
it( "defines correct constants", function () {
expect( pass ).to.changeAST( "start = [ab-def-hij-l]", constsDetails( [
"/^[ab-def-hij-l]/"
] ), {}, { reportFailures: false } );
} );
} ); } );
@ -805,25 +1012,56 @@ describe( "compiler pass |generateBytecode|", function () {
describe( "for any", function () { describe( "for any", function () {
const grammar = "start = ."; describe( "when |reportFailures=true|", function () {
it( "generates bytecode", function () { const grammar = "start = .";
expect( pass ).to.changeAST( grammar, bytecodeDetails( [ it( "generates bytecode", function () {
23, 0, // EXPECT <0>
17, 2, 1, // MATCH_ANY expect( pass ).to.changeAST( grammar, bytecodeDetails( [
21, 1, // * ACCEPT_N 23, 0, // EXPECT <0>
3 // * PUSH_FAILED 17, 2, 1, // MATCH_ANY
] ) ); 21, 1, // * ACCEPT_N
3 // * PUSH_FAILED
] ) );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST(
grammar,
constsDetails( [ "peg$anyExpectation()" ] )
);
} );
} ); } );
it( "defines correct constants", function () { describe( "when |reportFailures=false|", function () {
const grammar = "start = .";
expect( pass ).to.changeAST( it( "generates bytecode", function () {
grammar,
constsDetails( [ "peg$anyExpectation()" ] ) expect( pass ).to.changeAST( grammar, bytecodeDetails( [
); 17, 2, 1, // MATCH_ANY
21, 1, // * ACCEPT_N
3 // * PUSH_FAILED
] ), {}, { reportFailures: false } );
} );
it( "defines correct constants", function () {
expect( pass ).to.changeAST(
grammar,
constsDetails( [] ),
{},
{ reportFailures: false }
);
} );
} ); } );

@ -6,9 +6,10 @@ module.exports = function ( chai, utils ) {
const Assertion = chai.Assertion; const Assertion = chai.Assertion;
Assertion.addMethod( "changeAST", function ( grammar, props, options ) { Assertion.addMethod( "changeAST", function ( grammar, props, options, additionalRuleProps ) {
options = typeof options !== "undefined" ? options : {}; options = typeof options !== "undefined" ? options : {};
additionalRuleProps = typeof additionalRuleProps !== "undefined" ? additionalRuleProps : { reportFailures: true };
function matchProps( value, props ) { function matchProps( value, props ) {
@ -63,11 +64,13 @@ module.exports = function ( chai, utils ) {
} }
ast.rules = ast.rules.map( rule => Object.assign( rule, additionalRuleProps ) );
utils.flag( this, "object" )( ast, options ); utils.flag( this, "object" )( ast, options );
this.assert( this.assert(
matchProps( ast, props ), matchProps( ast, props ),
"expected #{this} to change the AST to match #{exp}", "expected #{this} to change the AST to match #{exp} but #{act} was produced",
"expected #{this} to not change the AST to match #{exp}", "expected #{this} to not change the AST to match #{exp}",
props, props,
ast ast

Loading…
Cancel
Save