diff --git a/gigaparsec.go b/gigaparsec.go index 2cc9f02..594eeb0 100644 --- a/gigaparsec.go +++ b/gigaparsec.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "slices" "git.codemonkeysoftware.net/b/gigaparsec/cursor" ) @@ -99,6 +100,52 @@ func Choose[In, Out any](p, q Parser[In, Out]) Parser[In, Out] { } } +func ChooseMany[In, Out any](p Parser[In, Out], ps ...Parser[In, Out]) Parser[In, Out] { + all := append([]Parser[In, Out]{p}, ps...) + return func(input State[In]) (bool, Result[In, Out], error) { + expecteds := make([][]string, 0, len(all)) + var value Out + var got string + var gotGot bool + var failed bool + for _, q := range all { + consumed, result, err := q(input) + if consumed { + return consumed, result, err + } + var qMsg Message + if err != nil { + var parseErr ParseError + if !errors.As(err, &parseErr) { + // It broke. Give up. + return consumed, result, err + } + failed = failed && true + qMsg = Message(parseErr) + } else { + if failed { + value = result.Value + failed = false + } + qMsg = result.Message + } + if !gotGot { + got = qMsg.Got + gotGot = true + } + } + msg := Message{Pos: input.Pos(), Got: got, Expected: slices.Concat(expecteds...)} + if failed { + return false, Result[In, Out]{}, ParseError(msg) + } + return false, Result[In, Out]{ + Value: value, + State: input, + Message: msg, + }, nil + } +} + // Try behaves identically to p, except that if p returns an error, // Try will pretend that no input was consumed. This allows infinite // lookahead: Since Choose only calls another parser when the previous