Error recovery with parser combinators, using Rust and nom
eyalkalderon.com
eyalkalderon.com
def Parse(G, U):
''' G: the grammar; U: the user's state. '''
r = G.StartRule()
return ParseRule(G, U, r)
def ParseRule(G, U, r):
potentials = [ ]
for p in r:
q = ParseProd(G, U, p)
if q.success:
potentials.append(q)
return U.pickBest(potentials)
def ParseProd(G, U, p):
res = [ ]
U.markTokens()
for t in p:
if t.isTerminal():
T = U.getToken()
if t != T:
return (False, [ ])
res.append(T)
else:
q = ParseRule(G, U, G.GetRule(t))
if not q[0]:
return (False, [ ])
res.append(q[1])
U.commitTokens()
return (True, res)
I'm 100% sure this code leaves a lot off the table, but a reasonably complete recursive-descent algorithm is very close to this. I use the operator-overloading thing a lot of parser-combinators design as a way to specify the grammar in the native code, without going through a stringy-API.Parser combinators are recursive descent, as in, they are just a neat way to write down a recursive descent parser using a compositional style. The reason they're popular is because they compose well and feel more declarative than traditional recursive descent.
And best of all, the final parsers are much shorter, easier to reason about, and are more directly analogous to their PEG equivalents...
I enjoyed the author's style, which made me feel as if I were following along with him on the journey of discovery. This quote, in particular, was delightful -- it revealed a scholar willing to let go of attachment to his preferred solution in exchange for something pragmatic which was "close enough". A lesson I take to heart.