Refactor named rules AST representation

PEG.js grammar rules are represented by |rule| nodes in the AST. Until
now, all such nodes had a |displayName| property which was either |null|
or stored rule's human-readable name. This commit gets rid of the
|displayName| property and starts representing rules with a
human-readable name using a new |named| node (a child of the |rule|
node).

This change simplifies code generation code a bit as tests for
|displayName| can be removed (see changes in generate-code.js). It also
separates different concerns from each other nicely.
redux
David Majda 12 years ago
parent b05b09a9f6
commit eb4badab24

@ -41,6 +41,13 @@ describe("compiler pass |computeParams|", function() {
}); });
describe("recursive walk", function() { describe("recursive walk", function() {
it("computes params for a named", function() {
expect(pass).toChangeAST(
'start "start" = a:"a" { }',
innerExpressionDetails({ params: { a: "result0" } })
);
});
it("computes params for a choice", function() { it("computes params for a choice", function() {
expect(pass).toChangeAST( expect(pass).toChangeAST(
'start = a:"a" { } / "b" / "c"', 'start = a:"a" { } / "b" / "c"',

@ -22,6 +22,17 @@ describe("compiler pass |computeVarNames|", function() {
function ruleDetails(details) { return { rules: [details] }; } function ruleDetails(details) { return { rules: [details] }; }
it("computes variable names for a named", function() {
expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({
resultVars: ["result0"],
posVars: ["pos0"],
expression: {
resultVar: "result0",
expression: { resultVar: "result0", posVar: "pos0" }
}
}));
});
it("computes variable names for a choice", function() { it("computes variable names for a choice", function() {
expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({ expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({
resultVars: ["result0"], resultVars: ["result0"],

@ -23,6 +23,13 @@ describe("compiler pass |removeProxyRules|", function() {
}); });
}); });
it("removes proxy rule from a named", function() {
expect(pass).toChangeAST(
proxyGrammar('start "start" = proxy'),
simpleDetails
);
});
it("removes proxy rule from a choice", function() { it("removes proxy rule from a choice", function() {
expect(pass).toChangeAST( expect(pass).toChangeAST(
proxyGrammar('start = proxy / "a" / "b"'), proxyGrammar('start = proxy / "a" / "b"'),

@ -42,6 +42,10 @@ describe("compiler pass |reportLeftRecursion|", function() {
expect(pass).toReportLeftRecursionIn('start = start'); expect(pass).toReportLeftRecursionIn('start = start');
}); });
it("reports left recursion inside a named", function() {
expect(pass).toReportLeftRecursionIn('start "start" = start');
});
it("reports left recursion inside a choice", function() { it("reports left recursion inside a choice", function() {
expect(pass).toReportLeftRecursionIn('start = start / "a" / "b"'); expect(pass).toReportLeftRecursionIn('start = start / "a" / "b"');
expect(pass).toReportLeftRecursionIn('start = "a" / "b" / start'); expect(pass).toReportLeftRecursionIn('start = "a" / "b" / start');

@ -42,6 +42,10 @@ describe("compiler pass |reportMissingRules|", function() {
expect(pass).toReportMissingRuleIn('start = missing'); expect(pass).toReportMissingRuleIn('start = missing');
}); });
it("reports missing rule referenced from a named", function() {
expect(pass).toReportMissingRuleIn('start "start" = missing');
});
it("reports missing rule referenced from a choice", function() { it("reports missing rule referenced from a choice", function() {
expect(pass).toReportMissingRuleIn('start = missing / "a" / "b"'); expect(pass).toReportMissingRuleIn('start = missing / "a" / "b"');
expect(pass).toReportMissingRuleIn('start = "a" / "b" / missing'); expect(pass).toReportMissingRuleIn('start = "a" / "b" / missing');

@ -171,17 +171,18 @@ describe("generated parser", function() {
expect(parser).toParse("ac", 2); expect(parser).toParse("ac", 2);
}); });
} }
});
it("does not overwrite expected string on failure when not named", function() { describe("named matching", function() {
var parser = PEG.buildParser('start = [0-9]', options); var parser = PEG.buildParser('start "start" = "a"');
expect(parser).toFailToParse("a", { expected: ["[0-9]"] }); it("delegates to the expression", function() {
expect(parser).toParse("a", "a");
expect(parser).toFailToParse("b");
}); });
it("overwrites expected string on failure when named", function() { it("overwrites expected string on failure", function() {
var parser = PEG.buildParser('start "digit" = [0-9]', options); expect(parser).toFailToParse("b", { expected: ["start"] });
expect(parser).toFailToParse("a", { expected: ["digit"] });
}); });
}); });

@ -23,18 +23,16 @@ describe("PEG.js grammar parser", function() {
}; };
function oneRuleGrammar(expression) { function oneRuleGrammar(expression) {
var initializer = arguments.length > 1 ? arguments[1] : null, var initializer = arguments.length > 1 ? arguments[1] : null;
displayName = arguments.length > 2 ? arguments[2] : null;
return { return {
type: "grammar", type: "grammar",
initializer: initializer, initializer: initializer,
rules: [ rules: [
{ {
type: "rule", type: "rule",
name: "start", name: "start",
displayName: displayName, expression: expression
expression: expression
} }
], ],
startRule: "start" startRule: "start"
@ -159,9 +157,9 @@ describe("PEG.js grammar parser", function() {
/* Canonical grammar is "a = \"abcd\"; b = \"efgh\"; c = \"ijkl\";". */ /* Canonical grammar is "a = \"abcd\"; b = \"efgh\"; c = \"ijkl\";". */
it("parses grammar", function() { it("parses grammar", function() {
var ruleA = { type: "rule", name: "a", displayName: null, expression: literalAbcd }, var ruleA = { type: "rule", name: "a", expression: literalAbcd },
ruleB = { type: "rule", name: "b", displayName: null, expression: literalEfgh }, ruleB = { type: "rule", name: "b", expression: literalEfgh },
ruleC = { type: "rule", name: "c", displayName: null, expression: literalIjkl }; ruleC = { type: "rule", name: "c", expression: literalIjkl };
expect('a = "abcd"').toParseAs({ expect('a = "abcd"').toParseAs({
type: "grammar", type: "grammar",
@ -200,7 +198,11 @@ describe("PEG.js grammar parser", function() {
oneRuleGrammar(choiceOfLiterals) oneRuleGrammar(choiceOfLiterals)
); );
expect('start "start rule" = "abcd" / "efgh" / "ijkl"').toParseAs( expect('start "start rule" = "abcd" / "efgh" / "ijkl"').toParseAs(
oneRuleGrammar(choiceOfLiterals, null, "start rule") oneRuleGrammar({
type: "named",
name: "start rule",
expression: choiceOfLiterals
})
); );
expect('start = "abcd" / "efgh" / "ijkl";').toParseAs( expect('start = "abcd" / "efgh" / "ijkl";').toParseAs(
oneRuleGrammar(choiceOfLiterals) oneRuleGrammar(choiceOfLiterals)
@ -369,7 +371,11 @@ describe("PEG.js grammar parser", function() {
/* Canonical string is "\"abcd\"". */ /* Canonical string is "\"abcd\"". */
it("parses string", function() { it("parses string", function() {
var grammar = oneRuleGrammar(literalAbcd, null, "abcd"); var grammar = oneRuleGrammar({
type: "named",
name: "abcd",
expression: literalAbcd
});
expect('start "abcd" = "abcd"' ).toParseAs(grammar); expect('start "abcd" = "abcd"' ).toParseAs(grammar);
expect('start \'abcd\' = "abcd"').toParseAs(grammar); expect('start \'abcd\' = "abcd"').toParseAs(grammar);

@ -42,6 +42,10 @@ PEG.compiler.passes.computeParams = function(ast) {
}, },
rule: computeForScopedExpression, rule: computeForScopedExpression,
named:
function(node) {
compute(node.expression);
},
choice: choice:
function(node) { function(node) {

@ -64,6 +64,8 @@ PEG.compiler.passes.computeVarNames = function(ast) {
node.posVars = map(range(depth.pos), posVar); node.posVars = map(range(depth.pos), posVar);
}, },
named: computeFromExpression({ result: 0, pos: 0 }),
choice: choice:
function(node, index) { function(node, index) {
var depths = map(node.alternatives, function(alternative) { var depths = map(node.alternatives, function(alternative) {

@ -565,16 +565,7 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' var #{node.posVars.join(", ")};', ' var #{node.posVars.join(", ")};',
' #end', ' #end',
' ', ' ',
' #if node.displayName !== null',
' reportFailures++;',
' #end',
' #block emit(node.expression)', ' #block emit(node.expression)',
' #if node.displayName !== null',
' reportFailures--;',
' if (reportFailures === 0 && #{node.resultVar} === null) {',
' matchFailed(#{string(node.displayName)});',
' }',
' #end',
' #if options.cache', ' #if options.cache',
' ', ' ',
' cache[cacheKey] = {', ' cache[cacheKey] = {',
@ -585,6 +576,14 @@ PEG.compiler.passes.generateCode = function(ast, options) {
' return #{node.resultVar};', ' return #{node.resultVar};',
'}' '}'
], ],
named: [
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (reportFailures === 0 && #{node.resultVar} === null) {',
' matchFailed(#{string(node.name)});',
'}'
],
choice: [ choice: [
'#block emit(alternative)', '#block emit(alternative)',
'#block nextAlternativesCode' '#block nextAlternativesCode'
@ -815,6 +814,8 @@ PEG.compiler.passes.generateCode = function(ast, options) {
* variables. * variables.
*/ */
named: emitSimple("named"),
choice: function(node) { choice: function(node) {
var code, nextAlternativesCode; var code, nextAlternativesCode;

@ -24,6 +24,7 @@ PEG.compiler.passes.removeProxyRules = function(ast) {
var replace = buildNodeVisitor({ var replace = buildNodeVisitor({
grammar: replaceInSubnodes("rules"), grammar: replaceInSubnodes("rules"),
rule: replaceInExpression, rule: replaceInExpression,
named: replaceInExpression,
choice: replaceInSubnodes("alternatives"), choice: replaceInSubnodes("alternatives"),
sequence: replaceInSubnodes("elements"), sequence: replaceInSubnodes("elements"),
labeled: replaceInExpression, labeled: replaceInExpression,

@ -22,6 +22,7 @@ PEG.compiler.passes.reportLeftRecursion = function(ast) {
check(node.expression, appliedRules.concat(node.name)); check(node.expression, appliedRules.concat(node.name));
}, },
named: checkExpression,
choice: checkSubnodes("alternatives"), choice: checkSubnodes("alternatives"),
sequence: sequence:

@ -11,6 +11,7 @@ PEG.compiler.passes.reportMissingRules = function(ast) {
var check = buildNodeVisitor({ var check = buildNodeVisitor({
grammar: checkSubnodes("rules"), grammar: checkSubnodes("rules"),
rule: checkExpression, rule: checkExpression,
named: checkExpression,
choice: checkSubnodes("alternatives"), choice: checkSubnodes("alternatives"),
sequence: checkSubnodes("elements"), sequence: checkSubnodes("elements"),
labeled: checkExpression, labeled: checkExpression,

@ -277,8 +277,13 @@ PEG.parser = (function(){
return { return {
type: "rule", type: "rule",
name: name, name: name,
displayName: displayName !== "" ? displayName : null, expression: displayName !== ""
expression: expression ? {
type: "named",
name: displayName,
expression: expression
}
: expression
}; };
})(pos0, result0[0], result0[1], result0[3]); })(pos0, result0[0], result0[1], result0[3]);
} }

@ -21,8 +21,13 @@ rule
return { return {
type: "rule", type: "rule",
name: name, name: name,
displayName: displayName !== "" ? displayName : null, expression: displayName !== ""
expression: expression ? {
type: "named",
name: displayName,
expression: expression
}
: expression
}; };
} }

Loading…
Cancel
Save