You are already pretty close to having this working. There are a number of cases where a pyparsing parser dynamically adjusts itself based on text that was previously parsed. The trick is to use a Forward
placeholder expression, and then insert the desired values into the placeholder as part of a parse action (very close to what you have in place now). Like this:
Element = Forward()
Alphabet = OneOrMore(~lineEnd + oneOf(list(alphas)))
def alphaHold(toks):
Element << oneOf(toks.asList())
Alphabet.setParseAction(alphaHold)
From here, I think the rest of your code works fairly well as-is. Actually, you won't even need the line validating function, as pyparsing will only match valid element names as elements using this method.
You might find that pyparsing's error reporting is a little fuzzy. You can get things to be a little better using '-' instead of '+' in some judicious places. Since pyparsing uses ParseExceptions for all of its internal signalling of expression matches/mismatches, it does not automatically recognize when you have gotten into a defined expression, but then have an invalid match on a contained expression. You can tell pyparsing to detect this using the '-' operator, like this:
ListDef = listName + '=' - OneOrMore(~lineEnd + Element)
Once pyparsing gets a name and an '=' sign, then any invalid Element found will immediately raise a ParseSyntaxException
, which will stop pyparsing's scan of the text at that point, and report the exception at the location of the invalid element.