You have the answer to your question as you expressed it.
You need to save a state. The hard part is identifying the state. Saving is easy.
Your problem is that the parser “has a state” when it begins to parse any grammar rule. (This becomes useless if you use the LALR parser, which combines the parsing of many rules into one state). This condition consists of:
- input state (for example, where is the input stream?).
- the state of the parsing stack (which left context is still being considered?)
- where the parser should continue to work, and where to continue the failure
When you understand and choose an alternative, as you described, you need to "save state", run the trial syntax for the first time. If successful, you can discard the saved state and continue. If it fails, restore the state and try the 2nd (and n-th option). (It’s easier to be brainless and just keep state regardless of whether you are faced with an alternative, but it is up to you).
How can you keep the state? Push it onto the stack. (You usually have a parser column, this is a pretty convenient place! If you don't like this, add another stack, but you will find it, and the syntax column will generally move in sync, I just create a parser column containing an entry with everything what I need, including input space, and you will find a “call stack” convenient for parts of the state, see below).
First of all, you need to save the input location; this is most likely the position of the source of the file, and probably the buffer index to optimize the reasons. This is just a scalar, so it’s pretty easy to save. Recovering an input stream may be more difficult; there is no guarantee that the parser input scanner is anywhere. Therefore, you need to move the file, re-read any buffer and move the pointer of any input buffer. Some simple checks can make it statistically cheap: keep the file position of the first character of any buffer; then deteimining, if you need to re-read the buffer, it is a matter of comparing the saved file position with the position of the original buffer file. The rest should be obvious.
You will fall back through fewer buffers (for example, your parser is faster) if you change your grammar to minimize this. In your specific grammar, you have "(a | ab) c", which can be manually rewritten as "b? C". The latter, at least, will not return through all that it represents.
The odd part keeps the parsing stack. Well, you don’t need it, because your trial syntax only extends your parsing stack that you have and restores it to the parsing state that you have if your subparameter succeeds or fails.
“where the parser continues to fail” and “where it succeeds” are just two scalars. You can think of them as parser code block indices and program counters (like continuations) or as the return address in the call stack (see "Another parallel stack!"), Followed by a conditional success / failure test.
If you want to know more about the latter, check out my SO answer on hand-coded recursive descent parsers.
If you are starting to build trees or doing something else as a side effect of parsing, you will have to think about how to capture / save the state of the side object and restore it. But whatever it is, you end up pushing the stack.