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,22 +400,16 @@ module.exports = function(ast) {
} }
} }
if (node.elements.length > 0) { failedIndex = addConst('peg$FAILED');
failedIndex = addConst('peg$FAILED');
return buildSequence(
[op.PUSH_CURR_POS],
buildElementsCode(node.elements, {
sp: context.sp + 1,
env: context.env,
action: context.action
})
);
} else {
emptyArrayIndex = addConst('[]');
return [op.PUSH, emptyArrayIndex]; return buildSequence(
} [op.PUSH_CURR_POS],
buildElementsCode(node.elements, {
sp: context.sp + 1,
env: context.env,
action: context.action
})
);
}, },
labeled: function(node, context) { labeled: function(node, context) {

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

@ -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,57 +172,41 @@ describe("compiler pass |generateBytecode|", function() {
}); });
describe("for sequence", function() { describe("for sequence", function() {
describe("empty", function() { var grammar = 'start = "a" "b" "c"';
var grammar = 'start = ';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
0, 0 // PUSH
]));
});
it("defines correct constants", function() { it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, constsDetails(['[]'])); expect(pass).toChangeAST(grammar, bytecodeDetails([
}); 1, // PUSH_CURR_POS
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]>
11, 35, 4, // IF_NOT_ERROR
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]>
11, 19, 5, // IF_NOT_ERROR
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 3, 5, // IF_NOT_ERROR
7, 3, // * WRAP
5, // NIP
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
]));
}); });
describe("non-empty", function() { it("defines correct constants", function() {
var grammar = 'start = "a" "b" "c"'; expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
it("generates correct bytecode", function() { '"a"',
expect(pass).toChangeAST(grammar, bytecodeDetails([ '{ type: "literal", value: "a", description: "\\"a\\"" }',
1, // PUSH_CURR_POS '"b"',
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]> '{ type: "literal", value: "b", description: "\\"b\\"" }',
11, 35, 4, // IF_NOT_ERROR '"c"',
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]> '{ type: "literal", value: "c", description: "\\"c\\"" }'
11, 19, 5, // IF_NOT_ERROR ]));
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 3, 5, // IF_NOT_ERROR
7, 3, // * WRAP
5, // NIP
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'{ type: "literal", value: "c", description: "\\"c\\"" }'
]));
});
}); });
}); });

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