From fb5f6c6ee94b962c45f591f64b293bc11ba57ae6 Mon Sep 17 00:00:00 2001 From: David Majda Date: Fri, 13 Feb 2015 14:10:32 +0100 Subject: [PATCH] Make labels behave like block-scoped variables Action and predicate code can now see variables defined in expressions "above" them. Based on a pull request by Bryon Vandiver (@asterick): https://github.com/pegjs/pegjs/pull/180 Fixes #316. --- lib/compiler/passes/generate-bytecode.js | 18 +- .../generated-parser-behavior.spec.js | 288 ++++++++++++++++++ 2 files changed, 298 insertions(+), 8 deletions(-) diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js index 2fba3d6..23c31bd 100644 --- a/lib/compiler/passes/generate-bytecode.js +++ b/lib/compiler/passes/generate-bytecode.js @@ -231,7 +231,7 @@ function generateBytecode(ast) { [op.SILENT_FAILS_ON], generate(expression, { sp: context.sp + 1, - env: { }, + env: objects.clone(context.env), action: null }), [op.SILENT_FAILS_OFF], @@ -317,7 +317,7 @@ function generateBytecode(ast) { return buildSequence( generate(alternatives[0], { sp: context.sp, - env: { }, + env: objects.clone(context.env), action: null }), alternatives.length > 1 @@ -337,7 +337,7 @@ function generateBytecode(ast) { }, action: function(node, context) { - var env = { }, + var env = objects.clone(context.env), emitCall = node.expression.type !== "sequence" || node.expression.elements.length === 0, expressionCode = generate(node.expression, { @@ -425,11 +425,13 @@ function generateBytecode(ast) { }, labeled: function(node, context) { + var env = objects.clone(context.env); + context.env[node.label] = context.sp + 1; return generate(node.expression, { sp: context.sp, - env: { }, + env: env, action: null }); }, @@ -439,7 +441,7 @@ function generateBytecode(ast) { [op.PUSH_CURR_POS], generate(node.expression, { sp: context.sp + 1, - env: { }, + env: objects.clone(context.env), action: null }), buildCondition( @@ -462,7 +464,7 @@ function generateBytecode(ast) { return buildSequence( generate(node.expression, { sp: context.sp, - env: { }, + env: objects.clone(context.env), action: null }), buildCondition( @@ -476,7 +478,7 @@ function generateBytecode(ast) { zero_or_more: function(node, context) { var expressionCode = generate(node.expression, { sp: context.sp + 1, - env: { }, + env: objects.clone(context.env), action: null }); @@ -491,7 +493,7 @@ function generateBytecode(ast) { one_or_more: function(node, context) { var expressionCode = generate(node.expression, { sp: context.sp + 1, - env: { }, + env: objects.clone(context.env), action: null }); diff --git a/spec/behavior/generated-parser-behavior.spec.js b/spec/behavior/generated-parser-behavior.spec.js index 188b00e..651cf31 100644 --- a/spec/behavior/generated-parser-behavior.spec.js +++ b/spec/behavior/generated-parser-behavior.spec.js @@ -440,6 +440,105 @@ describe("generated parser behavior", function() { expect(parser).toParse("abc"); }); + + it("can access label variables from preceding labeled elements in an outside sequence (group)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" &{ return a === "a" && b === "b" && c === "c"; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (optional)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" &{ return a === "a" && b === "b" && c === "c"; })?', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (zero or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" &{ return a === "a" && b === "b" && c === "c"; })*', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["d", undefined]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (one or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" &{ return a === "a" && b === "b" && c === "c"; })+', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["d", undefined]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (text)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" $("d" &{ return a === "a" && b === "b" && c === "c"; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (positive simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" &("d" &{ return a === "a" && b === "b" && c === "c"; }) "d"', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", undefined, "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (negative simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" !("d" &{ return a === "a" && b === "b" && c === "c"; }) "e"', + options + ); + + expect(parser).toParse("abce", ["a", "b", "c", undefined, "e"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (label)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" d:("d" &{ return a === "a" && b === "b" && c === "c"; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (sequence)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" "e" "f" &{ return a === "a" && b === "b" && c === "c"; })', + options + ); + + expect(parser).toParse("abcdef", ["a", "b", "c", ["d", "e", "f", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (action)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" (d:("d" &{ return a === "a" && b === "b" && c === "c"; }) { return d; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (choice)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" / "e" / "f" &{ return a === "a" && b === "b" && c === "c"; })', + options + ); + + expect(parser).toParse("abcf", ["a", "b", "c", ["f", undefined]]); + }); }); describe("initializer variables & functions", function() { @@ -532,6 +631,96 @@ describe("generated parser behavior", function() { expect(parser).toParse("abc"); }); + + it("can access label variables from preceding labeled elements in an outside sequence (optional)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" !{ return a !== "a" || b !== "b" || c !== "c"; })?', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (zero or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" !{ return a !== "a" || b !== "b" || c !== "c"; })*', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["d", undefined]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (one or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" !{ return a !== "a" || b !== "b" || c !== "c"; })+', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["d", undefined]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (text)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" $("d" !{ return a !== "a" || b !== "b" || c !== "c"; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (positive simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" &("d" !{ return a !== "a" || b !== "b" || c !== "c"; }) "d"', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", undefined, "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (negative simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" !("d" !{ return a !== "a" || b !== "b" || c !== "c"; }) "e"', + options + ); + + expect(parser).toParse("abce", ["a", "b", "c", undefined, "e"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (label)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" d:("d" !{ return a !== "a" || b !== "b" || c !== "c"; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (sequence)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" "e" "f" !{ return a !== "a" || b !== "b" || c !== "c"; })', + options + ); + + expect(parser).toParse("abcdef", ["a", "b", "c", ["d", "e", "f", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (action)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" (d:("d" !{ return a !== "a" || b !== "b" || c !== "c"; }) { return d; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["d", undefined]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (choice)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" / "e" / "f" !{ return a !== "a" || b !== "b" || c !== "c"; })', + options + ); + + expect(parser).toParse("abcf", ["a", "b", "c", ["f", undefined]]); + }); }); describe("initializer variables & functions", function() { @@ -820,6 +1009,105 @@ describe("generated parser behavior", function() { expect(parser).toParse("abc", ["a", "b", "c"]); }); + + it("can access label variables from preceding labeled elements in an outside sequence (group)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" { return [a, b, c]; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["a", "b", "c"]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (optional)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" { return [a, b, c]; })?', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["a", "b", "c"]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (zero or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" { return [a, b, c]; })*', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["a", "b", "c"]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (one or more)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" { return [a, b, c]; })+', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", [["a", "b", "c"]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (text)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" $("d" { return [a, b, c]; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (positive simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" &("d" { return [a, b, c]; }) "d"', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", undefined, "d"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (negative simple predicate)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" !("d" { return [a, b, c]; }) "e"', + options + ); + + expect(parser).toParse("abce", ["a", "b", "c", undefined, "e"]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (label)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" d:("d" { return [a, b, c]; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["a", "b", "c"]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (sequence)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" "e" ("f" { return [a, b, c]; }))', + options + ); + + expect(parser).toParse("abcdef", ["a", "b", "c", ["d", "e", ["a", "b", "c"]]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (action)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" (d:("d" { return [a, b, c]; }) { return d; })', + options + ); + + expect(parser).toParse("abcd", ["a", "b", "c", ["a", "b", "c"]]); + }); + + it("can access label variables from preceding labeled elements in an outside sequence (choice)", function() { + var parser = PEG.buildParser( + 'start = a:"a" b:"b" c:"c" ("d" / "e" / "f" { return [a, b, c]; })', + options + ); + + expect(parser).toParse("abcf", ["a", "b", "c", ["a", "b", "c"]]); + }); }); describe("initializer variables & functions", function() {