From da12c2f5d4ca1af767828dba34a894a7cb00a146 Mon Sep 17 00:00:00 2001 From: David Majda Date: Thu, 29 Sep 2011 13:47:50 +0200 Subject: [PATCH] Compile Codie templates only once --- src/emitter.js | 979 +++++++++++++++++++++++++------------------------ 1 file changed, 493 insertions(+), 486 deletions(-) diff --git a/src/emitter.js b/src/emitter.js index 7de9f0c..3341780 100644 --- a/src/emitter.js +++ b/src/emitter.js @@ -226,13 +226,415 @@ PEG.compiler.emitter = function(ast) { return Codie; })(); - function formatCode() { - var args = Array.prototype.slice.call(arguments); - var vars = args[args.length - 1] instanceof Object ? args.pop() : {}; + var templates = (function() { + var name, + templates = {}, + sources = { + grammar: [ + '(function(){', + ' /* Generated by PEG.js @VERSION (http://pegjs.majda.cz/). */', + ' ', + ' var result = {', + ' /*', + ' * Parses the input with a generated parser. If the parsing is successfull,', + ' * returns a value explicitly or implicitly specified by the grammar from', + ' * which the parser was generated (see |PEG.buildParser|). If the parsing is', + ' * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.', + ' */', + ' parse: function(input, startRule) {', + ' var parseFunctions = {', + ' #block parseFunctionTableItems.join(",\\n")', + ' };', + ' ', + ' if (startRule !== undefined) {', + ' if (parseFunctions[startRule] === undefined) {', + ' throw new Error("Invalid rule name: " + quote(startRule) + ".");', + ' }', + ' } else {', + ' startRule = #{string(startRule)};', + ' }', + ' ', + ' var pos = 0;', + ' var reportFailures = 0;', // 0 = report, anything > 0 = do not report + ' var rightmostFailuresPos = 0;', + ' var rightmostFailuresExpected = [];', + ' var cache = {};', + ' ', + /* This needs to be in sync with |padLeft| in utils.js. */ + ' function padLeft(input, padding, length) {', + ' var result = input;', + ' ', + ' var padLength = length - input.length;', + ' for (var i = 0; i < padLength; i++) {', + ' result = padding + result;', + ' }', + ' ', + ' return result;', + ' }', + ' ', + /* This needs to be in sync with |escape| in utils.js. */ + ' function escape(ch) {', + ' var charCode = ch.charCodeAt(0);', + ' var escapeChar;', + ' var length;', + ' ', + ' if (charCode <= 0xFF) {', + ' escapeChar = \'x\';', + ' length = 2;', + ' } else {', + ' escapeChar = \'u\';', + ' length = 4;', + ' }', + ' ', + ' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);', + ' }', + ' ', + /* This needs to be in sync with |quote| in utils.js. */ + ' function quote(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 return, line separator, paragraph separator, and line feed.', + ' * Any character may appear in the form of an escape sequence.', + ' *', + ' * For portability, we also escape escape all control and non-ASCII', + ' * characters. Note that "\\0" and "\\v" escape sequences are not used', + ' * because JSHint does not like the first and IE the second.', + ' */', + ' return \'"\' + s', + ' .replace(/\\\\/g, \'\\\\\\\\\') // backslash', + ' .replace(/"/g, \'\\\\"\') // closing quote character', + ' .replace(/\\x08/g, \'\\\\b\') // backspace', + ' .replace(/\\t/g, \'\\\\t\') // horizontal tab', + ' .replace(/\\n/g, \'\\\\n\') // line feed', + ' .replace(/\\f/g, \'\\\\f\') // form feed', + ' .replace(/\\r/g, \'\\\\r\') // carriage return', + ' .replace(/[\\x00-\\x07\\x0B\\x0E-\\x1F\\x80-\\uFFFF]/g, escape)', + ' + \'"\';', + ' }', + ' ', + ' function matchFailed(failure) {', + ' if (pos < rightmostFailuresPos) {', + ' return;', + ' }', + ' ', + ' if (pos > rightmostFailuresPos) {', + ' rightmostFailuresPos = pos;', + ' rightmostFailuresExpected = [];', + ' }', + ' ', + ' rightmostFailuresExpected.push(failure);', + ' }', + ' ', + ' #for definition in parseFunctionDefinitions', + ' #block definition', + ' ', + ' #end', + ' function buildErrorMessage() {', + ' function buildExpected(failuresExpected) {', + ' failuresExpected.sort();', + ' ', + ' var lastFailure = null;', + ' var failuresExpectedUnique = [];', + ' for (var i = 0; i < failuresExpected.length; i++) {', + ' if (failuresExpected[i] !== lastFailure) {', + ' failuresExpectedUnique.push(failuresExpected[i]);', + ' lastFailure = failuresExpected[i];', + ' }', + ' }', + ' ', + ' switch (failuresExpectedUnique.length) {', + ' case 0:', + ' return "end of input";', + ' case 1:', + ' return failuresExpectedUnique[0];', + ' default:', + ' return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(", ")', + ' + " or "', + ' + failuresExpectedUnique[failuresExpectedUnique.length - 1];', + ' }', + ' }', + ' ', + ' var expected = buildExpected(rightmostFailuresExpected);', + ' var actualPos = Math.max(pos, rightmostFailuresPos);', + ' var actual = actualPos < input.length', + ' ? quote(input.charAt(actualPos))', + ' : "end of input";', + ' ', + ' return "Expected " + expected + " but " + actual + " found.";', + ' }', + ' ', + ' function computeErrorPosition() {', + ' /*', + ' * The first idea was to use |String.split| to break the input up to the', + ' * error position along newlines and derive the line and column from', + ' * there. However IE\'s |split| implementation is so broken that it was', + ' * enough to prevent it.', + ' */', + ' ', + ' var line = 1;', + ' var column = 1;', + ' var seenCR = false;', + ' ', + ' for (var i = 0; i < rightmostFailuresPos; i++) {', + ' var ch = input.charAt(i);', + ' if (ch === "\\n") {', + ' if (!seenCR) { line++; }', + ' column = 1;', + ' seenCR = false;', + ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', + ' line++;', + ' column = 1;', + ' seenCR = true;', + ' } else {', + ' column++;', + ' seenCR = false;', + ' }', + ' }', + ' ', + ' return { line: line, column: column };', + ' }', + ' ', + ' #if initializerCode !== ""', + ' #block initializerCode', + ' #end', + ' ', + ' var result = parseFunctions[startRule]();', + ' ', + ' /*', + ' * The parser is now in one of the following three states:', + ' *', + ' * 1. The parser successfully parsed the whole input.', + ' *', + ' * - |result !== null|', + ' * - |pos === input.length|', + ' * - |rightmostFailuresExpected| may or may not contain something', + ' *', + ' * 2. The parser successfully parsed only a part of the input.', + ' *', + ' * - |result !== null|', + ' * - |pos < input.length|', + ' * - |rightmostFailuresExpected| may or may not contain something', + ' *', + ' * 3. The parser did not successfully parse any part of the input.', + ' *', + ' * - |result === null|', + ' * - |pos === 0|', + ' * - |rightmostFailuresExpected| contains at least one failure', + ' *', + ' * All code following this comment (including called functions) must', + ' * handle these states.', + ' */', + ' if (result === null || pos !== input.length) {', + ' var errorPosition = computeErrorPosition();', + ' throw new this.SyntaxError(', + ' buildErrorMessage(),', + ' errorPosition.line,', + ' errorPosition.column', + ' );', + ' }', + ' ', + ' return result;', + ' },', + ' ', + ' /* Returns the parser source code. */', + ' toSource: function() { return this._source; }', + ' };', + ' ', + ' /* Thrown when a parser encounters a syntax error. */', + ' ', + ' result.SyntaxError = function(message, line, column) {', + ' this.name = "SyntaxError";', + ' this.message = message;', + ' this.line = line;', + ' this.column = column;', + ' };', + ' ', + ' result.SyntaxError.prototype = Error.prototype;', + ' ', + ' return result;', + '})()' + ], + rule: [ + 'function parse_#{node.name}() {', + ' var cacheKey = "#{node.name}@" + pos;', + ' var cachedResult = cache[cacheKey];', + ' if (cachedResult) {', + ' pos = cachedResult.nextPos;', + ' return cachedResult.result;', + ' }', + ' ', + ' #if resultVars.length > 0', + ' var #{resultVars.join(", ")};', + ' #end', + ' #if posVars.length > 0', + ' var #{posVars.join(", ")};', + ' #end', + ' ', + ' #if node.displayName !== null', + ' reportFailures++;', + ' #end', + ' #block code', + ' #if node.displayName !== null', + ' reportFailures--;', + ' if (reportFailures === 0 && #{resultVar} === null) {', + ' matchFailed(#{string(node.displayName)});', + ' }', + ' #end', + ' ', + ' cache[cacheKey] = {', + ' nextPos: pos,', + ' result: #{resultVar}', + ' };', + ' return #{resultVar};', + '}' + ], + choice: [ + '#block currentAlternativeCode', + '#block nextAlternativesCode' + ], + "choice.next": [ + 'if (#{resultVar} === null) {', + ' #block code', + '}' + ], + sequence: [ + '#{posVar} = pos;', + '#block code' + ], + "sequence.iteration": [ + '#block elementCode', + 'if (#{elementResultVar} !== null) {', + ' #block code', + '} else {', + ' #{resultVar} = null;', + ' pos = #{posVar};', + '}' + ], + "sequence.inner": [ + '#{resultVar} = [#{elementResultVars.join(", ")}];' + ], + simple_and: [ + '#{posVar} = pos;', + 'reportFailures++;', + '#block expressionCode', + 'reportFailures--;', + 'if (#{resultVar} !== null) {', + ' #{resultVar} = "";', + ' pos = #{posVar};', + '} else {', + ' #{resultVar} = null;', + '}' + ], + simple_not: [ + '#{posVar} = pos;', + 'reportFailures++;', + '#block expressionCode', + 'reportFailures--;', + 'if (#{resultVar} === null) {', + ' #{resultVar} = "";', + '} else {', + ' #{resultVar} = null;', + ' pos = #{posVar};', + '}' + ], + semantic_and: [ + '#{resultVar} = (function() {#{node.code}})() ? "" : null;' + ], + semantic_not: [ + '#{resultVar} = (function() {#{node.code}})() ? null : "";' + ], + optional: [ + '#block expressionCode', + '#{resultVar} = #{resultVar} !== null ? #{resultVar} : "";' + ], + zero_or_more: [ + '#{resultVar} = [];', + '#block expressionCode', + 'while (#{expressionResultVar} !== null) {', + ' #{resultVar}.push(#{expressionResultVar});', + ' #block expressionCode', + '}' + ], + one_or_more: [ + '#block expressionCode', + 'if (#{expressionResultVar} !== null) {', + ' #{resultVar} = [];', + ' while (#{expressionResultVar} !== null) {', + ' #{resultVar}.push(#{expressionResultVar});', + ' #block expressionCode', + ' }', + '} else {', + ' #{resultVar} = null;', + '}' + ], + action: [ + '#{posVar} = pos;', + '#block expressionCode', + 'if (#{resultVar} !== null) {', + ' #{resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});', + '}', + 'if (#{resultVar} === null) {', + ' pos = #{posVar};', + '}' + ], + rule_ref: [ + '#{resultVar} = parse_#{node.name}();' + ], + literal: [ + '#if node.value.length === 0', + ' #{resultVar} = "";', + '#else', + ' #if node.value.length === 1', + ' if (input.charCodeAt(pos) === #{node.value.charCodeAt(0)}) {', + ' #else', + ' if (input.substr(pos, #{node.value.length}) === #{string(node.value)}) {', + ' #end', + ' #{resultVar} = #{string(node.value)};', + ' pos += #{node.value.length};', + ' } else {', + ' #{resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed(#{string(string(node.value))});', + ' }', + ' }', + '#end' + ], + any: [ + 'if (input.length > pos) {', + ' #{resultVar} = input.charAt(pos);', + ' pos++;', + '} else {', + ' #{resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed("any character");', + ' }', + '}' + ], + "class": [ + 'if (#{regexp}.test(input.charAt(pos))) {', + ' #{resultVar} = input.charAt(pos);', + ' pos++;', + '} else {', + ' #{resultVar} = null;', + ' if (reportFailures === 0) {', + ' matchFailed(#{string(node.rawText)});', + ' }', + '}' + ] + }; + + for (name in sources) { + templates[name] = Codie.template(sources[name].join('\n')); + } + + return templates; + })(); + function fill(name, vars) { vars.string = quote; - return Codie.template(args.join('\n'))(vars); + return templates[name](vars); } function resultVar(index) { return "result" + index; } @@ -256,237 +658,12 @@ PEG.compiler.emitter = function(ast) { parseFunctionDefinitions.push(emit(node.rules[name])); } - return formatCode( - '(function(){', - ' /* Generated by PEG.js @VERSION (http://pegjs.majda.cz/). */', - ' ', - ' var result = {', - ' /*', - ' * Parses the input with a generated parser. If the parsing is successfull,', - ' * returns a value explicitly or implicitly specified by the grammar from', - ' * which the parser was generated (see |PEG.buildParser|). If the parsing is', - ' * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.', - ' */', - ' parse: function(input, startRule) {', - ' var parseFunctions = {', - ' #block parseFunctionTableItems.join(",\\n")', - ' };', - ' ', - ' if (startRule !== undefined) {', - ' if (parseFunctions[startRule] === undefined) {', - ' throw new Error("Invalid rule name: " + quote(startRule) + ".");', - ' }', - ' } else {', - ' startRule = #{string(startRule)};', - ' }', - ' ', - ' var pos = 0;', - ' var reportFailures = 0;', // 0 = report, anything > 0 = do not report - ' var rightmostFailuresPos = 0;', - ' var rightmostFailuresExpected = [];', - ' var cache = {};', - ' ', - /* This needs to be in sync with |padLeft| in utils.js. */ - ' function padLeft(input, padding, length) {', - ' var result = input;', - ' ', - ' var padLength = length - input.length;', - ' for (var i = 0; i < padLength; i++) {', - ' result = padding + result;', - ' }', - ' ', - ' return result;', - ' }', - ' ', - /* This needs to be in sync with |escape| in utils.js. */ - ' function escape(ch) {', - ' var charCode = ch.charCodeAt(0);', - ' var escapeChar;', - ' var length;', - ' ', - ' if (charCode <= 0xFF) {', - ' escapeChar = \'x\';', - ' length = 2;', - ' } else {', - ' escapeChar = \'u\';', - ' length = 4;', - ' }', - ' ', - ' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);', - ' }', - ' ', - /* This needs to be in sync with |quote| in utils.js. */ - ' function quote(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 return, line separator, paragraph separator, and line feed.', - ' * Any character may appear in the form of an escape sequence.', - ' *', - ' * For portability, we also escape escape all control and non-ASCII', - ' * characters. Note that "\\0" and "\\v" escape sequences are not used', - ' * because JSHint does not like the first and IE the second.', - ' */', - ' return \'"\' + s', - ' .replace(/\\\\/g, \'\\\\\\\\\') // backslash', - ' .replace(/"/g, \'\\\\"\') // closing quote character', - ' .replace(/\\x08/g, \'\\\\b\') // backspace', - ' .replace(/\\t/g, \'\\\\t\') // horizontal tab', - ' .replace(/\\n/g, \'\\\\n\') // line feed', - ' .replace(/\\f/g, \'\\\\f\') // form feed', - ' .replace(/\\r/g, \'\\\\r\') // carriage return', - ' .replace(/[\\x00-\\x07\\x0B\\x0E-\\x1F\\x80-\\uFFFF]/g, escape)', - ' + \'"\';', - ' }', - ' ', - ' function matchFailed(failure) {', - ' if (pos < rightmostFailuresPos) {', - ' return;', - ' }', - ' ', - ' if (pos > rightmostFailuresPos) {', - ' rightmostFailuresPos = pos;', - ' rightmostFailuresExpected = [];', - ' }', - ' ', - ' rightmostFailuresExpected.push(failure);', - ' }', - ' ', - ' #for definition in parseFunctionDefinitions', - ' #block definition', - ' ', - ' #end', - ' function buildErrorMessage() {', - ' function buildExpected(failuresExpected) {', - ' failuresExpected.sort();', - ' ', - ' var lastFailure = null;', - ' var failuresExpectedUnique = [];', - ' for (var i = 0; i < failuresExpected.length; i++) {', - ' if (failuresExpected[i] !== lastFailure) {', - ' failuresExpectedUnique.push(failuresExpected[i]);', - ' lastFailure = failuresExpected[i];', - ' }', - ' }', - ' ', - ' switch (failuresExpectedUnique.length) {', - ' case 0:', - ' return "end of input";', - ' case 1:', - ' return failuresExpectedUnique[0];', - ' default:', - ' return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(", ")', - ' + " or "', - ' + failuresExpectedUnique[failuresExpectedUnique.length - 1];', - ' }', - ' }', - ' ', - ' var expected = buildExpected(rightmostFailuresExpected);', - ' var actualPos = Math.max(pos, rightmostFailuresPos);', - ' var actual = actualPos < input.length', - ' ? quote(input.charAt(actualPos))', - ' : "end of input";', - ' ', - ' return "Expected " + expected + " but " + actual + " found.";', - ' }', - ' ', - ' function computeErrorPosition() {', - ' /*', - ' * The first idea was to use |String.split| to break the input up to the', - ' * error position along newlines and derive the line and column from', - ' * there. However IE\'s |split| implementation is so broken that it was', - ' * enough to prevent it.', - ' */', - ' ', - ' var line = 1;', - ' var column = 1;', - ' var seenCR = false;', - ' ', - ' for (var i = 0; i < rightmostFailuresPos; i++) {', - ' var ch = input.charAt(i);', - ' if (ch === "\\n") {', - ' if (!seenCR) { line++; }', - ' column = 1;', - ' seenCR = false;', - ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', - ' line++;', - ' column = 1;', - ' seenCR = true;', - ' } else {', - ' column++;', - ' seenCR = false;', - ' }', - ' }', - ' ', - ' return { line: line, column: column };', - ' }', - ' ', - ' #if initializerCode !== ""', - ' #block initializerCode', - ' #end', - ' ', - ' var result = parseFunctions[startRule]();', - ' ', - ' /*', - ' * The parser is now in one of the following three states:', - ' *', - ' * 1. The parser successfully parsed the whole input.', - ' *', - ' * - |result !== null|', - ' * - |pos === input.length|', - ' * - |rightmostFailuresExpected| may or may not contain something', - ' *', - ' * 2. The parser successfully parsed only a part of the input.', - ' *', - ' * - |result !== null|', - ' * - |pos < input.length|', - ' * - |rightmostFailuresExpected| may or may not contain something', - ' *', - ' * 3. The parser did not successfully parse any part of the input.', - ' *', - ' * - |result === null|', - ' * - |pos === 0|', - ' * - |rightmostFailuresExpected| contains at least one failure', - ' *', - ' * All code following this comment (including called functions) must', - ' * handle these states.', - ' */', - ' if (result === null || pos !== input.length) {', - ' var errorPosition = computeErrorPosition();', - ' throw new this.SyntaxError(', - ' buildErrorMessage(),', - ' errorPosition.line,', - ' errorPosition.column', - ' );', - ' }', - ' ', - ' return result;', - ' },', - ' ', - ' /* Returns the parser source code. */', - ' toSource: function() { return this._source; }', - ' };', - ' ', - ' /* Thrown when a parser encounters a syntax error. */', - ' ', - ' result.SyntaxError = function(message, line, column) {', - ' this.name = "SyntaxError";', - ' this.message = message;', - ' this.line = line;', - ' this.column = column;', - ' };', - ' ', - ' result.SyntaxError.prototype = Error.prototype;', - ' ', - ' return result;', - '})()', - { - initializerCode: initializerCode, - parseFunctionTableItems: parseFunctionTableItems, - parseFunctionDefinitions: parseFunctionDefinitions, - startRule: node.startRule - } - ); + return fill("grammar", { + initializerCode: initializerCode, + parseFunctionTableItems: parseFunctionTableItems, + parseFunctionDefinitions: parseFunctionDefinitions, + startRule: node.startRule + }); }, initializer: function(node) { @@ -506,47 +683,13 @@ PEG.compiler.emitter = function(ast) { } }; - return formatCode( - 'function parse_#{node.name}() {', - ' var cacheKey = "#{node.name}@" + pos;', - ' var cachedResult = cache[cacheKey];', - ' if (cachedResult) {', - ' pos = cachedResult.nextPos;', - ' return cachedResult.result;', - ' }', - ' ', - ' #if resultVars.length > 0', - ' var #{resultVars.join(", ")};', - ' #end', - ' #if posVars.length > 0', - ' var #{posVars.join(", ")};', - ' #end', - ' ', - ' #if node.displayName !== null', - ' reportFailures++;', - ' #end', - ' #block code', - ' #if node.displayName !== null', - ' reportFailures--;', - ' if (reportFailures === 0 && #{resultVar} === null) {', - ' matchFailed(#{string(node.displayName)});', - ' }', - ' #end', - ' ', - ' cache[cacheKey] = {', - ' nextPos: pos,', - ' result: #{resultVar}', - ' };', - ' return #{resultVar};', - '}', - { - node: node, - resultVars: map(range(node.resultStackDepth), resultVar), - posVars: map(range(node.posStackDepth), posVar), - code: emit(node.expression, context), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("rule", { + node: node, + resultVars: map(range(node.resultStackDepth), resultVar), + posVars: map(range(node.posStackDepth), posVar), + code: emit(node.expression, context), + resultVar: resultVar(context.resultIndex) + }); }, /* @@ -584,24 +727,15 @@ PEG.compiler.emitter = function(ast) { for (var i = node.alternatives.length - 1; i >= 0; i--) { nextAlternativesCode = i !== node.alternatives.length - 1 - ? formatCode( - 'if (#{resultVar} === null) {', - ' #block code', - '}', - { - code: code, - resultVar: resultVar(context.resultIndex) - } - ) + ? fill("choice.next", { + code: code, + resultVar: resultVar(context.resultIndex) + }) : ''; - code = formatCode( - '#block currentAlternativeCode', - '#block nextAlternativesCode', - { - currentAlternativeCode: emit(node.alternatives[i], context), - nextAlternativesCode: nextAlternativesCode - } - ); + code = fill("choice", { + currentAlternativeCode: emit(node.alternatives[i], context), + nextAlternativesCode: nextAlternativesCode + }); } return code; @@ -612,41 +746,22 @@ PEG.compiler.emitter = function(ast) { return resultVar(context.resultIndex + i); }); - var code = formatCode( - '#{resultVar} = [#{elementResultVars.join(", ")}];', - { - resultVar: resultVar(context.resultIndex), - elementResultVars: elementResultVars - } - ); + var code = fill("sequence.inner", { + resultVar: resultVar(context.resultIndex), + elementResultVars: elementResultVars + }); for (var i = node.elements.length - 1; i >= 0; i--) { - code = formatCode( - '#block elementCode', - 'if (#{elementResultVar} !== null) {', - ' #block code', - '} else {', - ' #{resultVar} = null;', - ' pos = #{posVar};', - '}', - { - elementCode: emit(node.elements[i], context.delta(i, 1)), - elementResultVar: elementResultVars[i], - code: code, - posVar: posVar(context.posIndex), - resultVar: resultVar(context.resultIndex) - } - ); + code = fill("sequence.iteration", { + elementCode: emit(node.elements[i], context.delta(i, 1)), + elementResultVar: elementResultVars[i], + code: code, + posVar: posVar(context.posIndex), + resultVar: resultVar(context.resultIndex) + }); } - return formatCode( - '#{posVar} = pos;', - '#block code', - { - code: code, - posVar: posVar(context.posIndex) - } - ); + return fill("sequence", { code: code, posVar: posVar(context.posIndex) }); }, labeled: function(node, context) { @@ -654,110 +769,56 @@ PEG.compiler.emitter = function(ast) { }, simple_and: function(node, context) { - return formatCode( - '#{posVar} = pos;', - 'reportFailures++;', - '#block expressionCode', - 'reportFailures--;', - 'if (#{resultVar} !== null) {', - ' #{resultVar} = "";', - ' pos = #{posVar};', - '} else {', - ' #{resultVar} = null;', - '}', - { - expressionCode: emit(node.expression, context.delta(0, 1)), - posVar: posVar(context.posIndex), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("simple_and", { + expressionCode: emit(node.expression, context.delta(0, 1)), + posVar: posVar(context.posIndex), + resultVar: resultVar(context.resultIndex) + }); }, simple_not: function(node, context) { - return formatCode( - '#{posVar} = pos;', - 'reportFailures++;', - '#block expressionCode', - 'reportFailures--;', - 'if (#{resultVar} === null) {', - ' #{resultVar} = "";', - '} else {', - ' #{resultVar} = null;', - ' pos = #{posVar};', - '}', - { - expressionCode: emit(node.expression, context.delta(0, 1)), - posVar: posVar(context.posIndex), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("simple_not", { + expressionCode: emit(node.expression, context.delta(0, 1)), + posVar: posVar(context.posIndex), + resultVar: resultVar(context.resultIndex) + }); }, semantic_and: function(node, context) { - return formatCode( - '#{resultVar} = (function() {#{node.code}})() ? "" : null;', - { - node: node, - resultVar: resultVar(context.resultIndex) - } - ); + return fill("semantic_and", { + node: node, + resultVar: resultVar(context.resultIndex) + }); }, semantic_not: function(node, context) { - return formatCode( - '#{resultVar} = (function() {#{node.code}})() ? null : "";', - { - node: node, - resultVar: resultVar(context.resultIndex) - } - ); + return fill("semantic_not", { + node: node, + resultVar: resultVar(context.resultIndex) + }); }, optional: function(node, context) { - return formatCode( - '#block expressionCode', - '#{resultVar} = #{resultVar} !== null ? #{resultVar} : "";', - { - expressionCode: emit(node.expression, context), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("optional", { + expressionCode: emit(node.expression, context), + resultVar: resultVar(context.resultIndex) + }); }, zero_or_more: function(node, context) { - return formatCode( - '#{resultVar} = [];', - '#block expressionCode', - 'while (#{expressionResultVar} !== null) {', - ' #{resultVar}.push(#{expressionResultVar});', - ' #block expressionCode', - '}', - { - expressionCode: emit(node.expression, context.delta(1, 0)), - expressionResultVar: resultVar(context.resultIndex + 1), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("zero_or_more", { + expressionCode: emit(node.expression, context.delta(1, 0)), + expressionResultVar: resultVar(context.resultIndex + 1), + resultVar: resultVar(context.resultIndex) + }); }, one_or_more: function(node, context) { - return formatCode( - '#block expressionCode', - 'if (#{expressionResultVar} !== null) {', - ' #{resultVar} = [];', - ' while (#{expressionResultVar} !== null) {', - ' #{resultVar}.push(#{expressionResultVar});', - ' #block expressionCode', - ' }', - '} else {', - ' #{resultVar} = null;', - '}', - { - expressionCode: emit(node.expression, context.delta(1, 0)), - expressionResultVar: resultVar(context.resultIndex + 1), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("one_or_more", { + expressionCode: emit(node.expression, context.delta(1, 0)), + expressionResultVar: resultVar(context.resultIndex + 1), + resultVar: resultVar(context.resultIndex) + }); }, action: function(node, context) { @@ -793,75 +854,32 @@ PEG.compiler.emitter = function(ast) { actualParams = []; } - return formatCode( - '#{posVar} = pos;', - '#block expressionCode', - 'if (#{resultVar} !== null) {', - ' #{resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});', - '}', - 'if (#{resultVar} === null) {', - ' pos = #{posVar};', - '}', - { - node: node, - expressionCode: emit(node.expression, context.delta(0, 1)), - formalParams: formalParams, - actualParams: actualParams, - posVar: posVar(context.posIndex), - resultVar: resultVar(context.resultIndex) - } - ); + return fill("action", { + node: node, + expressionCode: emit(node.expression, context.delta(0, 1)), + formalParams: formalParams, + actualParams: actualParams, + posVar: posVar(context.posIndex), + resultVar: resultVar(context.resultIndex) + }); }, rule_ref: function(node, context) { - return formatCode( - '#{resultVar} = parse_#{node.name}();', - { - node: node, - resultVar: resultVar(context.resultIndex) - } - ); + return fill("rule_ref", { + node: node, + resultVar: resultVar(context.resultIndex) + }); }, literal: function(node, context) { - return formatCode( - '#if node.value.length === 0', - ' #{resultVar} = "";', - '#else', - ' #if node.value.length === 1', - ' if (input.charCodeAt(pos) === #{node.value.charCodeAt(0)}) {', - ' #else', - ' if (input.substr(pos, #{node.value.length}) === #{string(node.value)}) {', - ' #end', - ' #{resultVar} = #{string(node.value)};', - ' pos += #{node.value.length};', - ' } else {', - ' #{resultVar} = null;', - ' if (reportFailures === 0) {', - ' matchFailed(#{string(string(node.value))});', - ' }', - ' }', - '#end', - { - node: node, - resultVar: resultVar(context.resultIndex) - } - ); + return fill("literal", { + node: node, + resultVar: resultVar(context.resultIndex) + }); }, any: function(node, context) { - return formatCode( - 'if (input.length > pos) {', - ' #{resultVar} = input.charAt(pos);', - ' pos++;', - '} else {', - ' #{resultVar} = null;', - ' if (reportFailures === 0) {', - ' matchFailed("any character");', - ' }', - '}', - { resultVar: resultVar(context.resultIndex) } - ); + return fill("any", { resultVar: resultVar(context.resultIndex) }); }, "class": function(node, context) { @@ -886,22 +904,11 @@ PEG.compiler.emitter = function(ast) { regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; } - return formatCode( - 'if (#{regexp}.test(input.charAt(pos))) {', - ' #{resultVar} = input.charAt(pos);', - ' pos++;', - '} else {', - ' #{resultVar} = null;', - ' if (reportFailures === 0) {', - ' matchFailed(#{string(node.rawText)});', - ' }', - '}', - { - node: node, - regexp: regexp, - resultVar: resultVar(context.resultIndex) - } - ); + return fill("class", { + node: node, + regexp: regexp, + resultVar: resultVar(context.resultIndex) + }); } });