Repository: peggyjs/peggy
Pull request #454 (2023-12-12 16:18:09)

Fix a bug with named NEVER_MATCH expressions
Found while working on #452.

The condition was backwards, so the error got misreported:

Before the fix:
```
% echo 'start "start" = []' | node bin/peggy.js -t "x"
Error running test
Error: Expected , or undefined but "x" found.
 --> command line:1:1
  |
1 | x
  | ^
```

After the fix:
```
% echo 'start "start" = []' | node bin/peggy.js -t "x"
Error running test
Error: Expected start but "x" found.
 --> command line:1:1
  |
1 | x
  | ^
```

There was already a test, but it was testing for the incorrect result...

markw65: > This grammar does not expect "start" rule at this position, it expect literally nothing

But that was true whether or not it's a `named` rule.

> Probably, it would be better to emit compiler error here instead

I guess thats ok if it's a start rule that always fails - but my examples only used start rules to exemplify the problem. You could use it as a catch all where a promising start goes off the rails:

```
echo 'start = [ab] fail / [cde]; fail "bzzt try again" = []' | node bin/peggy.js -t "ax"
```

Ok, its still not a great example, but it _could_ be useful to have a `named`, `NEVER_MATCH` expression.
hildjj: @mingun did you end up being happy with where this patch is at the moment?
Mingun: Yes, please merge.
hildjj: @markw65 can you please rebase this onto `main`?  I'll deal with the conflicts with #593.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b853ea..cc30187 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,8 @@ Released: TBD (Not before 2025-05-01)
 - Small measures to try to get `deno run -A npm:peggy` to work.  We will not
   know if these were successful until the package is published next.  Testing
   with `--format es -t foo` will still not work in Deno.
+- Fix a bug with named NEVER_MATCH expressions.
+  [#454](https://github.com/peggyjs/peggy/pull/454)
 
 ### Documentation
 
diff --git a/docs/js/examples.js b/docs/js/examples.js
index 770d2e1..72a8285 100644
--- a/docs/js/examples.js
+++ b/docs/js/examples.js
@@ -210,7 +210,6 @@ function peg$parse(input, options) {
   const peg$e12 = peg$literalExpectation("c", false);
   const peg$e13 = peg$classExpectation([["a", "c"]], false, false);
   const peg$e14 = peg$literalExpectation("f", false);
-  const peg$e15 = peg$otherExpectation("The rest of the input");
 
   function peg$f0(match, rest) {    return {match, rest};  }
   function peg$f1(match, rest) {    return {match, rest};  }
@@ -1594,8 +1593,6 @@ function peg$parse(input, options) {
     }
     s0 = input.substring(s0, peg$currPos);
     peg$silentFails--;
-    s1 = peg$FAILED;
-    if (peg$silentFails === 0) { peg$fail(peg$e15); }
 
     return s0;
   }
diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js
index 1b4e530..78e2393 100644
--- a/lib/compiler/passes/generate-bytecode.js
+++ b/lib/compiler/passes/generate-bytecode.js
@@ -696,8 +696,8 @@ function generateBytecode(ast, options) {
 
     named(node, context) {
       const match = node.match || 0;
-      // Expectation not required if node always fail
-      const nameIndex = (match === NEVER_MATCH)
+      // Expectation not required if node always match
+      const nameIndex = (match === ALWAYS_MATCH)
         ? -1
         : expectations.add({ type: "rule", value: node.name });
 
@@ -709,7 +709,7 @@ function generateBytecode(ast, options) {
         [op.SILENT_FAILS_ON],
         generate(node.expression, context),
         [op.SILENT_FAILS_OFF],
-        buildCondition(match, [op.IF_ERROR], [op.FAIL, nameIndex], [])
+        buildCondition(-match, [op.IF_ERROR], [op.FAIL, nameIndex], [])
       );
     },
 


Test patch:
diff --git a/test/behavior/generated-parser-behavior.spec.js b/test/behavior/generated-parser-behavior.spec.js
index 5c4d169..fff202a 100644
--- a/test/behavior/generated-parser-behavior.spec.js
+++ b/test/behavior/generated-parser-behavior.spec.js
@@ -215,11 +215,11 @@ describe("generated parser behavior", () => {
             });
           });
 
-          it("reports match failure and doesn't records any expectations when expression never match", () => {
+          it("reports match failure and records an expectation of type \"other\" when expression never match", () => {
             const parser = peg.generate("start 'start' = []", options);
 
             expect(parser).to.failToParse("b", {
-              expected: [],
+              expected: [{ type:"other", description: "start" }],
             });
           });