Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should not allow left recursion grammar to pass conversion #79

Closed
cch123 opened this issue Feb 27, 2019 · 5 comments · Fixed by #123
Closed

Should not allow left recursion grammar to pass conversion #79

cch123 opened this issue Feb 27, 2019 · 5 comments · Fixed by #123

Comments

@cch123
Copy link

cch123 commented Feb 27, 2019

eg:

{
//------ start
package main

func main() {
    if len(os.Args) != 2 {
        log.Fatal("Usage: calculator 'EXPR'")
    }
    got, err := ParseReader("", strings.NewReader(os.Args[1]))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%#v\n", got)
}

// ------ end
}

Input <- expr:Expr EOF {
    return expr, nil
}

Expr <- _ Expr _ LogicOp _ Expr _/ _ Value _

LogicOp <- ("and" / "or") {
    return string(c.text), nil
}

Value <- [0-9]+ {
    return string(c.text),nil
}

_ "whitespace" <- [ \n\t\r]*

EOF <- !.

go run main.go "1 and 1"

Will cause dead loop

@breml
Copy link
Collaborator

breml commented Feb 28, 2019

Not sure, how we could detect this in a reliable way.
@cch123 if you have an idea, feel free to share.

@cch123
Copy link
Author

cch123 commented Mar 1, 2019

Maybe a simple idea is like detecting a cycle in a dag,
start from the rule root, push every left rule to its map, until every rule is traversed.
eg:

expr <- AX / B
A <- Z / C

make a map to record every left rule:
[expr] -> add A
[expr A] -> add B
[expr A B] -> add Child of A 1 -> add Z
[expr A B Z] -> add Child of A 2 -> add C
[expr A B Z C] -> finish, there are no cycles

expr <- AX / B
A <- expr / C

[expr] -> add A
[expr A] -> add B
[expr A B] -> add Child of A 1 -> add expr -> there is already a expr in map, so it's left recursion~

https://github.com/pest-parser/pest/blob/master/meta/src/validator.rs#L727, perhaps this can provide more ideas

breml added a commit to sc07kvm/pigeon that referenced this issue Aug 30, 2023
@breml
Copy link
Collaborator

breml commented Aug 30, 2023

@cch123 I am not sure, if you still follow this issue, since it has been several years since you raised it. But in #123 we have a PR, that fixes this issue and it would be great, if you could give it a try and report back whether it works for you or not. Thanks.

@cch123
Copy link
Author

cch123 commented Aug 31, 2023

code in master branch will cause a stack overflow

~/pigeon ❯❯❯ go run main.go "1 and 1"
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x140200e0350 stack=[0x140200e0000, 0x140400e0000]
fatal error: stack overflow

runtime stack:
runtime.throw({0x100a26f59?, 0x100ad9fe0?})
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/panic.go:1047 +0x40 fp=0x16f58ad20 sp=0x16f58acf0 pc=0x1009b8780
runtime.newstack()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/stack.go:1105 +0x468 fp=0x16f58aed0 sp=0x16f58ad20 pc=0x1009d0ee8
runtime.morestack()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:316 +0x70 fp=0x16f58aed0 sp=0x16f58aed0 pc=0x1009e3d00

goroutine 1 [running]:
runtime.writeHeapBits.write({0x14005d2d000?, 0x0?, 0x34?, 0x34?}, 0x2c?, 0x6?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mbitmap.go:791 +0x13c fp=0x140200e0350 sp=0x140200e0350 pc=0x10099a00c
runtime.heapBitsSetType(0x14005d2d1a0, 0x30, 0x30, 0x100a5ff80)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mbitmap.go:1026 +0xd4 fp=0x140200e0410 sp=0x140200e0350 pc=0x10099a3b4
runtime.mallocgc(0x30, 0x100a5ff80, 0x1)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/malloc.go:1074 +0x5a4 fp=0x140200e0480 sp=0x140200e0410 pc=0x100992f14
runtime.newobject(0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/malloc.go:1254 +0x2c fp=0x140200e04b0 sp=0x140200e0480 pc=0x10099337c
runtime.makemap_small()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/map.go:294 +0x24 fp=0x140200e04e0 sp=0x140200e04b0 pc=0x100993f64
main.(*parser).pushV(...)
	/Users/xargin/pigeon/main.go:734
main.(*parser).parseZeroOrMoreExpr(0x140000bc000, 0x100ae7200)
	/Users/xargin/pigeon/main.go:1484 +0x1cc fp=0x140200e05a0 sp=0x140200e04e0 pc=0x100a23fec
main.(*parser).parseExpr(0x140000bc000, {0x100a4e0a0?, 0x100ae7200?})
	/Users/xargin/pigeon/main.go:1115 +0x4fc fp=0x140200e0790 sp=0x140200e05a0 pc=0x100a1f5dc
main.(*parser).parseRule(0x140000bc000, 0x100aeb360)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e0a10 sp=0x140200e0790 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f40)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e0ac0 sp=0x140200e0a10 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f40?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e0cb0 sp=0x140200e0ac0 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e0dc0 sp=0x140200e0cb0 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e0fb0 sp=0x140200e0dc0 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e1090 sp=0x140200e0fb0 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e1280 sp=0x140200e1090 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e1500 sp=0x140200e1280 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e15b0 sp=0x140200e1500 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e17a0 sp=0x140200e15b0 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e18b0 sp=0x140200e17a0 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e1aa0 sp=0x140200e18b0 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e1b80 sp=0x140200e1aa0 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e1d70 sp=0x140200e1b80 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e1ff0 sp=0x140200e1d70 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e20a0 sp=0x140200e1ff0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e2290 sp=0x140200e20a0 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e23a0 sp=0x140200e2290 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e2590 sp=0x140200e23a0 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e2670 sp=0x140200e2590 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e2860 sp=0x140200e2670 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e2ae0 sp=0x140200e2860 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e2b90 sp=0x140200e2ae0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e2d80 sp=0x140200e2b90 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e2e90 sp=0x140200e2d80 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e3080 sp=0x140200e2e90 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e3160 sp=0x140200e3080 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e3350 sp=0x140200e3160 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e35d0 sp=0x140200e3350 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e3680 sp=0x140200e35d0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e3870 sp=0x140200e3680 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e3980 sp=0x140200e3870 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e3b70 sp=0x140200e3980 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e3c50 sp=0x140200e3b70 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e3e40 sp=0x140200e3c50 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e40c0 sp=0x140200e3e40 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e4170 sp=0x140200e40c0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e4360 sp=0x140200e4170 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e4470 sp=0x140200e4360 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e4660 sp=0x140200e4470 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e4740 sp=0x140200e4660 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e4930 sp=0x140200e4740 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e4bb0 sp=0x140200e4930 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e4c60 sp=0x140200e4bb0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e4e50 sp=0x140200e4c60 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e4f60 sp=0x140200e4e50 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e5150 sp=0x140200e4f60 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e5230 sp=0x140200e5150 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e5420 sp=0x140200e5230 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e56a0 sp=0x140200e5420 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e5750 sp=0x140200e56a0 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e5940 sp=0x140200e5750 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e5a50 sp=0x140200e5940 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e5c40 sp=0x140200e5a50 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e5d20 sp=0x140200e5c40 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e5f10 sp=0x140200e5d20 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e6190 sp=0x140200e5f10 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e6240 sp=0x140200e6190 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e6430 sp=0x140200e6240 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e6540 sp=0x140200e6430 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e6730 sp=0x140200e6540 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e6810 sp=0x140200e6730 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e6a00 sp=0x140200e6810 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e6c80 sp=0x140200e6a00 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e6d30 sp=0x140200e6c80 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e6f20 sp=0x140200e6d30 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e7030 sp=0x140200e6f20 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e7220 sp=0x140200e7030 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e7300 sp=0x140200e7220 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e74f0 sp=0x140200e7300 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e7770 sp=0x140200e74f0 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e7820 sp=0x140200e7770 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e7a10 sp=0x140200e7820 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e7b20 sp=0x140200e7a10 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e7d10 sp=0x140200e7b20 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e7df0 sp=0x140200e7d10 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e7fe0 sp=0x140200e7df0 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e8260 sp=0x140200e7fe0 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e8310 sp=0x140200e8260 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e8500 sp=0x140200e8310 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e8610 sp=0x140200e8500 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e8800 sp=0x140200e8610 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e88e0 sp=0x140200e8800 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e8ad0 sp=0x140200e88e0 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e8d50 sp=0x140200e8ad0 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e8e00 sp=0x140200e8d50 pc=0x100a234f8
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfa0?, 0x100ae6f80?})
	/Users/xargin/pigeon/main.go:1107 +0x334 fp=0x140200e8ff0 sp=0x140200e8e00 pc=0x100a1f414
main.(*parser).parseSeqExpr(0x140000bc000, 0x100ae7400)
	/Users/xargin/pigeon/main.go:1437 +0x140 fp=0x140200e9100 sp=0x140200e8ff0 pc=0x100a23830
main.(*parser).parseExpr(0x140000bc000, {0x100a4dfe0?, 0x100ae7400?})
	/Users/xargin/pigeon/main.go:1109 +0x420 fp=0x140200e92f0 sp=0x140200e9100 pc=0x100a1f500
main.(*parser).parseChoiceExpr(0x140000bc000, 0x100ae73c0)
	/Users/xargin/pigeon/main.go:1293 +0x234 fp=0x140200e93d0 sp=0x140200e92f0 pc=0x100a219e4
main.(*parser).parseExpr(0x140000bc000, {0x100a4dd60?, 0x100ae73c0?})
	/Users/xargin/pigeon/main.go:1093 +0x4d0 fp=0x140200e95c0 sp=0x140200e93d0 pc=0x100a1f5b0
main.(*parser).parseRule(0x140000bc000, 0x100aeb420)
	/Users/xargin/pigeon/main.go:1049 +0x488 fp=0x140200e9840 sp=0x140200e95c0 pc=0x100a1ec78
main.(*parser).parseRuleRefExpr(0x140000bc000, 0x100ae6f80)
	/Users/xargin/pigeon/main.go:1424 +0xd8 fp=0x140200e98f0 sp=0x140200e9840 pc=0x100a234f8

goroutine 2 [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000044fa0 sp=0x14000044f80 pc=0x1009bb1f4
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:387
runtime.forcegchelper()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:305 +0xb8 fp=0x14000044fd0 sp=0x14000044fa0 pc=0x1009bb038
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000044fd0 sp=0x14000044fd0 pc=0x1009e6094
created by runtime.init.6
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:293 +0x24

goroutine 3 [GC sweep wait]:
runtime.gopark(0x1?, 0x0?, 0x0?, 0x0?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000045760 sp=0x14000045740 pc=0x1009bb1f4
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:387
runtime.bgsweep(0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgcsweep.go:319 +0x110 fp=0x140000457b0 sp=0x14000045760 pc=0x1009a8be0
runtime.gcenable.func1()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:178 +0x28 fp=0x140000457d0 sp=0x140000457b0 pc=0x10099d928
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140000457d0 sp=0x140000457d0 pc=0x1009e6094
created by runtime.gcenable
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:178 +0x74

goroutine 4 [sleep]:
runtime.gopark(0x14000066000?, 0x74e2ff4f0909?, 0x0?, 0x0?, 0x100a65de8?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000045f00 sp=0x14000045ee0 pc=0x1009bb1f4
runtime.goparkunlock(...)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:387
runtime.(*scavengerState).sleep(0x100aeed20, 0x412e892000000000)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgcscavenge.go:479 +0x128 fp=0x14000045f80 sp=0x14000045f00 pc=0x1009a6bf8
runtime.bgscavenge(0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgcscavenge.go:637 +0x9c fp=0x14000045fb0 sp=0x14000045f80 pc=0x1009a6fcc
runtime.gcenable.func2()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:179 +0x28 fp=0x14000045fd0 sp=0x14000045fb0 pc=0x10099d8c8
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000045fd0 sp=0x14000045fd0 pc=0x1009e6094
created by runtime.gcenable
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:179 +0xb8

goroutine 17 [finalizer wait]:
runtime.gopark(0x140000445a8?, 0x6000010099b818?, 0x68?, 0x58?, 0x1?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000044580 sp=0x14000044560 pc=0x1009bb1f4
runtime.runfinq()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mfinal.go:193 +0x10c fp=0x140000447d0 sp=0x14000044580 pc=0x10099c9bc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140000447d0 sp=0x140000447d0 pc=0x1009e6094
created by runtime.createfing
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mfinal.go:163 +0x84

goroutine 18 [GC worker (idle)]:
runtime.gopark(0x74e2994f9be0?, 0x1?, 0xe3?, 0xdb?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000040740 sp=0x14000040720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x140000407d0 sp=0x14000040740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140000407d0 sp=0x140000407d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 19 [GC worker (idle)]:
runtime.gopark(0x74e299505690?, 0x3?, 0x87?, 0x15?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000040f40 sp=0x14000040f20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x14000040fd0 sp=0x14000040f40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000040fd0 sp=0x14000040fd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 5 [GC worker (idle)]:
runtime.gopark(0x100b22fc0?, 0x3?, 0x1?, 0xde?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000046740 sp=0x14000046720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x140000467d0 sp=0x14000046740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140000467d0 sp=0x140000467d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 33 [GC worker (idle)]:
runtime.gopark(0x100b22fc0?, 0x3?, 0x11?, 0x75?, 0x4?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000448740 sp=0x14000448720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x140004487d0 sp=0x14000448740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140004487d0 sp=0x140004487d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 20 [GC worker (idle)]:
runtime.gopark(0x74e299505a4e?, 0x3?, 0x5e?, 0x7?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000041740 sp=0x14000041720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x140000417d0 sp=0x14000041740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140000417d0 sp=0x140000417d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 34 [GC worker (idle)]:
runtime.gopark(0x74e2994e708f?, 0x3?, 0x15?, 0x27?, 0x100a5ff80?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000448f40 sp=0x14000448f20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x14000448fd0 sp=0x14000448f40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000448fd0 sp=0x14000448fd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 35 [GC worker (idle)]:
runtime.gopark(0x74e299501c9e?, 0x3?, 0xc4?, 0xfc?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000449740 sp=0x14000449720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x140004497d0 sp=0x14000449740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x140004497d0 sp=0x140004497d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 36 [GC worker (idle)]:
runtime.gopark(0x74e2994ee1ad?, 0x1?, 0xe7?, 0x18?, 0x100a564c0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000449f40 sp=0x14000449f20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x14000449fd0 sp=0x14000449f40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000449fd0 sp=0x14000449fd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 21 [GC worker (idle)]:
runtime.gopark(0x74e2994f89f9?, 0x3?, 0x29?, 0x7?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000041f40 sp=0x14000041f20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x14000041fd0 sp=0x14000041f40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000041fd0 sp=0x14000041fd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 37 [GC worker (idle)]:
runtime.gopark(0x74e2994ed76c?, 0x3?, 0x1b?, 0x87?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x1400044a740 sp=0x1400044a720 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x1400044a7d0 sp=0x1400044a740 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x1400044a7d0 sp=0x1400044a7d0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 38 [GC worker (idle)]:
runtime.gopark(0x74e2994e652a?, 0x3?, 0xf4?, 0x8?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x1400044af40 sp=0x1400044af20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x1400044afd0 sp=0x1400044af40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x1400044afd0 sp=0x1400044afd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28

goroutine 6 [GC worker (idle)]:
runtime.gopark(0x74e2994ea7bc?, 0x3?, 0xca?, 0x8?, 0x0?)
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/proc.go:381 +0xe4 fp=0x14000046f40 sp=0x14000046f20 pc=0x1009bb1f4
runtime.gcBgMarkWorker()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1275 +0xec fp=0x14000046fd0 sp=0x14000046f40 pc=0x10099f5dc
runtime.goexit()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/asm_arm64.s:1172 +0x4 fp=0x14000046fd0 sp=0x14000046fd0 pc=0x1009e6094
created by runtime.gcBgMarkStartWorkers
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/mgc.go:1199 +0x28
exit status 2

generated main.go

// Code generated by pigeon; DO NOT EDIT.

// ------ start
package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"unicode"
	"unicode/utf8"
)

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Usage: calculator 'EXPR'")
	}
	got, err := ParseReader("", strings.NewReader(os.Args[1]))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v\n", got)
}

// ------ end

var g = &grammar{
	rules: []*rule{
		{
			name: "Input",
			pos:  position{line: 19, col: 1, offset: 285},
			expr: &actionExpr{
				pos: position{line: 19, col: 10, offset: 294},
				run: (*parser).callonInput1,
				expr: &seqExpr{
					pos: position{line: 19, col: 10, offset: 294},
					exprs: []any{
						&labeledExpr{
							pos:   position{line: 19, col: 10, offset: 294},
							label: "expr",
							expr: &ruleRefExpr{
								pos:  position{line: 19, col: 15, offset: 299},
								name: "Expr",
							},
						},
						&ruleRefExpr{
							pos:  position{line: 19, col: 20, offset: 304},
							name: "EOF",
						},
					},
				},
			},
		},
		{
			name: "Expr",
			pos:  position{line: 23, col: 1, offset: 334},
			expr: &choiceExpr{
				pos: position{line: 23, col: 9, offset: 342},
				alternatives: []any{
					&seqExpr{
						pos: position{line: 23, col: 9, offset: 342},
						exprs: []any{
							&ruleRefExpr{
								pos:  position{line: 23, col: 9, offset: 342},
								name: "_",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 11, offset: 344},
								name: "Expr",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 16, offset: 349},
								name: "_",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 18, offset: 351},
								name: "LogicOp",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 26, offset: 359},
								name: "_",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 28, offset: 361},
								name: "Expr",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 33, offset: 366},
								name: "_",
							},
						},
					},
					&seqExpr{
						pos: position{line: 23, col: 36, offset: 369},
						exprs: []any{
							&ruleRefExpr{
								pos:  position{line: 23, col: 36, offset: 369},
								name: "_",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 38, offset: 371},
								name: "Value",
							},
							&ruleRefExpr{
								pos:  position{line: 23, col: 44, offset: 377},
								name: "_",
							},
						},
					},
				},
			},
		},
		{
			name: "LogicOp",
			pos:  position{line: 25, col: 1, offset: 380},
			expr: &actionExpr{
				pos: position{line: 25, col: 12, offset: 391},
				run: (*parser).callonLogicOp1,
				expr: &choiceExpr{
					pos: position{line: 25, col: 13, offset: 392},
					alternatives: []any{
						&litMatcher{
							pos:        position{line: 25, col: 13, offset: 392},
							val:        "and",
							ignoreCase: false,
							want:       "\"and\"",
						},
						&litMatcher{
							pos:        position{line: 25, col: 21, offset: 400},
							val:        "or",
							ignoreCase: false,
							want:       "\"or\"",
						},
					},
				},
			},
		},
		{
			name: "Value",
			pos:  position{line: 29, col: 1, offset: 442},
			expr: &actionExpr{
				pos: position{line: 29, col: 10, offset: 451},
				run: (*parser).callonValue1,
				expr: &oneOrMoreExpr{
					pos: position{line: 29, col: 10, offset: 451},
					expr: &charClassMatcher{
						pos:        position{line: 29, col: 10, offset: 451},
						val:        "[0-9]",
						ranges:     []rune{'0', '9'},
						ignoreCase: false,
						inverted:   false,
					},
				},
			},
		},
		{
			name:        "_",
			displayName: "\"whitespace\"",
			pos:         position{line: 33, col: 1, offset: 493},
			expr: &zeroOrMoreExpr{
				pos: position{line: 33, col: 19, offset: 511},
				expr: &charClassMatcher{
					pos:        position{line: 33, col: 19, offset: 511},
					val:        "[ \\n\\t\\r]",
					chars:      []rune{' ', '\n', '\t', '\r'},
					ignoreCase: false,
					inverted:   false,
				},
			},
		},
		{
			name: "EOF",
			pos:  position{line: 35, col: 1, offset: 523},
			expr: &notExpr{
				pos: position{line: 35, col: 8, offset: 530},
				expr: &anyMatcher{
					line: 35, col: 9, offset: 531,
				},
			},
		},
	},
}

func (c *current) onInput1(expr any) (any, error) {
	return expr, nil
}

func (p *parser) callonInput1() (any, error) {
	stack := p.vstack[len(p.vstack)-1]
	_ = stack
	return p.cur.onInput1(stack["expr"])
}

func (c *current) onLogicOp1() (any, error) {
	return string(c.text), nil
}

func (p *parser) callonLogicOp1() (any, error) {
	stack := p.vstack[len(p.vstack)-1]
	_ = stack
	return p.cur.onLogicOp1()
}

func (c *current) onValue1() (any, error) {
	return string(c.text), nil
}

func (p *parser) callonValue1() (any, error) {
	stack := p.vstack[len(p.vstack)-1]
	_ = stack
	return p.cur.onValue1()
}

var (
	// errNoRule is returned when the grammar to parse has no rule.
	errNoRule = errors.New("grammar has no rule")

	// errInvalidEntrypoint is returned when the specified entrypoint rule
	// does not exit.
	errInvalidEntrypoint = errors.New("invalid entrypoint")

	// errInvalidEncoding is returned when the source is not properly
	// utf8-encoded.
	errInvalidEncoding = errors.New("invalid encoding")

	// errMaxExprCnt is used to signal that the maximum number of
	// expressions have been parsed.
	errMaxExprCnt = errors.New("max number of expresssions parsed")
)

// Option is a function that can set an option on the parser. It returns
// the previous setting as an Option.
type Option func(*parser) Option

// MaxExpressions creates an Option to stop parsing after the provided
// number of expressions have been parsed, if the value is 0 then the parser will
// parse for as many steps as needed (possibly an infinite number).
//
// The default for maxExprCnt is 0.
func MaxExpressions(maxExprCnt uint64) Option {
	return func(p *parser) Option {
		oldMaxExprCnt := p.maxExprCnt
		p.maxExprCnt = maxExprCnt
		return MaxExpressions(oldMaxExprCnt)
	}
}

// Entrypoint creates an Option to set the rule name to use as entrypoint.
// The rule name must have been specified in the -alternate-entrypoints
// if generating the parser with the -optimize-grammar flag, otherwise
// it may have been optimized out. Passing an empty string sets the
// entrypoint to the first rule in the grammar.
//
// The default is to start parsing at the first rule in the grammar.
func Entrypoint(ruleName string) Option {
	return func(p *parser) Option {
		oldEntrypoint := p.entrypoint
		p.entrypoint = ruleName
		if ruleName == "" {
			p.entrypoint = g.rules[0].name
		}
		return Entrypoint(oldEntrypoint)
	}
}

// Statistics adds a user provided Stats struct to the parser to allow
// the user to process the results after the parsing has finished.
// Also the key for the "no match" counter is set.
//
// Example usage:
//
//	input := "input"
//	stats := Stats{}
//	_, err := Parse("input-file", []byte(input), Statistics(&stats, "no match"))
//	if err != nil {
//	    log.Panicln(err)
//	}
//	b, err := json.MarshalIndent(stats.ChoiceAltCnt, "", "  ")
//	if err != nil {
//	    log.Panicln(err)
//	}
//	fmt.Println(string(b))
func Statistics(stats *Stats, choiceNoMatch string) Option {
	return func(p *parser) Option {
		oldStats := p.Stats
		p.Stats = stats
		oldChoiceNoMatch := p.choiceNoMatch
		p.choiceNoMatch = choiceNoMatch
		if p.Stats.ChoiceAltCnt == nil {
			p.Stats.ChoiceAltCnt = make(map[string]map[string]int)
		}
		return Statistics(oldStats, oldChoiceNoMatch)
	}
}

// Debug creates an Option to set the debug flag to b. When set to true,
// debugging information is printed to stdout while parsing.
//
// The default is false.
func Debug(b bool) Option {
	return func(p *parser) Option {
		old := p.debug
		p.debug = b
		return Debug(old)
	}
}

// Memoize creates an Option to set the memoize flag to b. When set to true,
// the parser will cache all results so each expression is evaluated only
// once. This guarantees linear parsing time even for pathological cases,
// at the expense of more memory and slower times for typical cases.
//
// The default is false.
func Memoize(b bool) Option {
	return func(p *parser) Option {
		old := p.memoize
		p.memoize = b
		return Memoize(old)
	}
}

// AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes.
// Every invalid UTF-8 byte is treated as a utf8.RuneError (U+FFFD)
// by character class matchers and is matched by the any matcher.
// The returned matched value, c.text and c.offset are NOT affected.
//
// The default is false.
func AllowInvalidUTF8(b bool) Option {
	return func(p *parser) Option {
		old := p.allowInvalidUTF8
		p.allowInvalidUTF8 = b
		return AllowInvalidUTF8(old)
	}
}

// Recover creates an Option to set the recover flag to b. When set to
// true, this causes the parser to recover from panics and convert it
// to an error. Setting it to false can be useful while debugging to
// access the full stack trace.
//
// The default is true.
func Recover(b bool) Option {
	return func(p *parser) Option {
		old := p.recover
		p.recover = b
		return Recover(old)
	}
}

// GlobalStore creates an Option to set a key to a certain value in
// the globalStore.
func GlobalStore(key string, value any) Option {
	return func(p *parser) Option {
		old := p.cur.globalStore[key]
		p.cur.globalStore[key] = value
		return GlobalStore(key, old)
	}
}

// InitState creates an Option to set a key to a certain value in
// the global "state" store.
func InitState(key string, value any) Option {
	return func(p *parser) Option {
		old := p.cur.state[key]
		p.cur.state[key] = value
		return InitState(key, old)
	}
}

// ParseFile parses the file identified by filename.
func ParseFile(filename string, opts ...Option) (i any, err error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer func() {
		if closeErr := f.Close(); closeErr != nil {
			err = closeErr
		}
	}()
	return ParseReader(filename, f, opts...)
}

// ParseReader parses the data from r using filename as information in the
// error messages.
func ParseReader(filename string, r io.Reader, opts ...Option) (any, error) {
	b, err := io.ReadAll(r)
	if err != nil {
		return nil, err
	}

	return Parse(filename, b, opts...)
}

// Parse parses the data from b using filename as information in the
// error messages.
func Parse(filename string, b []byte, opts ...Option) (any, error) {
	return newParser(filename, b, opts...).parse(g)
}

// position records a position in the text.
type position struct {
	line, col, offset int
}

func (p position) String() string {
	return strconv.Itoa(p.line) + ":" + strconv.Itoa(p.col) + " [" + strconv.Itoa(p.offset) + "]"
}

// savepoint stores all state required to go back to this point in the
// parser.
type savepoint struct {
	position
	rn rune
	w  int
}

type current struct {
	pos  position // start position of the match
	text []byte   // raw text of the match

	// state is a store for arbitrary key,value pairs that the user wants to be
	// tied to the backtracking of the parser.
	// This is always rolled back if a parsing rule fails.
	state storeDict

	// globalStore is a general store for the user to store arbitrary key-value
	// pairs that they need to manage and that they do not want tied to the
	// backtracking of the parser. This is only modified by the user and never
	// rolled back by the parser. It is always up to the user to keep this in a
	// consistent state.
	globalStore storeDict
}

type storeDict map[string]any

// the AST types...

type grammar struct {
	pos   position
	rules []*rule
}

type rule struct {
	pos         position
	name        string
	displayName string
	expr        any
}

type choiceExpr struct {
	pos          position
	alternatives []any
}

type actionExpr struct {
	pos  position
	expr any
	run  func(*parser) (any, error)
}

type recoveryExpr struct {
	pos          position
	expr         any
	recoverExpr  any
	failureLabel []string
}

type seqExpr struct {
	pos   position
	exprs []any
}

type throwExpr struct {
	pos   position
	label string
}

type labeledExpr struct {
	pos   position
	label string
	expr  any
}

type expr struct {
	pos  position
	expr any
}

type (
	andExpr        expr
	notExpr        expr
	zeroOrOneExpr  expr
	zeroOrMoreExpr expr
	oneOrMoreExpr  expr
)

type ruleRefExpr struct {
	pos  position
	name string
}

type stateCodeExpr struct {
	pos position
	run func(*parser) error
}

type andCodeExpr struct {
	pos position
	run func(*parser) (bool, error)
}

type notCodeExpr struct {
	pos position
	run func(*parser) (bool, error)
}

type litMatcher struct {
	pos        position
	val        string
	ignoreCase bool
	want       string
}

type charClassMatcher struct {
	pos             position
	val             string
	basicLatinChars [128]bool
	chars           []rune
	ranges          []rune
	classes         []*unicode.RangeTable
	ignoreCase      bool
	inverted        bool
}

type anyMatcher position

// errList cumulates the errors found by the parser.
type errList []error

func (e *errList) add(err error) {
	*e = append(*e, err)
}

func (e errList) err() error {
	if len(e) == 0 {
		return nil
	}
	e.dedupe()
	return e
}

func (e *errList) dedupe() {
	var cleaned []error
	set := make(map[string]bool)
	for _, err := range *e {
		if msg := err.Error(); !set[msg] {
			set[msg] = true
			cleaned = append(cleaned, err)
		}
	}
	*e = cleaned
}

func (e errList) Error() string {
	switch len(e) {
	case 0:
		return ""
	case 1:
		return e[0].Error()
	default:
		var buf bytes.Buffer

		for i, err := range e {
			if i > 0 {
				buf.WriteRune('\n')
			}
			buf.WriteString(err.Error())
		}
		return buf.String()
	}
}

// parserError wraps an error with a prefix indicating the rule in which
// the error occurred. The original error is stored in the Inner field.
type parserError struct {
	Inner    error
	pos      position
	prefix   string
	expected []string
}

// Error returns the error message.
func (p *parserError) Error() string {
	return p.prefix + ": " + p.Inner.Error()
}

// newParser creates a parser with the specified input source and options.
func newParser(filename string, b []byte, opts ...Option) *parser {
	stats := Stats{
		ChoiceAltCnt: make(map[string]map[string]int),
	}

	p := &parser{
		filename: filename,
		errs:     new(errList),
		data:     b,
		pt:       savepoint{position: position{line: 1}},
		recover:  true,
		cur: current{
			state:       make(storeDict),
			globalStore: make(storeDict),
		},
		maxFailPos:      position{col: 1, line: 1},
		maxFailExpected: make([]string, 0, 20),
		Stats:           &stats,
		// start rule is rule [0] unless an alternate entrypoint is specified
		entrypoint: g.rules[0].name,
	}
	p.setOptions(opts)

	if p.maxExprCnt == 0 {
		p.maxExprCnt = math.MaxUint64
	}

	return p
}

// setOptions applies the options to the parser.
func (p *parser) setOptions(opts []Option) {
	for _, opt := range opts {
		opt(p)
	}
}

type resultTuple struct {
	v   any
	b   bool
	end savepoint
}

const choiceNoMatch = -1

// Stats stores some statistics, gathered during parsing
type Stats struct {
	// ExprCnt counts the number of expressions processed during parsing
	// This value is compared to the maximum number of expressions allowed
	// (set by the MaxExpressions option).
	ExprCnt uint64

	// ChoiceAltCnt is used to count for each ordered choice expression,
	// which alternative is used how may times.
	// These numbers allow to optimize the order of the ordered choice expression
	// to increase the performance of the parser
	//
	// The outer key of ChoiceAltCnt is composed of the name of the rule as well
	// as the line and the column of the ordered choice.
	// The inner key of ChoiceAltCnt is the number (one-based) of the matching alternative.
	// For each alternative the number of matches are counted. If an ordered choice does not
	// match, a special counter is incremented. The name of this counter is set with
	// the parser option Statistics.
	// For an alternative to be included in ChoiceAltCnt, it has to match at least once.
	ChoiceAltCnt map[string]map[string]int
}

type parser struct {
	filename string
	pt       savepoint
	cur      current

	data []byte
	errs *errList

	depth   int
	recover bool
	debug   bool

	memoize bool
	// memoization table for the packrat algorithm:
	// map[offset in source] map[expression or rule] {value, match}
	memo map[int]map[any]resultTuple

	// rules table, maps the rule identifier to the rule node
	rules map[string]*rule
	// variables stack, map of label to value
	vstack []map[string]any
	// rule stack, allows identification of the current rule in errors
	rstack []*rule

	// parse fail
	maxFailPos            position
	maxFailExpected       []string
	maxFailInvertExpected bool

	// max number of expressions to be parsed
	maxExprCnt uint64
	// entrypoint for the parser
	entrypoint string

	allowInvalidUTF8 bool

	*Stats

	choiceNoMatch string
	// recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse
	recoveryStack []map[string]any
}

// push a variable set on the vstack.
func (p *parser) pushV() {
	if cap(p.vstack) == len(p.vstack) {
		// create new empty slot in the stack
		p.vstack = append(p.vstack, nil)
	} else {
		// slice to 1 more
		p.vstack = p.vstack[:len(p.vstack)+1]
	}

	// get the last args set
	m := p.vstack[len(p.vstack)-1]
	if m != nil && len(m) == 0 {
		// empty map, all good
		return
	}

	m = make(map[string]any)
	p.vstack[len(p.vstack)-1] = m
}

// pop a variable set from the vstack.
func (p *parser) popV() {
	// if the map is not empty, clear it
	m := p.vstack[len(p.vstack)-1]
	if len(m) > 0 {
		// GC that map
		p.vstack[len(p.vstack)-1] = nil
	}
	p.vstack = p.vstack[:len(p.vstack)-1]
}

// push a recovery expression with its labels to the recoveryStack
func (p *parser) pushRecovery(labels []string, expr any) {
	if cap(p.recoveryStack) == len(p.recoveryStack) {
		// create new empty slot in the stack
		p.recoveryStack = append(p.recoveryStack, nil)
	} else {
		// slice to 1 more
		p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)+1]
	}

	m := make(map[string]any, len(labels))
	for _, fl := range labels {
		m[fl] = expr
	}
	p.recoveryStack[len(p.recoveryStack)-1] = m
}

// pop a recovery expression from the recoveryStack
func (p *parser) popRecovery() {
	// GC that map
	p.recoveryStack[len(p.recoveryStack)-1] = nil

	p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)-1]
}

func (p *parser) print(prefix, s string) string {
	if !p.debug {
		return s
	}

	fmt.Printf("%s %d:%d:%d: %s [%#U]\n",
		prefix, p.pt.line, p.pt.col, p.pt.offset, s, p.pt.rn)
	return s
}

func (p *parser) in(s string) string {
	p.depth++
	return p.print(strings.Repeat(" ", p.depth)+">", s)
}

func (p *parser) out(s string) string {
	p.depth--
	return p.print(strings.Repeat(" ", p.depth)+"<", s)
}

func (p *parser) addErr(err error) {
	p.addErrAt(err, p.pt.position, []string{})
}

func (p *parser) addErrAt(err error, pos position, expected []string) {
	var buf bytes.Buffer
	if p.filename != "" {
		buf.WriteString(p.filename)
	}
	if buf.Len() > 0 {
		buf.WriteString(":")
	}
	buf.WriteString(fmt.Sprintf("%d:%d (%d)", pos.line, pos.col, pos.offset))
	if len(p.rstack) > 0 {
		if buf.Len() > 0 {
			buf.WriteString(": ")
		}
		rule := p.rstack[len(p.rstack)-1]
		if rule.displayName != "" {
			buf.WriteString("rule " + rule.displayName)
		} else {
			buf.WriteString("rule " + rule.name)
		}
	}
	pe := &parserError{Inner: err, pos: pos, prefix: buf.String(), expected: expected}
	p.errs.add(pe)
}

func (p *parser) failAt(fail bool, pos position, want string) {
	// process fail if parsing fails and not inverted or parsing succeeds and invert is set
	if fail == p.maxFailInvertExpected {
		if pos.offset < p.maxFailPos.offset {
			return
		}

		if pos.offset > p.maxFailPos.offset {
			p.maxFailPos = pos
			p.maxFailExpected = p.maxFailExpected[:0]
		}

		if p.maxFailInvertExpected {
			want = "!" + want
		}
		p.maxFailExpected = append(p.maxFailExpected, want)
	}
}

// read advances the parser to the next rune.
func (p *parser) read() {
	p.pt.offset += p.pt.w
	rn, n := utf8.DecodeRune(p.data[p.pt.offset:])
	p.pt.rn = rn
	p.pt.w = n
	p.pt.col++
	if rn == '\n' {
		p.pt.line++
		p.pt.col = 0
	}

	if rn == utf8.RuneError && n == 1 { // see utf8.DecodeRune
		if !p.allowInvalidUTF8 {
			p.addErr(errInvalidEncoding)
		}
	}
}

// restore parser position to the savepoint pt.
func (p *parser) restore(pt savepoint) {
	if p.debug {
		defer p.out(p.in("restore"))
	}
	if pt.offset == p.pt.offset {
		return
	}
	p.pt = pt
}

// Cloner is implemented by any value that has a Clone method, which returns a
// copy of the value. This is mainly used for types which are not passed by
// value (e.g map, slice, chan) or structs that contain such types.
//
// This is used in conjunction with the global state feature to create proper
// copies of the state to allow the parser to properly restore the state in
// the case of backtracking.
type Cloner interface {
	Clone() any
}

var statePool = &sync.Pool{
	New: func() any { return make(storeDict) },
}

func (sd storeDict) Discard() {
	for k := range sd {
		delete(sd, k)
	}
	statePool.Put(sd)
}

// clone and return parser current state.
func (p *parser) cloneState() storeDict {
	if p.debug {
		defer p.out(p.in("cloneState"))
	}

	state := statePool.Get().(storeDict)
	for k, v := range p.cur.state {
		if c, ok := v.(Cloner); ok {
			state[k] = c.Clone()
		} else {
			state[k] = v
		}
	}
	return state
}

// restore parser current state to the state storeDict.
// every restoreState should applied only one time for every cloned state
func (p *parser) restoreState(state storeDict) {
	if p.debug {
		defer p.out(p.in("restoreState"))
	}
	p.cur.state.Discard()
	p.cur.state = state
}

// get the slice of bytes from the savepoint start to the current position.
func (p *parser) sliceFrom(start savepoint) []byte {
	return p.data[start.position.offset:p.pt.position.offset]
}

func (p *parser) getMemoized(node any) (resultTuple, bool) {
	if len(p.memo) == 0 {
		return resultTuple{}, false
	}
	m := p.memo[p.pt.offset]
	if len(m) == 0 {
		return resultTuple{}, false
	}
	res, ok := m[node]
	return res, ok
}

func (p *parser) setMemoized(pt savepoint, node any, tuple resultTuple) {
	if p.memo == nil {
		p.memo = make(map[int]map[any]resultTuple)
	}
	m := p.memo[pt.offset]
	if m == nil {
		m = make(map[any]resultTuple)
		p.memo[pt.offset] = m
	}
	m[node] = tuple
}

func (p *parser) buildRulesTable(g *grammar) {
	p.rules = make(map[string]*rule, len(g.rules))
	for _, r := range g.rules {
		p.rules[r.name] = r
	}
}

func (p *parser) parse(g *grammar) (val any, err error) {
	if len(g.rules) == 0 {
		p.addErr(errNoRule)
		return nil, p.errs.err()
	}

	// TODO : not super critical but this could be generated
	p.buildRulesTable(g)

	if p.recover {
		// panic can be used in action code to stop parsing immediately
		// and return the panic as an error.
		defer func() {
			if e := recover(); e != nil {
				if p.debug {
					defer p.out(p.in("panic handler"))
				}
				val = nil
				switch e := e.(type) {
				case error:
					p.addErr(e)
				default:
					p.addErr(fmt.Errorf("%v", e))
				}
				err = p.errs.err()
			}
		}()
	}

	startRule, ok := p.rules[p.entrypoint]
	if !ok {
		p.addErr(errInvalidEntrypoint)
		return nil, p.errs.err()
	}

	p.read() // advance to first rune
	val, ok = p.parseRule(startRule)
	if !ok {
		if len(*p.errs) == 0 {
			// If parsing fails, but no errors have been recorded, the expected values
			// for the farthest parser position are returned as error.
			maxFailExpectedMap := make(map[string]struct{}, len(p.maxFailExpected))
			for _, v := range p.maxFailExpected {
				maxFailExpectedMap[v] = struct{}{}
			}
			expected := make([]string, 0, len(maxFailExpectedMap))
			eof := false
			if _, ok := maxFailExpectedMap["!."]; ok {
				delete(maxFailExpectedMap, "!.")
				eof = true
			}
			for k := range maxFailExpectedMap {
				expected = append(expected, k)
			}
			sort.Strings(expected)
			if eof {
				expected = append(expected, "EOF")
			}
			p.addErrAt(errors.New("no match found, expected: "+listJoin(expected, ", ", "or")), p.maxFailPos, expected)
		}

		return nil, p.errs.err()
	}
	return val, p.errs.err()
}

func listJoin(list []string, sep string, lastSep string) string {
	switch len(list) {
	case 0:
		return ""
	case 1:
		return list[0]
	default:
		return strings.Join(list[:len(list)-1], sep) + " " + lastSep + " " + list[len(list)-1]
	}
}

func (p *parser) parseRule(rule *rule) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseRule " + rule.name))
	}

	if p.memoize {
		res, ok := p.getMemoized(rule)
		if ok {
			p.restore(res.end)
			return res.v, res.b
		}
	}

	start := p.pt
	p.rstack = append(p.rstack, rule)
	p.pushV()
	val, ok := p.parseExpr(rule.expr)
	p.popV()
	p.rstack = p.rstack[:len(p.rstack)-1]
	if ok && p.debug {
		p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start)))
	}

	if p.memoize {
		p.setMemoized(start, rule, resultTuple{val, ok, p.pt})
	}
	return val, ok
}

func (p *parser) parseExpr(expr any) (any, bool) {
	var pt savepoint

	if p.memoize {
		res, ok := p.getMemoized(expr)
		if ok {
			p.restore(res.end)
			return res.v, res.b
		}
		pt = p.pt
	}

	p.ExprCnt++
	if p.ExprCnt > p.maxExprCnt {
		panic(errMaxExprCnt)
	}

	var val any
	var ok bool
	switch expr := expr.(type) {
	case *actionExpr:
		val, ok = p.parseActionExpr(expr)
	case *andCodeExpr:
		val, ok = p.parseAndCodeExpr(expr)
	case *andExpr:
		val, ok = p.parseAndExpr(expr)
	case *anyMatcher:
		val, ok = p.parseAnyMatcher(expr)
	case *charClassMatcher:
		val, ok = p.parseCharClassMatcher(expr)
	case *choiceExpr:
		val, ok = p.parseChoiceExpr(expr)
	case *labeledExpr:
		val, ok = p.parseLabeledExpr(expr)
	case *litMatcher:
		val, ok = p.parseLitMatcher(expr)
	case *notCodeExpr:
		val, ok = p.parseNotCodeExpr(expr)
	case *notExpr:
		val, ok = p.parseNotExpr(expr)
	case *oneOrMoreExpr:
		val, ok = p.parseOneOrMoreExpr(expr)
	case *recoveryExpr:
		val, ok = p.parseRecoveryExpr(expr)
	case *ruleRefExpr:
		val, ok = p.parseRuleRefExpr(expr)
	case *seqExpr:
		val, ok = p.parseSeqExpr(expr)
	case *stateCodeExpr:
		val, ok = p.parseStateCodeExpr(expr)
	case *throwExpr:
		val, ok = p.parseThrowExpr(expr)
	case *zeroOrMoreExpr:
		val, ok = p.parseZeroOrMoreExpr(expr)
	case *zeroOrOneExpr:
		val, ok = p.parseZeroOrOneExpr(expr)
	default:
		panic(fmt.Sprintf("unknown expression type %T", expr))
	}
	if p.memoize {
		p.setMemoized(pt, expr, resultTuple{val, ok, p.pt})
	}
	return val, ok
}

func (p *parser) parseActionExpr(act *actionExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseActionExpr"))
	}

	start := p.pt
	val, ok := p.parseExpr(act.expr)
	if ok {
		p.cur.pos = start.position
		p.cur.text = p.sliceFrom(start)
		state := p.cloneState()
		actVal, err := act.run(p)
		if err != nil {
			p.addErrAt(err, start.position, []string{})
		}
		p.restoreState(state)

		val = actVal
	}
	if ok && p.debug {
		p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start)))
	}
	return val, ok
}

func (p *parser) parseAndCodeExpr(and *andCodeExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseAndCodeExpr"))
	}

	state := p.cloneState()

	ok, err := and.run(p)
	if err != nil {
		p.addErr(err)
	}
	p.restoreState(state)

	return nil, ok
}

func (p *parser) parseAndExpr(and *andExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseAndExpr"))
	}

	pt := p.pt
	state := p.cloneState()
	p.pushV()
	_, ok := p.parseExpr(and.expr)
	p.popV()
	p.restoreState(state)
	p.restore(pt)

	return nil, ok
}

func (p *parser) parseAnyMatcher(any *anyMatcher) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseAnyMatcher"))
	}

	if p.pt.rn == utf8.RuneError && p.pt.w == 0 {
		// EOF - see utf8.DecodeRune
		p.failAt(false, p.pt.position, ".")
		return nil, false
	}
	start := p.pt
	p.read()
	p.failAt(true, start.position, ".")
	return p.sliceFrom(start), true
}

func (p *parser) parseCharClassMatcher(chr *charClassMatcher) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseCharClassMatcher"))
	}

	cur := p.pt.rn
	start := p.pt

	// can't match EOF
	if cur == utf8.RuneError && p.pt.w == 0 { // see utf8.DecodeRune
		p.failAt(false, start.position, chr.val)
		return nil, false
	}

	if chr.ignoreCase {
		cur = unicode.ToLower(cur)
	}

	// try to match in the list of available chars
	for _, rn := range chr.chars {
		if rn == cur {
			if chr.inverted {
				p.failAt(false, start.position, chr.val)
				return nil, false
			}
			p.read()
			p.failAt(true, start.position, chr.val)
			return p.sliceFrom(start), true
		}
	}

	// try to match in the list of ranges
	for i := 0; i < len(chr.ranges); i += 2 {
		if cur >= chr.ranges[i] && cur <= chr.ranges[i+1] {
			if chr.inverted {
				p.failAt(false, start.position, chr.val)
				return nil, false
			}
			p.read()
			p.failAt(true, start.position, chr.val)
			return p.sliceFrom(start), true
		}
	}

	// try to match in the list of Unicode classes
	for _, cl := range chr.classes {
		if unicode.Is(cl, cur) {
			if chr.inverted {
				p.failAt(false, start.position, chr.val)
				return nil, false
			}
			p.read()
			p.failAt(true, start.position, chr.val)
			return p.sliceFrom(start), true
		}
	}

	if chr.inverted {
		p.read()
		p.failAt(true, start.position, chr.val)
		return p.sliceFrom(start), true
	}
	p.failAt(false, start.position, chr.val)
	return nil, false
}

func (p *parser) incChoiceAltCnt(ch *choiceExpr, altI int) {
	choiceIdent := fmt.Sprintf("%s %d:%d", p.rstack[len(p.rstack)-1].name, ch.pos.line, ch.pos.col)
	m := p.ChoiceAltCnt[choiceIdent]
	if m == nil {
		m = make(map[string]int)
		p.ChoiceAltCnt[choiceIdent] = m
	}
	// We increment altI by 1, so the keys do not start at 0
	alt := strconv.Itoa(altI + 1)
	if altI == choiceNoMatch {
		alt = p.choiceNoMatch
	}
	m[alt]++
}

func (p *parser) parseChoiceExpr(ch *choiceExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseChoiceExpr"))
	}

	for altI, alt := range ch.alternatives {
		// dummy assignment to prevent compile error if optimized
		_ = altI

		state := p.cloneState()

		p.pushV()
		val, ok := p.parseExpr(alt)
		p.popV()
		if ok {
			p.incChoiceAltCnt(ch, altI)
			return val, ok
		}
		p.restoreState(state)
	}
	p.incChoiceAltCnt(ch, choiceNoMatch)
	return nil, false
}

func (p *parser) parseLabeledExpr(lab *labeledExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseLabeledExpr"))
	}

	p.pushV()
	val, ok := p.parseExpr(lab.expr)
	p.popV()
	if ok && lab.label != "" {
		m := p.vstack[len(p.vstack)-1]
		m[lab.label] = val
	}
	return val, ok
}

func (p *parser) parseLitMatcher(lit *litMatcher) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseLitMatcher"))
	}

	start := p.pt
	for _, want := range lit.val {
		cur := p.pt.rn
		if lit.ignoreCase {
			cur = unicode.ToLower(cur)
		}
		if cur != want {
			p.failAt(false, start.position, lit.want)
			p.restore(start)
			return nil, false
		}
		p.read()
	}
	p.failAt(true, start.position, lit.want)
	return p.sliceFrom(start), true
}

func (p *parser) parseNotCodeExpr(not *notCodeExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseNotCodeExpr"))
	}

	state := p.cloneState()

	ok, err := not.run(p)
	if err != nil {
		p.addErr(err)
	}
	p.restoreState(state)

	return nil, !ok
}

func (p *parser) parseNotExpr(not *notExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseNotExpr"))
	}

	pt := p.pt
	state := p.cloneState()
	p.pushV()
	p.maxFailInvertExpected = !p.maxFailInvertExpected
	_, ok := p.parseExpr(not.expr)
	p.maxFailInvertExpected = !p.maxFailInvertExpected
	p.popV()
	p.restoreState(state)
	p.restore(pt)

	return nil, !ok
}

func (p *parser) parseOneOrMoreExpr(expr *oneOrMoreExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseOneOrMoreExpr"))
	}

	var vals []any

	for {
		p.pushV()
		val, ok := p.parseExpr(expr.expr)
		p.popV()
		if !ok {
			if len(vals) == 0 {
				// did not match once, no match
				return nil, false
			}
			return vals, true
		}
		vals = append(vals, val)
	}
}

func (p *parser) parseRecoveryExpr(recover *recoveryExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseRecoveryExpr (" + strings.Join(recover.failureLabel, ",") + ")"))
	}

	p.pushRecovery(recover.failureLabel, recover.recoverExpr)
	val, ok := p.parseExpr(recover.expr)
	p.popRecovery()

	return val, ok
}

func (p *parser) parseRuleRefExpr(ref *ruleRefExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseRuleRefExpr " + ref.name))
	}

	if ref.name == "" {
		panic(fmt.Sprintf("%s: invalid rule: missing name", ref.pos))
	}

	rule := p.rules[ref.name]
	if rule == nil {
		p.addErr(fmt.Errorf("undefined rule: %s", ref.name))
		return nil, false
	}
	return p.parseRule(rule)
}

func (p *parser) parseSeqExpr(seq *seqExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseSeqExpr"))
	}

	vals := make([]any, 0, len(seq.exprs))

	pt := p.pt
	state := p.cloneState()
	for _, expr := range seq.exprs {
		val, ok := p.parseExpr(expr)
		if !ok {
			p.restoreState(state)
			p.restore(pt)
			return nil, false
		}
		vals = append(vals, val)
	}
	return vals, true
}

func (p *parser) parseStateCodeExpr(state *stateCodeExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseStateCodeExpr"))
	}

	err := state.run(p)
	if err != nil {
		p.addErr(err)
	}
	return nil, true
}

func (p *parser) parseThrowExpr(expr *throwExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseThrowExpr"))
	}

	for i := len(p.recoveryStack) - 1; i >= 0; i-- {
		if recoverExpr, ok := p.recoveryStack[i][expr.label]; ok {
			if val, ok := p.parseExpr(recoverExpr); ok {
				return val, ok
			}
		}
	}

	return nil, false
}

func (p *parser) parseZeroOrMoreExpr(expr *zeroOrMoreExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseZeroOrMoreExpr"))
	}

	var vals []any

	for {
		p.pushV()
		val, ok := p.parseExpr(expr.expr)
		p.popV()
		if !ok {
			return vals, true
		}
		vals = append(vals, val)
	}
}

func (p *parser) parseZeroOrOneExpr(expr *zeroOrOneExpr) (any, bool) {
	if p.debug {
		defer p.out(p.in("parseZeroOrOneExpr"))
	}

	p.pushV()
	val, _ := p.parseExpr(expr.expr)
	p.popV()
	// whether it matched or not, consider it a match
	return val, true
}

@breml
Copy link
Collaborator

breml commented Aug 31, 2023

@cch123 Yes, it is known, that the master branch has this limitation. Do you mind to checkout the code from PR #123?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants