summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorptmcg <ptmcg@austin.rr.com>2018-12-28 14:42:07 -0600
committerptmcg <ptmcg@austin.rr.com>2018-12-28 14:42:07 -0600
commit45b78401e8c224619e1b18b9cf43fc02b196676e (patch)
treefd4c4db17624f48d8efb275946240794c7f059cf
parent3288d48409269b404e24888ec90e76ee751251e3 (diff)
downloadpyparsing-git-45b78401e8c224619e1b18b9cf43fc02b196676e.tar.gz
Fix partial named results when And embedded in named MatchFirst or Or
-rw-r--r--.idea/misc.xml2
-rw-r--r--.idea/pyparsing.iml14
-rw-r--r--.idea/vcs.xml2
-rw-r--r--CHANGES11
-rw-r--r--examples/decaf_parser.py175
-rw-r--r--examples/statemachine/statemachine.py258
-rw-r--r--examples/statemachine/trafficLightDemo.py12
-rw-r--r--examples/statemachine/trafficlight.pystate37
-rw-r--r--pyparsing.py26
-rw-r--r--unitTests.py25
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
diff --git a/CHANGES b/CHANGES
index 471e75b..430dd38 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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):