diff --git a/spec/compiler/passes/compute-params.spec.js b/spec/compiler/passes/compute-params.spec.js index 4355836..ec1bdee 100644 --- a/spec/compiler/passes/compute-params.spec.js +++ b/spec/compiler/passes/compute-params.spec.js @@ -63,6 +63,12 @@ describe("compiler pass |computeParams|", function() { ); }); + it("computes params for an action", function() { + expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({ + params: { a: "result0" } + })); + }); + it("computes params for a sequence", function() { expect(pass).toChangeAST( 'start = (a:"a" { }) "b" "c"', @@ -113,12 +119,6 @@ describe("compiler pass |computeParams|", function() { params: { a: "result1" } })); }); - - it("computes params for an action", function() { - expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({ - params: { a: "result0" } - })); - }); }); describe("scoping", function() { @@ -129,6 +129,12 @@ describe("compiler pass |computeParams|", function() { ); }); + it("creates a new scope for an action", function() { + expect(pass).toChangeAST('start = (a:"a" { }) { }', expressionDetails({ + params: {} + })); + }); + it("does not create a new scope for a sequence", function() { expect(pass).toChangeAST( 'start = a:"a" b:"b" c:"c" { }', @@ -197,11 +203,5 @@ describe("compiler pass |computeParams|", function() { params: {} })); }); - - it("creates a new scope for an action", function() { - expect(pass).toChangeAST('start = (a:"a" { }) { }', expressionDetails({ - params: {} - })); - }); }); }); diff --git a/spec/compiler/passes/compute-var-names.spec.js b/spec/compiler/passes/compute-var-names.spec.js index bbaf3b7..8d32efe 100644 --- a/spec/compiler/passes/compute-var-names.spec.js +++ b/spec/compiler/passes/compute-var-names.spec.js @@ -51,6 +51,18 @@ describe("compiler pass |computeVarNames|", function() { })); }); + it("computes variable names for an action", function() { + expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({ + resultVars: ["result0"], + posVars: ["pos0", "pos1"], + expression: { + resultVar: "result0", + posVar: "pos0", + expression: { resultVar: "result0", posVar: "pos1" } + } + })); + }); + it("computes variable names for a sequence", function() { expect(pass).toChangeAST('start = ', ruleDetails({ resultVars: ["result0"], @@ -183,18 +195,6 @@ describe("compiler pass |computeVarNames|", function() { })); }); - it("computes variable names for an action", function() { - expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({ - resultVars: ["result0"], - posVars: ["pos0", "pos1"], - expression: { - resultVar: "result0", - posVar: "pos0", - expression: { resultVar: "result0", posVar: "pos1" } - } - })); - }); - it("computes variable names for a rule reference", function() { expect(pass).toChangeAST('start = a', ruleDetails({ resultVars: ["result0"], diff --git a/spec/compiler/passes/remove-proxy-rules.spec.js b/spec/compiler/passes/remove-proxy-rules.spec.js index 6cde306..4030239 100644 --- a/spec/compiler/passes/remove-proxy-rules.spec.js +++ b/spec/compiler/passes/remove-proxy-rules.spec.js @@ -41,6 +41,10 @@ describe("compiler pass |removeProxyRules|", function() { ); }); + it("removes proxy rule from an action", function() { + expect(pass).toChangeAST(proxyGrammar('start = proxy { }'), simpleDetails); + }); + it("removes proxy rule from a sequence", function() { expect(pass).toChangeAST( proxyGrammar('start = proxy "a" "b"'), @@ -75,8 +79,4 @@ describe("compiler pass |removeProxyRules|", function() { it("removes proxy rule from a one or more", function() { expect(pass).toChangeAST(proxyGrammar('start = proxy+'), simpleDetails); }); - - it("removes proxy rule from an action", function() { - expect(pass).toChangeAST(proxyGrammar('start = proxy { }'), simpleDetails); - }); }); diff --git a/spec/compiler/passes/report-left-recursion.spec.js b/spec/compiler/passes/report-left-recursion.spec.js index 1155b02..68f8e09 100644 --- a/spec/compiler/passes/report-left-recursion.spec.js +++ b/spec/compiler/passes/report-left-recursion.spec.js @@ -51,6 +51,10 @@ describe("compiler pass |reportLeftRecursion|", function() { expect(pass).toReportLeftRecursionIn('start = "a" / "b" / start'); }); + it("reports left recursion inside an action", function() { + expect(pass).toReportLeftRecursionIn('start = start { }'); + }); + it("reports left recursion inside a sequence", function() { expect(pass).toReportLeftRecursionIn('start = start "a" "b"'); }); @@ -79,10 +83,6 @@ describe("compiler pass |reportLeftRecursion|", function() { expect(pass).toReportLeftRecursionIn('start = start+'); }); - it("reports left recursion inside an action", function() { - expect(pass).toReportLeftRecursionIn('start = start { }'); - }); - it("reports indirect left recursion", function() { expect(pass).toReportLeftRecursionIn([ 'start = stop', diff --git a/spec/compiler/passes/report-missing-rules.spec.js b/spec/compiler/passes/report-missing-rules.spec.js index edd221d..6acaa47 100644 --- a/spec/compiler/passes/report-missing-rules.spec.js +++ b/spec/compiler/passes/report-missing-rules.spec.js @@ -51,6 +51,10 @@ describe("compiler pass |reportMissingRules|", function() { expect(pass).toReportMissingRuleIn('start = "a" / "b" / missing'); }); + it("reports missing rule referenced from an action", function() { + expect(pass).toReportMissingRuleIn('start = missing { }'); + }); + it("reports missing rule referenced from a sequence", function() { expect(pass).toReportMissingRuleIn('start = missing "a" "b"'); expect(pass).toReportMissingRuleIn('start = "a" "b" missing'); @@ -79,8 +83,4 @@ describe("compiler pass |reportMissingRules|", function() { it("reports missing rule referenced from a one or more", function() { expect(pass).toReportMissingRuleIn('start = missing+'); }); - - it("reports missing rule referenced from an action", function() { - expect(pass).toReportMissingRuleIn('start = missing { }'); - }); }); diff --git a/spec/generated-parser.spec.js b/spec/generated-parser.spec.js index adc9bd9..0310388 100644 --- a/spec/generated-parser.spec.js +++ b/spec/generated-parser.spec.js @@ -197,6 +197,96 @@ describe("generated parser", function() { }); }); + describe("action code", function() { + it("tranforms the expression result by returnung a non-|null| value", function() { + var parser = PEG.buildParser('start = "a" { return 42; }', options); + + expect(parser).toParse("a", 42); + }); + + it("causes match failure by returning |null|", function() { + var parser = PEG.buildParser('start = "a" { return null; }', options); + + expect(parser).toFailToParse("a"); + }); + + it("is not called when the expression does not match", function() { + var parser = PEG.buildParser( + 'start = "a" { throw "Boom!"; } / "b"', + options + ); + + expect(parser).toParse("b", "b"); + }); + + it("can use label variables", function() { + var parser = PEG.buildParser('start = a:"a" { return a; }', options); + + expect(parser).toParse("a", "a"); + }); + + it("can use the |offset| variable to get the current parse position", function() { + var parser = PEG.buildParser( + 'start = "a" ("b" { return offset; })', + options + ); + + expect(parser).toParse("ab", ["a", 1]); + }); + + if (options.trackLineAndColumn) { + it("can use the |line| and |column| variables to get the current line and column", function() { + var parser = PEG.buildParser([ + '{ var result; }', + 'start = line (nl+ line)* { return result; }', + 'line = thing (" "+ thing)*', + 'thing = digit / mark', + 'digit = [0-9]', + 'mark = "x" { result = [line, column]; }', + 'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")' + ].join("\n"), options); + + expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [7, 5]); + + /* Non-Unix newlines */ + expect(parser).toParse("1\rx", [2, 1]); // Old Mac + expect(parser).toParse("1\r\nx", [2, 1]); // Windows + expect(parser).toParse("1\n\rx", [3, 1]); // mismatched + + /* Strange newlines */ + expect(parser).toParse("1\u2028x", [2, 1]); // line separator + expect(parser).toParse("1\u2029x", [2, 1]); // paragraph separator + }); + } + + it("can use variables defined in the initializer", function() { + var parser = PEG.buildParser([ + '{ var v = 42 }', + 'start = "a" { return v; }' + ].join("\n"), options); + + expect(parser).toParse("a", 42); + }); + + it("can use functions defined in the initializer", function() { + var parser = PEG.buildParser([ + '{ function f() { return 42; } }', + 'start = "a" { return f(); }' + ].join("\n"), options); + + expect(parser).toParse("a", 42); + }); + + it("does not advance position when the expression matches but the action returns |null|", function() { + var parser = PEG.buildParser( + 'start = "a" { return null; } / "a"', + options + ); + + expect(parser).toParse("a", "a"); + }); + }); + describe("sequence matching", function() { it("matches empty sequence correctly", function() { var parser = PEG.buildParser('start = ', options); @@ -447,96 +537,6 @@ describe("generated parser", function() { }); }); - describe("action code", function() { - it("tranforms the expression result by returnung a non-|null| value", function() { - var parser = PEG.buildParser('start = "a" { return 42; }', options); - - expect(parser).toParse("a", 42); - }); - - it("causes match failure by returning |null|", function() { - var parser = PEG.buildParser('start = "a" { return null; }', options); - - expect(parser).toFailToParse("a"); - }); - - it("is not called when the expression does not match", function() { - var parser = PEG.buildParser( - 'start = "a" { throw "Boom!"; } / "b"', - options - ); - - expect(parser).toParse("b", "b"); - }); - - it("can use label variables", function() { - var parser = PEG.buildParser('start = a:"a" { return a; }', options); - - expect(parser).toParse("a", "a"); - }); - - it("can use the |offset| variable to get the current parse position", function() { - var parser = PEG.buildParser( - 'start = "a" ("b" { return offset; })', - options - ); - - expect(parser).toParse("ab", ["a", 1]); - }); - - if (options.trackLineAndColumn) { - it("can use the |line| and |column| variables to get the current line and column", function() { - var parser = PEG.buildParser([ - '{ var result; }', - 'start = line (nl+ line)* { return result; }', - 'line = thing (" "+ thing)*', - 'thing = digit / mark', - 'digit = [0-9]', - 'mark = "x" { result = [line, column]; }', - 'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")' - ].join("\n"), options); - - expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [7, 5]); - - /* Non-Unix newlines */ - expect(parser).toParse("1\rx", [2, 1]); // Old Mac - expect(parser).toParse("1\r\nx", [2, 1]); // Windows - expect(parser).toParse("1\n\rx", [3, 1]); // mismatched - - /* Strange newlines */ - expect(parser).toParse("1\u2028x", [2, 1]); // line separator - expect(parser).toParse("1\u2029x", [2, 1]); // paragraph separator - }); - } - - it("can use variables defined in the initializer", function() { - var parser = PEG.buildParser([ - '{ var v = 42 }', - 'start = "a" { return v; }' - ].join("\n"), options); - - expect(parser).toParse("a", 42); - }); - - it("can use functions defined in the initializer", function() { - var parser = PEG.buildParser([ - '{ function f() { return 42; } }', - 'start = "a" { return f(); }' - ].join("\n"), options); - - expect(parser).toParse("a", 42); - }); - - it("does not advance position when the expression matches but the action returns |null|", function() { - var parser = PEG.buildParser( - 'start = "a" { return null; } / "a"', - options - ); - - expect(parser).toParse("a", "a"); - }); - }); - describe("rule reference matching", function() { it("follows rule references", function() { var parser = PEG.buildParser([ diff --git a/src/compiler/passes/compute-params.js b/src/compiler/passes/compute-params.js index 68379e8..926c7de 100644 --- a/src/compiler/passes/compute-params.js +++ b/src/compiler/passes/compute-params.js @@ -52,6 +52,14 @@ PEG.compiler.passes.computeParams = function(ast) { scoped(function() { each(node.alternatives, compute); }); }, + action: + function(node) { + scoped(function() { + compute(node.expression); + computeParams(node); + }); + }, + sequence: function(node) { var env = envs[envs.length - 1], name; @@ -86,15 +94,6 @@ PEG.compiler.passes.computeParams = function(ast) { optional: computeForScopedExpression, zero_or_more: computeForScopedExpression, one_or_more: computeForScopedExpression, - - action: - function(node) { - scoped(function() { - compute(node.expression); - computeParams(node); - }); - }, - rule_ref: nop, literal: nop, "class": nop, diff --git a/src/compiler/passes/compute-var-names.js b/src/compiler/passes/compute-var-names.js index 7a28fc5..6121fd3 100644 --- a/src/compiler/passes/compute-var-names.js +++ b/src/compiler/passes/compute-var-names.js @@ -80,6 +80,8 @@ PEG.compiler.passes.computeVarNames = function(ast) { }; }, + action: computeFromExpression({ result: 0, pos: 1 }), + sequence: function(node, index) { var depths = map(node.elements, function(element, i) { @@ -116,7 +118,6 @@ PEG.compiler.passes.computeVarNames = function(ast) { 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, "class": computeLeaf, diff --git a/src/compiler/passes/generate-code.js b/src/compiler/passes/generate-code.js index 67d89be..a74f089 100644 --- a/src/compiler/passes/generate-code.js +++ b/src/compiler/passes/generate-code.js @@ -593,6 +593,16 @@ PEG.compiler.passes.generateCode = function(ast, options) { ' #block code', '}' ], + action: [ + '#{posSave(node)};', + '#block emit(node.expression)', + 'if (#{node.resultVar} !== null) {', + ' #{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [node.posVar + ".offset", node.posVar + ".line", node.posVar + ".column"] : [node.posVar]).concat(values(node.params)).join(", ")});', + '}', + 'if (#{node.resultVar} === null) {', + ' #{posRestore(node)};', + '}' + ], sequence: [ '#{posSave(node)};', '#block code' @@ -663,16 +673,6 @@ PEG.compiler.passes.generateCode = function(ast, options) { ' #{node.resultVar} = null;', '}' ], - action: [ - '#{posSave(node)};', - '#block emit(node.expression)', - 'if (#{node.resultVar} !== null) {', - ' #{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [node.posVar + ".offset", node.posVar + ".line", node.posVar + ".column"] : [node.posVar]).concat(values(node.params)).join(", ")});', - '}', - 'if (#{node.resultVar} === null) {', - ' #{posRestore(node)};', - '}' - ], rule_ref: [ '#{node.resultVar} = parse_#{node.name}();' ], @@ -832,6 +832,8 @@ PEG.compiler.passes.generateCode = function(ast, options) { return code; }, + action: emitSimple("action"), + sequence: function(node) { var code = fill("sequence.inner", { node: node }); @@ -855,7 +857,6 @@ PEG.compiler.passes.generateCode = function(ast, options) { optional: emitSimple("optional"), zero_or_more: emitSimple("zero_or_more"), one_or_more: emitSimple("one_or_more"), - action: emitSimple("action"), rule_ref: emitSimple("rule_ref"), literal: emitSimple("literal"), diff --git a/src/compiler/passes/report-left-recursion.js b/src/compiler/passes/report-left-recursion.js index caef1cf..60251e8 100644 --- a/src/compiler/passes/report-left-recursion.js +++ b/src/compiler/passes/report-left-recursion.js @@ -24,6 +24,7 @@ PEG.compiler.passes.reportLeftRecursion = function(ast) { named: checkExpression, choice: checkSubnodes("alternatives"), + action: checkExpression, sequence: function(node, appliedRules) { @@ -40,7 +41,6 @@ PEG.compiler.passes.reportLeftRecursion = function(ast) { optional: checkExpression, zero_or_more: checkExpression, one_or_more: checkExpression, - action: checkExpression, rule_ref: function(node, appliedRules) { diff --git a/src/compiler/passes/report-missing-rules.js b/src/compiler/passes/report-missing-rules.js index 35add74..0a14877 100644 --- a/src/compiler/passes/report-missing-rules.js +++ b/src/compiler/passes/report-missing-rules.js @@ -13,6 +13,7 @@ PEG.compiler.passes.reportMissingRules = function(ast) { rule: checkExpression, named: checkExpression, choice: checkSubnodes("alternatives"), + action: checkExpression, sequence: checkSubnodes("elements"), labeled: checkExpression, simple_and: checkExpression, @@ -22,7 +23,6 @@ PEG.compiler.passes.reportMissingRules = function(ast) { optional: checkExpression, zero_or_more: checkExpression, one_or_more: checkExpression, - action: checkExpression, rule_ref: function(node) {