diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_Q0hBTkdFUw== --- /dev/null +++ b/CHANGES @@ -0,0 +1,1440 @@ +========== +Change Log +========== + +Version 1.5.2 - April, 2009 +------------------------------ +- Added pyparsing_py3.py module, so that Python 3 users can use + pyparsing by changing their pyparsing import statement to: + + import pyparsing_py3 + + Thanks for help from Patrick Laban and his friend Geremy + Condra on the pyparsing wiki. + +- Removed __slots__ declaration on ParseBaseException, for + compatibility with IronPython 2.0.1. Raised by David + Lawler on the pyparsing wiki, thanks David! + +- Fixed bug in SkipTo/failOn handling - caught by eagle eye + cpennington on the pyparsing wiki! + +- Fixed second bug in SkipTo when using the ignore constructor + argument, reported by Catherine Devlin, thanks! + +- Fixed obscure bug reported by Eike Welk when using a class + as a ParseAction with an errant __getitem__ method. + +- Simplified exception stack traces when reporting parse + exceptions back to caller of parseString or parseFile - thanks + to a tip from Peter Otten on comp.lang.python. + +- Changed behavior of scanString to avoid infinitely looping on + expressions that match zero-length strings. Prompted by a + question posted by ellisonbg on the wiki. + +- Enhanced classes that take a list of expressions (And, Or, + MatchFirst, and Each) to accept generator expressions also. + This can be useful when generating lists of alternative + expressions, as in this case, where the user wanted to match + any repetitions of '+', '*', '#', or '.', but not mixtures + of them (that is, match '+++', but not '+-+'): + + codes = "+*#." + format = MatchFirst(Word(c) for c in codes) + + Based on a problem posed by Denis Spir on the Python tutor + list. + +- Added new example eval_arith.py, which extends the example + simpleArith.py to actually evaluate the parsed expressions. + + +Version 1.5.1 - October, 2008 +------------------------------- +- Added new helper method originalTextFor, to replace the use of + the current keepOriginalText parse action. Now instead of + using the parse action, as in: + + fullName = Word(alphas) + Word(alphas) + fullName.setParseAction(keepOriginalText) + + (in this example, we used keepOriginalText to restore any white + space that may have been skipped between the first and last + names) + You can now write: + + fullName = originalTextFor(Word(alphas) + Word(alphas)) + + The implementation of originalTextFor is simpler and faster than + keepOriginalText, and does not depend on using the inspect or + imp modules. + +- Added optional parseAll argument to parseFile, to be consistent + with parseAll argument to parseString. Posted by pboucher on the + pyparsing wiki, thanks! + +- Added failOn argument to SkipTo, so that grammars can define + literal strings or pyparsing expressions which, if found in the + skipped text, will cause SkipTo to fail. Useful to prevent + SkipTo from reading past terminating expression. Instigated by + question posed by Aki Niimura on the pyparsing wiki. + +- Fixed bug in nestedExpr if multi-character expressions are given + for nesting delimiters. Patch provided by new pyparsing user, + Hans-Martin Gaudecker - thanks, H-M! + +- Removed dependency on xml.sax.saxutils.escape, and included + internal implementation instead - proposed by Mike Droettboom on + the pyparsing mailing list, thanks Mike! Also fixed erroneous + mapping in replaceHTMLEntity of " to ', now correctly maps + to ". (Also added support for mapping ' to '.) + +- Fixed typo in ParseResults.insert, found by Alejandro Dubrovsky, + good catch! + +- Added __dir__() methods to ParseBaseException and ParseResults, + to support new dir() behavior in Py2.6 and Py3.0. If dir() is + called on a ParseResults object, the returned list will include + the base set of attribute names, plus any results names that are + defined. + +- Fixed bug in ParseResults.asXML(), in which the first named + item within a ParseResults gets reported with an <ITEM> tag + instead of with the correct results name. + +- Fixed bug in '-' error stop, when '-' operator is used inside a + Combine expression. + +- Reverted generator expression to use list comprehension, for + better compatibility with old versions of Python. Reported by + jester/artixdesign on the SourceForge pyparsing discussion list. + +- Fixed bug in parseString(parseAll=True), when the input string + ends with a comment or whitespace. + +- Fixed bug in LineStart and LineEnd that did not recognize any + special whitespace chars defined using ParserElement.setDefault- + WhitespaceChars, found while debugging an issue for Marek Kubica, + thanks for the new test case, Marek! + +- Made Forward class more tolerant of subclassing. + + +Version 1.5.0 - June, 2008 +-------------------------- +This version of pyparsing includes work on two long-standing +FAQ's: support for forcing parsing of the complete input string +(without having to explicitly append StringEnd() to the grammar), +and a method to improve the mechanism of detecting where syntax +errors occur in an input string with various optional and +alternative paths. This release also includes a helper method +to simplify definition of indentation-based grammars. With +these changes (and the past few minor updates), I thought it was +finally time to bump the minor rev number on pyparsing - so +1.5.0 is now available! Read on... + +- AT LAST!!! You can now call parseString and have it raise + an exception if the expression does not parse the entire + input string. This has been an FAQ for a LONG time. + + The parseString method now includes an optional parseAll + argument (default=False). If parseAll is set to True, then + the given parse expression must parse the entire input + string. (This is equivalent to adding StringEnd() to the + end of the expression.) The default value is False to + retain backward compatibility. + + Inspired by MANY requests over the years, most recently by + ecir-hana on the pyparsing wiki! + +- Added new operator '-' for composing grammar sequences. '-' + behaves just like '+' in creating And expressions, but '-' + is used to mark grammar structures that should stop parsing + immediately and report a syntax error, rather than just + backtracking to the last successful parse and trying another + alternative. For instance, running the following code: + + port_definition = Keyword("port") + '=' + Word(nums) + entity_definition = Keyword("entity") + "{" + + Optional(port_definition) + "}" + + entity_definition.parseString("entity { port 100 }") + + pyparsing fails to detect the missing '=' in the port definition. + But, since this expression is optional, pyparsing then proceeds + to try to match the closing '}' of the entity_definition. Not + finding it, pyparsing reports that there was no '}' after the '{' + character. Instead, we would like pyparsing to parse the 'port' + keyword, and if not followed by an equals sign and an integer, + to signal this as a syntax error. + + This can now be done simply by changing the port_definition to: + + port_definition = Keyword("port") - '=' + Word(nums) + + Now after successfully parsing 'port', pyparsing must also find + an equals sign and an integer, or it will raise a fatal syntax + exception. + + By judicious insertion of '-' operators, a pyparsing developer + can have their grammar report much more informative syntax error + messages. + + Patches and suggestions proposed by several contributors on + the pyparsing mailing list and wiki - special thanks to + Eike Welk and Thomas/Poldy on the pyparsing wiki! + +- Added indentedBlock helper method, to encapsulate the parse + actions and indentation stack management needed to keep track of + indentation levels. Use indentedBlock to define grammars for + indentation-based grouping grammars, like Python's. + + indentedBlock takes up to 3 parameters: + - blockStatementExpr - expression defining syntax of statement + that is repeated within the indented block + - indentStack - list created by caller to manage indentation + stack (multiple indentedBlock expressions + within a single grammar should share a common indentStack) + - indent - boolean indicating whether block must be indented + beyond the the current level; set to False for block of + left-most statements (default=True) + + A valid block must contain at least one indented statement. + +- Fixed bug in nestedExpr in which ignored expressions needed + to be set off with whitespace. Reported by Stefaan Himpe, + nice catch! + +- Expanded multiplication of an expression by a tuple, to + accept tuple values of None: + . expr*(n,None) or expr*(n,) is equivalent + to expr*n + ZeroOrMore(expr) + (read as "at least n instances of expr") + . expr*(None,n) is equivalent to expr*(0,n) + (read as "0 to n instances of expr") + . expr*(None,None) is equivalent to ZeroOrMore(expr) + . expr*(1,None) is equivalent to OneOrMore(expr) + + Note that expr*(None,n) does not raise an exception if + more than n exprs exist in the input stream; that is, + expr*(None,n) does not enforce a maximum number of expr + occurrences. If this behavior is desired, then write + expr*(None,n) + ~expr + +- Added None as a possible operator for operatorPrecedence. + None signifies "no operator", as in multiplying m times x + in "y=mx+b". + +- Fixed bug in Each, reported by Michael Ramirez, in which the + order of terms in the Each affected the parsing of the results. + Problem was due to premature grouping of the expressions in + the overall Each during grammar construction, before the + complete Each was defined. Thanks, Michael! + +- Also fixed bug in Each in which Optional's with default values + were not getting the defaults added to the results of the + overall Each expression. + +- Fixed a bug in Optional in which results names were not + assigned if a default value was supplied. + +- Cleaned up Py3K compatibility statements, including exception + construction statements, and better equivalence between _ustr + and basestring, and __nonzero__ and __bool__. + + +Version 1.4.11 - February, 2008 +------------------------------- +- With help from Robert A. Clark, this version of pyparsing + is compatible with Python 3.0a3. Thanks for the help, + Robert! + +- Added WordStart and WordEnd positional classes, to support + expressions that must occur at the start or end of a word. + Proposed by piranha on the pyparsing wiki, good idea! + +- Added matchOnlyAtCol helper parser action, to simplify + parsing log or data files that have optional fields that are + column dependent. Inspired by a discussion thread with + hubritic on comp.lang.python. + +- Added withAttribute.ANY_VALUE as a match-all value when using + withAttribute. Used to ensure that an attribute is present, + without having to match on the actual attribute value. + +- Added get() method to ParseResults, similar to dict.get(). + Suggested by new pyparsing user, Alejandro Dubrovksy, thanks! + +- Added '==' short-cut to see if a given string matches a + pyparsing expression. For instance, you can now write: + + integer = Word(nums) + if "123" == integer: + # do something + + print [ x for x in "123 234 asld".split() if x==integer ] + # prints ['123', '234'] + +- Simplified the use of nestedExpr when using an expression for + the opening or closing delimiters. Now the content expression + will not have to explicitly negate closing delimiters. Found + while working with dfinnie on GHOP Task #277, thanks! + +- Fixed bug when defining ignorable expressions that are + later enclosed in a wrapper expression (such as ZeroOrMore, + OneOrMore, etc.) - found while working with Prabhu + Gurumurthy, thanks Prahbu! + +- Fixed bug in withAttribute in which keys were automatically + converted to lowercase, making it impossible to match XML + attributes with uppercase characters in them. Using with- + Attribute requires that you reference attributes in all + lowercase if parsing HTML, and in correct case when parsing + XML. + +- Changed '<<' operator on Forward to return None, since this + is really used as a pseudo-assignment operator, not as a + left-shift operator. By returning None, it is easier to + catch faulty statements such as a << b | c, where precedence + of operations causes the '|' operation to be performed + *after* inserting b into a, so no alternation is actually + implemented. The correct form is a << (b | c). With this + change, an error will be reported instead of silently + clipping the alternative term. (Note: this may break some + existing code, but if it does, the code had a silent bug in + it anyway.) Proposed by wcbarksdale on the pyparsing wiki, + thanks! + +- Several unit tests were added to pyparsing's regression + suite, courtesy of the Google Highly-Open Participation + Contest. Thanks to all who administered and took part in + this event! + + +Version 1.4.10 - December 9, 2007 +--------------------------------- +- Fixed bug introduced in v1.4.8, parse actions were called for + intermediate operator levels, not just the deepest matching + operation level. Again, big thanks to Torsten Marek for + helping isolate this problem! + + +Version 1.4.9 - December 8, 2007 +-------------------------------- +- Added '*' multiplication operator support when creating + grammars, accepting either an integer, or a two-integer + tuple multiplier, as in: + ipAddress = Word(nums) + ('.'+Word(nums))*3 + usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) + If multiplying by a tuple, the two integer values represent + min and max multiples. Suggested by Vincent of eToy.com, + great idea, Vincent! + +- Fixed bug in nestedExpr, original version was overly greedy! + Thanks to Michael Ramirez for raising this issue. + +- Fixed internal bug in ParseResults - when an item was deleted, + the key indices were not updated. Thanks to Tim Mitchell for + posting a bugfix patch to the SF bug tracking system! + +- Fixed internal bug in operatorPrecedence - when the results of + a right-associative term were sent to a parse action, the wrong + tokens were sent. Reported by Torsten Marek, nice job! + +- Added pop() method to ParseResults. If pop is called with an + integer or with no arguments, it will use list semantics and + update the ParseResults' list of tokens. If pop is called with + a non-integer (a string, for instance), then it will use dict + semantics and update the ParseResults' internal dict. + Suggested by Donn Ingle, thanks Donn! + +- Fixed quoted string built-ins to accept '\xHH' hex characters + within the string. + + +Version 1.4.8 - October, 2007 +----------------------------- +- Added new helper method nestedExpr to easily create expressions + that parse lists of data in nested parentheses, braces, brackets, + etc. + +- Added withAttribute parse action helper, to simplify creating + filtering parse actions to attach to expressions returned by + makeHTMLTags and makeXMLTags. Use withAttribute to qualify a + starting tag with one or more required attribute values, to avoid + false matches on common tags such as <TD> or <DIV>. + +- Added new examples nested.py and withAttribute.py to demonstrate + the new features. + +- Added performance speedup to grammars using operatorPrecedence, + instigated by Stefan Reich�r - thanks for the feedback, Stefan! + +- Fixed bug/typo when deleting an element from a ParseResults by + using the element's results name. + +- Fixed whitespace-skipping bug in wrapper classes (such as Group, + Suppress, Combine, etc.) and when using setDebug(), reported by + new pyparsing user dazzawazza on SourceForge, nice job! + +- Added restriction to prevent defining Word or CharsNotIn expressions + with minimum length of 0 (should use Optional if this is desired), + and enhanced docstrings to reflect this limitation. Issue was + raised by Joey Tallieu, who submitted a patch with a slightly + different solution. Thanks for taking the initiative, Joey, and + please keep submitting your ideas! + +- Fixed bug in makeHTMLTags that did not detect HTML tag attributes + with no '= value' portion (such as "<td nowrap>"), reported by + hamidh on the pyparsing wiki - thanks! + +- Fixed minor bug in makeHTMLTags and makeXMLTags, which did not + accept whitespace in closing tags. + + +Version 1.4.7 - July, 2007 +-------------------------- +- NEW NOTATION SHORTCUT: ParserElement now accepts results names using + a notational shortcut, following the expression with the results name + in parentheses. So this: + + stats = "AVE:" + realNum.setResultsName("average") + \ + "MIN:" + realNum.setResultsName("min") + \ + "MAX:" + realNum.setResultsName("max") + + can now be written as this: + + stats = "AVE:" + realNum("average") + \ + "MIN:" + realNum("min") + \ + "MAX:" + realNum("max") + + The intent behind this change is to make it simpler to define results + names for significant fields within the expression, while keeping + the grammar syntax clean and uncluttered. + +- Fixed bug when packrat parsing is enabled, with cached ParseResults + being updated by subsequent parsing. Reported on the pyparsing + wiki by Kambiz, thanks! + +- Fixed bug in operatorPrecedence for unary operators with left + associativity, if multiple operators were given for the same term. + +- Fixed bug in example simpleBool.py, corrected precedence of "and" vs. + "or" operations. + +- Fixed bug in Dict class, in which keys were converted to strings + whether they needed to be or not. Have narrowed this logic to + convert keys to strings only if the keys are ints (which would + confuse __getitem__ behavior for list indexing vs. key lookup). + +- Added ParserElement method setBreak(), which will invoke the pdb + module's set_trace() function when this expression is about to be + parsed. + +- Fixed bug in StringEnd in which reading off the end of the input + string raises an exception - should match. Resolved while + answering a question for Shawn on the pyparsing wiki. + + +Version 1.4.6 - April, 2007 +--------------------------- +- Simplified constructor for ParseFatalException, to support common + exception construction idiom: + raise ParseFatalException, "unexpected text: 'Spanish Inquisition'" + +- Added method getTokensEndLoc(), to be called from within a parse action, + for those parse actions that need both the starting *and* ending + location of the parsed tokens within the input text. + +- Enhanced behavior of keepOriginalText so that named parse fields are + preserved, even though tokens are replaced with the original input + text matched by the current expression. Also, cleaned up the stack + traversal to be more robust. Suggested by Tim Arnold - thanks, Tim! + +- Fixed subtle bug in which countedArray (and similar dynamic + expressions configured in parse actions) failed to match within Or, + Each, FollowedBy, or NotAny. Reported by Ralf Vosseler, thanks for + your patience, Ralf! + +- Fixed Unicode bug in upcaseTokens and downcaseTokens parse actions, + scanString, and default debugging actions; reported (and patch submitted) + by Nikolai Zamkovoi, spasibo! + +- Fixed bug when saving a tuple as a named result. The returned + token list gave the proper tuple value, but accessing the result by + name only gave the first element of the tuple. Reported by + Poromenos, nice catch! + +- Fixed bug in makeHTMLTags/makeXMLTags, which failed to match tag + attributes with namespaces. + +- Fixed bug in SkipTo when setting include=True, to have the skipped-to + tokens correctly included in the returned data. Reported by gunars on + the pyparsing wiki, thanks! + +- Fixed typobug in OnceOnly.reset method, omitted self argument. + Submitted by eike welk, thanks for the lint-picking! + +- Added performance enhancement to Forward class, suggested by + akkartik on the pyparsing Wiki discussion, nice work! + +- Added optional asKeyword to Word constructor, to indicate that the + given word pattern should be matched only as a keyword, that is, it + should only match if it is within word boundaries. + +- Added S-expression parser to examples directory. + +- Added macro substitution example to examples directory. + +- Added holaMundo.py example, excerpted from Marco Alfonso's blog - + muchas gracias, Marco! + +- Modified internal cyclic references in ParseResults to use weakrefs; + this should help reduce the memory footprint of large parsing + programs, at some cost to performance (3-5%). Suggested by bca48150 on + the pyparsing wiki, thanks! + +- Enhanced the documentation describing the vagaries and idiosyncracies + of parsing strings with embedded tabs, and the impact on: + . parse actions + . scanString + . col and line helper functions + (Suggested by eike welk in response to some unexplained inconsistencies + between parsed location and offsets in the input string.) + +- Cleaned up internal decorators to preserve function names, + docstrings, etc. + + +Version 1.4.5 - December, 2006 +------------------------------ +- Removed debugging print statement from QuotedString class. Sorry + for not stripping this out before the 1.4.4 release! + +- A significant performance improvement, the first one in a while! + For my Verilog parser, this version of pyparsing is about double the + speed - YMMV. + +- Added support for pickling of ParseResults objects. (Reported by + Jeff Poole, thanks Jeff!) + +- Fixed minor bug in makeHTMLTags that did not recognize tag attributes + with embedded '-' or '_' characters. Also, added support for + passing expressions to makeHTMLTags and makeXMLTags, and used this + feature to define the globals anyOpenTag and anyCloseTag. + +- Fixed error in alphas8bit, I had omitted the y-with-umlaut character. + +- Added punc8bit string to complement alphas8bit - it contains all the + non-alphabetic, non-blank 8-bit characters. + +- Added commonHTMLEntity expression, to match common HTML "ampersand" + codes, such as "<", ">", "&", " ", and """. This + expression also defines a results name 'entity', which can be used + to extract the entity field (that is, "lt", "gt", etc.). Also added + built-in parse action replaceHTMLEntity, which can be attached to + commonHTMLEntity to translate "<", ">", "&", " ", and + """ to "<", ">", "&", " ", and "'". + +- Added example, htmlStripper.py, that strips HTML tags and scripts + from HTML pages. It also translates common HTML entities to their + respective characters. + + +Version 1.4.4 - October, 2006 +------------------------------- +- Fixed traceParseAction decorator to also trap and record exception + returns from parse actions, and to handle parse actions with 0, + 1, 2, or 3 arguments. + +- Enhanced parse action normalization to support using classes as + parse actions; that is, the class constructor is called at parse + time and the __init__ function is called with 0, 1, 2, or 3 + arguments. If passing a class as a parse action, the __init__ + method must use one of the valid parse action parameter list + formats. (This technique is useful when using pyparsing to compile + parsed text into a series of application objects - see the new + example simpleBool.py.) + +- Fixed bug in ParseResults when setting an item using an integer + index. (Reported by Christopher Lambacher, thanks!) + +- Fixed whitespace-skipping bug, patch submitted by Paolo Losi - + grazie, Paolo! + +- Fixed bug when a Combine contained an embedded Forward expression, + reported by cie on the pyparsing wiki - good catch! + +- Fixed listAllMatches bug, when a listAllMatches result was + nested within another result. (Reported by don pasquale on + comp.lang.python, well done!) + +- Fixed bug in ParseResults items() method, when returning an item + marked as listAllMatches=True + +- Fixed bug in definition of cppStyleComment (and javaStyleComment) + in which '//' line comments were not continued to the next line + if the line ends with a '\'. (Reported by eagle-eyed Ralph + Corderoy!) + +- Optimized re's for cppStyleComment and quotedString for better + re performance - also provided by Ralph Corderoy, thanks! + +- Added new example, indentedGrammarExample.py, showing how to + define a grammar using indentation to show grouping (as Python + does for defining statement nesting). Instigated by an e-mail + discussion with Andrew Dalke, thanks Andrew! + +- Added new helper operatorPrecedence (based on e-mail list discussion + with Ralph Corderoy and Paolo Losi), to facilitate definition of + grammars for expressions with unary and binary operators. For + instance, this grammar defines a 6-function arithmetic expression + grammar, with unary plus and minus, proper operator precedence,and + right- and left-associativity: + + expr = operatorPrecedence( operand, + [("!", 1, opAssoc.LEFT), + ("^", 2, opAssoc.RIGHT), + (oneOf("+ -"), 1, opAssoc.RIGHT), + (oneOf("* /"), 2, opAssoc.LEFT), + (oneOf("+ -"), 2, opAssoc.LEFT),] + ) + + Also added example simpleArith.py and simpleBool.py to provide + more detailed code samples using this new helper method. + +- Added new helpers matchPreviousLiteral and matchPreviousExpr, for + creating adaptive parsing expressions that match the same content + as was parsed in a previous parse expression. For instance: + + first = Word(nums) + matchExpr = first + ":" + matchPreviousLiteral(first) + + will match "1:1", but not "1:2". Since this matches at the literal + level, this will also match the leading "1:1" in "1:10". + + In contrast: + + first = Word(nums) + matchExpr = first + ":" + matchPreviousExpr(first) + + will *not* match the leading "1:1" in "1:10"; the expressions are + evaluated first, and then compared, so "1" is compared with "10". + +- Added keepOriginalText parse action. Sometimes pyparsing's + whitespace-skipping leaves out too much whitespace. Adding this + parse action will restore any internal whitespace for a parse + expression. This is especially useful when defining expressions + for scanString or transformString applications. + +- Added __add__ method for ParseResults class, to better support + using Python sum built-in for summing ParseResults objects returned + from scanString. + +- Added reset method for the new OnlyOnce class wrapper for parse + actions (to allow a grammar to be used multiple times). + +- Added optional maxMatches argument to scanString and searchString, + to short-circuit scanning after 'n' expression matches are found. + + +Version 1.4.3 - July, 2006 +------------------------------ +- Fixed implementation of multiple parse actions for an expression + (added in 1.4.2). + . setParseAction() reverts to its previous behavior, setting + one (or more) actions for an expression, overwriting any + action or actions previously defined + . new method addParseAction() appends one or more parse actions + to the list of parse actions attached to an expression + Now it is harder to accidentally append parse actions to an + expression, when what you wanted to do was overwrite whatever had + been defined before. (Thanks, Jean-Paul Calderone!) + +- Simplified interface to parse actions that do not require all 3 + parse action arguments. Very rarely do parse actions require more + than just the parsed tokens, yet parse actions still require all + 3 arguments including the string being parsed and the location + within the string where the parse expression was matched. With this + release, parse actions may now be defined to be called as: + . fn(string,locn,tokens) (the current form) + . fn(locn,tokens) + . fn(tokens) + . fn() + The setParseAction and addParseAction methods will internally decorate + the provided parse actions with compatible wrappers to conform to + the full (string,locn,tokens) argument sequence. + +- REMOVED SUPPORT FOR RETURNING PARSE LOCATION FROM A PARSE ACTION. + I announced this in March, 2004, and gave a final warning in the last + release. Now you can return a tuple from a parse action, and it will + be treated like any other return value (i.e., the tuple will be + substituted for the incoming tokens passed to the parse action, + which is useful when trying to parse strings into tuples). + +- Added setFailAction method, taking a callable function fn that + takes the arguments fn(s,loc,expr,err) where: + . s - string being parsed + . loc - location where expression match was attempted and failed + . expr - the parse expression that failed + . err - the exception thrown + The function returns no values. It may throw ParseFatalException + if it is desired to stop parsing immediately. + (Suggested by peter21081944 on wikispaces.com) + +- Added class OnlyOnce as helper wrapper for parse actions. OnlyOnce + only permits a parse action to be called one time, after which + all subsequent calls throw a ParseException. + +- Added traceParseAction decorator to help debug parse actions. + Simply insert "@traceParseAction" ahead of the definition of your + parse action, and each invocation will be displayed, along with + incoming arguments, and returned value. + +- Fixed bug when copying ParserElements using copy() or + setResultsName(). (Reported by Dan Thill, great catch!) + +- Fixed bug in asXML() where token text contains <, >, and & + characters - generated XML now escapes these as <, > and + &. (Reported by Jacek Sieka, thanks!) + +- Fixed bug in SkipTo() when searching for a StringEnd(). (Reported + by Pete McEvoy, thanks Pete!) + +- Fixed "except Exception" statements, the most critical added as part + of the packrat parsing enhancement. (Thanks, Erick Tryzelaar!) + +- Fixed end-of-string infinite looping on LineEnd and StringEnd + expressions. (Thanks again to Erick Tryzelaar.) + +- Modified setWhitespaceChars to return self, to be consistent with + other ParserElement modifiers. (Suggested by Erick Tryzelaar.) + +- Fixed bug/typo in new ParseResults.dump() method. + +- Fixed bug in searchString() method, in which only the first token of + an expression was returned. searchString() now returns a + ParseResults collection of all search matches. + +- Added example program removeLineBreaks.py, a string transformer that + converts text files with hard line-breaks into one with line breaks + only between paragraphs. + +- Added example program listAllMatches.py, to illustrate using the + listAllMatches option when specifying results names (also shows new + support for passing lists to oneOf). + +- Added example program linenoExample.py, to illustrate using the + helper methods lineno, line, and col, and returning objects from a + parse action. + +- Added example program parseListString.py, to which can parse the + string representation of a Python list back into a true list. Taken + mostly from my PyCon presentation examples, but now with support + for tuple elements, too! + + + +Version 1.4.2 - April 1, 2006 (No foolin'!) +------------------------------------------- +- Significant speedup from memoizing nested expressions (a technique + known as "packrat parsing"), thanks to Chris Lesniewski-Laas! Your + mileage may vary, but my Verilog parser almost doubled in speed to + over 600 lines/sec! + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method ParserElement.enablePackrat(). If + your program uses psyco to "compile as you go", you must call + enablePackrat before calling psyco.full(). If you do not do this, + Python will crash. For best results, call enablePackrat() immediately + after importing pyparsing. + +- Added new helper method countedArray(expr), for defining patterns that + start with a leading integer to indicate the number of array elements, + followed by that many elements, matching the given expr parse + expression. For instance, this two-liner: + wordArray = countedArray(Word(alphas)) + print wordArray.parseString("3 Practicality beats purity")[0] + returns the parsed array of words: + ['Practicality', 'beats', 'purity'] + The leading token '3' is suppressed, although it is easily obtained + from the length of the returned array. + (Inspired by e-mail discussion with Ralf Vosseler.) + +- Added support for attaching multiple parse actions to a single + ParserElement. (Suggested by Dan "Dang" Griffith - nice idea, Dan!) + +- Added support for asymmetric quoting characters in the recently-added + QuotedString class. Now you can define your own quoted string syntax + like "<<This is a string in double angle brackets.>>". To define + this custom form of QuotedString, your code would define: + dblAngleQuotedString = QuotedString('<<',endQuoteChar='>>') + QuotedString also supports escaped quotes, escape character other + than '\', and multiline. + +- Changed the default value returned internally by Optional, so that + None can be used as a default value. (Suggested by Steven Bethard - + I finally saw the light!) + +- Added dump() method to ParseResults, to make it easier to list out + and diagnose values returned from calling parseString. + +- A new example, a search query string parser, submitted by Steven + Mooij and Rudolph Froger - a very interesting application, thanks! + +- Added an example that parses the BNF in Python's Grammar file, in + support of generating Python grammar documentation. (Suggested by + J H Stovall.) + +- A new example, submitted by Tim Cera, of a flexible parser module, + using a simple config variable to adjust parsing for input formats + that have slight variations - thanks, Tim! + +- Added an example for parsing Roman numerals, showing the capability + of parse actions to "compile" Roman numerals into their integer + values during parsing. + +- Added a new docs directory, for additional documentation or help. + Currently, this includes the text and examples from my recent + presentation at PyCon. + +- Fixed another typo in CaselessKeyword, thanks Stefan Behnel. + +- Expanded oneOf to also accept tuples, not just lists. This really + should be sufficient... + +- Added deprecation warnings when tuple is returned from a parse action. + Looking back, I see that I originally deprecated this feature in March, + 2004, so I'm guessing people really shouldn't have been using this + feature - I'll drop it altogether in the next release, which will + allow users to return a tuple from a parse action (which is really + handy when trying to reconstuct tuples from a tuple string + representation!). + + +Version 1.4.1 - February, 2006 +------------------------------ +- Converted generator expression in QuotedString class to list + comprehension, to retain compatibility with Python 2.3. (Thanks, Titus + Brown for the heads-up!) + +- Added searchString() method to ParserElement, as an alternative to + using "scanString(instring).next()[0][0]" to search through a string + looking for a substring matching a given parse expression. (Inspired by + e-mail conversation with Dave Feustel.) + +- Modified oneOf to accept lists of strings as well as a single string + of space-delimited literals. (Suggested by Jacek Sieka - thanks!) + +- Removed deprecated use of Upcase in pyparsing test code. (Also caught by + Titus Brown.) + +- Removed lstrip() call from Literal - too aggressive in stripping + whitespace which may be valid for some grammars. (Point raised by Jacek + Sieka). Also, made Literal more robust in the event of passing an empty + string. + +- Fixed bug in replaceWith when returning None. + +- Added cautionary documentation for Forward class when assigning a + MatchFirst expression, as in: + fwdExpr << a | b | c + Precedence of operators causes this to be evaluated as: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. Users must + explicitly group the values inserted into the Forward: + fwdExpr << (a | b | c) + (Suggested by Scot Wilcoxon - thanks, Scot!) + + +Version 1.4 - January 18, 2006 +------------------------------ +- Added Regex class, to permit definition of complex embedded expressions + using regular expressions. (Enhancement provided by John Beisley, great + job!) + +- Converted implementations of Word, oneOf, quoted string, and comment + helpers to utilize regular expression matching. Performance improvements + in the 20-40% range. + +- Added QuotedString class, to support definition of non-standard quoted + strings (Suggested by Guillaume Proulx, thanks!) + +- Added CaselessKeyword class, to streamline grammars with, well, caseless + keywords (Proposed by Stefan Behnel, thanks!) + +- Fixed bug in SkipTo, when using an ignoreable expression. (Patch provided + by Anonymous, thanks, whoever-you-are!) + +- Fixed typo in NoMatch class. (Good catch, Stefan Behnel!) + +- Fixed minor bug in _makeTags(), using string.printables instead of + pyparsing.printables. + +- Cleaned up some of the expressions created by makeXXXTags helpers, to + suppress extraneous <> characters. + +- Added some grammar definition-time checking to verify that a grammar is + being built using proper ParserElements. + +- Added examples: + . LAparser.py - linear algebra C preprocessor (submitted by Mike Ellis, + thanks Mike!) + . wordsToNum.py - converts word description of a number back to + the original number (such as 'one hundred and twenty three' -> 123) + . updated fourFn.py to support unary minus, added BNF comments + + +Version 1.3.3 - September 12, 2005 +---------------------------------- +- Improved support for Unicode strings that would be returned using + srange. Added greetingInKorean.py example, for a Korean version of + "Hello, World!" using Unicode. (Thanks, June Kim!) + +- Added 'hexnums' string constant (nums+"ABCDEFabcdef") for defining + hexadecimal value expressions. + +- NOTE: ===THIS CHANGE MAY BREAK EXISTING CODE=== + Modified tag and results definitions returned by makeHTMLTags(), + to better support the looseness of HTML parsing. Tags to be + parsed are now caseless, and keys generated for tag attributes are + now converted to lower case. + + Formerly, makeXMLTags("XYZ") would return a tag with results + name of "startXYZ", this has been changed to "startXyz". If this + tag is matched against '<XYZ Abc="1" DEF="2" ghi="3">', the + matched keys formerly would be "Abc", "DEF", and "ghi"; keys are + now converted to lower case, giving keys of "abc", "def", and + "ghi". These changes were made to try to address the lax + case sensitivity agreement between start and end tags in many + HTML pages. + + No changes were made to makeXMLTags(), which assumes more rigorous + parsing rules. + + Also, cleaned up case-sensitivity bugs in closing tags, and + switched to using Keyword instead of Literal class for tags. + (Thanks, Steve Young, for getting me to look at these in more + detail!) + +- Added two helper parse actions, upcaseTokens and downcaseTokens, + which will convert matched text to all uppercase or lowercase, + respectively. + +- Deprecated Upcase class, to be replaced by upcaseTokens parse + action. + +- Converted messages sent to stderr to use warnings module, such as + when constructing a Literal with an empty string, one should use + the Empty() class or the empty helper instead. + +- Added ' ' (space) as an escapable character within a quoted + string. + +- Added helper expressions for common comment types, in addition + to the existing cStyleComment (/*...*/) and htmlStyleComment + (<!-- ... -->) + . dblSlashComment = // ... (to end of line) + . cppStyleComment = cStyleComment or dblSlashComment + . javaStyleComment = cppStyleComment + . pythonStyleComment = # ... (to end of line) + + + +Version 1.3.2 - July 24, 2005 +----------------------------- +- Added Each class as an enhanced version of And. 'Each' requires + that all given expressions be present, but may occur in any order. + Special handling is provided to group ZeroOrMore and OneOrMore + elements that occur out-of-order in the input string. You can also + construct 'Each' objects by joining expressions with the '&' + operator. When using the Each class, results names are strongly + recommended for accessing the matched tokens. (Suggested by Pradam + Amini - thanks, Pradam!) + +- Stricter interpretation of 'max' qualifier on Word elements. If the + 'max' attribute is specified, matching will fail if an input field + contains more than 'max' consecutive body characters. For example, + previously, Word(nums,max=3) would match the first three characters + of '0123456', returning '012' and continuing parsing at '3'. Now, + when constructed using the max attribute, Word will raise an + exception with this string. + +- Cleaner handling of nested dictionaries returned by Dict. No + longer necessary to dereference sub-dictionaries as element [0] of + their parents. + === NOTE: THIS CHANGE MAY BREAK SOME EXISTING CODE, BUT ONLY IF + PARSING NESTED DICTIONARIES USING THE LITTLE-USED DICT CLASS === + (Prompted by discussion thread on the Python Tutor list, with + contributions from Danny Yoo, Kent Johnson, and original post by + Liam Clarke - thanks all!) + + + +Version 1.3.1 - June, 2005 +---------------------------------- +- Added markInputline() method to ParseException, to display the input + text line location of the parsing exception. (Thanks, Stefan Behnel!) + +- Added setDefaultKeywordChars(), so that Keyword definitions using a + custom keyword character set do not all need to add the keywordChars + constructor argument (similar to setDefaultWhitespaceChars()). + (suggested by rzhanka on the SourceForge pyparsing forum.) + +- Simplified passing debug actions to setDebugAction(). You can now + pass 'None' for a debug action if you want to take the default + debug behavior. To suppress a particular debug action, you can pass + the pyparsing method nullDebugAction. + +- Refactored parse exception classes, moved all behavior to + ParseBaseException, and the former ParseException is now a subclass of + ParseBaseException. Added a second subclass, ParseFatalException, as + a subclass of ParseBaseException. User-defined parse actions can raise + ParseFatalException if a data inconsistency is detected (such as a + begin-tag/end-tag mismatch), and this will stop all parsing immediately. + (Inspired by e-mail thread with Michele Petrazzo - thanks, Michelle!) + +- Added helper methods makeXMLTags and makeHTMLTags, that simplify the + definition of XML or HTML tag parse expressions for a given tagname. + Both functions return a pair of parse expressions, one for the opening + tag (that is, '<tagname>') and one for the closing tag ('</tagname>'). + The opening tagame also recognizes any attribute definitions that have + been included in the opening tag, as well as an empty tag (one with a + trailing '/', as in '<BODY/>' which is equivalent to '<BODY></BODY>'). + makeXMLTags uses stricter XML syntax for attributes, requiring that they + be enclosed in double quote characters - makeHTMLTags is more lenient, + and accepts single-quoted strings or any contiguous string of characters + up to the next whitespace character or '>' character. Attributes can + be retrieved as dictionary or attribute values of the returned results + from the opening tag. + +- Added example minimath2.py, a refinement on fourFn.py that adds + an interactive session and support for variables. (Thanks, Steven Siew!) + +- Added performance improvement, up to 20% reduction! (Found while working + with Wolfgang Borgert on performance tuning of his TTCN3 parser.) + +- And another performance improvement, up to 25%, when using scanString! + (Found while working with Henrik Westlund on his C header file scanner.) + +- Updated UML diagrams to reflect latest class/method changes. + + +Version 1.3 - March, 2005 +---------------------------------- +- Added new Keyword class, as a special form of Literal. Keywords + must be followed by whitespace or other non-keyword characters, to + distinguish them from variables or other identifiers that just + happen to start with the same characters as a keyword. For instance, + the input string containing "ifOnlyIfOnly" will match a Literal("if") + at the beginning and in the middle, but will fail to match a + Keyword("if"). Keyword("if") will match only strings such as "if only" + or "if(only)". (Proposed by Wolfgang Borgert, and Berteun Damman + separately requested this on comp.lang.python - great idea!) + +- Added setWhitespaceChars() method to override the characters to be + skipped as whitespace before matching a particular ParseElement. Also + added the class-level method setDefaultWhitespaceChars(), to allow + users to override the default set of whitespace characters (space, + tab, newline, and return) for all subsequently defined ParseElements. + (Inspired by Klaas Hofstra's inquiry on the Sourceforge pyparsing + forum.) + +- Added helper parse actions to support some very common parse + action use cases: + . replaceWith(replStr) - replaces the matching tokens with the + provided replStr replacement string; especially useful with + transformString() + . removeQuotes - removes first and last character from string enclosed + in quotes (note - NOT the same as the string strip() method, as only + a single character is removed at each end) + +- Added copy() method to ParseElement, to make it easier to define + different parse actions for the same basic parse expression. (Note, copy + is implicitly called when using setResultsName().) + + + (The following changes were posted to CVS as Version 1.2.3 - + October-December, 2004) + +- Added support for Unicode strings in creating grammar definitions. + (Big thanks to Gavin Panella!) + +- Added constant alphas8bit to include the following 8-bit characters: + ������������������������������������������������������������� + +- Added srange() function to simplify definition of Word elements, using + regexp-like '[A-Za-z0-9]' syntax. This also simplifies referencing + common 8-bit characters. + +- Fixed bug in Dict when a single element Dict was embedded within another + Dict. (Thanks Andy Yates for catching this one!) + +- Added 'formatted' argument to ParseResults.asXML(). If set to False, + suppresses insertion of whitespace for pretty-print formatting. Default + equals True for backward compatibility. + +- Added setDebugActions() function to ParserElement, to allow user-defined + debugging actions. + +- Added support for escaped quotes (either in \', \", or doubled quote + form) to the predefined expressions for quoted strings. (Thanks, Ero + Carrera!) + +- Minor performance improvement (~5%) converting "char in string" tests + to "char in dict". (Suggested by Gavin Panella, cool idea!) + + +Version 1.2.2 - September 27, 2004 +---------------------------------- +- Modified delimitedList to accept an expression as the delimiter, instead + of only accepting strings. + +- Modified ParseResults, to convert integer field keys to strings (to + avoid confusion with list access). + +- Modified Combine, to convert all embedded tokens to strings before + combining. + +- Fixed bug in MatchFirst in which parse actions would be called for + expressions that only partially match. (Thanks, John Hunter!) + +- Fixed bug in fourFn.py example that fixes right-associativity of ^ + operator. (Thanks, Andrea Griffini!) + +- Added class FollowedBy(expression), to look ahead in the input string + without consuming tokens. + +- Added class NoMatch that never matches any input. Can be useful in + debugging, and in very specialized grammars. + +- Added example pgn.py, for parsing chess game files stored in Portable + Game Notation. (Thanks, Alberto Santini!) + + +Version 1.2.1 - August 19, 2004 +------------------------------- +- Added SkipTo(expression) token type, simplifying grammars that only + want to specify delimiting expressions, and want to match any characters + between them. + +- Added helper method dictOf(key,value), making it easier to work with + the Dict class. (Inspired by Pavel Volkovitskiy, thanks!). + +- Added optional argument listAllMatches (default=False) to + setResultsName(). Setting listAllMatches to True overrides the default + modal setting of tokens to results names; instead, the results name + acts as an accumulator for all matching tokens within the local + repetition group. (Suggested by Amaury Le Leyzour - thanks!) + +- Fixed bug in ParseResults, throwing exception when trying to extract + slice, or make a copy using [:]. (Thanks, Wilson Fowlie!) + +- Fixed bug in transformString() when the input string contains <TAB>'s + (Thanks, Rick Walia!). + +- Fixed bug in returning tokens from un-Grouped And's, Or's and + MatchFirst's, where too many tokens would be included in the results, + confounding parse actions and returned results. + +- Fixed bug in naming ParseResults returned by And's, Or's, and Match + First's. + +- Fixed bug in LineEnd() - matching this token now correctly consumes + and returns the end of line "\n". + +- Added a beautiful example for parsing Mozilla calendar files (Thanks, + Petri Savolainen!). + +- Added support for dynamically modifying Forward expressions during + parsing. + + +Version 1.2 - 20 June 2004 +-------------------------- +- Added definition for htmlComment to help support HTML scanning and + parsing. + +- Fixed bug in generating XML for Dict classes, in which trailing item was + duplicated in the output XML. + +- Fixed release bug in which scanExamples.py was omitted from release + files. + +- Fixed bug in transformString() when parse actions are not defined on the + outermost parser element. + +- Added example urlExtractor.py, as another example of using scanString + and parse actions. + + +Version 1.2beta3 - 4 June 2004 +------------------------------ +- Added White() token type, analogous to Word, to match on whitespace + characters. Use White in parsers with significant whitespace (such as + configuration file parsers that use indentation to indicate grouping). + Construct White with a string containing the whitespace characters to be + matched. Similar to Word, White also takes optional min, max, and exact + parameters. + +- As part of supporting whitespace-signficant parsing, added parseWithTabs() + method to ParserElement, to override the default behavior in parseString + of automatically expanding tabs to spaces. To retain tabs during + parsing, call parseWithTabs() before calling parseString(), parseFile() or + scanString(). (Thanks, Jean-Guillaume Paradis for catching this, and for + your suggestions on whitespace-significant parsing.) + +- Added transformString() method to ParseElement, as a complement to + scanString(). To use transformString, define a grammar and attach a parse + action to the overall grammar that modifies the returned token list. + Invoking transformString() on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. transformString() returns the resulting transformed string. + (Note: transformString() does *not* automatically expand tabs to spaces.) + Also added scanExamples.py to the examples directory to show sample uses of + scanString() and transformString(). + +- Removed group() method that was introduced in beta2. This turns out NOT to + be equivalent to nesting within a Group() object, and I'd prefer not to sow + more seeds of confusion. + +- Fixed behavior of asXML() where tags for groups were incorrectly duplicated. + (Thanks, Brad Clements!) + +- Changed beta version message to display to stderr instead of stdout, to + make asXML() easier to use. (Thanks again, Brad.) + + +Version 1.2beta2 - 19 May 2004 +------------------------------ +- *** SIMPLIFIED API *** - Parse actions that do not modify the list of tokens + no longer need to return a value. This simplifies those parse actions that + use the list of tokens to update a counter or record or display some of the + token content; these parse actions can simply end without having to specify + 'return toks'. + +- *** POSSIBLE API INCOMPATIBILITY *** - Fixed CaselessLiteral bug, where the + returned token text was not the original string (as stated in the docs), + but the original string converted to upper case. (Thanks, Dang Griffith!) + **NOTE: this may break some code that relied on this erroneous behavior. + Users should scan their code for uses of CaselessLiteral.** + +- *** POSSIBLE CODE INCOMPATIBILITY *** - I have renamed the internal + attributes on ParseResults from 'dict' and 'list' to '__tokdict' and + '__toklist', to avoid collisions with user-defined data fields named 'dict' + and 'list'. Any client code that accesses these attributes directly will + need to be modified. Hopefully the implementation of methods such as keys(), + items(), len(), etc. on ParseResults will make such direct attribute + accessess unnecessary. + +- Added asXML() method to ParseResults. This greatly simplifies the process + of parsing an input data file and generating XML-structured data. + +- Added getName() method to ParseResults. This method is helpful when + a grammar specifies ZeroOrMore or OneOrMore of a MatchFirst or Or + expression, and the parsing code needs to know which expression matched. + (Thanks, Eric van der Vlist, for this idea!) + +- Added items() and values() methods to ParseResults, to better support using + ParseResults as a Dictionary. + +- Added parseFile() as a convenience function to parse the contents of an + entire text file. Accepts either a file name or a file object. (Thanks + again, Dang!) + +- Added group() method to And, Or, and MatchFirst, as a short-cut alternative + to enclosing a construct inside a Group object. + +- Extended fourFn.py to support exponentiation, and simple built-in functions. + +- Added EBNF parser to examples, including a demo where it parses its own + EBNF! (Thanks to Seo Sanghyeon!) + +- Added Delphi Form parser to examples, dfmparse.py, plus a couple of + sample Delphi forms as tests. (Well done, Dang!) + +- Another performance speedup, 5-10%, inspired by Dang! Plus about a 20% + speedup, by pre-constructing and cacheing exception objects instead of + constructing them on the fly. + +- Fixed minor bug when specifying oneOf() with 'caseless=True'. + +- Cleaned up and added a few more docstrings, to improve the generated docs. + + +Version 1.1.2 - 21 Mar 2004 +--------------------------- +- Fixed minor bug in scanString(), so that start location is at the start of + the matched tokens, not at the start of the whitespace before the matched + tokens. + +- Inclusion of HTML documentation, generated using Epydoc. Reformatted some + doc strings to better generate readable docs. (Beautiful work, Ed Loper, + thanks for Epydoc!) + +- Minor performance speedup, 5-15% + +- And on a process note, I've used the unittest module to define a series of + unit tests, to help avoid the embarrassment of the version 1.1 snafu. + + +Version 1.1.1 - 6 Mar 2004 +-------------------------- +- Fixed critical bug introduced in 1.1, which broke MatchFirst(!) token + matching. + **THANK YOU, SEO SANGHYEON!!!** + +- Added "from future import __generators__" to permit running under + pre-Python 2.3. + +- Added example getNTPservers.py, showing how to use pyparsing to extract + a text pattern from the HTML of a web page. + + +Version 1.1 - 3 Mar 2004 +------------------------- +- ***Changed API*** - While testing out parse actions, I found that the value + of loc passed in was not the starting location of the matched tokens, but + the location of the next token in the list. With this version, the location + passed to the parse action is now the starting location of the tokens that + matched. + + A second part of this change is that the return value of parse actions no + longer needs to return a tuple containing both the location and the parsed + tokens (which may optionally be modified); parse actions only need to return + the list of tokens. Parse actions that return a tuple are deprecated; they + will still work properly for conversion/compatibility, but this behavior will + be removed in a future version. + +- Added validate() method, to help diagnose infinite recursion in a grammar tree. + validate() is not 100% fool-proof, but it can help track down nasty infinite + looping due to recursively referencing the same grammar construct without some + intervening characters. + +- Cleaned up default listing of some parse element types, to more closely match + ordinary BNF. Instead of the form <classname>:[contents-list], some changes + are: + . And(token1,token2,token3) is "{ token1 token2 token3 }" + . Or(token1,token2,token3) is "{ token1 ^ token2 ^ token3 }" + . MatchFirst(token1,token2,token3) is "{ token1 | token2 | token3 }" + . Optional(token) is "[ token ]" + . OneOrMore(token) is "{ token }..." + . ZeroOrMore(token) is "[ token ]..." + +- Fixed an infinite loop in oneOf if the input string contains a duplicated + option. (Thanks Brad Clements) + +- Fixed a bug when specifying a results name on an Optional token. (Thanks + again, Brad Clements) + +- Fixed a bug introduced in 1.0.6 when I converted quotedString to use + CharsNotIn; I accidentally permitted quoted strings to span newlines. I have + fixed this in this version to go back to the original behavior, in which + quoted strings do *not* span newlines. + +- Fixed minor bug in HTTP server log parser. (Thanks Jim Richardson) + + +Version 1.0.6 - 13 Feb 2004 +---------------------------- +- Added CharsNotIn class (Thanks, Lee SangYeong). This is the opposite of + Word, in that it is constructed with a set of characters *not* to be matched. + (This enhancement also allowed me to clean up and simplify some of the + definitions for quoted strings, cStyleComment, and restOfLine.) + +- **MINOR API CHANGE** - Added joinString argument to the __init__ method of + Combine (Thanks, Thomas Kalka). joinString defaults to "", but some + applications might choose some other string to use instead, such as a blank + or newline. joinString was inserted as the second argument to __init__, + so if you have code that specifies an adjacent value, without using + 'adjacent=', this code will break. + +- Modified LineStart to recognize the start of an empty line. + +- Added optional caseless flag to oneOf(), to create a list of CaselessLiteral + tokens instead of Literal tokens. + +- Added some enhancements to the SQL example: + . Oracle-style comments (Thanks to Harald Armin Massa) + . simple WHERE clause + +- Minor performance speedup - 5-15% + + +Version 1.0.5 - 19 Jan 2004 +---------------------------- +- Added scanString() generator method to ParseElement, to support regex-like + pattern-searching + +- Added items() list to ParseResults, to return named results as a + list of (key,value) pairs + +- Fixed memory overflow in asList() for deeply nested ParseResults (Thanks, + Sverrir Valgeirsson) + +- Minor performance speedup - 10-15% + + +Version 1.0.4 - 8 Jan 2004 +--------------------------- +- Added positional tokens StringStart, StringEnd, LineStart, and LineEnd + +- Added commaSeparatedList to pre-defined global token definitions; also added + commasep.py to the examples directory, to demonstrate the differences between + parsing comma-separated data and simple line-splitting at commas + +- Minor API change: delimitedList does not automatically enclose the + list elements in a Group, but makes this the responsibility of the caller; + also, if invoked using 'combine=True', the list delimiters are also included + in the returned text (good for scoped variables, such as a.b.c or a::b::c, or + for directory paths such as a/b/c) + +- Performance speed-up again, 30-40% + +- Added httpServerLogParser.py to examples directory, as this is + a common parsing task + + +Version 1.0.3 - 23 Dec 2003 +--------------------------- +- Performance speed-up again, 20-40% + +- Added Python distutils installation setup.py, etc. (thanks, Dave Kuhlman) + + +Version 1.0.2 - 18 Dec 2003 +--------------------------- +- **NOTE: Changed API again!!!** (for the last time, I hope) + + + Renamed module from parsing to pyparsing, to better reflect Python + linkage. + +- Also added dictExample.py to examples directory, to illustrate + usage of the Dict class. + + +Version 1.0.1 - 17 Dec 2003 +--------------------------- +- **NOTE: Changed API!** + + + Renamed 'len' argument on Word.__init__() to 'exact' + +- Performance speed-up, 10-30% + + +Version 1.0.0 - 15 Dec 2003 +--------------------------- +- Initial public release + +Version 0.1.1 thru 0.1.17 - October-November, 2003 +-------------------------------------------------- +- initial development iterations: + - added Dict, Group + - added helper methods oneOf, delimitedList + - added helpers quotedString (and double and single), restOfLine, cStyleComment + - added MatchFirst as an alternative to the slower Or + - added UML class diagram + - fixed various logic bugs diff --git a/HowToUsePyparsing.html b/HowToUsePyparsing.html new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_SG93VG9Vc2VQeXBhcnNpbmcuaHRtbA== --- /dev/null +++ b/HowToUsePyparsing.html @@ -0,0 +1,1284 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.5: http://docutils.sourceforge.net/" /> +<title>Using the pyparsing module</title> +<meta name="author" content="Paul McGuire" /> +<meta name="date" content="October, 2008" /> +<meta name="copyright" content="Copyright © 2003-2008 Paul McGuire." /> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: html4css1.css 5196 2007-06-03 20:25:28Z wiemann $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left { + clear: left } + +img.align-right { + clear: right } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font-family: serif ; + font-size: 100% } + +pre.literal-block, pre.doctest-block { + margin-left: 2em ; + margin-right: 2em } + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document" id="using-the-pyparsing-module"> +<h1 class="title">Using the pyparsing module</h1> +<table class="docinfo" frame="void" rules="none"> +<col class="docinfo-name" /> +<col class="docinfo-content" /> +<tbody valign="top"> +<tr><th class="docinfo-name">Author:</th> +<td>Paul McGuire</td></tr> +<tr><th class="docinfo-name">Address:</th> +<td><pre class="address"> +<a class="first last reference external" href="mailto:ptmcg@users.sourceforge.net">ptmcg@users.sourceforge.net</a> +</pre> +</td></tr> +<tr><th class="docinfo-name">Revision:</th> +<td>1.5.1</td></tr> +<tr><th class="docinfo-name">Date:</th> +<td>October, 2008</td></tr> +<tr><th class="docinfo-name">Copyright:</th> +<td>Copyright © 2003-2008 Paul McGuire.</td></tr> +</tbody> +</table> +<table class="docutils field-list" frame="void" rules="none"> +<col class="field-name" /> +<col class="field-body" /> +<tbody valign="top"> +<tr class="field"><th class="field-name">abstract:</th><td class="field-body">This document provides how-to instructions for the +pyparsing library, an easy-to-use Python module for constructing +and executing basic text parsers. The pyparsing module is useful +for evaluating user-definable +expressions, processing custom application language commands, or +extracting data from formatted reports.</td> +</tr> +</tbody> +</table> +<div class="contents topic" id="contents"> +<p class="topic-title first">Contents</p> +<ul class="auto-toc simple"> +<li><a class="reference internal" href="#steps-to-follow" id="id1">1 Steps to follow</a><ul class="auto-toc"> +<li><a class="reference internal" href="#hello-world" id="id2">1.1 Hello, World!</a></li> +<li><a class="reference internal" href="#new-features-in-1-5-1" id="id3">1.2 New features in 1.5.1</a></li> +<li><a class="reference internal" href="#new-features-in-1-5-0" id="id4">1.3 New features in 1.5.0</a></li> +<li><a class="reference internal" href="#usage-notes" id="id5">1.4 Usage notes</a></li> +</ul> +</li> +<li><a class="reference internal" href="#classes" id="id6">2 Classes</a><ul class="auto-toc"> +<li><a class="reference internal" href="#classes-in-the-pyparsing-module" id="id7">2.1 Classes in the pyparsing module</a></li> +<li><a class="reference internal" href="#basic-parserelement-subclasses" id="id8">2.2 Basic ParserElement subclasses</a></li> +<li><a class="reference internal" href="#expression-subclasses" id="id9">2.3 Expression subclasses</a></li> +<li><a class="reference internal" href="#expression-operators" id="id10">2.4 Expression operators</a></li> +<li><a class="reference internal" href="#positional-subclasses" id="id11">2.5 Positional subclasses</a></li> +<li><a class="reference internal" href="#converter-subclasses" id="id12">2.6 Converter subclasses</a></li> +<li><a class="reference internal" href="#special-subclasses" id="id13">2.7 Special subclasses</a></li> +<li><a class="reference internal" href="#other-classes" id="id14">2.8 Other classes</a></li> +<li><a class="reference internal" href="#exception-classes-and-troubleshooting" id="id15">2.9 Exception classes and Troubleshooting</a></li> +</ul> +</li> +<li><a class="reference internal" href="#miscellaneous-attributes-and-methods" id="id16">3 Miscellaneous attributes and methods</a><ul class="auto-toc"> +<li><a class="reference internal" href="#helper-methods" id="id17">3.1 Helper methods</a></li> +<li><a class="reference internal" href="#helper-parse-actions" id="id18">3.2 Helper parse actions</a></li> +<li><a class="reference internal" href="#common-string-and-token-constants" id="id19">3.3 Common string and token constants</a></li> +</ul> +</li> +</ul> +</div> +<div class="section" id="steps-to-follow"> +<h1><a class="toc-backref" href="#id1">1 Steps to follow</a></h1> +<p>To parse an incoming data string, the client code must follow these steps:</p> +<ol class="arabic simple"> +<li>First define the tokens and patterns to be matched, and assign +this to a program variable. Optional results names or parsing +actions can also be defined at this time.</li> +<li>Call <tt class="docutils literal"><span class="pre">parseString()</span></tt> or <tt class="docutils literal"><span class="pre">scanString()</span></tt> on this variable, passing in +the string to +be parsed. During the matching process, whitespace between +tokens is skipped by default (although this can be changed). +When token matches occur, any defined parse action methods are +called.</li> +<li>Process the parsed results, returned as a list of strings. +Matching results may also be accessed as named attributes of +the returned results, if names are defined in the definition of +the token pattern, using <tt class="docutils literal"><span class="pre">setResultsName()</span></tt>.</li> +</ol> +<div class="section" id="hello-world"> +<h2><a class="toc-backref" href="#id2">1.1 Hello, World!</a></h2> +<p>The following complete Python program will parse the greeting "Hello, World!", +or any other greeting of the form "<salutation>, <addressee>!":</p> +<pre class="literal-block"> +from pyparsing import Word, alphas + +greet = Word( alphas ) + "," + Word( alphas ) + "!" +greeting = greet.parseString( "Hello, World!" ) +print greeting +</pre> +<p>The parsed tokens are returned in the following form:</p> +<pre class="literal-block"> +['Hello', ',', 'World', '!'] +</pre> +</div> +<div class="section" id="new-features-in-1-5-1"> +<h2><a class="toc-backref" href="#id3">1.2 New features in 1.5.1</a></h2> +<p>Some of the significant features added in version 1.5.0 are:</p> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">originalTextFor</span></tt> helper method, to simplify grammar expressions that need to preserve +the original input text; should be used in place of the <tt class="docutils literal"><span class="pre">keepOriginalText</span></tt> parse action:</p> +<pre class="literal-block"> +fullName = Word(alphas) + Word(alphas) +fullName.setParseAction(keepOriginalText) +</pre> +<p>should now be written:</p> +<pre class="literal-block"> +fullName = originalTextFor(Word(alphas) + Word(alphas)) +</pre> +</li> +<li><p class="first">added parameter <tt class="docutils literal"><span class="pre">parseAll</span></tt> to <tt class="docutils literal"><span class="pre">ParserElement.parseFile</span></tt> to +match the arguments for <tt class="docutils literal"><span class="pre">ParserElement.parseString</span></tt></p> +</li> +<li><p class="first">new argument <tt class="docutils literal"><span class="pre">failOn</span></tt> for SkipTo expressions, to define literal strings or expressions that +should not be included in the skipped text</p> +</li> +</ul> +</div> +<div class="section" id="new-features-in-1-5-0"> +<h2><a class="toc-backref" href="#id4">1.3 New features in 1.5.0</a></h2> +<p>Some of the significant features added in version 1.5.0 are:</p> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">indentedBlock</span></tt> helper method to define grammars using block indentation for grouping (like Python)</li> +<li>new parameter <tt class="docutils literal"><span class="pre">parseAll</span></tt> in <tt class="docutils literal"><span class="pre">ParserElement.parseString</span></tt></li> +<li>operator '-' for combining ParserElements; similar to the '+' operator, but raises an immediate +<tt class="docutils literal"><span class="pre">ParseSyntaxException</span></tt> if an expression after the '-' operator fails to match; using '-' can +provide error messages that are more useful for application users to find syntax errors in their +input text</li> +</ul> +</div> +<div class="section" id="usage-notes"> +<h2><a class="toc-backref" href="#id5">1.4 Usage notes</a></h2> +<ul> +<li><p class="first">The pyparsing module can be used to interpret simple command +strings or algebraic expressions, or can be used to extract data +from text reports with complicated format and structure ("screen +or report scraping"). However, it is possible that your defined +matching patterns may accept invalid inputs. Use pyparsing to +extract data from strings assumed to be well-formatted.</p> +</li> +<li><p class="first">To keep up the readability of your code, use <a class="reference internal" href="#operators">operators</a> such as <tt class="docutils literal"><span class="pre">+</span></tt>, <tt class="docutils literal"><span class="pre">|</span></tt>, +<tt class="docutils literal"><span class="pre">^</span></tt>, and <tt class="docutils literal"><span class="pre">~</span></tt> to combine expressions. You can also combine +string literals with ParseExpressions - they will be +automatically converted to Literal objects. For example:</p> +<pre class="literal-block"> +integer = Word( nums ) # simple unsigned integer +variable = Word( alphas, max=1 ) # single letter variable, such as x, z, m, etc. +arithOp = Word( "+-*/", max=1 ) # arithmetic operators +equation = variable + "=" + integer + arithOp + integer # will match "x=2+2", etc. +</pre> +<p>In the definition of <tt class="docutils literal"><span class="pre">equation</span></tt>, the string <tt class="docutils literal"><span class="pre">"="</span></tt> will get added as +a <tt class="docutils literal"><span class="pre">Literal("=")</span></tt>, but in a more readable way.</p> +</li> +<li><p class="first">The pyparsing module's default behavior is to ignore whitespace. This is the +case for 99% of all parsers ever written. This allows you to write simple, clean, +grammars, such as the above <tt class="docutils literal"><span class="pre">equation</span></tt>, without having to clutter it up with +extraneous <tt class="docutils literal"><span class="pre">ws</span></tt> markers. The <tt class="docutils literal"><span class="pre">equation</span></tt> grammar will successfully parse all of the +following statements:</p> +<pre class="literal-block"> +x=2+2 +x = 2+2 +a = 10 * 4 +r= 1234/ 100000 +</pre> +<p>Of course, it is quite simple to extend this example to support more elaborate expressions, with +nesting with parentheses, floating point numbers, scientific notation, and named constants +(such as <tt class="docutils literal"><span class="pre">e</span></tt> or <tt class="docutils literal"><span class="pre">pi</span></tt>). See <tt class="docutils literal"><span class="pre">fourFn.py</span></tt>, included in the examples directory.</p> +</li> +<li><p class="first">To modify pyparsing's default whitespace skipping, you can use one or +more of the following methods:</p> +<ul> +<li><p class="first">use the static method <tt class="docutils literal"><span class="pre">ParserElement.setDefaultWhitespaceChars</span></tt> +to override the normal set of whitespace chars (' tn'). For instance +when defining a grammar in which newlines are significant, you should +call <tt class="docutils literal"><span class="pre">ParserElement.setDefaultWhitespaceChars('</span> <span class="pre">\t')</span></tt> to remove +newline from the set of skippable whitespace characters. Calling +this method will affect all pyparsing expressions defined afterward.</p> +</li> +<li><p class="first">call <tt class="docutils literal"><span class="pre">leaveWhitespace()</span></tt> on individual expressions, to suppress the +skipping of whitespace before trying to match the expression</p> +</li> +<li><p class="first">use <tt class="docutils literal"><span class="pre">Combine</span></tt> to require that successive expressions must be +adjacent in the input string. For instance, this expression:</p> +<pre class="literal-block"> +real = Word(nums) + '.' + Word(nums) +</pre> +<p>will match "3.14159", but will also match "3 . 12". It will also +return the matched results as ['3', '.', '14159']. By changing this +expression to:</p> +<pre class="literal-block"> +real = Combine( Word(nums) + '.' + Word(nums) ) +</pre> +<p>it will not match numbers with embedded spaces, and it will return a +single concatenated string '3.14159' as the parsed token.</p> +</li> +</ul> +</li> +<li><p class="first">Repetition of expressions can be indicated using the '*' operator. An +expression may be multiplied by an integer value (to indicate an exact +repetition count), or by a tuple containing +two integers, or None and an integer, representing min and max repetitions +(with None representing no min or no max, depending whether it is the first or +second tuple element). See the following examples, where n is used to +indicate an integer value:</p> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">expr*3</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">expr</span> <span class="pre">+</span> <span class="pre">expr</span> <span class="pre">+</span> <span class="pre">expr</span></tt></li> +<li><tt class="docutils literal"><span class="pre">expr*(2,3)</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">expr</span> <span class="pre">+</span> <span class="pre">expr</span> <span class="pre">+</span> <span class="pre">Optional(expr)</span></tt></li> +<li><tt class="docutils literal"><span class="pre">expr*(n,None)</span></tt> or <tt class="docutils literal"><span class="pre">expr*(n,)</span></tt> is equivalent +to <tt class="docutils literal"><span class="pre">expr*n</span> <span class="pre">+</span> <span class="pre">ZeroOrMore(expr)</span></tt> (read as "at least n instances of expr")</li> +<li><tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">expr*(0,n)</span></tt> +(read as "0 to n instances of expr")</li> +<li><tt class="docutils literal"><span class="pre">expr*(None,None)</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">ZeroOrMore(expr)</span></tt></li> +<li><tt class="docutils literal"><span class="pre">expr*(1,None)</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">OneOrMore(expr)</span></tt></li> +</ul> +<p>Note that <tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> does not raise an exception if +more than n exprs exist in the input stream; that is, +<tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> does not enforce a maximum number of expr +occurrences. If this behavior is desired, then write +<tt class="docutils literal"><span class="pre">expr*(None,n)</span> <span class="pre">+</span> <span class="pre">~expr</span></tt>.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">MatchFirst</span></tt> expressions are matched left-to-right, and the first +match found will skip all later expressions within, so be sure +to define less-specific patterns after more-specific patterns. +If you are not sure which expressions are most specific, use Or +expressions (defined using the <tt class="docutils literal"><span class="pre">^</span></tt> operator) - they will always +match the longest expression, although they are more +compute-intensive.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">Or</span></tt> expressions will evaluate all of the specified subexpressions +to determine which is the "best" match, that is, which matches +the longest string in the input data. In case of a tie, the +left-most expression in the <tt class="docutils literal"><span class="pre">Or</span></tt> list will win.</p> +</li> +<li><p class="first">If parsing the contents of an entire file, pass it to the +<tt class="docutils literal"><span class="pre">parseFile</span></tt> method using:</p> +<pre class="literal-block"> +expr.parseFile( sourceFile ) +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">ParseExceptions</span></tt> will report the location where an expected token +or expression failed to match. For example, if we tried to use our +"Hello, World!" parser to parse "Hello World!" (leaving out the separating +comma), we would get an exception, with the message:</p> +<pre class="literal-block"> +pyparsing.ParseException: Expected "," (6), (1,7) +</pre> +<p>In the case of complex +expressions, the reported location may not be exactly where you +would expect. See more information under <a class="reference internal" href="#parseexception">ParseException</a> .</p> +</li> +<li><p class="first">Use the <tt class="docutils literal"><span class="pre">Group</span></tt> class to enclose logical groups of tokens within a +sublist. This will help organize your results into more +hierarchical form (the default behavior is to return matching +tokens as a flat list of matching input strings).</p> +</li> +<li><p class="first">Punctuation may be significant for matching, but is rarely of +much interest in the parsed results. Use the <tt class="docutils literal"><span class="pre">suppress()</span></tt> method +to keep these tokens from cluttering up your returned lists of +tokens. For example, <tt class="docutils literal"><span class="pre">delimitedList()</span></tt> matches a succession of +one or more expressions, separated by delimiters (commas by +default), but only returns a list of the actual expressions - +the delimiters are used for parsing, but are suppressed from the +returned output.</p> +</li> +<li><p class="first">Parse actions can be used to convert values from strings to +other data types (ints, floats, booleans, etc.).</p> +</li> +<li><p class="first">Results names are recommended for retrieving tokens from complex +expressions. It is much easier to access a token using its field +name than using a positional index, especially if the expression +contains optional elements. You can also shortcut +the <tt class="docutils literal"><span class="pre">setResultsName</span></tt> call:</p> +<pre class="literal-block"> +stats = "AVE:" + realNum.setResultsName("average") + \ + "MIN:" + realNum.setResultsName("min") + \ + "MAX:" + realNum.setResultsName("max") +</pre> +<p>can now be written as this:</p> +<pre class="literal-block"> +stats = "AVE:" + realNum("average") + \ + "MIN:" + realNum("min") + \ + "MAX:" + realNum("max") +</pre> +</li> +<li><p class="first">Be careful when defining parse actions that modify global variables or +data structures (as in <tt class="docutils literal"><span class="pre">fourFn.py</span></tt>), especially for low level tokens +or expressions that may occur within an <tt class="docutils literal"><span class="pre">And</span></tt> expression; an early element +of an <tt class="docutils literal"><span class="pre">And</span></tt> may match, but the overall expression may fail.</p> +</li> +<li><p class="first">Performance of pyparsing may be slow for complex grammars and/or large +input strings. The <a class="reference external" href="http://psyco.sourceforge.net/">psyco</a> package can be used to improve the speed of the +pyparsing module with no changes to grammar or program logic - observed +improvments have been in the 20-50% range.</p> +</li> +</ul> +</div> +</div> +<div class="section" id="classes"> +<h1><a class="toc-backref" href="#id6">2 Classes</a></h1> +<div class="section" id="classes-in-the-pyparsing-module"> +<h2><a class="toc-backref" href="#id7">2.1 Classes in the pyparsing module</a></h2> +<p><tt class="docutils literal"><span class="pre">ParserElement</span></tt> - abstract base class for all pyparsing classes; +methods for code to use are:</p> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">parseString(</span> <span class="pre">sourceString,</span> <span class="pre">parseAll=False</span> <span class="pre">)</span></tt> - only called once, on the overall +matching pattern; returns a <a class="reference internal" href="#parseresults">ParseResults</a> object that makes the +matched tokens available as a list, and optionally as a dictionary, +or as an object with named attributes; if parseAll is set to True, then +parseString will raise a ParseException if the grammar does not process +the complete input string.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">parseFile(</span> <span class="pre">sourceFile</span> <span class="pre">)</span></tt> - a convenience function, that accepts an +input file object or filename. The file contents are passed as a +string to <tt class="docutils literal"><span class="pre">parseString()</span></tt>. <tt class="docutils literal"><span class="pre">parseFile</span></tt> also supports the <tt class="docutils literal"><span class="pre">parseAll</span></tt> argument.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">scanString(</span> <span class="pre">sourceString</span> <span class="pre">)</span></tt> - generator function, used to find and +extract matching text in the given source string; for each matched text, +returns a tuple of:</p> +<ul class="simple"> +<li>matched tokens (packaged as a <a class="reference internal" href="#parseresults">ParseResults</a> object)</li> +<li>start location of the matched text in the given source string</li> +<li>end location in the given source string</li> +</ul> +<p><tt class="docutils literal"><span class="pre">scanString</span></tt> allows you to scan through the input source string for +random matches, instead of exhaustively defining the grammar for the entire +source text (as would be required with <tt class="docutils literal"><span class="pre">parseString</span></tt>).</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">transformString(</span> <span class="pre">sourceString</span> <span class="pre">)</span></tt> - convenience wrapper function for +<tt class="docutils literal"><span class="pre">scanString</span></tt>, to process the input source string, and replace matching +text with the tokens returned from parse actions defined in the grammar +(see <a class="reference internal" href="#setparseaction">setParseAction</a>).</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">searchString(</span> <span class="pre">sourceString</span> <span class="pre">)</span></tt> - another convenience wrapper function for +<tt class="docutils literal"><span class="pre">scanString</span></tt>, returns a list of the matching tokens returned from each +call to <tt class="docutils literal"><span class="pre">scanString</span></tt>.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setName(</span> <span class="pre">name</span> <span class="pre">)</span></tt> - associate a short descriptive name for this +element, useful in displaying exceptions and trace information</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setResultsName(</span> <span class="pre">string,</span> <span class="pre">listAllMatches=False</span> <span class="pre">)</span></tt> - name to be given +to tokens matching +the element; if multiple tokens within +a repetition group (such as <tt class="docutils literal"><span class="pre">ZeroOrMore</span></tt> or <tt class="docutils literal"><span class="pre">delimitedList</span></tt>) the +default is to return only the last matching token - if listAllMatches +is set to True, then a list of matching tokens is returned. Note: +<tt class="docutils literal"><span class="pre">setResultsName</span></tt> returns a <em>copy</em> of the element so that a single +basic element can be referenced multiple times and given +different names within a complex grammar.</p> +</li> +</ul> +<ul id="setparseaction"> +<li><p class="first"><tt class="docutils literal"><span class="pre">setParseAction(</span> <span class="pre">*fn</span> <span class="pre">)</span></tt> - specify one or more functions to call after successful +matching of the element; each function is defined as <tt class="docutils literal"><span class="pre">fn(</span> <span class="pre">s,</span> +<span class="pre">loc,</span> <span class="pre">toks</span> <span class="pre">)</span></tt>, where:</p> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">s</span></tt> is the original parse string</li> +<li><tt class="docutils literal"><span class="pre">loc</span></tt> is the location in the string where matching started</li> +<li><tt class="docutils literal"><span class="pre">toks</span></tt> is the list of the matched tokens, packaged as a <a class="reference internal" href="#parseresults">ParseResults</a> object</li> +</ul> +<p>Multiple functions can be attached to a ParserElement by specifying multiple +arguments to setParseAction, or by calling setParseAction multiple times.</p> +<p>Each parse action function can return a modified <tt class="docutils literal"><span class="pre">toks</span></tt> list, to perform conversion, or +string modifications. For brevity, <tt class="docutils literal"><span class="pre">fn</span></tt> may also be a +lambda - here is an example of using a parse action to convert matched +integer tokens from strings to integers:</p> +<pre class="literal-block"> +intNumber = Word(nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) +</pre> +<p>If <tt class="docutils literal"><span class="pre">fn</span></tt> does not modify the <tt class="docutils literal"><span class="pre">toks</span></tt> list, it does not need to return +anything at all.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setBreak(</span> <span class="pre">breakFlag=True</span> <span class="pre">)</span></tt> - if breakFlag is True, calls pdb.set_break() +as this expression is about to be parsed</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">copy()</span></tt> - returns a copy of a ParserElement; can be used to use the same +parse expression in different places in a grammar, with different parse actions +attached to each</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">leaveWhitespace()</span></tt> - change default behavior of skipping +whitespace before starting matching (mostly used internally to the +pyparsing module, rarely used by client code)</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setWhitespaceChars(</span> <span class="pre">chars</span> <span class="pre">)</span></tt> - define the set of chars to be ignored +as whitespace before trying to match a specific ParserElement, in place of the +default set of whitespace (space, tab, newline, and return)</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setDefaultWhitespaceChars(</span> <span class="pre">chars</span> <span class="pre">)</span></tt> - class-level method to override +the default set of whitespace chars for all subsequently created ParserElements +(including copies); useful when defining grammars that treat one or more of the +default whitespace characters as significant (such as a line-sensitive grammar, to +omit newline from the list of ignorable whitespace)</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">suppress()</span></tt> - convenience function to suppress the output of the +given element, instead of wrapping it with a Suppress object.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">ignore(</span> <span class="pre">expr</span> <span class="pre">)</span></tt> - function to specify parse expression to be +ignored while matching defined patterns; can be called +repeatedly to specify multiple expressions; useful to specify +patterns of comment syntax, for example</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">setDebug(</span> <span class="pre">dbgFlag=True</span> <span class="pre">)</span></tt> - function to enable/disable tracing output +when trying to match this element</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">validate()</span></tt> - function to verify that the defined grammar does not +contain infinitely recursive constructs</p> +</li> +</ul> +<ul class="simple" id="parsewithtabs"> +<li><tt class="docutils literal"><span class="pre">parseWithTabs()</span></tt> - function to override default behavior of converting +tabs to spaces before parsing the input string; rarely used, except when +specifying whitespace-significant grammars using the <a class="reference internal" href="#white">White</a> class.</li> +<li><tt class="docutils literal"><span class="pre">enablePackrat()</span></tt> - a class-level static method to enable a memoizing +performance enhancement, known as "packrat parsing". packrat parsing is +disabled by default, since it may conflict with some user programs that use +parse actions. To activate the packrat feature, your +program must call the class method ParserElement.enablePackrat(). If +your program uses psyco to "compile as you go", you must call +enablePackrat before calling psyco.full(). If you do not do this, +Python will crash. For best results, call enablePackrat() immediately +after importing pyparsing.</li> +</ul> +</div> +<div class="section" id="basic-parserelement-subclasses"> +<h2><a class="toc-backref" href="#id8">2.2 Basic ParserElement subclasses</a></h2> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">Literal</span></tt> - construct with a string to be matched exactly</li> +<li><tt class="docutils literal"><span class="pre">CaselessLiteral</span></tt> - construct with a string to be matched, but +without case checking; results are always returned as the +defining literal, NOT as they are found in the input string</li> +<li><tt class="docutils literal"><span class="pre">Keyword</span></tt> - similar to Literal, but must be immediately followed by +whitespace, punctuation, or other non-keyword characters; prevents +accidental matching of a non-keyword that happens to begin with a +defined keyword</li> +<li><tt class="docutils literal"><span class="pre">CaselessKeyword</span></tt> - similar to Keyword, but with caseless matching +behavior</li> +</ul> +<ul id="word"> +<li><p class="first"><tt class="docutils literal"><span class="pre">Word</span></tt> - one or more contiguous characters; construct with a +string containing the set of allowed initial characters, and an +optional second string of allowed body characters; for instance, +a common Word construct is to match a code identifier - in C, a +valid identifier must start with an alphabetic character or an +underscore ('_'), followed by a body that can also include numeric +digits. That is, <tt class="docutils literal"><span class="pre">a</span></tt>, <tt class="docutils literal"><span class="pre">i</span></tt>, <tt class="docutils literal"><span class="pre">MAX_LENGTH</span></tt>, <tt class="docutils literal"><span class="pre">_a1</span></tt>, <tt class="docutils literal"><span class="pre">b_109_</span></tt>, and +<tt class="docutils literal"><span class="pre">plan9FromOuterSpace</span></tt> +are all valid identifiers; <tt class="docutils literal"><span class="pre">9b7z</span></tt>, <tt class="docutils literal"><span class="pre">$a</span></tt>, <tt class="docutils literal"><span class="pre">.section</span></tt>, and <tt class="docutils literal"><span class="pre">0debug</span></tt> +are not. To +define an identifier using a Word, use either of the following:</p> +<pre class="literal-block"> +- Word( alphas+"_", alphanums+"_" ) +- Word( srange("[a-zA-Z_]"), srange("[a-zA-Z0-9_]") ) +</pre> +<p>If only one +string given, it specifies that the same character set defined +for the initial character is used for the word body; for instance, to +define an identifier that can only be composed of capital letters and +underscores, use:</p> +<pre class="literal-block"> +- Word( "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" ) +- Word( srange("[A-Z_]") ) +</pre> +<p>A Word may +also be constructed with any of the following optional parameters:</p> +<ul class="simple"> +<li>min - indicating a minimum length of matching characters</li> +<li>max - indicating a maximum length of matching characters</li> +<li>exact - indicating an exact length of matching characters</li> +</ul> +<p>If exact is specified, it will override any values for min or max.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">CharsNotIn</span></tt> - similar to <a class="reference internal" href="#word">Word</a>, but matches characters not +in the given constructor string (accepts only one string for both +initial and body characters); also supports min, max, and exact +optional parameters.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">Regex</span></tt> - a powerful construct, that accepts a regular expression +to be matched at the current parse position; accepts an optional +flags parameter, corresponding to the flags parameter in the re.compile +method; if the expression includes named sub-fields, they will be +represented in the returned <a class="reference internal" href="#parseresults">ParseResults</a></p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">QuotedString</span></tt> - supports the definition of custom quoted string +formats, in addition to pyparsing's built-in dblQuotedString and +sglQuotedString. QuotedString allows you to specify the following +parameters:</p> +<ul class="simple"> +<li>quoteChar - string of one or more characters defining the quote delimiting string</li> +<li>escChar - character to escape quotes, typically backslash (default=None)</li> +<li>escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None)</li> +<li>multiline - boolean indicating whether quotes can span multiple lines (default=False)</li> +<li>unquoteResults - boolean indicating whether the matched text should be unquoted (default=True)</li> +<li>endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar)</li> +</ul> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">SkipTo</span></tt> - skips ahead in the input string, accepting any +characters up to the specified pattern; may be constructed with +the following optional parameters:</p> +<ul class="simple"> +<li>include - if set to true, also consumes the match expression +(default is false)</li> +<li>ignore - allows the user to specify patterns to not be matched, +to prevent false matches</li> +<li>failOn - if a literal string or expression is given for this argument, it defines an expression that +should cause the <tt class="docutils literal"><span class="pre">SkipTo</span></tt> expression to fail, and not skip over that expression</li> +</ul> +</li> +</ul> +<ul class="simple" id="white"> +<li><tt class="docutils literal"><span class="pre">White</span></tt> - also similar to <a class="reference internal" href="#word">Word</a>, but matches whitespace +characters. Not usually needed, as whitespace is implicitly +ignored by pyparsing. However, some grammars are whitespace-sensitive, +such as those that use leading tabs or spaces to indicating grouping +or hierarchy. (If matching on tab characters, be sure to call +<a class="reference internal" href="#parsewithtabs">parseWithTabs</a> on the top-level parse element.)</li> +<li><tt class="docutils literal"><span class="pre">Empty</span></tt> - a null expression, requiring no characters - will always +match; useful for debugging and for specialized grammars</li> +<li><tt class="docutils literal"><span class="pre">NoMatch</span></tt> - opposite of Empty, will never match; useful for debugging +and for specialized grammars</li> +</ul> +</div> +<div class="section" id="expression-subclasses"> +<h2><a class="toc-backref" href="#id9">2.3 Expression subclasses</a></h2> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">And</span></tt> - construct with a list of ParserElements, all of which must +match for And to match; can also be created using the '+' +operator; multiple expressions can be Anded together using the '*' +operator as in:</p> +<pre class="literal-block"> +ipAddress = Word(nums) + ('.'+Word(nums))*3 +</pre> +<p>A tuple can be used as the multiplier, indicating a min/max:</p> +<pre class="literal-block"> +usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) +</pre> +<p>A special form of <tt class="docutils literal"><span class="pre">And</span></tt> is created if the '-' operator is used +instead of the '+' operator. In the ipAddress example above, if +no trailing '.' and Word(nums) are found after matching the initial +Word(nums), then pyparsing will back up in the grammar and try other +alternatives to ipAddress. However, if ipAddress is defined as:</p> +<pre class="literal-block"> +strictIpAddress = Word(nums) - ('.'+Word(nums))*3 +</pre> +<p>then no backing up is done. If the first Word(nums) of strictIpAddress +is matched, then any mismatch after that will raise a ParseSyntaxException, +which will halt the parsing process immediately. By careful use of the +'-' operator, grammars can provide meaningful error messages close to +the location where the incoming text does not match the specified +grammar.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">Or</span></tt> - construct with a list of ParserElements, any of which must +match for Or to match; if more than one expression matches, the +expression that makes the longest match will be used; can also +be created using the '^' operator</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">MatchFirst</span></tt> - construct with a list of ParserElements, any of +which must match for MatchFirst to match; matching is done +left-to-right, taking the first expression that matches; can +also be created using the '|' operator</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">Each</span></tt> - similar to And, in that all of the provided expressions +must match; however, Each permits matching to be done in any order; +can also be created using the '&' operator</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">Optional</span></tt> - construct with a ParserElement, but this element is +not required to match; can be constructed with an optional <tt class="docutils literal"><span class="pre">default</span></tt> argument, +containing a default string or object to be supplied if the given optional +parse element is not found in the input string; parse action will only +be called if a match is found, or if a default is specified</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">ZeroOrMore</span></tt> - similar to Optional, but can be repeated</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">OneOrMore</span></tt> - similar to ZeroOrMore, but at least one match must +be present</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">FollowedBy</span></tt> - a lookahead expression, requires matching of the given +expressions, but does not advance the parsing position within the input string</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">NotAny</span></tt> - a negative lookahead expression, prevents matching of named +expressions, does not advance the parsing position within the input string; +can also be created using the unary '~' operator</p> +</li> +</ul> +</div> +<div class="section" id="expression-operators"> +<span id="operators"></span><h2><a class="toc-backref" href="#id10">2.4 Expression operators</a></h2> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">+</span></tt> - consecutive sequence</li> +<li><tt class="docutils literal"><span class="pre">|</span></tt> - match first alternative</li> +<li><tt class="docutils literal"><span class="pre">^</span></tt> - match longest alternative</li> +<li><tt class="docutils literal"><span class="pre">&</span></tt> - match each alternative, in any order</li> +<li><tt class="docutils literal"><span class="pre">-</span></tt> - like <tt class="docutils literal"><span class="pre">+</span></tt> but with no backup and retry of alternatives</li> +<li><tt class="docutils literal"><span class="pre">*</span></tt> - repetition of expression</li> +<li><tt class="docutils literal"><span class="pre">==</span></tt> - matching expression to string; returns True if the string matches the given expression</li> +<li><tt class="docutils literal"><span class="pre"><<</span></tt> - expression definition for Forward expressions</li> +</ul> +</div> +<div class="section" id="positional-subclasses"> +<h2><a class="toc-backref" href="#id11">2.5 Positional subclasses</a></h2> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">StringStart</span></tt> - matches beginning of the text</li> +<li><tt class="docutils literal"><span class="pre">StringEnd</span></tt> - matches the end of the text</li> +<li><tt class="docutils literal"><span class="pre">LineStart</span></tt> - matches beginning of a line (lines delimited by <tt class="docutils literal"><span class="pre">\n</span></tt> characters)</li> +<li><tt class="docutils literal"><span class="pre">LineEnd</span></tt> - matches the end of a line</li> +<li><tt class="docutils literal"><span class="pre">WordStart</span></tt> - matches a leading word boundary</li> +<li><tt class="docutils literal"><span class="pre">WordEnd</span></tt> - matches a trailing word boundary</li> +</ul> +</div> +<div class="section" id="converter-subclasses"> +<h2><a class="toc-backref" href="#id12">2.6 Converter subclasses</a></h2> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">Upcase</span></tt> - converts matched tokens to uppercase (deprecated - +use <tt class="docutils literal"><span class="pre">upcaseTokens</span></tt> parse action instead)</li> +<li><tt class="docutils literal"><span class="pre">Combine</span></tt> - joins all matched tokens into a single string, using +specified joinString (default <tt class="docutils literal"><span class="pre">joinString=""</span></tt>); expects +all matching tokens to be adjacent, with no intervening +whitespace (can be overridden by specifying <tt class="docutils literal"><span class="pre">adjacent=False</span></tt> in constructor)</li> +<li><tt class="docutils literal"><span class="pre">Suppress</span></tt> - clears matched tokens; useful to keep returned +results from being cluttered with required but uninteresting +tokens (such as list delimiters)</li> +</ul> +</div> +<div class="section" id="special-subclasses"> +<h2><a class="toc-backref" href="#id13">2.7 Special subclasses</a></h2> +<ul class="simple"> +<li><tt class="docutils literal"><span class="pre">Group</span></tt> - causes the matched tokens to be enclosed in a list; +useful in repeated elements like <tt class="docutils literal"><span class="pre">ZeroOrMore</span></tt> and <tt class="docutils literal"><span class="pre">OneOrMore</span></tt> to +break up matched tokens into groups for each repeated pattern</li> +<li><tt class="docutils literal"><span class="pre">Dict</span></tt> - like <tt class="docutils literal"><span class="pre">Group</span></tt>, but also constructs a dictionary, using the +[0]'th elements of all enclosed token lists as the keys, and +each token list as the value</li> +<li><tt class="docutils literal"><span class="pre">SkipTo</span></tt> - catch-all matching expression that accepts all characters +up until the given pattern is found to match; useful for specifying +incomplete grammars</li> +<li><tt class="docutils literal"><span class="pre">Forward</span></tt> - placeholder token used to define recursive token +patterns; when defining the actual expression later in the +program, insert it into the <tt class="docutils literal"><span class="pre">Forward</span></tt> object using the <tt class="docutils literal"><span class="pre"><<</span></tt> +operator (see <tt class="docutils literal"><span class="pre">fourFn.py</span></tt> for an example).</li> +</ul> +</div> +<div class="section" id="other-classes"> +<h2><a class="toc-backref" href="#id14">2.8 Other classes</a></h2> +<ul id="parseresults"> +<li><p class="first"><tt class="docutils literal"><span class="pre">ParseResults</span></tt> - class used to contain and manage the lists of tokens +created from parsing the input using the user-defined parse +expression. ParseResults can be accessed in a number of ways:</p> +<ul class="simple"> +<li>as a list<ul> +<li>total list of elements can be found using len()</li> +<li>individual elements can be found using [0], [1], [-1], etc.</li> +<li>elements can be deleted using <tt class="docutils literal"><span class="pre">del</span></tt></li> +<li>the -1th element can be extracted and removed in a single operation +using <tt class="docutils literal"><span class="pre">pop()</span></tt>, or any element can be extracted and removed +using <tt class="docutils literal"><span class="pre">pop(n)</span></tt></li> +</ul> +</li> +<li>as a dictionary<ul> +<li>if <tt class="docutils literal"><span class="pre">setResultsName()</span></tt> is used to name elements within the +overall parse expression, then these fields can be referenced +as dictionary elements or as attributes</li> +<li>the Dict class generates dictionary entries using the data of the +input text - in addition to ParseResults listed as <tt class="docutils literal"><span class="pre">[</span> <span class="pre">[</span> <span class="pre">a1,</span> <span class="pre">b1,</span> <span class="pre">c1,</span> <span class="pre">...],</span> <span class="pre">[</span> <span class="pre">a2,</span> <span class="pre">b2,</span> <span class="pre">c2,</span> <span class="pre">...]</span> <span class="pre">]</span></tt> +it also acts as a dictionary with entries defined as <tt class="docutils literal"><span class="pre">{</span> <span class="pre">a1</span> <span class="pre">:</span> <span class="pre">[</span> <span class="pre">b1,</span> <span class="pre">c1,</span> <span class="pre">...</span> <span class="pre">]</span> <span class="pre">},</span> <span class="pre">{</span> <span class="pre">a2</span> <span class="pre">:</span> <span class="pre">[</span> <span class="pre">b2,</span> <span class="pre">c2,</span> <span class="pre">...</span> <span class="pre">]</span> <span class="pre">}</span></tt>; +this is especially useful when processing tabular data where the first column contains a key +value for that line of data</li> +<li>list elements that are deleted using <tt class="docutils literal"><span class="pre">del</span></tt> will still be accessible by their +dictionary keys</li> +<li>supports <tt class="docutils literal"><span class="pre">get()</span></tt>, <tt class="docutils literal"><span class="pre">items()</span></tt> and <tt class="docutils literal"><span class="pre">keys()</span></tt> methods, similar to a dictionary</li> +<li>a keyed item can be extracted and removed using <tt class="docutils literal"><span class="pre">pop(key)</span></tt>. Here +key must be non-numeric (such as a string), in order to use dict +extraction instead of list extraction.</li> +<li>new named elements can be added (in a parse action, for instance), using the same +syntax as adding an item to a dict (<tt class="docutils literal"><span class="pre">parseResults["X"]="new</span> <span class="pre">item"</span></tt>); named elements can be removed using <tt class="docutils literal"><span class="pre">del</span> <span class="pre">parseResults["X"]</span></tt></li> +</ul> +</li> +<li>as a nested list<ul> +<li>results returned from the Group class are encapsulated within their +own list structure, so that the tokens can be handled as a hierarchical +tree</li> +</ul> +</li> +</ul> +<p>ParseResults can also be converted to an ordinary list of strings +by calling <tt class="docutils literal"><span class="pre">asList()</span></tt>. Note that this will strip the results of any +field names that have been defined for any embedded parse elements. +(The <tt class="docutils literal"><span class="pre">pprint</span></tt> module is especially good at printing out the nested contents +given by <tt class="docutils literal"><span class="pre">asList()</span></tt>.)</p> +<p>Finally, ParseResults can be converted to an XML string by calling <tt class="docutils literal"><span class="pre">asXML()</span></tt>. Where +possible, results will be tagged using the results names defined for the respective +ParseExpressions. <tt class="docutils literal"><span class="pre">asXML()</span></tt> takes two optional arguments:</p> +<ul class="simple"> +<li>doctagname - for ParseResults that do not have a defined name, this argument +will wrap the resulting XML in a set of opening and closing tags <tt class="docutils literal"><span class="pre"><doctagname></span></tt> +and <tt class="docutils literal"><span class="pre"></doctagname></span></tt>.</li> +<li>namedItemsOnly (default=False) - flag to indicate if the generated XML should +skip items that do not have defined names. If a nested group item is named, then all +embedded items will be included, whether they have names or not.</li> +</ul> +</li> +</ul> +</div> +<div class="section" id="exception-classes-and-troubleshooting"> +<h2><a class="toc-backref" href="#id15">2.9 Exception classes and Troubleshooting</a></h2> +<ul id="parseexception"> +<li><p class="first"><tt class="docutils literal"><span class="pre">ParseException</span></tt> - exception returned when a grammar parse fails; +ParseExceptions have attributes loc, msg, line, lineno, and column; to view the +text line and location where the reported ParseException occurs, use:</p> +<pre class="literal-block"> +except ParseException, err: + print err.line + print " "*(err.column-1) + "^" + print err +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">RecursiveGrammarException</span></tt> - exception returned by <tt class="docutils literal"><span class="pre">validate()</span></tt> if +the grammar contains a recursive infinite loop, such as:</p> +<pre class="literal-block"> +badGrammar = Forward() +goodToken = Literal("A") +badGrammar << Optional(goodToken) + badGrammar +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">ParseFatalException</span></tt> - exception that parse actions can raise to stop parsing +immediately. Should be used when a semantic error is found in the input text, such +as a mismatched XML tag.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">ParseSyntaxException</span></tt> - subclass of <tt class="docutils literal"><span class="pre">ParseFatalException</span></tt> raised when a +syntax error is found, based on the use of the '-' operator when defining +a sequence of expressions in an <tt class="docutils literal"><span class="pre">And</span></tt> expression.</p> +</li> +</ul> +<p>You can also get some insights into the parsing logic using diagnostic parse actions, +and setDebug(), or test the matching of expression fragments by testing them using +scanString().</p> +</div> +</div> +<div class="section" id="miscellaneous-attributes-and-methods"> +<h1><a class="toc-backref" href="#id16">3 Miscellaneous attributes and methods</a></h1> +<div class="section" id="helper-methods"> +<h2><a class="toc-backref" href="#id17">3.1 Helper methods</a></h2> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">delimitedList(</span> <span class="pre">expr,</span> <span class="pre">delim=',')</span></tt> - convenience function for +matching one or more occurrences of expr, separated by delim. +By default, the delimiters are suppressed, so the returned results contain +only the separate list elements. Can optionally specify <tt class="docutils literal"><span class="pre">combine=True</span></tt>, +indicating that the expressions and delimiters should be returned as one +combined value (useful for scoped variables, such as "a.b.c", or +"a::b::c", or paths such as "a/b/c").</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">countedArray(</span> <span class="pre">expr</span> <span class="pre">)</span></tt> - convenience function for a pattern where an list of +instances of the given expression are preceded by an integer giving the count of +elements in the list. Returns an expression that parses the leading integer, +reads exactly that many expressions, and returns the array of expressions in the +parse results - the leading integer is suppressed from the results (although it +is easily reconstructed by using len on the returned array).</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">oneOf(</span> <span class="pre">string,</span> <span class="pre">caseless=False</span> <span class="pre">)</span></tt> - convenience function for quickly declaring an +alternative set of <tt class="docutils literal"><span class="pre">Literal</span></tt> tokens, by splitting the given string on +whitespace boundaries. The tokens are sorted so that longer +matches are attempted first; this ensures that a short token does +not mask a longer one that starts with the same characters. If <tt class="docutils literal"><span class="pre">caseless=True</span></tt>, +will create an alternative set of CaselessLiteral tokens.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">dictOf(</span> <span class="pre">key,</span> <span class="pre">value</span> <span class="pre">)</span></tt> - convenience function for quickly declaring a +dictionary pattern of <tt class="docutils literal"><span class="pre">Dict(</span> <span class="pre">ZeroOrMore(</span> <span class="pre">Group(</span> <span class="pre">key</span> <span class="pre">+</span> <span class="pre">value</span> <span class="pre">)</span> <span class="pre">)</span> <span class="pre">)</span></tt>.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">nestedExpr(opener,</span> <span class="pre">closer,</span> <span class="pre">content,</span> <span class="pre">ignoreExpr)</span></tt> - helper method for defining +nested lists enclosed in opening and closing delimiters ("(" and ")" are the default).</p> +<ul class="simple"> +<li>opener - opening character for a nested list (default="("); can also be a pyparsing expression</li> +<li>closer - closing character for a nested list (default=")"); can also be a pyparsing expression</li> +<li>content - expression for items within the nested lists (default=None)</li> +<li>ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString)</li> +</ul> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">makeHTMLTags(</span> <span class="pre">tagName</span> <span class="pre">)</span></tt> and <tt class="docutils literal"><span class="pre">makeXMLTags(</span> <span class="pre">tagName</span> <span class="pre">)</span></tt> - convenience +functions to create definitions of opening and closing tag expressions. Returns +a pair of expressions, for the corresponding <tag> and </tag> strings. Includes +support for attributes in the opening tag, such as <tag attr1="abc"> - attributes +are returned as keyed tokens in the returned ParseResults. <tt class="docutils literal"><span class="pre">makeHTMLTags</span></tt> is less +restrictive than <tt class="docutils literal"><span class="pre">makeXMLTags</span></tt>, especially with respect to case sensitivity.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">operatorPrecedence(baseOperand,</span> <span class="pre">operatorList)</span></tt> - convenience function to define a +grammar for parsing +expressions with a hierarchical precedence of operators. To use the operatorPrecedence +helper:</p> +<ol class="arabic simple"> +<li>Define the base "atom" operand term of the grammar. +For this simple grammar, the smallest operand is either +and integer or a variable. This will be the first argument +to the operatorPrecedence method.</li> +<li>Define a list of tuples for each level of operator +precendence. Each tuple is of the form +<tt class="docutils literal"><span class="pre">(opExpr,</span> <span class="pre">numTerms,</span> <span class="pre">rightLeftAssoc,</span> <span class="pre">parseAction)</span></tt>, where:<ul> +<li>opExpr is the pyparsing expression for the operator; +may also be a string, which will be converted to a Literal; if +None, indicates an empty operator, such as the implied +multiplication operation between 'm' and 'x' in "y = mx + b".</li> +<li>numTerms is the number of terms for this operator (must +be 1 or 2)</li> +<li>rightLeftAssoc is the indicator whether the operator is +right or left associative, using the pyparsing-defined +constants <tt class="docutils literal"><span class="pre">opAssoc.RIGHT</span></tt> and <tt class="docutils literal"><span class="pre">opAssoc.LEFT</span></tt>.</li> +<li>parseAction is the parse action to be associated with +expressions matching this operator expression (the +parse action tuple member may be omitted)</li> +</ul> +</li> +<li>Call operatorPrecedence passing the operand expression and +the operator precedence list, and save the returned value +as the generated pyparsing expression. You can then use +this expression to parse input strings, or incorporate it +into a larger, more complex grammar.</li> +</ol> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">matchPreviousLiteral</span></tt> and <tt class="docutils literal"><span class="pre">matchPreviousExpr</span></tt> - function to define and +expression that matches the same content +as was parsed in a previous parse expression. For instance:</p> +<pre class="literal-block"> +first = Word(nums) +matchExpr = first + ":" + matchPreviousLiteral(first) +</pre> +<p>will match "1:1", but not "1:2". Since this matches at the literal +level, this will also match the leading "1:1" in "1:10".</p> +<p>In contrast:</p> +<pre class="literal-block"> +first = Word(nums) +matchExpr = first + ":" + matchPreviousExpr(first) +</pre> +<p>will <em>not</em> match the leading "1:1" in "1:10"; the expressions are +evaluated first, and then compared, so "1" is compared with "10".</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">nestedExpr(opener,</span> <span class="pre">closer,</span> <span class="pre">content=None,</span> <span class="pre">ignoreExpr=quotedString)</span></tt> - method for defining nested +lists enclosed in opening and closing delimiters.</p> +<ul class="simple"> +<li>opener - opening character for a nested list (default="("); can also be a pyparsing expression</li> +<li>closer - closing character for a nested list (default=")"); can also be a pyparsing expression</li> +<li>content - expression for items within the nested lists (default=None)</li> +<li>ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString)</li> +</ul> +<p>If an expression is not provided for the content argument, the nested +expression will capture all whitespace-delimited content between delimiters +as a list of separate values.</p> +<p>Use the ignoreExpr argument to define expressions that may contain +opening or closing characters that should not be treated as opening +or closing characters for nesting, such as quotedString or a comment +expression. Specify multiple expressions using an Or or MatchFirst. +The default is quotedString, but if no expressions are to be ignored, +then pass None for this argument.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">indentedBlock(</span> <span class="pre">statementExpr,</span> <span class="pre">indentationStackVar,</span> <span class="pre">indent=True)</span></tt> - +function to define an indented block of statements, similar to +indentation-based blocking in Python source code:</p> +<ul class="simple"> +<li>statementExpr is the expression defining a statement that +will be found in the indented block; a valid indentedBlock +must contain at least 1 matching statementExpr</li> +<li>indentationStackVar is a Python list variable; this variable +should be common to all <tt class="docutils literal"><span class="pre">indentedBlock</span></tt> expressions defined +within the same grammar, and should be reinitialized to [1] +each time the grammar is to be used</li> +<li>indent is a boolean flag indicating whether the expressions +within the block must be indented from the current parse +location; if using indentedBlock to define the left-most +statements (all starting in column 1), set indent to False</li> +</ul> +</li> +</ul> +<ul id="originaltextfor"> +<li><p class="first"><tt class="docutils literal"><span class="pre">originalTextFor(</span> <span class="pre">expr</span> <span class="pre">)</span></tt> - helper function to preserve the originally parsed text, regardless of any +token processing or conversion done by the contained expression. For instance, the following expression:</p> +<pre class="literal-block"> +fullName = Word(alphas) + Word(alphas) +</pre> +<p>will return the parse of "John Smith" as ['John', 'Smith']. In some applications, the actual name as it +was given in the input string is what is desired. To do this, use <tt class="docutils literal"><span class="pre">originalTextFor</span></tt>:</p> +<pre class="literal-block"> +fullName = originalTextFor(Word(alphas) + Word(alphas)) +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">lineno(</span> <span class="pre">loc,</span> <span class="pre">string</span> <span class="pre">)</span></tt> - function to give the line number of the +location within the string; the first line is line 1, newlines +start new rows</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">col(</span> <span class="pre">loc,</span> <span class="pre">string</span> <span class="pre">)</span></tt> - function to give the column number of the +location within the string; the first column is column 1, +newlines reset the column number to 1</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">line(</span> <span class="pre">loc,</span> <span class="pre">string</span> <span class="pre">)</span></tt> - function to retrieve the line of text +representing <tt class="docutils literal"><span class="pre">lineno(</span> <span class="pre">loc,</span> <span class="pre">string</span> <span class="pre">)</span></tt>; useful when printing out diagnostic +messages for exceptions</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">srange(</span> <span class="pre">rangeSpec</span> <span class="pre">)</span></tt> - function to define a string of characters, +given a string of the form used by regexp string ranges, such as <tt class="docutils literal"><span class="pre">"[0-9]"</span></tt> for +all numeric digits, <tt class="docutils literal"><span class="pre">"[A-Z_]"</span></tt> for uppercase characters plus underscore, and +so on (note that rangeSpec does not include support for generic regular +expressions, just string range specs)</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">getTokensEndLoc()</span></tt> - function to call from within a parse action to get +the ending location for the matched tokens</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">traceParseAction(fn)</span></tt> - decorator function to debug parse actions. Lists +each call, called arguments, and return value or exception</p> +</li> +</ul> +</div> +<div class="section" id="helper-parse-actions"> +<h2><a class="toc-backref" href="#id18">3.2 Helper parse actions</a></h2> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">removeQuotes</span></tt> - removes the first and last characters of a quoted string; +useful to remove the delimiting quotes from quoted strings</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">replaceWith(replString)</span></tt> - returns a parse action that simply returns the +replString; useful when using transformString, or converting HTML entities, as in:</p> +<pre class="literal-block"> +nbsp = Literal("&nbsp;").setParseAction( replaceWith("<BLANK>") ) +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">keepOriginalText</span></tt>- (deprecated, use <a class="reference internal" href="#originaltextfor">originalTextFor</a> instead) restores any internal whitespace or suppressed +text within the tokens for a matched parse +expression. This is especially useful when defining expressions +for scanString or transformString applications.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">withAttribute(</span> <span class="pre">*args,</span> <span class="pre">**kwargs</span> <span class="pre">)</span></tt> - helper to create a validating parse action to be used with start tags created +with <tt class="docutils literal"><span class="pre">makeXMLTags</span></tt> or <tt class="docutils literal"><span class="pre">makeHTMLTags</span></tt>. Use <tt class="docutils literal"><span class="pre">withAttribute</span></tt> to qualify a starting tag +with a required attribute value, to avoid false matches on common tags such as +<tt class="docutils literal"><span class="pre"><TD></span></tt> or <tt class="docutils literal"><span class="pre"><DIV></span></tt>.</p> +<p><tt class="docutils literal"><span class="pre">withAttribute</span></tt> can be called with:</p> +<ul class="simple"> +<li>keyword arguments, as in <tt class="docutils literal"><span class="pre">(class="Customer",align="right")</span></tt>, or</li> +<li>a list of name-value tuples, as in <tt class="docutils literal"><span class="pre">(</span> <span class="pre">("ns1:class",</span> <span class="pre">"Customer"),</span> <span class="pre">("ns2:align","right")</span> <span class="pre">)</span></tt></li> +</ul> +<p>An attribute can be specified to have the special value +<tt class="docutils literal"><span class="pre">withAttribute.ANY_VALUE</span></tt>, which will match any value - use this to +ensure that an attribute is present but any attribute value is +acceptable.</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">downcaseTokens</span></tt> - converts all matched tokens to lowercase</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">upcaseTokens</span></tt> - converts all matched tokens to uppercase</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">matchOnlyAtCol(</span> <span class="pre">columnNumber</span> <span class="pre">)</span></tt> - a parse action that verifies that +an expression was matched at a particular column, raising a +ParseException if matching at a different column number; useful when parsing +tabular data</p> +</li> +</ul> +</div> +<div class="section" id="common-string-and-token-constants"> +<h2><a class="toc-backref" href="#id19">3.3 Common string and token constants</a></h2> +<ul> +<li><p class="first"><tt class="docutils literal"><span class="pre">alphas</span></tt> - same as <tt class="docutils literal"><span class="pre">string.letters</span></tt></p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">nums</span></tt> - same as <tt class="docutils literal"><span class="pre">string.digits</span></tt></p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">alphanums</span></tt> - a string containing <tt class="docutils literal"><span class="pre">alphas</span> <span class="pre">+</span> <span class="pre">nums</span></tt></p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">alphas8bit</span></tt> - a string containing alphabetic 8-bit characters:</p> +<pre class="literal-block"> +ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ +</pre> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">printables</span></tt> - same as <tt class="docutils literal"><span class="pre">string.printable</span></tt>, minus the space (<tt class="docutils literal"><span class="pre">'</span> <span class="pre">'</span></tt>) character</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">empty</span></tt> - a global <tt class="docutils literal"><span class="pre">Empty()</span></tt>; will always match</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">sglQuotedString</span></tt> - a string of characters enclosed in 's; may +include whitespace, but not newlines</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">dblQuotedString</span></tt> - a string of characters enclosed in "s; may +include whitespace, but not newlines</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">quotedString</span></tt> - <tt class="docutils literal"><span class="pre">sglQuotedString</span> <span class="pre">|</span> <span class="pre">dblQuotedString</span></tt></p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">cStyleComment</span></tt> - a comment block delimited by <tt class="docutils literal"><span class="pre">'/*'</span></tt> and <tt class="docutils literal"><span class="pre">'*/'</span></tt> sequences; can span +multiple lines, but does not support nesting of comments</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">htmlComment</span></tt> - a comment block delimited by <tt class="docutils literal"><span class="pre">'<!--'</span></tt> and <tt class="docutils literal"><span class="pre">'-->'</span></tt> sequences; can span +multiple lines, but does not support nesting of comments</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">commaSeparatedList</span></tt> - similar to <tt class="docutils literal"><span class="pre">delimitedList</span></tt>, except that the +list expressions can be any text value, or a quoted string; quoted strings can +safely include commas without incorrectly breaking the string into two tokens</p> +</li> +<li><p class="first"><tt class="docutils literal"><span class="pre">restOfLine</span></tt> - all remaining printable characters up to but not including the next +newline</p> +</li> +</ul> +</div> +</div> +</div> +</body> +</html> diff --git a/HowToUsePyparsing.txt b/HowToUsePyparsing.txt new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_SG93VG9Vc2VQeXBhcnNpbmcudHh0 --- /dev/null +++ b/HowToUsePyparsing.txt @@ -0,0 +1,1011 @@ +========================== +Using the pyparsing module +========================== + +:author: Paul McGuire +:address: ptmcg@users.sourceforge.net + +:revision: 1.5.2 +:date: April, 2009 + +:copyright: Copyright |copy| 2003-2009 Paul McGuire. + +.. |copy| unicode:: 0xA9 + +:abstract: This document provides how-to instructions for the + pyparsing library, an easy-to-use Python module for constructing + and executing basic text parsers. The pyparsing module is useful + for evaluating user-definable + expressions, processing custom application language commands, or + extracting data from formatted reports. + +.. sectnum:: :depth: 4 + +.. contents:: :depth: 4 + + +Steps to follow +=============== + +To parse an incoming data string, the client code must follow these steps: + +1. First define the tokens and patterns to be matched, and assign + this to a program variable. Optional results names or parsing + actions can also be defined at this time. + +2. Call ``parseString()`` or ``scanString()`` on this variable, passing in + the string to + be parsed. During the matching process, whitespace between + tokens is skipped by default (although this can be changed). + When token matches occur, any defined parse action methods are + called. + +3. Process the parsed results, returned as a list of strings. + Matching results may also be accessed as named attributes of + the returned results, if names are defined in the definition of + the token pattern, using ``setResultsName()``. + + +Hello, World! +------------- + +The following complete Python program will parse the greeting "Hello, World!", +or any other greeting of the form "<salutation>, <addressee>!":: + + from pyparsing import Word, alphas + + greet = Word( alphas ) + "," + Word( alphas ) + "!" + greeting = greet.parseString( "Hello, World!" ) + print greeting + +The parsed tokens are returned in the following form:: + + ['Hello', ',', 'World', '!'] + + +New features in 1.5.2 +--------------------- +There are no new pyparsing features in this release, it is primarily a bug-fixing release. +This release does include changes to support running pyparsing under IronPython 2.0.1, and +a pyparsing_py3 module for use with Python 3. + + +New features in 1.5.1 +--------------------- +Some of the significant features added in version 1.5.0 are: + +- ``originalTextFor`` helper method, to simplify grammar expressions that need to preserve + the original input text; should be used in place of the ``keepOriginalText`` parse action:: + + fullName = Word(alphas) + Word(alphas) + fullName.setParseAction(keepOriginalText) + + should now be written:: + + fullName = originalTextFor(Word(alphas) + Word(alphas)) + +- added parameter ``parseAll`` to ``ParserElement.parseFile`` to + match the arguments for ``ParserElement.parseString`` + +- new argument ``failOn`` for SkipTo expressions, to define literal strings or expressions that + should not be included in the skipped text + + +New features in 1.5.0 +--------------------- +Some of the significant features added in version 1.5.0 are: + +- ``indentedBlock`` helper method to define grammars using block indentation for grouping (like Python) + +- new parameter ``parseAll`` in ``ParserElement.parseString`` + +- operator '-' for combining ParserElements; similar to the '+' operator, but raises an immediate + ``ParseSyntaxException`` if an expression after the '-' operator fails to match; using '-' can + provide error messages that are more useful for application users to find syntax errors in their + input text + + +Usage notes +----------- + +- The pyparsing module can be used to interpret simple command + strings or algebraic expressions, or can be used to extract data + from text reports with complicated format and structure ("screen + or report scraping"). However, it is possible that your defined + matching patterns may accept invalid inputs. Use pyparsing to + extract data from strings assumed to be well-formatted. + +- To keep up the readability of your code, use operators_ such as ``+``, ``|``, + ``^``, and ``~`` to combine expressions. You can also combine + string literals with ParseExpressions - they will be + automatically converted to Literal objects. For example:: + + integer = Word( nums ) # simple unsigned integer + variable = Word( alphas, max=1 ) # single letter variable, such as x, z, m, etc. + arithOp = Word( "+-*/", max=1 ) # arithmetic operators + equation = variable + "=" + integer + arithOp + integer # will match "x=2+2", etc. + + In the definition of ``equation``, the string ``"="`` will get added as + a ``Literal("=")``, but in a more readable way. + +- The pyparsing module's default behavior is to ignore whitespace. This is the + case for 99% of all parsers ever written. This allows you to write simple, clean, + grammars, such as the above ``equation``, without having to clutter it up with + extraneous ``ws`` markers. The ``equation`` grammar will successfully parse all of the + following statements:: + + x=2+2 + x = 2+2 + a = 10 * 4 + r= 1234/ 100000 + + Of course, it is quite simple to extend this example to support more elaborate expressions, with + nesting with parentheses, floating point numbers, scientific notation, and named constants + (such as ``e`` or ``pi``). See ``fourFn.py``, included in the examples directory. + +- To modify pyparsing's default whitespace skipping, you can use one or + more of the following methods: + + - use the static method ``ParserElement.setDefaultWhitespaceChars`` + to override the normal set of whitespace chars (' \t\n'). For instance + when defining a grammar in which newlines are significant, you should + call ``ParserElement.setDefaultWhitespaceChars(' \t')`` to remove + newline from the set of skippable whitespace characters. Calling + this method will affect all pyparsing expressions defined afterward. + + - call ``leaveWhitespace()`` on individual expressions, to suppress the + skipping of whitespace before trying to match the expression + + - use ``Combine`` to require that successive expressions must be + adjacent in the input string. For instance, this expression:: + + real = Word(nums) + '.' + Word(nums) + + will match "3.14159", but will also match "3 . 12". It will also + return the matched results as ['3', '.', '14159']. By changing this + expression to:: + + real = Combine( Word(nums) + '.' + Word(nums) ) + + it will not match numbers with embedded spaces, and it will return a + single concatenated string '3.14159' as the parsed token. + +- Repetition of expressions can be indicated using the '*' operator. An + expression may be multiplied by an integer value (to indicate an exact + repetition count), or by a tuple containing + two integers, or None and an integer, representing min and max repetitions + (with None representing no min or no max, depending whether it is the first or + second tuple element). See the following examples, where n is used to + indicate an integer value: + + - ``expr*3`` is equivalent to ``expr + expr + expr`` + + - ``expr*(2,3)`` is equivalent to ``expr + expr + Optional(expr)`` + + - ``expr*(n,None)`` or ``expr*(n,)`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` (read as "at least n instances of expr") + + - ``expr*(None,n)`` is equivalent to ``expr*(0,n)`` + (read as "0 to n instances of expr") + + - ``expr*(None,None)`` is equivalent to ``ZeroOrMore(expr)`` + + - ``expr*(1,None)`` is equivalent to ``OneOrMore(expr)`` + + Note that ``expr*(None,n)`` does not raise an exception if + more than n exprs exist in the input stream; that is, + ``expr*(None,n)`` does not enforce a maximum number of expr + occurrences. If this behavior is desired, then write + ``expr*(None,n) + ~expr``. + +- ``MatchFirst`` expressions are matched left-to-right, and the first + match found will skip all later expressions within, so be sure + to define less-specific patterns after more-specific patterns. + If you are not sure which expressions are most specific, use Or + expressions (defined using the ``^`` operator) - they will always + match the longest expression, although they are more + compute-intensive. + +- ``Or`` expressions will evaluate all of the specified subexpressions + to determine which is the "best" match, that is, which matches + the longest string in the input data. In case of a tie, the + left-most expression in the ``Or`` list will win. + +- If parsing the contents of an entire file, pass it to the + ``parseFile`` method using:: + + expr.parseFile( sourceFile ) + +- ``ParseExceptions`` will report the location where an expected token + or expression failed to match. For example, if we tried to use our + "Hello, World!" parser to parse "Hello World!" (leaving out the separating + comma), we would get an exception, with the message:: + + pyparsing.ParseException: Expected "," (6), (1,7) + + In the case of complex + expressions, the reported location may not be exactly where you + would expect. See more information under ParseException_ . + +- Use the ``Group`` class to enclose logical groups of tokens within a + sublist. This will help organize your results into more + hierarchical form (the default behavior is to return matching + tokens as a flat list of matching input strings). + +- Punctuation may be significant for matching, but is rarely of + much interest in the parsed results. Use the ``suppress()`` method + to keep these tokens from cluttering up your returned lists of + tokens. For example, ``delimitedList()`` matches a succession of + one or more expressions, separated by delimiters (commas by + default), but only returns a list of the actual expressions - + the delimiters are used for parsing, but are suppressed from the + returned output. + +- Parse actions can be used to convert values from strings to + other data types (ints, floats, booleans, etc.). + +- Results names are recommended for retrieving tokens from complex + expressions. It is much easier to access a token using its field + name than using a positional index, especially if the expression + contains optional elements. You can also shortcut + the ``setResultsName`` call:: + + stats = "AVE:" + realNum.setResultsName("average") + \ + "MIN:" + realNum.setResultsName("min") + \ + "MAX:" + realNum.setResultsName("max") + + can now be written as this:: + + stats = "AVE:" + realNum("average") + \ + "MIN:" + realNum("min") + \ + "MAX:" + realNum("max") + +- Be careful when defining parse actions that modify global variables or + data structures (as in ``fourFn.py``), especially for low level tokens + or expressions that may occur within an ``And`` expression; an early element + of an ``And`` may match, but the overall expression may fail. + +- Performance of pyparsing may be slow for complex grammars and/or large + input strings. The psyco_ package can be used to improve the speed of the + pyparsing module with no changes to grammar or program logic - observed + improvments have been in the 20-50% range. + +.. _psyco: http://psyco.sourceforge.net/ + + +Classes +======= + +Classes in the pyparsing module +------------------------------- + +``ParserElement`` - abstract base class for all pyparsing classes; +methods for code to use are: + +- ``parseString( sourceString, parseAll=False )`` - only called once, on the overall + matching pattern; returns a ParseResults_ object that makes the + matched tokens available as a list, and optionally as a dictionary, + or as an object with named attributes; if parseAll is set to True, then + parseString will raise a ParseException if the grammar does not process + the complete input string. + +- ``parseFile( sourceFile )`` - a convenience function, that accepts an + input file object or filename. The file contents are passed as a + string to ``parseString()``. ``parseFile`` also supports the ``parseAll`` argument. + +- ``scanString( sourceString )`` - generator function, used to find and + extract matching text in the given source string; for each matched text, + returns a tuple of: + + - matched tokens (packaged as a ParseResults_ object) + + - start location of the matched text in the given source string + + - end location in the given source string + + ``scanString`` allows you to scan through the input source string for + random matches, instead of exhaustively defining the grammar for the entire + source text (as would be required with ``parseString``). + +- ``transformString( sourceString )`` - convenience wrapper function for + ``scanString``, to process the input source string, and replace matching + text with the tokens returned from parse actions defined in the grammar + (see setParseAction_). + +- ``searchString( sourceString )`` - another convenience wrapper function for + ``scanString``, returns a list of the matching tokens returned from each + call to ``scanString``. + +- ``setName( name )`` - associate a short descriptive name for this + element, useful in displaying exceptions and trace information + +- ``setResultsName( string, listAllMatches=False )`` - name to be given + to tokens matching + the element; if multiple tokens within + a repetition group (such as ``ZeroOrMore`` or ``delimitedList``) the + default is to return only the last matching token - if listAllMatches + is set to True, then a list of matching tokens is returned. Note: + ``setResultsName`` returns a *copy* of the element so that a single + basic element can be referenced multiple times and given + different names within a complex grammar. + +.. _setParseAction: + +- ``setParseAction( *fn )`` - specify one or more functions to call after successful + matching of the element; each function is defined as ``fn( s, + loc, toks )``, where: + + - ``s`` is the original parse string + + - ``loc`` is the location in the string where matching started + + - ``toks`` is the list of the matched tokens, packaged as a ParseResults_ object + + Multiple functions can be attached to a ParserElement by specifying multiple + arguments to setParseAction, or by calling setParseAction multiple times. + + Each parse action function can return a modified ``toks`` list, to perform conversion, or + string modifications. For brevity, ``fn`` may also be a + lambda - here is an example of using a parse action to convert matched + integer tokens from strings to integers:: + + intNumber = Word(nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) + + If ``fn`` does not modify the ``toks`` list, it does not need to return + anything at all. + +- ``setBreak( breakFlag=True )`` - if breakFlag is True, calls pdb.set_break() + as this expression is about to be parsed + +- ``copy()`` - returns a copy of a ParserElement; can be used to use the same + parse expression in different places in a grammar, with different parse actions + attached to each + +- ``leaveWhitespace()`` - change default behavior of skipping + whitespace before starting matching (mostly used internally to the + pyparsing module, rarely used by client code) + +- ``setWhitespaceChars( chars )`` - define the set of chars to be ignored + as whitespace before trying to match a specific ParserElement, in place of the + default set of whitespace (space, tab, newline, and return) + +- ``setDefaultWhitespaceChars( chars )`` - class-level method to override + the default set of whitespace chars for all subsequently created ParserElements + (including copies); useful when defining grammars that treat one or more of the + default whitespace characters as significant (such as a line-sensitive grammar, to + omit newline from the list of ignorable whitespace) + +- ``suppress()`` - convenience function to suppress the output of the + given element, instead of wrapping it with a Suppress object. + +- ``ignore( expr )`` - function to specify parse expression to be + ignored while matching defined patterns; can be called + repeatedly to specify multiple expressions; useful to specify + patterns of comment syntax, for example + +- ``setDebug( dbgFlag=True )`` - function to enable/disable tracing output + when trying to match this element + +- ``validate()`` - function to verify that the defined grammar does not + contain infinitely recursive constructs + +.. _parseWithTabs: + +- ``parseWithTabs()`` - function to override default behavior of converting + tabs to spaces before parsing the input string; rarely used, except when + specifying whitespace-significant grammars using the White_ class. + +- ``enablePackrat()`` - a class-level static method to enable a memoizing + performance enhancement, known as "packrat parsing". packrat parsing is + disabled by default, since it may conflict with some user programs that use + parse actions. To activate the packrat feature, your + program must call the class method ParserElement.enablePackrat(). If + your program uses psyco to "compile as you go", you must call + enablePackrat before calling psyco.full(). If you do not do this, + Python will crash. For best results, call enablePackrat() immediately + after importing pyparsing. + + +Basic ParserElement subclasses +------------------------------ + +- ``Literal`` - construct with a string to be matched exactly + +- ``CaselessLiteral`` - construct with a string to be matched, but + without case checking; results are always returned as the + defining literal, NOT as they are found in the input string + +- ``Keyword`` - similar to Literal, but must be immediately followed by + whitespace, punctuation, or other non-keyword characters; prevents + accidental matching of a non-keyword that happens to begin with a + defined keyword + +- ``CaselessKeyword`` - similar to Keyword, but with caseless matching + behavior + +.. _Word: + +- ``Word`` - one or more contiguous characters; construct with a + string containing the set of allowed initial characters, and an + optional second string of allowed body characters; for instance, + a common Word construct is to match a code identifier - in C, a + valid identifier must start with an alphabetic character or an + underscore ('_'), followed by a body that can also include numeric + digits. That is, ``a``, ``i``, ``MAX_LENGTH``, ``_a1``, ``b_109_``, and + ``plan9FromOuterSpace`` + are all valid identifiers; ``9b7z``, ``$a``, ``.section``, and ``0debug`` + are not. To + define an identifier using a Word, use either of the following:: + + - Word( alphas+"_", alphanums+"_" ) + - Word( srange("[a-zA-Z_]"), srange("[a-zA-Z0-9_]") ) + + If only one + string given, it specifies that the same character set defined + for the initial character is used for the word body; for instance, to + define an identifier that can only be composed of capital letters and + underscores, use:: + + - Word( "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" ) + - Word( srange("[A-Z_]") ) + + A Word may + also be constructed with any of the following optional parameters: + + - min - indicating a minimum length of matching characters + + - max - indicating a maximum length of matching characters + + - exact - indicating an exact length of matching characters + + If exact is specified, it will override any values for min or max. + +- ``CharsNotIn`` - similar to Word_, but matches characters not + in the given constructor string (accepts only one string for both + initial and body characters); also supports min, max, and exact + optional parameters. + +- ``Regex`` - a powerful construct, that accepts a regular expression + to be matched at the current parse position; accepts an optional + flags parameter, corresponding to the flags parameter in the re.compile + method; if the expression includes named sub-fields, they will be + represented in the returned ParseResults_ + +- ``QuotedString`` - supports the definition of custom quoted string + formats, in addition to pyparsing's built-in dblQuotedString and + sglQuotedString. QuotedString allows you to specify the following + parameters: + + - quoteChar - string of one or more characters defining the quote delimiting string + + - escChar - character to escape quotes, typically backslash (default=None) + + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) + + - multiline - boolean indicating whether quotes can span multiple lines (default=False) + + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) + + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) + +- ``SkipTo`` - skips ahead in the input string, accepting any + characters up to the specified pattern; may be constructed with + the following optional parameters: + + - include - if set to true, also consumes the match expression + (default is false) + + - ignore - allows the user to specify patterns to not be matched, + to prevent false matches + + - failOn - if a literal string or expression is given for this argument, it defines an expression that + should cause the ``SkipTo`` expression to fail, and not skip over that expression + +.. _White: + +- ``White`` - also similar to Word_, but matches whitespace + characters. Not usually needed, as whitespace is implicitly + ignored by pyparsing. However, some grammars are whitespace-sensitive, + such as those that use leading tabs or spaces to indicating grouping + or hierarchy. (If matching on tab characters, be sure to call + parseWithTabs_ on the top-level parse element.) + +- ``Empty`` - a null expression, requiring no characters - will always + match; useful for debugging and for specialized grammars + +- ``NoMatch`` - opposite of Empty, will never match; useful for debugging + and for specialized grammars + + +Expression subclasses +--------------------- + +- ``And`` - construct with a list of ParserElements, all of which must + match for And to match; can also be created using the '+' + operator; multiple expressions can be Anded together using the '*' + operator as in:: + + ipAddress = Word(nums) + ('.'+Word(nums))*3 + + A tuple can be used as the multiplier, indicating a min/max:: + + usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) + + A special form of ``And`` is created if the '-' operator is used + instead of the '+' operator. In the ipAddress example above, if + no trailing '.' and Word(nums) are found after matching the initial + Word(nums), then pyparsing will back up in the grammar and try other + alternatives to ipAddress. However, if ipAddress is defined as:: + + strictIpAddress = Word(nums) - ('.'+Word(nums))*3 + + then no backing up is done. If the first Word(nums) of strictIpAddress + is matched, then any mismatch after that will raise a ParseSyntaxException, + which will halt the parsing process immediately. By careful use of the + '-' operator, grammars can provide meaningful error messages close to + the location where the incoming text does not match the specified + grammar. + +- ``Or`` - construct with a list of ParserElements, any of which must + match for Or to match; if more than one expression matches, the + expression that makes the longest match will be used; can also + be created using the '^' operator + +- ``MatchFirst`` - construct with a list of ParserElements, any of + which must match for MatchFirst to match; matching is done + left-to-right, taking the first expression that matches; can + also be created using the '|' operator + +- ``Each`` - similar to And, in that all of the provided expressions + must match; however, Each permits matching to be done in any order; + can also be created using the '&' operator + +- ``Optional`` - construct with a ParserElement, but this element is + not required to match; can be constructed with an optional ``default`` argument, + containing a default string or object to be supplied if the given optional + parse element is not found in the input string; parse action will only + be called if a match is found, or if a default is specified + +- ``ZeroOrMore`` - similar to Optional, but can be repeated + +- ``OneOrMore`` - similar to ZeroOrMore, but at least one match must + be present + +- ``FollowedBy`` - a lookahead expression, requires matching of the given + expressions, but does not advance the parsing position within the input string + +- ``NotAny`` - a negative lookahead expression, prevents matching of named + expressions, does not advance the parsing position within the input string; + can also be created using the unary '~' operator + + +.. _operators: + +Expression operators +-------------------- + +- ``+`` - consecutive sequence + +- ``|`` - match first alternative + +- ``^`` - match longest alternative + +- ``&`` - match each alternative, in any order + +- ``-`` - like ``+`` but with no backup and retry of alternatives + +- ``*`` - repetition of expression + +- ``==`` - matching expression to string; returns True if the string matches the given expression + +- ``<<`` - expression definition for Forward expressions + + +Positional subclasses +--------------------- + +- ``StringStart`` - matches beginning of the text + +- ``StringEnd`` - matches the end of the text + +- ``LineStart`` - matches beginning of a line (lines delimited by ``\n`` characters) + +- ``LineEnd`` - matches the end of a line + +- ``WordStart`` - matches a leading word boundary + +- ``WordEnd`` - matches a trailing word boundary + + + +Converter subclasses +-------------------- + +- ``Upcase`` - converts matched tokens to uppercase (deprecated - + use ``upcaseTokens`` parse action instead) + +- ``Combine`` - joins all matched tokens into a single string, using + specified joinString (default ``joinString=""``); expects + all matching tokens to be adjacent, with no intervening + whitespace (can be overridden by specifying ``adjacent=False`` in constructor) + +- ``Suppress`` - clears matched tokens; useful to keep returned + results from being cluttered with required but uninteresting + tokens (such as list delimiters) + + +Special subclasses +------------------ + +- ``Group`` - causes the matched tokens to be enclosed in a list; + useful in repeated elements like ``ZeroOrMore`` and ``OneOrMore`` to + break up matched tokens into groups for each repeated pattern + +- ``Dict`` - like ``Group``, but also constructs a dictionary, using the + [0]'th elements of all enclosed token lists as the keys, and + each token list as the value + +- ``SkipTo`` - catch-all matching expression that accepts all characters + up until the given pattern is found to match; useful for specifying + incomplete grammars + +- ``Forward`` - placeholder token used to define recursive token + patterns; when defining the actual expression later in the + program, insert it into the ``Forward`` object using the ``<<`` + operator (see ``fourFn.py`` for an example). + + +Other classes +------------- +.. _ParseResults: + +- ``ParseResults`` - class used to contain and manage the lists of tokens + created from parsing the input using the user-defined parse + expression. ParseResults can be accessed in a number of ways: + + - as a list + + - total list of elements can be found using len() + + - individual elements can be found using [0], [1], [-1], etc. + + - elements can be deleted using ``del`` + + - the -1th element can be extracted and removed in a single operation + using ``pop()``, or any element can be extracted and removed + using ``pop(n)`` + + - as a dictionary + + - if ``setResultsName()`` is used to name elements within the + overall parse expression, then these fields can be referenced + as dictionary elements or as attributes + + - the Dict class generates dictionary entries using the data of the + input text - in addition to ParseResults listed as ``[ [ a1, b1, c1, ...], [ a2, b2, c2, ...] ]`` + it also acts as a dictionary with entries defined as ``{ a1 : [ b1, c1, ... ] }, { a2 : [ b2, c2, ... ] }``; + this is especially useful when processing tabular data where the first column contains a key + value for that line of data + + - list elements that are deleted using ``del`` will still be accessible by their + dictionary keys + + - supports ``get()``, ``items()`` and ``keys()`` methods, similar to a dictionary + + - a keyed item can be extracted and removed using ``pop(key)``. Here + key must be non-numeric (such as a string), in order to use dict + extraction instead of list extraction. + + - new named elements can be added (in a parse action, for instance), using the same + syntax as adding an item to a dict (``parseResults["X"]="new item"``); named elements can be removed using ``del parseResults["X"]`` + + - as a nested list + + - results returned from the Group class are encapsulated within their + own list structure, so that the tokens can be handled as a hierarchical + tree + + ParseResults can also be converted to an ordinary list of strings + by calling ``asList()``. Note that this will strip the results of any + field names that have been defined for any embedded parse elements. + (The ``pprint`` module is especially good at printing out the nested contents + given by ``asList()``.) + + Finally, ParseResults can be converted to an XML string by calling ``asXML()``. Where + possible, results will be tagged using the results names defined for the respective + ParseExpressions. ``asXML()`` takes two optional arguments: + + - doctagname - for ParseResults that do not have a defined name, this argument + will wrap the resulting XML in a set of opening and closing tags ``<doctagname>`` + and ``</doctagname>``. + + - namedItemsOnly (default=False) - flag to indicate if the generated XML should + skip items that do not have defined names. If a nested group item is named, then all + embedded items will be included, whether they have names or not. + + +Exception classes and Troubleshooting +------------------------------------- + +.. _ParseException: + +- ``ParseException`` - exception returned when a grammar parse fails; + ParseExceptions have attributes loc, msg, line, lineno, and column; to view the + text line and location where the reported ParseException occurs, use:: + + except ParseException, err: + print err.line + print " "*(err.column-1) + "^" + print err + +- ``RecursiveGrammarException`` - exception returned by ``validate()`` if + the grammar contains a recursive infinite loop, such as:: + + badGrammar = Forward() + goodToken = Literal("A") + badGrammar << Optional(goodToken) + badGrammar + +- ``ParseFatalException`` - exception that parse actions can raise to stop parsing + immediately. Should be used when a semantic error is found in the input text, such + as a mismatched XML tag. + +- ``ParseSyntaxException`` - subclass of ``ParseFatalException`` raised when a + syntax error is found, based on the use of the '-' operator when defining + a sequence of expressions in an ``And`` expression. + +You can also get some insights into the parsing logic using diagnostic parse actions, +and setDebug(), or test the matching of expression fragments by testing them using +scanString(). + + +Miscellaneous attributes and methods +==================================== + +Helper methods +-------------- + +- ``delimitedList( expr, delim=',')`` - convenience function for + matching one or more occurrences of expr, separated by delim. + By default, the delimiters are suppressed, so the returned results contain + only the separate list elements. Can optionally specify ``combine=True``, + indicating that the expressions and delimiters should be returned as one + combined value (useful for scoped variables, such as "a.b.c", or + "a::b::c", or paths such as "a/b/c"). + +- ``countedArray( expr )`` - convenience function for a pattern where an list of + instances of the given expression are preceded by an integer giving the count of + elements in the list. Returns an expression that parses the leading integer, + reads exactly that many expressions, and returns the array of expressions in the + parse results - the leading integer is suppressed from the results (although it + is easily reconstructed by using len on the returned array). + +- ``oneOf( string, caseless=False )`` - convenience function for quickly declaring an + alternative set of ``Literal`` tokens, by splitting the given string on + whitespace boundaries. The tokens are sorted so that longer + matches are attempted first; this ensures that a short token does + not mask a longer one that starts with the same characters. If ``caseless=True``, + will create an alternative set of CaselessLiteral tokens. + +- ``dictOf( key, value )`` - convenience function for quickly declaring a + dictionary pattern of ``Dict( ZeroOrMore( Group( key + value ) ) )``. + +- ``makeHTMLTags( tagName )`` and ``makeXMLTags( tagName )`` - convenience + functions to create definitions of opening and closing tag expressions. Returns + a pair of expressions, for the corresponding <tag> and </tag> strings. Includes + support for attributes in the opening tag, such as <tag attr1="abc"> - attributes + are returned as keyed tokens in the returned ParseResults. ``makeHTMLTags`` is less + restrictive than ``makeXMLTags``, especially with respect to case sensitivity. + +- ``operatorPrecedence(baseOperand, operatorList)`` - convenience function to define a + grammar for parsing + expressions with a hierarchical precedence of operators. To use the operatorPrecedence + helper: + + 1. Define the base "atom" operand term of the grammar. + For this simple grammar, the smallest operand is either + and integer or a variable. This will be the first argument + to the operatorPrecedence method. + + 2. Define a list of tuples for each level of operator + precendence. Each tuple is of the form + ``(opExpr, numTerms, rightLeftAssoc, parseAction)``, where: + + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; if + None, indicates an empty operator, such as the implied + multiplication operation between 'm' and 'x' in "y = mx + b". + + - numTerms is the number of terms for this operator (must + be 1 or 2) + + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants ``opAssoc.RIGHT`` and ``opAssoc.LEFT``. + + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted) + + 3. Call operatorPrecedence passing the operand expression and + the operator precedence list, and save the returned value + as the generated pyparsing expression. You can then use + this expression to parse input strings, or incorporate it + into a larger, more complex grammar. + +- ``matchPreviousLiteral`` and ``matchPreviousExpr`` - function to define and + expression that matches the same content + as was parsed in a previous parse expression. For instance:: + + first = Word(nums) + matchExpr = first + ":" + matchPreviousLiteral(first) + + will match "1:1", but not "1:2". Since this matches at the literal + level, this will also match the leading "1:1" in "1:10". + + In contrast:: + + first = Word(nums) + matchExpr = first + ":" + matchPreviousExpr(first) + + will *not* match the leading "1:1" in "1:10"; the expressions are + evaluated first, and then compared, so "1" is compared with "10". + +- ``nestedExpr(opener, closer, content=None, ignoreExpr=quotedString)`` - method for defining nested + lists enclosed in opening and closing delimiters. + + - opener - opening character for a nested list (default="("); can also be a pyparsing expression + + - closer - closing character for a nested list (default=")"); can also be a pyparsing expression + + - content - expression for items within the nested lists (default=None) + + - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the ignoreExpr argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an Or or MatchFirst. + The default is quotedString, but if no expressions are to be ignored, + then pass None for this argument. + + +- ``indentedBlock( statementExpr, indentationStackVar, indent=True)`` - + function to define an indented block of statements, similar to + indentation-based blocking in Python source code: + + - statementExpr is the expression defining a statement that + will be found in the indented block; a valid indentedBlock + must contain at least 1 matching statementExpr + + - indentationStackVar is a Python list variable; this variable + should be common to all ``indentedBlock`` expressions defined + within the same grammar, and should be reinitialized to [1] + each time the grammar is to be used + + - indent is a boolean flag indicating whether the expressions + within the block must be indented from the current parse + location; if using indentedBlock to define the left-most + statements (all starting in column 1), set indent to False + +.. _originalTextFor: + +- ``originalTextFor( expr )`` - helper function to preserve the originally parsed text, regardless of any + token processing or conversion done by the contained expression. For instance, the following expression:: + + fullName = Word(alphas) + Word(alphas) + + will return the parse of "John Smith" as ['John', 'Smith']. In some applications, the actual name as it + was given in the input string is what is desired. To do this, use ``originalTextFor``:: + + fullName = originalTextFor(Word(alphas) + Word(alphas)) + +- ``lineno( loc, string )`` - function to give the line number of the + location within the string; the first line is line 1, newlines + start new rows + +- ``col( loc, string )`` - function to give the column number of the + location within the string; the first column is column 1, + newlines reset the column number to 1 + +- ``line( loc, string )`` - function to retrieve the line of text + representing ``lineno( loc, string )``; useful when printing out diagnostic + messages for exceptions + +- ``srange( rangeSpec )`` - function to define a string of characters, + given a string of the form used by regexp string ranges, such as ``"[0-9]"`` for + all numeric digits, ``"[A-Z_]"`` for uppercase characters plus underscore, and + so on (note that rangeSpec does not include support for generic regular + expressions, just string range specs) + +- ``getTokensEndLoc()`` - function to call from within a parse action to get + the ending location for the matched tokens + +- ``traceParseAction(fn)`` - decorator function to debug parse actions. Lists + each call, called arguments, and return value or exception + + + +Helper parse actions +-------------------- + +- ``removeQuotes`` - removes the first and last characters of a quoted string; + useful to remove the delimiting quotes from quoted strings + +- ``replaceWith(replString)`` - returns a parse action that simply returns the + replString; useful when using transformString, or converting HTML entities, as in:: + + nbsp = Literal(" ").setParseAction( replaceWith("<BLANK>") ) + +- ``keepOriginalText``- (deprecated, use originalTextFor_ instead) restores any internal whitespace or suppressed + text within the tokens for a matched parse + expression. This is especially useful when defining expressions + for scanString or transformString applications. + +- ``withAttribute( *args, **kwargs )`` - helper to create a validating parse action to be used with start tags created + with ``makeXMLTags`` or ``makeHTMLTags``. Use ``withAttribute`` to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + ``<TD>`` or ``<DIV>``. + + ``withAttribute`` can be called with: + + - keyword arguments, as in ``(class="Customer",align="right")``, or + + - a list of name-value tuples, as in ``( ("ns1:class", "Customer"), ("ns2:align","right") )`` + + An attribute can be specified to have the special value + ``withAttribute.ANY_VALUE``, which will match any value - use this to + ensure that an attribute is present but any attribute value is + acceptable. + +- ``downcaseTokens`` - converts all matched tokens to lowercase + +- ``upcaseTokens`` - converts all matched tokens to uppercase + +- ``matchOnlyAtCol( columnNumber )`` - a parse action that verifies that + an expression was matched at a particular column, raising a + ParseException if matching at a different column number; useful when parsing + tabular data + + + +Common string and token constants +--------------------------------- + +- ``alphas`` - same as ``string.letters`` + +- ``nums`` - same as ``string.digits`` + +- ``alphanums`` - a string containing ``alphas + nums`` + +- ``alphas8bit`` - a string containing alphabetic 8-bit characters:: + + ������������������������������������������������������������� + +- ``printables`` - same as ``string.printable``, minus the space (``' '``) character + +- ``empty`` - a global ``Empty()``; will always match + +- ``sglQuotedString`` - a string of characters enclosed in 's; may + include whitespace, but not newlines + +- ``dblQuotedString`` - a string of characters enclosed in "s; may + include whitespace, but not newlines + +- ``quotedString`` - ``sglQuotedString | dblQuotedString`` + +- ``cStyleComment`` - a comment block delimited by ``'/*'`` and ``'*/'`` sequences; can span + multiple lines, but does not support nesting of comments + +- ``htmlComment`` - a comment block delimited by ``'<!--'`` and ``'-->'`` sequences; can span + multiple lines, but does not support nesting of comments + +- ``commaSeparatedList`` - similar to ``delimitedList``, except that the + list expressions can be any text value, or a quoted string; quoted strings can + safely include commas without incorrectly breaking the string into two tokens + +- ``restOfLine`` - all remaining printable characters up to but not including the next + newline diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_TElDRU5TRQ== --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_TUFOSUZFU1QuaW4= --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include pyparsing.py +include HowToUsePyparsing.html pyparsingClassDiagram.* +include README CHANGES LICENSE +include examples/*.py examples/Setup.ini examples/*.dfm examples/*.ics examples/*.html +include htmldoc/*.* +include docs/*.* +include robots.txt diff --git a/README b/README new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_UkVBRE1F --- /dev/null +++ b/README @@ -0,0 +1,72 @@ +==================================== +PyParsing -- A Python Parsing Module +==================================== + +Introduction +============ + +The pyparsing module is an alternative approach to creating and executing +simple grammars, vs. the traditional lex/yacc approach, or the use of +regular expressions. The pyparsing module provides a library of classes +that client code uses to construct the grammar directly in Python code. + +Here is a program to parse "Hello, World!" (or any greeting of the form +"<salutation>, <addressee>!"): + + from pyparsing import Word, alphas + greet = Word( alphas ) + "," + Word( alphas ) + "!" + hello = "Hello, World!" + print hello, "->", greet.parseString( hello ) + +The program outputs the following: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the +self-explanatory class names, and the use of '+', '|' and '^' operator +definitions. + +The parsed results returned from parseString() can be accessed as a +nested list, a dictionary, or an object with named attributes. + +The pyparsing module handles some of the problems that are typically +vexing when writing text parsers: +- extra or missing whitespace (the above program will also handle + "Hello,World!", "Hello , World !", etc.) +- quoted strings +- embedded comments + +The .zip file includes examples of a simple SQL parser, simple CORBA IDL +parser, a config file parser, a chemical formula parser, and a four- +function algebraic notation parser. It also includes a simple how-to +document, and a UML class diagram of the library's classes. + + + +Installation +============ + +Do the usual: + + python setup.py install + +(pyparsing requires Python 2.3.2 or later.) + + +Documentation +============= + +See: + + HowToUsePyparsing.html + + +License +======= + + MIT License. See header of pyparsing.py + +History +======= + + See CHANGES file. diff --git a/parsingClassDiagram.PNG b/parsingClassDiagram.PNG new file mode 100644 index 0000000000000000000000000000000000000000..310bfe72b61e39fa2295b397d6f6ead69763b2c6 GIT binary patch literal 31650 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%0D_PhO<F>3I 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 Oy6!cjtGLT|9{dl)!zCF2 diff --git a/pyparsing.py b/pyparsing.py new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_cHlwYXJzaW5nLnB5 --- /dev/null +++ b/pyparsing.py @@ -0,0 +1,3716 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2009 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +#from __future__ import generators + +__doc__ = \ +""" +pyparsing module - Classes and methods to define and execute parsing grammars + +The pyparsing module is an alternative approach to creating and executing simple grammars, +vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you +don't need to learn a new syntax for defining grammars or matching expressions - the parsing module +provides a library of classes that you use to construct the grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form "<salutation>, <addressee>!"):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word( alphas ) + "," + Word( alphas ) + "!" + + hello = "Hello, World!" + print hello, "->", greet.parseString( hello ) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the self-explanatory +class names, and the use of '+', '|' and '^' operators. + +The parsed results returned from parseString() can be accessed as a nested list, a dictionary, or an +object with named attributes. + +The pyparsing module handles some of the problems that are typically vexing when writing text parsers: + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments +""" + +__version__ = "1.5.2" +__versionTime__ = "9 April 2009 12:21" +__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" + +import string +from weakref import ref as wkref +import copy +import sys +import warnings +import re +import sre_constants +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'getTokensEndLoc', 'hexnums', +'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', +'indentedBlock', 'originalTextFor', +] + +""" +Detect if we are running version 3.X and make appropriate changes +Robert A. Clark +""" +_PY3K = sys.version_info[0] > 2 +if _PY3K: + _MAX_INT = sys.maxsize + basestring = str + unichr = chr + _ustr = str + _str2dict = set + alphas = string.ascii_lowercase + string.ascii_uppercase +else: + _MAX_INT = sys.maxint + + def _ustr(obj): + """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries + str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It + then < returns the unicode object | encodes it with the default encoding | ... >. + """ + if isinstance(obj,unicode): + return obj + + try: + # If this works, then _ustr(obj) has the same behaviour as str(obj), so + # it won't break any existing code. + return str(obj) + + except UnicodeEncodeError: + # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) + # state that "The return value must be a string object". However, does a + # unicode object (being a subclass of basestring) count as a "string + # object"? + # If so, then return a unicode object: + return unicode(obj) + # Else encode it... but how? There are many choices... :) + # Replace unprintables with escape codes? + #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') + # Replace unprintables with question marks? + #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') + # ... + + def _str2dict(strg): + return dict( [(c,0) for c in strg] ) + + alphas = string.lowercase + string.uppercase + + +def _xml_escape(data): + """Escape &, <, >, ", ', etc. in a string of data.""" + + # ampersand must be replaced first + from_symbols = '&><"\'' + to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] + for from_,to_ in zip(from_symbols, to_symbols): + data = data.replace(from_, to_) + return data + +class _Constants(object): + pass + +nums = string.digits +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) +printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, pstr, loc=0, msg=None, elem=None ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parserElement = elem + + def __getattr__( self, aname ): + """supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + if( aname == "lineno" ): + return lineno( self.loc, self.pstr ) + elif( aname in ("col", "column") ): + return col( self.loc, self.pstr ) + elif( aname == "line" ): + return line( self.loc, self.pstr ) + else: + raise AttributeError(aname) + + def __str__( self ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, self.loc, self.lineno, self.column ) + def __repr__( self ): + return _ustr(self) + def markInputline( self, markerString = ">!<" ): + """Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( [line_str[:line_column], + markerString, line_str[line_column:]]) + return line_str.strip() + def __dir__(self): + return "loc msg pstr parserElement lineno col line " \ + "markInputLine __str__ __repr__".split() + +class ParseException(ParseBaseException): + """exception thrown when parse expressions don't match class; + supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + pass + +class ParseFatalException(ParseBaseException): + """user-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately""" + pass + +class ParseSyntaxException(ParseFatalException): + """just like ParseFatalException, but thrown internally when an + ErrorStop indicates that parsing is to stop immediately because + an unbacktrackable syntax error has been found""" + def __init__(self, pe): + super(ParseSyntaxException, self).__init__( + pe.pstr, pe.loc, pe.msg, pe.parserElement) + +#~ class ReparseException(ParseBaseException): + #~ """Experimental class - parse actions can raise this exception to cause + #~ pyparsing to reparse the input string: + #~ - with a modified input string, and/or + #~ - with a modified start location + #~ Set the values of the ReparseException in the constructor, and raise the + #~ exception in a parse action to cause pyparsing to use the new string/location. + #~ Setting the values as None causes no change to be made. + #~ """ + #~ def __init_( self, newstring, restartLoc ): + #~ self.newParseText = newstring + #~ self.reparseLoc = restartLoc + +class RecursiveGrammarException(Exception): + """exception thrown by validate() if the grammar could be improperly recursive""" + def __init__( self, parseElementList ): + self.parseElementTrace = parseElementList + + def __str__( self ): + return "RecursiveGrammarException: %s" % self.parseElementTrace + +class _ParseResultsWithOffset(object): + def __init__(self,p1,p2): + self.tup = (p1,p2) + def __getitem__(self,i): + return self.tup[i] + def __repr__(self): + return repr(self.tup) + def setOffset(self,i): + self.tup = (self.tup[0],i) + +class ParseResults(object): + """Structured parse results, to provide multiple means of access to the parsed data: + - as a list (len(results)) + - by list index (results[0], results[1], etc.) + - by attribute (results.<resultsName>) + """ + __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) + def __new__(cls, toklist, name=None, asList=True, modal=True ): + if isinstance(toklist, cls): + return toklist + retobj = object.__new__(cls) + retobj.__doinit = True + return retobj + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, toklist, name=None, asList=True, modal=True ): + if self.__doinit: + self.__doinit = False + self.__name = None + self.__parent = None + self.__accumNames = {} + if isinstance(toklist, list): + self.__toklist = toklist[:] + else: + self.__toklist = [toklist] + self.__tokdict = dict() + + if name: + if not modal: + self.__accumNames[name] = 0 + if isinstance(name,int): + name = _ustr(name) # will always return a str, but use _ustr for consistency + self.__name = name + if not toklist in (None,'',[]): + if isinstance(toklist,basestring): + toklist = [ toklist ] + if asList: + if isinstance(toklist,ParseResults): + self[name] = _ParseResultsWithOffset(toklist.copy(),0) + else: + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name].__name = name + else: + try: + self[name] = toklist[0] + except (KeyError,TypeError,IndexError): + self[name] = toklist + + def __getitem__( self, i ): + if isinstance( i, (int,slice) ): + return self.__toklist[i] + else: + if i not in self.__accumNames: + return self.__tokdict[i][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[i] ]) + + def __setitem__( self, k, v ): + if isinstance(v,_ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + sub = v[0] + elif isinstance(k,int): + self.__toklist[k] = v + sub = v + else: + self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + sub = v + if isinstance(sub,ParseResults): + sub.__parent = wkref(self) + + def __delitem__( self, i ): + if isinstance(i,(int,slice)): + mylen = len( self.__toklist ) + del self.__toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i+1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) + else: + del self.__tokdict[i] + + def __contains__( self, k ): + return k in self.__tokdict + + def __len__( self ): return len( self.__toklist ) + def __bool__(self): return len( self.__toklist ) > 0 + __nonzero__ = __bool__ + def __iter__( self ): return iter( self.__toklist ) + def __reversed__( self ): return iter( reversed(self.__toklist) ) + def keys( self ): + """Returns all named result keys.""" + return self.__tokdict.keys() + + def pop( self, index=-1 ): + """Removes and returns item at specified index (default=last). + Will work with either numeric indices or dict-key indicies.""" + ret = self[index] + del self[index] + return ret + + def get(self, key, defaultValue=None): + """Returns named result matching the given key, or if there is no + such name, then returns the given defaultValue or None if no + defaultValue is specified.""" + if key in self: + return self[key] + else: + return defaultValue + + def insert( self, index, insStr ): + self.__toklist.insert(index, insStr) + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) + + def items( self ): + """Returns all named result keys and values as a list of tuples.""" + return [(k,self[k]) for k in self.__tokdict] + + def values( self ): + """Returns all named result values.""" + return [ v[-1][0] for v in self.__tokdict.values() ] + + def __getattr__( self, name ): + if name not in self.__slots__: + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + return "" + return None + + def __add__( self, other ): + ret = self.copy() + ret += other + return ret + + def __iadd__( self, other ): + if other.__tokdict: + offset = len(self.__toklist) + addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) + otheritems = other.__tokdict.items() + otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) + for (k,vlist) in otheritems for v in vlist] + for k,v in otherdictitems: + self[k] = v + if isinstance(v[0],ParseResults): + v[0].__parent = wkref(self) + + self.__toklist += other.__toklist + self.__accumNames.update( other.__accumNames ) + del other + return self + + def __repr__( self ): + return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + + def __str__( self ): + out = "[" + sep = "" + for i in self.__toklist: + if isinstance(i, ParseResults): + out += sep + _ustr(i) + else: + out += sep + repr(i) + sep = ", " + out += "]" + return out + + def _asStringList( self, sep='' ): + out = [] + for item in self.__toklist: + if out and sep: + out.append(sep) + if isinstance( item, ParseResults ): + out += item._asStringList() + else: + out.append( _ustr(item) ) + return out + + def asList( self ): + """Returns the parse results as a nested list of matching tokens, all converted to strings.""" + out = [] + for res in self.__toklist: + if isinstance(res,ParseResults): + out.append( res.asList() ) + else: + out.append( res ) + return out + + def asDict( self ): + """Returns the named parse results as dictionary.""" + return dict( self.items() ) + + def copy( self ): + """Returns a new copy of a ParseResults object.""" + ret = ParseResults( self.__toklist ) + ret.__tokdict = self.__tokdict.copy() + ret.__parent = self.__parent + ret.__accumNames.update( self.__accumNames ) + ret.__name = self.__name + return ret + + def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" + nl = "\n" + out = [] + namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() + for v in vlist ] ) + nextLevelIndent = indent + " " + + # collapse out indents if formatting is not desired + if not formatted: + indent = "" + nextLevelIndent = "" + nl = "" + + selfTag = None + if doctag is not None: + selfTag = doctag + else: + if self.__name: + selfTag = self.__name + + if not selfTag: + if namedItemsOnly: + return "" + else: + selfTag = "ITEM" + + out += [ nl, indent, "<", selfTag, ">" ] + + worklist = self.__toklist + for i,res in enumerate(worklist): + if isinstance(res,ParseResults): + if i in namedItems: + out += [ res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + out += [ res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + # individual token, see if there is a name for it + resTag = None + if i in namedItems: + resTag = namedItems[i] + if not resTag: + if namedItemsOnly: + continue + else: + resTag = "ITEM" + xmlBodyText = _xml_escape(_ustr(res)) + out += [ nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "</", resTag, ">" ] + + out += [ nl, indent, "</", selfTag, ">" ] + return "".join(out) + + def __lookup(self,sub): + for k,vlist in self.__tokdict.items(): + for v,loc in vlist: + if sub is v: + return k + return None + + def getName(self): + """Returns the results name for this token expression.""" + if self.__name: + return self.__name + elif self.__parent: + par = self.__parent() + if par: + return par.__lookup(self) + else: + return None + elif (len(self) == 1 and + len(self.__tokdict) == 1 and + self.__tokdict.values()[0][0][1] in (0,-1)): + return self.__tokdict.keys()[0] + else: + return None + + def dump(self,indent='',depth=0): + """Diagnostic method for listing out the contents of a ParseResults. + Accepts an optional indent argument so that this string can be embedded + in a nested display of other data.""" + out = [] + out.append( indent+_ustr(self.asList()) ) + keys = self.items() + keys.sort() + for k,v in keys: + if out: + out.append('\n') + out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) + if isinstance(v,ParseResults): + if v.keys(): + out.append( v.dump(indent,depth+1) ) + else: + out.append(_ustr(v)) + else: + out.append(_ustr(v)) + return "".join(out) + + # add support for pickle protocol + def __getstate__(self): + return ( self.__toklist, + ( self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name ) ) + + def __setstate__(self,state): + self.__toklist = state[0] + self.__tokdict, \ + par, \ + inAccumNames, \ + self.__name = state[1] + self.__accumNames = {} + self.__accumNames.update(inAccumNames) + if par is not None: + self.__parent = wkref(par) + else: + self.__parent = None + + def __dir__(self): + return dir(super(ParseResults,self)) + self.keys() + +def col (loc,strg): + """Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return (loc<len(strg) and strg[loc] == '\n') and 1 or loc - strg.rfind("\n", 0, loc) + +def lineno(loc,strg): + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return strg.count("\n",0,loc) + 1 + +def line( loc, strg ): + """Returns the line of text containing loc within a string, counting newlines as line separators. + """ + lastCR = strg.rfind("\n", 0, loc) + nextCR = strg.find("\n", loc) + if nextCR > 0: + return strg[lastCR+1:nextCR] + else: + return strg[lastCR+1:] + +def _defaultStartDebugAction( instring, loc, expr ): + print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): + print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) + +def _defaultExceptionDebugAction( instring, loc, expr, exc ): + print ("Exception raised:" + _ustr(exc)) + +def nullDebugAction(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + pass + +class ParserElement(object): + """Abstract base level parser element class.""" + DEFAULT_WHITE_CHARS = " \n\t\r" + + def setDefaultWhitespaceChars( chars ): + """Overrides the default whitespace chars + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) + + def __init__( self, savelist=False ): + self.parseAction = list() + self.failAction = None + #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall + self.strRepr = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + self.copyDefaultWhiteChars = True + self.mayReturnEmpty = False # used when checking for left-recursion + self.keepTabs = False + self.ignoreExprs = list() + self.debug = False + self.streamlined = False + self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index + self.errmsg = "" + self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) + self.debugActions = ( None, None, None ) #custom debug actions + self.re = None + self.callPreparse = True # used to avoid redundant calls to preParse + self.callDuringTry = False + + def copy( self ): + """Make a copy of this ParserElement. Useful for defining different parse actions + for the same parsing pattern, using copies of the original parse element.""" + cpy = copy.copy( self ) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + return cpy + + def setName( self, name ): + """Define name for this expression, for use in debugging.""" + self.name = name + self.errmsg = "Expected " + self.name + if hasattr(self,"exception"): + self.exception.msg = self.errmsg + return self + + def setResultsName( self, name, listAllMatches=False ): + """Define name for referencing matching tokens as a nested attribute + of the returned parse results. + NOTE: this returns a *copy* of the original ParserElement object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + """ + newself = self.copy() + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def setBreak(self,breakFlag = True): + """Method to invoke the Python pdb debugger when this element is + about to be parsed. Set breakFlag to True to enable, False to + disable. + """ + if breakFlag: + _parseMethod = self._parse + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + pdb.set_trace() + return _parseMethod( instring, loc, doActions, callPreParse ) + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse,"_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def _normalizeParseActionArgs( f ): + """Internal method used to decorate parse actions that take fewer than 3 arguments, + so that all parse actions can be called as f(s,l,t).""" + STAR_ARGS = 4 + + try: + restore = None + if isinstance(f,type): + restore = f + f = f.__init__ + if not _PY3K: + codeObj = f.func_code + else: + codeObj = f.code + if codeObj.co_flags & STAR_ARGS: + return f + numargs = codeObj.co_argcount + if not _PY3K: + if hasattr(f,"im_self"): + numargs -= 1 + else: + if hasattr(f,"__self__"): + numargs -= 1 + if restore: + f = restore + except AttributeError: + try: + if not _PY3K: + call_im_func_code = f.__call__.im_func.func_code + else: + call_im_func_code = f.__code__ + + # not a function, must be a callable object, get info from the + # im_func binding of its bound __call__ method + if call_im_func_code.co_flags & STAR_ARGS: + return f + numargs = call_im_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 0 + except AttributeError: + if not _PY3K: + call_func_code = f.__call__.func_code + else: + call_func_code = f.__call__.__code__ + # not a bound method, get info directly from __call__ method + if call_func_code.co_flags & STAR_ARGS: + return f + numargs = call_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 1 + + + #~ print ("adding function %s with %d args" % (f.func_name,numargs)) + if numargs == 3: + return f + else: + if numargs > 3: + def tmp(s,l,t): + return f(f.__call__.__self__, s,l,t) + if numargs == 2: + def tmp(s,l,t): + return f(l,t) + elif numargs == 1: + def tmp(s,l,t): + return f(t) + else: #~ numargs == 0: + def tmp(s,l,t): + return f() + try: + tmp.__name__ = f.__name__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__doc__ = f.__doc__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__dict__.update(f.__dict__) + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + return tmp + _normalizeParseActionArgs = staticmethod(_normalizeParseActionArgs) + + def setParseAction( self, *fns, **kwargs ): + """Define action to perform when successfully matching parse element definition. + Parse action fn is a callable method with 0-3 arguments, called as fn(s,loc,toks), + fn(loc,toks), fn(toks), or just fn(), where: + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a ParseResults object + If the functions in fns modify the tokens, they can return them as the return + value from fn, and the modified list of tokens will replace the original. + Otherwise, fn does not need to return any value. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{parseString}<parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + self.parseAction = list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def addParseAction( self, *fns, **kwargs ): + """Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.""" + self.parseAction += list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def setFailAction( self, fn ): + """Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + fn(s,loc,expr,err) where: + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + The function returns no value. It may throw ParseFatalException + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables( self, instring, loc ): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc,dummy = e._parse( instring, loc ) + exprsFound = True + except ParseException: + pass + return loc + + def preParse( self, instring, loc ): + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + + if self.skipWhitespace: + wt = self.whiteChars + instrlen = len(instring) + while loc < instrlen and instring[loc] in wt: + loc += 1 + + return loc + + def parseImpl( self, instring, loc, doActions=True ): + return loc, [] + + def postParse( self, instring, loc, tokenlist ): + return tokenlist + + #~ @profile + def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): + debugging = ( self.debug ) #and doActions ) + + if debugging or self.failAction: + #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + if (self.debugActions[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException: + #~ print ("Exception raised:", err) + err = None + if self.debugActions[2]: + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + if self.failAction: + if err is None: + err = sys.exc_info()[1] + self.failAction( instring, tokensStart, self, err ) + raise + else: + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + if self.mayIndexError or loc >= len(instring): + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + else: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + + tokens = self.postParse( instring, loc, tokens ) + + retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + except ParseBaseException: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + raise + else: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + + if debugging: + #~ print ("Matched",self,"->",retTokens.asList()) + if (self.debugActions[1] ): + self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + + return loc, retTokens + + def tryParse( self, instring, loc ): + try: + return self._parse( instring, loc, doActions=False )[0] + except ParseFatalException: + raise ParseException( instring, loc, self.errmsg, self) + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + lookup = (self,instring,loc,callPreParse,doActions) + if lookup in ParserElement._exprArgCache: + value = ParserElement._exprArgCache[ lookup ] + if isinstance(value,Exception): + raise value + return value + else: + try: + value = self._parseNoCache( instring, loc, doActions, callPreParse ) + ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) + return value + except ParseBaseException: + pe = sys.exc_info()[1] + ParserElement._exprArgCache[ lookup ] = pe + raise + + _parse = _parseNoCache + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + _exprArgCache = {} + def resetCache(): + ParserElement._exprArgCache.clear() + resetCache = staticmethod(resetCache) + + _packratEnabled = False + def enablePackrat(): + """Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method ParserElement.enablePackrat(). If + your program uses psyco to "compile as you go", you must call + enablePackrat before calling psyco.full(). If you do not do this, + Python will crash. For best results, call enablePackrat() immediately + after importing pyparsing. + """ + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + ParserElement._parse = ParserElement._parseCache + enablePackrat = staticmethod(enablePackrat) + + def parseString( self, instring, parseAll=False ): + """Execute the parse expression with the given string. + This is the main interface to the client code, once the complete + expression has been built. + + If you want the grammar to require that the entire input string be + successfully parsed, then set parseAll to True (equivalent to ending + the grammar with StringEnd()). + + Note: parseString implicitly calls expandtabs() on the input string, + in order to report proper column numbers in parse actions. + If the input string contains tabs and + the grammar uses parse actions that use the loc argument to index into the + string being parsed, you can ensure you have a consistent view of the input + string by: + - calling parseWithTabs on your grammar before calling parseString + (see L{I{parseWithTabs}<parseWithTabs>}) + - define your parse action using the full (s,loc,toks) signature, and + reference the input string using the parse action's s argument + - explictly expand the tabs in your input string before calling + parseString + """ + ParserElement.resetCache() + if not self.streamlined: + self.streamline() + #~ self.saveAsList = True + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse( instring, 0 ) + if parseAll: + loc = self.preParse( instring, loc ) + StringEnd()._parse( instring, loc ) + except ParseBaseException: + exc = sys.exc_info()[1] + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + else: + return tokens + + def scanString( self, instring, maxMatches=_MAX_INT ): + """Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + maxMatches argument, to clip scanning after 'n' matches are found. + + Note that the start and end locations are reported relative to the string + being parsed. See L{I{parseString}<parseString>} for more information on parsing + strings with embedded tabs.""" + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = _ustr(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn( instring, loc ) + nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + except ParseException: + loc = preloc+1 + else: + if nextLoc > loc: + matches += 1 + yield tokens, preloc, nextLoc + loc = nextLoc + else: + loc = preloc+1 + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def transformString( self, instring ): + """Extension to scanString, to modify matching text with modified tokens that may + be returned from a parse action. To use transformString, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking transformString() on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. transformString() returns the resulting transformed string.""" + out = [] + lastE = 0 + # force preservation of <TAB>s, to minimize unwanted transformation of string, and to + # keep string locs straight between transformString and scanString + self.keepTabs = True + try: + for t,s,e in self.scanString( instring ): + out.append( instring[lastE:s] ) + if t: + if isinstance(t,ParseResults): + out += t.asList() + elif isinstance(t,list): + out += t + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + return "".join(map(_ustr,out)) + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def searchString( self, instring, maxMatches=_MAX_INT ): + """Another extension to scanString, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + maxMatches argument, to clip searching after 'n' matches are found. + """ + try: + return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def __add__(self, other ): + """Implementation of + operator - returns And""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, other ] ) + + def __radd__(self, other ): + """Implementation of + operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other + self + + def __sub__(self, other): + """Implementation of - operator, returns And with error stop""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, And._ErrorStop(), other ] ) + + def __rsub__(self, other ): + """Implementation of - operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other - self + + def __mul__(self,other): + if isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0],int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self*other[0] + ZeroOrMore(self) + elif isinstance(other[0],int) and isinstance(other[1],int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + else: + raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError("second tuple value must be greater or equal to first tuple value") + if minElements == optElements == 0: + raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + + if (optElements): + def makeOptionalList(n): + if n>1: + return Optional(self + makeOptionalList(n-1)) + else: + return Optional(self) + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self]*minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self]*minElements) + return ret + + def __rmul__(self, other): + return self.__mul__(other) + + def __or__(self, other ): + """Implementation of | operator - returns MatchFirst""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst( [ self, other ] ) + + def __ror__(self, other ): + """Implementation of | operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other | self + + def __xor__(self, other ): + """Implementation of ^ operator - returns Or""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Or( [ self, other ] ) + + def __rxor__(self, other ): + """Implementation of ^ operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other ^ self + + def __and__(self, other ): + """Implementation of & operator - returns Each""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Each( [ self, other ] ) + + def __rand__(self, other ): + """Implementation of & operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other & self + + def __invert__( self ): + """Implementation of ~ operator - returns NotAny""" + return NotAny( self ) + + def __call__(self, name): + """Shortcut for setResultsName, with listAllMatches=default:: + userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") + could be written as:: + userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + """ + return self.setResultsName(name) + + def suppress( self ): + """Suppresses the output of this ParserElement; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress( self ) + + def leaveWhitespace( self ): + """Disables the skipping of whitespace before matching the characters in the + ParserElement's defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + """ + self.skipWhitespace = False + return self + + def setWhitespaceChars( self, chars ): + """Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = chars + self.copyDefaultWhiteChars = False + return self + + def parseWithTabs( self ): + """Overrides default behavior to expand <TAB>s to spaces before parsing the input string. + Must be called before parseString when the input grammar contains elements that + match <TAB> characters.""" + self.keepTabs = True + return self + + def ignore( self, other ): + """Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + """ + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + self.ignoreExprs.append( other ) + else: + self.ignoreExprs.append( Suppress( other ) ) + return self + + def setDebugActions( self, startAction, successAction, exceptionAction ): + """Enable display of debugging messages while doing pattern matching.""" + self.debugActions = (startAction or _defaultStartDebugAction, + successAction or _defaultSuccessDebugAction, + exceptionAction or _defaultExceptionDebugAction) + self.debug = True + return self + + def setDebug( self, flag=True ): + """Enable display of debugging messages while doing pattern matching. + Set flag to True to enable, False to disable.""" + if flag: + self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + else: + self.debug = False + return self + + def __str__( self ): + return self.name + + def __repr__( self ): + return _ustr(self) + + def streamline( self ): + self.streamlined = True + self.strRepr = None + return self + + def checkRecursion( self, parseElementList ): + pass + + def validate( self, validateTrace=[] ): + """Check defined expressions for valid structure, check for infinite recursive definitions.""" + self.checkRecursion( [] ) + + def parseFile( self, file_or_filename, parseAll=False ): + """Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + try: + file_contents = file_or_filename.read() + except AttributeError: + f = open(file_or_filename, "rb") + file_contents = f.read() + f.close() + try: + return self.parseString(file_contents, parseAll) + except ParseBaseException: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def getException(self): + return ParseException("",0,self.errmsg,self) + + def __getattr__(self,aname): + if aname == "myException": + self.myException = ret = self.getException(); + return ret; + else: + raise AttributeError("no such attribute " + aname) + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or self.__dict__ == other.__dict__ + elif isinstance(other, basestring): + try: + self.parseString(_ustr(other), parseAll=True) + return True + except ParseBaseException: + return False + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(id(self)) + + def __req__(self,other): + return self == other + + def __rne__(self,other): + return not (self == other) + + +class Token(ParserElement): + """Abstract ParserElement subclass, for defining atomic matching patterns.""" + def __init__( self ): + super(Token,self).__init__( savelist=False ) + #self.myException = ParseException("",0,"",self) + + def setName(self, name): + s = super(Token,self).setName(name) + self.errmsg = "Expected " + self.name + #s.myException.msg = self.errmsg + return s + + +class Empty(Token): + """An empty token, will always match.""" + def __init__( self ): + super(Empty,self).__init__() + self.name = "Empty" + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """A token that will never match.""" + def __init__( self ): + super(NoMatch,self).__init__() + self.name = "NoMatch" + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + +class Literal(Token): + """Token to exactly match a specified string.""" + def __init__( self, matchString ): + super(Literal,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Literal; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.__class__ = Empty + self.name = '"%s"' % _ustr(self.match) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + + # Performance tuning: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc +_L = Literal + +class Keyword(Token): + """Token to exactly match a specified string as a keyword, that is, it must be + immediately followed by a non-keyword character. Compare with Literal:: + Literal("if") will match the leading 'if' in 'ifAndOnlyIf'. + Keyword("if") will not; it will only match the leading 'if in 'if x=1', or 'if(y==2)' + Accepts two optional constructor arguments in addition to the keyword string: + identChars is a string of characters that would be valid identifier characters, + defaulting to all alphanumerics + "_" and "$"; caseless allows case-insensitive + matching, default is False. + """ + DEFAULT_KEYWORD_CHARS = alphanums+"_$" + + def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): + super(Keyword,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Keyword; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.name = '"%s"' % self.match + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = matchString.upper() + identChars = identChars.upper() + self.identChars = _str2dict(identChars) + + def parseImpl( self, instring, loc, doActions=True ): + if self.caseless: + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and + (loc == 0 or instring[loc-1].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + else: + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and + (loc == 0 or instring[loc-1] not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + def copy(self): + c = super(Keyword,self).copy() + c.identChars = Keyword.DEFAULT_KEYWORD_CHARS + return c + + def setDefaultKeywordChars( chars ): + """Overrides the default Keyword chars + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) + +class CaselessLiteral(Literal): + """Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + """ + def __init__( self, matchString ): + super(CaselessLiteral,self).__init__( matchString.upper() ) + # Preserve the defining literal. + self.returnString = matchString + self.name = "'%s'" % self.returnString + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if instring[ loc:loc+self.matchLen ].upper() == self.match: + return loc+self.matchLen, self.returnString + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class CaselessKeyword(Keyword): + def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): + super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + + def parseImpl( self, instring, loc, doActions=True ): + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Word(Token): + """Token for matching words composed of allowed character sets. + Defined with string containing all allowed initial characters, + an optional string containing allowed body characters (if omitted, + defaults to the initial character set), and an optional minimum, + maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False ): + super(Word,self).__init__() + self.initCharsOrig = initChars + self.initChars = _str2dict(initChars) + if bodyChars : + self.bodyCharsOrig = bodyChars + self.bodyChars = _str2dict(bodyChars) + else: + self.bodyCharsOrig = initChars + self.bodyChars = _str2dict(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.asKeyword = asKeyword + + if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if self.bodyCharsOrig == self.initCharsOrig: + self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) + elif len(self.bodyCharsOrig) == 1: + self.reString = "%s[%s]*" % \ + (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + else: + self.reString = "[%s][%s]*" % \ + (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + if self.asKeyword: + self.reString = r"\b"+self.reString+r"\b" + try: + self.re = re.compile( self.reString ) + except: + self.re = None + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + return loc,result.group() + + if not(instring[ loc ] in self.initChars): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min( maxloc, instrlen ) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if self.asKeyword: + if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): + throwException = True + + if throwException: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(Word,self).__str__() + except: + pass + + + if self.strRepr is None: + + def charsAsStr(s): + if len(s)>4: + return s[:4]+"..." + else: + return s + + if ( self.initCharsOrig != self.bodyCharsOrig ): + self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + else: + self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) + + return self.strRepr + + +class Regex(Token): + """Token for matching strings that match a given regular expression. + Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. + """ + def __init__( self, pattern, flags=0): + """The parameters pattern and flags are passed to the re.compile() function as-is. See the Python re module for an explanation of the acceptable patterns and flags.""" + super(Regex,self).__init__() + + if len(pattern) == 0: + warnings.warn("null string passed to Regex; use Empty() instead", + SyntaxWarning, stacklevel=2) + + self.pattern = pattern + self.flags = flags + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + d = result.groupdict() + ret = ParseResults(result.group()) + if d: + for k in d: + ret[k] = d[k] + return loc,ret + + def __str__( self ): + try: + return super(Regex,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "Re:(%s)" % repr(self.pattern) + + return self.strRepr + + +class QuotedString(Token): + """Token for matching strings that are delimited by quoting characters. + """ + def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): + """ + Defined with the following parameters: + - quoteChar - string of one or more characters defining the quote delimiting string + - escChar - character to escape quotes, typically backslash (default=None) + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) + - multiline - boolean indicating whether quotes can span multiple lines (default=False) + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) + """ + super(QuotedString,self).__init__() + + # remove white space from quote chars - wont work anyway + quoteChar = quoteChar.strip() + if len(quoteChar) == 0: + warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + if endQuoteChar is None: + endQuoteChar = quoteChar + else: + endQuoteChar = endQuoteChar.strip() + if len(endQuoteChar) == 0: + warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + self.quoteChar = quoteChar + self.quoteCharLen = len(quoteChar) + self.firstQuoteChar = quoteChar[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + self.pattern = r'%s(?:[^%s%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + else: + self.flags = 0 + self.pattern = r'%s(?:[^%s\n\r%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + if len(self.endQuoteChar) > 1: + self.pattern += ( + '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' + ) + if escQuote: + self.pattern += (r'|(?:%s)' % re.escape(escQuote)) + if escChar: + self.pattern += (r'|(?:%s.)' % re.escape(escChar)) + self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + + if isinstance(ret,basestring): + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + def __str__( self ): + try: + return super(QuotedString,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) + + return self.strRepr + + +class CharsNotIn(Token): + """Token for matching words composed of characters *not* in a given set. + Defined with string containing all disallowed characters, and an optional + minimum, maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, notChars, min=1, max=0, exact=0 ): + super(CharsNotIn,self).__init__() + self.skipWhitespace = False + self.notChars = notChars + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = ( self.minLen == 0 ) + #self.myException.msg = self.errmsg + self.mayIndexError = False + + def parseImpl( self, instring, loc, doActions=True ): + if instring[loc] in self.notChars: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + start = loc + loc += 1 + notchars = self.notChars + maxlen = min( start+self.maxLen, len(instring) ) + while loc < maxlen and \ + (instring[loc] not in notchars): + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(CharsNotIn, self).__str__() + except: + pass + + if self.strRepr is None: + if len(self.notChars) > 4: + self.strRepr = "!W:(%s...)" % self.notChars[:4] + else: + self.strRepr = "!W:(%s)" % self.notChars + + return self.strRepr + +class White(Token): + """Special matching class for matching whitespace. Normally, whitespace is ignored + by pyparsing grammars. This class is included when some whitespace structures + are significant. Define with a string containing the whitespace characters to be + matched; default is " \\t\\r\\n". Also takes optional min, max, and exact arguments, + as defined for the Word class.""" + whiteStrs = { + " " : "<SPC>", + "\t": "<TAB>", + "\n": "<LF>", + "\r": "<CR>", + "\f": "<FF>", + } + def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): + super(White,self).__init__() + self.matchWhite = ws + self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) + #~ self.leaveWhitespace() + self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def parseImpl( self, instring, loc, doActions=True ): + if not(instring[ loc ] in self.matchWhite): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min( maxloc, len(instring) ) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + +class _PositionToken(Token): + def __init__( self ): + super(_PositionToken,self).__init__() + self.name=self.__class__.__name__ + self.mayReturnEmpty = True + self.mayIndexError = False + +class GoToColumn(_PositionToken): + """Token to advance to a specific column of input text; useful for tabular report scraping.""" + def __init__( self, colno ): + super(GoToColumn,self).__init__() + self.col = colno + + def preParse( self, instring, loc ): + if col(loc,instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + thiscol = col( loc, instring ) + if thiscol > self.col: + raise ParseException( instring, loc, "Text not in expected column", self ) + newloc = loc + self.col - thiscol + ret = instring[ loc: newloc ] + return newloc, ret + +class LineStart(_PositionToken): + """Matches if current position is at the beginning of a line within the parse string""" + def __init__( self ): + super(LineStart,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected start of line" + #self.myException.msg = self.errmsg + + def preParse( self, instring, loc ): + preloc = super(LineStart,self).preParse(instring,loc) + if instring[preloc] == "\n": + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + if not( loc==0 or + (loc == self.preParse( instring, 0 )) or + (instring[loc-1] == "\n") ): #col(loc, instring) != 1: + #~ raise ParseException( instring, loc, "Expected start of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class LineEnd(_PositionToken): + """Matches if current position is at the end of a line within the parse string""" + def __init__( self ): + super(LineEnd,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected end of line" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc<len(instring): + if instring[loc] == "\n": + return loc+1, "\n" + else: + #~ raise ParseException( instring, loc, "Expected end of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + elif loc == len(instring): + return loc+1, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class StringStart(_PositionToken): + """Matches if current position is at the beginning of the parse string""" + def __init__( self ): + super(StringStart,self).__init__() + self.errmsg = "Expected start of text" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc != 0: + # see if entire string up to here is just whitespace and ignoreables + if loc != self.preParse( instring, 0 ): + #~ raise ParseException( instring, loc, "Expected start of text" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class StringEnd(_PositionToken): + """Matches if current position is at the end of the parse string""" + def __init__( self ): + super(StringEnd,self).__init__() + self.errmsg = "Expected end of text" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc < len(instring): + #~ raise ParseException( instring, loc, "Expected end of text" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + elif loc == len(instring): + return loc+1, [] + elif loc > len(instring): + return loc, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class WordStart(_PositionToken): + """Matches if the current position is at the beginning of a Word, and + is not preceded by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordStart(alphanums). WordStart will also match at the beginning of + the string being parsed, or at the beginning of a line. + """ + def __init__(self, wordChars = printables): + super(WordStart,self).__init__() + self.wordChars = _str2dict(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True ): + if loc != 0: + if (instring[loc-1] in self.wordChars or + instring[loc] not in self.wordChars): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class WordEnd(_PositionToken): + """Matches if the current position is at the end of a Word, and + is not followed by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordEnd(alphanums). WordEnd will also match at the end of + the string being parsed, or at the end of a line. + """ + def __init__(self, wordChars = printables): + super(WordEnd,self).__init__() + self.wordChars = _str2dict(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True ): + instrlen = len(instring) + if instrlen>0 and loc<instrlen: + if (instring[loc] in self.wordChars or + instring[loc-1] not in self.wordChars): + #~ raise ParseException( instring, loc, "Expected end of word" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + +class ParseExpression(ParserElement): + """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" + def __init__( self, exprs, savelist = False ): + super(ParseExpression,self).__init__(savelist) + if isinstance( exprs, list ): + self.exprs = exprs + elif isinstance( exprs, basestring ): + self.exprs = [ Literal( exprs ) ] + else: + try: + self.exprs = list( exprs ) + except TypeError: + self.exprs = [ exprs ] + self.callPreparse = False + + def __getitem__( self, i ): + return self.exprs[i] + + def append( self, other ): + self.exprs.append( other ) + self.strRepr = None + return self + + def leaveWhitespace( self ): + """Extends leaveWhitespace defined in base class, and also invokes leaveWhitespace on + all contained expressions.""" + self.skipWhitespace = False + self.exprs = [ e.copy() for e in self.exprs ] + for e in self.exprs: + e.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + else: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + return self + + def __str__( self ): + try: + return super(ParseExpression,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) + return self.strRepr + + def streamline( self ): + super(ParseExpression,self).streamline() + + for e in self.exprs: + e.streamline() + + # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) + # but only if there are no parse actions or resultsNames on the nested And's + # (likewise for Or's and MatchFirst's) + if ( len(self.exprs) == 2 ): + other = self.exprs[0] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = other.exprs[:] + [ self.exprs[1] ] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + other = self.exprs[-1] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = self.exprs[:-1] + other.exprs[:] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + return self + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ParseExpression,self).setResultsName(name,listAllMatches) + return ret + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + for e in self.exprs: + e.validate(tmp) + self.checkRecursion( [] ) + +class And(ParseExpression): + """Requires all given ParseExpressions to be found in the given order. + Expressions may be separated by whitespace. + May be constructed using the '+' operator. + """ + + class _ErrorStop(Empty): + def __init__(self, *args, **kwargs): + super(Empty,self).__init__(*args, **kwargs) + self.leaveWhitespace() + + def __init__( self, exprs, savelist = True ): + super(And,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.setWhitespaceChars( exprs[0].whiteChars ) + self.skipWhitespace = exprs[0].skipWhitespace + self.callPreparse = True + + 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 + loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) + errorStop = False + for e in self.exprs[1:]: + if isinstance(e, And._ErrorStop): + errorStop = True + continue + if errorStop: + try: + loc, exprtokens = e._parse( instring, loc, doActions ) + except ParseSyntaxException: + raise + except ParseBaseException: + pe = sys.exc_info()[1] + raise ParseSyntaxException(pe) + except IndexError: + raise ParseSyntaxException( ParseException(instring, len(instring), self.errmsg, self) ) + else: + loc, exprtokens = e._parse( instring, loc, doActions ) + if exprtokens or exprtokens.keys(): + resultlist += exprtokens + return loc, resultlist + + def __iadd__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #And( [ self, other ] ) + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + if not e.mayReturnEmpty: + break + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + +class Or(ParseExpression): + """Requires that at least one ParseExpression is found. + If two expressions match, the expression that matches the longest string will be used. + May be constructed using the '^' operator. + """ + def __init__( self, exprs, savelist = False ): + super(Or,self).__init__(exprs, savelist) + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxMatchLoc = -1 + maxException = None + for e in self.exprs: + try: + loc2 = e.tryParse( instring, loc ) + except ParseException: + err = sys.exc_info()[1] + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + else: + if loc2 > maxMatchLoc: + maxMatchLoc = loc2 + maxMatchExp = e + + if maxMatchLoc < 0: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + return maxMatchExp._parse( instring, loc, doActions ) + + def __ixor__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #Or( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class MatchFirst(ParseExpression): + """Requires that at least one ParseExpression is found. + If two expressions match, the first one listed is the one that will match. + May be constructed using the '|' operator. + """ + def __init__( self, exprs, savelist = False ): + super(MatchFirst,self).__init__(exprs, savelist) + if exprs: + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + for e in self.exprs: + try: + ret = e._parse( instring, loc, doActions ) + return ret + except ParseException, err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + + # only got here if no expression matched, raise exception for match that made it the furthest + else: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + def __ior__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #MatchFirst( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class Each(ParseExpression): + """Requires all given ParseExpressions to be found, but in any order. + Expressions may be separated by whitespace. + May be constructed using the '&' operator. + """ + def __init__( self, exprs, savelist = True ): + super(Each,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.skipWhitespace = True + self.initExprGroups = True + + def parseImpl( self, instring, loc, doActions=True ): + if self.initExprGroups: + self.optionals = [ e.expr for e in self.exprs if isinstance(e,Optional) ] + self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] + self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] + self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.required += self.multirequired + self.initExprGroups = False + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + matchOrder = [] + + keepMatching = True + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired + failed = [] + for e in tmpExprs: + try: + tmpLoc = e.tryParse( instring, tmpLoc ) + except ParseException: + failed.append(e) + else: + matchOrder.append(e) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + if tmpReqd: + missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) + raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + + # add any unmatched Optionals, in case they have default values defined + matchOrder += list(e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt) + + resultlist = [] + for e in matchOrder: + loc,results = e._parse(instring,loc,doActions) + resultlist.append(results) + + finalResults = ParseResults([]) + for r in resultlist: + dups = {} + for k in r.keys(): + if k in finalResults.keys(): + tmp = ParseResults(finalResults[k]) + tmp += ParseResults(r[k]) + dups[k] = tmp + finalResults += ParseResults(r) + for k,v in dups.items(): + finalResults[k] = v + return loc, finalResults + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" + def __init__( self, expr, savelist=False ): + super(ParseElementEnhance,self).__init__(savelist) + if isinstance( expr, basestring ): + expr = Literal(expr) + self.expr = expr + self.strRepr = None + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.setWhitespaceChars( expr.whiteChars ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr is not None: + return self.expr._parse( instring, loc, doActions, callPreParse=False ) + else: + raise ParseException("",loc,self.errmsg,self) + + def leaveWhitespace( self ): + self.skipWhitespace = False + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + else: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + return self + + def streamline( self ): + super(ParseElementEnhance,self).streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def checkRecursion( self, parseElementList ): + if self in parseElementList: + raise RecursiveGrammarException( parseElementList+[self] ) + subRecCheckList = parseElementList[:] + [ self ] + if self.expr is not None: + self.expr.checkRecursion( subRecCheckList ) + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion( [] ) + + def __str__( self ): + try: + return super(ParseElementEnhance,self).__str__() + except: + pass + + if self.strRepr is None and self.expr is not None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + return self.strRepr + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. FollowedBy + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression matches at the current + position. FollowedBy always returns a null token list.""" + def __init__( self, expr ): + super(FollowedBy,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + self.expr.tryParse( instring, loc ) + return loc, [] + + +class NotAny(ParseElementEnhance): + """Lookahead to disallow matching with the given parse expression. NotAny + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression does *not* match at the current + position. Also, NotAny does *not* skip over leading whitespace. NotAny + always returns a null token list. May be constructed using the '~' operator.""" + def __init__( self, expr ): + super(NotAny,self).__init__(expr) + #~ self.leaveWhitespace() + self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + try: + self.expr.tryParse( instring, loc ) + except (ParseException,IndexError): + pass + else: + #~ raise ParseException(instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "~{" + _ustr(self.expr) + "}" + + return self.strRepr + + +class ZeroOrMore(ParseElementEnhance): + """Optional repetition of zero or more of the given expression.""" + def __init__( self, expr ): + super(ZeroOrMore,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + tokens = [] + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + + +class OneOrMore(ParseElementEnhance): + """Repetition of one or more of the given expression.""" + def parseImpl( self, instring, loc, doActions=True ): + # must be at least one + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + try: + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + _ustr(self.expr) + "}..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(OneOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + +class _NullToken(object): + def __bool__(self): + return False + __nonzero__ = __bool__ + def __str__(self): + return "" + +_optionalNotMatched = _NullToken() +class Optional(ParseElementEnhance): + """Optional matching of the given expression. + A default return string can also be specified, if the optional expression + is not found. + """ + def __init__( self, exprs, default=_optionalNotMatched ): + super(Optional,self).__init__( exprs, savelist=False ) + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + except (ParseException,IndexError): + if self.defaultValue is not _optionalNotMatched: + if self.expr.resultsName: + tokens = ParseResults([ self.defaultValue ]) + tokens[self.expr.resultsName] = self.defaultValue + else: + tokens = [ self.defaultValue ] + else: + tokens = [] + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]" + + return self.strRepr + + +class SkipTo(ParseElementEnhance): + """Token for skipping over all undefined text until the matched expression is found. + If include is set to true, the matched expression is also parsed (the skipped text + and matched expression are returned as a 2-element list). The ignore + argument is used to define grammars (typically quoted strings and comments) that + might contain false matches. + """ + def __init__( self, other, include=False, ignore=None, failOn=None ): + super( SkipTo, self ).__init__( other ) + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.asList = False + if failOn is not None and isinstance(failOn, basestring): + self.failOn = Literal(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + startLoc = loc + instrlen = len(instring) + expr = self.expr + failParse = False + while loc <= instrlen: + try: + if self.failOn: + try: + self.failOn.tryParse(instring, loc) + except ParseBaseException: + pass + else: + failParse = True + raise ParseException(instring, loc, "Found expression " + str(self.failOn)) + failParse = False + if self.ignoreExpr is not None: + while 1: + try: + loc = self.ignoreExpr.tryParse(instring,loc) + # print "found ignoreExpr, advance to", loc + except ParseBaseException: + break + expr._parse( instring, loc, doActions=False, callPreParse=False ) + skipText = instring[startLoc:loc] + if self.includeMatch: + loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) + if mat: + skipRes = ParseResults( skipText ) + skipRes += mat + return loc, [ skipRes ] + else: + return loc, [ skipText ] + else: + return loc, [ skipText ] + except (ParseException,IndexError): + if failParse: + raise + else: + loc += 1 + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Forward(ParseElementEnhance): + """Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the Forward variable using the '<<' operator. + + Note: take care when assigning to Forward not to overlook precedence of operators. + Specifically, '|' has a lower precedence than '<<', so that:: + fwdExpr << a | b | c + will actually be evaluated as:: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the Forward:: + fwdExpr << (a | b | c) + """ + def __init__( self, other=None ): + super(Forward,self).__init__( other, savelist=False ) + + def __lshift__( self, other ): + if isinstance( other, basestring ): + other = Literal(other) + self.expr = other + self.mayReturnEmpty = other.mayReturnEmpty + self.strRepr = None + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.setWhitespaceChars( self.expr.whiteChars ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + return None + + def leaveWhitespace( self ): + self.skipWhitespace = False + return self + + def streamline( self ): + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate( self, validateTrace=[] ): + if self not in validateTrace: + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion([]) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret << self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +class TokenConverter(ParseElementEnhance): + """Abstract subclass of ParseExpression, for converting parsed results.""" + def __init__( self, expr, savelist=False ): + super(TokenConverter,self).__init__( expr )#, savelist ) + self.saveAsList = False + +class Upcase(TokenConverter): + """Converter to upper case all matching tokens.""" + def __init__(self, *args): + super(Upcase,self).__init__(*args) + warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", + DeprecationWarning,stacklevel=2) + + def postParse( self, instring, loc, tokenlist ): + return list(map( string.upper, tokenlist )) + + +class Combine(TokenConverter): + """Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the input string; + this can be disabled by specifying 'adjacent=False' in the constructor. + """ + def __init__( self, expr, joinString="", adjacent=True ): + super(Combine,self).__init__( expr ) + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leaveWhitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + + def ignore( self, other ): + if self.adjacent: + ParserElement.ignore(self, other) + else: + super( Combine, self).ignore( other ) + return self + + def postParse( self, instring, loc, tokenlist ): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + + if self.resultsName and len(retToks.keys())>0: + return [ retToks ] + else: + return retToks + +class Group(TokenConverter): + """Converter to return the matched tokens as a list - useful for returning tokens of ZeroOrMore and OneOrMore expressions.""" + def __init__( self, expr ): + super(Group,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + return [ tokenlist ] + +class Dict(TokenConverter): + """Converter to return a repetitive expression as a list, but also as a dictionary. + Each element can also be referenced using the first token in the expression as its key. + Useful for tabular report scraping when the first column can be used as a item key. + """ + def __init__( self, exprs ): + super(Dict,self).__init__( exprs ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + for i,tok in enumerate(tokenlist): + if len(tok) == 0: + continue + ikey = tok[0] + if isinstance(ikey,int): + ikey = _ustr(tok[0]).strip() + if len(tok)==1: + tokenlist[ikey] = _ParseResultsWithOffset("",i) + elif len(tok)==2 and not isinstance(tok[1],ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + else: + dictvalue = tok.copy() #ParseResults(i) + del dictvalue[0] + if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + + if self.resultsName: + return [ tokenlist ] + else: + return tokenlist + + +class Suppress(TokenConverter): + """Converter for ignoring the results of a parsed expression.""" + def postParse( self, instring, loc, tokenlist ): + return [] + + def suppress( self ): + return self + + +class OnlyOnce(object): + """Wrapper for parse actions, to ensure they are only called once.""" + def __init__(self, methodCall): + self.callable = ParserElement._normalizeParseActionArgs(methodCall) + self.called = False + def __call__(self,s,l,t): + if not self.called: + results = self.callable(s,l,t) + self.called = True + return results + raise ParseException(s,l,"") + def reset(self): + self.called = False + +def traceParseAction(f): + """Decorator for debugging parse actions.""" + f = ParserElement._normalizeParseActionArgs(f) + def z(*paArgs): + thisFunc = f.func_name + s,l,t = paArgs[-3:] + if len(paArgs)>3: + thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc + sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) + try: + ret = f(*paArgs) + except Exception: + exc = sys.exc_info()[1] + sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) + raise + sys.stderr.write( "<<leaving %s (ret: %s)\n" % (thisFunc,ret) ) + return ret + try: + z.__name__ = f.__name__ + except AttributeError: + pass + return z + +# +# global helpers +# +def delimitedList( expr, delim=",", combine=False ): + """Helper to define a delimited list of expressions - the delimiter defaults to ','. + By default, the list elements and delimiters can have intervening whitespace, and + comments, but this can be overridden by passing 'combine=True' in the constructor. + If combine is set to True, the matching tokens are returned as a single token + string, with the delimiters included; otherwise, the matching tokens are returned + as a list of tokens, with the delimiters suppressed. + """ + dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." + if combine: + return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) + else: + return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) + +def countedArray( expr ): + """Helper to define a counted list of expressions. + This helper defines a pattern of the form:: + integer expr expr expr... + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. + """ + arrayExpr = Forward() + def countFieldParseAction(s,l,t): + n = int(t[0]) + arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) + return [] + return ( Word(nums).setName("arrayLen").setParseAction(countFieldParseAction, callDuringTry=True) + arrayExpr ) + +def _flatten(L): + if type(L) is not list: return [L] + if L == []: return L + return _flatten(L[0]) + _flatten(L[1:]) + +def matchPreviousLiteral(expr): + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousLiteral(first) + matchExpr = first + ":" + second + will match "1:1", but not "1:2". Because this matches a + previous literal, will also match the leading "1:1" in "1:10". + If this is not desired, use matchPreviousExpr. + Do *not* use with packrat parsing enabled. + """ + rep = Forward() + def copyTokenToRepeater(s,l,t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.asList()) + rep << And( [ Literal(tt) for tt in tflat ] ) + else: + rep << Empty() + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + return rep + +def matchPreviousExpr(expr): + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousExpr(first) + matchExpr = first + ":" + second + will match "1:1", but not "1:2". Because this matches by + expressions, will *not* match the leading "1:1" in "1:10"; + the expressions are evaluated first, and then compared, so + "1" is compared with "10". + Do *not* use with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep << e2 + def copyTokenToRepeater(s,l,t): + matchTokens = _flatten(t.asList()) + def mustMatchTheseTokens(s,l,t): + theseTokens = _flatten(t.asList()) + if theseTokens != matchTokens: + raise ParseException("",0,"") + rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + return rep + +def _escapeRegexRangeChars(s): + #~ escape these chars: ^-] + for c in r"\^-]": + s = s.replace(c,_bslash+c) + s = s.replace("\n",r"\n") + s = s.replace("\t",r"\t") + return _ustr(s) + +def oneOf( strs, caseless=False, useRegex=True ): + """Helper to quickly define a set of alternative Literals, and makes sure to do + longest-first testing when there is a conflict, regardless of the input order, + but returns a MatchFirst for best performance. + + Parameters: + - strs - a string of space-delimited literals, or a list of string literals + - caseless - (default=False) - treat all literals as caseless + - useRegex - (default=True) - as an optimization, will generate a Regex + object; otherwise, will generate a MatchFirst object (if caseless=True, or + if creating a Regex raises an exception) + """ + if caseless: + isequal = ( lambda a,b: a.upper() == b.upper() ) + masks = ( lambda a,b: b.upper().startswith(a.upper()) ) + parseElementClass = CaselessLiteral + else: + isequal = ( lambda a,b: a == b ) + masks = ( lambda a,b: b.startswith(a) ) + parseElementClass = Literal + + if isinstance(strs,(list,tuple)): + symbols = list(strs[:]) + elif isinstance(strs,basestring): + symbols = strs.split() + else: + warnings.warn("Invalid argument to oneOf, expected string or list", + SyntaxWarning, stacklevel=2) + + i = 0 + while i < len(symbols)-1: + cur = symbols[i] + for j,other in enumerate(symbols[i+1:]): + if ( isequal(other, cur) ): + del symbols[i+j+1] + break + elif ( masks(cur, other) ): + del symbols[i+j+1] + symbols.insert(i,other) + cur = other + break + else: + i += 1 + + if not caseless and useRegex: + #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) + try: + if len(symbols)==len("".join(symbols)): + return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) + else: + return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) + except: + warnings.warn("Exception creating Regex for oneOf, building MatchFirst", + SyntaxWarning, stacklevel=2) + + + # last resort, just use MatchFirst + return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) + +def dictOf( key, value ): + """Helper to easily and clearly define a dictionary by specifying the respective patterns + for the key and value. Takes care of defining the Dict, ZeroOrMore, and Group tokens + in the proper order. The key pattern can include delimiting markers or punctuation, + as long as they are suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the Dict results can include named token + fields. + """ + return Dict( ZeroOrMore( Group ( key + value ) ) ) + +def originalTextFor(expr, asString=True): + """Helper to return the original, untokenized text for a given expression. Useful to + restore the parsed fields of an HTML start tag into the raw tag text itself, or to + revert separate tokens with intervening whitespace back to the original matching + input text. Simpler to use than the parse action keepOriginalText, and does not + require the inspect module to chase up the call stack. By default, returns a + string containing the original parsed text. + + If the optional asString argument is passed as False, then the return value is a + ParseResults containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if + the expression passed to originalTextFor contains expressions with defined + results names, you must set asString to False if you want to preserve those + results name values.""" + locMarker = Empty().setParseAction(lambda s,loc,t: loc) + matchExpr = locMarker("_original_start") + expr + locMarker("_original_end") + if asString: + extractText = lambda s,l,t: s[t._original_start:t._original_end] + else: + def extractText(s,l,t): + del t[:] + t.insert(0, s[t._original_start:t._original_end]) + del t["_original_start"] + del t["_original_end"] + matchExpr.setParseAction(extractText) + return matchExpr + +# convenience constants for positional expressions +empty = Empty().setName("empty") +lineStart = LineStart().setName("lineStart") +lineEnd = LineEnd().setName("lineEnd") +stringStart = StringStart().setName("stringStart") +stringEnd = StringEnd().setName("stringEnd") + +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) +_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) +_escapedHexChar = Combine( Suppress(_bslash + "0x") + Word(hexnums) ).setParseAction(lambda s,l,t:unichr(int(t[0],16))) +_escapedOctChar = Combine( Suppress(_bslash) + Word("0","01234567") ).setParseAction(lambda s,l,t:unichr(int(t[0],8))) +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) +_charRange = Group(_singleChar + Suppress("-") + _singleChar) +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" + +_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) + +def srange(s): + r"""Helper to easily define string ranges for use in Word construction. Borrows + syntax from regexp '[]' string range definitions:: + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + The input string must be enclosed in []'s, and the returned string is the expanded + character set joined into a single string. + The values enclosed in the []'s may be:: + a single character + an escaped character with a leading backslash (such as \- or \]) + an escaped hex character with a leading '\0x' (\0x21, which is a '!' character) + an escaped octal character with a leading '\0' (\041, which is a '!' character) + a range of any of the above, separated by a dash ('a-z', etc.) + any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) + """ + try: + return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) + except: + return "" + +def matchOnlyAtCol(n): + """Helper method for defining parse actions that require matching at a specific + column in the input text. + """ + def verifyCol(strg,locn,toks): + if col(locn,strg) != n: + raise ParseException(strg,locn,"matched token not at column %d" % n) + return verifyCol + +def replaceWith(replStr): + """Helper method for common parse actions that simply return a literal value. Especially + useful when used with transformString(). + """ + def _replFunc(*args): + return [replStr] + return _replFunc + +def removeQuotes(s,l,t): + """Helper parse action for removing quotation marks from parsed quoted strings. + To use, add this parse action to quoted string using:: + quotedString.setParseAction( removeQuotes ) + """ + return t[0][1:-1] + +def upcaseTokens(s,l,t): + """Helper parse action to convert tokens to upper case.""" + return [ tt.upper() for tt in map(_ustr,t) ] + +def downcaseTokens(s,l,t): + """Helper parse action to convert tokens to lower case.""" + return [ tt.lower() for tt in map(_ustr,t) ] + +def keepOriginalText(s,startLoc,t): + """Helper parse action to preserve original parsed text, + overriding any nested parse actions.""" + try: + endloc = getTokensEndLoc() + except ParseException: + raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") + del t[:] + t += ParseResults(s[startLoc:endloc]) + return t + +def getTokensEndLoc(): + """Method to be called from within a parse action to determine the end + location of the parsed tokens.""" + import inspect + fstack = inspect.stack() + try: + # search up the stack (through intervening argument normalizers) for correct calling routine + for f in fstack[2:]: + if f[3] == "_parseNoCache": + endloc = f[0].f_locals["loc"] + return endloc + else: + raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") + finally: + del fstack + +def _makeTags(tagStr, xml): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr,basestring): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas,alphanums+"_-:") + if (xml): + tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("</") + tagStr + ">") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % tagStr) + + return openTag, closeTag + +def makeHTMLTags(tagStr): + """Helper to construct opening and closing tag expressions for HTML, given a tag name""" + return _makeTags( tagStr, False ) + +def makeXMLTags(tagStr): + """Helper to construct opening and closing tag expressions for XML, given a tag name""" + return _makeTags( tagStr, True ) + +def withAttribute(*args,**attrDict): + """Helper to create a validating parse action to be used with start tags created + with makeXMLTags or makeHTMLTags. Use withAttribute to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + <TD> or <DIV>. + + Call withAttribute with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in (class="Customer",align="right"), or + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + To verify that the attribute exists, but without specifying a value, pass + withAttribute.ANY_VALUE as the value. + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def operatorPrecedence( baseExpr, opList ): + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants opAssoc.RIGHT and opAssoc.LEFT. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted) + """ + ret = Forward() + lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward()#.setName("expr%d" % i) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + matchExpr.setParseAction( pa ) + thisExpr << ( matchExpr | lastExpr ) + lastExpr = thisExpr + ret << lastExpr + return ret + +dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") +sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") +quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()) + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString): + """Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default="("); can also be a pyparsing expression + - closer - closing character for a nested list (default=")"); can also be a pyparsing expression + - content - expression for items within the nested lists (default=None) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the ignoreExpr argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an Or or MatchFirst. + The default is quotedString, but if no expressions are to be ignored, + then pass None for this argument. + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=True) + + A valid block must contain at least one blockStatement. + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = Empty() + Empty().setParseAction(checkSubIndent) + PEER = Empty().setParseAction(checkPeerIndent) + UNDENT = Empty().setParseAction(checkUnindent) + if indent: + smExpr = Group( Optional(NL) + + FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) +commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() +_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) +replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") + +htmlComment = Regex(r"<!--[\s\S]*?-->") +restOfLine = Regex(r".*").leaveWhitespace() +dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") +cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?<!\\)|\Z))").setName("C++ style comment") + +javaStyleComment = cppStyleComment +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +_noncomma = "".join( [ c for c in printables if c != "," ] ) +_commasepitem = Combine(OneOrMore(Word(_noncomma) + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString | _commasepitem, default="") ).setName("commaSeparatedList") + + +if __name__ == "__main__": + + def test( teststring ): + try: + tokens = simpleSQL.parseString( teststring ) + tokenlist = tokens.asList() + print (teststring + "->" + str(tokenlist)) + print ("tokens = " + str(tokens)) + print ("tokens.columns = " + str(tokens.columns)) + print ("tokens.tables = " + str(tokens.tables)) + print (tokens.asXML("SQL",True)) + except ParseBaseException: + err = sys.exc_info()[1] + print (teststring + "->") + print (err.line) + print (" "*(err.column-1) + "^") + print (err) + print() + + selectToken = CaselessLiteral( "select" ) + fromToken = CaselessLiteral( "from" ) + + ident = Word( alphas, alphanums + "_$" ) + columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + columnNameList = Group( delimitedList( columnName ) )#.setName("columns") + tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + tableNameList = Group( delimitedList( tableName ) )#.setName("tables") + simpleSQL = ( selectToken + \ + ( '*' | columnNameList ).setResultsName( "columns" ) + \ + fromToken + \ + tableNameList.setResultsName( "tables" ) ) + + test( "SELECT * from XYZZY, ABC" ) + test( "select * from SYS.XYZZY" ) + test( "Select A from Sys.dual" ) + test( "Select AA,BB,CC from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Xelect A, B, C from Sys.dual" ) + test( "Select A, B, C frox Sys.dual" ) + test( "Select" ) + test( "Select ^^^ frox Sys.dual" ) + test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/pyparsingClassDiagram.JPG b/pyparsingClassDiagram.JPG new file mode 100644 index 0000000000000000000000000000000000000000..ef10424899c565f0f4e1067395c6a11a68728abb GIT binary patch literal 236402 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%0D%d3SX{yoJ8pY3;j8hQgQuW*?BL=W+fVHSG#4< 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 diff --git a/pyparsingClassDiagram.PNG b/pyparsingClassDiagram.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f59baaf3929638e74d5f21565f0bf95a1aca14a9 GIT binary patch literal 141354 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%0D#Tsqm!IZe2CcCll+X|GSl)K%pQTY4&@!?bsaIH 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∾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%0DBq^B|3}_1{F7;lAQ#Pv+ z^5=TfWJ2#$oaV>G=22$*i8&c2gotDQ0`et$yQxo~q@2lV14QWTr4Gt;I7DXJA2{aQ z+#Tn-)_#$Q5QO&#g)F(5cM54C4bJ2<iHwlN(ZV(7D*d2acn_X)gv~H;YQrAZ?b8;9 zs$?s&hU5#RPm*DQMlBvKG28SlH{b1Rl)p^lT{<x@N_G9TwofoETb+-YN|!2SnDn?1 zcfTV|<>-qaUL1iB$sKrOTT+0FQ_QElDNJMk2(r~GaI(nJDB=NMITh$|uK1t-*3i&E zJQ8r5`=gGT5psr)c^7Cqt2P_paN%~Elo-M))rJXh)7|~hlFw*1jWR=Tg0>-NDA3u6 zumN8uE&4Z^XWv(8x_NA>mAdE>002<n$;%G{J~_fZjt*gs+F^!12Q-Y*Foa?;uWUSC zTgc=DK6%{88AR-Cj}N{&yr)5IR=^bOu1W<5I)esK$bBDo`I~_AFXlclKWc6R>&n5g zRddy<+0OWo>auBK5GFYLbGdtr02%-QAGi>4BKd)V%gw%gni($TgtpIb^JOoDa#j~< zziK8Q7K#~MxHbR)i}MWivVXnxe@vDyiuVi3W)G<U?K#JO%my{~@ymr4A8TomVGrKf zn8OaGq0p*5<_&)^s`+Z+;IWyuzN`#Z-{(q)hAZxA{^T~H=YR$u?^_TzxppThKn^P2 zw>X%j2ZQbCij`(<`-PnPR~GIrkcZwZDgLQUx{og%fK?r(ek8-I@V?0|eh`+cM{Nk5 zC)*n4oA*_(v;JF-&wi%FVqODt3pcdhWZ61M`W-$OAwImmFNm#JaHPyFOp}xOOtmu* z*KdewrXTi-M<H{HZjvzbqw?gYcR4TDbnUv6b0z(xB?cILaxqCa8Lu{dvRQ_6Fm)D; zl>aMQh{w8~&ATdNuf}N6i87ZnC}(x2`Z5suB|f3fyV8$UbzL{fZ6zhMkc>1Q9^>1^ zs%OODtk|1<qjfG3wKwW7v371xW=M;x7tM(Sw3wA|r3TXdY!&y7$M5JySJ@Z5Z`E09 z6onrrPgyoyw7rziZRMTv;29qWG-OU^iJ}h5r%g~V*!hgQ<;B;n-GA2htGU_Ty%hji z7S@>L|0ZycT1UB|JnayXuCICpx?gFQ*NWlJqxu!2qpt^iolwr>ic5al!r&8+-?8pE zU#sV~OqFu_M&bbJ>aKf%L9O?f{}WbF_(R=0kFLFqTmFI3;qM(5KlWL$!=TXlLIHVi za>;h^`|Dz&?`=u#1&Z+~MMND3MO6v}gP&h#kqDuyCmf~M4T9poE+^13Hs+P6?cacV z%``qB7T~o&>1NZ~_xM;FcKq;Yt`X0JkppHN{+|VK7$#gJ^<IbD_F<#m{ZY&;KKq<@ zJO`yhw>qn9e>y$^J|y_zA16PbA;&>OLqqj@dU#u*`Cc8}E|x2Em_-JIbhOWE13AxW zk?*YH!5&q}M&N^9TUd25&GgmcmlkbN_^!WDGL2rn+L!Wakh5}5JyqFNP`+$!N<2}$ z01GGzRrv?W`kI66!Tz&I-?{#sc8BR{Im^C#PQo{lnjDaGk_zXh;E$65DH>!>iup6s zgCh3Uum`n+!;O5)<sN-io5EjzewhWYx}!YQ)MW6c+iSLu9aAKgs%z%tr-L`zx!fDC z<>b2YsFzUy@|NP7a}>RIpDC5;aB>3QX~Ry|9#U3^CzK8Z2UC#W4}`N4b<6Ed&^eQ` zEX;0@$9oBjPw3=L(M`I`1u1pg)D%&d(`VHWa6ljnDhCPPZmAY$4)0U+xPMh$3=gI| zlh!oI=QNdt<=9wrIAYe`DG&c}q`lx4Id$VZ#Ljyt<95Hf#^*X<rT~wrbC2C`-Vs=I z@)z<-dNHQof67$HZ7~<fFK{L^3zTh#e)(6fI{p^nAde0Jz!*v#en%!Vp8~hr4ZC+E zs?~3)LQ}`^_JZC<&rj@s5N9M~p*gmZfO)m?j3*)2y{9wV<~;ogTyn`HK^RBbNm<2t z)&weF6icbcJ?3e!>nO+ltEtR(Pi-^lHiBul_voQX4CR77DC9u932ZSJtu7No$_s_g zzmLgb3JpJSabeu-91Sd4so1M^h1CwPCUsR-rX?+u1^H4u0f0}It+)c@!W%t}*(#OF z?ak7C1(-0jz?@{3q#@Omtg)MoF}Bbc=&>Hp9v_<&1d@qIgC?mpBXXmyoOeJ2PP-ZX zS_^rZTNu26vyh~p60Y4?vew~OCY>#>Z981u8>FP9tLK(mUz;m3akZP2eaN#op>(lm z#{Zevya`4o6x@|?S~)vLQBk?CZgohn>a1*POYoS!3j`Bsqpi%h1WUPksS}CRBDMn! zpUb6bfUd%x7RX)UR>S`=(peay`aR`h(-3L!<f`o|W#4U&kQsjd=wjFsX_0pWs;az& z|J17|By9%*OlA#HjT;)e(**AMmeB}0Fi0cx<IV<ysK^!*0K(W1MS4V-%0?ve|1HI% zt^AYX0fb!sEgpO41Yj@%8NeD--C4>dqNa|aSsWJ($8ie9TV0{Weut<+r|pH7&8!sQ zxw7h5A~&Yss6SJ~|4Q#XX2Jjfgt73-zx+QFd)jmi>nKO2H`&9y#vji$_@B_t3XTh8 zv$$&9f65;61M0oNhN^Nsj%ZidYq;(1s*e<pbLMT=rdF>2hjjB*;B}aF?n0sW$))X_ zD!CK$mqR)zw(6g-?_-2o@!2MFgm~Le0RW))LHKGAv%6I?wVOv_pEvoeyT|^G=V`4s zUl6B5RBi=GQ4G&st@k4oO1CN1Ry008aevF)m){m@)NKM2ne^82K6|b>Yb%XsOJ>OW zAZ$P#p>R246z~K9zTzTE^-rC+iGso|vr_UQWM61c=7zXOdR<|!n`mAyIJtF6BYh+! ztCO#jHzkTxcDge(>{GWLR(r>B!O)L+Cb1Fd9qnXE0lw+<PFca5Hnc<!I4P6fLueBt zYMcf#-DJFs{1YJhZ{w0$>r@TzRlQ6-V7@cR+w9kVH1-O--{x+XDa?9}nyDE~St!9{ zWt&YEq^duV@;AzP7#1X?579y^o693_mmpfup54v(bG;8;#MYL{e6xzF!6J+Ss#rg| zwh^wMGp+>juQUES*h{pkF=fiYuyU^ht6lz2Q-gZ<L(BUJ7l)hOPxPBP?NgwDLQB}X zm$YnfY*nE82j(wBH*Np`N-PkDhJ0>E<a>=TbnBtp+6Dl=R-!$GJ_8_sFrx_U|5SA& z_K4_zFcPw#X!tgLLCJZwM8CiC1~>WNCf*#mskm6W=K~K7;&*l%BgDZ!&}$2T{$=d{ z4`)=xJvfs;gD@u%LLZG1bkv{-=Wmef%MEK>k;m9o%yjh7d{5i|S;*Z}9KVjsn1`h5 z$PoI))cx#g&Tbl!<A6^{<k(sy(b4!p)l>DDj!UuC3$fLj7{Qel!g9+KhI|DQ0cSiL z!APbAmKSxMdc9VG9(EpdH&*gU`&Fz`5ie_ZTT)8}LU>XL5l3q&BgfWqZ>Vae?Uwgy zih3~Z-z4Fga|RuYF*GT3#pIFdt$MGWR%{hw1@em{zo;Y|L-Xx&a@OeV;<#YQzjx!% z%O<oox_&zCVqU&LG~RD6pf%~C)~jnjvUk3LxX`j$;ZDpqf5&BDVr;wFw#%_)k)!sQ zVnN{GOn&?@D%bl|C~cQvaV4r_6|?)#+SCe@lE)Pj)b12+Z>TD-Rlg2uPi>Gg>Sn%7 z`zgXM+!JETVek<Dal;Y`Qa+u)s+U6FV{89kUP~9YUQE#6V2LK8zq{(AK)PO;6g;Ow zzrA(ke#N!~|8~9=cso%L3TwO{7xBDDL@FZO-+EiI+FzU)Fr<15&B~pjjj}$tDS5oT zz4}=6MoZYf-Zo+j&fV@I@on1aVpqT4qx*Gysrux2s(j!hcmeTIVUPbO$kBSZdz`5V z$xvAXn);pOq!H&R5}P}7yLG}WOcPvPM(imC$@ov2h)OOaEnurDMr*nGs%8D8#mrxZ zsU0C8(Obur3=iK7?cRXLj5*}*$}6NT1UU|V`$QDt>I`U7n^S<(5*Z=?K@~nZdo}CQ zRy*+f*XydO60^seSRAF>v9kLSA(t7U&e)AtQ>WcTr(LtVgVV(IO?2VAa`nQ3oSxn9 z9`jB3g)_pLUa~uEh2Cf#*rM152rsO7c5v-B|Ed4ACEau}jHO6$`fz-D{kGW;`o+96 z#^`_^T%5AXA-_$!V9<`%<{VUFnI8@PB0a<%AJLWJ(^uA-Yp6!R-EjStbzB+v%9HlU zxqz<_8;2Fbirq^x?M5c;J|E*^EhasY_;W;#l7tVGGQt88#}|SHUmu8!&R}+|{UWIz zfL$r~TYXS&vMf!YB554!hE&|k(>jAjqlX2&L?$dQ`<ym0@5<r3y~od}Ar<HC@5g=7 zwJ#QR$Vk(?(ZjtC)y9lDNUWC0B?Dj-%*U1nb{(>-?=J&I<4o2_#dPxxK(JV13LpGP zMB#Q{BLo1Sgz)Wyjvv^CKmb6H6Ih6WKv?F-YH2<f2XT2f>9kO2$1_5cGnL`#EW7xA zs24@|QfTxdE!a%hJ>5_pNi&q8lV9m{_DECr<HdYTcml)1a>)2pdhLn<^DO1YQMWgJ z!hR&l=mN|+Jeup3fJ;uoaB<Zha~Afz8nb0DMK^O-8g-hEUQk`9t`YNTea|lDu!+iF z8e`hx5J+=B&1G|76>q_Eps8u1u15k)CcX1#aZI$3YfhXVF&j<zG+9yqV+P8I)BpVW zU8=&Lw>uSzbU3>YIoSm6QGRv&3>A#nR)43^jN+g8zO49*2G^$@a8=XWw6$P^A~~tb zuH!~uZ*A=eNCc{_1$3Cc#jTpfn|eh6=H{pF^h}8khnF9u7CY^vrg<nlQKn>y53;;& zpmyIvw|dAlk|!lJS1gl=DiGr}K0(kD6e%?`pU23S{vNMUw`{#1#=m=l*+TGbn7)O- z<X=Y?mHiJNc^3BnEHHsEtG`n;0F0r>;R8T{!2H{&x($BX>fXsLm1@4|1rh-G<o9=r z0subnATo8y)O9ftr&Oi5X-=+^`9~cPjMz9>sZcSGOR8MU;~=~E`a;xpU?J}{3?I3n zES$3L82}VKf}4^m*V~WsK~tf~9;bMi9Ts`?jVdC9M}sI<+<N@llbf3<A`YWGWG*h5 z74;K)G-K5$9;Z*1?#9uK(w#{^U`nlNSBw8F7CavmY2<SgO(`OI3VcO>@_6Ew<HOxK zw$?8|Y;%|^mS9gK$_4MCY|X><H;`XRk*u7jh<cB3eQ5kLU6ezj$%0s*R5^X#wZ>sn z-z4S<05BmT7DoeCwT?TUiGl%MX^>yB1YeF&GxQrkp*ik7Vi0()iAr=g??C)K429-8 zUPI<6f_q6&PYK)Y2>=wlG|Hj{0N_aczrCS>r~O$Ld~`Ej$&m-7xH@<}XJH3YJdTtL zwi5wOhGH1U)a-1JZEK&KG89^~kes>%R-^)mYKB%k5Snw=X85_z=>-6A1D$PcKBB1_ z8X7%kGTf+sqHJVGMsBZ5g#R19*@|zLRV~}s8P{q}25c7<lh9)qjOztn4OQtbM=gBC zK}9k{tiSHFw$y)S0lI4Ee%;plb?0b50a^$VloFV#5k2*2N!*qM0DuYoaktEj;Gl|S zWtTiRH?QJd!61L~sNE%_%M&9i3VB@HWDb@_c}nKa8-j)l{Rr0t0DzMI09%*HhfOi% zF!gLb@v;L)0P!9H0MHN~WVm2Xa<KRJ-VC!sz>l7mZ?6%*DD`x(14~DMnn<ECP%^db ztp#YThXrO5d4G-@Nv(K%Mah$8!T)m;^YXKC*m%l6J{|m$>)(G6Mru`GXQ||b#xr8= zl<msh8@os3LUIhb(*b5;YrZj5iWA(J;2&jy3qFqTd<?I78c@5z-yN!CpQP2>1C&rb zn(S4x-NRE8H>~Y7FI#~f0#C7)#Dwr#pg^}%J^5ziS<`jP;Kt}UI^%^F7-x!y?5Gm2 zNI?Z`R~e-X27@<x8s9!_4)lrXzSp)KgH$sN<<n#ol!{6+DcQPkAdau3KUT1<V$MGq zKa}GZlV8|u-9p;9{p~2|9o>5|=S}1rbgug`?g**5jM@zl9%%O9M1=hVVf`Gx-)3Ns zB@YC*zRezuD}j>?ZhPhsI(uJNoVBDdo@<rxa<W68_Wg>@4!3{^!Pnsa1;X=5!Mn!h z!FTXoWZV2JkW*}WO8BxFc2jr&X^&%&SJz~g%CrgrRl2hic5u>+ru_mg9LzGEUpPs; zQ_13Izu;au%MN=_Z(e2__M<%l0gW^|cAMhmDzogpFBw;p<tk}%`|x@GWF^Qx(Nq4j zEHT^^*e9b)pl5}775`K?8UuFS?4X;r?_p4P8u4@bzQxOwh);hlp`tm8<tF~AF!#fd zbO=p=_sv#rG5w!g-zoWwVtm0e$)frSO)J>or8MhA<#ynfDm*12E&nqp(sf~aC)aBR z=ZFjGLP=>!2bc;j#TK{@TDOtd_A8pR0DB5NAl+>Hm5mBucF3Eb>MVJ*Xt&i`1;5sF z-AV9vvp@Tu3~lh_%8c2OK)jFbjKdf|Cqor9f+L1wPzNKYRzilVriKpKciS&S;f!2M z$Hu3b?S(}(sls1Q22n_L>GxT<>{ECqv2pcW=N@X+Vq-I=2REbxI-icp*I^@ehVc&R z$5EdFGu$W<X(crlno8Y|bQvqJ@YvaauVNN?`_`ye@ds1ZRl*jiS7wF}ZiIe(Wg>?O za+O)ijhiXyqeMJfBCF-`^CvfY+4m4gu)BRAv}~y_bV*3BUUE)cj*dNw)J6RyW@sK8 zMNoh%$!t(ZB2BZ~yo!%?qc|%(@_PSz1_rw_8nw2T;e6gwN13bhl61M8$XXAPv=0P5 z^~PkntqnpvW_g|Tv&CyqZMKKFQAW>B+0qV_D#ddb?e%~0`!9@s?rigWgb)iP9h>fJ zN$^0ar3LYhO|N~wW6??V*I0^9a*28*|K#EDrn2w@kxl#VOS>u8N~&MGCvoB`@I(?h zO@iwQei{rVTMhQdp5vjT-k^>d-j9!&qgr|kPA>w;F`R$C7{ICv5ISw50oyfeRJ`mY z&V6wepR7x6yTxPndbO}YpCVJnm>t-S{~Jt1Jb+oIe`m-F#s=2~Xjd>7$3h=S_YjN% zc6LljcJQTvFW#GfdXEOYUv>HAY@HLN_dR##wEIP;cjSe<P<q)+bKWb<e#R_mOkxP+ zAXWOraw=V+$ee`&e0`@G_6b|b)&=`96)1m^%OezrKoX=d!ffTUKaS^G-OCH#)1KHw z4_>n#=5ZyN1q&R0_M0Dl2&_ko6(vbJP&DVC#Qajq{>x@+c4*w1hh!G#L9n#P7?Pgt z{_<Hfb$N5;ejV)YJ6_7mmK!xCUHSBE`$@v}+!(T7A+Bj6&8fcQ7|y?Rc{M-G&A~^- z;jr?v4{uB*A<C1Rc&K=mHxbx)eOGN_shWE(HR;D2jdOdvsXL|`=_-eOt=_)VO<G-K zi8OLDW75#b5TlPqSZl*Sv!;3jRw!@-Uj18H06-1xQCl(@>hcS{Z_3vq>$twLu1roC zW7qEQpulVc#WaxqMZ(_oPk60y^iO8Z%ctwl(hf#=zfe<9D$dH6t$oO5l&7A~x2dGR zbjAinP4Ou6a9z(;K2sHtU&>x#oVt|5H{e-#1bSFxZxmZ8VUZ5J50350*ms{dGx2Cp zLcatO8wa~Mh9QW}_tgLl#&Y4kv?=XX_tZi|PnTkDf%(#7niOZT0J}>1OJ#cl=Wc3S zUb*?=dK9aDJQ^|*+Pf;->O9Qxg-%w2i12B*Lggbqe1xRY8|q7MdK<oyGlf}r)*M`5 zOi>x?LwY*68yp&+gatDDil!<UyJ74Gi>CIR;Q&(&j_~SKQTstu-B6dxo<*T8Skks6 z@U}wq)PsE79{9=|7FgKuNBhmVGN93}qRD`>^8^6Y0HxzeboD(%18n=j^T!E0f)QEw zbO4{YMA;F#wTp0L8kti%>>E}2FdCz3n`igN&J6yEdq3+wdfxE?0Pxn00{hw5NpnYP zgQ6<9uy+=1Db)d9b$ars`k$K<008oz70o;W@(ELd3OmtUWa7EFk1v>H#{?Yf=5Df= zJYg<3-B4M~y`6RuEX8fVx>m{r{rdVj{!3@<(OYLvTtBP&*4qHYvl3C9Fkz3YZpE9? zg2MjWjRQ+Jt{YOKnOmRcSv;N*?;VWbkVe!{{JNK(_qV|!_tSy!g_>$w3o5Bf8bn=! zjuM9t6gUyccj;bEp5b>xp*i*AvGez3HBWdJEKs)s??PV|hVnLYmbw~w<YHbiKxqaE z>-*JHsp}xHXHBRlBJ34SYax4CTL;$Y4`-vwOIH56_FiDm&fmIDGm?_!vX(qc-W&Jt z6OwP5T%#T~v)&1X--^}jf4lz%mtBD#@_!VX#7scG*5Y`8&@S2x0#o?&-xzS&qn5zM z{*_M=XZIvdwu^i6f@hNDIAmZT{N9x-X{Q|mQMprG>l&OIzIy-MzJGUN+-nSr>tK)? z+~>uZ;uX1@?7s5VJ$oai%`<WpgQ$3RqahYmy~!!kuwyAXv{7g1Fy703m8Y4yG7<pt zK_@$B?=R{F5o~PAf6{SiMql@Gaz67S>uWLn(NX%?R$;MK{S}(242E-aqbyBj<o3`% znXR<AfhwWXg|YZ*+7!;jd-+pZdiw<c0R2Ak_&@;xfv`;Jh_-U91x%J2<;!NW93=?1 zdFjI%bp%c(_hxfL{#3&Xqq-kter18S_^<5XM(^qBz?Ed$*ZlQC*5Zgv)1gMowmj#Q z^o^_(e&1#2j12%lv}|cfNs0Kdzg`j!s+bK+?6xLQ@HvlZa{S?z^PO+fgSknbw-G(g z4;<3@vW#@%alhiW$%&KAK%tosX{NThY!!3jC4sB%PU_AVAG0nfo4-*%U=@%z#1n)+ zh8%){KWWJ;KlR?1nFqW@KH5ga<6Bk<7xbKYAjpuAp*~PjN2RQ)W|TYjCQ?PSPcg*w zSEKt#wZM2J!hQ1EbYlE})hrqxZG<eMd*igVHVLX|HzKC`*Vs!N8F9^RhZd>j1>;zx zwO?h^Z)ajGyJ<$6zZfq5ZNx-ap!qLP$Vxwzp*fyA<&bAidTdRuuH~H~vS<_O`AoX5 zT0`dciUS1zzOIYXL}Lsc_>%-1N*XBrdNGs=Gu<l7NjtUQ{JBW=_T%`pN&%1&BvTbh zQN#@^M{|6lH@<KQcV*Cx&Mbaa1Ud97t*F+txK*G706>GSr(>*z@2ntyIlqsjizQX9 z{k#N4lUq8oPbnAUgc?+|`TJ;9MaH`*MpcqfU^wSH*)Z=aB=Tm6%90p%FN<s!cZE!Y z$2a^-kJxe~$=0rs$vsTfC@xVOlZaF$$6PXC3sm1T$N=~0@Q;$A)W()B>CvQ7HcSfc zW!tJ?FV@~W9X)l3n^{cmU?kQJi<KR^VkF;3GXJGfHRd2bS+8w*@A>RvOBVCH$xBgV z+=Ye7u7@wqAk(D<;vdn)xQB;(KZ59S%4h}yyorSt44klQ_x*WI6R2Er?suEwz23`a z*IBZwV6Q&<R?z902>a2gMR`K!QxX6GsGh`W^w~DPzL6$*FNQu9n0l0Ig}kf%hIey2 zt@V(1#Vy!(GWn18n=2l|2As*}ZWq{}I5gx<pX8l)s|1mL{=ObdCa>9&pimkv{WFyz zBwzd1&kQ`Z&|M>D*JN@s=3y+`EuD$CD6@8-$y2TO-pbuVrXg>5@hnE{#PrZp81_#* zR3Q+`Yvy;eZ7OT8d|h=sVrDd{KZfCtnL?}r%X{pjaB#~@ib6o;9eF<~#giDMJ1^|+ zPCv6K@5NJ<x3&4$2q_lpxQrT7c58`$sGs)aN-~q(gdeDP2_>1yDn+qbwaD@$JZ;Kq zX%9{I#Ewd9U~ifyJ>o+0U?QnHB`cDyNV1l3*0p0_p#8Va9SD!liV-rCHQB6pw7jWd z3xv{D?{C~@;_b|xL2iDuQYL)x=d8&PYV3QkS9ajqYw_w8)Vr*7%Om4P@{s-3I7-Hd zDA50k)5zni52D1&SIJ|F8sfD(F4o`qmD2tS#Kw0GfryBZ%~A*|PLJUewCtwt$X_{p z<ufk#bX<%#Q8da`(X8`VER{9n%=Mf?hUa@IcIF1sA8c+?dc?O5o9IiOoWHRnH|I*q zdsPNBS?|3SJNIuTVNi|kyYgK7?Rwb5Dn8Lf+`pU>lBAl36bP~=w8e}2!LY8+-fhuZ z`{`&g*Hca2hUJSu7li9i7LEQiRUGc?*WYs^UH7pdA=N=)P6=kWh?T29PbkDpc-j;{ zx7T_5M&e@A^D#az@9a6<R*iQ}JlrC+bRTvrQlL4u)+`{!3$5#u1$%koqrC}-JT7OF zv%2CedlzfvmT8CZgC8a~y&W>~oGu-Lr)eTc`Y+^SH?4=e7Z<yV{k?AW$y6UnHBGdB zQR>fN(LzdIJmMkit%&-k9RjYmEOXcGBZ==7z1637wyHF??BY~387MXV>ZG<;Rzn3J z;=KP<=*@?>H$+y4%5z+KgL=i+-tw<Pd8t#2WgN1Gxba@fIrs^PyLQpqtjLM&_s6A< z97qf@jQVu=>o^du(CKf`<8i!@848)<A^@T2IQZ4-axa?G5)n*QOnY)U=jzazb$Y1$ zmo1JP&|iq7<a(kvK91%9JJ+I<(66u#&=<yAgdgkEfL}Can7cPbsWTaS9^gIB6)+*O zm;PFDgY%E7k_k5z<gBRFFwwLUm!m7!GDv6k5vJudGjcdi?YtxLMpnc{mv;zPuw!#O z>0Q+^M7~Wv7Q<KNLr-C)+K4S&uT>}o$rt?K7|~Wpj2PjGwlNzI@h9=lHGqDpc;{TQ zxoOiP|8nAY<ble>D?QZ{9XeyR7kE_s7H6C*pF%Xzs~u0+GEs-S)!Ic9^rsE=nz~~% zm5g$kT$4K(2RtmoT=&I^98lbOYkN|SxO)@fC~*q^FRPti$M>+VQ<<e)XuBy?5O!<R z>4Yao1arOF&!Vh-^wLx94(hyaHm4j-{#m7?gIlqWR=apLg_Z`rNz&p$i@ly8xz`u+ z7&K6X|5czvG@s*=d-+op=zQ#3Wrm*`9#PZ7*!+7;`v~*2!<9?4X`J#XtTvG8)BBV% z&#I$EQ)4l&lyDa&r$p{GPb3%2x~31z%*q4joz8vtYdXLMH2+2aloEd%nTebbX`HaO zqPFG7fe2g+r}_OvQwPq7fwAv=#$3b`xj_)fZ9(#BTry@0e@SkYj>*ipDHV9m!c!xB z`x;>%DSA3St$x~pg9@}}!~8Za?E<2oP$_*W(iE{y*jw;zT8K-24gf75zn}gOsE2Tp zbrk<Xc&07q#43R?Ts(ZrY(D)R7g>k=n?sA#dMZM3EJGZ7N-O-b(t0vQF}N&@!{;UX zmq(HG<w3a5J}mAw5yEfIlgxsJjI1|$g|8X^NK~**3$8$GzQMQp*X#FJNOEEbu>s&y zqbN<ZnVfR;&?(-saC57>N^+VZlV{@l>hTdHMX}ALKkR2i2k(5Nm`tVgluK{JUi`=4 zf_#Z;+;89Nv~({a-!$`^lV+}Y?t2}JTN`-#4ImvXy`cesmN|bJrcUkm*lO}u;*EZr zLQ^lKS-a26_TF*lLAuV%M7Q@oVdB+&wvCo@?2?3ZAjDN|jC{<Q#&mV&z?wa*AE!>f z^i<}$a};@jz{CZwhLl?{M`1bwW0cY<JRwTsjo=Iiu26q$NrdTlbA9%HEC$hdnQ%vL zn(p7@SmE|tam$egQ36)p-F_)Ae<o-2cU)Xh>FREy5mxpRcwZVVA(vQvmsa)vHl12e zo6nVG%mYf9MxXH$jeZEHm}ei&n&E|Tx@~422@fv{%`_*3o6`gm?BGvsIO@qZr)DWH za1)f9j(J>lkB$qqt>(P=Zi4?{Jfbf<F!?%fK~le~ah($YTKN5Cm?~>>14;Nm)%Qkr zPDhyHCt5yZc^K0M4VFg(NWS*(<j;Q}2la}!x7L2L85%zy0B2z%Gd{dT_ri<&5sWVs zW#9To+a|V8mSx3FQ#v_-wZrv<2_&&3hVBr?jE9VM(}l5CA@3mj+}FMpnOXg8@+fzV zWmsK|z5bO6w6%eyNqVoKMtAj7B`q)BOL#CcD|1=wqlm^@@@{BTi*+&;<QqVDD@XKp z@EOc<NcZes$otpIl%hopoS^2no<kaKmT@D4iLKE;bAjyg?iY%K-1#TtGZ+jalvH)j zlj{8mi54tS|K>)g-LIPdBcxViaJ9NEbJVLD4~py`^?^h-XttzYXL|_}#31_&nxoB! zy`Z7oc~{Kq<(-qJKw`PXw-$71>et>@sIm42-0M{f9?fT>ItjwuC%a8*^5^YYUyHZF zuCH%#<S`Jnhn{lFB}$<PPUHtYHW$P3ga%PWv+@;t>4d(WlZ=d52KxNO^M_A*ivl3o zM$$a^_u;={4Y*<In}x2gRimY%GR^UEVQZ(FB$1J1LL<WF&mJtBa#kifa4t_z!$IjG ztj$|NlpuR3anDEDY=-ZUuZKhe3W|-NZ33jCGTFVz_`XcAbiiT^`0mC_%Wtl)p<%h~ zj?BR)t)eTklx9`Su_`L`c#(X@tNFiDQIzH<AbUPk<qd6SZyecKwS9Vyr(UT=%2wXX zB0T8L;8cL@6FphWixtlX*4#Rq$Qe&>u13A0&FZ!2=?DIO+O)x`;qT8bq%a$Zj`0$% z5@IQDPn#ZTq&u9z%Xgdcdpfo!6Uo8h>BEIbkUXKlWIXAr#)M7V^EFY+A%m4rQ3wvv zb@ir!wRQi7`!?P!&So<2pV{1P74rlB*E;V2;Hx7>oW>7awSzZ1ZPfHJk|y(`c0u8$ zxs#)or6cR?xn(NiWvH1^W1Zauu5gZr7SrY$$^chd>m}iQg^CwLNS0$DN|Z4I36{B} zu_pG>IDVM+8{Sde1N?P@loq}KmfrpTI12dZy@JJF7;Om4h3qS*QB!C-G%j0vpz{r% zfIglfgxGd;ecf%=g*DN_P@}ryaLh8srXwl8u$%W{wZui{k4<%09n->YQ+)jdi%Pzf z-=C!LfQ!aP=G(#sePPqJhvVE;MqA;#=Z2!x_+mIj*M$xGw$t;hukSn9B+Q;RWo6^F z{d@)hC5rSMLf;&Vx9$?NH-Ag1c0Rb6xg2<a!ezj%6Po#&I%>Wn7HR!u@3YylMw^s_ zA-Z5oi<A=2VWzwSJ8)Xp@+S(#)WY>3sVHg-AxBnx7B~c*6E8zSy2*NAEzv|}J?1;_ zOFq+zcg|t{`R|{Iu!YIROulv4QAyu>74=?oz0P;-T4JCeWY*u$q@xLk=O6J!xz79< z7#Gxb+J4c4WINjA5y~dbC0a4OQ`*aH{r<~YC_2u08(T+l>?YZ0c<oWGt=xEPuD|yL z1)tU=>L3@J*hEb%<!!#X+eCICG$$GTs;6LE1sl=R$P!I6(pj@n?tL;>n?3#k(s_7l zT_S3~;P8bUp<K!zRA!1X(Iw8{I(kT>8+S3zlkw`Q+;dwBhcx(p6of(x2EXfCV|yy^ z3yx&&Z&T&xx3))q<s2I;Cmh*~6_n~=@mqnb@s)2ykPpdhzJdY3;?3V<%cUhHVcT&9 z*r<MBUc7-bCT`9q1|xy4SA4RX@1v}}UPt$L$Z^dWGRB2b<T0sAd7J+|-RyYG(efzR zO5LhY3-JD}(ChqnJ;fNk&CePCDmE53T&vXG#X&_1k^6q`&u7dvzahWEkn(Ey5HWlL zXsFsafD1%q7DRj;8S#sc8knyQ#sIZ!YLbtLqCGbt{{JSt|24YI)LBEyE!}w`#q#oy zJ-2KonfJ2Y?xN3f*=>enXv?Hbh2fYC)(_&7)y%3<cM8RGb=A&-3Q3h34G1D6+Ex!~ zb|WxG{q=Nzyf$QwlVoqXO(bhXs;RGJO!so~hzD|@V!pQ`@VU(T?LpN?M77JVOK$aB zFKB#eQ-078JCMT!aS?=u`nl?EX5ITmBQY-7p89(80$c(CR<tBlw!9}pZDO0)Iz^uJ z;p#C)1A@2$2`;Ycft9xm#h{|9bra$FmIWBf&UYfP$X)SJyaX|*R*&hL+8*Y-I4Xp@ zvSoExn$6o7n$$A3iD>q)hXrPSD=Kr-hdxwwoWESJ#P2{K;?URG4T^*&n)I=TaBmIq z210j@{A5wC8M?#hSGqMsNBwt=W_=47`qv!duKuD?hpdI=P^8hk*T!@YGg`&CNs^r7 zt-4KkNyT$|<R!`QJ+&^{0ZQ6C)HU#9myeD!;YYHOj7BeK8y8!g5y-$e?jI5Fx>q!I zW5I^OB3(YW?}i=SNB+jkkgfU47~e$}lJ2aFY8|?_;Co+B5dTk4MacAZzTy1a=2>QR zcGK5=dI(W(Jh<IZH@~%_!Rc04q#F-<tec%u;TztDVKS7hSu?!zZ<|xI^vdv3`LJvE zk#P`1G0~WD8vG5XrJvglsK5vAp$DtZ-%V;s(T*LN<)=BMF(v-HO4SZMu4?7LdpU$8 zH0j%Ag{hM!)kKZ%)$JWuufjr_gnxhTM%7Hs>zurYt%KeAN4w-*zaM1)1l_g^hl>CV zX4gnZMo0hrzZc1*O>Z}Q%OS+Rcw8gzs<iPxY!L~BaSRPy-$&5Ph+(kBPIo#7CgDz- z6Bk@O-}Gs19{rOkrpvX>39g0=a1+}XMWpG!o|<Gk_I8NVj{(Q|;Z9Ms-b!%WHB9<a z{lz!=SPA!%V$;$4OsX!`?$Q;!xVsh+pgc*OuSz(K(akX*-%PGK+AT?d>Ixh*=oH0- z><+olbfEO!`VUVnF21&RHdapvwmKQq;2+>|H}a?z`eTkdpK)L4FglHMRC+DSfNveS zX9GdGK=F|M7C<(d6r=4j^mmQC=wkT<0Jxdv<(Zgc<zEihC7umU*e}p_ZwV~-@|%<9 z_c73d(>!&?E_5+fd5-kv;taTnrrVX|Kp;?Lq`W9wwk_{f)ZOKdr|aP>Ma9O}viFm3 z5owa-rzsBCcLlW{i85Xo*j!(}{B<1CD~6lRq&z=7EFsRat<p837J}NDNNGpTSgOO< zR&m{w5ll+Ah*(!pBdgr_K0WwH(&X^4gp<nA`^id94DpywaHmd7&#b?JlA=*MwXF=j zmD!3)=QT95M^gXu``uR$_mtRttvm0q9<e1>?c7baNPcdx<aa|?J*9J#gpLC)2xJ!b zi5?ONBpc6l?}=${*4ZVKdNBKtOy&03!`hvkc(r=dpqOQU8)pefD1^(x7H=Kr<o#BN zc`vE9m*U~u_jVi)5G&V4_c?jkm-6#+ZFuh3vqPze=WlI!Ix!?Zrg`ksfYbJ>PqwQw zE{H$LDm_ja0~WkJ9b>-N8E$F$mjZpa8FC&!c8!7!(TCU|+QaV*AohhJ@Ea26f)EOD zjO6)rpyxHSG+Fr&x4MKj=IzpwZMR)owK%$>qixD0r3e8Z<*+x6KD5WQxh8$(PN2d% zWztNbtA{g~_=5>?s`%!0TbjZW^>89IS=LsiBg}paYkSAwqEjyWg9PMoO>Fqb?t=5^ zQ0RZ8|J8B2jLb1Y{jXvZNmdNoq}lAWX2M<2`p^AIJ3JqHAacU*zDK2b#_;PDn}=F} z!RlI@a4UvJ1zLJ=V<U3{or=|<SM^Qy1KW7<)Gz}T+hQN%qJ?GX!2Ly2WAnM9<b#zz z^e$A6<U&QHVD@4$wEf_Rs1QgN!?~H+*hre1gSZHIM%3Qc_7oJ%*M*pVnb8Of%zSZL z`dPpG_vbX0vq}H!nYRL0cHMt_0DnXU9VYmYQ?6ECSAYx#^K%J)E^%OWg=`Y2UQ>h9 zlrMc=^1ToDV?CFdc%>7a)B9x=i3U7(IRI8MAPn&JgZNcNb1<kzmz+;D{<&@&NtVF) z$2O=7&BkG>2R!xCCr@I@|F$)eD?UrX5Kz{KTQM;B&n$o=O7CfCtR_i0I@&PQliq4( z%e14FxSSB<L>dp9qx8`?WZL}4H?-lcti7(H;IvA+ly@*>NetwZMad?FE7Hbrw(Fw~ za{2wN#(E=hp+0?<BaaPI6e2omoW#2c=hh-aO%<2F#Luxh>dBfDiM%0N7>D}6vgW)R zHOF&bdwDz*6{jia9Z$fTl~0Ei66Y+y90E+-<_)tdA1Vs^U3y;9-nzeyEx+)7xhV1n z5Cb$k6_&qOBAmc)1-h$d<kkdbI-J(pPgy2Uw%ez(EV}W*+4SZ)N*me*8Ifc$fk|dz ztjwZRs?iLY&6PN)ogOVQbyw^)-{8s>WP#`{eTsQpA%`S)t1A&JVz6|YXr<nU=Zs&1 zo8qa-V!kg{n5)O@?N&;sD*UEFJ`$(mVw*EL?;){#Q39Rz<Ps9U*)kOwd-L^W+q4Am zfn%aW4c|;3FfG9_*9S_*qCaB$o*Obxbw~-b#9>}#9KyIz$*QSR|7O~|gYk06Q)g?R zB@v$@LC|iQwo-f|?7!!AzN&r}{-aEX6PJvX@q)b+XVq<nNqGx(YlwqEIWb>az}>wq z#y)f}iiQOnme7=dBDQzf=V+V!IR<Om^y3Y`ePhAiPyEAc+=?JE-^ov7+NV%TyUO#+ zYs%`H{hg}bBLms&Bi1uRyW?AAU0Obrv}sSS6*|KDtB~M8@+5?f$#~9QI>DG!lBjtG zYqH;}S_}9O3D^DQ1iDQqWBJLz&uTw<t;&eY2uM>zN)_T%>)a1|3!QTzdB{G|OFE~8 zVPR2R{#;wa|JyY@y?cxKkRK^OtWUYc_;h1FBmF7Rk^trB1R0eEKa11YS+CYOWm25P z+9ZfVa4MzTA>P0q*Q~phFTQdgW)j~I*AX`5&41p*!s0Co5b7!Oxb+oEtf=ZbF9|m# zo$k*AYe}K}K*!w{h^(-+xa1+|F<K#t)n2||chkHe&+lXBqZYu*Ly+=tCdGk*nq*(m z^4u-WuJ4b$JW}&Dn{jk6j;4u$-An#j*g}^unUH);;Qqd3L)$Wv<Q0wN2$~xD1Ep`4 za^W`T%e&WJW7IfJ0|(HmB9!KjFz27n5ik|lFt<~)4aoM{y?(!fqWKt8^w5A6_#a1c z#H$~9M=XBWfU`I}TaR+<%(47e@vYenI#<$8Rj-0<5YZOT#96){d2uG&q@~kmDXaR= z$7#d>06^Z+L}Lt;zRrvAkamjeGv(z)ScHJw`L(pPVt@H4C)XwhFt})1)H~N8C#RL4 z4QFWS<<uhr5c6HKRZ&^u`s|Day1f4rf-0#Pu*<f{?Xuz1TKhZOZN#O^5QKc<AXE<B zhCt|YLFw5_?2JV*m20k7u5M`&y*b}7$`svk1j5FJN4Y>RhmeRW5*@rHPPBZ={BnCJ zkWcHbSW3(P2gI-FF{elpUyFv$`Wp<XDya;#)$#j__^4#|VMTCe_tOiKJ*djr`S~F& zyQS&YAEot*hg1iZV11{&xo~b~KiF0=kH`&bJTO035kas3mIw(Cw8noSb!lOR;%sj> zcde?5TP@elX1V_06|?fuK^n=h|1uoS2l=*}LO6M5u1hmIcJ`k_CZ5CZQj$O~wCJ9K z{)?!?ADeWPb*B-L9WVEAMF?+D=femA;L~<kV5Y?AwYv&S^t+neKxO?K6%|`EeW`dX z-v14?U;H;}@9@xf;a;OMiLsS1O3kRAem|LVv<=t~7~k$Ra1E#RDdm<qpr`96<S1a> z7NCnoXx`s>7<S~WnzQn+e&`USL@85?93U0LP*JhQpHEdX$_-kQz=*#Lxr-Ou?=K~W zZ_hlwBH)dRj&!<5NtnwxG~SYz23NJObFG07@qQO|mcP&JQ$kmCz@oZkphfg9-Z-)s z>-h8>M}ySbvgY6j@L_dtyo(?pS={cUEX_h>*b$5yP+bo+%POxfTE5!#^D&6|c-5u; zX;IF6(52f=_GSM$72aC)rh!M=OzzYfez#ZBMgP#Z4|){DNm~ZnHY#eheDXJ&&%xln zX)7?Ik1Ar<{Gr*iG?WMcn4~W90(sxs`o5)!X7Sq$$Zfd80)ZmSSJQL^c?Z1|@4Dn6 zkVg_<`wxk4{_FXv<ax(PT<VMCgvPh|=`j7&ej~(!7~<|QWd0nsUArD0(`<~awoaME zjt|<8HB1hFwjD%idr2p_{{I6O$bhp-pcG&Rei1}K4dZojhPg*cbiBjU`CJqed^tz$ z_Rx}dtfqS$+I~mjbAXo`KV!cv%fmNM68;7Ez!O%}eE^2nkvPRXte-yf{<EYq+)zyY zj@!~*{v{E~8UiFJu02dn821mfPEHZHQl(ln#ke$goUu0OAtu{i_H>M8Z{;s5-=(*m zwyO-Oi}smnTw#gK{TBGbe(#u;FPx}_tD1-!(+Otl{<t;veR;LVdRZo^(oLZ|<(-OH zJGKthFYTaYj}sTXxaB63*V_fL2366UqMhP`r3^!F?cc=-{h&&mt4ROct+#{!+1uR< z1LdycEqWbO6oWaGaNYkZFXB*?tz183wqvxj>9}27o)TjtcG5<RmW)HvuK46p{xkcO zG*1Kr)nl(`$WP~jxwkMbY|8!@Wp5o7Rp0%A4k0K?jC8|DNJ~o#jDVzcceiwhA~3W{ zw{%H&qoj0qcMj4GcOCSJcis2Bzje=FtQqFaiM{tH_CE97C%222VavJujz-5ptBAg0 zoxf+g^$xJ?y|LUMTYI0P>z0=d^##N5Wp%+dZ?1sWyU^2{8lM3nmSi|<eGuK~&=#5b zCy{ajm8V9>juHC47FQ<&XA;FB&)EY-9JEi8$x6fc_R~0i*jCiY03N>w<m~ZA5CVu1 z+vZu-cRD;gJRG$4A3sF0P0M7T;{-2iy&!#NC$Q@ic|smE{78+fiRy26fWaQ^-W`BO z;px{Zspm)Cj{oopJ7|Cx)JpAfR!%`}>My@LYZ-u5ShF-%&gZ`iF*DQkZ)$;7sc-ki z^2Qe#E_t)xK97n|L1ILm=-Cb(<<xJDy~NQ6oE9hgXW${hf(_5?4WtrDt9<JT!c#}0 zAQ+b>xr&iH@a&8wzJ&n;qZcQuFFG)*sD@)<P|{6(<b&RAumf-gV2e+?AN1~``ePbw z;c4#vu(KdWFG_y+cNbhCbVtA4GO9=Gv7j)^0oNeIPh$g*5f^_4)BP_jIVsZk8-&hu zcFEa{BMPx!c8vXF>5dQm;oYRuQEWYG+-k8jS23>Ut~OtF8~D98MNa9+aR7n(I9_c# z+S#p5^Q?Ow(KXo~V@_5y_BC#@kKf}#zItNk*~cgw{MrKn3ZDpjBH};1_QhQW#v^kq zUz~*)H(*P$j0U+l+n}A($6U{tsQV%Qen6t<$<pnQ&+T|igiHMmqS-L=)^rWAz-9MI zKStslmLZqyE;~chvg`L+*>C3tegi!`H1aR`ABA%%;>5ItC18MGt8OQN>TjYrR37?> zAzrM8hNhg+pqDX{uB^XfmB3L0lAeB96ht3QFU74k_ZFv?4lcFDR@x{KgwNAJ!<v|M zfG<4#c>Lp=a<vq;`k-^DO$)P9!F9wFV1_ZWnYPRbd%^XO`EGKSsm^F^hje3d_Qf`% zf$jcWx<zSX+%wSYehmeQsOY6FEt2S3l^(ylSL^RtlHKxA!{*59pwXU4MAxzjvac)o z4TCXWLybzhPpOGm-RU<ze6L-65uK>jc2X9@kE#3ZXjL5&ULQiVNukO7gz7kqw97no zGvOi1#s_qbBA^~*X{Qti!_26fsh?}c1(O)OC=S_w%EwSN@ltK?QU3!!pN#KPA>XPU zi9TyWOo+pIRNu(6KHk|TfGoS;*6V+SN!EW$n_+#N2xV5>Eu$*$`jODqfc;~KhL~qw zQY5$$UI4hgXa5FU04-N76`kA`eg*Y0tn};wvz#=0wpk%f*}f5d%|~A%Z4OHvC*kS; zL(|g^t3Dx{!m%A48WC8^z@DKo&q8?s0{KP<N;957!e#k&p-}1Me_#*_T|IewLVL~a zxgOW2Qk>Dz4S_&tXoc7`lzNK$#RDPf*Cb+9EJK0?Z)fQ8Uvjdx|3S-gXtJG=f#jDu zD27pCwj!<wTFY4&X>RWPiy8p?2%95FIQB^|qJyUY@zc97^mFTC#4hwG53<3WGw-D7 z^P@rtWyfxphmu+2%Z&QvR<dafVyWX_<Be0DCVzTq-w^!~XGUpe0p}OzT;YP1*Cfub zu~8G2=`{car^o+7LF^%u9n;$D^<kuavH4hm3cBis6kA3;G^YVF=`UA4q5Ial$7Zdt z4D7zz&z}`Jfmsm7$84of7TwjKsrySx(1F3`PL7(4y0y~zcVwY6Jo&~k^t?(U&jx(f z$zSf_yR5$0U)`HAJ^9$LUrrk7DH8(d<61Kp$*XRt>9&=__eH2`jbW6jC*lT49aS}p zX~214X}LSCZR*RqNp9iE*Ey8o5R(*p)}ZGVTK*e>Tg&YoF?DgtCic#4%ssG|@bnPI zBUuRo!tqblhrt&WVRzMLho{?BAwui#>}#&}tcN?M_EqkVe_St4ycMamXJZ4-;n%02 zES<KYAo{9ULqV=jZ>jDZ$kYbg-^Tnz%IS%26De&fa$~Tyhti%-pM@5;<aV2%ds4D? zS}_ec9Zoof5}7f-lwKQdD*bqr;LNdmE=Ah9fBroswG}~#6{{IXbo4oQn`*QtesDal zLoZD@^Jy44mpjn`-r;X-QnVU8k)B*j3toH?-I*dO?!RFeYy9v2GH_B@6Je1dBpT+C zy@bA>-2K;$^7R{JDsL_~JiauCF8wy9Wh*<9&)(JycWiEvwYFHM%T)SgO9^dBo~L1_ z(Bj!SE-#DA(+B{k{Eff;#}7m}r24~OV^b~5=k8H^1fr`P#^}mLqZg{o*nDGH^4cG9 z-WumATxv?Cz3oNy%nKp!yLX>wIU<NRg<fA_Cy`YJpgfRx_MbrW<-`6eaq#a!L{R*_ z65m!5gQR<@3|vSLpuQU4X-Xh_=5<^lluRfu2Bg-c*;{N){oEjg%<t|6K?twVzS5vr zVl+kCK1cIM-1xtq08#KI-sG-l7YJ&XIeo2-y<n|cEn{K^%q$Y40dvZ$HPd*Fq7xb` zf6c##*7G2NK$!@@n>cQI8@6SP^C51-jPuS@yLjGFf{VY0)|tQjxF1^YH1$JrUa}dw zz3ZI6>8Nm8ze>Mj{>fs}{BrygDMl9A41^=|r;M|+T(=A(?$osKD5cgyS6JClk+3<o z{|wG+L&9yIFVr|cSptw(>Fu$>4`h-1VZu|-%P8!y<iq&COmVLrzf1V(gE~bP%l7lp zvju!p9kt~qwVn>2Qc6A1!y6cW$eO9Zz`mQ-^X6-^VyA_;(MeM|_h3gj>ML5#$CI-B z3A+t8M=Tf#T4Ir+Hkecv2Jzo11e0drU_VO8wCBe{E2&Q1Z5MB;<)bzj>`;w_L)$hL z5p>KmEe}uXzj(E36kEbhH2%zf!OV-y;<m-|a6&pU1FcjvLN22^5S=TlbNuw-Gn0Ch zG>Sn-j&1r7U4_KLiP*gX&Y==A?}*u8RQ+-^gv2ekT(_CI&Q$(~6Zs_s!62fWTpG)_ z?=ww|Pe0FL`l0R-J@G!XADCE|M_=@*n&qO7!)zF*?ac1WKx^6mEYuON%<>E@Z}*Fp zs+~5HL5lQ8Oq+Bl?0H0)N^C9euPJQ@U7RStpnlRK6i|DWfGc`~H}A>eFr|3?u|X~S z2j15w@gcX3hN=7|Z%of}d%iIL%%8pRuka{o+CmyTyx3-!xozwg5|+ksK#49;cMGF$ zG`DzBoGNR5r<7E0vQfKl?=KF8?q|~#pUcH~dM?nvbAf>6S*;3&V)%0UhDy$jEtEl@ zubN3`IOmU7t9hKCBDxZ2A%pJ9r7lq3awewhANhsUto2N3F7=hQ-&x3B$7vya#{z@p z1-}J>!MNG|Ci$9IW5$KdVKQ5vq!!?0skh>tX|$c$GETvm>(UcpFE6Y+IP>eYUpi{f zKdRUhjLo_8mDy^dIwfKDTQGb=Mq<s}Gw~!cc_Qou2?NzFTwAO(J64DQ0)cc(scC3D zMb<Yr&l~;RK#eSXzHqm2$LxdywXO9zm%A%TYpz5VUhI4_%!rZDF=TClD@5yTOKV#y zk(eS~bnLy48&RvhJ@;LAk@`D%H76GGdrnpeq?;v=Ccu89qG|L13k<e)>TQF;a<PJ^ zhu)3P@H_>BhlpNaYx>;%O1(`RrNjb$k#s1#L)t3&0Ndq$Mdm2{eFM4QYdc#3pXGXG zHfem6ll=I}y0=YS+y3Vnq#&|Y$v~~GPYl|ApTl5w#t)#tFOgbyN{7O7UkF|$8bfbQ zt5t?3%U!G{kPcV}N9t`z56|cIRx8z%E1=LsO|u&LqA2R7(AMuKJ2RG%b=HjMcVL}F za|$K3k&3Q0$(_2}{a3+M9J9|^UQ%fY-5*AmK!n;T8wLv^!c35OWb%Zy_xWzocl`tE z2p0Q=npqlsyI=LH{cw%w^W=1Aj&H?CzP>W~UwGO+%%Eu(zLazqO4E-WTJ0ucx+Z%T zVhBCP8`SZ_3V+>Nx(PEbJ1RJtw!|W8diTcTMEE7wJlBv7)ARU)sU4O=i%{t5k{ZXX zc6HS&)lle|C*Ss^LZ!EWqJCTu7`#^c(#*aqIy}RRWd^g*pm>pbH#DGcXiM(JDgQ@{ zt1kFu2<;vbh-D^qA9x*1Qn@@+!b8NhOX!hudAGl(9DI!7<;*SCI|RJC_)(2oGp#_2 zg$X0!nsdZJh}N<xM0F-1+$2IJgWQ_gBje;5WIE}3yn;S{fQ&_8suA{tV5PXjC6TsU zw=C*rLy@}?JUV3{KM|YX<ttAg?q)3+--ns1O;fFD{br8$(l?LYLk1~r7eUSI4XV1a zWelO_C1I*I4L2uI`RirPq9EgXdLC~LIpB&PKDTs=&(J?|)vR@xCP7csV7Jfw_E#cr z6-V(k=i)_dCkl3*+I})tCT~iR>OK>>ef2QJA;QHcA1tTjGflAoZ!H!pQ|6h`f# z`&AUjAeobTxLFWv9!=5oG<d5mX#O%pRRh;ITa%GPpl0#~k2A~dfPT!uJ4TBUe@|}O zN>%cYhL~bB17;v(RGE|5X_|~BHOI^CR&0fYOpB)FQP~ReU|Ra%*3Js-YER+weXS3# zFSBl3C*t2R@)`&)kftt6IS_wsylgqiqJQSgtJ#vb=QLRu-h{H@*l-;U)1t#Z%IY-{ z^x&@o_hezK=!ToIRt}Jazs@NhKO377>G_<SRZFYQX;AA3X<A_8xm|8h-$yesfI<`H z=9c#M#;=A`AAG)Q7FaqH>&_SSbX2{J?QfZDVtifx!>V8?m@eyF+05V#w4#!F?||4E zPcdTg>l+%??o(%3D$W_|rfiGb_1V$*M#-sp>=TbY9X<FK&Y&ubYWHejzjQkH?PK-1 z?Js3?$Jzv;xth&Y(&2MQ4}3&sz;w}<@5IByeBu%AUp_q%>BVJ-MC?;aTjC{ogkECi zgIF7Pi+XSi3BDMJhx<4tdhq%?1UiAf^4Mcw%ZV~{%Gtu=3BdCD+g7#-71GW+EXVAs zL^pofv>xqp4y5k(R@0;Tuaf(Gu??7d!oW8gLiBIPw}<(oEetSf*)|$qWtv^^utMDV z+^oB|HDwU!g1SXaBYcXUc)57y<_{qnG(IN@2{3fddrPx7rI>eyaFvhayWUlsml$69 zh%QTA8>BN6H1F^%(u22v-KHhc-CB~}X)G|#QxFxV$xMv;Og+P2X*9UO6&8@dIHjx{ zh8GKWLum2YP&nnP2>+OsMJ<eV&TjxoM;6917<!kNL6Oc4iDYP8@Mt$Il+_$o%XKZg zL&QYqhLiH$Qk-Pt2-feKk%LC7)dx`>YKWbuhB{TVOg<y5q_IbWd@n=Z51DW-R?@9- z&zL2KPCItZOYcsxk>Q=8cZVCWS&V$QjZSTGt<e;it*LfEoG$3c9H_`ae&^h;G<UmX zG@AVwog2RQ(c!YHe<DtyOw`Ze67Abop9XGEX)-bRsOZ?t%Xvy(t^ediCpeHOSe$L% z@hG=KaO=jXBXw^9;bNr<<wKkwEA}&pWQTO<CR2+L`hra2GXA;!X<59Lk<zuDCXz6g zQC9eS7z_s6WCD|LC|ApIzRYpT3jfF@1jHj03f&Q%+YYXb;e~zMwkkEtGyJO5V2BFT zc(8mauy0_a=K}sYYx%5e@BOOZU<re_gInM%j!%jjO6nXZ!OsH?!8H9?4m0QX!UriQ zL6hS$Ehcs3^o-|GPH&6c%80q~A&_+L-^Jec%N-7U7IxgbhAJa>TI6$!RWn_CLvqbS zyX@mE#Xq>2QMG+S=7sxor{vUB&NiVI3k)`Hl@5iOA$<(c7K>Dx1gD0Q5IMJ#kI*Rx zs$^S?CwW}+QdLFZ!(zyfw~nY%e-68?Hpw@1!_~?9?{=5k{Z3nFX6`g0oth*ZJj-_T z)yb|LQ*LwXaz)RwB2suNqf>54Fk4?<yI&Yt{F)N&QyZvwWb(*RdtC$yRibJD*a2CH zU-r(PBFI-zoUc21#9!GwAzVz`7&gnTks6p_sVW4oBI18+F!YB}u|AEE%Rtqs5}JUv zx>M*Wx_jz3^0J%kL>i8)%Rg8=>mpwXbv@*Z(>87q-f+2F5_KZQA|h)Zb7b45N5Vsu zS*fY1CqvyMqDVQs*tH3b%OJ`OjppTy78A<<S?YN3LZr2*ancE;>*4L9R?J(wt&h)c zUnz+82dhk@758YV+zEn*Xr2Mo3Q=D89&+Tzx7n%@1?w&0A<DZ^Ga1ZC_tYx6*jlAH zb(!4B)yrjjElV`qdCRDl{R4dzUo%!@<hI<*wFB75$ly15dZJu@H0x4~&~it<rx@|o zld3A0Tu^O73(qs$P&cYQ$Na;B14G>G!1C<MT<Y{EE|u?!ni!&E-*~uBH4;Q5e@%%z z=nsqAn<}q2HrKcMr3Eu9czzR^ydco)JrPik9Rv$r(|<eLbGEC15GLAs8z@7bTT&>d z2~U!}8XWD(876CLnQPip4({!*9y8PJ3nE7e!Q7}nd%o4G8X%B-w}doGz8E7n9lcS` zXLUB5{=DGw*U;Q~RZE-NgBg{KFx@8por=Zsj|s~2V>63DR28y}x4~d&il*#ijo;V) z&ByAe4vYBrXfd^nK^odbiNln!Itv11<49|m%?@31pq5&&1F7$Ee~(7J3t_utf-8;v z!ohi@t#y!HzL2#8UjSw}7rB5*9rHdisDdy(F!iUL7WeY1SwpvqDf?_#1ao-wnuC^i z1gq=2idx1M|1y<WVKi^I@vZL@S^jn7V~T}6S}+(a82T@=hXuCA`#@oJ(#I6ymu=^m zsL_t8J2z7sWJ^}#FOpUGnnu;V`7F3oBe^QG^zA{zgXWfM$ZbvGdy0nuDQ)!sVqx&y zMRrGubO5dDZ7z`aKqVj2d?U|Cq|=H{ET#Y0UlIzfuyO4KmV$>s(hUr0#QsI<fGs%e z{xgslFB{_Qe*nCW8Z7_@gQ;}<=5h6wFBG;ZR2F@l8TLZO2KatpgTdA+e^Ww^+ItfC z0)r*k)XMvy-M7zCh$`E;m8^D{Qq|q_^HoP?hhV)ZFJ{X=Yc_8E@|T1MR<vX?&N|f> z_^2|>89ynjQJ->__Ud7g%i5g5?m4#|P@}pH?FLsrF_4O1=~R*1cSS!??MMh0YhB+w zrX}!5c{b)Yb?a%$VHF;}n2Wm*&^J_~KQMWOh3G==b4Xzo9)Owu%vVfkS}SJnaMfh& z^z+;azT7k?NC$o~ouq><^~l6-@N#vsf`^Dp9CKyZV*CRP1{;6G&I(EA@7B;>H=E?2 zSAi#SiMM8}ycUk#laEF4HJC|BlIc#?mAFvw8bQrpI~p@hNZD29UG28pmwdKrp`w!^ zX`4XQyOX0hQ_!Yv=A6>QhYf*1G<<-o6}+bZ4h*^;IE+bC>dQ8pyk3OZT$JrgX(^XK znX#xSUT^ExxtAP6D4(KvL6CH&^nR0k55FE|r}}rBbq&~NGs(qIa7*asVnKRmt~F19 zSbH&p(q3T_x7%RzaY^k~ts4klOrB|Zi5E~Pv;zDa9b;l*BuO8EhfF9RiZBu}JK+$U z*$r}p+b9+w{_<Eob3ViiIp+HEGkSi%ak*3_hsm5#iQo2y<yT*jkc}n9LqtmhD0Dw5 zGl8HxdE`=|Qxpn4#xS4}gC1ijjD`nTN|7Ri!9(?-F1r-}0v2Fv2SNzgawma`g&V){ ztQyt60gr&B|JLXQfHHZegY19J0?;+2a&{58Ctm?8J{tj-3@wrxUxPSIa6^0LX27Pf z^P%Nuo?|pSK)oaa2T+0F={&JmB3|U!6^)Td-m?7lr!fc5>glWWaU3FNLK)`LFgsW9 z!@oo}(!<H9@XVfeS%EE6s;w|D=L5|MOy32=UUgF`4yh;F5=pOCo8(D`;HB~KKrTIm zAQ)`k`JMu+k#O*=N_Gx4#qI@?*c-o(E{)wAa1m==H=FduCL;$m%pO(pG<J_Vau9Ke z=LmhDJi^M~dse^0l2T!bMRaP|#@KZHi;s2e=k5z|O0x+-qcfHwITzeKAI^GR|MV;2 zArj0D2NB(vIjC1Xj7((0)P9u|;x|s)*#^d38KzeO7HoR@chwhfT|cO@-vENQk{+!V zkjinrN<P!47R-oW8^ogL%_8cafN*9`GgDMK$qO&~DOGt_18+Iv%SDHFauf}MH$O2H zSIadfgeaw&yO*hMePZZrWOyA{pg#XnS15T}hno3=$wqB-00jh+t_6IpH7vH;b2+7@ zWv<C}ULm(#c;sbWR)U&WLu<}3b4vwA^_W>Zj{G%SCH+?muGg*WsQBOr%6h~^<4Ki_ zu$&LKra-|07Y2PBMZf}EgPph(=jan+_uT}URhy8zlSi_78T)aar;Ik7p2>R8d~6IP z*#lM74<HDGp8K39i+Ur1`orQPA#|2b=2nt{T9?jeuC5!}Syx{iwNRQn3Halz{R~=S zhNVfS-!b}x8W+yhhZ?wA*Q*0uM*~~m-24sJ$E>U@1#uB*h5Ew}SRnxmxsOxlIL}_c zzapq6DK#>+PzHHNT+hqS8+_)Xj@R0{nP(ugbejIsA<|xs(cEHwat=^4iV~oH7DOsx zi`F`5=x)2hV6Z9(5bwl#(VB$SM6dVEYN|$s0m*Itzc|<5sbO}6HW`IcJ50$vho4Hl zUS5y#EKlZ|tX6=6i;P5P%<wzgG>gk?C=^Nz)M$DAG9zJP!St)8_Q;_Ux9e7+8!pwD ztRX4x&u2pQmHG4RGqiO;kw*tenHkc(E{*{7IVqPA=wpEPG6GgfJUq~Mu%yS#KFB&$ zA-(dYnLSm1uF!Ki#~N``AC7XCtQl8I20gw5v$CVQKFo804ifXk0cL0T0aLXu2}f-x zP2en<#AsU?GG47MPJx4S1e4C!X8;jw0qh=3IzHa%v$C5{qD71)Y^P;67yV|FB<RHN zeysG2<!_mBScS{_d??+Zq~PH9F4!{SunG^5BO@k08;|cDDDH{I=tQO6ONg#7tcutz zYIYqiLg2U@S}(KmdjJ=_T@{`rK_^!d|1!O@mQvieHC7VUf`?it7cP^Sq3>|~f*F_1 z1F%#4fNMo&T*&OeVMS)k;3Autp|4nw{;8llSr=9-bCP3SylzirtB@sCYK;k^I49}c z?q2pI^B=Q21H`va$iX>n4cazW;5NIA+$lY&{aOt-=P1!w_xna~-pWSFq)L;GTIDcc z5#}l2#!h3g*j}_TeRyB*r9gD_qUIs!*0i=;avKw?8BOr&<dlw1Tp_QTw$6**8eI!s z^)BB;k;BUSot;}0APhUon`XabgTX@<U6Q0A@DQH7Df?Jqy4yv{#)|!#)Yn-a>wKiQ z)sF*<didT~&DC~G=C5aTN>8jMgM7I}9WN^nK4p-CoFf9;71S<T_el=1LGNtZlJExZ zb16LkuJuoLWIO79acA(;wX%H~<HHQ3NT!56svNX}r)#xaA@G$F*8h<v+|BoCFH;CO zehV`{H5rFhDE>oeO=dSJW??MNwUbvPKKjs-KmZ1>eg1c<%p>0Oh8Fs2HXC=qi?Yu1 zqkur%6JOjXNP0CIch^u%iwEt~`^3Q|W@r5uZ>b1EMhQF;++xsUpTk4|sy(G@ur8Ty z5!rRhxndfy;@puQjTlsk0DzXMMb_&98{Ap0e?qNqXv@bDwxw=@NhDy)D+h&ETmWA@ z{qXaZSe9s!BSGMP2=%wYlAd*ZT`8POM6SH_wUP@2<llrPHe69h68_7{;M3324ax%v zdA{v@Wu~)|muB{aG8nZ2pK^oz0Wl-R>{J#vn==dlEOO`(dJ$p|g+h<lUH74obbdTk znTeV9z>lZD?c1xa^>*Pu3gS2ar_p?tIu{0ZP(u16F}#e<S{H@uP3#6&i{rV(ahEsA zQr8!?!~*k86qWWqGmskCZiw|d%Q!Wl{@bDDHyRnrEqL<zN^Ixo?Gfs{Yw9nL)d)Kj zUooM4w<BC2WgFB0mX+~s%s4!tZ^e48-`s&Ci5ng1*X7peDg6UbZUW~<S+#nSN5hpO z-Co{p#opNL;dtkST#il^`BO`SiJ2UozSs7SxkFO**sUl@5;OejIvJAW_J4s7%A>x1 z5@7(XQS^b^cUv`Pwh|wGH<fih75Ke9ligUET(FLKq1d&WB?aeAY>0X);L`$0LEOoA zJVlzv_bAds&EpfmS8-;OC7?=iyoq^5gH?ZgSR}I<rkF&f-~7noLOQwcovIa55;1E? zvYfLHz|NQLL6ZVx<JEOf%t^<mWc%ZbdidJ8*80sA6)*z~{gI~{dtAGQQbuX7$qauj znmMSaVN*jO>6}kvVq%bm)igBdov5JDM1P8hrDm>;?vE#>iw9~f*sIWDW|zO3lgwqS z95@*KW>C(b|IMJF&@E(PJ(*D0CTaA4WB?t2gjt#k2?XG1WJ?2RMf{@=Equ;c|1IGE zA?lKJFXCKj)K;71=>+?x0LqMRB^fyDxWK(D>E%wact*tu+|ok-dz|oep3pOD<U0|n zrzUk>4ZMw2caJJ}Be9|DITWfNHG$PxjKxn_95?LqgTX^F?EqTbFx__`)TYrUm!p{z z$w8j??C2kp72aqsyMrwkUR0j!D0qq~hFFCiF}TsMa}_+%Ceq5B@sk<@T%4GNiPlWp zm<b$u*6#*DyKtmvkt4~T$CP&$r7E5pp-1h8rNd~IH4x8u4BE7oO-JWgHCN}6sQL~Z zo^MI>{;K^z<TXlu_H<Jp3j_glYRIHZ_sW8o2t6@lBvo(M?O_O^A0oh+rik!;0s2m! zx+@Kh5?3$inMFI_EvjXaE)2fL2Z6dRl|VW|$?k4%+}+uH?M<OR**}|AFKB_{Y7vGh zf3j3i`*1skPdCY<AsFi{w+Hm9)EZOAl$fK|6f#Zmfg*|*Iel#DnZHyBpp{UjRWg`d zoO9M~`?Ag0MOup%F7Y&1$nWy`8g4cz<&@Q?ziyW6+TIOfDTC6Sh@xP@?=k&zn4mPO zF}xpLNCA!UV{}T|=|VH>zY-cD8opv;JNpWC^5iC4CEoC0^zuE4q}>MQ{iyJPu(-Vf z0wvvzGXg)<TH((y*cWNHoHnFFS<PvQd6^%>!(S*KGRiy!Q$PIB;xkDzy=`Qlb>>m* zs+H8tc2VG06|;O7`j$-?>;II5|Bbn{Zvbx5nqijcrDPJLgx)(>R$Jo4E$@dQJXDzj z`6!?ke?bBw5xwni^;kBad?|3ae|rGnH^6)QJK@SyBDbd1Xk@g%i?b4m*(EutfqTr9 zpIGVz2|EM=nFifMh3`O$l2`C7w<I--T!RR%t@D0#dA$m?m+JU7ThfllI+92$bD}PP z{Bw&3RtLlJeV7z~$ctmr;qW1>V`(C&l%_L9N|eDuT)Q%VjSTN!qVt{z{mhcZi@efG z@n10P)r|s<{o6g!rrg`%`-{tDvf=$JLe)ts1bMiu0X%7~iJCr|UD;C(US+=*ab+j_ z<@KzPnEod(mk!dV1<UGhm*4!U-ZA+W0OeOBJJ93@iz{&^rt97nH_|=QOh*N=oOxQy z76)*WhW*_(uqq$i6i@6y(-DJAs|nUi7irzHVW4uGeI0z4G0TC~z3?Jvp^jw6qF7rf zZK-3G`U^v0(=|SBza=~R$MGt`<8VMC-Dd*{dSd5Hck;-(+2m+`m&cov_-tB_XpCC> z*^mjjoaL<;la1QZlmfxt-uhvC_Mv;T$0D;|$Xcpt`(%WU^|fPH^zUjPBd)}086!Tr znKc)rk0hJ97P-brpE>o%dm4UTYa$Fy6q_;P04(1jaF5Z(nv)EBOkRSmxVEmzb$?f% zZFK7SPaaa>?RPCf>>hjbVUG$y(*>Aq)M%aQOYpjrok`Q;h`XK%`ia1K?cYI=KZS#y zNUqh!L>|+lb!(c($vR3pKkwbO;^(@65y5hJZ9XD=z~TphiqyUN&fTPYV54haRaGd) zkPTao%~LKd6eG4JAs;ez$%Iq&ChQmL&7hT2r1#O_T+?}PR<=vMIuIDxGWB$h*OCsq zRw<RP(9$XQ0298*u8NrOY~V7!7Rj3(eM#_hSLo%rHp_HV&>eA1-574jw4($ub)K0C z2?pYK;Yev43!K0zbSc(Vk4J<znc-i;2AHK1D%GeNtcI3ZVFjz3NZI|noapB<mImsv z(-xYnWbUy5389xxF8pK>CvmwrKHtB$zP>#U*RF0$7!$};jpVr8d>BqodWaq2D=v<C zv!$@%lt`Xmr@h&b;`!#@nP2J&12@JM$~fzAV`PkFxw${TXo49R7P+ph^~jMC*OS?< z6vxZLmpBg@de|O&%Z8ejZx!I*!53zD>})dYF}@2c-cXmm<?=dNdc?W_OC>DT;w}Ag z%HQMq60dSgq$x3Sx&ZS>DeALGQ;whLj;739U-y6LA-@{r<9c6ZtAVR7hWz$U7}qy5 zlbN-M1C3+OAbI<Hd%(|9kAp(5&Svz^j+u9PM?vbI=Cy;dD_OfYZQ=+ooJ?%D1%LiW zC&ooPhPqpjPI`|$&~Rw0;Kb{sYA|-4>y^jJ`yHuD4zpq9XE9Q=L|MrXBV3Xnb*4YV zJD!{Rnoey^TBd;d3VMu}UlrKryrp*eaV!@wHzrL`zYd9G`wW^L50waJ1Ok*3E;z!> zDCI*}&I%Yk;}ybB1vpe$Qini5m^C*{!}I$&L*&t6X4vLH0|00jq^>EV`_|?twkS0= z$hmt(+*$P*j&ds_7z`tVrj8Kym{~L4a{4$Fm3tQ-N~hmJu@AB|oAfN_8Z{Ns`)x=U z`v^*}Mle|~JTcyi9;}(2OPv=UVsB-Dz6&clT}8Pb);PjjN$Dx#hM!#MbdSr~6Yq_o zZfT;973ykE%N>vFKB=GwA#V)Fc6o3+D1fHJM-krcPDVN*u%0=hZ8VJ|_^x;H91EhS z(Ub9jL4X0urJ=yN)yA-auX#*%VB5(En}6g~_0wTfz~T&1nR7Z5Gs54{Ijl~yJESel zGf1Q_<ZX9KhY_#Gb-5{_G09wf2zsR98Oq&E{2TK-{dHI_pOvt#muI5xyI=M5p_C0= z#wUf@zG^-0etE@(<i&f?{o2nz1(L_OgnWCcyV>YK!xm5-eyHfWxOID0jpM*$&@EqV zS#_EV3E(9W%Y0T}oc=H$u8!4+?RW~b+fJ%lS?eti^VoEH?=L!Vum|iLVg`k1izN@d zOi$?tMlH*YS^m|g;ib4J@-<NZ=9T}Qrw|F0tI00*F`JLKvBy<{CPLTACbOP73oqxY z0zH<uU@bY<wtQ4h;L-B0X_}NFxqv)&1uskxQPPO2USabn7vb2Z2tM&?irKgLrn!V# zXnZ8%U_ia(l>95kY=1$sR}1ltG@;y-4s>naHKd;v9!W__eV|}uVv6Q{3Q6x1!TK0i zV;%^rV*JN9fP{AGlI~-rYCGf5d2m*L{e!wSe4qaqVt@WJ<NVX1DU1NR_WyqXdH#M6 zW?sML8&neoAQp`08#v%Xgw$PW&4+h#s`kJh$`(@<p_-(3-Bjr^;i>=KiulK+`^`i5 zCxrpoGX4m?{?xfinzz)0|6B19;1-Hmj}gDn(|hNtY3UF@0Dn0h>*;;ws-|0wu!8W) zfOl$x+CR!odwO|Pm!Bu0aLOXuqL2HlDLn=dmQ`QfBE}kh<8Q^ze&iP&)d_r$!Y)@4 zZFV6=dX~;{Z#5^R?XaYT2J{^iqe}$>0|X$D6PE+v^X`fzrV*?Jb6hg!$?|6!1DfsF znTo0Zr=j7>&IwtxK_5P<493c@<p7M=#P=qEzDnw;Rh<9(lrFI70}Kw*RHCDz<sVag zisq)LF7<L-<1B0t_h*9jmhUE?sq(H$2KKR}JO(^M_7Fds*rC-=Gf0B<uJbEB!;Hnp zr*A|(Jbw#C>1(gl`Ij8KYzbP2Yg~Ugvcr=`5{Ign_^2}BQANW<=K>caPnyMTFhLR; zkx`#STA6EyDXW)fO7fKD7ZY%5Mt_zw{7i_gtm|Dc&?<85k~tH-`0%ywBhWPAYLRZ< zZNT@<vfQ-F+$o@w=2w)dllwlUAcO!i+k{o_cQ)jdc1D=8(13QY`3DPB4fR`w?bZoF z$yt;YJLN~ZDnAmv$0E231e?%7xPg^vJ?w*|5>NOoD|=J-#tO<`+qW%@Ts>**@AkFe zARF#<c_Kt&mpY7#_Xvz@V>js7sD0fT@w+R=unKy_GLveVt}lR9LMPBvq9d@|3FFB} zfSiSmUs3j<w)6<-CW><+ByL5bE@|N6iGFSuE&wb}W!Q?lCs89&96(${U1m)r@`;!* z5{743F!#>73SF_d3b0DzQvwr&ewlEnAer$JIYm?co?<GhjM()i;5VCCO`1q4?#m$h z7@(aulGpz0yf8abUNyeP;IQA`bX(1d=*FzyW&{~7DEsY=I)o~9Zs~l{h$E>igvHHn zn<a%rnWCa2OE8Xu&nItOYs@N`OX_vA;e(r4u|$M>r~d=IXxown1k#z}<Vb?oTIZ@= zJS`S-thC+ikwo6W$NCP9W*x65KS~jEY@5A$?~l;4_Wt5V7twb;_^N^EF;Yy=+4@P~ zzHU?AUeJjOs9Z(#VJhV&yzc9EP%T57#5eY<dhq-0XLb}$h3h#!sqOD?4nCi`oWTch zgX$Vv*|>eGyo6>m6eQvr%V)XdsGt?}fNjOFSYE&0^!%D-#eM^Sc>z9_T6M7j$+Z;J zFtqQI1A!`1Vb&H(p!@-ji!=H=<S4!zPPrT%9o|(lImf5(-LoQu8vCtuEBQIL69wNE zPO72r{_b9oH830LaMFX`8cWljlXNy=`7v5L5vI`3?d2Jc6+-ePbg&gIudR~%zL7cq zjAlu@S$$uilBBV|Y}BE$s6cBHTRBq`rtPrx%wk%O?4u0*pvw1q)30*LvlrBR%q^C1 zxUL78GfjAG&1C8uFe0Gpi!$Z`dYjjrzWs&J9R%Vl3YICL9|OHD%|K)EtsM;UBC><s zh!csmvtwhEmRTwcpy%L!c(|vCgCnLvRgK=Wk@e}zTF6IO@ZuWNf`O$2Ay&;y^^M}p zz4l(u6=59B<iPErNYkIN^?I2NXtGrg2DrkEO61UdbwY>b9Ad07?>6S*r|c)Gb6$Ca z%lgi5EXyYIWrb3o6K<h}FlkoDOEtb!duv8qy$-a4s}qJpyen|;T_I5P<&2HMx<HtK zP``PWgRskl>Pun=RTyXKw+ZDu=W87GmiK6Sr~SyzUIFxD;Q=G2ifk=}$_?nh9E=bd z;6&2FRAy@Jw?BF%U}j9)R@q~-RcW3BM|QP`S!!c>NTV{74C7+=CbuV+E#cpNow_1I zt}IdJ#tIMqq*6ZCENP|j?cvPI0aAsSj%}+wKpU1G$~^3G+!_(qJ{mI2c1^MlFnj3* zaRM4bB_$rjG{tWSuL&vXEW}$YyHdvP?;g3d=EtYsYI|lA_LQfO|65<kJCf{a>h2do z+JN$EblkWzj;gshkS}Qr$}5MaD=)c~X4QF{1<aP6UH5~j3l;{|>m&lbEh5P#`b~J< zx0egaR}Kb0KRc4gRT8<oyLb!co#<kT{`kguz*C9{p_~@^BSXK9nX6jW3(jAwc3M=k zJ#7s74UE0t0qCLD3>3;4m;9EPwJM>m!SqC}!}&O6n29`!{%Lw&NIbz%cU(|(48=ph z3aSXoMK_Jm%#`!zOoDIq&*v}qVEB(UepL*e8|9i~Hl~7B6sgoke}+|Y0LU{O4LJ8Z z#!};LuwWQx>F`tJ_j1lO{~HPceVm?jds^Kb&5I1tsMNi?oXEm>4|{$;W5IASR_v|3 zjRiBqy}dnaj^#y$q<;Wr4*#2Kwmh`ho>>yVAQHGV>?9}0dqxE4TZ8`u+X|DrqX^Oo zxTx>)U0FiqT1%^bPYWg@$kQ`F$L-9%HPGk!w0K`Rymntyq9JPhJZr*kMf{rj@HST9 zt}chFoDu>_hX9IC>8rTU?k}Zl+2&s!a_#%QydWVxUAg_{<>(34;|<m6yOsBb%Z+!_ zBjuzmQpGC~m@c%YN<M^FS!p?Ey6*#9V}t~EZ(^IXxo`0oS)wn=j%*qdE>F*zIj-T2 zK?`@oE#H8pZJ7YqC>N`^0Yk2RU*iRb(g9z&4c{-c^YheGP|N9^R!}n{i_b-muJ^fD zXPo1A+XNj-x{ynm5&eUDfxBZL+3OSKn2r|Pc?MM9z3GKpo9!7&&)fPlo0r+$eba{q zGC;>GH~>I#1f81;ALy?EpPQpQ*kY;!2c6G8ceA<0MWKzyaBXZ*M)URgMl7#2&#zxT zx6RRa$Hi+Bw%5an)7q=ecNfKX4?f@BZm3R2F$ck5n}tB1A{R7M*}Qi#;IsSd`e^_7 zakGnS`&>|7x6^unzLa<%g|}wnrCafW(ye_)>mmNbX7kJ9<~gL@H@4R-LD|FbwmY_i zD_tV@ULX#k$14BLEF@@IxIL}bz1ddX8C+=<^1hmAZ8o+JF<ZI0+4s3SDfU5s^}D-I zTght;4v@XxrkH*je8(>G6IpmsdpGD0?E~0=%Hdh_ofoP5)Pc{P5Z`zn0|WwbX8=l6 z_nX-jAZ#n(|M1Fxq6$E`zMh>^<fAG(XY}6A=J+!XqRU3xm)i}U+d3@`IYWaGDNpr7 zaRt2%x;Y|^Z|oqGLR&ItjA5ewW9~Y9E)!%3B)#(9QIC6iO(*1GYT<<MdUw;&gren| z@e&`!9U}e&7gTa|<m>T#oh-OE5K-e!b%*mN)5Z{<21nW%vBWsw<R5hOwCJr*69pQ8 z_Z$@+?C3(R8rjhAirdBW7<7~(c0eINnsCOUQAec6ytU=!oH5N4(%Vz|LhrLilNy=B zY5(ko>5S04i;N`C2Y8W~RBZ2{5SGv{6UQ~DVC6hli6f!;Z<lJZ9y;c+v;n{Rk7>th z?74fYZMN8xw`?6fz9|g!lw+7!Hw+W0kc<bwdvOlOflGvRPXaQNwi{fQH3EAF72X<K z%^!_L(R|9KQLoz`qKxsS88YmP$22Rd2{eGG6#QC93@mDFEjv1Tb#?o-e&<8Jc)m=Y zSRwnvI+lge)3#aBQjFOoi8h{KpOJ#nyWOp|D8uw_0*6#y5~0C94`zwa$6Cq6q;Y}p z@&TX20mO&%XZs_&ode+9`ABAO=M3Jaz!G;!hxWJcLwm4ATgdCCxp5pYlf8T>*%zVg z+7{L3wd$vji(iKWhl7#S92%Y$=V4aX?PDwGv7%-9@RfD}*c6!fApFIA3Wd7mqHh?Y z=w&yv4m)bjR>e*XfXxy|4|<Bf?vxocp9jlk)MKf)Tul|_=PMz-Y7eKUQm%V}@6O6P zL|IScHS^A0pZVAzH5(B`f3m=Ft_y0}U9)6y9~x%hn%?5^c!l|uGDYPLYV;*TK688; zDyU?I-nTm<d?3v2J>P_q`0T?v(JPz-9{4>)r4^5Jb89gD7du!mLQW!i6Jz5rfP==7 zWHX%hfeilFEWjIl3k=nw$@7~eO9?2nV(K>_?=$=gB=<vUBx;fNIjO`qi`x?{A@`lw zpUdHS0CG4I-c>cchvO;*Q{{Y8^qc`2+V{d|;X2Ke`qzi?<nF}0273xk8%|YPDMP7! zoAjITOEgnMIGUb*i#?%ljrD#RwotOoHp<txHvqWrJ~FN9S)o6vo^$W@AoB43$i=?c zAQj`4-PM@-LTcqADGXt8qYIf{)k+u&RpP&&Sa4Vz-TSf6B%jxuzL(E63j%G@%!$6M z>Usk54G5n^0Fjf5i&rk8d@bi#g>OuSSv*E;+-QEIg`rj#P=~%jo5jAo_87_Mq4wU( z=o;Cap|QK~4$#E;)H)SEuF~*|)?v`~6D1Mr7e8LOHxwg+=2ctn9)A+R?rFGwcTHVf zp5-lqCg<`hV$@gB{)Z{|G6NC_J;%eQQ!^i(kiD;wOP$EHkVDckSDeadD5-n=gW23) z;xVA4qJrw-kKtj3yl0e<X+~sW*z4A;@N^i!e`+9m1X=_op?jMGSqyg4J8<h{{57h- zT7S$dsnlG(Q7+ME{M6IP!58czkl*%lx{Jo(9JS>U`M!kbyMpB&jP~T}{UJ?uL+;9h zfkZGEY>jx|4Dr~oFo4=V8*hW5btb4CJo?Zwa9_j<iEm#^K_4u`q5oAFbHz+daelht zQ3Vtv_nO{=9_t1hxsn#)4S}$jijJvH4Y<R+p%aVV{D_&^XX>V5KJ|hM8+4}e_$120 z5Rq|u&2sL?=Go15Ox65!KfgkuN^JMHu>8Hx7JT1%qp{vSy>B;}yN>kLc<ECw<|oI{ zaM*z4{DoVK0FDD@`>ZHJ;a$N=f`>vi>)BhEr3f?45X1@ka&^l6E*gigTj!&3)uZxQ zVELu{35t*Y2q8)`?qH!crw&Wu{;AQ91A{JUpNtv9=-}5q0?3imuE_$812#&0rsYm? z#)D6K45{-Kdc6AYT+6o&wc+YnVFZpYoEmjT0p7L>whWv-)fQXq8s@@mL<1f~Oxj^@ zUN@{(O2%4WzOn02rOOytd(CyM>7c@gaBU{zRm{F{GkYh1Gx$<U4^V`B>=!X{Fc=p) zKv3L}=d{^GLw5fvgA&G!n@ygIGuRH8?Y!?CM7CEy<crg@=7|;Z+#00R;PEVz1=sSu zyJh1IFo381q`zy-6gqR-BaKN;MV&q~b&liNL~FPm9BWF8^Vr<IdK@Hp;DDTemWelD zqttnZr6OluL_q&yzt%TA&g1zyyq1@no`w=zxg+ih=Zkv}uVQIDbLC9MEaR0w4<1#g zyG0Rf>AR1uw_71vvbTLwkHV#vj?r0XS7X;fNg&OTVKRcr<xW;skC%zRQ&-iY`Fn)7 z6Bgcwrc!)CFf+wv08J0y68CpLX_59Z%-pJ~abQUW#76=eD0kFnWYGtq*I!3k+OezX z4LXr53&l)FL%_&E4(9$SNl`C5R3k9nujtiT{JMx{58>A(#{p|UM>c{-dx~weHAhFU zY?ti@u<9HCkvJ>)^HMu|iM{b1iN^a}XQ5xaYAMu-nlY7>^AWxph~tjGxQTj+t#GBK zW3($Y{35|J3OdOf1{X1+gnfGLfX0yW=KAc@o=`L+gYLOe5X{Wse$?qUUy0BCrhE?d z6sg>Wreud*DbJZBNiZ>?>*g>(G}DQ|F$c5mi{y=UHQ7;0M+&(g3SGNAgObo|ymu5H zRfLW%UjF5*E)Pmx=+!0_aGS9sX|9BR@+j7h>EPi;Gl{gme1Jlzn;wnl204EG7NKS5 z3_g;iAd2f_Y5)6plKUDDe|m>Y>)|?3LehutnPwbPjt#!2$D_)n>&)`kCZMn791^BY zpZh_+Sv%-9DhIn_l@H#}hhng!L=zF)NOYo;mO5_@?pWoeeqvTKRM{>UMQDx^F^X3z zM6os~GzrW?I42DJoW(tdAfSN6mx$*HNg4g36hqzJh@wu^oLZP%p%NX-bYvQ7VW*xW zq~3DmIu=mg$+Qfi!gCu>nC9^fsFlzAI5r6811*kb3Xxg&v)AUtlpc0*P3alc_5(lZ zn)*NL92Fi4qamMr^|Uq8QO_5~!Z5XzI_q4JNnrqac#YiFc<~YUQB{+RCGB5TugI4_ z5ta)>W<>?6Qq(SKf19sc#L2DroWB<dJh|e9-qy>bC!f&V6HU@oj(9p;kl9%w)4f0` zFg}nqHGtYaHu=;2t)ImH|L*?YSGpINdgB(GXC~H*r^s2Y{s>Yby%gR|+^4*nMgc|d z-SqBDU3T+O<0sQmH@OVTp=g3v?^%IyO4i@~1<H#7$y1m5@_BvF)BY$&Jx)jdi|RBW z1IdD&(sa5LO!lQWY34SXziAO{29dBTExsB#$YZsgM<C|SEV|_2o@pyO?05x*LSO#? zTE@@X3N-Ns9&h#93(#-EzvL_94fvMloK1yUY%rR5pKm!19Im8bCZQ!^Xuo3tYTK`I z;>*V0R1Njsmf~ao+1jVUR2!lZeQ8T1!lJNaKV=BaoEZS~0#Sic6-j5ZT05TzHPG@x zAI!ztT6h@1)6`IHWHXz%_Ae|XvEThE!`a&oJ^cendslv+2L)euw|*T(!v$%31U{&H zb4E9J&#!dy$F5i9WRp%^iqDOf=y6l7lY{~qj)2R3^5btGLjY%Im@^3;R2zuq<_g!^ z^p8?e-flLR@Y>sWSV5k*BCx=vB$@GCN$9nLeS;oV)Ty;bzDi9kmUCmO5DK99h=78+ zQ!H7Tp_;GWH~*#}sIj<ZG-6oxN4$6Z5$e|U`-n5`of4W;lKx{=T}7|RsFSQr3nB4g zckQ1I8$XiP-`Q71Gu-bAPdyKb0x}K3NUHOq@L~8o{my%M3c?N=R3El5h`wZ)F0yDv z2l?6z-yD#N%2hsiuj5r+Z`v{p;fpwz%5#};)kU~tdG)|o4?ufKck6MBc%j59VY_s3 znk-AcqSItKH#(Om3|4kj5J<Wc6(bYVqYot&6-wI@Fc{b4KTlJ1RlLw6oEh?vtoD2b z<@vcPxqqf}pjI#v!+{4$cl<yBh~6O;`2*v?<!}!K0NZ8i6vRcLP^j$(PF6^|V`3$Z z7_<Nw2C_D}p8^Df!O@`qHRsT~W2NpLs+R30m*K$Gcp%-}JNV_=I2mDGAS@t8%w>2b z&Ggdvcf6079Uw>6_f5AF^?H1RKj=Nq2~G5yNG58PG${=5-cJDbKYc$miJM(wlp%qU z6kH3?=Esha@U%FOTusmMDcQNEJXYI==m~O*$=Yr*6;u7MwDxUp-y_L%Mt1b+pZ1Ru z0Yi}PeD{N1AoiOun+WB6PNpYn8b$$l>J9X3P8|&LZ`zB#)=OD^IvM*w7j7o%(zZR` zuo2$v>KQ#@8cK#aKWOXZjNZ$FKk|*e<r=VM#}Qt&FSXTtQMa_f?cdq`t#}f(){Z)^ zq#0G$zb#k^!(cEjy8jnAGg@Ei+F(yW#AiiWT~PaV#X8^i3UH{eHFIP3dN6-|2B<KP zqW&<9UL+7CrC6jxt(Xj6^f*7|KF!f`#j;5KumyqyKAvE^brb>_k@^6ufqY8&YJNLA z0jxRbb&;(Sx?v~{@}2lceIN*lfBh2hwzD&AW?`;CmDa1+a=!#{o|}By*N76Ja)*LH z^~N7iLLk$ZMp+oXFtc*Sf8lLfdP@P3Y$thBgQ7zX+m^rN4X$!*?;sLr(he6i=&^@F z#0&ZhfvI`bTdrP-iZiCPxF$2XsF2khW~?|E>NK1|GGAJ?G$OY4foHTf+Xcg3t^v-W zE~svR1AkhhA0WC#6cuvvn;*V0mTKJtD#AhG7j?YVmdj8HC{#(~ehT|PNWVJ2`4$oH z@;*`Af{$8d#?tXrl&7tC<ufIc@c`y}pO4!aEDJBI=*PlSAqI!+9;S%(f=K`9fwtzn z<zhr3ahkXBroJC*FDOn6u)Pqm6@^gH-8mOeJkHEz5t?D>1#I{q@12LaASl$I=beT2 zA%asO$^@@b0ABpkVI)2Og*tEXm9#^7CsON$tq7^DtB@#Q7+c<ISLO`~*2`OW9lbB; zog57)@62gq7y61I;b~E@*U{uNQm~*(L6s%_U7{=f&AeOOEe|UlN(imm#gHBByMz~_ zS#GeWvDs%r4dz_;FZ-nFIuZ9b>Er*H3s3x@MqU3e)Kk#W{loe-1=S*z;n!63AOAdY zTFGnu#*h*sN&67dh4v{}Ue=;@Y6Wl0Py;>3qi#gE_`FP1--%@t2dD?tA>`!ugGLDi zV0qs0HrPDC`-e<u9wG|S{vWL_de&lxti*EEIqkB{neQISOkwZ`y&gp9Okr^UHD~r4 zHqnU|{ule^()C`~(mu<~zib^@&i^!-qU;=g)v?>?r;dCZ8{F4c1inZU|9<=iC+7V( zzRD%fS37r-B<|UFAM(ZPX0HBJgLt4C#Ak#c<ru)z;_6!w=Q4rpk@VJ6{f_tB^msYw z$ZtMNVzMt?sQ(ZGNq2d8Z*066S=c;P%R(Ie;hXi^#OzH}Rc7F`96@~^`KxYEW$TxB zGMk4jp*cf$cEh68IN}v)d<IUI^A6(1{7u~i>?pyV=9|^L^(gDHA?D+Rdbgd4%H%ts zugmWYwBB5bCN=h_%$suOCzr>aaden2g`LH##rL90n`#pRqmxhnbTpXb(_TBz^Vt=O z##GV1s1CCH{0uk?qz#q~!{_T{3EvJE5>eL$@CU(wh7nNR5JO!@0D%WoDGk4=V!BK` zP?qZj^vwgMj3DcebpssS16L6~RYGpCe2qT2TXoR)tIyh%`-L_tg!;$;Cby>vbs&RW zZ_*09L|M-Ur_S(yIUv)UKXme(t+|<Nbaxs)t2@QGhiAGWlW)C)RPJBS-k>=2CIeZ~ zM$3l{UQ6RjiXiljzwMAvRCq%l5iladdtjEc{?2&#@xSc_(ATqRbPh*Q+~dZ$xKAmW zPrfc4BVs6l%Ezm^xNTNQMVlwQB&%n~#5*@S8eI2ZOV~~NdK4rmixD`a;a_nq@1ByI z6L=wP3XW)*S@IwOJ)aMQg0IsYbkz-v9f{`ryAYQ(sed2YUK<du@(<>;b(g*=g$DbS z^$c@z1P#4+yJy}p+aj!IgVv$|quvsppd&qXaY++2Cf94%x84lD>ZHw&7*$8rlp#>g zClz|#X1}nT{0Q_a>$b|{`S=-6w)aO7Tp!$X9ya-N35TQatAg)?^2oEa6TEHhonx)x z0DLly|79pt&Tc}I?=67Ca=-!zB?Yx1Dn)F(+rO|)Vq)_fog5x3-@zp1t`ry^M3t4f z)XZmk=*W0>s+f4^no1@y&3>^fR=E`5I6DaLsgioZ9?AlE=WXn~Qb-`zWTq2rga4<t zuZ)YT>(?GaP{{!WX&4ESl5Q|Yx;tb5C3NUUKtK@~q+42%2I+<Yq(eYr=<e>$_qRvy z=eeJA?(@Fq%R3+Uz@9yOt#$pc`0q9Mo~<2=?_6VQfNn}XCF{;k>oNNtb6S#3b>|O@ z4-NVDZOrz5AB(qNPP?{W%&jl8j>>xxIRtMU49An61AXu~|I|P%PxHJeK(lz<@3zqp z{A5;UPe2Bw^LJFS-ec6`{f~xwXp|B$Ht^+df(E<zx%m>eR+YSv|LO00dH}bG9Bu6* z6ZpAPEAapK&6K_CN}h5$6SbLr!U>+OXcx5Dpe#Z+_v>_x-${Qff2oeS)}#*#xzoB^ z@UXT_sT|aQ&Ob1{0&5>Q;7H&BgTK=O>3h~AD%bPFQBidvi}q<kkh3Q(Lb8f&W$Uc- zvJd`rY<jb2&$0*=Qsah)B_4hs>GreU_=4)r6uf1+086p$`q2{qLe1CfaXwQBL{Adn zMpi&HUeWgOE*lvRJL@5F0WYl6wg+=Z#N91rGRwIK^JaC8S|E$0&OS=*FC%ojyWe5B zK@9YV5W$(>a73=#^lS27vc=Zd_B21>;90iZi!$A<l6l|#$4fxT;hCK_4&JjD=sLDe zQ+DRxCR`E^j>&ILb75G$%n`~Xn3UB`(X}wvEZ`6Yu1*mB7))64xBaOA3mTqh@>c|J zK;Hi=Z29^ZSD@QZF38}+I9B;7e&^R&{n_zNW?QJ`t!r`aOgF&#WcZG@J~52Yj%kj) zi-F2vb90yGw;_rZz8v0b2|Rk&>H6*Ih&2}8%R@E%XOSx%@fL6YhCBVOcX=@k&VMnw z<@H>-IW~_Svnal;Qs#d2TLGMP2T8tqLP8E<^)qVSh&k^igYAvSE;dqcD-E@JFUj@S z6kd2@tN|#uaAD%*VwSYF84nCL`IZtRr1P1T5FKxxo6F0lRpunu(*MR~_)p_g?*9_V zp7)tbKIThR!v)<H&DnXn#_>*BkNC9;64!|H;N{DwZhXAnegk-qJS>H;eJW^N^0PxK zBRK;;vd62>75#B!S9jO{22lnH@3<1TN_x3UPYaHc<PwSp&BY3h7)zDGh7~_Oi#ZUk z#`gHrCOw=}?CuqIx-rkg_a{60Pu_jWQ6osJ#mn6zREk3&ZljZrtPflH1-1f9`5-bJ z_E>e8yWxAui+g-4D3IBisn<<@4GO&NDmgS28hlYSx3SvSrF7v~oh`bWr}uU3u-BPx zga|MPNK1;*gopdTk|O*eGa6gtFKu46@(v|!4wc#kILsMoGwBldyPAHxs<In$coBE7 zN>@wpst0NwI_la_TQkbfpWb!97Up^1c~{@0s4P<f3SIw*?%@E`j}c9^>M;Bla}2$+ z4yAX>d%2n4*6CjwsNs5g>hfc`-IhDnHR0<%%~eP?BsV%t@j(YtLLz$(Pw!17;ZuRS z{im<mze**H@D&7;6WmXoh~8NnGJ{q>BD_^D*qDwX7+BRhba!ifS8j({L|DW#K`qEC zO-n1vM76r}=@%F7`?_8ntVK-Yr=IOk!;_bfFOsGGpLt#en>7dH=8w|p1+Xhl$L+`6 zuFwqgnWbRaxXl0VJJRm6_pn{K9LyGUaN?i@(w3Up4v)c5Jpy?PEZ30n&y9e89N9B) zgLZ<>r3IqzRunJs5vBQv^ElUiYzt2g^H4<wFF}h{0={;A8o~SG%%t^>9?LgW&Ve`y z7PW0%%qM5+)~RP~68!4TDt>OYZ>v3z^HW%qBRK{rz&kO{((!(<I&#r@^8QD}$_DA) zX)XQDQ;)iVIAU<poe%U2R~O$0bQ1;q4UlmL5!yA+g15L`&k|AZlE4{6k|@vqgJJ8w z$-Wl|#KS+p+Goc*eV{*_P&3f+hQ!doj@;AZITShwIci2A9u~1kIpP$uNI_XB-2Wu1 zG!1fp64gnaKjN>9ij5nR+A}aHQ+zh5VDg$%^)zIG_wIFjnHdc;IE<{UqMeJBBIm_A zy~gH3LN6oft*lX(DLtCI#!QCvAr+GT%=a?EhSYFZ{|Mf4QwRhOPGOOP4kk8j^<RaM ztS5Yqq723I4*b?)F2aPG`RJA&9d%|lSXUvtQ>gNt(eRnGtSpgVZF}wf&>Gt<_lw(^ zv(&Vw)|!!&lQUQBR&fY~UUTw2a342<i!eLfyDz+*v%^%8$SVI=!Do5UucfG8NA|rh zFdwTC#JI>L<52P|M8Iuj2;<Y9f0tUAQNwv$l&#s{{*X?0%!}8og<M|bAu32Ey+gZM zjIjtS&YUeI_vNKe9#x4L@>z_;3)7e1J?z?{ehV`y9poDJluH;caOB1c$@`^hSf+Cq z6go%=+*(SRoFB=)1|V-UJ#X_DZ!+w_1~SvCO}sMLX>Y}DF0=ny`XFgmD+rGBmCQWC z7-f3v$vWWT(8;xJ%A9!?7NB5R?L7FVW{TnThu;Gn)DC{J{N6<<#kL}7yi2^lxTv+I zFDZ=TVQL7M{nxrQ%EUreVN~8SHR8Ct(sI~SW+HJ-ULxSoQ`ky+Hl=H~#X^ek*OJG3 zSJ#YO9xsj<w-b7gfe$82g}EEePxoZ`h4kyr7=xO_0`N60N7X*pr}%At+O2l8i0dq& ze9%0&^r7V-V(o%KzBk6txAH6PL3WNqa`tr#fmk->#Ek|g!=!TZ`Y&_Zw?@tl^d%W* z*aaBQ&7Nu!bd;0<qkRjQJvbZvj!N*;HE6HvY+OCL0ai=|5&P76`4?F=5NMJ}et9Ev zmbRRn%%BVEX9UJ*nAmK{2Y=sdEMc4}G5{6Ejs;6yX869u)Roa*T+JN2GemCb1Mi4( zlyeSVEqER)l+cr!Lqb|mV`g(&hbcDSspmC+Ii!OO8)48_%1}oRHHK>$s%u-M(w6Wc zMg7?gcsCe?_18V^_;Fk?2m@&zoPW^UqqPQ57nt>BnrZ|}Mm(AEhZ)kN<_20G$Ly?c zH$iiO8QW^KZRFk3&Lh?0^O1{Xsc-Esb}y*QTm8-DrpolKzUsVe9mRbd_y*~wSsp8! z+8fgBb|=LEmwk#K6{uh$^ESpZl_VD?o=s*rI9WUiAD1(q%`=#$b=G29{bAs;s^uWH zDbKx*-$PCLPLjrV@fVmsaf^c#tjY#l7Jr*?`I6X&M@OFI)~oOQtVgjnu*i(utE5|% z+TEvV*k5QfTZ`FtU;UCZd2`m2*sD?L@_`z^SIBtV#fqa@iZ&`%q^BrY$2hGm#R0RD z2h%IG6dzHH3n~?!F3SS9g~*ILInsJr=P<^Hdn~R+`rGIZn?B#Tp;AMVn=GVqlEo+9 zxB5IOJ@|l-6E(oa`Q7U!|9+NWWlp$`JtZuPkM<uREQt7@A*`0ASbh)glGP`UqnuJw zT`<smFq=819Dg;&Q7sye2UcZzLJT~C1zDrtSFCqJ&VH*<N;+BlRI@YB27#pNjga1l z<TNMW>!WB$z6VY++4zH1P9U~F9Mbs}({BzrXr0%LKqz4=1~XEH#Nlz8dMlX7?8V7l zVXsM&<diEgsyIak<zSOwyiv8_wiTDAuARePXq7$|Fb?(+?{vsgIhL1vMHR$Fn1oCy zCI%yUC6t2@S;D(5Uk_|g9U?<jUcMuZRGYW+3;eddbCR(U5kZUpmWt_@;I|{>;_c`V z2G@>Ti~z1JzOIXR5gE~<IZIDTClK+MuG2>`P7S0WvhaZ=@+vF<pOxEkTgq<7W_m92 z?d<My#u>38pP~h5!N~Pd#>aASe91z_*+YMZ8piYbF&xei3CBSLm4tl*pXtwpUOk77 z53Du~bO`UxF`{OgnN!EhgK>xMwxRgHm|=>Rx3xYxT&q5CRG}fY*hmi}fsRI=ZJZq_ z%4<Gtqr3=v_CUDWhXdE{d)UvQlB+`8p_sY*bvj}u!dV_iNxS^0tLj|mYC)Hs^psNq z!b<ghJB0MEr6VMj*GHR$1k4|%;ltcv9zT?d=NY+RaVEkx{Ek0SJ=z~}`T2<&LC;uJ zB4tnZEY0}*V`@G2!<{EWnSLxa#dA@Hbwbs=C~a}}?4E+k=tRn%(;^a%<|){gaAm8d zDpqs$4o+0VL@F#41HqYwi?UTBuzB3aWf&w<*Pi`q&Xckn@9`!!p&w0o>{GclO{nTX z5l(^7`((x8cO5|rCS+HMwD9d+wI!pj2eKJwAG?ZkL`G7_NQ0jpy>g?y0?sA`_nJhB za)`(9)<1Etw_uCOx^^M~BSV1+25;b0TuvJ*P;%7S02ysL8<CI5Z%Tdh9Qd&N)m%)} zVUpjy>-g>rh4#?x*ep$=arw=o>YpH^-KpJX=R+d6!7FAMT@v!H`-xHBqhZ(L!W9zp z0bLO7_zh5pW6m%fhki)zi_pdn@*e9~n_v5`6+>I0{H~j%g|6G<ADCq^K#r2^y7_!@ zqM9r(Vl*!Uk8|oNb%+Ql0Q7u@>!P_;`kuG`2_*HH7MMQHw!bmSH_=7$5JF0!mdwN? zk2IDo6TKt<Q|R0q4MYVMPn~50g0T(Q<V@%n%CP#n&1E|*a7#ZAty4Z5yJuc8R^G2G z)Ll3z^Q8lM4@@Y#s^c>pq2WXN`DeBneGOypusqJz8mYBFQKwDeRj?WyzlX0P_)O{e zX$jxqU%dc0#{;URJCazC)ME_b%9e!MhNfn<Ih}ul(h6b6j~%0*Cvm~l_~YRXNA{sX zNh*tl6mQ&`T&1JLeQkjfXU|<}J|S70omUynpZS)aW+S|4pGhp_;NwEzngJ_Igs&XF zfmSmNv^2NvAxAo5D_9cS0oL92zoRaPz3|gSwq#MqXE=FeyOHDF%Zd$*BW@KoioVt7 zr?-@2q_V|EeEeXCf<g`>oH;lyC`xmW)tFP>x+h%ytfTzSD_01_l;~556mo2alwKDR zQb=m#Q{s;kDfLbwfi4asWl^I{Z_7jvjs%Mb5_l~lC}gk?sBHMO*`35rWSy1-Px7xL z-9`UJ$BF{3@gGA6r9QxXib(E59y$%NanqCD6073bKXQ4_H8rJTlS93kbQ#Jo<kYw; zfsx9o^%CkB0sL*wEW5@_=(afez~6EwG$H#(kc--g*x;2tOL_i%CtqNhSN2CbepmqZ zpk)iSVlZRZlnl1qk^r$Gb<S-aR#J)x57VZ(Kq-kbjYajFJh54X`XhO?^-q?{?vsMS zNl$?j#t$g8?B$bSbX>2;&Z3v_3*zCRV#XrcyMlwO2{8cJ<<;UEynI&zQJM}KJD_Pw zYPz40tJh>q-ZB+otjE4srQ<@bc{;efOO>G6WD!xNy7V2&41tJ@0u_x0&no}dW*^m) zxb`q=KE21qeIwb_q+Zqh`9JTZ&}~wWQz&=m=vpb=dQ!v80Y{TD$U#h0FXJhu-D?NA z*X~v3oZ%Wi&+LCOa==@9)PkP}idVEWBu)+T5LK$L@AglSVpLThn$WNlc0q@%41)$o z-*D4`Wcgn|)W3Z5<pMkBUL4&<`jFD}(m6c@B4Ug#vqn**1A2}>Y;r1P3X)Ie`0q_7 z6Fk89)?+uR-6X8}7BLb^!HAm4Q|xQo7@@_TlK}`uv1jZg9E)D*)D%Ir_PH{}Zxa)Q zW?G5rO_2t=cAZ>?)RS>*Sj0{q;kg^En(QL=b3^3FPp^%mJ%_dON^4gWvJaz-rHys= zWY0>j@LFr)+VhIk7iXvD)A0%SI-tw~2BK#;lSKh^{fL)^j1(ZmLsaa`wFBgr9;E;T zLP=CTlawAJvWg~Jo2#WAf9FPcw*M8uxLbIW%k-`ESJ@Mn`i1TqAF3(VZp9-P&pTa@ zc7K3EH!&Y-aEz_@&mR-G(QXU4<A6Y2(cwr<0&-{dA&gs+ls0bvMk$*56hC*Jf|ZT_ z13hLQ>_D9ee30u!l}L1SU96iZ69gi%3E-)Sn{zIQ#tP3H?(eAs=?(y1$&D7IC2G5q zU%a35{jl4Td}BN*VyyQ83ZzqO0T((px|7hWSMB55G-L&R{1W+#w8>LM&qa!L-&?dS zU*a6)oIb)WpY!9E0;PD-C&LgYU84R#uJ|Y&wm7odP4sOM0@!%`Jhi@q0Ieo<EqFif zfHnl+KbjR3rSbzVOSfNvgzqKW9d&1QMr`mZu5SlW-HoMm)&;v`d@cis5qDPzc6qG3 ziDf0#vS}={v<VW-(n&kt-A_mhq6dfZMwW4^lpfcVTenEjy1uvErJ3l*a_*uvqAPsd zYtFKO+$KDd>1u`1R+@RePBiw77oE(GrUHZeRv`=fZ=VqFoqA;QGC)LFv7fYGp6X|o zWU<yT7C8II*KxhPi}WpHj(ZP%>?V>G60l7jxnjce?j-vtGNax(Q1Qb5HY8Pm1p7Tu zfs-Ih37+d`q7w?!oQffqXNnQMjI8?eO%U&swNLIkVma6W2t<}Jz>_pZeu#5J;AHM- zocUwu4`Kl89e9leNB=SOYXA@uq2c52pLwqr*B*uzeZj32rDrc}v)809Y`6faH!oKS z$y>l{#$d$kPjzP8%^kdke+nFeO@6*-NbTldQ%DT%V{MTGI1|m)vi-|<B>}4B(k{0{ zKTX-SF)OXqZ<ol+xv6-)JmhA2K_ZapXCCiGJTd-yDR(tNH8+J+<?{+P?dS_shzJJ+ zA~JhoVh%}trRu@REp|1bK{prp?2W~CqkYzi66$jrW#S0balHI%$k&VeR!X<5?(>;< zC?2Ac4jc56S{b+xmEFmlF=pmgl-g^wh3WrBqeiuvdD5B-kyISmMS}PVZ_DQ@tA`Hy zXe=2J76X()nMT8OTI<yD7x0BId!sI`F^5RLiEUViwz^k$<G}b_U`8Vu&MRC}+==W{ zq2~GT_W}?rCdN!s(7}c$1{mF?px(;jjAI8vuwgBZmlA*H_s3#lSWxnQm%$x{*`y9f z+o6mFjgmSj{gmJ>D2?I5(|7Gqzo6Csq6>5G$M(ce!nVR=f~h$}wm$w;3097T+opO{ z#pSm*XIMxl3BXz2jPk;gmEAmBzArpAGl4v-cw|o_TgI~-#|g?mUn`{<WqNzn7Ie%^ z`7!AUWjUKv1nQPKq-4>Z?t!IB0-l5iaow9IJduhy`~HcrQQ^lE4_2j7BaJ__BX7#= zyAt>4e(Ap9_0ttMnXhPiPI~~GBj%y?4;WA1)b`jJ1BSW!69i(#(g^tG-UjhKvX4FN z9*9p=;$VRo2Uth>GwR6<<}5|<*u>^rv7!!Lj?`-UvZhOo6n2BmF<dRlK@D?A8@zbN z8=zm8J3H+yx<V@p)InSxdKOiD6HP~rw1R4N7I>yuM`|@l&XVL_8fD80voOvE<035Y zs`2**pmTyo=J2&LvXG*Ov+?f374$Hd?y0?ZH(XnzMg0?D-Gw*!%T@MJGpQC0_A4`M zm*uIBcou4bkpYMmOJKYl4>q*)AH;mNJHq(&Qm?=Q|FPq~uG-!h-nP)MO^OtDj?D=8 z?sHqrNMTgou`eZ5NiaI1Nvn#QN6W)J?c>Q@*=y4pu54%WA-O#1-O<DNCplVcaAGpM zaV-%%^Ue69Fl5u5fjY7s>KA2hJ1mZj>1CMeoN=kOlI<XiP?PPhNC|0F^P&WUlb)dK z?>x1>#g(~OPf_sa-ZVjRv8zN<MAN*CvP@mp6p<Zf&#I_{FSj=}zA~BF__;x4dd16X zQ=&p59q-LWS_;vxo$Vb<54meJswRR4&<e4I#X8Ga13xT7FvtZus?H*#jgpIzGOo!( zAVF~Lmne6-2Ob(19Wm7Sfl6F!iL@%7eXrhP<+PINiIlEgrk<d5mKJ}SB*9)Bl5NP` zVRlpPUt@%%9)Aq$UB2<8eR)5r=ObbzjToEZc-cmpx->eFs<(P0qIQfXr!IEpP3%p+ zoaQy5T-<)+f3T|ndZU^UXLI&_o#lY=d^^=<$*S`aeO>h&=Y>iz0x?DP8@*zZ?}5w6 z|99vGFM&SR8`QhXx|%sjmEDWJx;5zaSc_FLm=VJS@{GyBpK?m(K7eAMbeV-CECn`k ze)z*T($inP3AVer+hQi*nNh9)g(kHw1H@pt-TK+I;J2f)E7f;BMTsIfjF|lcPs&b3 zzp@Moa_f3<j%q}%o5e;Q7_|rD+uAv1+U7Q-Q6{PsP<M-;$v4v0H!=vJWnY?9rc2E4 z6Uzl5R&?&9%;pXY%km2q1+0`?JwsMoM=6Xljb5V!K&nS+5;YR<2Gp`Lc)}T&{5B^a z5MIT>z=bI6QsHTS>e>-+k};{*JrOlia(hI1Y$3T8Hbv|7`HB{gk{`U&xrh<)J(*J( z8>=a##zceHRYCcBiViA!tNSjYagI$xQhew$F;`cY5+Yntq`q}2F9ae2s}%`HWW7(L z<dc;cC?zx45p=qw^*)*7KXHHyW%>zzS&7AyqA^^MbJLP_0-$ElRtfp(wbRXi%RUUA zaNYY~_H4Iz)iUgQN(97RUE7X?i2a4`PI;4j)*oEw{>_Q{u7zneZ^yP$#EK3ndP)+E z-*^?gxtQE|!$~|L|2(3~EaD8>tVTrl(E;vS%%-2DKRO%rkw4n-tG4^xQ#TcYWev%U z<yoTT!5zQ*N3SxDW<IHNh-0Ir+ymY3Lrp(=hGGH`StWlHboojO8S2Fr3SJ!5&7+QF z-kCa0AP^c-#Pz1XEbm%Bzv<sX2wJknb;i)CJ+^uhnFaz?(+S8Kag|X8pDE(C8j8>B z3U$vN_KoTn`4hE1l#Ue~C6twjQ##bcV^MjhXz-o!FksGUc!OhF+uQs_m3or?>g-^> zMnP*v-cAsCa)3u4zmc$`tu`vVd`*0U2Mng6(s~JnO7~iq`MwOGFsB1?WE@tvhUHVw z7dqLbm)OR?2Lv3dTCVUxHV(_9IQ8zw&a%lipS_!g09-1dDv_77WL(ixg<$!^99?Ag zoO8d2IRwh;)mr3`$F+y;;cs<LM8KN}P@@``?vaD{JY4VF{fEUQjb1My4wl`ct{Qeb zQa{ZfP7yT)m}oMYp;?a)LuWG1?#3nOL_e+zz;FDu2>?m=g5iB5(I>5m&iDbbFv6qu zkz2Zj$cg)}+NS09&>F3bt!RxA_cq;KuX;w@rqqGJ;)%w{bk*`$_q4^iX~3*AHbG@H z@^U0TcFo8g61pS?3~@{hzEjbnzl<aV{GYt=%}VXQaaN0ns>5+wXBB5z*d}4pCk=gj zt>Z%&@4txBR>;y*sD>;t_EeD%B8zvUf1y2Nn(<U2qFD1{u~*i-ZnYv`(4jYCfT^x} zPJ+V_0}M_AqoD?_tQ>?0x%>Zynq8oK-|2F<jV?*)JworOPnyn2<OU?Q6)kd>e264g z9B)GhKM2}PIReKscGjT(tl%P-$ClVYCyU|Nefvr*=(a52WBOS4x`uc!ThggIgS(Qt zeSw{6Fo4I94z!Xb&yE&vbmg33W9EmjdevahoF4D>t7CXy^{{PD?eShDZ&kk~m6ulr z9ZYLjzsRT7A}d?_ragv?L-B!hu3wF$O330V*InzJQqi7XkLFHPz7StLT&kF@zkhkY zO>A@-+F*5jDv*2|3<mQDMBf9eMtZ(o2>rb6Rx%C!>0%Suu${{#CbxpKbK!p4xBn|R zRPft9?I`&Mi%po0`@2;`?`aO$;!*nY^fXmnE@F9S?QmUNPsnXn^6nzFxAquPx|fUQ zpd6+xMO;*Mv9a%Me=fEoS5twXJ^jh+dYoy7{8i_^w0vvSOG=Ogou+k4o5d#VWO>*c zoG|UV-{^4s^dNzp*0~>nKq%n=i>TZ6`()tf?K5J+gzYJ^UJS0?jva3p>cv%8{d2>* zR@37ZUw6-I2&h2>A9S+N_!bmf@Q(IU3g)}hv18C1IPK+i7VL#WoOkS4ypS-zSbU-R zF_z2L^sx;*iQL93*pOD-T{nIxH}?F}tLl9}p9Bs&T?($Z1D*a3NrUk5u!~?l!K;HU z-$nO?<x!^l5Qqo{T2;;-_Fl`(7jSu9NmF`p)E=Lgq!<Ql?KkXuT^V*6TG#e(aWrK< zSUw2R8a;5>D!3~Mv+<<5qR^H&H5oZBbIpen3?g=RZ(M{H-A<O=yC%J?R1sOcXfsg0 zI_(PXidB*Mwnf(Ob$z<HEOULiJiMhBND2n4>Z4b6qww)?XItn+c>UC&S$$RJB$5L& zMF+-yzBtX`!E=#fXow?mMLm5H>!k_GyVQOkdlK8YF{fO0X7ezXt7aFuag28+ea6-1 zxO`p4gUMMVT`-Zbd+lPpVOV~F$9qg%d0E80yce8s%?_;*$$x<Z(`LHXs~6ebUlHUI z>$HLa&s{l^+&uMw5r6cMm|j&tAe3%DA-4Cr!kGz`ygHfjA%YXRU9F7AV)5(iLkFMO zOx2#Qd<zAAmblP3mmQdP4BS#$f<iT(6Jt-cB>xGhT%91`KI5s{x9aCUTi1v6HS1n- zP$<;#0Wb@kR)>Xv#YrQK{`7yXuz#)GzBjQqWh5eZ%~dT-Gcu$ix`Vo)nDFwx3Fwy= zR2Pi^(lfb5w=9#2bUyHz<|*A)mWwnLs)1er4tm$a9{P-!{qw!FAJG-jrB!`c@4-Ei z^@MKxE>70fCmj>7t*3)Tm<4Ja%bsNjMw)l8`UdpOhIeaz_caOe0QT&UMuP(~jgmUT z$IV|ZfsHS3iNqV$_xylUi$2*P;vI8`c|<UwLW_|VXJr%y$OQ7to>BMy*ueMQjs&t- z1~q)h*}kFq$ILuwL=SGM-F<P(itG4Z0b3*=1qJ5s;OaD?rHV<E!!ejAoIzNKF?uVv zQ1K17%7kiq^?>|-Ucn=zR)qK4h9tev*wEOn8dzJ6C<Kx!2izx0nVct)3%rUJeK+c5 z_xMLC0@0o;A-|A?dXQiArX4*YsL%J{3uRBXr$TQNT~GEbf%HZjKCxjfPQ8RHmK;4{ zpXvFUKeqQmq|SCMOyjvc+9$D&ZW=;H=SLem?XcE+*g2Z#aOvLF3QVV!xVb#IW1>xV z{$_*|WNJgUOy=@1hGVI^z|SFPkGP1AS0-nl&073Q6Ph4gTfCZX!LPw33)Fcny{^OV zkF`h4%(P8g2m}HI3_rnW`f4I&&yDB3l@aPq7SY42sHNXY5^7yAa$#L}1ZBb4ds3K1 zRJ}r3NUb27p<djTB?yhJjP7ZCs~IAJ3j1tWNFZVXEz!3^IcYz)(h_ui;-s@%ZLt{B z%XrG1Ont{a-9~G>G^pt%F36#x31KLcZS!lOc{EztMi$99?Kao99Rl>kpw0coLZ|h^ zgsbJ9hdtJ1zPH~|abz6obw8e7aD~6yS{*tm)ZvZahi`w7B=+~54tXqg{;HFI6&ti+ z8UYH^2_3?NNy-MgC=x8w2EE*;_|yXHNKl+y{>JF}b%!@XSn4ohLRnDfRAVCqf#@NY z7>z?@3HKE7n|DQS^dBS%r^xbLJ;ZI|_2(=Cv*90|I4C`?&$DI_*lTzp8ed`~b%@KD zvU|NW;0f|(5_B@fzM_3YL2Rl#IWq#=3NIqf!56)^ZxBdXuxS8phJnni@)r~t52~oP z%m|I`p+^a1wqf`r7{a`|p6_OigTy%YgUwJ1_ACp}MVM+@<#4o^ZPRM*Lm+U4_T~K# z^b7l^O6E_Yj%qhQgq`nKD4E;Yo5U`lwtkII3qJz5P1V0GkMCM9{VEcCU8Wlu;F}>z z*aQA4aLG4k78N5<O~t>Nsx5MPYRn|A_NEzCbnU1JgNRE*D=Z**-Sc;rW~~#7qLnCj z5=DyU_(TI~5iq~uMod8XO>#dqQsFl>_=nW{-z9p`F2K7q3yD(r*IHETun>-5zm=OE zo_2NhB9#ZqOrpY;LSPjn`3av&sX#B+O>1+twfSUDMf1vEbZrt01|td3_36!};f2*q z;q34`kc>pYp_?U}axthd#qazR%{$&!8Wibz!Xc&!AR`txuX5VRpCOZ;Wza>8&ko>` z{<c@~I;c;9sXm1ROs;RSe_5d3faxm{xxl-6nRPN3MtLi$PCeEBQJ98LtEfGrE`P(+ z_Sh#T&_?>s=wU80p*O;tOg{J^A@Dmb;FfS5-@{13kxOORw`{S~#3nWzY{HqtotJl3 zU7<9EJhQjW;=?SKFHY`%`DNrwu7-=-TkFF+8N+fUkI-i8-md-I;dNI4CJzLD8>xd& z(MD>7hoW;U5LT~!e)gYX5GWOI^$CBXdJ~ljW;zp;nFr5HiC|gdx9#~m2R~Td(g|J2 z4Kgb_*fNcf$=j~NVJDY(=0-F+DXw3!NS`CL7|Rv1rErMx_>Pa8N3-Wo5E?>$z0CF} zY82)d8;$0mDcF9>J|4I3Df=-EFkaQGKfUI~RTn_AS4gcf>9R}Xotbr}TurnBqr@Kw zU_6%d8<9*uhbMi!`3r-#k)d0MBb=ZS`OUS%1lheq_6_Uw5|G$@69n|wG2l!TC-As# zvq*L?j&WZ0hwnOmFrR=})~6xC6`Adp6zi&jk>IUgfrf_y+MA^Zp+6@HL4awj#S8j3 zoD7aUr|@rqkr;E8<w}FjaKsf8<xdY{2Icp@v(R1wgB_37iuZ&qC*3YHuKRK;SC)7$ z$AmdD?p}Q*0?G0Vy_fyQ;tU4;GJS6RXfAL#H7jJPFx{-4_x3~gdc0}zsT|HnYlZq& zQmn6c^Rs6seENJA<EM<iepbk%a~aNpMVznVu%&#O1cUqB{m^AD@s*-;liX<fswnHX z!1QaSTi)mv7v>H@3w5(ZCZBVIM|!aRdXb%V38UR9mo~S9)qxR}CRE?XC8y;$&Y=() zUO9KaVf6BmedvpIGG7C&G}xDu{Sy5yq@mlU@2<O|m94#ry7DS(P`)avx0KL)VaQdh zn=$e+Pf~<I`mmsupGZG2Bs2sJDZO|f!g`lQlWu|gWEpNg-RhkVVRHlQr3<p>le_Jl z9l4WVE9xD-OttAp9#DN0qtR%CGGf}B6<@^aDZ19^x*l^-rw(nc3_kkr%0NBSfgX}d z$TNMQj|JEA81MYWsTk7kIe-arZH=Y?tt8yycft*^w-Llc6y#U>4^Nqyy()8-hC&_r z&`GpoE#Ro)9W^6X7)i0;*Z<j004@luci^=NjNQKhrc4n+&jAO4{`e3M0hCz0;a?kN zqC&Xds#5UQ38k6!$)FC3pJTzb5_qjcybj{K!VriR@gHa(Ra!;np!;6M6XiS`S^=eV z<EmkwQJ=+6^SLZ!uLS19oB%_0M`!!{_HX0V4g&&79ry@vp71Wt4mW8waM%ZViaW{t zmIno;!c3*eiC2zI$v6ir{B&YLLPd3gBEn8(E<dvLKg|bn{i|Chtrtg(_vvc5Jf-#6 z3tLw8^8DSxd7mG>TGZMtjW`kTUyNKh@a<H4YgwLACf)1T+>gj#bb@wUBK%bna3``4 zU%@NEU~rPaKd_5F3brWf-AsM*-E|3Kuj<7*H4n3*zprb!Q07A-@HzK4uH$=#ZksC! z)Pid<b0_@)s%_*|d)7LR4B}aS4scg2xo$T6z=23O;HVDTDiLf02~7hK6t`A7nm`Vp zbMgu3I*`Y;vtI~{FC*LJDegqtWdX!uLlcVe_-{PK8#!HeH`j@u84T___XqZ#K-&k5 z^gua*S!|t&yVqN+`$PNv!nq9Ciaz=cwmb3eq_(PrP6hC|h!*030#1D=i9HxD2Li)o zGlyZXD8*VDvrl?X3U7jl0UI<hzj}2u-T9m9{QDM=*!(D?8dB?(#aCZb*9#{X<K3x> z8;TK_pcPM@h@UWc#c(?AD%FA5)Q#r#{89b70>D#dMoDr(AgRYhK>Jo(gT1NA_Aazj zWTuwqL*}|IflhqFJuk|=6`D+R)E%>&P?yLR&+|~6BZI;e1!KMi3OIZ{D&zlsoHzfD zwr=E4=Ugi`tAn!zBUh}@Se`PMa3K2Sb9+h7)<pK<j#jDb@SyG1j|T!NOc)!PU8D>U zh@00R+aRyQ?;fZ@Y)0x1ndr~SU*(qC56|_+yTt7et?*0ar_*U++iX_`jsah{Aszj@ zz#Aq7LlMcywzJUU{2!9@*zDR;g%kHr75dd(JBNJv-mxqSdakEmBXYYw4L<2os@>94 zg>>lE6du<Ue(6oRM!h#YCL$@6kG!97Cf)j}m8;X5<Jfbx3=0Up4uM}=0sRwZh$KBv zFa_v_%6^4$-V>YH@3Lm};IL#ut*OD?n=$n8<kcqSvJ}3{GJQuEK5|H^+y{D~Lk<f1 zDR?<_=u)HTC9AA3`taiQ=3@`17K20d^!Oys4M~;THdPK<$um|CLMZV7u@mqT@%|4# z@;54=30l77)ycO3R$|pIiL;e&N|}$LgZF@H-sG|K0~c*0>@0OBppW1ApCn9;+tsD` z<{|e-lz<`4I}Hc~A_4~RB}t*)QheR<HlT(q(T{dl?CaGW8|J>j7`nb!e$Phy8U}NF z2x>raFWSU{YWugk^D;0R&gkBcZ)q=L!QLFlq|6>W4l6F=bV|BiT{cFR?kt>#M%Np- z!MuhRkG2yK2t)`baJkHvd+l`G(}!mD0iU<0u41pr*SE=7K_FZvpcmk(E&5u+I;Cm7 z+ot}9VZm&)GuM9z|H%_qhtttzjK!n3GttgAr&H5j6+vvBOrYNSSUPc{OVOR0mfVTD zkHZPmPp?<XoVoIlb&OY5wUYaL)7Q?odg(a6Ugg=qT(@8d&;7=x9AK*x4Rr7lG(3hx zoaPhF7M^Zd?NTS#pG@w>I)H3@HmlB#V;geG>#wg4sIGIkXCr5a8}MFG2oZsVxTa`Z zkk|XWOqT;)UZ)i+UBe)WDq9#4kK1hWbp1_GAz>{0#oD6hwW0N3!r|d$w@uSMq`UFy z)Y9qr)_$A&P2h%`{CBy31C&_5(-G-)Wk0{iG!y$h^O0fGDPD>P&&Ii)m^RNk=GF4D zcIvs9_j0J2)<9*B@{d2Rz)U*#PPDTPg~W5=%;RP`ZHa3;ZR4+<x_$@KUY@7IfZi{w zakK*lzT@}DCZ1a?&P!jl8v_2!#K6`RS<AR!ee9(t#>#g6ZP0SdIPE+QXr(@TqH=>4 zX>ZedJ?$%a=ML@*8g0&b&950E*99r_3D<iCx4p}*AMTI{;ijSD+;2|Q+3ly4ncvM< zx)3|-#tXGi08&SA66Bv6B`{9rLnS3nW_*+~pFufX_m{e*>E2;C{AuyZ(06p<VS%JR z{6LT1_2PGZ%q0y#5bpn(@ldEaF)+ER4=fUa*&ae&pv3r3C3QvTHJtG@gn=iVp~L*) zRrs{<7qEkB?)6)SP3X)oW0Wx#oNTv+f&bk46yV=n=zEk|#&f@@?(LmG81<xTlQ2L( zMYl$pMTdpQh`NxpKZk<V6>4mxXh;)+;|<jMeIh8(!hMrQoe=_YliUfRfkLH~6V#uM z-|W_PwhUzv08f@%Wxe{mZxDzHLdTNcX_OnfBR9MQzlkzz!%*HLKf3&wP+y9m#KRf< zAp1vR^Lf-bAep@FU?o5H{r((g3SX@^ghVu5Zevxf*lC$QYoB$>sQ(!LuNkb|mqtI$ zuTg?NqZ3BxGB}wD4DK5R8edhhWcgo5cx1fcsGbp&tuqsO^;cUJF8$gGBr_xcUF^`l zynARY&tqnuqSZ0hgBmx+={!a5F~jn<_d*~e_o_@=r<c7M>vbklPKk{qh<{Z=foz=I zZ%nARlYR?nK(88v8YVY=vMT*t&QRytt;1Q1(b9f9|J)4|aQ%-43Jrcq79Q7>zYO>$ z@)iO~<qZ=5s{s^qF7VmvEPuzunqw#|{cqMf=7@5WxFr{RQTU#L{!Rkf?UL4#m4|#u za!s0Ttn3Vb3-PiiUzdu-@3&kuTHmY-yBca~PaCk{>NO?^XfVrgEHeQl=fQBQe4?Ux z-wh#lXV4>{6f+KF!z{Ymo<gAq$!Il42+-w@L^w{d{GJ%dQFnK;ByFJ#3&8~oY~;<4 z(sYYkn!BU>Z|~f?_ryfE^aP%@kScA}BS?4L6SZvg>4{$%U)m%buZ4uSM*^g$u+E`j zdiNE&g$h)d64Kbew@M5KBfb7=-W=-hP5IqQ5L`bB#C3w%=(tV@c|7@p_6-Gy-a5<p z=cz&jH;mBFyc!Pu9>S|~l6B(4DSVGCX=?9vr}=a`?HSS9gFYtMIVTTAPW$Xyb=pDu z$gR6Mtji-(dBkn=y}}kKf3)##7Ipj!_|Y-q|Dl;loe|w>(Aw-n4oxyY+fm<SQS{m* zoAeK1g1`m;Y&q>F4Kx;1$3ORQY7u957NLSbQq|w1>)l2GI0<7T0Nf|rn*688;TDqo zU#Rbi7&j!9Qi0BMZ1oOsVeG(IXvo$lZDf>>u0nxBKj%;Z|NZ|=%zsPq^0~;Q-&-yH zV_p3p*!KUmXX+n9!~c&bvx)`i2qyadzVpRvtm%_IF;2S{IPep@vd@*E`A>~L{2#C> BZH)i` diff --git a/pyparsing_py3.py b/pyparsing_py3.py new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_cHlwYXJzaW5nX3B5My5weQ== --- /dev/null +++ b/pyparsing_py3.py @@ -0,0 +1,3716 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2009 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +#from __future__ import generators + +__doc__ = \ +""" +pyparsing module - Classes and methods to define and execute parsing grammars + +The pyparsing module is an alternative approach to creating and executing simple grammars, +vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you +don't need to learn a new syntax for defining grammars or matching expressions - the parsing module +provides a library of classes that you use to construct the grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form "<salutation>, <addressee>!"):: + + from pyparsing_py3 import Word, alphas + + # define grammar of a greeting + greet = Word( alphas ) + "," + Word( alphas ) + "!" + + hello = "Hello, World!" + print hello, "->", greet.parseString( hello ) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the self-explanatory +class names, and the use of '+', '|' and '^' operators. + +The parsed results returned from parseString() can be accessed as a nested list, a dictionary, or an +object with named attributes. + +The pyparsing module handles some of the problems that are typically vexing when writing text parsers: + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments +""" + +__version__ = "1.5.2.Py3" +__versionTime__ = "9 April 2009 12:21" +__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" + +import string +from weakref import ref as wkref +import copy +import sys +import warnings +import re +import sre_constants +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'getTokensEndLoc', 'hexnums', +'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', +'indentedBlock', 'originalTextFor', +] + +""" +Detect if we are running version 3.X and make appropriate changes +Robert A. Clark +""" +_PY3K = sys.version_info[0] > 2 +if _PY3K: + _MAX_INT = sys.maxsize + basestring = str + unichr = chr + _ustr = str + _str2dict = set + alphas = string.ascii_lowercase + string.ascii_uppercase +else: + _MAX_INT = sys.maxint + + def _ustr(obj): + """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries + str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It + then < returns the unicode object | encodes it with the default encoding | ... >. + """ + if isinstance(obj,unicode): + return obj + + try: + # If this works, then _ustr(obj) has the same behaviour as str(obj), so + # it won't break any existing code. + return str(obj) + + except UnicodeEncodeError: + # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) + # state that "The return value must be a string object". However, does a + # unicode object (being a subclass of basestring) count as a "string + # object"? + # If so, then return a unicode object: + return unicode(obj) + # Else encode it... but how? There are many choices... :) + # Replace unprintables with escape codes? + #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') + # Replace unprintables with question marks? + #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') + # ... + + def _str2dict(strg): + return dict( [(c,0) for c in strg] ) + + alphas = string.lowercase + string.uppercase + + +def _xml_escape(data): + """Escape &, <, >, ", ', etc. in a string of data.""" + + # ampersand must be replaced first + from_symbols = '&><"\'' + to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] + for from_,to_ in zip(from_symbols, to_symbols): + data = data.replace(from_, to_) + return data + +class _Constants(object): + pass + +nums = string.digits +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) +printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, pstr, loc=0, msg=None, elem=None ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parserElement = elem + + def __getattr__( self, aname ): + """supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + if( aname == "lineno" ): + return lineno( self.loc, self.pstr ) + elif( aname in ("col", "column") ): + return col( self.loc, self.pstr ) + elif( aname == "line" ): + return line( self.loc, self.pstr ) + else: + raise AttributeError(aname) + + def __str__( self ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, self.loc, self.lineno, self.column ) + def __repr__( self ): + return _ustr(self) + def markInputline( self, markerString = ">!<" ): + """Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( [line_str[:line_column], + markerString, line_str[line_column:]]) + return line_str.strip() + def __dir__(self): + return "loc msg pstr parserElement lineno col line " \ + "markInputLine __str__ __repr__".split() + +class ParseException(ParseBaseException): + """exception thrown when parse expressions don't match class; + supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + pass + +class ParseFatalException(ParseBaseException): + """user-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately""" + pass + +class ParseSyntaxException(ParseFatalException): + """just like ParseFatalException, but thrown internally when an + ErrorStop indicates that parsing is to stop immediately because + an unbacktrackable syntax error has been found""" + def __init__(self, pe): + super(ParseSyntaxException, self).__init__( + pe.pstr, pe.loc, pe.msg, pe.parserElement) + +#~ class ReparseException(ParseBaseException): + #~ """Experimental class - parse actions can raise this exception to cause + #~ pyparsing to reparse the input string: + #~ - with a modified input string, and/or + #~ - with a modified start location + #~ Set the values of the ReparseException in the constructor, and raise the + #~ exception in a parse action to cause pyparsing to use the new string/location. + #~ Setting the values as None causes no change to be made. + #~ """ + #~ def __init_( self, newstring, restartLoc ): + #~ self.newParseText = newstring + #~ self.reparseLoc = restartLoc + +class RecursiveGrammarException(Exception): + """exception thrown by validate() if the grammar could be improperly recursive""" + def __init__( self, parseElementList ): + self.parseElementTrace = parseElementList + + def __str__( self ): + return "RecursiveGrammarException: %s" % self.parseElementTrace + +class _ParseResultsWithOffset(object): + def __init__(self,p1,p2): + self.tup = (p1,p2) + def __getitem__(self,i): + return self.tup[i] + def __repr__(self): + return repr(self.tup) + def setOffset(self,i): + self.tup = (self.tup[0],i) + +class ParseResults(object): + """Structured parse results, to provide multiple means of access to the parsed data: + - as a list (len(results)) + - by list index (results[0], results[1], etc.) + - by attribute (results.<resultsName>) + """ + __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) + def __new__(cls, toklist, name=None, asList=True, modal=True ): + if isinstance(toklist, cls): + return toklist + retobj = object.__new__(cls) + retobj.__doinit = True + return retobj + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, toklist, name=None, asList=True, modal=True ): + if self.__doinit: + self.__doinit = False + self.__name = None + self.__parent = None + self.__accumNames = {} + if isinstance(toklist, list): + self.__toklist = toklist[:] + else: + self.__toklist = [toklist] + self.__tokdict = dict() + + if name: + if not modal: + self.__accumNames[name] = 0 + if isinstance(name,int): + name = _ustr(name) # will always return a str, but use _ustr for consistency + self.__name = name + if not toklist in (None,'',[]): + if isinstance(toklist,basestring): + toklist = [ toklist ] + if asList: + if isinstance(toklist,ParseResults): + self[name] = _ParseResultsWithOffset(toklist.copy(),0) + else: + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name].__name = name + else: + try: + self[name] = toklist[0] + except (KeyError,TypeError,IndexError): + self[name] = toklist + + def __getitem__( self, i ): + if isinstance( i, (int,slice) ): + return self.__toklist[i] + else: + if i not in self.__accumNames: + return self.__tokdict[i][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[i] ]) + + def __setitem__( self, k, v ): + if isinstance(v,_ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + sub = v[0] + elif isinstance(k,int): + self.__toklist[k] = v + sub = v + else: + self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + sub = v + if isinstance(sub,ParseResults): + sub.__parent = wkref(self) + + def __delitem__( self, i ): + if isinstance(i,(int,slice)): + mylen = len( self.__toklist ) + del self.__toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i+1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) + else: + del self.__tokdict[i] + + def __contains__( self, k ): + return k in self.__tokdict + + def __len__( self ): return len( self.__toklist ) + def __bool__(self): return len( self.__toklist ) > 0 + __nonzero__ = __bool__ + def __iter__( self ): return iter( self.__toklist ) + def __reversed__( self ): return iter( reversed(self.__toklist) ) + def keys( self ): + """Returns all named result keys.""" + return self.__tokdict.keys() + + def pop( self, index=-1 ): + """Removes and returns item at specified index (default=last). + Will work with either numeric indices or dict-key indicies.""" + ret = self[index] + del self[index] + return ret + + def get(self, key, defaultValue=None): + """Returns named result matching the given key, or if there is no + such name, then returns the given defaultValue or None if no + defaultValue is specified.""" + if key in self: + return self[key] + else: + return defaultValue + + def insert( self, index, insStr ): + self.__toklist.insert(index, insStr) + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) + + def items( self ): + """Returns all named result keys and values as a list of tuples.""" + return [(k,self[k]) for k in self.__tokdict] + + def values( self ): + """Returns all named result values.""" + return [ v[-1][0] for v in self.__tokdict.values() ] + + def __getattr__( self, name ): + if name not in self.__slots__: + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + return "" + return None + + def __add__( self, other ): + ret = self.copy() + ret += other + return ret + + def __iadd__( self, other ): + if other.__tokdict: + offset = len(self.__toklist) + addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) + otheritems = other.__tokdict.items() + otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) + for (k,vlist) in otheritems for v in vlist] + for k,v in otherdictitems: + self[k] = v + if isinstance(v[0],ParseResults): + v[0].__parent = wkref(self) + + self.__toklist += other.__toklist + self.__accumNames.update( other.__accumNames ) + del other + return self + + def __repr__( self ): + return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + + def __str__( self ): + out = "[" + sep = "" + for i in self.__toklist: + if isinstance(i, ParseResults): + out += sep + _ustr(i) + else: + out += sep + repr(i) + sep = ", " + out += "]" + return out + + def _asStringList( self, sep='' ): + out = [] + for item in self.__toklist: + if out and sep: + out.append(sep) + if isinstance( item, ParseResults ): + out += item._asStringList() + else: + out.append( _ustr(item) ) + return out + + def asList( self ): + """Returns the parse results as a nested list of matching tokens, all converted to strings.""" + out = [] + for res in self.__toklist: + if isinstance(res,ParseResults): + out.append( res.asList() ) + else: + out.append( res ) + return out + + def asDict( self ): + """Returns the named parse results as dictionary.""" + return dict( self.items() ) + + def copy( self ): + """Returns a new copy of a ParseResults object.""" + ret = ParseResults( self.__toklist ) + ret.__tokdict = self.__tokdict.copy() + ret.__parent = self.__parent + ret.__accumNames.update( self.__accumNames ) + ret.__name = self.__name + return ret + + def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" + nl = "\n" + out = [] + namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() + for v in vlist ] ) + nextLevelIndent = indent + " " + + # collapse out indents if formatting is not desired + if not formatted: + indent = "" + nextLevelIndent = "" + nl = "" + + selfTag = None + if doctag is not None: + selfTag = doctag + else: + if self.__name: + selfTag = self.__name + + if not selfTag: + if namedItemsOnly: + return "" + else: + selfTag = "ITEM" + + out += [ nl, indent, "<", selfTag, ">" ] + + worklist = self.__toklist + for i,res in enumerate(worklist): + if isinstance(res,ParseResults): + if i in namedItems: + out += [ res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + out += [ res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + # individual token, see if there is a name for it + resTag = None + if i in namedItems: + resTag = namedItems[i] + if not resTag: + if namedItemsOnly: + continue + else: + resTag = "ITEM" + xmlBodyText = _xml_escape(_ustr(res)) + out += [ nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "</", resTag, ">" ] + + out += [ nl, indent, "</", selfTag, ">" ] + return "".join(out) + + def __lookup(self,sub): + for k,vlist in self.__tokdict.items(): + for v,loc in vlist: + if sub is v: + return k + return None + + def getName(self): + """Returns the results name for this token expression.""" + if self.__name: + return self.__name + elif self.__parent: + par = self.__parent() + if par: + return par.__lookup(self) + else: + return None + elif (len(self) == 1 and + len(self.__tokdict) == 1 and + self.__tokdict.values()[0][0][1] in (0,-1)): + return self.__tokdict.keys()[0] + else: + return None + + def dump(self,indent='',depth=0): + """Diagnostic method for listing out the contents of a ParseResults. + Accepts an optional indent argument so that this string can be embedded + in a nested display of other data.""" + out = [] + out.append( indent+_ustr(self.asList()) ) + keys = self.items() + keys.sort() + for k,v in keys: + if out: + out.append('\n') + out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) + if isinstance(v,ParseResults): + if v.keys(): + out.append( v.dump(indent,depth+1) ) + else: + out.append(_ustr(v)) + else: + out.append(_ustr(v)) + return "".join(out) + + # add support for pickle protocol + def __getstate__(self): + return ( self.__toklist, + ( self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name ) ) + + def __setstate__(self,state): + self.__toklist = state[0] + self.__tokdict, \ + par, \ + inAccumNames, \ + self.__name = state[1] + self.__accumNames = {} + self.__accumNames.update(inAccumNames) + if par is not None: + self.__parent = wkref(par) + else: + self.__parent = None + + def __dir__(self): + return dir(super(ParseResults,self)) + self.keys() + +def col (loc,strg): + """Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return (loc<len(strg) and strg[loc] == '\n') and 1 or loc - strg.rfind("\n", 0, loc) + +def lineno(loc,strg): + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return strg.count("\n",0,loc) + 1 + +def line( loc, strg ): + """Returns the line of text containing loc within a string, counting newlines as line separators. + """ + lastCR = strg.rfind("\n", 0, loc) + nextCR = strg.find("\n", loc) + if nextCR > 0: + return strg[lastCR+1:nextCR] + else: + return strg[lastCR+1:] + +def _defaultStartDebugAction( instring, loc, expr ): + print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): + print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) + +def _defaultExceptionDebugAction( instring, loc, expr, exc ): + print ("Exception raised:" + _ustr(exc)) + +def nullDebugAction(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + pass + +class ParserElement(object): + """Abstract base level parser element class.""" + DEFAULT_WHITE_CHARS = " \n\t\r" + + def setDefaultWhitespaceChars( chars ): + """Overrides the default whitespace chars + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) + + def __init__( self, savelist=False ): + self.parseAction = list() + self.failAction = None + #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall + self.strRepr = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + self.copyDefaultWhiteChars = True + self.mayReturnEmpty = False # used when checking for left-recursion + self.keepTabs = False + self.ignoreExprs = list() + self.debug = False + self.streamlined = False + self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index + self.errmsg = "" + self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) + self.debugActions = ( None, None, None ) #custom debug actions + self.re = None + self.callPreparse = True # used to avoid redundant calls to preParse + self.callDuringTry = False + + def copy( self ): + """Make a copy of this ParserElement. Useful for defining different parse actions + for the same parsing pattern, using copies of the original parse element.""" + cpy = copy.copy( self ) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + return cpy + + def setName( self, name ): + """Define name for this expression, for use in debugging.""" + self.name = name + self.errmsg = "Expected " + self.name + if hasattr(self,"exception"): + self.exception.msg = self.errmsg + return self + + def setResultsName( self, name, listAllMatches=False ): + """Define name for referencing matching tokens as a nested attribute + of the returned parse results. + NOTE: this returns a *copy* of the original ParserElement object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + """ + newself = self.copy() + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def setBreak(self,breakFlag = True): + """Method to invoke the Python pdb debugger when this element is + about to be parsed. Set breakFlag to True to enable, False to + disable. + """ + if breakFlag: + _parseMethod = self._parse + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + pdb.set_trace() + return _parseMethod( instring, loc, doActions, callPreParse ) + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse,"_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def _normalizeParseActionArgs( f ): + """Internal method used to decorate parse actions that take fewer than 3 arguments, + so that all parse actions can be called as f(s,l,t).""" + STAR_ARGS = 4 + + try: + restore = None + if isinstance(f,type): + restore = f + f = f.__init__ + if not _PY3K: + codeObj = f.func_code + else: + codeObj = f.code + if codeObj.co_flags & STAR_ARGS: + return f + numargs = codeObj.co_argcount + if not _PY3K: + if hasattr(f,"im_self"): + numargs -= 1 + else: + if hasattr(f,"__self__"): + numargs -= 1 + if restore: + f = restore + except AttributeError: + try: + if not _PY3K: + call_im_func_code = f.__call__.im_func.func_code + else: + call_im_func_code = f.__code__ + + # not a function, must be a callable object, get info from the + # im_func binding of its bound __call__ method + if call_im_func_code.co_flags & STAR_ARGS: + return f + numargs = call_im_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 0 + except AttributeError: + if not _PY3K: + call_func_code = f.__call__.func_code + else: + call_func_code = f.__call__.__code__ + # not a bound method, get info directly from __call__ method + if call_func_code.co_flags & STAR_ARGS: + return f + numargs = call_func_code.co_argcount + if not _PY3K: + if hasattr(f.__call__,"im_self"): + numargs -= 1 + else: + if hasattr(f.__call__,"__self__"): + numargs -= 1 + + + #~ print ("adding function %s with %d args" % (f.func_name,numargs)) + if numargs == 3: + return f + else: + if numargs > 3: + def tmp(s,l,t): + return f(f.__call__.__self__, s,l,t) + if numargs == 2: + def tmp(s,l,t): + return f(l,t) + elif numargs == 1: + def tmp(s,l,t): + return f(t) + else: #~ numargs == 0: + def tmp(s,l,t): + return f() + try: + tmp.__name__ = f.__name__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__doc__ = f.__doc__ + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + try: + tmp.__dict__.update(f.__dict__) + except (AttributeError,TypeError): + # no need for special handling if attribute doesnt exist + pass + return tmp + _normalizeParseActionArgs = staticmethod(_normalizeParseActionArgs) + + def setParseAction( self, *fns, **kwargs ): + """Define action to perform when successfully matching parse element definition. + Parse action fn is a callable method with 0-3 arguments, called as fn(s,loc,toks), + fn(loc,toks), fn(toks), or just fn(), where: + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a ParseResults object + If the functions in fns modify the tokens, they can return them as the return + value from fn, and the modified list of tokens will replace the original. + Otherwise, fn does not need to return any value. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{parseString}<parseString>} for more information + on parsing strings containing <TAB>s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + self.parseAction = list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def addParseAction( self, *fns, **kwargs ): + """Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.""" + self.parseAction += list(map(self._normalizeParseActionArgs, list(fns))) + self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def setFailAction( self, fn ): + """Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + fn(s,loc,expr,err) where: + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + The function returns no value. It may throw ParseFatalException + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables( self, instring, loc ): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc,dummy = e._parse( instring, loc ) + exprsFound = True + except ParseException: + pass + return loc + + def preParse( self, instring, loc ): + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + + if self.skipWhitespace: + wt = self.whiteChars + instrlen = len(instring) + while loc < instrlen and instring[loc] in wt: + loc += 1 + + return loc + + def parseImpl( self, instring, loc, doActions=True ): + return loc, [] + + def postParse( self, instring, loc, tokenlist ): + return tokenlist + + #~ @profile + def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): + debugging = ( self.debug ) #and doActions ) + + if debugging or self.failAction: + #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + if (self.debugActions[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException: + #~ print ("Exception raised:", err) + err = None + if self.debugActions[2]: + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + if self.failAction: + if err is None: + err = sys.exc_info()[1] + self.failAction( instring, tokensStart, self, err ) + raise + else: + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = loc + if self.mayIndexError or loc >= len(instring): + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + else: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + + tokens = self.postParse( instring, loc, tokens ) + + retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + except ParseBaseException: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + raise + else: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + + if debugging: + #~ print ("Matched",self,"->",retTokens.asList()) + if (self.debugActions[1] ): + self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + + return loc, retTokens + + def tryParse( self, instring, loc ): + try: + return self._parse( instring, loc, doActions=False )[0] + except ParseFatalException: + raise ParseException( instring, loc, self.errmsg, self) + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + lookup = (self,instring,loc,callPreParse,doActions) + if lookup in ParserElement._exprArgCache: + value = ParserElement._exprArgCache[ lookup ] + if isinstance(value,Exception): + raise value + return value + else: + try: + value = self._parseNoCache( instring, loc, doActions, callPreParse ) + ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) + return value + except ParseBaseException: + pe = sys.exc_info()[1] + ParserElement._exprArgCache[ lookup ] = pe + raise + + _parse = _parseNoCache + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + _exprArgCache = {} + def resetCache(): + ParserElement._exprArgCache.clear() + resetCache = staticmethod(resetCache) + + _packratEnabled = False + def enablePackrat(): + """Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing_py3 as pyparsing. To activate the packrat feature, your + program must call the class method ParserElement.enablePackrat(). If + your program uses psyco to "compile as you go", you must call + enablePackrat before calling psyco.full(). If you do not do this, + Python will crash. For best results, call enablePackrat() immediately + after importing pyparsing. + """ + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + ParserElement._parse = ParserElement._parseCache + enablePackrat = staticmethod(enablePackrat) + + def parseString( self, instring, parseAll=False ): + """Execute the parse expression with the given string. + This is the main interface to the client code, once the complete + expression has been built. + + If you want the grammar to require that the entire input string be + successfully parsed, then set parseAll to True (equivalent to ending + the grammar with StringEnd()). + + Note: parseString implicitly calls expandtabs() on the input string, + in order to report proper column numbers in parse actions. + If the input string contains tabs and + the grammar uses parse actions that use the loc argument to index into the + string being parsed, you can ensure you have a consistent view of the input + string by: + - calling parseWithTabs on your grammar before calling parseString + (see L{I{parseWithTabs}<parseWithTabs>}) + - define your parse action using the full (s,loc,toks) signature, and + reference the input string using the parse action's s argument + - explictly expand the tabs in your input string before calling + parseString + """ + ParserElement.resetCache() + if not self.streamlined: + self.streamline() + #~ self.saveAsList = True + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse( instring, 0 ) + if parseAll: + loc = self.preParse( instring, loc ) + StringEnd()._parse( instring, loc ) + except ParseBaseException: + exc = sys.exc_info()[1] + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + else: + return tokens + + def scanString( self, instring, maxMatches=_MAX_INT ): + """Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + maxMatches argument, to clip scanning after 'n' matches are found. + + Note that the start and end locations are reported relative to the string + being parsed. See L{I{parseString}<parseString>} for more information on parsing + strings with embedded tabs.""" + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = _ustr(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn( instring, loc ) + nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + except ParseException: + loc = preloc+1 + else: + if nextLoc > loc: + matches += 1 + yield tokens, preloc, nextLoc + loc = nextLoc + else: + loc = preloc+1 + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def transformString( self, instring ): + """Extension to scanString, to modify matching text with modified tokens that may + be returned from a parse action. To use transformString, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking transformString() on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. transformString() returns the resulting transformed string.""" + out = [] + lastE = 0 + # force preservation of <TAB>s, to minimize unwanted transformation of string, and to + # keep string locs straight between transformString and scanString + self.keepTabs = True + try: + for t,s,e in self.scanString( instring ): + out.append( instring[lastE:s] ) + if t: + if isinstance(t,ParseResults): + out += t.asList() + elif isinstance(t,list): + out += t + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + return "".join(map(_ustr,out)) + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def searchString( self, instring, maxMatches=_MAX_INT ): + """Another extension to scanString, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + maxMatches argument, to clip searching after 'n' matches are found. + """ + try: + return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + except ParseBaseException: + pe = sys.exc_info()[1] + raise pe + + def __add__(self, other ): + """Implementation of + operator - returns And""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, other ] ) + + def __radd__(self, other ): + """Implementation of + operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other + self + + def __sub__(self, other): + """Implementation of - operator, returns And with error stop""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, And._ErrorStop(), other ] ) + + def __rsub__(self, other ): + """Implementation of - operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other - self + + def __mul__(self,other): + if isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0],int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self*other[0] + ZeroOrMore(self) + elif isinstance(other[0],int) and isinstance(other[1],int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + else: + raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError("second tuple value must be greater or equal to first tuple value") + if minElements == optElements == 0: + raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + + if (optElements): + def makeOptionalList(n): + if n>1: + return Optional(self + makeOptionalList(n-1)) + else: + return Optional(self) + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self]*minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self]*minElements) + return ret + + def __rmul__(self, other): + return self.__mul__(other) + + def __or__(self, other ): + """Implementation of | operator - returns MatchFirst""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst( [ self, other ] ) + + def __ror__(self, other ): + """Implementation of | operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other | self + + def __xor__(self, other ): + """Implementation of ^ operator - returns Or""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Or( [ self, other ] ) + + def __rxor__(self, other ): + """Implementation of ^ operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other ^ self + + def __and__(self, other ): + """Implementation of & operator - returns Each""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Each( [ self, other ] ) + + def __rand__(self, other ): + """Implementation of & operator when left operand is not a ParserElement""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other & self + + def __invert__( self ): + """Implementation of ~ operator - returns NotAny""" + return NotAny( self ) + + def __call__(self, name): + """Shortcut for setResultsName, with listAllMatches=default:: + userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") + could be written as:: + userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + """ + return self.setResultsName(name) + + def suppress( self ): + """Suppresses the output of this ParserElement; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress( self ) + + def leaveWhitespace( self ): + """Disables the skipping of whitespace before matching the characters in the + ParserElement's defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + """ + self.skipWhitespace = False + return self + + def setWhitespaceChars( self, chars ): + """Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = chars + self.copyDefaultWhiteChars = False + return self + + def parseWithTabs( self ): + """Overrides default behavior to expand <TAB>s to spaces before parsing the input string. + Must be called before parseString when the input grammar contains elements that + match <TAB> characters.""" + self.keepTabs = True + return self + + def ignore( self, other ): + """Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + """ + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + self.ignoreExprs.append( other ) + else: + self.ignoreExprs.append( Suppress( other ) ) + return self + + def setDebugActions( self, startAction, successAction, exceptionAction ): + """Enable display of debugging messages while doing pattern matching.""" + self.debugActions = (startAction or _defaultStartDebugAction, + successAction or _defaultSuccessDebugAction, + exceptionAction or _defaultExceptionDebugAction) + self.debug = True + return self + + def setDebug( self, flag=True ): + """Enable display of debugging messages while doing pattern matching. + Set flag to True to enable, False to disable.""" + if flag: + self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + else: + self.debug = False + return self + + def __str__( self ): + return self.name + + def __repr__( self ): + return _ustr(self) + + def streamline( self ): + self.streamlined = True + self.strRepr = None + return self + + def checkRecursion( self, parseElementList ): + pass + + def validate( self, validateTrace=[] ): + """Check defined expressions for valid structure, check for infinite recursive definitions.""" + self.checkRecursion( [] ) + + def parseFile( self, file_or_filename, parseAll=False ): + """Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + try: + file_contents = file_or_filename.read() + except AttributeError: + f = open(file_or_filename, "rb") + file_contents = f.read() + f.close() + try: + return self.parseString(file_contents, parseAll) + except ParseBaseException: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def getException(self): + return ParseException("",0,self.errmsg,self) + + def __getattr__(self,aname): + if aname == "myException": + self.myException = ret = self.getException(); + return ret; + else: + raise AttributeError("no such attribute " + aname) + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or self.__dict__ == other.__dict__ + elif isinstance(other, basestring): + try: + self.parseString(_ustr(other), parseAll=True) + return True + except ParseBaseException: + return False + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(id(self)) + + def __req__(self,other): + return self == other + + def __rne__(self,other): + return not (self == other) + + +class Token(ParserElement): + """Abstract ParserElement subclass, for defining atomic matching patterns.""" + def __init__( self ): + super(Token,self).__init__( savelist=False ) + #self.myException = ParseException("",0,"",self) + + def setName(self, name): + s = super(Token,self).setName(name) + self.errmsg = "Expected " + self.name + #s.myException.msg = self.errmsg + return s + + +class Empty(Token): + """An empty token, will always match.""" + def __init__( self ): + super(Empty,self).__init__() + self.name = "Empty" + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """A token that will never match.""" + def __init__( self ): + super(NoMatch,self).__init__() + self.name = "NoMatch" + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + +class Literal(Token): + """Token to exactly match a specified string.""" + def __init__( self, matchString ): + super(Literal,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Literal; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.__class__ = Empty + self.name = '"%s"' % _ustr(self.match) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + + # Performance tuning: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc +_L = Literal + +class Keyword(Token): + """Token to exactly match a specified string as a keyword, that is, it must be + immediately followed by a non-keyword character. Compare with Literal:: + Literal("if") will match the leading 'if' in 'ifAndOnlyIf'. + Keyword("if") will not; it will only match the leading 'if in 'if x=1', or 'if(y==2)' + Accepts two optional constructor arguments in addition to the keyword string: + identChars is a string of characters that would be valid identifier characters, + defaulting to all alphanumerics + "_" and "$"; caseless allows case-insensitive + matching, default is False. + """ + DEFAULT_KEYWORD_CHARS = alphanums+"_$" + + def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): + super(Keyword,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Keyword; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.name = '"%s"' % self.match + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = matchString.upper() + identChars = identChars.upper() + self.identChars = _str2dict(identChars) + + def parseImpl( self, instring, loc, doActions=True ): + if self.caseless: + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and + (loc == 0 or instring[loc-1].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + else: + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and + (loc == 0 or instring[loc-1] not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + def copy(self): + c = super(Keyword,self).copy() + c.identChars = Keyword.DEFAULT_KEYWORD_CHARS + return c + + def setDefaultKeywordChars( chars ): + """Overrides the default Keyword chars + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) + +class CaselessLiteral(Literal): + """Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + """ + def __init__( self, matchString ): + super(CaselessLiteral,self).__init__( matchString.upper() ) + # Preserve the defining literal. + self.returnString = matchString + self.name = "'%s'" % self.returnString + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if instring[ loc:loc+self.matchLen ].upper() == self.match: + return loc+self.matchLen, self.returnString + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class CaselessKeyword(Keyword): + def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): + super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + + def parseImpl( self, instring, loc, doActions=True ): + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Word(Token): + """Token for matching words composed of allowed character sets. + Defined with string containing all allowed initial characters, + an optional string containing allowed body characters (if omitted, + defaults to the initial character set), and an optional minimum, + maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False ): + super(Word,self).__init__() + self.initCharsOrig = initChars + self.initChars = _str2dict(initChars) + if bodyChars : + self.bodyCharsOrig = bodyChars + self.bodyChars = _str2dict(bodyChars) + else: + self.bodyCharsOrig = initChars + self.bodyChars = _str2dict(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.asKeyword = asKeyword + + if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if self.bodyCharsOrig == self.initCharsOrig: + self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) + elif len(self.bodyCharsOrig) == 1: + self.reString = "%s[%s]*" % \ + (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + else: + self.reString = "[%s][%s]*" % \ + (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + if self.asKeyword: + self.reString = r"\b"+self.reString+r"\b" + try: + self.re = re.compile( self.reString ) + except: + self.re = None + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + return loc,result.group() + + if not(instring[ loc ] in self.initChars): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min( maxloc, instrlen ) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if self.asKeyword: + if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): + throwException = True + + if throwException: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(Word,self).__str__() + except: + pass + + + if self.strRepr is None: + + def charsAsStr(s): + if len(s)>4: + return s[:4]+"..." + else: + return s + + if ( self.initCharsOrig != self.bodyCharsOrig ): + self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + else: + self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) + + return self.strRepr + + +class Regex(Token): + """Token for matching strings that match a given regular expression. + Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. + """ + def __init__( self, pattern, flags=0): + """The parameters pattern and flags are passed to the re.compile() function as-is. See the Python re module for an explanation of the acceptable patterns and flags.""" + super(Regex,self).__init__() + + if len(pattern) == 0: + warnings.warn("null string passed to Regex; use Empty() instead", + SyntaxWarning, stacklevel=2) + + self.pattern = pattern + self.flags = flags + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + d = result.groupdict() + ret = ParseResults(result.group()) + if d: + for k in d: + ret[k] = d[k] + return loc,ret + + def __str__( self ): + try: + return super(Regex,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "Re:(%s)" % repr(self.pattern) + + return self.strRepr + + +class QuotedString(Token): + """Token for matching strings that are delimited by quoting characters. + """ + def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): + """ + Defined with the following parameters: + - quoteChar - string of one or more characters defining the quote delimiting string + - escChar - character to escape quotes, typically backslash (default=None) + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) + - multiline - boolean indicating whether quotes can span multiple lines (default=False) + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) + """ + super(QuotedString,self).__init__() + + # remove white space from quote chars - wont work anyway + quoteChar = quoteChar.strip() + if len(quoteChar) == 0: + warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + if endQuoteChar is None: + endQuoteChar = quoteChar + else: + endQuoteChar = endQuoteChar.strip() + if len(endQuoteChar) == 0: + warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + self.quoteChar = quoteChar + self.quoteCharLen = len(quoteChar) + self.firstQuoteChar = quoteChar[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + self.pattern = r'%s(?:[^%s%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + else: + self.flags = 0 + self.pattern = r'%s(?:[^%s\n\r%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + if len(self.endQuoteChar) > 1: + self.pattern += ( + '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' + ) + if escQuote: + self.pattern += (r'|(?:%s)' % re.escape(escQuote)) + if escChar: + self.pattern += (r'|(?:%s.)' % re.escape(escChar)) + self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + + if isinstance(ret,basestring): + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + def __str__( self ): + try: + return super(QuotedString,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) + + return self.strRepr + + +class CharsNotIn(Token): + """Token for matching words composed of characters *not* in a given set. + Defined with string containing all disallowed characters, and an optional + minimum, maximum, and/or exact length. The default value for min is 1 (a + minimum value < 1 is not valid); the default values for max and exact + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, notChars, min=1, max=0, exact=0 ): + super(CharsNotIn,self).__init__() + self.skipWhitespace = False + self.notChars = notChars + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = ( self.minLen == 0 ) + #self.myException.msg = self.errmsg + self.mayIndexError = False + + def parseImpl( self, instring, loc, doActions=True ): + if instring[loc] in self.notChars: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + start = loc + loc += 1 + notchars = self.notChars + maxlen = min( start+self.maxLen, len(instring) ) + while loc < maxlen and \ + (instring[loc] not in notchars): + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(CharsNotIn, self).__str__() + except: + pass + + if self.strRepr is None: + if len(self.notChars) > 4: + self.strRepr = "!W:(%s...)" % self.notChars[:4] + else: + self.strRepr = "!W:(%s)" % self.notChars + + return self.strRepr + +class White(Token): + """Special matching class for matching whitespace. Normally, whitespace is ignored + by pyparsing grammars. This class is included when some whitespace structures + are significant. Define with a string containing the whitespace characters to be + matched; default is " \\t\\r\\n". Also takes optional min, max, and exact arguments, + as defined for the Word class.""" + whiteStrs = { + " " : "<SPC>", + "\t": "<TAB>", + "\n": "<LF>", + "\r": "<CR>", + "\f": "<FF>", + } + def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): + super(White,self).__init__() + self.matchWhite = ws + self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) + #~ self.leaveWhitespace() + self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + #self.myException.msg = self.errmsg + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def parseImpl( self, instring, loc, doActions=True ): + if not(instring[ loc ] in self.matchWhite): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min( maxloc, len(instring) ) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + +class _PositionToken(Token): + def __init__( self ): + super(_PositionToken,self).__init__() + self.name=self.__class__.__name__ + self.mayReturnEmpty = True + self.mayIndexError = False + +class GoToColumn(_PositionToken): + """Token to advance to a specific column of input text; useful for tabular report scraping.""" + def __init__( self, colno ): + super(GoToColumn,self).__init__() + self.col = colno + + def preParse( self, instring, loc ): + if col(loc,instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + thiscol = col( loc, instring ) + if thiscol > self.col: + raise ParseException( instring, loc, "Text not in expected column", self ) + newloc = loc + self.col - thiscol + ret = instring[ loc: newloc ] + return newloc, ret + +class LineStart(_PositionToken): + """Matches if current position is at the beginning of a line within the parse string""" + def __init__( self ): + super(LineStart,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected start of line" + #self.myException.msg = self.errmsg + + def preParse( self, instring, loc ): + preloc = super(LineStart,self).preParse(instring,loc) + if instring[preloc] == "\n": + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + if not( loc==0 or + (loc == self.preParse( instring, 0 )) or + (instring[loc-1] == "\n") ): #col(loc, instring) != 1: + #~ raise ParseException( instring, loc, "Expected start of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class LineEnd(_PositionToken): + """Matches if current position is at the end of a line within the parse string""" + def __init__( self ): + super(LineEnd,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected end of line" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc<len(instring): + if instring[loc] == "\n": + return loc+1, "\n" + else: + #~ raise ParseException( instring, loc, "Expected end of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + elif loc == len(instring): + return loc+1, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class StringStart(_PositionToken): + """Matches if current position is at the beginning of the parse string""" + def __init__( self ): + super(StringStart,self).__init__() + self.errmsg = "Expected start of text" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc != 0: + # see if entire string up to here is just whitespace and ignoreables + if loc != self.preParse( instring, 0 ): + #~ raise ParseException( instring, loc, "Expected start of text" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class StringEnd(_PositionToken): + """Matches if current position is at the end of the parse string""" + def __init__( self ): + super(StringEnd,self).__init__() + self.errmsg = "Expected end of text" + #self.myException.msg = self.errmsg + + def parseImpl( self, instring, loc, doActions=True ): + if loc < len(instring): + #~ raise ParseException( instring, loc, "Expected end of text" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + elif loc == len(instring): + return loc+1, [] + elif loc > len(instring): + return loc, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class WordStart(_PositionToken): + """Matches if the current position is at the beginning of a Word, and + is not preceded by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordStart(alphanums). WordStart will also match at the beginning of + the string being parsed, or at the beginning of a line. + """ + def __init__(self, wordChars = printables): + super(WordStart,self).__init__() + self.wordChars = _str2dict(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True ): + if loc != 0: + if (instring[loc-1] in self.wordChars or + instring[loc] not in self.wordChars): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class WordEnd(_PositionToken): + """Matches if the current position is at the end of a Word, and + is not followed by any character in a given set of wordChars + (default=printables). To emulate the \b behavior of regular expressions, + use WordEnd(alphanums). WordEnd will also match at the end of + the string being parsed, or at the end of a line. + """ + def __init__(self, wordChars = printables): + super(WordEnd,self).__init__() + self.wordChars = _str2dict(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True ): + instrlen = len(instring) + if instrlen>0 and loc<instrlen: + if (instring[loc] in self.wordChars or + instring[loc-1] not in self.wordChars): + #~ raise ParseException( instring, loc, "Expected end of word" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + +class ParseExpression(ParserElement): + """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" + def __init__( self, exprs, savelist = False ): + super(ParseExpression,self).__init__(savelist) + if isinstance( exprs, list ): + self.exprs = exprs + elif isinstance( exprs, basestring ): + self.exprs = [ Literal( exprs ) ] + else: + try: + self.exprs = list( exprs ) + except TypeError: + self.exprs = [ exprs ] + self.callPreparse = False + + def __getitem__( self, i ): + return self.exprs[i] + + def append( self, other ): + self.exprs.append( other ) + self.strRepr = None + return self + + def leaveWhitespace( self ): + """Extends leaveWhitespace defined in base class, and also invokes leaveWhitespace on + all contained expressions.""" + self.skipWhitespace = False + self.exprs = [ e.copy() for e in self.exprs ] + for e in self.exprs: + e.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + else: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + return self + + def __str__( self ): + try: + return super(ParseExpression,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) + return self.strRepr + + def streamline( self ): + super(ParseExpression,self).streamline() + + for e in self.exprs: + e.streamline() + + # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) + # but only if there are no parse actions or resultsNames on the nested And's + # (likewise for Or's and MatchFirst's) + if ( len(self.exprs) == 2 ): + other = self.exprs[0] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = other.exprs[:] + [ self.exprs[1] ] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + other = self.exprs[-1] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = self.exprs[:-1] + other.exprs[:] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + return self + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ParseExpression,self).setResultsName(name,listAllMatches) + return ret + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + for e in self.exprs: + e.validate(tmp) + self.checkRecursion( [] ) + +class And(ParseExpression): + """Requires all given ParseExpressions to be found in the given order. + Expressions may be separated by whitespace. + May be constructed using the '+' operator. + """ + + class _ErrorStop(Empty): + def __init__(self, *args, **kwargs): + super(Empty,self).__init__(*args, **kwargs) + self.leaveWhitespace() + + def __init__( self, exprs, savelist = True ): + super(And,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.setWhitespaceChars( exprs[0].whiteChars ) + self.skipWhitespace = exprs[0].skipWhitespace + self.callPreparse = True + + 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 + loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) + errorStop = False + for e in self.exprs[1:]: + if isinstance(e, And._ErrorStop): + errorStop = True + continue + if errorStop: + try: + loc, exprtokens = e._parse( instring, loc, doActions ) + except ParseSyntaxException: + raise + except ParseBaseException: + pe = sys.exc_info()[1] + raise ParseSyntaxException(pe) + except IndexError: + raise ParseSyntaxException( ParseException(instring, len(instring), self.errmsg, self) ) + else: + loc, exprtokens = e._parse( instring, loc, doActions ) + if exprtokens or exprtokens.keys(): + resultlist += exprtokens + return loc, resultlist + + def __iadd__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #And( [ self, other ] ) + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + if not e.mayReturnEmpty: + break + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + +class Or(ParseExpression): + """Requires that at least one ParseExpression is found. + If two expressions match, the expression that matches the longest string will be used. + May be constructed using the '^' operator. + """ + def __init__( self, exprs, savelist = False ): + super(Or,self).__init__(exprs, savelist) + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxMatchLoc = -1 + maxException = None + for e in self.exprs: + try: + loc2 = e.tryParse( instring, loc ) + except ParseException: + err = sys.exc_info()[1] + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + else: + if loc2 > maxMatchLoc: + maxMatchLoc = loc2 + maxMatchExp = e + + if maxMatchLoc < 0: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + return maxMatchExp._parse( instring, loc, doActions ) + + def __ixor__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #Or( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class MatchFirst(ParseExpression): + """Requires that at least one ParseExpression is found. + If two expressions match, the first one listed is the one that will match. + May be constructed using the '|' operator. + """ + def __init__( self, exprs, savelist = False ): + super(MatchFirst,self).__init__(exprs, savelist) + if exprs: + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + for e in self.exprs: + try: + ret = e._parse( instring, loc, doActions ) + return ret + except ParseException as err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + + # only got here if no expression matched, raise exception for match that made it the furthest + else: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + def __ior__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #MatchFirst( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class Each(ParseExpression): + """Requires all given ParseExpressions to be found, but in any order. + Expressions may be separated by whitespace. + May be constructed using the '&' operator. + """ + def __init__( self, exprs, savelist = True ): + super(Each,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.skipWhitespace = True + self.initExprGroups = True + + def parseImpl( self, instring, loc, doActions=True ): + if self.initExprGroups: + self.optionals = [ e.expr for e in self.exprs if isinstance(e,Optional) ] + self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] + self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] + self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.required += self.multirequired + self.initExprGroups = False + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + matchOrder = [] + + keepMatching = True + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired + failed = [] + for e in tmpExprs: + try: + tmpLoc = e.tryParse( instring, tmpLoc ) + except ParseException: + failed.append(e) + else: + matchOrder.append(e) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + if tmpReqd: + missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) + raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + + # add any unmatched Optionals, in case they have default values defined + matchOrder += list(e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt) + + resultlist = [] + for e in matchOrder: + loc,results = e._parse(instring,loc,doActions) + resultlist.append(results) + + finalResults = ParseResults([]) + for r in resultlist: + dups = {} + for k in r.keys(): + if k in finalResults.keys(): + tmp = ParseResults(finalResults[k]) + tmp += ParseResults(r[k]) + dups[k] = tmp + finalResults += ParseResults(r) + for k,v in dups.items(): + finalResults[k] = v + return loc, finalResults + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" + def __init__( self, expr, savelist=False ): + super(ParseElementEnhance,self).__init__(savelist) + if isinstance( expr, basestring ): + expr = Literal(expr) + self.expr = expr + self.strRepr = None + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.setWhitespaceChars( expr.whiteChars ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr is not None: + return self.expr._parse( instring, loc, doActions, callPreParse=False ) + else: + raise ParseException("",loc,self.errmsg,self) + + def leaveWhitespace( self ): + self.skipWhitespace = False + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + else: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + return self + + def streamline( self ): + super(ParseElementEnhance,self).streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def checkRecursion( self, parseElementList ): + if self in parseElementList: + raise RecursiveGrammarException( parseElementList+[self] ) + subRecCheckList = parseElementList[:] + [ self ] + if self.expr is not None: + self.expr.checkRecursion( subRecCheckList ) + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion( [] ) + + def __str__( self ): + try: + return super(ParseElementEnhance,self).__str__() + except: + pass + + if self.strRepr is None and self.expr is not None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + return self.strRepr + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. FollowedBy + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression matches at the current + position. FollowedBy always returns a null token list.""" + def __init__( self, expr ): + super(FollowedBy,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + self.expr.tryParse( instring, loc ) + return loc, [] + + +class NotAny(ParseElementEnhance): + """Lookahead to disallow matching with the given parse expression. NotAny + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression does *not* match at the current + position. Also, NotAny does *not* skip over leading whitespace. NotAny + always returns a null token list. May be constructed using the '~' operator.""" + def __init__( self, expr ): + super(NotAny,self).__init__(expr) + #~ self.leaveWhitespace() + self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + try: + self.expr.tryParse( instring, loc ) + except (ParseException,IndexError): + pass + else: + #~ raise ParseException(instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "~{" + _ustr(self.expr) + "}" + + return self.strRepr + + +class ZeroOrMore(ParseElementEnhance): + """Optional repetition of zero or more of the given expression.""" + def __init__( self, expr ): + super(ZeroOrMore,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + tokens = [] + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + + +class OneOrMore(ParseElementEnhance): + """Repetition of one or more of the given expression.""" + def parseImpl( self, instring, loc, doActions=True ): + # must be at least one + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + try: + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + _ustr(self.expr) + "}..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(OneOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + +class _NullToken(object): + def __bool__(self): + return False + __nonzero__ = __bool__ + def __str__(self): + return "" + +_optionalNotMatched = _NullToken() +class Optional(ParseElementEnhance): + """Optional matching of the given expression. + A default return string can also be specified, if the optional expression + is not found. + """ + def __init__( self, exprs, default=_optionalNotMatched ): + super(Optional,self).__init__( exprs, savelist=False ) + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + except (ParseException,IndexError): + if self.defaultValue is not _optionalNotMatched: + if self.expr.resultsName: + tokens = ParseResults([ self.defaultValue ]) + tokens[self.expr.resultsName] = self.defaultValue + else: + tokens = [ self.defaultValue ] + else: + tokens = [] + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]" + + return self.strRepr + + +class SkipTo(ParseElementEnhance): + """Token for skipping over all undefined text until the matched expression is found. + If include is set to true, the matched expression is also parsed (the skipped text + and matched expression are returned as a 2-element list). The ignore + argument is used to define grammars (typically quoted strings and comments) that + might contain false matches. + """ + def __init__( self, other, include=False, ignore=None, failOn=None ): + super( SkipTo, self ).__init__( other ) + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.asList = False + if failOn is not None and isinstance(failOn, basestring): + self.failOn = Literal(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for "+_ustr(self.expr) + #self.myException = ParseException("",0,self.errmsg,self) + + def parseImpl( self, instring, loc, doActions=True ): + startLoc = loc + instrlen = len(instring) + expr = self.expr + failParse = False + while loc <= instrlen: + try: + if self.failOn: + try: + self.failOn.tryParse(instring, loc) + except ParseBaseException: + pass + else: + failParse = True + raise ParseException(instring, loc, "Found expression " + str(self.failOn)) + failParse = False + if self.ignoreExpr is not None: + while 1: + try: + loc = self.ignoreExpr.tryParse(instring,loc) + # print("found ignoreExpr, advance to", loc) + except ParseBaseException: + break + expr._parse( instring, loc, doActions=False, callPreParse=False ) + skipText = instring[startLoc:loc] + if self.includeMatch: + loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) + if mat: + skipRes = ParseResults( skipText ) + skipRes += mat + return loc, [ skipRes ] + else: + return loc, [ skipText ] + else: + return loc, [ skipText ] + except (ParseException,IndexError): + if failParse: + raise + else: + loc += 1 + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Forward(ParseElementEnhance): + """Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the Forward variable using the '<<' operator. + + Note: take care when assigning to Forward not to overlook precedence of operators. + Specifically, '|' has a lower precedence than '<<', so that:: + fwdExpr << a | b | c + will actually be evaluated as:: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the Forward:: + fwdExpr << (a | b | c) + """ + def __init__( self, other=None ): + super(Forward,self).__init__( other, savelist=False ) + + def __lshift__( self, other ): + if isinstance( other, basestring ): + other = Literal(other) + self.expr = other + self.mayReturnEmpty = other.mayReturnEmpty + self.strRepr = None + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.setWhitespaceChars( self.expr.whiteChars ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + return None + + def leaveWhitespace( self ): + self.skipWhitespace = False + return self + + def streamline( self ): + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate( self, validateTrace=[] ): + if self not in validateTrace: + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion([]) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret << self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +class TokenConverter(ParseElementEnhance): + """Abstract subclass of ParseExpression, for converting parsed results.""" + def __init__( self, expr, savelist=False ): + super(TokenConverter,self).__init__( expr )#, savelist ) + self.saveAsList = False + +class Upcase(TokenConverter): + """Converter to upper case all matching tokens.""" + def __init__(self, *args): + super(Upcase,self).__init__(*args) + warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", + DeprecationWarning,stacklevel=2) + + def postParse( self, instring, loc, tokenlist ): + return list(map( string.upper, tokenlist )) + + +class Combine(TokenConverter): + """Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the input string; + this can be disabled by specifying 'adjacent=False' in the constructor. + """ + def __init__( self, expr, joinString="", adjacent=True ): + super(Combine,self).__init__( expr ) + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leaveWhitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + + def ignore( self, other ): + if self.adjacent: + ParserElement.ignore(self, other) + else: + super( Combine, self).ignore( other ) + return self + + def postParse( self, instring, loc, tokenlist ): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + + if self.resultsName and len(retToks.keys())>0: + return [ retToks ] + else: + return retToks + +class Group(TokenConverter): + """Converter to return the matched tokens as a list - useful for returning tokens of ZeroOrMore and OneOrMore expressions.""" + def __init__( self, expr ): + super(Group,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + return [ tokenlist ] + +class Dict(TokenConverter): + """Converter to return a repetitive expression as a list, but also as a dictionary. + Each element can also be referenced using the first token in the expression as its key. + Useful for tabular report scraping when the first column can be used as a item key. + """ + def __init__( self, exprs ): + super(Dict,self).__init__( exprs ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + for i,tok in enumerate(tokenlist): + if len(tok) == 0: + continue + ikey = tok[0] + if isinstance(ikey,int): + ikey = _ustr(tok[0]).strip() + if len(tok)==1: + tokenlist[ikey] = _ParseResultsWithOffset("",i) + elif len(tok)==2 and not isinstance(tok[1],ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + else: + dictvalue = tok.copy() #ParseResults(i) + del dictvalue[0] + if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + + if self.resultsName: + return [ tokenlist ] + else: + return tokenlist + + +class Suppress(TokenConverter): + """Converter for ignoring the results of a parsed expression.""" + def postParse( self, instring, loc, tokenlist ): + return [] + + def suppress( self ): + return self + + +class OnlyOnce(object): + """Wrapper for parse actions, to ensure they are only called once.""" + def __init__(self, methodCall): + self.callable = ParserElement._normalizeParseActionArgs(methodCall) + self.called = False + def __call__(self,s,l,t): + if not self.called: + results = self.callable(s,l,t) + self.called = True + return results + raise ParseException(s,l,"") + def reset(self): + self.called = False + +def traceParseAction(f): + """Decorator for debugging parse actions.""" + f = ParserElement._normalizeParseActionArgs(f) + def z(*paArgs): + thisFunc = f.func_name + s,l,t = paArgs[-3:] + if len(paArgs)>3: + thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc + sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) + try: + ret = f(*paArgs) + except Exception: + exc = sys.exc_info()[1] + sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) + raise + sys.stderr.write( "<<leaving %s (ret: %s)\n" % (thisFunc,ret) ) + return ret + try: + z.__name__ = f.__name__ + except AttributeError: + pass + return z + +# +# global helpers +# +def delimitedList( expr, delim=",", combine=False ): + """Helper to define a delimited list of expressions - the delimiter defaults to ','. + By default, the list elements and delimiters can have intervening whitespace, and + comments, but this can be overridden by passing 'combine=True' in the constructor. + If combine is set to True, the matching tokens are returned as a single token + string, with the delimiters included; otherwise, the matching tokens are returned + as a list of tokens, with the delimiters suppressed. + """ + dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." + if combine: + return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) + else: + return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) + +def countedArray( expr ): + """Helper to define a counted list of expressions. + This helper defines a pattern of the form:: + integer expr expr expr... + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. + """ + arrayExpr = Forward() + def countFieldParseAction(s,l,t): + n = int(t[0]) + arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) + return [] + return ( Word(nums).setName("arrayLen").setParseAction(countFieldParseAction, callDuringTry=True) + arrayExpr ) + +def _flatten(L): + if type(L) is not list: return [L] + if L == []: return L + return _flatten(L[0]) + _flatten(L[1:]) + +def matchPreviousLiteral(expr): + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousLiteral(first) + matchExpr = first + ":" + second + will match "1:1", but not "1:2". Because this matches a + previous literal, will also match the leading "1:1" in "1:10". + If this is not desired, use matchPreviousExpr. + Do *not* use with packrat parsing enabled. + """ + rep = Forward() + def copyTokenToRepeater(s,l,t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.asList()) + rep << And( [ Literal(tt) for tt in tflat ] ) + else: + rep << Empty() + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + return rep + +def matchPreviousExpr(expr): + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousExpr(first) + matchExpr = first + ":" + second + will match "1:1", but not "1:2". Because this matches by + expressions, will *not* match the leading "1:1" in "1:10"; + the expressions are evaluated first, and then compared, so + "1" is compared with "10". + Do *not* use with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep << e2 + def copyTokenToRepeater(s,l,t): + matchTokens = _flatten(t.asList()) + def mustMatchTheseTokens(s,l,t): + theseTokens = _flatten(t.asList()) + if theseTokens != matchTokens: + raise ParseException("",0,"") + rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + return rep + +def _escapeRegexRangeChars(s): + #~ escape these chars: ^-] + for c in r"\^-]": + s = s.replace(c,_bslash+c) + s = s.replace("\n",r"\n") + s = s.replace("\t",r"\t") + return _ustr(s) + +def oneOf( strs, caseless=False, useRegex=True ): + """Helper to quickly define a set of alternative Literals, and makes sure to do + longest-first testing when there is a conflict, regardless of the input order, + but returns a MatchFirst for best performance. + + Parameters: + - strs - a string of space-delimited literals, or a list of string literals + - caseless - (default=False) - treat all literals as caseless + - useRegex - (default=True) - as an optimization, will generate a Regex + object; otherwise, will generate a MatchFirst object (if caseless=True, or + if creating a Regex raises an exception) + """ + if caseless: + isequal = ( lambda a,b: a.upper() == b.upper() ) + masks = ( lambda a,b: b.upper().startswith(a.upper()) ) + parseElementClass = CaselessLiteral + else: + isequal = ( lambda a,b: a == b ) + masks = ( lambda a,b: b.startswith(a) ) + parseElementClass = Literal + + if isinstance(strs,(list,tuple)): + symbols = list(strs[:]) + elif isinstance(strs,basestring): + symbols = strs.split() + else: + warnings.warn("Invalid argument to oneOf, expected string or list", + SyntaxWarning, stacklevel=2) + + i = 0 + while i < len(symbols)-1: + cur = symbols[i] + for j,other in enumerate(symbols[i+1:]): + if ( isequal(other, cur) ): + del symbols[i+j+1] + break + elif ( masks(cur, other) ): + del symbols[i+j+1] + symbols.insert(i,other) + cur = other + break + else: + i += 1 + + if not caseless and useRegex: + #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) + try: + if len(symbols)==len("".join(symbols)): + return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) + else: + return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) + except: + warnings.warn("Exception creating Regex for oneOf, building MatchFirst", + SyntaxWarning, stacklevel=2) + + + # last resort, just use MatchFirst + return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) + +def dictOf( key, value ): + """Helper to easily and clearly define a dictionary by specifying the respective patterns + for the key and value. Takes care of defining the Dict, ZeroOrMore, and Group tokens + in the proper order. The key pattern can include delimiting markers or punctuation, + as long as they are suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the Dict results can include named token + fields. + """ + return Dict( ZeroOrMore( Group ( key + value ) ) ) + +def originalTextFor(expr, asString=True): + """Helper to return the original, untokenized text for a given expression. Useful to + restore the parsed fields of an HTML start tag into the raw tag text itself, or to + revert separate tokens with intervening whitespace back to the original matching + input text. Simpler to use than the parse action keepOriginalText, and does not + require the inspect module to chase up the call stack. By default, returns a + string containing the original parsed text. + + If the optional asString argument is passed as False, then the return value is a + ParseResults containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if + the expression passed to originalTextFor contains expressions with defined + results names, you must set asString to False if you want to preserve those + results name values.""" + locMarker = Empty().setParseAction(lambda s,loc,t: loc) + matchExpr = locMarker("_original_start") + expr + locMarker("_original_end") + if asString: + extractText = lambda s,l,t: s[t._original_start:t._original_end] + else: + def extractText(s,l,t): + del t[:] + t.insert(0, s[t._original_start:t._original_end]) + del t["_original_start"] + del t["_original_end"] + matchExpr.setParseAction(extractText) + return matchExpr + +# convenience constants for positional expressions +empty = Empty().setName("empty") +lineStart = LineStart().setName("lineStart") +lineEnd = LineEnd().setName("lineEnd") +stringStart = StringStart().setName("stringStart") +stringEnd = StringEnd().setName("stringEnd") + +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) +_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) +_escapedHexChar = Combine( Suppress(_bslash + "0x") + Word(hexnums) ).setParseAction(lambda s,l,t:unichr(int(t[0],16))) +_escapedOctChar = Combine( Suppress(_bslash) + Word("0","01234567") ).setParseAction(lambda s,l,t:unichr(int(t[0],8))) +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) +_charRange = Group(_singleChar + Suppress("-") + _singleChar) +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" + +_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) + +def srange(s): + r"""Helper to easily define string ranges for use in Word construction. Borrows + syntax from regexp '[]' string range definitions:: + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + The input string must be enclosed in []'s, and the returned string is the expanded + character set joined into a single string. + The values enclosed in the []'s may be:: + a single character + an escaped character with a leading backslash (such as \- or \]) + an escaped hex character with a leading '\0x' (\0x21, which is a '!' character) + an escaped octal character with a leading '\0' (\041, which is a '!' character) + a range of any of the above, separated by a dash ('a-z', etc.) + any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) + """ + try: + return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) + except: + return "" + +def matchOnlyAtCol(n): + """Helper method for defining parse actions that require matching at a specific + column in the input text. + """ + def verifyCol(strg,locn,toks): + if col(locn,strg) != n: + raise ParseException(strg,locn,"matched token not at column %d" % n) + return verifyCol + +def replaceWith(replStr): + """Helper method for common parse actions that simply return a literal value. Especially + useful when used with transformString(). + """ + def _replFunc(*args): + return [replStr] + return _replFunc + +def removeQuotes(s,l,t): + """Helper parse action for removing quotation marks from parsed quoted strings. + To use, add this parse action to quoted string using:: + quotedString.setParseAction( removeQuotes ) + """ + return t[0][1:-1] + +def upcaseTokens(s,l,t): + """Helper parse action to convert tokens to upper case.""" + return [ tt.upper() for tt in map(_ustr,t) ] + +def downcaseTokens(s,l,t): + """Helper parse action to convert tokens to lower case.""" + return [ tt.lower() for tt in map(_ustr,t) ] + +def keepOriginalText(s,startLoc,t): + """Helper parse action to preserve original parsed text, + overriding any nested parse actions.""" + try: + endloc = getTokensEndLoc() + except ParseException: + raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") + del t[:] + t += ParseResults(s[startLoc:endloc]) + return t + +def getTokensEndLoc(): + """Method to be called from within a parse action to determine the end + location of the parsed tokens.""" + import inspect + fstack = inspect.stack() + try: + # search up the stack (through intervening argument normalizers) for correct calling routine + for f in fstack[2:]: + if f[3] == "_parseNoCache": + endloc = f[0].f_locals["loc"] + return endloc + else: + raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") + finally: + del fstack + +def _makeTags(tagStr, xml): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr,basestring): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas,alphanums+"_-:") + if (xml): + tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("</") + tagStr + ">") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % tagStr) + + return openTag, closeTag + +def makeHTMLTags(tagStr): + """Helper to construct opening and closing tag expressions for HTML, given a tag name""" + return _makeTags( tagStr, False ) + +def makeXMLTags(tagStr): + """Helper to construct opening and closing tag expressions for XML, given a tag name""" + return _makeTags( tagStr, True ) + +def withAttribute(*args,**attrDict): + """Helper to create a validating parse action to be used with start tags created + with makeXMLTags or makeHTMLTags. Use withAttribute to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + <TD> or <DIV>. + + Call withAttribute with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in (class="Customer",align="right"), or + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + To verify that the attribute exists, but without specifying a value, pass + withAttribute.ANY_VALUE as the value. + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def operatorPrecedence( baseExpr, opList ): + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants opAssoc.RIGHT and opAssoc.LEFT. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted) + """ + ret = Forward() + lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward()#.setName("expr%d" % i) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + matchExpr.setParseAction( pa ) + thisExpr << ( matchExpr | lastExpr ) + lastExpr = thisExpr + ret << lastExpr + return ret + +dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") +sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") +quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()) + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString): + """Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default="("); can also be a pyparsing expression + - closer - closing character for a nested list (default=")"); can also be a pyparsing expression + - content - expression for items within the nested lists (default=None) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the ignoreExpr argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an Or or MatchFirst. + The default is quotedString, but if no expressions are to be ignored, + then pass None for this argument. + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=True) + + A valid block must contain at least one blockStatement. + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = Empty() + Empty().setParseAction(checkSubIndent) + PEER = Empty().setParseAction(checkPeerIndent) + UNDENT = Empty().setParseAction(checkUnindent) + if indent: + smExpr = Group( Optional(NL) + + FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) +commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() +_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) +replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") + +htmlComment = Regex(r"<!--[\s\S]*?-->") +restOfLine = Regex(r".*").leaveWhitespace() +dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") +cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?<!\\)|\Z))").setName("C++ style comment") + +javaStyleComment = cppStyleComment +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +_noncomma = "".join( [ c for c in printables if c != "," ] ) +_commasepitem = Combine(OneOrMore(Word(_noncomma) + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString | _commasepitem, default="") ).setName("commaSeparatedList") + + +if __name__ == "__main__": + + def test( teststring ): + try: + tokens = simpleSQL.parseString( teststring ) + tokenlist = tokens.asList() + print (teststring + "->" + str(tokenlist)) + print ("tokens = " + str(tokens)) + print ("tokens.columns = " + str(tokens.columns)) + print ("tokens.tables = " + str(tokens.tables)) + print (tokens.asXML("SQL",True)) + except ParseBaseException: + err = sys.exc_info()[1] + print (teststring + "->") + print (err.line) + print (" "*(err.column-1) + "^") + print (err) + print() + + selectToken = CaselessLiteral( "select" ) + fromToken = CaselessLiteral( "from" ) + + ident = Word( alphas, alphanums + "_$" ) + columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + columnNameList = Group( delimitedList( columnName ) )#.setName("columns") + tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + tableNameList = Group( delimitedList( tableName ) )#.setName("tables") + simpleSQL = ( selectToken + \ + ( '*' | columnNameList ).setResultsName( "columns" ) + \ + fromToken + \ + tableNameList.setResultsName( "tables" ) ) + + test( "SELECT * from XYZZY, ABC" ) + test( "select * from SYS.XYZZY" ) + test( "Select A from Sys.dual" ) + test( "Select AA,BB,CC from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Xelect A, B, C from Sys.dual" ) + test( "Select A, B, C frox Sys.dual" ) + test( "Select" ) + test( "Select ^^^ frox Sys.dual" ) + test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_c2V0dXAucHk= --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +"""Setup script for the pyparsing module distribution.""" +from distutils.core import setup + +from pyparsing import __version__ + +setup(# Distribution meta-data + name = "pyparsing", + version = __version__, + description = "Python parsing module", + author = "Paul McGuire", + author_email = "ptmcg@users.sourceforge.net", + url = "http://pyparsing.wikispaces.com/", + download_url = "http://sourceforge.net/project/showfiles.php?group_id=97203", + license = "MIT License", + py_modules = ["pyparsing"], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + ] + ) diff --git a/src/CHANGES b/src/CHANGES deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL0NIQU5HRVM=..0000000000000000000000000000000000000000 --- a/src/CHANGES +++ /dev/null @@ -1,1668 +0,0 @@ -========== -Change Log -========== - -Version 1.5.7 - ------------------ -- An awesome new example is included in this release, submitted - by Luca DellOlio, for parsing ANTLR grammar definitions, nice - work Luca! - -- Fixed implementation of ParseResults.__str__ to use Pythonic - ''.join() instead of repeated string concatenation. This - purportedly has been a performance issue under PyPy. - -- Fixed bug in ParseResults.__dir__ under Python 3, reported by - Thomas Kluyver, thank you Thomas! - -- Added ParserElement.inlineLiteralsUsing static method, to - override pyparsing's default behavior of converting string - literals to Literal instances, to use other classes (such - as Suppress or CaselessLiteral). - -- Added new operator '<<=', which will eventually replace '<<' for - storing the contents of a Forward(). '<<=' does not have the same - operator precedence problems that '<<' does. - -- Added support for using single argument builtin functions as parse - actions. Now you can write 'expr.setParseAction(len)' and get back - the length of the list of matched tokens. Supported builtins are: - sum, len, sorted, reversed, list, tuple, set, any, all, min, and max. - -- Improved linking in generated docs, proposed on the pyparsing wiki - by techtonik, thanks! - -- Fixed a bug in the definition of 'alphas', which was based on the - string.uppercase and string.lowercase "constants", which in fact - *aren't* constant, but vary with locale settings. This could make - parsers locale-sensitive in a subtle way. Thanks to Kef Schecter for - his diligence in following through on reporting and monitoring - this bugfix! - -- Fixed a bug in the Py3 version of pyparsing, during exception - handling with packrat parsing enabled, reported by Catherine - Devlin - thanks Catherine! - -- Fixed typo in ParseBaseException.__dir__, reported anonymously on - the SourceForge bug tracker, thank you Pyparsing User With No Name. - -- Fixed bug in srange when using '\x###' hex character codes. - -- Addeed optional 'intExpr' argument to countedArray, so that you - can define your own expression that will evaluate to an integer, - to be used as the count for the following elements. Allows you - to define a countedArray with the count given in hex, for example, - by defining intExpr as "Word(hexnums).setParseAction(int(t[0],16))". - - -Version 1.5.6 - June, 2011 ----------------------------- -- Cleanup of parse action normalizing code, to be more version-tolerant, - and robust in the face of future Python versions - much thanks to - Raymond Hettinger for this rewrite! - -- Removal of exception cacheing, addressing a memory leak condition - in Python 3. Thanks to Michael Droettboom and the Cape Town PUG for - their analysis and work on this problem! - -- Fixed bug when using packrat parsing, where a previously parsed - expression would duplicate subsequent tokens - reported by Frankie - Ribery on stackoverflow, thanks! - -- Added 'ungroup' helper method, to address token grouping done - implicitly by And expressions, even if only one expression in the - And actually returns any text - also inspired by stackoverflow - discussion with Frankie Ribery! - -- Fixed bug in srange, which accepted escaped hex characters of the - form '\0x##', but should be '\x##'. Both forms will be supported - for backwards compatibility. - -- Enhancement to countedArray, accepting an optional expression to be - used for matching the leading integer count - proposed by Mathias on - the pyparsing mailing list, good idea! - -- Added the Verilog parser to the provided set of examples, under the - MIT license. While this frees up this parser for any use, if you find - yourself using it in a commercial purpose, please consider making a - charitable donation as described in the parser's header. - -- Added the excludeChars argument to the Word class, to simplify defining - a word composed of all characters in a large range except for one or - two. Suggested by JesterEE on the pyparsing wiki. - -- Added optional overlap parameter to scanString, to return overlapping - matches found in the source text. - -- Updated oneOf internal regular expression generation, with improved - parse time performance. - -- Slight performance improvement in transformString, removing empty - strings from the list of string fragments built while scanning the - source text, before calling ''.join. Especially useful when using - transformString to strip out selected text. - -- Enhanced form of using the "expr('name')" style of results naming, - in lieu of calling setResultsName. If name ends with an '*', then - this is equivalent to expr.setResultsName('name',listAllMatches=True). - -- Fixed up internal list flattener to use iteration instead of recursion, - to avoid stack overflow when transforming large files. - -- Added other new examples: - . protobuf parser - parses Google's protobuf language - . btpyparse - a BibTex parser contributed by Matthew Brett, - with test suite test_bibparse.py (thanks, Matthew!) - . groupUsingListAllMatches.py - demo using trailing '*' for results - names - - -Version 1.5.5 - August, 2010 ----------------------------- - -- Typo in Python3 version of pyparsing, "builtin" should be "builtins". - (sigh) - - -Version 1.5.4 - August, 2010 ----------------------------- - -- Fixed __builtins__ and file references in Python 3 code, thanks to - Greg Watson, saulspatz, sminos, and Mark Summerfield for reporting - their Python 3 experiences. - -- Added new example, apicheck.py, as a sample of scanning a Tcl-like - language for functions with incorrect number of arguments (difficult - to track down in Tcl languages). This example uses some interesting - methods for capturing exceptions while scanning through source - code. - -- Added new example deltaTime.py, that takes everyday time references - like "an hour from now", "2 days ago", "next Sunday at 2pm". - - -Version 1.5.3 - June, 2010 --------------------------- - -- ======= NOTE: API CHANGE!!!!!!! =============== - With this release, and henceforward, the pyparsing module is - imported as "pyparsing" on both Python 2.x and Python 3.x versions. - -- Fixed up setup.py to auto-detect Python version and install the - correct version of pyparsing - suggested by Alex Martelli, - thanks, Alex! (and my apologies to all those who struggled with - those spurious installation errors caused by my earlier - fumblings!) - -- Fixed bug on Python3 when using parseFile, getting bytes instead of - a str from the input file. - -- Fixed subtle bug in originalTextFor, if followed by - significant whitespace (like a newline) - discovered by - Francis Vidal, thanks! - -- Fixed very sneaky bug in Each, in which Optional elements were - not completely recognized as optional - found by Tal Weiss, thanks - for your patience. - -- Fixed off-by-1 bug in line() method when the first line of the - input text was an empty line. Thanks to John Krukoff for submitting - a patch! - -- Fixed bug in transformString if grammar contains Group expressions, - thanks to patch submitted by barnabas79, nice work! - -- Fixed bug in originalTextFor in which trailing comments or otherwised - ignored text got slurped in with the matched expression. Thanks to - michael_ramirez44 on the pyparsing wiki for reporting this just in - time to get into this release! - -- Added better support for summing ParseResults, see the new example, - parseResultsSumExample.py. - -- Added support for composing a Regex using a compiled RE object; - thanks to my new colleague, Mike Thornton! - -- In version 1.5.2, I changed the way exceptions are raised in order - to simplify the stacktraces reported during parsing. An anonymous - user posted a bug report on SF that this behavior makes it difficult - to debug some complex parsers, or parsers nested within parsers. In - this release I've added a class attribute ParserElement.verbose_stacktrace, - with a default value of False. If you set this to True, pyparsing will - report stacktraces using the pre-1.5.2 behavior. - -- New examples: - - . pymicko.py, a MicroC compiler submitted by Zarko Zivanov. - (Note: this example is separately licensed under the GPLv3, - and requires Python 2.6 or higher.) Thank you, Zarko! - - . oc.py, a subset C parser, using the BNF from the 1996 Obfuscated C - Contest. - - . stateMachine2.py, a modified version of stateMachine.py submitted - by Matt Anderson, that is compatible with Python versions 2.7 and - above - thanks so much, Matt! - - . select_parser.py, a parser for reading SQLite SELECT statements, - as specified at http://www.sqlite.org/lang_select.html; this goes - into much more detail than the simple SQL parser included in pyparsing's - source code - - . excelExpr.py, a *simplistic* first-cut at a parser for Excel - expressions, which I originally posted on comp.lang.python in January, - 2010; beware, this parser omits many common Excel cases (addition of - numbers represented as strings, references to named ranges) - - . cpp_enum_parser.py, a nice little parser posted my Mark Tolonen on - comp.lang.python in August, 2009 (redistributed here with Mark's - permission). Thanks a bunch, Mark! - - . partial_gene_match.py, a sample I posted to Stackoverflow.com, - implementing a special variation on Literal that does "close" matching, - up to a given number of allowed mismatches. The application was to - find matching gene sequences, with allowance for one or two mismatches. - - . tagCapture.py, a sample showing how to use a Forward placeholder to - enforce matching of text parsed in a previous expression. - - . matchPreviousDemo.py, simple demo showing how the matchPreviousLiteral - helper method is used to match a previously parsed token. - - -Version 1.5.2 - April, 2009 ------------------------------- -- Added pyparsing_py3.py module, so that Python 3 users can use - pyparsing by changing their pyparsing import statement to: - - import pyparsing_py3 - - Thanks for help from Patrick Laban and his friend Geremy - Condra on the pyparsing wiki. - -- Removed __slots__ declaration on ParseBaseException, for - compatibility with IronPython 2.0.1. Raised by David - Lawler on the pyparsing wiki, thanks David! - -- Fixed bug in SkipTo/failOn handling - caught by eagle eye - cpennington on the pyparsing wiki! - -- Fixed second bug in SkipTo when using the ignore constructor - argument, reported by Catherine Devlin, thanks! - -- Fixed obscure bug reported by Eike Welk when using a class - as a ParseAction with an errant __getitem__ method. - -- Simplified exception stack traces when reporting parse - exceptions back to caller of parseString or parseFile - thanks - to a tip from Peter Otten on comp.lang.python. - -- Changed behavior of scanString to avoid infinitely looping on - expressions that match zero-length strings. Prompted by a - question posted by ellisonbg on the wiki. - -- Enhanced classes that take a list of expressions (And, Or, - MatchFirst, and Each) to accept generator expressions also. - This can be useful when generating lists of alternative - expressions, as in this case, where the user wanted to match - any repetitions of '+', '*', '#', or '.', but not mixtures - of them (that is, match '+++', but not '+-+'): - - codes = "+*#." - format = MatchFirst(Word(c) for c in codes) - - Based on a problem posed by Denis Spir on the Python tutor - list. - -- Added new example eval_arith.py, which extends the example - simpleArith.py to actually evaluate the parsed expressions. - - -Version 1.5.1 - October, 2008 -------------------------------- -- Added new helper method originalTextFor, to replace the use of - the current keepOriginalText parse action. Now instead of - using the parse action, as in: - - fullName = Word(alphas) + Word(alphas) - fullName.setParseAction(keepOriginalText) - - (in this example, we used keepOriginalText to restore any white - space that may have been skipped between the first and last - names) - You can now write: - - fullName = originalTextFor(Word(alphas) + Word(alphas)) - - The implementation of originalTextFor is simpler and faster than - keepOriginalText, and does not depend on using the inspect or - imp modules. - -- Added optional parseAll argument to parseFile, to be consistent - with parseAll argument to parseString. Posted by pboucher on the - pyparsing wiki, thanks! - -- Added failOn argument to SkipTo, so that grammars can define - literal strings or pyparsing expressions which, if found in the - skipped text, will cause SkipTo to fail. Useful to prevent - SkipTo from reading past terminating expression. Instigated by - question posed by Aki Niimura on the pyparsing wiki. - -- Fixed bug in nestedExpr if multi-character expressions are given - for nesting delimiters. Patch provided by new pyparsing user, - Hans-Martin Gaudecker - thanks, H-M! - -- Removed dependency on xml.sax.saxutils.escape, and included - internal implementation instead - proposed by Mike Droettboom on - the pyparsing mailing list, thanks Mike! Also fixed erroneous - mapping in replaceHTMLEntity of " to ', now correctly maps - to ". (Also added support for mapping ' to '.) - -- Fixed typo in ParseResults.insert, found by Alejandro Dubrovsky, - good catch! - -- Added __dir__() methods to ParseBaseException and ParseResults, - to support new dir() behavior in Py2.6 and Py3.0. If dir() is - called on a ParseResults object, the returned list will include - the base set of attribute names, plus any results names that are - defined. - -- Fixed bug in ParseResults.asXML(), in which the first named - item within a ParseResults gets reported with an <ITEM> tag - instead of with the correct results name. - -- Fixed bug in '-' error stop, when '-' operator is used inside a - Combine expression. - -- Reverted generator expression to use list comprehension, for - better compatibility with old versions of Python. Reported by - jester/artixdesign on the SourceForge pyparsing discussion list. - -- Fixed bug in parseString(parseAll=True), when the input string - ends with a comment or whitespace. - -- Fixed bug in LineStart and LineEnd that did not recognize any - special whitespace chars defined using ParserElement.setDefault- - WhitespaceChars, found while debugging an issue for Marek Kubica, - thanks for the new test case, Marek! - -- Made Forward class more tolerant of subclassing. - - -Version 1.5.0 - June, 2008 --------------------------- -This version of pyparsing includes work on two long-standing -FAQ's: support for forcing parsing of the complete input string -(without having to explicitly append StringEnd() to the grammar), -and a method to improve the mechanism of detecting where syntax -errors occur in an input string with various optional and -alternative paths. This release also includes a helper method -to simplify definition of indentation-based grammars. With -these changes (and the past few minor updates), I thought it was -finally time to bump the minor rev number on pyparsing - so -1.5.0 is now available! Read on... - -- AT LAST!!! You can now call parseString and have it raise - an exception if the expression does not parse the entire - input string. This has been an FAQ for a LONG time. - - The parseString method now includes an optional parseAll - argument (default=False). If parseAll is set to True, then - the given parse expression must parse the entire input - string. (This is equivalent to adding StringEnd() to the - end of the expression.) The default value is False to - retain backward compatibility. - - Inspired by MANY requests over the years, most recently by - ecir-hana on the pyparsing wiki! - -- Added new operator '-' for composing grammar sequences. '-' - behaves just like '+' in creating And expressions, but '-' - is used to mark grammar structures that should stop parsing - immediately and report a syntax error, rather than just - backtracking to the last successful parse and trying another - alternative. For instance, running the following code: - - port_definition = Keyword("port") + '=' + Word(nums) - entity_definition = Keyword("entity") + "{" + - Optional(port_definition) + "}" - - entity_definition.parseString("entity { port 100 }") - - pyparsing fails to detect the missing '=' in the port definition. - But, since this expression is optional, pyparsing then proceeds - to try to match the closing '}' of the entity_definition. Not - finding it, pyparsing reports that there was no '}' after the '{' - character. Instead, we would like pyparsing to parse the 'port' - keyword, and if not followed by an equals sign and an integer, - to signal this as a syntax error. - - This can now be done simply by changing the port_definition to: - - port_definition = Keyword("port") - '=' + Word(nums) - - Now after successfully parsing 'port', pyparsing must also find - an equals sign and an integer, or it will raise a fatal syntax - exception. - - By judicious insertion of '-' operators, a pyparsing developer - can have their grammar report much more informative syntax error - messages. - - Patches and suggestions proposed by several contributors on - the pyparsing mailing list and wiki - special thanks to - Eike Welk and Thomas/Poldy on the pyparsing wiki! - -- Added indentedBlock helper method, to encapsulate the parse - actions and indentation stack management needed to keep track of - indentation levels. Use indentedBlock to define grammars for - indentation-based grouping grammars, like Python's. - - indentedBlock takes up to 3 parameters: - - blockStatementExpr - expression defining syntax of statement - that is repeated within the indented block - - indentStack - list created by caller to manage indentation - stack (multiple indentedBlock expressions - within a single grammar should share a common indentStack) - - indent - boolean indicating whether block must be indented - beyond the the current level; set to False for block of - left-most statements (default=True) - - A valid block must contain at least one indented statement. - -- Fixed bug in nestedExpr in which ignored expressions needed - to be set off with whitespace. Reported by Stefaan Himpe, - nice catch! - -- Expanded multiplication of an expression by a tuple, to - accept tuple values of None: - . expr*(n,None) or expr*(n,) is equivalent - to expr*n + ZeroOrMore(expr) - (read as "at least n instances of expr") - . expr*(None,n) is equivalent to expr*(0,n) - (read as "0 to n instances of expr") - . expr*(None,None) is equivalent to ZeroOrMore(expr) - . expr*(1,None) is equivalent to OneOrMore(expr) - - Note that expr*(None,n) does not raise an exception if - more than n exprs exist in the input stream; that is, - expr*(None,n) does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - expr*(None,n) + ~expr - -- Added None as a possible operator for operatorPrecedence. - None signifies "no operator", as in multiplying m times x - in "y=mx+b". - -- Fixed bug in Each, reported by Michael Ramirez, in which the - order of terms in the Each affected the parsing of the results. - Problem was due to premature grouping of the expressions in - the overall Each during grammar construction, before the - complete Each was defined. Thanks, Michael! - -- Also fixed bug in Each in which Optional's with default values - were not getting the defaults added to the results of the - overall Each expression. - -- Fixed a bug in Optional in which results names were not - assigned if a default value was supplied. - -- Cleaned up Py3K compatibility statements, including exception - construction statements, and better equivalence between _ustr - and basestring, and __nonzero__ and __bool__. - - -Version 1.4.11 - February, 2008 -------------------------------- -- With help from Robert A. Clark, this version of pyparsing - is compatible with Python 3.0a3. Thanks for the help, - Robert! - -- Added WordStart and WordEnd positional classes, to support - expressions that must occur at the start or end of a word. - Proposed by piranha on the pyparsing wiki, good idea! - -- Added matchOnlyAtCol helper parser action, to simplify - parsing log or data files that have optional fields that are - column dependent. Inspired by a discussion thread with - hubritic on comp.lang.python. - -- Added withAttribute.ANY_VALUE as a match-all value when using - withAttribute. Used to ensure that an attribute is present, - without having to match on the actual attribute value. - -- Added get() method to ParseResults, similar to dict.get(). - Suggested by new pyparsing user, Alejandro Dubrovksy, thanks! - -- Added '==' short-cut to see if a given string matches a - pyparsing expression. For instance, you can now write: - - integer = Word(nums) - if "123" == integer: - # do something - - print [ x for x in "123 234 asld".split() if x==integer ] - # prints ['123', '234'] - -- Simplified the use of nestedExpr when using an expression for - the opening or closing delimiters. Now the content expression - will not have to explicitly negate closing delimiters. Found - while working with dfinnie on GHOP Task #277, thanks! - -- Fixed bug when defining ignorable expressions that are - later enclosed in a wrapper expression (such as ZeroOrMore, - OneOrMore, etc.) - found while working with Prabhu - Gurumurthy, thanks Prahbu! - -- Fixed bug in withAttribute in which keys were automatically - converted to lowercase, making it impossible to match XML - attributes with uppercase characters in them. Using with- - Attribute requires that you reference attributes in all - lowercase if parsing HTML, and in correct case when parsing - XML. - -- Changed '<<' operator on Forward to return None, since this - is really used as a pseudo-assignment operator, not as a - left-shift operator. By returning None, it is easier to - catch faulty statements such as a << b | c, where precedence - of operations causes the '|' operation to be performed - *after* inserting b into a, so no alternation is actually - implemented. The correct form is a << (b | c). With this - change, an error will be reported instead of silently - clipping the alternative term. (Note: this may break some - existing code, but if it does, the code had a silent bug in - it anyway.) Proposed by wcbarksdale on the pyparsing wiki, - thanks! - -- Several unit tests were added to pyparsing's regression - suite, courtesy of the Google Highly-Open Participation - Contest. Thanks to all who administered and took part in - this event! - - -Version 1.4.10 - December 9, 2007 ---------------------------------- -- Fixed bug introduced in v1.4.8, parse actions were called for - intermediate operator levels, not just the deepest matching - operation level. Again, big thanks to Torsten Marek for - helping isolate this problem! - - -Version 1.4.9 - December 8, 2007 --------------------------------- -- Added '*' multiplication operator support when creating - grammars, accepting either an integer, or a two-integer - tuple multiplier, as in: - ipAddress = Word(nums) + ('.'+Word(nums))*3 - usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) - If multiplying by a tuple, the two integer values represent - min and max multiples. Suggested by Vincent of eToy.com, - great idea, Vincent! - -- Fixed bug in nestedExpr, original version was overly greedy! - Thanks to Michael Ramirez for raising this issue. - -- Fixed internal bug in ParseResults - when an item was deleted, - the key indices were not updated. Thanks to Tim Mitchell for - posting a bugfix patch to the SF bug tracking system! - -- Fixed internal bug in operatorPrecedence - when the results of - a right-associative term were sent to a parse action, the wrong - tokens were sent. Reported by Torsten Marek, nice job! - -- Added pop() method to ParseResults. If pop is called with an - integer or with no arguments, it will use list semantics and - update the ParseResults' list of tokens. If pop is called with - a non-integer (a string, for instance), then it will use dict - semantics and update the ParseResults' internal dict. - Suggested by Donn Ingle, thanks Donn! - -- Fixed quoted string built-ins to accept '\xHH' hex characters - within the string. - - -Version 1.4.8 - October, 2007 ------------------------------ -- Added new helper method nestedExpr to easily create expressions - that parse lists of data in nested parentheses, braces, brackets, - etc. - -- Added withAttribute parse action helper, to simplify creating - filtering parse actions to attach to expressions returned by - makeHTMLTags and makeXMLTags. Use withAttribute to qualify a - starting tag with one or more required attribute values, to avoid - false matches on common tags such as <TD> or <DIV>. - -- Added new examples nested.py and withAttribute.py to demonstrate - the new features. - -- Added performance speedup to grammars using operatorPrecedence, - instigated by Stefan Reich�r - thanks for the feedback, Stefan! - -- Fixed bug/typo when deleting an element from a ParseResults by - using the element's results name. - -- Fixed whitespace-skipping bug in wrapper classes (such as Group, - Suppress, Combine, etc.) and when using setDebug(), reported by - new pyparsing user dazzawazza on SourceForge, nice job! - -- Added restriction to prevent defining Word or CharsNotIn expressions - with minimum length of 0 (should use Optional if this is desired), - and enhanced docstrings to reflect this limitation. Issue was - raised by Joey Tallieu, who submitted a patch with a slightly - different solution. Thanks for taking the initiative, Joey, and - please keep submitting your ideas! - -- Fixed bug in makeHTMLTags that did not detect HTML tag attributes - with no '= value' portion (such as "<td nowrap>"), reported by - hamidh on the pyparsing wiki - thanks! - -- Fixed minor bug in makeHTMLTags and makeXMLTags, which did not - accept whitespace in closing tags. - - -Version 1.4.7 - July, 2007 --------------------------- -- NEW NOTATION SHORTCUT: ParserElement now accepts results names using - a notational shortcut, following the expression with the results name - in parentheses. So this: - - stats = "AVE:" + realNum.setResultsName("average") + \ - "MIN:" + realNum.setResultsName("min") + \ - "MAX:" + realNum.setResultsName("max") - - can now be written as this: - - stats = "AVE:" + realNum("average") + \ - "MIN:" + realNum("min") + \ - "MAX:" + realNum("max") - - The intent behind this change is to make it simpler to define results - names for significant fields within the expression, while keeping - the grammar syntax clean and uncluttered. - -- Fixed bug when packrat parsing is enabled, with cached ParseResults - being updated by subsequent parsing. Reported on the pyparsing - wiki by Kambiz, thanks! - -- Fixed bug in operatorPrecedence for unary operators with left - associativity, if multiple operators were given for the same term. - -- Fixed bug in example simpleBool.py, corrected precedence of "and" vs. - "or" operations. - -- Fixed bug in Dict class, in which keys were converted to strings - whether they needed to be or not. Have narrowed this logic to - convert keys to strings only if the keys are ints (which would - confuse __getitem__ behavior for list indexing vs. key lookup). - -- Added ParserElement method setBreak(), which will invoke the pdb - module's set_trace() function when this expression is about to be - parsed. - -- Fixed bug in StringEnd in which reading off the end of the input - string raises an exception - should match. Resolved while - answering a question for Shawn on the pyparsing wiki. - - -Version 1.4.6 - April, 2007 ---------------------------- -- Simplified constructor for ParseFatalException, to support common - exception construction idiom: - raise ParseFatalException, "unexpected text: 'Spanish Inquisition'" - -- Added method getTokensEndLoc(), to be called from within a parse action, - for those parse actions that need both the starting *and* ending - location of the parsed tokens within the input text. - -- Enhanced behavior of keepOriginalText so that named parse fields are - preserved, even though tokens are replaced with the original input - text matched by the current expression. Also, cleaned up the stack - traversal to be more robust. Suggested by Tim Arnold - thanks, Tim! - -- Fixed subtle bug in which countedArray (and similar dynamic - expressions configured in parse actions) failed to match within Or, - Each, FollowedBy, or NotAny. Reported by Ralf Vosseler, thanks for - your patience, Ralf! - -- Fixed Unicode bug in upcaseTokens and downcaseTokens parse actions, - scanString, and default debugging actions; reported (and patch submitted) - by Nikolai Zamkovoi, spasibo! - -- Fixed bug when saving a tuple as a named result. The returned - token list gave the proper tuple value, but accessing the result by - name only gave the first element of the tuple. Reported by - Poromenos, nice catch! - -- Fixed bug in makeHTMLTags/makeXMLTags, which failed to match tag - attributes with namespaces. - -- Fixed bug in SkipTo when setting include=True, to have the skipped-to - tokens correctly included in the returned data. Reported by gunars on - the pyparsing wiki, thanks! - -- Fixed typobug in OnceOnly.reset method, omitted self argument. - Submitted by eike welk, thanks for the lint-picking! - -- Added performance enhancement to Forward class, suggested by - akkartik on the pyparsing Wiki discussion, nice work! - -- Added optional asKeyword to Word constructor, to indicate that the - given word pattern should be matched only as a keyword, that is, it - should only match if it is within word boundaries. - -- Added S-expression parser to examples directory. - -- Added macro substitution example to examples directory. - -- Added holaMundo.py example, excerpted from Marco Alfonso's blog - - muchas gracias, Marco! - -- Modified internal cyclic references in ParseResults to use weakrefs; - this should help reduce the memory footprint of large parsing - programs, at some cost to performance (3-5%). Suggested by bca48150 on - the pyparsing wiki, thanks! - -- Enhanced the documentation describing the vagaries and idiosyncracies - of parsing strings with embedded tabs, and the impact on: - . parse actions - . scanString - . col and line helper functions - (Suggested by eike welk in response to some unexplained inconsistencies - between parsed location and offsets in the input string.) - -- Cleaned up internal decorators to preserve function names, - docstrings, etc. - - -Version 1.4.5 - December, 2006 ------------------------------- -- Removed debugging print statement from QuotedString class. Sorry - for not stripping this out before the 1.4.4 release! - -- A significant performance improvement, the first one in a while! - For my Verilog parser, this version of pyparsing is about double the - speed - YMMV. - -- Added support for pickling of ParseResults objects. (Reported by - Jeff Poole, thanks Jeff!) - -- Fixed minor bug in makeHTMLTags that did not recognize tag attributes - with embedded '-' or '_' characters. Also, added support for - passing expressions to makeHTMLTags and makeXMLTags, and used this - feature to define the globals anyOpenTag and anyCloseTag. - -- Fixed error in alphas8bit, I had omitted the y-with-umlaut character. - -- Added punc8bit string to complement alphas8bit - it contains all the - non-alphabetic, non-blank 8-bit characters. - -- Added commonHTMLEntity expression, to match common HTML "ampersand" - codes, such as "<", ">", "&", " ", and """. This - expression also defines a results name 'entity', which can be used - to extract the entity field (that is, "lt", "gt", etc.). Also added - built-in parse action replaceHTMLEntity, which can be attached to - commonHTMLEntity to translate "<", ">", "&", " ", and - """ to "<", ">", "&", " ", and "'". - -- Added example, htmlStripper.py, that strips HTML tags and scripts - from HTML pages. It also translates common HTML entities to their - respective characters. - - -Version 1.4.4 - October, 2006 -------------------------------- -- Fixed traceParseAction decorator to also trap and record exception - returns from parse actions, and to handle parse actions with 0, - 1, 2, or 3 arguments. - -- Enhanced parse action normalization to support using classes as - parse actions; that is, the class constructor is called at parse - time and the __init__ function is called with 0, 1, 2, or 3 - arguments. If passing a class as a parse action, the __init__ - method must use one of the valid parse action parameter list - formats. (This technique is useful when using pyparsing to compile - parsed text into a series of application objects - see the new - example simpleBool.py.) - -- Fixed bug in ParseResults when setting an item using an integer - index. (Reported by Christopher Lambacher, thanks!) - -- Fixed whitespace-skipping bug, patch submitted by Paolo Losi - - grazie, Paolo! - -- Fixed bug when a Combine contained an embedded Forward expression, - reported by cie on the pyparsing wiki - good catch! - -- Fixed listAllMatches bug, when a listAllMatches result was - nested within another result. (Reported by don pasquale on - comp.lang.python, well done!) - -- Fixed bug in ParseResults items() method, when returning an item - marked as listAllMatches=True - -- Fixed bug in definition of cppStyleComment (and javaStyleComment) - in which '//' line comments were not continued to the next line - if the line ends with a '\'. (Reported by eagle-eyed Ralph - Corderoy!) - -- Optimized re's for cppStyleComment and quotedString for better - re performance - also provided by Ralph Corderoy, thanks! - -- Added new example, indentedGrammarExample.py, showing how to - define a grammar using indentation to show grouping (as Python - does for defining statement nesting). Instigated by an e-mail - discussion with Andrew Dalke, thanks Andrew! - -- Added new helper operatorPrecedence (based on e-mail list discussion - with Ralph Corderoy and Paolo Losi), to facilitate definition of - grammars for expressions with unary and binary operators. For - instance, this grammar defines a 6-function arithmetic expression - grammar, with unary plus and minus, proper operator precedence,and - right- and left-associativity: - - expr = operatorPrecedence( operand, - [("!", 1, opAssoc.LEFT), - ("^", 2, opAssoc.RIGHT), - (oneOf("+ -"), 1, opAssoc.RIGHT), - (oneOf("* /"), 2, opAssoc.LEFT), - (oneOf("+ -"), 2, opAssoc.LEFT),] - ) - - Also added example simpleArith.py and simpleBool.py to provide - more detailed code samples using this new helper method. - -- Added new helpers matchPreviousLiteral and matchPreviousExpr, for - creating adaptive parsing expressions that match the same content - as was parsed in a previous parse expression. For instance: - - first = Word(nums) - matchExpr = first + ":" + matchPreviousLiteral(first) - - will match "1:1", but not "1:2". Since this matches at the literal - level, this will also match the leading "1:1" in "1:10". - - In contrast: - - first = Word(nums) - matchExpr = first + ":" + matchPreviousExpr(first) - - will *not* match the leading "1:1" in "1:10"; the expressions are - evaluated first, and then compared, so "1" is compared with "10". - -- Added keepOriginalText parse action. Sometimes pyparsing's - whitespace-skipping leaves out too much whitespace. Adding this - parse action will restore any internal whitespace for a parse - expression. This is especially useful when defining expressions - for scanString or transformString applications. - -- Added __add__ method for ParseResults class, to better support - using Python sum built-in for summing ParseResults objects returned - from scanString. - -- Added reset method for the new OnlyOnce class wrapper for parse - actions (to allow a grammar to be used multiple times). - -- Added optional maxMatches argument to scanString and searchString, - to short-circuit scanning after 'n' expression matches are found. - - -Version 1.4.3 - July, 2006 ------------------------------- -- Fixed implementation of multiple parse actions for an expression - (added in 1.4.2). - . setParseAction() reverts to its previous behavior, setting - one (or more) actions for an expression, overwriting any - action or actions previously defined - . new method addParseAction() appends one or more parse actions - to the list of parse actions attached to an expression - Now it is harder to accidentally append parse actions to an - expression, when what you wanted to do was overwrite whatever had - been defined before. (Thanks, Jean-Paul Calderone!) - -- Simplified interface to parse actions that do not require all 3 - parse action arguments. Very rarely do parse actions require more - than just the parsed tokens, yet parse actions still require all - 3 arguments including the string being parsed and the location - within the string where the parse expression was matched. With this - release, parse actions may now be defined to be called as: - . fn(string,locn,tokens) (the current form) - . fn(locn,tokens) - . fn(tokens) - . fn() - The setParseAction and addParseAction methods will internally decorate - the provided parse actions with compatible wrappers to conform to - the full (string,locn,tokens) argument sequence. - -- REMOVED SUPPORT FOR RETURNING PARSE LOCATION FROM A PARSE ACTION. - I announced this in March, 2004, and gave a final warning in the last - release. Now you can return a tuple from a parse action, and it will - be treated like any other return value (i.e., the tuple will be - substituted for the incoming tokens passed to the parse action, - which is useful when trying to parse strings into tuples). - -- Added setFailAction method, taking a callable function fn that - takes the arguments fn(s,loc,expr,err) where: - . s - string being parsed - . loc - location where expression match was attempted and failed - . expr - the parse expression that failed - . err - the exception thrown - The function returns no values. It may throw ParseFatalException - if it is desired to stop parsing immediately. - (Suggested by peter21081944 on wikispaces.com) - -- Added class OnlyOnce as helper wrapper for parse actions. OnlyOnce - only permits a parse action to be called one time, after which - all subsequent calls throw a ParseException. - -- Added traceParseAction decorator to help debug parse actions. - Simply insert "@traceParseAction" ahead of the definition of your - parse action, and each invocation will be displayed, along with - incoming arguments, and returned value. - -- Fixed bug when copying ParserElements using copy() or - setResultsName(). (Reported by Dan Thill, great catch!) - -- Fixed bug in asXML() where token text contains <, >, and & - characters - generated XML now escapes these as <, > and - &. (Reported by Jacek Sieka, thanks!) - -- Fixed bug in SkipTo() when searching for a StringEnd(). (Reported - by Pete McEvoy, thanks Pete!) - -- Fixed "except Exception" statements, the most critical added as part - of the packrat parsing enhancement. (Thanks, Erick Tryzelaar!) - -- Fixed end-of-string infinite looping on LineEnd and StringEnd - expressions. (Thanks again to Erick Tryzelaar.) - -- Modified setWhitespaceChars to return self, to be consistent with - other ParserElement modifiers. (Suggested by Erick Tryzelaar.) - -- Fixed bug/typo in new ParseResults.dump() method. - -- Fixed bug in searchString() method, in which only the first token of - an expression was returned. searchString() now returns a - ParseResults collection of all search matches. - -- Added example program removeLineBreaks.py, a string transformer that - converts text files with hard line-breaks into one with line breaks - only between paragraphs. - -- Added example program listAllMatches.py, to illustrate using the - listAllMatches option when specifying results names (also shows new - support for passing lists to oneOf). - -- Added example program linenoExample.py, to illustrate using the - helper methods lineno, line, and col, and returning objects from a - parse action. - -- Added example program parseListString.py, to which can parse the - string representation of a Python list back into a true list. Taken - mostly from my PyCon presentation examples, but now with support - for tuple elements, too! - - - -Version 1.4.2 - April 1, 2006 (No foolin'!) -------------------------------------------- -- Significant speedup from memoizing nested expressions (a technique - known as "packrat parsing"), thanks to Chris Lesniewski-Laas! Your - mileage may vary, but my Verilog parser almost doubled in speed to - over 600 lines/sec! - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method ParserElement.enablePackrat(). If - your program uses psyco to "compile as you go", you must call - enablePackrat before calling psyco.full(). If you do not do this, - Python will crash. For best results, call enablePackrat() immediately - after importing pyparsing. - -- Added new helper method countedArray(expr), for defining patterns that - start with a leading integer to indicate the number of array elements, - followed by that many elements, matching the given expr parse - expression. For instance, this two-liner: - wordArray = countedArray(Word(alphas)) - print wordArray.parseString("3 Practicality beats purity")[0] - returns the parsed array of words: - ['Practicality', 'beats', 'purity'] - The leading token '3' is suppressed, although it is easily obtained - from the length of the returned array. - (Inspired by e-mail discussion with Ralf Vosseler.) - -- Added support for attaching multiple parse actions to a single - ParserElement. (Suggested by Dan "Dang" Griffith - nice idea, Dan!) - -- Added support for asymmetric quoting characters in the recently-added - QuotedString class. Now you can define your own quoted string syntax - like "<<This is a string in double angle brackets.>>". To define - this custom form of QuotedString, your code would define: - dblAngleQuotedString = QuotedString('<<',endQuoteChar='>>') - QuotedString also supports escaped quotes, escape character other - than '\', and multiline. - -- Changed the default value returned internally by Optional, so that - None can be used as a default value. (Suggested by Steven Bethard - - I finally saw the light!) - -- Added dump() method to ParseResults, to make it easier to list out - and diagnose values returned from calling parseString. - -- A new example, a search query string parser, submitted by Steven - Mooij and Rudolph Froger - a very interesting application, thanks! - -- Added an example that parses the BNF in Python's Grammar file, in - support of generating Python grammar documentation. (Suggested by - J H Stovall.) - -- A new example, submitted by Tim Cera, of a flexible parser module, - using a simple config variable to adjust parsing for input formats - that have slight variations - thanks, Tim! - -- Added an example for parsing Roman numerals, showing the capability - of parse actions to "compile" Roman numerals into their integer - values during parsing. - -- Added a new docs directory, for additional documentation or help. - Currently, this includes the text and examples from my recent - presentation at PyCon. - -- Fixed another typo in CaselessKeyword, thanks Stefan Behnel. - -- Expanded oneOf to also accept tuples, not just lists. This really - should be sufficient... - -- Added deprecation warnings when tuple is returned from a parse action. - Looking back, I see that I originally deprecated this feature in March, - 2004, so I'm guessing people really shouldn't have been using this - feature - I'll drop it altogether in the next release, which will - allow users to return a tuple from a parse action (which is really - handy when trying to reconstuct tuples from a tuple string - representation!). - - -Version 1.4.1 - February, 2006 ------------------------------- -- Converted generator expression in QuotedString class to list - comprehension, to retain compatibility with Python 2.3. (Thanks, Titus - Brown for the heads-up!) - -- Added searchString() method to ParserElement, as an alternative to - using "scanString(instring).next()[0][0]" to search through a string - looking for a substring matching a given parse expression. (Inspired by - e-mail conversation with Dave Feustel.) - -- Modified oneOf to accept lists of strings as well as a single string - of space-delimited literals. (Suggested by Jacek Sieka - thanks!) - -- Removed deprecated use of Upcase in pyparsing test code. (Also caught by - Titus Brown.) - -- Removed lstrip() call from Literal - too aggressive in stripping - whitespace which may be valid for some grammars. (Point raised by Jacek - Sieka). Also, made Literal more robust in the event of passing an empty - string. - -- Fixed bug in replaceWith when returning None. - -- Added cautionary documentation for Forward class when assigning a - MatchFirst expression, as in: - fwdExpr << a | b | c - Precedence of operators causes this to be evaluated as: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. Users must - explicitly group the values inserted into the Forward: - fwdExpr << (a | b | c) - (Suggested by Scot Wilcoxon - thanks, Scot!) - - -Version 1.4 - January 18, 2006 ------------------------------- -- Added Regex class, to permit definition of complex embedded expressions - using regular expressions. (Enhancement provided by John Beisley, great - job!) - -- Converted implementations of Word, oneOf, quoted string, and comment - helpers to utilize regular expression matching. Performance improvements - in the 20-40% range. - -- Added QuotedString class, to support definition of non-standard quoted - strings (Suggested by Guillaume Proulx, thanks!) - -- Added CaselessKeyword class, to streamline grammars with, well, caseless - keywords (Proposed by Stefan Behnel, thanks!) - -- Fixed bug in SkipTo, when using an ignoreable expression. (Patch provided - by Anonymous, thanks, whoever-you-are!) - -- Fixed typo in NoMatch class. (Good catch, Stefan Behnel!) - -- Fixed minor bug in _makeTags(), using string.printables instead of - pyparsing.printables. - -- Cleaned up some of the expressions created by makeXXXTags helpers, to - suppress extraneous <> characters. - -- Added some grammar definition-time checking to verify that a grammar is - being built using proper ParserElements. - -- Added examples: - . LAparser.py - linear algebra C preprocessor (submitted by Mike Ellis, - thanks Mike!) - . wordsToNum.py - converts word description of a number back to - the original number (such as 'one hundred and twenty three' -> 123) - . updated fourFn.py to support unary minus, added BNF comments - - -Version 1.3.3 - September 12, 2005 ----------------------------------- -- Improved support for Unicode strings that would be returned using - srange. Added greetingInKorean.py example, for a Korean version of - "Hello, World!" using Unicode. (Thanks, June Kim!) - -- Added 'hexnums' string constant (nums+"ABCDEFabcdef") for defining - hexadecimal value expressions. - -- NOTE: ===THIS CHANGE MAY BREAK EXISTING CODE=== - Modified tag and results definitions returned by makeHTMLTags(), - to better support the looseness of HTML parsing. Tags to be - parsed are now caseless, and keys generated for tag attributes are - now converted to lower case. - - Formerly, makeXMLTags("XYZ") would return a tag with results - name of "startXYZ", this has been changed to "startXyz". If this - tag is matched against '<XYZ Abc="1" DEF="2" ghi="3">', the - matched keys formerly would be "Abc", "DEF", and "ghi"; keys are - now converted to lower case, giving keys of "abc", "def", and - "ghi". These changes were made to try to address the lax - case sensitivity agreement between start and end tags in many - HTML pages. - - No changes were made to makeXMLTags(), which assumes more rigorous - parsing rules. - - Also, cleaned up case-sensitivity bugs in closing tags, and - switched to using Keyword instead of Literal class for tags. - (Thanks, Steve Young, for getting me to look at these in more - detail!) - -- Added two helper parse actions, upcaseTokens and downcaseTokens, - which will convert matched text to all uppercase or lowercase, - respectively. - -- Deprecated Upcase class, to be replaced by upcaseTokens parse - action. - -- Converted messages sent to stderr to use warnings module, such as - when constructing a Literal with an empty string, one should use - the Empty() class or the empty helper instead. - -- Added ' ' (space) as an escapable character within a quoted - string. - -- Added helper expressions for common comment types, in addition - to the existing cStyleComment (/*...*/) and htmlStyleComment - (<!-- ... -->) - . dblSlashComment = // ... (to end of line) - . cppStyleComment = cStyleComment or dblSlashComment - . javaStyleComment = cppStyleComment - . pythonStyleComment = # ... (to end of line) - - - -Version 1.3.2 - July 24, 2005 ------------------------------ -- Added Each class as an enhanced version of And. 'Each' requires - that all given expressions be present, but may occur in any order. - Special handling is provided to group ZeroOrMore and OneOrMore - elements that occur out-of-order in the input string. You can also - construct 'Each' objects by joining expressions with the '&' - operator. When using the Each class, results names are strongly - recommended for accessing the matched tokens. (Suggested by Pradam - Amini - thanks, Pradam!) - -- Stricter interpretation of 'max' qualifier on Word elements. If the - 'max' attribute is specified, matching will fail if an input field - contains more than 'max' consecutive body characters. For example, - previously, Word(nums,max=3) would match the first three characters - of '0123456', returning '012' and continuing parsing at '3'. Now, - when constructed using the max attribute, Word will raise an - exception with this string. - -- Cleaner handling of nested dictionaries returned by Dict. No - longer necessary to dereference sub-dictionaries as element [0] of - their parents. - === NOTE: THIS CHANGE MAY BREAK SOME EXISTING CODE, BUT ONLY IF - PARSING NESTED DICTIONARIES USING THE LITTLE-USED DICT CLASS === - (Prompted by discussion thread on the Python Tutor list, with - contributions from Danny Yoo, Kent Johnson, and original post by - Liam Clarke - thanks all!) - - - -Version 1.3.1 - June, 2005 ----------------------------------- -- Added markInputline() method to ParseException, to display the input - text line location of the parsing exception. (Thanks, Stefan Behnel!) - -- Added setDefaultKeywordChars(), so that Keyword definitions using a - custom keyword character set do not all need to add the keywordChars - constructor argument (similar to setDefaultWhitespaceChars()). - (suggested by rzhanka on the SourceForge pyparsing forum.) - -- Simplified passing debug actions to setDebugAction(). You can now - pass 'None' for a debug action if you want to take the default - debug behavior. To suppress a particular debug action, you can pass - the pyparsing method nullDebugAction. - -- Refactored parse exception classes, moved all behavior to - ParseBaseException, and the former ParseException is now a subclass of - ParseBaseException. Added a second subclass, ParseFatalException, as - a subclass of ParseBaseException. User-defined parse actions can raise - ParseFatalException if a data inconsistency is detected (such as a - begin-tag/end-tag mismatch), and this will stop all parsing immediately. - (Inspired by e-mail thread with Michele Petrazzo - thanks, Michelle!) - -- Added helper methods makeXMLTags and makeHTMLTags, that simplify the - definition of XML or HTML tag parse expressions for a given tagname. - Both functions return a pair of parse expressions, one for the opening - tag (that is, '<tagname>') and one for the closing tag ('</tagname>'). - The opening tagame also recognizes any attribute definitions that have - been included in the opening tag, as well as an empty tag (one with a - trailing '/', as in '<BODY/>' which is equivalent to '<BODY></BODY>'). - makeXMLTags uses stricter XML syntax for attributes, requiring that they - be enclosed in double quote characters - makeHTMLTags is more lenient, - and accepts single-quoted strings or any contiguous string of characters - up to the next whitespace character or '>' character. Attributes can - be retrieved as dictionary or attribute values of the returned results - from the opening tag. - -- Added example minimath2.py, a refinement on fourFn.py that adds - an interactive session and support for variables. (Thanks, Steven Siew!) - -- Added performance improvement, up to 20% reduction! (Found while working - with Wolfgang Borgert on performance tuning of his TTCN3 parser.) - -- And another performance improvement, up to 25%, when using scanString! - (Found while working with Henrik Westlund on his C header file scanner.) - -- Updated UML diagrams to reflect latest class/method changes. - - -Version 1.3 - March, 2005 ----------------------------------- -- Added new Keyword class, as a special form of Literal. Keywords - must be followed by whitespace or other non-keyword characters, to - distinguish them from variables or other identifiers that just - happen to start with the same characters as a keyword. For instance, - the input string containing "ifOnlyIfOnly" will match a Literal("if") - at the beginning and in the middle, but will fail to match a - Keyword("if"). Keyword("if") will match only strings such as "if only" - or "if(only)". (Proposed by Wolfgang Borgert, and Berteun Damman - separately requested this on comp.lang.python - great idea!) - -- Added setWhitespaceChars() method to override the characters to be - skipped as whitespace before matching a particular ParseElement. Also - added the class-level method setDefaultWhitespaceChars(), to allow - users to override the default set of whitespace characters (space, - tab, newline, and return) for all subsequently defined ParseElements. - (Inspired by Klaas Hofstra's inquiry on the Sourceforge pyparsing - forum.) - -- Added helper parse actions to support some very common parse - action use cases: - . replaceWith(replStr) - replaces the matching tokens with the - provided replStr replacement string; especially useful with - transformString() - . removeQuotes - removes first and last character from string enclosed - in quotes (note - NOT the same as the string strip() method, as only - a single character is removed at each end) - -- Added copy() method to ParseElement, to make it easier to define - different parse actions for the same basic parse expression. (Note, copy - is implicitly called when using setResultsName().) - - - (The following changes were posted to CVS as Version 1.2.3 - - October-December, 2004) - -- Added support for Unicode strings in creating grammar definitions. - (Big thanks to Gavin Panella!) - -- Added constant alphas8bit to include the following 8-bit characters: - ������������������������������������������������������������� - -- Added srange() function to simplify definition of Word elements, using - regexp-like '[A-Za-z0-9]' syntax. This also simplifies referencing - common 8-bit characters. - -- Fixed bug in Dict when a single element Dict was embedded within another - Dict. (Thanks Andy Yates for catching this one!) - -- Added 'formatted' argument to ParseResults.asXML(). If set to False, - suppresses insertion of whitespace for pretty-print formatting. Default - equals True for backward compatibility. - -- Added setDebugActions() function to ParserElement, to allow user-defined - debugging actions. - -- Added support for escaped quotes (either in \', \", or doubled quote - form) to the predefined expressions for quoted strings. (Thanks, Ero - Carrera!) - -- Minor performance improvement (~5%) converting "char in string" tests - to "char in dict". (Suggested by Gavin Panella, cool idea!) - - -Version 1.2.2 - September 27, 2004 ----------------------------------- -- Modified delimitedList to accept an expression as the delimiter, instead - of only accepting strings. - -- Modified ParseResults, to convert integer field keys to strings (to - avoid confusion with list access). - -- Modified Combine, to convert all embedded tokens to strings before - combining. - -- Fixed bug in MatchFirst in which parse actions would be called for - expressions that only partially match. (Thanks, John Hunter!) - -- Fixed bug in fourFn.py example that fixes right-associativity of ^ - operator. (Thanks, Andrea Griffini!) - -- Added class FollowedBy(expression), to look ahead in the input string - without consuming tokens. - -- Added class NoMatch that never matches any input. Can be useful in - debugging, and in very specialized grammars. - -- Added example pgn.py, for parsing chess game files stored in Portable - Game Notation. (Thanks, Alberto Santini!) - - -Version 1.2.1 - August 19, 2004 -------------------------------- -- Added SkipTo(expression) token type, simplifying grammars that only - want to specify delimiting expressions, and want to match any characters - between them. - -- Added helper method dictOf(key,value), making it easier to work with - the Dict class. (Inspired by Pavel Volkovitskiy, thanks!). - -- Added optional argument listAllMatches (default=False) to - setResultsName(). Setting listAllMatches to True overrides the default - modal setting of tokens to results names; instead, the results name - acts as an accumulator for all matching tokens within the local - repetition group. (Suggested by Amaury Le Leyzour - thanks!) - -- Fixed bug in ParseResults, throwing exception when trying to extract - slice, or make a copy using [:]. (Thanks, Wilson Fowlie!) - -- Fixed bug in transformString() when the input string contains <TAB>'s - (Thanks, Rick Walia!). - -- Fixed bug in returning tokens from un-Grouped And's, Or's and - MatchFirst's, where too many tokens would be included in the results, - confounding parse actions and returned results. - -- Fixed bug in naming ParseResults returned by And's, Or's, and Match - First's. - -- Fixed bug in LineEnd() - matching this token now correctly consumes - and returns the end of line "\n". - -- Added a beautiful example for parsing Mozilla calendar files (Thanks, - Petri Savolainen!). - -- Added support for dynamically modifying Forward expressions during - parsing. - - -Version 1.2 - 20 June 2004 --------------------------- -- Added definition for htmlComment to help support HTML scanning and - parsing. - -- Fixed bug in generating XML for Dict classes, in which trailing item was - duplicated in the output XML. - -- Fixed release bug in which scanExamples.py was omitted from release - files. - -- Fixed bug in transformString() when parse actions are not defined on the - outermost parser element. - -- Added example urlExtractor.py, as another example of using scanString - and parse actions. - - -Version 1.2beta3 - 4 June 2004 ------------------------------- -- Added White() token type, analogous to Word, to match on whitespace - characters. Use White in parsers with significant whitespace (such as - configuration file parsers that use indentation to indicate grouping). - Construct White with a string containing the whitespace characters to be - matched. Similar to Word, White also takes optional min, max, and exact - parameters. - -- As part of supporting whitespace-signficant parsing, added parseWithTabs() - method to ParserElement, to override the default behavior in parseString - of automatically expanding tabs to spaces. To retain tabs during - parsing, call parseWithTabs() before calling parseString(), parseFile() or - scanString(). (Thanks, Jean-Guillaume Paradis for catching this, and for - your suggestions on whitespace-significant parsing.) - -- Added transformString() method to ParseElement, as a complement to - scanString(). To use transformString, define a grammar and attach a parse - action to the overall grammar that modifies the returned token list. - Invoking transformString() on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. transformString() returns the resulting transformed string. - (Note: transformString() does *not* automatically expand tabs to spaces.) - Also added scanExamples.py to the examples directory to show sample uses of - scanString() and transformString(). - -- Removed group() method that was introduced in beta2. This turns out NOT to - be equivalent to nesting within a Group() object, and I'd prefer not to sow - more seeds of confusion. - -- Fixed behavior of asXML() where tags for groups were incorrectly duplicated. - (Thanks, Brad Clements!) - -- Changed beta version message to display to stderr instead of stdout, to - make asXML() easier to use. (Thanks again, Brad.) - - -Version 1.2beta2 - 19 May 2004 ------------------------------- -- *** SIMPLIFIED API *** - Parse actions that do not modify the list of tokens - no longer need to return a value. This simplifies those parse actions that - use the list of tokens to update a counter or record or display some of the - token content; these parse actions can simply end without having to specify - 'return toks'. - -- *** POSSIBLE API INCOMPATIBILITY *** - Fixed CaselessLiteral bug, where the - returned token text was not the original string (as stated in the docs), - but the original string converted to upper case. (Thanks, Dang Griffith!) - **NOTE: this may break some code that relied on this erroneous behavior. - Users should scan their code for uses of CaselessLiteral.** - -- *** POSSIBLE CODE INCOMPATIBILITY *** - I have renamed the internal - attributes on ParseResults from 'dict' and 'list' to '__tokdict' and - '__toklist', to avoid collisions with user-defined data fields named 'dict' - and 'list'. Any client code that accesses these attributes directly will - need to be modified. Hopefully the implementation of methods such as keys(), - items(), len(), etc. on ParseResults will make such direct attribute - accessess unnecessary. - -- Added asXML() method to ParseResults. This greatly simplifies the process - of parsing an input data file and generating XML-structured data. - -- Added getName() method to ParseResults. This method is helpful when - a grammar specifies ZeroOrMore or OneOrMore of a MatchFirst or Or - expression, and the parsing code needs to know which expression matched. - (Thanks, Eric van der Vlist, for this idea!) - -- Added items() and values() methods to ParseResults, to better support using - ParseResults as a Dictionary. - -- Added parseFile() as a convenience function to parse the contents of an - entire text file. Accepts either a file name or a file object. (Thanks - again, Dang!) - -- Added group() method to And, Or, and MatchFirst, as a short-cut alternative - to enclosing a construct inside a Group object. - -- Extended fourFn.py to support exponentiation, and simple built-in functions. - -- Added EBNF parser to examples, including a demo where it parses its own - EBNF! (Thanks to Seo Sanghyeon!) - -- Added Delphi Form parser to examples, dfmparse.py, plus a couple of - sample Delphi forms as tests. (Well done, Dang!) - -- Another performance speedup, 5-10%, inspired by Dang! Plus about a 20% - speedup, by pre-constructing and cacheing exception objects instead of - constructing them on the fly. - -- Fixed minor bug when specifying oneOf() with 'caseless=True'. - -- Cleaned up and added a few more docstrings, to improve the generated docs. - - -Version 1.1.2 - 21 Mar 2004 ---------------------------- -- Fixed minor bug in scanString(), so that start location is at the start of - the matched tokens, not at the start of the whitespace before the matched - tokens. - -- Inclusion of HTML documentation, generated using Epydoc. Reformatted some - doc strings to better generate readable docs. (Beautiful work, Ed Loper, - thanks for Epydoc!) - -- Minor performance speedup, 5-15% - -- And on a process note, I've used the unittest module to define a series of - unit tests, to help avoid the embarrassment of the version 1.1 snafu. - - -Version 1.1.1 - 6 Mar 2004 --------------------------- -- Fixed critical bug introduced in 1.1, which broke MatchFirst(!) token - matching. - **THANK YOU, SEO SANGHYEON!!!** - -- Added "from future import __generators__" to permit running under - pre-Python 2.3. - -- Added example getNTPservers.py, showing how to use pyparsing to extract - a text pattern from the HTML of a web page. - - -Version 1.1 - 3 Mar 2004 -------------------------- -- ***Changed API*** - While testing out parse actions, I found that the value - of loc passed in was not the starting location of the matched tokens, but - the location of the next token in the list. With this version, the location - passed to the parse action is now the starting location of the tokens that - matched. - - A second part of this change is that the return value of parse actions no - longer needs to return a tuple containing both the location and the parsed - tokens (which may optionally be modified); parse actions only need to return - the list of tokens. Parse actions that return a tuple are deprecated; they - will still work properly for conversion/compatibility, but this behavior will - be removed in a future version. - -- Added validate() method, to help diagnose infinite recursion in a grammar tree. - validate() is not 100% fool-proof, but it can help track down nasty infinite - looping due to recursively referencing the same grammar construct without some - intervening characters. - -- Cleaned up default listing of some parse element types, to more closely match - ordinary BNF. Instead of the form <classname>:[contents-list], some changes - are: - . And(token1,token2,token3) is "{ token1 token2 token3 }" - . Or(token1,token2,token3) is "{ token1 ^ token2 ^ token3 }" - . MatchFirst(token1,token2,token3) is "{ token1 | token2 | token3 }" - . Optional(token) is "[ token ]" - . OneOrMore(token) is "{ token }..." - . ZeroOrMore(token) is "[ token ]..." - -- Fixed an infinite loop in oneOf if the input string contains a duplicated - option. (Thanks Brad Clements) - -- Fixed a bug when specifying a results name on an Optional token. (Thanks - again, Brad Clements) - -- Fixed a bug introduced in 1.0.6 when I converted quotedString to use - CharsNotIn; I accidentally permitted quoted strings to span newlines. I have - fixed this in this version to go back to the original behavior, in which - quoted strings do *not* span newlines. - -- Fixed minor bug in HTTP server log parser. (Thanks Jim Richardson) - - -Version 1.0.6 - 13 Feb 2004 ----------------------------- -- Added CharsNotIn class (Thanks, Lee SangYeong). This is the opposite of - Word, in that it is constructed with a set of characters *not* to be matched. - (This enhancement also allowed me to clean up and simplify some of the - definitions for quoted strings, cStyleComment, and restOfLine.) - -- **MINOR API CHANGE** - Added joinString argument to the __init__ method of - Combine (Thanks, Thomas Kalka). joinString defaults to "", but some - applications might choose some other string to use instead, such as a blank - or newline. joinString was inserted as the second argument to __init__, - so if you have code that specifies an adjacent value, without using - 'adjacent=', this code will break. - -- Modified LineStart to recognize the start of an empty line. - -- Added optional caseless flag to oneOf(), to create a list of CaselessLiteral - tokens instead of Literal tokens. - -- Added some enhancements to the SQL example: - . Oracle-style comments (Thanks to Harald Armin Massa) - . simple WHERE clause - -- Minor performance speedup - 5-15% - - -Version 1.0.5 - 19 Jan 2004 ----------------------------- -- Added scanString() generator method to ParseElement, to support regex-like - pattern-searching - -- Added items() list to ParseResults, to return named results as a - list of (key,value) pairs - -- Fixed memory overflow in asList() for deeply nested ParseResults (Thanks, - Sverrir Valgeirsson) - -- Minor performance speedup - 10-15% - - -Version 1.0.4 - 8 Jan 2004 ---------------------------- -- Added positional tokens StringStart, StringEnd, LineStart, and LineEnd - -- Added commaSeparatedList to pre-defined global token definitions; also added - commasep.py to the examples directory, to demonstrate the differences between - parsing comma-separated data and simple line-splitting at commas - -- Minor API change: delimitedList does not automatically enclose the - list elements in a Group, but makes this the responsibility of the caller; - also, if invoked using 'combine=True', the list delimiters are also included - in the returned text (good for scoped variables, such as a.b.c or a::b::c, or - for directory paths such as a/b/c) - -- Performance speed-up again, 30-40% - -- Added httpServerLogParser.py to examples directory, as this is - a common parsing task - - -Version 1.0.3 - 23 Dec 2003 ---------------------------- -- Performance speed-up again, 20-40% - -- Added Python distutils installation setup.py, etc. (thanks, Dave Kuhlman) - - -Version 1.0.2 - 18 Dec 2003 ---------------------------- -- **NOTE: Changed API again!!!** (for the last time, I hope) - - + Renamed module from parsing to pyparsing, to better reflect Python - linkage. - -- Also added dictExample.py to examples directory, to illustrate - usage of the Dict class. - - -Version 1.0.1 - 17 Dec 2003 ---------------------------- -- **NOTE: Changed API!** - - + Renamed 'len' argument on Word.__init__() to 'exact' - -- Performance speed-up, 10-30% - - -Version 1.0.0 - 15 Dec 2003 ---------------------------- -- Initial public release - -Version 0.1.1 thru 0.1.17 - October-November, 2003 --------------------------------------------------- -- initial development iterations: - - added Dict, Group - - added helper methods oneOf, delimitedList - - added helpers quotedString (and double and single), restOfLine, cStyleComment - - added MatchFirst as an alternative to the slower Or - - added UML class diagram - - fixed various logic bugs diff --git a/src/HowToUsePyparsing.html b/src/HowToUsePyparsing.html deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL0hvd1RvVXNlUHlwYXJzaW5nLmh0bWw=..0000000000000000000000000000000000000000 --- a/src/HowToUsePyparsing.html +++ /dev/null @@ -1,1288 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> -<meta name="generator" content="Docutils 0.8: http://docutils.sourceforge.net/" /> -<title>Using the pyparsing module</title> -<meta name="author" content="Paul McGuire" /> -<meta name="date" content="June, 2011" /> -<meta name="copyright" content="Copyright © 2003-2011 Paul McGuire." /> -<style type="text/css"> - -/* -:Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 6387 2010-08-13 12:23:41Z milde $ -:Copyright: This stylesheet has been placed in the public domain. - -Default cascading style sheet for the HTML output of Docutils. - -See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to -customize this style sheet. -*/ - -/* used to remove borders from tables and images */ -.borderless, table.borderless td, table.borderless th { - border: 0 } - -table.borderless td, table.borderless th { - /* Override padding for "table.docutils td" with "! important". - The right padding separates the table cells. */ - padding: 0 0.5em 0 0 ! important } - -.first { - /* Override more specific margin styles with "! important". */ - margin-top: 0 ! important } - -.last, .with-subtitle { - margin-bottom: 0 ! important } - -.hidden { - display: none } - -a.toc-backref { - text-decoration: none ; - color: black } - -blockquote.epigraph { - margin: 2em 5em ; } - -dl.docutils dd { - margin-bottom: 0.5em } - -object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { - overflow: hidden; -} - -/* Uncomment (and remove this text!) to get bold-faced definition list terms -dl.docutils dt { - font-weight: bold } -*/ - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.admonition, div.attention, div.caution, div.danger, div.error, -div.hint, div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.admonition p.admonition-title, div.hint p.admonition-title, -div.important p.admonition-title, div.note p.admonition-title, -div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -/* Uncomment (and remove this text!) to get reduced vertical space in - compound paragraphs. -div.compound .compound-first, div.compound .compound-middle { - margin-bottom: 0.5em } - -div.compound .compound-last, div.compound .compound-middle { - margin-top: 0.5em } -*/ - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em ; - margin-right: 2em } - -div.footer, div.header { - clear: both; - font-size: smaller } - -div.line-block { - display: block ; - margin-top: 1em ; - margin-bottom: 1em } - -div.line-block div.line-block { - margin-top: 0 ; - margin-bottom: 0 ; - margin-left: 1.5em } - -div.sidebar { - margin: 0 0 0.5em 1em ; - border: medium outset ; - padding: 1em ; - background-color: #ffffee ; - width: 40% ; - float: right ; - clear: right } - -div.sidebar p.rubric { - font-family: sans-serif ; - font-size: medium } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, -h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { - margin-top: 0.4em } - -h1.title { - text-align: center } - -h2.subtitle { - text-align: center } - -hr.docutils { - width: 75% } - -img.align-left, .figure.align-left, object.align-left { - clear: left ; - float: left ; - margin-right: 1em } - -img.align-right, .figure.align-right, object.align-right { - clear: right ; - float: right ; - margin-left: 1em } - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left } - -.align-center { - clear: both ; - text-align: center } - -.align-right { - text-align: right } - -/* reset inner alignment in figures */ -div.align-right { - text-align: left } - -/* div.align-center * { */ -/* text-align: left } */ - -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - -p.attribution { - text-align: right ; - margin-left: 50% } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.rubric { - font-weight: bold ; - font-size: larger ; - color: maroon ; - text-align: center } - -p.sidebar-title { - font-family: sans-serif ; - font-weight: bold ; - font-size: larger } - -p.sidebar-subtitle { - font-family: sans-serif ; - font-weight: bold } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font: inherit } - -pre.literal-block, pre.doctest-block { - margin-left: 2em ; - margin-right: 2em } - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option { - white-space: nowrap } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -span.section-subtitle { - /* font-size relative to parent (h1..h6 element) */ - font-size: 80% } - -table.citation { - border-left: solid 1px gray; - margin-left: 1px } - -table.docinfo { - margin: 2em 4em } - -table.docutils { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.footnote { - border-left: solid 1px black; - margin-left: 1px } - -table.docutils td, table.docutils th, -table.docinfo td, table.docinfo th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -table.docutils th.field-name, table.docinfo th.docinfo-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap ; - padding-left: 0 } - -h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, -h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { - font-size: 100% } - -ul.auto-toc { - list-style-type: none } - -</style> -</head> -<body> -<div class="document" id="using-the-pyparsing-module"> -<h1 class="title">Using the pyparsing module</h1> -<table class="docinfo" frame="void" rules="none"> -<col class="docinfo-name" /> -<col class="docinfo-content" /> -<tbody valign="top"> -<tr><th class="docinfo-name">Author:</th> -<td>Paul McGuire</td></tr> -<tr><th class="docinfo-name">Address:</th> -<td><pre class="address"> -<a class="first last reference external" href="mailto:ptmcg@users.sourceforge.net">ptmcg@users.sourceforge.net</a> -</pre> -</td></tr> -<tr><th class="docinfo-name">Revision:</th> -<td>1.5.6</td></tr> -<tr><th class="docinfo-name">Date:</th> -<td>June, 2011</td></tr> -<tr><th class="docinfo-name">Copyright:</th> -<td>Copyright © 2003-2011 Paul McGuire.</td></tr> -</tbody> -</table> -<table class="docutils field-list" frame="void" rules="none"> -<col class="field-name" /> -<col class="field-body" /> -<tbody valign="top"> -<tr class="field"><th class="field-name">abstract:</th><td class="field-body">This document provides how-to instructions for the -pyparsing library, an easy-to-use Python module for constructing -and executing basic text parsers. The pyparsing module is useful -for evaluating user-definable -expressions, processing custom application language commands, or -extracting data from formatted reports.</td> -</tr> -</tbody> -</table> -<div class="contents topic" id="contents"> -<p class="topic-title first">Contents</p> -<ul class="auto-toc simple"> -<li><a class="reference internal" href="#steps-to-follow" id="id1">1 Steps to follow</a><ul class="auto-toc"> -<li><a class="reference internal" href="#hello-world" id="id2">1.1 Hello, World!</a></li> -<li><a class="reference internal" href="#usage-notes" id="id3">1.2 Usage notes</a></li> -</ul> -</li> -<li><a class="reference internal" href="#classes" id="id4">2 Classes</a><ul class="auto-toc"> -<li><a class="reference internal" href="#classes-in-the-pyparsing-module" id="id5">2.1 Classes in the pyparsing module</a></li> -<li><a class="reference internal" href="#basic-parserelement-subclasses" id="id6">2.2 Basic ParserElement subclasses</a></li> -<li><a class="reference internal" href="#expression-subclasses" id="id7">2.3 Expression subclasses</a></li> -<li><a class="reference internal" href="#expression-operators" id="id8">2.4 Expression operators</a></li> -<li><a class="reference internal" href="#positional-subclasses" id="id9">2.5 Positional subclasses</a></li> -<li><a class="reference internal" href="#converter-subclasses" id="id10">2.6 Converter subclasses</a></li> -<li><a class="reference internal" href="#special-subclasses" id="id11">2.7 Special subclasses</a></li> -<li><a class="reference internal" href="#other-classes" id="id12">2.8 Other classes</a></li> -<li><a class="reference internal" href="#exception-classes-and-troubleshooting" id="id13">2.9 Exception classes and Troubleshooting</a></li> -</ul> -</li> -<li><a class="reference internal" href="#miscellaneous-attributes-and-methods" id="id14">3 Miscellaneous attributes and methods</a><ul class="auto-toc"> -<li><a class="reference internal" href="#helper-methods" id="id15">3.1 Helper methods</a></li> -<li><a class="reference internal" href="#helper-parse-actions" id="id16">3.2 Helper parse actions</a></li> -<li><a class="reference internal" href="#common-string-and-token-constants" id="id17">3.3 Common string and token constants</a></li> -</ul> -</li> -</ul> -</div> -<div class="section" id="steps-to-follow"> -<h1><a class="toc-backref" href="#id1">1 Steps to follow</a></h1> -<p>To parse an incoming data string, the client code must follow these steps:</p> -<ol class="arabic simple"> -<li>First define the tokens and patterns to be matched, and assign -this to a program variable. Optional results names or parsing -actions can also be defined at this time.</li> -<li>Call <tt class="docutils literal">parseString()</tt> or <tt class="docutils literal">scanString()</tt> on this variable, passing in -the string to -be parsed. During the matching process, whitespace between -tokens is skipped by default (although this can be changed). -When token matches occur, any defined parse action methods are -called.</li> -<li>Process the parsed results, returned as a list of strings. -Matching results may also be accessed as named attributes of -the returned results, if names are defined in the definition of -the token pattern, using <tt class="docutils literal">setResultsName()</tt>.</li> -</ol> -<div class="section" id="hello-world"> -<h2><a class="toc-backref" href="#id2">1.1 Hello, World!</a></h2> -<p>The following complete Python program will parse the greeting "Hello, World!", -or any other greeting of the form "<salutation>, <addressee>!":</p> -<pre class="literal-block"> -from pyparsing import Word, alphas - -greet = Word( alphas ) + "," + Word( alphas ) + "!" -greeting = greet.parseString( "Hello, World!" ) -print greeting -</pre> -<p>The parsed tokens are returned in the following form:</p> -<pre class="literal-block"> -['Hello', ',', 'World', '!'] -</pre> -</div> -<div class="section" id="usage-notes"> -<h2><a class="toc-backref" href="#id3">1.2 Usage notes</a></h2> -<ul> -<li><p class="first">The pyparsing module can be used to interpret simple command -strings or algebraic expressions, or can be used to extract data -from text reports with complicated format and structure ("screen -or report scraping"). However, it is possible that your defined -matching patterns may accept invalid inputs. Use pyparsing to -extract data from strings assumed to be well-formatted.</p> -</li> -<li><p class="first">To keep up the readability of your code, use <a class="reference internal" href="#operators">operators</a> such as <tt class="docutils literal">+</tt>, <tt class="docutils literal">|</tt>, -<tt class="docutils literal">^</tt>, and <tt class="docutils literal">~</tt> to combine expressions. You can also combine -string literals with ParseExpressions - they will be -automatically converted to Literal objects. For example:</p> -<pre class="literal-block"> -integer = Word( nums ) # simple unsigned integer -variable = Word( alphas, max=1 ) # single letter variable, such as x, z, m, etc. -arithOp = Word( "+-*/", max=1 ) # arithmetic operators -equation = variable + "=" + integer + arithOp + integer # will match "x=2+2", etc. -</pre> -<p>In the definition of <tt class="docutils literal">equation</tt>, the string <tt class="docutils literal">"="</tt> will get added as -a <tt class="docutils literal"><span class="pre">Literal("=")</span></tt>, but in a more readable way.</p> -</li> -<li><p class="first">The pyparsing module's default behavior is to ignore whitespace. This is the -case for 99% of all parsers ever written. This allows you to write simple, clean, -grammars, such as the above <tt class="docutils literal">equation</tt>, without having to clutter it up with -extraneous <tt class="docutils literal">ws</tt> markers. The <tt class="docutils literal">equation</tt> grammar will successfully parse all of the -following statements:</p> -<pre class="literal-block"> -x=2+2 -x = 2+2 -a = 10 * 4 -r= 1234/ 100000 -</pre> -<p>Of course, it is quite simple to extend this example to support more elaborate expressions, with -nesting with parentheses, floating point numbers, scientific notation, and named constants -(such as <tt class="docutils literal">e</tt> or <tt class="docutils literal">pi</tt>). See <tt class="docutils literal">fourFn.py</tt>, included in the examples directory.</p> -</li> -<li><p class="first">To modify pyparsing's default whitespace skipping, you can use one or -more of the following methods:</p> -<ul> -<li><p class="first">use the static method <tt class="docutils literal">ParserElement.setDefaultWhitespaceChars</tt> -to override the normal set of whitespace chars (' tn'). For instance -when defining a grammar in which newlines are significant, you should -call <tt class="docutils literal">ParserElement.setDefaultWhitespaceChars(' \t')</tt> to remove -newline from the set of skippable whitespace characters. Calling -this method will affect all pyparsing expressions defined afterward.</p> -</li> -<li><p class="first">call <tt class="docutils literal">leaveWhitespace()</tt> on individual expressions, to suppress the -skipping of whitespace before trying to match the expression</p> -</li> -<li><p class="first">use <tt class="docutils literal">Combine</tt> to require that successive expressions must be -adjacent in the input string. For instance, this expression:</p> -<pre class="literal-block"> -real = Word(nums) + '.' + Word(nums) -</pre> -<p>will match "3.14159", but will also match "3 . 12". It will also -return the matched results as ['3', '.', '14159']. By changing this -expression to:</p> -<pre class="literal-block"> -real = Combine( Word(nums) + '.' + Word(nums) ) -</pre> -<p>it will not match numbers with embedded spaces, and it will return a -single concatenated string '3.14159' as the parsed token.</p> -</li> -</ul> -</li> -<li><p class="first">Repetition of expressions can be indicated using the '*' operator. An -expression may be multiplied by an integer value (to indicate an exact -repetition count), or by a tuple containing -two integers, or None and an integer, representing min and max repetitions -(with None representing no min or no max, depending whether it is the first or -second tuple element). See the following examples, where n is used to -indicate an integer value:</p> -<ul class="simple"> -<li><tt class="docutils literal">expr*3</tt> is equivalent to <tt class="docutils literal">expr + expr + expr</tt></li> -<li><tt class="docutils literal"><span class="pre">expr*(2,3)</span></tt> is equivalent to <tt class="docutils literal">expr + expr + Optional(expr)</tt></li> -<li><tt class="docutils literal"><span class="pre">expr*(n,None)</span></tt> or <tt class="docutils literal"><span class="pre">expr*(n,)</span></tt> is equivalent -to <tt class="docutils literal">expr*n + ZeroOrMore(expr)</tt> (read as "at least n instances of expr")</li> -<li><tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> is equivalent to <tt class="docutils literal"><span class="pre">expr*(0,n)</span></tt> -(read as "0 to n instances of expr")</li> -<li><tt class="docutils literal"><span class="pre">expr*(None,None)</span></tt> is equivalent to <tt class="docutils literal">ZeroOrMore(expr)</tt></li> -<li><tt class="docutils literal"><span class="pre">expr*(1,None)</span></tt> is equivalent to <tt class="docutils literal">OneOrMore(expr)</tt></li> -</ul> -<p>Note that <tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> does not raise an exception if -more than n exprs exist in the input stream; that is, -<tt class="docutils literal"><span class="pre">expr*(None,n)</span></tt> does not enforce a maximum number of expr -occurrences. If this behavior is desired, then write -<tt class="docutils literal"><span class="pre">expr*(None,n)</span> + ~expr</tt>.</p> -</li> -<li><p class="first"><tt class="docutils literal">MatchFirst</tt> expressions are matched left-to-right, and the first -match found will skip all later expressions within, so be sure -to define less-specific patterns after more-specific patterns. -If you are not sure which expressions are most specific, use Or -expressions (defined using the <tt class="docutils literal">^</tt> operator) - they will always -match the longest expression, although they are more -compute-intensive.</p> -</li> -<li><p class="first"><tt class="docutils literal">Or</tt> expressions will evaluate all of the specified subexpressions -to determine which is the "best" match, that is, which matches -the longest string in the input data. In case of a tie, the -left-most expression in the <tt class="docutils literal">Or</tt> list will win.</p> -</li> -<li><p class="first">If parsing the contents of an entire file, pass it to the -<tt class="docutils literal">parseFile</tt> method using:</p> -<pre class="literal-block"> -expr.parseFile( sourceFile ) -</pre> -</li> -<li><p class="first"><tt class="docutils literal">ParseExceptions</tt> will report the location where an expected token -or expression failed to match. For example, if we tried to use our -"Hello, World!" parser to parse "Hello World!" (leaving out the separating -comma), we would get an exception, with the message:</p> -<pre class="literal-block"> -pyparsing.ParseException: Expected "," (6), (1,7) -</pre> -<p>In the case of complex -expressions, the reported location may not be exactly where you -would expect. See more information under <a class="reference internal" href="#parseexception">ParseException</a> .</p> -</li> -<li><p class="first">Use the <tt class="docutils literal">Group</tt> class to enclose logical groups of tokens within a -sublist. This will help organize your results into more -hierarchical form (the default behavior is to return matching -tokens as a flat list of matching input strings).</p> -</li> -<li><p class="first">Punctuation may be significant for matching, but is rarely of -much interest in the parsed results. Use the <tt class="docutils literal">suppress()</tt> method -to keep these tokens from cluttering up your returned lists of -tokens. For example, <tt class="docutils literal">delimitedList()</tt> matches a succession of -one or more expressions, separated by delimiters (commas by -default), but only returns a list of the actual expressions - -the delimiters are used for parsing, but are suppressed from the -returned output.</p> -</li> -<li><p class="first">Parse actions can be used to convert values from strings to -other data types (ints, floats, booleans, etc.).</p> -</li> -<li><p class="first">Results names are recommended for retrieving tokens from complex -expressions. It is much easier to access a token using its field -name than using a positional index, especially if the expression -contains optional elements. You can also shortcut -the <tt class="docutils literal">setResultsName</tt> call:</p> -<pre class="literal-block"> -stats = "AVE:" + realNum.setResultsName("average") + \ - "MIN:" + realNum.setResultsName("min") + \ - "MAX:" + realNum.setResultsName("max") -</pre> -<p>can now be written as this:</p> -<pre class="literal-block"> -stats = "AVE:" + realNum("average") + \ - "MIN:" + realNum("min") + \ - "MAX:" + realNum("max") -</pre> -</li> -<li><p class="first">Be careful when defining parse actions that modify global variables or -data structures (as in <tt class="docutils literal">fourFn.py</tt>), especially for low level tokens -or expressions that may occur within an <tt class="docutils literal">And</tt> expression; an early element -of an <tt class="docutils literal">And</tt> may match, but the overall expression may fail.</p> -</li> -<li><p class="first">Performance of pyparsing may be slow for complex grammars and/or large -input strings. The <a class="reference external" href="http://psyco.sourceforge.net/">psyco</a> package can be used to improve the speed of the -pyparsing module with no changes to grammar or program logic - observed -improvments have been in the 20-50% range.</p> -</li> -</ul> -</div> -</div> -<div class="section" id="classes"> -<h1><a class="toc-backref" href="#id4">2 Classes</a></h1> -<div class="section" id="classes-in-the-pyparsing-module"> -<h2><a class="toc-backref" href="#id5">2.1 Classes in the pyparsing module</a></h2> -<p><tt class="docutils literal">ParserElement</tt> - abstract base class for all pyparsing classes; -methods for code to use are:</p> -<ul> -<li><p class="first"><tt class="docutils literal">parseString( sourceString, parseAll=False )</tt> - only called once, on the overall -matching pattern; returns a <a class="reference internal" href="#parseresults">ParseResults</a> object that makes the -matched tokens available as a list, and optionally as a dictionary, -or as an object with named attributes; if parseAll is set to True, then -parseString will raise a ParseException if the grammar does not process -the complete input string.</p> -</li> -<li><p class="first"><tt class="docutils literal">parseFile( sourceFile )</tt> - a convenience function, that accepts an -input file object or filename. The file contents are passed as a -string to <tt class="docutils literal">parseString()</tt>. <tt class="docutils literal">parseFile</tt> also supports the <tt class="docutils literal">parseAll</tt> argument.</p> -</li> -<li><p class="first"><tt class="docutils literal">scanString( sourceString )</tt> - generator function, used to find and -extract matching text in the given source string; for each matched text, -returns a tuple of:</p> -<ul class="simple"> -<li>matched tokens (packaged as a <a class="reference internal" href="#parseresults">ParseResults</a> object)</li> -<li>start location of the matched text in the given source string</li> -<li>end location in the given source string</li> -</ul> -<p><tt class="docutils literal">scanString</tt> allows you to scan through the input source string for -random matches, instead of exhaustively defining the grammar for the entire -source text (as would be required with <tt class="docutils literal">parseString</tt>).</p> -</li> -<li><p class="first"><tt class="docutils literal">transformString( sourceString )</tt> - convenience wrapper function for -<tt class="docutils literal">scanString</tt>, to process the input source string, and replace matching -text with the tokens returned from parse actions defined in the grammar -(see <a class="reference internal" href="#setparseaction">setParseAction</a>).</p> -</li> -<li><p class="first"><tt class="docutils literal">searchString( sourceString )</tt> - another convenience wrapper function for -<tt class="docutils literal">scanString</tt>, returns a list of the matching tokens returned from each -call to <tt class="docutils literal">scanString</tt>.</p> -</li> -<li><p class="first"><tt class="docutils literal">setName( name )</tt> - associate a short descriptive name for this -element, useful in displaying exceptions and trace information</p> -</li> -<li><p class="first"><tt class="docutils literal">setResultsName( string, listAllMatches=False )</tt> - name to be given -to tokens matching -the element; if multiple tokens within -a repetition group (such as <tt class="docutils literal">ZeroOrMore</tt> or <tt class="docutils literal">delimitedList</tt>) the -default is to return only the last matching token - if listAllMatches -is set to True, then a list of all the matching tokens is returned. -(New in 1.5.6 - a results name with a trailing '*' character will be -interpreted as setting listAllMatches to True.) -Note: -<tt class="docutils literal">setResultsName</tt> returns a <em>copy</em> of the element so that a single -basic element can be referenced multiple times and given -different names within a complex grammar.</p> -</li> -</ul> -<ul id="setparseaction"> -<li><p class="first"><tt class="docutils literal">setParseAction( *fn )</tt> - specify one or more functions to call after successful -matching of the element; each function is defined as <tt class="docutils literal">fn( s, -loc, toks )</tt>, where:</p> -<ul class="simple"> -<li><tt class="docutils literal">s</tt> is the original parse string</li> -<li><tt class="docutils literal">loc</tt> is the location in the string where matching started</li> -<li><tt class="docutils literal">toks</tt> is the list of the matched tokens, packaged as a <a class="reference internal" href="#parseresults">ParseResults</a> object</li> -</ul> -<p>Multiple functions can be attached to a ParserElement by specifying multiple -arguments to setParseAction, or by calling setParseAction multiple times.</p> -<p>Each parse action function can return a modified <tt class="docutils literal">toks</tt> list, to perform conversion, or -string modifications. For brevity, <tt class="docutils literal">fn</tt> may also be a -lambda - here is an example of using a parse action to convert matched -integer tokens from strings to integers:</p> -<pre class="literal-block"> -intNumber = Word(nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) -</pre> -<p>If <tt class="docutils literal">fn</tt> does not modify the <tt class="docutils literal">toks</tt> list, it does not need to return -anything at all.</p> -</li> -<li><p class="first"><tt class="docutils literal">setBreak( breakFlag=True )</tt> - if breakFlag is True, calls pdb.set_break() -as this expression is about to be parsed</p> -</li> -<li><p class="first"><tt class="docutils literal">copy()</tt> - returns a copy of a ParserElement; can be used to use the same -parse expression in different places in a grammar, with different parse actions -attached to each</p> -</li> -<li><p class="first"><tt class="docutils literal">leaveWhitespace()</tt> - change default behavior of skipping -whitespace before starting matching (mostly used internally to the -pyparsing module, rarely used by client code)</p> -</li> -<li><p class="first"><tt class="docutils literal">setWhitespaceChars( chars )</tt> - define the set of chars to be ignored -as whitespace before trying to match a specific ParserElement, in place of the -default set of whitespace (space, tab, newline, and return)</p> -</li> -<li><p class="first"><tt class="docutils literal">setDefaultWhitespaceChars( chars )</tt> - class-level method to override -the default set of whitespace chars for all subsequently created ParserElements -(including copies); useful when defining grammars that treat one or more of the -default whitespace characters as significant (such as a line-sensitive grammar, to -omit newline from the list of ignorable whitespace)</p> -</li> -<li><p class="first"><tt class="docutils literal">suppress()</tt> - convenience function to suppress the output of the -given element, instead of wrapping it with a Suppress object.</p> -</li> -<li><p class="first"><tt class="docutils literal">ignore( expr )</tt> - function to specify parse expression to be -ignored while matching defined patterns; can be called -repeatedly to specify multiple expressions; useful to specify -patterns of comment syntax, for example</p> -</li> -<li><p class="first"><tt class="docutils literal">setDebug( dbgFlag=True )</tt> - function to enable/disable tracing output -when trying to match this element</p> -</li> -<li><p class="first"><tt class="docutils literal">validate()</tt> - function to verify that the defined grammar does not -contain infinitely recursive constructs</p> -</li> -</ul> -<ul class="simple" id="parsewithtabs"> -<li><tt class="docutils literal">parseWithTabs()</tt> - function to override default behavior of converting -tabs to spaces before parsing the input string; rarely used, except when -specifying whitespace-significant grammars using the <a class="reference internal" href="#white">White</a> class.</li> -<li><tt class="docutils literal">enablePackrat()</tt> - a class-level static method to enable a memoizing -performance enhancement, known as "packrat parsing". packrat parsing is -disabled by default, since it may conflict with some user programs that use -parse actions. To activate the packrat feature, your -program must call the class method ParserElement.enablePackrat(). If -your program uses psyco to "compile as you go", you must call -enablePackrat before calling psyco.full(). If you do not do this, -Python will crash. For best results, call enablePackrat() immediately -after importing pyparsing.</li> -</ul> -</div> -<div class="section" id="basic-parserelement-subclasses"> -<h2><a class="toc-backref" href="#id6">2.2 Basic ParserElement subclasses</a></h2> -<ul class="simple"> -<li><tt class="docutils literal">Literal</tt> - construct with a string to be matched exactly</li> -<li><tt class="docutils literal">CaselessLiteral</tt> - construct with a string to be matched, but -without case checking; results are always returned as the -defining literal, NOT as they are found in the input string</li> -<li><tt class="docutils literal">Keyword</tt> - similar to Literal, but must be immediately followed by -whitespace, punctuation, or other non-keyword characters; prevents -accidental matching of a non-keyword that happens to begin with a -defined keyword</li> -<li><tt class="docutils literal">CaselessKeyword</tt> - similar to Keyword, but with caseless matching -behavior</li> -</ul> -<ul id="word"> -<li><p class="first"><tt class="docutils literal">Word</tt> - one or more contiguous characters; construct with a -string containing the set of allowed initial characters, and an -optional second string of allowed body characters; for instance, -a common Word construct is to match a code identifier - in C, a -valid identifier must start with an alphabetic character or an -underscore ('_'), followed by a body that can also include numeric -digits. That is, <tt class="docutils literal">a</tt>, <tt class="docutils literal">i</tt>, <tt class="docutils literal">MAX_LENGTH</tt>, <tt class="docutils literal">_a1</tt>, <tt class="docutils literal">b_109_</tt>, and -<tt class="docutils literal">plan9FromOuterSpace</tt> -are all valid identifiers; <tt class="docutils literal">9b7z</tt>, <tt class="docutils literal">$a</tt>, <tt class="docutils literal">.section</tt>, and <tt class="docutils literal">0debug</tt> -are not. To -define an identifier using a Word, use either of the following:</p> -<pre class="literal-block"> -- Word( alphas+"_", alphanums+"_" ) -- Word( srange("[a-zA-Z_]"), srange("[a-zA-Z0-9_]") ) -</pre> -<p>If only one -string given, it specifies that the same character set defined -for the initial character is used for the word body; for instance, to -define an identifier that can only be composed of capital letters and -underscores, use:</p> -<pre class="literal-block"> -- Word( "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" ) -- Word( srange("[A-Z_]") ) -</pre> -<p>A Word may -also be constructed with any of the following optional parameters:</p> -<ul class="simple"> -<li>min - indicating a minimum length of matching characters</li> -<li>max - indicating a maximum length of matching characters</li> -<li>exact - indicating an exact length of matching characters</li> -</ul> -<p>If exact is specified, it will override any values for min or max.</p> -<p>New in 1.5.6 - Sometimes you want to define a word using all -characters in a range except for one or two of them; you can do this -with the new excludeChars argument. This is helpful if you want to define -a word with all printables except for a single delimiter character, such -as '.'. Previously, you would have to create a custom string to pass to Word. -With this change, you can just create <tt class="docutils literal">Word(printables, <span class="pre">excludeChars='.')</span></tt>.</p> -</li> -<li><p class="first"><tt class="docutils literal">CharsNotIn</tt> - similar to <a class="reference internal" href="#word">Word</a>, but matches characters not -in the given constructor string (accepts only one string for both -initial and body characters); also supports min, max, and exact -optional parameters.</p> -</li> -<li><p class="first"><tt class="docutils literal">Regex</tt> - a powerful construct, that accepts a regular expression -to be matched at the current parse position; accepts an optional -flags parameter, corresponding to the flags parameter in the re.compile -method; if the expression includes named sub-fields, they will be -represented in the returned <a class="reference internal" href="#parseresults">ParseResults</a></p> -</li> -<li><p class="first"><tt class="docutils literal">QuotedString</tt> - supports the definition of custom quoted string -formats, in addition to pyparsing's built-in dblQuotedString and -sglQuotedString. QuotedString allows you to specify the following -parameters:</p> -<ul class="simple"> -<li>quoteChar - string of one or more characters defining the quote delimiting string</li> -<li>escChar - character to escape quotes, typically backslash (default=None)</li> -<li>escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None)</li> -<li>multiline - boolean indicating whether quotes can span multiple lines (default=False)</li> -<li>unquoteResults - boolean indicating whether the matched text should be unquoted (default=True)</li> -<li>endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar)</li> -</ul> -</li> -<li><p class="first"><tt class="docutils literal">SkipTo</tt> - skips ahead in the input string, accepting any -characters up to the specified pattern; may be constructed with -the following optional parameters:</p> -<ul class="simple"> -<li>include - if set to true, also consumes the match expression -(default is false)</li> -<li>ignore - allows the user to specify patterns to not be matched, -to prevent false matches</li> -<li>failOn - if a literal string or expression is given for this argument, it defines an expression that -should cause the <tt class="docutils literal">SkipTo</tt> expression to fail, and not skip over that expression</li> -</ul> -</li> -</ul> -<ul class="simple" id="white"> -<li><tt class="docutils literal">White</tt> - also similar to <a class="reference internal" href="#word">Word</a>, but matches whitespace -characters. Not usually needed, as whitespace is implicitly -ignored by pyparsing. However, some grammars are whitespace-sensitive, -such as those that use leading tabs or spaces to indicating grouping -or hierarchy. (If matching on tab characters, be sure to call -<a class="reference internal" href="#parsewithtabs">parseWithTabs</a> on the top-level parse element.)</li> -<li><tt class="docutils literal">Empty</tt> - a null expression, requiring no characters - will always -match; useful for debugging and for specialized grammars</li> -<li><tt class="docutils literal">NoMatch</tt> - opposite of Empty, will never match; useful for debugging -and for specialized grammars</li> -</ul> -</div> -<div class="section" id="expression-subclasses"> -<h2><a class="toc-backref" href="#id7">2.3 Expression subclasses</a></h2> -<ul> -<li><p class="first"><tt class="docutils literal">And</tt> - construct with a list of ParserElements, all of which must -match for And to match; can also be created using the '+' -operator; multiple expressions can be Anded together using the '*' -operator as in:</p> -<pre class="literal-block"> -ipAddress = Word(nums) + ('.'+Word(nums))*3 -</pre> -<p>A tuple can be used as the multiplier, indicating a min/max:</p> -<pre class="literal-block"> -usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) -</pre> -<p>A special form of <tt class="docutils literal">And</tt> is created if the '-' operator is used -instead of the '+' operator. In the ipAddress example above, if -no trailing '.' and Word(nums) are found after matching the initial -Word(nums), then pyparsing will back up in the grammar and try other -alternatives to ipAddress. However, if ipAddress is defined as:</p> -<pre class="literal-block"> -strictIpAddress = Word(nums) - ('.'+Word(nums))*3 -</pre> -<p>then no backing up is done. If the first Word(nums) of strictIpAddress -is matched, then any mismatch after that will raise a ParseSyntaxException, -which will halt the parsing process immediately. By careful use of the -'-' operator, grammars can provide meaningful error messages close to -the location where the incoming text does not match the specified -grammar.</p> -</li> -<li><p class="first"><tt class="docutils literal">Or</tt> - construct with a list of ParserElements, any of which must -match for Or to match; if more than one expression matches, the -expression that makes the longest match will be used; can also -be created using the '^' operator</p> -</li> -<li><p class="first"><tt class="docutils literal">MatchFirst</tt> - construct with a list of ParserElements, any of -which must match for MatchFirst to match; matching is done -left-to-right, taking the first expression that matches; can -also be created using the '|' operator</p> -</li> -<li><p class="first"><tt class="docutils literal">Each</tt> - similar to And, in that all of the provided expressions -must match; however, Each permits matching to be done in any order; -can also be created using the '&' operator</p> -</li> -<li><p class="first"><tt class="docutils literal">Optional</tt> - construct with a ParserElement, but this element is -not required to match; can be constructed with an optional <tt class="docutils literal">default</tt> argument, -containing a default string or object to be supplied if the given optional -parse element is not found in the input string; parse action will only -be called if a match is found, or if a default is specified</p> -</li> -<li><p class="first"><tt class="docutils literal">ZeroOrMore</tt> - similar to Optional, but can be repeated</p> -</li> -<li><p class="first"><tt class="docutils literal">OneOrMore</tt> - similar to ZeroOrMore, but at least one match must -be present</p> -</li> -<li><p class="first"><tt class="docutils literal">FollowedBy</tt> - a lookahead expression, requires matching of the given -expressions, but does not advance the parsing position within the input string</p> -</li> -<li><p class="first"><tt class="docutils literal">NotAny</tt> - a negative lookahead expression, prevents matching of named -expressions, does not advance the parsing position within the input string; -can also be created using the unary '~' operator</p> -</li> -</ul> -</div> -<div class="section" id="expression-operators"> -<span id="operators"></span><h2><a class="toc-backref" href="#id8">2.4 Expression operators</a></h2> -<ul class="simple"> -<li><tt class="docutils literal">~</tt> - creates NotAny using the expression after the operator</li> -<li><tt class="docutils literal">+</tt> - creates And using the expressions before and after the operator</li> -<li><tt class="docutils literal">|</tt> - creates MatchFirst (first left-to-right match) using the expressions before and after the operator</li> -<li><tt class="docutils literal">^</tt> - creates Or (longest match) using the expressions before and after the operator</li> -<li><tt class="docutils literal">&</tt> - creates Each using the expressions before and after the operator</li> -<li><tt class="docutils literal">*</tt> - creates And by multiplying the expression by the integer operand; if -expression is multiplied by a 2-tuple, creates an And of (min,max) -expressions (similar to "{min,max}" form in regular expressions); if -min is None, intepret as (0,max); if max is None, interpret as -expr*min + ZeroOrMore(expr)</li> -<li><tt class="docutils literal">-</tt> - like <tt class="docutils literal">+</tt> but with no backup and retry of alternatives</li> -<li><tt class="docutils literal">*</tt> - repetition of expression</li> -<li><tt class="docutils literal">==</tt> - matching expression to string; returns True if the string matches the given expression</li> -<li><tt class="docutils literal"><<</tt> - inserts the expression following the operator as the body of the -Forward expression before the operator</li> -</ul> -</div> -<div class="section" id="positional-subclasses"> -<h2><a class="toc-backref" href="#id9">2.5 Positional subclasses</a></h2> -<ul class="simple"> -<li><tt class="docutils literal">StringStart</tt> - matches beginning of the text</li> -<li><tt class="docutils literal">StringEnd</tt> - matches the end of the text</li> -<li><tt class="docutils literal">LineStart</tt> - matches beginning of a line (lines delimited by <tt class="docutils literal">\n</tt> characters)</li> -<li><tt class="docutils literal">LineEnd</tt> - matches the end of a line</li> -<li><tt class="docutils literal">WordStart</tt> - matches a leading word boundary</li> -<li><tt class="docutils literal">WordEnd</tt> - matches a trailing word boundary</li> -</ul> -</div> -<div class="section" id="converter-subclasses"> -<h2><a class="toc-backref" href="#id10">2.6 Converter subclasses</a></h2> -<ul class="simple"> -<li><tt class="docutils literal">Upcase</tt> - converts matched tokens to uppercase (deprecated - -use <tt class="docutils literal">upcaseTokens</tt> parse action instead)</li> -<li><tt class="docutils literal">Combine</tt> - joins all matched tokens into a single string, using -specified joinString (default <tt class="docutils literal"><span class="pre">joinString=""</span></tt>); expects -all matching tokens to be adjacent, with no intervening -whitespace (can be overridden by specifying <tt class="docutils literal">adjacent=False</tt> in constructor)</li> -<li><tt class="docutils literal">Suppress</tt> - clears matched tokens; useful to keep returned -results from being cluttered with required but uninteresting -tokens (such as list delimiters)</li> -</ul> -</div> -<div class="section" id="special-subclasses"> -<h2><a class="toc-backref" href="#id11">2.7 Special subclasses</a></h2> -<ul class="simple"> -<li><tt class="docutils literal">Group</tt> - causes the matched tokens to be enclosed in a list; -useful in repeated elements like <tt class="docutils literal">ZeroOrMore</tt> and <tt class="docutils literal">OneOrMore</tt> to -break up matched tokens into groups for each repeated pattern</li> -<li><tt class="docutils literal">Dict</tt> - like <tt class="docutils literal">Group</tt>, but also constructs a dictionary, using the -[0]'th elements of all enclosed token lists as the keys, and -each token list as the value</li> -<li><tt class="docutils literal">SkipTo</tt> - catch-all matching expression that accepts all characters -up until the given pattern is found to match; useful for specifying -incomplete grammars</li> -<li><tt class="docutils literal">Forward</tt> - placeholder token used to define recursive token -patterns; when defining the actual expression later in the -program, insert it into the <tt class="docutils literal">Forward</tt> object using the <tt class="docutils literal"><<</tt> -operator (see <tt class="docutils literal">fourFn.py</tt> for an example).</li> -</ul> -</div> -<div class="section" id="other-classes"> -<h2><a class="toc-backref" href="#id12">2.8 Other classes</a></h2> -<ul id="parseresults"> -<li><p class="first"><tt class="docutils literal">ParseResults</tt> - class used to contain and manage the lists of tokens -created from parsing the input using the user-defined parse -expression. ParseResults can be accessed in a number of ways:</p> -<ul class="simple"> -<li>as a list<ul> -<li>total list of elements can be found using len()</li> -<li>individual elements can be found using [0], [1], [-1], etc.</li> -<li>elements can be deleted using <tt class="docutils literal">del</tt></li> -<li>the -1th element can be extracted and removed in a single operation -using <tt class="docutils literal">pop()</tt>, or any element can be extracted and removed -using <tt class="docutils literal">pop(n)</tt></li> -</ul> -</li> -<li>as a dictionary<ul> -<li>if <tt class="docutils literal">setResultsName()</tt> is used to name elements within the -overall parse expression, then these fields can be referenced -as dictionary elements or as attributes</li> -<li>the Dict class generates dictionary entries using the data of the -input text - in addition to ParseResults listed as <tt class="docutils literal">[ [ a1, b1, c1, <span class="pre">...],</span> [ a2, b2, c2, <span class="pre">...]</span> ]</tt> -it also acts as a dictionary with entries defined as <tt class="docutils literal">{ a1 : [ b1, c1, ... ] }, { a2 : [ b2, c2, ... ] }</tt>; -this is especially useful when processing tabular data where the first column contains a key -value for that line of data</li> -<li>list elements that are deleted using <tt class="docutils literal">del</tt> will still be accessible by their -dictionary keys</li> -<li>supports <tt class="docutils literal">get()</tt>, <tt class="docutils literal">items()</tt> and <tt class="docutils literal">keys()</tt> methods, similar to a dictionary</li> -<li>a keyed item can be extracted and removed using <tt class="docutils literal">pop(key)</tt>. Here -key must be non-numeric (such as a string), in order to use dict -extraction instead of list extraction.</li> -<li>new named elements can be added (in a parse action, for instance), using the same -syntax as adding an item to a dict (<tt class="docutils literal"><span class="pre">parseResults["X"]="new</span> item"</tt>); named elements can be removed using <tt class="docutils literal">del <span class="pre">parseResults["X"]</span></tt></li> -</ul> -</li> -<li>as a nested list<ul> -<li>results returned from the Group class are encapsulated within their -own list structure, so that the tokens can be handled as a hierarchical -tree</li> -</ul> -</li> -</ul> -<p>ParseResults can also be converted to an ordinary list of strings -by calling <tt class="docutils literal">asList()</tt>. Note that this will strip the results of any -field names that have been defined for any embedded parse elements. -(The <tt class="docutils literal">pprint</tt> module is especially good at printing out the nested contents -given by <tt class="docutils literal">asList()</tt>.)</p> -<p>Finally, ParseResults can be converted to an XML string by calling <tt class="docutils literal">asXML()</tt>. Where -possible, results will be tagged using the results names defined for the respective -ParseExpressions. <tt class="docutils literal">asXML()</tt> takes two optional arguments:</p> -<ul class="simple"> -<li>doctagname - for ParseResults that do not have a defined name, this argument -will wrap the resulting XML in a set of opening and closing tags <tt class="docutils literal"><doctagname></tt> -and <tt class="docutils literal"></doctagname></tt>.</li> -<li>namedItemsOnly (default=False) - flag to indicate if the generated XML should -skip items that do not have defined names. If a nested group item is named, then all -embedded items will be included, whether they have names or not.</li> -</ul> -</li> -</ul> -</div> -<div class="section" id="exception-classes-and-troubleshooting"> -<h2><a class="toc-backref" href="#id13">2.9 Exception classes and Troubleshooting</a></h2> -<ul id="parseexception"> -<li><p class="first"><tt class="docutils literal">ParseException</tt> - exception returned when a grammar parse fails; -ParseExceptions have attributes loc, msg, line, lineno, and column; to view the -text line and location where the reported ParseException occurs, use:</p> -<pre class="literal-block"> -except ParseException, err: - print err.line - print " "*(err.column-1) + "^" - print err -</pre> -</li> -<li><p class="first"><tt class="docutils literal">RecursiveGrammarException</tt> - exception returned by <tt class="docutils literal">validate()</tt> if -the grammar contains a recursive infinite loop, such as:</p> -<pre class="literal-block"> -badGrammar = Forward() -goodToken = Literal("A") -badGrammar << Optional(goodToken) + badGrammar -</pre> -</li> -<li><p class="first"><tt class="docutils literal">ParseFatalException</tt> - exception that parse actions can raise to stop parsing -immediately. Should be used when a semantic error is found in the input text, such -as a mismatched XML tag.</p> -</li> -<li><p class="first"><tt class="docutils literal">ParseSyntaxException</tt> - subclass of <tt class="docutils literal">ParseFatalException</tt> raised when a -syntax error is found, based on the use of the '-' operator when defining -a sequence of expressions in an <tt class="docutils literal">And</tt> expression.</p> -</li> -</ul> -<p>You can also get some insights into the parsing logic using diagnostic parse actions, -and setDebug(), or test the matching of expression fragments by testing them using -scanString().</p> -</div> -</div> -<div class="section" id="miscellaneous-attributes-and-methods"> -<h1><a class="toc-backref" href="#id14">3 Miscellaneous attributes and methods</a></h1> -<div class="section" id="helper-methods"> -<h2><a class="toc-backref" href="#id15">3.1 Helper methods</a></h2> -<ul> -<li><p class="first"><tt class="docutils literal">delimitedList( expr, <span class="pre">delim=',')</span></tt> - convenience function for -matching one or more occurrences of expr, separated by delim. -By default, the delimiters are suppressed, so the returned results contain -only the separate list elements. Can optionally specify <tt class="docutils literal">combine=True</tt>, -indicating that the expressions and delimiters should be returned as one -combined value (useful for scoped variables, such as "a.b.c", or -"a::b::c", or paths such as "a/b/c").</p> -</li> -<li><p class="first"><tt class="docutils literal">countedArray( expr )</tt> - convenience function for a pattern where an list of -instances of the given expression are preceded by an integer giving the count of -elements in the list. Returns an expression that parses the leading integer, -reads exactly that many expressions, and returns the array of expressions in the -parse results - the leading integer is suppressed from the results (although it -is easily reconstructed by using len on the returned array).</p> -</li> -<li><p class="first"><tt class="docutils literal">oneOf( string, caseless=False )</tt> - convenience function for quickly declaring an -alternative set of <tt class="docutils literal">Literal</tt> tokens, by splitting the given string on -whitespace boundaries. The tokens are sorted so that longer -matches are attempted first; this ensures that a short token does -not mask a longer one that starts with the same characters. If <tt class="docutils literal">caseless=True</tt>, -will create an alternative set of CaselessLiteral tokens.</p> -</li> -<li><p class="first"><tt class="docutils literal">dictOf( key, value )</tt> - convenience function for quickly declaring a -dictionary pattern of <tt class="docutils literal">Dict( ZeroOrMore( Group( key + value ) ) )</tt>.</p> -</li> -<li><p class="first"><tt class="docutils literal">makeHTMLTags( tagName )</tt> and <tt class="docutils literal">makeXMLTags( tagName )</tt> - convenience -functions to create definitions of opening and closing tag expressions. Returns -a pair of expressions, for the corresponding <tag> and </tag> strings. Includes -support for attributes in the opening tag, such as <tag attr1="abc"> - attributes -are returned as keyed tokens in the returned ParseResults. <tt class="docutils literal">makeHTMLTags</tt> is less -restrictive than <tt class="docutils literal">makeXMLTags</tt>, especially with respect to case sensitivity.</p> -</li> -<li><p class="first"><tt class="docutils literal">operatorPrecedence(baseOperand, operatorList)</tt> - convenience function to define a -grammar for parsing -expressions with a hierarchical precedence of operators. To use the operatorPrecedence -helper:</p> -<ol class="arabic simple"> -<li>Define the base "atom" operand term of the grammar. -For this simple grammar, the smallest operand is either -and integer or a variable. This will be the first argument -to the operatorPrecedence method.</li> -<li>Define a list of tuples for each level of operator -precendence. Each tuple is of the form -<tt class="docutils literal">(opExpr, numTerms, rightLeftAssoc, parseAction)</tt>, where:<ul> -<li>opExpr is the pyparsing expression for the operator; -may also be a string, which will be converted to a Literal; if -None, indicates an empty operator, such as the implied -multiplication operation between 'm' and 'x' in "y = mx + b".</li> -<li>numTerms is the number of terms for this operator (must -be 1 or 2)</li> -<li>rightLeftAssoc is the indicator whether the operator is -right or left associative, using the pyparsing-defined -constants <tt class="docutils literal">opAssoc.RIGHT</tt> and <tt class="docutils literal">opAssoc.LEFT</tt>.</li> -<li>parseAction is the parse action to be associated with -expressions matching this operator expression (the -parse action tuple member may be omitted)</li> -</ul> -</li> -<li>Call operatorPrecedence passing the operand expression and -the operator precedence list, and save the returned value -as the generated pyparsing expression. You can then use -this expression to parse input strings, or incorporate it -into a larger, more complex grammar.</li> -</ol> -</li> -<li><p class="first"><tt class="docutils literal">matchPreviousLiteral</tt> and <tt class="docutils literal">matchPreviousExpr</tt> - function to define and -expression that matches the same content -as was parsed in a previous parse expression. For instance:</p> -<pre class="literal-block"> -first = Word(nums) -matchExpr = first + ":" + matchPreviousLiteral(first) -</pre> -<p>will match "1:1", but not "1:2". Since this matches at the literal -level, this will also match the leading "1:1" in "1:10".</p> -<p>In contrast:</p> -<pre class="literal-block"> -first = Word(nums) -matchExpr = first + ":" + matchPreviousExpr(first) -</pre> -<p>will <em>not</em> match the leading "1:1" in "1:10"; the expressions are -evaluated first, and then compared, so "1" is compared with "10".</p> -</li> -<li><p class="first"><tt class="docutils literal">nestedExpr(opener, closer, content=None, ignoreExpr=quotedString)</tt> - method for defining nested -lists enclosed in opening and closing delimiters.</p> -<ul class="simple"> -<li>opener - opening character for a nested list (default="("); can also be a pyparsing expression</li> -<li>closer - closing character for a nested list (default=")"); can also be a pyparsing expression</li> -<li>content - expression for items within the nested lists (default=None)</li> -<li>ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString)</li> -</ul> -<p>If an expression is not provided for the content argument, the nested -expression will capture all whitespace-delimited content between delimiters -as a list of separate values.</p> -<p>Use the ignoreExpr argument to define expressions that may contain -opening or closing characters that should not be treated as opening -or closing characters for nesting, such as quotedString or a comment -expression. Specify multiple expressions using an Or or MatchFirst. -The default is quotedString, but if no expressions are to be ignored, -then pass None for this argument.</p> -</li> -<li><p class="first"><tt class="docutils literal">indentedBlock( statementExpr, indentationStackVar, indent=True)</tt> - -function to define an indented block of statements, similar to -indentation-based blocking in Python source code:</p> -<ul class="simple"> -<li>statementExpr is the expression defining a statement that -will be found in the indented block; a valid indentedBlock -must contain at least 1 matching statementExpr</li> -<li>indentationStackVar is a Python list variable; this variable -should be common to all <tt class="docutils literal">indentedBlock</tt> expressions defined -within the same grammar, and should be reinitialized to [1] -each time the grammar is to be used</li> -<li>indent is a boolean flag indicating whether the expressions -within the block must be indented from the current parse -location; if using indentedBlock to define the left-most -statements (all starting in column 1), set indent to False</li> -</ul> -</li> -</ul> -<ul id="originaltextfor"> -<li><p class="first"><tt class="docutils literal">originalTextFor( expr )</tt> - helper function to preserve the originally parsed text, regardless of any -token processing or conversion done by the contained expression. For instance, the following expression:</p> -<pre class="literal-block"> -fullName = Word(alphas) + Word(alphas) -</pre> -<p>will return the parse of "John Smith" as ['John', 'Smith']. In some applications, the actual name as it -was given in the input string is what is desired. To do this, use <tt class="docutils literal">originalTextFor</tt>:</p> -<pre class="literal-block"> -fullName = originalTextFor(Word(alphas) + Word(alphas)) -</pre> -</li> -<li><p class="first"><tt class="docutils literal">ungroup( expr )</tt> - function to "ungroup" returned tokens; useful -to undo the default behavior of And to always group the returned tokens, even -if there is only one in the list. (New in 1.5.6)</p> -</li> -<li><p class="first"><tt class="docutils literal">lineno( loc, string )</tt> - function to give the line number of the -location within the string; the first line is line 1, newlines -start new rows</p> -</li> -<li><p class="first"><tt class="docutils literal">col( loc, string )</tt> - function to give the column number of the -location within the string; the first column is column 1, -newlines reset the column number to 1</p> -</li> -<li><p class="first"><tt class="docutils literal">line( loc, string )</tt> - function to retrieve the line of text -representing <tt class="docutils literal">lineno( loc, string )</tt>; useful when printing out diagnostic -messages for exceptions</p> -</li> -<li><p class="first"><tt class="docutils literal">srange( rangeSpec )</tt> - function to define a string of characters, -given a string of the form used by regexp string ranges, such as <tt class="docutils literal"><span class="pre">"[0-9]"</span></tt> for -all numeric digits, <tt class="docutils literal"><span class="pre">"[A-Z_]"</span></tt> for uppercase characters plus underscore, and -so on (note that rangeSpec does not include support for generic regular -expressions, just string range specs)</p> -</li> -<li><p class="first"><tt class="docutils literal">getTokensEndLoc()</tt> - function to call from within a parse action to get -the ending location for the matched tokens</p> -</li> -<li><p class="first"><tt class="docutils literal">traceParseAction(fn)</tt> - decorator function to debug parse actions. Lists -each call, called arguments, and return value or exception</p> -</li> -</ul> -</div> -<div class="section" id="helper-parse-actions"> -<h2><a class="toc-backref" href="#id16">3.2 Helper parse actions</a></h2> -<ul> -<li><p class="first"><tt class="docutils literal">removeQuotes</tt> - removes the first and last characters of a quoted string; -useful to remove the delimiting quotes from quoted strings</p> -</li> -<li><p class="first"><tt class="docutils literal">replaceWith(replString)</tt> - returns a parse action that simply returns the -replString; useful when using transformString, or converting HTML entities, as in:</p> -<pre class="literal-block"> -nbsp = Literal("&nbsp;").setParseAction( replaceWith("<BLANK>") ) -</pre> -</li> -<li><p class="first"><tt class="docutils literal">keepOriginalText</tt>- (deprecated, use <a class="reference internal" href="#originaltextfor">originalTextFor</a> instead) restores any internal whitespace or suppressed -text within the tokens for a matched parse -expression. This is especially useful when defining expressions -for scanString or transformString applications.</p> -</li> -<li><p class="first"><tt class="docutils literal">withAttribute( *args, **kwargs )</tt> - helper to create a validating parse action to be used with start tags created -with <tt class="docutils literal">makeXMLTags</tt> or <tt class="docutils literal">makeHTMLTags</tt>. Use <tt class="docutils literal">withAttribute</tt> to qualify a starting tag -with a required attribute value, to avoid false matches on common tags such as -<tt class="docutils literal"><TD></tt> or <tt class="docutils literal"><DIV></tt>.</p> -<p><tt class="docutils literal">withAttribute</tt> can be called with:</p> -<ul class="simple"> -<li>keyword arguments, as in <tt class="docutils literal"><span class="pre">(class="Customer",align="right")</span></tt>, or</li> -<li>a list of name-value tuples, as in <tt class="docutils literal">( ("ns1:class", <span class="pre">"Customer"),</span> <span class="pre">("ns2:align","right")</span> )</tt></li> -</ul> -<p>An attribute can be specified to have the special value -<tt class="docutils literal">withAttribute.ANY_VALUE</tt>, which will match any value - use this to -ensure that an attribute is present but any attribute value is -acceptable.</p> -</li> -<li><p class="first"><tt class="docutils literal">downcaseTokens</tt> - converts all matched tokens to lowercase</p> -</li> -<li><p class="first"><tt class="docutils literal">upcaseTokens</tt> - converts all matched tokens to uppercase</p> -</li> -<li><p class="first"><tt class="docutils literal">matchOnlyAtCol( columnNumber )</tt> - a parse action that verifies that -an expression was matched at a particular column, raising a -ParseException if matching at a different column number; useful when parsing -tabular data</p> -</li> -</ul> -</div> -<div class="section" id="common-string-and-token-constants"> -<h2><a class="toc-backref" href="#id17">3.3 Common string and token constants</a></h2> -<ul> -<li><p class="first"><tt class="docutils literal">alphas</tt> - same as <tt class="docutils literal">string.letters</tt></p> -</li> -<li><p class="first"><tt class="docutils literal">nums</tt> - same as <tt class="docutils literal">string.digits</tt></p> -</li> -<li><p class="first"><tt class="docutils literal">alphanums</tt> - a string containing <tt class="docutils literal">alphas + nums</tt></p> -</li> -<li><p class="first"><tt class="docutils literal">alphas8bit</tt> - a string containing alphabetic 8-bit characters:</p> -<pre class="literal-block"> -ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ -</pre> -</li> -<li><p class="first"><tt class="docutils literal">printables</tt> - same as <tt class="docutils literal">string.printable</tt>, minus the space (<tt class="docutils literal">' '</tt>) character</p> -</li> -<li><p class="first"><tt class="docutils literal">empty</tt> - a global <tt class="docutils literal">Empty()</tt>; will always match</p> -</li> -<li><p class="first"><tt class="docutils literal">sglQuotedString</tt> - a string of characters enclosed in 's; may -include whitespace, but not newlines</p> -</li> -<li><p class="first"><tt class="docutils literal">dblQuotedString</tt> - a string of characters enclosed in "s; may -include whitespace, but not newlines</p> -</li> -<li><p class="first"><tt class="docutils literal">quotedString</tt> - <tt class="docutils literal">sglQuotedString | dblQuotedString</tt></p> -</li> -<li><p class="first"><tt class="docutils literal">cStyleComment</tt> - a comment block delimited by <tt class="docutils literal"><span class="pre">'/*'</span></tt> and <tt class="docutils literal"><span class="pre">'*/'</span></tt> sequences; can span -multiple lines, but does not support nesting of comments</p> -</li> -<li><p class="first"><tt class="docutils literal">htmlComment</tt> - a comment block delimited by <tt class="docutils literal"><span class="pre">'<!--'</span></tt> and <tt class="docutils literal"><span class="pre">'-->'</span></tt> sequences; can span -multiple lines, but does not support nesting of comments</p> -</li> -<li><p class="first"><tt class="docutils literal">commaSeparatedList</tt> - similar to <tt class="docutils literal">delimitedList</tt>, except that the -list expressions can be any text value, or a quoted string; quoted strings can -safely include commas without incorrectly breaking the string into two tokens</p> -</li> -<li><p class="first"><tt class="docutils literal">restOfLine</tt> - all remaining printable characters up to but not including the next -newline</p> -</li> -</ul> -</div> -</div> -</div> -</body> -</html> diff --git a/src/HowToUsePyparsing.txt b/src/HowToUsePyparsing.txt deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL0hvd1RvVXNlUHlwYXJzaW5nLnR4dA==..0000000000000000000000000000000000000000 --- a/src/HowToUsePyparsing.txt +++ /dev/null @@ -1,993 +0,0 @@ -========================== -Using the pyparsing module -========================== - -:author: Paul McGuire -:address: ptmcg@users.sourceforge.net - -:revision: 1.5.6 -:date: June, 2011 - -:copyright: Copyright |copy| 2003-2011 Paul McGuire. - -.. |copy| unicode:: 0xA9 - -:abstract: This document provides how-to instructions for the - pyparsing library, an easy-to-use Python module for constructing - and executing basic text parsers. The pyparsing module is useful - for evaluating user-definable - expressions, processing custom application language commands, or - extracting data from formatted reports. - -.. sectnum:: :depth: 4 - -.. contents:: :depth: 4 - - -Steps to follow -=============== - -To parse an incoming data string, the client code must follow these steps: - -1. First define the tokens and patterns to be matched, and assign - this to a program variable. Optional results names or parsing - actions can also be defined at this time. - -2. Call ``parseString()`` or ``scanString()`` on this variable, passing in - the string to - be parsed. During the matching process, whitespace between - tokens is skipped by default (although this can be changed). - When token matches occur, any defined parse action methods are - called. - -3. Process the parsed results, returned as a list of strings. - Matching results may also be accessed as named attributes of - the returned results, if names are defined in the definition of - the token pattern, using ``setResultsName()``. - - -Hello, World! -------------- - -The following complete Python program will parse the greeting "Hello, World!", -or any other greeting of the form "<salutation>, <addressee>!":: - - from pyparsing import Word, alphas - - greet = Word( alphas ) + "," + Word( alphas ) + "!" - greeting = greet.parseString( "Hello, World!" ) - print greeting - -The parsed tokens are returned in the following form:: - - ['Hello', ',', 'World', '!'] - - -Usage notes ------------ - -- The pyparsing module can be used to interpret simple command - strings or algebraic expressions, or can be used to extract data - from text reports with complicated format and structure ("screen - or report scraping"). However, it is possible that your defined - matching patterns may accept invalid inputs. Use pyparsing to - extract data from strings assumed to be well-formatted. - -- To keep up the readability of your code, use operators_ such as ``+``, ``|``, - ``^``, and ``~`` to combine expressions. You can also combine - string literals with ParseExpressions - they will be - automatically converted to Literal objects. For example:: - - integer = Word( nums ) # simple unsigned integer - variable = Word( alphas, max=1 ) # single letter variable, such as x, z, m, etc. - arithOp = Word( "+-*/", max=1 ) # arithmetic operators - equation = variable + "=" + integer + arithOp + integer # will match "x=2+2", etc. - - In the definition of ``equation``, the string ``"="`` will get added as - a ``Literal("=")``, but in a more readable way. - -- The pyparsing module's default behavior is to ignore whitespace. This is the - case for 99% of all parsers ever written. This allows you to write simple, clean, - grammars, such as the above ``equation``, without having to clutter it up with - extraneous ``ws`` markers. The ``equation`` grammar will successfully parse all of the - following statements:: - - x=2+2 - x = 2+2 - a = 10 * 4 - r= 1234/ 100000 - - Of course, it is quite simple to extend this example to support more elaborate expressions, with - nesting with parentheses, floating point numbers, scientific notation, and named constants - (such as ``e`` or ``pi``). See ``fourFn.py``, included in the examples directory. - -- To modify pyparsing's default whitespace skipping, you can use one or - more of the following methods: - - - use the static method ``ParserElement.setDefaultWhitespaceChars`` - to override the normal set of whitespace chars (' \t\n'). For instance - when defining a grammar in which newlines are significant, you should - call ``ParserElement.setDefaultWhitespaceChars(' \t')`` to remove - newline from the set of skippable whitespace characters. Calling - this method will affect all pyparsing expressions defined afterward. - - - call ``leaveWhitespace()`` on individual expressions, to suppress the - skipping of whitespace before trying to match the expression - - - use ``Combine`` to require that successive expressions must be - adjacent in the input string. For instance, this expression:: - - real = Word(nums) + '.' + Word(nums) - - will match "3.14159", but will also match "3 . 12". It will also - return the matched results as ['3', '.', '14159']. By changing this - expression to:: - - real = Combine( Word(nums) + '.' + Word(nums) ) - - it will not match numbers with embedded spaces, and it will return a - single concatenated string '3.14159' as the parsed token. - -- Repetition of expressions can be indicated using the '*' operator. An - expression may be multiplied by an integer value (to indicate an exact - repetition count), or by a tuple containing - two integers, or None and an integer, representing min and max repetitions - (with None representing no min or no max, depending whether it is the first or - second tuple element). See the following examples, where n is used to - indicate an integer value: - - - ``expr*3`` is equivalent to ``expr + expr + expr`` - - - ``expr*(2,3)`` is equivalent to ``expr + expr + Optional(expr)`` - - - ``expr*(n,None)`` or ``expr*(n,)`` is equivalent - to ``expr*n + ZeroOrMore(expr)`` (read as "at least n instances of expr") - - - ``expr*(None,n)`` is equivalent to ``expr*(0,n)`` - (read as "0 to n instances of expr") - - - ``expr*(None,None)`` is equivalent to ``ZeroOrMore(expr)`` - - - ``expr*(1,None)`` is equivalent to ``OneOrMore(expr)`` - - Note that ``expr*(None,n)`` does not raise an exception if - more than n exprs exist in the input stream; that is, - ``expr*(None,n)`` does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - ``expr*(None,n) + ~expr``. - -- ``MatchFirst`` expressions are matched left-to-right, and the first - match found will skip all later expressions within, so be sure - to define less-specific patterns after more-specific patterns. - If you are not sure which expressions are most specific, use Or - expressions (defined using the ``^`` operator) - they will always - match the longest expression, although they are more - compute-intensive. - -- ``Or`` expressions will evaluate all of the specified subexpressions - to determine which is the "best" match, that is, which matches - the longest string in the input data. In case of a tie, the - left-most expression in the ``Or`` list will win. - -- If parsing the contents of an entire file, pass it to the - ``parseFile`` method using:: - - expr.parseFile( sourceFile ) - -- ``ParseExceptions`` will report the location where an expected token - or expression failed to match. For example, if we tried to use our - "Hello, World!" parser to parse "Hello World!" (leaving out the separating - comma), we would get an exception, with the message:: - - pyparsing.ParseException: Expected "," (6), (1,7) - - In the case of complex - expressions, the reported location may not be exactly where you - would expect. See more information under ParseException_ . - -- Use the ``Group`` class to enclose logical groups of tokens within a - sublist. This will help organize your results into more - hierarchical form (the default behavior is to return matching - tokens as a flat list of matching input strings). - -- Punctuation may be significant for matching, but is rarely of - much interest in the parsed results. Use the ``suppress()`` method - to keep these tokens from cluttering up your returned lists of - tokens. For example, ``delimitedList()`` matches a succession of - one or more expressions, separated by delimiters (commas by - default), but only returns a list of the actual expressions - - the delimiters are used for parsing, but are suppressed from the - returned output. - -- Parse actions can be used to convert values from strings to - other data types (ints, floats, booleans, etc.). - -- Results names are recommended for retrieving tokens from complex - expressions. It is much easier to access a token using its field - name than using a positional index, especially if the expression - contains optional elements. You can also shortcut - the ``setResultsName`` call:: - - stats = "AVE:" + realNum.setResultsName("average") + \ - "MIN:" + realNum.setResultsName("min") + \ - "MAX:" + realNum.setResultsName("max") - - can now be written as this:: - - stats = "AVE:" + realNum("average") + \ - "MIN:" + realNum("min") + \ - "MAX:" + realNum("max") - -- Be careful when defining parse actions that modify global variables or - data structures (as in ``fourFn.py``), especially for low level tokens - or expressions that may occur within an ``And`` expression; an early element - of an ``And`` may match, but the overall expression may fail. - -- Performance of pyparsing may be slow for complex grammars and/or large - input strings. The psyco_ package can be used to improve the speed of the - pyparsing module with no changes to grammar or program logic - observed - improvments have been in the 20-50% range. - -.. _psyco: http://psyco.sourceforge.net/ - - -Classes -======= - -Classes in the pyparsing module -------------------------------- - -``ParserElement`` - abstract base class for all pyparsing classes; -methods for code to use are: - -- ``parseString( sourceString, parseAll=False )`` - only called once, on the overall - matching pattern; returns a ParseResults_ object that makes the - matched tokens available as a list, and optionally as a dictionary, - or as an object with named attributes; if parseAll is set to True, then - parseString will raise a ParseException if the grammar does not process - the complete input string. - -- ``parseFile( sourceFile )`` - a convenience function, that accepts an - input file object or filename. The file contents are passed as a - string to ``parseString()``. ``parseFile`` also supports the ``parseAll`` argument. - -- ``scanString( sourceString )`` - generator function, used to find and - extract matching text in the given source string; for each matched text, - returns a tuple of: - - - matched tokens (packaged as a ParseResults_ object) - - - start location of the matched text in the given source string - - - end location in the given source string - - ``scanString`` allows you to scan through the input source string for - random matches, instead of exhaustively defining the grammar for the entire - source text (as would be required with ``parseString``). - -- ``transformString( sourceString )`` - convenience wrapper function for - ``scanString``, to process the input source string, and replace matching - text with the tokens returned from parse actions defined in the grammar - (see setParseAction_). - -- ``searchString( sourceString )`` - another convenience wrapper function for - ``scanString``, returns a list of the matching tokens returned from each - call to ``scanString``. - -- ``setName( name )`` - associate a short descriptive name for this - element, useful in displaying exceptions and trace information - -- ``setResultsName( string, listAllMatches=False )`` - name to be given - to tokens matching - the element; if multiple tokens within - a repetition group (such as ``ZeroOrMore`` or ``delimitedList``) the - default is to return only the last matching token - if listAllMatches - is set to True, then a list of all the matching tokens is returned. - (New in 1.5.6 - a results name with a trailing '*' character will be - interpreted as setting listAllMatches to True.) - Note: - ``setResultsName`` returns a *copy* of the element so that a single - basic element can be referenced multiple times and given - different names within a complex grammar. - -.. _setParseAction: - -- ``setParseAction( *fn )`` - specify one or more functions to call after successful - matching of the element; each function is defined as ``fn( s, - loc, toks )``, where: - - - ``s`` is the original parse string - - - ``loc`` is the location in the string where matching started - - - ``toks`` is the list of the matched tokens, packaged as a ParseResults_ object - - Multiple functions can be attached to a ParserElement by specifying multiple - arguments to setParseAction, or by calling setParseAction multiple times. - - Each parse action function can return a modified ``toks`` list, to perform conversion, or - string modifications. For brevity, ``fn`` may also be a - lambda - here is an example of using a parse action to convert matched - integer tokens from strings to integers:: - - intNumber = Word(nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) - - If ``fn`` does not modify the ``toks`` list, it does not need to return - anything at all. - -- ``setBreak( breakFlag=True )`` - if breakFlag is True, calls pdb.set_break() - as this expression is about to be parsed - -- ``copy()`` - returns a copy of a ParserElement; can be used to use the same - parse expression in different places in a grammar, with different parse actions - attached to each - -- ``leaveWhitespace()`` - change default behavior of skipping - whitespace before starting matching (mostly used internally to the - pyparsing module, rarely used by client code) - -- ``setWhitespaceChars( chars )`` - define the set of chars to be ignored - as whitespace before trying to match a specific ParserElement, in place of the - default set of whitespace (space, tab, newline, and return) - -- ``setDefaultWhitespaceChars( chars )`` - class-level method to override - the default set of whitespace chars for all subsequently created ParserElements - (including copies); useful when defining grammars that treat one or more of the - default whitespace characters as significant (such as a line-sensitive grammar, to - omit newline from the list of ignorable whitespace) - -- ``suppress()`` - convenience function to suppress the output of the - given element, instead of wrapping it with a Suppress object. - -- ``ignore( expr )`` - function to specify parse expression to be - ignored while matching defined patterns; can be called - repeatedly to specify multiple expressions; useful to specify - patterns of comment syntax, for example - -- ``setDebug( dbgFlag=True )`` - function to enable/disable tracing output - when trying to match this element - -- ``validate()`` - function to verify that the defined grammar does not - contain infinitely recursive constructs - -.. _parseWithTabs: - -- ``parseWithTabs()`` - function to override default behavior of converting - tabs to spaces before parsing the input string; rarely used, except when - specifying whitespace-significant grammars using the White_ class. - -- ``enablePackrat()`` - a class-level static method to enable a memoizing - performance enhancement, known as "packrat parsing". packrat parsing is - disabled by default, since it may conflict with some user programs that use - parse actions. To activate the packrat feature, your - program must call the class method ParserElement.enablePackrat(). If - your program uses psyco to "compile as you go", you must call - enablePackrat before calling psyco.full(). If you do not do this, - Python will crash. For best results, call enablePackrat() immediately - after importing pyparsing. - - -Basic ParserElement subclasses ------------------------------- - -- ``Literal`` - construct with a string to be matched exactly - -- ``CaselessLiteral`` - construct with a string to be matched, but - without case checking; results are always returned as the - defining literal, NOT as they are found in the input string - -- ``Keyword`` - similar to Literal, but must be immediately followed by - whitespace, punctuation, or other non-keyword characters; prevents - accidental matching of a non-keyword that happens to begin with a - defined keyword - -- ``CaselessKeyword`` - similar to Keyword, but with caseless matching - behavior - -.. _Word: - -- ``Word`` - one or more contiguous characters; construct with a - string containing the set of allowed initial characters, and an - optional second string of allowed body characters; for instance, - a common Word construct is to match a code identifier - in C, a - valid identifier must start with an alphabetic character or an - underscore ('_'), followed by a body that can also include numeric - digits. That is, ``a``, ``i``, ``MAX_LENGTH``, ``_a1``, ``b_109_``, and - ``plan9FromOuterSpace`` - are all valid identifiers; ``9b7z``, ``$a``, ``.section``, and ``0debug`` - are not. To - define an identifier using a Word, use either of the following:: - - - Word( alphas+"_", alphanums+"_" ) - - Word( srange("[a-zA-Z_]"), srange("[a-zA-Z0-9_]") ) - - If only one - string given, it specifies that the same character set defined - for the initial character is used for the word body; for instance, to - define an identifier that can only be composed of capital letters and - underscores, use:: - - - Word( "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" ) - - Word( srange("[A-Z_]") ) - - A Word may - also be constructed with any of the following optional parameters: - - - min - indicating a minimum length of matching characters - - - max - indicating a maximum length of matching characters - - - exact - indicating an exact length of matching characters - - If exact is specified, it will override any values for min or max. - - New in 1.5.6 - Sometimes you want to define a word using all - characters in a range except for one or two of them; you can do this - with the new excludeChars argument. This is helpful if you want to define - a word with all printables except for a single delimiter character, such - as '.'. Previously, you would have to create a custom string to pass to Word. - With this change, you can just create ``Word(printables, excludeChars='.')``. - -- ``CharsNotIn`` - similar to Word_, but matches characters not - in the given constructor string (accepts only one string for both - initial and body characters); also supports min, max, and exact - optional parameters. - -- ``Regex`` - a powerful construct, that accepts a regular expression - to be matched at the current parse position; accepts an optional - flags parameter, corresponding to the flags parameter in the re.compile - method; if the expression includes named sub-fields, they will be - represented in the returned ParseResults_ - -- ``QuotedString`` - supports the definition of custom quoted string - formats, in addition to pyparsing's built-in dblQuotedString and - sglQuotedString. QuotedString allows you to specify the following - parameters: - - - quoteChar - string of one or more characters defining the quote delimiting string - - - escChar - character to escape quotes, typically backslash (default=None) - - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) - - - multiline - boolean indicating whether quotes can span multiple lines (default=False) - - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) - - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) - -- ``SkipTo`` - skips ahead in the input string, accepting any - characters up to the specified pattern; may be constructed with - the following optional parameters: - - - include - if set to true, also consumes the match expression - (default is false) - - - ignore - allows the user to specify patterns to not be matched, - to prevent false matches - - - failOn - if a literal string or expression is given for this argument, it defines an expression that - should cause the ``SkipTo`` expression to fail, and not skip over that expression - -.. _White: - -- ``White`` - also similar to Word_, but matches whitespace - characters. Not usually needed, as whitespace is implicitly - ignored by pyparsing. However, some grammars are whitespace-sensitive, - such as those that use leading tabs or spaces to indicating grouping - or hierarchy. (If matching on tab characters, be sure to call - parseWithTabs_ on the top-level parse element.) - -- ``Empty`` - a null expression, requiring no characters - will always - match; useful for debugging and for specialized grammars - -- ``NoMatch`` - opposite of Empty, will never match; useful for debugging - and for specialized grammars - - -Expression subclasses ---------------------- - -- ``And`` - construct with a list of ParserElements, all of which must - match for And to match; can also be created using the '+' - operator; multiple expressions can be Anded together using the '*' - operator as in:: - - ipAddress = Word(nums) + ('.'+Word(nums))*3 - - A tuple can be used as the multiplier, indicating a min/max:: - - usPhoneNumber = Word(nums) + ('-'+Word(nums))*(1,2) - - A special form of ``And`` is created if the '-' operator is used - instead of the '+' operator. In the ipAddress example above, if - no trailing '.' and Word(nums) are found after matching the initial - Word(nums), then pyparsing will back up in the grammar and try other - alternatives to ipAddress. However, if ipAddress is defined as:: - - strictIpAddress = Word(nums) - ('.'+Word(nums))*3 - - then no backing up is done. If the first Word(nums) of strictIpAddress - is matched, then any mismatch after that will raise a ParseSyntaxException, - which will halt the parsing process immediately. By careful use of the - '-' operator, grammars can provide meaningful error messages close to - the location where the incoming text does not match the specified - grammar. - -- ``Or`` - construct with a list of ParserElements, any of which must - match for Or to match; if more than one expression matches, the - expression that makes the longest match will be used; can also - be created using the '^' operator - -- ``MatchFirst`` - construct with a list of ParserElements, any of - which must match for MatchFirst to match; matching is done - left-to-right, taking the first expression that matches; can - also be created using the '|' operator - -- ``Each`` - similar to And, in that all of the provided expressions - must match; however, Each permits matching to be done in any order; - can also be created using the '&' operator - -- ``Optional`` - construct with a ParserElement, but this element is - not required to match; can be constructed with an optional ``default`` argument, - containing a default string or object to be supplied if the given optional - parse element is not found in the input string; parse action will only - be called if a match is found, or if a default is specified - -- ``ZeroOrMore`` - similar to Optional, but can be repeated - -- ``OneOrMore`` - similar to ZeroOrMore, but at least one match must - be present - -- ``FollowedBy`` - a lookahead expression, requires matching of the given - expressions, but does not advance the parsing position within the input string - -- ``NotAny`` - a negative lookahead expression, prevents matching of named - expressions, does not advance the parsing position within the input string; - can also be created using the unary '~' operator - - -.. _operators: - -Expression operators --------------------- - -- ``~`` - creates NotAny using the expression after the operator - -- ``+`` - creates And using the expressions before and after the operator - -- ``|`` - creates MatchFirst (first left-to-right match) using the expressions before and after the operator - -- ``^`` - creates Or (longest match) using the expressions before and after the operator - -- ``&`` - creates Each using the expressions before and after the operator - -- ``*`` - creates And by multiplying the expression by the integer operand; if - expression is multiplied by a 2-tuple, creates an And of (min,max) - expressions (similar to "{min,max}" form in regular expressions); if - min is None, intepret as (0,max); if max is None, interpret as - expr*min + ZeroOrMore(expr) - -- ``-`` - like ``+`` but with no backup and retry of alternatives - -- ``*`` - repetition of expression - -- ``==`` - matching expression to string; returns True if the string matches the given expression - -- ``<<`` - inserts the expression following the operator as the body of the - Forward expression before the operator - - - -Positional subclasses ---------------------- - -- ``StringStart`` - matches beginning of the text - -- ``StringEnd`` - matches the end of the text - -- ``LineStart`` - matches beginning of a line (lines delimited by ``\n`` characters) - -- ``LineEnd`` - matches the end of a line - -- ``WordStart`` - matches a leading word boundary - -- ``WordEnd`` - matches a trailing word boundary - - - -Converter subclasses --------------------- - -- ``Upcase`` - converts matched tokens to uppercase (deprecated - - use ``upcaseTokens`` parse action instead) - -- ``Combine`` - joins all matched tokens into a single string, using - specified joinString (default ``joinString=""``); expects - all matching tokens to be adjacent, with no intervening - whitespace (can be overridden by specifying ``adjacent=False`` in constructor) - -- ``Suppress`` - clears matched tokens; useful to keep returned - results from being cluttered with required but uninteresting - tokens (such as list delimiters) - - -Special subclasses ------------------- - -- ``Group`` - causes the matched tokens to be enclosed in a list; - useful in repeated elements like ``ZeroOrMore`` and ``OneOrMore`` to - break up matched tokens into groups for each repeated pattern - -- ``Dict`` - like ``Group``, but also constructs a dictionary, using the - [0]'th elements of all enclosed token lists as the keys, and - each token list as the value - -- ``SkipTo`` - catch-all matching expression that accepts all characters - up until the given pattern is found to match; useful for specifying - incomplete grammars - -- ``Forward`` - placeholder token used to define recursive token - patterns; when defining the actual expression later in the - program, insert it into the ``Forward`` object using the ``<<`` - operator (see ``fourFn.py`` for an example). - - -Other classes -------------- -.. _ParseResults: - -- ``ParseResults`` - class used to contain and manage the lists of tokens - created from parsing the input using the user-defined parse - expression. ParseResults can be accessed in a number of ways: - - - as a list - - - total list of elements can be found using len() - - - individual elements can be found using [0], [1], [-1], etc. - - - elements can be deleted using ``del`` - - - the -1th element can be extracted and removed in a single operation - using ``pop()``, or any element can be extracted and removed - using ``pop(n)`` - - - as a dictionary - - - if ``setResultsName()`` is used to name elements within the - overall parse expression, then these fields can be referenced - as dictionary elements or as attributes - - - the Dict class generates dictionary entries using the data of the - input text - in addition to ParseResults listed as ``[ [ a1, b1, c1, ...], [ a2, b2, c2, ...] ]`` - it also acts as a dictionary with entries defined as ``{ a1 : [ b1, c1, ... ] }, { a2 : [ b2, c2, ... ] }``; - this is especially useful when processing tabular data where the first column contains a key - value for that line of data - - - list elements that are deleted using ``del`` will still be accessible by their - dictionary keys - - - supports ``get()``, ``items()`` and ``keys()`` methods, similar to a dictionary - - - a keyed item can be extracted and removed using ``pop(key)``. Here - key must be non-numeric (such as a string), in order to use dict - extraction instead of list extraction. - - - new named elements can be added (in a parse action, for instance), using the same - syntax as adding an item to a dict (``parseResults["X"]="new item"``); named elements can be removed using ``del parseResults["X"]`` - - - as a nested list - - - results returned from the Group class are encapsulated within their - own list structure, so that the tokens can be handled as a hierarchical - tree - - ParseResults can also be converted to an ordinary list of strings - by calling ``asList()``. Note that this will strip the results of any - field names that have been defined for any embedded parse elements. - (The ``pprint`` module is especially good at printing out the nested contents - given by ``asList()``.) - - Finally, ParseResults can be converted to an XML string by calling ``asXML()``. Where - possible, results will be tagged using the results names defined for the respective - ParseExpressions. ``asXML()`` takes two optional arguments: - - - doctagname - for ParseResults that do not have a defined name, this argument - will wrap the resulting XML in a set of opening and closing tags ``<doctagname>`` - and ``</doctagname>``. - - - namedItemsOnly (default=False) - flag to indicate if the generated XML should - skip items that do not have defined names. If a nested group item is named, then all - embedded items will be included, whether they have names or not. - - -Exception classes and Troubleshooting -------------------------------------- - -.. _ParseException: - -- ``ParseException`` - exception returned when a grammar parse fails; - ParseExceptions have attributes loc, msg, line, lineno, and column; to view the - text line and location where the reported ParseException occurs, use:: - - except ParseException, err: - print err.line - print " "*(err.column-1) + "^" - print err - -- ``RecursiveGrammarException`` - exception returned by ``validate()`` if - the grammar contains a recursive infinite loop, such as:: - - badGrammar = Forward() - goodToken = Literal("A") - badGrammar << Optional(goodToken) + badGrammar - -- ``ParseFatalException`` - exception that parse actions can raise to stop parsing - immediately. Should be used when a semantic error is found in the input text, such - as a mismatched XML tag. - -- ``ParseSyntaxException`` - subclass of ``ParseFatalException`` raised when a - syntax error is found, based on the use of the '-' operator when defining - a sequence of expressions in an ``And`` expression. - -You can also get some insights into the parsing logic using diagnostic parse actions, -and setDebug(), or test the matching of expression fragments by testing them using -scanString(). - - -Miscellaneous attributes and methods -==================================== - -Helper methods --------------- - -- ``delimitedList( expr, delim=',')`` - convenience function for - matching one or more occurrences of expr, separated by delim. - By default, the delimiters are suppressed, so the returned results contain - only the separate list elements. Can optionally specify ``combine=True``, - indicating that the expressions and delimiters should be returned as one - combined value (useful for scoped variables, such as "a.b.c", or - "a::b::c", or paths such as "a/b/c"). - -- ``countedArray( expr )`` - convenience function for a pattern where an list of - instances of the given expression are preceded by an integer giving the count of - elements in the list. Returns an expression that parses the leading integer, - reads exactly that many expressions, and returns the array of expressions in the - parse results - the leading integer is suppressed from the results (although it - is easily reconstructed by using len on the returned array). - -- ``oneOf( string, caseless=False )`` - convenience function for quickly declaring an - alternative set of ``Literal`` tokens, by splitting the given string on - whitespace boundaries. The tokens are sorted so that longer - matches are attempted first; this ensures that a short token does - not mask a longer one that starts with the same characters. If ``caseless=True``, - will create an alternative set of CaselessLiteral tokens. - -- ``dictOf( key, value )`` - convenience function for quickly declaring a - dictionary pattern of ``Dict( ZeroOrMore( Group( key + value ) ) )``. - -- ``makeHTMLTags( tagName )`` and ``makeXMLTags( tagName )`` - convenience - functions to create definitions of opening and closing tag expressions. Returns - a pair of expressions, for the corresponding <tag> and </tag> strings. Includes - support for attributes in the opening tag, such as <tag attr1="abc"> - attributes - are returned as keyed tokens in the returned ParseResults. ``makeHTMLTags`` is less - restrictive than ``makeXMLTags``, especially with respect to case sensitivity. - -- ``operatorPrecedence(baseOperand, operatorList)`` - convenience function to define a - grammar for parsing - expressions with a hierarchical precedence of operators. To use the operatorPrecedence - helper: - - 1. Define the base "atom" operand term of the grammar. - For this simple grammar, the smallest operand is either - and integer or a variable. This will be the first argument - to the operatorPrecedence method. - - 2. Define a list of tuples for each level of operator - precendence. Each tuple is of the form - ``(opExpr, numTerms, rightLeftAssoc, parseAction)``, where: - - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; if - None, indicates an empty operator, such as the implied - multiplication operation between 'm' and 'x' in "y = mx + b". - - - numTerms is the number of terms for this operator (must - be 1 or 2) - - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants ``opAssoc.RIGHT`` and ``opAssoc.LEFT``. - - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted) - - 3. Call operatorPrecedence passing the operand expression and - the operator precedence list, and save the returned value - as the generated pyparsing expression. You can then use - this expression to parse input strings, or incorporate it - into a larger, more complex grammar. - -- ``matchPreviousLiteral`` and ``matchPreviousExpr`` - function to define and - expression that matches the same content - as was parsed in a previous parse expression. For instance:: - - first = Word(nums) - matchExpr = first + ":" + matchPreviousLiteral(first) - - will match "1:1", but not "1:2". Since this matches at the literal - level, this will also match the leading "1:1" in "1:10". - - In contrast:: - - first = Word(nums) - matchExpr = first + ":" + matchPreviousExpr(first) - - will *not* match the leading "1:1" in "1:10"; the expressions are - evaluated first, and then compared, so "1" is compared with "10". - -- ``nestedExpr(opener, closer, content=None, ignoreExpr=quotedString)`` - method for defining nested - lists enclosed in opening and closing delimiters. - - - opener - opening character for a nested list (default="("); can also be a pyparsing expression - - - closer - closing character for a nested list (default=")"); can also be a pyparsing expression - - - content - expression for items within the nested lists (default=None) - - - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the ignoreExpr argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an Or or MatchFirst. - The default is quotedString, but if no expressions are to be ignored, - then pass None for this argument. - - -- ``indentedBlock( statementExpr, indentationStackVar, indent=True)`` - - function to define an indented block of statements, similar to - indentation-based blocking in Python source code: - - - statementExpr is the expression defining a statement that - will be found in the indented block; a valid indentedBlock - must contain at least 1 matching statementExpr - - - indentationStackVar is a Python list variable; this variable - should be common to all ``indentedBlock`` expressions defined - within the same grammar, and should be reinitialized to [1] - each time the grammar is to be used - - - indent is a boolean flag indicating whether the expressions - within the block must be indented from the current parse - location; if using indentedBlock to define the left-most - statements (all starting in column 1), set indent to False - -.. _originalTextFor: - -- ``originalTextFor( expr )`` - helper function to preserve the originally parsed text, regardless of any - token processing or conversion done by the contained expression. For instance, the following expression:: - - fullName = Word(alphas) + Word(alphas) - - will return the parse of "John Smith" as ['John', 'Smith']. In some applications, the actual name as it - was given in the input string is what is desired. To do this, use ``originalTextFor``:: - - fullName = originalTextFor(Word(alphas) + Word(alphas)) - -- ``ungroup( expr )`` - function to "ungroup" returned tokens; useful - to undo the default behavior of And to always group the returned tokens, even - if there is only one in the list. (New in 1.5.6) - -- ``lineno( loc, string )`` - function to give the line number of the - location within the string; the first line is line 1, newlines - start new rows - -- ``col( loc, string )`` - function to give the column number of the - location within the string; the first column is column 1, - newlines reset the column number to 1 - -- ``line( loc, string )`` - function to retrieve the line of text - representing ``lineno( loc, string )``; useful when printing out diagnostic - messages for exceptions - -- ``srange( rangeSpec )`` - function to define a string of characters, - given a string of the form used by regexp string ranges, such as ``"[0-9]"`` for - all numeric digits, ``"[A-Z_]"`` for uppercase characters plus underscore, and - so on (note that rangeSpec does not include support for generic regular - expressions, just string range specs) - -- ``getTokensEndLoc()`` - function to call from within a parse action to get - the ending location for the matched tokens - -- ``traceParseAction(fn)`` - decorator function to debug parse actions. Lists - each call, called arguments, and return value or exception - - - -Helper parse actions --------------------- - -- ``removeQuotes`` - removes the first and last characters of a quoted string; - useful to remove the delimiting quotes from quoted strings - -- ``replaceWith(replString)`` - returns a parse action that simply returns the - replString; useful when using transformString, or converting HTML entities, as in:: - - nbsp = Literal(" ").setParseAction( replaceWith("<BLANK>") ) - -- ``keepOriginalText``- (deprecated, use originalTextFor_ instead) restores any internal whitespace or suppressed - text within the tokens for a matched parse - expression. This is especially useful when defining expressions - for scanString or transformString applications. - -- ``withAttribute( *args, **kwargs )`` - helper to create a validating parse action to be used with start tags created - with ``makeXMLTags`` or ``makeHTMLTags``. Use ``withAttribute`` to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - ``<TD>`` or ``<DIV>``. - - ``withAttribute`` can be called with: - - - keyword arguments, as in ``(class="Customer",align="right")``, or - - - a list of name-value tuples, as in ``( ("ns1:class", "Customer"), ("ns2:align","right") )`` - - An attribute can be specified to have the special value - ``withAttribute.ANY_VALUE``, which will match any value - use this to - ensure that an attribute is present but any attribute value is - acceptable. - -- ``downcaseTokens`` - converts all matched tokens to lowercase - -- ``upcaseTokens`` - converts all matched tokens to uppercase - -- ``matchOnlyAtCol( columnNumber )`` - a parse action that verifies that - an expression was matched at a particular column, raising a - ParseException if matching at a different column number; useful when parsing - tabular data - - - -Common string and token constants ---------------------------------- - -- ``alphas`` - same as ``string.letters`` - -- ``nums`` - same as ``string.digits`` - -- ``alphanums`` - a string containing ``alphas + nums`` - -- ``alphas8bit`` - a string containing alphabetic 8-bit characters:: - - ������������������������������������������������������������� - -- ``printables`` - same as ``string.printable``, minus the space (``' '``) character - -- ``empty`` - a global ``Empty()``; will always match - -- ``sglQuotedString`` - a string of characters enclosed in 's; may - include whitespace, but not newlines - -- ``dblQuotedString`` - a string of characters enclosed in "s; may - include whitespace, but not newlines - -- ``quotedString`` - ``sglQuotedString | dblQuotedString`` - -- ``cStyleComment`` - a comment block delimited by ``'/*'`` and ``'*/'`` sequences; can span - multiple lines, but does not support nesting of comments - -- ``htmlComment`` - a comment block delimited by ``'<!--'`` and ``'-->'`` sequences; can span - multiple lines, but does not support nesting of comments - -- ``commaSeparatedList`` - similar to ``delimitedList``, except that the - list expressions can be any text value, or a quoted string; quoted strings can - safely include commas without incorrectly breaking the string into two tokens - -- ``restOfLine`` - all remaining printable characters up to but not including the next - newline diff --git a/src/LICENSE b/src/LICENSE deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL0xJQ0VOU0U=..0000000000000000000000000000000000000000 --- a/src/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/MANIFEST.in b/src/MANIFEST.in deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL01BTklGRVNULmlu..0000000000000000000000000000000000000000 --- a/src/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include pyparsing.py -include HowToUsePyparsing.html pyparsingClassDiagram.* -include README CHANGES LICENSE -include examples/*.py examples/Setup.ini examples/*.dfm examples/*.ics examples/*.html -include htmldoc/*.* -include docs/*.* -include robots.txt diff --git a/src/MANIFEST.in_bdist b/src/MANIFEST.in_bdist deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL01BTklGRVNULmluX2JkaXN0..0000000000000000000000000000000000000000 --- a/src/MANIFEST.in_bdist +++ /dev/null @@ -1,7 +0,0 @@ -include pyparsing.py -include HowToUsePyparsing.html pyparsingClassDiagram.* -include README CHANGES LICENSE -include examples/*.py examples/Setup.ini examples/*.dfm examples/*.ics examples/*.html -include htmldoc/*.* -include docs/*.* -include robots.txt diff --git a/src/MANIFEST.in_src b/src/MANIFEST.in_src deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL01BTklGRVNULmluX3NyYw==..0000000000000000000000000000000000000000 --- a/src/MANIFEST.in_src +++ /dev/null @@ -1,7 +0,0 @@ -include pyparsing_py2.py pyparsing_py3.py -include HowToUsePyparsing.html pyparsingClassDiagram.* -include README CHANGES LICENSE -include examples/*.py examples/Setup.ini examples/*.dfm examples/*.ics examples/*.html -include htmldoc/*.* -include docs/*.* -include robots.txt diff --git a/src/README b/src/README deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL1JFQURNRQ==..0000000000000000000000000000000000000000 --- a/src/README +++ /dev/null @@ -1,72 +0,0 @@ -==================================== -PyParsing -- A Python Parsing Module -==================================== - -Introduction -============ - -The pyparsing module is an alternative approach to creating and executing -simple grammars, vs. the traditional lex/yacc approach, or the use of -regular expressions. The pyparsing module provides a library of classes -that client code uses to construct the grammar directly in Python code. - -Here is a program to parse "Hello, World!" (or any greeting of the form -"<salutation>, <addressee>!"): - - from pyparsing import Word, alphas - greet = Word( alphas ) + "," + Word( alphas ) + "!" - hello = "Hello, World!" - print hello, "->", greet.parseString( hello ) - -The program outputs the following: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the -self-explanatory class names, and the use of '+', '|' and '^' operator -definitions. - -The parsed results returned from parseString() can be accessed as a -nested list, a dictionary, or an object with named attributes. - -The pyparsing module handles some of the problems that are typically -vexing when writing text parsers: -- extra or missing whitespace (the above program will also handle - "Hello,World!", "Hello , World !", etc.) -- quoted strings -- embedded comments - -The .zip file includes examples of a simple SQL parser, simple CORBA IDL -parser, a config file parser, a chemical formula parser, and a four- -function algebraic notation parser. It also includes a simple how-to -document, and a UML class diagram of the library's classes. - - - -Installation -============ - -Do the usual: - - python setup.py install - -(pyparsing requires Python 2.3.2 or later.) - - -Documentation -============= - -See: - - HowToUsePyparsing.html - - -License -======= - - MIT License. See header of pyparsing.py - -History -======= - - See CHANGES file. diff --git a/src/genEpydoc.bat b/src/genEpydoc.bat deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL2dlbkVweWRvYy5iYXQ=..0000000000000000000000000000000000000000 --- a/src/genEpydoc.bat +++ /dev/null @@ -1,1 +0,0 @@ -python c:\python26\scripts\epydoc -v --name pyparsing -o htmldoc --inheritance listed --no-private pyparsing.py diff --git a/src/makeRelease.bat b/src/makeRelease.bat deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL21ha2VSZWxlYXNlLmJhdA==..0000000000000000000000000000000000000000 --- a/src/makeRelease.bat +++ /dev/null @@ -1,25 +0,0 @@ -set MAKING_PYPARSING_RELEASE=1 - -if exist pyparsing.py del pyparsing.py -rmdir build -rmdir dist - -copy/y MANIFEST.in_src MANIFEST.in -if exist MANIFEST del MANIFEST -python setup.py sdist --formats=gztar,zip - -copy/y MANIFEST.in_bdist MANIFEST.in -if exist MANIFEST del MANIFEST - -copy/y pyparsing_py2.py pyparsing.py -python setup.py bdist_wininst --target-version=2.4 -python setup.py bdist_wininst --target-version=2.5 -python setup.py bdist_wininst --target-version=2.6 -python setup.py bdist_wininst --target-version=2.7 - -copy/y pyparsing_py3.py pyparsing.py -python setup.py bdist_wininst --target-version=3.0 -python setup.py bdist_wininst --target-version=3.1 -python setup.py bdist_wininst --target-version=3.2 - -set MAKING_PYPARSING_RELEASE= \ No newline at end of file diff --git a/src/pyparsing.py b/src/pyparsing.py deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL3B5cGFyc2luZy5weQ==..0000000000000000000000000000000000000000 --- a/src/pyparsing.py +++ /dev/null @@ -1,3740 +0,0 @@ -# module pyparsing.py -# -# Copyright (c) 2003-2011 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -#from __future__ import generators - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars - -The pyparsing module is an alternative approach to creating and executing simple grammars, -vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you -don't need to learn a new syntax for defining grammars or matching expressions - the parsing module -provides a library of classes that you use to construct the grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form C{"<salutation>, <addressee>!"}):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word( alphas ) + "," + Word( alphas ) + "!" - - hello = "Hello, World!" - print hello, "->", greet.parseString( hello ) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the self-explanatory -class names, and the use of '+', '|' and '^' operators. - -The parsed results returned from C{parseString()} can be accessed as a nested list, a dictionary, or an -object with named attributes. - -The pyparsing module handles some of the problems that are typically vexing when writing text parsers: - - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments -""" - -__version__ = "1.5.7" -__versionTime__ = "3 August 2012 05:00" -__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) - -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', -] - -""" -Detect if we are running version 3.X and make appropriate changes -Robert A. Clark -""" -_PY3K = sys.version_info[0] > 2 -if _PY3K: - _MAX_INT = sys.maxsize - basestring = str - unichr = chr - _ustr = str -else: - _MAX_INT = sys.maxint - range = xrange - set = lambda s : dict( [(c,0) for c in s] ) - - def _ustr(obj): - """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries - str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It - then < returns the unicode object | encodes it with the default encoding | ... >. - """ - if isinstance(obj,unicode): - return obj - - try: - # If this works, then _ustr(obj) has the same behaviour as str(obj), so - # it won't break any existing code. - return str(obj) - - except UnicodeEncodeError: - # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) - # state that "The return value must be a string object". However, does a - # unicode object (being a subclass of basestring) count as a "string - # object"? - # If so, then return a unicode object: - return unicode(obj) - # Else encode it... but how? There are many choices... :) - # Replace unprintables with escape codes? - #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') - # Replace unprintables with question marks? - #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') - # ... - -# build list of single arg builtins, tolerant of Python version, that can be used as parse actions -singleArgBuiltins = [] -import __builtin__ -for fname in "sum len sorted reversed list tuple set any all min max".split(): - try: - singleArgBuiltins.append(getattr(__builtin__,fname)) - except AttributeError: - continue - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] - for from_,to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -class _Constants(object): - pass - -alphas = string.ascii_lowercase + string.ascii_uppercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - - def __getattr__( self, aname ): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) - else: - raise AttributeError(aname) - - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): - return _ustr(self) - def markInputline( self, markerString = ">!<" ): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join( [line_str[:line_column], - markerString, line_str[line_column:]]) - return line_str.strip() - def __dir__(self): - return "loc msg pstr parserElement lineno col line " \ - "markInputline __str__ __repr__".split() - -class ParseException(ParseBaseException): - """exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - pass - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like C{L{ParseFatalException}}, but thrown internally when an - C{L{ErrorStop<And._ErrorStop>}} ('-' operator) indicates that parsing is to stop immediately because - an unbacktrackable syntax error has been found""" - def __init__(self, pe): - super(ParseSyntaxException, self).__init__( - pe.pstr, pe.loc, pe.msg, pe.parserElement) - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by C{validate()} if the grammar could be improperly recursive""" - def __init__( self, parseElementList ): - self.parseElementTrace = parseElementList - - def __str__( self ): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): - return self.tup[i] - def __repr__(self): - return repr(self.tup) - def setOffset(self,i): - self.tup = (self.tup[0],i) - -class ParseResults(object): - """Structured parse results, to provide multiple means of access to the parsed data: - - as a list (C{len(results)}) - - by list index (C{results[0], results[1]}, etc.) - - by attribute (C{results.<resultsName>}) - """ - #~ __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) - def __new__(cls, toklist, name=None, asList=True, modal=True ): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, toklist, name=None, asList=True, modal=True, isinstance=isinstance ): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - if isinstance(toklist, list): - self.__toklist = toklist[:] - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not toklist in (None,'',[]): - if isinstance(toklist,basestring): - toklist = [ toklist ] - if asList: - if isinstance(toklist,ParseResults): - self[name] = _ParseResultsWithOffset(toklist.copy(),0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError,TypeError,IndexError): - self[name] = toklist - - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) - - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] - sub = v[0] - elif isinstance(k,int): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] - sub = v - if isinstance(sub,ParseResults): - sub.__parent = wkref(self) - - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i+1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__( self, k ): - return k in self.__tokdict - - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return len( self.__toklist ) > 0 - __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def keys( self ): - """Returns all named result keys.""" - return self.__tokdict.keys() - - def pop( self, index=-1 ): - """Removes and returns item at specified index (default=last). - Will work with either numeric indices or dict-key indicies.""" - ret = self[index] - del self[index] - return ret - - def get(self, key, defaultValue=None): - """Returns named result matching the given key, or if there is no - such name, then returns the given C{defaultValue} or C{None} if no - C{defaultValue} is specified.""" - if key in self: - return self[key] - else: - return defaultValue - - def insert( self, index, insStr ): - """Inserts new element at location index in the list of parsed tokens.""" - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def items( self ): - """Returns all named result keys and values as a list of tuples.""" - return [(k,self[k]) for k in self.__tokdict] - - def values( self ): - """Returns all named result values.""" - return [ v[-1][0] for v in self.__tokdict.values() ] - - def __getattr__( self, name ): - if True: #name not in self.__slots__: - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - return None - - def __add__( self, other ): - ret = self.copy() - ret += other - return ret - - def __iadd__( self, other ): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: - self[k] = v - if isinstance(v[0],ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) - return self - - def __radd__(self, other): - if isinstance(other,int) and other == 0: - return self.copy() - - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) - - def __str__( self ): - out = [] - for i in self.__toklist: - if isinstance(i, ParseResults): - out.append(_ustr(i)) - else: - out.append(repr(i)) - return '[' + ', '.join(out) + ']' - - def _asStringList( self, sep='' ): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance( item, ParseResults ): - out += item._asStringList() - else: - out.append( _ustr(item) ) - return out - - def asList( self ): - """Returns the parse results as a nested list of matching tokens, all converted to strings.""" - out = [] - for res in self.__toklist: - if isinstance(res,ParseResults): - out.append( res.asList() ) - else: - out.append( res ) - return out - - def asDict( self ): - """Returns the named parse results as dictionary.""" - return dict( self.items() ) - - def copy( self ): - """Returns a new copy of a C{ParseResults} object.""" - ret = ParseResults( self.__toklist ) - ret.__tokdict = self.__tokdict.copy() - ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) - ret.__name = self.__name - return ret - - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): - """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" - nl = "\n" - out = [] - namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist ] ) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [ nl, indent, "<", selfTag, ">" ] - - worklist = self.__toklist - for i,res in enumerate(worklist): - if isinstance(res,ParseResults): - if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] - - out += [ nl, indent, "</", selfTag, ">" ] - return "".join(out) - - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: - if sub is v: - return k - return None - - def getName(self): - """Returns the results name for this token expression.""" - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - self.__tokdict.values()[0][0][1] in (0,-1)): - return self.__tokdict.keys()[0] - else: - return None - - def dump(self,indent='',depth=0): - """Diagnostic method for listing out the contents of a C{ParseResults}. - Accepts an optional C{indent} argument so that this string can be embedded - in a nested display of other data.""" - out = [] - out.append( indent+_ustr(self.asList()) ) - keys = self.items() - keys.sort() - for k,v in keys: - if out: - out.append('\n') - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): - if v.keys(): - out.append( v.dump(indent,depth+1) ) - else: - out.append(_ustr(v)) - else: - out.append(_ustr(v)) - return "".join(out) - - # add support for pickle protocol - def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) - - def __setstate__(self,state): - self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __dir__(self): - return dir(super(ParseResults,self)) + list(self.keys()) - -def col (loc,strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return (loc<len(strg) and strg[loc] == '\n') and 1 or loc - strg.rfind("\n", 0, loc) - -def lineno(loc,strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return strg.count("\n",0,loc) + 1 - -def line( loc, strg ): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR+1:nextCR] - else: - return strg[lastCR+1:] - -def _defaultStartDebugAction( instring, loc, expr ): - print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s,l,t: func(t) - limit = [0] - def wrapper(*args): - while 1: - try: - return func(*args[limit[0]:]) - except TypeError: - if limit[0] <= maxargs: - limit[0] += 1 - continue - raise - return wrapper - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - def setDefaultWhitespaceChars( chars ): - """Overrides the default whitespace chars - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) - - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - """ - ParserElement.literalStringClass = cls - inlineLiteralsUsing = staticmethod(inlineLiteralsUsing) - - def __init__( self, savelist=False ): - self.parseAction = list() - self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy( self ): - """Make a copy of this C{ParserElement}. Useful for defining different parse actions - for the same parsing pattern, using copies of the original parse element.""" - cpy = copy.copy( self ) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName( self, name ): - """Define name for this expression, for use in debugging.""" - self.name = name - self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg - return self - - def setResultsName( self, name, listAllMatches=False ): - """Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original C{ParserElement} object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - C{expr("name")} in place of C{expr.setResultsName("name")} - - see L{I{__call__}<__call__>}. - """ - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches=True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self,breakFlag = True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set C{breakFlag} to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse,"_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction( self, *fns, **kwargs ): - """Define action to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, - C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{parseString}<parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def addParseAction( self, *fns, **kwargs ): - """Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.""" - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def setFailAction( self, fn ): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - C{fn(s,loc,expr,err)} where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw C{L{ParseFatalException}} - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables( self, instring, loc ): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc,dummy = e._parse( instring, loc ) - exprsFound = True - except ParseException: - pass - return loc - - def preParse( self, instring, loc ): - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl( self, instring, loc, doActions=True ): - return loc, [] - - def postParse( self, instring, loc, tokenlist ): - return tokenlist - - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) - - if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException: - #~ print ("Exception raised:", err) - err = None - if self.debugActions[2]: - err = sys.exc_info()[1] - self.debugActions[2]( instring, tokensStart, self, err ) - if self.failAction: - if err is None: - err = sys.exc_info()[1] - self.failAction( instring, tokensStart, self, err ) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or loc >= len(instring): - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - - tokens = self.postParse( instring, loc, tokens ) - - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - err = sys.exc_info()[1] - self.debugActions[2]( instring, tokensStart, self, err ) - raise - else: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - - if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) - - return loc, retTokens - - def tryParse( self, instring, loc ): - try: - return self._parse( instring, loc, doActions=False )[0] - except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): - lookup = (self,instring,loc,callPreParse,doActions) - if lookup in ParserElement._exprArgCache: - value = ParserElement._exprArgCache[ lookup ] - if isinstance(value, Exception): - raise value - return (value[0],value[1].copy()) - else: - try: - value = self._parseNoCache( instring, loc, doActions, callPreParse ) - ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) - return value - except ParseBaseException: - pe = sys.exc_info()[1] - ParserElement._exprArgCache[ lookup ] = pe - raise - - _parse = _parseNoCache - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - _exprArgCache = {} - def resetCache(): - ParserElement._exprArgCache.clear() - resetCache = staticmethod(resetCache) - - _packratEnabled = False - def enablePackrat(): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method C{ParserElement.enablePackrat()}. If - your program uses C{psyco} to "compile as you go", you must call - C{enablePackrat} before calling C{psyco.full()}. If you do not do this, - Python will crash. For best results, call C{enablePackrat()} immediately - after importing pyparsing. - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - ParserElement._parse = ParserElement._parseCache - enablePackrat = staticmethod(enablePackrat) - - def parseString( self, instring, parseAll=False ): - """Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - If you want the grammar to require that the entire input string be - successfully parsed, then set C{parseAll} to True (equivalent to ending - the grammar with C{L{StringEnd()}}). - - Note: C{parseString} implicitly calls C{expandtabs()} on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the C{loc} argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - calling C{parseWithTabs} on your grammar before calling C{parseString} - (see L{I{parseWithTabs}<parseWithTabs>}) - - define your parse action using the full C{(s,loc,toks)} signature, and - reference the input string using the parse action's C{s} argument - - explictly expand the tabs in your input string before calling - C{parseString} - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - #~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse( instring, 0 ) - if parseAll: - loc = self.preParse( instring, loc ) - se = Empty() + StringEnd() - se._parse( instring, loc ) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - else: - return tokens - - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): - """Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - C{maxMatches} argument, to clip scanning after 'n' matches are found. If - C{overlap} is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See L{I{parseString}<parseString>} for more information on parsing - strings with embedded tabs.""" - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) - except ParseException: - loc = preloc+1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn( instring, loc ) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc+1 - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def transformString( self, instring ): - """Extension to C{L{scanString}}, to modify matching text with modified tokens that may - be returned from a parse action. To use C{transformString}, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking C{transformString()} on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. C{transformString()} returns the resulting transformed string.""" - out = [] - lastE = 0 - # force preservation of <TAB>s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) - if t: - if isinstance(t,ParseResults): - out += t.asList() - elif isinstance(t,list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def searchString( self, instring, maxMatches=_MAX_INT ): - """Another extension to C{L{scanString}}, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - C{maxMatches} argument, to clip searching after 'n' matches are found. - """ - try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def __add__(self, other ): - """Implementation of + operator - returns C{L{And}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - - def __radd__(self, other ): - """Implementation of + operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """Implementation of - operator, returns C{L{And}} with error stop""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, And._ErrorStop(), other ] ) - - def __rsub__(self, other ): - """Implementation of - operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self,other): - """Implementation of * operator, allows use of C{expr * 3} in place of - C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer - tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples - may also include C{None} as in: - - C{expr*(n,None)} or C{expr*(n,)} is equivalent - to C{expr*n + L{ZeroOrMore}(expr)} - (read as "at least n instances of C{expr}") - - C{expr*(None,n)} is equivalent to C{expr*(0,n)} - (read as "0 to n instances of C{expr}") - - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} - - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} - - Note that C{expr*(None,n)} does not raise an exception if - more than n exprs exist in the input stream; that is, - C{expr*(None,n)} does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - C{expr*(None,n) + ~expr} - - """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") - - if (optElements): - def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self]*minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self]*minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other ): - """Implementation of | operator - returns C{L{MatchFirst}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) - - def __ror__(self, other ): - """Implementation of | operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other ): - """Implementation of ^ operator - returns C{L{Or}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or( [ self, other ] ) - - def __rxor__(self, other ): - """Implementation of ^ operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other ): - """Implementation of & operator - returns C{L{Each}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each( [ self, other ] ) - - def __rand__(self, other ): - """Implementation of & operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__( self ): - """Implementation of ~ operator - returns C{L{NotAny}}""" - return NotAny( self ) - - def __call__(self, name): - """Shortcut for C{L{setResultsName}}, with C{listAllMatches=default}:: - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - could be written as:: - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") - - If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be - passed as C{True}. - """ - return self.setResultsName(name) - - def suppress( self ): - """Suppresses the output of this C{ParserElement}; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress( self ) - - def leaveWhitespace( self ): - """Disables the skipping of whitespace before matching the characters in the - C{ParserElement}'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars( self, chars ): - """Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs( self ): - """Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. - Must be called before C{parseString} when the input grammar contains elements that - match C{<TAB>} characters.""" - self.keepTabs = True - return self - - def ignore( self, other ): - """Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - """ - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - self.ignoreExprs.append( other.copy() ) - else: - self.ignoreExprs.append( Suppress( other.copy() ) ) - return self - - def setDebugActions( self, startAction, successAction, exceptionAction ): - """Enable display of debugging messages while doing pattern matching.""" - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug( self, flag=True ): - """Enable display of debugging messages while doing pattern matching. - Set C{flag} to True to enable, False to disable.""" - if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) - else: - self.debug = False - return self - - def __str__( self ): - return self.name - - def __repr__( self ): - return _ustr(self) - - def streamline( self ): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion( self, parseElementList ): - pass - - def validate( self, validateTrace=[] ): - """Check defined expressions for valid structure, check for infinite recursive definitions.""" - self.checkRecursion( [] ) - - def parseFile( self, file_or_filename, parseAll=False ): - """Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - f = open(file_or_filename, "r") - file_contents = f.read() - f.close() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def getException(self): - return ParseException("",0,self.errmsg,self) - - def __getattr__(self,aname): - if aname == "myException": - self.myException = ret = self.getException(); - return ret; - else: - raise AttributeError("no such attribute " + aname) - - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or self.__dict__ == other.__dict__ - elif isinstance(other, basestring): - try: - self.parseString(_ustr(other), parseAll=True) - return True - except ParseBaseException: - return False - else: - return super(ParserElement,self)==other - - def __ne__(self,other): - return not (self == other) - - def __hash__(self): - return hash(id(self)) - - def __req__(self,other): - return self == other - - def __rne__(self,other): - return not (self == other) - - -class Token(ParserElement): - """Abstract C{ParserElement} subclass, for defining atomic matching patterns.""" - def __init__( self ): - super(Token,self).__init__( savelist=False ) - - def setName(self, name): - s = super(Token,self).setName(name) - self.errmsg = "Expected " + self.name - return s - - -class Empty(Token): - """An empty token, will always match.""" - def __init__( self ): - super(Empty,self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """A token that will never match.""" - def __init__( self ): - super(NoMatch,self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl( self, instring, loc, doActions=True ): - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - -class Literal(Token): - """Token to exactly match a specified string.""" - def __init__( self, matchString ): - super(Literal,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc -_L = Literal -ParserElement.literalStringClass = Literal - -class Keyword(Token): - """Token to exactly match a specified string as a keyword, that is, it must be - immediately followed by a non-keyword character. Compare with C{L{Literal}}:: - Literal("if") will match the leading C{'if'} in C{'ifAndOnlyIf'}. - Keyword("if") will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} - Accepts two optional constructor arguments in addition to the keyword string: - C{identChars} is a string of characters that would be valid identifier characters, - defaulting to all alphanumerics + "_" and "$"; C{caseless} allows case-insensitive - matching, default is C{False}. - """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" - - def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): - super(Keyword,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl( self, instring, loc, doActions=True ): - if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - def copy(self): - c = super(Keyword,self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - def setDefaultKeywordChars( chars ): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) - -class CaselessLiteral(Literal): - """Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class CaselessKeyword(Keyword): - def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) - - def parseImpl( self, instring, loc, doActions=True ): - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class Word(Token): - """Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, - an optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. An optional - C{exclude} parameter can list characters that might be found in - the input C{bodyChars} string; useful to define a word of all printables - except for one or two characters, for instance. - """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() - if excludeChars: - initChars = ''.join([c for c in initChars if c not in excludeChars]) - if bodyChars: - bodyChars = ''.join([c for c in bodyChars if c not in excludeChars]) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars : - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.bodyCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" - try: - self.re = re.compile( self.reString ) - except: - self.re = None - - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(Word,self).__str__() - except: - pass - - - if self.strRepr is None: - - def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." - else: - return s - - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - - -class Regex(Token): - """Token for matching strings that match a given regular expression. - Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. - """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0): - """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" - super(Regex,self).__init__() - - if isinstance(pattern, basestring): - if len(pattern) == 0: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif isinstance(pattern, Regex.compiledREtype): - self.re = pattern - self.pattern = \ - self.reString = str(pattern) - self.flags = flags - - else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - d = result.groupdict() - ret = ParseResults(result.group()) - if d: - for k in d: - ret[k] = d[k] - return loc,ret - - def __str__( self ): - try: - return super(Regex,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - -class QuotedString(Token): - """Token for matching strings that are delimited by quoting characters. - """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): - """ - Defined with the following parameters: - - quoteChar - string of one or more characters defining the quote delimiting string - - escChar - character to escape quotes, typically backslash (default=None) - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) - - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) - """ - super(QuotedString,self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if len(quoteChar) == 0: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if len(endQuoteChar) == 0: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' - ) - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - charset = ''.join(set(self.quoteChar[0]+self.endQuoteChar[0])).replace('^',r'\^').replace('-',r'\-') - self.escCharReplacePattern = re.escape(self.escChar)+("([%s])" % charset) - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] - - if isinstance(ret,basestring): - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__( self ): - try: - return super(QuotedString,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """Token for matching words composed of characters *not* in a given set. - Defined with string containing all disallowed characters, and an optional - minimum, maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. - """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) - self.mayIndexError = False - - def parseImpl( self, instring, loc, doActions=True ): - if instring[loc] in self.notChars: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): - loc += 1 - - if loc - start < self.minLen: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(CharsNotIn, self).__str__() - except: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """Special matching class for matching whitespace. Normally, whitespace is ignored - by pyparsing grammars. This class is included when some whitespace structures - are significant. Define with a string containing the whitespace characters to be - matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, - as defined for the C{L{Word}} class.""" - whiteStrs = { - " " : "<SPC>", - "\t": "<TAB>", - "\n": "<LF>", - "\r": "<CR>", - "\f": "<FF>", - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() - self.matchWhite = ws - self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) - #~ self.leaveWhitespace() - self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """Token to advance to a specific column of input text; useful for tabular report scraping.""" - def __init__( self, colno ): - super(GoToColumn,self).__init__() - self.col = colno - - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) - if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) - newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] - return newloc, ret - -class LineStart(_PositionToken): - """Matches if current position is at the beginning of a line within the parse string""" - def __init__( self ): - super(LineStart,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected start of line" - - def preParse( self, instring, loc ): - preloc = super(LineStart,self).preParse(instring,loc) - if instring[preloc] == "\n": - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - if not( loc==0 or - (loc == self.preParse( instring, 0 )) or - (instring[loc-1] == "\n") ): #col(loc, instring) != 1: - #~ raise ParseException( instring, loc, "Expected start of line" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class LineEnd(_PositionToken): - """Matches if current position is at the end of a line within the parse string""" - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected end of line" - - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): - if instring[loc] == "\n": - return loc+1, "\n" - else: - #~ raise ParseException( instring, loc, "Expected end of line" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - elif loc == len(instring): - return loc+1, [] - else: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class StringStart(_PositionToken): - """Matches if current position is at the beginning of the parse string""" - def __init__( self ): - super(StringStart,self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): - #~ raise ParseException( instring, loc, "Expected start of text" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class StringEnd(_PositionToken): - """Matches if current position is at the end of the parse string""" - def __init__( self ): - super(StringEnd,self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc < len(instring): - #~ raise ParseException( instring, loc, "Expected end of text" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - elif loc == len(instring): - return loc+1, [] - elif loc > len(instring): - return loc, [] - else: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class WordStart(_PositionToken): - """Matches if the current position is at the beginning of a Word, and - is not preceded by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of - the string being parsed, or at the beginning of a line. - """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True ): - if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class WordEnd(_PositionToken): - """Matches if the current position is at the end of a Word, and - is not followed by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of - the string being parsed, or at the end of a line. - """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True ): - instrlen = len(instring) - if instrlen>0 and loc<instrlen: - if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): - #~ raise ParseException( instring, loc, "Expected end of word" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - - -class ParseExpression(ParserElement): - """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, list ): - self.exprs = exprs - elif isinstance( exprs, basestring ): - self.exprs = [ Literal( exprs ) ] - else: - try: - self.exprs = list( exprs ) - except TypeError: - self.exprs = [ exprs ] - self.callPreparse = False - - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) - self.strRepr = None - return self - - def leaveWhitespace( self ): - """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - else: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - return self - - def __str__( self ): - try: - return super(ParseExpression,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) - return self.strRepr - - def streamline( self ): - super(ParseExpression,self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): - other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - return self - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion( [] ) - - def copy(self): - ret = super(ParseExpression,self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - -class And(ParseExpression): - """Requires all given C{ParseExpression}s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the C{'+'} operator. - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) - self.leaveWhitespace() - - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.setWhitespaceChars( exprs[0].whiteChars ) - self.skipWhitespace = exprs[0].skipWhitespace - self.callPreparse = True - - 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 - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse( instring, loc, doActions ) - except ParseSyntaxException: - raise - except ParseBaseException: - pe = sys.exc_info()[1] - raise ParseSyntaxException(pe) - except IndexError: - raise ParseSyntaxException( ParseException(instring, len(instring), self.errmsg, self) ) - else: - loc, exprtokens = e._parse( instring, loc, doActions ) - if exprtokens or exprtokens.keys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = Literal( other ) - return self.append( other ) #And( [ self, other ] ) - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - if not e.mayReturnEmpty: - break - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the expression that matches the longest string will be used. - May be constructed using the C{'^'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxMatchLoc = -1 - maxException = None - for e in self.exprs: - try: - loc2 = e.tryParse( instring, loc ) - except ParseException: - err = sys.exc_info()[1] - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - else: - if loc2 > maxMatchLoc: - maxMatchLoc = loc2 - maxMatchExp = e - - if maxMatchLoc < 0: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - return maxMatchExp._parse( instring, loc, doActions ) - - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class MatchFirst(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the first one listed is the one that will match. - May be constructed using the C{'|'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) - if exprs: - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse( instring, loc, doActions ) - return ret - except ParseException, err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class Each(ParseExpression): - """Requires all given C{ParseExpression}s to be found, but in any order. - Expressions may be separated by whitespace. - May be constructed using the C{'&'} operator. - """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.skipWhitespace = True - self.initExprGroups = True - - def parseImpl( self, instring, loc, doActions=True ): - if self.initExprGroups: - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and e not in opt1 ] - self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse( instring, tmpLoc ) - except ParseException: - failed.append(e) - else: - matchOrder.append(e) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] - - resultlist = [] - for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) - resultlist.append(results) - - finalResults = ParseResults([]) - for r in resultlist: - dups = {} - for k in r.keys(): - if k in finalResults.keys(): - tmp = ParseResults(finalResults[k]) - tmp += ParseResults(r[k]) - dups[k] = tmp - finalResults += ParseResults(r) - for k,v in dups.items(): - finalResults[k] = v - return loc, finalResults - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class ParseElementEnhance(ParserElement): - """Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.""" - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - expr = Literal(expr) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) - else: - raise ParseException("",loc,self.errmsg,self) - - def leaveWhitespace( self ): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - else: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - return self - - def streamline( self ): - super(ParseElementEnhance,self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion( self, parseElementList ): - if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] - if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion( [] ) - - def __str__( self ): - try: - return super(ParseElementEnhance,self).__str__() - except: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """Lookahead matching of the given parse expression. C{FollowedBy} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression matches at the current - position. C{FollowedBy} always returns a null token list.""" - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - self.expr.tryParse( instring, loc ) - return loc, [] - - -class NotAny(ParseElementEnhance): - """Lookahead to disallow matching with the given parse expression. C{NotAny} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression does *not* match at the current - position. Also, C{NotAny} does *not* skip over leading whitespace. C{NotAny} - always returns a null token list. May be constructed using the '~' operator.""" - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - try: - self.expr.tryParse( instring, loc ) - except (ParseException,IndexError): - pass - else: - #~ raise ParseException(instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - - -class ZeroOrMore(ParseElementEnhance): - """Optional repetition of zero or more of the given expression.""" - def __init__( self, expr ): - super(ZeroOrMore,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - tokens = [] - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - - -class OneOrMore(ParseElementEnhance): - """Repetition of one or more of the given expression.""" - def parseImpl( self, instring, loc, doActions=True ): - # must be at least one - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - try: - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(OneOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -_optionalNotMatched = _NullToken() -class Optional(ParseElementEnhance): - """Optional matching of the given expression. - A default return string can also be specified, if the optional expression - is not found. - """ - def __init__( self, exprs, default=_optionalNotMatched ): - super(Optional,self).__init__( exprs, savelist=False ) - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [ self.defaultValue ] - else: - tokens = [] - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - - -class SkipTo(ParseElementEnhance): - """Token for skipping over all undefined text until the matched expression is found. - If C{include} is set to true, the matched expression is also parsed (the skipped text - and matched expression are returned as a 2-element list). The C{ignore} - argument is used to define grammars (typically quoted strings and comments) that - might contain false matches. - """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.asList = False - if failOn is not None and isinstance(failOn, basestring): - self.failOn = Literal(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - startLoc = loc - instrlen = len(instring) - expr = self.expr - failParse = False - while loc <= instrlen: - try: - if self.failOn: - try: - self.failOn.tryParse(instring, loc) - except ParseBaseException: - pass - else: - failParse = True - raise ParseException(instring, loc, "Found expression " + str(self.failOn)) - failParse = False - if self.ignoreExpr is not None: - while 1: - try: - loc = self.ignoreExpr.tryParse(instring,loc) - # print "found ignoreExpr, advance to", loc - except ParseBaseException: - break - expr._parse( instring, loc, doActions=False, callPreParse=False ) - skipText = instring[startLoc:loc] - if self.includeMatch: - loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) - if mat: - skipRes = ParseResults( skipText ) - skipRes += mat - return loc, [ skipRes ] - else: - return loc, [ skipText ] - else: - return loc, [ skipText ] - except (ParseException,IndexError): - if failParse: - raise - else: - loc += 1 - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class Forward(ParseElementEnhance): - """Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. - - Note: take care when assigning to C{Forward} not to overlook precedence of operators. - Specifically, '|' has a lower precedence than '<<', so that:: - fwdExpr << a | b | c - will actually be evaluated as:: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the C{Forward}:: - fwdExpr << (a | b | c) - Converting to use the '<<=' operator instead will avoid this problem. - """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) - - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass(other) - self.expr = other - self.mayReturnEmpty = other.mayReturnEmpty - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return None - __ilshift__ = __lshift__ - - def leaveWhitespace( self ): - self.skipWhitespace = False - return self - - def streamline( self ): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate( self, validateTrace=[] ): - if self not in validateTrace: - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse - try: - if self.expr is not None: - retString = _ustr(self.expr) - else: - retString = "None" - finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString - - def copy(self): - if self.expr is not None: - return super(Forward,self).copy() - else: - ret = Forward() - ret << self - return ret - -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." - -class TokenConverter(ParseElementEnhance): - """Abstract subclass of C{ParseExpression}, for converting parsed results.""" - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) - self.saveAsList = False - -class Upcase(TokenConverter): - """Converter to upper case all matching tokens.""" - def __init__(self, *args): - super(Upcase,self).__init__(*args) - warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", - DeprecationWarning,stacklevel=2) - - def postParse( self, instring, loc, tokenlist ): - return list(map( string.upper, tokenlist )) - - -class Combine(TokenConverter): - """Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the input string; - this can be disabled by specifying C{'adjacent=False'} in the constructor. - """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore( self, other ): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super( Combine, self).ignore( other ) - return self - - def postParse( self, instring, loc, tokenlist ): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) - - if self.resultsName and len(retToks.keys())>0: - return [ retToks ] - else: - return retToks - -class Group(TokenConverter): - """Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.""" - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] - -class Dict(TokenConverter): - """Converter to return a repetitive expression as a list, but also as a dictionary. - Each element can also be referenced using the first token in the expression as its key. - Useful for tabular report scraping when the first column can be used as a item key. - """ - def __init__( self, exprs ): - super(Dict,self).__init__( exprs ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey,int): - ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) - else: - dictvalue = tok.copy() #ParseResults(i) - del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) - - if self.resultsName: - return [ tokenlist ] - else: - return tokenlist - - -class Suppress(TokenConverter): - """Converter for ignoring the results of a parsed expression.""" - def postParse( self, instring, loc, tokenlist ): - return [] - - def suppress( self ): - return self - - -class OnlyOnce(object): - """Wrapper for parse actions, to ensure they are only called once.""" - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self,s,l,t): - if not self.called: - results = self.callable(s,l,t) - self.called = True - return results - raise ParseException(s,l,"") - def reset(self): - self.called = False - -def traceParseAction(f): - """Decorator for debugging parse actions.""" - f = _trim_arity(f) - def z(*paArgs): - thisFunc = f.func_name - s,l,t = paArgs[-3:] - if len(paArgs)>3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) - try: - ret = f(*paArgs) - except Exception: - exc = sys.exc_info()[1] - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) - raise - sys.stderr.write( "<<leaving %s (ret: %s)\n" % (thisFunc,ret) ) - return ret - try: - z.__name__ = f.__name__ - except AttributeError: - pass - return z - -# -# global helpers -# -def delimitedList( expr, delim=",", combine=False ): - """Helper to define a delimited list of expressions - the delimiter defaults to ','. - By default, the list elements and delimiters can have intervening whitespace, and - comments, but this can be overridden by passing C{combine=True} in the constructor. - If C{combine} is set to C{True}, the matching tokens are returned as a single token - string, with the delimiters included; otherwise, the matching tokens are returned - as a list of tokens, with the delimiters suppressed. - """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." - if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) - else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) - -def countedArray( expr, intExpr=None ): - """Helper to define a counted list of expressions. - This helper defines a pattern of the form:: - integer expr expr expr... - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. - """ - arrayExpr = Forward() - def countFieldParseAction(s,l,t): - n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ) - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i,list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches a - previous literal, will also match the leading C{"1:1"} in C{"1:10"}. - If this is not desired, use C{matchPreviousExpr}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - def copyTokenToRepeater(s,l,t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And( [ Literal(tt) for tt in tflat ] ) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def matchPreviousExpr(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches by - expressions, will *not* match the leading C{"1:1"} in C{"1:10"}; - the expressions are evaluated first, and then compared, so - C{"1"} is compared with C{"10"}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep << e2 - def copyTokenToRepeater(s,l,t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") - return _ustr(s) - -def oneOf( strs, caseless=False, useRegex=True ): - """Helper to quickly define a set of alternative Literals, and makes sure to do - longest-first testing when there is a conflict, regardless of the input order, - but returns a C{L{MatchFirst}} for best performance. - - Parameters: - - strs - a string of space-delimited literals, or a list of string literals - - caseless - (default=False) - treat all literals as caseless - - useRegex - (default=True) - as an optimization, will generate a Regex - object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or - if creating a C{Regex} raises an exception) - """ - if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral - else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal - - if isinstance(strs,(list,tuple)): - symbols = list(strs[:]) - elif isinstance(strs,basestring): - symbols = strs.split() - else: - warnings.warn("Invalid argument to oneOf, expected string or list", - SyntaxWarning, stacklevel=2) - - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) - else: - return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) - except: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - - # last resort, just use MatchFirst - return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) - -def dictOf( key, value ): - """Helper to easily and clearly define a dictionary by specifying the respective patterns - for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens - in the proper order. The key pattern can include delimiting markers or punctuation, - as long as they are suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the C{Dict} results can include named token - fields. - """ - return Dict( ZeroOrMore( Group ( key + value ) ) ) - -def originalTextFor(expr, asString=True): - """Helper to return the original, untokenized text for a given expression. Useful to - restore the parsed fields of an HTML start tag into the raw tag text itself, or to - revert separate tokens with intervening whitespace back to the original matching - input text. Simpler to use than the parse action C{L{keepOriginalText}}, and does not - require the inspect module to chase up the call stack. By default, returns a - string containing the original parsed text. - - If the optional C{asString} argument is passed as C{False}, then the return value is a - C{L{ParseResults}} containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if - the expression passed to C{L{originalTextFor}} contains expressions with defined - results names, you must set C{asString} to C{False} if you want to preserve those - results name values.""" - locMarker = Empty().setParseAction(lambda s,loc,t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] - else: - def extractText(s,l,t): - del t[:] - t.insert(0, s[t._original_start:t._original_end]) - del t["_original_start"] - del t["_original_end"] - matchExpr.setParseAction(extractText) - return matchExpr - -def ungroup(expr): - """Helper to undo pyparsing's default grouping of And expressions, even - if all but one are non-empty.""" - return TokenConverter(expr).setParseAction(lambda t:t[0]) - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" - -_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) - -def srange(s): - r"""Helper to easily define string ranges for use in Word construction. Borrows - syntax from regexp '[]' string range definitions:: - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - The input string must be enclosed in []'s, and the returned string is the expanded - character set joined into a single string. - The values enclosed in the []'s may be:: - a single character - an escaped character with a leading backslash (such as \- or \]) - an escaped hex character with a leading '\x' (\x21, which is a '!' character) - (\0x## is also supported for backwards compatibility) - an escaped octal character with a leading '\0' (\041, which is a '!' character) - a range of any of the above, separated by a dash ('a-z', etc.) - any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) - """ - try: - return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) - except: - return "" - -def matchOnlyAtCol(n): - """Helper method for defining parse actions that require matching at a specific - column in the input text. - """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """Helper method for common parse actions that simply return a literal value. Especially - useful when used with C{L{transformString<ParserElement.transformString>}()}. - """ - def _replFunc(*args): - return [replStr] - return _replFunc - -def removeQuotes(s,l,t): - """Helper parse action for removing quotation marks from parsed quoted strings. - To use, add this parse action to quoted string using:: - quotedString.setParseAction( removeQuotes ) - """ - return t[0][1:-1] - -def upcaseTokens(s,l,t): - """Helper parse action to convert tokens to upper case.""" - return [ tt.upper() for tt in map(_ustr,t) ] - -def downcaseTokens(s,l,t): - """Helper parse action to convert tokens to lower case.""" - return [ tt.lower() for tt in map(_ustr,t) ] - -def keepOriginalText(s,startLoc,t): - """DEPRECATED - use new helper method C{L{originalTextFor}}. - Helper parse action to preserve original parsed text, - overriding any nested parse actions.""" - try: - endloc = getTokensEndLoc() - except ParseException: - raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") - del t[:] - t += ParseResults(s[startLoc:endloc]) - return t - -def getTokensEndLoc(): - """Method to be called from within a parse action to determine the end - location of the parsed tokens.""" - import inspect - fstack = inspect.stack() - try: - # search up the stack (through intervening argument normalizers) for correct calling routine - for f in fstack[2:]: - if f[3] == "_parseNoCache": - endloc = f[0].f_locals["loc"] - return endloc - else: - raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") - finally: - del fstack - -def _makeTags(tagStr, xml): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - else: - printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") - - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % tagStr) - openTag.tag = resname - closeTag.tag = resname - return openTag, closeTag - -def makeHTMLTags(tagStr): - """Helper to construct opening and closing tag expressions for HTML, given a tag name""" - return _makeTags( tagStr, False ) - -def makeXMLTags(tagStr): - """Helper to construct opening and closing tag expressions for XML, given a tag name""" - return _makeTags( tagStr, True ) - -def withAttribute(*args,**attrDict): - """Helper to create a validating parse action to be used with start tags created - with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - C{<TD>} or C{<DIV>}. - - Call C{withAttribute} with a series of attribute names and values. Specify the list - of filter attributes names and values as: - - keyword arguments, as in C{(align="right")}, or - - as an explicit dict with C{**} operator, when an attribute name is also a Python - reserved word, as in C{**{"class":"Customer", "align":"right"}} - - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) - For attribute names with a namespace prefix, you must use the second form. Attribute - names are matched insensitive to upper/lower case. - - To verify that the attribute exists, but without specifying a value, pass - C{withAttribute.ANY_VALUE} as the value. - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: - if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -opAssoc = _Constants() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def operatorPrecedence( baseExpr, opList ): - """Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary or - binary, left- or right-associative. Parse actions can also be attached - to operator expressions. - - Parameters: - - baseExpr - expression representing the most basic element for the nested - - opList - list of tuples, one for each operator precedence level in the - expression grammar; each tuple is of the form - (opExpr, numTerms, rightLeftAssoc, parseAction), where: - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; - if numTerms is 3, opExpr is a tuple of two expressions, for the - two operators separating the 3 terms - - numTerms is the number of terms for this operator (must - be 1, 2, or 3) - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted) - """ - ret = Forward() - lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward()#.setName("expr%d" % i) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) - else: - matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) - else: - matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - matchExpr.setParseAction( pa ) - thisExpr << ( matchExpr | lastExpr ) - lastExpr = thisExpr - ret << lastExpr - return ret - -dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") -sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") -quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()) - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """Helper method for defining nested lists enclosed in opening and closing - delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list (default="("); can also be a pyparsing expression - - closer - closing character for a nested list (default=")"); can also be a pyparsing expression - - content - expression for items within the nested lists (default=None) - - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the C{ignoreExpr} argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. - The default is L{quotedString}, but if no expressions are to be ignored, - then pass C{None} for this argument. - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) - else: - ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """Helper method for defining space-delimited indentation blocks, such as - those used to define block statements in Python source code. - - Parameters: - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single grammar - should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond the - the current level; set to False for block of left-most statements - (default=True) - - A valid block must contain at least one C{blockStatement}. - """ - def checkPeerIndent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") - - def checkSubIndent(s,l,t): - curCol = col(l,s) - if curCol > indentStack[-1]: - indentStack.append( curCol ) - else: - raise ParseException(s,l,"not a subentry") - - def checkUnindent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) - INDENT = Empty() + Empty().setParseAction(checkSubIndent) - PEER = Empty().setParseAction(checkPeerIndent) - UNDENT = Empty().setParseAction(checkUnindent) - if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) - else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) -commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() -_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) -replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") - -htmlComment = Regex(r"<!--[\s\S]*?-->") -restOfLine = Regex(r".*").leaveWhitespace() -dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") -cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?<!\\)|\Z))").setName("C++ style comment") - -javaStyleComment = cppStyleComment -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -_noncomma = "".join( [ c for c in printables if c != "," ] ) -_commasepitem = Combine(OneOrMore(Word(_noncomma) + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") - - -if __name__ == "__main__": - - def test( teststring ): - try: - tokens = simpleSQL.parseString( teststring ) - tokenlist = tokens.asList() - print (teststring + "->" + str(tokenlist)) - print ("tokens = " + str(tokens)) - print ("tokens.columns = " + str(tokens.columns)) - print ("tokens.tables = " + str(tokens.tables)) - print (tokens.asXML("SQL",True)) - except ParseBaseException: - err = sys.exc_info()[1] - print (teststring + "->") - print (err.line) - print (" "*(err.column-1) + "^") - print (err) - print() - - selectToken = CaselessLiteral( "select" ) - fromToken = CaselessLiteral( "from" ) - - ident = Word( alphas, alphanums + "_$" ) - columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - columnNameList = Group( delimitedList( columnName ) )#.setName("columns") - tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - tableNameList = Group( delimitedList( tableName ) )#.setName("tables") - simpleSQL = ( selectToken + \ - ( '*' | columnNameList ).setResultsName( "columns" ) + \ - fromToken + \ - tableNameList.setResultsName( "tables" ) ) - - test( "SELECT * from XYZZY, ABC" ) - test( "select * from SYS.XYZZY" ) - test( "Select A from Sys.dual" ) - test( "Select AA,BB,CC from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Xelect A, B, C from Sys.dual" ) - test( "Select A, B, C frox Sys.dual" ) - test( "Select" ) - test( "Select ^^^ frox Sys.dual" ) - test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/src/pyparsingClassDiagram.JPG b/src/pyparsingClassDiagram.JPG deleted file mode 100644 index ef10424899c565f0f4e1067395c6a11a68728abb..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001 diff --git a/src/pyparsingClassDiagram.PNG b/src/pyparsingClassDiagram.PNG deleted file mode 100644 index f59baaf3929638e74d5f21565f0bf95a1aca14a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@<O00001 diff --git a/src/pyparsing_py2.py b/src/pyparsing_py2.py deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL3B5cGFyc2luZ19weTIucHk=..0000000000000000000000000000000000000000 --- a/src/pyparsing_py2.py +++ /dev/null @@ -1,3740 +0,0 @@ -# module pyparsing.py -# -# Copyright (c) 2003-2011 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -#from __future__ import generators - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars - -The pyparsing module is an alternative approach to creating and executing simple grammars, -vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you -don't need to learn a new syntax for defining grammars or matching expressions - the parsing module -provides a library of classes that you use to construct the grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form C{"<salutation>, <addressee>!"}):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word( alphas ) + "," + Word( alphas ) + "!" - - hello = "Hello, World!" - print hello, "->", greet.parseString( hello ) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the self-explanatory -class names, and the use of '+', '|' and '^' operators. - -The parsed results returned from C{parseString()} can be accessed as a nested list, a dictionary, or an -object with named attributes. - -The pyparsing module handles some of the problems that are typically vexing when writing text parsers: - - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments -""" - -__version__ = "1.5.7" -__versionTime__ = "3 August 2012 05:00" -__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) - -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', -] - -""" -Detect if we are running version 3.X and make appropriate changes -Robert A. Clark -""" -_PY3K = sys.version_info[0] > 2 -if _PY3K: - _MAX_INT = sys.maxsize - basestring = str - unichr = chr - _ustr = str -else: - _MAX_INT = sys.maxint - range = xrange - set = lambda s : dict( [(c,0) for c in s] ) - - def _ustr(obj): - """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries - str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It - then < returns the unicode object | encodes it with the default encoding | ... >. - """ - if isinstance(obj,unicode): - return obj - - try: - # If this works, then _ustr(obj) has the same behaviour as str(obj), so - # it won't break any existing code. - return str(obj) - - except UnicodeEncodeError: - # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) - # state that "The return value must be a string object". However, does a - # unicode object (being a subclass of basestring) count as a "string - # object"? - # If so, then return a unicode object: - return unicode(obj) - # Else encode it... but how? There are many choices... :) - # Replace unprintables with escape codes? - #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') - # Replace unprintables with question marks? - #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') - # ... - -# build list of single arg builtins, tolerant of Python version, that can be used as parse actions -singleArgBuiltins = [] -import __builtin__ -for fname in "sum len sorted reversed list tuple set any all min max".split(): - try: - singleArgBuiltins.append(getattr(__builtin__,fname)) - except AttributeError: - continue - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] - for from_,to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -class _Constants(object): - pass - -alphas = string.ascii_lowercase + string.ascii_uppercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - - def __getattr__( self, aname ): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) - else: - raise AttributeError(aname) - - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): - return _ustr(self) - def markInputline( self, markerString = ">!<" ): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join( [line_str[:line_column], - markerString, line_str[line_column:]]) - return line_str.strip() - def __dir__(self): - return "loc msg pstr parserElement lineno col line " \ - "markInputline __str__ __repr__".split() - -class ParseException(ParseBaseException): - """exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - pass - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like C{L{ParseFatalException}}, but thrown internally when an - C{L{ErrorStop<And._ErrorStop>}} ('-' operator) indicates that parsing is to stop immediately because - an unbacktrackable syntax error has been found""" - def __init__(self, pe): - super(ParseSyntaxException, self).__init__( - pe.pstr, pe.loc, pe.msg, pe.parserElement) - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by C{validate()} if the grammar could be improperly recursive""" - def __init__( self, parseElementList ): - self.parseElementTrace = parseElementList - - def __str__( self ): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): - return self.tup[i] - def __repr__(self): - return repr(self.tup) - def setOffset(self,i): - self.tup = (self.tup[0],i) - -class ParseResults(object): - """Structured parse results, to provide multiple means of access to the parsed data: - - as a list (C{len(results)}) - - by list index (C{results[0], results[1]}, etc.) - - by attribute (C{results.<resultsName>}) - """ - #~ __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) - def __new__(cls, toklist, name=None, asList=True, modal=True ): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, toklist, name=None, asList=True, modal=True, isinstance=isinstance ): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - if isinstance(toklist, list): - self.__toklist = toklist[:] - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not toklist in (None,'',[]): - if isinstance(toklist,basestring): - toklist = [ toklist ] - if asList: - if isinstance(toklist,ParseResults): - self[name] = _ParseResultsWithOffset(toklist.copy(),0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError,TypeError,IndexError): - self[name] = toklist - - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) - - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] - sub = v[0] - elif isinstance(k,int): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] - sub = v - if isinstance(sub,ParseResults): - sub.__parent = wkref(self) - - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i+1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__( self, k ): - return k in self.__tokdict - - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return len( self.__toklist ) > 0 - __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def keys( self ): - """Returns all named result keys.""" - return self.__tokdict.keys() - - def pop( self, index=-1 ): - """Removes and returns item at specified index (default=last). - Will work with either numeric indices or dict-key indicies.""" - ret = self[index] - del self[index] - return ret - - def get(self, key, defaultValue=None): - """Returns named result matching the given key, or if there is no - such name, then returns the given C{defaultValue} or C{None} if no - C{defaultValue} is specified.""" - if key in self: - return self[key] - else: - return defaultValue - - def insert( self, index, insStr ): - """Inserts new element at location index in the list of parsed tokens.""" - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def items( self ): - """Returns all named result keys and values as a list of tuples.""" - return [(k,self[k]) for k in self.__tokdict] - - def values( self ): - """Returns all named result values.""" - return [ v[-1][0] for v in self.__tokdict.values() ] - - def __getattr__( self, name ): - if True: #name not in self.__slots__: - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - return None - - def __add__( self, other ): - ret = self.copy() - ret += other - return ret - - def __iadd__( self, other ): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: - self[k] = v - if isinstance(v[0],ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) - return self - - def __radd__(self, other): - if isinstance(other,int) and other == 0: - return self.copy() - - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) - - def __str__( self ): - out = [] - for i in self.__toklist: - if isinstance(i, ParseResults): - out.append(_ustr(i)) - else: - out.append(repr(i)) - return '[' + ', '.join(out) + ']' - - def _asStringList( self, sep='' ): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance( item, ParseResults ): - out += item._asStringList() - else: - out.append( _ustr(item) ) - return out - - def asList( self ): - """Returns the parse results as a nested list of matching tokens, all converted to strings.""" - out = [] - for res in self.__toklist: - if isinstance(res,ParseResults): - out.append( res.asList() ) - else: - out.append( res ) - return out - - def asDict( self ): - """Returns the named parse results as dictionary.""" - return dict( self.items() ) - - def copy( self ): - """Returns a new copy of a C{ParseResults} object.""" - ret = ParseResults( self.__toklist ) - ret.__tokdict = self.__tokdict.copy() - ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) - ret.__name = self.__name - return ret - - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): - """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" - nl = "\n" - out = [] - namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist ] ) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [ nl, indent, "<", selfTag, ">" ] - - worklist = self.__toklist - for i,res in enumerate(worklist): - if isinstance(res,ParseResults): - if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] - - out += [ nl, indent, "</", selfTag, ">" ] - return "".join(out) - - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: - if sub is v: - return k - return None - - def getName(self): - """Returns the results name for this token expression.""" - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - self.__tokdict.values()[0][0][1] in (0,-1)): - return self.__tokdict.keys()[0] - else: - return None - - def dump(self,indent='',depth=0): - """Diagnostic method for listing out the contents of a C{ParseResults}. - Accepts an optional C{indent} argument so that this string can be embedded - in a nested display of other data.""" - out = [] - out.append( indent+_ustr(self.asList()) ) - keys = self.items() - keys.sort() - for k,v in keys: - if out: - out.append('\n') - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): - if v.keys(): - out.append( v.dump(indent,depth+1) ) - else: - out.append(_ustr(v)) - else: - out.append(_ustr(v)) - return "".join(out) - - # add support for pickle protocol - def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) - - def __setstate__(self,state): - self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __dir__(self): - return dir(super(ParseResults,self)) + list(self.keys()) - -def col (loc,strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return (loc<len(strg) and strg[loc] == '\n') and 1 or loc - strg.rfind("\n", 0, loc) - -def lineno(loc,strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return strg.count("\n",0,loc) + 1 - -def line( loc, strg ): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR+1:nextCR] - else: - return strg[lastCR+1:] - -def _defaultStartDebugAction( instring, loc, expr ): - print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s,l,t: func(t) - limit = [0] - def wrapper(*args): - while 1: - try: - return func(*args[limit[0]:]) - except TypeError: - if limit[0] <= maxargs: - limit[0] += 1 - continue - raise - return wrapper - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - def setDefaultWhitespaceChars( chars ): - """Overrides the default whitespace chars - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) - - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - """ - ParserElement.literalStringClass = cls - inlineLiteralsUsing = staticmethod(inlineLiteralsUsing) - - def __init__( self, savelist=False ): - self.parseAction = list() - self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy( self ): - """Make a copy of this C{ParserElement}. Useful for defining different parse actions - for the same parsing pattern, using copies of the original parse element.""" - cpy = copy.copy( self ) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName( self, name ): - """Define name for this expression, for use in debugging.""" - self.name = name - self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg - return self - - def setResultsName( self, name, listAllMatches=False ): - """Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original C{ParserElement} object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - C{expr("name")} in place of C{expr.setResultsName("name")} - - see L{I{__call__}<__call__>}. - """ - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches=True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self,breakFlag = True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set C{breakFlag} to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse,"_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction( self, *fns, **kwargs ): - """Define action to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, - C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{parseString}<parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def addParseAction( self, *fns, **kwargs ): - """Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.""" - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def setFailAction( self, fn ): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - C{fn(s,loc,expr,err)} where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw C{L{ParseFatalException}} - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables( self, instring, loc ): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc,dummy = e._parse( instring, loc ) - exprsFound = True - except ParseException: - pass - return loc - - def preParse( self, instring, loc ): - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl( self, instring, loc, doActions=True ): - return loc, [] - - def postParse( self, instring, loc, tokenlist ): - return tokenlist - - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) - - if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException: - #~ print ("Exception raised:", err) - err = None - if self.debugActions[2]: - err = sys.exc_info()[1] - self.debugActions[2]( instring, tokensStart, self, err ) - if self.failAction: - if err is None: - err = sys.exc_info()[1] - self.failAction( instring, tokensStart, self, err ) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or loc >= len(instring): - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - - tokens = self.postParse( instring, loc, tokens ) - - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - err = sys.exc_info()[1] - self.debugActions[2]( instring, tokensStart, self, err ) - raise - else: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - - if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) - - return loc, retTokens - - def tryParse( self, instring, loc ): - try: - return self._parse( instring, loc, doActions=False )[0] - except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): - lookup = (self,instring,loc,callPreParse,doActions) - if lookup in ParserElement._exprArgCache: - value = ParserElement._exprArgCache[ lookup ] - if isinstance(value, Exception): - raise value - return (value[0],value[1].copy()) - else: - try: - value = self._parseNoCache( instring, loc, doActions, callPreParse ) - ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) - return value - except ParseBaseException: - pe = sys.exc_info()[1] - ParserElement._exprArgCache[ lookup ] = pe - raise - - _parse = _parseNoCache - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - _exprArgCache = {} - def resetCache(): - ParserElement._exprArgCache.clear() - resetCache = staticmethod(resetCache) - - _packratEnabled = False - def enablePackrat(): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method C{ParserElement.enablePackrat()}. If - your program uses C{psyco} to "compile as you go", you must call - C{enablePackrat} before calling C{psyco.full()}. If you do not do this, - Python will crash. For best results, call C{enablePackrat()} immediately - after importing pyparsing. - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - ParserElement._parse = ParserElement._parseCache - enablePackrat = staticmethod(enablePackrat) - - def parseString( self, instring, parseAll=False ): - """Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - If you want the grammar to require that the entire input string be - successfully parsed, then set C{parseAll} to True (equivalent to ending - the grammar with C{L{StringEnd()}}). - - Note: C{parseString} implicitly calls C{expandtabs()} on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the C{loc} argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - calling C{parseWithTabs} on your grammar before calling C{parseString} - (see L{I{parseWithTabs}<parseWithTabs>}) - - define your parse action using the full C{(s,loc,toks)} signature, and - reference the input string using the parse action's C{s} argument - - explictly expand the tabs in your input string before calling - C{parseString} - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - #~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse( instring, 0 ) - if parseAll: - loc = self.preParse( instring, loc ) - se = Empty() + StringEnd() - se._parse( instring, loc ) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - else: - return tokens - - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): - """Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - C{maxMatches} argument, to clip scanning after 'n' matches are found. If - C{overlap} is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See L{I{parseString}<parseString>} for more information on parsing - strings with embedded tabs.""" - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) - except ParseException: - loc = preloc+1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn( instring, loc ) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc+1 - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def transformString( self, instring ): - """Extension to C{L{scanString}}, to modify matching text with modified tokens that may - be returned from a parse action. To use C{transformString}, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking C{transformString()} on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. C{transformString()} returns the resulting transformed string.""" - out = [] - lastE = 0 - # force preservation of <TAB>s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) - if t: - if isinstance(t,ParseResults): - out += t.asList() - elif isinstance(t,list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def searchString( self, instring, maxMatches=_MAX_INT ): - """Another extension to C{L{scanString}}, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - C{maxMatches} argument, to clip searching after 'n' matches are found. - """ - try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) - except ParseBaseException: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def __add__(self, other ): - """Implementation of + operator - returns C{L{And}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - - def __radd__(self, other ): - """Implementation of + operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """Implementation of - operator, returns C{L{And}} with error stop""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, And._ErrorStop(), other ] ) - - def __rsub__(self, other ): - """Implementation of - operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self,other): - """Implementation of * operator, allows use of C{expr * 3} in place of - C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer - tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples - may also include C{None} as in: - - C{expr*(n,None)} or C{expr*(n,)} is equivalent - to C{expr*n + L{ZeroOrMore}(expr)} - (read as "at least n instances of C{expr}") - - C{expr*(None,n)} is equivalent to C{expr*(0,n)} - (read as "0 to n instances of C{expr}") - - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} - - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} - - Note that C{expr*(None,n)} does not raise an exception if - more than n exprs exist in the input stream; that is, - C{expr*(None,n)} does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - C{expr*(None,n) + ~expr} - - """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") - - if (optElements): - def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self]*minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self]*minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other ): - """Implementation of | operator - returns C{L{MatchFirst}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) - - def __ror__(self, other ): - """Implementation of | operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other ): - """Implementation of ^ operator - returns C{L{Or}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or( [ self, other ] ) - - def __rxor__(self, other ): - """Implementation of ^ operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other ): - """Implementation of & operator - returns C{L{Each}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each( [ self, other ] ) - - def __rand__(self, other ): - """Implementation of & operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__( self ): - """Implementation of ~ operator - returns C{L{NotAny}}""" - return NotAny( self ) - - def __call__(self, name): - """Shortcut for C{L{setResultsName}}, with C{listAllMatches=default}:: - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - could be written as:: - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") - - If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be - passed as C{True}. - """ - return self.setResultsName(name) - - def suppress( self ): - """Suppresses the output of this C{ParserElement}; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress( self ) - - def leaveWhitespace( self ): - """Disables the skipping of whitespace before matching the characters in the - C{ParserElement}'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars( self, chars ): - """Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs( self ): - """Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. - Must be called before C{parseString} when the input grammar contains elements that - match C{<TAB>} characters.""" - self.keepTabs = True - return self - - def ignore( self, other ): - """Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - """ - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - self.ignoreExprs.append( other.copy() ) - else: - self.ignoreExprs.append( Suppress( other.copy() ) ) - return self - - def setDebugActions( self, startAction, successAction, exceptionAction ): - """Enable display of debugging messages while doing pattern matching.""" - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug( self, flag=True ): - """Enable display of debugging messages while doing pattern matching. - Set C{flag} to True to enable, False to disable.""" - if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) - else: - self.debug = False - return self - - def __str__( self ): - return self.name - - def __repr__( self ): - return _ustr(self) - - def streamline( self ): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion( self, parseElementList ): - pass - - def validate( self, validateTrace=[] ): - """Check defined expressions for valid structure, check for infinite recursive definitions.""" - self.checkRecursion( [] ) - - def parseFile( self, file_or_filename, parseAll=False ): - """Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - f = open(file_or_filename, "r") - file_contents = f.read() - f.close() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - exc = sys.exc_info()[1] - raise exc - - def getException(self): - return ParseException("",0,self.errmsg,self) - - def __getattr__(self,aname): - if aname == "myException": - self.myException = ret = self.getException(); - return ret; - else: - raise AttributeError("no such attribute " + aname) - - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or self.__dict__ == other.__dict__ - elif isinstance(other, basestring): - try: - self.parseString(_ustr(other), parseAll=True) - return True - except ParseBaseException: - return False - else: - return super(ParserElement,self)==other - - def __ne__(self,other): - return not (self == other) - - def __hash__(self): - return hash(id(self)) - - def __req__(self,other): - return self == other - - def __rne__(self,other): - return not (self == other) - - -class Token(ParserElement): - """Abstract C{ParserElement} subclass, for defining atomic matching patterns.""" - def __init__( self ): - super(Token,self).__init__( savelist=False ) - - def setName(self, name): - s = super(Token,self).setName(name) - self.errmsg = "Expected " + self.name - return s - - -class Empty(Token): - """An empty token, will always match.""" - def __init__( self ): - super(Empty,self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """A token that will never match.""" - def __init__( self ): - super(NoMatch,self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl( self, instring, loc, doActions=True ): - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - -class Literal(Token): - """Token to exactly match a specified string.""" - def __init__( self, matchString ): - super(Literal,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc -_L = Literal -ParserElement.literalStringClass = Literal - -class Keyword(Token): - """Token to exactly match a specified string as a keyword, that is, it must be - immediately followed by a non-keyword character. Compare with C{L{Literal}}:: - Literal("if") will match the leading C{'if'} in C{'ifAndOnlyIf'}. - Keyword("if") will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} - Accepts two optional constructor arguments in addition to the keyword string: - C{identChars} is a string of characters that would be valid identifier characters, - defaulting to all alphanumerics + "_" and "$"; C{caseless} allows case-insensitive - matching, default is C{False}. - """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" - - def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): - super(Keyword,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl( self, instring, loc, doActions=True ): - if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - def copy(self): - c = super(Keyword,self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - def setDefaultKeywordChars( chars ): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) - -class CaselessLiteral(Literal): - """Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class CaselessKeyword(Keyword): - def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) - - def parseImpl( self, instring, loc, doActions=True ): - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class Word(Token): - """Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, - an optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. An optional - C{exclude} parameter can list characters that might be found in - the input C{bodyChars} string; useful to define a word of all printables - except for one or two characters, for instance. - """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() - if excludeChars: - initChars = ''.join([c for c in initChars if c not in excludeChars]) - if bodyChars: - bodyChars = ''.join([c for c in bodyChars if c not in excludeChars]) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars : - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.bodyCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" - try: - self.re = re.compile( self.reString ) - except: - self.re = None - - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(Word,self).__str__() - except: - pass - - - if self.strRepr is None: - - def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." - else: - return s - - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - - -class Regex(Token): - """Token for matching strings that match a given regular expression. - Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. - """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0): - """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" - super(Regex,self).__init__() - - if isinstance(pattern, basestring): - if len(pattern) == 0: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif isinstance(pattern, Regex.compiledREtype): - self.re = pattern - self.pattern = \ - self.reString = str(pattern) - self.flags = flags - - else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - d = result.groupdict() - ret = ParseResults(result.group()) - if d: - for k in d: - ret[k] = d[k] - return loc,ret - - def __str__( self ): - try: - return super(Regex,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - -class QuotedString(Token): - """Token for matching strings that are delimited by quoting characters. - """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): - """ - Defined with the following parameters: - - quoteChar - string of one or more characters defining the quote delimiting string - - escChar - character to escape quotes, typically backslash (default=None) - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) - - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) - """ - super(QuotedString,self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if len(quoteChar) == 0: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if len(endQuoteChar) == 0: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' - ) - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - charset = ''.join(set(self.quoteChar[0]+self.endQuoteChar[0])).replace('^',r'\^').replace('-',r'\-') - self.escCharReplacePattern = re.escape(self.escChar)+("([%s])" % charset) - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None - if not result: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] - - if isinstance(ret,basestring): - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__( self ): - try: - return super(QuotedString,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """Token for matching words composed of characters *not* in a given set. - Defined with string containing all disallowed characters, and an optional - minimum, maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. - """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) - self.mayIndexError = False - - def parseImpl( self, instring, loc, doActions=True ): - if instring[loc] in self.notChars: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): - loc += 1 - - if loc - start < self.minLen: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(CharsNotIn, self).__str__() - except: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """Special matching class for matching whitespace. Normally, whitespace is ignored - by pyparsing grammars. This class is included when some whitespace structures - are significant. Define with a string containing the whitespace characters to be - matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, - as defined for the C{L{Word}} class.""" - whiteStrs = { - " " : "<SPC>", - "\t": "<TAB>", - "\n": "<LF>", - "\r": "<CR>", - "\f": "<FF>", - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() - self.matchWhite = ws - self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) - #~ self.leaveWhitespace() - self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - #~ raise ParseException( instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """Token to advance to a specific column of input text; useful for tabular report scraping.""" - def __init__( self, colno ): - super(GoToColumn,self).__init__() - self.col = colno - - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) - if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) - newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] - return newloc, ret - -class LineStart(_PositionToken): - """Matches if current position is at the beginning of a line within the parse string""" - def __init__( self ): - super(LineStart,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected start of line" - - def preParse( self, instring, loc ): - preloc = super(LineStart,self).preParse(instring,loc) - if instring[preloc] == "\n": - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - if not( loc==0 or - (loc == self.preParse( instring, 0 )) or - (instring[loc-1] == "\n") ): #col(loc, instring) != 1: - #~ raise ParseException( instring, loc, "Expected start of line" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class LineEnd(_PositionToken): - """Matches if current position is at the end of a line within the parse string""" - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected end of line" - - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): - if instring[loc] == "\n": - return loc+1, "\n" - else: - #~ raise ParseException( instring, loc, "Expected end of line" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - elif loc == len(instring): - return loc+1, [] - else: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class StringStart(_PositionToken): - """Matches if current position is at the beginning of the parse string""" - def __init__( self ): - super(StringStart,self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): - #~ raise ParseException( instring, loc, "Expected start of text" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class StringEnd(_PositionToken): - """Matches if current position is at the end of the parse string""" - def __init__( self ): - super(StringEnd,self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc < len(instring): - #~ raise ParseException( instring, loc, "Expected end of text" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - elif loc == len(instring): - return loc+1, [] - elif loc > len(instring): - return loc, [] - else: - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class WordStart(_PositionToken): - """Matches if the current position is at the beginning of a Word, and - is not preceded by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of - the string being parsed, or at the beginning of a line. - """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True ): - if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - -class WordEnd(_PositionToken): - """Matches if the current position is at the end of a Word, and - is not followed by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of - the string being parsed, or at the end of a line. - """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True ): - instrlen = len(instring) - if instrlen>0 and loc<instrlen: - if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): - #~ raise ParseException( instring, loc, "Expected end of word" ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - - -class ParseExpression(ParserElement): - """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, list ): - self.exprs = exprs - elif isinstance( exprs, basestring ): - self.exprs = [ Literal( exprs ) ] - else: - try: - self.exprs = list( exprs ) - except TypeError: - self.exprs = [ exprs ] - self.callPreparse = False - - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) - self.strRepr = None - return self - - def leaveWhitespace( self ): - """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - else: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - return self - - def __str__( self ): - try: - return super(ParseExpression,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) - return self.strRepr - - def streamline( self ): - super(ParseExpression,self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): - other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - return self - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion( [] ) - - def copy(self): - ret = super(ParseExpression,self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - -class And(ParseExpression): - """Requires all given C{ParseExpression}s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the C{'+'} operator. - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) - self.leaveWhitespace() - - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.setWhitespaceChars( exprs[0].whiteChars ) - self.skipWhitespace = exprs[0].skipWhitespace - self.callPreparse = True - - 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 - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse( instring, loc, doActions ) - except ParseSyntaxException: - raise - except ParseBaseException: - pe = sys.exc_info()[1] - raise ParseSyntaxException(pe) - except IndexError: - raise ParseSyntaxException( ParseException(instring, len(instring), self.errmsg, self) ) - else: - loc, exprtokens = e._parse( instring, loc, doActions ) - if exprtokens or exprtokens.keys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = Literal( other ) - return self.append( other ) #And( [ self, other ] ) - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - if not e.mayReturnEmpty: - break - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the expression that matches the longest string will be used. - May be constructed using the C{'^'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxMatchLoc = -1 - maxException = None - for e in self.exprs: - try: - loc2 = e.tryParse( instring, loc ) - except ParseException: - err = sys.exc_info()[1] - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - else: - if loc2 > maxMatchLoc: - maxMatchLoc = loc2 - maxMatchExp = e - - if maxMatchLoc < 0: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - return maxMatchExp._parse( instring, loc, doActions ) - - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class MatchFirst(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the first one listed is the one that will match. - May be constructed using the C{'|'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) - if exprs: - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse( instring, loc, doActions ) - return ret - except ParseException, err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class Each(ParseExpression): - """Requires all given C{ParseExpression}s to be found, but in any order. - Expressions may be separated by whitespace. - May be constructed using the C{'&'} operator. - """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.skipWhitespace = True - self.initExprGroups = True - - def parseImpl( self, instring, loc, doActions=True ): - if self.initExprGroups: - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and e not in opt1 ] - self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse( instring, tmpLoc ) - except ParseException: - failed.append(e) - else: - matchOrder.append(e) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] - - resultlist = [] - for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) - resultlist.append(results) - - finalResults = ParseResults([]) - for r in resultlist: - dups = {} - for k in r.keys(): - if k in finalResults.keys(): - tmp = ParseResults(finalResults[k]) - tmp += ParseResults(r[k]) - dups[k] = tmp - finalResults += ParseResults(r) - for k,v in dups.items(): - finalResults[k] = v - return loc, finalResults - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class ParseElementEnhance(ParserElement): - """Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.""" - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - expr = Literal(expr) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) - else: - raise ParseException("",loc,self.errmsg,self) - - def leaveWhitespace( self ): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - else: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - return self - - def streamline( self ): - super(ParseElementEnhance,self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion( self, parseElementList ): - if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] - if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion( [] ) - - def __str__( self ): - try: - return super(ParseElementEnhance,self).__str__() - except: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """Lookahead matching of the given parse expression. C{FollowedBy} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression matches at the current - position. C{FollowedBy} always returns a null token list.""" - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - self.expr.tryParse( instring, loc ) - return loc, [] - - -class NotAny(ParseElementEnhance): - """Lookahead to disallow matching with the given parse expression. C{NotAny} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression does *not* match at the current - position. Also, C{NotAny} does *not* skip over leading whitespace. C{NotAny} - always returns a null token list. May be constructed using the '~' operator.""" - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - try: - self.expr.tryParse( instring, loc ) - except (ParseException,IndexError): - pass - else: - #~ raise ParseException(instring, loc, self.errmsg ) - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - - -class ZeroOrMore(ParseElementEnhance): - """Optional repetition of zero or more of the given expression.""" - def __init__( self, expr ): - super(ZeroOrMore,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - tokens = [] - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - - -class OneOrMore(ParseElementEnhance): - """Repetition of one or more of the given expression.""" - def parseImpl( self, instring, loc, doActions=True ): - # must be at least one - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - try: - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(OneOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -_optionalNotMatched = _NullToken() -class Optional(ParseElementEnhance): - """Optional matching of the given expression. - A default return string can also be specified, if the optional expression - is not found. - """ - def __init__( self, exprs, default=_optionalNotMatched ): - super(Optional,self).__init__( exprs, savelist=False ) - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [ self.defaultValue ] - else: - tokens = [] - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - - -class SkipTo(ParseElementEnhance): - """Token for skipping over all undefined text until the matched expression is found. - If C{include} is set to true, the matched expression is also parsed (the skipped text - and matched expression are returned as a 2-element list). The C{ignore} - argument is used to define grammars (typically quoted strings and comments) that - might contain false matches. - """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.asList = False - if failOn is not None and isinstance(failOn, basestring): - self.failOn = Literal(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - startLoc = loc - instrlen = len(instring) - expr = self.expr - failParse = False - while loc <= instrlen: - try: - if self.failOn: - try: - self.failOn.tryParse(instring, loc) - except ParseBaseException: - pass - else: - failParse = True - raise ParseException(instring, loc, "Found expression " + str(self.failOn)) - failParse = False - if self.ignoreExpr is not None: - while 1: - try: - loc = self.ignoreExpr.tryParse(instring,loc) - # print "found ignoreExpr, advance to", loc - except ParseBaseException: - break - expr._parse( instring, loc, doActions=False, callPreParse=False ) - skipText = instring[startLoc:loc] - if self.includeMatch: - loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) - if mat: - skipRes = ParseResults( skipText ) - skipRes += mat - return loc, [ skipRes ] - else: - return loc, [ skipText ] - else: - return loc, [ skipText ] - except (ParseException,IndexError): - if failParse: - raise - else: - loc += 1 - exc = self.myException - exc.loc = loc - exc.pstr = instring - raise exc - -class Forward(ParseElementEnhance): - """Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. - - Note: take care when assigning to C{Forward} not to overlook precedence of operators. - Specifically, '|' has a lower precedence than '<<', so that:: - fwdExpr << a | b | c - will actually be evaluated as:: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the C{Forward}:: - fwdExpr << (a | b | c) - Converting to use the '<<=' operator instead will avoid this problem. - """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) - - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass(other) - self.expr = other - self.mayReturnEmpty = other.mayReturnEmpty - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return None - __ilshift__ = __lshift__ - - def leaveWhitespace( self ): - self.skipWhitespace = False - return self - - def streamline( self ): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate( self, validateTrace=[] ): - if self not in validateTrace: - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse - try: - if self.expr is not None: - retString = _ustr(self.expr) - else: - retString = "None" - finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString - - def copy(self): - if self.expr is not None: - return super(Forward,self).copy() - else: - ret = Forward() - ret << self - return ret - -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." - -class TokenConverter(ParseElementEnhance): - """Abstract subclass of C{ParseExpression}, for converting parsed results.""" - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) - self.saveAsList = False - -class Upcase(TokenConverter): - """Converter to upper case all matching tokens.""" - def __init__(self, *args): - super(Upcase,self).__init__(*args) - warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", - DeprecationWarning,stacklevel=2) - - def postParse( self, instring, loc, tokenlist ): - return list(map( string.upper, tokenlist )) - - -class Combine(TokenConverter): - """Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the input string; - this can be disabled by specifying C{'adjacent=False'} in the constructor. - """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore( self, other ): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super( Combine, self).ignore( other ) - return self - - def postParse( self, instring, loc, tokenlist ): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) - - if self.resultsName and len(retToks.keys())>0: - return [ retToks ] - else: - return retToks - -class Group(TokenConverter): - """Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.""" - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] - -class Dict(TokenConverter): - """Converter to return a repetitive expression as a list, but also as a dictionary. - Each element can also be referenced using the first token in the expression as its key. - Useful for tabular report scraping when the first column can be used as a item key. - """ - def __init__( self, exprs ): - super(Dict,self).__init__( exprs ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey,int): - ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) - else: - dictvalue = tok.copy() #ParseResults(i) - del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) - - if self.resultsName: - return [ tokenlist ] - else: - return tokenlist - - -class Suppress(TokenConverter): - """Converter for ignoring the results of a parsed expression.""" - def postParse( self, instring, loc, tokenlist ): - return [] - - def suppress( self ): - return self - - -class OnlyOnce(object): - """Wrapper for parse actions, to ensure they are only called once.""" - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self,s,l,t): - if not self.called: - results = self.callable(s,l,t) - self.called = True - return results - raise ParseException(s,l,"") - def reset(self): - self.called = False - -def traceParseAction(f): - """Decorator for debugging parse actions.""" - f = _trim_arity(f) - def z(*paArgs): - thisFunc = f.func_name - s,l,t = paArgs[-3:] - if len(paArgs)>3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) - try: - ret = f(*paArgs) - except Exception: - exc = sys.exc_info()[1] - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) - raise - sys.stderr.write( "<<leaving %s (ret: %s)\n" % (thisFunc,ret) ) - return ret - try: - z.__name__ = f.__name__ - except AttributeError: - pass - return z - -# -# global helpers -# -def delimitedList( expr, delim=",", combine=False ): - """Helper to define a delimited list of expressions - the delimiter defaults to ','. - By default, the list elements and delimiters can have intervening whitespace, and - comments, but this can be overridden by passing C{combine=True} in the constructor. - If C{combine} is set to C{True}, the matching tokens are returned as a single token - string, with the delimiters included; otherwise, the matching tokens are returned - as a list of tokens, with the delimiters suppressed. - """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." - if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) - else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) - -def countedArray( expr, intExpr=None ): - """Helper to define a counted list of expressions. - This helper defines a pattern of the form:: - integer expr expr expr... - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. - """ - arrayExpr = Forward() - def countFieldParseAction(s,l,t): - n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ) - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i,list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches a - previous literal, will also match the leading C{"1:1"} in C{"1:10"}. - If this is not desired, use C{matchPreviousExpr}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - def copyTokenToRepeater(s,l,t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And( [ Literal(tt) for tt in tflat ] ) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def matchPreviousExpr(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches by - expressions, will *not* match the leading C{"1:1"} in C{"1:10"}; - the expressions are evaluated first, and then compared, so - C{"1"} is compared with C{"10"}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep << e2 - def copyTokenToRepeater(s,l,t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") - return _ustr(s) - -def oneOf( strs, caseless=False, useRegex=True ): - """Helper to quickly define a set of alternative Literals, and makes sure to do - longest-first testing when there is a conflict, regardless of the input order, - but returns a C{L{MatchFirst}} for best performance. - - Parameters: - - strs - a string of space-delimited literals, or a list of string literals - - caseless - (default=False) - treat all literals as caseless - - useRegex - (default=True) - as an optimization, will generate a Regex - object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or - if creating a C{Regex} raises an exception) - """ - if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral - else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal - - if isinstance(strs,(list,tuple)): - symbols = list(strs[:]) - elif isinstance(strs,basestring): - symbols = strs.split() - else: - warnings.warn("Invalid argument to oneOf, expected string or list", - SyntaxWarning, stacklevel=2) - - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) - else: - return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) - except: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - - # last resort, just use MatchFirst - return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) - -def dictOf( key, value ): - """Helper to easily and clearly define a dictionary by specifying the respective patterns - for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens - in the proper order. The key pattern can include delimiting markers or punctuation, - as long as they are suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the C{Dict} results can include named token - fields. - """ - return Dict( ZeroOrMore( Group ( key + value ) ) ) - -def originalTextFor(expr, asString=True): - """Helper to return the original, untokenized text for a given expression. Useful to - restore the parsed fields of an HTML start tag into the raw tag text itself, or to - revert separate tokens with intervening whitespace back to the original matching - input text. Simpler to use than the parse action C{L{keepOriginalText}}, and does not - require the inspect module to chase up the call stack. By default, returns a - string containing the original parsed text. - - If the optional C{asString} argument is passed as C{False}, then the return value is a - C{L{ParseResults}} containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if - the expression passed to C{L{originalTextFor}} contains expressions with defined - results names, you must set C{asString} to C{False} if you want to preserve those - results name values.""" - locMarker = Empty().setParseAction(lambda s,loc,t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] - else: - def extractText(s,l,t): - del t[:] - t.insert(0, s[t._original_start:t._original_end]) - del t["_original_start"] - del t["_original_end"] - matchExpr.setParseAction(extractText) - return matchExpr - -def ungroup(expr): - """Helper to undo pyparsing's default grouping of And expressions, even - if all but one are non-empty.""" - return TokenConverter(expr).setParseAction(lambda t:t[0]) - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" - -_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) - -def srange(s): - r"""Helper to easily define string ranges for use in Word construction. Borrows - syntax from regexp '[]' string range definitions:: - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - The input string must be enclosed in []'s, and the returned string is the expanded - character set joined into a single string. - The values enclosed in the []'s may be:: - a single character - an escaped character with a leading backslash (such as \- or \]) - an escaped hex character with a leading '\x' (\x21, which is a '!' character) - (\0x## is also supported for backwards compatibility) - an escaped octal character with a leading '\0' (\041, which is a '!' character) - a range of any of the above, separated by a dash ('a-z', etc.) - any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) - """ - try: - return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) - except: - return "" - -def matchOnlyAtCol(n): - """Helper method for defining parse actions that require matching at a specific - column in the input text. - """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """Helper method for common parse actions that simply return a literal value. Especially - useful when used with C{L{transformString<ParserElement.transformString>}()}. - """ - def _replFunc(*args): - return [replStr] - return _replFunc - -def removeQuotes(s,l,t): - """Helper parse action for removing quotation marks from parsed quoted strings. - To use, add this parse action to quoted string using:: - quotedString.setParseAction( removeQuotes ) - """ - return t[0][1:-1] - -def upcaseTokens(s,l,t): - """Helper parse action to convert tokens to upper case.""" - return [ tt.upper() for tt in map(_ustr,t) ] - -def downcaseTokens(s,l,t): - """Helper parse action to convert tokens to lower case.""" - return [ tt.lower() for tt in map(_ustr,t) ] - -def keepOriginalText(s,startLoc,t): - """DEPRECATED - use new helper method C{L{originalTextFor}}. - Helper parse action to preserve original parsed text, - overriding any nested parse actions.""" - try: - endloc = getTokensEndLoc() - except ParseException: - raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") - del t[:] - t += ParseResults(s[startLoc:endloc]) - return t - -def getTokensEndLoc(): - """Method to be called from within a parse action to determine the end - location of the parsed tokens.""" - import inspect - fstack = inspect.stack() - try: - # search up the stack (through intervening argument normalizers) for correct calling routine - for f in fstack[2:]: - if f[3] == "_parseNoCache": - endloc = f[0].f_locals["loc"] - return endloc - else: - raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") - finally: - del fstack - -def _makeTags(tagStr, xml): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - else: - printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") - - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % tagStr) - openTag.tag = resname - closeTag.tag = resname - return openTag, closeTag - -def makeHTMLTags(tagStr): - """Helper to construct opening and closing tag expressions for HTML, given a tag name""" - return _makeTags( tagStr, False ) - -def makeXMLTags(tagStr): - """Helper to construct opening and closing tag expressions for XML, given a tag name""" - return _makeTags( tagStr, True ) - -def withAttribute(*args,**attrDict): - """Helper to create a validating parse action to be used with start tags created - with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - C{<TD>} or C{<DIV>}. - - Call C{withAttribute} with a series of attribute names and values. Specify the list - of filter attributes names and values as: - - keyword arguments, as in C{(align="right")}, or - - as an explicit dict with C{**} operator, when an attribute name is also a Python - reserved word, as in C{**{"class":"Customer", "align":"right"}} - - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) - For attribute names with a namespace prefix, you must use the second form. Attribute - names are matched insensitive to upper/lower case. - - To verify that the attribute exists, but without specifying a value, pass - C{withAttribute.ANY_VALUE} as the value. - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: - if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -opAssoc = _Constants() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def operatorPrecedence( baseExpr, opList ): - """Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary or - binary, left- or right-associative. Parse actions can also be attached - to operator expressions. - - Parameters: - - baseExpr - expression representing the most basic element for the nested - - opList - list of tuples, one for each operator precedence level in the - expression grammar; each tuple is of the form - (opExpr, numTerms, rightLeftAssoc, parseAction), where: - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; - if numTerms is 3, opExpr is a tuple of two expressions, for the - two operators separating the 3 terms - - numTerms is the number of terms for this operator (must - be 1, 2, or 3) - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted) - """ - ret = Forward() - lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward()#.setName("expr%d" % i) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) - else: - matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) - else: - matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - matchExpr.setParseAction( pa ) - thisExpr << ( matchExpr | lastExpr ) - lastExpr = thisExpr - ret << lastExpr - return ret - -dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") -sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") -quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()) - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """Helper method for defining nested lists enclosed in opening and closing - delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list (default="("); can also be a pyparsing expression - - closer - closing character for a nested list (default=")"); can also be a pyparsing expression - - content - expression for items within the nested lists (default=None) - - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the C{ignoreExpr} argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. - The default is L{quotedString}, but if no expressions are to be ignored, - then pass C{None} for this argument. - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) - else: - ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """Helper method for defining space-delimited indentation blocks, such as - those used to define block statements in Python source code. - - Parameters: - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single grammar - should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond the - the current level; set to False for block of left-most statements - (default=True) - - A valid block must contain at least one C{blockStatement}. - """ - def checkPeerIndent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") - - def checkSubIndent(s,l,t): - curCol = col(l,s) - if curCol > indentStack[-1]: - indentStack.append( curCol ) - else: - raise ParseException(s,l,"not a subentry") - - def checkUnindent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) - INDENT = Empty() + Empty().setParseAction(checkSubIndent) - PEER = Empty().setParseAction(checkPeerIndent) - UNDENT = Empty().setParseAction(checkUnindent) - if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) - else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) -commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() -_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) -replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") - -htmlComment = Regex(r"<!--[\s\S]*?-->") -restOfLine = Regex(r".*").leaveWhitespace() -dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") -cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?<!\\)|\Z))").setName("C++ style comment") - -javaStyleComment = cppStyleComment -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -_noncomma = "".join( [ c for c in printables if c != "," ] ) -_commasepitem = Combine(OneOrMore(Word(_noncomma) + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") - - -if __name__ == "__main__": - - def test( teststring ): - try: - tokens = simpleSQL.parseString( teststring ) - tokenlist = tokens.asList() - print (teststring + "->" + str(tokenlist)) - print ("tokens = " + str(tokens)) - print ("tokens.columns = " + str(tokens.columns)) - print ("tokens.tables = " + str(tokens.tables)) - print (tokens.asXML("SQL",True)) - except ParseBaseException: - err = sys.exc_info()[1] - print (teststring + "->") - print (err.line) - print (" "*(err.column-1) + "^") - print (err) - print() - - selectToken = CaselessLiteral( "select" ) - fromToken = CaselessLiteral( "from" ) - - ident = Word( alphas, alphanums + "_$" ) - columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - columnNameList = Group( delimitedList( columnName ) )#.setName("columns") - tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - tableNameList = Group( delimitedList( tableName ) )#.setName("tables") - simpleSQL = ( selectToken + \ - ( '*' | columnNameList ).setResultsName( "columns" ) + \ - fromToken + \ - tableNameList.setResultsName( "tables" ) ) - - test( "SELECT * from XYZZY, ABC" ) - test( "select * from SYS.XYZZY" ) - test( "Select A from Sys.dual" ) - test( "Select AA,BB,CC from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Xelect A, B, C from Sys.dual" ) - test( "Select A, B, C frox Sys.dual" ) - test( "Select" ) - test( "Select ^^^ frox Sys.dual" ) - test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/src/pyparsing_py3.py b/src/pyparsing_py3.py deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL3B5cGFyc2luZ19weTMucHk=..0000000000000000000000000000000000000000 --- a/src/pyparsing_py3.py +++ /dev/null @@ -1,3595 +0,0 @@ -# module pyparsing.py -# -# Copyright (c) 2003-2011 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -#from __future__ import generators - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars - -The pyparsing module is an alternative approach to creating and executing simple grammars, -vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you -don't need to learn a new syntax for defining grammars or matching expressions - the parsing module -provides a library of classes that you use to construct the grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form C{"<salutation>, <addressee>!"}):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word( alphas ) + "," + Word( alphas ) + "!" - - hello = "Hello, World!" - print hello, "->", greet.parseString( hello ) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the self-explanatory -class names, and the use of '+', '|' and '^' operators. - -The parsed results returned from C{parseString()} can be accessed as a nested list, a dictionary, or an -object with named attributes. - -The pyparsing module handles some of the problems that are typically vexing when writing text parsers: - - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments -""" - -__version__ = "1.5.7" -__versionTime__ = "3 August 2012 05:00" -__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -import collections -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) - -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'getTokensEndLoc', 'hexnums', -'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', -] - -_MAX_INT = sys.maxsize -basestring = str -unichr = chr -_ustr = str - -# build list of single arg builtins, that can be used as parse actions -singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max] - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] - for from_,to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -class _Constants(object): - pass - -alphas = string.ascii_lowercase + string.ascii_uppercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - - def __getattr__( self, aname ): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) - else: - raise AttributeError(aname) - - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): - return _ustr(self) - def markInputline( self, markerString = ">!<" ): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join( [line_str[:line_column], - markerString, line_str[line_column:]]) - return line_str.strip() - def __dir__(self): - return "loc msg pstr parserElement lineno col line " \ - "markInputline __str__ __repr__".split() - -class ParseException(ParseBaseException): - """exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - pass - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like C{L{ParseFatalException}}, but thrown internally when an - C{L{ErrorStop<And._ErrorStop>}} ('-' operator) indicates that parsing is to stop immediately because - an unbacktrackable syntax error has been found""" - def __init__(self, pe): - super(ParseSyntaxException, self).__init__( - pe.pstr, pe.loc, pe.msg, pe.parserElement) - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by C{validate()} if the grammar could be improperly recursive""" - def __init__( self, parseElementList ): - self.parseElementTrace = parseElementList - - def __str__( self ): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): - return self.tup[i] - def __repr__(self): - return repr(self.tup) - def setOffset(self,i): - self.tup = (self.tup[0],i) - -class ParseResults(object): - """Structured parse results, to provide multiple means of access to the parsed data: - - as a list (C{len(results)}) - - by list index (C{results[0], results[1]}, etc.) - - by attribute (C{results.<resultsName>}) - """ - #~ __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) - def __new__(cls, toklist, name=None, asList=True, modal=True ): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, toklist, name=None, asList=True, modal=True, isinstance=isinstance ): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - if isinstance(toklist, list): - self.__toklist = toklist[:] - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not toklist in (None,'',[]): - if isinstance(toklist,basestring): - toklist = [ toklist ] - if asList: - if isinstance(toklist,ParseResults): - self[name] = _ParseResultsWithOffset(toklist.copy(),0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError,TypeError,IndexError): - self[name] = toklist - - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) - - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] - sub = v[0] - elif isinstance(k,int): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] - sub = v - if isinstance(sub,ParseResults): - sub.__parent = wkref(self) - - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i+1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__( self, k ): - return k in self.__tokdict - - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return len( self.__toklist ) > 0 - __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def keys( self ): - """Returns all named result keys.""" - return self.__tokdict.keys() - - def pop( self, index=-1 ): - """Removes and returns item at specified index (default=last). - Will work with either numeric indices or dict-key indicies.""" - ret = self[index] - del self[index] - return ret - - def get(self, key, defaultValue=None): - """Returns named result matching the given key, or if there is no - such name, then returns the given C{defaultValue} or C{None} if no - C{defaultValue} is specified.""" - if key in self: - return self[key] - else: - return defaultValue - - def insert( self, index, insStr ): - """Inserts new element at location index in the list of parsed tokens.""" - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name in self.__tokdict: - occurrences = self.__tokdict[name] - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def items( self ): - """Returns all named result keys and values as a list of tuples.""" - return [(k,self[k]) for k in self.__tokdict] - - def values( self ): - """Returns all named result values.""" - return [ v[-1][0] for v in self.__tokdict.values() ] - - def __getattr__( self, name ): - if True: #name not in self.__slots__: - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - return None - - def __add__( self, other ): - ret = self.copy() - ret += other - return ret - - def __iadd__( self, other ): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: - self[k] = v - if isinstance(v[0],ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) - return self - - def __radd__(self, other): - if isinstance(other,int) and other == 0: - return self.copy() - - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) - - def __str__( self ): - out = [] - for i in self.__toklist: - if isinstance(i, ParseResults): - out.append(_ustr(i)) - else: - out.append(repr(i)) - return '[' + ', '.join(out) + ']' - - def _asStringList( self, sep='' ): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance( item, ParseResults ): - out += item._asStringList() - else: - out.append( _ustr(item) ) - return out - - def asList( self ): - """Returns the parse results as a nested list of matching tokens, all converted to strings.""" - out = [] - for res in self.__toklist: - if isinstance(res,ParseResults): - out.append( res.asList() ) - else: - out.append( res ) - return out - - def asDict( self ): - """Returns the named parse results as dictionary.""" - return dict( self.items() ) - - def copy( self ): - """Returns a new copy of a C{ParseResults} object.""" - ret = ParseResults( self.__toklist ) - ret.__tokdict = self.__tokdict.copy() - ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) - ret.__name = self.__name - return ret - - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): - """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" - nl = "\n" - out = [] - namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist ] ) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [ nl, indent, "<", selfTag, ">" ] - - worklist = self.__toklist - for i,res in enumerate(worklist): - if isinstance(res,ParseResults): - if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] - - out += [ nl, indent, "</", selfTag, ">" ] - return "".join(out) - - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: - if sub is v: - return k - return None - - def getName(self): - """Returns the results name for this token expression.""" - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - self.__tokdict.values()[0][0][1] in (0,-1)): - return self.__tokdict.keys()[0] - else: - return None - - def dump(self,indent='',depth=0): - """Diagnostic method for listing out the contents of a C{ParseResults}. - Accepts an optional C{indent} argument so that this string can be embedded - in a nested display of other data.""" - out = [] - out.append( indent+_ustr(self.asList()) ) - keys = self.items() - keys.sort() - for k,v in keys: - if out: - out.append('\n') - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): - if v.keys(): - out.append( v.dump(indent,depth+1) ) - else: - out.append(_ustr(v)) - else: - out.append(_ustr(v)) - return "".join(out) - - # add support for pickle protocol - def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) - - def __setstate__(self,state): - self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __dir__(self): - return dir(super(ParseResults,self)) + list(self.keys()) - -collections.MutableMapping.register(ParseResults) - -def col (loc,strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return (loc<len(strg) and strg[loc] == '\n') and 1 or loc - strg.rfind("\n", 0, loc) - -def lineno(loc,strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return strg.count("\n",0,loc) + 1 - -def line( loc, strg ): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR+1:nextCR] - else: - return strg[lastCR+1:] - -def _defaultStartDebugAction( instring, loc, expr ): - print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s,l,t: func(t) - limit = maxargs - def wrapper(*args): - nonlocal limit - while 1: - try: - return func(*args[limit:]) - except TypeError: - if limit: - limit -= 1 - continue - raise - return wrapper - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - def setDefaultWhitespaceChars( chars ): - """Overrides the default whitespace chars - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) - - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - """ - ParserElement.literalStringClass = cls - inlineLiteralsUsing = staticmethod(inlineLiteralsUsing) - - def __init__( self, savelist=False ): - self.parseAction = list() - self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy( self ): - """Make a copy of this C{ParserElement}. Useful for defining different parse actions - for the same parsing pattern, using copies of the original parse element.""" - cpy = copy.copy( self ) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName( self, name ): - """Define name for this expression, for use in debugging.""" - self.name = name - self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg - return self - - def setResultsName( self, name, listAllMatches=False ): - """Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original C{ParserElement} object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - C{expr("name")} in place of C{expr.setResultsName("name")} - - see L{I{__call__}<__call__>}. - """ - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches=True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self,breakFlag = True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set C{breakFlag} to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse,"_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction( self, *fns, **kwargs ): - """Define action to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, - C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{parseString}<parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def addParseAction( self, *fns, **kwargs ): - """Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.""" - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) - return self - - def setFailAction( self, fn ): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - C{fn(s,loc,expr,err)} where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw C{L{ParseFatalException}} - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables( self, instring, loc ): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc,dummy = e._parse( instring, loc ) - exprsFound = True - except ParseException: - pass - return loc - - def preParse( self, instring, loc ): - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl( self, instring, loc, doActions=True ): - return loc, [] - - def postParse( self, instring, loc, tokenlist ): - return tokenlist - - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) - - if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException as err: - #~ print ("Exception raised:", err) - if self.debugActions[2]: - self.debugActions[2]( instring, tokensStart, self, err ) - if self.failAction: - self.failAction( instring, tokensStart, self, err ) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or loc >= len(instring): - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - - tokens = self.postParse( instring, loc, tokens ) - - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException as err: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - self.debugActions[2]( instring, tokensStart, self, err ) - raise - else: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - - if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) - - return loc, retTokens - - def tryParse( self, instring, loc ): - try: - return self._parse( instring, loc, doActions=False )[0] - except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): - lookup = (self,instring,loc,callPreParse,doActions) - if lookup in ParserElement._exprArgCache: - value = ParserElement._exprArgCache[ lookup ] - if isinstance(value, Exception): - raise value - return (value[0],value[1].copy()) - else: - try: - value = self._parseNoCache( instring, loc, doActions, callPreParse ) - ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) - return value - except ParseBaseException as pe: - pe.__traceback__ = None - ParserElement._exprArgCache[ lookup ] = pe - raise - - _parse = _parseNoCache - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - _exprArgCache = {} - def resetCache(): - ParserElement._exprArgCache.clear() - resetCache = staticmethod(resetCache) - - _packratEnabled = False - def enablePackrat(): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method C{ParserElement.enablePackrat()}. If - your program uses C{psyco} to "compile as you go", you must call - C{enablePackrat} before calling C{psyco.full()}. If you do not do this, - Python will crash. For best results, call C{enablePackrat()} immediately - after importing pyparsing. - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - ParserElement._parse = ParserElement._parseCache - enablePackrat = staticmethod(enablePackrat) - - def parseString( self, instring, parseAll=False ): - """Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - If you want the grammar to require that the entire input string be - successfully parsed, then set C{parseAll} to True (equivalent to ending - the grammar with C{L{StringEnd()}}). - - Note: C{parseString} implicitly calls C{expandtabs()} on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the C{loc} argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - calling C{parseWithTabs} on your grammar before calling C{parseString} - (see L{I{parseWithTabs}<parseWithTabs>}) - - define your parse action using the full C{(s,loc,toks)} signature, and - reference the input string using the parse action's C{s} argument - - explictly expand the tabs in your input string before calling - C{parseString} - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - #~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse( instring, 0 ) - if parseAll: - loc = self.preParse( instring, loc ) - se = Empty() + StringEnd() - se._parse( instring, loc ) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - else: - return tokens - - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): - """Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - C{maxMatches} argument, to clip scanning after 'n' matches are found. If - C{overlap} is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See L{I{parseString}<parseString>} for more information on parsing - strings with embedded tabs.""" - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) - except ParseException: - loc = preloc+1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn( instring, loc ) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc+1 - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def transformString( self, instring ): - """Extension to C{L{scanString}}, to modify matching text with modified tokens that may - be returned from a parse action. To use C{transformString}, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking C{transformString()} on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. C{transformString()} returns the resulting transformed string.""" - out = [] - lastE = 0 - # force preservation of <TAB>s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) - if t: - if isinstance(t,ParseResults): - out += t.asList() - elif isinstance(t,list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def searchString( self, instring, maxMatches=_MAX_INT ): - """Another extension to C{L{scanString}}, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - C{maxMatches} argument, to clip searching after 'n' matches are found. - """ - try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def __add__(self, other ): - """Implementation of + operator - returns C{L{And}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - - def __radd__(self, other ): - """Implementation of + operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """Implementation of - operator, returns C{L{And}} with error stop""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, And._ErrorStop(), other ] ) - - def __rsub__(self, other ): - """Implementation of - operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self,other): - """Implementation of * operator, allows use of C{expr * 3} in place of - C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer - tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples - may also include C{None} as in: - - C{expr*(n,None)} or C{expr*(n,)} is equivalent - to C{expr*n + L{ZeroOrMore}(expr)} - (read as "at least n instances of C{expr}") - - C{expr*(None,n)} is equivalent to C{expr*(0,n)} - (read as "0 to n instances of C{expr}") - - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} - - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} - - Note that C{expr*(None,n)} does not raise an exception if - more than n exprs exist in the input stream; that is, - C{expr*(None,n)} does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - C{expr*(None,n) + ~expr} - - """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") - - if (optElements): - def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self]*minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self]*minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other ): - """Implementation of | operator - returns C{L{MatchFirst}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) - - def __ror__(self, other ): - """Implementation of | operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other ): - """Implementation of ^ operator - returns C{L{Or}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or( [ self, other ] ) - - def __rxor__(self, other ): - """Implementation of ^ operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other ): - """Implementation of & operator - returns C{L{Each}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each( [ self, other ] ) - - def __rand__(self, other ): - """Implementation of & operator when left operand is not a C{L{ParserElement}}""" - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__( self ): - """Implementation of ~ operator - returns C{L{NotAny}}""" - return NotAny( self ) - - def __call__(self, name): - """Shortcut for C{L{setResultsName}}, with C{listAllMatches=default}:: - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - could be written as:: - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") - - If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be - passed as C{True}. - """ - return self.setResultsName(name) - - def suppress( self ): - """Suppresses the output of this C{ParserElement}; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress( self ) - - def leaveWhitespace( self ): - """Disables the skipping of whitespace before matching the characters in the - C{ParserElement}'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars( self, chars ): - """Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs( self ): - """Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. - Must be called before C{parseString} when the input grammar contains elements that - match C{<TAB>} characters.""" - self.keepTabs = True - return self - - def ignore( self, other ): - """Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - """ - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - self.ignoreExprs.append( other.copy() ) - else: - self.ignoreExprs.append( Suppress( other.copy() ) ) - return self - - def setDebugActions( self, startAction, successAction, exceptionAction ): - """Enable display of debugging messages while doing pattern matching.""" - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug( self, flag=True ): - """Enable display of debugging messages while doing pattern matching. - Set C{flag} to True to enable, False to disable.""" - if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) - else: - self.debug = False - return self - - def __str__( self ): - return self.name - - def __repr__( self ): - return _ustr(self) - - def streamline( self ): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion( self, parseElementList ): - pass - - def validate( self, validateTrace=[] ): - """Check defined expressions for valid structure, check for infinite recursive definitions.""" - self.checkRecursion( [] ) - - def parseFile( self, file_or_filename, parseAll=False ): - """Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - f = open(file_or_filename, "r") - file_contents = f.read() - f.close() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or self.__dict__ == other.__dict__ - elif isinstance(other, basestring): - try: - self.parseString(_ustr(other), parseAll=True) - return True - except ParseBaseException: - return False - else: - return super(ParserElement,self)==other - - def __ne__(self,other): - return not (self == other) - - def __hash__(self): - return hash(id(self)) - - def __req__(self,other): - return self == other - - def __rne__(self,other): - return not (self == other) - - -class Token(ParserElement): - """Abstract C{ParserElement} subclass, for defining atomic matching patterns.""" - def __init__( self ): - super(Token,self).__init__( savelist=False ) - - def setName(self, name): - s = super(Token,self).setName(name) - self.errmsg = "Expected " + self.name - return s - - -class Empty(Token): - """An empty token, will always match.""" - def __init__( self ): - super(Empty,self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """A token that will never match.""" - def __init__( self ): - super(NoMatch,self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl( self, instring, loc, doActions=True ): - raise ParseException(instring, loc, self.errmsg, self) - - -class Literal(Token): - """Token to exactly match a specified string.""" - def __init__( self, matchString ): - super(Literal,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) -_L = Literal -ParserElement.literalStringClass = Literal - -class Keyword(Token): - """Token to exactly match a specified string as a keyword, that is, it must be - immediately followed by a non-keyword character. Compare with C{L{Literal}}:: - Literal("if") will match the leading C{'if'} in C{'ifAndOnlyIf'}. - Keyword("if") will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} - Accepts two optional constructor arguments in addition to the keyword string: - C{identChars} is a string of characters that would be valid identifier characters, - defaulting to all alphanumerics + "_" and "$"; C{caseless} allows case-insensitive - matching, default is C{False}. - """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" - - def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): - super(Keyword,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl( self, instring, loc, doActions=True ): - if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - - def copy(self): - c = super(Keyword,self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - def setDefaultKeywordChars( chars ): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) - -class CaselessLiteral(Literal): - """Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString - raise ParseException(instring, loc, self.errmsg, self) - -class CaselessKeyword(Keyword): - def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) - - def parseImpl( self, instring, loc, doActions=True ): - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - -class Word(Token): - """Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, - an optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. An optional - C{exclude} parameter can list characters that might be found in - the input C{bodyChars} string; useful to define a word of all printables - except for one or two characters, for instance. - """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() - if excludeChars: - initChars = ''.join([c for c in initChars if c not in excludeChars]) - if bodyChars: - bodyChars = ''.join([c for c in bodyChars if c not in excludeChars]) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars : - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.bodyCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" - try: - self.re = re.compile( self.reString ) - except: - self.re = None - - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(Word,self).__str__() - except: - pass - - - if self.strRepr is None: - - def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." - else: - return s - - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - - -class Regex(Token): - """Token for matching strings that match a given regular expression. - Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. - """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0): - """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" - super(Regex,self).__init__() - - if isinstance(pattern, basestring): - if len(pattern) == 0: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif isinstance(pattern, Regex.compiledREtype): - self.re = pattern - self.pattern = \ - self.reString = str(pattern) - self.flags = flags - - else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - d = result.groupdict() - ret = ParseResults(result.group()) - if d: - for k in d: - ret[k] = d[k] - return loc,ret - - def __str__( self ): - try: - return super(Regex,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - -class QuotedString(Token): - """Token for matching strings that are delimited by quoting characters. - """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): - """ - Defined with the following parameters: - - quoteChar - string of one or more characters defining the quote delimiting string - - escChar - character to escape quotes, typically backslash (default=None) - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) - - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) - """ - super(QuotedString,self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if len(quoteChar) == 0: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if len(endQuoteChar) == 0: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' - ) - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - charset = ''.join(set(self.quoteChar[0]+self.endQuoteChar[0])).replace('^',r'\^').replace('-',r'\-') - self.escCharReplacePattern = re.escape(self.escChar)+("([%s])" % charset) - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] - - if isinstance(ret,basestring): - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__( self ): - try: - return super(QuotedString,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """Token for matching words composed of characters *not* in a given set. - Defined with string containing all disallowed characters, and an optional - minimum, maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. - """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) - self.mayIndexError = False - - def parseImpl( self, instring, loc, doActions=True ): - if instring[loc] in self.notChars: - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(CharsNotIn, self).__str__() - except: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """Special matching class for matching whitespace. Normally, whitespace is ignored - by pyparsing grammars. This class is included when some whitespace structures - are significant. Define with a string containing the whitespace characters to be - matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, - as defined for the C{L{Word}} class.""" - whiteStrs = { - " " : "<SPC>", - "\t": "<TAB>", - "\n": "<LF>", - "\r": "<CR>", - "\f": "<FF>", - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() - self.matchWhite = ws - self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) - #~ self.leaveWhitespace() - self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): - raise ParseException(instring, loc, self.errmsg, self) - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """Token to advance to a specific column of input text; useful for tabular report scraping.""" - def __init__( self, colno ): - super(GoToColumn,self).__init__() - self.col = colno - - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) - if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) - newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] - return newloc, ret - -class LineStart(_PositionToken): - """Matches if current position is at the beginning of a line within the parse string""" - def __init__( self ): - super(LineStart,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected start of line" - - def preParse( self, instring, loc ): - preloc = super(LineStart,self).preParse(instring,loc) - if instring[preloc] == "\n": - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - if not( loc==0 or - (loc == self.preParse( instring, 0 )) or - (instring[loc-1] == "\n") ): #col(loc, instring) != 1: - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class LineEnd(_PositionToken): - """Matches if current position is at the end of a line within the parse string""" - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected end of line" - - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): - if instring[loc] == "\n": - return loc+1, "\n" - else: - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class StringStart(_PositionToken): - """Matches if current position is at the beginning of the parse string""" - def __init__( self ): - super(StringStart,self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class StringEnd(_PositionToken): - """Matches if current position is at the end of the parse string""" - def __init__( self ): - super(StringEnd,self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc < len(instring): - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - elif loc > len(instring): - return loc, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class WordStart(_PositionToken): - """Matches if the current position is at the beginning of a Word, and - is not preceded by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of - the string being parsed, or at the beginning of a line. - """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True ): - if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class WordEnd(_PositionToken): - """Matches if the current position is at the end of a Word, and - is not followed by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of - the string being parsed, or at the end of a line. - """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True ): - instrlen = len(instring) - if instrlen>0 and loc<instrlen: - if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - -class ParseExpression(ParserElement): - """Abstract subclass of ParserElement, for combining and post-processing parsed tokens.""" - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, list ): - self.exprs = exprs - elif isinstance( exprs, basestring ): - self.exprs = [ Literal( exprs ) ] - else: - try: - self.exprs = list( exprs ) - except TypeError: - self.exprs = [ exprs ] - self.callPreparse = False - - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) - self.strRepr = None - return self - - def leaveWhitespace( self ): - """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - else: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - return self - - def __str__( self ): - try: - return super(ParseExpression,self).__str__() - except: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) - return self.strRepr - - def streamline( self ): - super(ParseExpression,self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): - other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - return self - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion( [] ) - - def copy(self): - ret = super(ParseExpression,self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - -class And(ParseExpression): - """Requires all given C{ParseExpression}s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the C{'+'} operator. - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) - self.leaveWhitespace() - - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.setWhitespaceChars( exprs[0].whiteChars ) - self.skipWhitespace = exprs[0].skipWhitespace - self.callPreparse = True - - 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 - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse( instring, loc, doActions ) - except ParseSyntaxException: - raise - except ParseBaseException as pe: - pe.__traceback__ = None - raise ParseSyntaxException(pe) - except IndexError: - raise ParseSyntaxException( ParseException(instring, len(instring), self.errmsg, self) ) - else: - loc, exprtokens = e._parse( instring, loc, doActions ) - if exprtokens or exprtokens.keys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = Literal( other ) - return self.append( other ) #And( [ self, other ] ) - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - if not e.mayReturnEmpty: - break - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the expression that matches the longest string will be used. - May be constructed using the C{'^'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxMatchLoc = -1 - maxException = None - for e in self.exprs: - try: - loc2 = e.tryParse( instring, loc ) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - else: - if loc2 > maxMatchLoc: - maxMatchLoc = loc2 - maxMatchExp = e - - if maxMatchLoc < 0: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - return maxMatchExp._parse( instring, loc, doActions ) - - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class MatchFirst(ParseExpression): - """Requires that at least one C{ParseExpression} is found. - If two expressions match, the first one listed is the one that will match. - May be constructed using the C{'|'} operator. - """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) - if exprs: - self.mayReturnEmpty = False - for e in self.exprs: - if e.mayReturnEmpty: - self.mayReturnEmpty = True - break - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse( instring, loc, doActions ) - return ret - except ParseException as err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class Each(ParseExpression): - """Requires all given C{ParseExpression}s to be found, but in any order. - Expressions may be separated by whitespace. - May be constructed using the C{'&'} operator. - """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) - self.mayReturnEmpty = True - for e in self.exprs: - if not e.mayReturnEmpty: - self.mayReturnEmpty = False - break - self.skipWhitespace = True - self.initExprGroups = True - - def parseImpl( self, instring, loc, doActions=True ): - if self.initExprGroups: - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and e not in opt1 ] - self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse( instring, tmpLoc ) - except ParseException: - failed.append(e) - else: - matchOrder.append(e) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += list(e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt) - - resultlist = [] - for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) - resultlist.append(results) - - finalResults = ParseResults([]) - for r in resultlist: - dups = {} - for k in r.keys(): - if k in finalResults.keys(): - tmp = ParseResults(finalResults[k]) - tmp += ParseResults(r[k]) - dups[k] = tmp - finalResults += ParseResults(r) - for k,v in dups.items(): - finalResults[k] = v - return loc, finalResults - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class ParseElementEnhance(ParserElement): - """Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.""" - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - expr = Literal(expr) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) - else: - raise ParseException("",loc,self.errmsg,self) - - def leaveWhitespace( self ): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - else: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - return self - - def streamline( self ): - super(ParseElementEnhance,self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion( self, parseElementList ): - if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] - if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion( [] ) - - def __str__( self ): - try: - return super(ParseElementEnhance,self).__str__() - except: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """Lookahead matching of the given parse expression. C{FollowedBy} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression matches at the current - position. C{FollowedBy} always returns a null token list.""" - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - self.expr.tryParse( instring, loc ) - return loc, [] - - -class NotAny(ParseElementEnhance): - """Lookahead to disallow matching with the given parse expression. C{NotAny} - does *not* advance the parsing position within the input string, it only - verifies that the specified parse expression does *not* match at the current - position. Also, C{NotAny} does *not* skip over leading whitespace. C{NotAny} - always returns a null token list. May be constructed using the '~' operator.""" - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - try: - self.expr.tryParse( instring, loc ) - except (ParseException,IndexError): - pass - else: - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - - -class ZeroOrMore(ParseElementEnhance): - """Optional repetition of zero or more of the given expression.""" - def __init__( self, expr ): - super(ZeroOrMore,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - tokens = [] - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - - -class OneOrMore(ParseElementEnhance): - """Repetition of one or more of the given expression.""" - def parseImpl( self, instring, loc, doActions=True ): - # must be at least one - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - try: - hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) - while 1: - if hasIgnoreExprs: - preloc = self._skipIgnorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self.expr._parse( instring, preloc, doActions ) - if tmptokens or tmptokens.keys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - - def setResultsName( self, name, listAllMatches=False ): - ret = super(OneOrMore,self).setResultsName(name,listAllMatches) - ret.saveAsList = True - return ret - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -_optionalNotMatched = _NullToken() -class Optional(ParseElementEnhance): - """Optional matching of the given expression. - A default return string can also be specified, if the optional expression - is not found. - """ - def __init__( self, exprs, default=_optionalNotMatched ): - super(Optional,self).__init__( exprs, savelist=False ) - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [ self.defaultValue ] - else: - tokens = [] - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - - -class SkipTo(ParseElementEnhance): - """Token for skipping over all undefined text until the matched expression is found. - If C{include} is set to true, the matched expression is also parsed (the skipped text - and matched expression are returned as a 2-element list). The C{ignore} - argument is used to define grammars (typically quoted strings and comments) that - might contain false matches. - """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.asList = False - if failOn is not None and isinstance(failOn, basestring): - self.failOn = Literal(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - startLoc = loc - instrlen = len(instring) - expr = self.expr - failParse = False - while loc <= instrlen: - try: - if self.failOn: - try: - self.failOn.tryParse(instring, loc) - except ParseBaseException: - pass - else: - failParse = True - raise ParseException(instring, loc, "Found expression " + str(self.failOn)) - failParse = False - if self.ignoreExpr is not None: - while 1: - try: - loc = self.ignoreExpr.tryParse(instring,loc) - # print("found ignoreExpr, advance to", loc) - except ParseBaseException: - break - expr._parse( instring, loc, doActions=False, callPreParse=False ) - skipText = instring[startLoc:loc] - if self.includeMatch: - loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) - if mat: - skipRes = ParseResults( skipText ) - skipRes += mat - return loc, [ skipRes ] - else: - return loc, [ skipText ] - else: - return loc, [ skipText ] - except (ParseException,IndexError): - if failParse: - raise - else: - loc += 1 - raise ParseException(instring, loc, self.errmsg, self) - -class Forward(ParseElementEnhance): - """Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. - - Note: take care when assigning to C{Forward} not to overlook precedence of operators. - Specifically, '|' has a lower precedence than '<<', so that:: - fwdExpr << a | b | c - will actually be evaluated as:: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the C{Forward}:: - fwdExpr << (a | b | c) - Converting to use the '<<=' operator instead will avoid this problem. - """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) - - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement.literalStringClass(other) - self.expr = other - self.mayReturnEmpty = other.mayReturnEmpty - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return None - __ilshift__ = __lshift__ - - def leaveWhitespace( self ): - self.skipWhitespace = False - return self - - def streamline( self ): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate( self, validateTrace=[] ): - if self not in validateTrace: - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse - try: - if self.expr is not None: - retString = _ustr(self.expr) - else: - retString = "None" - finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString - - def copy(self): - if self.expr is not None: - return super(Forward,self).copy() - else: - ret = Forward() - ret << self - return ret - -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." - -class TokenConverter(ParseElementEnhance): - """Abstract subclass of C{ParseExpression}, for converting parsed results.""" - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) - self.saveAsList = False - -class Upcase(TokenConverter): - """Converter to upper case all matching tokens.""" - def __init__(self, *args): - super(Upcase,self).__init__(*args) - warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", - DeprecationWarning,stacklevel=2) - - def postParse( self, instring, loc, tokenlist ): - return list(map( str.upper, tokenlist )) - - -class Combine(TokenConverter): - """Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the input string; - this can be disabled by specifying C{'adjacent=False'} in the constructor. - """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore( self, other ): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super( Combine, self).ignore( other ) - return self - - def postParse( self, instring, loc, tokenlist ): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) - - if self.resultsName and len(retToks.keys())>0: - return [ retToks ] - else: - return retToks - -class Group(TokenConverter): - """Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.""" - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] - -class Dict(TokenConverter): - """Converter to return a repetitive expression as a list, but also as a dictionary. - Each element can also be referenced using the first token in the expression as its key. - Useful for tabular report scraping when the first column can be used as a item key. - """ - def __init__( self, exprs ): - super(Dict,self).__init__( exprs ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey,int): - ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) - else: - dictvalue = tok.copy() #ParseResults(i) - del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) - - if self.resultsName: - return [ tokenlist ] - else: - return tokenlist - - -class Suppress(TokenConverter): - """Converter for ignoring the results of a parsed expression.""" - def postParse( self, instring, loc, tokenlist ): - return [] - - def suppress( self ): - return self - - -class OnlyOnce(object): - """Wrapper for parse actions, to ensure they are only called once.""" - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self,s,l,t): - if not self.called: - results = self.callable(s,l,t) - self.called = True - return results - raise ParseException(s,l,"") - def reset(self): - self.called = False - -def traceParseAction(f): - """Decorator for debugging parse actions.""" - f = _trim_arity(f) - def z(*paArgs): - thisFunc = f.func_name - s,l,t = paArgs[-3:] - if len(paArgs)>3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) - try: - ret = f(*paArgs) - except Exception as exc: - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) - raise - sys.stderr.write( "<<leaving %s (ret: %s)\n" % (thisFunc,ret) ) - return ret - try: - z.__name__ = f.__name__ - except AttributeError: - pass - return z - -# -# global helpers -# -def delimitedList( expr, delim=",", combine=False ): - """Helper to define a delimited list of expressions - the delimiter defaults to ','. - By default, the list elements and delimiters can have intervening whitespace, and - comments, but this can be overridden by passing C{combine=True} in the constructor. - If C{combine} is set to C{True}, the matching tokens are returned as a single token - string, with the delimiters included; otherwise, the matching tokens are returned - as a list of tokens, with the delimiters suppressed. - """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." - if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) - else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) - -def countedArray( expr, intExpr=None ): - """Helper to define a counted list of expressions. - This helper defines a pattern of the form:: - integer expr expr expr... - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. - """ - arrayExpr = Forward() - def countFieldParseAction(s,l,t): - n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ) - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i,list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches a - previous literal, will also match the leading C{"1:1"} in C{"1:10"}. - If this is not desired, use C{matchPreviousExpr}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - def copyTokenToRepeater(s,l,t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And( [ Literal(tt) for tt in tflat ] ) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def matchPreviousExpr(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches by - expressions, will *not* match the leading C{"1:1"} in C{"1:10"}; - the expressions are evaluated first, and then compared, so - C{"1"} is compared with C{"10"}. - Do *not* use with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep << e2 - def copyTokenToRepeater(s,l,t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - return rep - -def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") - return _ustr(s) - -def oneOf( strs, caseless=False, useRegex=True ): - """Helper to quickly define a set of alternative Literals, and makes sure to do - longest-first testing when there is a conflict, regardless of the input order, - but returns a C{L{MatchFirst}} for best performance. - - Parameters: - - strs - a string of space-delimited literals, or a list of string literals - - caseless - (default=False) - treat all literals as caseless - - useRegex - (default=True) - as an optimization, will generate a Regex - object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or - if creating a C{Regex} raises an exception) - """ - if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral - else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal - - if isinstance(strs,(list,tuple)): - symbols = list(strs[:]) - elif isinstance(strs,basestring): - symbols = strs.split() - else: - warnings.warn("Invalid argument to oneOf, expected string or list", - SyntaxWarning, stacklevel=2) - - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) - else: - return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) - except: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - - # last resort, just use MatchFirst - return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) - -def dictOf( key, value ): - """Helper to easily and clearly define a dictionary by specifying the respective patterns - for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens - in the proper order. The key pattern can include delimiting markers or punctuation, - as long as they are suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the C{Dict} results can include named token - fields. - """ - return Dict( ZeroOrMore( Group ( key + value ) ) ) - -def originalTextFor(expr, asString=True): - """Helper to return the original, untokenized text for a given expression. Useful to - restore the parsed fields of an HTML start tag into the raw tag text itself, or to - revert separate tokens with intervening whitespace back to the original matching - input text. Simpler to use than the parse action C{L{keepOriginalText}}, and does not - require the inspect module to chase up the call stack. By default, returns a - string containing the original parsed text. - - If the optional C{asString} argument is passed as C{False}, then the return value is a - C{L{ParseResults}} containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if - the expression passed to C{L{originalTextFor}} contains expressions with defined - results names, you must set C{asString} to C{False} if you want to preserve those - results name values.""" - locMarker = Empty().setParseAction(lambda s,loc,t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] - else: - def extractText(s,l,t): - del t[:] - t.insert(0, s[t._original_start:t._original_end]) - del t["_original_start"] - del t["_original_end"] - matchExpr.setParseAction(extractText) - return matchExpr - -def ungroup(expr): - """Helper to undo pyparsing's default grouping of And expressions, even - if all but one are non-empty.""" - return TokenConverter(expr).setParseAction(lambda t:t[0]) - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" - -_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) - -def srange(s): - r"""Helper to easily define string ranges for use in Word construction. Borrows - syntax from regexp '[]' string range definitions:: - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - The input string must be enclosed in []'s, and the returned string is the expanded - character set joined into a single string. - The values enclosed in the []'s may be:: - a single character - an escaped character with a leading backslash (such as \- or \]) - an escaped hex character with a leading '\x' (\x21, which is a '!' character) - (\0x## is also supported for backwards compatibility) - an escaped octal character with a leading '\0' (\041, which is a '!' character) - a range of any of the above, separated by a dash ('a-z', etc.) - any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) - """ - try: - return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) - except: - return "" - -def matchOnlyAtCol(n): - """Helper method for defining parse actions that require matching at a specific - column in the input text. - """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """Helper method for common parse actions that simply return a literal value. Especially - useful when used with C{L{transformString<ParserElement.transformString>}()}. - """ - def _replFunc(*args): - return [replStr] - return _replFunc - -def removeQuotes(s,l,t): - """Helper parse action for removing quotation marks from parsed quoted strings. - To use, add this parse action to quoted string using:: - quotedString.setParseAction( removeQuotes ) - """ - return t[0][1:-1] - -def upcaseTokens(s,l,t): - """Helper parse action to convert tokens to upper case.""" - return [ tt.upper() for tt in map(_ustr,t) ] - -def downcaseTokens(s,l,t): - """Helper parse action to convert tokens to lower case.""" - return [ tt.lower() for tt in map(_ustr,t) ] - -def keepOriginalText(s,startLoc,t): - """DEPRECATED - use new helper method C{L{originalTextFor}}. - Helper parse action to preserve original parsed text, - overriding any nested parse actions.""" - try: - endloc = getTokensEndLoc() - except ParseException: - raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") - del t[:] - t += ParseResults(s[startLoc:endloc]) - return t - -def getTokensEndLoc(): - """Method to be called from within a parse action to determine the end - location of the parsed tokens.""" - import inspect - fstack = inspect.stack() - try: - # search up the stack (through intervening argument normalizers) for correct calling routine - for f in fstack[2:]: - if f[3] == "_parseNoCache": - endloc = f[0].f_locals["loc"] - return endloc - else: - raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") - finally: - del fstack - -def _makeTags(tagStr, xml): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - else: - printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") - - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % tagStr) - openTag.tag = resname - closeTag.tag = resname - return openTag, closeTag - -def makeHTMLTags(tagStr): - """Helper to construct opening and closing tag expressions for HTML, given a tag name""" - return _makeTags( tagStr, False ) - -def makeXMLTags(tagStr): - """Helper to construct opening and closing tag expressions for XML, given a tag name""" - return _makeTags( tagStr, True ) - -def withAttribute(*args,**attrDict): - """Helper to create a validating parse action to be used with start tags created - with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - C{<TD>} or C{<DIV>}. - - Call C{withAttribute} with a series of attribute names and values. Specify the list - of filter attributes names and values as: - - keyword arguments, as in C{(align="right")}, or - - as an explicit dict with C{**} operator, when an attribute name is also a Python - reserved word, as in C{**{"class":"Customer", "align":"right"}} - - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) - For attribute names with a namespace prefix, you must use the second form. Attribute - names are matched insensitive to upper/lower case. - - To verify that the attribute exists, but without specifying a value, pass - C{withAttribute.ANY_VALUE} as the value. - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: - if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -opAssoc = _Constants() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def operatorPrecedence( baseExpr, opList ): - """Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary or - binary, left- or right-associative. Parse actions can also be attached - to operator expressions. - - Parameters: - - baseExpr - expression representing the most basic element for the nested - - opList - list of tuples, one for each operator precedence level in the - expression grammar; each tuple is of the form - (opExpr, numTerms, rightLeftAssoc, parseAction), where: - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; - if numTerms is 3, opExpr is a tuple of two expressions, for the - two operators separating the 3 terms - - numTerms is the number of terms for this operator (must - be 1, 2, or 3) - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted) - """ - ret = Forward() - lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward()#.setName("expr%d" % i) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) - else: - matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) - else: - matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - matchExpr.setParseAction( pa ) - thisExpr << ( matchExpr | lastExpr ) - lastExpr = thisExpr - ret << lastExpr - return ret - -dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") -sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") -quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()) - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """Helper method for defining nested lists enclosed in opening and closing - delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list (default="("); can also be a pyparsing expression - - closer - closing character for a nested list (default=")"); can also be a pyparsing expression - - content - expression for items within the nested lists (default=None) - - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the C{ignoreExpr} argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. - The default is L{quotedString}, but if no expressions are to be ignored, - then pass C{None} for this argument. - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) - else: - ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """Helper method for defining space-delimited indentation blocks, such as - those used to define block statements in Python source code. - - Parameters: - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single grammar - should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond the - the current level; set to False for block of left-most statements - (default=True) - - A valid block must contain at least one C{blockStatement}. - """ - def checkPeerIndent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") - - def checkSubIndent(s,l,t): - curCol = col(l,s) - if curCol > indentStack[-1]: - indentStack.append( curCol ) - else: - raise ParseException(s,l,"not a subentry") - - def checkUnindent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) - INDENT = Empty() + Empty().setParseAction(checkSubIndent) - PEER = Empty().setParseAction(checkPeerIndent) - UNDENT = Empty().setParseAction(checkUnindent) - if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) - else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) -commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() -_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) -replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") - -htmlComment = Regex(r"<!--[\s\S]*?-->") -restOfLine = Regex(r".*").leaveWhitespace() -dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") -cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?<!\\)|\Z))").setName("C++ style comment") - -javaStyleComment = cppStyleComment -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -_noncomma = "".join( [ c for c in printables if c != "," ] ) -_commasepitem = Combine(OneOrMore(Word(_noncomma) + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") - - -if __name__ == "__main__": - - def test( teststring ): - try: - tokens = simpleSQL.parseString( teststring ) - tokenlist = tokens.asList() - print (teststring + "->" + str(tokenlist)) - print ("tokens = " + str(tokens)) - print ("tokens.columns = " + str(tokens.columns)) - print ("tokens.tables = " + str(tokens.tables)) - print (tokens.asXML("SQL",True)) - except ParseBaseException as err: - print (teststring + "->") - print (err.line) - print (" "*(err.column-1) + "^") - print (err) - print() - - selectToken = CaselessLiteral( "select" ) - fromToken = CaselessLiteral( "from" ) - - ident = Word( alphas, alphanums + "_$" ) - columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - columnNameList = Group( delimitedList( columnName ) )#.setName("columns") - tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) - tableNameList = Group( delimitedList( tableName ) )#.setName("tables") - simpleSQL = ( selectToken + \ - ( '*' | columnNameList ).setResultsName( "columns" ) + \ - fromToken + \ - tableNameList.setResultsName( "tables" ) ) - - test( "SELECT * from XYZZY, ABC" ) - test( "select * from SYS.XYZZY" ) - test( "Select A from Sys.dual" ) - test( "Select AA,BB,CC from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Select A, B, C from Sys.dual" ) - test( "Xelect A, B, C from Sys.dual" ) - test( "Select A, B, C frox Sys.dual" ) - test( "Select" ) - test( "Select ^^^ frox Sys.dual" ) - test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/src/setup.py b/src/setup.py deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL3NldHVwLnB5..0000000000000000000000000000000000000000 --- a/src/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -"""Setup script for the pyparsing module distribution.""" -from distutils.core import setup - -import sys -import os - -_PY3 = sys.version_info[0] > 2 - -if _PY3: - from pyparsing_py3 import __version__ as pyparsing_version -else: - from pyparsing_py2 import __version__ as pyparsing_version - -modules = ["pyparsing",] - -# make sure that a pyparsing.py file exists - if not, copy the appropriate version -def fileexists(fname): - try: - return bool(os.stat(fname)) - except: - return False - -def copyfile(fromname, toname): - outf = open(toname,'w') - outf.write(open(fromname).read()) - outf.close() - -if "MAKING_PYPARSING_RELEASE" not in os.environ and not fileexists("pyparsing.py"): - if _PY3: - from_file = "pyparsing_py3.py" - else: - from_file = "pyparsing_py2.py" - copyfile(from_file, "pyparsing.py") - -setup(# Distribution meta-data - name = "pyparsing", - version = pyparsing_version, - description = "Python parsing module", - author = "Paul McGuire", - author_email = "ptmcg@users.sourceforge.net", - url = "http://pyparsing.wikispaces.com/", - download_url = "http://sourceforge.net/project/showfiles.php?group_id=97203", - license = "MIT License", - py_modules = modules, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - ] - ) diff --git a/src/unitTests.py b/src/unitTests.py deleted file mode 100644 index 9e68e57f81f41e2a63c9ac76ff543ae008265345_c3JjL3VuaXRUZXN0cy5weQ==..0000000000000000000000000000000000000000 --- a/src/unitTests.py +++ /dev/null @@ -1,2375 +0,0 @@ -# -*- coding: UTF-8 -*- -from unittest import TestCase, TestSuite, TextTestRunner -import unittest -from pyparsing import ParseException -import HTMLTestRunner - -import sys -import pprint -import pdb - -# see which Python implementation we are running -CPYTHON_ENV = (sys.platform == "win32") -IRON_PYTHON_ENV = (sys.platform == "cli") -JYTHON_ENV = sys.platform.startswith("java") - -TEST_USING_PACKRAT = True -#~ TEST_USING_PACKRAT = False - -# simple utility for flattening nested lists -def flatten(L): - if type(L) is not list: return [L] - if L == []: return L - return flatten(L[0]) + flatten(L[1:]) - -""" -class ParseTest(TestCase): - def setUp(self): - pass - - def runTest(self): - assert 1==1, "we've got bigger problems..." - - def tearDown(self): - pass -""" - -class ParseTestCase(TestCase): - def setUp(self): - print ">>>> Starting test",str(self) - - def runTest(self): - pass - - def tearDown(self): - print "<<<< End of test",str(self) - print - - def __str__(self): - return self.__class__.__name__ - -class PyparsingTestInit(ParseTestCase): - def setUp(self): - from pyparsing import __version__ as pyparsingVersion - print "Beginning test of pyparsing, version", pyparsingVersion - print "Python version", sys.version - def tearDown(self): - pass - -class ParseASMLTest(ParseTestCase): - def runTest(self): - import parseASML - files = [ ("A52759.txt", 2150, True, True, 0.38, 25, "21:47:17", "22:07:32", 235), - ("24141506_P5107RM59_399A1457N1_PHS04", 373,True, True, 0.5, 1, "11:35:25", "11:37:05", 183), - ("24141506_P5107RM59_399A1457N1_PHS04B", 373, True, True, 0.5, 1, "01:02:54", "01:04:49", 186), - ("24157800_P5107RM74_399A1828M1_PHS04", 1141, True, False, 0.5, 13, "00:00:54", "23:59:48", 154) ] - for testFile,numToks,trkInpUsed,trkOutpUsed,maxDelta,numWafers,minProcBeg,maxProcEnd,maxLevStatsIV in files: - print "Parsing",testFile,"...", - #~ text = "\n".join( [ line for line in file(testFile) ] ) - #~ results = parseASML.BNF().parseString( text ) - results = parseASML.BNF().parseFile( testFile ) - #~ pprint.pprint( results.asList() ) - #~ pprint.pprint( results.batchData.asList() ) - #~ print results.batchData.keys() - - allToks = flatten( results.asList() ) - assert len(allToks) == numToks, \ - "wrong number of tokens parsed (%s), got %d, expected %d" % (testFile, len(allToks),numToks) - assert results.batchData.trackInputUsed == trkInpUsed, "error evaluating results.batchData.trackInputUsed" - assert results.batchData.trackOutputUsed == trkOutpUsed, "error evaluating results.batchData.trackOutputUsed" - assert results.batchData.maxDelta == maxDelta,"error evaluating results.batchData.maxDelta" - assert len(results.waferData) == numWafers, "did not read correct number of wafers" - assert min([wd.procBegin for wd in results.waferData]) == minProcBeg, "error reading waferData.procBegin" - assert max([results.waferData[k].procEnd for k in range(len(results.waferData))]) == maxProcEnd, "error reading waferData.procEnd" - assert sum(results.levelStatsIV['MAX']) == maxLevStatsIV, "error reading levelStatsIV" - assert sum(results.levelStatsIV.MAX) == maxLevStatsIV, "error reading levelStatsIV" - print "OK" - print testFile,len(allToks) - #~ print "results.batchData.trackInputUsed =",results.batchData.trackInputUsed - #~ print "results.batchData.trackOutputUsed =",results.batchData.trackOutputUsed - #~ print "results.batchData.maxDelta =",results.batchData.maxDelta - #~ print len(results.waferData)," wafers" - #~ print min([wd.procBegin for wd in results.waferData]) - #~ print max([results.waferData[k].procEnd for k in range(len(results.waferData))]) - #~ print sum(results.levelStatsIV['MAX.']) - - -class ParseFourFnTest(ParseTestCase): - def runTest(self): - import fourFn - def test(s,ans): - fourFn.exprStack = [] - results = fourFn.BNF().parseString( s ) - resultValue = fourFn.evaluateStack( fourFn.exprStack ) - assert resultValue == ans, "failed to evaluate %s, got %f" % ( s, resultValue ) - print s, "->", resultValue - - test( "9", 9 ) - test( "9 + 3 + 6", 18 ) - test( "9 + 3 / 11", 9.0+3.0/11.0) - test( "(9 + 3)", 12 ) - test( "(9+3) / 11", (9.0+3.0)/11.0 ) - test( "9 - (12 - 6)", 3) - test( "2*3.14159", 6.28318) - test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535/10.0 ) - test( "PI * PI / 10", 3.1415926535*3.1415926535/10.0 ) - test( "PI*PI/10", 3.1415926535*3.1415926535/10.0 ) - test( "6.02E23 * 8.048", 6.02E23 * 8.048 ) - test( "e / 3", 2.718281828/3.0 ) - test( "sin(PI/2)", 1.0 ) - test( "trunc(E)", 2.0 ) - test( "E^PI", 2.718281828**3.1415926535 ) - test( "2^3^2", 2**3**2) - test( "2^3+2", 2**3+2) - test( "2^9", 2**9 ) - test( "sgn(-2)", -1 ) - test( "sgn(0)", 0 ) - test( "sgn(0.1)", 1 ) - -class ParseSQLTest(ParseTestCase): - def runTest(self): - import simpleSQL - - def test(s, numToks, errloc=-1 ): - try: - sqlToks = flatten( simpleSQL.simpleSQL.parseString(s).asList() ) - print s,sqlToks,len(sqlToks) - assert len(sqlToks) == numToks - except ParseException, e: - if errloc >= 0: - assert e.loc == errloc - - - test( "SELECT * from XYZZY, ABC", 6 ) - test( "select * from SYS.XYZZY", 5 ) - test( "Select A from Sys.dual", 5 ) - test( "Select A,B,C from Sys.dual", 7 ) - test( "Select A, B, C from Sys.dual", 7 ) - test( "Select A, B, C from Sys.dual, Table2 ", 8 ) - test( "Xelect A, B, C from Sys.dual", 0, 0 ) - test( "Select A, B, C frox Sys.dual", 0, 15 ) - test( "Select", 0, 6 ) - test( "Select &&& frox Sys.dual", 0, 7 ) - test( "Select A from Sys.dual where a in ('RED','GREEN','BLUE')", 12 ) - test( "Select A from Sys.dual where a in ('RED','GREEN','BLUE') and b in (10,20,30)", 20 ) - test( "Select A,b from table1,table2 where table1.id eq table2.id -- test out comparison operators", 10 ) - -class ParseConfigFileTest(ParseTestCase): - def runTest(self): - import configParse - - def test(fnam,numToks,resCheckList): - print "Parsing",fnam,"...", - iniFileLines = "\n".join([ lin for lin in file(fnam) ]) - iniData = configParse.inifile_BNF().parseString( iniFileLines ) - print len(flatten(iniData.asList())) - #~ pprint.pprint( iniData.asList() ) - #~ pprint.pprint( repr(iniData) ) - #~ print len(iniData), len(flatten(iniData.asList())) - print iniData.keys() - #~ print iniData.users.keys() - #~ print - assert len(flatten(iniData.asList())) == numToks, "file %s not parsed correctly" % fnam - for chk in resCheckList: - print chk[0], eval("iniData."+chk[0]), chk[1] - assert eval("iniData."+chk[0]) == chk[1] - print "OK" - - test("karthik.ini", 23, - [ ("users.K","8"), - ("users.mod_scheme","'QPSK'"), - ("users.Na", "K+2") ] - ) - test("setup.ini", 125, - [ ("Startup.audioinf", "M3i"), - ("Languages.key1", "0x0003"), - ("test.foo","bar") ] ) - -class ParseJSONDataTest(ParseTestCase): - def runTest(self): - from jsonParser import jsonObject - from jsonParserFull import test1,test2,test3,test4,test5 - - expected = [ - [], - [], - [], - [], - [], - ] - - import pprint - for t,exp in zip((test1,test2,test3,test4,test5),expected): - result = jsonObject.parseString(t) -## print result.dump() - pprint.pprint(result.asList()) - print -## if result.asList() != exp: -## print "Expected %s, parsed results as %s" % (exp, result.asList()) - -class ParseCommaSeparatedValuesTest(ParseTestCase): - def runTest(self): - from pyparsing import commaSeparatedList - import string - - testData = [ - "a,b,c,100.2,,3", - "d, e, j k , m ", - "'Hello, World', f, g , , 5.1,x", - "John Doe, 123 Main St., Cleveland, Ohio", - "Jane Doe, 456 St. James St., Los Angeles , California ", - "", - ] - testVals = [ - [ (3,'100.2'), (4,''), (5, '3') ], - [ (2, 'j k'), (3, 'm') ], - [ (0, "'Hello, World'"), (2, 'g'), (3, '') ], - [ (0,'John Doe'), (1, '123 Main St.'), (2, 'Cleveland'), (3, 'Ohio') ], - [ (0,'Jane Doe'), (1, '456 St. James St.'), (2, 'Los Angeles'), (3, 'California') ] - ] - for line,tests in zip(testData, testVals): - print "Parsing: \""+line+"\" ->", - results = commaSeparatedList.parseString(line) - print results.asList() - for t in tests: - if not(len(results)>t[0] and results[t[0]] == t[1]): - print "$$$", results.dump() - print "$$$", results[0] - assert len(results)>t[0] and results[t[0]] == t[1],"failed on %s, item %d s/b '%s', got '%s'" % ( line, t[0], t[1], str(results.asList()) ) - -class ParseEBNFTest(ParseTestCase): - def runTest(self): - import ebnf - from pyparsing import Word, quotedString, alphas, nums,ParserElement - - print 'Constructing EBNF parser with pyparsing...' - - grammar = ''' - syntax = (syntax_rule), {(syntax_rule)}; - syntax_rule = meta_identifier, '=', definitions_list, ';'; - definitions_list = single_definition, {'|', single_definition}; - single_definition = syntactic_term, {',', syntactic_term}; - syntactic_term = syntactic_factor,['-', syntactic_factor]; - syntactic_factor = [integer, '*'], syntactic_primary; - syntactic_primary = optional_sequence | repeated_sequence | - grouped_sequence | meta_identifier | terminal_string; - optional_sequence = '[', definitions_list, ']'; - repeated_sequence = '{', definitions_list, '}'; - grouped_sequence = '(', definitions_list, ')'; - (* - terminal_string = "'", character - "'", {character - "'"}, "'" | - '"', character - '"', {character - '"'}, '"'; - meta_identifier = letter, {letter | digit}; - integer = digit, {digit}; - *) - ''' - - table = {} - table['terminal_string'] = quotedString - table['meta_identifier'] = Word(alphas+"_", alphas+"_"+nums) - table['integer'] = Word(nums) - - print 'Parsing EBNF grammar with EBNF parser...' - parsers = ebnf.parse(grammar, table) - ebnf_parser = parsers['syntax'] - #~ print ",\n ".join( str(parsers.keys()).split(", ") ) - print "-","\n- ".join( parsers.keys() ) - assert len(parsers.keys()) == 13, "failed to construct syntax grammar" - - print 'Parsing EBNF grammar with generated EBNF parser...' - parsed_chars = ebnf_parser.parseString(grammar) - parsed_char_len = len(parsed_chars) - - print "],\n".join(str( parsed_chars.asList() ).split("],")) - assert len(flatten(parsed_chars.asList())) == 98, "failed to tokenize grammar correctly" - - -class ParseIDLTest(ParseTestCase): - def runTest(self): - import idlParse - - def test( strng, numToks, errloc=0 ): - print strng - try: - bnf = idlParse.CORBA_IDL_BNF() - tokens = bnf.parseString( strng ) - print "tokens = " - pprint.pprint( tokens.asList() ) - tokens = flatten( tokens.asList() ) - print len(tokens) - assert len(tokens) == numToks, "error matching IDL string, %s -> %s" % (strng, str(tokens) ) - except ParseException, err: - print err.line - print " "*(err.column-1) + "^" - print err - assert numToks == 0, "unexpected ParseException while parsing %s, %s" % (strng, str(err) ) - assert err.loc == errloc, "expected ParseException at %d, found exception at %d" % (errloc, err.loc) - - test( - """ - /* - * a block comment * - */ - typedef string[10] tenStrings; - typedef sequence<string> stringSeq; - typedef sequence< sequence<string> > stringSeqSeq; - - interface QoSAdmin { - stringSeq method1( in string arg1, inout long arg2 ); - stringSeqSeq method2( in string arg1, inout long arg2, inout long arg3); - string method3(); - }; - """, 59 - ) - test( - """ - /* - * a block comment * - */ - typedef string[10] tenStrings; - typedef - /** ** *** **** * - * a block comment * - */ - sequence<string> /*comment inside an And */ stringSeq; - /* */ /**/ /***/ /****/ - typedef sequence< sequence<string> > stringSeqSeq; - - interface QoSAdmin { - stringSeq method1( in string arg1, inout long arg2 ); - stringSeqSeq method2( in string arg1, inout long arg2, inout long arg3); - string method3(); - }; - """, 59 - ) - test( - r""" - const string test="Test String\n"; - const long a = 0; - const long b = -100; - const float c = 3.14159; - const long d = 0x007f7f7f; - exception TestException - { - string msg; - sequence<string> dataStrings; - }; - - interface TestInterface - { - void method1( in string arg1, inout long arg2 ); - }; - """, 60 - ) - test( - """ - module Test1 - { - exception TestException - { - string msg; - ]; - - interface TestInterface - { - void method1( in string arg1, inout long arg2 ) - raises ( TestException ); - }; - }; - """, 0, 57 - ) - test( - """ - module Test1 - { - exception TestException - { - string msg; - }; - - }; - """, 13 - ) - -class ParseVerilogTest(ParseTestCase): - def runTest(self): - pass - -class RunExamplesTest(ParseTestCase): - def runTest(self): - pass - -class ScanStringTest(ParseTestCase): - def runTest(self): - from pyparsing import Word, Combine, Suppress, CharsNotIn, nums, StringEnd - testdata = """ - <table border="0" cellpadding="3" cellspacing="3" frame="" width="90%"> - <tr align="left" valign="top"> - <td><b>Name</b></td> - <td><b>IP Address</b></td> - <td><b>Location</b></td> - </tr> - <tr align="left" valign="top" bgcolor="#c7efce"> - <td>time-a.nist.gov</td> - <td>129.6.15.28</td> - <td>NIST, Gaithersburg, Maryland</td> - </tr> - <tr align="left" valign="top"> - <td>time-b.nist.gov</td> - <td>129.6.15.29</td> - <td>NIST, Gaithersburg, Maryland</td> - </tr> - <tr align="left" valign="top" bgcolor="#c7efce"> - <td>time-a.timefreq.bldrdoc.gov</td> - <td>132.163.4.101</td> - <td>NIST, Boulder, Colorado</td> - </tr> - <tr align="left" valign="top"> - <td>time-b.timefreq.bldrdoc.gov</td> - <td>132.163.4.102</td> - <td>NIST, Boulder, Colorado</td> - </tr> - <tr align="left" valign="top" bgcolor="#c7efce"> - <td>time-c.timefreq.bldrdoc.gov</td> - <td>132.163.4.103</td> - <td>NIST, Boulder, Colorado</td> - </tr> - </table> - """ - integer = Word(nums) - ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer ) - tdStart = Suppress("<td>") - tdEnd = Suppress("</td>") - timeServerPattern = tdStart + ipAddress.setResultsName("ipAddr") + tdEnd + \ - tdStart + CharsNotIn("<").setResultsName("loc") + tdEnd - servers = \ - [ srvr.ipAddr for srvr,startloc,endloc in timeServerPattern.scanString( testdata ) ] - - print servers - assert servers == ['129.6.15.28', '129.6.15.29', '132.163.4.101', '132.163.4.102', '132.163.4.103'], \ - "failed scanString()" - - # test for stringEnd detection in scanString - foundStringEnds = [ r for r in StringEnd().scanString("xyzzy") ] - print foundStringEnds - assert foundStringEnds, "Failed to find StringEnd in scanString" - -class QuotedStringsTest(ParseTestCase): - def runTest(self): - from pyparsing import sglQuotedString,dblQuotedString,quotedString - testData = \ - """ - 'a valid single quoted string' - 'an invalid single quoted string - because it spans lines' - "a valid double quoted string" - "an invalid double quoted string - because it spans lines" - """ - print testData - sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(testData) ] - print sglStrings - assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==47), \ - "single quoted string failure" - dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(testData) ] - print dblStrings - assert len(dblStrings) == 1 and (dblStrings[0][1]==154 and dblStrings[0][2]==184), \ - "double quoted string failure" - allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(testData) ] - print allStrings - assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==47) and \ - (allStrings[1][1]==154 and allStrings[1][2]==184), \ - "quoted string failure" - - escapedQuoteTest = \ - r""" - 'This string has an escaped (\') quote character' - "This string has an escaped (\") quote character" - """ - sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(escapedQuoteTest) ] - print sglStrings - assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==66), \ - "single quoted string escaped quote failure (%s)" % str(sglStrings[0]) - dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(escapedQuoteTest) ] - print dblStrings - assert len(dblStrings) == 1 and (dblStrings[0][1]==83 and dblStrings[0][2]==132), \ - "double quoted string escaped quote failure (%s)" % str(dblStrings[0]) - allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(escapedQuoteTest) ] - print allStrings - assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==66 and - allStrings[1][1]==83 and allStrings[1][2]==132), \ - "quoted string escaped quote failure (%s)" % ([str(s[0]) for s in allStrings]) - - dblQuoteTest = \ - r""" - 'This string has an doubled ('') quote character' - "This string has an doubled ("") quote character" - """ - sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(dblQuoteTest) ] - print sglStrings - assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==66), \ - "single quoted string escaped quote failure (%s)" % str(sglStrings[0]) - dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(dblQuoteTest) ] - print dblStrings - assert len(dblStrings) == 1 and (dblStrings[0][1]==83 and dblStrings[0][2]==132), \ - "double quoted string escaped quote failure (%s)" % str(dblStrings[0]) - allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(dblQuoteTest) ] - print allStrings - assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==66 and - allStrings[1][1]==83 and allStrings[1][2]==132), \ - "quoted string escaped quote failure (%s)" % ([str(s[0]) for s in allStrings]) - -class CaselessOneOfTest(ParseTestCase): - def runTest(self): - from pyparsing import oneOf,ZeroOrMore - - caseless1 = oneOf("d a b c aA B A C", caseless=True) - caseless1str = str( caseless1 ) - print caseless1str - caseless2 = oneOf("d a b c Aa B A C", caseless=True) - caseless2str = str( caseless2 ) - print caseless2str - assert caseless1str.upper() == caseless2str.upper(), "oneOf not handling caseless option properly" - assert caseless1str != caseless2str, "Caseless option properly sorted" - - res = ZeroOrMore(caseless1).parseString("AAaaAaaA") - print res - assert len(res) == 4, "caseless1 oneOf failed" - assert "".join(res) == "aA"*4,"caseless1 CaselessLiteral return failed" - - res = ZeroOrMore(caseless2).parseString("AAaaAaaA") - print res - assert len(res) == 4, "caseless2 oneOf failed" - assert "".join(res) == "Aa"*4,"caseless1 CaselessLiteral return failed" - - -class AsXMLTest(ParseTestCase): - def runTest(self): - - import pyparsing - # test asXML() - - aaa = pyparsing.Word("a").setResultsName("A") - bbb = pyparsing.Group(pyparsing.Word("b")).setResultsName("B") - ccc = pyparsing.Combine(":" + pyparsing.Word("c")).setResultsName("C") - g1 = "XXX>&<" + pyparsing.ZeroOrMore( aaa | bbb | ccc ) - teststring = "XXX>&< b b a b b a b :c b a" - #~ print teststring - print "test including all items" - xml = g1.parseString(teststring).asXML("TEST",namedItemsOnly=False) - assert xml=="\n".join(["", - "<TEST>", - " <ITEM>XXX>&<</ITEM>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <C>:c</C>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - "</TEST>", - ] ), \ - "failed to generate XML correctly showing all items: \n[" + xml + "]" - print "test filtering unnamed items" - xml = g1.parseString(teststring).asXML("TEST",namedItemsOnly=True) - assert xml=="\n".join(["", - "<TEST>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <C>:c</C>", - " <B>", - " <ITEM>b</ITEM>", - " </B>", - " <A>a</A>", - "</TEST>", - ] ), \ - "failed to generate XML correctly, filtering unnamed items: " + xml - -class AsXMLTest2(ParseTestCase): - def runTest(self): - from pyparsing import Suppress,Optional,CharsNotIn,Combine,ZeroOrMore,Word,\ - Group,Literal,alphas,alphanums,delimitedList,OneOrMore - - EndOfLine = Word("\n").setParseAction(lambda s,l,t: [' ']) - whiteSpace=Word('\t ') - Mexpr = Suppress(Optional(whiteSpace)) + CharsNotIn('\\"\t \n') + Optional(" ") + \ - Suppress(Optional(whiteSpace)) - reducedString = Combine(Mexpr + ZeroOrMore(EndOfLine + Mexpr)) - _bslash = "\\" - _escapables = "tnrfbacdeghijklmopqsuvwxyz" + _bslash + "'" + '"' - _octDigits = "01234567" - _escapedChar = ( Word( _bslash, _escapables, exact=2 ) | - Word( _bslash, _octDigits, min=2, max=4 ) ) - _sglQuote = Literal("'") - _dblQuote = Literal('"') - QuotedReducedString = Combine( Suppress(_dblQuote) + ZeroOrMore( reducedString | - _escapedChar ) + \ - Suppress(_dblQuote )).streamline() - - Manifest_string = QuotedReducedString.setResultsName('manifest_string') - - Identifier = Word( alphas, alphanums+ '_$' ).setResultsName("identifier") - Index_string = CharsNotIn('\\";\n') - Index_string.setName('index_string') - Index_term_list = ( - Group(delimitedList(Manifest_string, delim=',')) | \ - Index_string - ).setResultsName('value') - - IndexKey = Identifier.setResultsName('key') - IndexKey.setName('key') - Index_clause = Group(IndexKey + Suppress(':') + Optional(Index_term_list)) - Index_clause.setName('index_clause') - Index_list = Index_clause.setResultsName('index') - Index_list.setName('index_list') - Index_block = Group('indexing' + Group(OneOrMore(Index_list + Suppress(';')))).setResultsName('indexes') - - -class CommentParserTest(ParseTestCase): - def runTest(self): - import pyparsing - print "verify processing of C and HTML comments" - testdata = """ - /* */ - /** **/ - /**/ - /***/ - /****/ - /* /*/ - /** /*/ - /*** /*/ - /* - ablsjdflj - */ - """ - foundLines = [ pyparsing.lineno(s,testdata) - for t,s,e in pyparsing.cStyleComment.scanString(testdata) ] - assert foundLines == range(11)[2:],"only found C comments on lines "+str(foundLines) - testdata = """ - <!-- --> - <!--- ---> - <!----> - <!-----> - <!------> - <!-- /--> - <!--- /--> - <!---- /--> - <!---- /- -> - <!---- / -- > - <!-- - ablsjdflj - --> - """ - foundLines = [ pyparsing.lineno(s,testdata) - for t,s,e in pyparsing.htmlComment.scanString(testdata) ] - assert foundLines == range(11)[2:],"only found HTML comments on lines "+str(foundLines) - - # test C++ single line comments that have line terminated with '\' (should continue comment to following line) - testSource = r""" - // comment1 - // comment2 \ - still comment 2 - // comment 3 - """ - assert len(pyparsing.cppStyleComment.searchString(testSource)[1][0]) == 41, r"failed to match single-line comment with '\' at EOL" - -class ParseExpressionResultsTest(ParseTestCase): - def runTest(self): - from pyparsing import Word,alphas,OneOrMore,Optional,Group - - a = Word("a",alphas).setName("A") - b = Word("b",alphas).setName("B") - c = Word("c",alphas).setName("C") - ab = (a + b).setName("AB") - abc = (ab + c).setName("ABC") - word = Word(alphas).setName("word") - - #~ words = OneOrMore(word).setName("words") - words = Group(OneOrMore(~a + word)).setName("words") - - #~ phrase = words.setResultsName("Head") + \ - #~ ( abc ^ ab ^ a ).setResultsName("ABC") + \ - #~ words.setResultsName("Tail") - #~ phrase = words.setResultsName("Head") + \ - #~ ( abc | ab | a ).setResultsName("ABC") + \ - #~ words.setResultsName("Tail") - phrase = words.setResultsName("Head") + \ - Group( a + Optional(b + Optional(c)) ).setResultsName("ABC") + \ - words.setResultsName("Tail") - - results = phrase.parseString("xavier yeti alpha beta charlie will beaver") - print results,results.Head, results.ABC,results.Tail - for key,ln in [("Head",2), ("ABC",3), ("Tail",2)]: - #~ assert len(results[key]) == ln,"expected %d elements in %s, found %s" % (ln, key, str(results[key].asList())) - assert len(results[key]) == ln,"expected %d elements in %s, found %s" % (ln, key, str(results[key])) - - -class ParseKeywordTest(ParseTestCase): - def runTest(self): - from pyparsing import Literal,Keyword - - kw = Keyword("if") - lit = Literal("if") - - def test(s,litShouldPass,kwShouldPass): - print "Test",s - print "Match Literal", - try: - print lit.parseString(s) - except: - print "failed" - if litShouldPass: assert False, "Literal failed to match %s, should have" % s - else: - if not litShouldPass: assert False, "Literal matched %s, should not have" % s - - print "Match Keyword", - try: - print kw.parseString(s) - except: - print "failed" - if kwShouldPass: assert False, "Keyword failed to match %s, should have" % s - else: - if not kwShouldPass: assert False, "Keyword matched %s, should not have" % s - - test("ifOnlyIfOnly", True, False) - test("if(OnlyIfOnly)", True, True) - test("if (OnlyIf Only)", True, True) - - kw = Keyword("if",caseless=True) - - test("IFOnlyIfOnly", False, False) - test("If(OnlyIfOnly)", False, True) - test("iF (OnlyIf Only)", False, True) - - - -class ParseExpressionResultsAccumulateTest(ParseTestCase): - def runTest(self): - from pyparsing import Word,delimitedList,Combine,alphas,nums - - num=Word(nums).setName("num").setResultsName("base10", listAllMatches=True) - hexnum=Combine("0x"+ Word(nums)).setName("hexnum").setResultsName("hex", listAllMatches=True) - name = Word(alphas).setName("word").setResultsName("word", listAllMatches=True) - list_of_num=delimitedList( hexnum | num | name, "," ) - - tokens = list_of_num.parseString('1, 0x2, 3, 0x4, aaa') - for k,llen,lst in ( ("base10",2,['1','3']), - ("hex",2,['0x2','0x4']), - ("word",1,['aaa']) ): - print k,tokens[k] - assert len(tokens[k]) == llen, "Wrong length for key %s, %s" % (k,str(tokens[k].asList())) - assert lst == tokens[k].asList(), "Incorrect list returned for key %s, %s" % (k,str(tokens[k].asList())) - assert tokens.base10.asList() == ['1','3'], "Incorrect list for attribute base10, %s" % str(tokens.base10.asList()) - assert tokens.hex.asList() == ['0x2','0x4'], "Incorrect list for attribute hex, %s" % str(tokens.hex.asList()) - assert tokens.word.asList() == ['aaa'], "Incorrect list for attribute word, %s" % str(tokens.word.asList()) - - from pyparsing import Literal, Word, nums, Group, Dict, alphas, \ - quotedString, oneOf, delimitedList, removeQuotes, alphanums - - lbrack = Literal("(").suppress() - rbrack = Literal(")").suppress() - integer = Word( nums ).setName("int") - variable = Word( alphas, max=1 ).setName("variable") - relation_body_item = variable | integer | quotedString.copy().setParseAction(removeQuotes) - relation_name = Word( alphas+"_", alphanums+"_" ) - relation_body = lbrack + Group(delimitedList(relation_body_item)) + rbrack - Goal = Dict(Group( relation_name + relation_body )) - Comparison_Predicate = Group(variable + oneOf("< >") + integer).setResultsName("pred",listAllMatches=True) - Query = Goal.setResultsName("head") + ":-" + delimitedList(Goal | Comparison_Predicate) - - test="""Q(x,y,z):-Bloo(x,"Mitsis",y),Foo(y,z,1243),y>28,x<12,x>3""" - - queryRes = Query.parseString(test) - print "pred",queryRes.pred - assert queryRes.pred.asList() == [['y', '>', '28'], ['x', '<', '12'], ['x', '>', '3']], "Incorrect list for attribute pred, %s" % str(queryRes.pred.asList()) - print queryRes.dump() - -class ReStringRangeTest(ParseTestCase): - def runTest(self): - import pyparsing - testCases = ( - (r"[A-Z]"), - (r"[A-A]"), - (r"[A-Za-z]"), - (r"[A-z]"), - (r"[\ -\~]"), - (r"[\0x20-0]"), - (r"[\0x21-\0x7E]"), - (r"[\0xa1-\0xfe]"), - (r"[\040-0]"), - (r"[A-Za-z0-9]"), - (r"[A-Za-z0-9_]"), - (r"[A-Za-z0-9_$]"), - (r"[A-Za-z0-9_$\-]"), - (r"[^0-9\\]"), - (r"[a-zA-Z]"), - (r"[/\^~]"), - (r"[=\+\-!]"), - (r"[A-]"), - (r"[-A]"), - ) - expectedResults = ( - "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "A", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz", - " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~", - " !\"#$%&'()*+,-./0", - "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~", - #~ "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ", - u'\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe', - " !\"#$%&'()*+,-./0", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$-", - "0123456789\\", - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - "/^~", - "=+-!", - "A-", - "-A", - ) - for test in zip( testCases, expectedResults ): - t,exp = test - res = pyparsing.srange(t) - #~ print t,"->",res - assert res == exp, "srange error, srange(%s)->'%s', expected '%s'" % (t, res, exp) - -class SkipToParserTests(ParseTestCase): - def runTest(self): - - from pyparsing import Literal, SkipTo, NotAny, cStyleComment - - thingToFind = Literal('working') - testExpr = SkipTo(Literal(';'), True, cStyleComment) + thingToFind - - def tryToParse (someText): - try: - print testExpr.parseString(someText) - except Exception, e: - print "Exception %s while parsing string %s" % (e,repr(someText)) - assert False, "Exception %s while parsing string %s" % (e,repr(someText)) - - # This first test works, as the SkipTo expression is immediately following the ignore expression (cStyleComment) - tryToParse('some text /* comment with ; in */; working') - # This second test fails, as there is text following the ignore expression, and before the SkipTo expression. - tryToParse('some text /* comment with ; in */some other stuff; working') - - -class CustomQuotesTest(ParseTestCase): - def runTest(self): - from pyparsing import QuotedString - - testString = r""" - sdlfjs :sdf\:jls::djf: sl:kfsjf - sdlfjs -sdf\:jls::--djf: sl-kfsjf - sdlfjs -sdf\:::jls::--djf: sl:::-kfsjf - sdlfjs ^sdf\:jls^^--djf^ sl-kfsjf - sdlfjs ^^^==sdf\:j=lz::--djf: sl=^^=kfsjf - sdlfjs ==sdf\:j=ls::--djf: sl==kfsjf^^^ - """ - colonQuotes = QuotedString(':','\\','::') - dashQuotes = QuotedString('-','\\', '--') - hatQuotes = QuotedString('^','\\') - hatQuotes1 = QuotedString('^','\\','^^') - dblEqQuotes = QuotedString('==','\\') - - def test(quoteExpr, expected): - print quoteExpr.pattern - print quoteExpr.searchString(testString) - print quoteExpr.searchString(testString)[0][0] - assert quoteExpr.searchString(testString)[0][0] == expected, \ - "failed to match %s, expected '%s', got '%s'" % \ - (quoteExpr,expected,quoteExpr.searchString(testString)[0]) - - test(colonQuotes, r"sdf:jls:djf") - test(dashQuotes, r"sdf\:jls::-djf: sl") - test(hatQuotes, r"sdf\:jls") - test(hatQuotes1, r"sdf\:jls^--djf") - test(dblEqQuotes, r"sdf\:j=ls::--djf: sl") - test( QuotedString(':::'), 'jls::--djf: sl') - test( QuotedString('==',endQuoteChar='--'), r'sdf\:j=lz::') - test( QuotedString('^^^',multiline=True), r"""==sdf\:j=lz::--djf: sl=^^=kfsjf - sdlfjs ==sdf\:j=ls::--djf: sl==kfsjf""") - try: - bad1 = QuotedString('','\\') - except SyntaxError,se: - pass - else: - assert False,"failed to raise SyntaxError with empty quote string" - -class RepeaterTest(ParseTestCase): - def runTest(self): - from pyparsing import matchPreviousLiteral,matchPreviousExpr, Forward, Literal, Word, alphas, nums - - first = Word("abcdef").setName("word1") - bridge = Word(nums).setName("number") - second = matchPreviousLiteral(first).setName("repeat(word1Literal)") - - seq = first + bridge + second - - tests = [ - ( "abc12abc", True ), - ( "abc12aabc", False ), - ( "abc12cba", True ), - ( "abc12bca", True ), - ] - - for tst,result in tests: - found = False - for tokens,start,end in seq.scanString(tst): - f,b,s = tokens - print f,b,s - found = True - if not found: - print "No literal match in", tst - assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) - print - - # retest using matchPreviousExpr instead of matchPreviousLiteral - second = matchPreviousExpr(first).setName("repeat(word1expr)") - seq = first + bridge + second - - tests = [ - ( "abc12abc", True ), - ( "abc12cba", False ), - ( "abc12abcdef", False ), - ] - - for tst,result in tests: - found = False - for tokens,start,end in seq.scanString(tst): - print tokens.asList() - found = True - if not found: - print "No expression match in", tst - assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) - - print - - first = Word("abcdef").setName("word1") - bridge = Word(nums).setName("number") - second = matchPreviousExpr(first).setName("repeat(word1)") - seq = first + bridge + second - csFirst = seq.setName("word-num-word") - csSecond = matchPreviousExpr(csFirst) - compoundSeq = csFirst + ":" + csSecond - compoundSeq.streamline() - print compoundSeq - - tests = [ - ( "abc12abc:abc12abc", True ), - ( "abc12cba:abc12abc", False ), - ( "abc12abc:abc12abcdef", False ), - ] - - #~ for tst,result in tests: - #~ print tst, - #~ try: - #~ compoundSeq.parseString(tst) - #~ print "MATCH" - #~ assert result, "matched when shouldn't have matched" - #~ except ParseException: - #~ print "NO MATCH" - #~ assert not result, "didnt match but should have" - - #~ for tst,result in tests: - #~ print tst, - #~ if compoundSeq == tst: - #~ print "MATCH" - #~ assert result, "matched when shouldn't have matched" - #~ else: - #~ print "NO MATCH" - #~ assert not result, "didnt match but should have" - - for tst,result in tests: - found = False - for tokens,start,end in compoundSeq.scanString(tst): - print "match:", tokens.asList() - found = True - break - if not found: - print "No expression match in", tst - assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) - - print - eFirst = Word(nums) - eSecond = matchPreviousExpr(eFirst) - eSeq = eFirst + ":" + eSecond - - tests = [ - ( "1:1A", True ), - ( "1:10", False ), - ] - - for tst,result in tests: - found = False - for tokens,start,end in eSeq.scanString(tst): - #~ f,b,s = tokens - #~ print f,b,s - print tokens.asList() - found = True - if not found: - print "No match in", tst - assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) - -class RecursiveCombineTest(ParseTestCase): - def runTest(self): - from pyparsing import Forward,Word,alphas,nums,Optional,Combine - - testInput = "myc(114)r(11)dd" - Stream=Forward() - Stream << Optional(Word(alphas))+Optional("("+Word(nums)+")"+Stream) - expected = Stream.parseString(testInput).asList() - print ["".join(expected)] - - Stream=Forward() - Stream << Combine(Optional(Word(alphas))+Optional("("+Word(nums)+")"+Stream)) - testVal = Stream.parseString(testInput).asList() - print testVal - - assert "".join(testVal) == "".join(expected), "Failed to process Combine with recursive content" - -class OperatorPrecedenceGrammarTest1(ParseTestCase): - def runTest(self): - from pyparsing import Word,nums,alphas,Literal,oneOf,operatorPrecedence,opAssoc - - integer = Word(nums).setParseAction(lambda t:int(t[0])) - variable = Word(alphas,exact=1) - operand = integer | variable - - expop = Literal('^') - signop = oneOf('+ -') - multop = oneOf('* /') - plusop = oneOf('+ -') - factop = Literal('!') - - expr = operatorPrecedence( operand, - [("!", 1, opAssoc.LEFT), - ("^", 2, opAssoc.RIGHT), - (signop, 1, opAssoc.RIGHT), - (multop, 2, opAssoc.LEFT), - (plusop, 2, opAssoc.LEFT),] - ) - - test = ["9 + 2 + 3", - "9 + 2 * 3", - "(9 + 2) * 3", - "(9 + -2) * 3", - "(9 + --2) * 3", - "(9 + -2) * 3^2^2", - "(9! + -2) * 3^2^2", - "M*X + B", - "M*(X + B)", - "1+2*-3^4*5+-+-6", - "3!!"] - expected = """[[9, '+', 2, '+', 3]] - [[9, '+', [2, '*', 3]]] - [[[9, '+', 2], '*', 3]] - [[[9, '+', ['-', 2]], '*', 3]] - [[[9, '+', ['-', ['-', 2]]], '*', 3]] - [[[9, '+', ['-', 2]], '*', [3, '^', [2, '^', 2]]]] - [[[[9, '!'], '+', ['-', 2]], '*', [3, '^', [2, '^', 2]]]] - [[['M', '*', 'X'], '+', 'B']] - [['M', '*', ['X', '+', 'B']]] - [[1, '+', [2, '*', ['-', [3, '^', 4]], '*', 5], '+', ['-', ['+', ['-', 6]]]]] - [[3, '!', '!']]""".split('\n') - expected = map(lambda x:eval(x),expected) - for t,e in zip(test,expected): - print t,"->",e, "got", expr.parseString(t).asList() - assert expr.parseString(t).asList() == e,"mismatched results for operatorPrecedence: got %s, expected %s" % (expr.parseString(t).asList(),e) - -class OperatorPrecedenceGrammarTest2(ParseTestCase): - def runTest(self): - - from pyparsing import operatorPrecedence, Word, alphas, oneOf, opAssoc - - boolVars = { "True":True, "False":False } - class BoolOperand(object): - def __init__(self,t): - self.args = t[0][0::2] - def __str__(self): - sep = " %s " % self.reprsymbol - return "(" + sep.join(map(str,self.args)) + ")" - - class BoolAnd(BoolOperand): - reprsymbol = '&' - def __nonzero__(self): - for a in self.args: - if isinstance(a,basestring): - v = boolVars[a] - else: - v = bool(a) - if not v: - return False - return True - - class BoolOr(BoolOperand): - reprsymbol = '|' - def __nonzero__(self): - for a in self.args: - if isinstance(a,basestring): - v = boolVars[a] - else: - v = bool(a) - if v: - return True - return False - - class BoolNot(BoolOperand): - def __init__(self,t): - self.arg = t[0][1] - def __str__(self): - return "~" + str(self.arg) - def __nonzero__(self): - if isinstance(self.arg,basestring): - v = boolVars[self.arg] - else: - v = bool(self.arg) - return not v - - boolOperand = Word(alphas,max=1) | oneOf("True False") - boolExpr = operatorPrecedence( boolOperand, - [ - ("not", 1, opAssoc.RIGHT, BoolNot), - ("and", 2, opAssoc.LEFT, BoolAnd), - ("or", 2, opAssoc.LEFT, BoolOr), - ]) - test = ["p and not q", - "not not p", - "not(p and q)", - "q or not p and r", - "q or not p or not r", - "q or not (p and r)", - "p or q or r", - "p or q or r and False", - "(p or q or r) and False", - ] - - boolVars["p"] = True - boolVars["q"] = False - boolVars["r"] = True - print "p =", boolVars["p"] - print "q =", boolVars["q"] - print "r =", boolVars["r"] - print - for t in test: - res = boolExpr.parseString(t)[0] - print t,'\n', res, '=', bool(res),'\n' - - -class OperatorPrecedenceGrammarTest3(ParseTestCase): - def runTest(self): - - from pyparsing import operatorPrecedence, Word, alphas, oneOf, opAssoc, nums, Literal - - global count - count = 0 - - def evaluate_int(t): - global count - value = int(t[0]) - print "evaluate_int", value - count += 1 - return value - - integer = Word(nums).setParseAction(evaluate_int) - variable = Word(alphas,exact=1) - operand = integer | variable - - expop = Literal('^') - signop = oneOf('+ -') - multop = oneOf('* /') - plusop = oneOf('+ -') - factop = Literal('!') - - expr = operatorPrecedence( operand, - [ - ("!", 1, opAssoc.LEFT), - ("^", 2, opAssoc.RIGHT), - (signop, 1, opAssoc.RIGHT), - (multop, 2, opAssoc.LEFT), - (plusop, 2, opAssoc.LEFT), - ]) - - test = ["9"] - for t in test: - count = 0 - print "%s => %s" % (t, expr.parseString(t)) - assert count == 1, "count evaluated too many times!" - -class OperatorPrecedenceGrammarTest4(ParseTestCase): - def runTest(self): - - import pyparsing - - word = pyparsing.Word(pyparsing.alphas) - - def supLiteral(s): - """Returns the suppressed literal s""" - return pyparsing.Literal(s).suppress() - - def booleanExpr(atom): - ops = [ - (supLiteral(u"!"), 1, pyparsing.opAssoc.RIGHT, lambda s, l, t: ["!", t[0][0]]), - (pyparsing.oneOf(u"= !="), 2, pyparsing.opAssoc.LEFT, ), - (supLiteral(u"&"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["&", t[0]]), - (supLiteral(u"|"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["|", t[0]])] - return pyparsing.operatorPrecedence(atom, ops) - - f = booleanExpr(word) + pyparsing.StringEnd() - - tests = [ - ("bar = foo", "[['bar', '=', 'foo']]"), - ("bar = foo & baz = fee", "['&', [['bar', '=', 'foo'], ['baz', '=', 'fee']]]"), - ] - for test,expected in tests: - print test - results = f.parseString(test) - print results - assert str(results) == expected, "failed to match expected results, got '%s'" % str(results) - print - - -class ParseResultsPickleTest(ParseTestCase): - def runTest(self): - from pyparsing import makeHTMLTags, ParseResults - import pickle - - body = makeHTMLTags("BODY")[0] - result = body.parseString("<BODY BGCOLOR='#00FFBB' FGCOLOR=black>") - print result.dump() - print - - # TODO - add support for protocols >= 2 - #~ for protocol in range(pickle.HIGHEST_PROTOCOL+1): - for protocol in range(2): - print "Test pickle dump protocol", protocol - try: - pickleString = pickle.dumps(result, protocol) - except Exception, e: - print "dumps exception:", e - newresult = ParseResults([]) - else: - newresult = pickle.loads(pickleString) - print newresult.dump() - - assert result.dump() == newresult.dump(), "Error pickling ParseResults object (protocol=%d)" % protocol - print - - -class ParseResultsWithNamedTupleTest(ParseTestCase): - def runTest(self): - - from pyparsing import Literal,replaceWith - - expr = Literal("A") - expr.setParseAction(replaceWith(tuple(["A","Z"]))) - expr = expr.setResultsName("Achar") - - res = expr.parseString("A") - print repr(res) - print res.Achar - assert res.Achar == ("A","Z"), "Failed accessing named results containing a tuple, got " + res.Achar - - -class ParseHTMLTagsTest(ParseTestCase): - def runTest(self): - import pyparsing - test = """ - <BODY> - <BODY BGCOLOR="#00FFCC"> - <BODY BGCOLOR="#00FFAA"/> - <BODY BGCOLOR='#00FFBB' FGCOLOR=black> - <BODY/> - </BODY> - """ - results = [ - ("startBody", False, "", ""), - ("startBody", False, "#00FFCC", ""), - ("startBody", True, "#00FFAA", ""), - ("startBody", False, "#00FFBB", "black"), - ("startBody", True, "", ""), - ("endBody", False, "", ""), - ] - - bodyStart, bodyEnd = pyparsing.makeHTMLTags("BODY") - resIter = iter(results) - for t,s,e in (bodyStart | bodyEnd).scanString( test ): - print test[s:e], "->", t.asList() - (expectedType, expectedEmpty, expectedBG, expectedFG) = resIter.next() - - tType = t.getName() - #~ print tType,"==",expectedType,"?" - assert tType in "startBody endBody".split(), "parsed token of unknown type '%s'" % tType - assert tType == expectedType, "expected token of type %s, got %s" % (expectedType, tType) - if tType == "startBody": - assert bool(t.empty) == expectedEmpty, "expected %s token, got %s" % ( expectedEmpty and "empty" or "not empty", - t.empty and "empty" or "not empty" ) - assert t.bgcolor == expectedBG, "failed to match BGCOLOR, expected %s, got %s" % ( expectedBG, t.bgcolor ) - assert t.fgcolor == expectedFG, "failed to match FGCOLOR, expected %s, got %s" % ( expectedFG, t.bgcolor ) - elif tType == "endBody": - #~ print "end tag" - pass - else: - print "BAD!!!" - -class UpcaseDowncaseUnicode(ParseTestCase): - def runTest(self): - - import pyparsing as pp - import sys - - a = u'\u00bfC\u00f3mo esta usted?' - ualphas = u"".join( [ unichr(i) for i in range(sys.maxunicode) - if unichr(i).isalpha() ] ) - uword = pp.Word(ualphas).setParseAction(pp.upcaseTokens) - - print uword.searchString(a) - - uword = pp.Word(ualphas).setParseAction(pp.downcaseTokens) - - print uword.searchString(a) - - if not IRON_PYTHON_ENV: - #test html data - html = "<TR class=maintxt bgColor=#ffffff> \ - <TD vAlign=top>Производитель, модель</TD> \ - <TD vAlign=top><STRONG>BenQ-Siemens CF61</STRONG></TD> \ - ".decode('utf-8') - - # u'Manufacturer, model - text_manuf = u'Производитель, модель' - manufacturer = pp.Literal(text_manuf) - - td_start, td_end = pp.makeHTMLTags("td") - manuf_body = td_start.suppress() + manufacturer + pp.SkipTo(td_end).setResultsName("cells", True) + td_end.suppress() - - #~ manuf_body.setDebug() - - for tokens in manuf_body.scanString(html): - print tokens - -class ParseUsingRegex(ParseTestCase): - def runTest(self): - - import re - import pyparsing - - signedInt = pyparsing.Regex(r'[-+][0-9]+') - unsignedInt = pyparsing.Regex(r'[0-9]+') - simpleString = pyparsing.Regex(r'("[^\"]*")|(\'[^\']*\')') - namedGrouping = pyparsing.Regex(r'("(?P<content>[^\"]*)")') - compiledRE = pyparsing.Regex(re.compile(r'[A-Z]+')) - - def testMatch (expression, instring, shouldPass, expectedString=None): - if shouldPass: - try: - result = expression.parseString(instring) - print '%s correctly matched %s' % (repr(expression), repr(instring)) - if expectedString != result[0]: - print '\tbut failed to match the pattern as expected:' - print '\tproduced %s instead of %s' % \ - (repr(result[0]), repr(expectedString)) - return True - except pyparsing.ParseException: - print '%s incorrectly failed to match %s' % \ - (repr(expression), repr(instring)) - else: - try: - result = expression.parseString(instring) - print '%s incorrectly matched %s' % (repr(expression), repr(instring)) - print '\tproduced %s as a result' % repr(result[0]) - except pyparsing.ParseException: - print '%s correctly failed to match %s' % \ - (repr(expression), repr(instring)) - return True - return False - - # These should fail - assert testMatch(signedInt, '1234 foo', False), "Re: (1) passed, expected fail" - assert testMatch(signedInt, ' +foo', False), "Re: (2) passed, expected fail" - assert testMatch(unsignedInt, 'abc', False), "Re: (3) passed, expected fail" - assert testMatch(unsignedInt, '+123 foo', False), "Re: (4) passed, expected fail" - assert testMatch(simpleString, 'foo', False), "Re: (5) passed, expected fail" - assert testMatch(simpleString, '"foo bar\'', False), "Re: (6) passed, expected fail" - assert testMatch(simpleString, '\'foo bar"', False), "Re: (7) passed, expected fail" - - # These should pass - assert testMatch(signedInt, ' +123', True, '+123'), "Re: (8) failed, expected pass" - assert testMatch(signedInt, '+123', True, '+123'), "Re: (9) failed, expected pass" - assert testMatch(signedInt, '+123 foo', True, '+123'), "Re: (10) failed, expected pass" - assert testMatch(signedInt, '-0 foo', True, '-0'), "Re: (11) failed, expected pass" - assert testMatch(unsignedInt, '123 foo', True, '123'), "Re: (12) failed, expected pass" - assert testMatch(unsignedInt, '0 foo', True, '0'), "Re: (13) failed, expected pass" - assert testMatch(simpleString, '"foo"', True, '"foo"'), "Re: (14) failed, expected pass" - assert testMatch(simpleString, "'foo bar' baz", True, "'foo bar'"), "Re: (15) failed, expected pass" - - assert testMatch(compiledRE, 'blah', False), "Re: (16) passed, expected fail" - assert testMatch(compiledRE, 'BLAH', True, 'BLAH'), "Re: (17) failed, expected pass" - - assert testMatch(namedGrouping, '"foo bar" baz', True, '"foo bar"'), "Re: (16) failed, expected pass" - ret = namedGrouping.parseString('"zork" blah') - print ret.asList() - print ret.items() - print ret.content - assert ret.content == 'zork', "named group lookup failed" - assert ret[0] == simpleString.parseString('"zork" blah')[0], "Regex not properly returning ParseResults for named vs. unnamed groups" - - try: - #~ print "lets try an invalid RE" - invRe = pyparsing.Regex('("[^\"]*")|(\'[^\']*\'') - except Exception,e: - print "successfully rejected an invalid RE:", - print e - else: - assert False, "failed to reject invalid RE" - - invRe = pyparsing.Regex('') - -class CountedArrayTest(ParseTestCase): - def runTest(self): - from pyparsing import Word,nums,OneOrMore,countedArray - - testString = "2 5 7 6 0 1 2 3 4 5 0 3 5 4 3" - - integer = Word(nums).setParseAction(lambda t: int(t[0])) - countedField = countedArray(integer) - - r = OneOrMore(countedField).parseString( testString ) - print testString - print r.asList() - - assert r.asList() == [[5,7],[0,1,2,3,4,5],[],[5,4,3]], \ - "Failed matching countedArray, got " + str(r.asList()) - -class CountedArrayTest2(ParseTestCase): - # addresses bug raised by Ralf Vosseler - def runTest(self): - from pyparsing import Word,nums,OneOrMore,countedArray - - testString = "2 5 7 6 0 1 2 3 4 5 0 3 5 4 3" - - integer = Word(nums).setParseAction(lambda t: int(t[0])) - countedField = countedArray(integer) - - dummy = Word("A") - r = OneOrMore(dummy ^ countedField).parseString( testString ) - print testString - print r.asList() - - assert r.asList() == [[5,7],[0,1,2,3,4,5],[],[5,4,3]], \ - "Failed matching countedArray, got " + str(r.asList()) - -class LineAndStringEndTest(ParseTestCase): - def runTest(self): - from pyparsing import OneOrMore,lineEnd,alphanums,Word,stringEnd,delimitedList,SkipTo - - les = OneOrMore(lineEnd) - bnf1 = delimitedList(Word(alphanums).leaveWhitespace(),les) - bnf2 = Word(alphanums) + stringEnd - bnf3 = Word(alphanums) + SkipTo(stringEnd) - tests = [ - ("testA\ntestB\ntestC\n", ['testA', 'testB', 'testC']), - ("testD\ntestE\ntestF", ['testD', 'testE', 'testF']), - ("a", ['a']), - ] - - for t in tests: - res1 = bnf1.parseString(t[0]) - print res1,'=?',t[1] - assert res1.asList() == t[1], "Failed lineEnd/stringEnd test (1): "+repr(t[0])+ " -> "+str(res1.asList()) - res2 = bnf2.searchString(t[0]) - print res2[0].asList(),'=?',t[1][-1:] - assert res2[0].asList() == t[1][-1:], "Failed lineEnd/stringEnd test (2): "+repr(t[0])+ " -> "+str(res2[0].asList()) - res3 = bnf3.parseString(t[0]) - print repr(res3[1]),'=?',repr(t[0][len(res3[0])+1:]) - assert res3[1] == t[0][len(res3[0])+1:], "Failed lineEnd/stringEnd test (3): " +repr(t[0])+ " -> "+str(res3[1].asList()) - - from pyparsing import Regex - import re - - k = Regex(r'a+',flags=re.S+re.M) - k = k.parseWithTabs() - k = k.leaveWhitespace() - - tests = [ - (r'aaa',['aaa']), - (r'\naaa',None), - (r'a\naa',None), - (r'aaa\n',None), - ] - for i,(src,expected) in enumerate(tests): - print i, repr(src).replace('\\\\','\\'), - try: - res = k.parseString(src, parseAll=True).asList() - except ParseException, pe: - res = None - print res - assert res == expected, "Failed on parseAll=True test %d" % i - -class VariableParseActionArgsTest(ParseTestCase): - def runTest(self): - - pa3 = lambda s,l,t: t - pa2 = lambda l,t: t - pa1 = lambda t: t - pa0 = lambda : None - class Callable3(object): - def __call__(self,s,l,t): - return t - class Callable2(object): - def __call__(self,l,t): - return t - class Callable1(object): - def __call__(self,t): - return t - class Callable0(object): - def __call__(self): - return - class CallableS3(object): - #~ @staticmethod - def __call__(s,l,t): - return t - __call__=staticmethod(__call__) - class CallableS2(object): - #~ @staticmethod - def __call__(l,t): - return t - __call__=staticmethod(__call__) - class CallableS1(object): - #~ @staticmethod - def __call__(t): - return t - __call__=staticmethod(__call__) - class CallableS0(object): - #~ @staticmethod - def __call__(): - return - __call__=staticmethod(__call__) - class CallableC3(object): - #~ @classmethod - def __call__(cls,s,l,t): - return t - __call__=classmethod(__call__) - class CallableC2(object): - #~ @classmethod - def __call__(cls,l,t): - return t - __call__=classmethod(__call__) - class CallableC1(object): - #~ @classmethod - def __call__(cls,t): - return t - __call__=classmethod(__call__) - class CallableC0(object): - #~ @classmethod - def __call__(cls): - return - __call__=classmethod(__call__) - - class parseActionHolder(object): - #~ @staticmethod - def pa3(s,l,t): - return t - pa3=staticmethod(pa3) - #~ @staticmethod - def pa2(l,t): - return t - pa2=staticmethod(pa2) - #~ @staticmethod - def pa1(t): - return t - pa1=staticmethod(pa1) - #~ @staticmethod - def pa0(): - return - pa0=staticmethod(pa0) - - def paArgs(*args): - print args - return args[2] - - class ClassAsPA0(object): - def __init__(self): - pass - def __str__(self): - return "A" - - class ClassAsPA1(object): - def __init__(self,t): - print "making a ClassAsPA1" - self.t = t - def __str__(self): - return self.t[0] - - class ClassAsPA2(object): - def __init__(self,l,t): - self.t = t - def __str__(self): - return self.t[0] - - class ClassAsPA3(object): - def __init__(self,s,l,t): - self.t = t - def __str__(self): - return self.t[0] - - class ClassAsPAStarNew(tuple): - def __new__(cls, *args): - print "make a ClassAsPAStarNew", args - return tuple.__new__(cls, *args[2].asList()) - def __str__(self): - return ''.join(self) - - #~ def ClassAsPANew(object): - #~ def __new__(cls, t): - #~ return object.__new__(cls, t) - #~ def __init__(self,t): - #~ self.t = t - #~ def __str__(self): - #~ return self.t - - from pyparsing import Literal,OneOrMore - - A = Literal("A").setParseAction(pa0) - B = Literal("B").setParseAction(pa1) - C = Literal("C").setParseAction(pa2) - D = Literal("D").setParseAction(pa3) - E = Literal("E").setParseAction(Callable0()) - F = Literal("F").setParseAction(Callable1()) - G = Literal("G").setParseAction(Callable2()) - H = Literal("H").setParseAction(Callable3()) - I = Literal("I").setParseAction(CallableS0()) - J = Literal("J").setParseAction(CallableS1()) - K = Literal("K").setParseAction(CallableS2()) - L = Literal("L").setParseAction(CallableS3()) - M = Literal("M").setParseAction(CallableC0()) - N = Literal("N").setParseAction(CallableC1()) - O = Literal("O").setParseAction(CallableC2()) - P = Literal("P").setParseAction(CallableC3()) - Q = Literal("Q").setParseAction(paArgs) - R = Literal("R").setParseAction(parseActionHolder.pa3) - S = Literal("S").setParseAction(parseActionHolder.pa2) - T = Literal("T").setParseAction(parseActionHolder.pa1) - U = Literal("U").setParseAction(parseActionHolder.pa0) - V = Literal("V") - - gg = OneOrMore( A | C | D | E | F | G | H | - I | J | K | L | M | N | O | P | Q | R | S | U | V | B | T) - testString = "VUTSRQPONMLKJIHGFEDCBA" - res = gg.parseString(testString) - print res.asList() - assert res.asList()==list(testString), "Failed to parse using variable length parse actions" - - A = Literal("A").setParseAction(ClassAsPA0) - B = Literal("B").setParseAction(ClassAsPA1) - C = Literal("C").setParseAction(ClassAsPA2) - D = Literal("D").setParseAction(ClassAsPA3) - E = Literal("E").setParseAction(ClassAsPAStarNew) - - gg = OneOrMore( A | B | C | D | E | F | G | H | - I | J | K | L | M | N | O | P | Q | R | S | T | U | V) - testString = "VUTSRQPONMLKJIHGFEDCBA" - res = gg.parseString(testString) - print map(str,res) - assert map(str,res)==list(testString), "Failed to parse using variable length parse actions using class constructors as parse actions" - -class EnablePackratParsing(ParseTestCase): - def runTest(self): - from pyparsing import ParserElement - ParserElement.enablePackrat() - -class SingleArgExceptionTest(ParseTestCase): - def runTest(self): - from pyparsing import ParseBaseException,ParseFatalException - - msg = "" - raisedMsg = "" - testMessage = "just one arg" - try: - raise ParseFatalException, testMessage - except ParseBaseException,pbe: - print "Received expected exception:", pbe - raisedMsg = pbe.msg - assert raisedMsg == testMessage, "Failed to get correct exception message" - - -class KeepOriginalTextTest(ParseTestCase): - def runTest(self): - from pyparsing import makeHTMLTags, keepOriginalText - - def rfn(t): - return "%s:%d" % (t.src, len("".join(t))) - - makeHTMLStartTag = lambda tag: makeHTMLTags(tag)[0].setParseAction(keepOriginalText) - - # use the lambda, Luke - #~ start, imge = makeHTMLTags('IMG') - start = makeHTMLStartTag('IMG') - - # don't replace our fancy parse action with rfn, - # append rfn to the list of parse actions - #~ start.setParseAction(rfn) - start.addParseAction(rfn) - - #start.setParseAction(lambda s,l,t:t.src) - text = '''_<img src="images/cal.png" - alt="cal image" width="16" height="15">_''' - s = start.transformString(text) - print s - assert s.startswith("_images/cal.png:"), "failed to preserve input s properly" - assert s.endswith("77_"),"failed to return full original text properly" - -class PackratParsingCacheCopyTest(ParseTestCase): - def runTest(self): - from pyparsing import Word,nums,ParserElement,delimitedList,Literal,Optional,alphas,alphanums,ZeroOrMore,empty - - integer = Word(nums).setName("integer") - id = Word(alphas+'_',alphanums+'_') - simpleType = Literal('int'); - arrayType= simpleType+ZeroOrMore('['+delimitedList(integer)+']') - varType = arrayType | simpleType - varDec = varType + delimitedList(id + Optional('='+integer))+';' - - codeBlock = Literal('{}') - - funcDef = Optional(varType | 'void')+id+'('+(delimitedList(varType+id)|'void'|empty)+')'+codeBlock - - program = varDec | funcDef - input = 'int f(){}' - results = program.parseString(input) - print "Parsed '%s' as %s" % (input, results.asList()) - assert results.asList() == ['int', 'f', '(', ')', '{}'], "Error in packrat parsing" - -class PackratParsingCacheCopyTest2(ParseTestCase): - def runTest(self): - from pyparsing import Keyword,Word,Suppress,Forward,Optional,delimitedList,ParserElement,Group - - DO,AA = map(Keyword, "DO AA".split()) - LPAR,RPAR = map(Suppress,"()") - identifier = ~AA + Word("Z") - - function_name = identifier.copy() - #~ function_name = ~AA + Word("Z") #identifier.copy() - expr = Forward().setName("expr") - expr << (Group(function_name + LPAR + Optional(delimitedList(expr)) + RPAR).setName("functionCall") | - identifier.setName("ident")#.setDebug()#.setBreak() - ) - - stmt = DO + Group(delimitedList(identifier + ".*" | expr)) - result = stmt.parseString("DO Z") - print result.asList() - assert len(result[1]) == 1, "packrat parsing is duplicating And term exprs" - -class ParseResultsDelTest(ParseTestCase): - def runTest(self): - from pyparsing import OneOrMore, Word, alphas, nums - - grammar = OneOrMore(Word(nums))("ints") + OneOrMore(Word(alphas))("words") - res = grammar.parseString("123 456 ABC DEF") - print res.dump() - origInts = res.ints.asList() - origWords = res.words.asList() - del res[1] - del res["words"] - print res.dump() - assert res[1]=='ABC',"failed to delete 0'th element correctly" - assert res.ints.asList()==origInts, "updated named attributes, should have updated list only" - assert res.words=="", "failed to update named attribute correctly" - assert res[-1]=='DEF', "updated list, should have updated named attributes only" - -class WithAttributeParseActionTest(ParseTestCase): - def runTest(self): - """ - This unit test checks withAttribute in these ways: - - * Argument forms as keywords and tuples - * Selecting matching tags by attribute - * Case-insensitive attribute matching - * Correctly matching tags having the attribute, and rejecting tags not having the attribute - - (Unit test written by voigts as part of the Google Highly Open Participation Contest) - """ - - from pyparsing import makeHTMLTags, Word, withAttribute, nums - - data = """ - <a>1</a> - <a b="x">2</a> - <a B="x">3</a> - <a b="X">4</a> - <a b="y">5</a> - """ - tagStart, tagEnd = makeHTMLTags("a") - - expr = tagStart + Word(nums).setResultsName("value") + tagEnd - - expected = [['a', ['b', 'x'], False, '2', '</a>'], ['a', ['b', 'x'], False, '3', '</a>']] - - for attrib in [ - withAttribute(b="x"), - #withAttribute(B="x"), - withAttribute(("b","x")), - #withAttribute(("B","x")), - ]: - - tagStart.setParseAction(attrib) - result = expr.searchString(data) - - print result.dump() - assert result.asList() == expected, "Failed test, expected %s, got %s" % (expected, result.asList()) - -class NestedExpressionsTest(ParseTestCase): - def runTest(self): - """ - This unit test checks nestedExpr in these ways: - - use of default arguments - - use of non-default arguments (such as a pyparsing-defined comment - expression in place of quotedString) - - use of a custom content expression - - use of a pyparsing expression for opener and closer is *OPTIONAL* - - use of input data containing nesting delimiters - - correct grouping of parsed tokens according to nesting of opening - and closing delimiters in the input string - - (Unit test written by christoph... as part of the Google Highly Open Participation Contest) - """ - from pyparsing import nestedExpr, Literal, Regex, restOfLine, quotedString - - #All defaults. Straight out of the example script. Also, qualifies for - #the bonus: note the fact that (Z | (E^F) & D) is not parsed :-). - # Tests for bug fixed in 1.4.10 - print "Test defaults:" - teststring = "(( ax + by)*C) (Z | (E^F) & D)" - - expr = nestedExpr() - - expected = [[['ax', '+', 'by'], '*C']] - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected, "Defaults didn't work. That's a bad sign. Expected: %s, got: %s" % (expected, result) - - #Going through non-defaults, one by one; trying to think of anything - #odd that might not be properly handled. - - #Change opener - print "\nNon-default opener" - opener = "[" - teststring = test_string = "[[ ax + by)*C)" - expected = [[['ax', '+', 'by'], '*C']] - expr = nestedExpr("[") - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected, "Non-default opener didn't work. Expected: %s, got: %s" % (expected, result) - - #Change closer - print "\nNon-default closer" - - teststring = test_string = "(( ax + by]*C]" - expected = [[['ax', '+', 'by'], '*C']] - expr = nestedExpr(closer="]") - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected, "Non-default closer didn't work. Expected: %s, got: %s" % (expected, result) - - # #Multicharacter opener, closer - # opener = "bar" - # closer = "baz" - print "\nLiteral expressions for opener and closer" - - opener,closer = map(Literal, "bar baz".split()) - expr = nestedExpr(opener, closer, - content=Regex(r"([^b ]|b(?!a)|ba(?![rz]))+")) - - teststring = "barbar ax + bybaz*Cbaz" - expected = [[['ax', '+', 'by'], '*C']] - # expr = nestedExpr(opener, closer) - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected, "Multicharacter opener and closer didn't work. Expected: %s, got: %s" % (expected, result) - - #Lisp-ish comments - print "\nUse ignore expression (1)" - comment = Regex(r";;.*") - teststring = \ - """ - (let ((greeting "Hello, world!")) ;;(foo bar - (display greeting)) - """ - - expected = [['let', [['greeting', '"Hello,', 'world!"']], ';;(foo bar',\ - ['display', 'greeting']]] - expr = nestedExpr(ignoreExpr=comment) - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected , "Lisp-ish comments (\";; <...> $\") didn't work. Expected: %s, got: %s" % (expected, result) - - - #Lisp-ish comments, using a standard bit of pyparsing, and an Or. - print "\nUse ignore expression (2)" - comment = ';;' + restOfLine - - teststring = \ - """ - (let ((greeting "Hello, )world!")) ;;(foo bar - (display greeting)) - """ - - expected = [['let', [['greeting', '"Hello, )world!"']], ';;', '(foo bar', - ['display', 'greeting']]] - expr = nestedExpr(ignoreExpr=(comment ^ quotedString)) - result = expr.parseString(teststring) - print result.dump() - assert result.asList() == expected , "Lisp-ish comments (\";; <...> $\") and quoted strings didn't work. Expected: %s, got: %s" % (expected, result) - -class ParseAllTest(ParseTestCase): - def runTest(self): - from pyparsing import Word, cppStyleComment - - testExpr = Word("A") - - tests = [ - ("AAAAA", False, True), - ("AAAAA", True, True), - ("AAABB", False, True), - ("AAABB", True, False), - ] - for s,parseAllFlag,shouldSucceed in tests: - try: - print "'%s' parseAll=%s (shouldSuceed=%s)" % (s, parseAllFlag, shouldSucceed) - testExpr.parseString(s,parseAllFlag) - assert shouldSucceed, "successfully parsed when should have failed" - except ParseException, pe: - assert not shouldSucceed, "failed to parse when should have succeeded" - - # add test for trailing comments - testExpr.ignore(cppStyleComment) - - tests = [ - ("AAAAA //blah", False, True), - ("AAAAA //blah", True, True), - ("AAABB //blah", False, True), - ("AAABB //blah", True, False), - ] - for s,parseAllFlag,shouldSucceed in tests: - try: - print "'%s' parseAll=%s (shouldSucceed=%s)" % (s, parseAllFlag, shouldSucceed) - testExpr.parseString(s,parseAllFlag) - assert shouldSucceed, "successfully parsed when should have failed" - except ParseException, pe: - assert not shouldSucceed, "failed to parse when should have succeeded" - -class GreedyQuotedStringsTest(ParseTestCase): - def runTest(self): - from pyparsing import QuotedString, sglQuotedString, dblQuotedString, quotedString, delimitedList - - src = """\ - "string1", "strin""g2" - 'string1', 'string2' - ^string1^, ^string2^ - <string1>, <string2>""" - - testExprs = (sglQuotedString, dblQuotedString, quotedString, - QuotedString('"', escQuote='""'), QuotedString("'", escQuote="''"), - QuotedString("^"), QuotedString("<",endQuoteChar=">")) - for expr in testExprs: - strs = delimitedList(expr).searchString(src) - print strs - assert bool(strs), "no matches found for test expression '%s'" % expr - for lst in strs: - assert len(lst) == 2, "invalid match found for test expression '%s'" % expr - - from pyparsing import alphas, nums, Word - src = """'ms1',1,0,'2009-12-22','2009-12-22 10:41:22') ON DUPLICATE KEY UPDATE sent_count = sent_count + 1, mtime = '2009-12-22 10:41:22';""" - tok_sql_quoted_value = ( - QuotedString("'", "\\", "''", True, False) ^ - QuotedString('"', "\\", '""', True, False)) - tok_sql_computed_value = Word(nums) - tok_sql_identifier = Word(alphas) - - val = tok_sql_quoted_value | tok_sql_computed_value | tok_sql_identifier - vals = delimitedList(val) - print vals.parseString(src) - assert len(vals.parseString(src)) == 5, "error in greedy quote escaping" - - -class WordBoundaryExpressionsTest(ParseTestCase): - def runTest(self): - from pyparsing import WordEnd, WordStart, oneOf - - ws = WordStart() - we = WordEnd() - vowel = oneOf(list("AEIOUY")) - consonant = oneOf(list("BCDFGHJKLMNPQRSTVWXZ")) - - leadingVowel = ws + vowel - trailingVowel = vowel + we - leadingConsonant = ws + consonant - trailingConsonant = consonant + we - internalVowel = ~ws + vowel + ~we - - bnf = leadingVowel | trailingVowel - - tests = """\ - ABC DEF GHI - JKL MNO PQR - STU VWX YZ """.splitlines() - tests.append( "\n".join(tests) ) - - expectedResult = [ - [['D', 'G'], ['A'], ['C', 'F'], ['I'], ['E'], ['A', 'I']], - [['J', 'M', 'P'], [], ['L', 'R'], ['O'], [], ['O']], - [['S', 'V'], ['Y'], ['X', 'Z'], ['U'], [], ['U', 'Y']], - [['D', 'G', 'J', 'M', 'P', 'S', 'V'], - ['A', 'Y'], - ['C', 'F', 'L', 'R', 'X', 'Z'], - ['I', 'O', 'U'], - ['E'], - ['A', 'I', 'O', 'U', 'Y']], - ] - - for t,expected in zip(tests, expectedResult): - print t - results = map(lambda e: flatten(e.searchString(t).asList()), - [ - leadingConsonant, - leadingVowel, - trailingConsonant, - trailingVowel, - internalVowel, - bnf, - ] - ) - print results - assert results==expected,"Failed WordBoundaryTest, expected %s, got %s" % (expected,results) - print - -class OptionalEachTest(ParseTestCase): - def runTest(self): - from pyparsing import Optional, Keyword - - the_input = "Major Tal Weiss" - parser1 = (Optional('Tal') + Optional('Weiss')) & Keyword('Major') - parser2 = Optional(Optional('Tal') + Optional('Weiss')) & Keyword('Major') - p1res = parser1.parseString( the_input) - p2res = parser2.parseString( the_input) - assert p1res.asList() == p2res.asList(), "Each failed to match with nested Optionals, " + \ - str(p1res.asList()) + " should match " + str(p2res.asList()) - -class SumParseResultsTest(ParseTestCase): - def runTest(self): - - samplestr1 = "garbage;DOB 10-10-2010;more garbage\nID PARI12345678;more garbage" - samplestr2 = "garbage;ID PARI12345678;more garbage\nDOB 10-10-2010;more garbage" - samplestr3 = "garbage;DOB 10-10-2010" - samplestr4 = "garbage;ID PARI12345678;more garbage- I am cool" - - res1 = "ID:PARI12345678 DOB:10-10-2010 INFO:" - res2 = "ID:PARI12345678 DOB:10-10-2010 INFO:" - res3 = "ID: DOB:10-10-2010 INFO:" - res4 = "ID:PARI12345678 DOB: INFO: I am cool" - - from pyparsing import Regex, Word, alphanums, restOfLine - dob_ref = "DOB" + Regex(r"\d{2}-\d{2}-\d{4}")("dob") - id_ref = "ID" + Word(alphanums,exact=12)("id") - info_ref = "-" + restOfLine("info") - - person_data = dob_ref | id_ref | info_ref - - tests = (samplestr1,samplestr2,samplestr3,samplestr4,) - results = (res1, res2, res3, res4,) - for test,expected in zip(tests, results): - person = sum(person_data.searchString(test)) - result = "ID:%s DOB:%s INFO:%s" % (person.id, person.dob, person.info) - print test - print expected - print result - for pd in person_data.searchString(test): - print pd.dump() - print - assert expected == result, \ - "Failed to parse '%s' correctly, \nexpected '%s', got '%s'" % (test,expected,result) - -class MiscellaneousParserTests(ParseTestCase): - def runTest(self): - import pyparsing - - runtests = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - if IRON_PYTHON_ENV: - runtests = "ABCDEGHIJKLMNOPQRSTUVWXYZ" - - # test making oneOf with duplicate symbols - if "A" in runtests: - print "verify oneOf handles duplicate symbols" - try: - test1 = pyparsing.oneOf("a b c d a") - except RuntimeError: - assert False,"still have infinite loop in oneOf with duplicate symbols" - - # test MatchFirst bugfix - if "B" in runtests: - print "verify MatchFirst iterates properly" - results = pyparsing.quotedString.parseString("'this is a single quoted string'") - assert len(results) > 0, "MatchFirst error - not iterating over all choices" - - # verify streamline of subexpressions - if "C" in runtests: - print "verify proper streamline logic" - compound = pyparsing.Literal("A") + "B" + "C" + "D" - assert len(compound.exprs) == 2,"bad test setup" - print compound - compound.streamline() - print compound - assert len(compound.exprs) == 4,"streamline not working" - - # test for Optional with results name and no match - if "D" in runtests: - print "verify Optional's do not cause match failure if have results name" - testGrammar = pyparsing.Literal("A") + pyparsing.Optional("B").setResultsName("gotB") + pyparsing.Literal("C") - try: - testGrammar.parseString("ABC") - testGrammar.parseString("AC") - except pyparsing.ParseException, pe: - print pe.pstr,"->",pe - assert False, "error in Optional matching of string %s" % pe.pstr - - # test return of furthest exception - if "E" in runtests: - testGrammar = ( pyparsing.Literal("A") | - ( pyparsing.Optional("B") + pyparsing.Literal("C") ) | - pyparsing.Literal("D") ) - try: - testGrammar.parseString("BC") - testGrammar.parseString("BD") - except pyparsing.ParseException, pe: - print pe.pstr,"->",pe - assert pe.pstr == "BD", "wrong test string failed to parse" - assert pe.loc == 1, "error in Optional matching, pe.loc="+str(pe.loc) - - # test validate - if "F" in runtests: - print "verify behavior of validate()" - def testValidation( grmr, gnam, isValid ): - try: - grmr.streamline() - grmr.validate() - assert isValid,"validate() accepted invalid grammar " + gnam - except pyparsing.RecursiveGrammarException,e: - print grmr - assert not isValid, "validate() rejected valid grammar " + gnam - - fwd = pyparsing.Forward() - g1 = pyparsing.OneOrMore( ( pyparsing.Literal("A") + "B" + "C" ) | fwd ) - g2 = pyparsing.ZeroOrMore("C" + g1) - fwd << pyparsing.Group(g2) - testValidation( fwd, "fwd", isValid=True ) - - fwd2 = pyparsing.Forward() - fwd2 << pyparsing.Group("A" | fwd2) - testValidation( fwd2, "fwd2", isValid=False ) - - fwd3 = pyparsing.Forward() - fwd3 << pyparsing.Optional("A") + fwd3 - testValidation( fwd3, "fwd3", isValid=False ) - - # test getName - if "G" in runtests: - print "verify behavior of getName()" - aaa = pyparsing.Group(pyparsing.Word("a")).setResultsName("A") - bbb = pyparsing.Group(pyparsing.Word("b")).setResultsName("B") - ccc = pyparsing.Group(":" + pyparsing.Word("c")).setResultsName("C") - g1 = "XXX" + pyparsing.ZeroOrMore( aaa | bbb | ccc ) - teststring = "XXX b b a b b a b :c b a" - names = [] - print g1.parseString(teststring).dump() - for t in g1.parseString(teststring): - print t, repr(t) - try: - names.append( t[0].getName() ) - except: - try: - names.append( t.getName() ) - except: - names.append( None ) - print teststring - print names - assert names==[None, 'B', 'B', 'A', 'B', 'B', 'A', 'B', 'C', 'B', 'A'], \ - "failure in getting names for tokens" - - # test ParseResults.get() method - if "H" in runtests: - print "verify behavior of ParseResults.get()" - res = g1.parseString(teststring) - print res.get("A","A not found")[0] - print res.get("D","!D") - assert res.get("A","A not found")[0] == "a", "get on existing key failed" - assert res.get("D","!D") == "!D", "get on missing key failed" - - if "I" in runtests: - print "verify handling of Optional's beyond the end of string" - testGrammar = "A" + pyparsing.Optional("B") + pyparsing.Optional("C") + pyparsing.Optional("D") - testGrammar.parseString("A") - testGrammar.parseString("AB") - - # test creating Literal with empty string - if "J" in runtests: - print 'verify non-fatal usage of Literal("")' - e = pyparsing.Literal("") - try: - e.parseString("SLJFD") - except Exception,e: - assert False, "Failed to handle empty Literal" - - # test line() behavior when starting at 0 and the opening line is an \n - if "K" in runtests: - print 'verify correct line() behavior when first line is empty string' - assert pyparsing.line(0, "\nabc\ndef\n") == '', "Error in line() with empty first line in text" - txt = "\nabc\ndef\n" - results = [ pyparsing.line(i,txt) for i in range(len(txt)) ] - assert results == ['', 'abc', 'abc', 'abc', 'abc', 'def', 'def', 'def', 'def'], "Error in line() with empty first line in text" - txt = "abc\ndef\n" - results = [ pyparsing.line(i,txt) for i in range(len(txt)) ] - assert results == ['abc', 'abc', 'abc', 'abc', 'def', 'def', 'def', 'def'], "Error in line() with non-empty first line in text" - - # test bugfix with repeated tokens when packrat parsing enabled - if "L" in runtests: - a = pyparsing.Literal("a") - b = pyparsing.Literal("b") - c = pyparsing.Literal("c") - - abb = a + b + b - abc = a + b + c - aba = a + b + a - grammar = abb | abc | aba - - assert ''.join(grammar.parseString( "aba" )) == 'aba', "Packrat ABA failure!" - -def makeTestSuite(): - suite = TestSuite() - suite.addTest( PyparsingTestInit() ) - suite.addTest( ParseIDLTest() ) - suite.addTest( ParseASMLTest() ) - suite.addTest( ParseFourFnTest() ) - suite.addTest( ParseSQLTest() ) - suite.addTest( ParseConfigFileTest() ) - suite.addTest( ParseJSONDataTest() ) - suite.addTest( ParseCommaSeparatedValuesTest() ) - suite.addTest( ParseEBNFTest() ) - suite.addTest( ScanStringTest() ) - suite.addTest( QuotedStringsTest() ) - suite.addTest( CustomQuotesTest() ) - suite.addTest( CaselessOneOfTest() ) - suite.addTest( AsXMLTest() ) - suite.addTest( CommentParserTest() ) - suite.addTest( ParseExpressionResultsTest() ) - suite.addTest( ParseExpressionResultsAccumulateTest() ) - suite.addTest( ReStringRangeTest() ) - suite.addTest( ParseKeywordTest() ) - suite.addTest( ParseHTMLTagsTest() ) - suite.addTest( ParseUsingRegex() ) - suite.addTest( SkipToParserTests() ) - suite.addTest( CountedArrayTest() ) - suite.addTest( CountedArrayTest2() ) - suite.addTest( LineAndStringEndTest() ) - suite.addTest( VariableParseActionArgsTest() ) - suite.addTest( RepeaterTest() ) - suite.addTest( RecursiveCombineTest() ) - suite.addTest( OperatorPrecedenceGrammarTest1() ) - suite.addTest( OperatorPrecedenceGrammarTest2() ) - suite.addTest( OperatorPrecedenceGrammarTest3() ) - suite.addTest( OperatorPrecedenceGrammarTest4() ) - suite.addTest( ParseResultsPickleTest() ) - suite.addTest( ParseResultsWithNamedTupleTest() ) - suite.addTest( ParseResultsDelTest() ) - suite.addTest( SingleArgExceptionTest() ) - suite.addTest( UpcaseDowncaseUnicode() ) - if not IRON_PYTHON_ENV: - suite.addTest( KeepOriginalTextTest() ) - suite.addTest( PackratParsingCacheCopyTest() ) - suite.addTest( PackratParsingCacheCopyTest2() ) - suite.addTest( WithAttributeParseActionTest() ) - suite.addTest( NestedExpressionsTest() ) - suite.addTest( WordBoundaryExpressionsTest() ) - suite.addTest( ParseAllTest() ) - suite.addTest( GreedyQuotedStringsTest() ) - suite.addTest( OptionalEachTest() ) - suite.addTest( SumParseResultsTest() ) - suite.addTest( MiscellaneousParserTests() ) - if TEST_USING_PACKRAT: - # retest using packrat parsing (disable those tests that aren't compatible) - suite.addTest( EnablePackratParsing() ) - - unpackrattables = [ EnablePackratParsing, RepeaterTest, ] - - # add tests to test suite a second time, to run with packrat parsing - # (leaving out those that we know wont work with packrat) - packratTests = [t.__class__() for t in suite._tests - if t.__class__ not in unpackrattables] - suite.addTests( packratTests ) - - return suite - -def makeTestSuiteTemp(): - suite = TestSuite() - suite.addTest( PyparsingTestInit() ) - suite.addTest( OptionalEachTest() ) - - return suite - -console = False -console = True - -#~ from line_profiler import LineProfiler -#~ from pyparsing import ParseResults -#~ lp = LineProfiler(ParseResults.__setitem__) - -if console: - #~ # console mode - testRunner = TextTestRunner() - testRunner.run( makeTestSuite() ) - #~ testRunner.run( makeTestSuiteTemp() ) - #~ lp.run("testRunner.run( makeTestSuite() )") -else: - # HTML mode - outfile = "testResults.html" - outstream = file(outfile,"w") - testRunner = HTMLTestRunner.HTMLTestRunner( stream=outstream ) - testRunner.run( makeTestSuite() ) - outstream.close() - - import os - os.system(r'"C:\Program Files\Internet Explorer\iexplore.exe" file://' + outfile) - -#~ lp.print_stats() \ No newline at end of file diff --git a/unitTests.py b/unitTests.py new file mode 100644 index 0000000000000000000000000000000000000000..ee11877283be72c01e9132c20abf73b3891f98d2_dW5pdFRlc3RzLnB5 --- /dev/null +++ b/unitTests.py @@ -0,0 +1,2117 @@ +# -*- coding: UTF-8 -*- +from unittest import TestCase, TestSuite, TextTestRunner +from pyparsing import ParseException +import HTMLTestRunner + +import sys +import pprint +import pdb + +TEST_USING_PACKRAT = True +#~ TEST_USING_PACKRAT = False + +# simple utility for flattening nested lists +def flatten(L): + if type(L) is not list: return [L] + if L == []: return L + return flatten(L[0]) + flatten(L[1:]) + +""" +class ParseTest(TestCase): + def setUp(self): + pass + + def runTest(self): + assert 1==1, "we've got bigger problems..." + + def tearDown(self): + pass +""" + +class ParseTestCase(TestCase): + def setUp(self): + print ">>>> Starting test",str(self) + + def runTest(self): + pass + + def tearDown(self): + print "<<<< End of test",str(self) + print + + def __str__(self): + return self.__class__.__name__ + +class PyparsingTestInit(ParseTestCase): + def setUp(self): + from pyparsing import __version__ as pyparsingVersion + print "Beginning test of pyparsing, version", pyparsingVersion + print "Python version", sys.version + def tearDown(self): + pass + +class ParseASMLTest(ParseTestCase): + def runTest(self): + import parseASML + files = [ ("A52759.txt", 2150, True, True, 0.38, 25, "21:47:17", "22:07:32", 235), + ("24141506_P5107RM59_399A1457N1_PHS04", 373,True, True, 0.5, 1, "11:35:25", "11:37:05", 183), + ("24141506_P5107RM59_399A1457N1_PHS04B", 373, True, True, 0.5, 1, "01:02:54", "01:04:49", 186), + ("24157800_P5107RM74_399A1828M1_PHS04", 1141, True, False, 0.5, 13, "00:00:54", "23:59:48", 154) ] + for testFile,numToks,trkInpUsed,trkOutpUsed,maxDelta,numWafers,minProcBeg,maxProcEnd,maxLevStatsIV in files: + print "Parsing",testFile,"...", + #~ text = "\n".join( [ line for line in file(testFile) ] ) + #~ results = parseASML.BNF().parseString( text ) + results = parseASML.BNF().parseFile( testFile ) + #~ pprint.pprint( results.asList() ) + #~ pprint.pprint( results.batchData.asList() ) + #~ print results.batchData.keys() + + allToks = flatten( results.asList() ) + assert len(allToks) == numToks, \ + "wrong number of tokens parsed (%s), got %d, expected %d" % (testFile, len(allToks),numToks) + assert results.batchData.trackInputUsed == trkInpUsed, "error evaluating results.batchData.trackInputUsed" + assert results.batchData.trackOutputUsed == trkOutpUsed, "error evaluating results.batchData.trackOutputUsed" + assert results.batchData.maxDelta == maxDelta,"error evaluating results.batchData.maxDelta" + assert len(results.waferData) == numWafers, "did not read correct number of wafers" + assert min([wd.procBegin for wd in results.waferData]) == minProcBeg, "error reading waferData.procBegin" + assert max([results.waferData[k].procEnd for k in range(len(results.waferData))]) == maxProcEnd, "error reading waferData.procEnd" + assert sum(results.levelStatsIV['MAX']) == maxLevStatsIV, "error reading levelStatsIV" + assert sum(results.levelStatsIV.MAX) == maxLevStatsIV, "error reading levelStatsIV" + print "OK" + print testFile,len(allToks) + #~ print "results.batchData.trackInputUsed =",results.batchData.trackInputUsed + #~ print "results.batchData.trackOutputUsed =",results.batchData.trackOutputUsed + #~ print "results.batchData.maxDelta =",results.batchData.maxDelta + #~ print len(results.waferData)," wafers" + #~ print min([wd.procBegin for wd in results.waferData]) + #~ print max([results.waferData[k].procEnd for k in range(len(results.waferData))]) + #~ print sum(results.levelStatsIV['MAX.']) + + +class ParseFourFnTest(ParseTestCase): + def runTest(self): + import fourFn + def test(s,ans): + fourFn.exprStack = [] + results = fourFn.BNF().parseString( s ) + resultValue = fourFn.evaluateStack( fourFn.exprStack ) + assert resultValue == ans, "failed to evaluate %s, got %f" % ( s, resultValue ) + print s, "->", resultValue + + test( "9", 9 ) + test( "9 + 3 + 6", 18 ) + test( "9 + 3 / 11", 9.0+3.0/11.0) + test( "(9 + 3)", 12 ) + test( "(9+3) / 11", (9.0+3.0)/11.0 ) + test( "9 - (12 - 6)", 3) + test( "2*3.14159", 6.28318) + test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535/10.0 ) + test( "PI * PI / 10", 3.1415926535*3.1415926535/10.0 ) + test( "PI*PI/10", 3.1415926535*3.1415926535/10.0 ) + test( "6.02E23 * 8.048", 6.02E23 * 8.048 ) + test( "e / 3", 2.718281828/3.0 ) + test( "sin(PI/2)", 1.0 ) + test( "trunc(E)", 2.0 ) + test( "E^PI", 2.718281828**3.1415926535 ) + test( "2^3^2", 2**3**2) + test( "2^3+2", 2**3+2) + test( "2^9", 2**9 ) + test( "sgn(-2)", -1 ) + test( "sgn(0)", 0 ) + test( "sgn(0.1)", 1 ) + +class ParseSQLTest(ParseTestCase): + def runTest(self): + import simpleSQL + + def test(s, numToks, errloc=-1 ): + try: + sqlToks = flatten( simpleSQL.simpleSQL.parseString(s).asList() ) + print s,sqlToks,len(sqlToks) + assert len(sqlToks) == numToks + except ParseException, e: + if errloc >= 0: + assert e.loc == errloc + + + test( "SELECT * from XYZZY, ABC", 6 ) + test( "select * from SYS.XYZZY", 5 ) + test( "Select A from Sys.dual", 5 ) + test( "Select A,B,C from Sys.dual", 7 ) + test( "Select A, B, C from Sys.dual", 7 ) + test( "Select A, B, C from Sys.dual, Table2 ", 8 ) + test( "Xelect A, B, C from Sys.dual", 0, 0 ) + test( "Select A, B, C frox Sys.dual", 0, 15 ) + test( "Select", 0, 6 ) + test( "Select &&& frox Sys.dual", 0, 7 ) + test( "Select A from Sys.dual where a in ('RED','GREEN','BLUE')", 12 ) + test( "Select A from Sys.dual where a in ('RED','GREEN','BLUE') and b in (10,20,30)", 20 ) + test( "Select A,b from table1,table2 where table1.id eq table2.id -- test out comparison operators", 10 ) + +class ParseConfigFileTest(ParseTestCase): + def runTest(self): + import configParse + + def test(fnam,numToks,resCheckList): + print "Parsing",fnam,"...", + iniFileLines = "\n".join([ lin for lin in file(fnam) ]) + iniData = configParse.inifile_BNF().parseString( iniFileLines ) + print len(flatten(iniData.asList())) + #~ pprint.pprint( iniData.asList() ) + #~ pprint.pprint( repr(iniData) ) + #~ print len(iniData), len(flatten(iniData.asList())) + print iniData.keys() + #~ print iniData.users.keys() + #~ print + assert len(flatten(iniData.asList())) == numToks, "file %s not parsed correctly" % fnam + for chk in resCheckList: + print chk[0], eval("iniData."+chk[0]), chk[1] + assert eval("iniData."+chk[0]) == chk[1] + print "OK" + + test("karthik.ini", 23, + [ ("users.K","8"), + ("users.mod_scheme","'QPSK'"), + ("users.Na", "K+2") ] + ) + test("setup.ini", 125, + [ ("Startup.audioinf", "M3i"), + ("Languages.key1", "0x0003"), + ("test.foo","bar") ] ) + +class ParseJSONDataTest(ParseTestCase): + def runTest(self): + from jsonParser import jsonObject + from jsonParserFull import test1,test2,test3,test4,test5 + + expected = [ + [], + [], + [], + [], + [], + ] + + import pprint + for t,exp in zip((test1,test2,test3,test4,test5),expected): + result = jsonObject.parseString(t) +## print result.dump() + pprint.pprint(result.asList()) + print +## if result.asList() != exp: +## print "Expected %s, parsed results as %s" % (exp, result.asList()) + +class ParseCommaSeparatedValuesTest(ParseTestCase): + def runTest(self): + from pyparsing import commaSeparatedList + import string + + testData = [ + "a,b,c,100.2,,3", + "d, e, j k , m ", + "'Hello, World', f, g , , 5.1,x", + "John Doe, 123 Main St., Cleveland, Ohio", + "Jane Doe, 456 St. James St., Los Angeles , California ", + "", + ] + testVals = [ + [ (3,'100.2'), (4,''), (5, '3') ], + [ (2, 'j k'), (3, 'm') ], + [ (0, "'Hello, World'"), (2, 'g'), (3, '') ], + [ (0,'John Doe'), (1, '123 Main St.'), (2, 'Cleveland'), (3, 'Ohio') ], + [ (0,'Jane Doe'), (1, '456 St. James St.'), (2, 'Los Angeles'), (3, 'California') ] + ] + for line,tests in zip(testData, testVals): + print "Parsing: \""+line+"\" ->", + results = commaSeparatedList.parseString(line) + print results.asList() + for t in tests: + if not(len(results)>t[0] and results[t[0]] == t[1]): + print "$$$", results.dump() + print "$$$", results[0] + assert len(results)>t[0] and results[t[0]] == t[1],"failed on %s, item %d s/b '%s', got '%s'" % ( line, t[0], t[1], str(results.asList()) ) + +class ParseEBNFTest(ParseTestCase): + def runTest(self): + import ebnf + from pyparsing import Word, quotedString, alphas, nums,ParserElement + + print 'Constructing EBNF parser with pyparsing...' + + grammar = ''' + syntax = (syntax_rule), {(syntax_rule)}; + syntax_rule = meta_identifier, '=', definitions_list, ';'; + definitions_list = single_definition, {'|', single_definition}; + single_definition = syntactic_term, {',', syntactic_term}; + syntactic_term = syntactic_factor,['-', syntactic_factor]; + syntactic_factor = [integer, '*'], syntactic_primary; + syntactic_primary = optional_sequence | repeated_sequence | + grouped_sequence | meta_identifier | terminal_string; + optional_sequence = '[', definitions_list, ']'; + repeated_sequence = '{', definitions_list, '}'; + grouped_sequence = '(', definitions_list, ')'; + (* + terminal_string = "'", character - "'", {character - "'"}, "'" | + '"', character - '"', {character - '"'}, '"'; + meta_identifier = letter, {letter | digit}; + integer = digit, {digit}; + *) + ''' + + table = {} + table['terminal_string'] = quotedString + table['meta_identifier'] = Word(alphas+"_", alphas+"_"+nums) + table['integer'] = Word(nums) + + print 'Parsing EBNF grammar with EBNF parser...' + parsers = ebnf.parse(grammar, table) + ebnf_parser = parsers['syntax'] + #~ print ",\n ".join( str(parsers.keys()).split(", ") ) + print "-","\n- ".join( parsers.keys() ) + assert len(parsers.keys()) == 13, "failed to construct syntax grammar" + + print 'Parsing EBNF grammar with generated EBNF parser...' + parsed_chars = ebnf_parser.parseString(grammar) + parsed_char_len = len(parsed_chars) + + print "],\n".join(str( parsed_chars.asList() ).split("],")) + assert len(flatten(parsed_chars.asList())) == 98, "failed to tokenize grammar correctly" + + +class ParseIDLTest(ParseTestCase): + def runTest(self): + import idlParse + + def test( strng, numToks, errloc=0 ): + print strng + try: + bnf = idlParse.CORBA_IDL_BNF() + tokens = bnf.parseString( strng ) + print "tokens = " + pprint.pprint( tokens.asList() ) + tokens = flatten( tokens.asList() ) + print len(tokens) + assert len(tokens) == numToks, "error matching IDL string, %s -> %s" % (strng, str(tokens) ) + except ParseException, err: + print err.line + print " "*(err.column-1) + "^" + print err + assert numToks == 0, "unexpected ParseException while parsing %s, %s" % (strng, str(err) ) + assert err.loc == errloc, "expected ParseException at %d, found exception at %d" % (errloc, err.loc) + + test( + """ + /* + * a block comment * + */ + typedef string[10] tenStrings; + typedef sequence<string> stringSeq; + typedef sequence< sequence<string> > stringSeqSeq; + + interface QoSAdmin { + stringSeq method1( in string arg1, inout long arg2 ); + stringSeqSeq method2( in string arg1, inout long arg2, inout long arg3); + string method3(); + }; + """, 59 + ) + test( + """ + /* + * a block comment * + */ + typedef string[10] tenStrings; + typedef + /** ** *** **** * + * a block comment * + */ + sequence<string> /*comment inside an And */ stringSeq; + /* */ /**/ /***/ /****/ + typedef sequence< sequence<string> > stringSeqSeq; + + interface QoSAdmin { + stringSeq method1( in string arg1, inout long arg2 ); + stringSeqSeq method2( in string arg1, inout long arg2, inout long arg3); + string method3(); + }; + """, 59 + ) + test( + r""" + const string test="Test String\n"; + const long a = 0; + const long b = -100; + const float c = 3.14159; + const long d = 0x007f7f7f; + exception TestException + { + string msg; + sequence<string> dataStrings; + }; + + interface TestInterface + { + void method1( in string arg1, inout long arg2 ); + }; + """, 60 + ) + test( + """ + module Test1 + { + exception TestException + { + string msg; + ]; + + interface TestInterface + { + void method1( in string arg1, inout long arg2 ) + raises ( TestException ); + }; + }; + """, 0, 57 + ) + test( + """ + module Test1 + { + exception TestException + { + string msg; + }; + + }; + """, 13 + ) + +class ParseVerilogTest(ParseTestCase): + def runTest(self): + pass + +class RunExamplesTest(ParseTestCase): + def runTest(self): + pass + +class ScanStringTest(ParseTestCase): + def runTest(self): + from pyparsing import Word, Combine, Suppress, CharsNotIn, nums, StringEnd + testdata = """ + <table border="0" cellpadding="3" cellspacing="3" frame="" width="90%"> + <tr align="left" valign="top"> + <td><b>Name</b></td> + <td><b>IP Address</b></td> + <td><b>Location</b></td> + </tr> + <tr align="left" valign="top" bgcolor="#c7efce"> + <td>time-a.nist.gov</td> + <td>129.6.15.28</td> + <td>NIST, Gaithersburg, Maryland</td> + </tr> + <tr align="left" valign="top"> + <td>time-b.nist.gov</td> + <td>129.6.15.29</td> + <td>NIST, Gaithersburg, Maryland</td> + </tr> + <tr align="left" valign="top" bgcolor="#c7efce"> + <td>time-a.timefreq.bldrdoc.gov</td> + <td>132.163.4.101</td> + <td>NIST, Boulder, Colorado</td> + </tr> + <tr align="left" valign="top"> + <td>time-b.timefreq.bldrdoc.gov</td> + <td>132.163.4.102</td> + <td>NIST, Boulder, Colorado</td> + </tr> + <tr align="left" valign="top" bgcolor="#c7efce"> + <td>time-c.timefreq.bldrdoc.gov</td> + <td>132.163.4.103</td> + <td>NIST, Boulder, Colorado</td> + </tr> + </table> + """ + integer = Word(nums) + ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer ) + tdStart = Suppress("<td>") + tdEnd = Suppress("</td>") + timeServerPattern = tdStart + ipAddress.setResultsName("ipAddr") + tdEnd + \ + tdStart + CharsNotIn("<").setResultsName("loc") + tdEnd + servers = \ + [ srvr.ipAddr for srvr,startloc,endloc in timeServerPattern.scanString( testdata ) ] + + print servers + assert servers == ['129.6.15.28', '129.6.15.29', '132.163.4.101', '132.163.4.102', '132.163.4.103'], \ + "failed scanString()" + + # test for stringEnd detection in scanString + foundStringEnds = [ r for r in StringEnd().scanString("xyzzy") ] + print foundStringEnds + assert foundStringEnds, "Failed to find StringEnd in scanString" + +class QuotedStringsTest(ParseTestCase): + def runTest(self): + from pyparsing import sglQuotedString,dblQuotedString,quotedString + testData = \ + """ + 'a valid single quoted string' + 'an invalid single quoted string + because it spans lines' + "a valid double quoted string" + "an invalid double quoted string + because it spans lines" + """ + print testData + sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(testData) ] + print sglStrings + assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==47), \ + "single quoted string failure" + dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(testData) ] + print dblStrings + assert len(dblStrings) == 1 and (dblStrings[0][1]==154 and dblStrings[0][2]==184), \ + "double quoted string failure" + allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(testData) ] + print allStrings + assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==47) and \ + (allStrings[1][1]==154 and allStrings[1][2]==184), \ + "quoted string failure" + + escapedQuoteTest = \ + r""" + 'This string has an escaped (\') quote character' + "This string has an escaped (\") quote character" + """ + sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(escapedQuoteTest) ] + print sglStrings + assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==66), \ + "single quoted string escaped quote failure (%s)" % str(sglStrings[0]) + dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(escapedQuoteTest) ] + print dblStrings + assert len(dblStrings) == 1 and (dblStrings[0][1]==83 and dblStrings[0][2]==132), \ + "double quoted string escaped quote failure (%s)" % str(dblStrings[0]) + allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(escapedQuoteTest) ] + print allStrings + assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==66 and + allStrings[1][1]==83 and allStrings[1][2]==132), \ + "quoted string escaped quote failure (%s)" % ([str(s[0]) for s in allStrings]) + + dblQuoteTest = \ + r""" + 'This string has an doubled ('') quote character' + "This string has an doubled ("") quote character" + """ + sglStrings = [ (t[0],b,e) for (t,b,e) in sglQuotedString.scanString(dblQuoteTest) ] + print sglStrings + assert len(sglStrings) == 1 and (sglStrings[0][1]==17 and sglStrings[0][2]==66), \ + "single quoted string escaped quote failure (%s)" % str(sglStrings[0]) + dblStrings = [ (t[0],b,e) for (t,b,e) in dblQuotedString.scanString(dblQuoteTest) ] + print dblStrings + assert len(dblStrings) == 1 and (dblStrings[0][1]==83 and dblStrings[0][2]==132), \ + "double quoted string escaped quote failure (%s)" % str(dblStrings[0]) + allStrings = [ (t[0],b,e) for (t,b,e) in quotedString.scanString(dblQuoteTest) ] + print allStrings + assert len(allStrings) == 2 and (allStrings[0][1]==17 and allStrings[0][2]==66 and + allStrings[1][1]==83 and allStrings[1][2]==132), \ + "quoted string escaped quote failure (%s)" % ([str(s[0]) for s in allStrings]) + +class CaselessOneOfTest(ParseTestCase): + def runTest(self): + from pyparsing import oneOf,ZeroOrMore + + caseless1 = oneOf("d a b c aA B A C", caseless=True) + caseless1str = str( caseless1 ) + print caseless1str + caseless2 = oneOf("d a b c Aa B A C", caseless=True) + caseless2str = str( caseless2 ) + print caseless2str + assert caseless1str.upper() == caseless2str.upper(), "oneOf not handling caseless option properly" + assert caseless1str != caseless2str, "Caseless option properly sorted" + + res = ZeroOrMore(caseless1).parseString("AAaaAaaA") + print res + assert len(res) == 4, "caseless1 oneOf failed" + assert "".join(res) == "aA"*4,"caseless1 CaselessLiteral return failed" + + res = ZeroOrMore(caseless2).parseString("AAaaAaaA") + print res + assert len(res) == 4, "caseless2 oneOf failed" + assert "".join(res) == "Aa"*4,"caseless1 CaselessLiteral return failed" + + +class AsXMLTest(ParseTestCase): + def runTest(self): + + import pyparsing + # test asXML() + + aaa = pyparsing.Word("a").setResultsName("A") + bbb = pyparsing.Group(pyparsing.Word("b")).setResultsName("B") + ccc = pyparsing.Combine(":" + pyparsing.Word("c")).setResultsName("C") + g1 = "XXX>&<" + pyparsing.ZeroOrMore( aaa | bbb | ccc ) + teststring = "XXX>&< b b a b b a b :c b a" + #~ print teststring + print "test including all items" + xml = g1.parseString(teststring).asXML("TEST",namedItemsOnly=False) + assert xml=="\n".join(["", + "<TEST>", + " <ITEM>XXX>&<</ITEM>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <C>:c</C>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + "</TEST>", + ] ), \ + "failed to generate XML correctly showing all items: \n[" + xml + "]" + print "test filtering unnamed items" + xml = g1.parseString(teststring).asXML("TEST",namedItemsOnly=True) + assert xml=="\n".join(["", + "<TEST>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <C>:c</C>", + " <B>", + " <ITEM>b</ITEM>", + " </B>", + " <A>a</A>", + "</TEST>", + ] ), \ + "failed to generate XML correctly, filtering unnamed items: " + xml + +class AsXMLTest2(ParseTestCase): + def runTest(self): + from pyparsing import Suppress,Optional,CharsNotIn,Combine,ZeroOrMore,Word,Group + + EndOfLine = Word("\n").setParseAction(lambda s,l,t: [' ']) + whiteSpace=Word('\t ') + Mexpr = Suppress(Optional(whiteSpace)) + CharsNotIn('\\"\t \n') + Optional(" ") + \ + Suppress(Optional(whiteSpace)) + reducedString = Combine(Mexpr + ZeroOrMore(EndOfLine + Mexpr)) + + QuotedReducedString = Combine( Suppress(_dblQuote) + ZeroOrMore( reducedString | + _escapedChar ) + \ + Suppress(_dblQuote )).streamline() + + Manifest_string = QuotedReducedString.setResultsName('manifest_string') + + Identifier = Word( alphas, alphanums+ '_$' ).setResultsName("identifier") + Index_string = CharsNotIn('\\";\n') + Index_string.setName('index_string') + Index_term_list = ( + Group(delimitedList(Manifest_string, delim=',')) | \ + Index_string + ).setResultsName('value') + + IndexKey = Identifier.setResultsName('key') + IndexKey.setName('key') + Index_clause = Group(IndexKey + Suppress(':') + Optional(Index_term_list)) + Index_clause.setName('index_clause') + Index_list = Index_clause.setResultsName('index') + Index_list.setName('index_list') + Index_block = Group('indexing' + Group(OneOrMore(Index_list + Suppress(';')))).setResultsName('indexes') + + +class CommentParserTest(ParseTestCase): + def runTest(self): + import pyparsing + print "verify processing of C and HTML comments" + testdata = """ + /* */ + /** **/ + /**/ + /***/ + /****/ + /* /*/ + /** /*/ + /*** /*/ + /* + ablsjdflj + */ + """ + foundLines = [ pyparsing.lineno(s,testdata) + for t,s,e in pyparsing.cStyleComment.scanString(testdata) ] + assert foundLines == range(11)[2:],"only found C comments on lines "+str(foundLines) + testdata = """ + <!-- --> + <!--- ---> + <!----> + <!-----> + <!------> + <!-- /--> + <!--- /--> + <!---- /--> + <!---- /- -> + <!---- / -- > + <!-- + ablsjdflj + --> + """ + foundLines = [ pyparsing.lineno(s,testdata) + for t,s,e in pyparsing.htmlComment.scanString(testdata) ] + assert foundLines == range(11)[2:],"only found HTML comments on lines "+str(foundLines) + + # test C++ single line comments that have line terminated with '\' (should continue comment to following line) + testSource = r""" + // comment1 + // comment2 \ + still comment 2 + // comment 3 + """ + assert len(pyparsing.cppStyleComment.searchString(testSource)[1][0]) == 41, r"failed to match single-line comment with '\' at EOL" + +class ParseExpressionResultsTest(ParseTestCase): + def runTest(self): + from pyparsing import Word,alphas,OneOrMore,Optional,Group + + a = Word("a",alphas).setName("A") + b = Word("b",alphas).setName("B") + c = Word("c",alphas).setName("C") + ab = (a + b).setName("AB") + abc = (ab + c).setName("ABC") + word = Word(alphas).setName("word") + + #~ words = OneOrMore(word).setName("words") + words = Group(OneOrMore(~a + word)).setName("words") + + #~ phrase = words.setResultsName("Head") + \ + #~ ( abc ^ ab ^ a ).setResultsName("ABC") + \ + #~ words.setResultsName("Tail") + #~ phrase = words.setResultsName("Head") + \ + #~ ( abc | ab | a ).setResultsName("ABC") + \ + #~ words.setResultsName("Tail") + phrase = words.setResultsName("Head") + \ + Group( a + Optional(b + Optional(c)) ).setResultsName("ABC") + \ + words.setResultsName("Tail") + + results = phrase.parseString("xavier yeti alpha beta charlie will beaver") + print results,results.Head, results.ABC,results.Tail + for key,ln in [("Head",2), ("ABC",3), ("Tail",2)]: + #~ assert len(results[key]) == ln,"expected %d elements in %s, found %s" % (ln, key, str(results[key].asList())) + assert len(results[key]) == ln,"expected %d elements in %s, found %s" % (ln, key, str(results[key])) + + +class ParseKeywordTest(ParseTestCase): + def runTest(self): + from pyparsing import Literal,Keyword + + kw = Keyword("if") + lit = Literal("if") + + def test(s,litShouldPass,kwShouldPass): + print "Test",s + print "Match Literal", + try: + print lit.parseString(s) + except: + print "failed" + if litShouldPass: assert False, "Literal failed to match %s, should have" % s + else: + if not litShouldPass: assert False, "Literal matched %s, should not have" % s + + print "Match Keyword", + try: + print kw.parseString(s) + except: + print "failed" + if kwShouldPass: assert False, "Keyword failed to match %s, should have" % s + else: + if not kwShouldPass: assert False, "Keyword matched %s, should not have" % s + + test("ifOnlyIfOnly", True, False) + test("if(OnlyIfOnly)", True, True) + test("if (OnlyIf Only)", True, True) + + kw = Keyword("if",caseless=True) + + test("IFOnlyIfOnly", False, False) + test("If(OnlyIfOnly)", False, True) + test("iF (OnlyIf Only)", False, True) + + + +class ParseExpressionResultsAccumulateTest(ParseTestCase): + def runTest(self): + from pyparsing import Word,delimitedList,Combine,alphas,nums + + num=Word(nums).setName("num").setResultsName("base10", listAllMatches=True) + hexnum=Combine("0x"+ Word(nums)).setName("hexnum").setResultsName("hex", listAllMatches=True) + name = Word(alphas).setName("word").setResultsName("word", listAllMatches=True) + list_of_num=delimitedList( hexnum | num | name, "," ) + + tokens = list_of_num.parseString('1, 0x2, 3, 0x4, aaa') + for k,llen,lst in ( ("base10",2,['1','3']), + ("hex",2,['0x2','0x4']), + ("word",1,['aaa']) ): + print k,tokens[k] + assert len(tokens[k]) == llen, "Wrong length for key %s, %s" % (k,str(tokens[k].asList())) + assert lst == tokens[k].asList(), "Incorrect list returned for key %s, %s" % (k,str(tokens[k].asList())) + assert tokens.base10.asList() == ['1','3'], "Incorrect list for attribute base10, %s" % str(tokens.base10.asList()) + assert tokens.hex.asList() == ['0x2','0x4'], "Incorrect list for attribute hex, %s" % str(tokens.hex.asList()) + assert tokens.word.asList() == ['aaa'], "Incorrect list for attribute word, %s" % str(tokens.word.asList()) + + from pyparsing import Literal, Word, nums, Group, Dict, alphas, \ + quotedString, oneOf, delimitedList, removeQuotes, alphanums + + lbrack = Literal("(").suppress() + rbrack = Literal(")").suppress() + integer = Word( nums ).setName("int") + variable = Word( alphas, max=1 ).setName("variable") + relation_body_item = variable | integer | quotedString.copy().setParseAction(removeQuotes) + relation_name = Word( alphas+"_", alphanums+"_" ) + relation_body = lbrack + Group(delimitedList(relation_body_item)) + rbrack + Goal = Dict(Group( relation_name + relation_body )) + Comparison_Predicate = Group(variable + oneOf("< >") + integer).setResultsName("pred",listAllMatches=True) + Query = Goal.setResultsName("head") + ":-" + delimitedList(Goal | Comparison_Predicate) + + test="""Q(x,y,z):-Bloo(x,"Mitsis",y),Foo(y,z,1243),y>28,x<12,x>3""" + + queryRes = Query.parseString(test) + print "pred",queryRes.pred + assert queryRes.pred.asList() == [['y', '>', '28'], ['x', '<', '12'], ['x', '>', '3']], "Incorrect list for attribute pred, %s" % str(queryRes.pred.asList()) + print queryRes.dump() + +class ReStringRangeTest(ParseTestCase): + def runTest(self): + import pyparsing + testCases = ( + (r"[A-Z]"), + (r"[A-A]"), + (r"[A-Za-z]"), + (r"[A-z]"), + (r"[\ -\~]"), + (r"[\0x20-0]"), + (r"[\0x21-\0x7E]"), + (r"[\0xa1-\0xfe]"), + (r"[\040-0]"), + (r"[A-Za-z0-9]"), + (r"[A-Za-z0-9_]"), + (r"[A-Za-z0-9_$]"), + (r"[A-Za-z0-9_$\-]"), + (r"[^0-9\\]"), + (r"[a-zA-Z]"), + (r"[/\^~]"), + (r"[=\+\-!]"), + (r"[A-]"), + (r"[-A]"), + ) + expectedResults = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "A", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz", + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + " !\"#$%&'()*+,-./0", + "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + #~ "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ", + u'\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe', + " !\"#$%&'()*+,-./0", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$-", + "0123456789\\", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + "/^~", + "=+-!", + "A-", + "-A", + ) + for test in zip( testCases, expectedResults ): + t,exp = test + res = pyparsing.srange(t) + #~ print t,"->",res + assert res == exp, "srange error, srange(%s)->'%s', expected '%s'" % (t, res, exp) + +class SkipToParserTests(ParseTestCase): + def runTest(self): + + from pyparsing import Literal, SkipTo, NotAny, cStyleComment + + thingToFind = Literal('working') + testExpr = SkipTo(Literal(';'), True, cStyleComment) + thingToFind + + def tryToParse (someText): + try: + print testExpr.parseString(someText) + except Exception, e: + print "Exception %s while parsing string %s" % (e,repr(someText)) + assert False, "Exception %s while parsing string %s" % (e,repr(someText)) + + # This first test works, as the SkipTo expression is immediately following the ignore expression (cStyleComment) + tryToParse('some text /* comment with ; in */; working') + # This second test fails, as there is text following the ignore expression, and before the SkipTo expression. + tryToParse('some text /* comment with ; in */some other stuff; working') + + +class CustomQuotesTest(ParseTestCase): + def runTest(self): + from pyparsing import QuotedString + + testString = r""" + sdlfjs :sdf\:jls::djf: sl:kfsjf + sdlfjs -sdf\:jls::--djf: sl-kfsjf + sdlfjs -sdf\:::jls::--djf: sl:::-kfsjf + sdlfjs ^sdf\:jls^^--djf^ sl-kfsjf + sdlfjs ^^^==sdf\:j=lz::--djf: sl=^^=kfsjf + sdlfjs ==sdf\:j=ls::--djf: sl==kfsjf^^^ + """ + colonQuotes = QuotedString(':','\\','::') + dashQuotes = QuotedString('-','\\', '--') + hatQuotes = QuotedString('^','\\') + hatQuotes1 = QuotedString('^','\\','^^') + dblEqQuotes = QuotedString('==','\\') + + def test(quoteExpr, expected): + print quoteExpr.pattern + print quoteExpr.searchString(testString) + print quoteExpr.searchString(testString)[0][0] + assert quoteExpr.searchString(testString)[0][0] == expected, \ + "failed to match %s, expected '%s', got '%s'" % \ + (quoteExpr,expected,quoteExpr.searchString(testString)[0]) + + test(colonQuotes, r"sdf:jls:djf") + test(dashQuotes, r"sdf:jls::-djf: sl") + test(hatQuotes, r"sdf:jls") + test(hatQuotes1, r"sdf:jls^--djf") + test(dblEqQuotes, r"sdf:j=ls::--djf: sl") + test( QuotedString(':::'), 'jls::--djf: sl') + test( QuotedString('==',endQuoteChar='--'), 'sdf\:j=lz::') + test( QuotedString('^^^',multiline=True), """==sdf\:j=lz::--djf: sl=^^=kfsjf + sdlfjs ==sdf\:j=ls::--djf: sl==kfsjf""") + try: + bad1 = QuotedString('','\\') + except SyntaxError,se: + pass + else: + assert False,"failed to raise SyntaxError with empty quote string" + +class RepeaterTest(ParseTestCase): + def runTest(self): + from pyparsing import matchPreviousLiteral,matchPreviousExpr, Forward, Literal, Word, alphas, nums + + first = Word("abcdef").setName("word1") + bridge = Word(nums).setName("number") + second = matchPreviousLiteral(first).setName("repeat(word1)") + + seq = first + bridge + second + + tests = [ + ( "abc12abc", True ), + ( "abc12aabc", False ), + ( "abc12cba", True ), + ( "abc12bca", True ), + ] + + for tst,result in tests: + found = False + for tokens,start,end in seq.scanString(tst): + f,b,s = tokens + print f,b,s + found = True + if not found: + print "No literal match in", tst + assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) + print + seq = first + bridge + second + csFirst = seq.setName("word-num-word") + csSecond = matchPreviousLiteral(csFirst) + compoundSeq = csFirst + ":" + csSecond + + first = Word("abcdef").setName("word1") + bridge = Word(nums).setName("number") + second = matchPreviousExpr(first).setName("repeat(word1)") + seq = first + bridge + second + + tests = [ + ( "abc12abc", True ), + ( "abc12cba", False ), + ( "abc12abcdef", False ), + ] + + for tst,result in tests: + found = False + for tokens,start,end in seq.scanString(tst): + print tokens.asList() + found = True + if not found: + print "No expression match in", tst + assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) + + print + seq = first + bridge + second + csFirst = seq.setName("word-num-word") + csSecond = matchPreviousExpr(csFirst) + compoundSeq = csFirst + ":" + csSecond + compoundSeq.streamline() + print compoundSeq + + tests = [ + ( "abc12abc:abc12abc", True ), + ( "abc12cba:abc12abc", False ), + ( "abc12abc:abc12abcdef", False ), + ] + + for tst,result in tests: + found = False + for tokens,start,end in compoundSeq.scanString(tst): + print tokens.asList() + found = True + if not found: + print "No expression match in", tst + assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) + + print + eFirst = Word(nums) + eSecond = matchPreviousExpr(eFirst) + eSeq = eFirst + ":" + eSecond + + tests = [ + ( "1:1A", True ), + ( "1:10", False ), + ] + + for tst,result in tests: + found = False + for tokens,start,end in eSeq.scanString(tst): + #~ f,b,s = tokens + #~ print f,b,s + print tokens.asList() + found = True + if not found: + print "No match in", tst + assert found == result, "Failed repeater for test: %s, matching %s" % (tst, str(seq)) + +class RecursiveCombineTest(ParseTestCase): + def runTest(self): + from pyparsing import Forward,Word,alphas,nums,Optional,Combine + + testInput = "myc(114)r(11)dd" + Stream=Forward() + Stream << Optional(Word(alphas))+Optional("("+Word(nums)+")"+Stream) + expected = Stream.parseString(testInput).asList() + print ["".join(expected)] + + Stream=Forward() + Stream << Combine(Optional(Word(alphas))+Optional("("+Word(nums)+")"+Stream)) + testVal = Stream.parseString(testInput).asList() + print testVal + + assert "".join(testVal) == "".join(expected), "Failed to process Combine with recursive content" + +class OperatorPrecedenceGrammarTest1(ParseTestCase): + def runTest(self): + from pyparsing import Word,nums,alphas,Literal,oneOf,operatorPrecedence,opAssoc + + integer = Word(nums).setParseAction(lambda t:int(t[0])) + variable = Word(alphas,exact=1) + operand = integer | variable + + expop = Literal('^') + signop = oneOf('+ -') + multop = oneOf('* /') + plusop = oneOf('+ -') + factop = Literal('!') + + expr = operatorPrecedence( operand, + [("!", 1, opAssoc.LEFT), + ("^", 2, opAssoc.RIGHT), + (signop, 1, opAssoc.RIGHT), + (multop, 2, opAssoc.LEFT), + (plusop, 2, opAssoc.LEFT),] + ) + + test = ["9 + 2 + 3", + "9 + 2 * 3", + "(9 + 2) * 3", + "(9 + -2) * 3", + "(9 + --2) * 3", + "(9 + -2) * 3^2^2", + "(9! + -2) * 3^2^2", + "M*X + B", + "M*(X + B)", + "1+2*-3^4*5+-+-6", + "3!!"] + expected = """[[9, '+', 2, '+', 3]] + [[9, '+', [2, '*', 3]]] + [[[9, '+', 2], '*', 3]] + [[[9, '+', ['-', 2]], '*', 3]] + [[[9, '+', ['-', ['-', 2]]], '*', 3]] + [[[9, '+', ['-', 2]], '*', [3, '^', [2, '^', 2]]]] + [[[[9, '!'], '+', ['-', 2]], '*', [3, '^', [2, '^', 2]]]] + [[['M', '*', 'X'], '+', 'B']] + [['M', '*', ['X', '+', 'B']]] + [[1, '+', [2, '*', ['-', [3, '^', 4]], '*', 5], '+', ['-', ['+', ['-', 6]]]]] + [[3, '!', '!']]""".split('\n') + expected = map(lambda x:eval(x),expected) + for t,e in zip(test,expected): + print t,"->",e, "got", expr.parseString(t).asList() + assert expr.parseString(t).asList() == e,"mismatched results for operatorPrecedence: got %s, expected %s" % (expr.parseString(t).asList(),e) + +class OperatorPrecedenceGrammarTest2(ParseTestCase): + def runTest(self): + + from pyparsing import operatorPrecedence, Word, alphas, oneOf, opAssoc + + boolVars = { "True":True, "False":False } + class BoolOperand(object): + def __init__(self,t): + self.args = t[0][0::2] + def __str__(self): + sep = " %s " % self.reprsymbol + return "(" + sep.join(map(str,self.args)) + ")" + + class BoolAnd(BoolOperand): + reprsymbol = '&' + def __nonzero__(self): + for a in self.args: + if isinstance(a,basestring): + v = boolVars[a] + else: + v = bool(a) + if not v: + return False + return True + + class BoolOr(BoolOperand): + reprsymbol = '|' + def __nonzero__(self): + for a in self.args: + if isinstance(a,basestring): + v = boolVars[a] + else: + v = bool(a) + if v: + return True + return False + + class BoolNot(BoolOperand): + def __init__(self,t): + self.arg = t[0][1] + def __str__(self): + return "~" + str(self.arg) + def __nonzero__(self): + if isinstance(self.arg,basestring): + v = boolVars[self.arg] + else: + v = bool(self.arg) + return not v + + boolOperand = Word(alphas,max=1) | oneOf("True False") + boolExpr = operatorPrecedence( boolOperand, + [ + ("not", 1, opAssoc.RIGHT, BoolNot), + ("and", 2, opAssoc.LEFT, BoolAnd), + ("or", 2, opAssoc.LEFT, BoolOr), + ]) + test = ["p and not q", + "not not p", + "not(p and q)", + "q or not p and r", + "q or not p or not r", + "q or not (p and r)", + "p or q or r", + "p or q or r and False", + "(p or q or r) and False", + ] + + boolVars["p"] = True + boolVars["q"] = False + boolVars["r"] = True + print "p =", boolVars["p"] + print "q =", boolVars["q"] + print "r =", boolVars["r"] + print + for t in test: + res = boolExpr.parseString(t)[0] + print t,'\n', res, '=', bool(res),'\n' + + +class OperatorPrecedenceGrammarTest3(ParseTestCase): + def runTest(self): + + from pyparsing import operatorPrecedence, Word, alphas, oneOf, opAssoc, nums, Literal + + global count + count = 0 + + def evaluate_int(t): + global count + value = int(t[0]) + print "evaluate_int", value + count += 1 + return value + + integer = Word(nums).setParseAction(evaluate_int) + variable = Word(alphas,exact=1) + operand = integer | variable + + expop = Literal('^') + signop = oneOf('+ -') + multop = oneOf('* /') + plusop = oneOf('+ -') + factop = Literal('!') + + expr = operatorPrecedence( operand, + [ + ("!", 1, opAssoc.LEFT), + ("^", 2, opAssoc.RIGHT), + (signop, 1, opAssoc.RIGHT), + (multop, 2, opAssoc.LEFT), + (plusop, 2, opAssoc.LEFT), + ]) + + test = ["9"] + for t in test: + count = 0 + print "%s => %s" % (t, expr.parseString(t)) + assert count == 1, "count evaluated too many times!" + +class OperatorPrecedenceGrammarTest4(ParseTestCase): + def runTest(self): + + import pyparsing + + word = pyparsing.Word(pyparsing.alphas) + + def supLiteral(s): + """Returns the suppressed literal s""" + return pyparsing.Literal(s).suppress() + + def booleanExpr(atom): + ops = [ + (supLiteral(u"!"), 1, pyparsing.opAssoc.RIGHT, lambda s, l, t: ["!", t[0][0]]), + (pyparsing.oneOf(u"= !="), 2, pyparsing.opAssoc.LEFT, ), + (supLiteral(u"&"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["&", t[0]]), + (supLiteral(u"|"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["|", t[0]])] + return pyparsing.operatorPrecedence(atom, ops) + + f = booleanExpr(word) + pyparsing.StringEnd() + + tests = [ + ("bar = foo", "[['bar', '=', 'foo']]"), + ("bar = foo & baz = fee", "['&', [['bar', '=', 'foo'], ['baz', '=', 'fee']]]"), + ] + for test,expected in tests: + print test + results = f.parseString(test) + print results + assert str(results) == expected, "failed to match expected results, got '%s'" % str(results) + print + + +class ParseResultsPickleTest(ParseTestCase): + def runTest(self): + from pyparsing import makeHTMLTags + import pickle + + body = makeHTMLTags("BODY")[0] + result = body.parseString("<BODY BGCOLOR='#00FFBB' FGCOLOR=black>") + print result.dump() + print + + pickleString = pickle.dumps(result) + newresult = pickle.loads(pickleString) + print newresult.dump() + + assert result.dump() == newresult.dump(), "Error pickling ParseResults object" + +class ParseResultsWithNamedTupleTest(ParseTestCase): + def runTest(self): + + from pyparsing import Literal,replaceWith + + expr = Literal("A") + expr.setParseAction(replaceWith(tuple(["A","Z"]))) + expr = expr.setResultsName("Achar") + + res = expr.parseString("A") + print repr(res) + print res.Achar + assert res.Achar == ("A","Z"), "Failed accessing named results containing a tuple, got " + res.Achar + + +class MiscellaneousParserTests(ParseTestCase): + def runTest(self): + import pyparsing + + # test making oneOf with duplicate symbols + print "verify oneOf handles duplicate symbols" + try: + test1 = pyparsing.oneOf("a b c d a") + except RuntimeError: + assert False,"still have infinite loop in oneOf with duplicate symbols" + + # test MatchFirst bugfix + print "verify MatchFirst iterates properly" + results = pyparsing.quotedString.parseString("'this is a single quoted string'") + assert len(results) > 0, "MatchFirst error - not iterating over all choices" + + # verify streamline of subexpressions + print "verify proper streamline logic" + compound = pyparsing.Literal("A") + "B" + "C" + "D" + assert len(compound.exprs) == 2,"bad test setup" + print compound + compound.streamline() + print compound + assert len(compound.exprs) == 4,"streamline not working" + + # test for Optional with results name and no match + print "verify Optional's do not cause match failure if have results name" + testGrammar = pyparsing.Literal("A") + pyparsing.Optional("B").setResultsName("gotB") + pyparsing.Literal("C") + try: + testGrammar.parseString("ABC") + testGrammar.parseString("AC") + except pyparsing.ParseException, pe: + print pe.pstr,"->",pe + assert False, "error in Optional matching of string %s" % pe.pstr + + # test return of furthest exception + testGrammar = ( pyparsing.Literal("A") | + ( pyparsing.Optional("B") + pyparsing.Literal("C") ) | + pyparsing.Literal("D") ) + try: + testGrammar.parseString("BC") + testGrammar.parseString("BD") + except pyparsing.ParseException, pe: + print pe.pstr,"->",pe + assert pe.pstr == "BD", "wrong test string failed to parse" + assert pe.loc == 1, "error in Optional matching, pe.loc="+str(pe.loc) + + # test validate + print "verify behavior of validate()" + def testValidation( grmr, gnam, isValid ): + try: + grmr.validate() + assert isValid,"validate() accepted invalid grammar " + gnam + except pyparsing.RecursiveGrammarException,e: + assert not isValid, "validate() rejected valid grammar " + gnam + + fwd = pyparsing.Forward() + g1 = pyparsing.OneOrMore( ( pyparsing.Literal("A") + "B" + "C" ) | fwd ) + g2 = pyparsing.ZeroOrMore("C" + g1) + fwd << pyparsing.Group(g2) + testValidation( fwd, "fwd", isValid=True ) + + fwd2 = pyparsing.Forward() + fwd2 << pyparsing.Group("A" | fwd2) + testValidation( fwd2, "fwd2", isValid=False ) + + fwd3 = pyparsing.Forward() + fwd3 << pyparsing.Optional("A") + fwd3 + testValidation( fwd3, "fwd3", isValid=False ) + + # test getName + print "verify behavior of getName()" + aaa = pyparsing.Group(pyparsing.Word("a")).setResultsName("A") + bbb = pyparsing.Group(pyparsing.Word("b")).setResultsName("B") + ccc = pyparsing.Group(":" + pyparsing.Word("c")).setResultsName("C") + g1 = "XXX" + pyparsing.ZeroOrMore( aaa | bbb | ccc ) + teststring = "XXX b b a b b a b :c b a" + names = [] + for t in g1.parseString(teststring): + #~ print t, repr(t) + try: + names.append( t[0].getName() ) + except: + try: + names.append( t.getName() ) + except: + names.append( None ) + print teststring + print names + assert names==[None, 'B', 'B', 'A', 'B', 'B', 'A', 'B', 'C', 'B', 'A'], \ + "failure in getting names for tokens" + + # test ParseResults.get() method + print "verify behavior of ParseResults.get()" + res = g1.parseString(teststring) + print res.get("A","A not found")[0] + print res.get("D","!D") + assert res.get("A","A not found")[0] == "a", "get on existing key failed" + assert res.get("D","!D") == "!D", "get on missing key failed" + + print "verify handling of Optional's beyond the end of string" + testGrammar = "A" + pyparsing.Optional("B") + pyparsing.Optional("C") + pyparsing.Optional("D") + testGrammar.parseString("A") + testGrammar.parseString("AB") + + # test creating Literal with empty string + print 'verify non-fatal usage of Literal("")' + e = pyparsing.Literal("") + try: + e.parseString("SLJFD") + except Exception,e: + assert False, "Failed to handle empty Literal" + + +class ParseHTMLTagsTest(ParseTestCase): + def runTest(self): + import pyparsing + test = """ + <BODY> + <BODY BGCOLOR="#00FFCC"> + <BODY BGCOLOR="#00FFAA"/> + <BODY BGCOLOR='#00FFBB' FGCOLOR=black> + <BODY/> + </BODY> + """ + results = [ + ("startBody", False, "", ""), + ("startBody", False, "#00FFCC", ""), + ("startBody", True, "#00FFAA", ""), + ("startBody", False, "#00FFBB", "black"), + ("startBody", True, "", ""), + ("endBody", False, "", ""), + ] + + bodyStart, bodyEnd = pyparsing.makeHTMLTags("BODY") + resIter = iter(results) + for t,s,e in (bodyStart | bodyEnd).scanString( test ): + print test[s:e], "->", t.asList() + (expectedType, expectedEmpty, expectedBG, expectedFG) = resIter.next() + + tType = t.getName() + #~ print tType,"==",expectedType,"?" + assert tType in "startBody endBody".split(), "parsed token of unknown type '%s'" % tType + assert tType == expectedType, "expected token of type %s, got %s" % (expectedType, tType) + if tType == "startBody": + assert bool(t.empty) == expectedEmpty, "expected %s token, got %s" % ( expectedEmpty and "empty" or "not empty", + t.empty and "empty" or "not empty" ) + assert t.bgcolor == expectedBG, "failed to match BGCOLOR, expected %s, got %s" % ( expectedBG, t.bgcolor ) + assert t.fgcolor == expectedFG, "failed to match FGCOLOR, expected %s, got %s" % ( expectedFG, t.bgcolor ) + elif tType == "endBody": + #~ print "end tag" + pass + else: + print "BAD!!!" + +class UpcaseDowncaseUnicode(ParseTestCase): + def runTest(self): + + import pyparsing as pp + import sys + + a = u'\u00bfC\u00f3mo esta usted?' + ualphas = u"".join( [ unichr(i) for i in range(sys.maxunicode) + if unichr(i).isalpha() ] ) + uword = pp.Word(ualphas).setParseAction(pp.upcaseTokens) + + print uword.searchString(a) + + uword = pp.Word(ualphas).setParseAction(pp.downcaseTokens) + + print uword.searchString(a) + + + #test html data + html = "<TR class=maintxt bgColor=#ffffff> \ + <TD vAlign=top>Производитель, модель</TD> \ + <TD vAlign=top><STRONG>BenQ-Siemens CF61</STRONG></TD> \ + ".decode('utf-8') + + # u'Manufacturer, model + text_manuf = u'Производитель, модель' + manufacturer = pp.Literal(text_manuf) + + td_start, td_end = pp.makeHTMLTags("td") + manuf_body = td_start.suppress() + manufacturer + pp.SkipTo(td_end).setResultsName("cells", True) + td_end.suppress() + + #~ manuf_body.setDebug() + + for tokens in manuf_body.scanString(html): + print tokens + +class ParseUsingRegex(ParseTestCase): + def runTest(self): + + import re + import pyparsing + + signedInt = pyparsing.Regex('[-+][0-9]+') + unsignedInt = pyparsing.Regex('[0-9]+') + simpleString = pyparsing.Regex('("[^\"]*")|(\'[^\']*\')') + namedGrouping = pyparsing.Regex('("(?P<content>[^\"]*)")') + + def testMatch (expression, instring, shouldPass, expectedString=None): + if shouldPass: + try: + result = expression.parseString(instring) + print '%s correctly matched %s' % (repr(expression), repr(instring)) + if expectedString != result[0]: + print '\tbut failed to match the pattern as expected:' + print '\tproduced %s instead of %s' % \ + (repr(result[0]), repr(expectedString)) + return True + except pyparsing.ParseException: + print '%s incorrectly failed to match %s' % \ + (repr(expression), repr(instring)) + else: + try: + result = expression.parseString(instring) + print '%s incorrectly matched %s' % (repr(expression), repr(instring)) + print '\tproduced %s as a result' % repr(result[0]) + except pyparsing.ParseException: + print '%s correctly failed to match %s' % \ + (repr(expression), repr(instring)) + return True + return False + + # These should fail + assert testMatch(signedInt, '1234 foo', False), "Re: (1) passed, expected fail" + assert testMatch(signedInt, ' +foo', False), "Re: (2) passed, expected fail" + assert testMatch(unsignedInt, 'abc', False), "Re: (3) passed, expected fail" + assert testMatch(unsignedInt, '+123 foo', False), "Re: (4) passed, expected fail" + assert testMatch(simpleString, 'foo', False), "Re: (5) passed, expected fail" + assert testMatch(simpleString, '"foo bar\'', False), "Re: (6) passed, expected fail" + assert testMatch(simpleString, '\'foo bar"', False), "Re: (7) passed, expected fail" + + # These should pass + assert testMatch(signedInt, ' +123', True, '+123'), "Re: (8) failed, expected pass" + assert testMatch(signedInt, '+123', True, '+123'), "Re: (9) failed, expected pass" + assert testMatch(signedInt, '+123 foo', True, '+123'), "Re: (10) failed, expected pass" + assert testMatch(signedInt, '-0 foo', True, '-0'), "Re: (11) failed, expected pass" + assert testMatch(unsignedInt, '123 foo', True, '123'), "Re: (12) failed, expected pass" + assert testMatch(unsignedInt, '0 foo', True, '0'), "Re: (13) failed, expected pass" + assert testMatch(simpleString, '"foo"', True, '"foo"'), "Re: (14) failed, expected pass" + assert testMatch(simpleString, "'foo bar' baz", True, "'foo bar'"), "Re: (15) failed, expected pass" + + # This one is going to match correctly, but fail to pull out the correct result + # (for now), as there is no actual handling for extracted named groups + assert testMatch(namedGrouping, '"foo bar" baz', True, '"foo bar"'), "Re: (16) failed, expected pass" + ret = namedGrouping.parseString('"zork" blah') + print ret.asList() + print ret.items() + print ret.content + assert ret.content == 'zork', "named group lookup failed" + assert ret[0] == simpleString.parseString('"zork" blah')[0], "Regex not properly returning ParseResults for named vs. unnamed groups" + + try: + #~ print "lets try an invalid RE" + invRe = pyparsing.Regex('("[^\"]*")|(\'[^\']*\'') + except Exception,e: + print "successfully rejected an invalid RE:", + print e + else: + assert False, "failed to reject invalid RE" + + invRe = pyparsing.Regex('') + +class CountedArrayTest(ParseTestCase): + def runTest(self): + from pyparsing import Word,nums,OneOrMore,countedArray + + testString = "2 5 7 6 0 1 2 3 4 5 0 3 5 4 3" + + integer = Word(nums).setParseAction(lambda t: int(t[0])) + countedField = countedArray(integer) + + r = OneOrMore(countedField).parseString( testString ) + print testString + print r.asList() + + assert r.asList() == [[5,7],[0,1,2,3,4,5],[],[5,4,3]], \ + "Failed matching countedArray, got " + str(r.asList()) + +class CountedArrayTest2(ParseTestCase): + # addresses bug raised by Ralf Vosseler + def runTest(self): + from pyparsing import Word,nums,OneOrMore,countedArray + + testString = "2 5 7 6 0 1 2 3 4 5 0 3 5 4 3" + + integer = Word(nums).setParseAction(lambda t: int(t[0])) + countedField = countedArray(integer) + + dummy = Word("A") + r = OneOrMore(dummy ^ countedField).parseString( testString ) + print testString + print r.asList() + + assert r.asList() == [[5,7],[0,1,2,3,4,5],[],[5,4,3]], \ + "Failed matching countedArray, got " + str(r.asList()) + +class LineAndStringEndTest(ParseTestCase): + def runTest(self): + from pyparsing import OneOrMore,lineEnd,alphanums,Word,stringEnd,delimitedList,SkipTo + + les = OneOrMore(lineEnd) + bnf1 = delimitedList(Word(alphanums).leaveWhitespace(),les) + bnf2 = Word(alphanums) + stringEnd + bnf3 = Word(alphanums) + SkipTo(stringEnd) + tests = [ + ("testA\ntestB\ntestC\n", ['testA', 'testB', 'testC']), + ("testD\ntestE\ntestF", ['testD', 'testE', 'testF']), + ("a", ['a']), + ] + + for t in tests: + res1 = bnf1.parseString(t[0]) + print res1,'=?',t[1] + assert res1.asList() == t[1], "Failed lineEnd/stringEnd test (1): "+repr(t[0])+ " -> "+str(res1.asList()) + res2 = bnf2.searchString(t[0]) + print res2[0].asList(),'=?',t[1][-1:] + assert res2[0].asList() == t[1][-1:], "Failed lineEnd/stringEnd test (2): "+repr(t[0])+ " -> "+str(res2[0].asList()) + res3 = bnf3.parseString(t[0]) + print repr(res3[1]),'=?',repr(t[0][len(res3[0])+1:]) + assert res3[1] == t[0][len(res3[0])+1:], "Failed lineEnd/stringEnd test (3): " +repr(t[0])+ " -> "+str(res3[1].asList()) + +class VariableParseActionArgsTest(ParseTestCase): + def runTest(self): + + pa3 = lambda s,l,t: t + pa2 = lambda l,t: t + pa1 = lambda t: t + def pa0(): return + class Callable3(object): + def __call__(self,s,l,t): + return t + class Callable2(object): + def __call__(self,l,t): + return t + class Callable1(object): + def __call__(self,t): + return t + class Callable0(object): + def __call__(self): + return + class CallableS3(object): + #~ @staticmethod + def __call__(s,l,t): + return t + __call__=staticmethod(__call__) + class CallableS2(object): + #~ @staticmethod + def __call__(l,t): + return t + __call__=staticmethod(__call__) + class CallableS1(object): + #~ @staticmethod + def __call__(t): + return t + __call__=staticmethod(__call__) + class CallableS0(object): + #~ @staticmethod + def __call__(): + return + __call__=staticmethod(__call__) + class CallableC3(object): + #~ @classmethod + def __call__(cls,s,l,t): + return t + __call__=classmethod(__call__) + class CallableC2(object): + #~ @classmethod + def __call__(cls,l,t): + return t + __call__=classmethod(__call__) + class CallableC1(object): + #~ @classmethod + def __call__(cls,t): + return t + __call__=classmethod(__call__) + class CallableC0(object): + #~ @classmethod + def __call__(cls): + return + __call__=classmethod(__call__) + + class parseActionHolder(object): + #~ @staticmethod + def pa3(s,l,t): + return t + pa3=staticmethod(pa3) + #~ @staticmethod + def pa2(l,t): + return t + pa2=staticmethod(pa2) + #~ @staticmethod + def pa1(t): + return t + pa1=staticmethod(pa1) + #~ @staticmethod + def pa0(): + return + pa0=staticmethod(pa0) + + def paArgs(*args): + print args + return args[2] + + def ClassAsPA0(object): + def __init__(self): + pass + def __str__(self): + return "*" + + def ClassAsPA1(object): + def __init__(self,t): + self.t = t + def __str__(self): + return self.t + + def ClassAsPA2(object): + def __init__(self,l,t): + self.t = t + def __str__(self): + return self.t + + def ClassAsPA3(object): + def __init__(self,s,l,t): + self.t = t + def __str__(self): + return self.t + + + from pyparsing import Literal,OneOrMore + + A = Literal("A").setParseAction(pa0) + B = Literal("B").setParseAction(pa1) + C = Literal("C").setParseAction(pa2) + D = Literal("D").setParseAction(pa3) + E = Literal("E").setParseAction(Callable0()) + F = Literal("F").setParseAction(Callable1()) + G = Literal("G").setParseAction(Callable2()) + H = Literal("H").setParseAction(Callable3()) + I = Literal("I").setParseAction(CallableS0()) + J = Literal("J").setParseAction(CallableS1()) + K = Literal("K").setParseAction(CallableS2()) + L = Literal("L").setParseAction(CallableS3()) + M = Literal("M").setParseAction(CallableC0()) + N = Literal("N").setParseAction(CallableC1()) + O = Literal("O").setParseAction(CallableC2()) + P = Literal("P").setParseAction(CallableC3()) + Q = Literal("Q").setParseAction(paArgs) + R = Literal("R").setParseAction(parseActionHolder.pa3) + S = Literal("S").setParseAction(parseActionHolder.pa2) + T = Literal("T").setParseAction(parseActionHolder.pa1) + U = Literal("U").setParseAction(parseActionHolder.pa0) + V = Literal("V") + + gg = OneOrMore( A | B | C | D | E | F | G | H | + I | J | K | L | M | N | O | P | Q | R | S | T | U | V) + testString = "VUTSRQPONMLKJIHGFEDCBA" + res = gg.parseString(testString) + print res.asList() + assert res.asList()==list(testString), "Failed to parse using variable length parse actions" + + A = Literal("A").setParseAction(ClassAsPA0) + B = Literal("B").setParseAction(ClassAsPA1) + C = Literal("C").setParseAction(ClassAsPA2) + D = Literal("D").setParseAction(ClassAsPA3) + + gg = OneOrMore( A | B | C | D | E | F | G | H | + I | J | K | L | M | N | O | P | Q | R | S | T | U | V) + testString = "VUTSRQPONMLKJIHGFEDCBA" + res = gg.parseString(testString) + print map(str,res) + assert map(str,res)==list(testString), "Failed to parse using variable length parse actions using class constructors as parse actions" + +class EnablePackratParsing(ParseTestCase): + def runTest(self): + from pyparsing import ParserElement + ParserElement.enablePackrat() + +class SingleArgExceptionTest(ParseTestCase): + def runTest(self): + from pyparsing import ParseBaseException,ParseFatalException + + msg = "" + raisedMsg = "" + testMessage = "just one arg" + try: + raise ParseFatalException, testMessage + except ParseBaseException,pbe: + print "Received expected exception:", pbe + raisedMsg = pbe.msg + assert raisedMsg == testMessage, "Failed to get correct exception message" + + +class KeepOriginalTextTest(ParseTestCase): + def runTest(self): + from pyparsing import makeHTMLTags, keepOriginalText + + def rfn(t): + return "%s:%d" % (t.src, len("".join(t))) + + makeHTMLStartTag = lambda tag: makeHTMLTags(tag)[0].setParseAction(keepOriginalText) + + # use the lambda, Luke + #~ start, imge = makeHTMLTags('IMG') + start = makeHTMLStartTag('IMG') + + # don't replace our fancy parse action with rfn, + # append rfn to the list of parse actions + #~ start.setParseAction(rfn) + start.addParseAction(rfn) + + #start.setParseAction(lambda s,l,t:t.src) + text = '''_<img src="images/cal.png" + alt="cal image" width="16" height="15">_''' + s = start.transformString(text) + print s + assert s.startswith("_images/cal.png:"), "failed to preserve input s properly" + assert s.endswith("77_"),"failed to return full original text properly" + +class PackratParsingCacheCopyTest(ParseTestCase): + def runTest(self): + from pyparsing import Word,nums,ParserElement,delimitedList,Literal,Optional,alphas,alphanums,ZeroOrMore,empty + + integer = Word(nums).setName("integer") + id = Word(alphas+'_',alphanums+'_') + simpleType = Literal('int'); + arrayType= simpleType+ZeroOrMore('['+delimitedList(integer)+']') + varType = arrayType | simpleType + varDec = varType + delimitedList(id + Optional('='+integer))+';' + + codeBlock = Literal('{}') + + funcDef = Optional(varType | 'void')+id+'('+(delimitedList(varType+id)|'void'|empty)+')'+codeBlock + + program = varDec | funcDef + input = 'int f(){}' + results = program.parseString(input) + print "Parsed '%s' as %s" % (input, results.asList()) + assert results.asList() == ['int', 'f', '(', ')', '{}'], "Error in packrat parsing" + +class ParseResultsDelTest(ParseTestCase): + def runTest(self): + from pyparsing import OneOrMore, Word, alphas, nums + + grammar = OneOrMore(Word(nums))("ints") + OneOrMore(Word(alphas))("words") + res = grammar.parseString("123 456 ABC DEF") + print res.dump() + origInts = res.ints.asList() + origWords = res.words.asList() + del res[1] + del res["words"] + print res.dump() + assert res[1]=='ABC',"failed to delete 0'th element correctly" + assert res.ints.asList()==origInts, "updated named attributes, should have updated list only" + assert res.words=="", "failed to update named attribute correctly" + assert res[-1]=='DEF', "updated list, should have updated named attributes only" + +class WithAttributeParseActionTest(ParseTestCase): + def runTest(self): + """ + This unit test checks withAttribute in these ways: + + * Argument forms as keywords and tuples + * Selecting matching tags by attribute + * Case-insensitive attribute matching + * Correctly matching tags having the attribute, and rejecting tags not having the attribute + + (Unit test written by voigts as part of the Google Highly Open Participation Contest) + """ + + from pyparsing import makeHTMLTags, Word, withAttribute, nums + + data = """ + <a>1</a> + <a b="x">2</a> + <a B="x">3</a> + <a b="X">4</a> + <a b="y">5</a> + """ + tagStart, tagEnd = makeHTMLTags("a") + + expr = tagStart + Word(nums).setResultsName("value") + tagEnd + + expected = [['a', ['b', 'x'], False, '2', '</a>'], ['a', ['b', 'x'], False, '3', '</a>']] + + for attrib in [ + withAttribute(b="x"), + withAttribute(B="x"), + withAttribute(("b","x")), + withAttribute(("B","x")) + ]: + + tagStart.setParseAction(attrib) + result = expr.searchString(data) + + print result.dump() + assert result.asList() == expected, "Failed test, expected %s, got %s" % (expected, result.asList()) + +class NestedExpressionsTest(ParseTestCase): + def runTest(self): + """ + This unit test checks nestedExpr in these ways: + - use of default arguments + - use of non-default arguments (such as a pyparsing-defined comment + expression in place of quotedString) + - use of a custom content expression + - use of a pyparsing expression for opener and closer is *OPTIONAL* + - use of input data containing nesting delimiters + - correct grouping of parsed tokens according to nesting of opening + and closing delimiters in the input string + + (Unit test written by christoph... as part of the Google Highly Open Participation Contest) + """ + from pyparsing import nestedExpr, Literal, Regex, restOfLine, quotedString + + #All defaults. Straight out of the example script. Also, qualifies for + #the bonus: note the fact that (Z | (E^F) & D) is not parsed :-). + # Tests for bug fixed in 1.4.10 + print "Test defaults:" + teststring = "(( ax + by)*C) (Z | (E^F) & D)" + + expr = nestedExpr() + + expected = [[['ax', '+', 'by'], '*C']] + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected, "Defaults didn't work. That's a bad sign. Expected: %s, got: %s" % (expected, result) + + #Going through non-defaults, one by one; trying to think of anything + #odd that might not be properly handled. + + #Change opener + print "\nNon-default opener" + opener = "[" + teststring = test_string = "[[ ax + by)*C)" + expected = [[['ax', '+', 'by'], '*C']] + expr = nestedExpr("[") + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected, "Non-default opener didn't work. Expected: %s, got: %s" % (expected, result) + + #Change closer + print "\nNon-default closer" + + teststring = test_string = "(( ax + by]*C]" + expected = [[['ax', '+', 'by'], '*C']] + expr = nestedExpr(closer="]") + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected, "Non-default closer didn't work. Expected: %s, got: %s" % (expected, result) + + # #Multicharacter opener, closer + # opener = "bar" + # closer = "baz" + print "\nLiteral expressions for opener and closer" + + opener,closer = map(Literal, "bar baz".split()) + expr = nestedExpr(opener, closer, + content=Regex(r"([^b ]|b(?!a)|ba(?![rz]))+")) + + teststring = "barbar ax + bybaz*Cbaz" + expected = [[['ax', '+', 'by'], '*C']] + # expr = nestedExpr(opener, closer) + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected, "Multicharacter opener and closer didn't work. Expected: %s, got: %s" % (expected, result) + + #Lisp-ish comments + print "\nUse ignore expression (1)" + comment = Regex(r";;.*") + teststring = \ + """ + (let ((greeting "Hello, world!")) ;;(foo bar + (display greeting)) + """ + + expected = [['let', [['greeting', '"Hello,', 'world!"']], ';;(foo bar',\ + ['display', 'greeting']]] + expr = nestedExpr(ignoreExpr=comment) + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected , "Lisp-ish comments (\";; <...> $\") didn't work. Expected: %s, got: %s" % (expected, result) + + + #Lisp-ish comments, using a standard bit of pyparsing, and an Or. + print "\nUse ignore expression (2)" + comment = ';;' + restOfLine + + teststring = \ + """ + (let ((greeting "Hello, )world!")) ;;(foo bar + (display greeting)) + """ + + expected = [['let', [['greeting', '"Hello, )world!"']], ';;', '(foo bar', + ['display', 'greeting']]] + expr = nestedExpr(ignoreExpr=(comment ^ quotedString)) + result = expr.parseString(teststring) + print result.dump() + assert result.asList() == expected , "Lisp-ish comments (\";; <...> $\") and quoted strings didn't work. Expected: %s, got: %s" % (expected, result) + +class ParseAllTest(ParseTestCase): + def runTest(self): + from pyparsing import Word + + testExpr = Word("A") + + tests = [ + ("AAAAA", False, True), + ("AAAAA", True, True), + ("AAABB", False, True), + ("AAABB", True, False), + ] + for s,parseAllFlag,shouldSucceed in tests: + try: + print "'%s' parseAll=%s (shouldSuceed=%s)" % (s, parseAllFlag, shouldSucceed) + testExpr.parseString(s,parseAllFlag) + assert shouldSucceed, "successfully parsed when should have failed" + except ParseException, pe: + assert not shouldSucceed, "failed to parse when should have succeeded" + +class WordBoundaryExpressionsTest(ParseTestCase): + def runTest(self): + from pyparsing import WordEnd, WordStart, oneOf + + ws = WordStart() + we = WordEnd() + vowel = oneOf(list("AEIOUY")) + consonant = oneOf(list("BCDFGHJKLMNPQRSTVWXZ")) + + leadingVowel = ws + vowel + trailingVowel = vowel + we + leadingConsonant = ws + consonant + trailingConsonant = consonant + we + internalVowel = ~ws + vowel + ~we + + bnf = leadingVowel | trailingVowel + + tests = """\ + ABC DEF GHI + JKL MNO PQR + STU VWX YZ """.splitlines() + tests.append( "\n".join(tests) ) + + expectedResult = [ + [['D', 'G'], ['A'], ['C', 'F'], ['I'], ['E'], ['A', 'I']], + [['J', 'M', 'P'], [], ['L', 'R'], ['O'], [], ['O']], + [['S', 'V'], ['Y'], ['X', 'Z'], ['U'], [], ['U', 'Y']], + [['D', 'G', 'J', 'M', 'P', 'S', 'V'], + ['A', 'Y'], + ['C', 'F', 'L', 'R', 'X', 'Z'], + ['I', 'O', 'U'], + ['E'], + ['A', 'I', 'O', 'U', 'Y']], + ] + + for t,expected in zip(tests, expectedResult): + print t + results = map(lambda e: flatten(e.searchString(t).asList()), + [ + leadingConsonant, + leadingVowel, + trailingConsonant, + trailingVowel, + internalVowel, + bnf, + ] + ) + print results + assert results==expected,"Failed WordBoundaryTest, expected %s, got %s" % (expected,results) + print + + +def makeTestSuite(): + suite = TestSuite() + suite.addTest( PyparsingTestInit() ) + suite.addTest( ParseIDLTest() ) + suite.addTest( ParseASMLTest() ) + suite.addTest( ParseFourFnTest() ) + suite.addTest( ParseSQLTest() ) + suite.addTest( ParseConfigFileTest() ) + suite.addTest( ParseJSONDataTest() ) + suite.addTest( ParseCommaSeparatedValuesTest() ) + suite.addTest( ParseEBNFTest() ) + suite.addTest( ScanStringTest() ) + suite.addTest( QuotedStringsTest() ) + suite.addTest( CustomQuotesTest() ) + suite.addTest( CaselessOneOfTest() ) + suite.addTest( AsXMLTest() ) + suite.addTest( CommentParserTest() ) + suite.addTest( ParseExpressionResultsTest() ) + suite.addTest( ParseExpressionResultsAccumulateTest() ) + suite.addTest( ReStringRangeTest() ) + suite.addTest( ParseKeywordTest() ) + suite.addTest( ParseHTMLTagsTest() ) + suite.addTest( ParseUsingRegex() ) + suite.addTest( SkipToParserTests() ) + suite.addTest( CountedArrayTest() ) + suite.addTest( CountedArrayTest2() ) + suite.addTest( LineAndStringEndTest() ) + suite.addTest( VariableParseActionArgsTest() ) + suite.addTest( RepeaterTest() ) + suite.addTest( RecursiveCombineTest() ) + suite.addTest( OperatorPrecedenceGrammarTest1() ) + suite.addTest( OperatorPrecedenceGrammarTest2() ) + suite.addTest( OperatorPrecedenceGrammarTest3() ) + suite.addTest( OperatorPrecedenceGrammarTest4() ) + suite.addTest( ParseResultsPickleTest() ) + suite.addTest( ParseResultsWithNamedTupleTest() ) + suite.addTest( ParseResultsDelTest() ) + suite.addTest( SingleArgExceptionTest() ) + suite.addTest( UpcaseDowncaseUnicode() ) + suite.addTest( KeepOriginalTextTest() ) + suite.addTest( PackratParsingCacheCopyTest() ) + suite.addTest( WithAttributeParseActionTest() ) + suite.addTest( NestedExpressionsTest() ) + suite.addTest( WordBoundaryExpressionsTest() ) + suite.addTest( ParseAllTest() ) + suite.addTest( MiscellaneousParserTests() ) + + if TEST_USING_PACKRAT: + # retest using packrat parsing (disable those tests that aren't compatible) + suite.addTest( EnablePackratParsing() ) + + unpackrattables = [ EnablePackratParsing, RepeaterTest, ] + + # add tests to test suite a second time, to run with packrat parsing + # (leaving out those that we know wont work with packrat) + packratTests = [t.__class__() for t in suite._tests + if t.__class__ not in unpackrattables] + suite.addTests( packratTests ) + + return suite + + +console = False +#~ console = True +if console: + #~ # console mode + testRunner = TextTestRunner() + testRunner.run( makeTestSuite() ) +else: + # HTML mode + outfile = "testResults.html" + outstream = file(outfile,"w") + testRunner = HTMLTestRunner.HTMLTestRunner( stream=outstream ) + testRunner.run( makeTestSuite() ) + outstream.close() + + import os + os.system(outfile)