I could use a parser that is equivalent to the one produced by “deriving Read” but …
A) faster, and using less space (Data.Text(.Lazy) instead of String)
B) streaming (parse a long list literal “[0,1,2, … ]” in constant space, produce the first cons of the result right after reading “[0”)
C) with some minimal error reporting (e.g., some prefix of not-consumed input)
Perhaps attoparsec with some form of early-commited choice?
I do have something (only for A) at https://codeberg.org/jwaldmann/text-read (attoparsec on lazy text) and it seems twice as fast as “deriving Read”, and uses less memory, in my tests.
Not that I’m an expert in all kind of parsing libraries, but isn’t requirement B hard to get right, when you must hope for something like a closing parenthesis? Should you have passed down the 0,1,2 to the stream consumer when blargh is what ends the text?
That said, especially with the monad transformer parsers you can add a Writer component into the stack and achieve partial output. megaparsec can lift MonadWriter since 9.5.0. Searching for generic+parser on hackage yields incremental-parser as a candidate for B.
Are you also after the convenience of deriving Read or is it okay if all the parsers are hand-rolled?
yes. when the parser sees open-bracket, it should commit to the following parser being successful, in the sense that if the closing-bracket is later found missing, the parser uses error to throw an exception.
I made this proof-of-concept
import Control.Monad.State.Lazy
import qualified Data.Text.Lazy as L
type Parser = StateT L.Text (Either String)
and I think I get the streaming behaviour (see test case at top of linked file) from sprinkling the code with
p *>! q = p *> commit q
commit :: Parser a -> Parser a
commit p =
StateT $ \ t -> Right $ case runStateT p t of
Left msg -> error msg
Right (a, s) -> (a, s)
commit is an interesting combinator. Is there prior art?
I hope this does not derail the thread too much, but I wonder whether one can use a pure and total data structure instead of error for this.
Consider: The Generic representation of lists is essentially:
type Rep [a] ~ (U1 :+: (Rec0 a :*: Rec0 [a]))
type Rep1 [] ~ U1 :+: (Par1 :*: Rec1 [])
In order to obtain a type of lists that can end in a parse error, all we have to do is to replace Rec1 [] with Rec1 (Either String :+: []) or likewise for the Rec0 [a] in the rank-0 Rep. A commited parser could produce this data structure in streaming fashion, and the consumer can inspect each Rec1 value for L1 (parse error) or R1 (parsing continues).