Update code format and style
This is related to my last commit. I've updated all the JavaScript files to satisfy 'eslint-config-futagozaryuu', my eslint configuration. I'm sure I've probally missed something, but I've run all NPM scripts and Gulp tasks, fixed any bugs that cropped up, and updated some stuff (mainly related to generated messages), so as far as I can, tell this conversion is over (I know I've probally jixed it just by saying this ;P).master
parent
3c6523ff83
commit
e6d018a88d
@ -1,89 +1,96 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let babelify = require("babelify");
|
const version = require( "./package" ).version;
|
||||||
let browserify = require("browserify");
|
const spawn = require( "child_process" ).spawn;
|
||||||
let buffer = require("vinyl-buffer");
|
const gulp = require( "gulp" );
|
||||||
let del = require("del");
|
const task = gulp.task.bind( gulp );
|
||||||
let eslint = require("gulp-eslint");
|
const eslint = require( "gulp-eslint" );
|
||||||
let gulp = require("gulp");
|
const mocha = require( "gulp-mocha" );
|
||||||
let header = require("gulp-header");
|
const dedent = require( "dedent" );
|
||||||
let mocha = require("gulp-mocha");
|
const browserify = require( "browserify" );
|
||||||
let rename = require("gulp-rename");
|
const babelify = require( "babelify" );
|
||||||
let runSequence = require("run-sequence");
|
const source = require( "vinyl-source-stream" );
|
||||||
let source = require("vinyl-source-stream");
|
const rename = require( "gulp-rename" );
|
||||||
let spawn = require("child_process").spawn;
|
const buffer = require( "vinyl-buffer" );
|
||||||
let uglify = require("gulp-uglify");
|
const uglify = require( "gulp-uglify" );
|
||||||
|
const header = require( "gulp-header" );
|
||||||
function execFile(args) {
|
const del = require( "del" );
|
||||||
return spawn("node", args.split(" "), { stdio: "inherit" });
|
const runSequence = require( "run-sequence" );
|
||||||
|
|
||||||
|
function node( args ) {
|
||||||
|
|
||||||
|
return spawn( "node", args.split( " " ), { stdio: "inherit" } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run ESLint on all JavaScript files.
|
// Run ESLint on all JavaScript files.
|
||||||
gulp.task("lint", () =>
|
task( "lint", () => gulp
|
||||||
gulp.src([
|
.src( [
|
||||||
"lib/**/*.js",
|
"**/.*rc.js",
|
||||||
"!lib/parser.js",
|
"lib/**/*.js",
|
||||||
"test/benchmark/**/*.js",
|
"!lib/parser.js",
|
||||||
"test/benchmark/run",
|
"test/benchmark/**/*.js",
|
||||||
"test/impact",
|
"test/benchmark/run",
|
||||||
"test/spec/**/*.js",
|
"test/impact",
|
||||||
"test/server/run",
|
"test/spec/**/*.js",
|
||||||
"bin/*.js",
|
"test/server/run",
|
||||||
"gulpfile.js"
|
"bin/*.js",
|
||||||
])
|
"gulpfile.js"
|
||||||
.pipe(eslint())
|
] )
|
||||||
.pipe(eslint.format())
|
.pipe( eslint( { dotfiles: true } ) )
|
||||||
.pipe(eslint.failAfterError())
|
.pipe( eslint.format() )
|
||||||
|
.pipe( eslint.failAfterError() )
|
||||||
);
|
);
|
||||||
|
|
||||||
// Run tests.
|
// Run tests.
|
||||||
gulp.task("test", () =>
|
task( "test", () => gulp
|
||||||
gulp.src("test/spec/**/*.spec.js", { read: false })
|
.src( "test/spec/**/*.spec.js", { read: false } )
|
||||||
.pipe(mocha())
|
.pipe( mocha() )
|
||||||
);
|
);
|
||||||
|
|
||||||
// Run benchmarks.
|
// Run benchmarks.
|
||||||
gulp.task("benchmark", () => execFile("test/benchmark/run"));
|
task( "benchmark", () => node( "test/benchmark/run" ) );
|
||||||
|
|
||||||
// Create the browser build.
|
// Create the browser build.
|
||||||
gulp.task("browser:build", () => {
|
task( "browser:build", () => {
|
||||||
const HEADER = [
|
|
||||||
"//",
|
const HEADER = dedent`
|
||||||
"// PEG.js v" + require("./package").version,
|
|
||||||
"// https://pegjs.org/",
|
/**
|
||||||
"//",
|
* PEG.js v${ version }
|
||||||
"// Copyright (c) 2010-2016 David Majda",
|
* https://pegjs.org/
|
||||||
"// Copyright (c) 2017+ Futago-za Ryuu",
|
*
|
||||||
"//",
|
* Copyright (c) 2010-2016 David Majda
|
||||||
"// Licensed under the MIT License.",
|
* Copyright (c) 2017+ Futago-za Ryuu
|
||||||
"//",
|
*
|
||||||
""
|
* Released under the MIT License.
|
||||||
]
|
*/\n\n
|
||||||
.map(line => `${line}\n`)
|
|
||||||
.join("");
|
`;
|
||||||
|
|
||||||
return browserify("lib/peg.js", { standalone: "peg" })
|
return browserify( "lib/peg.js", { standalone: "peg" } )
|
||||||
.transform(babelify, { presets: "es2015", compact: false })
|
.transform( babelify, { presets: "es2015", compact: false } )
|
||||||
.bundle()
|
.bundle()
|
||||||
.pipe(source("peg.js"))
|
.pipe( source( "peg.js" ) )
|
||||||
.pipe(header(HEADER))
|
.pipe( header( HEADER ) )
|
||||||
.pipe(gulp.dest("browser"))
|
.pipe( gulp.dest( "browser" ) )
|
||||||
.pipe(rename({ suffix: ".min" }))
|
.pipe( rename( { suffix: ".min" } ) )
|
||||||
.pipe(buffer())
|
.pipe( buffer() )
|
||||||
.pipe(uglify())
|
.pipe( uglify() )
|
||||||
.pipe(header(HEADER))
|
.pipe( header( HEADER ) )
|
||||||
.pipe(gulp.dest("browser"));
|
.pipe( gulp.dest( "browser" ) );
|
||||||
});
|
|
||||||
|
} );
|
||||||
|
|
||||||
// Delete the browser build.
|
// Delete the browser build.
|
||||||
gulp.task("browser:clean", () => del("browser"));
|
task( "browser:clean", () => del( "browser" ) );
|
||||||
|
|
||||||
// Generate the grammar parser.
|
// Generate the grammar parser.
|
||||||
gulp.task("parser", () =>
|
task( "parser", () =>
|
||||||
execFile("bin/peg src/parser.pegjs -o lib/parser.js")
|
node( "bin/peg src/parser.pegjs -o lib/parser.js" )
|
||||||
);
|
);
|
||||||
|
|
||||||
// Default task.
|
// Default task.
|
||||||
gulp.task("default", cb =>
|
task( "default", cb =>
|
||||||
runSequence("lint", "test", cb)
|
runSequence( "benchmark", "test", cb )
|
||||||
);
|
);
|
||||||
|
@ -1,76 +1,102 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let visitor = require("./visitor");
|
const visitor = require( "./visitor" );
|
||||||
|
|
||||||
// AST utilities.
|
// AST utilities.
|
||||||
let asts = {
|
const asts = {
|
||||||
findRule(ast, name) {
|
findRule( ast, name ) {
|
||||||
for (let i = 0; i < ast.rules.length; i++) {
|
|
||||||
if (ast.rules[i].name === name) {
|
|
||||||
return ast.rules[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
for ( let i = 0; i < ast.rules.length; i++ ) {
|
||||||
},
|
|
||||||
|
|
||||||
indexOfRule(ast, name) {
|
if ( ast.rules[ i ].name === name ) return ast.rules[ i ];
|
||||||
for (let i = 0; i < ast.rules.length; i++) {
|
|
||||||
if (ast.rules[i].name === name) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
}
|
||||||
},
|
|
||||||
|
|
||||||
alwaysConsumesOnSuccess(ast, node) {
|
return void 0;
|
||||||
function consumesTrue() { return true; }
|
|
||||||
function consumesFalse() { return false; }
|
|
||||||
|
|
||||||
function consumesExpression(node) {
|
},
|
||||||
return consumes(node.expression);
|
|
||||||
}
|
indexOfRule( ast, name ) {
|
||||||
|
|
||||||
|
for ( let i = 0; i < ast.rules.length; i++ ) {
|
||||||
|
|
||||||
|
if ( ast.rules[ i ].name === name ) return i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
alwaysConsumesOnSuccess( ast, node ) {
|
||||||
|
|
||||||
|
let consumes;
|
||||||
|
|
||||||
|
function consumesTrue() {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
function consumesFalse() {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function consumesExpression( node ) {
|
||||||
|
|
||||||
|
return consumes( node.expression );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
let consumes = visitor.build({
|
consumes = visitor.build( {
|
||||||
rule: consumesExpression,
|
rule: consumesExpression,
|
||||||
named: consumesExpression,
|
named: consumesExpression,
|
||||||
|
|
||||||
choice(node) {
|
choice( node ) {
|
||||||
return node.alternatives.every(consumes);
|
|
||||||
},
|
return node.alternatives.every( consumes );
|
||||||
|
|
||||||
action: consumesExpression,
|
},
|
||||||
|
|
||||||
sequence(node) {
|
action: consumesExpression,
|
||||||
return node.elements.some(consumes);
|
|
||||||
},
|
sequence( node ) {
|
||||||
|
|
||||||
labeled: consumesExpression,
|
return node.elements.some( consumes );
|
||||||
text: consumesExpression,
|
|
||||||
simple_and: consumesFalse,
|
},
|
||||||
simple_not: consumesFalse,
|
|
||||||
optional: consumesFalse,
|
labeled: consumesExpression,
|
||||||
zero_or_more: consumesFalse,
|
text: consumesExpression,
|
||||||
one_or_more: consumesExpression,
|
simple_and: consumesFalse,
|
||||||
group: consumesExpression,
|
simple_not: consumesFalse,
|
||||||
semantic_and: consumesFalse,
|
optional: consumesFalse,
|
||||||
semantic_not: consumesFalse,
|
zero_or_more: consumesFalse,
|
||||||
|
one_or_more: consumesExpression,
|
||||||
rule_ref(node) {
|
group: consumesExpression,
|
||||||
return consumes(asts.findRule(ast, node.name));
|
semantic_and: consumesFalse,
|
||||||
},
|
semantic_not: consumesFalse,
|
||||||
|
|
||||||
literal(node) {
|
rule_ref( node ) {
|
||||||
return node.value !== "";
|
|
||||||
},
|
return consumes( asts.findRule( ast, node.name ) );
|
||||||
|
|
||||||
class: consumesTrue,
|
},
|
||||||
any: consumesTrue
|
|
||||||
});
|
literal( node ) {
|
||||||
|
|
||||||
return consumes(node);
|
return node.value !== "";
|
||||||
}
|
|
||||||
|
},
|
||||||
|
|
||||||
|
class: consumesTrue,
|
||||||
|
any: consumesTrue
|
||||||
|
} );
|
||||||
|
|
||||||
|
return consumes( node );
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = asts;
|
module.exports = asts;
|
||||||
|
@ -1,91 +1,109 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let generateBytecode = require("./passes/generate-bytecode");
|
const generateBytecode = require( "./passes/generate-bytecode" );
|
||||||
let generateJS = require("./passes/generate-js");
|
const generateJS = require( "./passes/generate-js" );
|
||||||
let removeProxyRules = require("./passes/remove-proxy-rules");
|
const removeProxyRules = require( "./passes/remove-proxy-rules" );
|
||||||
let reportDuplicateLabels = require("./passes/report-duplicate-labels");
|
const reportDuplicateLabels = require( "./passes/report-duplicate-labels" );
|
||||||
let reportDuplicateRules = require("./passes/report-duplicate-rules");
|
const reportDuplicateRules = require( "./passes/report-duplicate-rules" );
|
||||||
let reportInfiniteRecursion = require("./passes/report-infinite-recursion");
|
const reportInfiniteRecursion = require( "./passes/report-infinite-recursion" );
|
||||||
let reportInfiniteRepetition = require("./passes/report-infinite-repetition");
|
const reportInfiniteRepetition = require( "./passes/report-infinite-repetition" );
|
||||||
let reportUndefinedRules = require("./passes/report-undefined-rules");
|
const reportUndefinedRules = require( "./passes/report-undefined-rules" );
|
||||||
let visitor = require("./visitor");
|
const visitor = require( "./visitor" );
|
||||||
|
|
||||||
function processOptions(options, defaults) {
|
function processOptions( options, defaults ) {
|
||||||
let processedOptions = {};
|
|
||||||
|
const processedOptions = {};
|
||||||
Object.keys(options).forEach(name => {
|
|
||||||
processedOptions[name] = options[name];
|
Object.keys( options ).forEach( name => {
|
||||||
});
|
|
||||||
|
processedOptions[ name ] = options[ name ];
|
||||||
Object.keys(defaults).forEach(name => {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(processedOptions, name)) {
|
} );
|
||||||
processedOptions[name] = defaults[name];
|
|
||||||
}
|
Object.keys( defaults ).forEach( name => {
|
||||||
});
|
|
||||||
|
if ( ! Object.prototype.hasOwnProperty.call( processedOptions, name ) ) {
|
||||||
|
|
||||||
|
processedOptions[ name ] = defaults[ name ];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
return processedOptions;
|
||||||
|
|
||||||
return processedOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let compiler = {
|
const compiler = {
|
||||||
// AST node visitor builder. Useful mainly for plugins which manipulate the
|
// AST node visitor builder. Useful mainly for plugins which manipulate the
|
||||||
// AST.
|
// AST.
|
||||||
visitor: visitor,
|
visitor: visitor,
|
||||||
|
|
||||||
// Compiler passes.
|
// Compiler passes.
|
||||||
//
|
//
|
||||||
// Each pass is a function that is passed the AST. It can perform checks on it
|
// Each pass is a function that is passed the AST. It can perform checks on it
|
||||||
// or modify it as needed. If the pass encounters a semantic error, it throws
|
// or modify it as needed. If the pass encounters a semantic error, it throws
|
||||||
// |peg.GrammarError|.
|
// |peg.GrammarError|.
|
||||||
passes: {
|
passes: {
|
||||||
check: {
|
check: {
|
||||||
reportUndefinedRules: reportUndefinedRules,
|
reportUndefinedRules: reportUndefinedRules,
|
||||||
reportDuplicateRules: reportDuplicateRules,
|
reportDuplicateRules: reportDuplicateRules,
|
||||||
reportDuplicateLabels: reportDuplicateLabels,
|
reportDuplicateLabels: reportDuplicateLabels,
|
||||||
reportInfiniteRecursion: reportInfiniteRecursion,
|
reportInfiniteRecursion: reportInfiniteRecursion,
|
||||||
reportInfiniteRepetition: reportInfiniteRepetition
|
reportInfiniteRepetition: reportInfiniteRepetition
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
removeProxyRules: removeProxyRules
|
removeProxyRules: removeProxyRules
|
||||||
|
},
|
||||||
|
generate: {
|
||||||
|
generateBytecode: generateBytecode,
|
||||||
|
generateJS: generateJS
|
||||||
|
}
|
||||||
},
|
},
|
||||||
generate: {
|
|
||||||
generateBytecode: generateBytecode,
|
// Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
|
||||||
generateJS: generateJS
|
// if the AST contains a semantic error. Note that not all errors are detected
|
||||||
}
|
// during the generation and some may protrude to the generated parser and
|
||||||
},
|
// cause its malfunction.
|
||||||
|
compile( ast, passes, options ) {
|
||||||
// Generates a parser from a specified grammar AST. Throws |peg.GrammarError|
|
|
||||||
// if the AST contains a semantic error. Note that not all errors are detected
|
options = typeof options !== "undefined" ? options : {};
|
||||||
// during the generation and some may protrude to the generated parser and
|
|
||||||
// cause its malfunction.
|
options = processOptions( options, {
|
||||||
compile(ast, passes, options) {
|
allowedStartRules: [ ast.rules[ 0 ].name ],
|
||||||
options = options !== undefined ? options : {};
|
cache: false,
|
||||||
|
dependencies: {},
|
||||||
options = processOptions(options, {
|
exportVar: null,
|
||||||
allowedStartRules: [ast.rules[0].name],
|
format: "bare",
|
||||||
cache: false,
|
optimize: "speed",
|
||||||
dependencies: {},
|
output: "parser",
|
||||||
exportVar: null,
|
trace: false
|
||||||
format: "bare",
|
} );
|
||||||
optimize: "speed",
|
|
||||||
output: "parser",
|
Object.keys( passes ).forEach( stage => {
|
||||||
trace: false
|
|
||||||
});
|
passes[ stage ].forEach( pass => {
|
||||||
|
|
||||||
Object.keys(passes).forEach(stage => {
|
pass( ast, options );
|
||||||
passes[stage].forEach(p => { p(ast, options); });
|
|
||||||
});
|
} );
|
||||||
|
|
||||||
switch (options.output) {
|
} );
|
||||||
case "parser":
|
|
||||||
return eval(ast.code);
|
switch ( options.output ) {
|
||||||
|
|
||||||
case "source":
|
case "parser":
|
||||||
return ast.code;
|
return eval( ast.code );
|
||||||
|
|
||||||
default:
|
case "source":
|
||||||
throw new Error("Invalid output format: " + options.output + ".");
|
return ast.code;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error( `Invalid output format: ${ options.output }.` );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = compiler;
|
module.exports = compiler;
|
||||||
|
@ -1,54 +1,62 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
|
function hex( ch ) {
|
||||||
|
|
||||||
|
return ch.charCodeAt( 0 ).toString( 16 ).toUpperCase();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// JavaScript code generation helpers.
|
// JavaScript code generation helpers.
|
||||||
let js = {
|
const js = {
|
||||||
stringEscape(s) {
|
stringEscape( s ) {
|
||||||
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
|
|
||||||
// literal except for the closing quote character, backslash, carriage
|
// ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
|
||||||
// return, line separator, paragraph separator, and line feed. Any character
|
// literal except for the closing quote character, backslash, carriage
|
||||||
// may appear in the form of an escape sequence.
|
// return, line separator, paragraph separator, and line feed. Any character
|
||||||
//
|
// may appear in the form of an escape sequence.
|
||||||
// For portability, we also escape all control and non-ASCII characters.
|
//
|
||||||
return s
|
// For portability, we also escape all control and non-ASCII characters.
|
||||||
.replace(/\\/g, "\\\\") // backslash
|
return s
|
||||||
.replace(/"/g, "\\\"") // closing double quote
|
.replace( /\\/g, "\\\\" ) // backslash
|
||||||
.replace(/\0/g, "\\0") // null
|
.replace( /"/g, "\\\"" ) // closing double quote
|
||||||
.replace(/\x08/g, "\\b") // backspace
|
.replace( /\0/g, "\\0" ) // null
|
||||||
.replace(/\t/g, "\\t") // horizontal tab
|
.replace( /\x08/g, "\\b" ) // backspace
|
||||||
.replace(/\n/g, "\\n") // line feed
|
.replace( /\t/g, "\\t" ) // horizontal tab
|
||||||
.replace(/\v/g, "\\v") // vertical tab
|
.replace( /\n/g, "\\n" ) // line feed
|
||||||
.replace(/\f/g, "\\f") // form feed
|
.replace( /\v/g, "\\v" ) // vertical tab
|
||||||
.replace(/\r/g, "\\r") // carriage return
|
.replace( /\f/g, "\\f" ) // form feed
|
||||||
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
|
.replace( /\r/g, "\\r" ) // carriage return
|
||||||
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
|
.replace( /[\x00-\x0F]/g, ch => "\\x0" + hex( ch ) )
|
||||||
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
|
.replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) )
|
||||||
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
|
.replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) )
|
||||||
},
|
.replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) );
|
||||||
|
|
||||||
regexpClassEscape(s) {
|
},
|
||||||
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
|
|
||||||
//
|
regexpClassEscape( s ) {
|
||||||
// For portability, we also escape all control and non-ASCII characters.
|
|
||||||
return s
|
// Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1.
|
||||||
.replace(/\\/g, "\\\\") // backslash
|
//
|
||||||
.replace(/\//g, "\\/") // closing slash
|
// For portability, we also escape all control and non-ASCII characters.
|
||||||
.replace(/]/g, "\\]") // closing bracket
|
return s
|
||||||
.replace(/\^/g, "\\^") // caret
|
.replace( /\\/g, "\\\\" ) // backslash
|
||||||
.replace(/-/g, "\\-") // dash
|
.replace( /\//g, "\\/" ) // closing slash
|
||||||
.replace(/\0/g, "\\0") // null
|
.replace( /]/g, "\\]" ) // closing bracket
|
||||||
.replace(/\x08/g, "\\b") // backspace
|
.replace( /\^/g, "\\^" ) // caret
|
||||||
.replace(/\t/g, "\\t") // horizontal tab
|
.replace( /-/g, "\\-" ) // dash
|
||||||
.replace(/\n/g, "\\n") // line feed
|
.replace( /\0/g, "\\0" ) // null
|
||||||
.replace(/\v/g, "\\v") // vertical tab
|
.replace( /\x08/g, "\\b" ) // backspace
|
||||||
.replace(/\f/g, "\\f") // form feed
|
.replace( /\t/g, "\\t" ) // horizontal tab
|
||||||
.replace(/\r/g, "\\r") // carriage return
|
.replace( /\n/g, "\\n" ) // line feed
|
||||||
.replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch))
|
.replace( /\v/g, "\\v" ) // vertical tab
|
||||||
.replace(/[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex(ch))
|
.replace( /\f/g, "\\f" ) // form feed
|
||||||
.replace(/[\u0100-\u0FFF]/g, ch => "\\u0" + hex(ch))
|
.replace( /\r/g, "\\r" ) // carriage return
|
||||||
.replace(/[\u1000-\uFFFF]/g, ch => "\\u" + hex(ch));
|
.replace( /[\x00-\x0F]/g, ch => "\\x0" + hex( ch ) )
|
||||||
}
|
.replace( /[\x10-\x1F\x7F-\xFF]/g, ch => "\\x" + hex( ch ) )
|
||||||
|
.replace( /[\u0100-\u0FFF]/g, ch => "\\u0" + hex( ch ) )
|
||||||
|
.replace( /[\u1000-\uFFFF]/g, ch => "\\u" + hex( ch ) );
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = js;
|
module.exports = js;
|
||||||
|
@ -1,54 +1,56 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Bytecode instruction opcodes.
|
// Bytecode instruction opcodes.
|
||||||
let opcodes = {
|
const opcodes = {
|
||||||
// Stack Manipulation
|
|
||||||
|
// Stack Manipulation
|
||||||
PUSH: 0, // PUSH c
|
|
||||||
PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
|
PUSH: 0, // PUSH c
|
||||||
PUSH_NULL: 2, // PUSH_NULL
|
PUSH_UNDEFINED: 1, // PUSH_UNDEFINED
|
||||||
PUSH_FAILED: 3, // PUSH_FAILED
|
PUSH_NULL: 2, // PUSH_NULL
|
||||||
PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
|
PUSH_FAILED: 3, // PUSH_FAILED
|
||||||
PUSH_CURR_POS: 5, // PUSH_CURR_POS
|
PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY
|
||||||
POP: 6, // POP
|
PUSH_CURR_POS: 5, // PUSH_CURR_POS
|
||||||
POP_CURR_POS: 7, // POP_CURR_POS
|
POP: 6, // POP
|
||||||
POP_N: 8, // POP_N n
|
POP_CURR_POS: 7, // POP_CURR_POS
|
||||||
NIP: 9, // NIP
|
POP_N: 8, // POP_N n
|
||||||
APPEND: 10, // APPEND
|
NIP: 9, // NIP
|
||||||
WRAP: 11, // WRAP n
|
APPEND: 10, // APPEND
|
||||||
TEXT: 12, // TEXT
|
WRAP: 11, // WRAP n
|
||||||
|
TEXT: 12, // TEXT
|
||||||
// Conditions and Loops
|
|
||||||
|
// Conditions and Loops
|
||||||
IF: 13, // IF t, f
|
|
||||||
IF_ERROR: 14, // IF_ERROR t, f
|
IF: 13, // IF t, f
|
||||||
IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
|
IF_ERROR: 14, // IF_ERROR t, f
|
||||||
WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
|
IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f
|
||||||
|
WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b
|
||||||
// Matching
|
|
||||||
|
// Matching
|
||||||
MATCH_ANY: 17, // MATCH_ANY a, f, ...
|
|
||||||
MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
|
MATCH_ANY: 17, // MATCH_ANY a, f, ...
|
||||||
MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
|
MATCH_STRING: 18, // MATCH_STRING s, a, f, ...
|
||||||
MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
|
MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ...
|
||||||
ACCEPT_N: 21, // ACCEPT_N n
|
MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ...
|
||||||
ACCEPT_STRING: 22, // ACCEPT_STRING s
|
ACCEPT_N: 21, // ACCEPT_N n
|
||||||
FAIL: 23, // FAIL e
|
ACCEPT_STRING: 22, // ACCEPT_STRING s
|
||||||
|
FAIL: 23, // FAIL e
|
||||||
// Calls
|
|
||||||
|
// Calls
|
||||||
LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
|
|
||||||
UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
|
LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p
|
||||||
CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
|
UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS
|
||||||
|
CALL: 26, // CALL f, n, pc, p1, p2, ..., pN
|
||||||
// Rules
|
|
||||||
|
// Rules
|
||||||
RULE: 27, // RULE r
|
|
||||||
|
RULE: 27, // RULE r
|
||||||
// Failure Reporting
|
|
||||||
|
// Failure Reporting
|
||||||
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
|
|
||||||
SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
|
SILENT_FAILS_ON: 28, // SILENT_FAILS_ON
|
||||||
|
SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = opcodes;
|
module.exports = opcodes;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,59 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let visitor = require("../visitor");
|
const visitor = require( "../visitor" );
|
||||||
|
|
||||||
// Removes proxy rules -- that is, rules that only delegate to other rule.
|
// Removes proxy rules -- that is, rules that only delegate to other rule.
|
||||||
function removeProxyRules(ast, options) {
|
function removeProxyRules( ast, options ) {
|
||||||
function isProxyRule(node) {
|
|
||||||
return node.type === "rule" && node.expression.type === "rule_ref";
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceRuleRefs(ast, from, to) {
|
|
||||||
let replace = visitor.build({
|
|
||||||
rule_ref(node) {
|
|
||||||
if (node.name === from) {
|
|
||||||
node.name = to;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
replace(ast);
|
function isProxyRule( node ) {
|
||||||
}
|
|
||||||
|
|
||||||
let indices = [];
|
return node.type === "rule" && node.expression.type === "rule_ref";
|
||||||
|
|
||||||
ast.rules.forEach((rule, i) => {
|
|
||||||
if (isProxyRule(rule)) {
|
|
||||||
replaceRuleRefs(ast, rule.name, rule.expression.name);
|
|
||||||
if (options.allowedStartRules.indexOf(rule.name) === -1) {
|
|
||||||
indices.push(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
indices.reverse();
|
function replaceRuleRefs( ast, from, to ) {
|
||||||
|
|
||||||
|
const replace = visitor.build( {
|
||||||
|
rule_ref( node ) {
|
||||||
|
|
||||||
|
if ( node.name === from ) {
|
||||||
|
|
||||||
|
node.name = to;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
replace( ast );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = [];
|
||||||
|
|
||||||
|
ast.rules.forEach( ( rule, i ) => {
|
||||||
|
|
||||||
|
if ( isProxyRule( rule ) ) {
|
||||||
|
|
||||||
|
replaceRuleRefs( ast, rule.name, rule.expression.name );
|
||||||
|
if ( options.allowedStartRules.indexOf( rule.name ) === -1 ) {
|
||||||
|
|
||||||
|
indices.push( i );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
indices.reverse();
|
||||||
|
|
||||||
|
indices.forEach( i => {
|
||||||
|
|
||||||
|
ast.rules.splice( i, 1 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
indices.forEach(i => { ast.rules.splice(i, 1); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = removeProxyRules;
|
module.exports = removeProxyRules;
|
||||||
|
@ -1,62 +1,83 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let GrammarError = require("../../grammar-error");
|
const GrammarError = require( "../../grammar-error" );
|
||||||
let visitor = require("../visitor");
|
const visitor = require( "../visitor" );
|
||||||
|
|
||||||
// Checks that each label is defined only once within each scope.
|
// Checks that each label is defined only once within each scope.
|
||||||
function reportDuplicateLabels(ast) {
|
function reportDuplicateLabels( ast ) {
|
||||||
function cloneEnv(env) {
|
|
||||||
let clone = {};
|
let check;
|
||||||
|
|
||||||
Object.keys(env).forEach(name => {
|
function cloneEnv( env ) {
|
||||||
clone[name] = env[name];
|
|
||||||
});
|
const clone = {};
|
||||||
|
|
||||||
return clone;
|
Object.keys( env ).forEach( name => {
|
||||||
}
|
|
||||||
|
clone[ name ] = env[ name ];
|
||||||
function checkExpressionWithClonedEnv(node, env) {
|
|
||||||
check(node.expression, cloneEnv(env));
|
} );
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
let check = visitor.build({
|
|
||||||
rule(node) {
|
}
|
||||||
check(node.expression, { });
|
|
||||||
},
|
function checkExpressionWithClonedEnv( node, env ) {
|
||||||
|
|
||||||
choice(node, env) {
|
check( node.expression, cloneEnv( env ) );
|
||||||
node.alternatives.forEach(alternative => {
|
|
||||||
check(alternative, cloneEnv(env));
|
}
|
||||||
});
|
|
||||||
},
|
check = visitor.build( {
|
||||||
|
rule( node ) {
|
||||||
action: checkExpressionWithClonedEnv,
|
|
||||||
|
check( node.expression, {} );
|
||||||
labeled(node, env) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(env, node.label)) {
|
},
|
||||||
throw new GrammarError(
|
|
||||||
"Label \"" + node.label + "\" is already defined "
|
choice( node, env ) {
|
||||||
+ "at line " + env[node.label].start.line + ", "
|
|
||||||
+ "column " + env[node.label].start.column + ".",
|
node.alternatives.forEach( alternative => {
|
||||||
node.location
|
|
||||||
);
|
check( alternative, cloneEnv( env ) );
|
||||||
}
|
|
||||||
|
} );
|
||||||
check(node.expression, env);
|
|
||||||
|
},
|
||||||
env[node.label] = node.location;
|
|
||||||
},
|
action: checkExpressionWithClonedEnv,
|
||||||
|
|
||||||
text: checkExpressionWithClonedEnv,
|
labeled( node, env ) {
|
||||||
simple_and: checkExpressionWithClonedEnv,
|
|
||||||
simple_not: checkExpressionWithClonedEnv,
|
const label = node.label;
|
||||||
optional: checkExpressionWithClonedEnv,
|
|
||||||
zero_or_more: checkExpressionWithClonedEnv,
|
if ( Object.prototype.hasOwnProperty.call( env, label ) ) {
|
||||||
one_or_more: checkExpressionWithClonedEnv,
|
|
||||||
group: checkExpressionWithClonedEnv
|
const start = env[ label ].start;
|
||||||
});
|
|
||||||
|
throw new GrammarError(
|
||||||
check(ast);
|
`Label "${ label }" is already defined at line ${ start.line }, column ${ start.column }.`,
|
||||||
|
node.location
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
check( node.expression, env );
|
||||||
|
env[ label ] = node.location;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
text: checkExpressionWithClonedEnv,
|
||||||
|
simple_and: checkExpressionWithClonedEnv,
|
||||||
|
simple_not: checkExpressionWithClonedEnv,
|
||||||
|
optional: checkExpressionWithClonedEnv,
|
||||||
|
zero_or_more: checkExpressionWithClonedEnv,
|
||||||
|
one_or_more: checkExpressionWithClonedEnv,
|
||||||
|
group: checkExpressionWithClonedEnv
|
||||||
|
} );
|
||||||
|
|
||||||
|
check( ast );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = reportDuplicateLabels;
|
module.exports = reportDuplicateLabels;
|
||||||
|
@ -1,28 +1,36 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let GrammarError = require("../../grammar-error");
|
const GrammarError = require( "../../grammar-error" );
|
||||||
let visitor = require("../visitor");
|
const visitor = require( "../visitor" );
|
||||||
|
|
||||||
// Checks that each rule is defined only once.
|
// Checks that each rule is defined only once.
|
||||||
function reportDuplicateRules(ast) {
|
function reportDuplicateRules( ast ) {
|
||||||
let rules = {};
|
|
||||||
|
const rules = {};
|
||||||
let check = visitor.build({
|
|
||||||
rule(node) {
|
const check = visitor.build( {
|
||||||
if (Object.prototype.hasOwnProperty.call(rules, node.name)) {
|
rule( node ) {
|
||||||
throw new GrammarError(
|
|
||||||
"Rule \"" + node.name + "\" is already defined "
|
const name = node.name;
|
||||||
+ "at line " + rules[node.name].start.line + ", "
|
|
||||||
+ "column " + rules[node.name].start.column + ".",
|
if ( Object.prototype.hasOwnProperty.call( rules, name ) ) {
|
||||||
node.location
|
|
||||||
);
|
const start = rules[ name ].start;
|
||||||
}
|
|
||||||
|
throw new GrammarError(
|
||||||
rules[node.name] = node.location;
|
`Rule "${ name }" is already defined at line ${ start.line }, column ${ start.column }.`,
|
||||||
}
|
node.location
|
||||||
});
|
);
|
||||||
|
|
||||||
check(ast);
|
}
|
||||||
|
|
||||||
|
rules[ node.name ] = node.location;
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
check( ast );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = reportDuplicateRules;
|
module.exports = reportDuplicateRules;
|
||||||
|
@ -1,33 +1,43 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let GrammarError = require("../../grammar-error");
|
const GrammarError = require( "../../grammar-error" );
|
||||||
let asts = require("../asts");
|
const asts = require( "../asts" );
|
||||||
let visitor = require("../visitor");
|
const visitor = require( "../visitor" );
|
||||||
|
|
||||||
// Reports expressions that don't consume any input inside |*| or |+| in the
|
// Reports expressions that don't consume any input inside |*| or |+| in the
|
||||||
// grammar, which prevents infinite loops in the generated parser.
|
// grammar, which prevents infinite loops in the generated parser.
|
||||||
function reportInfiniteRepetition(ast) {
|
function reportInfiniteRepetition( ast ) {
|
||||||
let check = visitor.build({
|
|
||||||
zero_or_more(node) {
|
const check = visitor.build( {
|
||||||
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
|
zero_or_more( node ) {
|
||||||
throw new GrammarError(
|
|
||||||
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
if ( ! asts.alwaysConsumesOnSuccess( ast, node.expression ) ) {
|
||||||
node.location
|
|
||||||
);
|
throw new GrammarError(
|
||||||
}
|
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
||||||
},
|
node.location
|
||||||
|
);
|
||||||
one_or_more(node) {
|
|
||||||
if (!asts.alwaysConsumesOnSuccess(ast, node.expression)) {
|
}
|
||||||
throw new GrammarError(
|
|
||||||
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
},
|
||||||
node.location
|
|
||||||
);
|
one_or_more( node ) {
|
||||||
}
|
|
||||||
}
|
if ( ! asts.alwaysConsumesOnSuccess( ast, node.expression ) ) {
|
||||||
});
|
|
||||||
|
throw new GrammarError(
|
||||||
check(ast);
|
"Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
||||||
|
node.location
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
check( ast );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = reportInfiniteRepetition;
|
module.exports = reportInfiniteRepetition;
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let GrammarError = require("../../grammar-error");
|
const GrammarError = require( "../../grammar-error" );
|
||||||
let asts = require("../asts");
|
const asts = require( "../asts" );
|
||||||
let visitor = require("../visitor");
|
const visitor = require( "../visitor" );
|
||||||
|
|
||||||
// Checks that all referenced rules exist.
|
// Checks that all referenced rules exist.
|
||||||
function reportUndefinedRules(ast, options) {
|
function reportUndefinedRules( ast, options ) {
|
||||||
let check = visitor.build({
|
|
||||||
rule_ref(node) {
|
const check = visitor.build( {
|
||||||
if (!asts.findRule(ast, node.name)) {
|
rule_ref( node ) {
|
||||||
throw new GrammarError(
|
|
||||||
"Rule \"" + node.name + "\" is not defined.",
|
if ( ! asts.findRule( ast, node.name ) ) {
|
||||||
node.location
|
|
||||||
);
|
throw new GrammarError(
|
||||||
}
|
`Rule "${ node.name }" is not defined.`,
|
||||||
|
node.location
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
check( ast );
|
||||||
|
|
||||||
|
if ( options.allowedStartRules ) {
|
||||||
|
|
||||||
|
options.allowedStartRules.forEach( rule => {
|
||||||
|
|
||||||
|
if ( ! asts.findRule( ast, rule ) ) {
|
||||||
|
|
||||||
|
throw new GrammarError( `Start rule "${ rule }" is not defined.` );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
check(ast);
|
|
||||||
|
|
||||||
if (options.allowedStartRules) {
|
|
||||||
options.allowedStartRules.forEach(rule => {
|
|
||||||
if (!asts.findRule(ast, rule)) {
|
|
||||||
throw new GrammarError(
|
|
||||||
"Start rule \"" + rule + "\" is not defined.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = reportUndefinedRules;
|
module.exports = reportUndefinedRules;
|
||||||
|
@ -1,75 +1,97 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Simple AST node visitor builder.
|
// Simple AST node visitor builder.
|
||||||
let visitor = {
|
const visitor = {
|
||||||
build(functions) {
|
build( functions ) {
|
||||||
function visit(node) {
|
|
||||||
return functions[node.type].apply(null, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
function visitNop() {
|
function visit( node ) {
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
function visitExpression(node) {
|
return functions[ node.type ].apply( null, arguments );
|
||||||
let extraArgs = Array.prototype.slice.call(arguments, 1);
|
|
||||||
|
|
||||||
visit.apply(null, [node.expression].concat(extraArgs));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function visitChildren(property) {
|
function visitNop() {
|
||||||
return function(node) {
|
// Do nothing.
|
||||||
let extraArgs = Array.prototype.slice.call(arguments, 1);
|
}
|
||||||
|
|
||||||
node[property].forEach(child => {
|
function visitExpression( node ) {
|
||||||
visit.apply(null, [child].concat(extraArgs));
|
|
||||||
});
|
const extraArgs = Array.prototype.slice.call( arguments, 1 );
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_FUNCTIONS = {
|
visit( ...[ node.expression ].concat( extraArgs ) );
|
||||||
grammar(node) {
|
|
||||||
let extraArgs = Array.prototype.slice.call(arguments, 1);
|
|
||||||
|
|
||||||
if (node.initializer) {
|
|
||||||
visit.apply(null, [node.initializer].concat(extraArgs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.rules.forEach(rule => {
|
function visitChildren( property ) {
|
||||||
visit.apply(null, [rule].concat(extraArgs));
|
|
||||||
});
|
return function visitProperty( node ) {
|
||||||
},
|
|
||||||
|
const extraArgs = Array.prototype.slice.call( arguments, 1 );
|
||||||
initializer: visitNop,
|
|
||||||
rule: visitExpression,
|
node[ property ].forEach( child => {
|
||||||
named: visitExpression,
|
|
||||||
choice: visitChildren("alternatives"),
|
visit( ...[ child ].concat( extraArgs ) );
|
||||||
action: visitExpression,
|
|
||||||
sequence: visitChildren("elements"),
|
} );
|
||||||
labeled: visitExpression,
|
|
||||||
text: visitExpression,
|
};
|
||||||
simple_and: visitExpression,
|
|
||||||
simple_not: visitExpression,
|
}
|
||||||
optional: visitExpression,
|
|
||||||
zero_or_more: visitExpression,
|
const DEFAULT_FUNCTIONS = {
|
||||||
one_or_more: visitExpression,
|
grammar( node ) {
|
||||||
group: visitExpression,
|
|
||||||
semantic_and: visitNop,
|
const extraArgs = Array.prototype.slice.call( arguments, 1 );
|
||||||
semantic_not: visitNop,
|
|
||||||
rule_ref: visitNop,
|
if ( node.initializer ) {
|
||||||
literal: visitNop,
|
|
||||||
class: visitNop,
|
visit( ...[ node.initializer ].concat( extraArgs ) );
|
||||||
any: visitNop
|
|
||||||
};
|
}
|
||||||
|
|
||||||
Object.keys(DEFAULT_FUNCTIONS).forEach(type => {
|
node.rules.forEach( rule => {
|
||||||
if (!Object.prototype.hasOwnProperty.call(functions, type)) {
|
|
||||||
functions[type] = DEFAULT_FUNCTIONS[type];
|
visit( ...[ rule ].concat( extraArgs ) );
|
||||||
}
|
|
||||||
});
|
} );
|
||||||
|
|
||||||
return visit;
|
},
|
||||||
}
|
|
||||||
|
initializer: visitNop,
|
||||||
|
rule: visitExpression,
|
||||||
|
named: visitExpression,
|
||||||
|
choice: visitChildren( "alternatives" ),
|
||||||
|
action: visitExpression,
|
||||||
|
sequence: visitChildren( "elements" ),
|
||||||
|
labeled: visitExpression,
|
||||||
|
text: visitExpression,
|
||||||
|
simple_and: visitExpression,
|
||||||
|
simple_not: visitExpression,
|
||||||
|
optional: visitExpression,
|
||||||
|
zero_or_more: visitExpression,
|
||||||
|
one_or_more: visitExpression,
|
||||||
|
group: visitExpression,
|
||||||
|
semantic_and: visitNop,
|
||||||
|
semantic_not: visitNop,
|
||||||
|
rule_ref: visitNop,
|
||||||
|
literal: visitNop,
|
||||||
|
class: visitNop,
|
||||||
|
any: visitNop
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys( DEFAULT_FUNCTIONS ).forEach( type => {
|
||||||
|
|
||||||
|
if ( ! Object.prototype.hasOwnProperty.call( functions, type ) ) {
|
||||||
|
|
||||||
|
functions[ type ] = DEFAULT_FUNCTIONS[ type ];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
return visit;
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = visitor;
|
module.exports = visitor;
|
||||||
|
@ -1,54 +1,64 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let GrammarError = require("./grammar-error");
|
const GrammarError = require( "./grammar-error" );
|
||||||
let compiler = require("./compiler");
|
const compiler = require( "./compiler" );
|
||||||
let parser = require("./parser");
|
const parser = require( "./parser" );
|
||||||
|
|
||||||
let peg = {
|
const peg = {
|
||||||
// PEG.js version (uses semantic versioning).
|
// PEG.js version (uses semantic versioning).
|
||||||
VERSION: "0.10.0",
|
VERSION: "0.10.0",
|
||||||
|
|
||||||
GrammarError: GrammarError,
|
GrammarError: GrammarError,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
compiler: compiler,
|
compiler: compiler,
|
||||||
|
|
||||||
// Generates a parser from a specified grammar and returns it.
|
// Generates a parser from a specified grammar and returns it.
|
||||||
//
|
//
|
||||||
// The grammar must be a string in the format described by the metagramar in
|
// The grammar must be a string in the format described by the metagramar in
|
||||||
// the parser.pegjs file.
|
// the parser.pegjs file.
|
||||||
//
|
//
|
||||||
// Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
|
// Throws |peg.parser.SyntaxError| if the grammar contains a syntax error or
|
||||||
// |peg.GrammarError| if it contains a semantic error. Note that not all
|
// |peg.GrammarError| if it contains a semantic error. Note that not all
|
||||||
// errors are detected during the generation and some may protrude to the
|
// errors are detected during the generation and some may protrude to the
|
||||||
// generated parser and cause its malfunction.
|
// generated parser and cause its malfunction.
|
||||||
generate(grammar, options) {
|
generate( grammar, options ) {
|
||||||
options = options !== undefined ? options : {};
|
|
||||||
|
options = typeof options !== "undefined" ? options : {};
|
||||||
function convertPasses(passes) {
|
|
||||||
let converted = {};
|
function convertPasses( passes ) {
|
||||||
|
|
||||||
Object.keys(passes).forEach(stage => {
|
const converted = {};
|
||||||
converted[stage] = Object.keys(passes[stage])
|
|
||||||
.map(name => passes[stage][name]);
|
Object.keys( passes ).forEach( stage => {
|
||||||
});
|
|
||||||
|
|
||||||
return converted;
|
|
||||||
}
|
|
||||||
|
|
||||||
let plugins = "plugins" in options ? options.plugins : [];
|
converted[ stage ] = Object.keys( passes[ stage ] )
|
||||||
let config = {
|
.map( name => passes[ stage ][ name ] );
|
||||||
parser: peg.parser,
|
|
||||||
passes: convertPasses(peg.compiler.passes)
|
|
||||||
};
|
|
||||||
|
|
||||||
plugins.forEach(p => { p.use(config, options); });
|
} );
|
||||||
|
|
||||||
return peg.compiler.compile(
|
return converted;
|
||||||
config.parser.parse(grammar),
|
|
||||||
config.passes,
|
}
|
||||||
options
|
|
||||||
);
|
const plugins = "plugins" in options ? options.plugins : [];
|
||||||
}
|
const config = {
|
||||||
|
parser: peg.parser,
|
||||||
|
passes: convertPasses( peg.compiler.passes )
|
||||||
|
};
|
||||||
|
|
||||||
|
plugins.forEach( p => {
|
||||||
|
|
||||||
|
p.use( config, options );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
return peg.compiler.compile(
|
||||||
|
config.parser.parse( grammar ),
|
||||||
|
config.passes,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = peg;
|
module.exports = peg;
|
||||||
|
@ -1,42 +1,42 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let benchmarks = [
|
const benchmarks = [
|
||||||
{
|
{
|
||||||
id: "json",
|
id: "json",
|
||||||
title: "JSON",
|
title: "JSON",
|
||||||
tests: [
|
tests: [
|
||||||
{ file: "example1.json", title: "Example 1" },
|
{ file: "example1.json", title: "Example 1" },
|
||||||
{ file: "example2.json", title: "Example 2" },
|
{ file: "example2.json", title: "Example 2" },
|
||||||
{ file: "example3.json", title: "Example 3" },
|
{ file: "example3.json", title: "Example 3" },
|
||||||
{ file: "example4.json", title: "Example 4" },
|
{ file: "example4.json", title: "Example 4" },
|
||||||
{ file: "example5.json", title: "Example 5" }
|
{ file: "example5.json", title: "Example 5" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "css",
|
id: "css",
|
||||||
title: "CSS",
|
title: "CSS",
|
||||||
tests: [
|
tests: [
|
||||||
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
|
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
|
||||||
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
|
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
|
||||||
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
|
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
|
||||||
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
|
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
|
||||||
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
|
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
|
||||||
// Contains syntax errors.
|
// Contains syntax errors.
|
||||||
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
|
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
|
||||||
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
|
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
|
||||||
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
|
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
|
||||||
// Contains syntax errors.
|
// Contains syntax errors.
|
||||||
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
|
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
|
||||||
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
|
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
|
||||||
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
|
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
|
||||||
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
|
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
|
||||||
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
|
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
|
||||||
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
|
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
|
||||||
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
|
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
|
||||||
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
|
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
|
||||||
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
|
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = benchmarks;
|
module.exports = benchmarks;
|
||||||
|
@ -1,118 +1,150 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global setTimeout */
|
const peg = require( "../../lib/peg" );
|
||||||
|
|
||||||
let peg = require("../../lib/peg");
|
const Runner = {
|
||||||
|
run( benchmarks, runCount, options, callbacks ) {
|
||||||
|
|
||||||
let Runner = {
|
// Queue
|
||||||
run(benchmarks, runCount, options, callbacks) {
|
|
||||||
// Queue
|
|
||||||
|
|
||||||
let Q = {
|
const Q = {
|
||||||
functions: [],
|
functions: [],
|
||||||
|
|
||||||
add(f) {
|
add( f ) {
|
||||||
this.functions.push(f);
|
|
||||||
},
|
|
||||||
|
|
||||||
run() {
|
this.functions.push( f );
|
||||||
if (this.functions.length > 0) {
|
|
||||||
this.functions.shift()();
|
},
|
||||||
|
|
||||||
|
run() {
|
||||||
|
|
||||||
|
if ( this.functions.length > 0 ) {
|
||||||
|
|
||||||
|
this.functions.shift()();
|
||||||
|
|
||||||
|
// We can't use |arguments.callee| here because |this| would get
|
||||||
|
// messed-up in that case.
|
||||||
|
setTimeout( () => {
|
||||||
|
|
||||||
|
Q.run();
|
||||||
|
|
||||||
|
}, 0 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The benchmark itself is factored out into several functions (some of them
|
||||||
|
// generated), which are enqueued and run one by one using |setTimeout|. We
|
||||||
|
// do this for two reasons:
|
||||||
|
//
|
||||||
|
// 1. To avoid bowser mechanism for interrupting long-running scripts to
|
||||||
|
// kick-in (or at least to not kick-in that often).
|
||||||
|
//
|
||||||
|
// 2. To ensure progressive rendering of results in the browser (some
|
||||||
|
// browsers do not render at all when running JavaScript code).
|
||||||
|
//
|
||||||
|
// The enqueued functions share state, which is all stored in the properties
|
||||||
|
// of the |state| object.
|
||||||
|
|
||||||
|
const state = {};
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
|
||||||
|
callbacks.start();
|
||||||
|
|
||||||
|
state.totalInputSize = 0;
|
||||||
|
state.totalParseTime = 0;
|
||||||
|
|
||||||
// We can't use |arguments.callee| here because |this| would get
|
|
||||||
// messed-up in that case.
|
|
||||||
setTimeout(() => { Q.run(); }, 0);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The benchmark itself is factored out into several functions (some of them
|
|
||||||
// generated), which are enqueued and run one by one using |setTimeout|. We
|
|
||||||
// do this for two reasons:
|
|
||||||
//
|
|
||||||
// 1. To avoid bowser mechanism for interrupting long-running scripts to
|
|
||||||
// kick-in (or at least to not kick-in that often).
|
|
||||||
//
|
|
||||||
// 2. To ensure progressive rendering of results in the browser (some
|
|
||||||
// browsers do not render at all when running JavaScript code).
|
|
||||||
//
|
|
||||||
// The enqueued functions share state, which is all stored in the properties
|
|
||||||
// of the |state| object.
|
|
||||||
|
|
||||||
let state = {};
|
|
||||||
|
|
||||||
function initialize() {
|
|
||||||
callbacks.start();
|
|
||||||
|
|
||||||
state.totalInputSize = 0;
|
|
||||||
state.totalParseTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function benchmarkInitializer(benchmark) {
|
function benchmarkInitializer( benchmark ) {
|
||||||
return function() {
|
|
||||||
callbacks.benchmarkStart(benchmark);
|
return function () {
|
||||||
|
|
||||||
state.parser = peg.generate(
|
callbacks.benchmarkStart( benchmark );
|
||||||
callbacks.readFile("../../examples/" + benchmark.id + ".pegjs"),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
state.benchmarkInputSize = 0;
|
|
||||||
state.benchmarkParseTime = 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function testRunner(benchmark, test) {
|
state.parser = peg.generate(
|
||||||
return function() {
|
callbacks.readFile( "../../examples/" + benchmark.id + ".pegjs" ),
|
||||||
callbacks.testStart(benchmark, test);
|
options
|
||||||
|
);
|
||||||
|
state.benchmarkInputSize = 0;
|
||||||
|
state.benchmarkParseTime = 0;
|
||||||
|
|
||||||
let input = callbacks.readFile(benchmark.id + "/" + test.file);
|
};
|
||||||
|
|
||||||
let parseTime = 0;
|
|
||||||
for (let i = 0; i < runCount; i++) {
|
|
||||||
let t = (new Date()).getTime();
|
|
||||||
state.parser.parse(input);
|
|
||||||
parseTime += (new Date()).getTime() - t;
|
|
||||||
}
|
}
|
||||||
let averageParseTime = parseTime / runCount;
|
|
||||||
|
|
||||||
callbacks.testFinish(benchmark, test, input.length, averageParseTime);
|
function testRunner( benchmark, test ) {
|
||||||
|
|
||||||
state.benchmarkInputSize += input.length;
|
return function () {
|
||||||
state.benchmarkParseTime += averageParseTime;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function benchmarkFinalizer(benchmark) {
|
callbacks.testStart( benchmark, test );
|
||||||
return function() {
|
|
||||||
callbacks.benchmarkFinish(
|
|
||||||
benchmark,
|
|
||||||
state.benchmarkInputSize,
|
|
||||||
state.benchmarkParseTime
|
|
||||||
);
|
|
||||||
|
|
||||||
state.totalInputSize += state.benchmarkInputSize;
|
|
||||||
state.totalParseTime += state.benchmarkParseTime;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalize() {
|
const input = callbacks.readFile( benchmark.id + "/" + test.file );
|
||||||
callbacks.finish(state.totalInputSize, state.totalParseTime);
|
|
||||||
}
|
let parseTime = 0;
|
||||||
|
for ( let i = 0; i < runCount; i++ ) {
|
||||||
|
|
||||||
|
const t = ( new Date() ).getTime();
|
||||||
|
state.parser.parse( input );
|
||||||
|
parseTime += ( new Date() ).getTime() - t;
|
||||||
|
|
||||||
// Main
|
}
|
||||||
|
const averageParseTime = parseTime / runCount;
|
||||||
|
|
||||||
Q.add(initialize);
|
callbacks.testFinish( benchmark, test, input.length, averageParseTime );
|
||||||
benchmarks.forEach(benchmark => {
|
|
||||||
Q.add(benchmarkInitializer(benchmark));
|
|
||||||
benchmark.tests.forEach(test => {
|
|
||||||
Q.add(testRunner(benchmark, test));
|
|
||||||
});
|
|
||||||
Q.add(benchmarkFinalizer(benchmark));
|
|
||||||
});
|
|
||||||
Q.add(finalize);
|
|
||||||
|
|
||||||
Q.run();
|
state.benchmarkInputSize += input.length;
|
||||||
}
|
state.benchmarkParseTime += averageParseTime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchmarkFinalizer( benchmark ) {
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
|
||||||
|
callbacks.benchmarkFinish(
|
||||||
|
benchmark,
|
||||||
|
state.benchmarkInputSize,
|
||||||
|
state.benchmarkParseTime
|
||||||
|
);
|
||||||
|
|
||||||
|
state.totalInputSize += state.benchmarkInputSize;
|
||||||
|
state.totalParseTime += state.benchmarkParseTime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalize() {
|
||||||
|
|
||||||
|
callbacks.finish( state.totalInputSize, state.totalParseTime );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main
|
||||||
|
|
||||||
|
Q.add( initialize );
|
||||||
|
benchmarks.forEach( benchmark => {
|
||||||
|
|
||||||
|
Q.add( benchmarkInitializer( benchmark ) );
|
||||||
|
benchmark.tests.forEach( test => {
|
||||||
|
|
||||||
|
Q.add( testRunner( benchmark, test ) );
|
||||||
|
|
||||||
|
} );
|
||||||
|
Q.add( benchmarkFinalizer( benchmark ) );
|
||||||
|
|
||||||
|
} );
|
||||||
|
Q.add( finalize );
|
||||||
|
|
||||||
|
Q.run();
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Runner;
|
module.exports = Runner;
|
||||||
|
@ -1,150 +1,179 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/* eslint camelcase:0, max-len:0, one-var:0 */
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Measures impact of a Git commit (or multiple commits) on generated parsers
|
// Measures impact of a Git commit (or multiple commits) on generated parsers
|
||||||
// speed and size. Makes sense to use only on PEG.js git repository checkout.
|
// speed and size. Makes sense to use only on PEG.js git repository checkout.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
/* eslint prefer-const: 0 */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let child_process = require("child_process");
|
const child_process = require( "child_process" );
|
||||||
let fs = require("fs");
|
const fs = require( "fs" );
|
||||||
let os = require("os");
|
const os = require( "os" );
|
||||||
let path = require("path");
|
const path = require( "path" );
|
||||||
let glob = require("glob");
|
const dedent = require( "dedent" );
|
||||||
|
const glob = require( "glob" );
|
||||||
|
|
||||||
// Current Working Directory
|
// Current Working Directory
|
||||||
|
|
||||||
let cwd = path.join(__dirname, "..");
|
const cwd = path.join( __dirname, ".." );
|
||||||
|
if ( process.cwd() !== cwd ) process.chdir( cwd );
|
||||||
if (process.cwd() !== cwd) {
|
|
||||||
process.chdir(cwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execution Files
|
// Execution Files
|
||||||
|
|
||||||
let PEGJS_BIN = "bin/peg.js";
|
let PEGJS_BIN = "bin/peg.js";
|
||||||
let BENCHMARK_BIN = "test/benchmark/run";
|
let BENCHMARK_BIN = "test/benchmark/run";
|
||||||
|
|
||||||
if (!fs.existsSync(PEGJS_BIN)) {
|
if ( ! fs.existsSync( PEGJS_BIN ) ) {
|
||||||
PEGJS_BIN = "bin/pegjs";
|
|
||||||
|
PEGJS_BIN = "bin/pegjs";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(BENCHMARK_BIN)) {
|
if ( ! fs.existsSync( BENCHMARK_BIN ) ) {
|
||||||
BENCHMARK_BIN = "benchmark/run";
|
|
||||||
|
BENCHMARK_BIN = "benchmark/run";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
let print = console.log;
|
function echo( message ) {
|
||||||
|
|
||||||
|
process.stdout.write( message );
|
||||||
|
|
||||||
function echo(message) {
|
|
||||||
process.stdout.write(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(command) {
|
function exec( command ) {
|
||||||
return child_process.execSync(command, { encoding: "utf8" });
|
|
||||||
|
return child_process.execSync( command, { encoding: "utf8" } );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepare(commit) {
|
function prepare( commit ) {
|
||||||
exec(`git checkout --quiet "${commit}"`);
|
|
||||||
|
exec( `git checkout --quiet "${ commit }"` );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function runBenchmark() {
|
function runBenchmark() {
|
||||||
return parseFloat(
|
|
||||||
exec("node " + BENCHMARK_BIN)
|
return parseFloat(
|
||||||
// Split by table seprator, reverse and return the total bytes per second
|
exec( "node " + BENCHMARK_BIN )
|
||||||
.split("│")
|
|
||||||
.reverse()[1]
|
// Split by table seprator, reverse and return the total bytes per second
|
||||||
// Trim the whitespaces and remove ` kB/s` from the end
|
.split( "│" )
|
||||||
.trim()
|
.reverse()[ 1 ]
|
||||||
.slice(0, -5)
|
|
||||||
);
|
// Trim the whitespaces and remove ` kB/s` from the end
|
||||||
|
.trim()
|
||||||
|
.slice( 0, -5 )
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function measureSpeed() {
|
function measureSpeed() {
|
||||||
return (runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5).toFixed(2);
|
|
||||||
|
return ( runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() + runBenchmark() / 5 ).toFixed( 2 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function measureSize() {
|
function measureSize() {
|
||||||
let size = 0;
|
|
||||||
|
|
||||||
glob.sync("examples/*.pegjs")
|
let size = 0;
|
||||||
.forEach(example => {
|
|
||||||
exec(`node ${PEGJS_BIN} ${example}`);
|
glob.sync( "examples/*.pegjs" )
|
||||||
example = example.slice(0, -5) + "js";
|
.forEach( example => {
|
||||||
size += fs.statSync(example).size;
|
|
||||||
fs.unlinkSync(example);
|
exec( `node ${ PEGJS_BIN } ${ example }` );
|
||||||
});
|
example = example.slice( 0, -5 ) + "js";
|
||||||
|
size += fs.statSync( example ).size;
|
||||||
|
fs.unlinkSync( example );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
return size;
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function difference($1, $2) {
|
function difference( $1, $2 ) {
|
||||||
return (($2 / $1 - 1) * 100).toFixed(4);
|
|
||||||
|
return ( ( $2 / $1 - 1 ) * 100 ).toFixed( 4 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare
|
// Prepare
|
||||||
|
|
||||||
let argv = process.argv.slice(2);
|
const argv = process.argv.slice( 2 );
|
||||||
let commit_before, commit_after;
|
let commit_before, commit_after;
|
||||||
|
|
||||||
if (argv.length === 1) {
|
if ( argv.length === 1 ) {
|
||||||
commit_before = argv[0] + "~1";
|
|
||||||
commit_after = argv[0];
|
commit_before = argv[ 0 ] + "~1";
|
||||||
} else if (argv.length === 2) {
|
commit_after = argv[ 0 ];
|
||||||
commit_before = argv[0];
|
|
||||||
commit_after = argv[1];
|
} else if ( argv.length === 2 ) {
|
||||||
|
|
||||||
|
commit_before = argv[ 0 ];
|
||||||
|
commit_after = argv[ 1 ];
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
print("Usage:");
|
|
||||||
print("");
|
console.log( dedent`
|
||||||
print(" test/impact <commit>");
|
|
||||||
print(" test/impact <commit_before> <commit_after>");
|
Usage:
|
||||||
print("");
|
|
||||||
print("Measures impact of a Git commit (or multiple commits) on generated parsers'");
|
test/impact <commit>
|
||||||
print("speed and size. Makes sense to use only on PEG.js Git repository checkout.");
|
test/impact <commit_before> <commit_after>
|
||||||
print("");
|
|
||||||
process.exit(1);
|
Measures impact of a Git commit (or multiple commits) on generated parser's
|
||||||
|
speed and size. Makes sense to use only on PEG.js Git repository checkout.
|
||||||
|
|
||||||
|
` );
|
||||||
|
process.exit( 1 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure
|
// Measure
|
||||||
|
|
||||||
let branch = exec("git rev-parse --abbrev-ref HEAD");
|
const branch = exec( "git rev-parse --abbrev-ref HEAD" );
|
||||||
let speed1, size1, speed2, size2;
|
let speed1, size1, speed2, size2;
|
||||||
|
|
||||||
echo(`Measuring commit ${commit_before}...`);
|
echo( `Measuring commit ${ commit_before }...` );
|
||||||
prepare(commit_before);
|
prepare( commit_before );
|
||||||
speed1 = measureSpeed();
|
speed1 = measureSpeed();
|
||||||
size1 = measureSize();
|
size1 = measureSize();
|
||||||
echo(" OK" + os.EOL);
|
echo( " OK" + os.EOL );
|
||||||
|
|
||||||
echo(`Measuring commit ${commit_after}...`);
|
echo( `Measuring commit ${ commit_after }...` );
|
||||||
prepare(commit_after);
|
prepare( commit_after );
|
||||||
speed2 = measureSpeed();
|
speed2 = measureSpeed();
|
||||||
size2 = measureSize();
|
size2 = measureSize();
|
||||||
echo(" OK" + os.EOL);
|
echo( " OK" + os.EOL );
|
||||||
|
|
||||||
// Finish
|
// Finish
|
||||||
|
|
||||||
prepare(branch);
|
prepare( branch );
|
||||||
|
|
||||||
|
console.log( dedent`
|
||||||
|
|
||||||
|
test/impact ${ commit_before } ${ commit_after }
|
||||||
|
|
||||||
print(`
|
Speed impact
|
||||||
test/impact ${commit_before} ${commit_after}
|
------------
|
||||||
|
Before: ${ speed1 } kB/s
|
||||||
|
After: ${ speed2 } kB/s
|
||||||
|
Difference: ${ difference( parseFloat( speed1 ), parseFloat( speed2 ) ) }%
|
||||||
|
|
||||||
Speed impact
|
Size impact
|
||||||
------------
|
-----------
|
||||||
Before: ${speed1} kB/s
|
Before: ${ size1 } b
|
||||||
After: ${speed2} kB/s
|
After: ${ size2 } b
|
||||||
Difference: ${difference(parseFloat(speed1), parseFloat(speed2))}%
|
Difference: ${ difference( size1, size2 ) }%
|
||||||
|
|
||||||
Size impact
|
- Measured by /test/impact with Node.js ${ process.version }
|
||||||
-----------
|
- Your system: ${ os.type() } ${ os.release() } ${ os.arch() }.
|
||||||
Before: ${size1} b
|
|
||||||
After: ${size2} b
|
|
||||||
Difference: ${difference(size1, size2)}%
|
|
||||||
|
|
||||||
- Measured by /test/impact with Node.js ${process.version}
|
` );
|
||||||
- Your system: ${os.type()} ${os.release()} ${os.arch()}.
|
|
||||||
`);
|
|
||||||
|
@ -1,168 +1,216 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* global console */
|
const chai = require( "chai" );
|
||||||
|
const peg = require( "../../../lib/peg" );
|
||||||
let chai = require("chai");
|
const sinon = require( "sinon" );
|
||||||
let peg = require("../../../lib/peg");
|
|
||||||
let sinon = require("sinon");
|
const expect = chai.expect;
|
||||||
|
|
||||||
let expect = chai.expect;
|
describe( "generated parser API", function () {
|
||||||
|
|
||||||
describe("generated parser API", function() {
|
describe( "parse", function () {
|
||||||
describe("parse", function() {
|
|
||||||
it("parses input", function() {
|
it( "parses input", function () {
|
||||||
let parser = peg.generate("start = 'a'");
|
|
||||||
|
const parser = peg.generate( "start = 'a'" );
|
||||||
expect(parser.parse("a")).to.equal("a");
|
expect( parser.parse( "a" ) ).to.equal( "a" );
|
||||||
});
|
|
||||||
|
} );
|
||||||
it("throws an exception on syntax error", function() {
|
|
||||||
let parser = peg.generate("start = 'a'");
|
it( "throws an exception on syntax error", function () {
|
||||||
|
|
||||||
expect(() => { parser.parse("b"); }).to.throw();
|
const parser = peg.generate( "start = 'a'" );
|
||||||
});
|
expect( () => {
|
||||||
|
|
||||||
describe("start rule", function() {
|
parser.parse( "b" );
|
||||||
let parser = peg.generate([
|
|
||||||
"a = 'x' { return 'a'; }",
|
} ).to.throw();
|
||||||
"b = 'x' { return 'b'; }",
|
|
||||||
"c = 'x' { return 'c'; }"
|
} );
|
||||||
].join("\n"), { allowedStartRules: ["b", "c"] });
|
|
||||||
|
describe( "start rule", function () {
|
||||||
describe("when |startRule| is not set", function() {
|
|
||||||
it("starts parsing from the first allowed rule", function() {
|
const parser = peg.generate( `
|
||||||
expect(parser.parse("x")).to.equal("b");
|
|
||||||
});
|
a = 'x' { return 'a'; }
|
||||||
});
|
b = 'x' { return 'b'; }
|
||||||
|
c = 'x' { return 'c'; }
|
||||||
describe("when |startRule| is set to an allowed rule", function() {
|
|
||||||
it("starts parsing from specified rule", function() {
|
`, { allowedStartRules: [ "b", "c" ] } );
|
||||||
expect(parser.parse("x", { startRule: "b" })).to.equal("b");
|
|
||||||
expect(parser.parse("x", { startRule: "c" })).to.equal("c");
|
describe( "when |startRule| is not set", function () {
|
||||||
});
|
|
||||||
});
|
it( "starts parsing from the first allowed rule", function () {
|
||||||
|
|
||||||
describe("when |startRule| is set to a disallowed start rule", function() {
|
expect( parser.parse( "x" ) ).to.equal( "b" );
|
||||||
it("throws an exception", function() {
|
|
||||||
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
|
} );
|
||||||
});
|
|
||||||
});
|
} );
|
||||||
});
|
|
||||||
|
describe( "when |startRule| is set to an allowed rule", function () {
|
||||||
describe("tracing", function() {
|
|
||||||
let parser = peg.generate([
|
it( "starts parsing from specified rule", function () {
|
||||||
"start = a / b",
|
|
||||||
"a = 'a'",
|
expect( parser.parse( "x", { startRule: "b" } ) ).to.equal( "b" );
|
||||||
"b = 'b'"
|
expect( parser.parse( "x", { startRule: "c" } ) ).to.equal( "c" );
|
||||||
].join("\n"), { trace: true });
|
|
||||||
|
} );
|
||||||
describe("default tracer", function() {
|
|
||||||
it("traces using console.log (if console is defined)", function() {
|
} );
|
||||||
let messages = [
|
|
||||||
"1:1-1:1 rule.enter start",
|
describe( "when |startRule| is set to a disallowed start rule", function () {
|
||||||
"1:1-1:1 rule.enter a",
|
|
||||||
"1:1-1:1 rule.fail a",
|
it( "throws an exception", function () {
|
||||||
"1:1-1:1 rule.enter b",
|
|
||||||
"1:1-1:2 rule.match b",
|
expect( () => {
|
||||||
"1:1-1:2 rule.match start"
|
|
||||||
];
|
parser.parse( "x", { startRule: "a" } );
|
||||||
|
|
||||||
if (typeof console === "object") {
|
} ).to.throw();
|
||||||
sinon.stub(console, "log");
|
|
||||||
}
|
} );
|
||||||
|
|
||||||
try {
|
} );
|
||||||
parser.parse("b");
|
|
||||||
|
} );
|
||||||
if (typeof console === "object") {
|
|
||||||
expect(console.log.callCount).to.equal(messages.length);
|
describe( "tracing", function () {
|
||||||
messages.forEach((message, index) => {
|
|
||||||
let call = console.log.getCall(index);
|
const parser = peg.generate( `
|
||||||
expect(call.calledWithExactly(message)).to.equal(true);
|
|
||||||
});
|
start = a / b
|
||||||
}
|
a = 'a'
|
||||||
} finally {
|
b = 'b'
|
||||||
if (typeof console === "object") {
|
|
||||||
console.log.restore();
|
`, { trace: true } );
|
||||||
}
|
|
||||||
}
|
describe( "default tracer", function () {
|
||||||
});
|
|
||||||
});
|
it( "traces using console.log (if console is defined)", function () {
|
||||||
|
|
||||||
describe("custom tracers", function() {
|
const messages = [
|
||||||
describe("trace", function() {
|
"1:1-1:1 rule.enter start",
|
||||||
it("receives tracing events", function() {
|
"1:1-1:1 rule.enter a",
|
||||||
let events = [
|
"1:1-1:1 rule.fail a",
|
||||||
{
|
"1:1-1:1 rule.enter b",
|
||||||
type: "rule.enter",
|
"1:1-1:2 rule.match b",
|
||||||
rule: "start",
|
"1:1-1:2 rule.match start"
|
||||||
location: {
|
];
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
|
||||||
end: { offset: 0, line: 1, column: 1 }
|
if ( typeof console === "object" ) sinon.stub( console, "log" );
|
||||||
}
|
|
||||||
},
|
try {
|
||||||
{
|
|
||||||
type: "rule.enter",
|
parser.parse( "b" );
|
||||||
rule: "a",
|
|
||||||
location: {
|
if ( typeof console === "object" ) {
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
|
||||||
end: { offset: 0, line: 1, column: 1 }
|
expect( console.log.callCount ).to.equal( messages.length );
|
||||||
}
|
messages.forEach( ( message, index ) => {
|
||||||
},
|
|
||||||
{
|
const call = console.log.getCall( index );
|
||||||
type: "rule.fail",
|
expect( call.calledWithExactly( message ) ).to.equal( true );
|
||||||
rule: "a",
|
|
||||||
location: {
|
} );
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
|
||||||
end: { offset: 0, line: 1, column: 1 }
|
}
|
||||||
}
|
|
||||||
},
|
} finally {
|
||||||
{
|
|
||||||
type: "rule.enter",
|
if ( typeof console === "object" ) console.log.restore();
|
||||||
rule: "b",
|
|
||||||
location: {
|
}
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
|
||||||
end: { offset: 0, line: 1, column: 1 }
|
} );
|
||||||
}
|
|
||||||
},
|
} );
|
||||||
{
|
|
||||||
type: "rule.match",
|
describe( "custom tracers", function () {
|
||||||
rule: "b",
|
|
||||||
result: "b",
|
describe( "trace", function () {
|
||||||
location: {
|
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
it( "receives tracing events", function () {
|
||||||
end: { offset: 1, line: 1, column: 2 }
|
|
||||||
}
|
const events = [
|
||||||
},
|
{
|
||||||
{
|
type: "rule.enter",
|
||||||
type: "rule.match",
|
rule: "start",
|
||||||
rule: "start",
|
location: {
|
||||||
result: "b",
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
location: {
|
end: { offset: 0, line: 1, column: 1 }
|
||||||
start: { offset: 0, line: 1, column: 1 },
|
}
|
||||||
end: { offset: 1, line: 1, column: 2 }
|
},
|
||||||
}
|
{
|
||||||
}
|
type: "rule.enter",
|
||||||
];
|
rule: "a",
|
||||||
|
location: {
|
||||||
let tracer = { trace: sinon.spy() };
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
|
end: { offset: 0, line: 1, column: 1 }
|
||||||
parser.parse("b", { tracer: tracer });
|
}
|
||||||
|
},
|
||||||
expect(tracer.trace.callCount).to.equal(events.length);
|
{
|
||||||
events.forEach((event, index) => {
|
type: "rule.fail",
|
||||||
let call = tracer.trace.getCall(index);
|
rule: "a",
|
||||||
expect(call.calledWithExactly(event)).to.equal(true);
|
location: {
|
||||||
});
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
});
|
end: { offset: 0, line: 1, column: 1 }
|
||||||
});
|
}
|
||||||
});
|
},
|
||||||
});
|
{
|
||||||
|
type: "rule.enter",
|
||||||
it("accepts custom options", function() {
|
rule: "b",
|
||||||
let parser = peg.generate("start = 'a'");
|
location: {
|
||||||
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
parser.parse("a", { foo: 42 });
|
end: { offset: 0, line: 1, column: 1 }
|
||||||
});
|
}
|
||||||
});
|
},
|
||||||
});
|
{
|
||||||
|
type: "rule.match",
|
||||||
|
rule: "b",
|
||||||
|
result: "b",
|
||||||
|
location: {
|
||||||
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
|
end: { offset: 1, line: 1, column: 2 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "rule.match",
|
||||||
|
rule: "start",
|
||||||
|
result: "b",
|
||||||
|
location: {
|
||||||
|
start: { offset: 0, line: 1, column: 1 },
|
||||||
|
end: { offset: 1, line: 1, column: 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const tracer = { trace: sinon.spy() };
|
||||||
|
parser.parse( "b", { tracer: tracer } );
|
||||||
|
|
||||||
|
expect( tracer.trace.callCount ).to.equal( events.length );
|
||||||
|
events.forEach( ( event, index ) => {
|
||||||
|
|
||||||
|
const call = tracer.trace.getCall( index );
|
||||||
|
expect( call.calledWithExactly( event ) ).to.equal( true );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "accepts custom options", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( "start = 'a'" );
|
||||||
|
parser.parse( "a", { foo: 42 } );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,206 +1,319 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let peg = require("../../../lib/peg");
|
const peg = require( "../../../lib/peg" );
|
||||||
let sinon = require("sinon");
|
const sinon = require( "sinon" );
|
||||||
|
|
||||||
let expect = chai.expect;
|
|
||||||
|
|
||||||
describe("PEG.js API", function() {
|
|
||||||
describe("generate", function() {
|
|
||||||
it("generates a parser", function() {
|
|
||||||
let parser = peg.generate("start = 'a'");
|
|
||||||
|
|
||||||
expect(parser).to.be.an("object");
|
|
||||||
expect(parser.parse("a")).to.equal("a");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an exception on syntax error", function() {
|
|
||||||
expect(() => { peg.generate("start = @"); }).to.throw();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an exception on semantic error", function() {
|
|
||||||
expect(() => { peg.generate("start = undefined"); }).to.throw();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("allowed start rules", function() {
|
|
||||||
let grammar = [
|
|
||||||
"a = 'x'",
|
|
||||||
"b = 'x'",
|
|
||||||
"c = 'x'"
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
it("throws an error on missing rule", function() {
|
|
||||||
expect(() => peg.generate(grammar, {
|
|
||||||
allowedStartRules: ["missing"]
|
|
||||||
})).to.throw();
|
|
||||||
});
|
|
||||||
|
|
||||||
// The |allowedStartRules| option is implemented separately for each
|
|
||||||
// optimization mode, so we need to test it in both.
|
|
||||||
|
|
||||||
describe("when optimizing for parsing speed", function() {
|
|
||||||
describe("when |allowedStartRules| is not set", function() {
|
|
||||||
it("generated parser can start only from the first rule", function() {
|
|
||||||
let parser = peg.generate(grammar, { optimize: "speed" });
|
|
||||||
|
|
||||||
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
|
|
||||||
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
|
|
||||||
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |allowedStartRules| is set", function() {
|
|
||||||
it("generated parser can start only from specified rules", function() {
|
|
||||||
let parser = peg.generate(grammar, {
|
|
||||||
optimize: "speed",
|
|
||||||
allowedStartRules: ["b", "c"]
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
|
|
||||||
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
|
|
||||||
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when optimizing for code size", function() {
|
|
||||||
describe("when |allowedStartRules| is not set", function() {
|
|
||||||
it("generated parser can start only from the first rule", function() {
|
|
||||||
let parser = peg.generate(grammar, { optimize: "size" });
|
|
||||||
|
|
||||||
expect(parser.parse("x", { startRule: "a" })).to.equal("x");
|
|
||||||
expect(() => { parser.parse("x", { startRule: "b" }); }).to.throw();
|
|
||||||
expect(() => { parser.parse("x", { startRule: "c" }); }).to.throw();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |allowedStartRules| is set", function() {
|
|
||||||
it("generated parser can start only from specified rules", function() {
|
|
||||||
let parser = peg.generate(grammar, {
|
|
||||||
optimize: "size",
|
|
||||||
allowedStartRules: ["b", "c"]
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
|
|
||||||
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
|
|
||||||
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("intermediate results caching", function() {
|
|
||||||
let grammar = [
|
|
||||||
"{ var n = 0; }",
|
|
||||||
"start = (a 'b') / (a 'c') { return n; }",
|
|
||||||
"a = 'a' { n++; }"
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
describe("when |cache| is not set", function() {
|
|
||||||
it("generated parser doesn't cache intermediate parse results", function() {
|
|
||||||
let parser = peg.generate(grammar);
|
|
||||||
|
|
||||||
expect(parser.parse("ac")).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |cache| is set to |false|", function() {
|
|
||||||
it("generated parser doesn't cache intermediate parse results", function() {
|
|
||||||
let parser = peg.generate(grammar, { cache: false });
|
|
||||||
|
|
||||||
expect(parser.parse("ac")).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |cache| is set to |true|", function() {
|
|
||||||
it("generated parser caches intermediate parse results", function() {
|
|
||||||
let parser = peg.generate(grammar, { cache: true });
|
|
||||||
|
|
||||||
expect(parser.parse("ac")).to.equal(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("tracing", function() {
|
|
||||||
let grammar = "start = 'a'";
|
|
||||||
|
|
||||||
describe("when |trace| is not set", function() {
|
|
||||||
it("generated parser doesn't trace", function() {
|
|
||||||
let parser = peg.generate(grammar);
|
|
||||||
let tracer = { trace: sinon.spy() };
|
|
||||||
|
|
||||||
parser.parse("a", { tracer: tracer });
|
|
||||||
|
|
||||||
expect(tracer.trace.called).to.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |trace| is set to |false|", function() {
|
|
||||||
it("generated parser doesn't trace", function() {
|
|
||||||
let parser = peg.generate(grammar, { trace: false });
|
|
||||||
let tracer = { trace: sinon.spy() };
|
|
||||||
|
|
||||||
parser.parse("a", { tracer: tracer });
|
|
||||||
|
|
||||||
expect(tracer.trace.called).to.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |trace| is set to |true|", function() {
|
|
||||||
it("generated parser traces", function() {
|
|
||||||
let parser = peg.generate(grammar, { trace: true });
|
|
||||||
let tracer = { trace: sinon.spy() };
|
|
||||||
|
|
||||||
parser.parse("a", { tracer: tracer });
|
|
||||||
|
|
||||||
expect(tracer.trace.called).to.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// The |optimize| option isn't tested because there is no meaningful way to
|
|
||||||
// write the tests without turning this into a performance test.
|
|
||||||
|
|
||||||
describe("output", function() {
|
|
||||||
let grammar = "start = 'a'";
|
|
||||||
|
|
||||||
describe("when |output| is not set", function() {
|
|
||||||
it("returns generated parser object", function() {
|
|
||||||
let parser = peg.generate(grammar);
|
|
||||||
|
|
||||||
expect(parser).to.be.an("object");
|
|
||||||
expect(parser.parse("a")).to.equal("a");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |output| is set to |\"parser\"|", function() {
|
|
||||||
it("returns generated parser object", function() {
|
|
||||||
let parser = peg.generate(grammar, { output: "parser" });
|
|
||||||
|
|
||||||
expect(parser).to.be.an("object");
|
|
||||||
expect(parser.parse("a")).to.equal("a");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when |output| is set to |\"source\"|", function() {
|
|
||||||
it("returns generated parser source code", function() {
|
|
||||||
let source = peg.generate(grammar, { output: "source" });
|
|
||||||
|
|
||||||
expect(source).to.be.a("string");
|
|
||||||
expect(eval(source).parse("a")).to.equal("a");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// The |format|, |exportVars|, and |dependencies| options are not tested
|
|
||||||
// becasue there is no meaningful way to thest their effects without turning
|
|
||||||
// this into an integration test.
|
|
||||||
|
|
||||||
// The |plugins| option is tested in plugin API tests.
|
const expect = chai.expect;
|
||||||
|
|
||||||
it("accepts custom options", function() {
|
describe( "PEG.js API", function () {
|
||||||
peg.generate("start = 'a'", { foo: 42 });
|
|
||||||
});
|
describe( "generate", function () {
|
||||||
});
|
|
||||||
});
|
it( "generates a parser", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( "start = 'a'" );
|
||||||
|
|
||||||
|
expect( parser ).to.be.an( "object" );
|
||||||
|
expect( parser.parse( "a" ) ).to.equal( "a" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "throws an exception on syntax error", function () {
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
peg.generate( "start = @" );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "throws an exception on semantic error", function () {
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
peg.generate( "start = undefined" );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "allowed start rules", function () {
|
||||||
|
|
||||||
|
const grammar = `
|
||||||
|
|
||||||
|
a = 'x'
|
||||||
|
b = 'x'
|
||||||
|
c = 'x'
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
it( "throws an error on missing rule", function () {
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
peg.generate( grammar, { allowedStartRules: [ "missing" ] } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
// The |allowedStartRules| option is implemented separately for each
|
||||||
|
// optimization mode, so we need to test it in both.
|
||||||
|
|
||||||
|
describe( "when optimizing for parsing speed", function () {
|
||||||
|
|
||||||
|
describe( "when |allowedStartRules| is not set", function () {
|
||||||
|
|
||||||
|
it( "generated parser can start only from the first rule", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { optimize: "speed" } );
|
||||||
|
|
||||||
|
expect( parser.parse( "x", { startRule: "a" } ) ).to.equal( "x" );
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "b" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "c" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |allowedStartRules| is set", function () {
|
||||||
|
|
||||||
|
it( "generated parser can start only from specified rules", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, {
|
||||||
|
optimize: "speed",
|
||||||
|
allowedStartRules: [ "b", "c" ]
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "a" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
expect( parser.parse( "x", { startRule: "b" } ) ).to.equal( "x" );
|
||||||
|
expect( parser.parse( "x", { startRule: "c" } ) ).to.equal( "x" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when optimizing for code size", function () {
|
||||||
|
|
||||||
|
describe( "when |allowedStartRules| is not set", function () {
|
||||||
|
|
||||||
|
it( "generated parser can start only from the first rule", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { optimize: "size" } );
|
||||||
|
|
||||||
|
expect( parser.parse( "x", { startRule: "a" } ) ).to.equal( "x" );
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "b" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "c" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |allowedStartRules| is set", function () {
|
||||||
|
|
||||||
|
it( "generated parser can start only from specified rules", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, {
|
||||||
|
optimize: "size",
|
||||||
|
allowedStartRules: [ "b", "c" ]
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "a" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
expect( parser.parse( "x", { startRule: "b" } ) ).to.equal( "x" );
|
||||||
|
expect( parser.parse( "x", { startRule: "c" } ) ).to.equal( "x" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "intermediate results caching", function () {
|
||||||
|
|
||||||
|
const grammar = `
|
||||||
|
|
||||||
|
{ var n = 0; }
|
||||||
|
start = (a 'b') / (a 'c') { return n; }
|
||||||
|
a = 'a' { n++; }
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe( "when |cache| is not set", function () {
|
||||||
|
|
||||||
|
it( "generated parser doesn't cache intermediate parse results", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar );
|
||||||
|
expect( parser.parse( "ac" ) ).to.equal( 2 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |cache| is set to |false|", function () {
|
||||||
|
|
||||||
|
it( "generated parser doesn't cache intermediate parse results", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { cache: false } );
|
||||||
|
expect( parser.parse( "ac" ) ).to.equal( 2 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |cache| is set to |true|", function () {
|
||||||
|
|
||||||
|
it( "generated parser caches intermediate parse results", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { cache: true } );
|
||||||
|
expect( parser.parse( "ac" ) ).to.equal( 1 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "tracing", function () {
|
||||||
|
|
||||||
|
const grammar = "start = 'a'";
|
||||||
|
|
||||||
|
describe( "when |trace| is not set", function () {
|
||||||
|
|
||||||
|
it( "generated parser doesn't trace", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar );
|
||||||
|
const tracer = { trace: sinon.spy() };
|
||||||
|
|
||||||
|
parser.parse( "a", { tracer: tracer } );
|
||||||
|
|
||||||
|
expect( tracer.trace.called ).to.equal( false );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |trace| is set to |false|", function () {
|
||||||
|
|
||||||
|
it( "generated parser doesn't trace", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { trace: false } );
|
||||||
|
const tracer = { trace: sinon.spy() };
|
||||||
|
|
||||||
|
parser.parse( "a", { tracer: tracer } );
|
||||||
|
|
||||||
|
expect( tracer.trace.called ).to.equal( false );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |trace| is set to |true|", function () {
|
||||||
|
|
||||||
|
it( "generated parser traces", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { trace: true } );
|
||||||
|
const tracer = { trace: sinon.spy() };
|
||||||
|
|
||||||
|
parser.parse( "a", { tracer: tracer } );
|
||||||
|
|
||||||
|
expect( tracer.trace.called ).to.equal( true );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
// The |optimize| option isn't tested because there is no meaningful way to
|
||||||
|
// write the tests without turning this into a performance test.
|
||||||
|
|
||||||
|
describe( "output", function () {
|
||||||
|
|
||||||
|
const grammar = "start = 'a'";
|
||||||
|
|
||||||
|
describe( "when |output| is not set", function () {
|
||||||
|
|
||||||
|
it( "returns generated parser object", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar );
|
||||||
|
|
||||||
|
expect( parser ).to.be.an( "object" );
|
||||||
|
expect( parser.parse( "a" ) ).to.equal( "a" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |output| is set to |\"parser\"|", function () {
|
||||||
|
|
||||||
|
it( "returns generated parser object", function () {
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { output: "parser" } );
|
||||||
|
|
||||||
|
expect( parser ).to.be.an( "object" );
|
||||||
|
expect( parser.parse( "a" ) ).to.equal( "a" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( "when |output| is set to |\"source\"|", function () {
|
||||||
|
|
||||||
|
it( "returns generated parser source code", function () {
|
||||||
|
|
||||||
|
const source = peg.generate( grammar, { output: "source" } );
|
||||||
|
|
||||||
|
expect( source ).to.be.a( "string" );
|
||||||
|
expect( eval( source ).parse( "a" ) ).to.equal( "a" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
// The |format|, |exportVars|, and |dependencies| options are not tested
|
||||||
|
// becasue there is no meaningful way to thest their effects without turning
|
||||||
|
// this into an integration test.
|
||||||
|
|
||||||
|
// The |plugins| option is tested in plugin API tests.
|
||||||
|
|
||||||
|
it( "accepts custom options", function () {
|
||||||
|
|
||||||
|
peg.generate( "start = 'a'", { foo: 42 } );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,128 +1,185 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let peg = require("../../../lib/peg");
|
const peg = require( "../../../lib/peg" );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("plugin API", function() {
|
describe( "plugin API", function () {
|
||||||
describe("use", function() {
|
|
||||||
let grammar = "start = 'a'";
|
describe( "use", function () {
|
||||||
|
|
||||||
it("is called for each plugin", function() {
|
const grammar = "start = 'a'";
|
||||||
let pluginsUsed = [false, false, false];
|
|
||||||
let plugins = [
|
it( "is called for each plugin", function () {
|
||||||
{ use() { pluginsUsed[0] = true; } },
|
|
||||||
{ use() { pluginsUsed[1] = true; } },
|
const pluginsUsed = [ false, false, false ];
|
||||||
{ use() { pluginsUsed[2] = true; } }
|
const plugins = [
|
||||||
];
|
{ use() {
|
||||||
|
|
||||||
peg.generate(grammar, { plugins: plugins });
|
pluginsUsed[ 0 ] = true;
|
||||||
|
|
||||||
expect(pluginsUsed).to.deep.equal([true, true, true]);
|
} },
|
||||||
});
|
{ use() {
|
||||||
|
|
||||||
it("receives configuration", function() {
|
pluginsUsed[ 1 ] = true;
|
||||||
let plugin = {
|
|
||||||
use(config) {
|
} },
|
||||||
expect(config).to.be.an("object");
|
{ use() {
|
||||||
|
|
||||||
expect(config.parser).to.be.an("object");
|
pluginsUsed[ 2 ] = true;
|
||||||
expect(config.parser.parse("start = 'a'")).to.be.an("object");
|
|
||||||
|
} }
|
||||||
expect(config.passes).to.be.an("object");
|
];
|
||||||
|
|
||||||
expect(config.passes.check).to.be.an("array");
|
peg.generate( grammar, { plugins: plugins } );
|
||||||
config.passes.check.forEach(pass => {
|
|
||||||
expect(pass).to.be.a("function");
|
expect( pluginsUsed ).to.deep.equal( [ true, true, true ] );
|
||||||
});
|
|
||||||
|
} );
|
||||||
expect(config.passes.transform).to.be.an("array");
|
|
||||||
config.passes.transform.forEach(pass => {
|
it( "receives configuration", function () {
|
||||||
expect(pass).to.be.a("function");
|
|
||||||
});
|
const plugin = {
|
||||||
|
use( config ) {
|
||||||
expect(config.passes.generate).to.be.an("array");
|
|
||||||
config.passes.generate.forEach(pass => {
|
expect( config ).to.be.an( "object" );
|
||||||
expect(pass).to.be.a("function");
|
|
||||||
});
|
expect( config.parser ).to.be.an( "object" );
|
||||||
}
|
expect( config.parser.parse( "start = 'a'" ) ).to.be.an( "object" );
|
||||||
};
|
|
||||||
|
expect( config.passes ).to.be.an( "object" );
|
||||||
peg.generate(grammar, { plugins: [plugin] });
|
|
||||||
});
|
expect( config.passes.check ).to.be.an( "array" );
|
||||||
|
config.passes.check.forEach( pass => {
|
||||||
it("receives options", function() {
|
|
||||||
let plugin = {
|
expect( pass ).to.be.a( "function" );
|
||||||
use(config, options) {
|
|
||||||
expect(options).to.equal(generateOptions);
|
} );
|
||||||
}
|
|
||||||
};
|
expect( config.passes.transform ).to.be.an( "array" );
|
||||||
let generateOptions = { plugins: [plugin], foo: 42 };
|
config.passes.transform.forEach( pass => {
|
||||||
|
|
||||||
peg.generate(grammar, generateOptions);
|
expect( pass ).to.be.a( "function" );
|
||||||
});
|
|
||||||
|
} );
|
||||||
it("can replace parser", function() {
|
|
||||||
let plugin = {
|
expect( config.passes.generate ).to.be.an( "array" );
|
||||||
use(config) {
|
config.passes.generate.forEach( pass => {
|
||||||
let parser = peg.generate([
|
|
||||||
"start = .* {",
|
expect( pass ).to.be.a( "function" );
|
||||||
" return {",
|
|
||||||
" type: 'grammar',",
|
} );
|
||||||
" rules: [",
|
|
||||||
" {",
|
}
|
||||||
" type: 'rule',",
|
};
|
||||||
" name: 'start',",
|
|
||||||
" expression: { type: 'literal', value: text(), ignoreCase: false }",
|
peg.generate( grammar, { plugins: [ plugin ] } );
|
||||||
" }",
|
|
||||||
" ]",
|
} );
|
||||||
" };",
|
|
||||||
"}"
|
it( "receives options", function () {
|
||||||
].join("\n"));
|
|
||||||
|
const generateOptions = {
|
||||||
config.parser = parser;
|
plugins: [ {
|
||||||
}
|
use( config, options ) {
|
||||||
};
|
|
||||||
let parser = peg.generate("a", { plugins: [plugin] });
|
expect( options ).to.equal( generateOptions );
|
||||||
|
|
||||||
expect(parser.parse("a")).to.equal("a");
|
}
|
||||||
});
|
} ],
|
||||||
|
foo: 42
|
||||||
it("can change compiler passes", function() {
|
};
|
||||||
let plugin = {
|
|
||||||
use(config) {
|
peg.generate( grammar, generateOptions );
|
||||||
function pass(ast) {
|
|
||||||
ast.code = "({ parse: function() { return 42; } })";
|
} );
|
||||||
}
|
|
||||||
|
it( "can replace parser", function () {
|
||||||
config.passes.generate = [pass];
|
|
||||||
}
|
const plugin = {
|
||||||
};
|
use( config ) {
|
||||||
let parser = peg.generate(grammar, { plugins: [plugin] });
|
|
||||||
|
config.parser = peg.generate( `
|
||||||
expect(parser.parse("a")).to.equal(42);
|
|
||||||
});
|
start = .* {
|
||||||
|
return {
|
||||||
it("can change options", function() {
|
type: 'grammar',
|
||||||
let grammar = [
|
rules: [{
|
||||||
"a = 'x'",
|
type: 'rule',
|
||||||
"b = 'x'",
|
name: 'start',
|
||||||
"c = 'x'"
|
expression: {
|
||||||
].join("\n");
|
type: 'literal',
|
||||||
let plugin = {
|
value: text(),
|
||||||
use(config, options) {
|
ignoreCase: false
|
||||||
options.allowedStartRules = ["b", "c"];
|
}
|
||||||
}
|
}]
|
||||||
};
|
};
|
||||||
let parser = peg.generate(grammar, {
|
}
|
||||||
allowedStartRules: ["a"],
|
|
||||||
plugins: [plugin]
|
` );
|
||||||
});
|
|
||||||
|
}
|
||||||
expect(() => { parser.parse("x", { startRule: "a" }); }).to.throw();
|
};
|
||||||
expect(parser.parse("x", { startRule: "b" })).to.equal("x");
|
|
||||||
expect(parser.parse("x", { startRule: "c" })).to.equal("x");
|
const parser = peg.generate( "a", { plugins: [ plugin ] } );
|
||||||
});
|
expect( parser.parse( "a" ) ).to.equal( "a" );
|
||||||
});
|
|
||||||
});
|
} );
|
||||||
|
|
||||||
|
it( "can change compiler passes", function () {
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
use( config ) {
|
||||||
|
|
||||||
|
function pass( ast ) {
|
||||||
|
|
||||||
|
ast.code = "({ parse: function() { return 42; } })";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
config.passes.generate = [ pass ];
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, { plugins: [ plugin ] } );
|
||||||
|
expect( parser.parse( "a" ) ).to.equal( 42 );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "can change options", function () {
|
||||||
|
|
||||||
|
const grammar = `
|
||||||
|
|
||||||
|
a = 'x'
|
||||||
|
b = 'x'
|
||||||
|
c = 'x'
|
||||||
|
|
||||||
|
`;
|
||||||
|
const plugin = {
|
||||||
|
use( config, options ) {
|
||||||
|
|
||||||
|
options.allowedStartRules = [ "b", "c" ];
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = peg.generate( grammar, {
|
||||||
|
allowedStartRules: [ "a" ],
|
||||||
|
plugins: [ plugin ]
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( () => {
|
||||||
|
|
||||||
|
parser.parse( "x", { startRule: "a" } );
|
||||||
|
|
||||||
|
} ).to.throw();
|
||||||
|
expect( parser.parse( "x", { startRule: "b" } ) ).to.equal( "x" );
|
||||||
|
expect( parser.parse( "x", { startRule: "c" } ) ).to.equal( "x" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,90 +1,112 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let parser = require("../../../../../lib/parser");
|
const parser = require( "../../../../../lib/parser" );
|
||||||
|
|
||||||
module.exports = function(chai, utils) {
|
module.exports = function ( chai, utils ) {
|
||||||
let Assertion = chai.Assertion;
|
|
||||||
|
|
||||||
Assertion.addMethod("changeAST", function(grammar, props, options) {
|
const Assertion = chai.Assertion;
|
||||||
options = options !== undefined ? options : {};
|
|
||||||
|
|
||||||
function matchProps(value, props) {
|
Assertion.addMethod( "changeAST", function ( grammar, props, options ) {
|
||||||
function isArray(value) {
|
|
||||||
return Object.prototype.toString.apply(value) === "[object Array]";
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObject(value) {
|
options = typeof options !== "undefined" ? options : {};
|
||||||
return value !== null && typeof value === "object";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isArray(props)) {
|
function matchProps( value, props ) {
|
||||||
if (!isArray(value)) { return false; }
|
|
||||||
|
function isObject( value ) {
|
||||||
|
|
||||||
|
return value !== null && typeof value === "object";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Array.isArray( props ) ) {
|
||||||
|
|
||||||
|
if ( ! Array.isArray( value ) ) return false;
|
||||||
|
if ( value.length !== props.length ) return false;
|
||||||
|
|
||||||
|
for ( let i = 0; i < props.length; i++ ) {
|
||||||
|
|
||||||
|
if ( ! matchProps( value[ i ], props[ i ] ) ) return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if ( isObject( props ) ) {
|
||||||
|
|
||||||
|
if ( ! isObject( value ) ) return false;
|
||||||
|
|
||||||
|
const keys = Object.keys( props );
|
||||||
|
for ( let i = 0; i < keys.length; i++ ) {
|
||||||
|
|
||||||
|
const key = keys[ i ];
|
||||||
|
|
||||||
|
if ( ! ( key in value ) ) return false;
|
||||||
|
if ( ! matchProps( value[ key ], props[ key ] ) ) return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return value === props;
|
||||||
|
|
||||||
if (value.length !== props.length) { return false; }
|
|
||||||
for (let i = 0; i < props.length; i++) {
|
|
||||||
if (!matchProps(value[i], props[i])) { return false; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
const ast = parser.parse( grammar );
|
||||||
} else if (isObject(props)) {
|
|
||||||
if (!isObject(value)) { return false; }
|
|
||||||
|
|
||||||
let keys = Object.keys(props);
|
utils.flag( this, "object" )( ast, options );
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
let key = keys[i];
|
|
||||||
|
|
||||||
if (!(key in value)) { return false; }
|
this.assert(
|
||||||
|
matchProps( ast, props ),
|
||||||
|
"expected #{this} to change the AST to match #{exp}",
|
||||||
|
"expected #{this} to not change the AST to match #{exp}",
|
||||||
|
props,
|
||||||
|
ast
|
||||||
|
);
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
Assertion.addMethod( "reportError", function ( grammar, props, options ) {
|
||||||
|
|
||||||
|
options = typeof options !== "undefined" ? options : {};
|
||||||
|
|
||||||
|
const ast = parser.parse( grammar );
|
||||||
|
|
||||||
|
let passed, result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
utils.flag( this, "object" )( ast, options );
|
||||||
|
passed = true;
|
||||||
|
|
||||||
|
} catch ( e ) {
|
||||||
|
|
||||||
|
result = e;
|
||||||
|
passed = false;
|
||||||
|
|
||||||
if (!matchProps(value[key], props[key])) { return false; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
this.assert(
|
||||||
} else {
|
! passed,
|
||||||
return value === props;
|
"expected #{this} to report an error but it didn't",
|
||||||
}
|
"expected #{this} to not report an error but #{act} was reported",
|
||||||
}
|
null,
|
||||||
|
result
|
||||||
let ast = parser.parse(grammar);
|
);
|
||||||
|
|
||||||
utils.flag(this, "object")(ast, options);
|
if ( ! passed && typeof props !== "undefined" ) {
|
||||||
|
|
||||||
this.assert(
|
Object.keys( props ).forEach( key => {
|
||||||
matchProps(ast, props),
|
|
||||||
"expected #{this} to change the AST to match #{exp}",
|
new Assertion( result )
|
||||||
"expected #{this} to not change the AST to match #{exp}",
|
.to.have.property( key )
|
||||||
props,
|
.that.is.deep.equal( props[ key ] );
|
||||||
ast
|
|
||||||
);
|
} );
|
||||||
});
|
|
||||||
|
}
|
||||||
Assertion.addMethod("reportError", function(grammar, props, options) {
|
|
||||||
options = options !== undefined ? options : {};
|
} );
|
||||||
|
|
||||||
let ast = parser.parse(grammar);
|
|
||||||
|
|
||||||
let passed, result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
utils.flag(this, "object")(ast, options);
|
|
||||||
passed = true;
|
|
||||||
} catch (e) {
|
|
||||||
result = e;
|
|
||||||
passed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.assert(
|
|
||||||
!passed,
|
|
||||||
"expected #{this} to report an error but it didn't",
|
|
||||||
"expected #{this} to not report an error but #{act} was reported",
|
|
||||||
null,
|
|
||||||
result
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!passed && props !== undefined) {
|
|
||||||
Object.keys(props).forEach(key => {
|
|
||||||
new Assertion(result).to.have.property(key)
|
|
||||||
.that.is.deep.equal(props[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -1,59 +1,69 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/remove-proxy-rules");
|
const pass = require( "../../../../../lib/compiler/passes/remove-proxy-rules" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |removeProxyRules|", function() {
|
describe( "compiler pass |removeProxyRules|", function () {
|
||||||
describe("when a proxy rule isn't listed in |allowedStartRules|", function() {
|
|
||||||
it("updates references and removes it", function() {
|
describe( "when a proxy rule isn't listed in |allowedStartRules|", function () {
|
||||||
expect(pass).to.changeAST(
|
|
||||||
[
|
it( "updates references and removes it", function () {
|
||||||
"start = proxy",
|
|
||||||
"proxy = proxied",
|
expect( pass ).to.changeAST(
|
||||||
"proxied = 'a'"
|
[
|
||||||
].join("\n"),
|
"start = proxy",
|
||||||
{
|
"proxy = proxied",
|
||||||
rules: [
|
"proxied = 'a'"
|
||||||
{
|
].join( "\n" ),
|
||||||
name: "start",
|
{
|
||||||
expression: { type: "rule_ref", name: "proxied" }
|
rules: [
|
||||||
},
|
{
|
||||||
{ name: "proxied" }
|
name: "start",
|
||||||
]
|
expression: { type: "rule_ref", name: "proxied" }
|
||||||
},
|
},
|
||||||
{ allowedStartRules: ["start"] }
|
{ name: "proxied" }
|
||||||
);
|
]
|
||||||
});
|
},
|
||||||
});
|
{ allowedStartRules: [ "start" ] }
|
||||||
|
);
|
||||||
describe("when a proxy rule is listed in |allowedStartRules|", function() {
|
|
||||||
it("updates references but doesn't remove it", function() {
|
} );
|
||||||
expect(pass).to.changeAST(
|
|
||||||
[
|
} );
|
||||||
"start = proxy",
|
|
||||||
"proxy = proxied",
|
describe( "when a proxy rule is listed in |allowedStartRules|", function () {
|
||||||
"proxied = 'a'"
|
|
||||||
].join("\n"),
|
it( "updates references but doesn't remove it", function () {
|
||||||
{
|
|
||||||
rules: [
|
expect( pass ).to.changeAST(
|
||||||
{
|
[
|
||||||
name: "start",
|
"start = proxy",
|
||||||
expression: { type: "rule_ref", name: "proxied" }
|
"proxy = proxied",
|
||||||
},
|
"proxied = 'a'"
|
||||||
{
|
].join( "\n" ),
|
||||||
name: "proxy",
|
{
|
||||||
expression: { type: "rule_ref", name: "proxied" }
|
rules: [
|
||||||
},
|
{
|
||||||
{ name: "proxied" }
|
name: "start",
|
||||||
]
|
expression: { type: "rule_ref", name: "proxied" }
|
||||||
},
|
},
|
||||||
{ allowedStartRules: ["start", "proxy"] }
|
{
|
||||||
);
|
name: "proxy",
|
||||||
});
|
expression: { type: "rule_ref", name: "proxied" }
|
||||||
});
|
},
|
||||||
});
|
{ name: "proxied" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ allowedStartRules: [ "start", "proxy" ] }
|
||||||
|
);
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,63 +1,83 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/report-duplicate-labels");
|
const pass = require( "../../../../../lib/compiler/passes/report-duplicate-labels" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |reportDuplicateLabels|", function() {
|
describe( "compiler pass |reportDuplicateLabels|", function () {
|
||||||
describe("in a sequence", function() {
|
|
||||||
it("reports labels duplicate with labels of preceding elements", function() {
|
describe( "in a sequence", function () {
|
||||||
expect(pass).to.reportError("start = a:'a' a:'a'", {
|
|
||||||
message: "Label \"a\" is already defined at line 1, column 9.",
|
it( "reports labels duplicate with labels of preceding elements", function () {
|
||||||
location: {
|
|
||||||
start: { offset: 14, line: 1, column: 15 },
|
expect( pass ).to.reportError( "start = a:'a' a:'a'", {
|
||||||
end: { offset: 19, line: 1, column: 20 }
|
message: "Label \"a\" is already defined at line 1, column 9.",
|
||||||
}
|
location: {
|
||||||
});
|
start: { offset: 14, line: 1, column: 15 },
|
||||||
});
|
end: { offset: 19, line: 1, column: 20 }
|
||||||
|
}
|
||||||
it("doesn't report labels duplicate with labels in subexpressions", function() {
|
} );
|
||||||
expect(pass).to.not.reportError("start = ('a' / a:'a' / 'a') a:'a'");
|
|
||||||
expect(pass).to.not.reportError("start = (a:'a' { }) a:'a'");
|
} );
|
||||||
expect(pass).to.not.reportError("start = ('a' a:'a' 'a') a:'a'");
|
|
||||||
expect(pass).to.not.reportError("start = b:(a:'a') a:'a'");
|
it( "doesn't report labels duplicate with labels in subexpressions", function () {
|
||||||
expect(pass).to.not.reportError("start = $(a:'a') a:'a'");
|
|
||||||
expect(pass).to.not.reportError("start = &(a:'a') a:'a'");
|
expect( pass ).to.not.reportError( "start = ('a' / a:'a' / 'a') a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = !(a:'a') a:'a'");
|
expect( pass ).to.not.reportError( "start = (a:'a' { }) a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = (a:'a')? a:'a'");
|
expect( pass ).to.not.reportError( "start = ('a' a:'a' 'a') a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
|
expect( pass ).to.not.reportError( "start = b:(a:'a') a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
|
expect( pass ).to.not.reportError( "start = $(a:'a') a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
|
expect( pass ).to.not.reportError( "start = &(a:'a') a:'a'" );
|
||||||
});
|
expect( pass ).to.not.reportError( "start = !(a:'a') a:'a'" );
|
||||||
});
|
expect( pass ).to.not.reportError( "start = (a:'a')? a:'a'" );
|
||||||
|
expect( pass ).to.not.reportError( "start = (a:'a')* a:'a'" );
|
||||||
describe("in a choice", function() {
|
expect( pass ).to.not.reportError( "start = (a:'a')+ a:'a'" );
|
||||||
it("doesn't report labels duplicate with labels of preceding alternatives", function() {
|
expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" );
|
||||||
expect(pass).to.not.reportError("start = a:'a' / a:'a'");
|
|
||||||
});
|
} );
|
||||||
});
|
|
||||||
|
} );
|
||||||
describe("in outer sequence", function() {
|
|
||||||
it("reports labels duplicate with labels of preceding elements", function() {
|
describe( "in a choice", function () {
|
||||||
expect(pass).to.reportError("start = a:'a' (a:'a')", {
|
|
||||||
message: "Label \"a\" is already defined at line 1, column 9.",
|
it( "doesn't report labels duplicate with labels of preceding alternatives", function () {
|
||||||
location: {
|
|
||||||
start: { offset: 15, line: 1, column: 16 },
|
expect( pass ).to.not.reportError( "start = a:'a' / a:'a'" );
|
||||||
end: { offset: 20, line: 1, column: 21 }
|
|
||||||
}
|
} );
|
||||||
});
|
|
||||||
});
|
} );
|
||||||
|
|
||||||
it("doesn't report labels duplicate with the label of the current element", function() {
|
describe( "in outer sequence", function () {
|
||||||
expect(pass).to.not.reportError("start = a:(a:'a')");
|
|
||||||
});
|
it( "reports labels duplicate with labels of preceding elements", function () {
|
||||||
|
|
||||||
it("doesn't report labels duplicate with labels of following elements", function() {
|
expect( pass ).to.reportError( "start = a:'a' (a:'a')", {
|
||||||
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
|
message: "Label \"a\" is already defined at line 1, column 9.",
|
||||||
});
|
location: {
|
||||||
});
|
start: { offset: 15, line: 1, column: 16 },
|
||||||
});
|
end: { offset: 20, line: 1, column: 21 }
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "doesn't report labels duplicate with the label of the current element", function () {
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = a:(a:'a')" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "doesn't report labels duplicate with labels of following elements", function () {
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = (a:'a') a:'a'" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/report-duplicate-rules");
|
const pass = require( "../../../../../lib/compiler/passes/report-duplicate-rules" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |reportDuplicateRules|", function() {
|
describe( "compiler pass |reportDuplicateRules|", function () {
|
||||||
it("reports duplicate rules", function() {
|
|
||||||
expect(pass).to.reportError([
|
it( "reports duplicate rules", function () {
|
||||||
"start = 'a'",
|
|
||||||
"start = 'b'"
|
expect( pass ).to.reportError( [
|
||||||
].join("\n"), {
|
"start = 'a'",
|
||||||
message: "Rule \"start\" is already defined at line 1, column 1.",
|
"start = 'b'"
|
||||||
location: {
|
].join( "\n" ), {
|
||||||
start: { offset: 12, line: 2, column: 1 },
|
message: "Rule \"start\" is already defined at line 1, column 1.",
|
||||||
end: { offset: 23, line: 2, column: 12 }
|
location: {
|
||||||
}
|
start: { offset: 12, line: 2, column: 1 },
|
||||||
});
|
end: { offset: 23, line: 2, column: 12 }
|
||||||
});
|
}
|
||||||
});
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,119 +1,135 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/report-infinite-recursion");
|
const pass = require( "../../../../../lib/compiler/passes/report-infinite-recursion" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |reportInfiniteRecursion|", function() {
|
describe( "compiler pass |reportInfiniteRecursion|", function () {
|
||||||
it("reports direct left recursion", function() {
|
|
||||||
expect(pass).to.reportError("start = start", {
|
it( "reports direct left recursion", function () {
|
||||||
message: "Possible infinite loop when parsing (left recursion: start -> start).",
|
|
||||||
location: {
|
expect( pass ).to.reportError( "start = start", {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
message: "Possible infinite loop when parsing (left recursion: start -> start).",
|
||||||
end: { offset: 13, line: 1, column: 14 }
|
location: {
|
||||||
}
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
});
|
end: { offset: 13, line: 1, column: 14 }
|
||||||
});
|
}
|
||||||
|
} );
|
||||||
it("reports indirect left recursion", function() {
|
|
||||||
expect(pass).to.reportError([
|
} );
|
||||||
"start = stop",
|
|
||||||
"stop = start"
|
it( "reports indirect left recursion", function () {
|
||||||
].join("\n"), {
|
|
||||||
message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
|
expect( pass ).to.reportError( [
|
||||||
location: {
|
"start = stop",
|
||||||
start: { offset: 20, line: 2, column: 8 },
|
"stop = start"
|
||||||
end: { offset: 25, line: 2, column: 13 }
|
].join( "\n" ), {
|
||||||
}
|
message: "Possible infinite loop when parsing (left recursion: start -> stop -> start).",
|
||||||
});
|
location: {
|
||||||
});
|
start: { offset: 20, line: 2, column: 8 },
|
||||||
|
end: { offset: 25, line: 2, column: 13 }
|
||||||
describe("in sequences", function() {
|
}
|
||||||
it("reports left recursion if all preceding elements match empty string", function() {
|
} );
|
||||||
expect(pass).to.reportError("start = '' '' '' start");
|
|
||||||
});
|
} );
|
||||||
|
|
||||||
it("doesn't report left recursion if some preceding element doesn't match empty string", function() {
|
describe( "in sequences", function () {
|
||||||
expect(pass).to.not.reportError("start = 'a' '' '' start");
|
|
||||||
expect(pass).to.not.reportError("start = '' 'a' '' start");
|
it( "reports left recursion if all preceding elements match empty string", function () {
|
||||||
expect(pass).to.not.reportError("start = '' '' 'a' start");
|
|
||||||
});
|
expect( pass ).to.reportError( "start = '' '' '' start" );
|
||||||
|
|
||||||
// Regression test for #359.
|
} );
|
||||||
it("reports left recursion when rule reference is wrapped in an expression", function() {
|
|
||||||
expect(pass).to.reportError("start = '' start?");
|
it( "doesn't report left recursion if some preceding element doesn't match empty string", function () {
|
||||||
});
|
|
||||||
|
expect( pass ).to.not.reportError( "start = 'a' '' '' start" );
|
||||||
it("computes expressions that always consume input on success correctly", function() {
|
expect( pass ).to.not.reportError( "start = '' 'a' '' start" );
|
||||||
expect(pass).to.reportError([
|
expect( pass ).to.not.reportError( "start = '' '' 'a' start" );
|
||||||
"start = a start",
|
|
||||||
"a 'a' = ''"
|
} );
|
||||||
].join("\n"));
|
|
||||||
expect(pass).to.not.reportError([
|
// Regression test for #359.
|
||||||
"start = a start",
|
it( "reports left recursion when rule reference is wrapped in an expression", function () {
|
||||||
"a 'a' = 'a'"
|
|
||||||
].join("\n"));
|
expect( pass ).to.reportError( "start = '' start?" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = ('' / 'a' / 'b') start");
|
} );
|
||||||
expect(pass).to.reportError("start = ('a' / '' / 'b') start");
|
|
||||||
expect(pass).to.reportError("start = ('a' / 'b' / '') start");
|
it( "computes expressions that always consume input on success correctly", function () {
|
||||||
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c') start");
|
|
||||||
|
expect( pass ).to.reportError( [
|
||||||
expect(pass).to.reportError("start = ('' { }) start");
|
"start = a start",
|
||||||
expect(pass).to.not.reportError("start = ('a' { }) start");
|
"a 'a' = ''"
|
||||||
|
].join( "\n" ) );
|
||||||
expect(pass).to.reportError("start = ('' '' '') start");
|
expect( pass ).to.not.reportError( [
|
||||||
expect(pass).to.not.reportError("start = ('a' '' '') start");
|
"start = a start",
|
||||||
expect(pass).to.not.reportError("start = ('' 'a' '') start");
|
"a 'a' = 'a'"
|
||||||
expect(pass).to.not.reportError("start = ('' '' 'a') start");
|
].join( "\n" ) );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = a:'' start");
|
expect( pass ).to.reportError( "start = ('' / 'a' / 'b') start" );
|
||||||
expect(pass).to.not.reportError("start = a:'a' start");
|
expect( pass ).to.reportError( "start = ('a' / '' / 'b') start" );
|
||||||
|
expect( pass ).to.reportError( "start = ('a' / 'b' / '') start" );
|
||||||
expect(pass).to.reportError("start = $'' start");
|
expect( pass ).to.not.reportError( "start = ('a' / 'b' / 'c') start" );
|
||||||
expect(pass).to.not.reportError("start = $'a' start");
|
|
||||||
|
expect( pass ).to.reportError( "start = ('' { }) start" );
|
||||||
expect(pass).to.reportError("start = &'' start");
|
expect( pass ).to.not.reportError( "start = ('a' { }) start" );
|
||||||
expect(pass).to.reportError("start = &'a' start");
|
|
||||||
|
expect( pass ).to.reportError( "start = ('' '' '') start" );
|
||||||
expect(pass).to.reportError("start = !'' start");
|
expect( pass ).to.not.reportError( "start = ('a' '' '') start" );
|
||||||
expect(pass).to.reportError("start = !'a' start");
|
expect( pass ).to.not.reportError( "start = ('' 'a' '') start" );
|
||||||
|
expect( pass ).to.not.reportError( "start = ('' '' 'a') start" );
|
||||||
expect(pass).to.reportError("start = ''? start");
|
|
||||||
expect(pass).to.reportError("start = 'a'? start");
|
expect( pass ).to.reportError( "start = a:'' start" );
|
||||||
|
expect( pass ).to.not.reportError( "start = a:'a' start" );
|
||||||
expect(pass).to.reportError("start = ''* start");
|
|
||||||
expect(pass).to.reportError("start = 'a'* start");
|
expect( pass ).to.reportError( "start = $'' start" );
|
||||||
|
expect( pass ).to.not.reportError( "start = $'a' start" );
|
||||||
expect(pass).to.reportError("start = ''+ start");
|
|
||||||
expect(pass).to.not.reportError("start = 'a'+ start");
|
expect( pass ).to.reportError( "start = &'' start" );
|
||||||
|
expect( pass ).to.reportError( "start = &'a' start" );
|
||||||
expect(pass).to.reportError("start = ('') start");
|
|
||||||
expect(pass).to.not.reportError("start = ('a') start");
|
expect( pass ).to.reportError( "start = !'' start" );
|
||||||
|
expect( pass ).to.reportError( "start = !'a' start" );
|
||||||
expect(pass).to.reportError("start = &{ } start");
|
|
||||||
|
expect( pass ).to.reportError( "start = ''? start" );
|
||||||
expect(pass).to.reportError("start = !{ } start");
|
expect( pass ).to.reportError( "start = 'a'? start" );
|
||||||
|
|
||||||
expect(pass).to.reportError([
|
expect( pass ).to.reportError( "start = ''* start" );
|
||||||
"start = a start",
|
expect( pass ).to.reportError( "start = 'a'* start" );
|
||||||
"a = ''"
|
|
||||||
].join("\n"));
|
expect( pass ).to.reportError( "start = ''+ start" );
|
||||||
expect(pass).to.not.reportError([
|
expect( pass ).to.not.reportError( "start = 'a'+ start" );
|
||||||
"start = a start",
|
|
||||||
"a = 'a'"
|
expect( pass ).to.reportError( "start = ('') start" );
|
||||||
].join("\n"));
|
expect( pass ).to.not.reportError( "start = ('a') start" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = '' start");
|
expect( pass ).to.reportError( "start = &{ } start" );
|
||||||
expect(pass).to.not.reportError("start = 'a' start");
|
|
||||||
|
expect( pass ).to.reportError( "start = !{ } start" );
|
||||||
expect(pass).to.not.reportError("start = [a-d] start");
|
|
||||||
|
expect( pass ).to.reportError( [
|
||||||
expect(pass).to.not.reportError("start = . start");
|
"start = a start",
|
||||||
});
|
"a = ''"
|
||||||
});
|
].join( "\n" ) );
|
||||||
});
|
expect( pass ).to.not.reportError( [
|
||||||
|
"start = a start",
|
||||||
|
"a = 'a'"
|
||||||
|
].join( "\n" ) );
|
||||||
|
|
||||||
|
expect( pass ).to.reportError( "start = '' start" );
|
||||||
|
expect( pass ).to.not.reportError( "start = 'a' start" );
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = [a-d] start" );
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = . start" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,99 +1,107 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/report-infinite-repetition");
|
const pass = require( "../../../../../lib/compiler/passes/report-infinite-repetition" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |reportInfiniteRepetition|", function() {
|
describe( "compiler pass |reportInfiniteRepetition|", function () {
|
||||||
it("reports infinite loops for zero_or_more", function() {
|
|
||||||
expect(pass).to.reportError("start = ('')*", {
|
it( "reports infinite loops for zero_or_more", function () {
|
||||||
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
|
||||||
location: {
|
expect( pass ).to.reportError( "start = ('')*", {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
||||||
end: { offset: 13, line: 1, column: 14 }
|
location: {
|
||||||
}
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
});
|
end: { offset: 13, line: 1, column: 14 }
|
||||||
});
|
}
|
||||||
|
} );
|
||||||
it("reports infinite loops for one_or_more", function() {
|
|
||||||
expect(pass).to.reportError("start = ('')+", {
|
} );
|
||||||
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
|
||||||
location: {
|
it( "reports infinite loops for one_or_more", function () {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
|
||||||
end: { offset: 13, line: 1, column: 14 }
|
expect( pass ).to.reportError( "start = ('')+", {
|
||||||
}
|
message: "Possible infinite loop when parsing (repetition used with an expression that may not consume any input).",
|
||||||
});
|
location: {
|
||||||
});
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
|
end: { offset: 13, line: 1, column: 14 }
|
||||||
it("computes expressions that always consume input on success correctly", function() {
|
}
|
||||||
expect(pass).to.reportError([
|
} );
|
||||||
"start = a*",
|
|
||||||
"a 'a' = ''"
|
} );
|
||||||
].join("\n"));
|
|
||||||
expect(pass).to.not.reportError([
|
it( "computes expressions that always consume input on success correctly", function () {
|
||||||
"start = a*",
|
|
||||||
"a 'a' = 'a'"
|
expect( pass ).to.reportError( [
|
||||||
].join("\n"));
|
"start = a*",
|
||||||
|
"a 'a' = ''"
|
||||||
expect(pass).to.reportError("start = ('' / 'a' / 'b')*");
|
].join( "\n" ) );
|
||||||
expect(pass).to.reportError("start = ('a' / '' / 'b')*");
|
expect( pass ).to.not.reportError( [
|
||||||
expect(pass).to.reportError("start = ('a' / 'b' / '')*");
|
"start = a*",
|
||||||
expect(pass).to.not.reportError("start = ('a' / 'b' / 'c')*");
|
"a 'a' = 'a'"
|
||||||
|
].join( "\n" ) );
|
||||||
expect(pass).to.reportError("start = ('' { })*");
|
|
||||||
expect(pass).to.not.reportError("start = ('a' { })*");
|
expect( pass ).to.reportError( "start = ('' / 'a' / 'b')*" );
|
||||||
|
expect( pass ).to.reportError( "start = ('a' / '' / 'b')*" );
|
||||||
expect(pass).to.reportError("start = ('' '' '')*");
|
expect( pass ).to.reportError( "start = ('a' / 'b' / '')*" );
|
||||||
expect(pass).to.not.reportError("start = ('a' '' '')*");
|
expect( pass ).to.not.reportError( "start = ('a' / 'b' / 'c')*" );
|
||||||
expect(pass).to.not.reportError("start = ('' 'a' '')*");
|
|
||||||
expect(pass).to.not.reportError("start = ('' '' 'a')*");
|
expect( pass ).to.reportError( "start = ('' { })*" );
|
||||||
|
expect( pass ).to.not.reportError( "start = ('a' { })*" );
|
||||||
expect(pass).to.reportError("start = (a:'')*");
|
|
||||||
expect(pass).to.not.reportError("start = (a:'a')*");
|
expect( pass ).to.reportError( "start = ('' '' '')*" );
|
||||||
|
expect( pass ).to.not.reportError( "start = ('a' '' '')*" );
|
||||||
expect(pass).to.reportError("start = ($'')*");
|
expect( pass ).to.not.reportError( "start = ('' 'a' '')*" );
|
||||||
expect(pass).to.not.reportError("start = ($'a')*");
|
expect( pass ).to.not.reportError( "start = ('' '' 'a')*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (&'')*");
|
expect( pass ).to.reportError( "start = (a:'')*" );
|
||||||
expect(pass).to.reportError("start = (&'a')*");
|
expect( pass ).to.not.reportError( "start = (a:'a')*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (!'')*");
|
expect( pass ).to.reportError( "start = ($'')*" );
|
||||||
expect(pass).to.reportError("start = (!'a')*");
|
expect( pass ).to.not.reportError( "start = ($'a')*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (''?)*");
|
expect( pass ).to.reportError( "start = (&'')*" );
|
||||||
expect(pass).to.reportError("start = ('a'?)*");
|
expect( pass ).to.reportError( "start = (&'a')*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (''*)*");
|
expect( pass ).to.reportError( "start = (!'')*" );
|
||||||
expect(pass).to.reportError("start = ('a'*)*");
|
expect( pass ).to.reportError( "start = (!'a')*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (''+)*");
|
expect( pass ).to.reportError( "start = (''?)*" );
|
||||||
expect(pass).to.not.reportError("start = ('a'+)*");
|
expect( pass ).to.reportError( "start = ('a'?)*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = ('')*");
|
expect( pass ).to.reportError( "start = (''*)*" );
|
||||||
expect(pass).to.not.reportError("start = ('a')*");
|
expect( pass ).to.reportError( "start = ('a'*)*" );
|
||||||
|
|
||||||
expect(pass).to.reportError("start = (&{ })*");
|
expect( pass ).to.reportError( "start = (''+)*" );
|
||||||
|
expect( pass ).to.not.reportError( "start = ('a'+)*" );
|
||||||
expect(pass).to.reportError("start = (!{ })*");
|
|
||||||
|
expect( pass ).to.reportError( "start = ('')*" );
|
||||||
expect(pass).to.reportError([
|
expect( pass ).to.not.reportError( "start = ('a')*" );
|
||||||
"start = a*",
|
|
||||||
"a = ''"
|
expect( pass ).to.reportError( "start = (&{ })*" );
|
||||||
].join("\n"));
|
|
||||||
expect(pass).to.not.reportError([
|
expect( pass ).to.reportError( "start = (!{ })*" );
|
||||||
"start = a*",
|
|
||||||
"a = 'a'"
|
expect( pass ).to.reportError( [
|
||||||
].join("\n"));
|
"start = a*",
|
||||||
|
"a = ''"
|
||||||
expect(pass).to.reportError("start = ''*");
|
].join( "\n" ) );
|
||||||
expect(pass).to.not.reportError("start = 'a'*");
|
expect( pass ).to.not.reportError( [
|
||||||
|
"start = a*",
|
||||||
expect(pass).to.not.reportError("start = [a-d]*");
|
"a = 'a'"
|
||||||
|
].join( "\n" ) );
|
||||||
expect(pass).to.not.reportError("start = .*");
|
|
||||||
});
|
expect( pass ).to.reportError( "start = ''*" );
|
||||||
});
|
expect( pass ).to.not.reportError( "start = 'a'*" );
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = [a-d]*" );
|
||||||
|
|
||||||
|
expect( pass ).to.not.reportError( "start = .*" );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
@ -1,29 +1,35 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let chai = require("chai");
|
const chai = require( "chai" );
|
||||||
let helpers = require("./helpers");
|
const helpers = require( "./helpers" );
|
||||||
let pass = require("../../../../../lib/compiler/passes/report-undefined-rules");
|
const pass = require( "../../../../../lib/compiler/passes/report-undefined-rules" );
|
||||||
|
|
||||||
chai.use(helpers);
|
chai.use( helpers );
|
||||||
|
|
||||||
let expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe("compiler pass |reportUndefinedRules|", function() {
|
describe( "compiler pass |reportUndefinedRules|", function () {
|
||||||
it("reports undefined rules", function() {
|
|
||||||
expect(pass).to.reportError("start = undefined", {
|
it( "reports undefined rules", function () {
|
||||||
message: "Rule \"undefined\" is not defined.",
|
|
||||||
location: {
|
expect( pass ).to.reportError( "start = undefined", {
|
||||||
start: { offset: 8, line: 1, column: 9 },
|
message: "Rule \"undefined\" is not defined.",
|
||||||
end: { offset: 17, line: 1, column: 18 }
|
location: {
|
||||||
}
|
start: { offset: 8, line: 1, column: 9 },
|
||||||
});
|
end: { offset: 17, line: 1, column: 18 }
|
||||||
});
|
}
|
||||||
|
} );
|
||||||
it("checks allowedStartRules", function() {
|
|
||||||
expect(pass).to.reportError("start = 'a'", {
|
} );
|
||||||
message: "Start rule \"missing\" is not defined."
|
|
||||||
}, {
|
it( "checks allowedStartRules", function () {
|
||||||
allowedStartRules: ["missing"]
|
|
||||||
});
|
expect( pass ).to.reportError( "start = 'a'", {
|
||||||
});
|
message: "Start rule \"missing\" is not defined."
|
||||||
});
|
}, {
|
||||||
|
allowedStartRules: [ "missing" ]
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
|
||||||
|
} );
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue