From 7f8b3f70126aaa7720593804131cd9441050347a Mon Sep 17 00:00:00 2001 From: David Majda Date: Mon, 2 May 2016 11:51:47 +0200 Subject: [PATCH] UMD parsers: Generate parser wrapper separately from its toplevel code Code which was at the toplevel of the "generateJS" function in the code generator is now split into "generateToplevel" (which genreates parser toplevel code) and "generateWrapper" (which generates a wrapper around it). This is pure refactoring, generated parser code is exactly the same as before. This change will make it easier to modifiy the code genreator to produce UMD modules. Part of work on #362. --- lib/compiler/passes/generate-js.js | 745 +++++++++++++++-------------- 1 file changed, 381 insertions(+), 364 deletions(-) diff --git a/lib/compiler/passes/generate-js.js b/lib/compiler/passes/generate-js.js index 594a7c9..0a9a8ee 100644 --- a/lib/compiler/passes/generate-js.js +++ b/lib/compiler/passes/generate-js.js @@ -9,8 +9,7 @@ var arrays = require("../../utils/arrays"), function generateJS(ast, options) { /* These only indent non-empty lines to avoid trailing whitespace. */ function indent2(code) { return code.replace(/^(.+)$/gm, ' $1'); } - function indent4(code) { return code.replace(/^(.+)$/gm, ' $1'); } - function indent8(code) { return code.replace(/^(.+)$/gm, ' $1'); } + function indent6(code) { return code.replace(/^(.+)$/gm, ' $1'); } function indent10(code) { return code.replace(/^(.+)$/gm, ' $1'); } function generateTables() { @@ -755,416 +754,434 @@ function generateJS(ast, options) { return parts.join('\n'); } - var parts = [], - startRuleIndices, startRuleIndex, - startRuleFunctions, startRuleFunction, - ruleNames; - - parts.push([ - '(function() {', - ' "use strict";', - '', - ' /*', - ' * Generated by PEG.js 0.9.0.', - ' *', - ' * http://pegjs.org/', - ' */', - '', - ' function peg$subclass(child, parent) {', - ' function ctor() { this.constructor = child; }', - ' ctor.prototype = parent.prototype;', - ' child.prototype = new ctor();', - ' }', - '', - ' function peg$SyntaxError(message, expected, location) {', - ' this.message = message;', - ' this.expected = expected;', - ' this.location = location;', - ' this.name = "SyntaxError";', - '', - ' if (typeof Error.captureStackTrace === "function") {', - ' Error.captureStackTrace(this, peg$SyntaxError);', - ' }', - ' }', - '', - ' peg$subclass(peg$SyntaxError, Error);', - '' - ].join('\n')); - - if (options.trace) { + function generateToplevel() { + var parts = [], + startRuleIndices, startRuleIndex, + startRuleFunctions, startRuleFunction, + ruleNames; + parts.push([ - ' function peg$DefaultTracer() {', - ' this.indentLevel = 0;', - ' }', - '', - ' peg$DefaultTracer.prototype.trace = function(event) {', - ' var that = this;', - '', - ' function log(event) {', - ' function repeat(string, n) {', - ' var result = "", i;', - '', - ' for (i = 0; i < n; i++) {', - ' result += string;', - ' }', - '', - ' return result;', - ' }', - '', - ' function pad(string, length) {', - ' return string + repeat(" ", length - string.length);', - ' }', - '', - ' if (typeof console === "object") {', // IE 8-10 - ' console.log(', - ' event.location.start.line + ":" + event.location.start.column + "-"', - ' + event.location.end.line + ":" + event.location.end.column + " "', - ' + pad(event.type, 10) + " "', - ' + repeat(" ", that.indentLevel) + event.rule', - ' );', - ' }', - ' }', - '', - ' switch (event.type) {', - ' case "rule.enter":', - ' log(event);', - ' this.indentLevel++;', - ' break;', + 'function peg$subclass(child, parent) {', + ' function ctor() { this.constructor = child; }', + ' ctor.prototype = parent.prototype;', + ' child.prototype = new ctor();', + '}', '', - ' case "rule.match":', - ' this.indentLevel--;', - ' log(event);', - ' break;', + 'function peg$SyntaxError(message, expected, location) {', + ' this.message = message;', + ' this.expected = expected;', + ' this.location = location;', + ' this.name = "SyntaxError";', '', - ' case "rule.fail":', - ' this.indentLevel--;', - ' log(event);', - ' break;', + ' if (typeof Error.captureStackTrace === "function") {', + ' Error.captureStackTrace(this, peg$SyntaxError);', + ' }', + '}', '', - ' default:', - ' throw new Error("Invalid event type: " + event.type + ".");', - ' }', - ' };', + 'peg$subclass(peg$SyntaxError, Error);', '' ].join('\n')); - } - parts.push([ - ' function peg$parse(input, options) {', - ' options = options !== void 0 ? options : {};', - '', - ' var parser = this,', - '', - ' peg$FAILED = {},', - '' - ].join('\n')); - - if (options.optimize === "size") { - startRuleIndices = '{ ' - + arrays.map( - options.allowedStartRules, - function(r) { return r + ': ' + asts.indexOfRule(ast, r); } - ).join(', ') - + ' }'; - startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]); + if (options.trace) { + parts.push([ + 'function peg$DefaultTracer() {', + ' this.indentLevel = 0;', + '}', + '', + 'peg$DefaultTracer.prototype.trace = function(event) {', + ' var that = this;', + '', + ' function log(event) {', + ' function repeat(string, n) {', + ' var result = "", i;', + '', + ' for (i = 0; i < n; i++) {', + ' result += string;', + ' }', + '', + ' return result;', + ' }', + '', + ' function pad(string, length) {', + ' return string + repeat(" ", length - string.length);', + ' }', + '', + ' if (typeof console === "object") {', // IE 8-10 + ' console.log(', + ' event.location.start.line + ":" + event.location.start.column + "-"', + ' + event.location.end.line + ":" + event.location.end.column + " "', + ' + pad(event.type, 10) + " "', + ' + repeat(" ", that.indentLevel) + event.rule', + ' );', + ' }', + ' }', + '', + ' switch (event.type) {', + ' case "rule.enter":', + ' log(event);', + ' this.indentLevel++;', + ' break;', + '', + ' case "rule.match":', + ' this.indentLevel--;', + ' log(event);', + ' break;', + '', + ' case "rule.fail":', + ' this.indentLevel--;', + ' log(event);', + ' break;', + '', + ' default:', + ' throw new Error("Invalid event type: " + event.type + ".");', + ' }', + '};', + '' + ].join('\n')); + } parts.push([ - ' peg$startRuleIndices = ' + startRuleIndices + ',', - ' peg$startRuleIndex = ' + startRuleIndex + ',' + 'function peg$parse(input, options) {', + ' options = options !== void 0 ? options : {};', + '', + ' var parser = this,', + '', + ' peg$FAILED = {},', + '' ].join('\n')); - } else { - startRuleFunctions = '{ ' - + arrays.map( - options.allowedStartRules, - function(r) { return r + ': peg$parse' + r; } - ).join(', ') - + ' }'; - startRuleFunction = 'peg$parse' + options.allowedStartRules[0]; - parts.push([ - ' peg$startRuleFunctions = ' + startRuleFunctions + ',', - ' peg$startRuleFunction = ' + startRuleFunction + ',' - ].join('\n')); - } + if (options.optimize === "size") { + startRuleIndices = '{ ' + + arrays.map( + options.allowedStartRules, + function(r) { return r + ': ' + asts.indexOfRule(ast, r); } + ).join(', ') + + ' }'; + startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]); - parts.push(''); + parts.push([ + ' peg$startRuleIndices = ' + startRuleIndices + ',', + ' peg$startRuleIndex = ' + startRuleIndex + ',' + ].join('\n')); + } else { + startRuleFunctions = '{ ' + + arrays.map( + options.allowedStartRules, + function(r) { return r + ': peg$parse' + r; } + ).join(', ') + + ' }'; + startRuleFunction = 'peg$parse' + options.allowedStartRules[0]; - parts.push(indent8(generateTables())); + parts.push([ + ' peg$startRuleFunctions = ' + startRuleFunctions + ',', + ' peg$startRuleFunction = ' + startRuleFunction + ',' + ].join('\n')); + } - parts.push([ - '', - ' peg$currPos = 0,', - ' peg$savedPos = 0,', - ' peg$posDetailsCache = [{ line: 1, column: 1 }],', - ' peg$maxFailPos = 0,', - ' peg$maxFailExpected = [],', - ' peg$silentFails = 0,', // 0 = report failures, > 0 = silence failures - '' - ].join('\n')); + parts.push(''); + + parts.push(indent6(generateTables())); - if (options.cache) { parts.push([ - ' peg$resultsCache = {},', + '', + ' peg$currPos = 0,', + ' peg$savedPos = 0,', + ' peg$posDetailsCache = [{ line: 1, column: 1 }],', + ' peg$maxFailPos = 0,', + ' peg$maxFailExpected = [],', + ' peg$silentFails = 0,', // 0 = report failures, > 0 = silence failures '' ].join('\n')); - } - if (options.trace) { - if (options.optimize === "size") { - ruleNames = '[' - + arrays.map( - ast.rules, - function(r) { return '"' + js.stringEscape(r.name) + '"'; } - ).join(', ') - + ']'; + if (options.cache) { + parts.push([ + ' peg$resultsCache = {},', + '' + ].join('\n')); + } + + if (options.trace) { + if (options.optimize === "size") { + ruleNames = '[' + + arrays.map( + ast.rules, + function(r) { return '"' + js.stringEscape(r.name) + '"'; } + ).join(', ') + + ']'; + + parts.push([ + ' peg$ruleNames = ' + ruleNames + ',', + '' + ].join('\n')); + } parts.push([ - ' peg$ruleNames = ' + ruleNames + ',', + ' peg$tracer = "tracer" in options ? options.tracer : new peg$DefaultTracer(),', '' ].join('\n')); } parts.push([ - ' peg$tracer = "tracer" in options ? options.tracer : new peg$DefaultTracer(),', + ' peg$result;', '' ].join('\n')); - } - parts.push([ - ' peg$result;', - '' - ].join('\n')); + if (options.optimize === "size") { + parts.push([ + ' if ("startRule" in options) {', + ' if (!(options.startRule in peg$startRuleIndices)) {', + ' throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");', + ' }', + '', + ' peg$startRuleIndex = peg$startRuleIndices[options.startRule];', + ' }' + ].join('\n')); + } else { + parts.push([ + ' if ("startRule" in options) {', + ' if (!(options.startRule in peg$startRuleFunctions)) {', + ' throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");', + ' }', + '', + ' peg$startRuleFunction = peg$startRuleFunctions[options.startRule];', + ' }' + ].join('\n')); + } - if (options.optimize === "size") { parts.push([ - ' if ("startRule" in options) {', - ' if (!(options.startRule in peg$startRuleIndices)) {', - ' throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");', + '', + ' function text() {', + ' return input.substring(peg$savedPos, peg$currPos);', + ' }', + '', + ' function location() {', + ' return peg$computeLocation(peg$savedPos, peg$currPos);', + ' }', + '', + ' function expected(description, location) {', + ' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)', + '', + ' throw peg$buildException(', + ' null,', + ' [{ type: "other", description: description }],', + ' location', + ' );', + ' }', + '', + ' function error(message, location) {', + ' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)', + '', + ' throw peg$buildException(message, null, location);', + ' }', + '', + ' function peg$computePosDetails(pos) {', + ' var details = peg$posDetailsCache[pos], p;', + '', + ' if (details) {', + ' return details;', + ' } else {', + ' p = pos - 1;', + ' while (!peg$posDetailsCache[p]) {', + ' p--;', ' }', '', - ' peg$startRuleIndex = peg$startRuleIndices[options.startRule];', - ' }' - ].join('\n')); - } else { - parts.push([ - ' if ("startRule" in options) {', - ' if (!(options.startRule in peg$startRuleFunctions)) {', - ' throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");', + ' details = peg$posDetailsCache[p];', + ' details = {', + ' line: details.line,', + ' column: details.column', + ' };', + '', + ' while (p < pos) {', + ' if (input.charCodeAt(p) === 10) {', + ' details.line++;', + ' details.column = 1;', + ' } else {', + ' details.column++;', + ' }', + '', + ' p++;', + ' }', + '', + ' peg$posDetailsCache[pos] = details;', + ' return details;', + ' }', + ' }', + '', + ' function peg$computeLocation(startPos, endPos) {', + ' var startPosDetails = peg$computePosDetails(startPos),', + ' endPosDetails = peg$computePosDetails(endPos);', + '', + ' return {', + ' start: {', + ' offset: startPos,', + ' line: startPosDetails.line,', + ' column: startPosDetails.column', + ' },', + ' end: {', + ' offset: endPos,', + ' line: endPosDetails.line,', + ' column: endPosDetails.column', + ' }', + ' };', + ' }', + '', + ' function peg$fail(expected) {', + ' if (peg$currPos < peg$maxFailPos) { return; }', + '', + ' if (peg$currPos > peg$maxFailPos) {', + ' peg$maxFailPos = peg$currPos;', + ' peg$maxFailExpected = [];', + ' }', + '', + ' peg$maxFailExpected.push(expected);', + ' }', + '', + ' function peg$buildException(message, expected, location) {', + ' function cleanupExpected(expected) {', + ' var i, j;', + '', + ' expected.sort(function(a, b) {', + ' if (a.description < b.description) {', + ' return -1;', + ' } else if (a.description > b.description) {', + ' return 1;', + ' } else {', + ' return 0;', + ' }', + ' });', + '', + /* + * This works because the bytecode generator guarantees that every + * expectation object exists only once, so it's enough to use |===| instead + * of deeper structural comparison. + */ + ' if (expected.length > 0) {', + ' for (i = 1, j = 1; i < expected.length; i++) {', + ' if (expected[i - 1] !== expected[i]) {', + ' expected[j] = expected[i];', + ' j++;', + ' }', + ' }', + ' expected.length = j;', + ' }', + ' }', + '', + ' function buildMessage(expected) {', + ' var expectedDescs = new Array(expected.length),', + ' expectedDesc, i;', + '', + ' for (i = 0; i < expected.length; i++) {', + ' expectedDescs[i] = expected[i].description;', ' }', '', - ' peg$startRuleFunction = peg$startRuleFunctions[options.startRule];', - ' }' + ' expectedDesc = expected.length > 1', + ' ? expectedDescs.slice(0, -1).join(", ")', + ' + " or "', + ' + expectedDescs[expected.length - 1]', + ' : expectedDescs[0];', + '', + ' return "Expected " + expectedDesc + ".";', + ' }', + '', + ' if (expected !== null) {', + ' cleanupExpected(expected);', + ' }', + '', + ' return new peg$SyntaxError(', + ' message !== null ? message : buildMessage(expected),', + ' expected,', + ' location', + ' );', + ' }', + '' ].join('\n')); - } - parts.push([ - '', - ' function text() {', - ' return input.substring(peg$savedPos, peg$currPos);', - ' }', - '', - ' function location() {', - ' return peg$computeLocation(peg$savedPos, peg$currPos);', - ' }', - '', - ' function expected(description, location) {', - ' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)', - '', - ' throw peg$buildException(', - ' null,', - ' [{ type: "other", description: description }],', - ' location', - ' );', - ' }', - '', - ' function error(message, location) {', - ' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)', - '', - ' throw peg$buildException(message, null, location);', - ' }', - '', - ' function peg$computePosDetails(pos) {', - ' var details = peg$posDetailsCache[pos], p;', - '', - ' if (details) {', - ' return details;', - ' } else {', - ' p = pos - 1;', - ' while (!peg$posDetailsCache[p]) {', - ' p--;', - ' }', - '', - ' details = peg$posDetailsCache[p];', - ' details = {', - ' line: details.line,', - ' column: details.column', - ' };', - '', - ' while (p < pos) {', - ' if (input.charCodeAt(p) === 10) {', - ' details.line++;', - ' details.column = 1;', - ' } else {', - ' details.column++;', - ' }', - '', - ' p++;', - ' }', - '', - ' peg$posDetailsCache[pos] = details;', - ' return details;', - ' }', - ' }', - '', - ' function peg$computeLocation(startPos, endPos) {', - ' var startPosDetails = peg$computePosDetails(startPos),', - ' endPosDetails = peg$computePosDetails(endPos);', - '', - ' return {', - ' start: {', - ' offset: startPos,', - ' line: startPosDetails.line,', - ' column: startPosDetails.column', - ' },', - ' end: {', - ' offset: endPos,', - ' line: endPosDetails.line,', - ' column: endPosDetails.column', - ' }', - ' };', - ' }', - '', - ' function peg$fail(expected) {', - ' if (peg$currPos < peg$maxFailPos) { return; }', - '', - ' if (peg$currPos > peg$maxFailPos) {', - ' peg$maxFailPos = peg$currPos;', - ' peg$maxFailExpected = [];', - ' }', - '', - ' peg$maxFailExpected.push(expected);', - ' }', - '', - ' function peg$buildException(message, expected, location) {', - ' function cleanupExpected(expected) {', - ' var i, j;', - '', - ' expected.sort(function(a, b) {', - ' if (a.description < b.description) {', - ' return -1;', - ' } else if (a.description > b.description) {', - ' return 1;', - ' } else {', - ' return 0;', - ' }', - ' });', - '', - /* - * This works because the bytecode generator guarantees that every - * expectation object exists only once, so it's enough to use |===| instead - * of deeper structural comparison. - */ - ' if (expected.length > 0) {', - ' for (i = 1, j = 1; i < expected.length; i++) {', - ' if (expected[i - 1] !== expected[i]) {', - ' expected[j] = expected[i];', - ' j++;', - ' }', - ' }', - ' expected.length = j;', - ' }', - ' }', - '', - ' function buildMessage(expected) {', - ' var expectedDescs = new Array(expected.length),', - ' expectedDesc, i;', - '', - ' for (i = 0; i < expected.length; i++) {', - ' expectedDescs[i] = expected[i].description;', - ' }', - '', - ' expectedDesc = expected.length > 1', - ' ? expectedDescs.slice(0, -1).join(", ")', - ' + " or "', - ' + expectedDescs[expected.length - 1]', - ' : expectedDescs[0];', - '', - ' return "Expected " + expectedDesc + ".";', - ' }', - '', - ' if (expected !== null) {', - ' cleanupExpected(expected);', - ' }', - '', - ' return new peg$SyntaxError(', - ' message !== null ? message : buildMessage(expected),', - ' expected,', - ' location', - ' );', - ' }', - '' - ].join('\n')); - - if (options.optimize === "size") { - parts.push(indent4(generateInterpreter())); - parts.push(''); - } else { - arrays.each(ast.rules, function(rule) { - parts.push(indent4(generateRuleFunction(rule))); + if (options.optimize === "size") { + parts.push(indent2(generateInterpreter())); parts.push(''); - }); - } + } else { + arrays.each(ast.rules, function(rule) { + parts.push(indent2(generateRuleFunction(rule))); + parts.push(''); + }); + } - if (ast.initializer) { - parts.push(indent4(ast.initializer.code)); - parts.push(''); - } + if (ast.initializer) { + parts.push(indent2(ast.initializer.code)); + parts.push(''); + } + + if (options.optimize === "size") { + parts.push(' peg$result = peg$parseRule(peg$startRuleIndex);'); + } else { + parts.push(' peg$result = peg$startRuleFunction();'); + } - if (options.optimize === "size") { - parts.push(' peg$result = peg$parseRule(peg$startRuleIndex);'); - } else { - parts.push(' peg$result = peg$startRuleFunction();'); + parts.push([ + '', + ' if (peg$result !== peg$FAILED && peg$currPos === input.length) {', + ' return peg$result;', + ' } else {', + ' if (peg$result !== peg$FAILED && peg$currPos < input.length) {', + ' peg$fail({ type: "end", description: "end of input" });', + ' }', + '', + ' throw peg$buildException(', + ' null,', + ' peg$maxFailExpected,', + ' peg$computeLocation(peg$maxFailPos, peg$maxFailPos)', + ' );', + ' }', + '}' + ].join('\n')); + + return parts.join('\n'); } - parts.push([ - '', - ' if (peg$result !== peg$FAILED && peg$currPos === input.length) {', - ' return peg$result;', - ' } else {', - ' if (peg$result !== peg$FAILED && peg$currPos < input.length) {', - ' peg$fail({ type: "end", description: "end of input" });', - ' }', - '', - ' throw peg$buildException(', - ' null,', - ' peg$maxFailExpected,', - ' peg$computeLocation(peg$maxFailPos, peg$maxFailPos)', - ' );', - ' }', - ' }', - '', - ' return {' - ].join('\n')); - - if (options.trace) { + function generateWrapper(toplevelCode) { + var parts = []; + parts.push([ - ' SyntaxError: peg$SyntaxError,', - ' DefaultTracer: peg$DefaultTracer,', - ' parse: peg$parse' + '(function() {', + ' "use strict";', + '', + ' /*', + ' * Generated by PEG.js 0.9.0.', + ' *', + ' * http://pegjs.org/', + ' */', + '' ].join('\n')); - } else { + + parts.push(indent2(toplevelCode)); + parts.push([ - ' SyntaxError: peg$SyntaxError,', - ' parse: peg$parse' + '', + ' return {' + ].join('\n')); + + if (options.trace) { + parts.push([ + ' SyntaxError: peg$SyntaxError,', + ' DefaultTracer: peg$DefaultTracer,', + ' parse: peg$parse' + ].join('\n')); + } else { + parts.push([ + ' SyntaxError: peg$SyntaxError,', + ' parse: peg$parse' + ].join('\n')); + } + + parts.push([ + ' };', + '})()' ].join('\n')); - } - parts.push([ - ' };', - '})()' - ].join('\n')); + return parts.join('\n'); + } - ast.code = parts.join('\n'); + ast.code = generateWrapper(generateToplevel()); } module.exports = generateJS;