It's actually not too difficult to avoid backtracking if you make the subparsers smarter. I just didn't do it because it also makes them less readable, but oh well.
So right now I parse booleans like this, for instance: string("true") || string("false"), which means it tries the first parser, and on failure the second one (and therefore the consumed input must get "un-consumed").
The smarter solution is to pick the right branch directly so there is no "un-consuming" necessary. Like this:val c = one-of("tf")
if c == 't' then
string("rue")
else
string("alse")