Skip to content

Commit

Permalink
Ranges (pegjs/pegjs#30): Add testcases for delimiter support in range…
Browse files Browse the repository at this point in the history
…s and regenerate parser
  • Loading branch information
Mingun committed Feb 19, 2023
1 parent b12d917 commit 9ef3d05
Show file tree
Hide file tree
Showing 9 changed files with 2,363 additions and 274 deletions.
574 changes: 301 additions & 273 deletions lib/parser.js

Large diffs are not rendered by default.

537 changes: 537 additions & 0 deletions test/behavior/generated-parser-behavior.spec.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/types/peg.test-d.ts
Expand Up @@ -355,6 +355,7 @@ describe("peg.d.ts", () => {
expectType<peggy.LocationRange>(node.location);
expectType<peggy.ast.RepeatedBoundary | null>(node.min);
expectType<peggy.ast.RepeatedBoundary>(node.max);
expectType<peggy.ast.Expression | null>(node.delimiter);
expectType<peggy.ast.Primary>(node.expression);
visit(node.expression);
},
Expand Down
1,032 changes: 1,032 additions & 0 deletions test/unit/compiler/passes/generate-bytecode.spec.js

Large diffs are not rendered by default.

164 changes: 164 additions & 0 deletions test/unit/compiler/passes/inference-match-result.spec.js
Expand Up @@ -176,6 +176,170 @@ describe("compiler pass |inferenceMatchResult|", () => {
});
});
});

describe("with delimiter", () => {
describe("with constant boundaries", () => {
it("for | .. , delimiter| correctly", () => {
expect(pass).to.changeAST("start = .| .. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| .. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| .. ,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = ''| .. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| .. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| .. ,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = []| .. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| .. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| .. ,[]|", { rules: [{ match: 1 }] });
});
it("for | ..1, delimiter| correctly", () => {
expect(pass).to.changeAST("start = .| ..1, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| ..1,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| ..1,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = ''| ..1, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| ..1,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| ..1,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = []| ..1, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| ..1,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| ..1,[]|", { rules: [{ match: 1 }] });
});
it("for | ..3, delimiter| correctly", () => {
expect(pass).to.changeAST("start = .| ..3, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| ..3,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .| ..3,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = ''| ..3, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| ..3,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| ..3,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = []| ..3, .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| ..3,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []| ..3,[]|", { rules: [{ match: 1 }] });
});
it("for |0.. , delimiter| correctly", () => {
expect(pass).to.changeAST("start = .|0.. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .|0.. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = .|0.. ,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = ''|0.. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|0.. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|0.. ,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = []|0.. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []|0.. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = []|0.. ,[]|", { rules: [{ match: 1 }] });
});
it("for |1.. , delimiter| correctly", () => {
expect(pass).to.changeAST("start = .|1.. , .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|1.. ,''|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|1.. ,[]|", { rules: [{ match: 0 }] });

expect(pass).to.changeAST("start = ''|1.. , .|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|1.. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|1.. ,[]|", { rules: [{ match: 1 }] });

expect(pass).to.changeAST("start = []|1.. , .|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|1.. ,''|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|1.. ,[]|", { rules: [{ match: -1 }] });
});
it("for |2.. , delimiter| correctly", () => {
expect(pass).to.changeAST("start = .|2.. , .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|2.. ,''|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|2.. ,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = ''|2.. , .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = ''|2.. ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|2.. ,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = []|2.. , .|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|2.. ,''|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|2.. ,[]|", { rules: [{ match: -1 }] });
});
it("for |2..3, delimiter| correctly", () => {
expect(pass).to.changeAST("start = .|2..3, .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|2..3,''|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .|2..3,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = ''|2..3, .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = ''|2..3,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''|2..3,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = []|2..3, .|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|2..3,''|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []|2..3,[]|", { rules: [{ match: -1 }] });
});
it("for | 42 , delimiter| correctly", () => {
expect(pass).to.changeAST("start = .| 42 , .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .| 42 ,''|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = .| 42 ,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = ''| 42 , .|", { rules: [{ match: 0 }] });
expect(pass).to.changeAST("start = ''| 42 ,''|", { rules: [{ match: 1 }] });
expect(pass).to.changeAST("start = ''| 42 ,[]|", { rules: [{ match: -1 }] });

expect(pass).to.changeAST("start = []| 42 , .|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []| 42 ,''|", { rules: [{ match: -1 }] });
expect(pass).to.changeAST("start = []| 42 ,[]|", { rules: [{ match: -1 }] });
});
});

describe("with variable boundaries", () => {
it("for | ..max, delimiter| 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 }] });

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 }] });

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.. , delimiter| 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 }] });

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 }] });

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, delimiter| 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 }] });

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 }] });

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 , delimiter| 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 }] });

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 }] });

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 }] });
});
});
});
});

it("calculate |match| property for |group| correctly", () => {
Expand Down
1 change: 1 addition & 0 deletions test/unit/compiler/passes/report-duplicate-labels.spec.js
Expand Up @@ -41,6 +41,7 @@ describe("compiler pass |reportDuplicateLabels|", () => {
expect(pass).to.not.reportError("start = (a:'a')* a:'a'");
expect(pass).to.not.reportError("start = (a:'a')+ a:'a'");
expect(pass).to.not.reportError("start = (a:'a')|2..3| a:'a'");
expect(pass).to.not.reportError("start = 'a'|2..3, a:'a'| a:'a'");
expect(pass).to.not.reportError("start = (a:'a') a:'a'");
});
});
Expand Down
22 changes: 22 additions & 0 deletions test/unit/compiler/passes/report-infinite-recursion.spec.js
Expand Up @@ -136,4 +136,26 @@ describe("compiler pass |reportInfiniteRecursion|", () => {
expect(pass).to.not.reportError("start = . start");
});
});

describe("in repeated with delimiter", () => {
it("doesn't report left recursion for delimiter if expression not match empty string", () => {
expect(pass).to.not.reportError("start = 'a'| .. , start|");
expect(pass).to.not.reportError("start = 'a'|0.. , start|");
expect(pass).to.not.reportError("start = 'a'|1.. , start|");
expect(pass).to.not.reportError("start = 'a'|2.. , start|");
expect(pass).to.not.reportError("start = 'a'| ..3, start|");
expect(pass).to.not.reportError("start = 'a'|2..3, start|");
expect(pass).to.not.reportError("start = 'a'| 42 , start|");
});

it("reports left recursion for delimiter if expression match empty string", () => {
expect(pass).to.reportError("start = ''| .. , start|");
expect(pass).to.reportError("start = ''|0.. , start|");
expect(pass).to.reportError("start = ''|1.. , start|");
expect(pass).to.reportError("start = ''|2.. , start|");
expect(pass).to.reportError("start = ''| ..3, start|");
expect(pass).to.reportError("start = ''|2..3, start|");
expect(pass).to.reportError("start = ''| 42 , start|");
});
});
});
79 changes: 79 additions & 0 deletions test/unit/compiler/passes/report-infinite-repetition.spec.js
Expand Up @@ -88,6 +88,85 @@ describe("compiler pass |reportInfiniteRepetition|", () => {
expect(pass).to.not.reportError("start = ('')|len|");
});
});

describe("with empty delimiter", () => {
it("with constant boundaries", () => {
expect(pass).to.reportError("start = ('')| .., ''|", {
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: 21, line: 1, column: 22 },
},
});
expect(pass).to.reportError("start = ('')|0.., ''|", {
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: 21, line: 1, column: 22 },
},
});
expect(pass).to.reportError("start = ('')|1.., ''|", {
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: 21, line: 1, column: 22 },
},
});
expect(pass).to.reportError("start = ('')|2.., ''|", {
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: 21, line: 1, column: 22 },
},
});

expect(pass).to.not.reportError("start = ('')| ..1, ''|");
expect(pass).to.not.reportError("start = ('')| ..3, ''|");
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: 23, line: 1, column: 24 },
},
});

expect(pass).to.not.reportError("start = ('')|..len, ''|");
expect(pass).to.not.reportError("start = ('')|len1..len2, ''|");
expect(pass).to.not.reportError("start = ('')|len, ''|");
});
});

describe("with non-empty delimiter", () => {
it("with constant boundaries", () => {
expect(pass).to.not.reportError("start = ('')| .., 'a'|");
expect(pass).to.not.reportError("start = ('')|0.., 'a'|");
expect(pass).to.not.reportError("start = ('')|1.., 'a'|");
expect(pass).to.not.reportError("start = ('')|2.., 'a'|");

expect(pass).to.not.reportError("start = ('')| ..1, 'a'|");
expect(pass).to.not.reportError("start = ('')| ..3, 'a'|");
expect(pass).to.not.reportError("start = ('')|2..3, 'a'|");
expect(pass).to.not.reportError("start = ('')| 42 , 'a'|");
});

it("with variable boundaries", () => {
expect(pass).to.not.reportError("start = ('')|len.., 'a'|");

expect(pass).to.not.reportError("start = ('')|..len, 'a'|");
expect(pass).to.not.reportError("start = ('')|len1..len2, 'a'|");
expect(pass).to.not.reportError("start = ('')|len, 'a'|");
});
});
});

it("computes expressions that always consume input on success correctly", () => {
Expand Down

0 comments on commit 9ef3d05

Please sign in to comment.