diff --git a/lib/parser.js b/lib/parser.js index 5945e13c..3d6c3a79 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -455,7 +455,7 @@ function peg$parse(input, options) { var peg$f13 = function(expression, boundaries) { let min = boundaries[0]; let max = boundaries[1]; - if (max.value === 0) { + if (max.type === "constant" && max.value === 0) { error("The maximum count of repetitions of the rule must be > 0", max.location); } @@ -475,7 +475,8 @@ function peg$parse(input, options) { }; var peg$f15 = function(exact) { return [null, exact]; }; var peg$f16 = function(value) { return { type: "constant", value, location: location() }; }; - var peg$f17 = function(expression) { + var peg$f17 = function(value) { return { type: "variable", value: value[0], location: location() }; }; + var peg$f18 = function(expression) { // The purpose of the "group" AST node is just to isolate label scope. We // don't need to put it around nodes that can't contain any labels or // nodes that already isolate label scope themselves. This leaves us with @@ -484,10 +485,10 @@ function peg$parse(input, options) { ? { type: "group", expression, location: location() } : expression; }; - var peg$f18 = function(name) { + var peg$f19 = function(name) { return { type: "rule_ref", name: name[0], location: location() }; }; - var peg$f19 = function(operator, code) { + var peg$f20 = function(operator, code) { return { type: OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], code: code[0], @@ -495,10 +496,10 @@ function peg$parse(input, options) { location: location() }; }; - var peg$f20 = function(head, tail) { + var peg$f21 = function(head, tail) { return [head + tail.join(""), location()]; }; - var peg$f21 = function(value, ignoreCase) { + var peg$f22 = function(value, ignoreCase) { return { type: "literal", value, @@ -506,9 +507,9 @@ function peg$parse(input, options) { location: location() }; }; - var peg$f22 = function(chars) { return chars.join(""); }; var peg$f23 = function(chars) { return chars.join(""); }; - var peg$f24 = function(inverted, parts, ignoreCase) { + var peg$f24 = function(chars) { return chars.join(""); }; + var peg$f25 = function(inverted, parts, ignoreCase) { return { type: "class", parts: parts.filter(part => part !== ""), @@ -517,7 +518,7 @@ function peg$parse(input, options) { location: location() }; }; - var peg$f25 = function(begin, end) { + var peg$f26 = function(begin, end) { if (begin.charCodeAt(0) > end.charCodeAt(0)) { error( "Invalid character range: " + text() + "." @@ -526,23 +527,23 @@ function peg$parse(input, options) { return [begin, end]; }; - var peg$f26 = function() { return ""; }; - var peg$f27 = function() { return "\0"; }; - var peg$f28 = function() { return "\b"; }; - var peg$f29 = function() { return "\f"; }; - var peg$f30 = function() { return "\n"; }; - var peg$f31 = function() { return "\r"; }; - var peg$f32 = function() { return "\t"; }; - var peg$f33 = function() { return "\v"; }; - var peg$f34 = function(digits) { + var peg$f27 = function() { return ""; }; + var peg$f28 = function() { return "\0"; }; + var peg$f29 = function() { return "\b"; }; + var peg$f30 = function() { return "\f"; }; + var peg$f31 = function() { return "\n"; }; + var peg$f32 = function() { return "\r"; }; + var peg$f33 = function() { return "\t"; }; + var peg$f34 = function() { return "\v"; }; + var peg$f35 = function(digits) { return String.fromCharCode(parseInt(digits, 16)); }; - var peg$f35 = function(digits) { + var peg$f36 = function(digits) { return String.fromCharCode(parseInt(digits, 16)); }; - var peg$f36 = function() { return { type: "any", location: location() }; }; - var peg$f37 = function(code) { return [code, location()]; }; - var peg$f38 = function(digits) { return parseInt(digits); }; + var peg$f37 = function() { return { type: "any", location: location() }; }; + var peg$f38 = function(code) { return [code, location()]; }; + var peg$f39 = function(digits) { return parseInt(digits); }; var peg$currPos = 0; var peg$savedPos = 0; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -1331,6 +1332,15 @@ function peg$parse(input, options) { s1 = peg$f16(s1); } s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseIdentifierName(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f17(s1); + } + s0 = s1; + } return s0; } @@ -1370,7 +1380,7 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f17(s3); + s0 = peg$f18(s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1438,7 +1448,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f18(s1); + s0 = peg$f19(s1); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1461,7 +1471,7 @@ function peg$parse(input, options) { s3 = peg$parseCodeBlock(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f19(s1, s3); + s0 = peg$f20(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1956,7 +1966,7 @@ function peg$parse(input, options) { s3 = peg$parseIdentifierPart(); } peg$savedPos = s0; - s0 = peg$f20(s1, s2); + s0 = peg$f21(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2105,7 +2115,7 @@ function peg$parse(input, options) { s2 = null; } peg$savedPos = s0; - s0 = peg$f21(s1, s2); + s0 = peg$f22(s1, s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2147,7 +2157,7 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f22(s2); + s0 = peg$f23(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2181,7 +2191,7 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f23(s2); + s0 = peg$f24(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2414,7 +2424,7 @@ function peg$parse(input, options) { s5 = null; } peg$savedPos = s0; - s0 = peg$f24(s2, s3, s5); + s0 = peg$f25(s2, s3, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2449,7 +2459,7 @@ function peg$parse(input, options) { s3 = peg$parseClassCharacter(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f25(s1, s3); + s0 = peg$f26(s1, s3); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2561,7 +2571,7 @@ function peg$parse(input, options) { s2 = peg$parseLineTerminatorSequence(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f26(); + s0 = peg$f27(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2600,7 +2610,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f27(); + s0 = peg$f28(); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2668,7 +2678,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f28(); + s1 = peg$f29(); } s0 = s1; if (s0 === peg$FAILED) { @@ -2682,7 +2692,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f29(); + s1 = peg$f30(); } s0 = s1; if (s0 === peg$FAILED) { @@ -2696,7 +2706,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f30(); + s1 = peg$f31(); } s0 = s1; if (s0 === peg$FAILED) { @@ -2710,7 +2720,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f31(); + s1 = peg$f32(); } s0 = s1; if (s0 === peg$FAILED) { @@ -2724,7 +2734,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f32(); + s1 = peg$f33(); } s0 = s1; if (s0 === peg$FAILED) { @@ -2738,7 +2748,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f33(); + s1 = peg$f34(); } s0 = s1; } @@ -2857,7 +2867,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f34(s2); + s0 = peg$f35(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2917,7 +2927,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f35(s2); + s0 = peg$f36(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -2971,7 +2981,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f36(); + s1 = peg$f37(); } s0 = s1; @@ -3024,7 +3034,7 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parseCode(); peg$savedPos = s0; - s1 = peg$f37(s1); + s1 = peg$f38(s1); s0 = s1; return s0; @@ -3261,7 +3271,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f38(s1); + s1 = peg$f39(s1); } s0 = s1; diff --git a/test/behavior/generated-parser-behavior.spec.js b/test/behavior/generated-parser-behavior.spec.js index 3e73e946..350bc031 100644 --- a/test/behavior/generated-parser-behavior.spec.js +++ b/test/behavior/generated-parser-behavior.spec.js @@ -6,17 +6,17 @@ const expect = chai.expect; const { stub } = require("../utils.js"); describe("generated parser behavior", () => { - function varyOptimizationOptions(block) { - function clone(object) { - const result = {}; + function clone(object) { + const result = {}; - Object.keys(object).forEach(key => { - result[key] = object[key]; - }); + Object.keys(object).forEach(key => { + result[key] = object[key]; + }); - return result; - } + return result; + } + function varyOptimizationOptions(block) { const optionsVariants = [ { cache: false, trace: false }, { cache: false, trace: true }, @@ -978,6 +978,42 @@ describe("generated parser behavior", () => { }); describe("repeated", () => { + function buildParser(boundary) { + const start = []; + for (let i = 1; i <= 4; ++i) { + for (let j = 1; j <= 4; ++j) { + start[start.length] = "start" + String(i) + j; + } + } + const opt = clone(options); + opt.allowedStartRules = start; + + return peg.generate([ + "start11 = val:n1 x|" + boundary + "| $.*", + "start12 = val:n1 data:x|" + boundary + "| $.*", + "start13 = val:n1 x|" + boundary + "| rest:$.*", + "start14 = val:n1 data:x|" + boundary + "| rest:$.*", + + "start21 = . val:n1 x|" + boundary + "| $.*", + "start22 = . val:n1 data:x|" + boundary + "| $.*", + "start23 = . val:n1 x|" + boundary + "| rest:$.*", + "start24 = . val:n1 data:x|" + boundary + "| rest:$.*", + + "start31 = a:. val:n1 x|" + boundary + "| $.*", + "start32 = a:. val:n1 data:x|" + boundary + "| $.*", + "start33 = a:. val:n1 x|" + boundary + "| rest:$.*", + "start34 = a:. val:n1 data:x|" + boundary + "| rest:$.*", + + "start41 = a:. b:. val:n1 x|" + boundary + "| $.*", + "start42 = a:. b:. val:n1 data:x|" + boundary + "| $.*", + "start43 = a:. b:. val:n1 x|" + boundary + "| rest:$.*", + "start44 = a:. b:. val:n1 data:x|" + boundary + "| rest:$.*", + + "n1 = n:$[0-9] { return parseInt(n, 10); }", + "x = '='", + ].join(";\n"), opt); + } + describe("without delimiter", () => { describe("with constant boundaries", () => { it("| .. | matches correctly", () => { @@ -1077,6 +1113,272 @@ describe("generated parser behavior", () => { expect(parser).to.parse("aa", 42); }); }); + + describe("with variable boundaries", () => { + it("|min.. | matches correctly", () => { + const parser = buildParser("val.."); + + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start1" + i }; + expect(parser).to.failToParse("", undefined, opt); + expect(parser).to.parse("0", [0, [], ""], opt); + expect(parser).to.parse("0==", [0, ["=", "="], ""], opt); + expect(parser).to.failToParse("1", undefined, opt); + expect(parser).to.failToParse("2=", undefined, opt); + expect(parser).to.parse("2==", [2, ["=", "="], ""], opt); + expect(parser).to.parse("2=====", [2, ["=", "=", "=", "=", "="], ""], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start2" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, ["=", "="], ""], opt); + expect(parser).to.failToParse("-1", undefined, opt); + expect(parser).to.failToParse("-2=", undefined, opt); + expect(parser).to.parse("-2==", ["-", 2, ["=", "="], ""], opt); + expect(parser).to.parse("-2=====", ["-", 2, ["=", "=", "=", "=", "="], ""], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start3" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, ["=", "="], ""], opt); + expect(parser).to.failToParse("-1", undefined, opt); + expect(parser).to.failToParse("-2=", undefined, opt); + expect(parser).to.parse("-2==", ["-", 2, ["=", "="], ""], opt); + expect(parser).to.parse("-2=====", ["-", 2, ["=", "=", "=", "=", "="], ""], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start4" + i }; + expect(parser).to.failToParse("--", undefined, opt); + expect(parser).to.parse("--0", ["-", "-", 0, [], ""], opt); + expect(parser).to.parse("--0==", ["-", "-", 0, ["=", "="], ""], opt); + expect(parser).to.failToParse("--1", undefined, opt); + expect(parser).to.failToParse("--2=", undefined, opt); + expect(parser).to.parse("--2==", ["-", "-", 2, ["=", "="], ""], opt); + expect(parser).to.parse("--2=====", ["-", "-", 2, ["=", "=", "=", "=", "="], ""], opt); + } + }); + + it("| ..max| matches correctly", () => { + const parser = buildParser("..val"); + + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start1" + i }; + expect(parser).to.failToParse("", undefined, opt); + expect(parser).to.parse("0", [0, [], ""], opt); + expect(parser).to.parse("0==", [0, [], "=="], opt); + expect(parser).to.parse("3", [3, [], ""], opt); + expect(parser).to.parse("3=", [3, ["="], ""], opt); + expect(parser).to.parse("3===", [3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("3=====", [3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start2" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.parse("-3", ["-", 3, [], ""], opt); + expect(parser).to.parse("-3=", ["-", 3, ["="], ""], opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start3" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.parse("-3", ["-", 3, [], ""], opt); + expect(parser).to.parse("-3=", ["-", 3, ["="], ""], opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start4" + i }; + expect(parser).to.failToParse("--", undefined, opt); + expect(parser).to.parse("--0", ["-", "-", 0, [], ""], opt); + expect(parser).to.parse("--0==", ["-", "-", 0, [], "=="], opt); + expect(parser).to.parse("--3", ["-", "-", 3, [], ""], opt); + expect(parser).to.parse("--3=", ["-", "-", 3, ["="], ""], opt); + expect(parser).to.parse("--3===", ["-", "-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("--3=====", ["-", "-", 3, ["=", "=", "="], "=="], opt); + } + }); + + it("|min..max| matches correctly", () => { + function buildRangeParser() { + const start = []; + for (let i = 1; i <= 4; ++i) { + for (let j = 1; j <= 4; ++j) { + start[start.length] = "start" + String(i) + j; + } + } + const opt = clone(options); + opt.allowedStartRules = start; + + return peg.generate([ + "start11 = min:n1 max:n1 .|min..max| $.*", + "start12 = min:n1 max:n1 data:.|min..max| $.*", + "start13 = min:n1 max:n1 .|min..max| rest:$.*", + "start14 = min:n1 max:n1 data:.|min..max| rest:$.*", + + "start21 = . min:n1 max:n1 .|min..max| $.*", + "start22 = . min:n1 max:n1 data:.|min..max| $.*", + "start23 = . min:n1 max:n1 .|min..max| rest:$.*", + "start24 = . min:n1 max:n1 data:.|min..max| rest:$.*", + + "start31 = a:. min:n1 max:n1 .|min..max| $.*", + "start32 = a:. min:n1 max:n1 data:.|min..max| $.*", + "start33 = a:. min:n1 max:n1 .|min..max| rest:$.*", + "start34 = a:. min:n1 max:n1 data:.|min..max| rest:$.*", + + "start41 = a:. b:. min:n1 max:n1 .|min..max| $.*", + "start42 = a:. b:. min:n1 max:n1 data:.|min..max| $.*", + "start43 = a:. b:. min:n1 max:n1 .|min..max| rest:$.*", + "start44 = a:. b:. min:n1 max:n1 data:.|min..max| rest:$.*", + + "n1 = n:$[0-9] { return parseInt(n, 10); }", + ].join(";\n"), opt); + } + + const parser = buildRangeParser(); + + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start1" + i }; + expect(parser).to.failToParse("", undefined, opt); + expect(parser).to.parse("00", [0, 0, [], ""], opt); + expect(parser).to.parse("00==", [0, 0, [], "=="], opt); + expect(parser).to.failToParse("23", undefined, opt); + expect(parser).to.failToParse("23=", undefined, opt); + expect(parser).to.parse("23==", [2, 3, ["=", "="], ""], opt); + expect(parser).to.parse("23===", [2, 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("23=====", [2, 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start2" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-00", ["-", 0, 0, [], ""], opt); + expect(parser).to.parse("-00==", ["-", 0, 0, [], "=="], opt); + expect(parser).to.failToParse("-23", undefined, opt); + expect(parser).to.failToParse("-23=", undefined, opt); + expect(parser).to.parse("-23==", ["-", 2, 3, ["=", "="], ""], opt); + expect(parser).to.parse("-23===", ["-", 2, 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-23=====", ["-", 2, 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start3" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-00", ["-", 0, 0, [], ""], opt); + expect(parser).to.parse("-00==", ["-", 0, 0, [], "=="], opt); + expect(parser).to.failToParse("-23", undefined, opt); + expect(parser).to.failToParse("-23=", undefined, opt); + expect(parser).to.parse("-23==", ["-", 2, 3, ["=", "="], ""], opt); + expect(parser).to.parse("-23===", ["-", 2, 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-23=====", ["-", 2, 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start4" + i }; + expect(parser).to.failToParse("--", undefined, opt); + expect(parser).to.parse("--00", ["-", "-", 0, 0, [], ""], opt); + expect(parser).to.parse("--00==", ["-", "-", 0, 0, [], "=="], opt); + expect(parser).to.failToParse("--23", undefined, opt); + expect(parser).to.failToParse("--23=", undefined, opt); + expect(parser).to.parse("--23==", ["-", "-", 2, 3, ["=", "="], ""], opt); + expect(parser).to.parse("--23===", ["-", "-", 2, 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("--23=====", ["-", "-", 2, 3, ["=", "=", "="], "=="], opt); + } + }); + + it("|val..val| matches correctly", () => { + const parser = buildParser("val"); + + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start1" + i }; + expect(parser).to.failToParse("", undefined, opt); + expect(parser).to.parse("0", [0, [], ""], opt); + expect(parser).to.parse("0==", [0, [], "=="], opt); + expect(parser).to.failToParse("3", undefined, opt); + expect(parser).to.failToParse("3=", undefined, opt); + expect(parser).to.parse("3===", [3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("3=====", [3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start2" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.failToParse("-3", undefined, opt); + expect(parser).to.failToParse("-3=", undefined, opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start3" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.failToParse("-3", undefined, opt); + expect(parser).to.failToParse("-3=", undefined, opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start4" + i }; + expect(parser).to.failToParse("--", undefined, opt); + expect(parser).to.parse("--0", ["-", "-", 0, [], ""], opt); + expect(parser).to.parse("--0==", ["-", "-", 0, [], "=="], opt); + expect(parser).to.failToParse("--3", undefined, opt); + expect(parser).to.failToParse("--3=", undefined, opt); + expect(parser).to.parse("--3===", ["-", "-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("--3=====", ["-", "-", 3, ["=", "=", "="], "=="], opt); + } + }); + + it("| exact | matches correctly", () => { + const parser = buildParser("val"); + + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start1" + i }; + expect(parser).to.failToParse("", undefined, opt); + expect(parser).to.parse("0", [0, [], ""], opt); + expect(parser).to.parse("0==", [0, [], "=="], opt); + expect(parser).to.failToParse("3", undefined, opt); + expect(parser).to.failToParse("3=", undefined, opt); + expect(parser).to.parse("3===", [3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("3=====", [3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start2" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.failToParse("-3", undefined, opt); + expect(parser).to.failToParse("-3=", undefined, opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start3" + i }; + expect(parser).to.failToParse("-", undefined, opt); + expect(parser).to.parse("-0", ["-", 0, [], ""], opt); + expect(parser).to.parse("-0==", ["-", 0, [], "=="], opt); + expect(parser).to.failToParse("-3", undefined, opt); + expect(parser).to.failToParse("-3=", undefined, opt); + expect(parser).to.parse("-3===", ["-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("-3=====", ["-", 3, ["=", "=", "="], "=="], opt); + } + for (let i = 1; i <= 4; ++i) { + const opt = { startRule: "start4" + i }; + expect(parser).to.failToParse("--", undefined, opt); + expect(parser).to.parse("--0", ["-", "-", 0, [], ""], opt); + expect(parser).to.parse("--0==", ["-", "-", 0, [], "=="], opt); + expect(parser).to.failToParse("--3", undefined, opt); + expect(parser).to.failToParse("--3=", undefined, opt); + expect(parser).to.parse("--3===", ["-", "-", 3, ["=", "=", "="], ""], opt); + expect(parser).to.parse("--3=====", ["-", "-", 3, ["=", "=", "="], "=="], opt); + } + }); + }); }); }); diff --git a/test/unit/compiler/passes/generate-bytecode.spec.js b/test/unit/compiler/passes/generate-bytecode.spec.js index 469a0e34..7f54e098 100644 --- a/test/unit/compiler/passes/generate-bytecode.spec.js +++ b/test/unit/compiler/passes/generate-bytecode.spec.js @@ -679,6 +679,232 @@ describe("compiler pass |generateBytecode|", () => { }); }); }); + + describe("with variable boundaries", () => { + describe("| ..x| (edge case -- no min boundary)", () => { + const grammar = "start = max:('a'{return 42;}) 'a'| ..max|"; + + it("generates correct bytecode", () => { + expect(pass).to.changeAST(grammar, bytecodeDetails([ + 5, // PUSH_CURR_POS + // "a"{return 42;} + 5, // PUSH_CURR_POS + 18, 0, 2, 2, 22, 0, 23, 0, // + 15, 6, 0, // IF_NOT_ERROR + 24, 1, // * REPORT_SAVED_POS <1> + 26, 0, 1, 0, // CALL <0> + 9, // NIP + + 15, 41, 3, // IF_NOT_ERROR + // "a"| ..max| + 4, // * PUSH_EMPTY_ARRAY + 33, 1, 1, 8, // IF_GE_DYNAMIC <1> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 16, 14, // WHILE_NOT_ERROR + 10, // * APPEND + 33, 1, 1, 8, // IF_GE_DYNAMIC <1> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 6, // POP + + 15, 3, 4, // IF_NOT_ERROR + 11, 2, // * WRAP <2> + 9, // POP + 8, 2, // * POP_N <2> + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ])); + }); + + it("defines correct constants", () => { + expect(pass).to.changeAST(grammar, constsDetails( + ["a"], + [], + [{ type: "literal", value: "a", ignoreCase: false }], + [{ predicate: false, params: [], body: "return 42;" }] + )); + }); + }); + + describe("|x.. | (edge case -- no max boundary)", () => { + const grammar = "start = min:('a'{return 42;}) 'a'|min.. |"; + + it("generates correct bytecode", () => { + expect(pass).to.changeAST(grammar, bytecodeDetails([ + 5, // PUSH_CURR_POS + // "a"{return 42;} + 5, // PUSH_CURR_POS + 18, 0, 2, 2, 22, 0, 23, 0, // + 15, 6, 0, // IF_NOT_ERROR + 24, 1, // * REPORT_SAVED_POS <1> + 26, 0, 1, 0, // CALL <0> + 9, // NIP + + 15, 40, 3, // IF_NOT_ERROR + // "a"|min..| + 5, // * PUSH_CURR_POS + 4, // PUSH_EMPTY_ARRAY + 18, 0, 2, 2, 22, 0, 23, 0, // + 16, 9, // WHILE_NOT_ERROR + 10, // * APPEND + 18, 0, 2, 2, 22, 0, 23, 0, // + 6, // POP + 32, 2, 3, 1, // IF_LT_DYNAMIC <2> + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 9, // * NIP + + 15, 3, 4, // IF_NOT_ERROR + 11, 2, // * WRAP <2> + 9, // POP + 8, 2, // * POP_N <2> + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ])); + }); + + it("defines correct constants", () => { + expect(pass).to.changeAST(grammar, constsDetails( + ["a"], + [], + [{ type: "literal", value: "a", ignoreCase: false }], + [{ predicate: false, params: [], body: "return 42;" }] + )); + }); + }); + + describe("|x..y|", () => { + const grammar = "start = min:('a'{return 42;}) max:('a'{return 42;}) 'a'|min..max|"; + + it("generates correct bytecode", () => { + expect(pass).to.changeAST(grammar, bytecodeDetails([ + 5, // PUSH_CURR_POS + // "a"{return 42;} + 5, // PUSH_CURR_POS + 18, 0, 2, 2, 22, 0, 23, 0, // + 15, 6, 0, // IF_NOT_ERROR + 24, 1, // * REPORT_SAVED_POS <1> + 26, 0, 1, 0, // CALL <0> + 9, // NIP + + 15, 77, 3, // IF_NOT_ERROR + // "a"{return 42;} + 5, // * PUSH_CURR_POS + 18, 0, 2, 2, 22, 0, 23, 0, // + 15, 7, 0, // IF_NOT_ERROR + 24, 1, // * REPORT_SAVED_POS <1> + 26, 1, 1, 1, 2, // CALL <1> + 9, // NIP + 15, 50, 4, // IF_NOT_ERROR + // "a"|min..max| + 5, // * PUSH_CURR_POS + 4, // PUSH_EMPTY_ARRAY + 33, 2, 1, 8, // IF_GE_DYNAMIC <2> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 16, 14, // WHILE_NOT_ERROR + 10, // * APPEND + 33, 2, 1, 8, // IF_GE_DYNAMIC <2> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 6, // POP + 32, 3, 3, 1, // IF_LT_DYNAMIC <3> + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 9, // * NIP + + 15, 3, 4, // IF_NOT_ERROR + 11, 3, // * WRAP <3> + 9, // NIP + 8, 3, // * POP_N <3> + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 8, 2, // * WRAP <2> + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ])); + }); + + it("defines correct constants", () => { + expect(pass).to.changeAST(grammar, constsDetails( + ["a"], + [], + [{ type: "literal", value: "a", ignoreCase: false }], + [ + { predicate: false, params: [], body: "return 42;" }, + { predicate: false, params: ["min"], body: "return 42;" }, + ] + )); + }); + }); + + describe("|exact| (edge case -- exact repetitions)", () => { + const grammar = "start = exact:('a'{return 42;}) 'a'|exact|"; + + it("generates correct bytecode", () => { + expect(pass).to.changeAST(grammar, bytecodeDetails([ + 5, // PUSH_CURR_POS + // "a"{return 42;} + 5, // PUSH_CURR_POS + 18, 0, 2, 2, 22, 0, 23, 0, // + 15, 6, 0, // IF_NOT_ERROR + 24, 1, // * REPORT_SAVED_POS <1> + 26, 0, 1, 0, // CALL <0> + 9, // NIP + + 15, 50, 3, // IF_NOT_ERROR + // "a"|exact| + 5, // * PUSH_CURR_POS + 4, // PUSH_EMPTY_ARRAY + 33, 2, 1, 8, // IF_GE_DYNAMIC <2> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 16, 14, // WHILE_NOT_ERROR + 10, // * APPEND + 33, 2, 1, 8, // IF_GE_DYNAMIC <2> + 3, // * PUSH_FAILED + 18, 0, 2, 2, 22, 0, 23, 0, // * + 6, // POP + 32, 2, 3, 1, // IF_LT_DYNAMIC <2> + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 9, // * NIP + + 15, 3, 4, // IF_NOT_ERROR + 11, 2, // * WRAP <2> + 9, // NIP + 8, 2, // * POP_N <2> + 7, // POP_CURR_POS + 3, // PUSH_FAILED + 6, // * POP + 7, // POP_CURR_POS + 3, // PUSH_FAILED + ])); + }); + + it("defines correct constants", () => { + expect(pass).to.changeAST(grammar, constsDetails( + ["a"], + [], + [{ type: "literal", value: "a", ignoreCase: false }], + [{ predicate: false, params: [], body: "return 42;" }] + )); + }); + }); + }); }); }); diff --git a/test/unit/compiler/passes/inference-match-result.spec.js b/test/unit/compiler/passes/inference-match-result.spec.js index 3a11d4e5..a8ac1a18 100644 --- a/test/unit/compiler/passes/inference-match-result.spec.js +++ b/test/unit/compiler/passes/inference-match-result.spec.js @@ -152,6 +152,29 @@ describe("compiler pass |inferenceMatchResult|", () => { expect(pass).to.changeAST("start = []| 42 |", { rules: [{ match: -1 }] }); }); }); + + describe("with variable boundaries", () => { + it("for | ..max| correctly", () => { + expect(pass).to.changeAST("start = .| ..max|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = ''| ..max|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = []| ..max|", { rules: [{ match: 0 }] }); + }); + it("for |min.. | correctly", () => { + expect(pass).to.changeAST("start = .|min.. |", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = ''|min.. |", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = []|min.. |", { rules: [{ match: 0 }] }); + }); + it("for |min..max| correctly", () => { + expect(pass).to.changeAST("start = .|min..max|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = ''|min..max|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = []|min..max|", { rules: [{ match: 0 }] }); + }); + it("for | exact | correctly", () => { + expect(pass).to.changeAST("start = .|exact|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = ''|exact|", { rules: [{ match: 0 }] }); + expect(pass).to.changeAST("start = []|exact|", { rules: [{ match: 0 }] }); + }); + }); }); }); diff --git a/test/unit/compiler/passes/report-infinite-repetition.spec.js b/test/unit/compiler/passes/report-infinite-repetition.spec.js index 097471e6..c258e469 100644 --- a/test/unit/compiler/passes/report-infinite-repetition.spec.js +++ b/test/unit/compiler/passes/report-infinite-repetition.spec.js @@ -72,6 +72,21 @@ describe("compiler pass |reportInfiniteRepetition|", () => { expect(pass).to.not.reportError("start = ('')|2..3|"); expect(pass).to.not.reportError("start = ('')| 42 |"); }); + + it("with variable boundaries", () => { + expect(pass).to.reportError("start = ('')|len..|", { + message: "Possible infinite loop when parsing (unbounded range repetition used with an expression that may not consume any input)", + location: { + source: undefined, + start: { offset: 8, line: 1, column: 9 }, + end: { offset: 19, line: 1, column: 20 }, + }, + }); + + expect(pass).to.not.reportError("start = ('')|..len|"); + expect(pass).to.not.reportError("start = ('')|len1..len2|"); + expect(pass).to.not.reportError("start = ('')|len|"); + }); }); }); diff --git a/test/unit/parser.spec.js b/test/unit/parser.spec.js index 50ceb96a..4b5099ca 100644 --- a/test/unit/parser.spec.js +++ b/test/unit/parser.spec.js @@ -104,8 +104,8 @@ describe("Peggy grammar parser", () => { function repeatedGrammar(min, max) { return oneRuleGrammar({ type: "repeated", - min: { type: "constant", value: min }, - max: { type: "constant", value: max }, + min: { type: typeof min === "string" ? "variable" : "constant", value: min }, + max: { type: typeof max === "string" ? "variable" : "constant", value: max }, expression: literalAbcd, }); } @@ -451,6 +451,44 @@ describe("Peggy grammar parser", () => { expect("start = 'abcd'|\n3|").to.parseAs(grammar); expect("start = 'abcd'|3\n|").to.parseAs(grammar); }); + + it("with variable boundaries", () => { + let grammar = repeatedGrammar("min", "max"); + expect("start = 'abcd'|min..max| ").to.parseAs(grammar); + expect("start = 'abcd'\n|min..max|").to.parseAs(grammar); + expect("start = 'abcd'|\nmin..max|").to.parseAs(grammar); + expect("start = 'abcd'|min\n..max|").to.parseAs(grammar); + expect("start = 'abcd'|min..\nmax|").to.parseAs(grammar); + expect("start = 'abcd'|min..max\n|").to.parseAs(grammar); + + grammar = oneRuleGrammar({ + type: "repeated", + min: null, + max: { type: "variable", value: "exact" }, + expression: literalAbcd, + }); + expect("start = 'abcd'\n|exact|").to.parseAs(grammar); + expect("start = 'abcd'|\nexact|").to.parseAs(grammar); + expect("start = 'abcd'|exact\n|").to.parseAs(grammar); + }); + + it("with mixed boundaries", () => { + let grammar = repeatedGrammar(2, "max"); + expect("start = 'abcd'|2..max| ").to.parseAs(grammar); + expect("start = 'abcd'\n|2..max|").to.parseAs(grammar); + expect("start = 'abcd'|\n2..max|").to.parseAs(grammar); + expect("start = 'abcd'|2\n..max|").to.parseAs(grammar); + expect("start = 'abcd'|2..\nmax|").to.parseAs(grammar); + expect("start = 'abcd'|2..max\n|").to.parseAs(grammar); + + grammar = repeatedGrammar("min", 3); + expect("start = 'abcd'|min..3| ").to.parseAs(grammar); + expect("start = 'abcd'\n|min..3|").to.parseAs(grammar); + expect("start = 'abcd'|\nmin..3|").to.parseAs(grammar); + expect("start = 'abcd'|min\n..3|").to.parseAs(grammar); + expect("start = 'abcd'|min..\n3|").to.parseAs(grammar); + expect("start = 'abcd'|min..3\n|").to.parseAs(grammar); + }); }); }); @@ -479,6 +517,23 @@ describe("Peggy grammar parser", () => { expect("start = 'abcd'|0..0|").to.failToParse(); expect("start = 'abcd'|0| ").to.failToParse(); }); + + it("with variable boundaries", () => { + expect("start = 'abcd'|min.. |").to.parseAs(repeatedGrammar("min", null)); + expect("start = 'abcd'| ..max|").to.parseAs(repeatedGrammar(0, "max")); + expect("start = 'abcd'|min..max|").to.parseAs(repeatedGrammar("min", "max")); + expect("start = 'abcd'|exact| ").to.parseAs(oneRuleGrammar({ + type: "repeated", + min: null, + max: { type: "variable", value: "exact" }, + expression: literalAbcd, + })); + }); + + it("with mixed boundaries", () => { + expect("start = 'abcd'|2..max|").to.parseAs(repeatedGrammar(2, "max")); + expect("start = 'abcd'|min..3|").to.parseAs(repeatedGrammar("min", 3)); + }); }); });