Error handling: Structured expectations

Before this commit, the |expected| property of an exception object
thrown when a generated parser encountered an error contained
expectations as strings. These strings were in a human-readable format
suitable for displaying in the UI but not suitable for machine
processing. For example, expected string literals included quotes and a
string "any character" was used when any character was expected.

This commit makes expectations structured objects. This makes the
machine processing easier, while still allowing to generate a
human-readable representation if needed.

Implements part of #198.

Speed impact
------------
Before:     1180.41 kB/s
After:      1165.31 kB/s
Difference: -1.28%

Size impact
-----------
Before:     863523 b
After:      950817 b
Difference: 10.10%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
redux
David Majda 11 years ago
parent 5312e124cd
commit 435bb8f2df

@ -286,7 +286,9 @@ module.exports = function(ast, options) {
},
named: function(node, context) {
var nameIndex = addConst(utils.quote(node.name));
var nameIndex = addConst(
'{ type: "other", description: ' + utils.quote(node.name) + ' }'
);
/*
* The code generated below is slightly suboptimal because |FAIL| pushes
@ -528,7 +530,13 @@ module.exports = function(ast, options) {
? utils.quote(node.value.toLowerCase())
: utils.quote(node.value)
);
expectedIndex = addConst(utils.quote(utils.quote(node.value)));
expectedIndex = addConst([
'{',
'type: "literal",',
'value: ' + utils.quote(node.value) + ',',
'description: ' + utils.quote(utils.quote(node.value)),
'}'
].join(' '));
/*
* For case-sensitive strings the value must match the beginning of the
@ -574,7 +582,13 @@ module.exports = function(ast, options) {
}
regexpIndex = addConst(regexp);
expectedIndex = addConst(utils.quote(node.rawText));
expectedIndex = addConst([
'{',
'type: "class",',
'value: ' + utils.quote(node.rawText) + ',',
'description: ' + utils.quote(node.rawText),
'}'
].join(' '));
return buildCondition(
[op.MATCH_REGEXP, regexpIndex],
@ -584,7 +598,7 @@ module.exports = function(ast, options) {
},
any: function(node) {
var expectedIndex = addConst(utils.quote("any character"));
var expectedIndex = addConst('{ type: "any", description: "any character" }');
return buildCondition(
[op.MATCH_ANY],

@ -703,7 +703,7 @@ module.exports = function(ast, options) {
' .replace(/[\\u1080-\\uFFFF]/g, function(ch) { return \'\\\\u\' + hex(ch); });',
' }',
'',
' var expectedDesc, foundDesc;',
' var expectedDescs, expectedDesc, foundDesc, i;',
'',
' switch (expected.length) {',
' case 0:',
@ -711,13 +711,19 @@ module.exports = function(ast, options) {
' break;',
'',
' case 1:',
' expectedDesc = expected[0];',
' expectedDesc = expected[0].description;',
' break;',
'',
' default:',
' expectedDesc = expected.slice(0, -1).join(", ")',
' expectedDescs = new Array(expected.length);',
'',
' for (i = 0; i < expected.length; i++) {',
' expectedDescs[i] = expected[i].description;',
' }',
'',
' expectedDesc = expectedDescs.slice(0, -1).join(", ")',
' + " or "',
' + expected[expected.length - 1];',
' + expectedDescs[expected.length - 1];',
' }',
'',
' foundDesc = found ? "\\"" + stringEscape(found) + "\\"" : "end of input";',
@ -882,8 +888,21 @@ module.exports = function(ast, options) {
' function peg$cleanupExpected(expected) {',
' var i = 0;',
'',
' expected.sort();',
' expected.sort(function(a, b) {',
' if (a.description < b.description) {',
' return -1;',
' } else if (a.description > b.description) {',
' return 1;',
' } else {',
' return 0;',
' }',
' });',
'',
/*
* This works because the bytecode generator guarantees that every
* expectation object exists only once, so it's enough to use |===| instead
* of deeper structural comparison.
*/
' while (i < expected.length) {',
' if (expected[i - 1] === expected[i]) {',
' expected.splice(i, 1);',

@ -30,7 +30,7 @@ module.exports = (function() {
.replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); });
}
var expectedDesc, foundDesc;
var expectedDescs, expectedDesc, foundDesc, i;
switch (expected.length) {
case 0:
@ -38,13 +38,19 @@ module.exports = (function() {
break;
case 1:
expectedDesc = expected[0];
expectedDesc = expected[0].description;
break;
default:
expectedDesc = expected.slice(0, -1).join(", ")
expectedDescs = new Array(expected.length);
for (i = 0; i < expected.length; i++) {
expectedDescs[i] = expected[i].description;
}
expectedDesc = expectedDescs.slice(0, -1).join(", ")
+ " or "
+ expected[expected.length - 1];
+ expectedDescs[expected.length - 1];
}
foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
@ -197,60 +203,60 @@ module.exports = (function() {
},
peg$c19 = function() { return { type: "any" }; },
peg$c20 = function(expression) { return expression; },
peg$c21 = "action",
peg$c21 = { type: "other", description: "action" },
peg$c22 = function(braced) { return braced.substr(1, braced.length - 2); },
peg$c23 = "{",
peg$c24 = "\"{\"",
peg$c24 = { type: "literal", value: "{", description: "\"{\"" },
peg$c25 = "}",
peg$c26 = "\"}\"",
peg$c26 = { type: "literal", value: "}", description: "\"}\"" },
peg$c27 = /^[^{}]/,
peg$c28 = "[^{}]",
peg$c28 = { type: "class", value: "[^{}]", description: "[^{}]" },
peg$c29 = "=",
peg$c30 = "\"=\"",
peg$c30 = { type: "literal", value: "=", description: "\"=\"" },
peg$c31 = function() { return "="; },
peg$c32 = ":",
peg$c33 = "\":\"",
peg$c33 = { type: "literal", value: ":", description: "\":\"" },
peg$c34 = function() { return ":"; },
peg$c35 = ";",
peg$c36 = "\";\"",
peg$c36 = { type: "literal", value: ";", description: "\";\"" },
peg$c37 = function() { return ";"; },
peg$c38 = "/",
peg$c39 = "\"/\"",
peg$c39 = { type: "literal", value: "/", description: "\"/\"" },
peg$c40 = function() { return "/"; },
peg$c41 = "&",
peg$c42 = "\"&\"",
peg$c42 = { type: "literal", value: "&", description: "\"&\"" },
peg$c43 = function() { return "&"; },
peg$c44 = "!",
peg$c45 = "\"!\"",
peg$c45 = { type: "literal", value: "!", description: "\"!\"" },
peg$c46 = function() { return "!"; },
peg$c47 = "$",
peg$c48 = "\"$\"",
peg$c48 = { type: "literal", value: "$", description: "\"$\"" },
peg$c49 = function() { return "$"; },
peg$c50 = "?",
peg$c51 = "\"?\"",
peg$c51 = { type: "literal", value: "?", description: "\"?\"" },
peg$c52 = function() { return "?"; },
peg$c53 = "*",
peg$c54 = "\"*\"",
peg$c54 = { type: "literal", value: "*", description: "\"*\"" },
peg$c55 = function() { return "*"; },
peg$c56 = "+",
peg$c57 = "\"+\"",
peg$c57 = { type: "literal", value: "+", description: "\"+\"" },
peg$c58 = function() { return "+"; },
peg$c59 = "(",
peg$c60 = "\"(\"",
peg$c60 = { type: "literal", value: "(", description: "\"(\"" },
peg$c61 = function() { return "("; },
peg$c62 = ")",
peg$c63 = "\")\"",
peg$c63 = { type: "literal", value: ")", description: "\")\"" },
peg$c64 = function() { return ")"; },
peg$c65 = ".",
peg$c66 = "\".\"",
peg$c66 = { type: "literal", value: ".", description: "\".\"" },
peg$c67 = function() { return "."; },
peg$c68 = "identifier",
peg$c68 = { type: "other", description: "identifier" },
peg$c69 = "_",
peg$c70 = "\"_\"",
peg$c70 = { type: "literal", value: "_", description: "\"_\"" },
peg$c71 = function(chars) { return chars; },
peg$c72 = "literal",
peg$c72 = { type: "other", description: "literal" },
peg$c73 = "i",
peg$c74 = "\"i\"",
peg$c74 = { type: "literal", value: "i", description: "\"i\"" },
peg$c75 = function(value, flags) {
return {
type: "literal",
@ -258,24 +264,24 @@ module.exports = (function() {
ignoreCase: flags === "i"
};
},
peg$c76 = "string",
peg$c76 = { type: "other", description: "string" },
peg$c77 = function(string) { return string; },
peg$c78 = "\"",
peg$c79 = "\"\\\"\"",
peg$c79 = { type: "literal", value: "\"", description: "\"\\\"\"" },
peg$c80 = function(chars) { return chars.join(""); },
peg$c81 = "\\",
peg$c82 = "\"\\\\\"",
peg$c83 = "any character",
peg$c82 = { type: "literal", value: "\\", description: "\"\\\\\"" },
peg$c83 = { type: "any", description: "any character" },
peg$c84 = function(char_) { return char_; },
peg$c85 = "'",
peg$c86 = "\"'\"",
peg$c87 = "character class",
peg$c86 = { type: "literal", value: "'", description: "\"'\"" },
peg$c87 = { type: "other", description: "character class" },
peg$c88 = "[",
peg$c89 = "\"[\"",
peg$c89 = { type: "literal", value: "[", description: "\"[\"" },
peg$c90 = "^",
peg$c91 = "\"^\"",
peg$c91 = { type: "literal", value: "^", description: "\"^\"" },
peg$c92 = "]",
peg$c93 = "\"]\"",
peg$c93 = { type: "literal", value: "]", description: "\"]\"" },
peg$c94 = function(inverted, parts, flags) {
var partsConverted = utils.map(parts, function(part) { return part.data; });
var rawText = "["
@ -294,7 +300,7 @@ module.exports = (function() {
};
},
peg$c95 = "-",
peg$c96 = "\"-\"",
peg$c96 = { type: "literal", value: "-", description: "\"-\"" },
peg$c97 = function(begin, end) {
if (begin.data.charCodeAt(0) > end.data.charCodeAt(0)) {
throw new this.SyntaxError(
@ -316,9 +322,9 @@ module.exports = (function() {
};
},
peg$c99 = "x",
peg$c100 = "\"x\"",
peg$c100 = { type: "literal", value: "x", description: "\"x\"" },
peg$c101 = "u",
peg$c102 = "\"u\"",
peg$c102 = { type: "literal", value: "u", description: "\"u\"" },
peg$c103 = function(char_) {
return char_
.replace("b", "\b")
@ -329,47 +335,47 @@ module.exports = (function() {
.replace("v", "\x0B"); // IE does not recognize "\v".
},
peg$c104 = "\\0",
peg$c105 = "\"\\\\0\"",
peg$c105 = { type: "literal", value: "\\0", description: "\"\\\\0\"" },
peg$c106 = function() { return "\x00"; },
peg$c107 = "\\x",
peg$c108 = "\"\\\\x\"",
peg$c108 = { type: "literal", value: "\\x", description: "\"\\\\x\"" },
peg$c109 = function(digits) {
return String.fromCharCode(parseInt(digits, 16));
},
peg$c110 = "\\u",
peg$c111 = "\"\\\\u\"",
peg$c111 = { type: "literal", value: "\\u", description: "\"\\\\u\"" },
peg$c112 = function(eol) { return eol; },
peg$c113 = /^[0-9]/,
peg$c114 = "[0-9]",
peg$c114 = { type: "class", value: "[0-9]", description: "[0-9]" },
peg$c115 = /^[0-9a-fA-F]/,
peg$c116 = "[0-9a-fA-F]",
peg$c116 = { type: "class", value: "[0-9a-fA-F]", description: "[0-9a-fA-F]" },
peg$c117 = /^[a-z]/,
peg$c118 = "[a-z]",
peg$c118 = { type: "class", value: "[a-z]", description: "[a-z]" },
peg$c119 = /^[A-Z]/,
peg$c120 = "[A-Z]",
peg$c121 = "comment",
peg$c120 = { type: "class", value: "[A-Z]", description: "[A-Z]" },
peg$c121 = { type: "other", description: "comment" },
peg$c122 = "//",
peg$c123 = "\"//\"",
peg$c123 = { type: "literal", value: "//", description: "\"//\"" },
peg$c124 = "/*",
peg$c125 = "\"/*\"",
peg$c125 = { type: "literal", value: "/*", description: "\"/*\"" },
peg$c126 = "*/",
peg$c127 = "\"*/\"",
peg$c128 = "end of line",
peg$c127 = { type: "literal", value: "*/", description: "\"*/\"" },
peg$c128 = { type: "other", description: "end of line" },
peg$c129 = "\n",
peg$c130 = "\"\\n\"",
peg$c130 = { type: "literal", value: "\n", description: "\"\\n\"" },
peg$c131 = "\r\n",
peg$c132 = "\"\\r\\n\"",
peg$c132 = { type: "literal", value: "\r\n", description: "\"\\r\\n\"" },
peg$c133 = "\r",
peg$c134 = "\"\\r\"",
peg$c134 = { type: "literal", value: "\r", description: "\"\\r\"" },
peg$c135 = "\u2028",
peg$c136 = "\"\\u2028\"",
peg$c136 = { type: "literal", value: "\u2028", description: "\"\\u2028\"" },
peg$c137 = "\u2029",
peg$c138 = "\"\\u2029\"",
peg$c138 = { type: "literal", value: "\u2029", description: "\"\\u2029\"" },
peg$c139 = /^[\n\r\u2028\u2029]/,
peg$c140 = "[\\n\\r\\u2028\\u2029]",
peg$c141 = "whitespace",
peg$c140 = { type: "class", value: "[\\n\\r\\u2028\\u2029]", description: "[\\n\\r\\u2028\\u2029]" },
peg$c141 = { type: "other", description: "whitespace" },
peg$c142 = /^[ \t\x0B\f\xA0\uFEFF\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]/,
peg$c143 = "[ \\t\\x0B\\f\\xA0\\uFEFF\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]",
peg$c143 = { type: "class", value: "[ \\t\\x0B\\f\\xA0\\uFEFF\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]", description: "[ \\t\\x0B\\f\\xA0\\uFEFF\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]" },
peg$currPos = 0,
peg$reportedPos = 0,
@ -452,7 +458,15 @@ module.exports = (function() {
function peg$cleanupExpected(expected) {
var i = 0;
expected.sort();
expected.sort(function(a, b) {
if (a.description < b.description) {
return -1;
} else if (a.description > b.description) {
return 1;
} else {
return 0;
}
});
while (i < expected.length) {
if (expected[i - 1] === expected[i]) {

@ -31,11 +31,11 @@ describe("compiler pass |generateBytecode|", function() {
'c = "c"'
].join("\n"), constsDetails([
'"a"',
'"\\"a\\""',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'"\\"b\\""',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'"\\"c\\""'
'{ type: "literal", value: "c", description: "\\"c\\"" }'
]));
});
});
@ -61,10 +61,11 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['"start"', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'{ type: "other", description: "start" }',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -124,10 +125,11 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['"a"', '"\\"a\\""', 'function(a) { code }'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'function(a) { code }'
]));
});
});
@ -163,11 +165,11 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'null',
'"a"',
'"\\"a\\""',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'"\\"b\\""',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'"\\"c\\""',
'{ type: "literal", value: "c", description: "\\"c\\"" }',
'function(a, b, c) { code }'
]));
});
@ -216,18 +218,15 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails([
'null',
'"a"',
'"\\"a\\""',
'"b"',
'"\\"b\\""',
'"c"',
'"\\"c\\""'
])
);
expect(pass).toChangeAST(grammar, constsDetails([
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'{ type: "literal", value: "c", description: "\\"c\\"" }'
]));
});
});
});
@ -272,10 +271,12 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['""', 'null', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'""',
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -299,10 +300,12 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['""', 'null', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'""',
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -371,11 +374,11 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'null',
'"a"',
'"\\"a\\""',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'"\\"b\\""',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'"\\"c\\""',
'{ type: "literal", value: "c", description: "\\"c\\"" }',
'function(a, b, c) { code }',
'""'
]));
@ -448,11 +451,11 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'null',
'"a"',
'"\\"a\\""',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'"\\"b\\""',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'"\\"c\\""',
'{ type: "literal", value: "c", description: "\\"c\\"" }',
'function(a, b, c) { code }',
'""'
]));
@ -473,10 +476,11 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['""', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'""',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -495,10 +499,11 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['[]', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'[]',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -521,10 +526,12 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['[]', 'null', '"a"', '"\\"a\\""'])
);
expect(pass).toChangeAST(grammar, constsDetails([
'[]',
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -571,7 +578,10 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(['"a"', '"\\"a\\""']));
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
});
});
@ -587,7 +597,10 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(['"a"', '"\\"A\\""']));
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'{ type: "literal", value: "A", description: "\\"A\\"" }'
]));
});
});
});
@ -603,55 +616,55 @@ describe("compiler pass |generateBytecode|", function() {
describe("non-empty non-inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = [a]',
constsDetails(['/^[a]/', '"[a]"'])
);
expect(pass).toChangeAST('start = [a]', constsDetails([
'/^[a]/',
'{ type: "class", value: "[a]", description: "[a]" }'
]));
});
});
describe("non-empty inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = [^a]',
constsDetails(['/^[^a]/', '"[^a]"'])
);
expect(pass).toChangeAST('start = [^a]', constsDetails([
'/^[^a]/',
'{ type: "class", value: "[^a]", description: "[^a]" }'
]));
});
});
describe("non-empty non-inverted case-insensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = [a]i',
constsDetails(['/^[a]/i', '"[a]i"'])
);
expect(pass).toChangeAST('start = [a]i', constsDetails([
'/^[a]/i',
'{ type: "class", value: "[a]i", description: "[a]i" }'
]));
});
});
describe("non-empty complex", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = [ab-def-hij-l]',
constsDetails(['/^[ab-def-hij-l]/', '"[ab-def-hij-l]"'])
);
expect(pass).toChangeAST('start = [ab-def-hij-l]', constsDetails([
'/^[ab-def-hij-l]/',
'{ type: "class", value: "[ab-def-hij-l]", description: "[ab-def-hij-l]" }'
]));
});
});
describe("empty non-inverted", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = []',
constsDetails(['/^(?!)/', '"[]"'])
);
expect(pass).toChangeAST('start = []', constsDetails([
'/^(?!)/',
'{ type: "class", value: "[]", description: "[]" }'
]));
});
});
describe("empty inverted", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST(
'start = [^]',
constsDetails(['/^[\\S\\s]/', '"[^]"'])
);
expect(pass).toChangeAST('start = [^]', constsDetails([
'/^[\\S\\s]/',
'{ type: "class", value: "[^]", description: "[^]" }'
]));
});
});
});
@ -668,7 +681,10 @@ describe("compiler pass |generateBytecode|", function() {
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(['"any character"']));
expect(pass).toChangeAST(
grammar,
constsDetails(['{ type: "any", description: "any character" }'])
);
});
});
});

@ -248,7 +248,9 @@ describe("generated parser", function() {
});
it("overwrites expected string on failure", function() {
expect(parser).toFailToParse("b", { expected: ["start"] });
expect(parser).toFailToParse("b", {
expected: [{ type: "other", description: "start" }]
});
});
});
@ -716,10 +718,12 @@ describe("generated parser", function() {
expect(parser).toParse("ab", ["a", "b"]);
});
it("sets expected string correctly on failure", function() {
it("sets expectation correctly on failure", function() {
var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("b", { expected: ['"a"'] });
expect(parser).toFailToParse("b", {
expected: [{ type: "literal", value: "a", description: '"a"' }]
});
});
});
@ -775,10 +779,12 @@ describe("generated parser", function() {
expect(parser).toParse("ab", ["a", "b"]);
});
it("sets expected string correctly on failure", function() {
it("sets expectation correctly on failure", function() {
var parser = PEG.buildParser('start = [a]', options);
expect(parser).toFailToParse("b", { expected: ["[a]"] });
expect(parser).toFailToParse("b", {
expected: [{ type: "class", value: "[a]", description: "[a]" }]
});
});
});
@ -795,10 +801,12 @@ describe("generated parser", function() {
expect(parser).toParse("ab", ["a", "b"]);
});
it("sets expected string correctly on failure", function() {
it("sets expectation correctly on failure", function() {
var parser = PEG.buildParser('start = .', options);
expect(parser).toFailToParse("", { expected: ['any character'] });
expect(parser).toFailToParse("", {
expected: [{ type: "any", description: "any character" }]
});
});
});
@ -807,32 +815,41 @@ describe("generated parser", function() {
it("reports only the rightmost error", function() {
var parser = PEG.buildParser('start = "a" "b" / "a" "c" "d"', options);
expect(parser).toFailToParse("ace", { offset: 2, expected: ['"d"'] });
expect(parser).toFailToParse("ace", {
offset: 2,
expected: [{ type: "literal", value: "d", description: '"d"' }]
});
});
});
describe("expected strings reporting", function() {
it("reports expected strings correctly with no alternative", function() {
describe("expectations reporting", function() {
it("reports expectations correctly with no alternative", function() {
var parser = PEG.buildParser('start = ', options);
expect(parser).toFailToParse("a", { expected: [] });
});
it("reports expected strings correctly with one alternative", function() {
it("reports expectations correctly with one alternative", function() {
var parser = PEG.buildParser('start = "a"', options);
expect(parser).toFailToParse("b", { expected: ['"a"'] });
expect(parser).toFailToParse("b", {
expected: [{ type: "literal", value: "a", description: '"a"' }]
});
});
it("reports expected strings correctly with multiple alternatives", function() {
it("reports expectations correctly with multiple alternatives", function() {
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
expect(parser).toFailToParse("d", {
expected: ['"a"', '"b"', '"c"']
expected: [
{ type: "literal", value: "a", description: '"a"' },
{ type: "literal", value: "b", description: '"b"' },
{ type: "literal", value: "c", description: '"c"' }
]
});
});
it("removes duplicates from expected strings", function() {
it("removes duplicates from expectations", function() {
/*
* There was a bug in the code that manifested only with three
* duplicates. This is why the following test uses three choices
@ -842,14 +859,20 @@ describe("generated parser", function() {
*/
var parser = PEG.buildParser('start = "a" / "a" / "a"', options);
expect(parser).toFailToParse("b", { expected: ['"a"'] });
expect(parser).toFailToParse("b", {
expected: [{ type: "literal", value: "a", description: '"a"' }]
});
});
it("sorts expected strings", function() {
it("sorts expectations", function() {
var parser = PEG.buildParser('start = "c" / "b" / "a"', options);
expect(parser).toFailToParse("d", {
expected: ['"a"', '"b"', '"c"']
expected: [
{ type: "literal", value: "a", description: '"a"' },
{ type: "literal", value: "b", description: '"b"' },
{ type: "literal", value: "c", description: '"c"' }
]
});
});
});

Loading…
Cancel
Save