diff options
-rw-r--r-- | .idea/misc.xml | 2 | ||||
-rw-r--r-- | .idea/pyparsing.iml | 14 | ||||
-rw-r--r-- | .idea/vcs.xml | 2 | ||||
-rw-r--r-- | CHANGES | 11 | ||||
-rw-r--r-- | examples/decaf_parser.py | 175 | ||||
-rw-r--r-- | examples/statemachine/statemachine.py | 258 | ||||
-rw-r--r-- | examples/statemachine/trafficLightDemo.py | 12 | ||||
-rw-r--r-- | examples/statemachine/trafficlight.pystate | 37 | ||||
-rw-r--r-- | pyparsing.py | 26 | ||||
-rw-r--r-- | unitTests.py | 25 |
10 files changed, 550 insertions, 12 deletions
diff --git a/.idea/misc.xml b/.idea/misc.xml index 65531ca..4225ef2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" /> + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (pyparsing)" project-jdk-type="Python SDK" /> </project>
\ No newline at end of file diff --git a/.idea/pyparsing.iml b/.idea/pyparsing.iml index 11892f9..ed99d55 100644 --- a/.idea/pyparsing.iml +++ b/.idea/pyparsing.iml @@ -1,18 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> <module type="PYTHON_MODULE" version="4"> <component name="NewModuleRootManager"> - <content url="file://$MODULE_DIR$/.."> + <content url="file://$MODULE_DIR$"> <excludeFolder url="file://$MODULE_DIR$/User_samples" /> + <excludeFolder url="file://$MODULE_DIR$/build" /> + <excludeFolder url="file://$MODULE_DIR$/dist" /> + <excludeFolder url="file://$MODULE_DIR$/venv" /> </content> - <orderEntry type="jdk" jdkName="Python 3.6" jdkType="Python SDK" /> + <orderEntry type="jdk" jdkName="Python 3.6 (pyparsing)" jdkType="Python SDK" /> <orderEntry type="sourceFolder" forTests="false" /> </component> - <component name="PackageRequirementsSettings"> - <option name="requirementsPath" value="" /> - </component> <component name="ReSTService"> - <option name="workdir" value="$MODULE_DIR$/../docs" /> - <option name="DOC_DIR" value="$MODULE_DIR$/../docs" /> + <option name="workdir" value="$MODULE_DIR$" /> + <option name="DOC_DIR" value="$MODULE_DIR$" /> </component> <component name="TestRunnerService"> <option name="PROJECT_TEST_RUNNER" value="Unittests" /> diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 6c0b863..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="VcsDirectoryMappings"> - <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> </component> </project>
\ No newline at end of file @@ -4,6 +4,14 @@ Change Log Version 2.3.1 - --------------- +- POSSIBLE API CHANGE: this release fixes a bug when results names were + attached to a MatchFirst or Or object containing an And object. + Previously, a results name on an And object within an enclosing MatchFirst + or Or could return just the first token in the And. Now, all the tokens + matched by the And are correctly returned. This may result in subtle + changes in the tokens returned if you have this condition in your pyparsing + scripts. + - Added unicode sets to pyparsing_unicode for Latin-A and Latin-B ranges. - Added ability to define custom unicode sets as combinations of other sets @@ -27,7 +35,8 @@ Version 2.3.1 - results. - Removed distutils fallback in setup.py. If installing the package fails, - please update to the latest version of setuptools. + please update to the latest version of setuptools. Plus overall project code + cleanup (CRLFs, whitespace, imports, etc.), thanks Jon Dufresne! Version 2.3.0 - October, 2018 diff --git a/examples/decaf_parser.py b/examples/decaf_parser.py new file mode 100644 index 0000000..c3574b3 --- /dev/null +++ b/examples/decaf_parser.py @@ -0,0 +1,175 @@ +# +# decaf_parser.py +# +# Rudimentary parser for decaf language, used in Stanford University CS143 +# (https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/handouts/030%20Decaf%20Specification.pdf) +# +# To convert this parser into one that gives more of an AST, change all the Group wrappers to add parse +# actions that will result in ASTNode classes, or statement-specific subclasses. +# +# Copyright 2018, Paul McGuire +# +""" + Program ::= Decl+ + Decl ::= VariableDecl | FunctionDecl | ClassDecl | InterfaceDecl + VariableDecl ::= Variable ; + Variable ::= Type ident + Type ::= int | double | bool | string | ident | Type [] + FunctionDecl ::= Type ident ( Formals ) StmtBlock | void ident ( Formals ) StmtBlock + Formals ::= Variable+, | e + ClassDecl ::= class ident <extends ident> <implements ident + ,> { Field* } + Field ::= VariableDecl | FunctionDecl + InterfaceDecl ::= interface ident { Prototype* } + Prototype ::= Type ident ( Formals ) ; | void ident ( Formals ) ; + StmtBlock ::= { VariableDecl* Stmt* } + Stmt ::= <Expr> ; | IfStmt | WhileStmt | ForStmt | BreakStmt | ReturnStmt | PrintStmt | StmtBlock + IfStmt ::= if ( Expr ) Stmt <else Stmt> + WhileStmt ::= while ( Expr ) Stmt + ForStmt ::= for ( <Expr> ; Expr ; <Expr> ) Stmt + ReturnStmt ::= return <Expr> ; + BreakStmt ::= break ; + PrintStmt ::= Print ( Expr+, ) ; + Expr ::= LValue = Expr | Constant | LValue | this | Call + | ( Expr ) + | Expr + Expr | Expr - Expr | Expr * Expr | Expr / Expr | Expr % Expr | - Expr + | Expr < Expr | Expr <= Expr | Expr > Expr | Expr >= Expr | Expr == Expr | Expr != Expr + | Expr && Expr | Expr || Expr | ! Expr + | ReadInteger ( ) | ReadLine ( ) | new ident | NewArray ( Expr , Typev) + LValue ::= ident | Expr . ident | Expr [ Expr ] + Call ::= ident ( Actuals ) | Expr . ident ( Actuals ) + Actuals ::= Expr+, | e + Constant ::= intConstant | doubleConstant | boolConstant | stringConstant | null +""" +import pyparsing as pp +pp.ParserElement.enablePackrat() + +# keywords +keywords = (VOID, INT, DOUBLE, BOOL, STRING, CLASS, INTERFACE, NULL, THIS, EXTENDS, IMPLEMENTS, FOR, WHILE, + IF, ELSE, RETURN, BREAK, NEW, NEWARRAY, PRINT, READINTEGER, READLINE, TRUE, FALSE) = map(pp.Keyword, + """void int double bool string class interface null this extends implements or while + if else return break new NewArray Print ReadInteger ReadLine true false""".split()) +keywords = pp.MatchFirst(list(keywords)) + +LPAR, RPAR, LBRACE, RBRACE, LBRACK, RBRACK, DOT, EQ, COMMA, SEMI = map(pp.Suppress, "(){}[].=,;") +hexConstant = pp.Regex(r"0[xX][0-9a-fA-F]+").addParseAction(lambda t: int(t[0][2:], 16)) +intConstant = hexConstant | pp.pyparsing_common.integer +doubleConstant = pp.pyparsing_common.real +boolConstant = TRUE | FALSE +stringConstant = pp.dblQuotedString +null = NULL +constant = doubleConstant | boolConstant | intConstant | stringConstant | null +ident = ~keywords + pp.Word(pp.alphas, pp.alphanums+'_') +type_ = pp.Group((INT | DOUBLE | BOOL | STRING | ident) + pp.ZeroOrMore("[]")) + +variable = type_ + ident +variable_decl = variable + SEMI + +expr = pp.Forward() +expr_parens = pp.Group(LPAR + expr + RPAR) +actuals = pp.Optional(pp.delimitedList(expr)) +call = pp.Group(ident("call_ident") + LPAR + actuals("call_args") + RPAR + | (expr_parens + pp.ZeroOrMore(DOT + ident))("call_ident_expr") + LPAR + actuals("call_args") + RPAR) +lvalue = ((ident | expr_parens) + + pp.ZeroOrMore(DOT + (ident | expr_parens)) + + pp.ZeroOrMore(LBRACK + expr + RBRACK)) +assignment = pp.Group(lvalue("lhs") + EQ + expr("rhs")) +read_integer = pp.Group(READINTEGER + LPAR + RPAR) +read_line = pp.Group(READLINE + LPAR + RPAR) +new_statement = pp.Group(NEW + ident) +new_array = pp.Group(NEWARRAY + LPAR + expr + COMMA + type_ + RPAR) +rvalue = constant | call | read_integer | read_line | new_statement | new_array | ident +arith_expr = pp.infixNotation(rvalue, + [ + ('-', 1, pp.opAssoc.RIGHT,), + (pp.oneOf("* / %"), 2, pp.opAssoc.LEFT,), + (pp.oneOf("+ -"), 2, pp.opAssoc.LEFT,), + ]) +comparison_expr = pp.infixNotation(arith_expr, + [ + ('!', 1, pp.opAssoc.RIGHT,), + (pp.oneOf("< > <= >="), 2, pp.opAssoc.LEFT,), + (pp.oneOf("== !="), 2, pp.opAssoc.LEFT,), + (pp.oneOf("&&"), 2, pp.opAssoc.LEFT,), + (pp.oneOf("||"), 2, pp.opAssoc.LEFT,), + ]) +expr <<= (assignment + | call + | THIS + | comparison_expr + | arith_expr + | lvalue + | constant + | read_integer + | read_line + | new_statement + | new_array + ) + +stmt = pp.Forward() +print_stmt = pp.Group(PRINT("statement") + LPAR + pp.Group(pp.Optional(pp.delimitedList(expr)))("args") + RPAR + SEMI) +break_stmt = pp.Group(BREAK("statement") + SEMI) +return_stmt = pp.Group(RETURN("statement") + expr + SEMI) +for_stmt = pp.Group(FOR("statement") + LPAR + pp.Optional(expr) + SEMI + expr + SEMI + pp.Optional(expr) + RPAR + stmt) +while_stmt = pp.Group(WHILE("statement") + LPAR + expr + RPAR + stmt) +if_stmt = pp.Group(IF("statement") + + LPAR + pp.Group(expr)("condition") + RPAR + + pp.Group(stmt)("then_statement") + + pp.Group(pp.Optional(ELSE + stmt))("else_statement")) +stmt_block = pp.Group(LBRACE + pp.ZeroOrMore(variable_decl) + pp.ZeroOrMore(stmt) + RBRACE) +stmt <<= (if_stmt + | while_stmt + | for_stmt + | break_stmt + | return_stmt + | print_stmt + | stmt_block + | pp.Group(expr + SEMI) + ) + +formals = pp.Optional(pp.delimitedList(variable)) +prototype = pp.Group((type_ | VOID)("return_type") + + ident("function_name") + + LPAR + formals("args") + RPAR + SEMI)("prototype") +function_decl = pp.Group((type_ | VOID)("return_type") + ident("function_name") + + LPAR + formals("args") + RPAR + + stmt_block("body"))("function_decl") + +interface_decl = pp.Group(INTERFACE + ident("interface_name") + + LBRACE + pp.ZeroOrMore(prototype)("prototypes") + RBRACE)("interface") +field = variable_decl | function_decl +class_decl = pp.Group(CLASS + ident("class_name") + + pp.Optional(EXTENDS + ident)("extends") + + pp.Optional(IMPLEMENTS + pp.delimitedList(ident))("implements") + + LBRACE + pp.ZeroOrMore(field)("fields") + RBRACE)("class_decl") + +decl = variable_decl | function_decl | class_decl | interface_decl | prototype +program = pp.OneOrMore(pp.Group(decl)) +decaf_parser = program + +stmt.runTests("""\ + sin(30); + a = 1; + b = 1 + 1; + b = 1 != 2 && false; + print("A"); + a.b = 100; + a.b = 100.0; + a[100] = b; + a[0][0] = 2; + a = 0x1234; +""") + +test_program = """ + void getenv(string var); + int main(string[] args) { + if (a > 100) { + Print(a, " is too big"); + } else if (a < 100) { + Print(a, " is too small"); + } else { + Print(a, "just right!"); + } + } +""" + +print(decaf_parser.parseString(test_program).dump()) diff --git a/examples/statemachine/statemachine.py b/examples/statemachine/statemachine.py new file mode 100644 index 0000000..7565375 --- /dev/null +++ b/examples/statemachine/statemachine.py @@ -0,0 +1,258 @@ +# stateMachine.py +# +# module to define .pystate import handler +# +# import imputil +import keyword +import sys +import os +import types +try: + import urllib.parse + url_parse = urllib.parse.urlparse +except ImportError: + import urllib + url_parse = urllib.parse + + +DEBUG = True #False + +from pyparsing import Word, Group, ZeroOrMore, alphas, \ + alphanums, ParserElement, ParseException, ParseSyntaxException, \ + Empty, LineEnd, OneOrMore, col, Keyword, pythonStyleComment, \ + StringEnd, traceParseAction + +ident = Word(alphas + "_", alphanums + "_$") + +def no_keywords_allowed(s, l, t): + wd = t[0] + return not keyword.iskeyword(wd) + +ident.addCondition(no_keywords_allowed, message="cannot use a Python keyword for state or transition identifier") + +stateTransition = ident("fromState") + "->" + ident("toState") +stateMachine = (Keyword("statemachine") + ident("name") + ":" + + OneOrMore(Group(stateTransition))("transitions")) + +namedStateTransition = (ident("fromState") + + "-(" + ident("transition") + ")->" + + ident("toState")) +namedStateMachine = (Keyword("statemachine") + ident("name") + ":" + + OneOrMore(Group(namedStateTransition))("transitions")) + + +def expand_state_definition(source, loc, tokens): + indent = " " * (col(loc, source) - 1) + statedef = [] + + # build list of states + states = set() + fromTo = {} + for tn in tokens.transitions: + states.add(tn.fromState) + states.add(tn.toState) + fromTo[tn.fromState] = tn.toState + + # define base class for state classes + baseStateClass = tokens.name + "State" + statedef.extend([ + "class %s(object):" % baseStateClass, + " def __str__(self):", + " return self.__class__.__name__", + " def next_state(self):", + " return self._next_state_class()"]) + + # define all state classes + statedef.extend( + "class {}({}): pass".format(s, baseStateClass) + for s in states) + statedef.extend( + "{}._next_state_class = {}".format(s, fromTo[s]) + for s in states if s in fromTo) + + return indent + ("\n" + indent).join(statedef) + "\n" + + +stateMachine.setParseAction(expand_state_definition) + + +def expand_named_state_definition(source, loc, tokens): + indent = " " * (col(loc, source) - 1) + statedef = [] + # build list of states and transitions + states = set() + transitions = set() + + baseStateClass = tokens.name + "State" + + fromTo = {} + for tn in tokens.transitions: + states.add(tn.fromState) + states.add(tn.toState) + transitions.add(tn.transition) + if tn.fromState in fromTo: + fromTo[tn.fromState][tn.transition] = tn.toState + else: + fromTo[tn.fromState] = {tn.transition: tn.toState} + + # add entries for terminal states + for s in states: + if s not in fromTo: + fromTo[s] = {} + + # define state transition class + statedef.extend([ + "class %sTransition:" % baseStateClass, + " def __str__(self):", + " return self.transitionName", + ]) + statedef.extend( + "{} = {}Transition()".format(tn, baseStateClass) + for tn in transitions) + statedef.extend("{}.transitionName = '{}'".format(tn, tn) + for tn in transitions) + + # define base class for state classes + excmsg = "'" + tokens.name + \ + '.%s does not support transition "%s"' \ + "'% (self, tn)" + statedef.extend([ + "class %s(object):" % baseStateClass, + " def __str__(self):", + " return self.__class__.__name__", + " def next_state(self,tn):", + " try:", + " return self.tnmap[tn]()", + " except KeyError:", + " raise Exception(%s)" % excmsg, + " def __getattr__(self,name):", + " raise Exception(%s)" % excmsg, + ]) + + # define all state classes + for s in states: + statedef.append("class %s(%s): pass" % + (s, baseStateClass)) + + # define state transition maps and transition methods + for s in states: + trns = list(fromTo[s].items()) + statedef.append("%s.tnmap = {%s}" % + (s, ",".join("%s:%s" % tn for tn in trns))) + statedef.extend([ + "%s.%s = staticmethod(lambda : %s())" % + (s, tn_, to_) + for tn_, to_ in trns + ]) + + return indent + ("\n" + indent).join(statedef) + "\n" + + +namedStateMachine.setParseAction( + expand_named_state_definition) + + +# ====================================================================== +# NEW STUFF - Matt Anderson, 2009-11-26 +# ====================================================================== +class SuffixImporter(object): + """An importer designed using the mechanism defined in :pep:`302`. I read + the PEP, and also used Doug Hellmann's PyMOTW article `Modules and + Imports`_, as a pattern. + + .. _`Modules and Imports`: http://www.doughellmann.com/PyMOTW/sys/imports.html + + Define a subclass that specifies a :attr:`suffix` attribute, and + implements a :meth:`process_filedata` method. Then call the classmethod + :meth:`register` on your class to actually install it in the appropriate + places in :mod:`sys`. """ + + scheme = 'suffix' + suffix = None + path_entry = None + + @classmethod + def trigger_url(cls): + if cls.suffix is None: + raise ValueError('%s.suffix is not set' % cls.__name__) + return 'suffix:%s' % cls.suffix + + @classmethod + def register(cls): + sys.path_hooks.append(cls) + sys.path.append(cls.trigger_url()) + + def __init__(self, path_entry): + pr = url_parse(str(path_entry)) + if pr.scheme != self.scheme or pr.path != self.suffix: + raise ImportError() + self.path_entry = path_entry + self._found = {} + + def checkpath_iter(self, fullname): + for dirpath in sys.path: + # if the value in sys.path_importer_cache is None, then this + # path *should* be imported by the builtin mechanism, and the + # entry is thus a path to a directory on the filesystem; + # if it's not None, then some other importer is in charge, and + # it probably isn't even a filesystem path + if sys.path_importer_cache.get(dirpath, False) is None: + checkpath = os.path.join( + dirpath, '{}.{}'.format(fullname, self.suffix)) + yield checkpath + + def find_module(self, fullname, path=None): + for checkpath in self.checkpath_iter(fullname): + if os.path.isfile(checkpath): + self._found[fullname] = checkpath + return self + return None + + def load_module(self, fullname): + assert fullname in self._found + if fullname in sys.modules: + module = sys.modules[fullname] + else: + sys.modules[fullname] = module = types.ModuleType(fullname) + data = None + with open(self._found[fullname]) as f: + data = f.read() + + module.__dict__.clear() + module.__file__ = self._found[fullname] + module.__name__ = fullname + module.__loader__ = self + self.process_filedata(module, data) + return module + + def process_filedata(self, module, data): + pass + + +class PystateImporter(SuffixImporter): + suffix = 'pystate' + + def process_filedata(self, module, data): + # MATT-NOTE: re-worked :func:`get_state_machine` + + # convert any statemachine expressions + stateMachineExpr = (stateMachine | namedStateMachine).ignore(pythonStyleComment) + generated_code = stateMachineExpr.transformString(data) + + if DEBUG: print(generated_code) + + # compile code object from generated code + # (strip trailing spaces and tabs, compile doesn't like + # dangling whitespace) + COMPILE_MODE = 'exec' + + codeobj = compile(generated_code.rstrip(" \t"), + module.__file__, + COMPILE_MODE) + + exec(codeobj, module.__dict__) + + +PystateImporter.register() + +print("registered {!r} importer".format(PystateImporter.suffix))
\ No newline at end of file diff --git a/examples/statemachine/trafficLightDemo.py b/examples/statemachine/trafficLightDemo.py new file mode 100644 index 0000000..30fe934 --- /dev/null +++ b/examples/statemachine/trafficLightDemo.py @@ -0,0 +1,12 @@ +import statemachine +import trafficlight + +tl = trafficLight.Red() +for i in range(10): + print(tl, end='') + print(("STOP", "GO")[tl.carsCanGo]) + tl.crossingSignal() + tl.delay() + print() + + tl = tl.nextState() diff --git a/examples/statemachine/trafficlight.pystate b/examples/statemachine/trafficlight.pystate new file mode 100644 index 0000000..3de7b7f --- /dev/null +++ b/examples/statemachine/trafficlight.pystate @@ -0,0 +1,37 @@ +# define state machine +statemachine TrafficLight: + Red -> Green + Green -> Yellow + Yellow -> Red + + +# define some class level constants +Red.carsCanGo = False +Yellow.carsCanGo = True +Green.carsCanGo = True + + +# setup some class level methods +def flashCrosswalk(s): + def flash(): + print("%s...%s...%s" % (s, s, s)) + + return flash + + +Red.crossingSignal = staticmethod(flashCrosswalk("WALK")) +Yellow.crossingSignal = staticmethod(flashCrosswalk("DONT WALK")) +Green.crossingSignal = staticmethod(flashCrosswalk("DONT WALK")) + + +# setup some instance methods +def wait(nSeconds): + def waitFn(self): + print("<wait %d seconds>" % nSeconds) + + return waitFn + + +Red.delay = wait(20) +Yellow.delay = wait(3) +Green.delay = wait(15)
\ No newline at end of file diff --git a/pyparsing.py b/pyparsing.py index 9be5fed..48e5e23 100644 --- a/pyparsing.py +++ b/pyparsing.py @@ -94,7 +94,7 @@ classes inherit from. Use the docstrings for examples of how to: """ __version__ = "2.3.1" -__versionTime__ = "22 Dec 2018 15:31 UTC" +__versionTime__ = "28 Dec 2018 20:39 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -3629,6 +3629,11 @@ class And(ParseExpression): self.skipWhitespace = self.exprs[0].skipWhitespace self.callPreparse = True + def streamline(self): + super(And, self).streamline() + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + return self + def parseImpl( self, instring, loc, doActions=True ): # pass False as last arg to _parse for first element, since we already # pre-parsed the string as part of our And pre-parsing @@ -3700,6 +3705,11 @@ class Or(ParseExpression): else: self.mayReturnEmpty = True + def streamline(self): + super(Or, self).streamline() + self.saveAsList = any(e.saveAsList for e in self.exprs) + return self + def parseImpl( self, instring, loc, doActions=True ): maxExcLoc = -1 maxException = None @@ -3779,9 +3789,15 @@ class MatchFirst(ParseExpression): super(MatchFirst,self).__init__(exprs, savelist) if self.exprs: self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + # self.saveAsList = any(e.saveAsList for e in self.exprs) else: self.mayReturnEmpty = True + def streamline(self): + super(MatchFirst, self).streamline() + self.saveAsList = any(e.saveAsList for e in self.exprs) + return self + def parseImpl( self, instring, loc, doActions=True ): maxExcLoc = -1 maxException = None @@ -3888,6 +3904,12 @@ class Each(ParseExpression): self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) self.skipWhitespace = True self.initExprGroups = True + self.saveAsList = True + + def streamline(self): + super(Each, self).streamline() + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + return self def parseImpl( self, instring, loc, doActions=True ): if self.initExprGroups: @@ -4639,7 +4661,7 @@ class Group(TokenConverter): """ def __init__( self, expr ): super(Group,self).__init__( expr ) - self.saveAsList = True + self.saveAsList = expr.saveAsList def postParse( self, instring, loc, tokenlist ): return [ tokenlist ] diff --git a/unitTests.py b/unitTests.py index 851a98a..cd219bf 100644 --- a/unitTests.py +++ b/unitTests.py @@ -3825,6 +3825,31 @@ class IndentedBlockTest(ParseTestCase): self.assertEqual(result.c.c1, 200, "invalid indented block result")
self.assertEqual(result.c.c2.c21, 999, "invalid indented block result")
+class ParseResultsWithNameMatchFirst(ParseTestCase):
+ def runTest(self):
+ import pyparsing as pp
+ expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird')
+ expr_b = pp.Literal('the') + pp.Literal('bird')
+ expr = (expr_a | expr_b)('rexp')
+ expr.runTests("""\
+ not the bird
+ the bird
+ """)
+ self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split())
+ self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split())
+
+class ParseResultsWithNameOr(ParseTestCase):
+ def runTest(self):
+ import pyparsing as pp
+ expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird')
+ expr_b = pp.Literal('the') + pp.Literal('bird')
+ expr = (expr_a ^ expr_b)('rexp')
+ expr.runTests("""\
+ not the bird
+ the bird
+ """)
+ self.assertEqual(list(expr.parseString('not the bird')['rexp']), 'not the bird'.split())
+ self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split())
class MiscellaneousParserTests(ParseTestCase):
def runTest(self):
|