Change ordering of "action" code

Places all code that does something with "action" AST nodes under code
handling "choice" nodes.

This ordering is logical because now all the node handling code matches
the sequence in which various node types usually appear when descending
through the AST tree.
redux
David Majda 13 years ago
parent cdf23e0a49
commit 725927e05f

@ -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: {}
}));
});
});
});

@ -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"],

@ -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);
});
});

@ -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',

@ -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 { }');
});
});

@ -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([

@ -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,

@ -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,

@ -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"),

@ -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) {

@ -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) {

Loading…
Cancel
Save