PEG.js grammar: Disallow empty sequences

Empty sequences are useless and they only confused users. Let's disallow
them.
redux
David Majda 10 years ago committed by David Majda
parent 2005345976
commit df154daafb

@ -351,8 +351,6 @@ module.exports = function(ast) {
}, },
sequence: function(node, context) { sequence: function(node, context) {
var emptyArrayIndex;
function buildElementsCode(elements, context) { function buildElementsCode(elements, context) {
var processedCount, functionIndex; var processedCount, functionIndex;
@ -402,7 +400,6 @@ module.exports = function(ast) {
} }
} }
if (node.elements.length > 0) {
failedIndex = addConst('peg$FAILED'); failedIndex = addConst('peg$FAILED');
return buildSequence( return buildSequence(
@ -413,11 +410,6 @@ module.exports = function(ast) {
action: context.action action: context.action
}) })
); );
} else {
emptyArrayIndex = addConst('[]');
return [op.PUSH, emptyArrayIndex];
}
}, },
labeled: function(node, context) { labeled: function(node, context) {

@ -676,10 +676,14 @@ module.exports = (function() {
s0 = peg$currPos; s0 = peg$currPos;
s1 = []; s1 = [];
s2 = peg$parselabeled(); s2 = peg$parselabeled();
if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) {
s1.push(s2); s1.push(s2);
s2 = peg$parselabeled(); s2 = peg$parselabeled();
} }
} else {
s1 = peg$c0;
}
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
s2 = peg$parseaction(); s2 = peg$parseaction();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
@ -698,10 +702,14 @@ module.exports = (function() {
s0 = peg$currPos; s0 = peg$currPos;
s1 = []; s1 = [];
s2 = peg$parselabeled(); s2 = peg$parselabeled();
if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) {
s1.push(s2); s1.push(s2);
s2 = peg$parselabeled(); s2 = peg$parselabeled();
} }
} else {
s1 = peg$c0;
}
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$reportedPos = s0; peg$reportedPos = s0;
s1 = peg$c8(s1); s1 = peg$c8(s1);

@ -85,24 +85,25 @@ describe("compiler pass |generateBytecode|", function() {
describe("for action", function() { describe("for action", function() {
describe("without labels", function() { describe("without labels", function() {
var grammar = 'start = { code }'; var grammar = 'start = "a" { code }';
it("generates correct bytecode", function() { it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([ expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS 1, // PUSH_CURR_POS
0, 0, // <expression> 14, 0, 2, 2, 18, 0, 19, 1, // <expression>
11, 6, 0, // IF_NOT_ERROR 11, 6, 0, // IF_NOT_ERROR
20, 1, // * REPORT_SAVED_POS 20, 1, // * REPORT_SAVED_POS
22, 1, 1, 0, // CALL 22, 2, 1, 0, // CALL
5 // NIP 5 // NIP
])); ]));
}); });
it("defines correct constants", function() { it("defines correct constants", function() {
expect(pass).toChangeAST( expect(pass).toChangeAST(grammar, constsDetails([
grammar, '"a"',
constsDetails(['[]', 'function() { code }']) '{ type: "literal", value: "a", description: "\\"a\\"" }',
); 'function() { code }'
]));
}); });
}); });
@ -171,21 +172,6 @@ describe("compiler pass |generateBytecode|", function() {
}); });
describe("for sequence", function() { describe("for sequence", function() {
describe("empty", function() {
var grammar = 'start = ';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
0, 0 // PUSH
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(['[]']));
});
});
describe("non-empty", function() {
var grammar = 'start = "a" "b" "c"'; var grammar = 'start = "a" "b" "c"';
it("generates correct bytecode", function() { it("generates correct bytecode", function() {
@ -223,7 +209,6 @@ describe("compiler pass |generateBytecode|", function() {
])); ]));
}); });
}); });
});
describe("for labeled", function() { describe("for labeled", function() {
it("generates correct bytecode", function() { it("generates correct bytecode", function() {

@ -393,13 +393,7 @@ describe("generated parser", function() {
}); });
describe("sequence matching", function() { describe("sequence matching", function() {
it("matches empty sequence correctly", function() { it("matches correctly", function() {
var parser = PEG.buildParser('start = ', options);
expect(parser).toParse("", []);
});
it("matches non-empty sequence correctly", function() {
var parser = PEG.buildParser('start = "a" "b" "c"', options); var parser = PEG.buildParser('start = "a" "b" "c"', options);
expect(parser).toParse("abc", ["a", "b", "c"]); expect(parser).toParse("abc", ["a", "b", "c"]);
@ -845,9 +839,9 @@ describe("generated parser", function() {
describe("expectations reporting", function() { describe("expectations reporting", function() {
it("reports expectations correctly with no alternative", function() { it("reports expectations correctly with no alternative", function() {
var parser = PEG.buildParser('start = ', options); var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("a", { expect(parser).toFailToParse("ab", {
expected: [{ type: "end", description: "end of input" }] expected: [{ type: "end", description: "end of input" }]
}); });
}); });
@ -916,10 +910,10 @@ describe("generated parser", function() {
describe("message building", function() { describe("message building", function() {
it("builds message correctly with no alternative", function() { it("builds message correctly with no alternative", function() {
var parser = PEG.buildParser('start = ', options); var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("a", { expect(parser).toFailToParse("ab", {
message: 'Expected end of input but "a" found.' message: 'Expected end of input but "b" found.'
}); });
}); });

@ -8,7 +8,6 @@ describe("PEG.js grammar parser", function() {
labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd }, labeledAbcd = { type: "labeled", label: "a", expression: literalAbcd },
labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh }, labeledEfgh = { type: "labeled", label: "b", expression: literalEfgh },
labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl }, labeledIjkl = { type: "labeled", label: "c", expression: literalIjkl },
sequenceEmpty = { type: "sequence", elements: [] },
sequenceOfLiterals = { sequenceOfLiterals = {
type: "sequence", type: "sequence",
elements: [literalAbcd, literalEfgh, literalIjkl] elements: [literalAbcd, literalEfgh, literalIjkl]
@ -235,13 +234,6 @@ describe("PEG.js grammar parser", function() {
/* Canonical sequence is "\"abcd\" \"efgh\" \"ijkl\"". */ /* Canonical sequence is "\"abcd\" \"efgh\" \"ijkl\"". */
it("parses sequence", function() { it("parses sequence", function() {
expect('start = { code }').toParseAs(
oneRuleGrammar({
type: "action",
expression: sequenceEmpty,
code: " code "
})
);
expect('start = a:"abcd" { code }').toParseAs( expect('start = a:"abcd" { code }').toParseAs(
oneRuleGrammar({ type: "action", expression: labeledAbcd, code: " code " }) oneRuleGrammar({ type: "action", expression: labeledAbcd, code: " code " })
); );
@ -253,9 +245,6 @@ describe("PEG.js grammar parser", function() {
}) })
); );
expect('start = ').toParseAs(
oneRuleGrammar(sequenceEmpty)
);
expect('start = a:"abcd"').toParseAs( expect('start = a:"abcd"').toParseAs(
oneRuleGrammar(labeledAbcd) oneRuleGrammar(labeledAbcd)
); );

@ -54,7 +54,7 @@ choice
} }
sequence sequence
= elements:labeled* code:action { = elements:labeled+ code:action {
var expression = elements.length !== 1 var expression = elements.length !== 1
? { ? {
type: "sequence", type: "sequence",
@ -67,7 +67,7 @@ sequence
code: code code: code
}; };
} }
/ elements:labeled* { / elements:labeled+ {
return elements.length !== 1 return elements.length !== 1
? { ? {
type: "sequence", type: "sequence",

Loading…
Cancel
Save