diff --git a/src/compiler.js b/src/compiler.js index a370328..c95baed 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -7,7 +7,7 @@ PEG.compiler = { "reportMissingRules", "reportLeftRecursion", "removeProxyRules", - "computeStackDepths" + "computeVarNames" ], /* diff --git a/src/emitter.js b/src/emitter.js index 12541a5..e9a741a 100644 --- a/src/emitter.js +++ b/src/emitter.js @@ -265,7 +265,7 @@ PEG.compiler.emitter = function(ast) { ' throw new Error("Invalid rule name: " + quote(startRule) + ".");', ' }', ' } else {', - ' startRule = #{string(startRule)};', + ' startRule = #{string(node.startRule)};', ' }', ' ', ' var pos = 0;', @@ -390,8 +390,8 @@ PEG.compiler.emitter = function(ast) { ' return { line: line, column: column };', ' }', ' ', - ' #if initializerCode !== ""', - ' #block initializerCode', + ' #if node.initializer', + ' #block emit(node.initializer)', ' #end', ' ', ' var result = parseFunctions[startRule]();', @@ -488,126 +488,126 @@ PEG.compiler.emitter = function(ast) { ' return cachedResult.result;', ' }', ' ', - ' #if resultVars.length > 0', - ' var #{resultVars.join(", ")};', + ' #if node.resultVars.length > 0', + ' var #{node.resultVars.join(", ")};', ' #end', - ' #if posVars.length > 0', - ' var #{posVars.join(", ")};', + ' #if node.posVars.length > 0', + ' var #{node.posVars.join(", ")};', ' #end', ' ', ' #if node.displayName !== null', ' reportFailures++;', ' #end', - ' #block code', + ' #block emit(node.expression)', ' #if node.displayName !== null', ' reportFailures--;', - ' if (reportFailures === 0 && #{resultVar} === null) {', + ' if (reportFailures === 0 && #{node.resultVar} === null) {', ' matchFailed(#{string(node.displayName)});', ' }', ' #end', ' ', ' cache[cacheKey] = {', ' nextPos: pos,', - ' result: #{resultVar}', + ' result: #{node.resultVar}', ' };', - ' return #{resultVar};', + ' return #{node.resultVar};', '}' ], choice: [ - '#block currentAlternativeCode', + '#block emit(alternative)', '#block nextAlternativesCode' ], "choice.next": [ - 'if (#{resultVar} === null) {', + 'if (#{node.resultVar} === null) {', ' #block code', '}' ], sequence: [ - '#{posVar} = pos;', + '#{node.posVar} = pos;', '#block code' ], "sequence.iteration": [ - '#block elementCode', - 'if (#{elementResultVar} !== null) {', + '#block emit(element)', + 'if (#{element.resultVar} !== null) {', ' #block code', '} else {', - ' #{resultVar} = null;', - ' pos = #{posVar};', + ' #{node.resultVar} = null;', + ' pos = #{node.posVar};', '}' ], "sequence.inner": [ - '#{resultVar} = [#{elementResultVars.join(", ")}];' + '#{node.resultVar} = [#{pluck(node.elements, "resultVar").join(", ")}];' ], simple_and: [ - '#{posVar} = pos;', + '#{node.posVar} = pos;', 'reportFailures++;', - '#block expressionCode', + '#block emit(node.expression)', 'reportFailures--;', - 'if (#{resultVar} !== null) {', - ' #{resultVar} = "";', - ' pos = #{posVar};', + 'if (#{node.resultVar} !== null) {', + ' #{node.resultVar} = "";', + ' pos = #{node.posVar};', '} else {', - ' #{resultVar} = null;', + ' #{node.resultVar} = null;', '}' ], simple_not: [ - '#{posVar} = pos;', + '#{node.posVar} = pos;', 'reportFailures++;', - '#block expressionCode', + '#block emit(node.expression)', 'reportFailures--;', - 'if (#{resultVar} === null) {', - ' #{resultVar} = "";', + 'if (#{node.resultVar} === null) {', + ' #{node.resultVar} = "";', '} else {', - ' #{resultVar} = null;', - ' pos = #{posVar};', + ' #{node.resultVar} = null;', + ' pos = #{node.posVar};', '}' ], semantic_and: [ - '#{resultVar} = (function() {#{node.code}})() ? "" : null;' + '#{node.resultVar} = (function() {#{node.code}})() ? "" : null;' ], semantic_not: [ - '#{resultVar} = (function() {#{node.code}})() ? null : "";' + '#{node.resultVar} = (function() {#{node.code}})() ? null : "";' ], optional: [ - '#block expressionCode', - '#{resultVar} = #{resultVar} !== null ? #{resultVar} : "";' + '#block emit(node.expression)', + '#{node.resultVar} = #{node.resultVar} !== null ? #{node.resultVar} : "";' ], zero_or_more: [ - '#{resultVar} = [];', - '#block expressionCode', - 'while (#{expressionResultVar} !== null) {', - ' #{resultVar}.push(#{expressionResultVar});', - ' #block expressionCode', + '#{node.resultVar} = [];', + '#block emit(node.expression)', + 'while (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar}.push(#{node.expression.resultVar});', + ' #block emit(node.expression)', '}' ], one_or_more: [ - '#block expressionCode', - 'if (#{expressionResultVar} !== null) {', - ' #{resultVar} = [];', - ' while (#{expressionResultVar} !== null) {', - ' #{resultVar}.push(#{expressionResultVar});', - ' #block expressionCode', + '#block emit(node.expression)', + 'if (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar} = [];', + ' while (#{node.expression.resultVar} !== null) {', + ' #{node.resultVar}.push(#{node.expression.resultVar});', + ' #block emit(node.expression)', ' }', '} else {', - ' #{resultVar} = null;', + ' #{node.resultVar} = null;', '}' ], action: [ - '#{posVar} = pos;', - '#block expressionCode', - 'if (#{resultVar} !== null) {', - ' #{resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});', + '#{node.posVar} = pos;', + '#block emit(node.expression)', + 'if (#{node.resultVar} !== null) {', + ' #{node.resultVar} = (function(#{formalParams.join(", ")}) {#{node.code}})(#{actualParams.join(", ")});', '}', - 'if (#{resultVar} === null) {', - ' pos = #{posVar};', + 'if (#{node.resultVar} === null) {', + ' pos = #{node.posVar};', '}' ], rule_ref: [ - '#{resultVar} = parse_#{node.name}();' + '#{node.resultVar} = parse_#{node.name}();' ], literal: [ '#if node.value.length === 0', - ' #{resultVar} = "";', + ' #{node.resultVar} = "";', '#else', ' #if !node.ignoreCase', ' #if node.value.length === 1', @@ -627,13 +627,13 @@ PEG.compiler.emitter = function(ast) { ' if (input.substr(pos, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {', ' #end', ' #if !node.ignoreCase', - ' #{resultVar} = #{string(node.value)};', + ' #{node.resultVar} = #{string(node.value)};', ' #else', - ' #{resultVar} = input.substr(pos, #{node.value.length});', + ' #{node.resultVar} = input.substr(pos, #{node.value.length});', ' #end', ' pos += #{node.value.length};', ' } else {', - ' #{resultVar} = null;', + ' #{node.resultVar} = null;', ' if (reportFailures === 0) {', ' matchFailed(#{string(string(node.value))});', ' }', @@ -642,10 +642,10 @@ PEG.compiler.emitter = function(ast) { ], any: [ 'if (input.length > pos) {', - ' #{resultVar} = input.charAt(pos);', + ' #{node.resultVar} = input.charAt(pos);', ' pos++;', '} else {', - ' #{resultVar} = null;', + ' #{node.resultVar} = null;', ' if (reportFailures === 0) {', ' matchFailed("any character");', ' }', @@ -653,10 +653,10 @@ PEG.compiler.emitter = function(ast) { ], "class": [ 'if (#{regexp}.test(input.charAt(pos))) {', - ' #{resultVar} = input.charAt(pos);', + ' #{node.resultVar} = input.charAt(pos);', ' pos++;', '} else {', - ' #{resultVar} = null;', + ' #{node.resultVar} = null;', ' if (reportFailures === 0) {', ' matchFailed(#{string(node.rawText)});', ' }', @@ -673,18 +673,18 @@ PEG.compiler.emitter = function(ast) { function fill(name, vars) { vars.string = quote; + vars.pluck = pluck; + vars.emit = emit; return templates[name](vars); } - function resultVar(index) { return "result" + index; } - function posVar(index) { return "pos" + index; } + function emitSimple(name) { + return function(node) { return fill(name, { node: node }); }; + } var emit = buildNodeVisitor({ grammar: function(node) { - var initializerCode = node.initializer !== null - ? emit(node.initializer) - : ""; var name; var parseFunctionTableItems = []; @@ -699,38 +699,15 @@ PEG.compiler.emitter = function(ast) { } return fill("grammar", { - initializerCode: initializerCode, + node: node, parseFunctionTableItems: parseFunctionTableItems, - parseFunctionDefinitions: parseFunctionDefinitions, - startRule: node.startRule + parseFunctionDefinitions: parseFunctionDefinitions }); }, - initializer: function(node) { - return node.code; - }, + initializer: function(node) { return node.code; }, - rule: function(node) { - var context = { - resultIndex: 0, - posIndex: 0, - delta: function(resultIndexDelta, posIndexDelta) { - return { - resultIndex: this.resultIndex + resultIndexDelta, - posIndex: this.posIndex + posIndexDelta, - delta: this.delta - }; - } - }; - - 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) - }); - }, + rule: emitSimple("rule"), /* * The contract for all code fragments generated by the following functions @@ -742,126 +719,59 @@ PEG.compiler.emitter = function(ast) { * * * If the code fragment matches the input, it advances |pos| to point to * the first chracter following the matched part of the input and sets - * variable with a name computed by calling - * |resultVar(context.resultIndex)| to an appropriate value. This value is - * always non-|null|. + * variable with a name stored in |node.resultVar| to an appropriate + * value. This value is always non-|null|. * * * If the code fragment does not match the input, it returns with |pos| - * set to the original value and it sets a variable with a name computed - * by calling |resultVar(context.resultIndex)| to |null|. + * set to the original value and it sets a variable with a name stored in + * |node.resultVar| to |null|. * - * The code can use variables with names computed by calling - * - * |resultVar(context.resultIndex + i)| - * - * and - * - * |posVar(context.posIndex + i)| - * - * where |i| >= 1 to store necessary data (return values and positions). It - * won't use any other variables. + * The code can use variables with names stored in |resultVar| and |posVar| + * properties of the current node's subnodes. It can't use any other + * variables. */ - choice: function(node, context) { + choice: function(node) { var code, nextAlternativesCode; for (var i = node.alternatives.length - 1; i >= 0; i--) { nextAlternativesCode = i !== node.alternatives.length - 1 - ? fill("choice.next", { - code: code, - resultVar: resultVar(context.resultIndex) - }) + ? fill("choice.next", { node: node, code: code }) : ''; code = fill("choice", { - currentAlternativeCode: emit(node.alternatives[i], context), - nextAlternativesCode: nextAlternativesCode + alternative: node.alternatives[i], + nextAlternativesCode: nextAlternativesCode }); } return code; }, - sequence: function(node, context) { - var elementResultVars = map(node.elements, function(element, i) { - return resultVar(context.resultIndex + i); - }); - - var code = fill("sequence.inner", { - resultVar: resultVar(context.resultIndex), - elementResultVars: elementResultVars - }); + sequence: function(node) { + var code = fill("sequence.inner", { node: node }); for (var i = node.elements.length - 1; i >= 0; i--) { 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) + node: node, + element: node.elements[i], + code: code }); } - return fill("sequence", { code: code, posVar: posVar(context.posIndex) }); - }, - - labeled: function(node, context) { - return emit(node.expression, context); - }, - - simple_and: function(node, context) { - 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 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 fill("semantic_and", { - node: node, - resultVar: resultVar(context.resultIndex) - }); + return fill("sequence", { node: node, code: code }); }, - semantic_not: function(node, context) { - return fill("semantic_not", { - node: node, - resultVar: resultVar(context.resultIndex) - }); - }, + labeled: function(node) { return emit(node.expression); }, - optional: function(node, context) { - return fill("optional", { - expressionCode: emit(node.expression, context), - resultVar: resultVar(context.resultIndex) - }); - }, + simple_and: emitSimple("simple_and"), + simple_not: emitSimple("simple_not"), + semantic_and: emitSimple("semantic_and"), + semantic_not: emitSimple("semantic_not"), + optional: emitSimple("optional"), + zero_or_more: emitSimple("zero_or_more"), + one_or_more: emitSimple("one_or_more"), - zero_or_more: function(node, context) { - 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 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) { + action: function(node) { /* * In case of sequences, we splat their elements into function arguments * one by one. Example: @@ -881,12 +791,12 @@ PEG.compiler.emitter = function(ast) { each(node.expression.elements, function(element, i) { if (element.type === "labeled") { formalParams.push(element.label); - actualParams.push(resultVar(context.resultIndex) + '[' + i + ']'); + actualParams.push(node.resultVar + '[' + i + ']'); } }); } else if (node.expression.type === "labeled") { formalParams = [node.expression.label]; - actualParams = [resultVar(context.resultIndex)]; + actualParams = [node.resultVar]; } else { formalParams = []; actualParams = []; @@ -894,33 +804,16 @@ PEG.compiler.emitter = function(ast) { 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 fill("rule_ref", { - node: node, - resultVar: resultVar(context.resultIndex) + actualParams: actualParams }); }, - literal: function(node, context) { - return fill("literal", { - node: node, - resultVar: resultVar(context.resultIndex) - }); - }, + rule_ref: emitSimple("rule_ref"), + literal: emitSimple("literal"), + any: emitSimple("any"), - any: function(node, context) { - return fill("any", { resultVar: resultVar(context.resultIndex) }); - }, - - "class": function(node, context) { + "class": function(node) { var regexp; if (node.parts.length > 0) { @@ -942,11 +835,7 @@ PEG.compiler.emitter = function(ast) { regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; } - return fill("class", { - node: node, - regexp: regexp, - resultVar: resultVar(context.resultIndex) - }); + return fill("class", { node: node, regexp: regexp }); } }); diff --git a/src/passes.js b/src/passes.js index 6c87152..ad2bdba 100644 --- a/src/passes.js +++ b/src/passes.js @@ -188,80 +188,131 @@ PEG.compiler.passes = { }, /* - * Adds |resultStackDepth| and |posStackDepth| properties to each AST node. - * These properties specify how many positions on the result or position stack - * code generated by the emitter for the node will use. This information is - * used to declare varibles holding the stack data in the generated code. + * Computes names of variables used for storing match results and parse + * positions in generated code. These variables are organized as two stacks. + * The following will hold after running this pass: + * + * * All nodes except "grammar" and "rule" nodes will have a |resultVar| + * property. It will contain a name of the variable that will store a + * match result of the expression represented by the node in generated + * code. + * + * * Some nodes will have a |posVar| property. It will contain a name of the + * variable that will store a parse position in generated code. + * + * * All "rule" nodes will contain |resultVars| and |posVars| properties. + * They will contain a list of values of |resultVar| and |posVar| + * properties used in rule's subnodes. (This is useful to declare + * variables in generated code.) */ - computeStackDepths: function(ast) { - function computeZeroes(node) { - node.resultStackDepth = 0; - node.posStackDepth = 0; + computeVarNames: function(ast) { + function resultVar(index) { return "result" + index; } + function posVar(index) { return "pos" + index; } + + function computeLeaf(node, index) { + node.resultVar = resultVar(index.result); + + return { result: 0, pos: 0 }; } - function computeFromExpression(resultStackDelta, posStackDelta) { - return function(node) { - compute(node.expression); - node.resultStackDepth = node.expression.resultStackDepth + resultStackDelta; - node.posStackDepth = node.expression.posStackDepth + posStackDelta; + function computeFromExpression(delta) { + return function(node, index) { + var depth = compute( + node.expression, + { + result: index.result + delta.result, + pos: index.pos + delta.pos + } + ); + + node.resultVar = resultVar(index.result); + if (delta.pos !== 0) { + node.posVar = posVar(index.pos); + } + + return { + result: depth.result + delta.result, + pos: depth.pos + delta.pos + }; }; } var compute = buildNodeVisitor({ grammar: - function(node) { - for (var name in node.rules) { - compute(node.rules[name]); + function(node, index) { + var name; + + for (name in node.rules) { + compute(node.rules[name], index); } }, - rule: computeFromExpression(1, 0), + rule: + function(node, index) { + var depth = compute(node.expression, index); + + node.resultVar = resultVar(index.result); + node.resultVars = map(range(depth.result + 1), resultVar); + node.posVars = map(range(depth.pos), posVar); + }, choice: - function(node) { - each(node.alternatives, compute); - node.resultStackDepth = Math.max.apply( - null, - map(node.alternatives, function(e) { return e.resultStackDepth; }) - ); - node.posStackDepth = Math.max.apply( - null, - map(node.alternatives, function(e) { return e.posStackDepth; }) - ); + function(node, index) { + var depths = map(node.alternatives, function(alternative) { + return compute(alternative, index); + }); + + node.resultVar = resultVar(index.result); + + return { + result: Math.max.apply(null, pluck(depths, "result")), + pos: Math.max.apply(null, pluck(depths, "pos")) + }; }, sequence: - function(node) { - each(node.elements, compute); - node.resultStackDepth = node.elements.length > 0 - ? Math.max.apply( - null, - map(node.elements, function(e, i) { return i + e.resultStackDepth; }) - ) - : 0; - node.posStackDepth = node.elements.length > 0 - ? 1 + Math.max.apply( - null, - map(node.elements, function(e) { return e.posStackDepth; }) - ) - : 1; + function(node, index) { + var depths = map(node.elements, function(element, i) { + return compute( + element, + { result: index.result + i, pos: index.pos + 1 } + ); + }); + + node.resultVar = resultVar(index.result); + node.posVar = posVar(index.pos); + + return { + result: + node.elements.length > 0 + ? Math.max.apply( + null, + map(depths, function(d, i) { return i + d.result; }) + ) + : 0, + + pos: + node.elements.length > 0 + ? 1 + Math.max.apply(null, pluck(depths, "pos")) + : 1 + }; }, - labeled: computeFromExpression(0, 0), - simple_and: computeFromExpression(0, 1), - simple_not: computeFromExpression(0, 1), - semantic_and: computeZeroes, - semantic_not: computeZeroes, - optional: computeFromExpression(0, 0), - zero_or_more: computeFromExpression(1, 0), - one_or_more: computeFromExpression(1, 0), - action: computeFromExpression(0, 1), - rule_ref: computeZeroes, - literal: computeZeroes, - any: computeZeroes, - "class": computeZeroes + labeled: computeFromExpression({ result: 0, pos: 0 }), + simple_and: computeFromExpression({ result: 0, pos: 1 }), + simple_not: computeFromExpression({ result: 0, pos: 1 }), + semantic_and: computeLeaf, + semantic_not: computeLeaf, + optional: computeFromExpression({ result: 0, pos: 0 }), + zero_or_more: computeFromExpression({ result: 1, pos: 0 }), + one_or_more: computeFromExpression({ result: 1, pos: 0 }), + action: computeFromExpression({ result: 0, pos: 1 }), + rule_ref: computeLeaf, + literal: computeLeaf, + any: computeLeaf, + "class": computeLeaf }); - compute(ast); + compute(ast, { result: 0, pos: 0 }); } }; diff --git a/src/utils.js b/src/utils.js index d4daa8e..3c76570 100644 --- a/src/utils.js +++ b/src/utils.js @@ -42,6 +42,10 @@ function map(array, callback) { return result; } +function pluck(array, key) { + return map(array, function (e) { return e[key]; }); +} + /* * Returns a string padded on the left to a desired length with a character. * diff --git a/test/passes-test.js b/test/passes-test.js index 0f27def..fb30472 100644 --- a/test/passes-test.js +++ b/test/passes-test.js @@ -216,94 +216,229 @@ test("removes proxy rules", function() { } }); -test("computes stack depths", function() { +test("computes variable names", function() { + var leafDetails = { resultVar: "result0" }, + choiceDetails = { + resultVar: "result0", + alternatives: [ + { resultVar: "result0", posVar: "pos0" }, + { resultVar: "result0", posVar: "pos0" }, + { resultVar: "result0", posVar: "pos0" } + ] + }, + sequenceDetails = { + resultVar: "result0", + posVar: "pos0", + elements: [ + { resultVar: "result0", posVar: "pos1" }, + { resultVar: "result1", posVar: "pos1" }, + { resultVar: "result2", posVar: "pos1" } + ] + }; + var cases = [ /* Choice */ { - grammar: 'start = "a" / "b" / "c"', - resultStackDepth: 1, - posStackDepth: 0 + grammar: 'start = &"a" / &"b" / &"c"', + resultVars: ["result0"], + posVars: ["pos0"], + details: choiceDetails }, { - grammar: 'start = "a" / "b"* / "c"', - resultStackDepth: 2, - posStackDepth: 0 + grammar: 'start = &"a" / &"b"* / &"c"', + resultVars: ["result0", "result1"], + posVars: ["pos0"], + details: choiceDetails }, { - grammar: 'start = "a" / &"b" / "c"', - resultStackDepth: 1, - posStackDepth: 1 + grammar: 'start = &"a" / &(&"b") / &"c"', + resultVars: ["result0"], + posVars: ["pos0", "pos1"], + details: choiceDetails }, /* Sequence */ { - grammar: 'start = ', - resultStackDepth: 1, - posStackDepth: 1 + grammar: 'start = ', + resultVars: ["result0"], + posVars: ["pos0"], + details: { resultVar: "result0", posVar: "pos0" } }, { - grammar: 'start = "a" "b" "c"', - resultStackDepth: 3, - posStackDepth: 1 + grammar: 'start = &"a" &"b" &"c"', + resultVars: ["result0", "result1", "result2"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = "a" "b" "c"*', - resultStackDepth: 4, - posStackDepth: 1 + grammar: 'start = &"a" &"b" &"c"*', + resultVars: ["result0", "result1", "result2", "result3"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = "a" "b"* "c"', - resultStackDepth: 3, - posStackDepth: 1 + grammar: 'start = &"a" &"b"* &"c"', + resultVars: ["result0", "result1", "result2"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = "a" ("b"*)* "c"', - resultStackDepth: 4, - posStackDepth: 1 + grammar: 'start = &"a" &("b"*)* &"c"', + resultVars: ["result0", "result1", "result2", "result3"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = "a"* "b" "c"', - resultStackDepth: 3, - posStackDepth: 1 + grammar: 'start = &"a"* &"b" &"c"', + resultVars: ["result0", "result1", "result2"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = ("a"*)* "b" "c"', - resultStackDepth: 3, - posStackDepth: 1 + grammar: 'start = &("a"*)* &"b" &"c"', + resultVars: ["result0", "result1", "result2"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = (("a"*)*)* "b" "c"', - resultStackDepth: 4, - posStackDepth: 1 + grammar: 'start = &(("a"*)*)* &"b" &"c"', + resultVars: ["result0", "result1", "result2", "result3"], + posVars: ["pos0", "pos1"], + details: sequenceDetails }, { - grammar: 'start = "a" &"b" "c"', - resultStackDepth: 3, - posStackDepth: 2 + grammar: 'start = &"a" &(&"b") &"c"', + resultVars: ["result0", "result1", "result2"], + posVars: ["pos0", "pos1", "pos2"], + details: sequenceDetails }, /* Others */ - { grammar: 'start = label:"a"', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = &"a"', resultStackDepth: 1, posStackDepth: 1 }, - { grammar: 'start = !"a"', resultStackDepth: 1, posStackDepth: 1 }, - { grammar: 'start = &{ code }', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = !{ code }', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = "a"?', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = "a"*', resultStackDepth: 2, posStackDepth: 0 }, - { grammar: 'start = "a"+', resultStackDepth: 2, posStackDepth: 0 }, - { grammar: 'start = "a" { code }', resultStackDepth: 1, posStackDepth: 1 }, - { grammar: 'start = a', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = "a"', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = .', resultStackDepth: 1, posStackDepth: 0 }, - { grammar: 'start = [a-z]', resultStackDepth: 1, posStackDepth: 0 } + { + grammar: 'start = label:&"a"', + resultVars: ["result0"], + posVars: ["pos0"], + details: { + resultVar: "result0", + expression: { resultVar: "result0", posVar: "pos0" } + } + }, + { + grammar: 'start = &(&"a")', + resultVars: ["result0"], + posVars: ["pos0", "pos1"], + details: { + resultVar: "result0", + posVar: "pos0", + expression: { resultVar: "result0", posVar: "pos1" } + } + }, + { + grammar: 'start = !(&"a")', + resultVars: ["result0"], + posVars: ["pos0", "pos1"], + details: { + resultVar: "result0", + posVar: "pos0", + expression: { resultVar: "result0", posVar: "pos1" } + } + }, + { + grammar: 'start = &{ code }', + resultVars: ["result0"], + posVars: [], + details: leafDetails + }, + { + grammar: 'start = !{ code }', + resultVars: ["result0"], + posVars: [], + details: leafDetails + }, + { + grammar: 'start = (&"a")?', + resultVars: ["result0"], + posVars: ["pos0"], + details: { + resultVar: "result0", + expression: { resultVar: "result0", posVar: "pos0" } + } + }, + { + grammar: 'start = (&"a")*', + resultVars: ["result0", "result1"], + posVars: ["pos0"], + details: { + resultVar: "result0", + expression: { resultVar: "result1", posVar: "pos0" } + } + }, + { + grammar: 'start = (&"a")+', + resultVars: ["result0", "result1"], + posVars: ["pos0"], + details: { + resultVar: "result0", + expression: { resultVar: "result1", posVar: "pos0" } + } + }, + { + grammar: 'start = &"a" { code }', + resultVars: ["result0"], + posVars: ["pos0", "pos1"], + details: { + resultVar: "result0", + posVar: "pos0", + expression: { resultVar: "result0", posVar: "pos1" } + } + }, + { + grammar: 'start = a', + resultVars: ["result0"], + posVars: [], + details: leafDetails + }, + { + grammar: 'start = "a"', + resultVars: ["result0"], + posVars: [], + details: leafDetails + }, + { + grammar: 'start = .', + resultVars: ["result0"], + posVars: [], + details: leafDetails + }, + { + grammar: 'start = [a-z]', + resultVars: ["result0"], + posVars: [], + details: leafDetails + } ]; + function checkDetails(node, details) { + for (var key in details) { + if (Object.prototype.toString.call(details[key]) === "[object Array]") { + for (var i = 0; i < details[key].length; i++) { + checkDetails(node[key][i], details[key][i]); + } + } else if (typeof details[key] === "object") { + checkDetails(node[key], details[key]); + } else { + strictEqual(node[key], details[key]); + } + } + } + for (var i = 0; i < cases.length; i++) { var ast = PEG.parser.parse(cases[i].grammar); - PEG.compiler.passes.computeStackDepths(ast); + PEG.compiler.passes.computeVarNames(ast); - deepEqual(ast.rules["start"].resultStackDepth, cases[i].resultStackDepth); - deepEqual(ast.rules["start"].posStackDepth, cases[i].posStackDepth); + deepEqual(ast.rules["start"].resultVars, cases[i].resultVars); + deepEqual(ast.rules["start"].posVars, cases[i].posVars); + checkDetails(ast.rules["start"].expression, cases[i].details); } });