I'm updating a parser from nom 4 to nom 7. I'm parsing term rewriting systems, which involves parsing a lot of first-order terms. These first-order terms can contain function symbols and constants, collectively called operators, as well as variables. The identity of each operator and variable is maintained by assigning it a numeric id, and these ids are established while parsing. The parser maintains a map between strings and ids.
In nom 4, I created a parser struct and used combinations of the method! and call_m! macros to build up the id map. In nom 7, these macros no longer exist, nor do corresponding functions.
but the compiler helpfully reminds me that this requires simultaneous unique access to self in both closures.
I was hopeful that Carry state within nom parser might have an answer, but the linked code carries a fixed map; it doesn't look like anything gets updated during parsing.
What's the idiomatic way to pass mutable state around in nom 7? Do I use some sort of interior mutability trick? Is there something else I'm missing here?
Yeah, from skimming the docs, it doesn't seem like the whole parser framework is generic over a State parameter it could keep folding over, when parsing.
So all you have left are the implicit captures, but, as you mentioned, you won't be able to get hold of exclusive references if self is captured by two closures concurrently. In that case, all you have access to are shared-access-based APIs (e.g., &self-based methods), which you can offer by having Self offer interior/shared mutability. For simple values, I recommend to use finer-grained Cells, since they perform mutation without a runtime cost. For the other cases (such as a map), you'll have to use something like a RefCell, and "tank" the runtime cost of a flag guarding against concurrent mutation; there is not that much else you can do when the API is limited in that fashion.
for reference, this is why it's generally a good practice to have alt-like APIs take a state parameter (say, of type &mut State), and then give it "back" to the closures through, for instance, a &mut State closure arg:
Alternatively, you could manually implement alt for this case if need be based on the existing alt implementation. Something like this should be equivalent (untested)
use nom::error::{ParseError,ErrorKind};
fn application(&mut self, s: &'a str) -> IResult<&'a str, Term> {
let first_err = match self.standard_application(s) {
Err(nom::Err::Error(e)) => e,
res => return res,
};
let second_err = match self.binary_application(s) {
Err(nom::Err::Error(e)) => e,
res => return res,
};
Err(nom::Err::Error(ParseError::append(s,ErrorKind::Alt,first_err.or(second_err))))
}
I make use of a variety of nom's combinators, so I'll probably thread a Cell or RefCell through my parser rather than reimplement a significant chunk of nom.
I like the idea of expanding the API to allow for a mutable state object and have opened nom issue #1419 to discuss the idea further.