summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorptmcg <ptmcg@9bf210a0-9d2d-494c-87cf-cfb32e7dff7b>2016-08-09 00:23:49 +0000
committerptmcg <ptmcg@9bf210a0-9d2d-494c-87cf-cfb32e7dff7b>2016-08-09 00:23:49 +0000
commitb2c3ade75384efe76b8774b607e17fe98fab92ef (patch)
treeb162262f3f0a4bc976d45bed08ccfc6cc9a2eb23
parent0be19d2d8545f1ac4b93ffd0d10524613837ba39 (diff)
downloadpyparsing-b2c3ade75384efe76b8774b607e17fe98fab92ef.tar.gz
TagTag for 2.1.6 release
git-svn-id: svn://svn.code.sf.net/p/pyparsing/code/tags/pyparsing_2.1.6@402 9bf210a0-9d2d-494c-87cf-cfb32e7dff7b
-rw-r--r--trunk/src/CHANGES2139
-rw-r--r--trunk/src/HowToUsePyparsing.html1289
-rw-r--r--trunk/src/HowToUsePyparsing.txt993
-rw-r--r--trunk/src/LICENSE18
-rw-r--r--trunk/src/MANIFEST.in_bdist7
-rw-r--r--trunk/src/MANIFEST.in_src7
-rw-r--r--trunk/src/README80
-rw-r--r--trunk/src/examples/0README.html309
-rw-r--r--trunk/src/examples/AcManForm.dfm885
-rw-r--r--trunk/src/examples/LAparser.py417
-rw-r--r--trunk/src/examples/Setup.ini73
-rw-r--r--trunk/src/examples/SimpleCalc.py118
-rw-r--r--trunk/src/examples/SingleForm.dfm751
-rw-r--r--trunk/src/examples/TAP.py217
-rw-r--r--trunk/src/examples/__init__.py0
-rw-r--r--trunk/src/examples/adventureEngine.py648
-rw-r--r--trunk/src/examples/antlr_grammar.py218
-rw-r--r--trunk/src/examples/antlr_grammar_tests.py85
-rw-r--r--trunk/src/examples/apicheck.py55
-rw-r--r--trunk/src/examples/btpyparse.py128
-rw-r--r--trunk/src/examples/builtin_parse_action_demo.py29
-rw-r--r--trunk/src/examples/cLibHeader.py25
-rw-r--r--trunk/src/examples/chemicalFormulas.py67
-rw-r--r--trunk/src/examples/commasep.py23
-rw-r--r--trunk/src/examples/configParse.py72
-rw-r--r--trunk/src/examples/cpp_enum_parser.py52
-rw-r--r--trunk/src/examples/datetimeParseActions.py43
-rw-r--r--trunk/src/examples/deltaTime.py208
-rw-r--r--trunk/src/examples/dfmparse.py176
-rw-r--r--trunk/src/examples/dhcpd_leases_parser.py87
-rw-r--r--trunk/src/examples/dictExample.py41
-rw-r--r--trunk/src/examples/dictExample2.py59
-rw-r--r--trunk/src/examples/ebnf.py149
-rw-r--r--trunk/src/examples/ebnftest.py66
-rw-r--r--trunk/src/examples/eval_arith.py227
-rw-r--r--trunk/src/examples/excelExpr.py68
-rw-r--r--trunk/src/examples/fourFn.py192
-rw-r--r--trunk/src/examples/gen_ctypes.py167
-rw-r--r--trunk/src/examples/getNTPservers.py30
-rw-r--r--trunk/src/examples/getNTPserversNew.py35
-rw-r--r--trunk/src/examples/greeting.py17
-rw-r--r--trunk/src/examples/greetingInGreek.py18
-rw-r--r--trunk/src/examples/greetingInKorean.py20
-rw-r--r--trunk/src/examples/groupUsingListAllMatches.py16
-rw-r--r--trunk/src/examples/holaMundo.py30
-rw-r--r--trunk/src/examples/htmlStripper.py39
-rw-r--r--trunk/src/examples/httpServerLogParser.py71
-rw-r--r--trunk/src/examples/idlParse.py222
-rw-r--r--trunk/src/examples/indentedGrammarExample.py54
-rw-r--r--trunk/src/examples/invRegex.py257
-rw-r--r--trunk/src/examples/javascript_grammar.g894
-rw-r--r--trunk/src/examples/jsonParser.py111
-rw-r--r--trunk/src/examples/linenoExample.py49
-rw-r--r--trunk/src/examples/list1.py50
-rw-r--r--trunk/src/examples/listAllMatches.py52
-rw-r--r--trunk/src/examples/lucene_grammar.py330
-rw-r--r--trunk/src/examples/macroExpander.py60
-rw-r--r--trunk/src/examples/makeHTMLTagExample.py21
-rw-r--r--trunk/src/examples/matchPreviousDemo.py33
-rw-r--r--trunk/src/examples/mozilla.ics50
-rw-r--r--trunk/src/examples/mozillaCalendarParser.py81
-rw-r--r--trunk/src/examples/nested.py30
-rw-r--r--trunk/src/examples/numerics.py62
-rw-r--r--trunk/src/examples/oc.py193
-rw-r--r--trunk/src/examples/parseListString.py82
-rw-r--r--trunk/src/examples/parsePythonValue.py70
-rw-r--r--trunk/src/examples/parseResultsSumExample.py26
-rw-r--r--trunk/src/examples/parseTabularData.py50
-rw-r--r--trunk/src/examples/partial_gene_match.py88
-rw-r--r--trunk/src/examples/pgn.py94
-rw-r--r--trunk/src/examples/position.py55
-rw-r--r--trunk/src/examples/protobuf_parser.py100
-rw-r--r--trunk/src/examples/pymicko.py1387
-rw-r--r--trunk/src/examples/pythonGrammarParser.py220
-rw-r--r--trunk/src/examples/rangeCheck.py62
-rw-r--r--trunk/src/examples/readJson.py1917
-rw-r--r--trunk/src/examples/removeLineBreaks.py45
-rw-r--r--trunk/src/examples/romanNumerals.py74
-rw-r--r--trunk/src/examples/scanExamples.py75
-rw-r--r--trunk/src/examples/scanYahoo.py14
-rw-r--r--trunk/src/examples/searchParserAppDemo.py34
-rw-r--r--trunk/src/examples/searchparser.py292
-rw-r--r--trunk/src/examples/select_parser.py126
-rw-r--r--trunk/src/examples/sexpParser.py167
-rw-r--r--trunk/src/examples/shapes.py64
-rw-r--r--trunk/src/examples/simpleArith.py67
-rw-r--r--trunk/src/examples/simpleBool.py102
-rw-r--r--trunk/src/examples/simpleSQL.py72
-rw-r--r--trunk/src/examples/simpleWiki.py32
-rw-r--r--trunk/src/examples/snmp_api.h795
-rw-r--r--trunk/src/examples/sparser.py365
-rw-r--r--trunk/src/examples/sql2dot.py96
-rw-r--r--trunk/src/examples/stackish.py81
-rw-r--r--trunk/src/examples/stateMachine2.py258
-rw-r--r--trunk/src/examples/test_bibparse.py195
-rw-r--r--trunk/src/examples/urlExtractor.py33
-rw-r--r--trunk/src/examples/urlExtractorNew.py35
-rw-r--r--trunk/src/examples/verilogParse.py720
-rw-r--r--trunk/src/examples/withAttribute.py24
-rw-r--r--trunk/src/examples/wordsToNum.py98
-rw-r--r--trunk/src/genEpydoc.bat2
-rw-r--r--trunk/src/makeRelease.bat24
-rw-r--r--trunk/src/pyparsing.py5528
-rw-r--r--trunk/src/pyparsingClassDiagram.JPGbin0 -> 236402 bytes
-rw-r--r--trunk/src/pyparsingClassDiagram.PNGbin0 -> 141354 bytes
-rw-r--r--trunk/src/setup.cfg5
-rw-r--r--trunk/src/setup.py37
-rw-r--r--trunk/src/test/__init__.py0
-rw-r--r--trunk/src/test/jsonParserTests.py360
-rw-r--r--trunk/src/test/karthik.ini14
-rw-r--r--trunk/src/test/parsefiletest_input_file.txt1
-rw-r--r--trunk/src/unitTests.py3169
-rw-r--r--trunk/src/update_pyparsing_timestamp.py17
113 files changed, 30185 insertions, 0 deletions
diff --git a/trunk/src/CHANGES b/trunk/src/CHANGES
new file mode 100644
index 0000000..0e48b21
--- /dev/null
+++ b/trunk/src/CHANGES
@@ -0,0 +1,2139 @@
+==========
+Change Log
+==========
+
+Version 2.1.6 -
+------------------------------
+- *Major packrat upgrade*, inspired by patch provided by Tal Einat -
+ many, many, thanks to Tal for working on this! Tal's tests show
+ faster parsing performance (2X in some tests), *and* memory reduction
+ from 3GB down to ~100MB! Requires no changes to existing code using
+ packratting. (Uses OrderedDict, available in Python 2.7 and later.
+ For Python 2.6 users, will attempt to import from ordereddict
+ backport. If not present, will implement pure-Python Fifo dict.)
+
+- Minor API change - to better distinguish between the flexible
+ numeric types defined in pyparsing_common, I've changed "numeric"
+ (which parsed numbers of different types and returned int for ints,
+ float for floats, etc.) and "number" (which parsed numbers of int
+ or float type, and returned all floats) to "number" and "fnumber"
+ respectively. I hope the "f" prefix of "fnumber" will be a better
+ indicator of its internal conversion of parsed values to floats,
+ while the generic "number" is similar to the flexible number syntax
+ in other languages. Also fixed a bug in pyparsing_common.numeric
+ (now renamed to pyparsing_common.number), integers were parsed and
+ returned as floats instead of being retained as ints.
+
+- Fixed bug in upcaseTokens and downcaseTokens introduced in 2.1.5,
+ when the parse action was used in conjunction with results names.
+ Reported by Steven Arcangeli from the dql project, thanks for your
+ patience, Steven!
+
+- Major change to docs! After seeing some comments on reddit about
+ general issue with docs of Python modules, and thinking that I'm a
+ little overdue in doing some doc tuneup on pyparsing, I decided to
+ following the suggestions of the redditor and add more inline examples
+ to the pyparsing reference documentation. I hope this addition
+ will clarify some of the more common questions people have, especially
+ when first starting with pyparsing/Python.
+
+- Deprecated ParseResults.asXML. I've never been too happy with this
+ method, and it usually forces some unnatural code in the parsers in
+ order to get decent tag names. The amount of guesswork that asXML
+ has to do to try to match names with values should have been a red
+ flag from day one. If you are using asXML, you will need to implement
+ your own ParseResults->XML serialization. Or consider migrating to
+ a more current format such as JSON (which is very easy to do:
+ results_as_json = json.dumps(parse_result.asDict()) Hopefully, when
+ I remove this code in a future version, I'll also be able to simplify
+ some of the craziness in ParseResults, which IIRC was only there to try
+ to make asXML work.
+
+- Updated traceParseAction parse action decorator to show the repr
+ of the input and output tokens, instead of the str format, since
+ str has been simplified to just show the token list content.
+
+ (The change to ParseResults.__str__ occurred in pyparsing 2.0.4, but
+ it seems that didn't make it into the release notes - sorry! Too
+ many users, especially beginners, were confused by the
+ "([token_list], {names_dict})" str format for ParseResults, thinking
+ they were getting a tuple containing a list and a dict. The full form
+ can be seen if using repr().)
+
+ For tracing tokens in and out of parse actions, the more complete
+ repr form provides important information when debugging parse actions.
+
+
+Verison 2.1.5 - June, 2016
+------------------------------
+- Added ParserElement.split() generator method, similar to re.split().
+ Includes optional arguments maxsplit (to limit the number of splits),
+ and includeSeparators (to include the separating matched text in the
+ returned output, default=False).
+
+- Added a new parse action construction helper tokenMap, which will
+ apply a function and optional arguments to each element in a
+ ParseResults. So this parse action:
+
+ def lowercase_all(tokens):
+ return [str(t).lower() for t in tokens]
+ OneOrMore(Word(alphas)).setParseAction(lowercase_all)
+
+ can now be written:
+
+ OneOrMore(Word(alphas)).setParseAction(tokenMap(str.lower))
+
+ Also simplifies writing conversion parse actions like:
+
+ integer = Word(nums).setParseAction(lambda t: int(t[0]))
+
+ to just:
+
+ integer = Word(nums).setParseAction(tokenMap(int))
+
+ If additional arguments are necessary, they can be included in the
+ call to tokenMap, as in:
+
+ hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))
+
+- Added more expressions to pyparsing_common:
+ . IPv4 and IPv6 addresses (including long, short, and mixed forms
+ of IPv6)
+ . MAC address
+ . ISO8601 date and date time strings (with named fields for year, month, etc.)
+ . UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
+ . hex integer (returned as int)
+ . fraction (integer '/' integer, returned as float)
+ . mixed integer (integer '-' fraction, or just fraction, returned as float)
+ . stripHTMLTags (parse action to remove tags from HTML source)
+ . parse action helpers convertToDate and convertToDatetime to do custom parse
+ time conversions of parsed ISO8601 strings
+
+- runTests now returns a two-tuple: success if all tests succeed,
+ and an output list of each test and its output lines.
+
+- Added failureTests argument (default=False) to runTests, so that
+ tests can be run that are expected failures, and runTests' success
+ value will return True only if all tests *fail* as expected. Also,
+ parseAll now defaults to True.
+
+- New example numerics.py, shows samples of parsing integer and real
+ numbers using locale-dependent formats:
+
+ 4.294.967.295,000
+ 4 294 967 295,000
+ 4,294,967,295.000
+
+
+Version 2.1.4 - May, 2016
+------------------------------
+- Split out the '==' behavior in ParserElement, now implemented
+ as the ParserElement.matches() method. Using '==' for string test
+ purposes will be removed in a future release.
+
+- Expanded capabilities of runTests(). Will now accept embedded
+ comments (default is Python style, leading '#' character, but
+ customizable). Comments will be emitted along with the tests and
+ test output. Useful during test development, to create a test string
+ consisting only of test case description comments separated by
+ blank lines, and then fill in the test cases. Will also highlight
+ ParseFatalExceptions with "(FATAL)".
+
+- Added a 'pyparsing_common' class containing common/helpful little
+ expressions such as integer, float, identifier, etc. I used this
+ class as a sort of embedded namespace, to contain these helpers
+ without further adding to pyparsing's namespace bloat.
+
+- Minor enhancement to traceParseAction decorator, to retain the
+ parse action's name for the trace output.
+
+- Added optional 'fatal' keyword arg to addCondition, to indicate that
+ a condition failure should halt parsing immediately.
+
+
+Version 2.1.3 - May, 2016
+------------------------------
+- _trim_arity fix in 2.1.2 was very version-dependent on Py 3.5.0.
+ Now works for Python 2.x, 3.3, 3.4, 3.5.0, and 3.5.1 (and hopefully
+ beyond).
+
+
+Version 2.1.2 - May, 2016
+------------------------------
+- Fixed bug in _trim_arity when pyparsing code is included in a
+ PyInstaller, reported by maluwa.
+
+- Fixed catastrophic regex backtracking in implementation of the
+ quoted string expressions (dblQuotedString, sglQuotedString, and
+ quotedString). Reported on the pyparsing wiki by webpentest,
+ good catch! (Also tuned up some other expressions susceptible to the
+ same backtracking problem, such as cStyleComment, cppStyleComment,
+ etc.)
+
+
+Version 2.1.1 - March, 2016
+---------------------------
+- Added support for assigning to ParseResults using slices.
+
+- Fixed bug in ParseResults.toDict(), in which dict values were always
+ converted to dicts, even if they were just unkeyed lists of tokens.
+ Reported on SO by Gerald Thibault, thanks Gerald!
+
+- Fixed bug in SkipTo when using failOn, reported by robyschek, thanks!
+
+- Fixed bug in Each introduced in 2.1.0, reported by AND patch and
+ unit test submitted by robyschek, well done!
+
+- Removed use of functools.partial in replaceWith, as this creates
+ an ambiguous signature for the generated parse action, which fails in
+ PyPy. Reported by Evan Hubinger, thanks Evan!
+
+- Added default behavior to QuotedString to convert embedded '\t', '\n',
+ etc. characters to their whitespace counterparts. Found during Q&A
+ exchange on SO with Maxim.
+
+
+Version 2.1.0 - February, 2016
+------------------------------
+- Modified the internal _trim_arity method to distinguish between
+ TypeError's raised while trying to determine parse action arity and
+ those raised within the parse action itself. This will clear up those
+ confusing "<lambda>() takes exactly 1 argument (0 given)" error
+ messages when there is an actual TypeError in the body of the parse
+ action. Thanks to all who have raised this issue in the past, and
+ most recently to Michael Cohen, who sent in a proposed patch, and got
+ me to finally tackle this problem.
+
+- Added compatibility for pickle protocols 2-4 when pickling ParseResults.
+ In Python 2.x, protocol 0 was the default, and protocol 2 did not work.
+ In Python 3.x, protocol 3 is the default, so explicitly naming
+ protocol 0 or 1 was required to pickle ParseResults. With this release,
+ all protocols 0-4 are supported. Thanks for reporting this on StackOverflow,
+ Arne Wolframm, and for providing a nice simple test case!
+
+- Added optional 'stopOn' argument to ZeroOrMore and OneOrMore, to
+ simplify breaking on stop tokens that would match the repetition
+ expression.
+
+ It is a common problem to fail to look ahead when matching repetitive
+ tokens if the sentinel at the end also matches the repetition
+ expression, as when parsing "BEGIN aaa bbb ccc END" with:
+
+ "BEGIN" + OneOrMore(Word(alphas)) + "END"
+
+ Since "END" matches the repetition expression "Word(alphas)", it will
+ never get parsed as the terminating sentinel. Up until now, this has
+ to be resolved by the user inserting their own negative lookahead:
+
+ "BEGIN" + OneOrMore(~Literal("END") + Word(alphas)) + "END"
+
+ Using stopOn, they can more easily write:
+
+ "BEGIN" + OneOrMore(Word(alphas), stopOn="END") + "END"
+
+ The stopOn argument can be a literal string or a pyparsing expression.
+ Inspired by a question by Lamakaha on StackOverflow (and many previous
+ questions with the same negative-lookahead resolution).
+
+- Added expression names for many internal and builtin expressions, to
+ reduce name and error message overhead during parsing.
+
+- Converted helper lambdas to functions to refactor and add docstring
+ support.
+
+- Fixed ParseResults.asDict() to correctly convert nested ParseResults
+ values to dicts.
+
+- Cleaned up some examples, fixed typo in fourFn.py identified by
+ aristotle2600 on reddit.
+
+- Removed keepOriginalText helper method, which was deprecated ages ago.
+ Superceded by originalTextFor.
+
+- Same for the Upcase class, which was long ago deprecated and replaced
+ with the upcaseTokens method.
+
+
+
+Version 2.0.7 - December, 2015
+------------------------------
+- Simplified string representation of Forward class, to avoid memory
+ and performance errors while building ParseException messages. Thanks,
+ Will McGugan, Andrea Censi, and Martijn Vermaat for the bug reports and
+ test code.
+
+- Cleaned up additional issues from enhancing the error messages for
+ Or and MatchFirst, handling Unicode values in expressions. Fixes Unicode
+ encoding issues in Python 2, thanks to Evan Hubinger for the bug report.
+
+- Fixed implementation of dir() for ParseResults - was leaving out all the
+ defined methods and just adding the custom results names.
+
+- Fixed bug in ignore() that was introduced in pyparsing 1.5.3, that would
+ not accept a string literal as the ignore expression.
+
+- Added new example parseTabularData.py to illustrate parsing of data
+ formatted in columns, with detection of empty cells.
+
+- Updated a number of examples to more current Python and pyparsing
+ forms.
+
+
+Version 2.0.6 - November, 2015
+------------------------------
+- Fixed a bug in Each when multiple Optional elements are present.
+ Thanks for reporting this, whereswalden on SO.
+
+- Fixed another bug in Each, when Optional elements have results names
+ or parse actions, reported by Max Rothman - thank you, Max!
+
+- Added optional parseAll argument to runTests, whether tests should
+ require the entire input string to be parsed or not (similar to
+ parseAll argument to parseString). Plus a little neaten-up of the
+ output on Python 2 (no stray ()'s).
+
+- Modified exception messages from MatchFirst and Or expressions. These
+ were formerly misleading as they would only give the first or longest
+ exception mismatch error message. Now the error message includes all
+ the alternatives that were possible matches. Originally proposed by
+ a pyparsing user, but I've lost the email thread - finally figured out
+ a fairly clean way to do this.
+
+- Fixed a bug in Or, when a parse action on an alternative raises an
+ exception, other potentially matching alternatives were not always tried.
+ Reported by TheVeryOmni on the pyparsing wiki, thanks!
+
+- Fixed a bug to dump() introduced in 2.0.4, where list values were shown
+ in duplicate.
+
+
+Version 2.0.5 - October, 2015
+-----------------------------
+- (&$(@#&$(@!!!! Some "print" statements snuck into pyparsing v2.0.4,
+ breaking Python 3 compatibility! Fixed. Reported by jenshn, thanks!
+
+
+Version 2.0.4 - October, 2015
+-----------------------------
+- Added ParserElement.addCondition, to simplify adding parse actions
+ that act primarily as filters. If the given condition evaluates False,
+ pyparsing will raise a ParseException. The condition should be a method
+ with the same method signature as a parse action, but should return a
+ boolean. Suggested by Victor Porton, nice idea Victor, thanks!
+
+- Slight mod to srange to accept unicode literals for the input string,
+ such as "[а-ÑÐ-Я]" instead of "[\u0430-\u044f\u0410-\u042f]". Thanks
+ to Alexandr Suchkov for the patch!
+
+- Enhanced implementation of replaceWith.
+
+- Fixed enhanced ParseResults.dump() method when the results consists
+ only of an unnamed array of sub-structure results. Reported by Robin
+ Siebler, thanks for your patience and persistence, Robin!
+
+- Fixed bug in fourFn.py example code, where pi and e were defined using
+ CaselessLiteral instead of CaselessKeyword. This was not a problem until
+ adding a new function 'exp', and the leading 'e' of 'exp' was accidentally
+ parsed as the mathematical constant 'e'. Nice catch, Tom Grydeland - thanks!
+
+- Adopt new-fangled Python features, like decorators and ternary expressions,
+ per suggestions from Williamzjc - thanks William! (Oh yeah, I'm not
+ supporting Python 2.3 with this code any more...) Plus, some additional
+ code fixes/cleanup - thanks again!
+
+- Added ParserElement.runTests, a little test bench for quickly running
+ an expression against a list of sample input strings. Basically, I got
+ tired of writing the same test code over and over, and finally added it
+ as a test point method on ParserElement.
+
+- Added withClass helper method, a simplified version of withAttribute for
+ the common but annoying case when defining a filter on a div's class -
+ made difficult because 'class' is a Python reserved word.
+
+
+Version 2.0.3 - October, 2014
+-----------------------------
+- Fixed escaping behavior in QuotedString. Formerly, only quotation
+ marks (or characters designated as quotation marks in the QuotedString
+ constructor) would be escaped. Now all escaped characters will be
+ escaped, and the escaping backslashes will be removed.
+
+- Fixed regression in ParseResults.pop() - pop() was pretty much
+ broken after I added *improvements* in 2.0.2. Reported by Iain
+ Shelvington, thanks Iain!
+
+- Fixed bug in And class when initializing using a generator.
+
+- Enhanced ParseResults.dump() method to list out nested ParseResults that
+ are unnamed arrays of sub-structures.
+
+- Fixed UnboundLocalError under Python 3.4 in oneOf method, reported
+ on Sourceforge by aldanor, thanks!
+
+- Fixed bug in ParseResults __init__ method, when returning non-ParseResults
+ types from parse actions that implement __eq__. Raised during discussion
+ on the pyparsing wiki with cyrfer.
+
+
+Version 2.0.2 - April, 2014
+---------------------------
+- Extended "expr(name)" shortcut (same as "expr.setResultsName(name)")
+ to accept "expr()" as a shortcut for "expr.copy()".
+
+- Added "locatedExpr(expr)" helper, to decorate any returned tokens
+ with their location within the input string. Adds the results names
+ locn_start and locn_end to the output parse results.
+
+- Added "pprint()" method to ParseResults, to simplify troubleshooting
+ and prettified output. Now instead of importing the pprint module
+ and then writing "pprint.pprint(result)", you can just write
+ "result.pprint()". This method also accepts addtional positional and
+ keyword arguments (such as indent, width, etc.), which get passed
+ through directly to the pprint method
+ (see http://docs.python.org/2/library/pprint.html#pprint.pprint).
+
+- Removed deprecation warnings when using '<<' for Forward expression
+ assignment. '<<=' is still preferred, but '<<' will be retained
+ for cases whre '<<=' operator is not suitable (such as in defining
+ lambda expressions).
+
+- Expanded argument compatibility for classes and functions that
+ take list arguments, to now accept generators as well.
+
+- Extended list-like behavior of ParseResults, adding support for
+ append and extend. NOTE: if you have existing applications using
+ these names as results names, you will have to access them using
+ dict-style syntax: res["append"] and res["extend"]
+
+- ParseResults emulates the change in list vs. iterator semantics for
+ methods like keys(), values(), and items(). Under Python 2.x, these
+ methods will return lists, under Python 3.x, these methods will
+ return iterators.
+
+- ParseResults now has a method haskeys() which returns True or False
+ depending on whether any results names have been defined. This simplifies
+ testing for the existence of results names under Python 3.x, which
+ returns keys() as an iterator, not a list.
+
+- ParseResults now supports both list and dict semantics for pop().
+ If passed no argument or an integer argument, it will use list semantics
+ and pop tokens from the list of parsed tokens. If passed a non-integer
+ argument (most likely a string), it will use dict semantics and
+ pop the corresponding value from any defined results names. A
+ second default return value argument is supported, just as in
+ dict.pop().
+
+- Fixed bug in markInputline, thanks for reporting this, Matt Grant!
+
+- Cleaned up my unit test environment, now runs with Python 2.6 and
+ 3.3.
+
+
+Version 2.0.1 - July, 2013
+--------------------------
+- Removed use of "nonlocal" that prevented using this version of
+ pyparsing with Python 2.6 and 2.7. This will make it easier to
+ install for packages that depend on pyparsing, under Python
+ versions 2.6 and later. Those using older versions of Python
+ will have to manually install pyparsing 1.5.7.
+
+- Fixed implementation of <<= operator to return self; reported by
+ Luc J. Bourhis, with patch fix by Mathias Mamsch - thanks, Luc
+ and Mathias!
+
+
+Version 2.0.0 - November, 2012
+------------------------------
+- Rather than release another combined Python 2.x/3.x release
+ I've decided to start a new major version that is only
+ compatible with Python 3.x (and consequently Python 2.7 as
+ well due to backporting of key features). This version will
+ be the main development path from now on, with little follow-on
+ development on the 1.5.x path.
+
+- Operator '<<' is now deprecated, in favor of operator '<<=' for
+ attaching parsing expressions to Forward() expressions. This is
+ being done to address precedence of operations problems with '<<'.
+ Operator '<<' will be removed in a future version of pyparsing.
+
+
+Version 1.5.7 - November, 2012
+-----------------------------
+- NOTE: This is the last release of pyparsing that will try to
+ maintain compatibility with Python versions < 2.6. The next
+ release of pyparsing will be version 2.0.0, using new Python
+ syntax that will not be compatible for Python version 2.5 or
+ older.
+
+- 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.
+
+- 'operatorPrecedence' is being renamed 'infixNotation' as a better
+ description of what this helper function creates. 'operatorPrecedence'
+ is deprecated, and will be dropped entirely in a future release.
+
+- Added optional arguments lpar and rpar to operatorPrecedence, so that
+ expressions that use it can override the default suppression of the
+ grouping characters.
+
+- 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.
+ A script demonstrating this feature is included in the examples
+ directory.
+
+- 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 &quot; to ', now correctly maps
+ to ". (Also added support for mapping &apos; 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 "&lt;", "&gt;", "&amp;", "&nbsp;", and "&quot;". 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 "&lt;", "&gt;", "&amp;", "&nbsp;", and
+ "&quot;" 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 &lt;, &gt; and
+ &amp;. (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/trunk/src/HowToUsePyparsing.html b/trunk/src/HowToUsePyparsing.html
new file mode 100644
index 0000000..1bd180b
--- /dev/null
+++ b/trunk/src/HowToUsePyparsing.html
@@ -0,0 +1,1289 @@
+<?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&#64;users.sourceforge.net">ptmcg&#64;users.sourceforge.net</a>
+</pre>
+</td></tr>
+<tr><th class="docinfo-name">Revision:</th>
+<td>2.0.1</td></tr>
+<tr><th class="docinfo-name">Date:</th>
+<td>July, 2013</td></tr>
+<tr><th class="docinfo-name">Copyright:</th>
+<td>Copyright © 2003-2013 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&nbsp;&nbsp;&nbsp;Steps to follow</a><ul class="auto-toc">
+<li><a class="reference internal" href="#hello-world" id="id2">1.1&nbsp;&nbsp;&nbsp;Hello, World!</a></li>
+<li><a class="reference internal" href="#usage-notes" id="id3">1.2&nbsp;&nbsp;&nbsp;Usage notes</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#classes" id="id4">2&nbsp;&nbsp;&nbsp;Classes</a><ul class="auto-toc">
+<li><a class="reference internal" href="#classes-in-the-pyparsing-module" id="id5">2.1&nbsp;&nbsp;&nbsp;Classes in the pyparsing module</a></li>
+<li><a class="reference internal" href="#basic-parserelement-subclasses" id="id6">2.2&nbsp;&nbsp;&nbsp;Basic ParserElement subclasses</a></li>
+<li><a class="reference internal" href="#expression-subclasses" id="id7">2.3&nbsp;&nbsp;&nbsp;Expression subclasses</a></li>
+<li><a class="reference internal" href="#expression-operators" id="id8">2.4&nbsp;&nbsp;&nbsp;Expression operators</a></li>
+<li><a class="reference internal" href="#positional-subclasses" id="id9">2.5&nbsp;&nbsp;&nbsp;Positional subclasses</a></li>
+<li><a class="reference internal" href="#converter-subclasses" id="id10">2.6&nbsp;&nbsp;&nbsp;Converter subclasses</a></li>
+<li><a class="reference internal" href="#special-subclasses" id="id11">2.7&nbsp;&nbsp;&nbsp;Special subclasses</a></li>
+<li><a class="reference internal" href="#other-classes" id="id12">2.8&nbsp;&nbsp;&nbsp;Other classes</a></li>
+<li><a class="reference internal" href="#exception-classes-and-troubleshooting" id="id13">2.9&nbsp;&nbsp;&nbsp;Exception classes and Troubleshooting</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#miscellaneous-attributes-and-methods" id="id14">3&nbsp;&nbsp;&nbsp;Miscellaneous attributes and methods</a><ul class="auto-toc">
+<li><a class="reference internal" href="#helper-methods" id="id15">3.1&nbsp;&nbsp;&nbsp;Helper methods</a></li>
+<li><a class="reference internal" href="#helper-parse-actions" id="id16">3.2&nbsp;&nbsp;&nbsp;Helper parse actions</a></li>
+<li><a class="reference internal" href="#common-string-and-token-constants" id="id17">3.3&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;Hello, World!</a></h2>
+<p>The following complete Python program will parse the greeting &quot;Hello, World!&quot;,
+or any other greeting of the form &quot;&lt;salutation&gt;, &lt;addressee&gt;!&quot;:</p>
+<pre class="literal-block">
+from pyparsing import Word, alphas
+
+greet = Word( alphas ) + &quot;,&quot; + Word( alphas ) + &quot;!&quot;
+greeting = greet.parseString( &quot;Hello, World!&quot; )
+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&nbsp;&nbsp;&nbsp;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 (&quot;screen
+or report scraping&quot;). 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( &quot;+-*/&quot;, max=1 ) # arithmetic operators
+equation = variable + &quot;=&quot; + integer + arithOp + integer # will match &quot;x=2+2&quot;, etc.
+</pre>
+<p>In the definition of <tt class="docutils literal">equation</tt>, the string <tt class="docutils literal">&quot;=&quot;</tt> will get added as
+a <tt class="docutils literal"><span class="pre">Literal(&quot;=&quot;)</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 &quot;3.14159&quot;, but will also match &quot;3 . 12&quot;. 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 &quot;at least n instances of expr&quot;)</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 &quot;0 to n instances of expr&quot;)</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 &quot;best&quot; 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
+&quot;Hello, World!&quot; parser to parse &quot;Hello World!&quot; (leaving out the separating
+comma), we would get an exception, with the message:</p>
+<pre class="literal-block">
+pyparsing.ParseException: Expected &quot;,&quot; (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 = &quot;AVE:&quot; + realNum.setResultsName(&quot;average&quot;) + \
+ &quot;MIN:&quot; + realNum.setResultsName(&quot;min&quot;) + \
+ &quot;MAX:&quot; + realNum.setResultsName(&quot;max&quot;)
+</pre>
+<p>can now be written as this:</p>
+<pre class="literal-block">
+stats = &quot;AVE:&quot; + realNum(&quot;average&quot;) + \
+ &quot;MIN:&quot; + realNum(&quot;min&quot;) + \
+ &quot;MAX:&quot; + realNum(&quot;max&quot;)
+</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&nbsp;&nbsp;&nbsp;Classes</a></h1>
+<div class="section" id="classes-in-the-pyparsing-module">
+<h2><a class="toc-backref" href="#id5">2.1&nbsp;&nbsp;&nbsp;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 &quot;packrat parsing&quot;. 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 &quot;compile as you go&quot;, 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&nbsp;&nbsp;&nbsp;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+&quot;_&quot;, alphanums+&quot;_&quot; )
+- Word( srange(&quot;[a-zA-Z_]&quot;), srange(&quot;[a-zA-Z0-9_]&quot;) )
+</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( &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ_&quot; )
+- Word( srange(&quot;[A-Z_]&quot;) )
+</pre>
+<p>A Word may
+also be constructed with any of the following optional parameters:</p>
+<ul class="simple">
+<li><tt class="docutils literal">min</tt> - indicating a minimum length of matching characters</li>
+<li><tt class="docutils literal">max</tt> - indicating a maximum length of matching characters</li>
+<li><tt class="docutils literal">exact</tt> - 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 <tt class="docutils literal">excludeChars</tt> 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 <tt class="docutils literal">Word</tt>.
+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><tt class="docutils literal">quoteChar</tt> - string of one or more characters defining the quote delimiting string</li>
+<li><tt class="docutils literal">escChar</tt> - character to escape quotes, typically backslash (default=None)</li>
+<li><tt class="docutils literal">escQuote</tt> - special quote sequence to escape an embedded quote string (such as SQL's &quot;&quot; to escape an embedded &quot;) (default=None)</li>
+<li><tt class="docutils literal">multiline</tt> - boolean indicating whether quotes can span multiple lines (default=<tt class="docutils literal">False</tt>)</li>
+<li><tt class="docutils literal">unquoteResults</tt> - boolean indicating whether the matched text should be unquoted (default=<tt class="docutils literal">True</tt>)</li>
+<li><tt class="docutils literal">endQuoteChar</tt> - string of one or more characters defining the end of the quote delimited string (default=None =&gt; 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><tt class="docutils literal">include</tt> - if set to true, also consumes the match expression
+(default is <tt class="docutils literal">False</tt>)</li>
+<li><tt class="docutils literal">ignore</tt> - allows the user to specify patterns to not be matched,
+to prevent false matches</li>
+<li><tt class="docutils literal">failOn</tt> - 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&nbsp;&nbsp;&nbsp;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 '&amp;' 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&nbsp;&nbsp;&nbsp;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">&amp;</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 &quot;{min,max}&quot; 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">&lt;&lt;=</tt> - inserts the expression following the operator as the body of the
+Forward expression before the operator (formerly &lt;&lt;, which is now deprecated)</li>
+</ul>
+</div>
+<div class="section" id="positional-subclasses">
+<h2><a class="toc-backref" href="#id9">2.5&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;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=&quot;&quot;</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&nbsp;&nbsp;&nbsp;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">&lt;&lt;=</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&nbsp;&nbsp;&nbsp;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>&nbsp; ]</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[&quot;X&quot;]=&quot;new</span> item&quot;</tt>); named elements can be removed using <tt class="docutils literal">del <span class="pre">parseResults[&quot;X&quot;]</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><tt class="docutils literal">doctagname</tt> - 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">&lt;doctagname&gt;</tt>
+and <tt class="docutils literal">&lt;/doctagname&gt;</tt>.</li>
+<li><tt class="docutils literal">namedItemsOnly</tt> (default=<tt class="docutils literal">False</tt>) - 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&nbsp;&nbsp;&nbsp;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 &quot; &quot;*(err.column-1) + &quot;^&quot;
+ 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(&quot;A&quot;)
+badGrammar &lt;&lt;= 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&nbsp;&nbsp;&nbsp;Miscellaneous attributes and methods</a></h1>
+<div class="section" id="helper-methods">
+<h2><a class="toc-backref" href="#id15">3.1&nbsp;&nbsp;&nbsp;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 &quot;a.b.c&quot;, or
+&quot;a::b::c&quot;, or paths such as &quot;a/b/c&quot;).</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 &lt;tag&gt; and &lt;/tag&gt; strings. Includes
+support for attributes in the opening tag, such as &lt;tag attr1=&quot;abc&quot;&gt; - 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">infixNotation(baseOperand, operatorList)</tt> - (formerly named <tt class="docutils literal">operatorPrecedence</tt>) convenience function to define a
+grammar for parsing infix notation
+expressions with a hierarchical precedence of operators. To use the <tt class="docutils literal">infixNotation</tt>
+helper:</p>
+<ol class="arabic simple">
+<li>Define the base &quot;atom&quot; 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 <tt class="docutils literal">infixNotation</tt> 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><tt class="docutils literal">opExpr</tt> 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 &quot;y = mx + b&quot;.
+If <tt class="docutils literal">numTerms</tt> parameter is 3, this must be a 2-tuple containing the 2 delimiting operators.</li>
+<li><tt class="docutils literal">numTerms</tt> is the number of terms for this operator (must
+be 1,2, or 3)</li>
+<li><tt class="docutils literal">rightLeftAssoc</tt> 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><tt class="docutils literal">parseAction</tt> 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 <tt class="docutils literal">infixNotation</tt> 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 + &quot;:&quot; + matchPreviousLiteral(first)
+</pre>
+<p>will match &quot;1:1&quot;, but not &quot;1:2&quot;. Since this matches at the literal
+level, this will also match the leading &quot;1:1&quot; in &quot;1:10&quot;.</p>
+<p>In contrast:</p>
+<pre class="literal-block">
+first = Word(nums)
+matchExpr = first + &quot;:&quot; + matchPreviousExpr(first)
+</pre>
+<p>will <em>not</em> match the leading &quot;1:1&quot; in &quot;1:10&quot;; the expressions are
+evaluated first, and then compared, so &quot;1&quot; is compared with &quot;10&quot;.</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><tt class="docutils literal">opener</tt> - opening character for a nested list (default=&quot;(&quot;); can also be a pyparsing expression</li>
+<li><tt class="docutils literal">closer</tt> - closing character for a nested list (default=&quot;)&quot;); can also be a pyparsing expression</li>
+<li><tt class="docutils literal">content</tt> - expression for items within the nested lists (default=None)</li>
+<li><tt class="docutils literal">ignoreExpr</tt> - 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 <tt class="docutils literal">ignoreExpr</tt> 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><tt class="docutils literal">statementExpr</tt> - 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><tt class="docutils literal">indentationStackVar</tt> - 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><tt class="docutils literal">indent</tt> - 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 &quot;John Smith&quot; 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 &quot;ungroup&quot; 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">&quot;[0-9]&quot;</span></tt> for
+all numeric digits, <tt class="docutils literal"><span class="pre">&quot;[A-Z_]&quot;</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&nbsp;&nbsp;&nbsp;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(&quot;&amp;nbsp;&quot;).setParseAction( replaceWith(&quot;&lt;BLANK&gt;&quot;) )
+</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">&lt;TD&gt;</tt> or <tt class="docutils literal">&lt;DIV&gt;</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=&quot;Customer&quot;,align=&quot;right&quot;)</span></tt>, or</li>
+<li>a list of name-value tuples, as in <tt class="docutils literal">( (&quot;ns1:class&quot;, <span class="pre">&quot;Customer&quot;),</span> <span class="pre">(&quot;ns2:align&quot;,&quot;right&quot;)</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&nbsp;&nbsp;&nbsp;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 &quot;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">'&lt;!--'</span></tt> and <tt class="docutils literal"><span class="pre">'--&gt;'</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/trunk/src/HowToUsePyparsing.txt b/trunk/src/HowToUsePyparsing.txt
new file mode 100644
index 0000000..71d5313
--- /dev/null
+++ b/trunk/src/HowToUsePyparsing.txt
@@ -0,0 +1,993 @@
+==========================
+Using the pyparsing module
+==========================
+
+:author: Paul McGuire
+:address: ptmcg@users.sourceforge.net
+
+:revision: 2.0.1
+:date: July, 2013
+
+:copyright: Copyright |copy| 2003-2013 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.
+
+- ``infixNotation(baseOperand, operatorList)`` - (formerly named ``operatorPrecedence``) convenience function to define a
+ grammar for parsing infix notation
+ expressions with a hierarchical precedence of operators. To use the ``infixNotation``
+ 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 ``infixNotation`` 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`` - 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`` - 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
+ ``parseAction`` tuple member may be omitted)
+
+ 3. Call ``infixNotation`` 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`` - the expression defining a statement that
+ will be found in the indented block; a valid ``indentedBlock``
+ must contain at least 1 matching ``statementExpr``
+
+ - ``indentationStackVar`` - 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`` - 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("&nbsp;").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/trunk/src/LICENSE b/trunk/src/LICENSE
new file mode 100644
index 0000000..bbc959e
--- /dev/null
+++ b/trunk/src/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/trunk/src/MANIFEST.in_bdist b/trunk/src/MANIFEST.in_bdist
new file mode 100644
index 0000000..5f2b98b
--- /dev/null
+++ b/trunk/src/MANIFEST.in_bdist
@@ -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/trunk/src/MANIFEST.in_src b/trunk/src/MANIFEST.in_src
new file mode 100644
index 0000000..5f2b98b
--- /dev/null
+++ b/trunk/src/MANIFEST.in_src
@@ -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/trunk/src/README b/trunk/src/README
new file mode 100644
index 0000000..dc959ec
--- /dev/null
+++ b/trunk/src/README
@@ -0,0 +1,80 @@
+====================================
+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.6 or later.)
+
+Or corresponding commands using pip, easy_install, or wheel:
+
+ pip install pyparsing
+
+ easy_install pyparsing
+
+ wheel install pyparsing
+
+
+Documentation
+=============
+
+See:
+
+ HowToUsePyparsing.html
+
+
+License
+=======
+
+ MIT License. See header of pyparsing.py
+
+History
+=======
+
+ See CHANGES file.
diff --git a/trunk/src/examples/0README.html b/trunk/src/examples/0README.html
new file mode 100644
index 0000000..303d44d
--- /dev/null
+++ b/trunk/src/examples/0README.html
@@ -0,0 +1,309 @@
+<HTML>
+<title>pyparsing Examples</title>
+<body>
+<h1>pyparsing Examples</h1>
+<p>
+This directory contains a number of Python scripts that can get you started in learning to use pyparsing.
+
+<ul>
+<li><a href="greeting.py">greeting.py</a><br>
+Parse "Hello, World!".
+</li>
+<p>
+
+<li><a href="greetingInKorean.py">greetingInKorean.py</a> <i>~ submission by June Kim</i><br>
+Unicode example to parse "Hello, World!" in Korean.
+</li>
+<p>
+
+<li><a href="greetingInGreek.py">greetingInGreek.py</a> <i>~ submission by ???</i><br>
+Unicode example to parse "Hello, World!" in Greek.
+</li>
+<p>
+
+<li><a href="holaMundo.py">holaMundo.py</a> <i>~ submission by Marco Alfonso</i><br>
+"Hello, World!" example translated to Spanish, from Marco Alfonso's blog.
+</li>
+<p>
+
+<li><a href="chemicalFormulas.py">chemicalFormulas.py</a><br>
+Simple example to demonstrate the use of ParseResults returned from parseString().
+Parses a chemical formula (such as "H2O" or "C6H5OH"), and walks the returned list of tokens to calculate the molecular weight.
+</li>
+<p>
+
+<li><a href="wordsToNum.py">wordsToNum.py</a><br>
+A sample program that reads a number in words (such as "fifteen hundred and sixty four"), and returns the actual number (1564).
+Also demonstrates some processing of ParseExceptions, including marking where the parse failure was found.
+</li>
+<p>
+
+<li><a href="pythonGrammarparser.py">pythonGrammarparser.py</a> <i>~ suggested by JH Stovall</i><br>
+A sample program that parses the EBNF used in the Python source code to define the Python grammar. From this parser,
+one can generate Python grammar documentation tools, such as railroad track diagrams. Also demonstrates use of
+Dict class.
+</li>
+<p>
+
+<li><a href="commasep.py">commasep.py</a><br>
+Demonstration of the use of the commaSeparatedList helper. Shows examples of
+proper handling of commas within quotes, trimming of whitespace around delimited entries, and handling of consecutive commas (null arguments). Includes comparison with simple string.split(',').
+</li>
+<p>
+
+<li><a href="dictExample.py">dictExample.py</a><br>
+A demonstration of using the Dict class, to parse a table of ASCII tabulated data.
+</li>
+<p>
+
+<li><a href="dictExample2.py">dictExample2.py</a> <i>~ submission by Mike Kelly</i><br>
+An extended version of dictExample.py, in which Mike Kelly also parses the column headers, and generates a transposed version of the original table!
+</li>
+<p>
+
+<li><a href="scanExamples.py">scanExamples.py</a><br>
+Some examples of using scanString and transformString, as alternative parsing methods to parseString, to do macro substitution, and selection and/or removal of matching strings within a source file.
+</li>
+<p>
+
+<li><a href="urlExtractor.py">urlExtractor.py</a><br>
+Another example using scanString, this time to extract all HREF references found on Yahoo!'s home page, and return them as a dictionary.
+</li>
+<p>
+
+<li><a href="makeHTMLTagExample.py">makeHTMLTagExample.py</a><br>
+A sample program showing sample definitions and applications of HTML tag expressions
+created using makeHTMLTags helper function. Very useful for scraping data from HTML pages.
+</li>
+<p>
+
+<li><a href="urlExtractorNew.py">urlExtractorNew.py</a><br>
+Another updated version of urlExtractor.py, using the new makeHTMLTags() method.
+</li>
+<p>
+
+<li><a href="fourFn.py">fourFn.py</a><br>
+A simple algebraic expression parser, that performs +,-,*,/, and ^ arithmetic operations. (With suggestions and bug-fixes graciously offered by Andrea Griffini.)
+</li>
+<p>
+
+<li><a href="SimpleCalc.py">SimpleCalc.py</a> <i>~ submission by Steven Siew</i><br>
+An interactive version of fourFn.py, with support for variables.
+</li>
+<p>
+
+<li><a href="LAParser.py">LAParser.py</a> <i>~ submission by Mike Ellis</i><br>
+An interactive Linear Algebra Parser, an extension of SimpleCalc.py. Supports linear algebra (LA) notation for vectors, matrices, and scalars,
+including matrix operations such as inversion and determinants. Converts LA expressions to C code - uses a separate C library for runtime
+evaluation of results.
+</li>
+<p>
+
+<li><a href="configParse.py">configParse.py</a><br>
+A simple alternative to Python's ConfigParse module, demonstrating the use of the Dict class to return nested dictionary access to configuration values.
+</li>
+<p>
+
+<li><a href="getNTPservers.py">getNTPservers.py</a><br>
+Yet another scanString example, to read/extract the list of NTP servers from NIST's web site.
+</li>
+<p>
+
+<li><a href="getNTPserversNew.py">getNTPserversNew.py</a><br>
+An updated version of getNTPservers.py, using the new makeHTMLTags() method.
+</li>
+<p>
+
+<li><a href="httpServerLogParser.py">httpServerLogParser.py</a><br>
+Parser for Apache server log files.
+</li>
+<p>
+
+<li><a href="idlParse.py">idlParse.py</a><br>
+Parser for CORBA IDL files.
+</li>
+<p>
+
+<li><a href="mozillaCalendarParser.py">mozillaCalendarParser.py</a>
+<i>~ submission by Petri Savolainen</i><br>
+Parser for Mozilla calendar (*.ics) files.
+</li>
+<p>
+
+<li><a href="pgn.py">pgn.py</a> <i>~ submission by Alberto Santini</i><br>
+Parser for PGN (Portable Game Notation) files, the standard form for documenting the moves in chess games.
+</li>
+<p>
+
+<li><a href="simpleSQL.py">simpleSQL.py</a><br>
+A simple parser that will extract table and column names from SQL SELECT statements..
+</li>
+<p>
+
+<li><a href="dfmparse.py">dfmparse.py</a> <i>~ submission by Dan Griffith</i><br>
+Parser for Delphi forms.
+</li>
+<p>
+
+<li><a href="ebnf.py">ebnf.py / ebnftest.py</a> <i>~ submission by Seo Sanghyeon</i><br>
+An EBNF-compiler that reads EBNF and generates a pyparsing grammar! Including a test that compiles... EBNF itself!
+</li>
+<p>
+
+<li><a href="searchparser.py">searchparser.py</a> <i>~ submission by Steven Mooij and Rudolph Froger</i><br>
+An expression parser that parses search strings, with special keyword and expression operations using (), not, and, or, and quoted strings.
+</li>
+<p>
+
+<li><a href="sparser.py">sparser.py</a> <i>~ submission by Tim Cera</i><br>
+A configurable parser module that can be configured with a list of tuples, giving a high-level definition for parsing common sets
+of water table data files. Tim had to contend with several different styles of data file formats, each with slight variations of its own.
+Tim created a configurable parser (or "SPECIFIED parser" - hence the name "sparser"), that simply works from a config variable listing
+the field names and data types, and implicitly, their order in the source data file.
+<p>
+See <a href="mayport_florida_8720220_data_def.txt">mayport_florida_8720220_data_def.txt</a> for an
+example configuration file.
+</li>
+<p>
+
+<li><a href="romanNumerals.py">romanNumerals.py</a><br>
+A Roman numeral generator and parser example, showing the power of parse actions
+to compile Roman numerals into their integer values.
+</li>
+<p>
+
+<li><a href="removeLineBreaks.py">removeLineBreaks.py</a><br>
+A string transformer that converts text files with hard line-breaks into one with line breaks
+only between paragraphs. Useful when converting downloads from
+<a href="http://www.gutenberg.org">Project Gutenberg</a> to import to word processing apps
+that can reformat paragraphs once hard line-breaks are removed, or for loading into your Palm Pilot for portable perusal.
+<p>
+See <a href="Successful Methods of Public Speaking.txt">Successful Methods of Public Speaking.txt</a> and
+<a href="Successful Methods of Public Speaking(2).txt">Successful Methods of Public Speaking(2).txt</a> for a sample
+before and after (text file courtesy of Project Gutenberg).
+</li>
+<p>
+
+<li><a href="listAllMatches.py">listAllMatches.py</a><br>
+An example program showing the utility of the listAllMatches option when specifying results naming.
+</li>
+<p>
+
+<li><a href="linenoExample.py">linenoExample.py</a><br>
+An example program showing how to use the string location to extract line and column numbers, or the
+source line of text.
+</li>
+<p>
+
+<li><a href="parseListString.py">parseListString.py</a><br>
+An example program showing a progression of steps, how to parse a string representation of a Python
+list back into a true list.
+</li>
+<p>
+
+<li><a href="parsePythonValue.py">parsePythonValue.py</a><br>
+An extension of parseListString.py to parse tuples and dicts, including nested values,
+returning a Python value of the original type.
+</li>
+<p>
+
+<li><a href="indentedGrammarExample.py">indentedGrammarExample.py</a><br>
+An example program showing how to parse a grammar using indentation for grouping,
+such as is done in Python.
+</li>
+<p>
+
+<li><a href="simpleArith.py">simpleArith.py</a><br>
+An example program showing how to use the new operatorPrecedence helper method to define a 6-function
+(+, -, *, /, ^, and !) arithmetic expression parser, with unary plus and minus signs.
+</li>
+<p>
+
+<li><a href="simpleBool.py">simpleBool.py</a><br>
+An example program showing how to use the new operatorPrecedence helper method to define a
+boolean expression parser, with parse actions associated with each operator to "compile" the expression
+into a data structure that will evaluate the expression's boolean value.
+</li>
+<p>
+
+<li><a href="simpleWiki.py">simpleWiki.py</a><br>
+An example program showing how to use transformString to implement a simple Wiki markup parser.
+</li>
+<p>
+
+<li><a href="sql2dot.py">sql2dot.py</a><i>~ submission by EnErGy [CSDX]</i><br>
+A nice graphing program that generates schema diagrams from SQL table definition statements.
+</li>
+<p>
+
+<li><a href="htmlStripper.py">htmlStripper.py</a><br>
+An example implementation of a common application, removing HTML markup tags from an HTML page,
+leaving just the text content.
+</li>
+<p>
+
+<li><a href="macroExpansion.py">macroExpansion.py</a><br>
+An example implementation of a simple preprocessor, that will read embedded macro definitions
+and replace macro references with the defined substitution string.
+</li>
+<p>
+
+<li><a href="sexpParser.py">sexpParser.py</a><br>
+A parser that uses a recursive grammar to parse S-expressions.
+</li>
+<p>
+
+<li><a href="nested.py">nested.py</a><br>
+An example using nestedExpr, a helper method to simplify definitions of expressions of nested lists.
+</li>
+<p>
+
+<li><a href="withAttribute.py">withAttribute.py</a><br>
+An example using withAttribute, a helper method to define parse actions to validate matched HTML tags
+using additional attributes. Especially helpful for matching common tags such as &lt;DIV&gt; and &lt;TD&gt;.
+</li>
+<p>
+
+<li><a href="stackish.py">stackish.py</a><br>
+A parser for the data representation format, Stackish.
+</li>
+<p>
+
+<li><a href="builtin_parse_action_demo.py">builtin_parse_action_demo.py</a><br>
+<b>New in version 1.5.7</b><br>
+Demonstration of using builtins (min, max, sum, len, etc.) as parse actions.
+</li>
+<p>
+
+<li><a href="antlr_grammar.py">antlr_grammar.py</a><i>~ submission by Luca DellOlio</i><br>
+<b>New in version 1.5.7</b><br>
+Pyparsing example parsing ANTLR .a files and generating a working pyparsing parser.
+</li>
+<p>
+
+<li><a href="shapes.py">shapes.py</a><br>
+<b>New in version 1.5.7</b><br>
+Parse actions example simple shape definition syntax, and returning the matched tokens as
+domain objects instead of just strings.
+</li>
+<p>
+
+<li><a href="datetimeParseActions.py">datetimeParseActions.py</a><br>
+<b>New in version 1.5.7</b><br>
+Parse actions example showing a parse action returning a datetime object instead of
+string tokens, and doing validation of the tokens, raising a ParseException if the
+given YYYY/MM/DD string does not represent a valid date.
+</li>
+<p>
+
+<li><a href="position.py">position.py</a><br>
+<b>New in version 1.5.7</b><br>
+Demonstration of a couple of different ways to capture the location a particular
+expression was found within the overall input string.
+</li>
+<p>
+
+
+</ul>
+
+</body></html>
diff --git a/trunk/src/examples/AcManForm.dfm b/trunk/src/examples/AcManForm.dfm
new file mode 100644
index 0000000..db80f6a
--- /dev/null
+++ b/trunk/src/examples/AcManForm.dfm
@@ -0,0 +1,885 @@
+object Form1: TForm1
+ Left = 193
+ Top = 105
+ Width = 696
+ Height = 480
+ Caption = 'AcManTest'
+ Color = clBtnFace
+ Font.Charset = DEFAULT_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'MS Sans Serif'
+ Font.Style = []
+ OldCreateOrder = False
+ OnCreate = FormCreate
+ PixelsPerInch = 96
+ TextHeight = 13
+ object RichEdit1: TRichEdit
+ Left = 0
+ Top = 107
+ Width = 688
+ Height = 346
+ Align = alClient
+ Lines.Strings = (
+ 'RichEdit1')
+ TabOrder = 0
+ end
+ object ActionToolBar1: TActionToolBar
+ Left = 0
+ Top = 25
+ Width = 688
+ Height = 28
+ ActionManager = ActionManager1
+ Caption = 'ActionToolBar1'
+ ColorMap.HighlightColor = 14410210
+ ColorMap.BtnSelectedColor = clBtnFace
+ ColorMap.UnusedColor = 14410210
+ EdgeBorders = [ebTop, ebBottom]
+ Font.Charset = DEFAULT_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'MS Sans Serif'
+ Font.Style = []
+ ParentFont = False
+ ParentShowHint = False
+ ShowHint = True
+ Spacing = 0
+ end
+ object ActionMainMenuBar1: TActionMainMenuBar
+ Left = 0
+ Top = 0
+ Width = 688
+ Height = 25
+ UseSystemFont = False
+ ActionManager = ActionManager1
+ AnimationStyle = asSlide
+ Caption = 'ActionMainMenuBar1'
+ ColorMap.HighlightColor = 14410210
+ ColorMap.BtnSelectedColor = clBtnFace
+ ColorMap.UnusedColor = 14410210
+ EdgeBorders = [ebTop, ebBottom]
+ EdgeOuter = esNone
+ Font.Charset = ANSI_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'Tahoma'
+ Font.Style = []
+ ParentShowHint = False
+ ShowHint = True
+ Spacing = 0
+ end
+ object ActionToolBar2: TActionToolBar
+ Left = 0
+ Top = 53
+ Width = 688
+ Height = 28
+ ActionManager = ActionManager1
+ Caption = 'ActionToolBar2'
+ ColorMap.HighlightColor = 14410210
+ ColorMap.BtnSelectedColor = clBtnFace
+ ColorMap.UnusedColor = 14410210
+ EdgeBorders = [ebTop, ebBottom]
+ Font.Charset = DEFAULT_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'MS Sans Serif'
+ Font.Style = []
+ ParentFont = False
+ ParentShowHint = False
+ ShowHint = True
+ Spacing = 0
+ end
+ object ActionToolBar3: TActionToolBar
+ Left = 0
+ Top = 81
+ Width = 688
+ Height = 26
+ ActionManager = ActionManager1
+ Caption = 'ActionToolBar3'
+ ColorMap.HighlightColor = 14410210
+ ColorMap.BtnSelectedColor = clBtnFace
+ ColorMap.UnusedColor = 14410210
+ Font.Charset = DEFAULT_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'MS Sans Serif'
+ Font.Style = []
+ ParentFont = False
+ Spacing = 0
+ end
+ object ActionManager1: TActionManager
+ FileName = 'settings'
+ ActionBars.SessionCount = 4
+ ActionBars = <
+ item
+ Items = <
+ item
+ Action = EditUndo1
+ ImageIndex = 3
+ ShortCut = 16474
+ end
+ item
+ Action = EditCut1
+ ImageIndex = 0
+ ShortCut = 16472
+ end
+ item
+ Action = EditCopy1
+ ImageIndex = 1
+ ShortCut = 16451
+ end
+ item
+ Action = EditPaste1
+ ImageIndex = 2
+ ShortCut = 16470
+ end
+ item
+ Action = SearchFind1
+ ImageIndex = 15
+ ShortCut = 16454
+ end
+ item
+ Action = SearchReplace1
+ ImageIndex = 17
+ end>
+ ActionBar = ActionToolBar1
+ AutoSize = False
+ end
+ item
+ Items = <
+ item
+ Items = <
+ item
+ Action = FileOpen1
+ ImageIndex = 12
+ ShortCut = 16463
+ end
+ item
+ Action = FileSaveAs1
+ ImageIndex = 13
+ end
+ item
+ Action = FilePrintSetup1
+ end
+ item
+ Action = FileRun1
+ end
+ item
+ Action = FileExit1
+ ImageIndex = 14
+ LastSession = -1
+ UsageCount = -1
+ end>
+ Caption = '&File'
+ end
+ item
+ Items = <
+ item
+ Action = EditCut1
+ ImageIndex = 0
+ ShortCut = 16472
+ end
+ item
+ Action = EditCopy1
+ ImageIndex = 1
+ ShortCut = 16451
+ end
+ item
+ Action = EditPaste1
+ ImageIndex = 2
+ ShortCut = 16470
+ end
+ item
+ Action = EditSelectAll1
+ ShortCut = 16449
+ end
+ item
+ Action = EditUndo1
+ ImageIndex = 3
+ ShortCut = 16474
+ end
+ item
+ Action = EditDelete1
+ ImageIndex = 4
+ ShortCut = 46
+ end>
+ Caption = '&Edit'
+ end
+ item
+ Items = <
+ item
+ Action = RichEditBold1
+ ImageIndex = 5
+ ShortCut = 16450
+ end
+ item
+ Action = RichEditItalic1
+ ImageIndex = 6
+ ShortCut = 16457
+ end
+ item
+ Action = RichEditUnderline1
+ ImageIndex = 7
+ ShortCut = 16469
+ end
+ item
+ Action = RichEditStrikeOut1
+ end
+ item
+ Action = RichEditBullets1
+ ImageIndex = 8
+ end
+ item
+ Action = RichEditAlignLeft1
+ ImageIndex = 9
+ end
+ item
+ Action = RichEditAlignRight1
+ ImageIndex = 10
+ end
+ item
+ Action = RichEditAlignCenter1
+ ImageIndex = 11
+ end>
+ Caption = 'F&ormat'
+ end
+ item
+ Items = <
+ item
+ Action = SearchFind1
+ ImageIndex = 15
+ ShortCut = 16454
+ end
+ item
+ Action = SearchFindNext1
+ ImageIndex = 16
+ ShortCut = 114
+ end
+ item
+ Action = SearchReplace1
+ ImageIndex = 17
+ end
+ item
+ Action = SearchFindFirst1
+ end>
+ Caption = '&Search'
+ end
+ item
+ Items = <
+ item
+ Action = CustomizeActionBars1
+ end>
+ Caption = '&Tools'
+ end
+ item
+ Items = <
+ item
+ Action = HelpContents1
+ ImageIndex = 18
+ end>
+ Caption = '&Help'
+ end>
+ ActionBar = ActionMainMenuBar1
+ AutoSize = False
+ end
+ item
+ Items = <
+ item
+ Action = RichEditBold1
+ ImageIndex = 5
+ ShortCut = 16450
+ end
+ item
+ Action = RichEditItalic1
+ ImageIndex = 6
+ ShortCut = 16457
+ end
+ item
+ Action = RichEditUnderline1
+ ImageIndex = 7
+ ShortCut = 16469
+ end
+ item
+ Action = RichEditBullets1
+ Caption = 'Bull&ets'
+ ImageIndex = 8
+ end
+ item
+ Action = RichEditAlignLeft1
+ ImageIndex = 9
+ end
+ item
+ Action = RichEditAlignRight1
+ ImageIndex = 10
+ end
+ item
+ Action = RichEditAlignCenter1
+ ImageIndex = 11
+ end>
+ ActionBar = ActionToolBar2
+ AutoSize = False
+ end
+ item
+ AutoSize = False
+ end
+ item
+ AutoSize = False
+ end
+ item
+ Items = <
+ item
+ Action = FileSaveAs1
+ ImageIndex = 13
+ LastSession = 2
+ end
+ item
+ Action = CustomizeActionBars1
+ end
+ item
+ Action = FileExit1
+ ImageIndex = 14
+ end
+ item
+ Action = HelpContents1
+ Caption = 'C&ontents'
+ ImageIndex = 18
+ end
+ item
+ Action = ActionShowStatus
+ Caption = '&ShowStatus'
+ end>
+ ActionBar = ActionToolBar3
+ AutoSize = False
+ end>
+ Images = ImageList1
+ Left = 88
+ Top = 136
+ StyleName = 'XP Style'
+ object EditCut1: TEditCut
+ Category = 'Edit'
+ Caption = 'Cu&t'
+ Hint = 'Cut|Cuts the selection and puts it on the Clipboard'
+ ImageIndex = 0
+ ShortCut = 16472
+ end
+ object EditCopy1: TEditCopy
+ Category = 'Edit'
+ Caption = '&Copy'
+ Hint = 'Copy|Copies the selection and puts it on the Clipboard'
+ ImageIndex = 1
+ ShortCut = 16451
+ end
+ object EditPaste1: TEditPaste
+ Category = 'Edit'
+ Caption = '&Paste'
+ Hint = 'Paste|Inserts Clipboard contents'
+ ImageIndex = 2
+ ShortCut = 16470
+ end
+ object EditSelectAll1: TEditSelectAll
+ Category = 'Edit'
+ Caption = 'Select &All'
+ Hint = 'Select All|Selects the entire document'
+ ShortCut = 16449
+ end
+ object EditUndo1: TEditUndo
+ Category = 'Edit'
+ Caption = '&Undo'
+ Hint = 'Undo|Reverts the last action'
+ ImageIndex = 3
+ ShortCut = 16474
+ end
+ object EditDelete1: TEditDelete
+ Category = 'Edit'
+ Caption = '&Delete'
+ Hint = 'Delete|Erases the selection'
+ ImageIndex = 4
+ ShortCut = 46
+ end
+ object RichEditBold1: TRichEditBold
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Bold'
+ Hint = 'Bold'
+ ImageIndex = 5
+ ShortCut = 16450
+ end
+ object RichEditItalic1: TRichEditItalic
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Italic'
+ Hint = 'Italic'
+ ImageIndex = 6
+ ShortCut = 16457
+ end
+ object RichEditUnderline1: TRichEditUnderline
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Underline'
+ Hint = 'Underline'
+ ImageIndex = 7
+ ShortCut = 16469
+ end
+ object RichEditStrikeOut1: TRichEditStrikeOut
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Strikeout'
+ Hint = 'Strikeout'
+ end
+ object RichEditBullets1: TRichEditBullets
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Bullets'
+ Hint = 'Bullets|Inserts a bullet on the current line'
+ ImageIndex = 8
+ end
+ object RichEditAlignLeft1: TRichEditAlignLeft
+ Category = 'Format'
+ AutoCheck = True
+ Caption = 'Align &Left'
+ Hint = 'Align Left|Aligns text at the left indent'
+ ImageIndex = 9
+ end
+ object RichEditAlignRight1: TRichEditAlignRight
+ Category = 'Format'
+ AutoCheck = True
+ Caption = 'Align &Right'
+ Hint = 'Align Right|Aligns text at the right indent'
+ ImageIndex = 10
+ end
+ object RichEditAlignCenter1: TRichEditAlignCenter
+ Category = 'Format'
+ AutoCheck = True
+ Caption = '&Center'
+ Hint = 'Center|Centers text between margins'
+ ImageIndex = 11
+ end
+ object FileOpen1: TFileOpen
+ Category = 'File'
+ Caption = '&Open...'
+ Hint = 'Open|Opens an existing file'
+ ImageIndex = 12
+ ShortCut = 16463
+ end
+ object FileSaveAs1: TFileSaveAs
+ Category = 'File'
+ Caption = 'Save &As...'
+ Hint = 'Save As|Saves the active file with a new name'
+ ImageIndex = 13
+ end
+ object FilePrintSetup1: TFilePrintSetup
+ Category = 'File'
+ Caption = 'Print Set&up...'
+ Hint = 'Print Setup'
+ end
+ object FileRun1: TFileRun
+ Category = 'File'
+ Browse = False
+ BrowseDlg.Title = 'Run'
+ Caption = '&Run...'
+ Hint = 'Run|Runs an application'
+ Operation = 'open'
+ ShowCmd = scShowNormal
+ end
+ object FileExit1: TFileExit
+ Category = 'File'
+ Caption = 'E&xit'
+ Hint = 'Exit|Quits the application'
+ ImageIndex = 14
+ end
+ object SearchFind1: TSearchFind
+ Category = 'Search'
+ Caption = '&Find...'
+ Hint = 'Find|Finds the specified text'
+ ImageIndex = 15
+ ShortCut = 16454
+ end
+ object SearchFindNext1: TSearchFindNext
+ Category = 'Search'
+ Caption = 'Find &Next'
+ Enabled = False
+ Hint = 'Find Next|Repeats the last find'
+ ImageIndex = 16
+ ShortCut = 114
+ end
+ object SearchReplace1: TSearchReplace
+ Category = 'Search'
+ Caption = '&Replace'
+ Hint = 'Replace|Replaces specific text with different text'
+ ImageIndex = 17
+ end
+ object SearchFindFirst1: TSearchFindFirst
+ Category = 'Search'
+ Caption = 'F&ind First'
+ Hint = 'Find First|Finds the first occurance of specified text'
+ end
+ object CustomizeActionBars1: TCustomizeActionBars
+ Category = 'Tools'
+ Caption = '&Customize'
+ CustomizeDlg.StayOnTop = False
+ end
+ object HelpContents1: THelpContents
+ Category = 'Help'
+ Caption = '&Contents'
+ Enabled = False
+ Hint = 'Help Contents'
+ ImageIndex = 18
+ end
+ object ActionShowStatus: TAction
+ Category = 'Tools'
+ Caption = 'ShowStatus'
+ OnExecute = ActionShowStatusExecute
+ end
+ end
+ object ImageList1: TImageList
+ Left = 168
+ Top = 136
+ Bitmap = {
+ 494C010113001400040010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
+ 0000000000003600000028000000400000005000000001001000000000000028
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 1040104010420000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000010401040
+ FF7FFF7F18631042000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000000010401040FF7FFF7F
+ 0000000018631863104200000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000104210401040FF7FFF7F00000000
+ 1040104000001863186310420000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000001863000000000000
+ 0000000000000000186300000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000010421040FF7F0000000010401040
+ 1040104010400000186318631042000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000010420000
+ 0000000010420000000000000000000000000000000000001863000000000000
+ 0000000000000000186300000000000000001042000000001040104010400042
+ E07F104010401040000018631863104200000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000010420000
+ 0000000010420000000000000000000000001042104010401040104010401040
+ 0042104010401040104000001863000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000001863000000000000
+ 0000186300000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000001040FF7F1040104010401040
+ 1040E07FE07F1040104010400000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000001863000000000000
+ 0000186300000000000000000000000000000000000000001863000000000000
+ 000018630000000000000000000000000000000000001040FF7F104010401040
+ 104010400042E07FE07F10401040000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000001863000000000000
+ 0000186300000000000000000000000000000000000000001040FF7F10401040
+ 104000421040E07FE07F10401040104000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 1042000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000000000001040FF7F1040
+ 1040E07FE07FE07F104010401040000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 104200000000000000000000000000000000000000000000000000001040FF7F
+ 1040104010401040104000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000001040
+ FF7F104010400000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 1040104000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000001042104210421042104210421042
+ 104210421042FF7F186310421863FF7F18630000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000001042104210421042104210421042
+ 1042104210421042FF7F1042FF7F104210420000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420042000000000000
+ 0000000000000000000000000042000000000000000000000000000000000000
+ 0000000000000000000000000000000000001000100010001000000000001042
+ 10421042FF7FFF7FFF7F10001000100010000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420042000000000000
+ 0000000000000000000000000042000000000000000000000000004200420000
+ 00000000000018630000004200000000000000000000000010001F0010000000
+ 00001042FF7FFF7FFF7F10000000000000000000FF7F00000000000000000000
+ 0000000000000000FF7F00000000000000000000000000420042000000000000
+ 0000000000000000000000000042000000000000000000000000004200420000
+ 000000000000186300000042000000000000000000000000100010001F001000
+ 0000FF7FFF7FFF7FFF7F10000000000000000000FF7F00000000000000000000
+ 0000000000000000FF7F00000000000000000000000000420042000000000000
+ 0000000000000000000000000042000000000000000000000000004200420000
+ 00000000000000000000004200000000000000000000000010001F0010001F00
+ 0000FF7FFF7FFF7FFF7F10000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420042004200420042
+ 0042004200420042004200420042000000000000000000000000004200420042
+ 004200420042004200420042000000000000000000000000100010001F001000
+ 0000FF7FFF03FF7FFF03100000000000000000000000FF7F0000000000000000
+ 00000000FF7F0000000000000000000000000000000000420042000000000000
+ 0000000000000000000000420042000000000000000000000000004200420000
+ 00000000000000000042004200000000000000000000000010001F0010001F00
+ 0000FF03FF7FFF03FF7F100000000000000000000000FF7F0000000000001863
+ 00000000FF7F0000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000042000000000000000000000000004200001863
+ 186318631863186300000042000000000000000000000000100010001F001000
+ 0000FF7FFF03FF7FFF03100000000000000000000000FF7F0000000000001863
+ 00000000FF7F0000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000042000000000000000000000000004200001863
+ 18631863186318630000004200000000000000000000000010001F0010001F00
+ 0000FF03FF7FFF03FF7F10000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000042000000000000000000000000004200001863
+ 1863186318631863000000000000000000000000000000001000100010001000
+ 100010001000100010001000000000000000000000000000FF7F000000000000
+ 00000000FF7F0000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000042000000000000000000000000004200001863
+ 1863186318631863000018630000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000420000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000002
+ 0002000200020000000000000000000000000000000000000000FF7F00000000
+ 000000000000FF7F000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000100010001000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000010001000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000001000
+ 1000100010001000100010001000100010000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000100000000000
+ 1000000000001000100000000000000000000000000000000000000000000000
+ 1000100010001000100010001000100010000000000000000000000000001000
+ FF7FFF7FFF7FFF7FFF7FFF7FFF7FFF7F10000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000100000000000
+ 1000000010000000000010000000000000000000000000000000000000000000
+ 1000FF7FFF7FFF7FFF7FFF7FFF7FFF7F10000000104200421042004210421000
+ FF7F000000000000000000000000FF7F10000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000100000000000
+ 1000000010000000000010000000000000000000000000000000000000000000
+ 1000FF7F00000000000000000000FF7F10000000004210420042104200421000
+ FF7FFF7FFF7FFF7FFF7FFF7FFF7FFF7F10000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000010001000
+ 1000000010000000000010000000000000000000000000000000000000000000
+ 1000FF7FFF7FFF7FFF7FFF7FFF7FFF7F10000000104200421042004210421000
+ FF7F000000000000FF7F10001000100010000000000000000000000000000000
+ 0000000000000000000010000000000000000000000000000000000000000000
+ 10000000100010001000000000000000000000000000FF7FFF7FFF7FFF7FFF7F
+ 1000FF7F00000000000000000000FF7F10000000004210420042104200421000
+ FF7FFF7FFF7FFF7FFF7F1000FF7F100000000000000010001000100010001000
+ 0000000000000000000010000000000000000000000000000000000000000000
+ 10000000100000000000000000000000000000000000FF7F0000000000000000
+ 1000FF7FFF7FFF7FFF7FFF7FFF7FFF7F10000000104200421042004210421000
+ FF7FFF7FFF7FFF7FFF7F10001000000000000000000010001000100010000000
+ 0000000000000000000000001000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7FFF7FFF7FFF7FFF7F
+ 1000FF7F00000000FF7F10001000100010000000004210420042104200421000
+ 1000100010001000100010000000000000000000000010001000100000000000
+ 0000000000000000000000001000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7F0000000000000000
+ 1000FF7FFF7FFF7FFF7F1000FF7F100000000000104200421042004210420042
+ 1042004210420042104200420000000000000000000010001000000010000000
+ 0000000000000000000000001000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7FFF7FFF7FFF7FFF7F
+ 1000FF7FFF7FFF7FFF7F10001000000000000000004210420000000000000000
+ 0000000000000000104210420000000000000000000010000000000000001000
+ 1000000000000000000010000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7F00000000FF7F0000
+ 1000100010001000100010000000000000000000104210420000000000000000
+ 0000000000000000104200420000000000000000000000000000000000000000
+ 0000100010001000100000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7FFF7FFF7FFF7F0000
+ FF7F0000000000000000000000000000000000000042104200420000E07F0000
+ 0000E07F00001042004210420000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000FF7FFF7FFF7FFF7F0000
+ 000000000000000000000000000000000000000000000000000000000000E07F
+ E07F000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000424D3E000000000000003E000000
+ 2800000040000000500000000100010000000000800200000000000000000000
+ 000000000000000000000000FFFFFF00FFFFB6E7FFFF0000FE49B76BFE3F0000
+ FE498427F81F0000FFFFB76BE00F0000FFFFCEE780070000C7C7FFFF00030000
+ C7C7C7C700010000C387C7C700000000C007C38700010000C007C00780010000
+ C007C007C0010000C007C007E0000000C007C007F0000000F39FC007F8030000
+ F39FF39FFC0F0000F39FF39FFE3F0000FFFFFF7E0000FFFFC001BFFF0000FFFF
+ 8031F003000007C18031E003E00707C18031E003E00707C18001E003E0070101
+ 8001E003E007000180012003E00700018FF1E002E00700018FF1E003E0078003
+ 8FF1E003E007C1078FF1E003FFFFC1078FF1E003F81FE38F8FF5FFFFF81FE38F
+ 8001BF7DF81FE38FFFFF7F7EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ 8FFFFFFFFFFFFFFF8C03C007C007C0078FFFFFFFFFFFFFFFFFFFC03FF807F83F
+ FFFFFFFFFFFFFFFF8FFFC007C007C0078C03FFFFFFFFFFFF8FFFC03FF807F01F
+ FFFFFFFFFFFFFFFFFFFFC007C007C0078FFFFFFFFFFFFFFF8C03C03FF807F83F
+ 8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ EFFDFFFFFFFFE00FC7FFFFFFFFFFFFFFC3FBF00F81FFF83FE3F7F8C7E3FFF39F
+ F1E7F8C7F1FFF39FF8CFF8C7F8FFF39FFC1FF80FFC7FF39FFE3FF8C7FE3FF39F
+ FC1FF8C7FF1FF39FF8CFF8C7FF8FF39FE1E7F00FFF03E10FC3F3FFFFFFFFFFFF
+ C7FDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FFFFFFFC00FFFF
+ F6CFFE008000FFFFF6B7FE000000FFFFF6B7FE000000FFFFF8B780000000FFF7
+ FE8F80000001C1F7FE3F80000003C3FBFF7F80000003C7FBFE3F80010003CBFB
+ FEBF80030003DCF7FC9F80070FC3FF0FFDDF807F0003FFFFFDDF80FF8007FFFF
+ FDDF81FFF87FFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000
+ 000000000000}
+ end
+end
diff --git a/trunk/src/examples/LAparser.py b/trunk/src/examples/LAparser.py
new file mode 100644
index 0000000..ec75d6c
--- /dev/null
+++ b/trunk/src/examples/LAparser.py
@@ -0,0 +1,417 @@
+"""
+Purpose: Linear Algebra Parser
+Based on: SimpleCalc.py example (author Paul McGuire) in pyparsing-1.3.3
+Author: Mike Ellis
+Copyright: Ellis & Grant, Inc. 2005
+License: You may freely use, modify, and distribute this software.
+Warranty: THIS SOFTWARE HAS NO WARRANTY WHATSOEVER. USE AT YOUR OWN RISK.
+Notes: Parses infix linear algebra (LA) notation for vectors, matrices, and scalars.
+ Output is C code function calls. The parser can be run as an interactive
+ interpreter or included as module to use for in-place substitution into C files
+ containing LA equations.
+
+ Supported operations are:
+ OPERATION: INPUT OUTPUT
+ Scalar addition: "a = b+c" "a=(b+c)"
+ Scalar subtraction: "a = b-c" "a=(b-c)"
+ Scalar multiplication: "a = b*c" "a=b*c"
+ Scalar division: "a = b/c" "a=b/c"
+ Scalar exponentiation: "a = b^c" "a=pow(b,c)"
+ Vector scaling: "V3_a = V3_b * c" "vCopy(a,vScale(b,c))"
+ Vector addition: "V3_a = V3_b + V3_c" "vCopy(a,vAdd(b,c))"
+ Vector subtraction: "V3_a = V3_b - V3_c" "vCopy(a,vSubtract(b,c))"
+ Vector dot product: "a = V3_b * V3_c" "a=vDot(b,c)"
+ Vector outer product: "M3_a = V3_b @ V3_c" "a=vOuterProduct(b,c)"
+ Vector magn. squared: "a = V3_b^Mag2" "a=vMagnitude2(b)"
+ Vector magnitude: "a = V3_b^Mag" "a=sqrt(vMagnitude2(b))"
+ Matrix scaling: "M3_a = M3_b * c" "mCopy(a,mScale(b,c))"
+ Matrix addition: "M3_a = M3_b + M3_c" "mCopy(a,mAdd(b,c))"
+ Matrix subtraction: "M3_a = M3_b - M3_c" "mCopy(a,mSubtract(b,c))"
+ Matrix multiplication: "M3_a = M3_b * M3_c" "mCopy(a,mMultiply(b,c))"
+ Matrix by vector mult.: "V3_a = M3_b * V3_c" "vCopy(a,mvMultiply(b,c))"
+ Matrix inversion: "M3_a = M3_b^-1" "mCopy(a,mInverse(b))"
+ Matrix transpose: "M3_a = M3_b^T" "mCopy(a,mTranspose(b))"
+ Matrix determinant: "a = M3_b^Det" "a=mDeterminant(b)"
+
+ The parser requires the expression to be an equation. Each non-scalar variable
+ must be prefixed with a type tag, 'M3_' for 3x3 matrices and 'V3_' for 3-vectors.
+ For proper compilation of the C code, the variables need to be declared without
+ the prefix as float[3] for vectors and float[3][3] for matrices. The operations do
+ not modify any variables on the right-hand side of the equation.
+
+ Equations may include nested expressions within parentheses. The allowed binary
+ operators are '+-*/^' for scalars, and '+-*^@' for vectors and matrices with the
+ meanings defined in the table above.
+
+ Specifying an improper combination of operands, e.g. adding a vector to a matrix,
+ is detected by the parser and results in a Python TypeError Exception. The usual cause
+ of this is omitting one or more tag prefixes. The parser knows nothing about a
+ a variable's C declaration and relies entirely on the type tags. Errors in C
+ declarations are not caught until compile time.
+
+Usage: To process LA equations embedded in source files, import this module and
+ pass input and output file objects to the fprocess() function. You can
+ can also invoke the parser from the command line, e.g. 'python LAparser.py',
+ to run a small test suite and enter an interactive loop where you can enter
+ LA equations and see the resulting C code.
+
+"""
+
+import re,os,sys
+from pyparsing import Word, alphas, ParseException, Literal, CaselessLiteral \
+, Combine, Optional, nums, Or, Forward, OneOrMore, ZeroOrMore, \
+ FollowedBy, StringStart, StringEnd, alphanums
+import math
+
+# Debugging flag can be set to either "debug_flag=True" or "debug_flag=False"
+debug_flag=False
+
+#----------------------------------------------------------------------------
+# Variables that hold intermediate parsing results and a couple of
+# helper functions.
+exprStack = [] # Holds operators and operands parsed from input.
+targetvar = None # Holds variable name to left of '=' sign in LA equation.
+
+
+def _pushFirst( str, loc, toks ):
+ if debug_flag: print("pushing ", toks[0], "str is ", str)
+ exprStack.append( toks[0] )
+
+def _assignVar( str, loc, toks ):
+ global targetvar
+ targetvar = toks[0]
+
+#-----------------------------------------------------------------------------
+# The following statements define the grammar for the parser.
+
+point = Literal('.')
+e = CaselessLiteral('E')
+plusorminus = Literal('+') | Literal('-')
+number = Word(nums)
+integer = Combine( Optional(plusorminus) + number )
+floatnumber = Combine( integer +
+ Optional( point + Optional(number) ) +
+ Optional( e + integer )
+ )
+
+lbracket = Literal("[")
+rbracket = Literal("]")
+ident = Forward()
+## The definition below treats array accesses as identifiers. This means your expressions
+## can include references to array elements, rows and columns, e.g., a = b[i] + 5.
+## Expressions within []'s are not presently supported, so a = b[i+1] will raise
+## a ParseException.
+ident = Combine(Word(alphas + '-',alphanums + '_') + \
+ ZeroOrMore(lbracket + (Word(alphas + '-',alphanums + '_')|integer) + rbracket) \
+ )
+
+plus = Literal( "+" )
+minus = Literal( "-" )
+mult = Literal( "*" )
+div = Literal( "/" )
+outer = Literal( "@" )
+lpar = Literal( "(" ).suppress()
+rpar = Literal( ")" ).suppress()
+addop = plus | minus
+multop = mult | div | outer
+expop = Literal( "^" )
+assignop = Literal( "=" )
+
+expr = Forward()
+atom = ( ( e | floatnumber | integer | ident ).setParseAction(_pushFirst) |
+ ( lpar + expr.suppress() + rpar )
+ )
+factor = Forward()
+factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( _pushFirst ) )
+
+term = factor + ZeroOrMore( ( multop + factor ).setParseAction( _pushFirst ) )
+expr << term + ZeroOrMore( ( addop + term ).setParseAction( _pushFirst ) )
+equation = (ident + assignop).setParseAction(_assignVar) + expr + StringEnd()
+
+# End of grammar definition
+#-----------------------------------------------------------------------------
+## The following are helper variables and functions used by the Binary Infix Operator
+## Functions described below.
+
+vprefix = 'V3_'
+vplen = len(vprefix)
+mprefix = 'M3_'
+mplen = len(mprefix)
+
+## We don't support unary negation for vectors and matrices
+class UnaryUnsupportedError(Exception): pass
+
+def _isvec(ident):
+ if ident[0] == '-' and ident[1:vplen+1] == vprefix:
+ raise UnaryUnsupportedError
+ else: return ident[0:vplen] == vprefix
+
+def _ismat(ident):
+ if ident[0] == '-' and ident[1:mplen+1] == mprefix:
+ raise UnaryUnsupportedError
+ else: return ident[0:mplen] == mprefix
+
+def _isscalar(ident): return not (_isvec(ident) or _ismat(ident))
+
+## Binary infix operator (BIO) functions. These are called when the stack evaluator
+## pops a binary operator like '+' or '*". The stack evaluator pops the two operand, a and b,
+## and calls the function that is mapped to the operator with a and b as arguments. Thus,
+## 'x + y' yields a call to addfunc(x,y). Each of the BIO functions checks the prefixes of its
+## arguments to determine whether the operand is scalar, vector, or matrix. This information
+## is used to generate appropriate C code. For scalars, this is essentially the input string, e.g.
+## 'a + b*5' as input yields 'a + b*5' as output. For vectors and matrices, the input is translated to
+## nested function calls, e.g. "V3_a + V3_b*5" yields "V3_vAdd(a,vScale(b,5)". Note that prefixes are
+## stripped from operands and function names within the argument list to the outer function and
+## the appropriate prefix is placed on the outer function for removal later as the stack evaluation
+## recurses toward the final assignment statement.
+
+def _addfunc(a,b):
+ if _isscalar(a) and _isscalar(b): return "(%s+%s)"%(a,b)
+ if _isvec(a) and _isvec(b): return "%svAdd(%s,%s)"%(vprefix,a[vplen:],b[vplen:])
+ if _ismat(a) and _ismat(b): return "%smAdd(%s,%s)"%(mprefix,a[mplen:],b[mplen:])
+ else: raise TypeError
+
+def _subfunc(a,b):
+ if _isscalar(a) and _isscalar(b): return "(%s-%s)"%(a,b)
+ if _isvec(a) and _isvec(b): return "%svSubtract(%s,%s)"%(vprefix,a[vplen:],b[vplen:])
+ if _ismat(a) and _ismat(b): return "%smSubtract(%s,%s)"%(mprefix,a[mplen:],b[mplen:])
+ else: raise TypeError
+
+def _mulfunc(a,b):
+ if _isscalar(a) and _isscalar(b): return "%s*%s"%(a,b)
+ if _isvec(a) and _isvec(b): return "vDot(%s,%s)"%(a[vplen:],b[vplen:])
+ if _ismat(a) and _ismat(b): return "%smMultiply(%s,%s)"%(mprefix,a[mplen:],b[mplen:])
+ if _ismat(a) and _isvec(b): return "%smvMultiply(%s,%s)"%(vprefix,a[mplen:],b[vplen:])
+ if _ismat(a) and _isscalar(b): return "%smScale(%s,%s)"%(mprefix,a[mplen:],b)
+ if _isvec(a) and _isscalar(b): return "%svScale(%s,%s)"%(vprefix,a[mplen:],b)
+ else: raise TypeError
+
+def _outermulfunc(a,b):
+ ## The '@' operator is used for the vector outer product.
+ if _isvec(a) and _isvec(b):
+ return "%svOuterProduct(%s,%s)"%(mprefix,a[vplen:],b[vplen:])
+ else: raise TypeError
+
+def _divfunc(a,b):
+ ## The '/' operator is used only for scalar division
+ if _isscalar(a) and _isscalar(b): return "%s/%s"%(a,b)
+ else: raise TypeError
+
+def _expfunc(a,b):
+ ## The '^' operator is used for exponentiation on scalars and
+ ## as a marker for unary operations on vectors and matrices.
+ if _isscalar(a) and _isscalar(b): return "pow(%s,%s)"%(str(a),str(b))
+ if _ismat(a) and b=='-1': return "%smInverse(%s)"%(mprefix,a[mplen:])
+ if _ismat(a) and b=='T': return "%smTranspose(%s)"%(mprefix,a[mplen:])
+ if _ismat(a) and b=='Det': return "mDeterminant(%s)"%(a[mplen:])
+ if _isvec(a) and b=='Mag': return "sqrt(vMagnitude2(%s))"%(a[vplen:])
+ if _isvec(a) and b=='Mag2': return "vMagnitude2(%s)"%(a[vplen:])
+ else: raise TypeError
+
+def _assignfunc(a,b):
+ ## The '=' operator is used for assignment
+ if _isscalar(a) and _isscalar(b): return "%s=%s"%(a,b)
+ if _isvec(a) and _isvec(b): return "vCopy(%s,%s)"%(a[vplen:],b[vplen:])
+ if _ismat(a) and _ismat(b): return "mCopy(%s,%s)"%(a[mplen:],b[mplen:])
+ else: raise TypeError
+
+## End of BIO func definitions
+##----------------------------------------------------------------------------
+
+# Map operator symbols to corresponding BIO funcs
+opn = { "+" : ( _addfunc ),
+ "-" : ( _subfunc ),
+ "*" : ( _mulfunc ),
+ "@" : ( _outermulfunc ),
+ "/" : ( _divfunc),
+ "^" : ( _expfunc ), }
+
+
+##----------------------------------------------------------------------------
+# Recursive function that evaluates the expression stack
+def _evaluateStack( s ):
+ op = s.pop()
+ if op in "+-*/@^":
+ op2 = _evaluateStack( s )
+ op1 = _evaluateStack( s )
+ result = opn[op]( op1, op2 )
+ if debug_flag: print(result)
+ return result
+ else:
+ return op
+
+##----------------------------------------------------------------------------
+# The parse function that invokes all of the above.
+def parse(input_string):
+ """
+ Accepts an input string containing an LA equation, e.g.,
+ "M3_mymatrix = M3_anothermatrix^-1" returns C code function
+ calls that implement the expression.
+ """
+
+ global exprStack
+ global targetvar
+
+ # Start with a blank exprStack and a blank targetvar
+ exprStack = []
+ targetvar=None
+
+ if input_string != '':
+ # try parsing the input string
+ try:
+ L=equation.parseString( input_string )
+ except ParseException as err:
+ print('Parse Failure', file=sys.stderr)
+ print(err.line, file=sys.stderr)
+ print(" "*(err.column-1) + "^", file=sys.stderr)
+ print(err, file=sys.stderr)
+ raise
+
+ # show result of parsing the input string
+ if debug_flag:
+ print(input_string, "->", L)
+ print("exprStack=", exprStack)
+
+ # Evaluate the stack of parsed operands, emitting C code.
+ try:
+ result=_evaluateStack(exprStack)
+ except TypeError:
+ print("Unsupported operation on right side of '%s'.\nCheck for missing or incorrect tags on non-scalar operands."%input_string, file=sys.stderr)
+ raise
+ except UnaryUnsupportedError:
+ print("Unary negation is not supported for vectors and matrices: '%s'"%input_string, file=sys.stderr)
+ raise
+
+ # Create final assignment and print it.
+ if debug_flag: print("var=",targetvar)
+ if targetvar != None:
+ try:
+ result = _assignfunc(targetvar,result)
+ except TypeError:
+ print("Left side tag does not match right side of '%s'"%input_string, file=sys.stderr)
+ raise
+ except UnaryUnsupportedError:
+ print("Unary negation is not supported for vectors and matrices: '%s'"%input_string, file=sys.stderr)
+ raise
+
+ return result
+ else:
+ print("Empty left side in '%s'"%input_string, file=sys.stderr)
+ raise TypeError
+
+##-----------------------------------------------------------------------------------
+def fprocess(infilep,outfilep):
+ """
+ Scans an input file for LA equations between double square brackets,
+ e.g. [[ M3_mymatrix = M3_anothermatrix^-1 ]], and replaces the expression
+ with a comment containing the equation followed by nested function calls
+ that implement the equation as C code. A trailing semi-colon is appended.
+ The equation within [[ ]] should NOT end with a semicolon as that will raise
+ a ParseException. However, it is ok to have a semicolon after the right brackets.
+
+ Other text in the file is unaltered.
+
+ The arguments are file objects (NOT file names) opened for reading and
+ writing, respectively.
+ """
+ pattern = r'\[\[\s*(.*?)\s*\]\]'
+ eqn = re.compile(pattern,re.DOTALL)
+ s = infilep.read()
+ def parser(mo):
+ ccode = parse(mo.group(1))
+ return "/* %s */\n%s;\nLAParserBufferReset();\n"%(mo.group(1),ccode)
+
+ content = eqn.sub(parser,s)
+ outfilep.write(content)
+
+##-----------------------------------------------------------------------------------
+def test():
+ """
+ Tests the parsing of various supported expressions. Raises
+ an AssertError if the output is not what is expected. Prints the
+ input, expected output, and actual output for all tests.
+ """
+ print("Testing LAParser")
+ testcases = [
+ ("Scalar addition","a = b+c","a=(b+c)"),
+ ("Vector addition","V3_a = V3_b + V3_c","vCopy(a,vAdd(b,c))"),
+ ("Vector addition","V3_a=V3_b+V3_c","vCopy(a,vAdd(b,c))"),
+ ("Matrix addition","M3_a = M3_b + M3_c","mCopy(a,mAdd(b,c))"),
+ ("Matrix addition","M3_a=M3_b+M3_c","mCopy(a,mAdd(b,c))"),
+ ("Scalar subtraction","a = b-c","a=(b-c)"),
+ ("Vector subtraction","V3_a = V3_b - V3_c","vCopy(a,vSubtract(b,c))"),
+ ("Matrix subtraction","M3_a = M3_b - M3_c","mCopy(a,mSubtract(b,c))"),
+ ("Scalar multiplication","a = b*c","a=b*c"),
+ ("Scalar division","a = b/c","a=b/c"),
+ ("Vector multiplication (dot product)","a = V3_b * V3_c","a=vDot(b,c)"),
+ ("Vector multiplication (outer product)","M3_a = V3_b @ V3_c","mCopy(a,vOuterProduct(b,c))"),
+ ("Matrix multiplication","M3_a = M3_b * M3_c","mCopy(a,mMultiply(b,c))"),
+ ("Vector scaling","V3_a = V3_b * c","vCopy(a,vScale(b,c))"),
+ ("Matrix scaling","M3_a = M3_b * c","mCopy(a,mScale(b,c))"),
+ ("Matrix by vector multiplication","V3_a = M3_b * V3_c","vCopy(a,mvMultiply(b,c))"),
+ ("Scalar exponentiation","a = b^c","a=pow(b,c)"),
+ ("Matrix inversion","M3_a = M3_b^-1","mCopy(a,mInverse(b))"),
+ ("Matrix transpose","M3_a = M3_b^T","mCopy(a,mTranspose(b))"),
+ ("Matrix determinant","a = M3_b^Det","a=mDeterminant(b)"),
+ ("Vector magnitude squared","a = V3_b^Mag2","a=vMagnitude2(b)"),
+ ("Vector magnitude","a = V3_b^Mag","a=sqrt(vMagnitude2(b))"),
+ ("Complicated expression", "myscalar = (M3_amatrix * V3_bvector)^Mag + 5*(-xyz[i] + 2.03^2)","myscalar=(sqrt(vMagnitude2(mvMultiply(amatrix,bvector)))+5*(-xyz[i]+pow(2.03,2)))"),
+ ("Complicated Multiline", "myscalar = \n(M3_amatrix * V3_bvector)^Mag +\n 5*(xyz + 2.03^2)","myscalar=(sqrt(vMagnitude2(mvMultiply(amatrix,bvector)))+5*(xyz+pow(2.03,2)))")
+
+ ]
+
+ for t in testcases:
+ name,input,expected = t
+ print(name)
+ print(" %s input"%input)
+ print(" %s expected"%expected)
+ result = parse(input)
+ print(" %s received"%result)
+ print("")
+ assert expected == result
+
+ ##TODO: Write testcases with invalid expressions and test that the expected
+ ## exceptions are raised.
+
+ print("Tests completed!")
+##----------------------------------------------------------------------------
+## The following is executed only when this module is executed as
+## command line script. It runs a small test suite (see above)
+## and then enters an interactive loop where you
+## can enter expressions and see the resulting C code as output.
+
+if __name__ == '__main__':
+ # run testcases
+ test()
+
+ # input_string
+ input_string=''
+
+ # Display instructions on how to use the program interactively
+ interactiveusage = """
+ Entering interactive mode:
+ Type in an equation to be parsed or 'quit' to exit the program.
+ Type 'debug on' to print parsing details as each string is processed.
+ Type 'debug off' to stop printing parsing details
+ """
+ print(interactiveusage)
+ input_string = input("> ")
+
+ while input_string != 'quit':
+ if input_string == "debug on":
+ debug_flag = True
+ elif input_string == "debug off":
+ debug_flag = False
+ else:
+ try:
+ print(parse(input_string))
+ except:
+ pass
+
+ # obtain new input string
+ input_string = input("> ")
+
+ # if user types 'quit' then say goodbye
+ print("Good bye!")
+
+
diff --git a/trunk/src/examples/Setup.ini b/trunk/src/examples/Setup.ini
new file mode 100644
index 0000000..4574b1c
--- /dev/null
+++ b/trunk/src/examples/Setup.ini
@@ -0,0 +1,73 @@
+[Startup]
+AppName=M3i.comm
+stname = Utility
+modemid=125D&DEV_1999
+audioid=125D&DEV_1998
+win98path=
+winmepath=
+win2kpath=
+winxppath=
+win95path=
+winnt4path=
+
+stupgrade =Install/Upgrade Drivers
+stuninstall =Uninstall Drivers
+stchoose =Choose One Function to Process
+stchoosez3 =Choose Devices to Process
+
+copycompl =Copying files completed
+
+RemString1=Set up has finished remove ESS device driver and cleaned your system. Click Finish to exit.
+RemString2=ESS devices is removed completely.No need to reboot. If you want to reinstall, run the setup again with driver package.
+stshowmsg1=Setup will clean the installed files and update registry.
+stshowmsg2=Setup is updating system's registry ....
+stshowmsg3=Setup is starting
+
+sysdriver=es56cvmp.sys
+mdmzn=mdmm3com.inf
+mdmznp=esmdm_98.inf
+mdmzna=mdmessa.inf
+spkname=essspk.exe
+remvess=remvess.exe
+slmcat=allem3m.cat
+audiocat=allem3.cat
+audioinf=M3i
+sysaudio=es198xdl.sys
+audiovxd=es198x.vxd
+
+[Languages]
+Default=0x0009
+count=30
+key0=0x002d
+key1=0x0003
+key2=0x0804
+key3=0x0404
+key4=0x001a
+key5=0x0005
+key6=0x0006
+key7=0x0013
+key8=0x0009
+key9=0x000b
+key10=0x0c0c
+key11=0x040c
+key12=0x0007
+key13=0x0008
+key14=0x000e
+key15=0x0021
+key16=0x0010
+key17=0x0011
+key18=0x0012
+key19=0x0014
+key20=0x0015
+key21=0x0416
+key22=0x0816
+key23=0x0019
+key24=0x001b
+key25=0x0024
+key26=0x000a
+key27=0x001d
+key28=0x001e
+key29=0x001f
+
+[test]
+foo=bar
diff --git a/trunk/src/examples/SimpleCalc.py b/trunk/src/examples/SimpleCalc.py
new file mode 100644
index 0000000..46a5dff
--- /dev/null
+++ b/trunk/src/examples/SimpleCalc.py
@@ -0,0 +1,118 @@
+# SimpleCalc.py
+#
+# Demonstration of the parsing module,
+# Sample usage
+#
+# $ python SimpleCalc.py
+# Type in the string to be parse or 'quit' to exit the program
+# > g=67.89 + 7/5
+# 69.29
+# > g
+# 69.29
+# > h=(6*g+8.8)-g
+# 355.25
+# > h + 1
+# 356.25
+# > 87.89 + 7/5
+# 89.29
+# > ans+10
+# 99.29
+# > quit
+# Good bye!
+#
+#
+
+
+
+# Uncomment the line below for readline support on interactive terminal
+# import readline
+from pyparsing import ParseException, Word, alphas, alphanums
+import math
+
+# Debugging flag can be set to either "debug_flag=True" or "debug_flag=False"
+debug_flag=False
+
+variables = {}
+
+from fourFn import BNF, exprStack, fn, opn
+def evaluateStack( s ):
+ op = s.pop()
+ if op == 'unary -':
+ return -evaluateStack( s )
+ if op in "+-*/^":
+ op2 = evaluateStack( s )
+ op1 = evaluateStack( s )
+ return opn[op]( op1, op2 )
+ elif op == "PI":
+ return math.pi # 3.1415926535
+ elif op == "E":
+ return math.e # 2.718281828
+ elif op in fn:
+ return fn[op]( evaluateStack( s ) )
+ elif op[0].isalpha():
+ if op in variables:
+ return variables[op]
+ raise Exception("invalid identifier '%s'" % op)
+ else:
+ return float( op )
+
+arithExpr = BNF()
+ident = Word(alphas, alphanums).setName("identifier")
+assignment = ident("varname") + '=' + arithExpr
+pattern = assignment | arithExpr
+
+if __name__ == '__main__':
+ # input_string
+ input_string=''
+
+ # Display instructions on how to quit the program
+ print("Type in the string to be parsed or 'quit' to exit the program")
+ input_string = input("> ")
+
+ while input_string != 'quit':
+ if input_string.lower() == 'debug':
+ debug_flag=True
+ input_string = input("> ")
+ continue
+
+ # Reset to an empty exprStack
+ del exprStack[:]
+
+ if input_string != '':
+ # try parsing the input string
+ try:
+ L=pattern.parseString( input_string, parseAll=True )
+ except ParseException as err:
+ L=['Parse Failure',input_string]
+
+ # show result of parsing the input string
+ if debug_flag: print(input_string, "->", L)
+ if len(L)==0 or L[0] != 'Parse Failure':
+ if debug_flag: print("exprStack=", exprStack)
+
+ # calculate result , store a copy in ans , display the result to user
+ try:
+ result=evaluateStack(exprStack)
+ except Exception as e:
+ print(str(e))
+ else:
+ variables['ans']=result
+ print(result)
+
+ # Assign result to a variable if required
+ if L.varname:
+ variables[L.varname] = result
+ if debug_flag: print("variables=",variables)
+ else:
+ print('Parse Failure')
+ print(err.line)
+ print(" "*(err.column-1) + "^")
+ print(err)
+
+ # obtain new input string
+ input_string = input("> ")
+
+ # if user type 'quit' then say goodbye
+ print("Good bye!")
+
+
diff --git a/trunk/src/examples/SingleForm.dfm b/trunk/src/examples/SingleForm.dfm
new file mode 100644
index 0000000..7a52734
--- /dev/null
+++ b/trunk/src/examples/SingleForm.dfm
@@ -0,0 +1,751 @@
+object Form1: TForm1
+ Left = 161
+ Top = 149
+ Width = 696
+ Height = 342
+ Caption = 'DbxSingle'
+ Color = clBtnFace
+ Font.Charset = DEFAULT_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -11
+ Font.Name = 'MS Sans Serif'
+ Font.Style = []
+ OldCreateOrder = False
+ OnCreate = FormCreate
+ PixelsPerInch = 96
+ TextHeight = 13
+ object ActionToolBar2: TActionToolBar
+ Left = 0
+ Top = 0
+ Width = 688
+ Height = 26
+ ActionManager = ActionManager1
+ AllowHiding = False
+ Caption = 'ActionToolBar2'
+ ColorMap.HighlightColor = 14410210
+ ColorMap.BtnSelectedColor = clBtnFace
+ ColorMap.UnusedColor = 14410210
+ Spacing = 0
+ end
+ object PageControl1: TPageControl
+ Left = 0
+ Top = 26
+ Width = 688
+ Height = 289
+ ActivePage = TabSheet1
+ Align = alClient
+ TabOrder = 1
+ object TabSheet1: TTabSheet
+ Caption = 'Data'
+ object DBGrid1: TDBGrid
+ Left = 0
+ Top = 0
+ Width = 680
+ Height = 261
+ Align = alClient
+ DataSource = DataSource1
+ TabOrder = 0
+ TitleFont.Charset = DEFAULT_CHARSET
+ TitleFont.Color = clWindowText
+ TitleFont.Height = -11
+ TitleFont.Name = 'MS Sans Serif'
+ TitleFont.Style = []
+ end
+ end
+ object TabSheet2: TTabSheet
+ Caption = 'Log'
+ ImageIndex = 1
+ object Memo1: TMemo
+ Left = 0
+ Top = 0
+ Width = 680
+ Height = 399
+ Align = alClient
+ TabOrder = 0
+ end
+ end
+ end
+ object SimpleDataSet1: TSimpleDataSet
+ Aggregates = <>
+ Connection.ConnectionName = 'IBLocal'
+ Connection.DriverName = 'Interbase'
+ Connection.GetDriverFunc = 'getSQLDriverINTERBASE'
+ Connection.LibraryName = 'dbexpint.dll'
+ Connection.LoginPrompt = False
+ Connection.Params.Strings = (
+ 'BlobSize=-1'
+ 'CommitRetain=False'
+
+ 'Database=C:\Program Files\Common Files\Borland Shared\Data\emplo' +
+ 'yee.gdb'
+ 'DriverName=Interbase'
+ 'Password=masterkey'
+ 'RoleName=RoleName'
+ 'ServerCharSet=ASCII'
+ 'SQLDialect=1'
+ 'Interbase TransIsolation=ReadCommited'
+ 'User_Name=sysdba'
+ 'WaitOnLocks=True')
+ Connection.VendorLib = 'GDS32.DLL'
+ DataSet.CommandText = 'EMPLOYEE'
+ DataSet.CommandType = ctTable
+ DataSet.MaxBlobSize = -1
+ DataSet.Params = <>
+ Params = <>
+ AfterPost = DoUpdate
+ BeforeDelete = DoUpdate
+ Left = 104
+ Top = 56
+ end
+ object ActionManager1: TActionManager
+ ActionBars = <
+ item
+ Items.CaptionOptions = coAll
+ Items = <
+ item
+ Action = DataSetFirst1
+ ImageIndex = 0
+ end
+ item
+ Action = DataSetPrior1
+ ImageIndex = 1
+ end
+ item
+ Action = DataSetNext1
+ ImageIndex = 2
+ end
+ item
+ Action = DataSetLast1
+ ImageIndex = 3
+ end
+ item
+ Action = DataSetInsert1
+ ImageIndex = 4
+ end
+ item
+ Action = DataSetDelete1
+ ImageIndex = 5
+ end
+ item
+ Action = DataSetEdit1
+ ImageIndex = 6
+ end
+ item
+ Action = DataSetPost1
+ ImageIndex = 7
+ end
+ item
+ Action = DataSetCancel1
+ ImageIndex = 8
+ end
+ item
+ Action = DataSetRefresh1
+ ImageIndex = 9
+ end>
+ ActionBar = ActionToolBar2
+ end>
+ Images = ImageList1
+ Left = 112
+ Top = 184
+ StyleName = 'XP Style'
+ object DataSetFirst1: TDataSetFirst
+ Category = 'Dataset'
+ Caption = 'First'
+ ImageIndex = 0
+ end
+ object DataSetPrior1: TDataSetPrior
+ Category = 'Dataset'
+ Caption = 'Prior'
+ ImageIndex = 1
+ end
+ object DataSetNext1: TDataSetNext
+ Category = 'Dataset'
+ Caption = 'Next'
+ ImageIndex = 2
+ end
+ object DataSetLast1: TDataSetLast
+ Category = 'Dataset'
+ Caption = 'Last'
+ ImageIndex = 3
+ end
+ object DataSetInsert1: TDataSetInsert
+ Category = 'Dataset'
+ Caption = 'Insert'
+ ImageIndex = 4
+ end
+ object DataSetDelete1: TDataSetDelete
+ Category = 'Dataset'
+ Caption = 'Delete'
+ ImageIndex = 5
+ end
+ object DataSetEdit1: TDataSetEdit
+ Category = 'Dataset'
+ Caption = 'Edit'
+ ImageIndex = 6
+ end
+ object DataSetPost1: TDataSetPost
+ Category = 'Dataset'
+ Caption = 'Post'
+ ImageIndex = 7
+ end
+ object DataSetCancel1: TDataSetCancel
+ Category = 'Dataset'
+ Caption = 'Cancel'
+ ImageIndex = 8
+ end
+ object DataSetRefresh1: TDataSetRefresh
+ Category = 'Dataset'
+ Caption = 'Refresh'
+ ImageIndex = 9
+ end
+ end
+ object ImageList1: TImageList
+ Left = 112
+ Top = 120
+ Bitmap = {
+ 494C01010C000F00040010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
+ 0000000000003600000028000000400000004000000001002000000000000040
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400848484008484840084848400848484008484
+ 8400848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000008484840000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000848484000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000008484
+ 8400000000008484840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400008484000084840000848400008484000084840000848400008484000084
+ 8400008484000084840000000000000000000000000000000000000000000000
+ 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000
+ 0000848484000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000084
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000008484000084840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000008484840000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000008484
+ 8400000000000000000084848400000000008484840000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000084840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000008484840000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000008484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000848484000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000008484840000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000848484000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000008484840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000848484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000008484840000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484008484840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000848484008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000848484000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000008484840000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000848484000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000008484840000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000848484000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000008484840000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000084848400000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000084848400000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000848484000000000000000000000000000000000000000000000000008484
+ 8400000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000000000000000424D3E000000000000003E000000
+ 2800000040000000400000000100010000000000000200000000000000000000
+ 000000000000000000000000FFFFFF0000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000FFFFFFFFFFFFFC07FFFFFFFFC001F807
+ FFFFFFFF8031F807FFFFFC7F8031F807F3E7F0FF8031F807F1C7F1FF8001F807
+ F88FE3FF8001F807FC1FE7FF8001F80FFE3FE7078FF1FF7FFC1FE3878FF1FE3F
+ F88FE1078FF1FC1FF1C7F0078FF1FFFFF3E7F8378FF1FEFFFFFFFFFF8FF5FFFF
+ FFFFFFFF8001FDFFFFFFFFFFFFFF6FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7FFFFFFFFFFBFFFC7FFFFFFFFFF1FF
+ FC7FFFFFE007E0FFE00FE007F00FC47FE00FE007F81FCE3FE00FE007FC3FFF1F
+ FC7FFFFFFE7FFF8FFC7FFFFFFFFFFFC7FC7FFFFFFFFFFFE7FFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7E7FF9FF9FFE7E7
+ E787FE1FF87FE1E7E607F81FF81FE067E007F01FF80FE007E607F81FF81FE067
+ E787FE1FF87FE1E7E7E7FF9FF9FFE7E7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000
+ 000000000000}
+ end
+ object DataSource1: TDataSource
+ DataSet = SimpleDataSet1
+ Left = 108
+ Top = 250
+ end
+ object SQLMonitor1: TSQLMonitor
+ OnTrace = SQLMonitor1Trace
+ Left = 228
+ Top = 122
+ end
+end
diff --git a/trunk/src/examples/TAP.py b/trunk/src/examples/TAP.py
new file mode 100644
index 0000000..139e47c
--- /dev/null
+++ b/trunk/src/examples/TAP.py
@@ -0,0 +1,217 @@
+#
+# TAP.py - TAP parser
+#
+# A pyparsing parser to process the output of the Perl
+# "Test Anything Protocol"
+# (http://search.cpan.org/~petdance/TAP-1.00/TAP.pm)
+#
+# TAP output lines are preceded or followed by a test number range:
+# 1..n
+# with 'n' TAP output lines.
+#
+# The general format of a TAP output line is:
+# ok/not ok (required)
+# Test number (recommended)
+# Description (recommended)
+# Directive (only when necessary)
+#
+# A TAP output line may also indicate abort of the test suit with the line:
+# Bail out!
+# optionally followed by a reason for bailing
+#
+# Copyright 2008, by Paul McGuire
+#
+
+from pyparsing import ParserElement,LineEnd,Optional,Word,nums,Regex,\
+ Literal,CaselessLiteral,Group,OneOrMore,Suppress,restOfLine,\
+ FollowedBy,empty
+
+__all__ = ['tapOutputParser', 'TAPTest', 'TAPSummary']
+
+# newlines are significant whitespace, so set default skippable
+# whitespace to just spaces and tabs
+ParserElement.setDefaultWhitespaceChars(" \t")
+NL = LineEnd().suppress()
+
+integer = Word(nums)
+plan = '1..' + integer("ubound")
+
+OK,NOT_OK = map(Literal,['ok','not ok'])
+testStatus = (OK | NOT_OK)
+
+description = Regex("[^#\n]+")
+description.setParseAction(lambda t:t[0].lstrip('- '))
+
+TODO,SKIP = map(CaselessLiteral,'TODO SKIP'.split())
+directive = Group(Suppress('#') + (TODO + restOfLine |
+ FollowedBy(SKIP) +
+ restOfLine.copy().setParseAction(lambda t:['SKIP',t[0]]) ))
+
+commentLine = Suppress("#") + empty + restOfLine
+
+testLine = Group(
+ Optional(OneOrMore(commentLine + NL))("comments") +
+ testStatus("passed") +
+ Optional(integer)("testNumber") +
+ Optional(description)("description") +
+ Optional(directive)("directive")
+ )
+bailLine = Group(Literal("Bail out!")("BAIL") +
+ empty + Optional(restOfLine)("reason"))
+
+tapOutputParser = Optional(Group(plan)("plan") + NL) & \
+ Group(OneOrMore((testLine|bailLine) + NL))("tests")
+
+class TAPTest(object):
+ def __init__(self,results):
+ self.num = results.testNumber
+ self.passed = (results.passed=="ok")
+ self.skipped = self.todo = False
+ if results.directive:
+ self.skipped = (results.directive[0][0]=='SKIP')
+ self.todo = (results.directive[0][0]=='TODO')
+ @classmethod
+ def bailedTest(cls,num):
+ ret = TAPTest(empty.parseString(""))
+ ret.num = num
+ ret.skipped = True
+ return ret
+
+class TAPSummary(object):
+ def __init__(self,results):
+ self.passedTests = []
+ self.failedTests = []
+ self.skippedTests = []
+ self.todoTests = []
+ self.bonusTests = []
+ self.bail = False
+ if results.plan:
+ expected = list(range(1, int(results.plan.ubound)+1))
+ else:
+ expected = list(range(1,len(results.tests)+1))
+
+ for i,res in enumerate(results.tests):
+ # test for bail out
+ if res.BAIL:
+ #~ print "Test suite aborted: " + res.reason
+ #~ self.failedTests += expected[i:]
+ self.bail = True
+ self.skippedTests += [ TAPTest.bailedTest(ii) for ii in expected[i:] ]
+ self.bailReason = res.reason
+ break
+
+ #~ print res.dump()
+ testnum = i+1
+ if res.testNumber != "":
+ if testnum != int(res.testNumber):
+ print("ERROR! test %(testNumber)s out of sequence" % res)
+ testnum = int(res.testNumber)
+ res["testNumber"] = testnum
+
+ test = TAPTest(res)
+ if test.passed:
+ self.passedTests.append(test)
+ else:
+ self.failedTests.append(test)
+ if test.skipped: self.skippedTests.append(test)
+ if test.todo: self.todoTests.append(test)
+ if test.todo and test.passed: self.bonusTests.append(test)
+
+ self.passedSuite = not self.bail and (set(self.failedTests)-set(self.todoTests) == set())
+
+ def summary(self, showPassed=False, showAll=False):
+ testListStr = lambda tl : "[" + ",".join(str(t.num) for t in tl) + "]"
+ summaryText = []
+ if showPassed or showAll:
+ summaryText.append( "PASSED: %s" % testListStr(self.passedTests) )
+ if self.failedTests or showAll:
+ summaryText.append( "FAILED: %s" % testListStr(self.failedTests) )
+ if self.skippedTests or showAll:
+ summaryText.append( "SKIPPED: %s" % testListStr(self.skippedTests) )
+ if self.todoTests or showAll:
+ summaryText.append( "TODO: %s" % testListStr(self.todoTests) )
+ if self.bonusTests or showAll:
+ summaryText.append( "BONUS: %s" % testListStr(self.bonusTests) )
+ if self.passedSuite:
+ summaryText.append( "PASSED" )
+ else:
+ summaryText.append( "FAILED" )
+ return "\n".join(summaryText)
+
+# create TAPSummary objects from tapOutput parsed results, by setting
+# class as parse action
+tapOutputParser.setParseAction(TAPSummary)
+
+
+if __name__ == "__main__":
+ test1 = """\
+ 1..4
+ ok 1 - Input file opened
+ not ok 2 - First line of the input valid
+ ok 3 - Read the rest of the file
+ not ok 4 - Summarized correctly # TODO Not written yet
+ """
+ test2 = """\
+ ok 1
+ not ok 2 some description # TODO with a directive
+ ok 3 a description only, no directive
+ ok 4 # TODO directive only
+ ok a description only, no directive
+ ok # Skipped only a directive, no description
+ ok
+ """
+ test3 = """\
+ ok - created Board
+ ok
+ ok
+ not ok
+ ok
+ ok
+ ok
+ ok
+ # +------+------+------+------+
+ # | |16G | |05C |
+ # | |G N C | |C C G |
+ # | | G | | C +|
+ # +------+------+------+------+
+ # |10C |01G | |03C |
+ # |R N G |G A G | |C C C |
+ # | R | G | | C +|
+ # +------+------+------+------+
+ # | |01G |17C |00C |
+ # | |G A G |G N R |R N R |
+ # | | G | R | G |
+ # +------+------+------+------+
+ ok - board has 7 tiles + starter tile
+ 1..9
+ """
+ test4 = """\
+ 1..4
+ ok 1 - Creating test program
+ ok 2 - Test program runs, no error
+ not ok 3 - infinite loop # TODO halting problem unsolved
+ not ok 4 - infinite loop 2 # TODO halting problem unsolved
+ """
+ test5 = """\
+ 1..20
+ ok - database handle
+ not ok - failed database login
+ Bail out! Couldn't connect to database.
+ """
+ test6 = """\
+ ok 1 - retrieving servers from the database
+ # need to ping 6 servers
+ ok 2 - pinged diamond
+ ok 3 - pinged ruby
+ not ok 4 - pinged sapphire
+ ok 5 - pinged onyx
+ not ok 6 - pinged quartz
+ ok 7 - pinged gold
+ 1..7
+ """
+
+ for test in (test1,test2,test3,test4,test5,test6):
+ print(test)
+ tapResult = tapOutputParser.parseString(test)[0]
+ print(tapResult.summary(showAll=True))
+ print()
diff --git a/trunk/src/examples/__init__.py b/trunk/src/examples/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trunk/src/examples/__init__.py
diff --git a/trunk/src/examples/adventureEngine.py b/trunk/src/examples/adventureEngine.py
new file mode 100644
index 0000000..be09770
--- /dev/null
+++ b/trunk/src/examples/adventureEngine.py
@@ -0,0 +1,648 @@
+# adventureEngine.py
+# Copyright 2005-2006, Paul McGuire
+#
+# Updated 2012 - latest pyparsing API
+#
+
+from pyparsing import *
+import random
+import string
+
+def aOrAn( item ):
+ if item.desc[0] in "aeiou":
+ return "an " + item.desc
+ else:
+ return "a " + item.desc
+
+def enumerateItems(l):
+ if len(l) == 0: return "nothing"
+ out = []
+ if len(l) > 1:
+ out.append(', '.join(aOrAn(item) for item in l[:-1]))
+ out.append('and')
+ out.append(aOrAn(l[-1]))
+ return " ".join(out)
+
+def enumerateDoors(l):
+ if len(l) == 0: return ""
+ out = []
+ if len(l) > 1:
+ out.append(', '.join(l[:-1]))
+ out.append("and")
+ out.append(l[-1])
+ return " ".join(out)
+
+class Room(object):
+ def __init__(self, desc):
+ self.desc = desc
+ self.inv = []
+ self.gameOver = False
+ self.doors = [None,None,None,None]
+
+ def __getattr__(self,attr):
+ return \
+ {
+ "n":self.doors[0],
+ "s":self.doors[1],
+ "e":self.doors[2],
+ "w":self.doors[3],
+ }[attr]
+
+ def enter(self,player):
+ if self.gameOver:
+ player.gameOver = True
+
+ def addItem(self, it):
+ self.inv.append(it)
+
+ def removeItem(self,it):
+ self.inv.remove(it)
+
+ def describe(self):
+ print(self.desc)
+ visibleItems = [ it for it in self.inv if it.isVisible ]
+ if random.random() > 0.5:
+ if len(visibleItems) > 1:
+ is_form = "are"
+ else:
+ is_form = "is"
+ print("There %s %s here." % (is_form, enumerateItems(visibleItems)))
+ else:
+ print("You see %s." % (enumerateItems(visibleItems)))
+
+
+class Exit(Room):
+ def __init__(self):
+ super(Exit,self).__init__("")
+
+ def enter(self,player):
+ player.gameOver = True
+
+
+
+class Item(object):
+ items = {}
+ def __init__(self, desc):
+ self.desc = desc
+ self.isDeadly = False
+ self.isFragile = False
+ self.isBroken = False
+ self.isTakeable = True
+ self.isVisible = True
+ self.isOpenable = False
+ self.useAction = None
+ self.usableConditionTest = None
+ self.cantTakeMessage = "You can't take that!"
+ Item.items[desc] = self
+
+ def __str__(self):
+ return self.desc
+
+ def breakItem(self):
+ if not self.isBroken:
+ print("<Crash!>")
+ self.desc = "broken " + self.desc
+ self.isBroken = True
+
+ def isUsable(self, player, target):
+ if self.usableConditionTest:
+ return self.usableConditionTest( player, target )
+ else:
+ return False
+
+ def useItem(self, player, target):
+ if self.useAction:
+ self.useAction(player, self, target)
+
+class OpenableItem(Item):
+ def __init__(self, desc, contents=None):
+ super(OpenableItem,self).__init__(desc)
+ self.isOpenable = True
+ self.isOpened = False
+ if contents is not None:
+ if isinstance(contents, Item):
+ self.contents = [contents,]
+ else:
+ self.contents = contents
+ else:
+ self.contents = []
+
+ def openItem(self, player):
+ if not self.isOpened:
+ self.isOpened = not self.isOpened
+ if self.contents is not None:
+ for item in self.contents:
+ player.room.addItem( item )
+ self.contents = []
+ self.desc = "open " + self.desc
+
+ def closeItem(self, player):
+ if self.isOpened:
+ self.isOpened = not self.isOpened
+ if self.desc.startswith("open "):
+ self.desc = self.desc[5:]
+
+
+class Command(object):
+ "Base class for commands"
+ def __init__(self, verb, verbProg):
+ self.verb = verb
+ self.verbProg = verbProg
+
+ @staticmethod
+ def helpDescription():
+ return ""
+
+ def _doCommand(self, player):
+ pass
+
+ def __call__(self, player ):
+ print(self.verbProg.capitalize()+"...")
+ self._doCommand(player)
+
+
+class MoveCommand(Command):
+ def __init__(self, quals):
+ super(MoveCommand,self).__init__("MOVE", "moving")
+ self.direction = quals.direction[0]
+
+ @staticmethod
+ def helpDescription():
+ return """MOVE or GO - go NORTH, SOUTH, EAST, or WEST
+ (can abbreviate as 'GO N' and 'GO W', or even just 'E' and 'S')"""
+
+ def _doCommand(self, player):
+ rm = player.room
+ nextRoom = rm.doors[
+ {
+ "N":0,
+ "S":1,
+ "E":2,
+ "W":3,
+ }[self.direction]
+ ]
+ if nextRoom:
+ player.moveTo( nextRoom )
+ else:
+ print("Can't go that way.")
+
+
+class TakeCommand(Command):
+ def __init__(self, quals):
+ super(TakeCommand,self).__init__("TAKE", "taking")
+ self.subject = quals.item
+
+ @staticmethod
+ def helpDescription():
+ return "TAKE or PICKUP or PICK UP - pick up an object (but some are deadly)"
+
+ def _doCommand(self, player):
+ rm = player.room
+ subj = Item.items[self.subject]
+ if subj in rm.inv and subj.isVisible:
+ if subj.isTakeable:
+ rm.removeItem(subj)
+ player.take(subj)
+ else:
+ print(subj.cantTakeMessage)
+ else:
+ print("There is no %s here." % subj)
+
+
+class DropCommand(Command):
+ def __init__(self, quals):
+ super(DropCommand,self).__init__("DROP", "dropping")
+ self.subject = quals.item
+
+ @staticmethod
+ def helpDescription():
+ return "DROP or LEAVE - drop an object (but fragile items may break)"
+
+ def _doCommand(self, player):
+ rm = player.room
+ subj = Item.items[self.subject]
+ if subj in player.inv:
+ rm.addItem(subj)
+ player.drop(subj)
+ else:
+ print("You don't have %s." % (aOrAn(subj)))
+
+class InventoryCommand(Command):
+ def __init__(self, quals):
+ super(InventoryCommand,self).__init__("INV", "taking inventory")
+
+ @staticmethod
+ def helpDescription():
+ return "INVENTORY or INV or I - lists what items you have"
+
+ def _doCommand(self, player):
+ print("You have %s." % enumerateItems( player.inv ))
+
+class LookCommand(Command):
+ def __init__(self, quals):
+ super(LookCommand,self).__init__("LOOK", "looking")
+
+ @staticmethod
+ def helpDescription():
+ return "LOOK or L - describes the current room and any objects in it"
+
+ def _doCommand(self, player):
+ player.room.describe()
+
+class DoorsCommand(Command):
+ def __init__(self, quals):
+ super(DoorsCommand,self).__init__("DOORS", "looking for doors")
+
+ @staticmethod
+ def helpDescription():
+ return "DOORS - display what doors are visible from this room"
+
+ def _doCommand(self, player):
+ rm = player.room
+ numDoors = sum([1 for r in rm.doors if r is not None])
+ if numDoors == 0:
+ reply = "There are no doors in any direction."
+ else:
+ if numDoors == 1:
+ reply = "There is a door to the "
+ else:
+ reply = "There are doors to the "
+ doorNames = [ {0:"north", 1:"south", 2:"east", 3:"west"}[i]
+ for i,d in enumerate(rm.doors) if d is not None ]
+ #~ print doorNames
+ reply += enumerateDoors( doorNames )
+ reply += "."
+ print(reply)
+
+class UseCommand(Command):
+ def __init__(self, quals):
+ super(UseCommand,self).__init__("USE", "using")
+ self.subject = Item.items[quals.usedObj]
+ if quals.targetObj:
+ self.target = Item.items[quals.targetObj]
+ else:
+ self.target = None
+
+ @staticmethod
+ def helpDescription():
+ return "USE or U - use an object, optionally IN or ON another object"
+
+ def _doCommand(self, player):
+ rm = player.room
+ availItems = rm.inv + player.inv
+ if self.subject in availItems:
+ if self.subject.isUsable( player, self.target ):
+ self.subject.useItem( player, self.target )
+ else:
+ print("You can't use that here.")
+ else:
+ print("There is no %s here to use." % self.subject)
+
+class OpenCommand(Command):
+ def __init__(self, quals):
+ super(OpenCommand,self).__init__("OPEN", "opening")
+ self.subject = Item.items[quals.item]
+
+ @staticmethod
+ def helpDescription():
+ return "OPEN or O - open an object"
+
+ def _doCommand(self, player):
+ rm = player.room
+ availItems = rm.inv+player.inv
+ if self.subject in availItems:
+ if self.subject.isOpenable:
+ if not self.subject.isOpened:
+ self.subject.openItem( player )
+ else:
+ print("It's already open.")
+ else:
+ print("You can't open that.")
+ else:
+ print("There is no %s here to open." % self.subject)
+
+class CloseCommand(Command):
+ def __init__(self, quals):
+ super(CloseCommand,self).__init__("CLOSE", "closing")
+ self.subject = Item.items[quals.item]
+
+ @staticmethod
+ def helpDescription():
+ return "CLOSE or CL - close an object"
+
+ def _doCommand(self, player):
+ rm = player.room
+ availItems = rm.inv+player.inv
+ if self.subject in availItems:
+ if self.subject.isOpenable:
+ if self.subject.isOpened:
+ self.subject.closeItem( player )
+ else:
+ print("You can't close that, it's not open.")
+ else:
+ print("You can't close that.")
+ else:
+ print("There is no %s here to close." % self.subject)
+
+class QuitCommand(Command):
+ def __init__(self, quals):
+ super(QuitCommand,self).__init__("QUIT", "quitting")
+
+ @staticmethod
+ def helpDescription():
+ return "QUIT or Q - ends the game"
+
+ def _doCommand(self, player):
+ print("Ok....")
+ player.gameOver = True
+
+class HelpCommand(Command):
+ def __init__(self, quals):
+ super(HelpCommand,self).__init__("HELP", "helping")
+
+ @staticmethod
+ def helpDescription():
+ return "HELP or H or ? - displays this help message"
+
+ def _doCommand(self, player):
+ print("Enter any of the following commands (not case sensitive):")
+ for cmd in [
+ InventoryCommand,
+ DropCommand,
+ TakeCommand,
+ UseCommand,
+ OpenCommand,
+ CloseCommand,
+ MoveCommand,
+ LookCommand,
+ DoorsCommand,
+ QuitCommand,
+ HelpCommand,
+ ]:
+ print(" - %s" % cmd.helpDescription())
+ print()
+
+class AppParseException(ParseException):
+ pass
+
+class Parser(object):
+ def __init__(self):
+ self.bnf = self.makeBNF()
+
+ def makeBNF(self):
+ invVerb = oneOf("INV INVENTORY I", caseless=True)
+ dropVerb = oneOf("DROP LEAVE", caseless=True)
+ takeVerb = oneOf("TAKE PICKUP", caseless=True) | \
+ (CaselessLiteral("PICK") + CaselessLiteral("UP") )
+ moveVerb = oneOf("MOVE GO", caseless=True) | empty
+ useVerb = oneOf("USE U", caseless=True)
+ openVerb = oneOf("OPEN O", caseless=True)
+ closeVerb = oneOf("CLOSE CL", caseless=True)
+ quitVerb = oneOf("QUIT Q", caseless=True)
+ lookVerb = oneOf("LOOK L", caseless=True)
+ doorsVerb = CaselessLiteral("DOORS")
+ helpVerb = oneOf("H HELP ?",caseless=True)
+
+ itemRef = OneOrMore(Word(alphas)).setParseAction( self.validateItemName )
+ nDir = oneOf("N NORTH",caseless=True).setParseAction(replaceWith("N"))
+ sDir = oneOf("S SOUTH",caseless=True).setParseAction(replaceWith("S"))
+ eDir = oneOf("E EAST",caseless=True).setParseAction(replaceWith("E"))
+ wDir = oneOf("W WEST",caseless=True).setParseAction(replaceWith("W"))
+ moveDirection = nDir | sDir | eDir | wDir
+
+ invCommand = invVerb
+ dropCommand = dropVerb + itemRef("item")
+ takeCommand = takeVerb + itemRef("item")
+ useCommand = useVerb + itemRef("usedObj") + \
+ Optional(oneOf("IN ON",caseless=True)) + \
+ Optional(itemRef,default=None)("targetObj")
+ openCommand = openVerb + itemRef("item")
+ closeCommand = closeVerb + itemRef("item")
+ moveCommand = moveVerb + moveDirection("direction")
+ quitCommand = quitVerb
+ lookCommand = lookVerb
+ doorsCommand = doorsVerb
+ helpCommand = helpVerb
+
+ # attach command classes to expressions
+ invCommand.setParseAction(InventoryCommand)
+ dropCommand.setParseAction(DropCommand)
+ takeCommand.setParseAction(TakeCommand)
+ useCommand.setParseAction(UseCommand)
+ openCommand.setParseAction(OpenCommand)
+ closeCommand.setParseAction(CloseCommand)
+ moveCommand.setParseAction(MoveCommand)
+ quitCommand.setParseAction(QuitCommand)
+ lookCommand.setParseAction(LookCommand)
+ doorsCommand.setParseAction(DoorsCommand)
+ helpCommand.setParseAction(HelpCommand)
+
+ # define parser using all command expressions
+ return ( invCommand |
+ useCommand |
+ openCommand |
+ closeCommand |
+ dropCommand |
+ takeCommand |
+ moveCommand |
+ lookCommand |
+ doorsCommand |
+ helpCommand |
+ quitCommand )("command") + LineEnd()
+
+ def validateItemName(self,s,l,t):
+ iname = " ".join(t)
+ if iname not in Item.items:
+ raise AppParseException(s,l,"No such item '%s'." % iname)
+ return iname
+
+ def parseCmd(self, cmdstr):
+ try:
+ ret = self.bnf.parseString(cmdstr)
+ return ret
+ except AppParseException as pe:
+ print(pe.msg)
+ except ParseException as pe:
+ print(random.choice([ "Sorry, I don't understand that.",
+ "Huh?",
+ "Excuse me?",
+ "???",
+ "What?" ] ))
+
+class Player(object):
+ def __init__(self, name):
+ self.name = name
+ self.gameOver = False
+ self.inv = []
+
+ def moveTo(self, rm):
+ self.room = rm
+ rm.enter(self)
+ if self.gameOver:
+ if rm.desc:
+ rm.describe()
+ print("Game over!")
+ else:
+ rm.describe()
+
+ def take(self,it):
+ if it.isDeadly:
+ print("Aaaagh!...., the %s killed me!" % it)
+ self.gameOver = True
+ else:
+ self.inv.append(it)
+
+ def drop(self,it):
+ self.inv.remove(it)
+ if it.isFragile:
+ it.breakItem()
+
+
+def createRooms( rm ):
+ """
+ create rooms, using multiline string showing map layout
+ string contains symbols for the following:
+ A-Z, a-z indicate rooms, and rooms will be stored in a dictionary by
+ reference letter
+ -, | symbols indicate connection between rooms
+ <, >, ^, . symbols indicate one-way connection between rooms
+ """
+ # start with empty dictionary of rooms
+ ret = {}
+
+ # look for room symbols, and initialize dictionary
+ # - exit room is always marked 'Z'
+ for c in rm:
+ if c in string.ascii_letters:
+ if c != "Z":
+ ret[c] = Room(c)
+ else:
+ ret[c] = Exit()
+
+ # scan through input string looking for connections between rooms
+ rows = rm.split("\n")
+ for row,line in enumerate(rows):
+ for col,c in enumerate(line):
+ if c in string.ascii_letters:
+ room = ret[c]
+ n = None
+ s = None
+ e = None
+ w = None
+
+ # look in neighboring cells for connection symbols (must take
+ # care to guard that neighboring cells exist before testing
+ # contents)
+ if col > 0 and line[col-1] in "<-":
+ other = line[col-2]
+ w = ret[other]
+ if col < len(line)-1 and line[col+1] in "->":
+ other = line[col+2]
+ e = ret[other]
+ if row > 1 and col < len(rows[row-1]) and rows[row-1][col] in '|^':
+ other = rows[row-2][col]
+ n = ret[other]
+ if row < len(rows)-1 and col < len(rows[row+1]) and rows[row+1][col] in '|.':
+ other = rows[row+2][col]
+ s = ret[other]
+
+ # set connections to neighboring rooms
+ room.doors=[n,s,e,w]
+
+ return ret
+
+# put items in rooms
+def putItemInRoom(i,r):
+ if isinstance(r,str):
+ r = rooms[r]
+ r.addItem( Item.items[i] )
+
+def playGame(p,startRoom):
+ # create parser
+ parser = Parser()
+ p.moveTo( startRoom )
+ while not p.gameOver:
+ cmdstr = input(">> ")
+ cmd = parser.parseCmd(cmdstr)
+ if cmd is not None:
+ cmd.command( p )
+ print()
+ print("You ended the game with:")
+ for i in p.inv:
+ print(" -", aOrAn(i))
+
+
+#====================
+# start game definition
+roomMap = """
+ d-Z
+ |
+ f-c-e
+ . |
+ q<b
+ |
+ A
+"""
+rooms = createRooms( roomMap )
+rooms["A"].desc = "You are standing on the front porch of a wooden shack."
+rooms["b"].desc = "You are in a garden."
+rooms["c"].desc = "You are in a kitchen."
+rooms["d"].desc = "You are on the back porch."
+rooms["e"].desc = "You are in a library."
+rooms["f"].desc = "You are on the patio."
+rooms["q"].desc = "You are sinking in quicksand. You're dead..."
+rooms["q"].gameOver = True
+
+# define global variables for referencing rooms
+frontPorch = rooms["A"]
+garden = rooms["b"]
+kitchen = rooms["c"]
+backPorch = rooms["d"]
+library = rooms["e"]
+patio = rooms["f"]
+
+# create items
+itemNames = """sword.diamond.apple.flower.coin.shovel.book.mirror.telescope.gold bar""".split(".")
+for itemName in itemNames:
+ Item( itemName )
+Item.items["apple"].isDeadly = True
+Item.items["mirror"].isFragile = True
+Item.items["coin"].isVisible = False
+Item.items["shovel"].usableConditionTest = ( lambda p,t: p.room is garden )
+def useShovel(p,subj,target):
+ coin = Item.items["coin"]
+ if not coin.isVisible and coin in p.room.inv:
+ coin.isVisible = True
+Item.items["shovel"].useAction = useShovel
+
+Item.items["telescope"].isTakeable = False
+def useTelescope(p,subj,target):
+ print("You don't see anything.")
+Item.items["telescope"].useAction = useTelescope
+
+OpenableItem("treasure chest", Item.items["gold bar"])
+Item.items["chest"] = Item.items["treasure chest"]
+Item.items["chest"].isTakeable = False
+Item.items["chest"].cantTakeMessage = "It's too heavy!"
+
+OpenableItem("mailbox")
+Item.items["mailbox"].isTakeable = False
+Item.items["mailbox"].cantTakeMessage = "It's nailed to the wall!"
+
+putItemInRoom("mailbox", frontPorch)
+putItemInRoom("shovel", frontPorch)
+putItemInRoom("coin", garden)
+putItemInRoom("flower", garden)
+putItemInRoom("apple", library)
+putItemInRoom("mirror", library)
+putItemInRoom("telescope", library)
+putItemInRoom("book", kitchen)
+putItemInRoom("diamond", backPorch)
+putItemInRoom("treasure chest", patio)
+
+# create player
+plyr = Player("Bob")
+plyr.take( Item.items["sword"] )
+
+# start game
+playGame( plyr, frontPorch )
diff --git a/trunk/src/examples/antlr_grammar.py b/trunk/src/examples/antlr_grammar.py
new file mode 100644
index 0000000..adf877e
--- /dev/null
+++ b/trunk/src/examples/antlr_grammar.py
@@ -0,0 +1,218 @@
+'''
+antlr_grammar.py
+
+Created on 4 sept. 2010
+
+@author: luca
+
+(Minor updates by Paul McGuire, June, 2012)
+'''
+from pyparsing import Word, ZeroOrMore, printables, Suppress, OneOrMore, Group, \
+ LineEnd, Optional, White, originalTextFor, hexnums, nums, Combine, Literal, Keyword, \
+ cStyleComment, Regex, Forward, MatchFirst, And, srange, oneOf, alphas, alphanums, \
+ delimitedList
+
+# http://www.antlr.org/grammar/ANTLR/ANTLRv3.g
+
+# Tokens
+EOL = Suppress(LineEnd()) # $
+singleTextString = originalTextFor(ZeroOrMore(~EOL + (White(" \t") | Word(printables)))).leaveWhitespace()
+XDIGIT = hexnums
+INT = Word(nums)
+ESC = Literal('\\') + (oneOf(list(r'nrtbf\">'+"'")) | ('u' + Word(hexnums, exact=4)) | Word(printables, exact=1))
+LITERAL_CHAR = ESC | ~(Literal("'") | Literal('\\')) + Word(printables, exact=1)
+CHAR_LITERAL = Suppress("'") + LITERAL_CHAR + Suppress("'")
+STRING_LITERAL = Suppress("'") + Combine(OneOrMore(LITERAL_CHAR)) + Suppress("'")
+DOUBLE_QUOTE_STRING_LITERAL = '"' + ZeroOrMore(LITERAL_CHAR) + '"'
+DOUBLE_ANGLE_STRING_LITERAL = '<<' + ZeroOrMore(Word(printables, exact=1)) + '>>'
+TOKEN_REF = Word(alphas.upper(), alphanums+'_')
+RULE_REF = Word(alphas.lower(), alphanums+'_')
+ACTION_ESC = (Suppress("\\") + Suppress("'")) | Suppress('\\"') | Suppress('\\') + (~(Literal("'") | Literal('"')) + Word(printables, exact=1))
+ACTION_CHAR_LITERAL = Suppress("'") + (ACTION_ESC | ~(Literal('\\') | Literal("'")) + Word(printables, exact=1)) + Suppress("'")
+ACTION_STRING_LITERAL = Suppress('"') + ZeroOrMore(ACTION_ESC | ~(Literal('\\') | Literal('"')) + Word(printables, exact=1)) + Suppress('"')
+SRC = Suppress('src') + ACTION_STRING_LITERAL("file") + INT("line")
+id = TOKEN_REF | RULE_REF
+SL_COMMENT = Suppress('//') + Suppress('$ANTLR') + SRC | ZeroOrMore(~EOL + Word(printables)) + EOL
+ML_COMMENT = cStyleComment
+WS = OneOrMore(Suppress(' ') | Suppress('\t') | (Optional(Suppress('\r')) + Literal('\n')))
+WS_LOOP = ZeroOrMore(SL_COMMENT | ML_COMMENT)
+NESTED_ARG_ACTION = Forward()
+NESTED_ARG_ACTION << Suppress('[') + ZeroOrMore(NESTED_ARG_ACTION | ACTION_STRING_LITERAL | ACTION_CHAR_LITERAL) + Suppress(']')
+ARG_ACTION = NESTED_ARG_ACTION
+NESTED_ACTION = Forward()
+NESTED_ACTION << Suppress('{') + ZeroOrMore(NESTED_ACTION | SL_COMMENT | ML_COMMENT | ACTION_STRING_LITERAL | ACTION_CHAR_LITERAL) + Suppress('}')
+ACTION = NESTED_ACTION + Optional('?')
+SCOPE = Suppress('scope')
+OPTIONS = Suppress('options') + Suppress('{') # + WS_LOOP + Suppress('{')
+TOKENS = Suppress('tokens') + Suppress('{') # + WS_LOOP + Suppress('{')
+FRAGMENT = 'fragment';
+TREE_BEGIN = Suppress('^(')
+ROOT = Suppress('^')
+BANG = Suppress('!')
+RANGE = Suppress('..')
+REWRITE = Suppress('->')
+
+# General Parser Definitions
+
+# Grammar heading
+optionValue = id | STRING_LITERAL | CHAR_LITERAL | INT | Literal('*').setName("s")
+
+option = Group(id("id") + Suppress('=') + optionValue("value"))("option")
+optionsSpec = OPTIONS + Group(OneOrMore(option + Suppress(';')))("options") + Suppress('}')
+tokenSpec = Group(TOKEN_REF("token_ref") + (Suppress('=') + (STRING_LITERAL | CHAR_LITERAL)("lit")))("token") + Suppress(';')
+tokensSpec = TOKENS + Group(OneOrMore(tokenSpec))("tokens") + Suppress('}')
+attrScope = Suppress('scope') + id + ACTION
+grammarType = Keyword('lexer') + Keyword('parser') + Keyword('tree')
+actionScopeName = id | Keyword('lexer')("l") | Keyword('parser')("p")
+action = Suppress('@') + Optional(actionScopeName + Suppress('::')) + id + ACTION
+
+grammarHeading = Optional(ML_COMMENT("ML_COMMENT")) + Optional(grammarType) + Suppress('grammar') + id("grammarName") + Suppress(';') + Optional(optionsSpec) + Optional(tokensSpec) + ZeroOrMore(attrScope) + ZeroOrMore(action)
+
+modifier = Keyword('protected') | Keyword('public') | Keyword('private') | Keyword('fragment')
+ruleAction = Suppress('@') + id + ACTION
+throwsSpec = Suppress('throws') + delimitedList(id)
+ruleScopeSpec = (Suppress('scope') + ACTION) | (Suppress('scope') + delimitedList(id) + Suppress(';')) | (Suppress('scope') + ACTION + Suppress('scope') + delimitedList(id) + Suppress(';'))
+unary_op = oneOf("^ !")
+notTerminal = CHAR_LITERAL | TOKEN_REF | STRING_LITERAL
+terminal = (CHAR_LITERAL | TOKEN_REF + Optional(ARG_ACTION) | STRING_LITERAL | '.') + Optional(unary_op)
+block = Forward()
+notSet = Suppress('~') + (notTerminal | block)
+rangeNotPython = CHAR_LITERAL("c1") + RANGE + CHAR_LITERAL("c2")
+atom = Group(rangeNotPython + Optional(unary_op)("op")) | terminal | (notSet + Optional(unary_op)("op")) | (RULE_REF + Optional(ARG_ACTION("arg")) + Optional(unary_op)("op"))
+element = Forward()
+treeSpec = Suppress('^(') + element*(2,) + Suppress(')')
+ebnfSuffix = oneOf("? * +")
+ebnf = block + Optional(ebnfSuffix("op") | '=>')
+elementNoOptionSpec = (id("result_name") + oneOf('= +=')("labelOp") + atom("atom") + Optional(ebnfSuffix)) | (id("result_name") + oneOf('= +=')("labelOp") + block + Optional(ebnfSuffix)) | atom("atom") + Optional(ebnfSuffix) | ebnf | ACTION | (treeSpec + Optional(ebnfSuffix)) # | SEMPRED ( '=>' -> GATED_SEMPRED | -> SEMPRED )
+element << Group(elementNoOptionSpec)("element")
+alternative = Group(Group(OneOrMore(element))("elements")) # Do not ask me why group is needed twice... seems like the xml that you see is not always the real structure?
+rewrite = Optional(Literal('TODO REWRITE RULES TODO'))
+block << Suppress('(') + Optional(Optional(optionsSpec("opts")) + Suppress(':')) + Group(alternative('a1') + rewrite + Group(ZeroOrMore(Suppress('|') + alternative('a2') + rewrite))("alternatives"))("block") + Suppress(')')
+altList = alternative('a1') + rewrite + Group(ZeroOrMore(Suppress('|') + alternative('a2') + rewrite))("alternatives")
+exceptionHandler = Suppress('catch') + ARG_ACTION + ACTION
+finallyClause = Suppress('finally') + ACTION
+exceptionGroup = (OneOrMore(exceptionHandler) + Optional(finallyClause)) | finallyClause
+
+ruleHeading = Optional(ML_COMMENT)("ruleComment") + Optional(modifier)("modifier") + id("ruleName") + Optional("!") + Optional(ARG_ACTION("arg")) + Optional(Suppress('returns') + ARG_ACTION("rt")) + Optional(throwsSpec) + Optional(optionsSpec) + Optional(ruleScopeSpec) + ZeroOrMore(ruleAction)
+rule = Group(ruleHeading + Suppress(':') + altList + Suppress(';') + Optional(exceptionGroup))("rule")
+
+grammarDef = grammarHeading + Group(OneOrMore(rule))("rules")
+
+def grammar():
+ return grammarDef
+
+def __antlrAlternativesConverter(pyparsingRules, antlrBlock):
+ rule = None
+ if hasattr(antlrBlock, 'alternatives') and antlrBlock.alternatives != '' and len(antlrBlock.alternatives) > 0:
+ alternatives = []
+ alternatives.append(__antlrAlternativeConverter(pyparsingRules, antlrBlock.a1))
+ for alternative in antlrBlock.alternatives:
+ alternatives.append(__antlrAlternativeConverter(pyparsingRules, alternative))
+ rule = MatchFirst(alternatives)("anonymous_or")
+ elif hasattr(antlrBlock, 'a1') and antlrBlock.a1 != '':
+ rule = __antlrAlternativeConverter(pyparsingRules, antlrBlock.a1)
+ else:
+ raise Exception('Not yet implemented')
+ assert rule != None
+ return rule
+
+def __antlrAlternativeConverter(pyparsingRules, antlrAlternative):
+ elementList = []
+ for element in antlrAlternative.elements:
+ rule = None
+ if hasattr(element.atom, 'c1') and element.atom.c1 != '':
+ regex = r'['+str(element.atom.c1[0])+'-'+str(element.atom.c2[0]+']')
+ rule = Regex(regex)("anonymous_regex")
+ elif hasattr(element, 'block') and element.block != '':
+ rule = __antlrAlternativesConverter(pyparsingRules, element.block)
+ else:
+ ruleRef = element.atom
+ assert ruleRef in pyparsingRules
+ rule = pyparsingRules[element.atom](element.atom)
+ if hasattr(element, 'op') and element.op != '':
+ if element.op == '+':
+ rule = Group(OneOrMore(rule))("anonymous_one_or_more")
+ elif element.op == '*':
+ rule = Group(ZeroOrMore(rule))("anonymous_zero_or_more")
+ elif element.op == '?':
+ rule = Optional(rule)
+ else:
+ raise Exception('rule operator not yet implemented : ' + element.op)
+ rule = rule
+ elementList.append(rule)
+ if len(elementList) > 1:
+ rule = Group(And(elementList))("anonymous_and")
+ else:
+ rule = elementList[0]
+ assert rule != None
+ return rule
+
+def __antlrRuleConverter(pyparsingRules, antlrRule):
+ rule = None
+ rule = __antlrAlternativesConverter(pyparsingRules, antlrRule)
+ assert rule != None
+ rule(antlrRule.ruleName)
+ return rule
+
+def antlrConverter(antlrGrammarTree):
+ pyparsingRules = {}
+ antlrTokens = {}
+ for antlrToken in antlrGrammarTree.tokens:
+ antlrTokens[antlrToken.token_ref] = antlrToken.lit
+ for antlrTokenName, antlrToken in list(antlrTokens.items()):
+ pyparsingRules[antlrTokenName] = Literal(antlrToken)
+ antlrRules = {}
+ for antlrRule in antlrGrammarTree.rules:
+ antlrRules[antlrRule.ruleName] = antlrRule
+ pyparsingRules[antlrRule.ruleName] = Forward() # antlr is a top down grammar
+ for antlrRuleName, antlrRule in list(antlrRules.items()):
+ pyparsingRule = __antlrRuleConverter(pyparsingRules, antlrRule)
+ assert pyparsingRule != None
+ pyparsingRules[antlrRuleName] << pyparsingRule
+ return pyparsingRules
+
+if __name__ == "__main__":
+
+ text = """grammar SimpleCalc;
+
+options {
+ language = Python;
+}
+
+tokens {
+ PLUS = '+' ;
+ MINUS = '-' ;
+ MULT = '*' ;
+ DIV = '/' ;
+}
+
+/*------------------------------------------------------------------
+ * PARSER RULES
+ *------------------------------------------------------------------*/
+
+expr : term ( ( PLUS | MINUS ) term )* ;
+
+term : factor ( ( MULT | DIV ) factor )* ;
+
+factor : NUMBER ;
+
+
+/*------------------------------------------------------------------
+ * LEXER RULES
+ *------------------------------------------------------------------*/
+
+NUMBER : (DIGIT)+ ;
+
+/* WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ { $channel = HIDDEN; } ; */
+
+fragment DIGIT : '0'..'9' ;
+
+"""
+
+ grammar().validate()
+ antlrGrammarTree = grammar().parseString(text)
+ print(antlrGrammarTree.asXML("antlrGrammarTree"))
+ pyparsingRules = antlrConverter(antlrGrammarTree)
+ pyparsingRule = pyparsingRules["expr"]
+ pyparsingTree = pyparsingRule.parseString("2 - 5 * 42 + 7 / 25")
+ print(pyparsingTree.asXML("pyparsingTree"))
diff --git a/trunk/src/examples/antlr_grammar_tests.py b/trunk/src/examples/antlr_grammar_tests.py
new file mode 100644
index 0000000..31aab29
--- /dev/null
+++ b/trunk/src/examples/antlr_grammar_tests.py
@@ -0,0 +1,85 @@
+'''
+Created on 4 sept. 2010
+
+@author: luca
+'''
+import unittest
+import antlr_grammar
+
+class Test(unittest.TestCase):
+
+
+ def testOptionsSpec(self):
+ text = """options {
+ language = Python;
+ }"""
+ antlr_grammar.optionsSpec.parseString(text) #@UndefinedVariable
+
+ def testTokensSpec(self):
+ text = """tokens {
+ PLUS = '+' ;
+ MINUS = '-' ;
+ MULT = '*' ;
+ DIV = '/' ;
+ }"""
+ antlr_grammar.tokensSpec.parseString(text) #@UndefinedVariable
+
+ def testBlock(self):
+ text = """( PLUS | MINUS )"""
+ antlr_grammar.block.parseString(text) #@UndefinedVariable
+
+ def testRule(self):
+ text = """expr : term ( ( PLUS | MINUS ) term )* ;"""
+ antlr_grammar.rule.parseString(text) #@UndefinedVariable
+
+ def testLexerRule(self):
+ text = """fragment DIGIT : '0'..'9' ;"""
+ antlr_grammar.rule.parseString(text) #@UndefinedVariable
+
+ def testLexerRule2(self):
+ text = """WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ { $channel = HIDDEN; } ;"""
+ #antlr_grammar.rule.parseString(text) #@UndefinedVariable
+
+ def testGrammar(self):
+ text = """grammar SimpleCalc;
+
+options {
+ language = Python;
+}
+
+tokens {
+ PLUS = '+' ;
+ MINUS = '-' ;
+ MULT = '*' ;
+ DIV = '/' ;
+}
+
+/*------------------------------------------------------------------
+ * PARSER RULES
+ *------------------------------------------------------------------*/
+
+expr : term ( ( PLUS | MINUS ) term )* ;
+
+term : factor ( ( MULT | DIV ) factor )* ;
+
+factor : NUMBER ;
+
+
+/*------------------------------------------------------------------
+ * LEXER RULES
+ *------------------------------------------------------------------*/
+
+NUMBER : (DIGIT)+ ;
+
+/* WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ { $channel = HIDDEN; } ; */
+
+fragment DIGIT : '0'..'9' ;"""
+ antlrGrammarTree = antlr_grammar.grammarDef.parseString(text) #@UndefinedVariable
+ pyparsingRules = antlr_grammar.antlrConverter(antlrGrammarTree)
+ pyparsingRule = pyparsingRules["expr"]
+ pyparsingTree = pyparsingRule.parseString("2 - 5 * 42 + 7 / 25")
+ self.assertNotEqual(None, pyparsingTree)
+
+if __name__ == "__main__":
+ #import sys;sys.argv = ['', 'Test.testOptionsSpec']
+ unittest.main() \ No newline at end of file
diff --git a/trunk/src/examples/apicheck.py b/trunk/src/examples/apicheck.py
new file mode 100644
index 0000000..4315ac9
--- /dev/null
+++ b/trunk/src/examples/apicheck.py
@@ -0,0 +1,55 @@
+# apicheck.py
+# A simple source code scanner for finding patterns of the form
+# [ procname1 $arg1 $arg2 ]
+# and verifying the number of arguments
+
+from pyparsing import *
+
+# define punctuation and simple tokens for locating API calls
+LBRACK,RBRACK,LBRACE,RBRACE = map(Suppress,"[]{}")
+ident = Word(alphas,alphanums+"_") | QuotedString("{",endQuoteChar="}")
+arg = "$" + ident
+
+# define an API call with a specific number of arguments - using '-'
+# will ensure that after matching procname, an incorrect number of args will
+# raise a ParseSyntaxException, which will interrupt the scanString
+def apiProc(name, numargs):
+ return LBRACK + Keyword(name)("procname") - arg*numargs + RBRACK
+
+# create an apiReference, listing all API functions to be scanned for, and
+# their respective number of arguments. Beginning the overall expression
+# with FollowedBy allows us to quickly rule out non-api calls while scanning,
+# since all of the api calls begin with a "["
+apiRef = FollowedBy("[") + MatchFirst([
+ apiProc("procname1", 2),
+ apiProc("procname2", 1),
+ apiProc("procname3", 2),
+ ])
+
+test = """[ procname1 $par1 $par2 ]
+ other code here
+ [ procname1 $par1 $par2 $par3 ]
+ more code here
+ [ procname1 $par1 ]
+ [ procname3 ${arg with spaces} $par2 ]"""
+
+
+# now explicitly iterate through the scanner using next(), so that
+# we can trap ParseSyntaxException's that would be raised due to
+# an incorrect number of arguments. If an exception does occur,
+# then see how we reset the input text and scanner to advance to the
+# next line of source code
+api_scanner = apiRef.scanString(test)
+while 1:
+ try:
+ t,s,e = next(api_scanner)
+ print("found %s on line %d" % (t.procname, lineno(s,test)))
+ except ParseSyntaxException as pe:
+ print("invalid arg count on line", pe.lineno)
+ print(pe.lineno,':',pe.line)
+ # reset api scanner to start after this exception location
+ test = "\n"*(pe.lineno-1)+test[pe.loc+1:]
+ api_scanner = apiRef.scanString(test)
+ except StopIteration:
+ break
+
diff --git a/trunk/src/examples/btpyparse.py b/trunk/src/examples/btpyparse.py
new file mode 100644
index 0000000..f3c11ae
--- /dev/null
+++ b/trunk/src/examples/btpyparse.py
@@ -0,0 +1,128 @@
+""" Pyparsing parser for BibTeX files
+
+A standalone parser using pyparsing.
+
+pyparsing has a simple and expressive syntax so the grammar is easy to read and
+write.
+
+Matthew Brett 2010
+Simplified BSD license
+"""
+
+from pyparsing import (Regex, Suppress, ZeroOrMore, Group, Optional, Forward,
+ SkipTo, CaselessLiteral, Dict)
+
+
+class Macro(object):
+ """ Class to encapsulate undefined macro references """
+ def __init__(self, name):
+ self.name = name
+ def __repr__(self):
+ return 'Macro("%s")' % self.name
+ def __eq__(self, other):
+ return self.name == other.name
+ def __ne__(self, other):
+ return self.name != other.name
+
+
+# Character literals
+LCURLY,RCURLY,LPAREN,RPAREN,QUOTE,COMMA,AT,EQUALS,HASH = map(Suppress,'{}()",@=#')
+
+
+def bracketed(expr):
+ """ Return matcher for `expr` between curly brackets or parentheses """
+ return (LPAREN + expr + RPAREN) | (LCURLY + expr + RCURLY)
+
+
+# Define parser components for strings (the hard bit)
+chars_no_curly = Regex(r"[^{}]+")
+chars_no_curly.leaveWhitespace()
+chars_no_quotecurly = Regex(r'[^"{}]+')
+chars_no_quotecurly.leaveWhitespace()
+# Curly string is some stuff without curlies, or nested curly sequences
+curly_string = Forward()
+curly_item = Group(curly_string) | chars_no_curly
+curly_string << LCURLY + ZeroOrMore(curly_item) + RCURLY
+# quoted string is either just stuff within quotes, or stuff within quotes, within
+# which there is nested curliness
+quoted_item = Group(curly_string) | chars_no_quotecurly
+quoted_string = QUOTE + ZeroOrMore(quoted_item) + QUOTE
+
+# Numbers can just be numbers. Only integers though.
+number = Regex('[0-9]+')
+
+# Basis characters (by exclusion) for variable / field names. The following
+# list of characters is from the btparse documentation
+any_name = Regex('[^\s"#%\'(),={}]+')
+
+# btparse says, and the test bibs show by experiment, that macro and field names
+# cannot start with a digit. In fact entry type names cannot start with a digit
+# either (see tests/bibs). Cite keys can start with a digit
+not_digname = Regex('[^\d\s"#%\'(),={}][^\s"#%\'(),={}]*')
+
+# Comment comments out to end of line
+comment = (AT + CaselessLiteral('comment') +
+ Regex("[\s{(].*").leaveWhitespace())
+
+# The name types with their digiteyness
+not_dig_lower = not_digname.copy().setParseAction(lambda t: t[0].lower())
+macro_def = not_dig_lower.copy()
+macro_ref = not_dig_lower.copy().setParseAction(lambda t : Macro(t[0].lower()))
+field_name = not_dig_lower.copy()
+# Spaces in names mean they cannot clash with field names
+entry_type = not_dig_lower('entry_type')
+cite_key = any_name('cite_key')
+# Number has to be before macro name
+string = (number | macro_ref | quoted_string | curly_string)
+
+# There can be hash concatenation
+field_value = string + ZeroOrMore(HASH + string)
+field_def = Group(field_name + EQUALS + field_value)
+entry_contents = Dict(ZeroOrMore(field_def + COMMA) + Optional(field_def))
+
+# Entry is surrounded either by parentheses or curlies
+entry = (AT + entry_type + bracketed(cite_key + COMMA + entry_contents))
+
+# Preamble is a macro-like thing with no name
+preamble = AT + CaselessLiteral('preamble') + bracketed(field_value)
+
+# Macros (aka strings)
+macro_contents = macro_def + EQUALS + field_value
+macro = AT + CaselessLiteral('string') + bracketed(macro_contents)
+
+# Implicit comments
+icomment = SkipTo('@').setParseAction(lambda t : t.insert(0, 'icomment'))
+
+# entries are last in the list (other than the fallback) because they have
+# arbitrary start patterns that would match comments, preamble or macro
+definitions = Group(comment |
+ preamble |
+ macro |
+ entry |
+ icomment)
+
+# Start symbol
+bibfile = ZeroOrMore(definitions)
+
+
+def parse_str(str):
+ return bibfile.parseString(str)
+
+
+if __name__ == '__main__':
+ # Run basic test
+ txt = """
+Some introductory text
+(implicit comment)
+
+@ARTICLE{Authors2011,
+ author = {First Author and Second Author and Third Author},
+ title = {An article about {S}omething},
+ journal = "Journal of Articles",
+ year = {2011},
+ volume = {16},
+ pages = {1140--1141},
+ number = {2}
+}
+"""
+ print('\n\n'.join(defn.dump() for defn in parse_str(txt)))
diff --git a/trunk/src/examples/builtin_parse_action_demo.py b/trunk/src/examples/builtin_parse_action_demo.py
new file mode 100644
index 0000000..3ec6af8
--- /dev/null
+++ b/trunk/src/examples/builtin_parse_action_demo.py
@@ -0,0 +1,29 @@
+#
+# builtin_parse_action_demo.py
+# Copyright, 2012 - Paul McGuire
+#
+# Simple example of using builtin functions as parse actions.
+#
+
+from pyparsing import *
+
+integer = Word(nums).setParseAction(lambda t : int(t[0]))
+
+# make an expression that will match a list of ints (which
+# will be converted to actual ints by the parse action attached
+# to integer)
+nums = OneOrMore(integer)
+
+
+test = "2 54 34 2 211 66 43 2 0"
+print(test)
+
+# try each of these builtins as parse actions
+for fn in (sum, max, min, len, sorted, reversed, list, tuple, set, any, all):
+ fn_name = fn.__name__
+ if fn is reversed:
+ # reversed returns an iterator, we really want to show the list of items
+ fn = lambda x : list(reversed(x))
+
+ # show how each builtin works as a free-standing parse action
+ print(fn_name, nums.setParseAction(fn).parseString(test))
diff --git a/trunk/src/examples/cLibHeader.py b/trunk/src/examples/cLibHeader.py
new file mode 100644
index 0000000..bb98521
--- /dev/null
+++ b/trunk/src/examples/cLibHeader.py
@@ -0,0 +1,25 @@
+#
+# cLibHeader.py
+#
+# A simple parser to extract API doc info from a C header file
+#
+# Copyright, 2012 - Paul McGuire
+#
+
+from pyparsing import Word, alphas, alphanums, Combine, oneOf, Optional, delimitedList, Group, Keyword
+
+testdata = """
+ int func1(float *vec, int len, double arg1);
+ int func2(float **arr, float *vec, int len, double arg1, double arg2);
+ """
+
+ident = Word(alphas, alphanums + "_")
+vartype = Combine( oneOf("float double int char") + Optional(Word("*")), adjacent = False)
+arglist = delimitedList(Group(vartype("type") + ident("name")))
+
+functionCall = Keyword("int") + ident("name") + "(" + arglist("args") + ")" + ";"
+
+for fn,s,e in functionCall.scanString(testdata):
+ print(fn.name)
+ for a in fn.args:
+ print(" - %(name)s (%(type)s)" % a)
diff --git a/trunk/src/examples/chemicalFormulas.py b/trunk/src/examples/chemicalFormulas.py
new file mode 100644
index 0000000..ce66afd
--- /dev/null
+++ b/trunk/src/examples/chemicalFormulas.py
@@ -0,0 +1,67 @@
+# chemicalFormulas.py
+#
+# Copyright (c) 2003, Paul McGuire
+#
+
+from pyparsing import Word, Optional, OneOrMore, Group, ParseException, Regex
+from pyparsing import alphas
+
+atomicWeight = {
+ "O" : 15.9994,
+ "H" : 1.00794,
+ "Na" : 22.9897,
+ "Cl" : 35.4527,
+ "C" : 12.0107
+ }
+
+def test( bnf, strg, fn=None ):
+ try:
+ print(strg,"->", bnf.parseString( strg ), end=' ')
+ except ParseException as pe:
+ print(pe)
+ else:
+ if fn != None:
+ print(fn( bnf.parseString( strg ) ))
+ else:
+ print()
+
+digits = "0123456789"
+
+# Version 1
+element = Regex("A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|"
+ "E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|"
+ "M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|"
+ "S[bcegimnr]?|T[abcehilm]|U(u[bhopqst])?|V|W|Xe|Yb?|Z[nr]")
+
+element = Word( alphas.upper(), alphas.lower(), max=2)
+elementRef = Group( element + Optional( Word( digits ), default="1" ) )
+formula = OneOrMore( elementRef )
+
+fn = lambda elemList : sum(atomicWeight[elem]*int(qty) for elem,qty in elemList)
+test( formula, "H2O", fn )
+test( formula, "C6H5OH", fn )
+test( formula, "NaCl", fn )
+print()
+
+# Version 2 - access parsed items by field name
+elementRef = Group( element("symbol") + Optional( Word( digits ), default="1" )("qty") )
+formula = OneOrMore( elementRef )
+
+fn = lambda elemList : sum(atomicWeight[elem.symbol]*int(elem.qty) for elem in elemList)
+test( formula, "H2O", fn )
+test( formula, "C6H5OH", fn )
+test( formula, "NaCl", fn )
+print()
+
+# Version 3 - convert integers during parsing process
+integer = Word( digits ).setParseAction(lambda t:int(t[0]))
+elementRef = Group( element("symbol") + Optional( integer, default=1 )("qty") )
+formula = OneOrMore( elementRef )
+
+fn = lambda elemList : sum(atomicWeight[elem.symbol]*elem.qty for elem in elemList)
+test( formula, "H2O", fn )
+test( formula, "C6H5OH", fn )
+test( formula, "NaCl", fn )
+
+
+
diff --git a/trunk/src/examples/commasep.py b/trunk/src/examples/commasep.py
new file mode 100644
index 0000000..7696871
--- /dev/null
+++ b/trunk/src/examples/commasep.py
@@ -0,0 +1,23 @@
+# commasep.py
+#
+# comma-separated list example, to illustrate the advantages of using
+# the pyparsing commaSeparatedList as opposed to string.split(","):
+# - leading and trailing whitespace is implicitly trimmed from list elements
+# - list elements can be quoted strings, which can safely contain commas without breaking
+# into separate elements
+
+from pyparsing import commaSeparatedList
+
+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 ",
+ "",
+ ]
+
+for line in testData:
+ print(commaSeparatedList.parseString(line))
+ print(line.split(","))
+ print()
diff --git a/trunk/src/examples/configParse.py b/trunk/src/examples/configParse.py
new file mode 100644
index 0000000..769249c
--- /dev/null
+++ b/trunk/src/examples/configParse.py
@@ -0,0 +1,72 @@
+#
+# configparse.py
+#
+# an example of using the parsing module to be able to process a .INI configuration file
+#
+# Copyright (c) 2003, Paul McGuire
+#
+
+from pyparsing import \
+ Literal, Word, ZeroOrMore, Group, Dict, Optional, \
+ printables, ParseException, restOfLine, empty
+import pprint
+
+
+inibnf = None
+def inifile_BNF():
+ global inibnf
+
+ if not inibnf:
+
+ # punctuation
+ lbrack = Literal("[").suppress()
+ rbrack = Literal("]").suppress()
+ equals = Literal("=").suppress()
+ semi = Literal(";")
+
+ comment = semi + Optional( restOfLine )
+
+ nonrbrack = "".join( [ c for c in printables if c != "]" ] ) + " \t"
+ nonequals = "".join( [ c for c in printables if c != "=" ] ) + " \t"
+
+ sectionDef = lbrack + Word( nonrbrack ) + rbrack
+ keyDef = ~lbrack + Word( nonequals ) + equals + empty + restOfLine
+ # strip any leading or trailing blanks from key
+ def stripKey(tokens):
+ tokens[0] = tokens[0].strip()
+ keyDef.setParseAction(stripKey)
+
+ # using Dict will allow retrieval of named data fields as attributes of the parsed results
+ inibnf = Dict( ZeroOrMore( Group( sectionDef + Dict( ZeroOrMore( Group( keyDef ) ) ) ) ) )
+
+ inibnf.ignore( comment )
+
+ return inibnf
+
+
+pp = pprint.PrettyPrinter(2)
+
+def test( strng ):
+ print(strng)
+ try:
+ iniFile = open(strng)
+ iniData = "".join( iniFile.readlines() )
+ bnf = inifile_BNF()
+ tokens = bnf.parseString( iniData )
+ pp.pprint( tokens.asList() )
+
+ except ParseException as err:
+ print(err.line)
+ print(" "*(err.column-1) + "^")
+ print(err)
+
+ iniFile.close()
+ print()
+ return tokens
+
+if __name__ == "__main__":
+ ini = test("setup.ini")
+ print("ini['Startup']['modemid'] =", ini['Startup']['modemid'])
+ print("ini.Startup =", ini.Startup)
+ print("ini.Startup.modemid =", ini.Startup.modemid)
+
diff --git a/trunk/src/examples/cpp_enum_parser.py b/trunk/src/examples/cpp_enum_parser.py
new file mode 100644
index 0000000..cbd0932
--- /dev/null
+++ b/trunk/src/examples/cpp_enum_parser.py
@@ -0,0 +1,52 @@
+#
+# cpp_enum_parser.py
+#
+# Posted by Mark Tolenen on comp.lang.python in August, 2009,
+# Used with permission.
+#
+# Parser that scans through C or C++ code for enum definitions, and
+# generates corresponding Python constant definitions.
+#
+#
+
+from pyparsing import *
+# sample string with enums and other stuff
+sample = '''
+ stuff before
+ enum hello {
+ Zero,
+ One,
+ Two,
+ Three,
+ Five=5,
+ Six,
+ Ten=10
+ };
+ in the middle
+ enum blah
+ {
+ alpha,
+ beta,
+ gamma = 10 ,
+ zeta = 50
+ };
+ at the end
+ '''
+
+# syntax we don't want to see in the final parse tree
+LBRACE,RBRACE,EQ,COMMA = map(Suppress,"{}=,")
+_enum = Suppress('enum')
+identifier = Word(alphas,alphanums+'_')
+integer = Word(nums)
+enumValue = Group(identifier('name') + Optional(EQ + integer('value')))
+enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
+enum = _enum + identifier('enum') + LBRACE + enumList('names') + RBRACE
+
+# find instances of enums ignoring other syntax
+for item,start,stop in enum.scanString(sample):
+ id = 0
+ for entry in item.names:
+ if entry.value != '':
+ id = int(entry.value)
+ print('%s_%s = %d' % (item.enum.upper(),entry.name.upper(),id))
+ id += 1
diff --git a/trunk/src/examples/datetimeParseActions.py b/trunk/src/examples/datetimeParseActions.py
new file mode 100644
index 0000000..26d96a3
--- /dev/null
+++ b/trunk/src/examples/datetimeParseActions.py
@@ -0,0 +1,43 @@
+# parseActions.py
+#
+# A sample program a parser to match a date string of the form "YYYY/MM/DD",
+# and return it as a datetime, or raise an exception if not a valid date.
+#
+# Copyright 2012, Paul T. McGuire
+#
+from datetime import datetime
+from pyparsing import *
+
+# define an integer string, and a parse action to convert it
+# to an integer at parse time
+integer = Word(nums).setName("integer")
+def convertToInt(tokens):
+ # no need to test for validity - we can't get here
+ # unless tokens[0] contains all numeric digits
+ return int(tokens[0])
+integer.setParseAction(convertToInt)
+# or can be written as one line as
+#integer = Word(nums).setParseAction(lambda t: int(t[0]))
+
+# define a pattern for a year/month/day date
+date_expr = integer('year') + '/' + integer('month') + '/' + integer('day')
+
+def convertToDatetime(s,loc,tokens):
+ try:
+ # note that the year, month, and day fields were already
+ # converted to ints from strings by the parse action defined
+ # on the integer expression above
+ return datetime(tokens.year, tokens.month, tokens.day).date()
+ except Exception as ve:
+ errmsg = "'%d/%d/%d' is not a valid date, %s" % \
+ (tokens.year, tokens.month, tokens.day, ve)
+ raise ParseException(s, loc, errmsg)
+date_expr.setParseAction(convertToDatetime)
+
+
+date_expr.runTests("""\
+ 2000/1/1
+ 2000/13/1 # invalid month
+ 1900/2/29 # 1900 was not a leap year
+ 2000/2/29 # but 2000 was
+ """) \ No newline at end of file
diff --git a/trunk/src/examples/deltaTime.py b/trunk/src/examples/deltaTime.py
new file mode 100644
index 0000000..e38da00
--- /dev/null
+++ b/trunk/src/examples/deltaTime.py
@@ -0,0 +1,208 @@
+# deltaTime.py
+#
+# Parser to convert a conversational time reference such as "in a minute" or
+# "noon tomorrow" and convert it to a Python datetime. The returned
+# ParseResults object contains the results name "timeOffset" containing
+# the timedelta, and "calculatedTime" containing the computed time relative
+# to datetime.now().
+#
+# Copyright 2010, by Paul McGuire
+#
+
+from datetime import datetime, timedelta
+from pyparsing import *
+import calendar
+
+__all__ = ["nlTimeExpression"]
+
+# string conversion parse actions
+def convertToTimedelta(toks):
+ unit = toks.timeunit.lower().rstrip("s")
+ td = {
+ 'week' : timedelta(7),
+ 'day' : timedelta(1),
+ 'hour' : timedelta(0,0,0,0,0,1),
+ 'minute' : timedelta(0,0,0,0,1),
+ 'second' : timedelta(0,1),
+ }[unit]
+ if toks.qty:
+ td *= int(toks.qty)
+ if toks.dir:
+ td *= toks.dir
+ toks["timeOffset"] = td
+
+def convertToDay(toks):
+ now = datetime.now()
+ if "wkdayRef" in toks:
+ todaynum = now.weekday()
+ daynames = [n.lower() for n in calendar.day_name]
+ nameddaynum = daynames.index(toks.wkdayRef.day.lower())
+ if toks.wkdayRef.dir > 0:
+ daydiff = (nameddaynum + 7 - todaynum) % 7
+ else:
+ daydiff = -((todaynum + 7 - nameddaynum) % 7)
+ toks["absTime"] = datetime(now.year, now.month, now.day)+timedelta(daydiff)
+ else:
+ name = toks.name.lower()
+ toks["absTime"] = {
+ "now" : now,
+ "today" : datetime(now.year, now.month, now.day),
+ "yesterday" : datetime(now.year, now.month, now.day)+timedelta(-1),
+ "tomorrow" : datetime(now.year, now.month, now.day)+timedelta(+1),
+ }[name]
+
+def convertToAbsTime(toks):
+ now = datetime.now()
+ if "dayRef" in toks:
+ day = toks.dayRef.absTime
+ day = datetime(day.year, day.month, day.day)
+ else:
+ day = datetime(now.year, now.month, now.day)
+ if "timeOfDay" in toks:
+ if isinstance(toks.timeOfDay,str):
+ timeOfDay = {
+ "now" : timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond),
+ "noon" : timedelta(0,0,0,0,0,12),
+ "midnight" : timedelta(),
+ }[toks.timeOfDay]
+ else:
+ hhmmss = toks.timeparts
+ if hhmmss.miltime:
+ hh,mm = hhmmss.miltime
+ ss = 0
+ else:
+ hh,mm,ss = (hhmmss.HH % 12), hhmmss.MM, hhmmss.SS
+ if not mm: mm = 0
+ if not ss: ss = 0
+ if toks.timeOfDay.ampm == 'pm':
+ hh += 12
+ timeOfDay = timedelta(0, (hh*60+mm)*60+ss, 0)
+ else:
+ timeOfDay = timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond)
+ toks["absTime"] = day + timeOfDay
+
+def calculateTime(toks):
+ if toks.absTime:
+ absTime = toks.absTime
+ else:
+ absTime = datetime.now()
+ if toks.timeOffset:
+ absTime += toks.timeOffset
+ toks["calculatedTime"] = absTime
+
+# grammar definitions
+CL = CaselessLiteral
+today, tomorrow, yesterday, noon, midnight, now = map( CL,
+ "today tomorrow yesterday noon midnight now".split())
+plural = lambda s : Combine(CL(s) + Optional(CL("s")))
+week, day, hour, minute, second = map( plural,
+ "week day hour minute second".split())
+am = CL("am")
+pm = CL("pm")
+COLON = Suppress(':')
+
+# are these actually operators?
+in_ = CL("in").setParseAction(replaceWith(1))
+from_ = CL("from").setParseAction(replaceWith(1))
+before = CL("before").setParseAction(replaceWith(-1))
+after = CL("after").setParseAction(replaceWith(1))
+ago = CL("ago").setParseAction(replaceWith(-1))
+next_ = CL("next").setParseAction(replaceWith(1))
+last_ = CL("last").setParseAction(replaceWith(-1))
+at_ = CL("at")
+on_ = CL("on")
+
+couple = (Optional(CL("a")) + CL("couple") + Optional(CL("of"))).setParseAction(replaceWith(2))
+a_qty = CL("a").setParseAction(replaceWith(1))
+integer = Word(nums).setParseAction(lambda t:int(t[0]))
+int4 = Group(Word(nums,exact=4).setParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])] ))
+def fill_timefields(t):
+ t[0]['HH'] = t[0][0]
+ t[0]['MM'] = t[0][1]
+ t[0]['ampm'] = ('am','pm')[t[0].HH >= 12]
+int4.addParseAction(fill_timefields)
+qty = integer | couple | a_qty
+dayName = oneOf( list(calendar.day_name) )
+
+dayOffset = (qty("qty") + (week | day)("timeunit"))
+dayFwdBack = (from_ + now.suppress() | ago)("dir")
+weekdayRef = (Optional(next_ | last_,1)("dir") + dayName("day"))
+dayRef = Optional( (dayOffset + (before | after | from_)("dir") ).setParseAction(convertToTimedelta) ) + \
+ ((yesterday | today | tomorrow)("name")|
+ weekdayRef("wkdayRef")).setParseAction(convertToDay)
+todayRef = (dayOffset + dayFwdBack).setParseAction(convertToTimedelta) | \
+ (in_("dir") + qty("qty") + day("timeunit")).setParseAction(convertToTimedelta)
+
+dayTimeSpec = dayRef | todayRef
+dayTimeSpec.setParseAction(calculateTime)
+
+relativeTimeUnit = (week | day | hour | minute | second)
+
+timespec = Group(ungroup(int4) |
+ integer("HH") +
+ ungroup(Optional(COLON + integer,[0]))("MM") +
+ ungroup(Optional(COLON + integer,[0]))("SS") +
+ (am | pm)("ampm")
+ )
+
+absTimeSpec = ((noon | midnight | now | timespec("timeparts"))("timeOfDay") +
+ Optional(on_) + Optional(dayRef)("dayRef") |
+ dayRef("dayRef") + at_ +
+ (noon | midnight | now | timespec("timeparts"))("timeOfDay"))
+absTimeSpec.setParseAction(convertToAbsTime,calculateTime)
+
+relTimeSpec = qty("qty") + relativeTimeUnit("timeunit") + \
+ (from_ | before | after)("dir") + \
+ Optional(at_) + \
+ absTimeSpec("absTime") | \
+ qty("qty") + relativeTimeUnit("timeunit") + ago("dir") | \
+ in_ + qty("qty") + relativeTimeUnit("timeunit")
+relTimeSpec.setParseAction(convertToTimedelta,calculateTime)
+
+nlTimeExpression = (absTimeSpec + Optional(dayTimeSpec) |
+ dayTimeSpec + Optional(Optional(at_) + absTimeSpec) |
+ relTimeSpec + Optional(absTimeSpec))
+
+if __name__ == "__main__":
+ # test grammar
+ tests = """\
+ today
+ tomorrow
+ yesterday
+ in a couple of days
+ a couple of days from now
+ a couple of days from today
+ in a day
+ 3 days ago
+ 3 days from now
+ a day ago
+ in 2 weeks
+ in 3 days at 5pm
+ now
+ 10 minutes ago
+ 10 minutes from now
+ in 10 minutes
+ in a minute
+ in a couple of minutes
+ 20 seconds ago
+ in 30 seconds
+ 20 seconds before noon
+ 20 seconds before noon tomorrow
+ noon
+ midnight
+ noon tomorrow
+ 6am tomorrow
+ 0800 yesterday
+ 12:15 AM today
+ 3pm 2 days from today
+ a week from today
+ a week from now
+ 3 weeks ago
+ noon next Sunday
+ noon Sunday
+ noon last Sunday
+ 2pm next Sunday
+ next Sunday at 2pm"""
+
+ print("(relative to %s)" % datetime.now())
+ nlTimeExpression.runTests(tests)
diff --git a/trunk/src/examples/dfmparse.py b/trunk/src/examples/dfmparse.py
new file mode 100644
index 0000000..96afbad
--- /dev/null
+++ b/trunk/src/examples/dfmparse.py
@@ -0,0 +1,176 @@
+"""
+This module can parse a Delphi Form (dfm) file.
+
+The main is used in experimenting (to find which files fail
+to parse, and where), but isn't useful for anything else.
+"""
+__version__ = "1.0"
+__author__ = "Daniel 'Dang' Griffith <pythondev - dang at lazytwinacres . net>"
+
+
+from pyparsing import Literal, CaselessLiteral, Word, delimitedList \
+ , Optional, Combine, Group, alphas, nums, alphanums, Forward \
+ , oneOf, sglQuotedString, OneOrMore, ZeroOrMore, CharsNotIn
+
+
+# This converts DFM character constants into Python string (unicode) values.
+def to_chr(x):
+ """chr(x) if 0 < x < 128 ; unicode(x) if x > 127."""
+ return 0 < x < 128 and chr(x) or eval("u'\\u%d'" % x )
+
+#################
+# BEGIN GRAMMAR
+#################
+
+COLON = Literal(":").suppress()
+CONCAT = Literal("+").suppress()
+EQUALS = Literal("=").suppress()
+LANGLE = Literal("<").suppress()
+LBRACE = Literal("[").suppress()
+LPAREN = Literal("(").suppress()
+PERIOD = Literal(".").suppress()
+RANGLE = Literal(">").suppress()
+RBRACE = Literal("]").suppress()
+RPAREN = Literal(")").suppress()
+
+CATEGORIES = CaselessLiteral("categories").suppress()
+END = CaselessLiteral("end").suppress()
+FONT = CaselessLiteral("font").suppress()
+HINT = CaselessLiteral("hint").suppress()
+ITEM = CaselessLiteral("item").suppress()
+OBJECT = CaselessLiteral("object").suppress()
+
+attribute_value_pair = Forward() # this is recursed in item_list_entry
+
+simple_identifier = Word(alphas, alphanums + "_")
+identifier = Combine( simple_identifier + ZeroOrMore( Literal(".") + simple_identifier ))
+object_name = identifier
+object_type = identifier
+
+# Integer and floating point values are converted to Python longs and floats, respectively.
+int_value = Combine(Optional("-") + Word(nums)).setParseAction(lambda s,l,t: [ int(t[0]) ] )
+float_value = Combine(Optional("-") + Optional(Word(nums)) + "." + Word(nums)).setParseAction(lambda s,l,t: [ float(t[0]) ] )
+number_value = float_value | int_value
+
+# Base16 constants are left in string form, including the surrounding braces.
+base16_value = Combine(Literal("{") + OneOrMore(Word("0123456789ABCDEFabcdef")) + Literal("}"), adjacent=False)
+
+# This is the first part of a hack to convert the various delphi partial sglQuotedStrings
+# into a single sglQuotedString equivalent. The gist of it is to combine
+# all sglQuotedStrings (with their surrounding quotes removed (suppressed))
+# with sequences of #xyz character constants, with "strings" concatenated
+# with a '+' sign.
+unquoted_sglQuotedString = Combine( Literal("'").suppress() + ZeroOrMore( CharsNotIn("'\n\r") ) + Literal("'").suppress() )
+
+# The parse action on this production converts repetitions of constants into a single string.
+pound_char = Combine(
+ OneOrMore((Literal("#").suppress()+Word(nums)
+ ).setParseAction( lambda s, l, t: to_chr(int(t[0]) ))))
+
+# This is the second part of the hack. It combines the various "unquoted"
+# partial strings into a single one. Then, the parse action puts
+# a single matched pair of quotes around it.
+delphi_string = Combine(
+ OneOrMore(CONCAT | pound_char | unquoted_sglQuotedString)
+ , adjacent=False
+ ).setParseAction(lambda s, l, t: "'%s'" % t[0])
+
+string_value = delphi_string | base16_value
+
+list_value = LBRACE + Optional(Group(delimitedList(identifier | number_value | string_value))) + RBRACE
+paren_list_value = LPAREN + ZeroOrMore(identifier | number_value | string_value) + RPAREN
+
+item_list_entry = ITEM + ZeroOrMore(attribute_value_pair) + END
+item_list = LANGLE + ZeroOrMore(item_list_entry) + RANGLE
+
+generic_value = identifier
+value = item_list | number_value | string_value | list_value | paren_list_value | generic_value
+
+category_attribute = CATEGORIES + PERIOD + oneOf("strings itemsvisibles visibles", True)
+event_attribute = oneOf("onactivate onclosequery onclose oncreate ondeactivate onhide onshow", True)
+font_attribute = FONT + PERIOD + oneOf("charset color height name style", True)
+hint_attribute = HINT
+layout_attribute = oneOf("left top width height", True)
+generic_attribute = identifier
+attribute = (category_attribute | event_attribute | font_attribute | hint_attribute | layout_attribute | generic_attribute)
+
+category_attribute_value_pair = category_attribute + EQUALS + paren_list_value
+event_attribute_value_pair = event_attribute + EQUALS + value
+font_attribute_value_pair = font_attribute + EQUALS + value
+hint_attribute_value_pair = hint_attribute + EQUALS + value
+layout_attribute_value_pair = layout_attribute + EQUALS + value
+generic_attribute_value_pair = attribute + EQUALS + value
+attribute_value_pair << Group(
+ category_attribute_value_pair
+ | event_attribute_value_pair
+ | font_attribute_value_pair
+ | hint_attribute_value_pair
+ | layout_attribute_value_pair
+ | generic_attribute_value_pair
+ )
+
+object_declaration = Group((OBJECT + object_name + COLON + object_type))
+object_attributes = Group(ZeroOrMore(attribute_value_pair))
+
+nested_object = Forward()
+object_definition = object_declaration + object_attributes + ZeroOrMore(nested_object) + END
+nested_object << Group(object_definition)
+
+#################
+# END GRAMMAR
+#################
+
+def printer(s, loc, tok):
+ print(tok, end=' ')
+ return tok
+
+def get_filename_list(tf):
+ import sys, glob
+ if tf == None:
+ tf = sys.argv[1:]
+ elif type(tf) == str:
+ tf = [tf]
+ testfiles = []
+ for arg in tf:
+ for i in glob.glob(arg):
+ testfiles.append(i)
+ return testfiles
+
+def main(testfiles=None, action=printer):
+ """testfiles can be None, in which case the command line arguments are used as filenames.
+ testfiles can be a string, in which case that file is parsed.
+ testfiles can be a list.
+ In all cases, the filenames will be globbed.
+ If more than one file is parsed successfully, a dictionary of ParseResults is returned.
+ Otherwise, a simple ParseResults is returned.
+ """
+ testfiles = get_filename_list(testfiles)
+
+ if action:
+ for i in (simple_identifier, value, item_list):
+ i.setParseAction(action)
+
+ success = 0
+ failures = []
+
+ retval = {}
+ for f in testfiles:
+ try:
+ retval[f] = object_definition.parseFile(f)
+ success += 1
+ except:
+ failures.append(f)
+
+ if failures:
+ print('\nfailed while processing %s' % ', '.join(failures))
+ print('\nsucceeded on %d of %d files' %(success, len(testfiles)))
+
+ if len(retval) == 1 and len(testfiles) == 1:
+ # if only one file is parsed, return the parseResults directly
+ return retval[list(retval.keys())[0]]
+
+ # else, return a dictionary of parseResults
+ return retval
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/trunk/src/examples/dhcpd_leases_parser.py b/trunk/src/examples/dhcpd_leases_parser.py
new file mode 100644
index 0000000..145e6ea
--- /dev/null
+++ b/trunk/src/examples/dhcpd_leases_parser.py
@@ -0,0 +1,87 @@
+#
+# dhcpd_leases_parser.py
+#
+# Copyright 2008, Paul McGuire
+#
+# Sample parser to parse a dhcpd.leases file to extract leases
+# and lease attributes
+#
+# format ref: http://www.linuxmanpages.com/man5/dhcpd.leases.5.php
+#
+
+sample = r"""\
+# All times in this file are in UTC (GMT), not your local timezone. This is
+# not a bug, so please don't ask about it. There is no portable way to
+# store leases in the local timezone, so please don't request this as a
+# feature. If this is inconvenient or confusing to you, we sincerely
+# apologize. Seriously, though - don't ask.
+# The format of this file is documented in the dhcpd.leases(5) manual page.
+# This lease file was written by isc-dhcp-V3.0.4
+
+lease 192.168.0.250 {
+ starts 3 2008/01/23 17:16:41;
+ ends 6 2008/02/02 17:16:41;
+ tstp 6 2008/02/02 17:16:41;
+ binding state free;
+ hardware ethernet 00:17:f2:9b:d8:19;
+ uid "\001\000\027\362\233\330\031";
+}
+lease 192.168.0.198 {
+ starts 1 2008/02/04 13:46:55;
+ ends never;
+ tstp 1 2008/02/04 17:04:14;
+ binding state free;
+ hardware ethernet 00:13:72:d3:3b:98;
+ uid "\001\000\023r\323;\230";
+}
+lease 192.168.0.239 {
+ starts 3 2008/02/06 12:12:03;
+ ends 4 2008/02/07 12:12:03;
+ tstp 4 2008/02/07 12:12:03;
+ binding state free;
+ hardware ethernet 00:1d:09:65:93:26;
+}
+"""
+
+from pyparsing import *
+import datetime,time
+
+LBRACE,RBRACE,SEMI,QUOTE = map(Suppress,'{};"')
+ipAddress = Combine(Word(nums) + ('.' + Word(nums))*3)
+hexint = Word(hexnums,exact=2)
+macAddress = Combine(hexint + (':'+hexint)*5)
+hdwType = Word(alphanums)
+
+yyyymmdd = Combine((Word(nums,exact=4)|Word(nums,exact=2))+
+ ('/'+Word(nums,exact=2))*2)
+hhmmss = Combine(Word(nums,exact=2)+(':'+Word(nums,exact=2))*2)
+dateRef = oneOf(list("0123456"))("weekday") + yyyymmdd("date") + \
+ hhmmss("time")
+
+def utcToLocalTime(tokens):
+ utctime = datetime.datetime.strptime("%(date)s %(time)s" % tokens,
+ "%Y/%m/%d %H:%M:%S")
+ localtime = utctime-datetime.timedelta(0,time.timezone,0)
+ tokens["utcdate"],tokens["utctime"] = tokens["date"],tokens["time"]
+ tokens["localdate"],tokens["localtime"] = str(localtime).split()
+ del tokens["date"]
+ del tokens["time"]
+dateRef.setParseAction(utcToLocalTime)
+
+startsStmt = "starts" + dateRef + SEMI
+endsStmt = "ends" + (dateRef | "never") + SEMI
+tstpStmt = "tstp" + dateRef + SEMI
+tsfpStmt = "tsfp" + dateRef + SEMI
+hdwStmt = "hardware" + hdwType("type") + macAddress("mac") + SEMI
+uidStmt = "uid" + QuotedString('"')("uid") + SEMI
+bindingStmt = "binding" + Word(alphanums) + Word(alphanums) + SEMI
+
+leaseStatement = startsStmt | endsStmt | tstpStmt | tsfpStmt | hdwStmt | \
+ uidStmt | bindingStmt
+leaseDef = "lease" + ipAddress("ipaddress") + LBRACE + \
+ Dict(ZeroOrMore(Group(leaseStatement))) + RBRACE
+
+for lease in leaseDef.searchString(sample):
+ print(lease.dump())
+ print(lease.ipaddress,'->',lease.hardware.mac)
+ print()
diff --git a/trunk/src/examples/dictExample.py b/trunk/src/examples/dictExample.py
new file mode 100644
index 0000000..5085aed
--- /dev/null
+++ b/trunk/src/examples/dictExample.py
@@ -0,0 +1,41 @@
+#
+# dictExample.py
+#
+# Illustration of using pyparsing's Dict class to process tabular data
+#
+# Copyright (c) 2003, Paul McGuire
+#
+from pyparsing import Literal, Word, Group, Dict, ZeroOrMore, alphas, nums, delimitedList
+import pprint
+
+testData = """
++-------+------+------+------+------+------+------+------+------+
+| | A1 | B1 | C1 | D1 | A2 | B2 | C2 | D2 |
++=======+======+======+======+======+======+======+======+======+
+| min | 7 | 43 | 7 | 15 | 82 | 98 | 1 | 37 |
+| max | 11 | 52 | 10 | 17 | 85 | 112 | 4 | 39 |
+| ave | 9 | 47 | 8 | 16 | 84 | 106 | 3 | 38 |
+| sdev | 1 | 3 | 1 | 1 | 1 | 3 | 1 | 1 |
++-------+------+------+------+------+------+------+------+------+
+"""
+
+# define grammar for datatable
+heading = (Literal(
+"+-------+------+------+------+------+------+------+------+------+") +
+"| | A1 | B1 | C1 | D1 | A2 | B2 | C2 | D2 |" +
+"+=======+======+======+======+======+======+======+======+======+").suppress()
+vert = Literal("|").suppress()
+number = Word(nums)
+rowData = Group( vert + Word(alphas) + vert + delimitedList(number,"|") + vert )
+trailing = Literal(
+"+-------+------+------+------+------+------+------+------+------+").suppress()
+
+datatable = heading + Dict( ZeroOrMore(rowData) ) + trailing
+
+# now parse data and print results
+data = datatable.parseString(testData)
+print(data)
+pprint.pprint(data.asList())
+print("data keys=", list(data.keys()))
+print("data['min']=", data['min'])
+print("data.max", data.max)
diff --git a/trunk/src/examples/dictExample2.py b/trunk/src/examples/dictExample2.py
new file mode 100644
index 0000000..cae463b
--- /dev/null
+++ b/trunk/src/examples/dictExample2.py
@@ -0,0 +1,59 @@
+#
+# dictExample2.py
+#
+# Illustration of using pyparsing's Dict class to process tabular data
+# Enhanced Dict example, courtesy of Mike Kelly
+#
+# Copyright (c) 2004, Paul McGuire
+#
+from pyparsing import Literal, Word, Group, Dict, ZeroOrMore, alphas, nums, delimitedList, pyparsing_common
+import pprint
+
+testData = """
++-------+------+------+------+------+------+------+------+------+
+| | A1 | B1 | C1 | D1 | A2 | B2 | C2 | D2 |
++=======+======+======+======+======+======+======+======+======+
+| min | 7 | 43 | 7 | 15 | 82 | 98 | 1 | 37 |
+| max | 11 | 52 | 10 | 17 | 85 | 112 | 4 | 39 |
+| ave | 9 | 47 | 8 | 16 | 84 | 106 | 3 | 38 |
+| sdev | 1 | 3 | 1 | 1 | 1 | 3 | 1 | 1 |
++-------+------+------+------+------+------+------+------+------+
+"""
+
+# define grammar for datatable
+underline = Word("-=")
+number = pyparsing_common.integer
+
+vert = Literal("|").suppress()
+
+rowDelim = ("+" + ZeroOrMore( underline + "+" ) ).suppress()
+columnHeader = Group(vert + vert + delimitedList(Word(alphas + nums), "|") + vert)
+
+heading = rowDelim + columnHeader("columns") + rowDelim
+rowData = Group( vert + Word(alphas) + vert + delimitedList(number,"|") + vert )
+trailing = rowDelim
+
+datatable = heading + Dict( ZeroOrMore(rowData) ) + trailing
+
+# now parse data and print results
+data = datatable.parseString(testData)
+print(data.dump())
+print("data keys=", list(data.keys()))
+print("data['min']=", data['min'])
+print("sum(data['min']) =", sum(data['min']))
+print("data.max =", data.max)
+print("sum(data.max) =", sum(data.max))
+
+# now print transpose of data table, using column labels read from table header and
+# values from data lists
+print()
+print(" " * 5, end=' ')
+for i in range(1,len(data)):
+ print("|%5s" % data[i][0], end=' ')
+print()
+print(("-" * 6) + ("+------" * (len(data)-1)))
+for i in range(len(data.columns)):
+ print("%5s" % data.columns[i], end=' ')
+ for j in range(len(data) - 1):
+ print('|%5s' % data[j + 1][i + 1], end=' ')
+ print()
diff --git a/trunk/src/examples/ebnf.py b/trunk/src/examples/ebnf.py
new file mode 100644
index 0000000..8dfcaea
--- /dev/null
+++ b/trunk/src/examples/ebnf.py
@@ -0,0 +1,149 @@
+# This module tries to implement ISO 14977 standard with pyparsing.
+# pyparsing version 1.1 or greater is required.
+
+# ISO 14977 standardize The Extended Backus-Naur Form(EBNF) syntax.
+# You can read a final draft version here:
+# http://www.cl.cam.ac.uk/~mgk25/iso-ebnf.html
+
+
+from pyparsing import *
+
+
+all_names = '''
+integer
+meta_identifier
+terminal_string
+optional_sequence
+repeated_sequence
+grouped_sequence
+syntactic_primary
+syntactic_factor
+syntactic_term
+single_definition
+definitions_list
+syntax_rule
+syntax
+'''.split()
+
+
+integer = Word(nums)
+meta_identifier = Word(alphas, alphanums + '_')
+terminal_string = Suppress("'") + CharsNotIn("'") + Suppress("'") ^ \
+ Suppress('"') + CharsNotIn('"') + Suppress('"')
+definitions_list = Forward()
+optional_sequence = Suppress('[') + definitions_list + Suppress(']')
+repeated_sequence = Suppress('{') + definitions_list + Suppress('}')
+grouped_sequence = Suppress('(') + definitions_list + Suppress(')')
+syntactic_primary = optional_sequence ^ repeated_sequence ^ \
+ grouped_sequence ^ meta_identifier ^ terminal_string
+syntactic_factor = Optional(integer + Suppress('*')) + syntactic_primary
+syntactic_term = syntactic_factor + Optional(Suppress('-') + syntactic_factor)
+single_definition = delimitedList(syntactic_term, ',')
+definitions_list << delimitedList(single_definition, '|')
+syntax_rule = meta_identifier + Suppress('=') + definitions_list + \
+ Suppress(';')
+
+ebnfComment = ( "(*" +
+ ZeroOrMore( CharsNotIn("*") | ( "*" + ~Literal(")") ) ) +
+ "*)" ).streamline().setName("ebnfComment")
+
+syntax = OneOrMore(syntax_rule)
+syntax.ignore(ebnfComment)
+
+
+def do_integer(str, loc, toks):
+ return int(toks[0])
+
+def do_meta_identifier(str, loc, toks):
+ if toks[0] in symbol_table:
+ return symbol_table[toks[0]]
+ else:
+ forward_count.value += 1
+ symbol_table[toks[0]] = Forward()
+ return symbol_table[toks[0]]
+
+def do_terminal_string(str, loc, toks):
+ return Literal(toks[0])
+
+def do_optional_sequence(str, loc, toks):
+ return Optional(toks[0])
+
+def do_repeated_sequence(str, loc, toks):
+ return ZeroOrMore(toks[0])
+
+def do_grouped_sequence(str, loc, toks):
+ return Group(toks[0])
+
+def do_syntactic_primary(str, loc, toks):
+ return toks[0]
+
+def do_syntactic_factor(str, loc, toks):
+ if len(toks) == 2:
+ # integer * syntactic_primary
+ return And([toks[1]] * toks[0])
+ else:
+ # syntactic_primary
+ return [ toks[0] ]
+
+def do_syntactic_term(str, loc, toks):
+ if len(toks) == 2:
+ # syntactic_factor - syntactic_factor
+ return NotAny(toks[1]) + toks[0]
+ else:
+ # syntactic_factor
+ return [ toks[0] ]
+
+def do_single_definition(str, loc, toks):
+ toks = toks.asList()
+ if len(toks) > 1:
+ # syntactic_term , syntactic_term , ...
+ return And(toks)
+ else:
+ # syntactic_term
+ return [ toks[0] ]
+
+def do_definitions_list(str, loc, toks):
+ toks = toks.asList()
+ if len(toks) > 1:
+ # single_definition | single_definition | ...
+ return Or(toks)
+ else:
+ # single_definition
+ return [ toks[0] ]
+
+def do_syntax_rule(str, loc, toks):
+ # meta_identifier = definitions_list ;
+ assert toks[0].expr is None, "Duplicate definition"
+ forward_count.value -= 1
+ toks[0] << toks[1]
+ return [ toks[0] ]
+
+def do_syntax(str, loc, toks):
+ # syntax_rule syntax_rule ...
+ return symbol_table
+
+
+
+symbol_table = {}
+class forward_count:
+ pass
+forward_count.value = 0
+for name in all_names:
+ expr = vars()[name]
+ action = vars()['do_' + name]
+ expr.setName(name)
+ expr.setParseAction(action)
+ #~ expr.setDebug()
+
+
+def parse(ebnf, given_table={}):
+ symbol_table.clear()
+ symbol_table.update(given_table)
+ forward_count.value = 0
+ table = syntax.parseString(ebnf)[0]
+ assert forward_count.value == 0, "Missing definition"
+ for name in table:
+ expr = table[name]
+ expr.setName(name)
+ #~ expr.setDebug()
+ return table
diff --git a/trunk/src/examples/ebnftest.py b/trunk/src/examples/ebnftest.py
new file mode 100644
index 0000000..32c7fed
--- /dev/null
+++ b/trunk/src/examples/ebnftest.py
@@ -0,0 +1,66 @@
+print('Importing pyparsing...')
+from pyparsing import *
+
+print('Constructing EBNF parser with pyparsing...')
+import ebnf
+import sets
+
+
+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['character'] = Word(printables, exact=1)
+#~ table['letter'] = Word(alphas + '_', exact=1)
+#~ table['digit'] = Word(nums, exact=1)
+table['terminal_string'] = sglQuotedString
+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']
+
+commentcharcount = 0
+commentlocs = sets.Set()
+def tallyCommentChars(s,l,t):
+ global commentcharcount,commentlocs
+ # only count this comment if we haven't seen it before
+ if l not in commentlocs:
+ charCount = ( len(t[0]) - len(list(filter(str.isspace, t[0]))) )
+ commentcharcount += charCount
+ commentlocs.add(l)
+ return l,t
+
+#ordinarily, these lines wouldn't be necessary, but we are doing extra stuff with the comment expression
+ebnf.ebnfComment.setParseAction( tallyCommentChars )
+ebnf_parser.ignore( ebnf.ebnfComment )
+
+print('Parsing EBNF grammar with generated EBNF parser...\n')
+parsed_chars = ebnf_parser.parseString(grammar)
+parsed_char_len = len(parsed_chars)
+
+print("],\n".join(str( parsed_chars.asList() ).split("],")))
+
+#~ grammar_length = len(grammar) - len(filter(str.isspace, grammar))-commentcharcount
+
+#~ assert parsed_char_len == grammar_length
+
+print('Ok!')
diff --git a/trunk/src/examples/eval_arith.py b/trunk/src/examples/eval_arith.py
new file mode 100644
index 0000000..9562253
--- /dev/null
+++ b/trunk/src/examples/eval_arith.py
@@ -0,0 +1,227 @@
+# eval_arith.py
+#
+# Copyright 2009, 2011 Paul McGuire
+#
+# Expansion on the pyparsing example simpleArith.py, to include evaluation
+# of the parsed tokens.
+#
+# Added support for exponentiation, using right-to-left evaluation of
+# operands
+#
+from pyparsing import Word, nums, alphas, Combine, oneOf, \
+ opAssoc, infixNotation, Literal
+
+class EvalConstant(object):
+ "Class to evaluate a parsed constant or variable"
+ vars_ = {}
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ if self.value in EvalConstant.vars_:
+ return EvalConstant.vars_[self.value]
+ else:
+ return float(self.value)
+
+class EvalSignOp(object):
+ "Class to evaluate expressions with a leading + or - sign"
+ def __init__(self, tokens):
+ self.sign, self.value = tokens[0]
+ def eval(self):
+ mult = {'+':1, '-':-1}[self.sign]
+ return mult * self.value.eval()
+
+def operatorOperands(tokenlist):
+ "generator to extract operators and operands in pairs"
+ it = iter(tokenlist)
+ while 1:
+ try:
+ yield (next(it), next(it))
+ except StopIteration:
+ break
+
+class EvalPowerOp(object):
+ "Class to evaluate multiplication and division expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ res = self.value[-1].eval()
+ for val in self.value[-3::-2]:
+ res = val.eval()**res
+ return res
+
+class EvalMultOp(object):
+ "Class to evaluate multiplication and division expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ prod = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ if op == '*':
+ prod *= val.eval()
+ if op == '/':
+ prod /= val.eval()
+ return prod
+
+class EvalAddOp(object):
+ "Class to evaluate addition and subtraction expressions"
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ sum = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ if op == '+':
+ sum += val.eval()
+ if op == '-':
+ sum -= val.eval()
+ return sum
+
+class EvalComparisonOp(object):
+ "Class to evaluate comparison expressions"
+ opMap = {
+ "<" : lambda a,b : a < b,
+ "<=" : lambda a,b : a <= b,
+ ">" : lambda a,b : a > b,
+ ">=" : lambda a,b : a >= b,
+ "!=" : lambda a,b : a != b,
+ "=" : lambda a,b : a == b,
+ "LT" : lambda a,b : a < b,
+ "LE" : lambda a,b : a <= b,
+ "GT" : lambda a,b : a > b,
+ "GE" : lambda a,b : a >= b,
+ "NE" : lambda a,b : a != b,
+ "EQ" : lambda a,b : a == b,
+ "<>" : lambda a,b : a != b,
+ }
+ def __init__(self, tokens):
+ self.value = tokens[0]
+ def eval(self):
+ val1 = self.value[0].eval()
+ for op,val in operatorOperands(self.value[1:]):
+ fn = EvalComparisonOp.opMap[op]
+ val2 = val.eval()
+ if not fn(val1,val2):
+ break
+ val1 = val2
+ else:
+ return True
+ return False
+
+
+# define the parser
+integer = Word(nums)
+real = Combine(Word(nums) + "." + Word(nums))
+variable = Word(alphas,exact=1)
+operand = real | integer | variable
+
+signop = oneOf('+ -')
+multop = oneOf('* /')
+plusop = oneOf('+ -')
+expop = Literal('**')
+
+# use parse actions to attach EvalXXX constructors to sub-expressions
+operand.setParseAction(EvalConstant)
+arith_expr = infixNotation(operand,
+ [
+ (signop, 1, opAssoc.RIGHT, EvalSignOp),
+ (expop, 2, opAssoc.LEFT, EvalPowerOp),
+ (multop, 2, opAssoc.LEFT, EvalMultOp),
+ (plusop, 2, opAssoc.LEFT, EvalAddOp),
+ ])
+
+comparisonop = oneOf("< <= > >= != = <> LT GT LE GE EQ NE")
+comp_expr = infixNotation(arith_expr,
+ [
+ (comparisonop, 2, opAssoc.LEFT, EvalComparisonOp),
+ ])
+
+def main():
+ # sample expressions posted on comp.lang.python, asking for advice
+ # in safely evaluating them
+ rules=[
+ '( A - B ) = 0',
+ '(A + B + C + D + E + F + G + H + I) = J',
+ '(A + B + C + D + E + F + G + H) = I',
+ '(A + B + C + D + E + F) = G',
+ '(A + B + C + D + E) = (F + G + H + I + J)',
+ '(A + B + C + D + E) = (F + G + H + I)',
+ '(A + B + C + D + E) = F',
+ '(A + B + C + D) = (E + F + G + H)',
+ '(A + B + C) = (D + E + F)',
+ '(A + B) = (C + D + E + F)',
+ '(A + B) = (C + D)',
+ '(A + B) = (C - D + E - F - G + H + I + J)',
+ '(A + B) = C',
+ '(A + B) = 0',
+ '(A+B+C+D+E) = (F+G+H+I+J)',
+ '(A+B+C+D) = (E+F+G+H)',
+ '(A+B+C+D)=(E+F+G+H)',
+ '(A+B+C)=(D+E+F)',
+ '(A+B)=(C+D)',
+ '(A+B)=C',
+ '(A-B)=C',
+ '(A/(B+C))',
+ '(B/(C+D))',
+ '(G + H) = I',
+ '-0.99 LE ((A+B+C)-(D+E+F+G)) LE 0.99',
+ '-0.99 LE (A-(B+C)) LE 0.99',
+ '-1000.00 LE A LE 0.00',
+ '-5000.00 LE A LE 0.00',
+ 'A < B',
+ 'A < 7000',
+ 'A = -(B)',
+ 'A = C',
+ 'A = 0',
+ 'A GT 0',
+ 'A GT 0.00',
+ 'A GT 7.00',
+ 'A LE B',
+ 'A LT -1000.00',
+ 'A LT -5000',
+ 'A LT 0',
+ 'A=(B+C+D)',
+ 'A=B',
+ 'I = (G + H)',
+ '0.00 LE A LE 4.00',
+ '4.00 LT A LE 7.00',
+ '0.00 LE A LE 4.00 LE E > D',
+ '2**2**(A+3)',
+ ]
+ vars_={'A': 0, 'B': 1.1, 'C': 2.2, 'D': 3.3, 'E': 4.4, 'F': 5.5, 'G':
+ 6.6, 'H':7.7, 'I':8.8, 'J':9.9}
+
+ # define tests from given rules
+ tests = []
+ for t in rules:
+ t_orig = t
+ t = t.replace("=","==")
+ t = t.replace("EQ","==")
+ t = t.replace("LE","<=")
+ t = t.replace("GT",">")
+ t = t.replace("LT","<")
+ t = t.replace("GE",">=")
+ t = t.replace("LE","<=")
+ t = t.replace("NE","!=")
+ t = t.replace("<>","!=")
+ tests.append( (t_orig,eval(t,vars_)) )
+
+ # copy vars_ to EvalConstant lookup dict
+ EvalConstant.vars_ = vars_
+ failed = 0
+ for test,expected in tests:
+ ret = comp_expr.parseString(test)[0]
+ parsedvalue = ret.eval()
+ print(test, expected, parsedvalue)
+ if parsedvalue != expected:
+ print("<<< FAIL")
+ failed += 1
+ else:
+ print('')
+
+ print('')
+ if failed:
+ print(failed, "tests FAILED")
+ else:
+ print("all tests PASSED")
+
+if __name__=='__main__':
+ main()
diff --git a/trunk/src/examples/excelExpr.py b/trunk/src/examples/excelExpr.py
new file mode 100644
index 0000000..2700100
--- /dev/null
+++ b/trunk/src/examples/excelExpr.py
@@ -0,0 +1,68 @@
+# excelExpr.py
+#
+# Copyright 2010, Paul McGuire
+#
+# A partial implementation of a parser of Excel formula expressions.
+#
+from pyparsing import (CaselessKeyword, Suppress, Word, alphas,
+ alphanums, nums, Optional, Group, oneOf, Forward, Regex,
+ infixNotation, opAssoc, dblQuotedString, delimitedList,
+ Combine, Literal, QuotedString, ParserElement)
+ParserElement.enablePackrat()
+
+EQ,EXCL,LPAR,RPAR,COLON,COMMA = map(Suppress, '=!():,')
+EXCL, DOLLAR = map(Literal,"!$")
+sheetRef = Word(alphas, alphanums) | QuotedString("'",escQuote="''")
+colRef = Optional(DOLLAR) + Word(alphas,max=2)
+rowRef = Optional(DOLLAR) + Word(nums)
+cellRef = Combine(Group(Optional(sheetRef + EXCL)("sheet") + colRef("col") +
+ rowRef("row")))
+
+cellRange = (Group(cellRef("start") + COLON + cellRef("end"))("range")
+ | cellRef | Word(alphas,alphanums))
+
+expr = Forward()
+
+COMPARISON_OP = oneOf("< = > >= <= != <>")
+condExpr = expr + COMPARISON_OP + expr
+
+ifFunc = (CaselessKeyword("if") +
+ LPAR +
+ Group(condExpr)("condition") +
+ COMMA + Group(expr)("if_true") +
+ COMMA + Group(expr)("if_false") + RPAR)
+
+statFunc = lambda name : Group(CaselessKeyword(name) + Group(LPAR + delimitedList(expr) + RPAR))
+sumFunc = statFunc("sum")
+minFunc = statFunc("min")
+maxFunc = statFunc("max")
+aveFunc = statFunc("ave")
+funcCall = ifFunc | sumFunc | minFunc | maxFunc | aveFunc
+
+multOp = oneOf("* /")
+addOp = oneOf("+ -")
+numericLiteral = Regex(r"\-?\d+(\.\d+)?")
+operand = numericLiteral | funcCall | cellRange | cellRef
+arithExpr = infixNotation(operand,
+ [
+ (multOp, 2, opAssoc.LEFT),
+ (addOp, 2, opAssoc.LEFT),
+ ])
+
+textOperand = dblQuotedString | cellRef
+textExpr = infixNotation(textOperand,
+ [
+ ('&', 2, opAssoc.LEFT),
+ ])
+
+expr << (arithExpr | textExpr)
+
+
+(EQ + expr).runTests("""\
+ =3*A7+5"
+ =3*Sheet1!$A$7+5"
+ =3*'Sheet 1'!$A$7+5"
+ =3*'O''Reilly''s sheet'!$A$7+5"
+ =if(Sum(A1:A25)>42,Min(B1:B25),if(Sum(C1:C25)>3.14, (Min(C1:C25)+3)*18,Max(B1:B25)))"
+ =sum(a1:a25,10,min(b1,c2,d3))
+""") \ No newline at end of file
diff --git a/trunk/src/examples/fourFn.py b/trunk/src/examples/fourFn.py
new file mode 100644
index 0000000..75f3909
--- /dev/null
+++ b/trunk/src/examples/fourFn.py
@@ -0,0 +1,192 @@
+# fourFn.py
+#
+# Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
+# with support for scientific notation, and symbols for e and pi.
+# Extended to add exponentiation and simple built-in functions.
+# Extended test cases, simplified pushFirst method.
+# Removed unnecessary expr.suppress() call (thanks Nathaniel Peterson!), and added Group
+# Changed fnumber to use a Regex, which is now the preferred method
+#
+# Copyright 2003-2009 by Paul McGuire
+#
+from pyparsing import Literal,CaselessLiteral,Word,Group,Optional,\
+ ZeroOrMore,Forward,nums,alphas,alphanums,Regex,ParseException,\
+ CaselessKeyword, Suppress
+import math
+import operator
+
+exprStack = []
+
+def pushFirst( strg, loc, toks ):
+ exprStack.append( toks[0] )
+def pushUMinus( strg, loc, toks ):
+ for t in toks:
+ if t == '-':
+ exprStack.append( 'unary -' )
+ #~ exprStack.append( '-1' )
+ #~ exprStack.append( '*' )
+ else:
+ break
+
+bnf = None
+def BNF():
+ """
+ expop :: '^'
+ multop :: '*' | '/'
+ addop :: '+' | '-'
+ integer :: ['+' | '-'] '0'..'9'+
+ atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
+ factor :: atom [ expop factor ]*
+ term :: factor [ multop factor ]*
+ expr :: term [ addop term ]*
+ """
+ global bnf
+ if not bnf:
+ point = Literal( "." )
+ # use CaselessKeyword for e and pi, to avoid accidentally matching
+ # functions that start with 'e' or 'pi' (such as 'exp'); Keyword
+ # and CaselessKeyword only match whole words
+ e = CaselessKeyword( "E" )
+ pi = CaselessKeyword( "PI" )
+ #~ fnumber = Combine( Word( "+-"+nums, nums ) +
+ #~ Optional( point + Optional( Word( nums ) ) ) +
+ #~ Optional( e + Word( "+-"+nums, nums ) ) )
+ fnumber = Regex(r"[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?")
+ ident = Word(alphas, alphanums+"_$")
+
+ plus, minus, mult, div = map(Literal, "+-*/")
+ lpar, rpar = map(Suppress, "()")
+ addop = plus | minus
+ multop = mult | div
+ expop = Literal( "^" )
+
+ expr = Forward()
+ atom = ((0,None)*minus + ( pi | e | fnumber | ident + lpar + expr + rpar | ident ).setParseAction( pushFirst ) |
+ Group( lpar + expr + rpar )).setParseAction(pushUMinus)
+
+ # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
+ # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
+ factor = Forward()
+ factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
+
+ term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
+ expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
+ bnf = expr
+ return bnf
+
+# map operator symbols to corresponding arithmetic operations
+epsilon = 1e-12
+opn = { "+" : operator.add,
+ "-" : operator.sub,
+ "*" : operator.mul,
+ "/" : operator.truediv,
+ "^" : operator.pow }
+fn = { "sin" : math.sin,
+ "cos" : math.cos,
+ "tan" : math.tan,
+ "exp" : math.exp,
+ "abs" : abs,
+ "trunc" : lambda a: int(a),
+ "round" : round,
+ "sgn" : lambda a: (a > epsilon) - (a < -epsilon) }
+def evaluateStack( s ):
+ op = s.pop()
+ if op == 'unary -':
+ return -evaluateStack( s )
+ if op in "+-*/^":
+ op2 = evaluateStack( s )
+ op1 = evaluateStack( s )
+ return opn[op]( op1, op2 )
+ elif op == "PI":
+ return math.pi # 3.1415926535
+ elif op == "E":
+ return math.e # 2.718281828
+ elif op in fn:
+ return fn[op]( evaluateStack( s ) )
+ elif op[0].isalpha():
+ raise Exception("invalid identifier '%s'" % op)
+ else:
+ return float( op )
+
+if __name__ == "__main__":
+
+ def test( s, expVal ):
+ global exprStack
+ exprStack[:] = []
+ try:
+ results = BNF().parseString( s, parseAll=True )
+ val = evaluateStack( exprStack[:] )
+ except ParseException as e:
+ print(s, "failed parse:", str(pe))
+ except Exception as e:
+ print(s, "failed eval:", str(e))
+ else:
+ if val == expVal:
+ print(s, "=", val, results, "=>", exprStack)
+ else:
+ print(s+"!!!", val, "!=", expVal, results, "=>", exprStack)
+
+ test( "9", 9 )
+ test( "-9", -9 )
+ test( "--9", 9 )
+ test( "-E", -math.e )
+ test( "9 + 3 + 6", 9 + 3 + 6 )
+ test( "9 + 3 / 11", 9 + 3.0 / 11 )
+ test( "(9 + 3)", (9 + 3) )
+ test( "(9+3) / 11", (9+3.0) / 11 )
+ test( "9 - 12 - 6", 9 - 12 - 6 )
+ test( "9 - (12 - 6)", 9 - (12 - 6) )
+ test( "2*3.14159", 2*3.14159 )
+ test( "3.1415926535*3.1415926535 / 10", 3.1415926535*3.1415926535 / 10 )
+ test( "PI * PI / 10", math.pi * math.pi / 10 )
+ test( "PI*PI/10", math.pi*math.pi/10 )
+ test( "PI^2", math.pi**2 )
+ test( "round(PI^2)", round(math.pi**2) )
+ test( "6.02E23 * 8.048", 6.02E23 * 8.048 )
+ test( "e / 3", math.e / 3 )
+ test( "sin(PI/2)", math.sin(math.pi/2) )
+ test( "trunc(E)", int(math.e) )
+ test( "trunc(-E)", int(-math.e) )
+ test( "round(E)", round(math.e) )
+ test( "round(-E)", round(-math.e) )
+ test( "E^PI", math.e**math.pi )
+ test( "exp(0)", 1 )
+ test( "exp(1)", math.e )
+ test( "2^3^2", 2**3**2 )
+ test( "2^3+2", 2**3+2 )
+ test( "2^3+5", 2**3+5 )
+ test( "2^9", 2**9 )
+ test( "sgn(-2)", -1 )
+ test( "sgn(0)", 0 )
+ test( "foo(0.1)", None )
+ test( "sgn(0.1)", 1 )
+
+
+"""
+Test output:
+>pythonw -u fourFn.py
+9 = 9.0 ['9'] => ['9']
+9 + 3 + 6 = 18.0 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+']
+9 + 3 / 11 = 9.27272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+']
+(9 + 3) = 12.0 [] => ['9', '3', '+']
+(9+3) / 11 = 1.09090909091 ['/', '11'] => ['9', '3', '+', '11', '/']
+9 - 12 - 6 = -9.0 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-']
+9 - (12 - 6) = 3.0 ['9', '-'] => ['9', '12', '6', '-', '-']
+2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*']
+3.1415926535*3.1415926535 / 10 = 0.986960440053 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/']
+PI * PI / 10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
+PI*PI/10 = 0.986960440109 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']
+PI^2 = 9.86960440109 ['PI', '^', '2'] => ['PI', '2', '^']
+6.02E23 * 8.048 = 4.844896e+024 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*']
+e / 3 = 0.90609394282 ['E', '/', '3'] => ['E', '3', '/']
+sin(PI/2) = 1.0 ['sin', 'PI', '/', '2'] => ['PI', '2', '/', 'sin']
+trunc(E) = 2 ['trunc', 'E'] => ['E', 'trunc']
+E^PI = 23.1406926328 ['E', '^', 'PI'] => ['E', 'PI', '^']
+2^3^2 = 512.0 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^']
+2^3+2 = 10.0 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+']
+2^9 = 512.0 ['2', '^', '9'] => ['2', '9', '^']
+sgn(-2) = -1 ['sgn', '-2'] => ['-2', 'sgn']
+sgn(0) = 0 ['sgn', '0'] => ['0', 'sgn']
+sgn(0.1) = 1 ['sgn', '0.1'] => ['0.1', 'sgn']
+>Exit code: 0
+"""
diff --git a/trunk/src/examples/gen_ctypes.py b/trunk/src/examples/gen_ctypes.py
new file mode 100644
index 0000000..2c909e4
--- /dev/null
+++ b/trunk/src/examples/gen_ctypes.py
@@ -0,0 +1,167 @@
+from pyparsing import *
+
+typemap = {
+ "byte" : "c_byte",
+ "char" : "c_char",
+ "char *" : "c_char_p",
+ "double" : "c_double",
+ "float" : "c_float",
+ "int" : "c_int",
+ "int16" : "c_int16",
+ "int32" : "c_int32",
+ "int64" : "c_int64",
+ "int8" : "c_int8",
+ "long" : "c_long",
+ "longlong" : "c_longlong",
+ "short" : "c_short",
+ "size_t" : "c_size_t",
+ "ubyte" : "c_ubyte",
+ "uchar" : "c_ubyte",
+ "u_char" : "c_ubyte",
+ "uint" : "c_uint",
+ "u_int" : "c_uint",
+ "uint16" : "c_uint16",
+ "uint32" : "c_uint32",
+ "uint64" : "c_uint64",
+ "uint8" : "c_uint8",
+ "u_long" : "c_ulong",
+ "ulong" : "c_ulong",
+ "ulonglong" : "c_ulonglong",
+ "ushort" : "c_ushort",
+ "u_short" : "c_ushort",
+ "void *" : "c_void_p",
+ "voidp" : "c_voidp",
+ "wchar" : "c_wchar",
+ "wchar *" : "c_wchar_p",
+ "Bool" : "c_bool",
+ "void" : "None",
+ }
+
+LPAR,RPAR,LBRACE,RBRACE,COMMA,SEMI = map(Suppress,"(){},;")
+ident = Word(alphas, alphanums + "_")
+integer = Regex(r"[+-]?\d+")
+hexinteger = Regex(r"0x[0-9a-fA-F]+")
+
+const = Suppress("const")
+primitiveType = oneOf(t for t in typemap if not t.endswith("*"))
+structType = Suppress("struct") + ident
+vartype = (Optional(const) +
+ (primitiveType | structType | ident) +
+ Optional(Word("*")("ptr")))
+def normalizetype(t):
+ if isinstance(t, ParseResults):
+ return ' '.join(t)
+ #~ ret = ParseResults([' '.join(t)])
+ #~ return ret
+
+vartype.setParseAction(normalizetype)
+
+arg = Group(vartype("argtype") + Optional(ident("argname")))
+func_def = (vartype("fn_type") + ident("fn_name") +
+ LPAR + Optional(delimitedList(arg|"..."))("fn_args") + RPAR + SEMI)
+def derivefields(t):
+ if t.fn_args and t.fn_args[-1] == "...":
+ t["varargs"]=True
+func_def.setParseAction(derivefields)
+
+fn_typedef = "typedef" + func_def
+var_typedef = "typedef" + primitiveType("primType") + ident("name") + SEMI
+
+enum_def = (Keyword("enum") + LBRACE +
+ delimitedList(Group(ident("name") + '=' + (hexinteger|integer)("value")))("evalues")
+ + Optional(COMMA)
+ + RBRACE)
+
+c_header = open("snmp_api.h").read()
+
+
+module = "pynetsnmp"
+
+user_defined_types = set()
+typedefs = []
+fn_typedefs = []
+functions = []
+enum_constants = []
+
+# add structures commonly included from std lib headers
+def addStdType(t,namespace=""):
+ fullname = namespace+'_'+t if namespace else t
+ typemap[t] = fullname
+ user_defined_types.add(t)
+addStdType("fd_set", "sys_select")
+addStdType("timeval", "sys_time")
+
+def getUDType(typestr):
+ key = typestr.rstrip(" *")
+ if key not in typemap:
+ user_defined_types.add(key)
+ typemap[key] = "%s_%s" % (module, key)
+
+def typeAsCtypes(typestr):
+ if typestr in typemap:
+ return typemap[typestr]
+ if typestr.endswith("*"):
+ return "POINTER(%s)" % typeAsCtypes(typestr.rstrip(" *"))
+ return typestr
+
+# scan input header text for primitive typedefs
+for td,_,_ in var_typedef.scanString(c_header):
+ typedefs.append( (td.name, td.primType) )
+ # add typedef type to typemap to map to itself
+ typemap[td.name] = td.name
+
+# scan input header text for function typedefs
+fn_typedefs = fn_typedef.searchString(c_header)
+# add each function typedef to typemap to map to itself
+for fntd in fn_typedefs:
+ typemap[fntd.fn_name] = fntd.fn_name
+
+# scan input header text, and keep running list of user-defined types
+for fn,_,_ in (cStyleComment.suppress() | fn_typedef.suppress() | func_def).scanString(c_header):
+ if not fn: continue
+ getUDType(fn.fn_type)
+ for arg in fn.fn_args:
+ if arg != "...":
+ if arg.argtype not in typemap:
+ getUDType(arg.argtype)
+ functions.append(fn)
+
+# scan input header text for enums
+enum_def.ignore(cppStyleComment)
+for en_,_,_ in enum_def.scanString(c_header):
+ for ev in en_.evalues:
+ enum_constants.append( (ev.name, ev.value) )
+
+print("from ctypes import *")
+print("%s = CDLL('%s.dll')" % (module, module))
+print()
+print("# user defined types")
+for tdname,tdtyp in typedefs:
+ print("%s = %s" % (tdname, typemap[tdtyp]))
+for fntd in fn_typedefs:
+ print("%s = CFUNCTYPE(%s)" % (fntd.fn_name,
+ ',\n '.join(typeAsCtypes(a.argtype) for a in fntd.fn_args)))
+for udtype in user_defined_types:
+ print("class %s(Structure): pass" % typemap[udtype])
+
+print()
+print("# constant definitions")
+for en,ev in enum_constants:
+ print("%s = %s" % (en,ev))
+
+print()
+print("# functions")
+for fn in functions:
+ prefix = "%s.%s" % (module, fn.fn_name)
+
+ print("%s.restype = %s" % (prefix, typeAsCtypes(fn.fn_type)))
+ if fn.varargs:
+ print("# warning - %s takes variable argument list" % prefix)
+ del fn.fn_args[-1]
+
+ if fn.fn_args.asList() != [['void']]:
+ print("%s.argtypes = (%s,)" % (prefix, ','.join(typeAsCtypes(a.argtype) for a in fn.fn_args)))
+ else:
+ print("%s.argtypes = ()" % (prefix))
+
+
diff --git a/trunk/src/examples/getNTPservers.py b/trunk/src/examples/getNTPservers.py
new file mode 100644
index 0000000..bbf1d60
--- /dev/null
+++ b/trunk/src/examples/getNTPservers.py
@@ -0,0 +1,30 @@
+# getNTPservers.py
+#
+# Demonstration of the parsing module, implementing a HTML page scanner,
+# to extract a list of NTP time servers from the NIST web site.
+#
+# Copyright 2004, by Paul McGuire
+#
+from pyparsing import Word, Combine, Suppress, CharsNotIn, nums
+import urllib.request, urllib.parse, urllib.error
+
+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
+
+# get list of time servers
+nistTimeServerURL = "http://www.boulder.nist.gov/timefreq/service/time-servers.html"
+serverListPage = urllib.request.urlopen( nistTimeServerURL )
+serverListHTML = serverListPage.read()
+serverListPage.close()
+
+addrs = {}
+for srvr,startloc,endloc in timeServerPattern.scanString( serverListHTML ):
+ print(srvr.ipAddr, "-", srvr.loc)
+ addrs[srvr.ipAddr] = srvr.loc
+ # or do this:
+ #~ addr,loc = srvr
+ #~ print addr, "-", loc
diff --git a/trunk/src/examples/getNTPserversNew.py b/trunk/src/examples/getNTPserversNew.py
new file mode 100644
index 0000000..14e710c
--- /dev/null
+++ b/trunk/src/examples/getNTPserversNew.py
@@ -0,0 +1,35 @@
+# getNTPserversNew.py
+#
+# Demonstration of the parsing module, implementing a HTML page scanner,
+# to extract a list of NTP time servers from the NIST web site.
+#
+# Copyright 2004-2010, by Paul McGuire
+# September, 2010 - updated to more current use of setResultsName, new NIST URL
+#
+from pyparsing import (Word, Combine, Suppress, SkipTo, nums, makeHTMLTags,
+ delimitedList, alphas, alphanums)
+try:
+ import urllib.request
+ urlopen = urllib.request.urlopen
+except ImportError:
+ import urllib
+ urlopen = urllib.urlopen
+
+integer = Word(nums)
+ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer )
+hostname = delimitedList(Word(alphas,alphanums+"-_"),".",combine=True)
+tdStart,tdEnd = makeHTMLTags("td")
+timeServerPattern = (tdStart + hostname("hostname") + tdEnd +
+ tdStart + ipAddress("ipAddr") + tdEnd +
+ tdStart + SkipTo(tdEnd)("loc") + tdEnd)
+
+# get list of time servers
+nistTimeServerURL = "http://tf.nist.gov/tf-cgi/servers.cgi#"
+serverListPage = urlopen( nistTimeServerURL )
+serverListHTML = serverListPage.read().decode("UTF-8")
+serverListPage.close()
+
+addrs = {}
+for srvr,startloc,endloc in timeServerPattern.scanString( serverListHTML ):
+ print("%s (%s) - %s" % (srvr.ipAddr, srvr.hostname.strip(), srvr.loc.strip()))
+ addrs[srvr.ipAddr] = srvr.loc
diff --git a/trunk/src/examples/greeting.py b/trunk/src/examples/greeting.py
new file mode 100644
index 0000000..2e6b241
--- /dev/null
+++ b/trunk/src/examples/greeting.py
@@ -0,0 +1,17 @@
+# greeting.py
+#
+# Demonstration of the pyparsing module, on the prototypical "Hello, World!"
+# example
+#
+# Copyright 2003, by Paul McGuire
+#
+from pyparsing import Word, alphas
+
+# define grammar
+greet = Word( alphas ) + "," + Word( alphas ) + "!"
+
+# input string
+hello = "Hello, World!"
+
+# parse input string
+print(hello, "->", greet.parseString( hello ))
diff --git a/trunk/src/examples/greetingInGreek.py b/trunk/src/examples/greetingInGreek.py
new file mode 100644
index 0000000..07a8f0a
--- /dev/null
+++ b/trunk/src/examples/greetingInGreek.py
@@ -0,0 +1,18 @@
+# vim:fileencoding=utf-8
+#
+# greetingInGreek.py
+#
+# Demonstration of the parsing module, on the prototypical "Hello, World!" example
+#
+from pyparsing import Word
+
+# define grammar
+alphas = ''.join(chr(x) for x in range(0x386, 0x3ce))
+greet = Word(alphas) + ',' + Word(alphas) + '!'
+
+# input string
+hello = "ΚαλημέÏα, κόσμε!".decode('utf-8')
+
+# parse input string
+print(greet.parseString( hello ))
+
diff --git a/trunk/src/examples/greetingInKorean.py b/trunk/src/examples/greetingInKorean.py
new file mode 100644
index 0000000..d0ea0e5
--- /dev/null
+++ b/trunk/src/examples/greetingInKorean.py
@@ -0,0 +1,20 @@
+# vim:fileencoding=utf-8
+#
+# greetingInKorean.py
+#
+# Demonstration of the parsing module, on the prototypical "Hello, World!" example
+#
+from pyparsing import Word, srange
+
+koreanChars = srange(r"[\0xac00-\0xd7a3]")
+koreanWord = Word(koreanChars,min=2)
+
+# define grammar
+greet = koreanWord + "," + koreanWord + "!"
+
+# input string
+hello = '\uc548\ub155, \uc5ec\ub7ec\ubd84!' #"Hello, World!" in Korean
+
+# parse input string
+print(greet.parseString( hello ))
+
diff --git a/trunk/src/examples/groupUsingListAllMatches.py b/trunk/src/examples/groupUsingListAllMatches.py
new file mode 100644
index 0000000..d5037b8
--- /dev/null
+++ b/trunk/src/examples/groupUsingListAllMatches.py
@@ -0,0 +1,16 @@
+#
+# A simple example showing the use of the implied listAllMatches=True for
+# results names with a trailing '*' character.
+#
+# This example performs work similar to itertools.groupby, but without
+# having to sort the input first.
+#
+from pyparsing import Word, ZeroOrMore, nums
+
+aExpr = Word("A", nums)
+bExpr = Word("B", nums)
+cExpr = Word("C", nums)
+grammar = ZeroOrMore(aExpr("A*") | bExpr("B*") | cExpr("C*"))
+
+results = grammar.parseString("A1 B1 A2 C1 B2 A3")
+print(results.dump())
diff --git a/trunk/src/examples/holaMundo.py b/trunk/src/examples/holaMundo.py
new file mode 100644
index 0000000..357e3c8
--- /dev/null
+++ b/trunk/src/examples/holaMundo.py
@@ -0,0 +1,30 @@
+# -*- coding: UTF-8 -*-
+
+# escrito por Marco Alfonso, 2004 Noviembre
+
+# importamos el modulo
+from pyparsing import *
+saludo= Word(alphas) + ',' + Word(alphas) + '!'
+
+# Aqui decimos que la gramatica "saludo" DEBE contener
+# una palabra compuesta de caracteres alfanumericos
+# (Word(alphas)) mas una ',' mas otra palabra alfanumerica,
+# mas '!' y esos seian nuestros tokens
+tokens = saludo.parseString("Hola, Mundo !")
+
+# Ahora parseamos una cadena, "Hola, Mundo!",
+# el metodo parseString, nos devuelve una lista con los tokens
+# encontrados, en caso de no haber errores...
+for i in range(len(tokens)):
+ print ("Token %d -> %s" % (i,tokens[i]))
+
+#imprimimos cada uno de los tokens Y listooo!!, he aquí la salida
+# Token 0—> Hola Token 1—> , Token 2—> Mundo Token 3—> !
+
+# Por supuesto, se pueden “reutilizar” gramáticas, por ejemplo:
+numimag = Word(nums) + 'i'
+numreal = Word(nums)
+numcomplex = numreal + '+' + numimag
+print (numcomplex.parseString("3+5i"))
+
+# Excelente!!, bueno, los dejo, me voy a seguir tirando código…
diff --git a/trunk/src/examples/htmlStripper.py b/trunk/src/examples/htmlStripper.py
new file mode 100644
index 0000000..0b0f459
--- /dev/null
+++ b/trunk/src/examples/htmlStripper.py
@@ -0,0 +1,39 @@
+#
+# htmlStripper.py
+#
+# Sample code for stripping HTML markup tags and scripts from
+# HTML source files.
+#
+# Copyright (c) 2006, Paul McGuire
+#
+from pyparsing import *
+import urllib.request, urllib.parse, urllib.error
+
+removeText = replaceWith("")
+scriptOpen,scriptClose = makeHTMLTags("script")
+scriptBody = scriptOpen + SkipTo(scriptClose) + scriptClose
+scriptBody.setParseAction(removeText)
+
+anyTag,anyClose = makeHTMLTags(Word(alphas,alphanums+":_"))
+anyTag.setParseAction(removeText)
+anyClose.setParseAction(removeText)
+htmlComment.setParseAction(removeText)
+
+commonHTMLEntity.setParseAction(replaceHTMLEntity)
+
+# get some HTML
+targetURL = "http://wiki.python.org/moin/PythonDecoratorLibrary"
+targetPage = urllib.request.urlopen( targetURL )
+targetHTML = targetPage.read()
+targetPage.close()
+
+# first pass, strip out tags and translate entities
+firstPass = (htmlComment | scriptBody | commonHTMLEntity |
+ anyTag | anyClose ).transformString(targetHTML)
+
+# first pass leaves many blank lines, collapse these down
+repeatedNewlines = LineEnd() + OneOrMore(LineEnd())
+repeatedNewlines.setParseAction(replaceWith("\n\n"))
+secondPass = repeatedNewlines.transformString(firstPass)
+
+print(secondPass) \ No newline at end of file
diff --git a/trunk/src/examples/httpServerLogParser.py b/trunk/src/examples/httpServerLogParser.py
new file mode 100644
index 0000000..1a808ae
--- /dev/null
+++ b/trunk/src/examples/httpServerLogParser.py
@@ -0,0 +1,71 @@
+# httpServerLogParser.py
+#
+"""
+Parser for HTTP server log output, of the form:
+
+195.146.134.15 - - [20/Jan/2003:08:55:36 -0800]
+"GET /path/to/page.html HTTP/1.0" 200 4649 "http://www.somedomain.com/020602/page.html"
+"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
+127.0.0.1 - u.surname@domain.com [12/Sep/2006:14:13:53 +0300]
+"GET /skins/monobook/external.png HTTP/1.0" 304 - "http://wiki.mysite.com/skins/monobook/main.css"
+"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6"
+
+You can then break it up as follows:
+IP ADDRESS - -
+Server Date / Time [SPACE]
+"GET /path/to/page
+HTTP/Type Request"
+Success Code
+Bytes Sent To Client
+Referer
+Client Software
+"""
+
+from pyparsing import alphas,nums, dblQuotedString, Combine, Word, Group, delimitedList, Suppress, removeQuotes
+import string
+
+def getCmdFields( s, l, t ):
+ t["method"],t["requestURI"],t["protocolVersion"] = t[0].strip('"').split()
+
+logLineBNF = None
+def getLogLineBNF():
+ global logLineBNF
+
+ if logLineBNF is None:
+ integer = Word( nums )
+ ipAddress = delimitedList( integer, ".", combine=True )
+
+ timeZoneOffset = Word("+-",nums)
+ month = Word(string.uppercase, string.lowercase, exact=3)
+ serverDateTime = Group( Suppress("[") +
+ Combine( integer + "/" + month + "/" + integer +
+ ":" + integer + ":" + integer + ":" + integer ) +
+ timeZoneOffset +
+ Suppress("]") )
+
+ logLineBNF = ( ipAddress.setResultsName("ipAddr") +
+ Suppress("-") +
+ ("-" | Word( alphas+nums+"@._" )).setResultsName("auth") +
+ serverDateTime.setResultsName("timestamp") +
+ dblQuotedString.setResultsName("cmd").setParseAction(getCmdFields) +
+ (integer | "-").setResultsName("statusCode") +
+ (integer | "-").setResultsName("numBytesSent") +
+ dblQuotedString.setResultsName("referrer").setParseAction(removeQuotes) +
+ dblQuotedString.setResultsName("clientSfw").setParseAction(removeQuotes) )
+ return logLineBNF
+
+testdata = """
+195.146.134.15 - - [20/Jan/2003:08:55:36 -0800] "GET /path/to/page.html HTTP/1.0" 200 4649 "http://www.somedomain.com/020602/page.html" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
+111.111.111.11 - - [16/Feb/2004:04:09:49 -0800] "GET /ads/redirectads/336x280redirect.htm HTTP/1.1" 304 - "http://www.foobarp.org/theme_detail.php?type=vs&cat=0&mid=27512" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
+11.111.11.111 - - [16/Feb/2004:10:35:12 -0800] "GET /ads/redirectads/468x60redirect.htm HTTP/1.1" 200 541 "http://11.11.111.11/adframe.php?n=ad1f311a&what=zone:56" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) Opera 7.20 [ru\"]"
+127.0.0.1 - u.surname@domain.com [12/Sep/2006:14:13:53 +0300] "GET /skins/monobook/external.png HTTP/1.0" 304 - "http://wiki.mysite.com/skins/monobook/main.css" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6"
+"""
+for line in testdata.split("\n"):
+ if not line: continue
+ fields = getLogLineBNF().parseString(line)
+ print(fields.dump())
+ #~ print repr(fields)
+ #~ for k in fields.keys():
+ #~ print "fields." + k + " =", fields[k]
+ print(fields.asXML("LOG"))
+ print()
diff --git a/trunk/src/examples/idlParse.py b/trunk/src/examples/idlParse.py
new file mode 100644
index 0000000..dd556e5
--- /dev/null
+++ b/trunk/src/examples/idlParse.py
@@ -0,0 +1,222 @@
+#
+# idlparse.py
+#
+# an example of using the parsing module to be able to process a subset of the CORBA IDL grammar
+#
+# Copyright (c) 2003, Paul McGuire
+#
+
+from pyparsing import Literal, CaselessLiteral, Word, OneOrMore, ZeroOrMore, \
+ Forward, NotAny, delimitedList, oneOf, Group, Optional, Combine, alphas, nums, restOfLine, cStyleComment, \
+ alphanums, printables, empty, quotedString, ParseException, ParseResults, Keyword, Regex
+import pprint
+#~ import tree2image
+
+bnf = None
+def CORBA_IDL_BNF():
+ global bnf
+
+ if not bnf:
+
+ # punctuation
+ colon = Literal(":")
+ lbrace = Literal("{")
+ rbrace = Literal("}")
+ lbrack = Literal("[")
+ rbrack = Literal("]")
+ lparen = Literal("(")
+ rparen = Literal(")")
+ equals = Literal("=")
+ comma = Literal(",")
+ dot = Literal(".")
+ slash = Literal("/")
+ bslash = Literal("\\")
+ star = Literal("*")
+ semi = Literal(";")
+ langle = Literal("<")
+ rangle = Literal(">")
+
+ # keywords
+ any_ = Keyword("any")
+ attribute_ = Keyword("attribute")
+ boolean_ = Keyword("boolean")
+ case_ = Keyword("case")
+ char_ = Keyword("char")
+ const_ = Keyword("const")
+ context_ = Keyword("context")
+ default_ = Keyword("default")
+ double_ = Keyword("double")
+ enum_ = Keyword("enum")
+ exception_ = Keyword("exception")
+ false_ = Keyword("FALSE")
+ fixed_ = Keyword("fixed")
+ float_ = Keyword("float")
+ inout_ = Keyword("inout")
+ interface_ = Keyword("interface")
+ in_ = Keyword("in")
+ long_ = Keyword("long")
+ module_ = Keyword("module")
+ object_ = Keyword("Object")
+ octet_ = Keyword("octet")
+ oneway_ = Keyword("oneway")
+ out_ = Keyword("out")
+ raises_ = Keyword("raises")
+ readonly_ = Keyword("readonly")
+ sequence_ = Keyword("sequence")
+ short_ = Keyword("short")
+ string_ = Keyword("string")
+ struct_ = Keyword("struct")
+ switch_ = Keyword("switch")
+ true_ = Keyword("TRUE")
+ typedef_ = Keyword("typedef")
+ unsigned_ = Keyword("unsigned")
+ union_ = Keyword("union")
+ void_ = Keyword("void")
+ wchar_ = Keyword("wchar")
+ wstring_ = Keyword("wstring")
+
+ identifier = Word( alphas, alphanums + "_" ).setName("identifier")
+
+ #~ real = Combine( Word(nums+"+-", nums) + dot + Optional( Word(nums) )
+ #~ + Optional( CaselessLiteral("E") + Word(nums+"+-",nums) ) )
+ real = Regex(r"[+-]?\d+\.\d*([Ee][+-]?\d+)?").setName("real")
+ #~ integer = ( Combine( CaselessLiteral("0x") + Word( nums+"abcdefABCDEF" ) ) |
+ #~ Word( nums+"+-", nums ) ).setName("int")
+ integer = Regex(r"0x[0-9a-fA-F]+|[+-]?\d+").setName("int")
+
+ udTypeName = delimitedList( identifier, "::", combine=True ).setName("udType")
+ # have to use longest match for type, in case a user-defined type name starts with a keyword type, like "stringSeq" or "longArray"
+ typeName = ( any_ ^ boolean_ ^ char_ ^ double_ ^ fixed_ ^
+ float_ ^ long_ ^ octet_ ^ short_ ^ string_ ^
+ wchar_ ^ wstring_ ^ udTypeName ).setName("type")
+ sequenceDef = Forward().setName("seq")
+ sequenceDef << Group( sequence_ + langle + ( sequenceDef | typeName ) + rangle )
+ typeDef = sequenceDef | ( typeName + Optional( lbrack + integer + rbrack ) )
+ typedefDef = Group( typedef_ + typeDef + identifier + semi ).setName("typedef")
+
+ moduleDef = Forward()
+ constDef = Group( const_ + typeDef + identifier + equals + ( real | integer | quotedString ) + semi ) #| quotedString )
+ exceptionItem = Group( typeDef + identifier + semi )
+ exceptionDef = ( exception_ + identifier + lbrace + ZeroOrMore( exceptionItem ) + rbrace + semi )
+ attributeDef = Optional( readonly_ ) + attribute_ + typeDef + identifier + semi
+ paramlist = delimitedList( Group( ( inout_ | in_ | out_ ) + typeName + identifier ) ).setName( "paramlist" )
+ operationDef = ( ( void_ ^ typeDef ) + identifier + lparen + Optional( paramlist ) + rparen + \
+ Optional( raises_ + lparen + Group( delimitedList( typeName ) ) + rparen ) + semi )
+ interfaceItem = ( constDef | exceptionDef | attributeDef | operationDef )
+ interfaceDef = Group( interface_ + identifier + Optional( colon + delimitedList( typeName ) ) + lbrace + \
+ ZeroOrMore( interfaceItem ) + rbrace + semi ).setName("opnDef")
+ moduleItem = ( interfaceDef | exceptionDef | constDef | typedefDef | moduleDef )
+ moduleDef << module_ + identifier + lbrace + ZeroOrMore( moduleItem ) + rbrace + semi
+
+ bnf = ( moduleDef | OneOrMore( moduleItem ) )
+
+ singleLineComment = "//" + restOfLine
+ bnf.ignore( singleLineComment )
+ bnf.ignore( cStyleComment )
+
+ return bnf
+
+testnum = 1
+def test( strng ):
+ global testnum
+ print(strng)
+ try:
+ bnf = CORBA_IDL_BNF()
+ tokens = bnf.parseString( strng )
+ print("tokens = ")
+ pprint.pprint( tokens.asList() )
+ imgname = "idlParse%02d.bmp" % testnum
+ testnum += 1
+ #~ tree2image.str2image( str(tokens.asList()), imgname )
+ except ParseException as err:
+ print(err.line)
+ print(" "*(err.column-1) + "^")
+ print(err)
+ print()
+
+if __name__ == "__main__":
+ 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();
+ };
+ """
+ )
+ 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();
+ };
+ """
+ )
+ 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 );
+ };
+ """
+ )
+ test(
+ """
+ module Test1
+ {
+ exception TestException
+ {
+ string msg;
+ ];
+
+ interface TestInterface
+ {
+ void method1( in string arg1, inout long arg2 )
+ raises ( TestException );
+ };
+ };
+ """
+ )
+ test(
+ """
+ module Test1
+ {
+ exception TestException
+ {
+ string msg;
+ };
+
+ };
+ """
+ )
diff --git a/trunk/src/examples/indentedGrammarExample.py b/trunk/src/examples/indentedGrammarExample.py
new file mode 100644
index 0000000..442e6a4
--- /dev/null
+++ b/trunk/src/examples/indentedGrammarExample.py
@@ -0,0 +1,54 @@
+# indentedGrammarExample.py
+#
+# Copyright (c) 2006,2016 Paul McGuire
+#
+# A sample of a pyparsing grammar using indentation for
+# grouping (like Python does).
+#
+# Updated to use indentedBlock helper method.
+#
+
+from pyparsing import *
+
+data = """\
+def A(z):
+ A1
+ B = 100
+ G = A2
+ A2
+ A3
+B
+def BB(a,b,c):
+ BB1
+ def BBA():
+ bba1
+ bba2
+ bba3
+C
+D
+def spam(x,y):
+ def eggs(z):
+ pass
+"""
+
+
+indentStack = [1]
+stmt = Forward()
+suite = indentedBlock(stmt, indentStack)
+
+identifier = Word(alphas, alphanums)
+funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":")
+funcDef = Group( funcDecl + suite )
+
+rvalue = Forward()
+funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")")
+rvalue << (funcCall | identifier | Word(nums))
+assignment = Group(identifier + "=" + rvalue)
+stmt << ( funcDef | assignment | identifier )
+
+module_body = OneOrMore(stmt)
+
+print(data)
+parseTree = module_body.parseString(data)
+parseTree.pprint()
+
diff --git a/trunk/src/examples/invRegex.py b/trunk/src/examples/invRegex.py
new file mode 100644
index 0000000..b6fe1f1
--- /dev/null
+++ b/trunk/src/examples/invRegex.py
@@ -0,0 +1,257 @@
+#
+# invRegex.py
+#
+# Copyright 2008, Paul McGuire
+#
+# pyparsing script to expand a regular expression into all possible matching strings
+# Supports:
+# - {n} and {m,n} repetition, but not unbounded + or * repetition
+# - ? optional elements
+# - [] character ranges
+# - () grouping
+# - | alternation
+#
+__all__ = ["count","invert"]
+
+from pyparsing import (Literal, oneOf, printables, ParserElement, Combine,
+ SkipTo, infixNotation, ParseFatalException, Word, nums, opAssoc,
+ Suppress, ParseResults, srange)
+
+class CharacterRangeEmitter(object):
+ def __init__(self,chars):
+ # remove duplicate chars in character range, but preserve original order
+ seen = set()
+ self.charset = "".join( seen.add(c) or c for c in chars if c not in seen )
+ def __str__(self):
+ return '['+self.charset+']'
+ def __repr__(self):
+ return '['+self.charset+']'
+ def makeGenerator(self):
+ def genChars():
+ for s in self.charset:
+ yield s
+ return genChars
+
+class OptionalEmitter(object):
+ def __init__(self,expr):
+ self.expr = expr
+ def makeGenerator(self):
+ def optionalGen():
+ yield ""
+ for s in self.expr.makeGenerator()():
+ yield s
+ return optionalGen
+
+class DotEmitter(object):
+ def makeGenerator(self):
+ def dotGen():
+ for c in printables:
+ yield c
+ return dotGen
+
+class GroupEmitter(object):
+ def __init__(self,exprs):
+ self.exprs = ParseResults(exprs)
+ def makeGenerator(self):
+ def groupGen():
+ def recurseList(elist):
+ if len(elist)==1:
+ for s in elist[0].makeGenerator()():
+ yield s
+ else:
+ for s in elist[0].makeGenerator()():
+ for s2 in recurseList(elist[1:]):
+ yield s + s2
+ if self.exprs:
+ for s in recurseList(self.exprs):
+ yield s
+ return groupGen
+
+class AlternativeEmitter(object):
+ def __init__(self,exprs):
+ self.exprs = exprs
+ def makeGenerator(self):
+ def altGen():
+ for e in self.exprs:
+ for s in e.makeGenerator()():
+ yield s
+ return altGen
+
+class LiteralEmitter(object):
+ def __init__(self,lit):
+ self.lit = lit
+ def __str__(self):
+ return "Lit:"+self.lit
+ def __repr__(self):
+ return "Lit:"+self.lit
+ def makeGenerator(self):
+ def litGen():
+ yield self.lit
+ return litGen
+
+def handleRange(toks):
+ return CharacterRangeEmitter(srange(toks[0]))
+
+def handleRepetition(toks):
+ toks=toks[0]
+ if toks[1] in "*+":
+ raise ParseFatalException("",0,"unbounded repetition operators not supported")
+ if toks[1] == "?":
+ return OptionalEmitter(toks[0])
+ if "count" in toks:
+ return GroupEmitter([toks[0]] * int(toks.count))
+ if "minCount" in toks:
+ mincount = int(toks.minCount)
+ maxcount = int(toks.maxCount)
+ optcount = maxcount - mincount
+ if optcount:
+ opt = OptionalEmitter(toks[0])
+ for i in range(1,optcount):
+ opt = OptionalEmitter(GroupEmitter([toks[0],opt]))
+ return GroupEmitter([toks[0]] * mincount + [opt])
+ else:
+ return [toks[0]] * mincount
+
+def handleLiteral(toks):
+ lit = ""
+ for t in toks:
+ if t[0] == "\\":
+ if t[1] == "t":
+ lit += '\t'
+ else:
+ lit += t[1]
+ else:
+ lit += t
+ return LiteralEmitter(lit)
+
+def handleMacro(toks):
+ macroChar = toks[0][1]
+ if macroChar == "d":
+ return CharacterRangeEmitter("0123456789")
+ elif macroChar == "w":
+ return CharacterRangeEmitter(srange("[A-Za-z0-9_]"))
+ elif macroChar == "s":
+ return LiteralEmitter(" ")
+ else:
+ raise ParseFatalException("",0,"unsupported macro character (" + macroChar + ")")
+
+def handleSequence(toks):
+ return GroupEmitter(toks[0])
+
+def handleDot():
+ return CharacterRangeEmitter(printables)
+
+def handleAlternative(toks):
+ return AlternativeEmitter(toks[0])
+
+
+_parser = None
+def parser():
+ global _parser
+ if _parser is None:
+ ParserElement.setDefaultWhitespaceChars("")
+ lbrack,rbrack,lbrace,rbrace,lparen,rparen,colon,qmark = map(Literal,"[]{}():?")
+
+ reMacro = Combine("\\" + oneOf(list("dws")))
+ escapedChar = ~reMacro + Combine("\\" + oneOf(list(printables)))
+ reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t"
+
+ reRange = Combine(lbrack + SkipTo(rbrack,ignore=escapedChar) + rbrack)
+ reLiteral = ( escapedChar | oneOf(list(reLiteralChar)) )
+ reNonCaptureGroup = Suppress("?:")
+ reDot = Literal(".")
+ repetition = (
+ ( lbrace + Word(nums)("count") + rbrace ) |
+ ( lbrace + Word(nums)("minCount")+","+ Word(nums)("maxCount") + rbrace ) |
+ oneOf(list("*+?"))
+ )
+
+ reRange.setParseAction(handleRange)
+ reLiteral.setParseAction(handleLiteral)
+ reMacro.setParseAction(handleMacro)
+ reDot.setParseAction(handleDot)
+
+ reTerm = ( reLiteral | reRange | reMacro | reDot | reNonCaptureGroup)
+ reExpr = infixNotation( reTerm,
+ [
+ (repetition, 1, opAssoc.LEFT, handleRepetition),
+ (None, 2, opAssoc.LEFT, handleSequence),
+ (Suppress('|'), 2, opAssoc.LEFT, handleAlternative),
+ ]
+ )
+ _parser = reExpr
+
+ return _parser
+
+def count(gen):
+ """Simple function to count the number of elements returned by a generator."""
+ return sum(1 for _ in gen)
+
+def invert(regex):
+ """Call this routine as a generator to return all the strings that
+ match the input regular expression.
+ for s in invert("[A-Z]{3}\d{3}"):
+ print s
+ """
+ invReGenerator = GroupEmitter(parser().parseString(regex)).makeGenerator()
+ return invReGenerator()
+
+def main():
+ tests = r"""
+ [A-EA]
+ [A-D]*
+ [A-D]{3}
+ X[A-C]{3}Y
+ X[A-C]{3}\(
+ X\d
+ foobar\d\d
+ foobar{2}
+ foobar{2,9}
+ fooba[rz]{2}
+ (foobar){2}
+ ([01]\d)|(2[0-5])
+ (?:[01]\d)|(2[0-5])
+ ([01]\d\d)|(2[0-4]\d)|(25[0-5])
+ [A-C]{1,2}
+ [A-C]{0,3}
+ [A-C]\s[A-C]\s[A-C]
+ [A-C]\s?[A-C][A-C]
+ [A-C]\s([A-C][A-C])
+ [A-C]\s([A-C][A-C])?
+ [A-C]{2}\d{2}
+ @|TH[12]
+ @(@|TH[12])?
+ @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))?
+ @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))?
+ (([ECMP]|HA|AK)[SD]|HS)T
+ [A-CV]{2}
+ A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr]
+ (a|b)|(x|y)
+ (a|b) (x|y)
+ [ABCDEFG](?:#|##|b|bb)?(?:maj|min|m|sus|aug|dim)?[0-9]?(?:/[ABCDEFG](?:#|##|b|bb)?)?
+ (Fri|Mon|S(atur|un)|T(hur|ue)s|Wednes)day
+ A(pril|ugust)|((Dec|Nov|Sept)em|Octo)ber|(Febr|Jan)uary|Ju(ly|ne)|Ma(rch|y)
+ """.split('\n')
+
+ for t in tests:
+ t = t.strip()
+ if not t: continue
+ print('-'*50)
+ print(t)
+ try:
+ num = count(invert(t))
+ print(num)
+ maxprint = 30
+ for s in invert(t):
+ print(s)
+ maxprint -= 1
+ if not maxprint:
+ break
+ except ParseFatalException as pfe:
+ print(pfe.msg)
+ print('')
+ continue
+ print('')
+
+if __name__ == "__main__":
+ main()
diff --git a/trunk/src/examples/javascript_grammar.g b/trunk/src/examples/javascript_grammar.g
new file mode 100644
index 0000000..c30eac2
--- /dev/null
+++ b/trunk/src/examples/javascript_grammar.g
@@ -0,0 +1,894 @@
+/*
+ Copyright 2008 Chris Lambrou.
+ All rights reserved.
+*/
+
+grammar JavaScript;
+
+options
+{
+ output=AST;
+ backtrack=true;
+ memoize=true;
+}
+
+program
+ : LT!* sourceElements LT!* EOF!
+ ;
+
+sourceElements
+ : sourceElement (LT!* sourceElement)*
+ ;
+
+sourceElement
+ : functionDeclaration
+ | statement
+ ;
+
+// functions
+functionDeclaration
+ : 'function' LT!* Identifier LT!* formalParameterList LT!* functionBody
+ ;
+
+functionExpression
+ : 'function' LT!* Identifier? LT!* formalParameterList LT!* functionBody
+ ;
+
+formalParameterList
+ : '(' (LT!* Identifier (LT!* ',' LT!* Identifier)*)? LT!* ')'
+ ;
+
+functionBody
+ : '{' LT!* sourceElements LT!* '}'
+ ;
+
+// statements
+statement
+ : statementBlock
+ | variableStatement
+ | emptyStatement
+ | expressionStatement
+ | ifStatement
+ | iterationStatement
+ | continueStatement
+ | breakStatement
+ | returnStatement
+ | withStatement
+ | labelledStatement
+ | switchStatement
+ | throwStatement
+ | tryStatement
+ ;
+
+statementBlock
+ : '{' LT!* statementList? LT!* '}'
+ ;
+
+statementList
+ : statement (LT!* statement)*
+ ;
+
+variableStatement
+ : 'var' LT!* variableDeclarationList (LT | ';')!
+ ;
+
+variableDeclarationList
+ : variableDeclaration (LT!* ',' LT!* variableDeclaration)*
+ ;
+
+variableDeclarationListNoIn
+ : variableDeclarationNoIn (LT!* ',' LT!* variableDeclarationNoIn)*
+ ;
+
+variableDeclaration
+ : Identifier LT!* initialiser?
+ ;
+
+variableDeclarationNoIn
+ : Identifier LT!* initialiserNoIn?
+ ;
+
+initialiser
+ : '=' LT!* assignmentExpression
+ ;
+
+initialiserNoIn
+ : '=' LT!* assignmentExpressionNoIn
+ ;
+
+emptyStatement
+ : ';'
+ ;
+
+expressionStatement
+ : expression (LT | ';')!
+ ;
+
+ifStatement
+ : 'if' LT!* '(' LT!* expression LT!* ')' LT!* statement (LT!* 'else' LT!* statement)?
+ ;
+
+iterationStatement
+ : doWhileStatement
+ | whileStatement
+ | forStatement
+ | forInStatement
+ ;
+
+doWhileStatement
+ : 'do' LT!* statement LT!* 'while' LT!* '(' expression ')' (LT | ';')!
+ ;
+
+whileStatement
+ : 'while' LT!* '(' LT!* expression LT!* ')' LT!* statement
+ ;
+
+forStatement
+ : 'for' LT!* '(' (LT!* forStatementInitialiserPart)? LT!* ';' (LT!* expression)? LT!* ';' (LT!* expression)? LT!* ')' LT!* statement
+ ;
+
+forStatementInitialiserPart
+ : expressionNoIn
+ | 'var' LT!* variableDeclarationListNoIn
+ ;
+
+forInStatement
+ : 'for' LT!* '(' LT!* forInStatementInitialiserPart LT!* 'in' LT!* expression LT!* ')' LT!* statement
+ ;
+
+forInStatementInitialiserPart
+ : leftHandSideExpression
+ | 'var' LT!* variableDeclarationNoIn
+ ;
+
+continueStatement
+ : 'continue' Identifier? (LT | ';')!
+ ;
+
+breakStatement
+ : 'break' Identifier? (LT | ';')!
+ ;
+
+returnStatement
+ : 'return' expression? (LT | ';')!
+ ;
+
+withStatement
+ : 'with' LT!* '(' LT!* expression LT!* ')' LT!* statement
+ ;
+
+labelledStatement
+ : Identifier LT!* ':' LT!* statement
+ ;
+
+switchStatement
+ : 'switch' LT!* '(' LT!* expression LT!* ')' LT!* caseBlock
+ ;
+
+caseBlock
+ : '{' (LT!* caseClause)* (LT!* defaultClause (LT!* caseClause)*)? LT!* '}'
+ ;
+
+caseClause
+ : 'case' LT!* expression LT!* ':' LT!* statementList?
+ ;
+
+defaultClause
+ : 'default' LT!* ':' LT!* statementList?
+ ;
+
+throwStatement
+ : 'throw' expression (LT | ';')!
+ ;
+
+tryStatement
+ : 'try' LT!* statementBlock LT!* (finallyClause | catchClause (LT!* finallyClause)?)
+ ;
+
+catchClause
+ : 'catch' LT!* '(' LT!* Identifier LT!* ')' LT!* statementBlock
+ ;
+
+finallyClause
+ : 'finally' LT!* statementBlock
+ ;
+
+// expressions
+expression
+ : assignmentExpression (LT!* ',' LT!* assignmentExpression)*
+ ;
+
+expressionNoIn
+ : assignmentExpressionNoIn (LT!* ',' LT!* assignmentExpressionNoIn)*
+ ;
+
+assignmentExpression
+ : conditionalExpression
+ | leftHandSideExpression LT!* assignmentOperator LT!* assignmentExpression
+ ;
+
+assignmentExpressionNoIn
+ : conditionalExpressionNoIn
+ | leftHandSideExpression LT!* assignmentOperator LT!* assignmentExpressionNoIn
+ ;
+
+leftHandSideExpression
+ : callExpression
+ | newExpression
+ ;
+
+newExpression
+ : memberExpression
+ | 'new' LT!* newExpression
+ ;
+
+memberExpression
+ : (primaryExpression | functionExpression | 'new' LT!* memberExpression LT!* arguments) (LT!* memberExpressionSuffix)*
+ ;
+
+memberExpressionSuffix
+ : indexSuffix
+ | propertyReferenceSuffix
+ ;
+
+callExpression
+ : memberExpression LT!* arguments (LT!* callExpressionSuffix)*
+ ;
+
+callExpressionSuffix
+ : arguments
+ | indexSuffix
+ | propertyReferenceSuffix
+ ;
+
+arguments
+ : '(' (LT!* assignmentExpression (LT!* ',' LT!* assignmentExpression)*)? LT!* ')'
+ ;
+
+indexSuffix
+ : '[' LT!* expression LT!* ']'
+ ;
+
+propertyReferenceSuffix
+ : '.' LT!* Identifier
+ ;
+
+assignmentOperator
+ : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '>>>=' | '&=' | '^=' | '|='
+ ;
+
+conditionalExpression
+ : logicalORExpression (LT!* '?' LT!* assignmentExpression LT!* ':' LT!* assignmentExpression)?
+ ;
+
+conditionalExpressionNoIn
+ : logicalORExpressionNoIn (LT!* '?' LT!* assignmentExpressionNoIn LT!* ':' LT!* assignmentExpressionNoIn)?
+ ;
+
+logicalORExpression
+ : logicalANDExpression (LT!* '||' LT!* logicalANDExpression)*
+ ;
+
+logicalORExpressionNoIn
+ : logicalANDExpressionNoIn (LT!* '||' LT!* logicalANDExpressionNoIn)*
+ ;
+
+logicalANDExpression
+ : bitwiseORExpression (LT!* '&&' LT!* bitwiseORExpression)*
+ ;
+
+logicalANDExpressionNoIn
+ : bitwiseORExpressionNoIn (LT!* '&&' LT!* bitwiseORExpressionNoIn)*
+ ;
+
+bitwiseORExpression
+ : bitwiseXORExpression (LT!* '|' LT!* bitwiseXORExpression)*
+ ;
+
+bitwiseORExpressionNoIn
+ : bitwiseXORExpressionNoIn (LT!* '|' LT!* bitwiseXORExpressionNoIn)*
+ ;
+
+bitwiseXORExpression
+ : bitwiseANDExpression (LT!* '^' LT!* bitwiseANDExpression)*
+ ;
+
+bitwiseXORExpressionNoIn
+ : bitwiseANDExpressionNoIn (LT!* '^' LT!* bitwiseANDExpressionNoIn)*
+ ;
+
+bitwiseANDExpression
+ : equalityExpression (LT!* '&' LT!* equalityExpression)*
+ ;
+
+bitwiseANDExpressionNoIn
+ : equalityExpressionNoIn (LT!* '&' LT!* equalityExpressionNoIn)*
+ ;
+
+equalityExpression
+ : relationalExpression (LT!* ('==' | '!=' | '===' | '!==') LT!* relationalExpression)*
+ ;
+
+equalityExpressionNoIn
+ : relationalExpressionNoIn (LT!* ('==' | '!=' | '===' | '!==') LT!* relationalExpressionNoIn)*
+ ;
+
+relationalExpression
+ : shiftExpression (LT!* ('<' | '>' | '<=' | '>=' | 'instanceof' | 'in') LT!* shiftExpression)*
+ ;
+
+relationalExpressionNoIn
+ : shiftExpression (LT!* ('<' | '>' | '<=' | '>=' | 'instanceof') LT!* shiftExpression)*
+ ;
+
+shiftExpression
+ : additiveExpression (LT!* ('<<' | '>>' | '>>>') LT!* additiveExpression)*
+ ;
+
+additiveExpression
+ : multiplicativeExpression (LT!* ('+' | '-') LT!* multiplicativeExpression)*
+ ;
+
+multiplicativeExpression
+ : unaryExpression (LT!* ('*' | '/' | '%') LT!* unaryExpression)*
+ ;
+
+unaryExpression
+ : postfixExpression
+ | ('delete' | 'void' | 'typeof' | '++' | '--' | '+' | '-' | '~' | '!') unaryExpression
+ ;
+
+postfixExpression
+ : leftHandSideExpression ('++' | '--')?
+ ;
+
+primaryExpression
+ : 'this'
+ | Identifier
+ | literal
+ | arrayLiteral
+ | objectLiteral
+ | '(' LT!* expression LT!* ')'
+ ;
+
+// arrayLiteral definition.
+arrayLiteral
+ : '[' LT!* assignmentExpression? (LT!* ',' (LT!* assignmentExpression)?)* LT!* ']'
+ ;
+
+// objectLiteral definition.
+objectLiteral
+ : '{' LT!* propertyNameAndValue (LT!* ',' LT!* propertyNameAndValue)* LT!* '}'
+ ;
+
+propertyNameAndValue
+ : propertyName LT!* ':' LT!* assignmentExpression
+ ;
+
+propertyName
+ : Identifier
+ | StringLiteral
+ | NumericLiteral
+ ;
+
+// primitive literal definition.
+literal
+ : 'null'
+ | 'true'
+ | 'false'
+ | StringLiteral
+ | NumericLiteral
+ ;
+
+// lexer rules.
+StringLiteral
+ : '"' DoubleStringCharacter* '"'
+ | '\'' SingleStringCharacter* '\''
+ ;
+
+fragment DoubleStringCharacter
+ : ~('"' | '\\' | LT)
+ | '\\' EscapeSequence
+ ;
+
+fragment SingleStringCharacter
+ : ~('\'' | '\\' | LT)
+ | '\\' EscapeSequence
+ ;
+
+fragment EscapeSequence
+ : CharacterEscapeSequence
+ | '0'
+ | HexEscapeSequence
+ | UnicodeEscapeSequence
+ ;
+
+fragment CharacterEscapeSequence
+ : SingleEscapeCharacter
+ | NonEscapeCharacter
+ ;
+
+fragment NonEscapeCharacter
+ : ~(EscapeCharacter | LT)
+ ;
+
+fragment SingleEscapeCharacter
+ : '\'' | '"' | '\\' | 'b' | 'f' | 'n' | 'r' | 't' | 'v'
+ ;
+
+fragment EscapeCharacter
+ : SingleEscapeCharacter
+ | DecimalDigit
+ | 'x'
+ | 'u'
+ ;
+
+fragment HexEscapeSequence
+ : 'x' HexDigit HexDigit
+ ;
+
+fragment UnicodeEscapeSequence
+ : 'u' HexDigit HexDigit HexDigit HexDigit
+ ;
+
+NumericLiteral
+ : DecimalLiteral
+ | HexIntegerLiteral
+ ;
+
+fragment HexIntegerLiteral
+ : '0' ('x' | 'X') HexDigit+
+ ;
+
+fragment HexDigit
+ : DecimalDigit | ('a'..'f') | ('A'..'F')
+ ;
+
+fragment DecimalLiteral
+ : DecimalDigit+ '.' DecimalDigit* ExponentPart?
+ | '.'? DecimalDigit+ ExponentPart?
+ ;
+
+fragment DecimalDigit
+ : ('0'..'9')
+ ;
+
+fragment ExponentPart
+ : ('e' | 'E') ('+' | '-') ? DecimalDigit+
+ ;
+
+Identifier
+ : IdentifierStart IdentifierPart*
+ ;
+
+fragment IdentifierStart
+ : UnicodeLetter
+ | '$'
+ | '_'
+ | '\\' UnicodeEscapeSequence
+ ;
+
+fragment IdentifierPart
+ : (IdentifierStart) => IdentifierStart // Avoids ambiguity, as some IdentifierStart chars also match following alternatives.
+ | UnicodeDigit
+ | UnicodeConnectorPunctuation
+ ;
+
+fragment UnicodeLetter // Any character in the Unicode categories "Uppercase letter (Lu)",
+ : '\u0041'..'\u005A' // "Lowercase letter (Ll)", "Titlecase letter (Lt)",
+ | '\u0061'..'\u007A' // "Modifier letter (Lm)", "Other letter (Lo)", or "Letter number (Nl)".
+ | '\u00AA'
+ | '\u00B5'
+ | '\u00BA'
+ | '\u00C0'..'\u00D6'
+ | '\u00D8'..'\u00F6'
+ | '\u00F8'..'\u021F'
+ | '\u0222'..'\u0233'
+ | '\u0250'..'\u02AD'
+ | '\u02B0'..'\u02B8'
+ | '\u02BB'..'\u02C1'
+ | '\u02D0'..'\u02D1'
+ | '\u02E0'..'\u02E4'
+ | '\u02EE'
+ | '\u037A'
+ | '\u0386'
+ | '\u0388'..'\u038A'
+ | '\u038C'
+ | '\u038E'..'\u03A1'
+ | '\u03A3'..'\u03CE'
+ | '\u03D0'..'\u03D7'
+ | '\u03DA'..'\u03F3'
+ | '\u0400'..'\u0481'
+ | '\u048C'..'\u04C4'
+ | '\u04C7'..'\u04C8'
+ | '\u04CB'..'\u04CC'
+ | '\u04D0'..'\u04F5'
+ | '\u04F8'..'\u04F9'
+ | '\u0531'..'\u0556'
+ | '\u0559'
+ | '\u0561'..'\u0587'
+ | '\u05D0'..'\u05EA'
+ | '\u05F0'..'\u05F2'
+ | '\u0621'..'\u063A'
+ | '\u0640'..'\u064A'
+ | '\u0671'..'\u06D3'
+ | '\u06D5'
+ | '\u06E5'..'\u06E6'
+ | '\u06FA'..'\u06FC'
+ | '\u0710'
+ | '\u0712'..'\u072C'
+ | '\u0780'..'\u07A5'
+ | '\u0905'..'\u0939'
+ | '\u093D'
+ | '\u0950'
+ | '\u0958'..'\u0961'
+ | '\u0985'..'\u098C'
+ | '\u098F'..'\u0990'
+ | '\u0993'..'\u09A8'
+ | '\u09AA'..'\u09B0'
+ | '\u09B2'
+ | '\u09B6'..'\u09B9'
+ | '\u09DC'..'\u09DD'
+ | '\u09DF'..'\u09E1'
+ | '\u09F0'..'\u09F1'
+ | '\u0A05'..'\u0A0A'
+ | '\u0A0F'..'\u0A10'
+ | '\u0A13'..'\u0A28'
+ | '\u0A2A'..'\u0A30'
+ | '\u0A32'..'\u0A33'
+ | '\u0A35'..'\u0A36'
+ | '\u0A38'..'\u0A39'
+ | '\u0A59'..'\u0A5C'
+ | '\u0A5E'
+ | '\u0A72'..'\u0A74'
+ | '\u0A85'..'\u0A8B'
+ | '\u0A8D'
+ | '\u0A8F'..'\u0A91'
+ | '\u0A93'..'\u0AA8'
+ | '\u0AAA'..'\u0AB0'
+ | '\u0AB2'..'\u0AB3'
+ | '\u0AB5'..'\u0AB9'
+ | '\u0ABD'
+ | '\u0AD0'
+ | '\u0AE0'
+ | '\u0B05'..'\u0B0C'
+ | '\u0B0F'..'\u0B10'
+ | '\u0B13'..'\u0B28'
+ | '\u0B2A'..'\u0B30'
+ | '\u0B32'..'\u0B33'
+ | '\u0B36'..'\u0B39'
+ | '\u0B3D'
+ | '\u0B5C'..'\u0B5D'
+ | '\u0B5F'..'\u0B61'
+ | '\u0B85'..'\u0B8A'
+ | '\u0B8E'..'\u0B90'
+ | '\u0B92'..'\u0B95'
+ | '\u0B99'..'\u0B9A'
+ | '\u0B9C'
+ | '\u0B9E'..'\u0B9F'
+ | '\u0BA3'..'\u0BA4'
+ | '\u0BA8'..'\u0BAA'
+ | '\u0BAE'..'\u0BB5'
+ | '\u0BB7'..'\u0BB9'
+ | '\u0C05'..'\u0C0C'
+ | '\u0C0E'..'\u0C10'
+ | '\u0C12'..'\u0C28'
+ | '\u0C2A'..'\u0C33'
+ | '\u0C35'..'\u0C39'
+ | '\u0C60'..'\u0C61'
+ | '\u0C85'..'\u0C8C'
+ | '\u0C8E'..'\u0C90'
+ | '\u0C92'..'\u0CA8'
+ | '\u0CAA'..'\u0CB3'
+ | '\u0CB5'..'\u0CB9'
+ | '\u0CDE'
+ | '\u0CE0'..'\u0CE1'
+ | '\u0D05'..'\u0D0C'
+ | '\u0D0E'..'\u0D10'
+ | '\u0D12'..'\u0D28'
+ | '\u0D2A'..'\u0D39'
+ | '\u0D60'..'\u0D61'
+ | '\u0D85'..'\u0D96'
+ | '\u0D9A'..'\u0DB1'
+ | '\u0DB3'..'\u0DBB'
+ | '\u0DBD'
+ | '\u0DC0'..'\u0DC6'
+ | '\u0E01'..'\u0E30'
+ | '\u0E32'..'\u0E33'
+ | '\u0E40'..'\u0E46'
+ | '\u0E81'..'\u0E82'
+ | '\u0E84'
+ | '\u0E87'..'\u0E88'
+ | '\u0E8A'
+ | '\u0E8D'
+ | '\u0E94'..'\u0E97'
+ | '\u0E99'..'\u0E9F'
+ | '\u0EA1'..'\u0EA3'
+ | '\u0EA5'
+ | '\u0EA7'
+ | '\u0EAA'..'\u0EAB'
+ | '\u0EAD'..'\u0EB0'
+ | '\u0EB2'..'\u0EB3'
+ | '\u0EBD'..'\u0EC4'
+ | '\u0EC6'
+ | '\u0EDC'..'\u0EDD'
+ | '\u0F00'
+ | '\u0F40'..'\u0F6A'
+ | '\u0F88'..'\u0F8B'
+ | '\u1000'..'\u1021'
+ | '\u1023'..'\u1027'
+ | '\u1029'..'\u102A'
+ | '\u1050'..'\u1055'
+ | '\u10A0'..'\u10C5'
+ | '\u10D0'..'\u10F6'
+ | '\u1100'..'\u1159'
+ | '\u115F'..'\u11A2'
+ | '\u11A8'..'\u11F9'
+ | '\u1200'..'\u1206'
+ | '\u1208'..'\u1246'
+ | '\u1248'
+ | '\u124A'..'\u124D'
+ | '\u1250'..'\u1256'
+ | '\u1258'
+ | '\u125A'..'\u125D'
+ | '\u1260'..'\u1286'
+ | '\u1288'
+ | '\u128A'..'\u128D'
+ | '\u1290'..'\u12AE'
+ | '\u12B0'
+ | '\u12B2'..'\u12B5'
+ | '\u12B8'..'\u12BE'
+ | '\u12C0'
+ | '\u12C2'..'\u12C5'
+ | '\u12C8'..'\u12CE'
+ | '\u12D0'..'\u12D6'
+ | '\u12D8'..'\u12EE'
+ | '\u12F0'..'\u130E'
+ | '\u1310'
+ | '\u1312'..'\u1315'
+ | '\u1318'..'\u131E'
+ | '\u1320'..'\u1346'
+ | '\u1348'..'\u135A'
+ | '\u13A0'..'\u13B0'
+ | '\u13B1'..'\u13F4'
+ | '\u1401'..'\u1676'
+ | '\u1681'..'\u169A'
+ | '\u16A0'..'\u16EA'
+ | '\u1780'..'\u17B3'
+ | '\u1820'..'\u1877'
+ | '\u1880'..'\u18A8'
+ | '\u1E00'..'\u1E9B'
+ | '\u1EA0'..'\u1EE0'
+ | '\u1EE1'..'\u1EF9'
+ | '\u1F00'..'\u1F15'
+ | '\u1F18'..'\u1F1D'
+ | '\u1F20'..'\u1F39'
+ | '\u1F3A'..'\u1F45'
+ | '\u1F48'..'\u1F4D'
+ | '\u1F50'..'\u1F57'
+ | '\u1F59'
+ | '\u1F5B'
+ | '\u1F5D'
+ | '\u1F5F'..'\u1F7D'
+ | '\u1F80'..'\u1FB4'
+ | '\u1FB6'..'\u1FBC'
+ | '\u1FBE'
+ | '\u1FC2'..'\u1FC4'
+ | '\u1FC6'..'\u1FCC'
+ | '\u1FD0'..'\u1FD3'
+ | '\u1FD6'..'\u1FDB'
+ | '\u1FE0'..'\u1FEC'
+ | '\u1FF2'..'\u1FF4'
+ | '\u1FF6'..'\u1FFC'
+ | '\u207F'
+ | '\u2102'
+ | '\u2107'
+ | '\u210A'..'\u2113'
+ | '\u2115'
+ | '\u2119'..'\u211D'
+ | '\u2124'
+ | '\u2126'
+ | '\u2128'
+ | '\u212A'..'\u212D'
+ | '\u212F'..'\u2131'
+ | '\u2133'..'\u2139'
+ | '\u2160'..'\u2183'
+ | '\u3005'..'\u3007'
+ | '\u3021'..'\u3029'
+ | '\u3031'..'\u3035'
+ | '\u3038'..'\u303A'
+ | '\u3041'..'\u3094'
+ | '\u309D'..'\u309E'
+ | '\u30A1'..'\u30FA'
+ | '\u30FC'..'\u30FE'
+ | '\u3105'..'\u312C'
+ | '\u3131'..'\u318E'
+ | '\u31A0'..'\u31B7'
+ | '\u3400'
+ | '\u4DB5'
+ | '\u4E00'
+ | '\u9FA5'
+ | '\uA000'..'\uA48C'
+ | '\uAC00'
+ | '\uD7A3'
+ | '\uF900'..'\uFA2D'
+ | '\uFB00'..'\uFB06'
+ | '\uFB13'..'\uFB17'
+ | '\uFB1D'
+ | '\uFB1F'..'\uFB28'
+ | '\uFB2A'..'\uFB36'
+ | '\uFB38'..'\uFB3C'
+ | '\uFB3E'
+ | '\uFB40'..'\uFB41'
+ | '\uFB43'..'\uFB44'
+ | '\uFB46'..'\uFBB1'
+ | '\uFBD3'..'\uFD3D'
+ | '\uFD50'..'\uFD8F'
+ | '\uFD92'..'\uFDC7'
+ | '\uFDF0'..'\uFDFB'
+ | '\uFE70'..'\uFE72'
+ | '\uFE74'
+ | '\uFE76'..'\uFEFC'
+ | '\uFF21'..'\uFF3A'
+ | '\uFF41'..'\uFF5A'
+ | '\uFF66'..'\uFFBE'
+ | '\uFFC2'..'\uFFC7'
+ | '\uFFCA'..'\uFFCF'
+ | '\uFFD2'..'\uFFD7'
+ | '\uFFDA'..'\uFFDC'
+ ;
+
+fragment UnicodeCombiningMark // Any character in the Unicode categories "Non-spacing mark (Mn)"
+ : '\u0300'..'\u034E' // or "Combining spacing mark (Mc)".
+ | '\u0360'..'\u0362'
+ | '\u0483'..'\u0486'
+ | '\u0591'..'\u05A1'
+ | '\u05A3'..'\u05B9'
+ | '\u05BB'..'\u05BD'
+ | '\u05BF'
+ | '\u05C1'..'\u05C2'
+ | '\u05C4'
+ | '\u064B'..'\u0655'
+ | '\u0670'
+ | '\u06D6'..'\u06DC'
+ | '\u06DF'..'\u06E4'
+ | '\u06E7'..'\u06E8'
+ | '\u06EA'..'\u06ED'
+ | '\u0711'
+ | '\u0730'..'\u074A'
+ | '\u07A6'..'\u07B0'
+ | '\u0901'..'\u0903'
+ | '\u093C'
+ | '\u093E'..'\u094D'
+ | '\u0951'..'\u0954'
+ | '\u0962'..'\u0963'
+ | '\u0981'..'\u0983'
+ | '\u09BC'..'\u09C4'
+ | '\u09C7'..'\u09C8'
+ | '\u09CB'..'\u09CD'
+ | '\u09D7'
+ | '\u09E2'..'\u09E3'
+ | '\u0A02'
+ | '\u0A3C'
+ | '\u0A3E'..'\u0A42'
+ | '\u0A47'..'\u0A48'
+ | '\u0A4B'..'\u0A4D'
+ | '\u0A70'..'\u0A71'
+ | '\u0A81'..'\u0A83'
+ | '\u0ABC'
+ | '\u0ABE'..'\u0AC5'
+ | '\u0AC7'..'\u0AC9'
+ | '\u0ACB'..'\u0ACD'
+ | '\u0B01'..'\u0B03'
+ | '\u0B3C'
+ | '\u0B3E'..'\u0B43'
+ | '\u0B47'..'\u0B48'
+ | '\u0B4B'..'\u0B4D'
+ | '\u0B56'..'\u0B57'
+ | '\u0B82'..'\u0B83'
+ | '\u0BBE'..'\u0BC2'
+ | '\u0BC6'..'\u0BC8'
+ | '\u0BCA'..'\u0BCD'
+ | '\u0BD7'
+ | '\u0C01'..'\u0C03'
+ | '\u0C3E'..'\u0C44'
+ | '\u0C46'..'\u0C48'
+ | '\u0C4A'..'\u0C4D'
+ | '\u0C55'..'\u0C56'
+ | '\u0C82'..'\u0C83'
+ | '\u0CBE'..'\u0CC4'
+ | '\u0CC6'..'\u0CC8'
+ | '\u0CCA'..'\u0CCD'
+ | '\u0CD5'..'\u0CD6'
+ | '\u0D02'..'\u0D03'
+ | '\u0D3E'..'\u0D43'
+ | '\u0D46'..'\u0D48'
+ | '\u0D4A'..'\u0D4D'
+ | '\u0D57'
+ | '\u0D82'..'\u0D83'
+ | '\u0DCA'
+ | '\u0DCF'..'\u0DD4'
+ | '\u0DD6'
+ | '\u0DD8'..'\u0DDF'
+ | '\u0DF2'..'\u0DF3'
+ | '\u0E31'
+ | '\u0E34'..'\u0E3A'
+ | '\u0E47'..'\u0E4E'
+ | '\u0EB1'
+ | '\u0EB4'..'\u0EB9'
+ | '\u0EBB'..'\u0EBC'
+ | '\u0EC8'..'\u0ECD'
+ | '\u0F18'..'\u0F19'
+ | '\u0F35'
+ | '\u0F37'
+ | '\u0F39'
+ | '\u0F3E'..'\u0F3F'
+ | '\u0F71'..'\u0F84'
+ | '\u0F86'..'\u0F87'
+ | '\u0F90'..'\u0F97'
+ | '\u0F99'..'\u0FBC'
+ | '\u0FC6'
+ | '\u102C'..'\u1032'
+ | '\u1036'..'\u1039'
+ | '\u1056'..'\u1059'
+ | '\u17B4'..'\u17D3'
+ | '\u18A9'
+ | '\u20D0'..'\u20DC'
+ | '\u20E1'
+ | '\u302A'..'\u302F'
+ | '\u3099'..'\u309A'
+ | '\uFB1E'
+ | '\uFE20'..'\uFE23'
+ ;
+
+fragment UnicodeDigit // Any character in the Unicode category "Decimal number (Nd)".
+ : '\u0030'..'\u0039'
+ | '\u0660'..'\u0669'
+ | '\u06F0'..'\u06F9'
+ | '\u0966'..'\u096F'
+ | '\u09E6'..'\u09EF'
+ | '\u0A66'..'\u0A6F'
+ | '\u0AE6'..'\u0AEF'
+ | '\u0B66'..'\u0B6F'
+ | '\u0BE7'..'\u0BEF'
+ | '\u0C66'..'\u0C6F'
+ | '\u0CE6'..'\u0CEF'
+ | '\u0D66'..'\u0D6F'
+ | '\u0E50'..'\u0E59'
+ | '\u0ED0'..'\u0ED9'
+ | '\u0F20'..'\u0F29'
+ | '\u1040'..'\u1049'
+ | '\u1369'..'\u1371'
+ | '\u17E0'..'\u17E9'
+ | '\u1810'..'\u1819'
+ | '\uFF10'..'\uFF19'
+ ;
+
+fragment UnicodeConnectorPunctuation // Any character in the Unicode category "Connector punctuation (Pc)".
+ : '\u005F'
+ | '\u203F'..'\u2040'
+ | '\u30FB'
+ | '\uFE33'..'\uFE34'
+ | '\uFE4D'..'\uFE4F'
+ | '\uFF3F'
+ | '\uFF65'
+ ;
+
+Comment
+ : '/*' (options {greedy=false;} : .)* '*/' {$channel=HIDDEN;}
+ ;
+
+LineComment
+ : '//' ~(LT)* {$channel=HIDDEN;}
+ ;
+
+LT
+ : '\n' // Line feed.
+ | '\r' // Carriage return.
+ | '\u2028' // Line separator.
+ | '\u2029' // Paragraph separator.
+ ;
+
+WhiteSpace // Tab, vertical tab, form feed, space, non-breaking space and any other unicode "space separator".
+ : ('\t' | '\v' | '\f' | ' ' | '\u00A0') {$channel=HIDDEN;}
+ ;
diff --git a/trunk/src/examples/jsonParser.py b/trunk/src/examples/jsonParser.py
new file mode 100644
index 0000000..45cdef3
--- /dev/null
+++ b/trunk/src/examples/jsonParser.py
@@ -0,0 +1,111 @@
+# jsonParser.py
+#
+# Implementation of a simple JSON parser, returning a hierarchical
+# ParseResults object support both list- and dict-style data access.
+#
+# Copyright 2006, by Paul McGuire
+#
+# Updated 8 Jan 2007 - fixed dict grouping bug, and made elements and
+# members optional in array and object collections
+#
+json_bnf = """
+object
+ { members }
+ {}
+members
+ string : value
+ members , string : value
+array
+ [ elements ]
+ []
+elements
+ value
+ elements , value
+value
+ string
+ number
+ object
+ array
+ true
+ false
+ null
+"""
+
+from pyparsing import *
+
+TRUE = Keyword("true").setParseAction( replaceWith(True) )
+FALSE = Keyword("false").setParseAction( replaceWith(False) )
+NULL = Keyword("null").setParseAction( replaceWith(None) )
+
+jsonString = dblQuotedString.setParseAction( removeQuotes )
+jsonNumber = Combine( Optional('-') + ( '0' | Word('123456789',nums) ) +
+ Optional( '.' + Word(nums) ) +
+ Optional( Word('eE',exact=1) + Word(nums+'+-',nums) ) )
+
+jsonObject = Forward()
+jsonValue = Forward()
+jsonElements = delimitedList( jsonValue )
+jsonArray = Group(Suppress('[') + Optional(jsonElements, []) + Suppress(']') )
+jsonValue << ( jsonString | jsonNumber | Group(jsonObject) | jsonArray | TRUE | FALSE | NULL )
+memberDef = Group( jsonString + Suppress(':') + jsonValue )
+jsonMembers = delimitedList( memberDef )
+jsonObject << Dict( Suppress('{') + Optional(jsonMembers) + Suppress('}') )
+
+jsonComment = cppStyleComment
+jsonObject.ignore( jsonComment )
+
+def convertNumbers(s,l,toks):
+ n = toks[0]
+ try:
+ return int(n)
+ except ValueError as ve:
+ return float(n)
+
+jsonNumber.setParseAction( convertNumbers )
+
+if __name__ == "__main__":
+ testdata = """
+ {
+ "glossary": {
+ "title": "example glossary",
+ "GlossDiv": {
+ "title": "S",
+ "GlossList":
+ {
+ "ID": "SGML",
+ "SortAs": "SGML",
+ "GlossTerm": "Standard Generalized Markup Language",
+ "TrueValue": true,
+ "FalseValue": false,
+ "Gravity": -9.8,
+ "LargestPrimeLessThan100": 97,
+ "AvogadroNumber": 6.02E23,
+ "EvenPrimesGreaterThan2": null,
+ "PrimesLessThan10" : [2,3,5,7],
+ "Acronym": "SGML",
+ "Abbrev": "ISO 8879:1986",
+ "GlossDef": "A meta-markup language, used to create markup languages such as DocBook.",
+ "GlossSeeAlso": ["GML", "XML", "markup"],
+ "EmptyDict" : {},
+ "EmptyList" : []
+ }
+ }
+ }
+ }
+ """
+
+ import pprint
+ results = jsonObject.parseString(testdata)
+ pprint.pprint( results.asList() )
+ print()
+ def testPrint(x):
+ print(type(x),repr(x))
+ print(list(results.glossary.GlossDiv.GlossList.keys()))
+ testPrint( results.glossary.title )
+ testPrint( results.glossary.GlossDiv.GlossList.ID )
+ testPrint( results.glossary.GlossDiv.GlossList.FalseValue )
+ testPrint( results.glossary.GlossDiv.GlossList.Acronym )
+ testPrint( results.glossary.GlossDiv.GlossList.EvenPrimesGreaterThan2 )
+ testPrint( results.glossary.GlossDiv.GlossList.PrimesLessThan10 )
+
+
diff --git a/trunk/src/examples/linenoExample.py b/trunk/src/examples/linenoExample.py
new file mode 100644
index 0000000..1186f48
--- /dev/null
+++ b/trunk/src/examples/linenoExample.py
@@ -0,0 +1,49 @@
+#
+# linenoExample.py
+#
+# an example of using the location value returned by pyparsing to
+# extract the line and column number of the location of the matched text,
+# or to extract the entire line of text.
+#
+# Copyright (c) 2006, Paul McGuire
+#
+from pyparsing import *
+
+data = """Now is the time
+for all good men
+to come to the aid
+of their country."""
+
+# demonstrate use of lineno, line, and col in a parse action
+def reportLongWords(st,locn,toks):
+ word = toks[0]
+ if len(word) > 3:
+ print("Found '%s' on line %d at column %d" % (word, lineno(locn,st), col(locn,st)))
+ print("The full line of text was:")
+ print("'%s'" % line(locn,st))
+ print((" "*col(locn,st))+" ^")
+ print()
+
+wd = Word(alphas).setParseAction( reportLongWords )
+OneOrMore(wd).parseString(data)
+
+
+# demonstrate returning an object from a parse action, containing more information
+# than just the matching token text
+class Token(object):
+ def __init__(self, st, locn, tokString):
+ self.tokenString = tokString
+ self.locn = locn
+ self.sourceLine = line(locn,st)
+ self.lineNo = lineno(locn,st)
+ self.col = col(locn,st)
+ def __str__(self):
+ return "%(tokenString)s (line: %(lineNo)d, col: %(col)d)" % self.__dict__
+
+def createTokenObject(st,locn,toks):
+ return Token(st,locn, toks[0])
+
+wd = Word(alphas).setParseAction( createTokenObject )
+
+for tokenObj in OneOrMore(wd).parseString(data):
+ print(tokenObj)
diff --git a/trunk/src/examples/list1.py b/trunk/src/examples/list1.py
new file mode 100644
index 0000000..e410070
--- /dev/null
+++ b/trunk/src/examples/list1.py
@@ -0,0 +1,50 @@
+from pyparsing import *
+
+# first pass
+lbrack = Literal("[")
+rbrack = Literal("]")
+integer = Word(nums).setName("integer")
+real = Combine(Optional(oneOf("+ -")) + Word(nums) + "." +
+ Optional(Word(nums))).setName("real")
+
+listItem = real | integer | quotedString
+
+listStr = lbrack + delimitedList(listItem) + rbrack
+
+test = "['a', 100, 3.14]"
+
+print(listStr.parseString(test))
+
+
+# second pass, cleanup and add converters
+lbrack = Literal("[").suppress()
+rbrack = Literal("]").suppress()
+cvtInt = lambda s,l,toks: int(toks[0])
+integer = Word(nums).setName("integer").setParseAction( cvtInt )
+cvtReal = lambda s,l,toks: float(toks[0])
+real = Combine(Optional(oneOf("+ -")) + Word(nums) + "." +
+ Optional(Word(nums))).setName("real").setParseAction( cvtReal )
+listItem = real | integer | quotedString.setParseAction( removeQuotes )
+
+listStr = lbrack + delimitedList(listItem) + rbrack
+
+test = "['a', 100, 3.14]"
+
+print(listStr.parseString(test))
+
+# third pass, add nested list support
+cvtInt = lambda s,l,toks: int(toks[0])
+cvtReal = lambda s,l,toks: float(toks[0])
+
+lbrack = Literal("[").suppress()
+rbrack = Literal("]").suppress()
+integer = Word(nums).setName("integer").setParseAction( cvtInt )
+real = Combine(Optional(oneOf("+ -")) + Word(nums) + "." +
+ Optional(Word(nums))).setName("real").setParseAction( cvtReal )
+
+listStr = Forward()
+listItem = real | integer | quotedString.setParseAction(removeQuotes) | Group(listStr)
+listStr << lbrack + delimitedList(listItem) + rbrack
+
+test = "['a', 100, 3.14, [ +2.718, 'xyzzy', -1.414] ]"
+print(listStr.parseString(test)) \ No newline at end of file
diff --git a/trunk/src/examples/listAllMatches.py b/trunk/src/examples/listAllMatches.py
new file mode 100644
index 0000000..1b1bdd4
--- /dev/null
+++ b/trunk/src/examples/listAllMatches.py
@@ -0,0 +1,52 @@
+# listAllMatches.py
+#
+# Sample program showing how/when to use listAllMatches to get all matching tokens in a results name.
+#
+# copyright 2006, Paul McGuire
+#
+
+from pyparsing import oneOf, OneOrMore, printables, StringEnd
+
+test = "The quick brown fox named 'Aloysius' lives at 123 Main Street (and jumps over lazy dogs in his spare time)."
+nonAlphas = [ c for c in printables if not c.isalpha() ]
+
+print("Extract vowels, consonants, and special characters from this test string:")
+print("'" + test + "'")
+print('')
+
+print("Define grammar using normal results names")
+print("(only last matching symbol is saved)")
+vowels = oneOf(list("aeiouy"), caseless=True)("vowels")
+cons = oneOf(list("bcdfghjklmnpqrstvwxz"), caseless=True)("cons")
+other = oneOf(nonAlphas)("others")
+letters = OneOrMore(cons | vowels | other) + StringEnd()
+
+results = letters.parseString(test)
+print(results)
+print(results.vowels)
+print(results.cons)
+print(results.others)
+print('')
+
+
+print("Define grammar using results names, with listAllMatches=True")
+print("(all matching symbols are saved)")
+vowels = oneOf(list("aeiouy"), caseless=True)("vowels*")
+cons = oneOf(list("bcdfghjklmnpqrstvwxz"), caseless=True)("cons*")
+other = oneOf(nonAlphas)("others*")
+
+letters = OneOrMore(cons | vowels | other)
+
+results = letters.parseString(test, parseAll=True)
+print(results)
+print(sorted(set(results)))
+print('')
+print(results.vowels)
+print(sorted(set(results.vowels)))
+print('')
+print(results.cons)
+print(sorted(set(results.cons)))
+print('')
+print(results.others)
+print(sorted(set(results.others)))
+
diff --git a/trunk/src/examples/lucene_grammar.py b/trunk/src/examples/lucene_grammar.py
new file mode 100644
index 0000000..179f25e
--- /dev/null
+++ b/trunk/src/examples/lucene_grammar.py
@@ -0,0 +1,330 @@
+#
+# lucene_grammar.py
+#
+# Copyright 2011, Paul McGuire
+#
+# implementation of Lucene grammar, as decribed
+# at http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/docs/queryparsersyntax.html
+#
+
+from pyparsing import (Literal, CaselessKeyword, Forward, Regex, QuotedString, Suppress,
+ Optional, Group, FollowedBy, infixNotation, opAssoc, ParseException, ParserElement)
+ParserElement.enablePackrat()
+
+COLON,LBRACK,RBRACK,LBRACE,RBRACE,TILDE,CARAT = map(Literal,":[]{}~^")
+LPAR,RPAR = map(Suppress,"()")
+and_ = CaselessKeyword("AND")
+or_ = CaselessKeyword("OR")
+not_ = CaselessKeyword("NOT")
+to_ = CaselessKeyword("TO")
+keyword = and_ | or_ | not_
+
+expression = Forward()
+
+valid_word = Regex(r'([a-zA-Z0-9*_+.-]|\\[!(){}\[\]^"~*?\\:])+').setName("word")
+valid_word.setParseAction(
+ lambda t : t[0].replace('\\\\',chr(127)).replace('\\','').replace(chr(127),'\\')
+ )
+
+string = QuotedString('"')
+
+required_modifier = Literal("+")("required")
+prohibit_modifier = Literal("-")("prohibit")
+integer = Regex(r"\d+").setParseAction(lambda t:int(t[0]))
+proximity_modifier = Group(TILDE + integer("proximity"))
+number = Regex(r'\d+(\.\d+)?').setParseAction(lambda t:float(t[0]))
+fuzzy_modifier = TILDE + Optional(number, default=0.5)("fuzzy")
+
+term = Forward()
+field_name = valid_word.copy().setName("fieldname")
+incl_range_search = Group(LBRACK + term("lower") + to_ + term("upper") + RBRACK)
+excl_range_search = Group(LBRACE + term("lower") + to_ + term("upper") + RBRACE)
+range_search = incl_range_search("incl_range") | excl_range_search("excl_range")
+boost = (CARAT + number("boost"))
+
+string_expr = Group(string + proximity_modifier) | string
+word_expr = Group(valid_word + fuzzy_modifier) | valid_word
+term << (Optional(field_name("field") + COLON) +
+ (word_expr | string_expr | range_search | Group(LPAR + expression + RPAR)) +
+ Optional(boost))
+term.setParseAction(lambda t:[t] if 'field' in t or 'boost' in t else None)
+
+expression << infixNotation(term,
+ [
+ (required_modifier | prohibit_modifier, 1, opAssoc.RIGHT),
+ ((not_ | '!').setParseAction(lambda:"NOT"), 1, opAssoc.RIGHT),
+ ((and_ | '&&').setParseAction(lambda:"AND"), 2, opAssoc.LEFT),
+ (Optional(or_ | '||').setParseAction(lambda:"OR"), 2, opAssoc.LEFT),
+ ])
+
+# test strings taken from grammar description doc, and TestQueryParser.java
+tests = r"""
+ a and b
+ a and not b
+ a and !b
+ a && !b
+ a&&!b
+ name:a
+ name:a and not title:b
+ (a^100 c d f) and !z
+ name:"blah de blah"
+ title:(+return +"pink panther")
+ title:"The Right Way" AND text:go
+ title:"Do it right" AND right
+ title:Do it right
+ roam~
+ roam~0.8
+ "jakarta apache"~10
+ mod_date:[20020101 TO 20030101]
+ title:{Aida TO Carmen}
+ jakarta apache
+ jakarta^4 apache
+ "jakarta apache"^4 "Apache Lucene"
+ "jakarta apache" jakarta
+ "jakarta apache" OR jakarta
+ "jakarta apache" AND "Apache Lucene"
+ +jakarta lucene
+ "jakarta apache" NOT "Apache Lucene"
+ "jakarta apache" -"Apache Lucene"
+ (jakarta OR apache) AND website
+ \(1+1\)\:2
+ c\:\\windows
+ (fieldX:xxxxx OR fieldy:xxxxxxxx)^2 AND (fieldx:the OR fieldy:foo)
+ (fieldX:xxxxx fieldy:xxxxxxxx)^2 AND (fieldx:the fieldy:foo)
+ (fieldX:xxxxx~0.5 fieldy:xxxxxxxx)^2 AND (fieldx:the fieldy:foo)
+ +term -term term
+ foo:term AND field:anotherTerm
+ germ term^2.0
+ (term)^2.0
+ (foo OR bar) AND (baz OR boo)
+ +(apple \"steve jobs\") -(foo bar baz)
+ +title:(dog OR cat) -author:\"bob dole\"
+ a AND b
+ +a +b
+ (a AND b)
+ c OR (a AND b)
+ c (+a +b)
+ a AND NOT b
+ +a -b
+ a AND -b
+ a AND !b
+ a && b
+ a && ! b
+ a OR b
+ a b
+ a || b
+ a OR !b
+ a -b
+ a OR ! b
+ a OR -b
+ a - b
+ a + b
+ a ! b
+ +foo:term +anotherterm
+ hello
+ term^2.0
+ (germ term)^2.0
+ term^2
+ +(foo bar) +(baz boo)
+ ((a OR b) AND NOT c) OR d
+ (+(a b) -c) d
+ field
+ a&&b
+ .NET
+ term
+ germ
+ 3
+ term 1.0 1 2
+ term term1 term2
+ term term term
+ term*
+ term*^2
+ term*^2.0
+ term~
+ term~2.0
+ term~0.7
+ term~^3
+ term~2.0^3.0
+ term*germ
+ term*germ^3
+ term*germ^3.0
+ term~1.1
+ [A TO C]
+ t*erm*
+ *term*
+ term term^3.0 term
+ term stop^3.0 term
+ term +stop term
+ term -stop term
+ drop AND (stop) AND roll
+ +drop +roll
+ term +(stop) term
+ term -(stop) term
+ drop AND stop AND roll
+ term phrase term
+ term (phrase1 phrase2) term
+ term AND NOT phrase term
+ +term -(phrase1 phrase2) term
+ stop^3
+ stop
+ (stop)^3
+ ((stop))^3
+ (stop^3)
+ ((stop)^3)
+ (stop)
+ ((stop))
+ term +stop
+ [ a TO z]
+ [a TO z]
+ [ a TO z ]
+ { a TO z}
+ {a TO z}
+ { a TO z }
+ { a TO z }^2.0
+ {a TO z}^2.0
+ [ a TO z] OR bar
+ [a TO z] bar
+ [ a TO z] AND bar
+ +[a TO z] +bar
+ ( bar blar { a TO z})
+ bar blar {a TO z}
+ gack ( bar blar { a TO z})
+ gack (bar blar {a TO z})
+ [* TO Z]
+ [* TO z]
+ [A TO *]
+ [a TO *]
+ [* TO *]
+ [\* TO \*]
+ \!blah
+ \:blah
+ blah
+ \~blah
+ \*blah
+ a
+ a-b:c
+ a+b:c
+ a\:b:c
+ a\\b:c
+ a:b-c
+ a:b+c
+ a:b\:c
+ a:b\\c
+ a:b-c*
+ a:b+c*
+ a:b\:c*
+ a:b\\c*
+ a:b-c~2.0
+ a:b+c~2.0
+ a:b\:c~
+ a:b\\c~
+ [a- TO a+]
+ [ a\\ TO a\* ]
+ c\:\\temp\\\~foo.txt
+ abc
+ XYZ
+ (item:\\ item:ABCD\\)
+ \*
+ *
+ \\
+ a\:b\:c
+ a\\b\:c
+ a\:b\\c
+ a\:b\:c\*
+ a\:b\\\\c\*
+ a:b-c~
+ a:b+c~
+ a\:b\:c\~
+ a\:b\\c\~
+ +weltbank +worlbank
+ +term +term +term
+ term +term term
+ term term +term
+ term +term +term
+ -term term term
+ -term +term +term
+ on
+ on^1.0
+ hello^2.0
+ the^3
+ the
+ some phrase
+ xunit~
+ one two three
+ A AND B OR C AND D
+ +A +B +C +D
+ foo:zoo*
+ foo:zoo*^2
+ zoo
+ foo:*
+ foo:*^2
+ *:foo
+ a:the OR a:foo
+ a:woo OR a:the
+ *:*
+ (*:*)
+ +*:* -*:*
+ the wizard of ozzy
+ """.splitlines()
+
+failtests = r"""
+ field:term:with:colon some more terms
+ (sub query)^5.0^2.0 plus more
+ a:b:c
+ a:b:c~
+ a:b:c*
+ a:b:c~2.0
+ \+blah
+ \-blah
+ foo \|| bar
+ foo \AND bar
+ \a
+ a\-b:c
+ a\+b:c
+ a\b:c
+ a:b\-c
+ a:b\+c
+ a\-b\:c
+ a\+b\:c
+ a:b\c*
+ a:b\-c~
+ a:b\+c~
+ a:b\c
+ a:b\-c*
+ a:b\+c*
+ [ a\- TO a\+ ]
+ [a\ TO a*]
+ a\\\+b
+ a\+b
+ c:\temp\~foo.txt
+ XY\
+ a\u0062c
+ a:b\c~2.0
+ XY\u005a
+ XY\u005A
+ item:\ item:ABCD\
+ \
+ a\ or b
+ a\:b\-c
+ a\:b\+c
+ a\:b\-c\*
+ a\:b\+c\*
+ a\:b\-c\~
+ a\:b\+c\~
+ a:b\c~
+ [ a\ TO a* ]
+ """.splitlines()
+
+allpass = True
+for t in [_f for _f in map(str.strip,tests) if _f]:
+ print(t)
+ try:
+ #~ expression.parseString(t,parseAll=True)
+ print(expression.parseString(t,parseAll=True))
+ except ParseException as pe:
+ print(t)
+ print(pe)
+ allpass = False
+ print('')
+
+print(("FAIL", "OK")[allpass])
diff --git a/trunk/src/examples/macroExpander.py b/trunk/src/examples/macroExpander.py
new file mode 100644
index 0000000..327976c
--- /dev/null
+++ b/trunk/src/examples/macroExpander.py
@@ -0,0 +1,60 @@
+# macroExpander.py
+#
+# Example pyparsing program for performing macro expansion, similar to
+# the C pre-processor. This program is not as fully-featured, simply
+# processing macros of the form:
+# #def xxx yyyyy
+# and replacing xxx with yyyyy in the rest of the input string. Macros
+# can also be composed using other macros, such as
+# #def zzz xxx+1
+# Since xxx was previously defined as yyyyy, then zzz will be replaced
+# with yyyyy+1.
+#
+# Copyright 2007 by Paul McGuire
+#
+from pyparsing import *
+
+# define the structure of a macro definition (the empty term is used
+# to advance to the next non-whitespace character)
+identifier = Word(alphas+"_",alphanums+"_")
+macroDef = "#def" + identifier("macro") + empty + restOfLine("value")
+
+# define a placeholder for defined macros - initially nothing
+macroExpr = Forward()
+macroExpr << NoMatch()
+
+# global dictionary for macro definitions
+macros = {}
+
+# parse action for macro definitions
+def processMacroDefn(s,l,t):
+ macroVal = macroExpander.transformString(t.value)
+ macros[t.macro] = macroVal
+ macroExpr << MatchFirst(map(Keyword, macros.keys()))
+ return "#def " + t.macro + " " + macroVal
+
+# parse action to replace macro references with their respective definition
+def processMacroRef(s,l,t):
+ return macros[t[0]]
+
+# attach parse actions to expressions
+macroExpr.setParseAction(processMacroRef)
+macroDef.setParseAction(processMacroDefn)
+
+# define pattern for scanning through the input string
+macroExpander = macroExpr | macroDef
+
+
+
+# test macro substitution using transformString
+testString = """
+ #def A 100
+ #def ALEN A+1
+
+ char Astring[ALEN];
+ char AA[A];
+ typedef char[ALEN] Acharbuf;
+ """
+
+print(macroExpander.transformString(testString))
+print(macros)
diff --git a/trunk/src/examples/makeHTMLTagExample.py b/trunk/src/examples/makeHTMLTagExample.py
new file mode 100644
index 0000000..3b771c7
--- /dev/null
+++ b/trunk/src/examples/makeHTMLTagExample.py
@@ -0,0 +1,21 @@
+import urllib.request, urllib.parse, urllib.error
+
+from pyparsing import makeHTMLTags, SkipTo
+
+# read HTML from a web page
+serverListPage = urllib.request.urlopen( "http://www.yahoo.com" )
+htmlText = serverListPage.read()
+serverListPage.close()
+
+# using makeHTMLTags to define opening and closing tags
+anchorStart,anchorEnd = makeHTMLTags("a")
+
+# compose an expression for an anchored reference
+anchor = anchorStart + SkipTo(anchorEnd)("body") + anchorEnd
+
+# use scanString to scan through the HTML source, extracting
+# just the anchor tags and their associated body text
+# (note the href attribute of the opening A tag is available
+# as an attribute in the returned parse results)
+for tokens,start,end in anchor.scanString(htmlText):
+ print(tokens.body,'->',tokens.href)
diff --git a/trunk/src/examples/matchPreviousDemo.py b/trunk/src/examples/matchPreviousDemo.py
new file mode 100644
index 0000000..f0812e9
--- /dev/null
+++ b/trunk/src/examples/matchPreviousDemo.py
@@ -0,0 +1,33 @@
+#
+# matchPreviousDemo.py
+#
+
+from pyparsing import *
+
+src = """
+class a
+...
+end a;
+
+class b
+...
+end b;
+
+class c
+...
+end d;"""
+
+
+identifier = Word(alphas)
+
+classIdent = identifier("classname") # note that this also makes a copy of identifier
+classHead = "class" + classIdent
+classBody = "..."
+classEnd = "end" + matchPreviousLiteral(classIdent) + ';'
+classDefn = classHead + classBody + classEnd
+
+# use this form to catch syntax error
+# classDefn = classHead + classBody - classEnd
+
+for tokens in classDefn.searchString(src):
+ print(tokens.classname) \ No newline at end of file
diff --git a/trunk/src/examples/mozilla.ics b/trunk/src/examples/mozilla.ics
new file mode 100644
index 0000000..e6dd3dc
--- /dev/null
+++ b/trunk/src/examples/mozilla.ics
@@ -0,0 +1,50 @@
+BEGIN:VCALENDAR
+VERSION
+ :2.0
+PRODID
+ :-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN
+METHOD
+ :PUBLISH
+BEGIN:VEVENT
+UID
+ :153ed0e0-1dd2-11b2-9d71-96da104537a4
+SUMMARY
+ :Test event
+DESCRIPTION
+ :Some notes
+LOCATION
+ :Somewhere over the rainbow
+CATEGORIES
+ :Personal
+URL
+ :http://personal.amec.fi
+STATUS
+ :CONFIRMED
+CLASS
+ :PRIVATE
+X
+ ;MEMBER=AlarmEmailAddress
+ :petri.savolainen@iki.fi
+X-MOZILLA-RECUR-DEFAULT-UNITS
+ :months
+RRULE
+ :FREQ=MONTHLY;UNTIL=20040914;INTERVAL=1
+EXDATE
+ :20040714T000000
+DTSTART
+ ;VALUE=DATE
+ :20040714
+DTEND
+ ;VALUE=DATE
+ :20040815
+DTSTAMP
+ :20040714T141219Z
+LAST-MODIFIED
+ :20040714T141457Z
+BEGIN:VALARM
+TRIGGER
+ ;VALUE=DURATION
+ :PT15M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
diff --git a/trunk/src/examples/mozillaCalendarParser.py b/trunk/src/examples/mozillaCalendarParser.py
new file mode 100644
index 0000000..210f284
--- /dev/null
+++ b/trunk/src/examples/mozillaCalendarParser.py
@@ -0,0 +1,81 @@
+from pyparsing import Optional, oneOf, Dict, Literal, Word, printables, Group, OneOrMore, ZeroOrMore
+
+"""
+A simple parser for calendar (*.ics) files,
+as exported by the Mozilla calendar.
+
+Any suggestions and comments welcome.
+
+Version: 0.1
+Copyright: Petri Savolainen <firstname.lastname@iki.fi>
+License: Free for any use
+"""
+
+
+# TERMINALS
+
+BEGIN = Literal("BEGIN:").suppress()
+END = Literal("END:").suppress()
+valstr = printables + "\xe4\xf6\xe5\xd6\xc4\xc5 "
+
+EQ = Literal("=").suppress()
+SEMI = Literal(";").suppress()
+COLON = Literal(":").suppress()
+
+EVENT = Literal("VEVENT").suppress()
+CALENDAR = Literal("VCALENDAR").suppress()
+ALARM = Literal("VALARM").suppress()
+
+# TOKENS
+
+CALPROP = oneOf("VERSION PRODID METHOD")
+ALMPROP = oneOf("TRIGGER")
+EVTPROP = oneOf("X-MOZILLA-RECUR-DEFAULT-INTERVAL \
+ X-MOZILLA-RECUR-DEFAULT-UNITS \
+ UID DTSTAMP LAST-MODIFIED X RRULE EXDATE")
+
+propval = Word(valstr)
+typeval = Word(valstr)
+typename = oneOf("VALUE MEMBER FREQ UNTIL INTERVAL")
+
+proptype = Group(SEMI + typename + EQ + typeval).suppress()
+
+calprop = Group(CALPROP + ZeroOrMore(proptype) + COLON + propval)
+almprop = Group(ALMPROP + ZeroOrMore(proptype) + COLON + propval)
+evtprop = Group(EVTPROP + ZeroOrMore(proptype) + COLON + propval).suppress() \
+ | "CATEGORIES" + COLON + propval.setResultsName("categories") \
+ | "CLASS" + COLON + propval.setResultsName("class") \
+ | "DESCRIPTION" + COLON + propval.setResultsName("description") \
+ | "DTSTART" + proptype + COLON + propval.setResultsName("begin") \
+ | "DTEND" + proptype + COLON + propval.setResultsName("end") \
+ | "LOCATION" + COLON + propval.setResultsName("location") \
+ | "PRIORITY" + COLON + propval.setResultsName("priority") \
+ | "STATUS" + COLON + propval.setResultsName("status") \
+ | "SUMMARY" + COLON + propval.setResultsName("summary") \
+ | "URL" + COLON + propval.setResultsName("url") \
+
+calprops = Group(OneOrMore(calprop)).suppress()
+evtprops = Group(OneOrMore(evtprop))
+almprops = Group(OneOrMore(almprop)).suppress()
+
+alarm = BEGIN + ALARM + almprops + END + ALARM
+event = BEGIN + EVENT + evtprops + Optional(alarm) + END + EVENT
+events = Group(OneOrMore(event))
+calendar = BEGIN + CALENDAR + calprops + ZeroOrMore(event) + END + CALENDAR
+calendars = OneOrMore(calendar)
+
+
+# PARSE ACTIONS
+
+def gotEvent(s,loc,toks):
+ for event in toks:
+ print(event.dump())
+
+event.setParseAction(gotEvent)
+
+
+# MAIN PROGRAM
+
+if __name__=="__main__":
+
+ calendars.parseFile("mozilla.ics")
diff --git a/trunk/src/examples/nested.py b/trunk/src/examples/nested.py
new file mode 100644
index 0000000..24cf2f4
--- /dev/null
+++ b/trunk/src/examples/nested.py
@@ -0,0 +1,30 @@
+#
+# nested.py
+# Copyright, 2007 - Paul McGuire
+#
+# Simple example of using nestedExpr to define expressions using
+# paired delimiters for grouping lists and sublists
+#
+
+from pyparsing import *
+import pprint
+
+data = """
+{
+ { item1 "item with } in it" }
+ {
+ {item2a item2b }
+ {item3}
+ }
+
+}
+"""
+
+# use {}'s for nested lists
+nestedItems = nestedExpr("{", "}")
+print(( (nestedItems+stringEnd).parseString(data).asList() ))
+
+# use default delimiters of ()'s
+mathExpr = nestedExpr()
+print(( mathExpr.parseString( "((( ax + by)*C) *(Z | (E^F) & D))") ))
+
diff --git a/trunk/src/examples/numerics.py b/trunk/src/examples/numerics.py
new file mode 100644
index 0000000..5ab99dd
--- /dev/null
+++ b/trunk/src/examples/numerics.py
@@ -0,0 +1,62 @@
+#
+# numerics.py
+#
+# Examples of parsing real and integers using various grouping and
+# decimal point characters, varying by locale.
+#
+# Copyright 2016, Paul McGuire
+#
+# Format samples from https://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
+#
+tests = """\
+# Canadian (English and French)
+4 294 967 295,000
+
+# Danish
+4 294 967 295,000
+
+# Finnish
+4 294 967 295,000
+
+# French
+4 294 967 295,000
+
+# German
+4 294 967 295,000
+
+# Italian
+4.294.967.295,000
+
+# Norwegian
+4.294.967.295,000
+
+# Spanish
+4.294.967.295,000
+
+# Swedish
+4 294 967 295,000
+
+# GB-English
+4,294,967,295.000
+
+# US-English
+4,294,967,295.000
+
+# Thai
+4,294,967,295.000
+"""
+
+from pyparsing import Regex
+
+comma_decimal = Regex(r'\d{1,2}(([ .])\d\d\d(\2\d\d\d)*)?,\d*')
+comma_decimal.setParseAction(lambda t: float(t[0].replace(' ','').replace('.','').replace(',','.')))
+
+dot_decimal = Regex(r'\d{1,2}(([ ,])\d\d\d(\2\d\d\d)*)?\.\d*')
+dot_decimal.setParseAction(lambda t: float(t[0].replace(' ','').replace(',','')))
+
+decimal = comma_decimal ^ dot_decimal
+decimal.runTests(tests, parseAll=True)
+
+grouped_integer = Regex(r'\d{1,2}(([ .,])\d\d\d(\2\d\d\d)*)?')
+grouped_integer.setParseAction(lambda t: int(t[0].replace(' ','').replace(',','').replace('.','')))
+grouped_integer.runTests(tests, parseAll=False)
diff --git a/trunk/src/examples/oc.py b/trunk/src/examples/oc.py
new file mode 100644
index 0000000..77ea195
--- /dev/null
+++ b/trunk/src/examples/oc.py
@@ -0,0 +1,193 @@
+# oc.py
+#
+# A subset-C parser, (BNF taken from 1996 International Obfuscated C Code Contest)
+#
+# Copyright, 2010, Paul McGuire
+#
+"""
+http://www.ioccc.org/1996/august.hint
+
+The following is a description of the OC grammar:
+
+ OC grammar
+ ==========
+ Terminals are in quotes, () is used for bracketing.
+
+ program: decl*
+
+ decl: vardecl
+ fundecl
+
+ vardecl: type NAME ;
+ type NAME "[" INT "]" ;
+
+ fundecl: type NAME "(" args ")" "{" body "}"
+
+ args: /*empty*/
+ ( arg "," )* arg
+
+ arg: type NAME
+
+ body: vardecl* stmt*
+
+ stmt: ifstmt
+ whilestmt
+ dowhilestmt
+ "return" expr ";"
+ expr ";"
+ "{" stmt* "}"
+ ";"
+
+ ifstmt: "if" "(" expr ")" stmt
+ "if" "(" expr ")" stmt "else" stmt
+
+ whilestmt: "while" "(" expr ")" stmt
+
+ dowhilestmt: "do" stmt "while" "(" expr ")" ";"
+
+ expr: expr binop expr
+ unop expr
+ expr "[" expr "]"
+ "(" expr ")"
+ expr "(" exprs ")"
+ NAME
+ INT
+ CHAR
+ STRING
+
+ exprs: /*empty*/
+ (expr ",")* expr
+
+ binop: "+" | "-" | "*" | "/" | "%" |
+ "=" |
+ "<" | "==" | "!="
+
+ unop: "!" | "-" | "*"
+
+ type: "int" stars
+ "char" stars
+
+ stars: "*"*
+"""
+
+from pyparsing import *
+
+LPAR,RPAR,LBRACK,RBRACK,LBRACE,RBRACE,SEMI,COMMA = map(Suppress, "()[]{};,")
+INT = Keyword("int")
+CHAR = Keyword("char")
+WHILE = Keyword("while")
+DO = Keyword("do")
+IF = Keyword("if")
+ELSE = Keyword("else")
+RETURN = Keyword("return")
+
+NAME = Word(alphas+"_", alphanums+"_")
+integer = Regex(r"[+-]?\d+")
+char = Regex(r"'.'")
+string_ = dblQuotedString
+
+INT = Keyword("int")
+CHAR = Keyword("char")
+TYPE = Group((INT | CHAR) + ZeroOrMore("*"))
+
+expr = Forward()
+operand = NAME | integer | char | string_
+expr << (infixNotation(operand,
+ [
+ (oneOf('! - *'), 1, opAssoc.RIGHT),
+ (oneOf('++ --'), 1, opAssoc.RIGHT),
+ (oneOf('++ --'), 1, opAssoc.LEFT),
+ (oneOf('* / %'), 2, opAssoc.LEFT),
+ (oneOf('+ -'), 2, opAssoc.LEFT),
+ (oneOf('< == > <= >= !='), 2, opAssoc.LEFT),
+ (Regex(r'=[^=]'), 2, opAssoc.LEFT),
+ ]) +
+ Optional( LBRACK + expr + RBRACK |
+ LPAR + Group(Optional(delimitedList(expr))) + RPAR )
+ )
+
+stmt = Forward()
+
+ifstmt = IF - LPAR + expr + RPAR + stmt + Optional(ELSE + stmt)
+whilestmt = WHILE - LPAR + expr + RPAR + stmt
+dowhilestmt = DO - stmt + WHILE + LPAR + expr + RPAR + SEMI
+returnstmt = RETURN - expr + SEMI
+
+stmt << Group( ifstmt |
+ whilestmt |
+ dowhilestmt |
+ returnstmt |
+ expr + SEMI |
+ LBRACE + ZeroOrMore(stmt) + RBRACE |
+ SEMI)
+
+vardecl = Group(TYPE + NAME + Optional(LBRACK + integer + RBRACK)) + SEMI
+
+arg = Group(TYPE + NAME)
+body = ZeroOrMore(vardecl) + ZeroOrMore(stmt)
+fundecl = Group(TYPE + NAME + LPAR + Optional(Group(delimitedList(arg))) + RPAR +
+ LBRACE + Group(body) + RBRACE)
+decl = fundecl | vardecl
+program = ZeroOrMore(decl)
+
+program.ignore(cStyleComment)
+
+# set parser element names
+for vname in ("ifstmt whilestmt dowhilestmt returnstmt TYPE "
+ "NAME fundecl vardecl program arg body stmt".split()):
+ v = vars()[vname]
+ v.setName(vname)
+
+#~ for vname in "fundecl stmt".split():
+ #~ v = vars()[vname]
+ #~ v.setDebug()
+
+test = r"""
+/* A factorial program */
+int
+putstr(char *s)
+{
+ while(*s)
+ putchar(*s++);
+}
+
+int
+fac(int n)
+{
+ if (n == 0)
+ return 1;
+ else
+ return n*fac(n-1);
+}
+
+int
+putn(int n)
+{
+ if (9 < n)
+ putn(n / 10);
+ putchar((n%10) + '0');
+}
+
+int
+facpr(int n)
+{
+ putstr("factorial ");
+ putn(n);
+ putstr(" = ");
+ putn(fac(n));
+ putstr("\n");
+}
+
+int
+main()
+{
+ int i;
+ i = 0;
+ while(i < 10)
+ facpr(i++);
+ return 0;
+}
+"""
+
+ast = program.parseString(test,parseAll=True)
+ast.pprint()
diff --git a/trunk/src/examples/parseListString.py b/trunk/src/examples/parseListString.py
new file mode 100644
index 0000000..d74f3af
--- /dev/null
+++ b/trunk/src/examples/parseListString.py
@@ -0,0 +1,82 @@
+# parseListString.py
+#
+# Copyright, 2006, by Paul McGuire
+#
+
+from pyparsing import *
+
+# first pass
+lbrack = Literal("[")
+rbrack = Literal("]")
+integer = Word(nums).setName("integer")
+real = Combine(Optional(oneOf("+ -")) + Word(nums) + "." +
+ Optional(Word(nums))).setName("real")
+
+listItem = real | integer | quotedString
+
+listStr = lbrack + delimitedList(listItem) + rbrack
+
+test = "['a', 100, 3.14]"
+
+print(listStr.parseString(test))
+
+
+# second pass, cleanup and add converters
+lbrack = Literal("[").suppress()
+rbrack = Literal("]").suppress()
+cvtInt = lambda s,l,toks: int(toks[0])
+integer = Word(nums).setName("integer").setParseAction( cvtInt )
+cvtReal = lambda s,l,toks: float(toks[0])
+real = Regex(r'[+-]?\d+\.\d*').setName("floating-point number").setParseAction( cvtReal )
+listItem = real | integer | quotedString.setParseAction( removeQuotes )
+
+listStr = lbrack + delimitedList(listItem) + rbrack
+
+test = "['a', 100, 3.14]"
+
+print(listStr.parseString(test))
+
+# third pass, add nested list support, and tuples, too!
+cvtInt = lambda s,l,toks: int(toks[0])
+cvtReal = lambda s,l,toks: float(toks[0])
+
+lbrack = Literal("[").suppress()
+rbrack = Literal("]").suppress()
+integer = Word(nums).setName("integer").setParseAction( cvtInt )
+real = Regex(r'[+-]?\d+\.\d*').setName("floating-point number").setParseAction( cvtReal )
+tupleStr = Forward()
+listStr = Forward()
+listItem = real | integer | quotedString.setParseAction(removeQuotes) | Group(listStr) | tupleStr
+tupleStr << ( Suppress("(") + delimitedList(listItem) + Optional(Suppress(",")) + Suppress(")") )
+tupleStr.setParseAction( lambda t:tuple(t.asList()) )
+listStr << lbrack + delimitedList(listItem) + Optional(Suppress(",")) + rbrack
+
+test = "['a', 100, ('A', [101,102]), 3.14, [ +2.718, 'xyzzy', -1.414] ]"
+print(listStr.parseString(test))
+
+# fourth pass, add parsing of dicts
+cvtInt = lambda s,l,toks: int(toks[0])
+cvtReal = lambda s,l,toks: float(toks[0])
+cvtDict = lambda s,l,toks: dict(toks[0])
+
+lbrack = Literal("[").suppress()
+rbrack = Literal("]").suppress()
+lbrace = Literal("{").suppress()
+rbrace = Literal("}").suppress()
+colon = Literal(":").suppress()
+integer = Word(nums).setName("integer").setParseAction( cvtInt )
+real = Regex(r'[+-]?\d+\.\d*').setName("real").setParseAction( cvtReal )
+
+tupleStr = Forward()
+listStr = Forward()
+dictStr = Forward()
+listItem = real | integer | quotedString.setParseAction(removeQuotes) | Group(listStr) | tupleStr | dictStr
+tupleStr <<= ( Suppress("(") + delimitedList(listItem) + Optional(Suppress(",")) + Suppress(")") )
+tupleStr.setParseAction( lambda t:tuple(t.asList()) )
+listStr <<= (lbrack + Optional(delimitedList(listItem)) + Optional(Suppress(",")) + rbrack)
+dictKeyStr = real | integer | quotedString.setParseAction(removeQuotes)
+dictStr <<= lbrace + Optional(delimitedList( Group( dictKeyStr + colon + listItem ))) + Optional(Suppress(",")) + rbrace
+dictStr.setParseAction(lambda t: dict((k_v[0],(k_v[1].asList() if isinstance(k_v[1],ParseResults) else k_v[1])) for k_v in t))
+
+test = '[{0: [2], 1: []}, {0: [], 1: [], 2: [,]}, {0: [1, 2,],}]'
+print(listStr.parseString(test))
diff --git a/trunk/src/examples/parsePythonValue.py b/trunk/src/examples/parsePythonValue.py
new file mode 100644
index 0000000..53c61fc
--- /dev/null
+++ b/trunk/src/examples/parsePythonValue.py
@@ -0,0 +1,70 @@
+# parsePythonValue.py
+#
+# Copyright, 2006, by Paul McGuire
+#
+from __future__ import print_function
+from pyparsing import *
+
+
+cvtBool = lambda t:t[0]=='True'
+cvtInt = lambda toks: int(toks[0])
+cvtReal = lambda toks: float(toks[0])
+cvtTuple = lambda toks : tuple(toks.asList())
+cvtDict = lambda toks: dict(toks.asList())
+cvtList = lambda toks: [toks.asList()]
+
+# define punctuation as suppressed literals
+lparen,rparen,lbrack,rbrack,lbrace,rbrace,colon = \
+ map(Suppress,"()[]{}:")
+
+integer = Regex(r"[+-]?\d+")\
+ .setName("integer")\
+ .setParseAction( cvtInt )
+real = Regex(r"[+-]?\d+\.\d*([Ee][+-]?\d+)?")\
+ .setName("real")\
+ .setParseAction( cvtReal )
+tupleStr = Forward()
+listStr = Forward()
+dictStr = Forward()
+
+unicodeString.setParseAction(lambda t:t[0][2:-1].decode('unicode-escape'))
+quotedString.setParseAction(lambda t:t[0][1:-1].decode('string-escape'))
+boolLiteral = oneOf("True False").setParseAction(cvtBool)
+noneLiteral = Literal("None").setParseAction(replaceWith(None))
+
+listItem = real|integer|quotedString|unicodeString|boolLiteral|noneLiteral| \
+ Group(listStr) | tupleStr | dictStr
+
+tupleStr << ( Suppress("(") + Optional(delimitedList(listItem)) +
+ Optional(Suppress(",")) + Suppress(")") )
+tupleStr.setParseAction( cvtTuple )
+
+listStr << (lbrack + Optional(delimitedList(listItem) +
+ Optional(Suppress(","))) + rbrack)
+listStr.setParseAction(cvtList, lambda t: t[0])
+
+dictEntry = Group( listItem + colon + listItem )
+dictStr << (lbrace + Optional(delimitedList(dictEntry) + \
+ Optional(Suppress(","))) + rbrace)
+dictStr.setParseAction( cvtDict )
+
+tests = """['a', 100, ('A', [101,102]), 3.14, [ +2.718, 'xyzzy', -1.414] ]
+ [{0: [2], 1: []}, {0: [], 1: [], 2: []}, {0: [1, 2]}]
+ { 'A':1, 'B':2, 'C': {'a': 1.2, 'b': 3.4} }
+ 3.14159
+ 42
+ 6.02E23
+ 6.02e+023
+ 1.0e-7
+ 'a quoted string'""".split("\n")
+
+for test in tests:
+ print("Test:", test.strip())
+ result = listItem.parseString(test)[0]
+ print("Result:", result)
+ try:
+ for dd in result:
+ if isinstance(dd,dict): print(list(dd.items()))
+ except TypeError as te:
+ pass
+ print()
diff --git a/trunk/src/examples/parseResultsSumExample.py b/trunk/src/examples/parseResultsSumExample.py
new file mode 100644
index 0000000..1fb694a
--- /dev/null
+++ b/trunk/src/examples/parseResultsSumExample.py
@@ -0,0 +1,26 @@
+#
+# parseResultsSumExample.py
+#
+# Sample script showing the value in merging ParseResults retrieved by searchString,
+# using Python's builtin sum() method
+#
+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"
+
+from pyparsing import *
+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
+
+for test in (samplestr1,samplestr2,samplestr3,samplestr4,):
+ person = sum(person_data.searchString(test))
+ print(person.id)
+ print(person.dump())
+ print()
+
+
+ \ No newline at end of file
diff --git a/trunk/src/examples/parseTabularData.py b/trunk/src/examples/parseTabularData.py
new file mode 100644
index 0000000..3846310
--- /dev/null
+++ b/trunk/src/examples/parseTabularData.py
@@ -0,0 +1,50 @@
+#
+# parseTabularData.py
+#
+# Example of parsing data that is formatted in a tabular listing, with
+# potential for missing values. Uses new addCondition method on
+# ParserElements.
+#
+# Copyright 2015, Paul McGuire
+#
+from pyparsing import col,Word,Optional,alphas,nums,ParseException
+
+table = """\
+ 1 2
+12345678901234567890
+COLOR S M L
+RED 10 2 2
+BLUE 5 10
+GREEN 3 5
+PURPLE 8"""
+
+# function to create column-specific parse conditions
+def mustMatchCols(startloc,endloc):
+ return lambda s,l,t: startloc <= col(l,s) <= endloc
+
+# helper to define values in a space-delimited table
+# (change empty_cell_is_zero to True if a value of 0 is desired for empty cells)
+def tableValue(expr, colstart, colend):
+ empty_cell_is_zero = False
+ if empty_cell_is_zero:
+ return Optional(expr.copy().addCondition(mustMatchCols(colstart,colend),
+ message="text not in expected columns"),
+ default=0)
+ else:
+ return Optional(expr.copy().addCondition(mustMatchCols(colstart,colend),
+ message="text not in expected columns"))
+
+
+# define the grammar for this simple table
+colorname = Word(alphas)
+integer = Word(nums).setParseAction(lambda t: int(t[0])).setName("integer")
+row = (colorname("name") +
+ tableValue(integer, 11, 12)("S") +
+ tableValue(integer, 15, 16)("M") +
+ tableValue(integer, 19, 20)("L"))
+
+# parse the sample text - skip over the header and counter lines
+for line in table.splitlines()[3:]:
+ print(line)
+ print(row.parseString(line).dump())
+ print('')
diff --git a/trunk/src/examples/partial_gene_match.py b/trunk/src/examples/partial_gene_match.py
new file mode 100644
index 0000000..8bf5f7c
--- /dev/null
+++ b/trunk/src/examples/partial_gene_match.py
@@ -0,0 +1,88 @@
+# parital_gene_match.py
+#
+# Example showing how to create a customized pyparsing Token, in this case,
+# one that is very much like Literal, but which tolerates up to 'n' character
+# mismatches
+from pyparsing import *
+
+import urllib.request, urllib.parse, urllib.error
+
+# read in a bunch of genomic data
+datafile = urllib.request.urlopen("http://toxodb.org/common/downloads/release-6.0/Tgondii/TgondiiApicoplastORFsNAs_ToxoDB-6.0.fasta")
+fastasrc = datafile.read()
+datafile.close()
+
+"""
+Sample header:
+>NC_001799-6-2978-2778 | organism=Toxoplasma_gondii_RH | location=NC_001799:2778-2978(-) | length=201
+"""
+integer = Word(nums).setParseAction(lambda t:int(t[0]))
+genebit = Group(">" + Word(alphanums.upper()+"-_") + "|" +
+ Word(printables)("id") + SkipTo("length=", include=True) +
+ integer("genelen") + LineEnd() +
+ Combine(OneOrMore(Word("ACGTN")),adjacent=False)("gene"))
+
+# read gene data from .fasta file - takes just a few seconds
+genedata = OneOrMore(genebit).parseString(fastasrc)
+
+
+class CloseMatch(Token):
+ """A special subclass of Token that does *close* matches. For each
+ close match of the given string, a tuple is returned giving the
+ found close match, and a list of mismatch positions."""
+ def __init__(self, seq, maxMismatches=1):
+ super(CloseMatch,self).__init__()
+ self.name = seq
+ self.sequence = seq
+ self.maxMismatches = maxMismatches
+ self.errmsg = "Expected " + self.sequence
+ self.mayIndexError = False
+ self.mayReturnEmpty = False
+
+ def parseImpl( self, instring, loc, doActions=True ):
+ start = loc
+ instrlen = len(instring)
+ maxloc = start + len(self.sequence)
+
+ if maxloc <= instrlen:
+ seq = self.sequence
+ seqloc = 0
+ mismatches = []
+ throwException = False
+ done = False
+ while loc < maxloc and not done:
+ if instring[loc] != seq[seqloc]:
+ mismatches.append(seqloc)
+ if len(mismatches) > self.maxMismatches:
+ throwException = True
+ done = True
+ loc += 1
+ seqloc += 1
+ else:
+ throwException = True
+
+ if throwException:
+ exc = self.myException
+ exc.loc = loc
+ exc.pstr = instring
+ raise exc
+
+ return loc, (instring[start:loc],mismatches)
+
+# using the genedata extracted above, look for close matches of a gene sequence
+searchseq = CloseMatch("TTAAATCTAGAAGAT", 3)
+for g in genedata:
+ print("%s (%d)" % (g.id, g.genelen))
+ print("-"*24)
+ for t,startLoc,endLoc in searchseq.scanString(g.gene, overlap=True):
+ matched, mismatches = t[0]
+ print("MATCH:", searchseq.sequence)
+ print("FOUND:", matched)
+ if mismatches:
+ print(" ", ''.join(' ' if i not in mismatches else '*'
+ for i,c in enumerate(searchseq.sequence)))
+ else:
+ print("<exact match>")
+ print("at location", startLoc)
+ print()
+ print() \ No newline at end of file
diff --git a/trunk/src/examples/pgn.py b/trunk/src/examples/pgn.py
new file mode 100644
index 0000000..d13f83e
--- /dev/null
+++ b/trunk/src/examples/pgn.py
@@ -0,0 +1,94 @@
+# pgn.py rel. 1.1 17-sep-2004
+#
+# Demonstration of the parsing module, implementing a pgn parser.
+#
+# The aim of this parser is not to support database application,
+# but to create automagically a pgn annotated reading the log console file
+# of a lecture of ICC (Internet Chess Club), saved by Blitzin.
+# Of course you can modify the Abstract Syntax Tree to your purpose.
+#
+# Copyright 2004, by Alberto Santini http://www.albertosantini.it/chess/
+#
+from pyparsing import alphanums, nums, quotedString
+from pyparsing import Combine, Forward, Group, Literal, oneOf, OneOrMore, Optional, Suppress, ZeroOrMore, White, Word
+from pyparsing import ParseException
+
+#
+# define pgn grammar
+#
+
+tag = Suppress("[") + Word(alphanums) + Combine(quotedString) + Suppress("]")
+comment = Suppress("{") + Word(alphanums + " ") + Suppress("}")
+
+dot = Literal(".")
+piece = oneOf("K Q B N R")
+file_coord = oneOf("a b c d e f g h")
+rank_coord = oneOf("1 2 3 4 5 6 7 8")
+capture = oneOf("x :")
+promote = Literal("=")
+castle_queenside = oneOf("O-O-O 0-0-0 o-o-o")
+castle_kingside = oneOf("O-O 0-0 o-o")
+
+move_number = Optional(comment) + Word(nums) + dot
+m1 = file_coord + rank_coord # pawn move e.g. d4
+m2 = file_coord + capture + file_coord + rank_coord # pawn capture move e.g. dxe5
+m3 = file_coord + "8" + promote + piece # pawn promotion e.g. e8=Q
+m4 = piece + file_coord + rank_coord # piece move e.g. Be6
+m5 = piece + file_coord + file_coord + rank_coord # piece move e.g. Nbd2
+m6 = piece + rank_coord + file_coord + rank_coord # piece move e.g. R4a7
+m7 = piece + capture + file_coord + rank_coord # piece capture move e.g. Bxh7
+m8 = castle_queenside | castle_kingside # castling e.g. o-o
+
+check = oneOf("+ ++")
+mate = Literal("#")
+annotation = Word("!?", max=2)
+nag = " $" + Word(nums)
+decoration = check | mate | annotation | nag
+
+variant = Forward()
+half_move = Combine((m3 | m1 | m2 | m4 | m5 | m6 | m7 | m8) + Optional(decoration)) \
+ + Optional(comment) +Optional(variant)
+move = Suppress(move_number) + half_move + Optional(half_move)
+variant << "(" + OneOrMore(move) + ")"
+# grouping the plies (half-moves) for each move: useful to group annotations, variants...
+# suggested by Paul McGuire :)
+move = Group(Suppress(move_number) + half_move + Optional(half_move))
+variant << Group("(" + OneOrMore(move) + ")")
+game_terminator = oneOf("1-0 0-1 1/2-1/2 *")
+
+pgnGrammar = Suppress(ZeroOrMore(tag)) + ZeroOrMore(move) + Optional(Suppress(game_terminator))
+
+def parsePGN( pgn, bnf=pgnGrammar, fn=None ):
+ try:
+ return bnf.parseString( pgn )
+ except ParseException as err:
+ print(err.line)
+ print(" "*(err.column-1) + "^")
+ print(err)
+
+if __name__ == "__main__":
+ # input string
+ pgn = """
+[Event "ICC 5 0 u"]
+[Site "Internet Chess Club"]
+[Date "2004.01.25"]
+[Round "-"]
+[White "guest920"]
+[Black "IceBox"]
+[Result "0-1"]
+[ICCResult "White checkmated"]
+[BlackElo "1498"]
+[Opening "French defense"]
+[ECO "C00"]
+[NIC "FR.01"]
+[Time "04:44:56"]
+[TimeControl "300+0"]
+
+1. e4 e6 2. Nf3 d5 $2 3. exd5 (3. e5 g6 4. h4) exd5 4. Qe2+ Qe7 5. Qxe7+ Bxe7 6. d3 Nf6 7. Be3
+Bg4 8. Nbd2 c5 9. h3 Be6 10. O-O-O Nc6 11. g4 Bd6 12. g5 Nd7 13. Rg1 d4 14.
+g6 fxg6 15. Bg5 Rf8 16. a3 Bd5 17. Re1+ Nde5 18. Nxe5 Nxe5 19. Bf4 Rf5 20.
+Bxe5 Rxe5 21. Rg5 Rxe1# {Black wins} 0-1
+"""
+ # parse input string
+ tokens = parsePGN(pgn, pgnGrammar)
+ print(tokens.dump())
diff --git a/trunk/src/examples/position.py b/trunk/src/examples/position.py
new file mode 100644
index 0000000..984c018
--- /dev/null
+++ b/trunk/src/examples/position.py
@@ -0,0 +1,55 @@
+from pyparsing import *
+
+text = """Lorem ipsum dolor sit amet, consectetur adipisicing
+elit, sed do eiusmod tempor incididunt ut labore et dolore magna
+aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
+aute irure dolor in reprehenderit in voluptate velit esse cillum
+dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+cupidatat non proident, sunt in culpa qui officia deserunt
+mollit anim id est laborum"""
+
+# find all words beginning with a vowel
+vowels = "aeiouAEIOU"
+initialVowelWord = Word(vowels,alphas)
+
+# Unfortunately, searchString will advance character by character through
+# the input text, so it will detect that the initial "Lorem" is not an
+# initialVowelWord, but then it will test "orem" and think that it is. So
+# we need to add a do-nothing term that will match the words that start with
+# consonants, but we will just throw them away when we match them. The key is
+# that, in having been matched, the parser will skip over them entirely when
+# looking for initialVowelWords.
+consonants = ''.join(c for c in alphas if c not in vowels)
+initialConsWord = Word(consonants, alphas).suppress()
+
+# using scanString to locate where tokens are matched
+for t,start,end in (initialConsWord|initialVowelWord).scanString(text):
+ if t:
+ print(start,':', t[0])
+
+# add parse action to annotate the parsed tokens with their location in the
+# input string
+def addLocnToTokens(s,l,t):
+ t['locn'] = l
+ t['word'] = t[0]
+initialVowelWord.setParseAction(addLocnToTokens)
+
+for ivowelInfo in (initialConsWord | initialVowelWord).searchString(text):
+ if not ivowelInfo:
+ continue
+ print(ivowelInfo.locn, ':', ivowelInfo.word)
+
+
+# alternative - add an Empty that will save the current location
+def location(name):
+ return Empty().setParseAction(lambda s,l,t: t.__setitem__(name,l))
+locateInitialVowels = location("locn") + initialVowelWord("word")
+
+# search through the input text
+for ivowelInfo in (initialConsWord | locateInitialVowels).searchString(text):
+ if not ivowelInfo:
+ continue
+ print(ivowelInfo.locn, ':', ivowelInfo.word)
+
+
diff --git a/trunk/src/examples/protobuf_parser.py b/trunk/src/examples/protobuf_parser.py
new file mode 100644
index 0000000..04ce0d8
--- /dev/null
+++ b/trunk/src/examples/protobuf_parser.py
@@ -0,0 +1,100 @@
+# protobuf_parser.py
+#
+# simple parser for parsing protobuf .proto files
+#
+# Copyright 2010, Paul McGuire
+#
+
+from pyparsing import (Word, alphas, alphanums, Regex, Suppress, Forward,
+ Group, oneOf, ZeroOrMore, Optional, delimitedList, Keyword,
+ restOfLine, quotedString, Dict)
+
+ident = Word(alphas+"_",alphanums+"_").setName("identifier")
+integer = Regex(r"[+-]?\d+")
+
+LBRACE,RBRACE,LBRACK,RBRACK,LPAR,RPAR,EQ,SEMI = map(Suppress,"{}[]()=;")
+
+kwds = """message required optional repeated enum extensions extends extend
+ to package service rpc returns true false option import"""
+for kw in kwds.split():
+ exec("%s_ = Keyword('%s')" % (kw.upper(), kw))
+
+messageBody = Forward()
+
+messageDefn = MESSAGE_ - ident("messageId") + LBRACE + messageBody("body") + RBRACE
+
+typespec = oneOf("""double float int32 int64 uint32 uint64 sint32 sint64
+ fixed32 fixed64 sfixed32 sfixed64 bool string bytes""") | ident
+rvalue = integer | TRUE_ | FALSE_ | ident
+fieldDirective = LBRACK + Group(ident + EQ + rvalue) + RBRACK
+fieldDefn = (( REQUIRED_ | OPTIONAL_ | REPEATED_ )("fieldQualifier") -
+ typespec("typespec") + ident("ident") + EQ + integer("fieldint") + ZeroOrMore(fieldDirective) + SEMI)
+
+# enumDefn ::= 'enum' ident '{' { ident '=' integer ';' }* '}'
+enumDefn = ENUM_("typespec") - ident('name') + LBRACE + Dict( ZeroOrMore( Group(ident + EQ + integer + SEMI) ))('values') + RBRACE
+
+# extensionsDefn ::= 'extensions' integer 'to' integer ';'
+extensionsDefn = EXTENSIONS_ - integer + TO_ + integer + SEMI
+
+# messageExtension ::= 'extend' ident '{' messageBody '}'
+messageExtension = EXTEND_ - ident + LBRACE + messageBody + RBRACE
+
+# messageBody ::= { fieldDefn | enumDefn | messageDefn | extensionsDefn | messageExtension }*
+messageBody << Group(ZeroOrMore( Group(fieldDefn | enumDefn | messageDefn | extensionsDefn | messageExtension) ))
+
+# methodDefn ::= 'rpc' ident '(' [ ident ] ')' 'returns' '(' [ ident ] ')' ';'
+methodDefn = (RPC_ - ident("methodName") +
+ LPAR + Optional(ident("methodParam")) + RPAR +
+ RETURNS_ + LPAR + Optional(ident("methodReturn")) + RPAR)
+
+# serviceDefn ::= 'service' ident '{' methodDefn* '}'
+serviceDefn = SERVICE_ - ident("serviceName") + LBRACE + ZeroOrMore(Group(methodDefn)) + RBRACE
+
+# packageDirective ::= 'package' ident [ '.' ident]* ';'
+packageDirective = Group(PACKAGE_ - delimitedList(ident, '.', combine=True) + SEMI)
+
+comment = '//' + restOfLine
+
+importDirective = IMPORT_ - quotedString("importFileSpec") + SEMI
+
+optionDirective = OPTION_ - ident("optionName") + EQ + quotedString("optionValue") + SEMI
+
+topLevelStatement = Group(messageDefn | messageExtension | enumDefn | serviceDefn | importDirective | optionDirective)
+
+parser = Optional(packageDirective) + ZeroOrMore(topLevelStatement)
+
+parser.ignore(comment)
+
+
+test1 = """message Person {
+ required int32 id = 1;
+ required string name = 2;
+ optional string email = 3;
+}"""
+
+test2 = """package tutorial;
+
+message Person {
+ required string name = 1;
+ required int32 id = 2;
+ optional string email = 3;
+
+ enum PhoneType {
+ MOBILE = 0;
+ HOME = 1;
+ WORK = 2;
+ }
+
+ message PhoneNumber {
+ required string number = 1;
+ optional PhoneType type = 2 [default = HOME];
+ }
+
+ repeated PhoneNumber phone = 4;
+}
+
+message AddressBook {
+ repeated Person person = 1;
+}"""
+
+parser.runTests([test1, test2])
diff --git a/trunk/src/examples/pymicko.py b/trunk/src/examples/pymicko.py
new file mode 100644
index 0000000..b136689
--- /dev/null
+++ b/trunk/src/examples/pymicko.py
@@ -0,0 +1,1387 @@
+#!/usr/bin/python
+
+# Python/pyparsing educational microC compiler v1.0
+# Copyright (C) 2009 Zarko Zivanov
+# (largely based on flex/bison microC compiler by Zorica Suvajdzin, used with her permission;
+# current version can be found at http://www.acs.uns.ac.rs, under "Programski Prevodioci" [Serbian site])
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# A copy of the GNU General Public License can be found at <http://www.gnu.org/licenses/>.
+
+from pyparsing import *
+from sys import stdin, stdout, stderr, argv, exit
+
+#defines debug level
+# 0 - no debug
+# 1 - print parsing results
+# 2 - print parsing results and symbol table
+# 3 - print parsing results only, without executing parse actions (grammar-only testing)
+DEBUG = 0
+
+##########################################################################################
+##########################################################################################
+
+# About microC language and microC compiler
+
+# microC language and microC compiler are educational tools, and their goal is to show some basic principles
+# of writing a C language compiler. Compiler represents one (relatively simple) solution, not necessarily the best one.
+# This Python/pyparsing version is made using Python 2.6.4 and pyparsing 1.5.2 (and it may contain errors :) )
+
+##########################################################################################
+##########################################################################################
+
+# Model of the used hypothetical processor
+
+# The reason behind using a hypothetical processor is to simplify code generation and to concentrate on the compiler itself.
+# This compiler can relatively easily be ported to x86, but one must know all the little details about which register
+# can be used for what, which registers are default for various operations, etc.
+
+# The hypothetical processor has 16 registers, called %0 to %15. Register %13 is used for the function return value (x86's eax),
+# %14 is the stack frame pointer (x86's ebp) and %15 is the stack pointer (x86's esp). All data-handling instructions can be
+# unsigned (suffix U), or signed (suffix S). These are ADD, SUB, MUL and DIV. These are three-address instructions,
+# the first two operands are input, the third one is output. Whether these operands are registers, memory or constant
+# is not relevant, all combinations are possible (except that output cannot be a constant). Constants are writen with a $ prefix (10-base only).
+# Conditional jumps are handled by JXXY instructions, where XX is LT, GT, LE, GE, EQ, NE (less than, greater than, less than or equal, etc.)
+# and Y is U or S (unsigned or signed, except for JEQ i JNE). Unconditional jump is JMP. The move instruction is MOV.
+# Function handling is done using CALL, RET, PUSH and POP (C style function calls). Static data is defined using the WORD directive
+# (example: variable: WORD 1), whose only argument defines the number of locations that are reserved.
+
+##########################################################################################
+##########################################################################################
+
+# Grammar of The microC Programming Language
+# (small subset of C made for compiler course at Faculty of Technical Sciences, Chair for Applied Computer Sciences, Novi Sad, Serbia)
+
+# Patterns:
+
+# letter
+# -> "_" | "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "E" | "f"
+# | "F" | "g" | "G" | "h" | "H" | "i" | "I" | "j" | "J" | "k" | "K" | "l"
+# | "L" | "m" | "M" | "n" | "N" | "o" | "O" | "p" | "P" | "q" | "Q" | "r"
+# | "R" | "s" | "S" | "t" | "T" | "u" | "U" | "v" | "V" | "w" | "W" | "x"
+# | "X" | "y" | "Y" | "z" | "Z"
+
+# digit
+# -> "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+
+# identifier
+# -> letter ( letter | digit )*
+
+# int_constant
+# -> digit +
+
+# unsigned_constant
+# -> digit + ( "u" | "U" )
+
+# Productions:
+
+# program
+# -> variable_list function_list
+# -> function_list
+
+# variable_list
+# -> variable ";"
+# -> variable_list variable ";"
+
+# variable
+# -> type identifier
+
+# type
+# -> "int"
+# -> "unsigned"
+
+# function_list
+# -> function
+# -> function_list function
+
+# function
+# -> type identifier "(" parameters ")" body
+
+# parameters
+# -> <empty>
+# -> parameter_list
+
+# parameter_list
+# -> variable
+# -> parameter_list "," variable
+
+# body
+# -> "{" variable_list statement_list "}"
+# -> "{" statement_list "}"
+
+# statement_list
+# -> <empty>
+# -> statement_list statement
+
+# statement
+# -> assignement_statement
+# -> function_call_statement
+# -> if_statement
+# -> while_statement
+# -> return_statement
+# -> compound_statement
+
+# assignement_statement
+# -> identifier "=" num_exp ";"
+
+# num_exp
+# -> mul_exp
+# -> num_exp "+" mul_exp
+# -> num_exp "-" mul_exp
+
+# mul_exp
+# -> exp
+# -> mul_exp "*" exp
+# -> mul_exp "/" exp
+
+# exp
+# -> constant
+# -> identifier
+# -> function_call
+# -> "(" num_exp ")"
+# -> "+" exp
+# -> "-" exp
+
+# constant
+# -> int_constant
+# -> unsigned_constant
+
+# function_call
+# -> identifier "(" arguments ")"
+
+# arguments
+# -> <empty>
+# -> argument_list
+
+# argument_list
+# -> num_exp
+# -> argument_list "," num_exp
+
+# function_call_statement
+# -> function_call ";"
+
+# if_statement
+# -> "if" "(" log_exp ")" statement
+# -> "if" "(" log_exp ")" statement "else" statement
+# -> -> -> -> -> -> -> -> 2
+
+# log_exp
+# -> and_exp
+# -> log_exp "||" and_exp
+
+# and_exp
+# -> rel_exp
+# -> and_exp "&&" rel_exp
+
+# rel_exp
+# -> num_exp "<" num_exp
+# -> num_exp ">" num_exp
+# -> num_exp "<=" num_exp
+# -> num_exp ">=" num_exp
+# -> num_exp "==" num_exp
+# -> num_exp "!=" num_exp
+
+# while_statement
+# -> "while" "(" log_exp ")" statement
+
+# return_statement
+# -> "return" num_exp ";"
+
+# compound_statement
+# -> "{" statement_list "}"
+
+# Comment: /* a comment */
+
+##########################################################################################
+##########################################################################################
+
+class Enumerate(dict):
+ """C enum emulation (original by Scott David Daniels)"""
+ def __init__(self, names):
+ for number, name in enumerate(names.split()):
+ setattr(self, name, number)
+ self[number] = name
+
+class SharedData(object):
+ """Data used in all three main classes"""
+
+ #Possible kinds of symbol table entries
+ KINDS = Enumerate("NO_KIND WORKING_REGISTER GLOBAL_VAR FUNCTION PARAMETER LOCAL_VAR CONSTANT")
+ #Supported types of functions and variables
+ TYPES = Enumerate("NO_TYPE INT UNSIGNED")
+
+ #bit size of variables
+ TYPE_BIT_SIZE = 16
+ #min/max values of constants
+ MIN_INT = -2 ** (TYPE_BIT_SIZE - 1)
+ MAX_INT = 2 ** (TYPE_BIT_SIZE - 1) - 1
+ MAX_UNSIGNED = 2 ** TYPE_BIT_SIZE - 1
+ #available working registers (the last one is the register for function's return value!)
+ REGISTERS = "%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13".split()
+ #register for function's return value
+ FUNCTION_REGISTER = len(REGISTERS) - 1
+ #the index of last working register
+ LAST_WORKING_REGISTER = len(REGISTERS) - 2
+ #list of relational operators
+ RELATIONAL_OPERATORS = "< > <= >= == !=".split()
+
+ def __init__(self):
+ #index of the currently parsed function
+ self.functon_index = 0
+ #name of the currently parsed function
+ self.functon_name = 0
+ #number of parameters of the currently parsed function
+ self.function_params = 0
+ #number of local variables of the currently parsed function
+ self.function_vars = 0
+
+##########################################################################################
+##########################################################################################
+
+class ExceptionSharedData(object):
+ """Class for exception handling data"""
+
+ def __init__(self):
+ #position in currently parsed text
+ self.location = 0
+ #currently parsed text
+ self.text = ""
+
+ def setpos(self, location, text):
+ """Helper function for setting curently parsed text and position"""
+ self.location = location
+ self.text = text
+
+exshared = ExceptionSharedData()
+
+class SemanticException(Exception):
+ """Exception for semantic errors found during parsing, similar to ParseException.
+ Introduced because ParseException is used internally in pyparsing and custom
+ messages got lost and replaced by pyparsing's generic errors.
+ """
+
+ def __init__(self, message, print_location=True):
+ super(SemanticException,self).__init__()
+ self._message = message
+ self.location = exshared.location
+ self.print_location = print_location
+ if exshared.location != None:
+ self.line = lineno(exshared.location, exshared.text)
+ self.col = col(exshared.location, exshared.text)
+ self.text = line(exshared.location, exshared.text)
+ else:
+ self.line = self.col = self.text = None
+
+ def _get_message(self):
+ return self._message
+ def _set_message(self, message):
+ self._message = message
+ message = property(_get_message, _set_message)
+
+ def __str__(self):
+ """String representation of the semantic error"""
+ msg = "Error"
+ if self.print_location and (self.line != None):
+ msg += " at line %d, col %d" % (self.line, self.col)
+ msg += ": %s" % self.message
+ if self.print_location and (self.line != None):
+ msg += "\n%s" % self.text
+ return msg
+
+##########################################################################################
+##########################################################################################
+
+class SymbolTableEntry(object):
+ """Class which represents one symbol table entry."""
+
+ def __init__(self, sname = "", skind = 0, stype = 0, sattr = None, sattr_name = "None"):
+ """Initialization of symbol table entry.
+ sname - symbol name
+ skind - symbol kind
+ stype - symbol type
+ sattr - symbol attribute
+ sattr_name - symbol attribute name (used only for table display)
+ """
+ self.name = sname
+ self.kind = skind
+ self.type = stype
+ self.attribute = sattr
+ self.attribute_name = sattr_name
+ self.param_types = []
+
+ def set_attribute(self, name, value):
+ """Sets attribute's name and value"""
+ self.attribute_name = name
+ self.attribute = value
+
+ def attribute_str(self):
+ """Returns attribute string (used only for table display)"""
+ return "{0}={1}".format(self.attribute_name, self.attribute) if self.attribute != None else "None"
+
+class SymbolTable(object):
+ """Class for symbol table of microC program"""
+
+ def __init__(self, shared):
+ """Initialization of the symbol table"""
+ self.table = []
+ self.lable_len = 0
+ #put working registers in the symbol table
+ for reg in range(SharedData.FUNCTION_REGISTER+1):
+ self.insert_symbol(SharedData.REGISTERS[reg], SharedData.KINDS.WORKING_REGISTER, SharedData.TYPES.NO_TYPE)
+ #shared data
+ self.shared = shared
+
+ def error(self, text=""):
+ """Symbol table error exception. It should happen only if index is out of range while accessing symbol table.
+ This exeption is not handled by the compiler, so as to allow traceback printing
+ """
+ if text == "":
+ raise Exception("Symbol table index out of range")
+ else:
+ raise Exception("Symbol table error: %s" % text)
+
+ def display(self):
+ """Displays the symbol table content"""
+ #Finding the maximum length for each column
+ sym_name = "Symbol name"
+ sym_len = max(max(len(i.name) for i in self.table),len(sym_name))
+ kind_name = "Kind"
+ kind_len = max(max(len(SharedData.KINDS[i.kind]) for i in self.table),len(kind_name))
+ type_name = "Type"
+ type_len = max(max(len(SharedData.TYPES[i.type]) for i in self.table),len(type_name))
+ attr_name = "Attribute"
+ attr_len = max(max(len(i.attribute_str()) for i in self.table),len(attr_name))
+ #print table header
+ print("{0:3s} | {1:^{2}s} | {3:^{4}s} | {5:^{6}s} | {7:^{8}} | {9:s}".format(" No", sym_name, sym_len, kind_name, kind_len, type_name, type_len, attr_name, attr_len, "Parameters"))
+ print("-----------------------------" + "-" * (sym_len + kind_len + type_len + attr_len))
+ #print symbol table
+ for i,sym in enumerate(self.table):
+ parameters = ""
+ for p in sym.param_types:
+ if parameters == "":
+ parameters = "{0}".format(SharedData.TYPES[p])
+ else:
+ parameters += ", {0}".format(SharedData.TYPES[p])
+ print("{0:3d} | {1:^{2}s} | {3:^{4}s} | {5:^{6}s} | {7:^{8}} | ({9})".format(i, sym.name, sym_len, SharedData.KINDS[sym.kind], kind_len, SharedData.TYPES[sym.type], type_len, sym.attribute_str(), attr_len, parameters))
+
+ def insert_symbol(self, sname, skind, stype):
+ """Inserts new symbol at the end of the symbol table.
+ Returns symbol index
+ sname - symbol name
+ skind - symbol kind
+ stype - symbol type
+ """
+ self.table.append(SymbolTableEntry(sname, skind, stype))
+ self.table_len = len(self.table)
+ return self.table_len-1
+
+ def clear_symbols(self, index):
+ """Clears all symbols begining with the index to the end of table"""
+ try:
+ del self.table[index:]
+ except Exception:
+ self.error()
+ self.table_len = len(self.table)
+
+ def lookup_symbol(self, sname, skind=list(SharedData.KINDS.keys()), stype=list(SharedData.TYPES.keys())):
+ """Searches for symbol, from the end to the begining.
+ Returns symbol index or None
+ sname - symbol name
+ skind - symbol kind (one kind, list of kinds, or None) deafult: any kind
+ stype - symbol type (or None) default: any type
+ """
+ skind = skind if isinstance(skind, list) else [skind]
+ stype = stype if isinstance(stype, list) else [stype]
+ for i, sym in [[x, self.table[x]] for x in range(len(self.table) - 1, SharedData.LAST_WORKING_REGISTER, -1)]:
+ if (sym.name == sname) and (sym.kind in skind) and (sym.type in stype):
+ return i
+ return None
+
+ def insert_id(self, sname, skind, skinds, stype):
+ """Inserts a new identifier at the end of the symbol table, if possible.
+ Returns symbol index, or raises an exception if the symbol alredy exists
+ sname - symbol name
+ skind - symbol kind
+ skinds - symbol kinds to check for
+ stype - symbol type
+ """
+ index = self.lookup_symbol(sname, skinds)
+ if index == None:
+ index = self.insert_symbol(sname, skind, stype)
+ return index
+ else:
+ raise SemanticException("Redefinition of '%s'" % sname)
+
+ def insert_global_var(self, vname, vtype):
+ "Inserts a new global variable"
+ return self.insert_id(vname, SharedData.KINDS.GLOBAL_VAR, [SharedData.KINDS.GLOBAL_VAR, SharedData.KINDS.FUNCTION], vtype)
+
+ def insert_local_var(self, vname, vtype, position):
+ "Inserts a new local variable"
+ index = self.insert_id(vname, SharedData.KINDS.LOCAL_VAR, [SharedData.KINDS.LOCAL_VAR, SharedData.KINDS.PARAMETER], vtype)
+ self.table[index].attribute = position
+
+ def insert_parameter(self, pname, ptype):
+ "Inserts a new parameter"
+ index = self.insert_id(pname, SharedData.KINDS.PARAMETER, SharedData.KINDS.PARAMETER, ptype)
+ #set parameter's attribute to it's ordinal number
+ self.table[index].set_attribute("Index", self.shared.function_params)
+ #set parameter's type in param_types list of a function
+ self.table[self.shared.function_index].param_types.append(ptype)
+ return index
+
+ def insert_function(self, fname, ftype):
+ "Inserts a new function"
+ index = self.insert_id(fname, SharedData.KINDS.FUNCTION, [SharedData.KINDS.GLOBAL_VAR, SharedData.KINDS.FUNCTION], ftype)
+ self.table[index].set_attribute("Params",0)
+ return index
+
+ def insert_constant(self, cname, ctype):
+ """Inserts a constant (or returns index if the constant already exists)
+ Additionally, checks for range.
+ """
+ index = self.lookup_symbol(cname, stype=ctype)
+ if index == None:
+ num = int(cname)
+ if ctype == SharedData.TYPES.INT:
+ if (num < SharedData.MIN_INT) or (num > SharedData.MAX_INT):
+ raise SemanticException("Integer constant '%s' out of range" % cname)
+ elif ctype == SharedData.TYPES.UNSIGNED:
+ if (num < 0) or (num > SharedData.MAX_UNSIGNED):
+ raise SemanticException("Unsigned constant '%s' out of range" % cname)
+ index = self.insert_symbol(cname, SharedData.KINDS.CONSTANT, ctype)
+ return index
+
+ def same_types(self, index1, index2):
+ """Returns True if both symbol table elements are of the same type"""
+ try:
+ same = self.table[index1].type == self.table[index2].type != SharedData.TYPES.NO_TYPE
+ except Exception:
+ self.error()
+ return same
+
+ def same_type_as_argument(self, index, function_index, argument_number):
+ """Returns True if index and function's argument are of the same type
+ index - index in symbol table
+ function_index - function's index in symbol table
+ argument_number - # of function's argument
+ """
+ try:
+ same = self.table[function_index].param_types[argument_number] == self.table[index].type
+ except Exception:
+ self.error()
+ return same
+
+ def get_attribute(self, index):
+ try:
+ return self.table[index].attribute
+ except Exception:
+ self.error()
+
+ def set_attribute(self, index, value):
+ try:
+ self.table[index].attribute = value
+ except Exception:
+ self.error()
+
+ def get_name(self, index):
+ try:
+ return self.table[index].name
+ except Exception:
+ self.error()
+
+ def get_kind(self, index):
+ try:
+ return self.table[index].kind
+ except Exception:
+ self.error()
+
+ def get_type(self, index):
+ try:
+ return self.table[index].type
+ except Exception:
+ self.error()
+
+ def set_type(self, index, stype):
+ try:
+ self.table[index].type = stype
+ except Exception:
+ self.error()
+
+##########################################################################################
+##########################################################################################
+
+class CodeGenerator(object):
+ """Class for code generation methods."""
+
+ #dictionary of relational operators
+ RELATIONAL_DICT = dict([op,i] for i, op in enumerate(SharedData.RELATIONAL_OPERATORS))
+ #conditional jumps for relational operators
+ CONDITIONAL_JUMPS = ["JLTS", "JGTS", "JLES", "JGES", "JEQ ", "JNE ",
+ "JLTU", "JGTU", "JLEU", "JGEU", "JEQ ", "JNE "]
+ #opposite conditional jumps for relational operators
+ OPPOSITE_JUMPS = ["JGES", "JLES", "JGTS", "JLTS", "JNE ", "JEQ ",
+ "JGEU", "JLEU", "JGTU", "JLTU", "JNE ", "JEQ "]
+ #supported operations
+ OPERATIONS = {"+" : "ADD", "-" : "SUB", "*" : "MUL", "/" : "DIV"}
+ #suffixes for signed and unsigned operations (if no type is specified, unsigned will be assumed)
+ OPSIGNS = {SharedData.TYPES.NO_TYPE : "U", SharedData.TYPES.INT : "S", SharedData.TYPES.UNSIGNED : "U"}
+ #text at start of data segment
+ DATA_START_TEXT = "#DATA"
+ #text at start of code segment
+ CODE_START_TEXT = "#CODE"
+
+ def __init__(self, shared, symtab):
+ #generated code
+ self.code = ""
+ #prefix for internal labels
+ self.internal = "@"
+ #suffix for label definition
+ self.definition = ":"
+ #list of free working registers
+ self.free_registers = list(range(SharedData.FUNCTION_REGISTER, -1, -1))
+ #list of used working registers
+ self.used_registers = []
+ #list of used registers needed when function call is inside of a function call
+ self.used_registers_stack = []
+ #shared data
+ self.shared = shared
+ #symbol table
+ self.symtab = symtab
+
+ def error(self, text):
+ """Compiler error exception. It should happen only if something is wrong with compiler.
+ This exeption is not handled by the compiler, so as to allow traceback printing
+ """
+ raise Exception("Compiler error: %s" % text)
+
+ def take_register(self, rtype = SharedData.TYPES.NO_TYPE):
+ """Reserves one working register and sets its type"""
+ if len(self.free_registers) == 0:
+ self.error("no more free registers")
+ reg = self.free_registers.pop()
+ self.used_registers.append(reg)
+ self.symtab.set_type(reg, rtype)
+ return reg
+
+ def take_function_register(self, rtype = SharedData.TYPES.NO_TYPE):
+ """Reserves register for function return value and sets its type"""
+ reg = SharedData.FUNCTION_REGISTER
+ if reg not in self.free_registers:
+ self.error("function register already taken")
+ self.free_registers.remove(reg)
+ self.used_registers.append(reg)
+ self.symtab.set_type(reg, rtype)
+ return reg
+
+ def free_register(self, reg):
+ """Releases working register"""
+ if reg not in self.used_registers:
+ self.error("register %s is not taken" % self.REGISTERS[reg])
+ self.used_registers.remove(reg)
+ self.free_registers.append(reg)
+ self.free_registers.sort(reverse = True)
+
+ def free_if_register(self, index):
+ """If index is a working register, free it, otherwise just return (helper function)"""
+ if (index < 0) or (index > SharedData.FUNCTION_REGISTER):
+ return
+ else:
+ self.free_register(index)
+
+ def label(self, name, internal=False, definition=False):
+ """Generates label name (helper function)
+ name - label name
+ internal - boolean value, adds "@" prefix to label
+ definition - boolean value, adds ":" suffix to label
+ """
+ return "{0}{1}{2}".format(self.internal if internal else "", name, self.definition if definition else "")
+
+ def symbol(self, index):
+ """Generates symbol name from index"""
+ #if index is actually a string, just return it
+ if isinstance(index, str):
+ return index
+ elif (index < 0) or (index >= self.symtab.table_len):
+ self.error("symbol table index out of range")
+ sym = self.symtab.table[index]
+ #local variables are located at negative offset from frame pointer register
+ if sym.kind == SharedData.KINDS.LOCAL_VAR:
+ return "-{0}(%14)".format(sym.attribute * 4 + 4)
+ #parameters are located at positive offset from frame pointer register
+ elif sym.kind == SharedData.KINDS.PARAMETER:
+ return "{0}(%14)".format(8 + sym.attribute * 4)
+ elif sym.kind == SharedData.KINDS.CONSTANT:
+ return "${0}".format(sym.name)
+ else:
+ return "{0}".format(sym.name)
+
+ def save_used_registers(self):
+ """Pushes all used working registers before function call"""
+ used = self.used_registers[:]
+ del self.used_registers[:]
+ self.used_registers_stack.append(used[:])
+ used.sort()
+ for reg in used:
+ self.newline_text("PUSH\t%s" % SharedData.REGISTERS[reg], True)
+ self.free_registers.extend(used)
+ self.free_registers.sort(reverse = True)
+
+ def restore_used_registers(self):
+ """Pops all used working registers after function call"""
+ used = self.used_registers_stack.pop()
+ self.used_registers = used[:]
+ used.sort(reverse = True)
+ for reg in used:
+ self.newline_text("POP \t%s" % SharedData.REGISTERS[reg], True)
+ self.free_registers.remove(reg)
+
+ def text(self, text):
+ """Inserts text into generated code"""
+ self.code += text
+
+ def prepare_data_segment(self):
+ """Inserts text at the start of data segment"""
+ self.text(self.DATA_START_TEXT)
+
+ def prepare_code_segment(self):
+ """Inserts text at the start of code segment"""
+ self.newline_text(self.CODE_START_TEXT)
+
+ def newline(self, indent=False):
+ """Inserts a newline, optionally with indentation."""
+ self.text("\n")
+ if indent:
+ self.text("\t\t\t")
+
+ def newline_text(self, text, indent = False):
+ """Inserts a newline and text, optionally with indentation (helper function)"""
+ self.newline(indent)
+ self.text(text)
+
+ def newline_label(self, name, internal=False, definition=False):
+ """Inserts a newline and a label (helper function)
+ name - label name
+ internal - boolean value, adds "@" prefix to label
+ definition - boolean value, adds ":" suffix to label
+ """
+ self.newline_text(self.label("{0}{1}{2}".format("@" if internal else "", name, ":" if definition else "")))
+
+ def global_var(self, name):
+ """Inserts a new static (global) variable definition"""
+ self.newline_label(name, False, True)
+ self.newline_text("WORD\t1", True)
+
+ def arithmetic_mnemonic(self, op_name, op_type):
+ """Generates an arithmetic instruction mnemonic"""
+ return self.OPERATIONS[op_name] + self.OPSIGNS[op_type]
+
+ def arithmetic(self, operation, operand1, operand2, operand3 = None):
+ """Generates an arithmetic instruction
+ operation - one of supporetd operations
+ operandX - index in symbol table or text representation of operand
+ First two operands are input, third one is output
+ """
+ if isinstance(operand1, int):
+ output_type = self.symtab.get_type(operand1)
+ self.free_if_register(operand1)
+ else:
+ output_type = None
+ if isinstance(operand2, int):
+ output_type = self.symtab.get_type(operand2) if output_type == None else output_type
+ self.free_if_register(operand2)
+ else:
+ output_type = SharedData.TYPES.NO_TYPE if output_type == None else output_type
+ #if operand3 is not defined, reserve one free register for it
+ output = self.take_register(output_type) if operand3 == None else operand3
+ mnemonic = self.arithmetic_mnemonic(operation, output_type)
+ self.newline_text("{0}\t{1},{2},{3}".format(mnemonic, self.symbol(operand1), self.symbol(operand2), self.symbol(output)), True)
+ return output
+
+ def relop_code(self, relop, operands_type):
+ """Returns code for relational operator
+ relop - relational operator
+ operands_type - int or unsigned
+ """
+ code = self.RELATIONAL_DICT[relop]
+ offset = 0 if operands_type == SharedData.TYPES.INT else len(SharedData.RELATIONAL_OPERATORS)
+ return code + offset
+
+ def jump(self, relcode, opposite, label):
+ """Generates a jump instruction
+ relcode - relational operator code
+ opposite - generate normal or opposite jump
+ label - jump label
+ """
+ jump = self.OPPOSITE_JUMPS[relcode] if opposite else self.CONDITIONAL_JUMPS[relcode]
+ self.newline_text("{0}\t{1}".format(jump, label), True)
+
+ def unconditional_jump(self, label):
+ """Generates an unconditional jump instruction
+ label - jump label
+ """
+ self.newline_text("JMP \t{0}".format(label), True)
+
+ def move(self,operand1, operand2):
+ """Generates a move instruction
+ If the output operand (opernad2) is a working register, sets it's type
+ operandX - index in symbol table or text representation of operand
+ """
+ if isinstance(operand1, int):
+ output_type = self.symtab.get_type(operand1)
+ self.free_if_register(operand1)
+ else:
+ output_type = SharedData.TYPES.NO_TYPE
+ self.newline_text("MOV \t{0},{1}".format(self.symbol(operand1), self.symbol(operand2)), True)
+ if isinstance(operand2, int):
+ if self.symtab.get_kind(operand2) == SharedData.KINDS.WORKING_REGISTER:
+ self.symtab.set_type(operand2, output_type)
+
+ def push(self, operand):
+ """Generates a push operation"""
+ self.newline_text("PUSH\t%s" % self.symbol(operand), True)
+
+ def pop(self, operand):
+ """Generates a pop instruction"""
+ self.newline_text("POP \t%s" % self.symbol(operand), True)
+
+ def compare(self, operand1, operand2):
+ """Generates a compare instruction
+ operandX - index in symbol table
+ """
+ typ = self.symtab.get_type(operand1)
+ self.free_if_register(operand1)
+ self.free_if_register(operand2)
+ self.newline_text("CMP{0}\t{1},{2}".format(self.OPSIGNS[typ], self.symbol(operand1), self.symbol(operand2)), True)
+
+ def function_begin(self):
+ """Inserts function name label and function frame initialization"""
+ self.newline_label(self.shared.function_name, False, True)
+ self.push("%14")
+ self.move("%15", "%14")
+
+ def function_body(self):
+ """Inserts a local variable initialization and body label"""
+ if self.shared.function_vars > 0:
+ const = self.symtab.insert_constant("{0}".format(self.shared.function_vars * 4), SharedData.TYPES.UNSIGNED)
+ self.arithmetic("-", "%15", const, "%15")
+ self.newline_label(self.shared.function_name + "_body", True, True)
+
+ def function_end(self):
+ """Inserts an exit label and function return instructions"""
+ self.newline_label(self.shared.function_name + "_exit", True, True)
+ self.move("%14", "%15")
+ self.pop("%14")
+ self.newline_text("RET", True)
+
+ def function_call(self, function, arguments):
+ """Generates code for a function call
+ function - function index in symbol table
+ arguments - list of arguments (indexes in symbol table)
+ """
+ #push each argument to stack
+ for arg in arguments:
+ self.push(self.symbol(arg))
+ self.free_if_register(arg)
+ self.newline_text("CALL\t"+self.symtab.get_name(function), True)
+ args = self.symtab.get_attribute(function)
+ #generates stack cleanup if function has arguments
+ if args > 0:
+ args_space = self.symtab.insert_constant("{0}".format(args * 4), SharedData.TYPES.UNSIGNED)
+ self.arithmetic("+", "%15", args_space, "%15")
+
+##########################################################################################
+##########################################################################################
+
+class MicroC(object):
+ """Class for microC parser/compiler"""
+
+ def __init__(self):
+ #Definitions of terminal symbols for microC programming language
+ self.tId = Word(alphas+"_",alphanums+"_")
+ self.tInteger = Word(nums).setParseAction(lambda x : [x[0], SharedData.TYPES.INT])
+ self.tUnsigned = Regex(r"[0-9]+[uU]").setParseAction(lambda x : [x[0][:-1], SharedData.TYPES.UNSIGNED])
+ self.tConstant = (self.tUnsigned | self.tInteger).setParseAction(self.constant_action)
+ self.tType = Keyword("int").setParseAction(lambda x : SharedData.TYPES.INT) | \
+ Keyword("unsigned").setParseAction(lambda x : SharedData.TYPES.UNSIGNED)
+ self.tRelOp = oneOf(SharedData.RELATIONAL_OPERATORS)
+ self.tMulOp = oneOf("* /")
+ self.tAddOp = oneOf("+ -")
+
+ #Definitions of rules for global variables
+ self.rGlobalVariable = (self.tType("type") + self.tId("name") +
+ FollowedBy(";")).setParseAction(self.global_variable_action)
+ self.rGlobalVariableList = ZeroOrMore(self.rGlobalVariable + Suppress(";"))
+
+ #Definitions of rules for numeric expressions
+ self.rExp = Forward()
+ self.rMulExp = Forward()
+ self.rNumExp = Forward()
+ self.rArguments = delimitedList(self.rNumExp("exp").setParseAction(self.argument_action))
+ self.rFunctionCall = ((self.tId("name") + FollowedBy("(")).setParseAction(self.function_call_prepare_action) +
+ Suppress("(") + Optional(self.rArguments)("args") + Suppress(")")).setParseAction(self.function_call_action)
+ self.rExp << (self.rFunctionCall |
+ self.tConstant |
+ self.tId("name").setParseAction(self.lookup_id_action) |
+ Group(Suppress("(") + self.rNumExp + Suppress(")")) |
+ Group("+" + self.rExp) |
+ Group("-" + self.rExp)).setParseAction(lambda x : x[0])
+ self.rMulExp << ((self.rExp + ZeroOrMore(self.tMulOp + self.rExp))).setParseAction(self.mulexp_action)
+ self.rNumExp << (self.rMulExp + ZeroOrMore(self.tAddOp + self.rMulExp)).setParseAction(self.numexp_action)
+
+ #Definitions of rules for logical expressions (these are without parenthesis support)
+ self.rAndExp = Forward()
+ self.rLogExp = Forward()
+ self.rRelExp = (self.rNumExp + self.tRelOp + self.rNumExp).setParseAction(self.relexp_action)
+ self.rAndExp << (self.rRelExp("exp") + ZeroOrMore(Literal("&&").setParseAction(self.andexp_action) +
+ self.rRelExp("exp")).setParseAction(lambda x : self.relexp_code))
+ self.rLogExp << (self.rAndExp("exp") + ZeroOrMore(Literal("||").setParseAction(self.logexp_action) +
+ self.rAndExp("exp")).setParseAction(lambda x : self.andexp_code))
+
+ #Definitions of rules for statements
+ self.rStatement = Forward()
+ self.rStatementList = Forward()
+ self.rReturnStatement = (Keyword("return") + self.rNumExp("exp") +
+ Suppress(";")).setParseAction(self.return_action)
+ self.rAssignmentStatement = (self.tId("var") + Suppress("=") + self.rNumExp("exp") +
+ Suppress(";")).setParseAction(self.assignment_action)
+ self.rFunctionCallStatement = self.rFunctionCall + Suppress(";")
+ self.rIfStatement = ( (Keyword("if") + FollowedBy("(")).setParseAction(self.if_begin_action) +
+ (Suppress("(") + self.rLogExp + Suppress(")")).setParseAction(self.if_body_action) +
+ (self.rStatement + Empty()).setParseAction(self.if_else_action) +
+ Optional(Keyword("else") + self.rStatement)).setParseAction(self.if_end_action)
+ self.rWhileStatement = ( (Keyword("while") + FollowedBy("(")).setParseAction(self.while_begin_action) +
+ (Suppress("(") + self.rLogExp + Suppress(")")).setParseAction(self.while_body_action) +
+ self.rStatement).setParseAction(self.while_end_action)
+ self.rCompoundStatement = Group(Suppress("{") + self.rStatementList + Suppress("}"))
+ self.rStatement << (self.rReturnStatement | self.rIfStatement | self.rWhileStatement |
+ self.rFunctionCallStatement | self.rAssignmentStatement | self.rCompoundStatement)
+ self.rStatementList << ZeroOrMore(self.rStatement)
+
+ self.rLocalVariable = (self.tType("type") + self.tId("name") + FollowedBy(";")).setParseAction(self.local_variable_action)
+ self.rLocalVariableList = ZeroOrMore(self.rLocalVariable + Suppress(";"))
+ self.rFunctionBody = Suppress("{") + Optional(self.rLocalVariableList).setParseAction(self.function_body_action) + \
+ self.rStatementList + Suppress("}")
+ self.rParameter = (self.tType("type") + self.tId("name")).setParseAction(self.parameter_action)
+ self.rParameterList = delimitedList(self.rParameter)
+ self.rFunction = ( (self.tType("type") + self.tId("name")).setParseAction(self.function_begin_action) +
+ Group(Suppress("(") + Optional(self.rParameterList)("params") + Suppress(")") +
+ self.rFunctionBody)).setParseAction(self.function_end_action)
+
+ self.rFunctionList = OneOrMore(self.rFunction)
+ self.rProgram = (Empty().setParseAction(self.data_begin_action) + self.rGlobalVariableList +
+ Empty().setParseAction(self.code_begin_action) + self.rFunctionList).setParseAction(self.program_end_action)
+
+ #shared data
+ self.shared = SharedData()
+ #symbol table
+ self.symtab = SymbolTable(self.shared)
+ #code generator
+ self.codegen = CodeGenerator(self.shared, self.symtab)
+
+ #index of the current function call
+ self.function_call_index = -1
+ #stack for the nested function calls
+ self.function_call_stack = []
+ #arguments of the current function call
+ self.function_arguments = []
+ #stack for arguments of the nested function calls
+ self.function_arguments_stack = []
+ #number of arguments for the curent function call
+ self.function_arguments_number = -1
+ #stack for the number of arguments for the nested function calls
+ self.function_arguments_number_stack = []
+
+ #last relational expression
+ self.relexp_code = None
+ #last and expression
+ self.andexp_code = None
+ #label number for "false" internal labels
+ self.false_label_number = -1
+ #label number for all other internal labels
+ self.label_number = None
+ #label stack for nested statements
+ self.label_stack = []
+
+ def warning(self, message, print_location=True):
+ """Displays warning message. Uses exshared for current location of parsing"""
+ msg = "Warning"
+ if print_location and (exshared.location != None):
+ wline = lineno(exshared.location, exshared.text)
+ wcol = col(exshared.location, exshared.text)
+ wtext = line(exshared.location, exshared.text)
+ msg += " at line %d, col %d" % (wline, wcol)
+ msg += ": %s" % message
+ if print_location and (exshared.location != None):
+ msg += "\n%s" % wtext
+ print(msg)
+
+
+ def data_begin_action(self):
+ """Inserts text at start of data segment"""
+ self.codegen.prepare_data_segment()
+
+ def code_begin_action(self):
+ """Inserts text at start of code segment"""
+ self.codegen.prepare_code_segment()
+
+ def global_variable_action(self, text, loc, var):
+ """Code executed after recognising a global variable"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("GLOBAL_VAR:",var)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ index = self.symtab.insert_global_var(var.name, var.type)
+ self.codegen.global_var(var.name)
+ return index
+
+ def local_variable_action(self, text, loc, var):
+ """Code executed after recognising a local variable"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("LOCAL_VAR:",var, var.name, var.type)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ index = self.symtab.insert_local_var(var.name, var.type, self.shared.function_vars)
+ self.shared.function_vars += 1
+ return index
+
+ def parameter_action(self, text, loc, par):
+ """Code executed after recognising a parameter"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("PARAM:",par)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ index = self.symtab.insert_parameter(par.name, par.type)
+ self.shared.function_params += 1
+ return index
+
+ def constant_action(self, text, loc, const):
+ """Code executed after recognising a constant"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("CONST:",const)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ return self.symtab.insert_constant(const[0], const[1])
+
+ def function_begin_action(self, text, loc, fun):
+ """Code executed after recognising a function definition (type and function name)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("FUN_BEGIN:",fun)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ self.shared.function_index = self.symtab.insert_function(fun.name, fun.type)
+ self.shared.function_name = fun.name
+ self.shared.function_params = 0
+ self.shared.function_vars = 0
+ self.codegen.function_begin();
+
+ def function_body_action(self, text, loc, fun):
+ """Code executed after recognising the beginning of function's body"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("FUN_BODY:",fun)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ self.codegen.function_body()
+
+ def function_end_action(self, text, loc, fun):
+ """Code executed at the end of function definition"""
+ if DEBUG > 0:
+ print("FUN_END:",fun)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #set function's attribute to number of function parameters
+ self.symtab.set_attribute(self.shared.function_index, self.shared.function_params)
+ #clear local function symbols (but leave function name)
+ self.symtab.clear_symbols(self.shared.function_index + 1)
+ self.codegen.function_end()
+
+ def return_action(self, text, loc, ret):
+ """Code executed after recognising a return statement"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("RETURN:",ret)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ if not self.symtab.same_types(self.shared.function_index, ret.exp[0]):
+ raise SemanticException("Incompatible type in return")
+ #set register for function's return value to expression value
+ reg = self.codegen.take_function_register()
+ self.codegen.move(ret.exp[0], reg)
+ #after return statement, register for function's return value is available again
+ self.codegen.free_register(reg)
+ #jump to function's exit
+ self.codegen.unconditional_jump(self.codegen.label(self.shared.function_name+"_exit", True))
+
+ def lookup_id_action(self, text, loc, var):
+ """Code executed after recognising an identificator in expression"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("EXP_VAR:",var)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ var_index = self.symtab.lookup_symbol(var.name, [SharedData.KINDS.GLOBAL_VAR, SharedData.KINDS.PARAMETER, SharedData.KINDS.LOCAL_VAR])
+ if var_index == None:
+ raise SemanticException("'%s' undefined" % var.name)
+ return var_index
+
+ def assignment_action(self, text, loc, assign):
+ """Code executed after recognising an assignment statement"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("ASSIGN:",assign)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ var_index = self.symtab.lookup_symbol(assign.var, [SharedData.KINDS.GLOBAL_VAR, SharedData.KINDS.PARAMETER, SharedData.KINDS.LOCAL_VAR])
+ if var_index == None:
+ raise SemanticException("Undefined lvalue '%s' in assignment" % assign.var)
+ if not self.symtab.same_types(var_index, assign.exp[0]):
+ raise SemanticException("Incompatible types in assignment")
+ self.codegen.move(assign.exp[0], var_index)
+
+ def mulexp_action(self, text, loc, mul):
+ """Code executed after recognising a mulexp expression (something *|/ something)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("MUL_EXP:",mul)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #iterate through all multiplications/divisions
+ m = list(mul)
+ while len(m) > 1:
+ if not self.symtab.same_types(m[0], m[2]):
+ raise SemanticException("Invalid opernads to binary '%s'" % m[1])
+ reg = self.codegen.arithmetic(m[1], m[0], m[2])
+ #replace first calculation with it's result
+ m[0:3] = [reg]
+ return m[0]
+
+ def numexp_action(self, text, loc, num):
+ """Code executed after recognising a numexp expression (something +|- something)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("NUM_EXP:",num)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #iterate through all additions/substractions
+ n = list(num)
+ while len(n) > 1:
+ if not self.symtab.same_types(n[0], n[2]):
+ raise SemanticException("Invalid opernads to binary '%s'" % n[1])
+ reg = self.codegen.arithmetic(n[1], n[0], n[2])
+ #replace first calculation with it's result
+ n[0:3] = [reg]
+ return n[0]
+
+ def function_call_prepare_action(self, text, loc, fun):
+ """Code executed after recognising a function call (type and function name)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("FUN_PREP:",fun)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ index = self.symtab.lookup_symbol(fun.name, SharedData.KINDS.FUNCTION)
+ if index == None:
+ raise SemanticException("'%s' is not a function" % fun.name)
+ #save any previous function call data (for nested function calls)
+ self.function_call_stack.append(self.function_call_index)
+ self.function_call_index = index
+ self.function_arguments_stack.append(self.function_arguments[:])
+ del self.function_arguments[:]
+ self.codegen.save_used_registers()
+
+ def argument_action(self, text, loc, arg):
+ """Code executed after recognising each of function's arguments"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("ARGUMENT:",arg.exp)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ arg_ordinal = len(self.function_arguments)
+ #check argument's type
+ if not self.symtab.same_type_as_argument(arg.exp, self.function_call_index, arg_ordinal):
+ raise SemanticException("Incompatible type for argument %d in '%s'" % (arg_ordinal + 1, self.symtab.get_name(self.function_call_index)))
+ self.function_arguments.append(arg.exp)
+
+ def function_call_action(self, text, loc, fun):
+ """Code executed after recognising the whole function call"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("FUN_CALL:",fun)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #check number of arguments
+ if len(self.function_arguments) != self.symtab.get_attribute(self.function_call_index):
+ raise SemanticException("Wrong number of arguments for function '%s'" % fun.name)
+ #arguments should be pushed to stack in reverse order
+ self.function_arguments.reverse()
+ self.codegen.function_call(self.function_call_index, self.function_arguments)
+ self.codegen.restore_used_registers()
+ return_type = self.symtab.get_type(self.function_call_index)
+ #restore previous function call data
+ self.function_call_index = self.function_call_stack.pop()
+ self.function_arguments = self.function_arguments_stack.pop()
+ register = self.codegen.take_register(return_type)
+ #move result to a new free register, to allow the next function call
+ self.codegen.move(self.codegen.take_function_register(return_type), register)
+ return register
+
+ def relexp_action(self, text, loc, arg):
+ """Code executed after recognising a relexp expression (something relop something)"""
+ if DEBUG > 0:
+ print("REL_EXP:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ exshared.setpos(loc, text)
+ if not self.symtab.same_types(arg[0], arg[2]):
+ raise SemanticException("Invalid operands for operator '{0}'".format(arg[1]))
+ self.codegen.compare(arg[0], arg[2])
+ #return relational operator's code
+ self.relexp_code = self.codegen.relop_code(arg[1], self.symtab.get_type(arg[0]))
+ return self.relexp_code
+
+ def andexp_action(self, text, loc, arg):
+ """Code executed after recognising a andexp expression (something and something)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("AND+EXP:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ label = self.codegen.label("false{0}".format(self.false_label_number), True, False)
+ self.codegen.jump(self.relexp_code, True, label)
+ self.andexp_code = self.relexp_code
+ return self.andexp_code
+
+ def logexp_action(self, text, loc, arg):
+ """Code executed after recognising logexp expression (something or something)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("LOG_EXP:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ label = self.codegen.label("true{0}".format(self.label_number), True, False)
+ self.codegen.jump(self.relexp_code, False, label)
+ self.codegen.newline_label("false{0}".format(self.false_label_number), True, True)
+ self.false_label_number += 1
+
+ def if_begin_action(self, text, loc, arg):
+ """Code executed after recognising an if statement (if keyword)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("IF_BEGIN:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ self.false_label_number += 1
+ self.label_number = self.false_label_number
+ self.codegen.newline_label("if{0}".format(self.label_number), True, True)
+
+ def if_body_action(self, text, loc, arg):
+ """Code executed after recognising if statement's body"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("IF_BODY:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #generate conditional jump (based on last compare)
+ label = self.codegen.label("false{0}".format(self.false_label_number), True, False)
+ self.codegen.jump(self.relexp_code, True, label)
+ #generate 'true' label (executes if condition is satisfied)
+ self.codegen.newline_label("true{0}".format(self.label_number), True, True)
+ #save label numbers (needed for nested if/while statements)
+ self.label_stack.append(self.false_label_number)
+ self.label_stack.append(self.label_number)
+
+ def if_else_action(self, text, loc, arg):
+ """Code executed after recognising if statement's else body"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("IF_ELSE:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #jump to exit after all statements for true condition are executed
+ self.label_number = self.label_stack.pop()
+ label = self.codegen.label("exit{0}".format(self.label_number), True, False)
+ self.codegen.unconditional_jump(label)
+ #generate final 'false' label (executes if condition isn't satisfied)
+ self.codegen.newline_label("false{0}".format(self.label_stack.pop()), True, True)
+ self.label_stack.append(self.label_number)
+
+ def if_end_action(self, text, loc, arg):
+ """Code executed after recognising a whole if statement"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("IF_END:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ self.codegen.newline_label("exit{0}".format(self.label_stack.pop()), True, True)
+
+ def while_begin_action(self, text, loc, arg):
+ """Code executed after recognising a while statement (while keyword)"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("WHILE_BEGIN:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ self.false_label_number += 1
+ self.label_number = self.false_label_number
+ self.codegen.newline_label("while{0}".format(self.label_number), True, True)
+
+ def while_body_action(self, text, loc, arg):
+ """Code executed after recognising while statement's body"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("WHILE_BODY:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #generate conditional jump (based on last compare)
+ label = self.codegen.label("false{0}".format(self.false_label_number), True, False)
+ self.codegen.jump(self.relexp_code, True, label)
+ #generate 'true' label (executes if condition is satisfied)
+ self.codegen.newline_label("true{0}".format(self.label_number), True, True)
+ self.label_stack.append(self.false_label_number)
+ self.label_stack.append(self.label_number)
+
+ def while_end_action(self, text, loc, arg):
+ """Code executed after recognising a whole while statement"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("WHILE_END:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ #jump to condition checking after while statement body
+ self.label_number = self.label_stack.pop()
+ label = self.codegen.label("while{0}".format(self.label_number), True, False)
+ self.codegen.unconditional_jump(label)
+ #generate final 'false' label and exit label
+ self.codegen.newline_label("false{0}".format(self.label_stack.pop()), True, True)
+ self.codegen.newline_label("exit{0}".format(self.label_number), True, True)
+
+ def program_end_action(self, text, loc, arg):
+ """Checks if there is a 'main' function and the type of 'main' function"""
+ exshared.setpos(loc, text)
+ if DEBUG > 0:
+ print("PROGRAM_END:",arg)
+ if DEBUG == 2: self.symtab.display()
+ if DEBUG > 2: return
+ index = self.symtab.lookup_symbol("main",SharedData.KINDS.FUNCTION)
+ if index == None:
+ raise SemanticException("Undefined reference to 'main'", False)
+ elif self.symtab.get_type(index) != SharedData.TYPES.INT:
+ self.warning("Return type of 'main' is not int", False)
+
+ def parse_text(self,text):
+ """Parse string (helper function)"""
+ try:
+ return self.rProgram.ignore(cStyleComment).parseString(text, parseAll=True)
+ except SemanticException as err:
+ print(err)
+ exit(3)
+ except ParseException as err:
+ print(err)
+ exit(3)
+
+ def parse_file(self,filename):
+ """Parse file (helper function)"""
+ try:
+ return self.rProgram.ignore(cStyleComment).parseFile(filename, parseAll=True)
+ except SemanticException as err:
+ print(err)
+ exit(3)
+ except ParseException as err:
+ print(err)
+ exit(3)
+
+##########################################################################################
+##########################################################################################
+if 0:
+ #main program
+ mc = MicroC()
+ output_file = "output.asm"
+
+ if len(argv) == 1:
+ input_file = stdin
+ elif len(argv) == 2:
+ input_file = argv[1]
+ elif len(argv) == 3:
+ input_file = argv[1]
+ output_file = argv[2]
+ else:
+ usage = """Usage: {0} [input_file [output_file]]
+ If output file is omitted, output.asm is used
+ If input file is omitted, stdin is used""".format(argv[0])
+ print(usage)
+ exit(1)
+ try:
+ parse = stdin if input_file == stdin else open(input_file,'r')
+ except Exception:
+ print("Input file '%s' open error" % input_file)
+ exit(2)
+ mc.parse_file(parse)
+ #if you want to see the final symbol table, uncomment next line
+ #mc.symtab.display()
+ try:
+ out = open(output_file, 'w')
+ out.write(mc.codegen.code)
+ out.close
+ except Exception:
+ print("Output file '%s' open error" % output_file)
+ exit(2)
+
+##########################################################################################
+##########################################################################################
+
+if __name__ == "__main__":
+
+ test_program_example = """
+ int a;
+ int b;
+ int c;
+ unsigned d;
+
+ int fun1(int x, unsigned y) {
+ return 123;
+ }
+
+ int fun2(int a) {
+ return 1 + a * fun1(a, 456u);
+ }
+
+ int main(int x, int y) {
+ int w;
+ unsigned z;
+ if (9 > 8 && 2 < 3 || 6 != 5 && a <= b && c < x || w >= y) {
+ a = b + 1;
+ if (x == y)
+ while (d < 4u)
+ x = x * w;
+ else
+ while (a + b < c - y && x > 3 || y < 2)
+ if (z > d)
+ a = a - 4;
+ else
+ b = a * b * c * x / y;
+ }
+ else
+ c = 4;
+ a = fun1(x,d) + fun2(fun1(fun2(w + 3 * 2) + 2 * c, 2u));
+ return 2;
+ }
+ """
+
+ mc = MicroC()
+ mc.parse_text(test_program_example)
+ print(mc.codegen.code) \ No newline at end of file
diff --git a/trunk/src/examples/pythonGrammarParser.py b/trunk/src/examples/pythonGrammarParser.py
new file mode 100644
index 0000000..f0631b8
--- /dev/null
+++ b/trunk/src/examples/pythonGrammarParser.py
@@ -0,0 +1,220 @@
+# pythonGrammarParser.py
+#
+# Copyright, 2006, by Paul McGuire
+#
+
+from pyparsing import *
+
+# should probably read this from the Grammar file provided with the Python source, but
+# this just skips that step and inlines the bnf text directly - this grammar was taken from
+# Python 2.4.1
+#
+grammar = """
+# Grammar for Python
+
+# Note: Changing the grammar specified in this file will most likely
+# require corresponding changes in the parser module
+# (../Modules/parsermodule.c). If you can't make the changes to
+# that module yourself, please co-ordinate the required changes
+# with someone who can; ask around on python-dev for help. Fred
+# Drake <fdrake@acm.org> will probably be listening there.
+
+# Commands for Kees Blom's railroad program
+#diagram:token NAME
+#diagram:token NUMBER
+#diagram:token STRING
+#diagram:token NEWLINE
+#diagram:token ENDMARKER
+#diagram:token INDENT
+#diagram:output\input python.bla
+#diagram:token DEDENT
+#diagram:output\textwidth 20.04cm\oddsidemargin 0.0cm\evensidemargin 0.0cm
+#diagram:rules
+
+# Start symbols for the grammar:
+# single_input is a single interactive statement;
+# file_input is a module or sequence of commands read from an input file;
+# eval_input is the input for the eval() and input() functions.
+# NB: compound_stmt in single_input is followed by extra NEWLINE!
+single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
+file_input: (NEWLINE | stmt)* ENDMARKER
+eval_input: testlist NEWLINE* ENDMARKER
+
+decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
+decorators: decorator+
+funcdef: [decorators] 'def' NAME parameters ':' suite
+parameters: '(' [varargslist] ')'
+varargslist: (fpdef ['=' test] ',')* ('*' NAME [',' '**' NAME] | '**' NAME) | fpdef ['=' test] (',' fpdef ['=' test])* [',']
+fpdef: NAME | '(' fplist ')'
+fplist: fpdef (',' fpdef)* [',']
+
+stmt: simple_stmt | compound_stmt
+simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
+small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | exec_stmt | assert_stmt
+expr_stmt: testlist (augassign testlist | ('=' testlist)*)
+augassign: '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//='
+# For normal assignments, additional restrictions enforced by the interpreter
+print_stmt: 'print' ( [ test (',' test)* [','] ] | '>>' test [ (',' test)+ [','] ] )
+del_stmt: 'del' exprlist
+pass_stmt: 'pass'
+flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
+break_stmt: 'break'
+continue_stmt: 'continue'
+return_stmt: 'return' [testlist]
+yield_stmt: 'yield' testlist
+raise_stmt: 'raise' [test [',' test [',' test]]]
+import_stmt: import_name | import_from
+import_name: 'import' dotted_as_names
+import_from: 'from' dotted_name 'import' ('*' | '(' import_as_names ')' | import_as_names)
+import_as_name: NAME [NAME NAME]
+dotted_as_name: dotted_name [NAME NAME]
+import_as_names: import_as_name (',' import_as_name)* [',']
+dotted_as_names: dotted_as_name (',' dotted_as_name)*
+dotted_name: NAME ('.' NAME)*
+global_stmt: 'global' NAME (',' NAME)*
+exec_stmt: 'exec' expr ['in' test [',' test]]
+assert_stmt: 'assert' test [',' test]
+#35
+compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef
+if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
+while_stmt: 'while' test ':' suite ['else' ':' suite]
+for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+try_stmt: ('try' ':' suite (except_clause ':' suite)+ #diagram:break
+ ['else' ':' suite] | 'try' ':' suite 'finally' ':' suite)
+# NB compile.c makes sure that the default except clause is last
+except_clause: 'except' [test [',' test]]
+suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
+
+test: and_test ('or' and_test)* | lambdef
+and_test: not_test ('and' not_test)*
+not_test: 'not' not_test | comparison
+comparison: expr (comp_op expr)*
+comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
+expr: xor_expr ('|' xor_expr)*
+xor_expr: and_expr ('^' and_expr)*
+and_expr: shift_expr ('&' shift_expr)*
+shift_expr: arith_expr (('<<'|'>>') arith_expr)*
+arith_expr: term (('+'|'-') term)*
+term: factor (('*'|'/'|'%'|'//') factor)*
+factor: ('+'|'-'|'~') factor | power
+power: atom trailer* ['**' factor]
+atom: '(' [testlist_gexp] ')' | '[' [listmaker] ']' | '{' [dictmaker] '}' | '`' testlist1 '`' | NAME | NUMBER | STRING+
+listmaker: test ( list_for | (',' test)* [','] )
+testlist_gexp: test ( gen_for | (',' test)* [','] )
+lambdef: 'lambda' [varargslist] ':' test
+trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
+subscriptlist: subscript (',' subscript)* [',']
+subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]
+sliceop: ':' [test]
+exprlist: expr (',' expr)* [',']
+testlist: test (',' test)* [',']
+testlist_safe: test [(',' test)+ [',']]
+dictmaker: test ':' test (',' test ':' test)* [',']
+
+classdef: 'class' NAME ['(' testlist ')'] ':' suite
+
+arglist: (argument ',')* (argument [',']| '*' test [',' '**' test] | '**' test)
+argument: [test '='] test [gen_for] # Really [keyword '='] test
+
+list_iter: list_for | list_if
+list_for: 'for' exprlist 'in' testlist_safe [list_iter]
+list_if: 'if' test [list_iter]
+
+gen_iter: gen_for | gen_if
+gen_for: 'for' exprlist 'in' test [gen_iter]
+gen_if: 'if' test [gen_iter]
+
+testlist1: test (',' test)*
+
+# not used in grammar, but may appear in "node" passed from Parser to Compiler
+encoding_decl: NAME
+"""
+
+class SemanticGroup(object):
+ def __init__(self,contents):
+ self.contents = contents
+ while self.contents[-1].__class__ == self.__class__:
+ self.contents = self.contents[:-1] + self.contents[-1].contents
+
+ def __str__(self):
+ return "%s(%s)" % (self.label,
+ " ".join([isinstance(c,str) and c or str(c) for c in self.contents]) )
+
+class OrList(SemanticGroup):
+ label = "OR"
+ pass
+
+class AndList(SemanticGroup):
+ label = "AND"
+ pass
+
+class OptionalGroup(SemanticGroup):
+ label = "OPT"
+ pass
+
+class Atom(SemanticGroup):
+ def __init__(self,contents):
+ if len(contents) > 1:
+ self.rep = contents[1]
+ else:
+ self.rep = ""
+ if isinstance(contents,str):
+ self.contents = contents
+ else:
+ self.contents = contents[0]
+
+ def __str__(self):
+ return "%s%s" % (self.rep, self.contents)
+
+def makeGroupObject(cls):
+ def groupAction(s,l,t):
+ try:
+ return cls(t[0].asList())
+ except:
+ return cls(t)
+ return groupAction
+
+
+# bnf punctuation
+LPAREN = Suppress("(")
+RPAREN = Suppress(")")
+LBRACK = Suppress("[")
+RBRACK = Suppress("]")
+COLON = Suppress(":")
+ALT_OP = Suppress("|")
+
+# bnf grammar
+ident = Word(alphanums+"_")
+bnfToken = Word(alphanums+"_") + ~FollowedBy(":")
+repSymbol = oneOf("* +")
+bnfExpr = Forward()
+optionalTerm = Group(LBRACK + bnfExpr + RBRACK).setParseAction(makeGroupObject(OptionalGroup))
+bnfTerm = ( (bnfToken | quotedString | optionalTerm | ( LPAREN + bnfExpr + RPAREN )) + Optional(repSymbol) ).setParseAction(makeGroupObject(Atom))
+andList = Group(bnfTerm + OneOrMore(bnfTerm)).setParseAction(makeGroupObject(AndList))
+bnfFactor = andList | bnfTerm
+orList = Group( bnfFactor + OneOrMore( ALT_OP + bnfFactor ) ).setParseAction(makeGroupObject(OrList))
+bnfExpr << ( orList | bnfFactor )
+bnfLine = ident + COLON + bnfExpr
+
+bnfComment = "#" + restOfLine
+
+# build return tokens as a dictionary
+bnf = Dict(OneOrMore(Group(bnfLine)))
+bnf.ignore(bnfComment)
+
+# bnf is defined, parse the grammar text
+bnfDefs = bnf.parseString(grammar)
+
+# correct answer is 78
+expected = 78
+assert len(bnfDefs) == expected, \
+ "Error, found %d BNF defns, expected %d" % (len(bnfDefs), expected)
+
+# list out defns in order they were parsed (to verify accuracy of parsing)
+for k,v in bnfDefs:
+ print(k,"=",v)
+print()
+
+# list out parsed grammar defns (demonstrates dictionary access to parsed tokens)
+for k in list(bnfDefs.keys()):
+ print(k,"=",bnfDefs[k])
diff --git a/trunk/src/examples/rangeCheck.py b/trunk/src/examples/rangeCheck.py
new file mode 100644
index 0000000..67cf267
--- /dev/null
+++ b/trunk/src/examples/rangeCheck.py
@@ -0,0 +1,62 @@
+# rangeCheck.py
+#
+# A sample program showing how parse actions can convert parsed
+# strings into a data type or object, and to validate the parsed value.
+#
+# Updated to use new addCondition method and expr() copy.
+#
+# Copyright 2011,2015 Paul T. McGuire
+#
+
+from pyparsing import Word, nums, Suppress, ParseException, empty, Optional
+from datetime import datetime
+
+def ranged_value(expr, minval=None, maxval=None):
+ # have to specify at least one range boundary
+ if minval is None and maxval is None:
+ raise ValueError("minval or maxval must be specified")
+
+ # set range testing function and error message depending on
+ # whether either or both min and max values are given
+ inRangeCondition = {
+ (True, False) : lambda s,l,t : t[0] <= maxval,
+ (False, True) : lambda s,l,t : minval <= t[0],
+ (False, False) : lambda s,l,t : minval <= t[0] <= maxval,
+ }[minval is None, maxval is None]
+ outOfRangeMessage = {
+ (True, False) : "value is greater than %s" % maxval,
+ (False, True) : "value is less than %s" % minval,
+ (False, False) : "value is not in the range (%s to %s)" % (minval,maxval),
+ }[minval is None, maxval is None]
+
+ return expr().addCondition(inRangeCondition, message=outOfRangeMessage)
+
+# define the expressions for a date of the form YYYY/MM/DD or YYYY/MM (assumes YYYY/MM/01)
+integer = Word(nums).setName("integer")
+integer.setParseAction(lambda t:int(t[0]))
+
+month = ranged_value(integer, 1, 12)
+day = ranged_value(integer, 1, 31)
+year = ranged_value(integer, 2000, None)
+
+SLASH = Suppress('/')
+dateExpr = year("year") + SLASH + month("month") + Optional(SLASH + day("day"))
+dateExpr.setName("date")
+
+# convert date fields to datetime (also validates dates as truly valid dates)
+dateExpr.setParseAction(lambda t: datetime(t.year, t.month, t.day or 1).date())
+
+# add range checking on dates
+mindate = datetime(2002,1,1).date()
+maxdate = datetime.now().date()
+dateExpr = ranged_value(dateExpr, mindate, maxdate)
+
+
+dateExpr.runTests("""
+ 2011/5/8
+ 2001/1/1
+ 2004/2/29
+ 2004/2
+ 1999/12/31""")
+
+
diff --git a/trunk/src/examples/readJson.py b/trunk/src/examples/readJson.py
new file mode 100644
index 0000000..deca53b
--- /dev/null
+++ b/trunk/src/examples/readJson.py
@@ -0,0 +1,1917 @@
+
+#~ url = "http://cmsdoc.cern.ch/cms/test/aprom/phedex/dev/gowri/datasvc/tbedi/requestDetails"
+#~ params = {'format':'json'}
+#~ import urllib
+#~ eparams = urllib.urlencode(params)
+#~ import urllib2
+#~ request = urllib2.Request(url,eparams)
+#~ response = urllib2.urlopen(request)
+#~ s = response.read()
+#~ response.close()
+
+#~ print s
+
+s = """
+{"phedex":{"request":[{"last_update":"1188037561", "numofapproved":"1",
+"id":"7425"}, {"last_update":"1188751826", "numofapproved":"1",
+"id":"8041"}, {"last_update":"1190116795", "numofapproved":"1",
+"id":"9281"}, {"last_update":"1190248781", "numofapproved":"1",
+"id":"9521"}, {"last_update":"1192615612", "numofapproved":"1",
+"id":"12821"}, {"last_update":"1192729887", "numofapproved":"1",
+"id":"13121"}, {"last_update":"1193152971", "numofapproved":"1",
+"id":"13501"}, {"last_update":"1194022054", "numofapproved":"1",
+"id":"14782"}, {"last_update":"1194429365", "numofapproved":"1",
+"id":"15081"}, {"last_update":"1195069848", "numofapproved":"1",
+"id":"16661"}, {"last_update":"1178403225", "numofapproved":"1",
+"id":"1281"}, {"last_update":"1179239056", "numofapproved":"1",
+"id":"1387"}, {"last_update":"1179842205", "numofapproved":"1",
+"id":"1665"}, {"last_update":"1179842040", "numofapproved":"1",
+"id":"1661"}, {"last_update":"1179935333", "numofapproved":"1",
+"id":"1741"}, {"last_update":"1183151195", "numofapproved":"1",
+"id":"3841"}, {"last_update":"1187031531", "numofapproved":"1",
+"id":"6601"}, {"last_update":"1188820478", "numofapproved":"1",
+"id":"8121"}, {"last_update":"1190652719", "numofapproved":"1",
+"id":"9983"}, {"last_update":"1192628950", "numofapproved":"1",
+"id":"12841"}, {"last_update":"1193075426", "numofapproved":"1",
+"id":"13341"}, {"last_update":"1194214609", "numofapproved":"1",
+"id":"14882"}, {"last_update":"1194387864", "numofapproved":"1",
+"id":"15062"}, {"last_update":"1195134504", "numofapproved":"1",
+"id":"16741"}, {"last_update":"1182431453", "numofapproved":"1",
+"id":"3421"}, {"last_update":"1183448188", "numofapproved":"1",
+"id":"4061"}, {"last_update":"1184588081", "numofapproved":"1",
+"id":"4908"}, {"last_update":"1184681258", "numofapproved":"1",
+"id":"4913"}, {"last_update":"1188039048", "numofapproved":"1",
+"id":"7426"}, {"last_update":"1192699041", "numofapproved":"1",
+"id":"12982"}, {"last_update":"1193219685", "numofapproved":"1",
+"id":"13529"}, {"last_update":"1193401408", "numofapproved":"1",
+"id":"14081"}, {"last_update":"1194454724", "numofapproved":"1",
+"id":"15201"}, {"last_update":"1194937690", "numofapproved":"1",
+"id":"16044"}, {"last_update":"1194947125", "numofapproved":"1",
+"id":"16103"}, {"last_update":"1195134890", "numofapproved":"1",
+"id":"16761"}, {"last_update":"1195486898", "numofapproved":"1",
+"id":"17301"}, {"last_update":"1195497774", "numofapproved":"1",
+"id":"17341"}, {"last_update":"1184744080", "numofapproved":"1",
+"id":"4941"}, {"last_update":"1186558911", "numofapproved":"1",
+"id":"6321"}, {"last_update":"1189524520", "numofapproved":"1",
+"id":"8802"}, {"last_update":"1192683178", "numofapproved":"1",
+"id":"12921"}, {"last_update":"1193260655", "numofapproved":"1",
+"id":"13530"}, {"last_update":"1194280038", "numofapproved":"1",
+"id":"15002"}, {"last_update":"1182077478", "numofapproved":"1",
+"id":"3162"}, {"last_update":"1183386650", "numofapproved":"1",
+"id":"3961"}, {"last_update":"1192063369", "numofapproved":"1",
+"id":"12182"}, {"last_update":"1181931262", "numofapproved":"1",
+"id":"3101"}, {"last_update":"1178648271", "numofapproved":"1",
+"id":"1308"}, {"last_update":"1179239923", "numofapproved":"1",
+"id":"1405"}, {"last_update":"1184370745", "numofapproved":"1",
+"id":"4861"}, {"last_update":"1185280568", "numofapproved":"1",
+"id":"5302"}, {"last_update":"1187875115", "numofapproved":"1",
+"id":"7344"}, {"last_update":"1189140441", "numofapproved":"1",
+"id":"8541"}, {"last_update":"1189180903", "numofapproved":"1",
+"id":"8661"}, {"last_update":"1189767643", "numofapproved":"1",
+"id":"9001"}, {"last_update":"1190726167", "numofapproved":"1",
+"id":"10101"}, {"last_update":"1190972990", "numofapproved":"1",
+"id":"10661"}, {"last_update":"1190990720", "numofapproved":"1",
+"id":"10712"}, {"last_update":"1192004838", "numofapproved":"1",
+"id":"12021"}, {"last_update":"1192612211", "numofapproved":"1",
+"id":"12803"}, {"last_update":"1194441407", "numofapproved":"1",
+"id":"15103"}, {"last_update":"1194792356", "numofapproved":"1",
+"id":"15681"}, {"last_update":"1194860650", "numofapproved":"1",
+"id":"15801"}, {"last_update":"1194877395", "numofapproved":"1",
+"id":"15881"}, {"last_update":"1194950552", "numofapproved":"1",
+"id":"16124"}, {"last_update":"1194992714", "numofapproved":"1",
+"id":"16421"}, {"last_update":"1195054500", "numofapproved":"1",
+"id":"16581"}, {"last_update":"1195228524", "numofapproved":"1",
+"id":"17001"}, {"last_update":"1195469382", "numofapproved":"1",
+"id":"17161"}, {"last_update":"1178035947", "numofapproved":"1",
+"id":"1202"}, {"last_update":"1178869668", "numofapproved":"1",
+"id":"1356"}, {"last_update":"1183563268", "numofapproved":"1",
+"id":"4201"}, {"last_update":"1185314677", "numofapproved":"1",
+"id":"5361"}, {"last_update":"1188467567", "numofapproved":"1",
+"id":"7781"}, {"last_update":"1190011821", "numofapproved":"1",
+"id":"9202"}, {"last_update":"1190206214", "numofapproved":"1",
+"id":"9481"}, {"last_update":"1190973037", "numofapproved":"1",
+"id":"10663"}, {"last_update":"1190819127", "numofapproved":"1",
+"id":"10342"}, {"last_update":"1192154959", "numofapproved":"1",
+"id":"12381"}, {"last_update":"1192634509", "numofapproved":"1",
+"id":"12862"}, {"last_update":"1194004677", "numofapproved":"1",
+"id":"14722"}, {"last_update":"1195548191", "numofapproved":"1",
+"id":"17501"}, {"last_update":"1195548953", "numofapproved":"1",
+"id":"17502"}, {"last_update":"1195559809", "numofapproved":"1",
+"id":"17541"}, {"last_update":"1177589103", "numofapproved":"1",
+"id":"1044"}, {"last_update":"1183416879", "numofapproved":"1",
+"id":"4041"}, {"last_update":"1186646977", "numofapproved":"1",
+"id":"6342"}, {"last_update":"1189656586", "numofapproved":"1",
+"id":"8902"}, {"last_update":"1190150645", "numofapproved":"1",
+"id":"9421"}, {"last_update":"1190409040", "numofapproved":"1",
+"id":"9741"}, {"last_update":"1190973011", "numofapproved":"1",
+"id":"10662"}, {"last_update":"1190993896", "numofapproved":"1",
+"id":"10761"}, {"last_update":"1193973610", "numofapproved":"1",
+"id":"14661"}, {"last_update":"1193973848", "numofapproved":"1",
+"id":"14664"}, {"last_update":"1194539978", "numofapproved":"1",
+"id":"15381"}, {"last_update":"1194947356", "numofapproved":"1",
+"id":"16105"}, {"last_update":"1195399589", "numofapproved":"1",
+"id":"17101"}, {"last_update":"1195464953", "numofapproved":"1",
+"id":"17141"}, {"last_update":"1171962221", "numofapproved":"1",
+"id":"109"}, {"last_update":"1173113812", "numofapproved":"1",
+"id":"247"}, {"last_update":"1173975435", "numofapproved":"1",
+"id":"343"}, {"last_update":"1174050971", "numofapproved":"1",
+"id":"353"}, {"last_update":"1174301484", "numofapproved":"1",
+"id":"393"}, {"last_update":"1172565853", "numofapproved":"1",
+"id":"208"}, {"last_update":"1172593328", "numofapproved":"1",
+"id":"215"}, {"last_update":"1175267391", "numofapproved":"1",
+"id":"565"}, {"last_update":"1171379845", "numofapproved":"1",
+"id":"25"}, {"last_update":"1171477466", "numofapproved":"1",
+"id":"53"}, {"last_update":"1171799296", "numofapproved":"1",
+"id":"77"}, {"last_update":"1172671474", "numofapproved":"1",
+"id":"223"}, {"last_update":"1174301354", "numofapproved":"1",
+"id":"388"}, {"last_update":"1174899552", "numofapproved":"1",
+"id":"511"}, {"last_update":"1174899458", "numofapproved":"1",
+"id":"505"}, {"last_update":"1175502936", "numofapproved":"1",
+"id":"604"}, {"last_update":"1175613825", "numofapproved":"1",
+"id":"665"}, {"last_update":"1175776232", "numofapproved":"1",
+"id":"673"}, {"last_update":"1171621302", "numofapproved":"1",
+"id":"68"}, {"last_update":"1171904738", "numofapproved":"1",
+"id":"98"}, {"last_update":"1171968012", "numofapproved":"1",
+"id":"115"}, {"last_update":"1172145037", "numofapproved":"1",
+"id":"168"}, {"last_update":"1172246599", "numofapproved":"1",
+"id":"185"}, {"last_update":"1173886280", "numofapproved":"1",
+"id":"318"}, {"last_update":"1174562010", "numofapproved":"1",
+"id":"423"}, {"last_update":"1176308974", "numofapproved":"1",
+"id":"884"}, {"last_update":"1176482150", "numofapproved":"1",
+"id":"943"}, {"last_update":"1176702424", "numofapproved":"1",
+"id":"1001"}, {"last_update":"1176748776", "numofapproved":"1",
+"id":"984"}, {"last_update":"1172669745", "numofapproved":"1",
+"id":"222"}, {"last_update":"1174899538", "numofapproved":"1",
+"id":"510"}, {"last_update":"1174899143", "numofapproved":"1",
+"id":"493"}, {"last_update":"1174899043", "numofapproved":"1",
+"id":"488"}, {"last_update":"1175711780", "numofapproved":"1",
+"id":"667"}, {"last_update":"1175712851", "numofapproved":"1",
+"id":"705"}, {"last_update":"1176296548", "numofapproved":"1",
+"id":"841"}, {"last_update":"1175862269", "numofapproved":"1",
+"id":"781"}, {"last_update":"1171483107", "numofapproved":"1",
+"id":"54"}, {"last_update":"1171645737", "numofapproved":"1",
+"id":"71"}, {"last_update":"1172253423", "numofapproved":"1",
+"id":"188"}, {"last_update":"1173888726", "numofapproved":"1",
+"id":"321"}, {"last_update":"1173975649", "numofapproved":"1",
+"id":"346"}, {"last_update":"1174299379", "numofapproved":"1",
+"id":"363"}, {"last_update":"1174301359", "numofapproved":"1",
+"id":"389"}, {"last_update":"1174301073", "numofapproved":"1",
+"id":"379"}, {"last_update":"1174300650", "numofapproved":"1",
+"id":"371"}, {"last_update":"1171485069", "numofapproved":"1",
+"id":"55"}, {"last_update":"1171799178", "numofapproved":"1",
+"id":"73"}, {"last_update":"1171896809", "numofapproved":"1",
+"id":"95"}, {"last_update":"1172672959", "numofapproved":"1",
+"id":"224"}, {"last_update":"1172693619", "numofapproved":"1",
+"id":"230"}, {"last_update":"1173207656", "numofapproved":"1",
+"id":"253"}, {"last_update":"1174059533", "numofapproved":"1",
+"id":"356"}, {"last_update":"1174300538", "numofapproved":"1",
+"id":"368"}, {"last_update":"1176137457", "numofapproved":"1",
+"id":"807"}, {"last_update":"1173728124", "numofapproved":"1",
+"id":"305"}, {"last_update":"1172507633", "numofapproved":"1",
+"id":"198"}, {"last_update":"1174301173", "numofapproved":"1",
+"id":"383"}, {"last_update":"1174899102", "numofapproved":"1",
+"id":"491"}, {"last_update":"1174301362", "numofapproved":"1",
+"id":"390"}, {"last_update":"1175254095", "numofapproved":"1",
+"id":"561"}, {"last_update":"1174037250", "numofapproved":"1",
+"id":"348"}, {"last_update":"1175865081", "numofapproved":"1",
+"id":"782"}, {"last_update":"1177591942", "numofapproved":"1",
+"id":"1046"}, {"last_update":"1177989191", "numofapproved":"1",
+"id":"1201"}, {"last_update":"1178743279", "numofapproved":"1",
+"id":"1323"}, {"last_update":"1178876587", "numofapproved":"1",
+"id":"1357"}, {"last_update":"1179239620", "numofapproved":"1",
+"id":"1401"}, {"last_update":"1180725458", "numofapproved":"1",
+"id":"2141"}, {"last_update":"1181205209", "numofapproved":"1",
+"id":"2421"}, {"last_update":"1181575615", "numofapproved":"1",
+"id":"2761"}, {"last_update":"1182184775", "numofapproved":"1",
+"id":"3201"}, {"last_update":"1182963728", "numofapproved":"1",
+"id":"3661"}, {"last_update":"1178727735", "numofapproved":"1",
+"id":"1349"}, {"last_update":"1182497720", "numofapproved":"1",
+"id":"3441"}, {"last_update":"1184381847", "numofapproved":"1",
+"id":"4881"}, {"last_update":"1184568423", "numofapproved":"1",
+"id":"4904"}, {"last_update":"1185364813", "numofapproved":"1",
+"id":"5421"}, {"last_update":"1188043594", "numofapproved":"1",
+"id":"7441"}, {"last_update":"1188675287", "numofapproved":"1",
+"id":"7981"}, {"last_update":"1188741594", "numofapproved":"1",
+"id":"8021"}, {"last_update":"1189144234", "numofapproved":"1",
+"id":"8561"}, {"last_update":"1189170150", "numofapproved":"1",
+"id":"8641"}, {"last_update":"1189501508", "numofapproved":"1",
+"id":"8761"}, {"last_update":"1189811918", "numofapproved":"1",
+"id":"9041"}, {"last_update":"1189812095", "numofapproved":"1",
+"id":"9042"}, {"last_update":"1177591716", "numofapproved":"1",
+"id":"1045"}, {"last_update":"1178040595", "numofapproved":"1",
+"id":"1203"}, {"last_update":"1182437936", "numofapproved":"1",
+"id":"3423"}, {"last_update":"1190480042", "numofapproved":"1",
+"id":"9781"}, {"last_update":"1190821494", "numofapproved":"1",
+"id":"10361"}, {"last_update":"1190959672", "numofapproved":"1",
+"id":"10602"}, {"last_update":"1190964023", "numofapproved":"1",
+"id":"10621"}, {"last_update":"1190991147", "numofapproved":"1",
+"id":"10721"}, {"last_update":"1190992132", "numofapproved":"1",
+"id":"10741"}, {"last_update":"1190990410", "numofapproved":"1",
+"id":"10706"}, {"last_update":"1181667132", "numofapproved":"1",
+"id":"2861"}, {"last_update":"1183746653", "numofapproved":"1",
+"id":"4321"}, {"last_update":"1191184539", "numofapproved":"1",
+"id":"10861"}, {"last_update":"1191490599", "numofapproved":"1",
+"id":"11261"}, {"last_update":"1191834884", "numofapproved":"1",
+"id":"11801"}, {"last_update":"1191834899", "numofapproved":"1",
+"id":"11802"}, {"last_update":"1191940759", "numofapproved":"1",
+"id":"11961"}, {"last_update":"1179971250", "numofapproved":"1",
+"id":"1643"}, {"last_update":"1181663618", "numofapproved":"1",
+"id":"2841"}, {"last_update":"1181932994", "numofapproved":"1",
+"id":"3102"}, {"last_update":"1182420732", "numofapproved":"1",
+"id":"3382"}, {"last_update":"1192118127", "numofapproved":"1",
+"id":"12281"}, {"last_update":"1192222036", "numofapproved":"1",
+"id":"12481"}, {"last_update":"1192155814", "numofapproved":"1",
+"id":"12364"}, {"last_update":"1192563924", "numofapproved":"1",
+"id":"12761"}, {"last_update":"1193124530", "numofapproved":"1",
+"id":"13441"}, {"last_update":"1193345545", "numofapproved":"1",
+"id":"13921"}, {"last_update":"1193396927", "numofapproved":"1",
+"id":"14041"}, {"last_update":"1180015411", "numofapproved":"1",
+"id":"1651"}, {"last_update":"1180107815", "numofapproved":"1",
+"id":"1658"}, {"last_update":"1186050394", "numofapproved":"1",
+"id":"6021"}, {"last_update":"1188519417", "numofapproved":"1",
+"id":"7841"}, {"last_update":"1193222002", "numofapproved":"1",
+"id":"13541"}, {"last_update":"1193965081", "numofapproved":"1",
+"id":"14641"}, {"last_update":"1193660582", "numofapproved":"1",
+"id":"14381"}, {"last_update":"1194088240", "numofapproved":"1",
+"id":"14821"}, {"last_update":"1194110475", "numofapproved":"1",
+"id":"14841"}, {"last_update":"1194246367", "numofapproved":"1",
+"id":"14902"}, {"last_update":"1194464283", "numofapproved":"1",
+"id":"15221"}, {"last_update":"1194622250", "numofapproved":"1",
+"id":"15461"}, {"last_update":"1194635632", "numofapproved":"1",
+"id":"15601"}, {"last_update":"1179147506", "numofapproved":"1",
+"id":"1382"}, {"last_update":"1179240025", "numofapproved":"1",
+"id":"1388"}, {"last_update":"1179748089", "numofapproved":"1",
+"id":"1561"}, {"last_update":"1179868997", "numofapproved":"1",
+"id":"1681"}, {"last_update":"1183019667", "numofapproved":"1",
+"id":"3702"}, {"last_update":"1184531598", "numofapproved":"1",
+"id":"4902"}, {"last_update":"1187294472", "numofapproved":"1",
+"id":"6841"}, {"last_update":"1189521494", "numofapproved":"1",
+"id":"8801"}, {"last_update":"1192726867", "numofapproved":"1",
+"id":"13081"}, {"last_update":"1193049178", "numofapproved":"1",
+"id":"13301"}, {"last_update":"1193387050", "numofapproved":"1",
+"id":"13947"}, {"last_update":"1194277280", "numofapproved":"1",
+"id":"14981"}, {"last_update":"1179150720", "numofapproved":"1",
+"id":"1383"}, {"last_update":"1179842104", "numofapproved":"1",
+"id":"1663"}, {"last_update":"1183766887", "numofapproved":"1",
+"id":"4341"}, {"last_update":"1185542132", "numofapproved":"1",
+"id":"5682"}, {"last_update":"1186737114", "numofapproved":"1",
+"id":"6382"}, {"last_update":"1187015679", "numofapproved":"1",
+"id":"6521"}, {"last_update":"1190326980", "numofapproved":"1",
+"id":"9641"}, {"last_update":"1191595711", "numofapproved":"1",
+"id":"11622"}, {"last_update":"1192106288", "numofapproved":"1",
+"id":"12221"}, {"last_update":"1192454432", "numofapproved":"1",
+"id":"12622"}, {"last_update":"1194339640", "numofapproved":"1",
+"id":"15021"}, {"last_update":"1177758209", "numofapproved":"1",
+"id":"1181"}, {"last_update":"1179842392", "numofapproved":"1",
+"id":"1669"}, {"last_update":"1179872870", "numofapproved":"1",
+"id":"1682"}, {"last_update":"1181233887", "numofapproved":"1",
+"id":"2541"}, {"last_update":"1182349297", "numofapproved":"1",
+"id":"3342"}, {"last_update":"1182375421", "numofapproved":"1",
+"id":"3350"}, {"last_update":"1183485259", "numofapproved":"1",
+"id":"4081"}, {"last_update":"1184319308", "numofapproved":"1",
+"id":"4821"}, {"last_update":"1187626648", "numofapproved":"1",
+"id":"6981"}, {"last_update":"1193153090", "numofapproved":"1",
+"id":"13502"}, {"last_update":"1194366368", "numofapproved":"1",
+"id":"15041"}, {"last_update":"1194617018", "numofapproved":"1",
+"id":"15421"}, {"last_update":"1195230640", "numofapproved":"1",
+"id":"17021"}, {"last_update":"1179908379", "numofapproved":"1",
+"id":"1701"}, {"last_update":"1188049228", "numofapproved":"1",
+"id":"7427"}, {"last_update":"1177581166", "numofapproved":"1",
+"id":"1061"}, {"last_update":"1187160654", "numofapproved":"1",
+"id":"6661"}, {"last_update":"1192983992", "numofapproved":"1",
+"id":"13222"}, {"last_update":"1193388978", "numofapproved":"1",
+"id":"13954"}, {"last_update":"1194617112", "numofapproved":"1",
+"id":"15422"}, {"last_update":"1195398876", "numofapproved":"1",
+"id":"17081"}, {"last_update":"1184262511", "numofapproved":"1",
+"id":"4801"}, {"last_update":"1192112284", "numofapproved":"1",
+"id":"12241"}, {"last_update":"1193082767", "numofapproved":"1",
+"id":"13401"}, {"last_update":"1193179243", "numofapproved":"1",
+"id":"13526"}, {"last_update":"1178142915", "numofapproved":"1",
+"id":"1206"}, {"last_update":"1178648333", "numofapproved":"1",
+"id":"1310"}, {"last_update":"1179279626", "numofapproved":"1",
+"id":"1391"}, {"last_update":"1182882268", "numofapproved":"1",
+"id":"3584"}, {"last_update":"1183128448", "numofapproved":"1",
+"id":"3823"}, {"last_update":"1183377394", "numofapproved":"1",
+"id":"3941"}, {"last_update":"1188582729", "numofapproved":"1",
+"id":"7902"}, {"last_update":"1189695063", "numofapproved":"1",
+"id":"8962"}, {"last_update":"1192001165", "numofapproved":"1",
+"id":"12001"}, {"last_update":"1192155647", "numofapproved":"1",
+"id":"12363"}, {"last_update":"1193418304", "numofapproved":"1",
+"id":"14202"}, {"last_update":"1193632105", "numofapproved":"1",
+"id":"14341"}, {"last_update":"1194011106", "numofapproved":"1",
+"id":"14741"}, {"last_update":"1194818628", "numofapproved":"1",
+"id":"15701"}, {"last_update":"1194875153", "numofapproved":"1",
+"id":"15861"}, {"last_update":"1194727029", "numofapproved":"1",
+"id":"15665"}, {"last_update":"1194950210", "numofapproved":"1",
+"id":"16122"}, {"last_update":"1194976681", "numofapproved":"1",
+"id":"16241"}, {"last_update":"1194979189", "numofapproved":"1",
+"id":"16281"}, {"last_update":"1194962224", "numofapproved":"1",
+"id":"16201"}, {"last_update":"1195046085", "numofapproved":"1",
+"id":"16481"}, {"last_update":"1195399919", "numofapproved":"1",
+"id":"17102"}, {"last_update":"1183113736", "numofapproved":"1",
+"id":"3782"}, {"last_update":"1183114202", "numofapproved":"1",
+"id":"3783"}, {"last_update":"1189017904", "numofapproved":"1",
+"id":"8441"}, {"last_update":"1189694944", "numofapproved":"1",
+"id":"8961"}, {"last_update":"1190766842", "numofapproved":"1",
+"id":"10181"}, {"last_update":"1190973066", "numofapproved":"1",
+"id":"10665"}, {"last_update":"1190990264", "numofapproved":"1",
+"id":"10702"}, {"last_update":"1193043204", "numofapproved":"1",
+"id":"13281"}, {"last_update":"1194627082", "numofapproved":"1",
+"id":"15561"}, {"last_update":"1194894589", "numofapproved":"1",
+"id":"15941"}, {"last_update":"1195485915", "numofapproved":"1",
+"id":"17281"}, {"last_update":"1195485806", "numofapproved":"1",
+"id":"17261"}, {"last_update":"1195498836", "numofapproved":"1",
+"id":"17361"}, {"last_update":"1195514951", "numofapproved":"1",
+"id":"17421"}, {"last_update":"1183722351", "numofapproved":"1",
+"id":"4261"}, {"last_update":"1184218083", "numofapproved":"1",
+"id":"4682"}, {"last_update":"1186848968", "numofapproved":"1",
+"id":"6441"}, {"last_update":"1187023846", "numofapproved":"1",
+"id":"6561"}, {"last_update":"1187870812", "numofapproved":"1",
+"id":"7342"}, {"last_update":"1188657717", "numofapproved":"1",
+"id":"7961"}, {"last_update":"1190541897", "numofapproved":"1",
+"id":"9841"}, {"last_update":"1190629135", "numofapproved":"1",
+"id":"9922"}, {"last_update":"1191226530", "numofapproved":"1",
+"id":"10922"}, {"last_update":"1191505214", "numofapproved":"1",
+"id":"11321"}, {"last_update":"1192304524", "numofapproved":"1",
+"id":"12541"}, {"last_update":"1193948730", "numofapproved":"1",
+"id":"14601"}, {"last_update":"1194073812", "numofapproved":"1",
+"id":"14801"}, {"last_update":"1194387224", "numofapproved":"1",
+"id":"14892"}, {"last_update":"1194464384", "numofapproved":"1",
+"id":"15223"}, {"last_update":"1194726799", "numofapproved":"1",
+"id":"15663"}, {"last_update":"1171969969", "numofapproved":"1",
+"id":"119"}, {"last_update":"1174444717", "numofapproved":"1",
+"id":"405"}, {"last_update":"1174899431", "numofapproved":"1",
+"id":"504"}, {"last_update":"1174899204", "numofapproved":"1",
+"id":"496"}, {"last_update":"1174925591", "numofapproved":"1",
+"id":"530"}, {"last_update":"1176902523", "numofapproved":"1",
+"id":"1008"}, {"last_update":"1172765523", "numofapproved":"1",
+"id":"232"}, {"last_update":"1173315950", "numofapproved":"1",
+"id":"260"}, {"last_update":"1174899524", "numofapproved":"1",
+"id":"509"}, {"last_update":"1174300691", "numofapproved":"1",
+"id":"373"}, {"last_update":"1175502917", "numofapproved":"1",
+"id":"625"}, {"last_update":"1175601578", "numofapproved":"1",
+"id":"662"}, {"last_update":"1175608600", "numofapproved":"1",
+"id":"684"}, {"last_update":"1176755309", "numofapproved":"1",
+"id":"985"}, {"last_update":"1171386411", "numofapproved":"1",
+"id":"45"}, {"last_update":"1171800366", "numofapproved":"1",
+"id":"81"}, {"last_update":"1172847417", "numofapproved":"1",
+"id":"241"}, {"last_update":"1174734904", "numofapproved":"1",
+"id":"462"}, {"last_update":"1174735234", "numofapproved":"1",
+"id":"469"}, {"last_update":"1174735074", "numofapproved":"1",
+"id":"465"}, {"last_update":"1175267646", "numofapproved":"1",
+"id":"566"}, {"last_update":"1176331857", "numofapproved":"1",
+"id":"888"}, {"last_update":"1176387926", "numofapproved":"1",
+"id":"890"}, {"last_update":"1176458401", "numofapproved":"1",
+"id":"904"}, {"last_update":"1173088626", "numofapproved":"1",
+"id":"244"}, {"last_update":"1173109009", "numofapproved":"1",
+"id":"246"}, {"last_update":"1173671557", "numofapproved":"1",
+"id":"284"}, {"last_update":"1174927658", "numofapproved":"1",
+"id":"532"}, {"last_update":"1175592399", "numofapproved":"1",
+"id":"661"}, {"last_update":"1176480402", "numofapproved":"1",
+"id":"941"}, {"last_update":"1176561564", "numofapproved":"1",
+"id":"945"}, {"last_update":"1172218707", "numofapproved":"1",
+"id":"180"}, {"last_update":"1172771475", "numofapproved":"1",
+"id":"233"}, {"last_update":"1173267863", "numofapproved":"1",
+"id":"257"}, {"last_update":"1176493803", "numofapproved":"1",
+"id":"963"}, {"last_update":"1171449646", "numofapproved":"1",
+"id":"49"}, {"last_update":"1171471549", "numofapproved":"1",
+"id":"51"}, {"last_update":"1171800487", "numofapproved":"1",
+"id":"88"}, {"last_update":"1171800431", "numofapproved":"1",
+"id":"85"}, {"last_update":"1175502995", "numofapproved":"1",
+"id":"627"}, {"last_update":"1175712797", "numofapproved":"1",
+"id":"704"}, {"last_update":"1171122384", "numofapproved":"1",
+"id":"3"}, {"last_update":"1171380774", "numofapproved":"1", "id":"26"},
+{"last_update":"1171904757", "numofapproved":"1", "id":"99"},
+{"last_update":"1174300705", "numofapproved":"1", "id":"374"},
+{"last_update":"1174924802", "numofapproved":"1", "id":"526"},
+{"last_update":"1175935441", "numofapproved":"1", "id":"801"},
+{"last_update":"1175610915", "numofapproved":"1", "id":"686"},
+{"last_update":"1171977081", "numofapproved":"1", "id":"125"},
+{"last_update":"1173165324", "numofapproved":"1", "id":"249"},
+{"last_update":"1173888337", "numofapproved":"1", "id":"319"},
+{"last_update":"1173889473", "numofapproved":"1", "id":"331"},
+{"last_update":"1172180902", "numofapproved":"1", "id":"175"},
+{"last_update":"1174058063", "numofapproved":"1", "id":"354"},
+{"last_update":"1174300674", "numofapproved":"1", "id":"372"},
+{"last_update":"1171886332", "numofapproved":"1", "id":"93"},
+{"last_update":"1176731068", "numofapproved":"1", "id":"1003"},
+{"last_update":"1178645848", "numofapproved":"1", "id":"1306"},
+{"last_update":"1178706683", "numofapproved":"1", "id":"1321"},
+{"last_update":"1179240076", "numofapproved":"1", "id":"1406"},
+{"last_update":"1180380411", "numofapproved":"1", "id":"1862"},
+{"last_update":"1180683561", "numofapproved":"1", "id":"2041"},
+{"last_update":"1181229731", "numofapproved":"1", "id":"2521"},
+{"last_update":"1182210982", "numofapproved":"1", "id":"3203"},
+{"last_update":"1182421105", "numofapproved":"1", "id":"3401"},
+{"last_update":"1182199404", "numofapproved":"1", "id":"3202"},
+{"last_update":"1182258596", "numofapproved":"1", "id":"3241"},
+{"last_update":"1183556842", "numofapproved":"1", "id":"4161"},
+{"last_update":"1184146825", "numofapproved":"1", "id":"4601"},
+{"last_update":"1184771229", "numofapproved":"1", "id":"4981"},
+{"last_update":"1185355415", "numofapproved":"1", "id":"5401"},
+{"last_update":"1185377130", "numofapproved":"1", "id":"5481"},
+{"last_update":"1185483994", "numofapproved":"1", "id":"5621"},
+{"last_update":"1186496707", "numofapproved":"1", "id":"6261"},
+{"last_update":"1187704347", "numofapproved":"1", "id":"7001"},
+{"last_update":"1187758331", "numofapproved":"1", "id":"7101"},
+{"last_update":"1187765716", "numofapproved":"1", "id":"7161"},
+{"last_update":"1188284185", "numofapproved":"1", "id":"7581"},
+{"last_update":"1188463286", "numofapproved":"1", "id":"7761"},
+{"last_update":"1189012058", "numofapproved":"1", "id":"8421"},
+{"last_update":"1189814265", "numofapproved":"1", "id":"9061"},
+{"last_update":"1180880867", "numofapproved":"1", "id":"2161"},
+{"last_update":"1181218244", "numofapproved":"1", "id":"2463"},
+{"last_update":"1183515137", "numofapproved":"1", "id":"4141"},
+{"last_update":"1183515248", "numofapproved":"1", "id":"4142"},
+{"last_update":"1188311100", "numofapproved":"1", "id":"7641"},
+{"last_update":"1190011501", "numofapproved":"1", "id":"9201"},
+{"last_update":"1190012299", "numofapproved":"1", "id":"9221"},
+{"last_update":"1190149196", "numofapproved":"1", "id":"9382"},
+{"last_update":"1190202046", "numofapproved":"1", "id":"9461"},
+{"last_update":"1190626607", "numofapproved":"1", "id":"9881"},
+{"last_update":"1190632230", "numofapproved":"1", "id":"9941"},
+{"last_update":"1190660429", "numofapproved":"1", "id":"10002"},
+{"last_update":"1190819102", "numofapproved":"1", "id":"10341"},
+{"last_update":"1190824319", "numofapproved":"1", "id":"10382"},
+{"last_update":"1190825791", "numofapproved":"1", "id":"10402"},
+{"last_update":"1190847397", "numofapproved":"1", "id":"10421"},
+{"last_update":"1190876679", "numofapproved":"1", "id":"10441"},
+{"last_update":"1190918894", "numofapproved":"1", "id":"10541"},
+{"last_update":"1190924961", "numofapproved":"1", "id":"10582"},
+{"last_update":"1190991179", "numofapproved":"1", "id":"10723"},
+{"last_update":"1190663960", "numofapproved":"1", "id":"10042"},
+{"last_update":"1191222270", "numofapproved":"1", "id":"10881"},
+{"last_update":"1178869580", "numofapproved":"1", "id":"1355"},
+{"last_update":"1180054057", "numofapproved":"1", "id":"1655"},
+{"last_update":"1180428815", "numofapproved":"1", "id":"1881"},
+{"last_update":"1183369278", "numofapproved":"1", "id":"3901"},
+{"last_update":"1185018445", "numofapproved":"1", "id":"5163"},
+{"last_update":"1185201628", "numofapproved":"1", "id":"5221"},
+{"last_update":"1189345395", "numofapproved":"1", "id":"8741"},
+{"last_update":"1191406141", "numofapproved":"1", "id":"11041"},
+{"last_update":"1191410914", "numofapproved":"1", "id":"11067"},
+{"last_update":"1191558362", "numofapproved":"1", "id":"11461"},
+{"last_update":"1191584539", "numofapproved":"1", "id":"11541"},
+{"last_update":"1191584660", "numofapproved":"1", "id":"11542"},
+{"last_update":"1191599491", "numofapproved":"1", "id":"11661"},
+{"last_update":"1191813292", "numofapproved":"1", "id":"11781"},
+{"last_update":"1191856553", "numofapproved":"1", "id":"11842"},
+{"last_update":"1191861142", "numofapproved":"1", "id":"11862"},
+{"last_update":"1177509523", "numofapproved":"1", "id":"1041"},
+{"last_update":"1190627650", "numofapproved":"1", "id":"9901"},
+{"last_update":"1192034749", "numofapproved":"1", "id":"12141"},
+{"last_update":"1192165574", "numofapproved":"1", "id":"12401"},
+{"last_update":"1192431750", "numofapproved":"1", "id":"12581"},
+{"last_update":"1192536591", "numofapproved":"1", "id":"12721"},
+{"last_update":"1193035428", "numofapproved":"1", "id":"13261"},
+{"last_update":"1193239266", "numofapproved":"1", "id":"13581"},
+{"last_update":"1193314455", "numofapproved":"1", "id":"13841"},
+{"last_update":"1193333733", "numofapproved":"1", "id":"13901"},
+{"last_update":"1193389116", "numofapproved":"1", "id":"14001"},
+{"last_update":"1184970339", "numofapproved":"1", "id":"5121"},
+{"last_update":"1190892760", "numofapproved":"1", "id":"10481"},
+{"last_update":"1192823398", "numofapproved":"1", "id":"13182"},
+{"last_update":"1193911671", "numofapproved":"1", "id":"14541"},
+{"last_update":"1193916761", "numofapproved":"1", "id":"14543"},
+{"last_update":"1194212665", "numofapproved":"1", "id":"14881"},
+{"last_update":"1194248205", "numofapproved":"1", "id":"14921"},
+{"last_update":"1194513600", "numofapproved":"1", "id":"15110"},
+{"last_update":"1194539704", "numofapproved":"1", "id":"15361"},
+{"last_update":"1194569643", "numofapproved":"1", "id":"15112"},
+{"last_update":"1194619794", "numofapproved":"1", "id":"15441"},
+{"last_update":"1194623621", "numofapproved":"1", "id":"15501"},
+{"last_update":"1194624477", "numofapproved":"1", "id":"15521"},
+{"last_update":"1194635685", "numofapproved":"1", "id":"15602"},
+{"last_update":"1179311539", "numofapproved":"1", "id":"1393"},
+{"last_update":"1179672561", "numofapproved":"1", "id":"1521"},
+{"last_update":"1180712413", "numofapproved":"1", "id":"2101"},
+{"last_update":"1181646264", "numofapproved":"1", "id":"2821"},
+{"last_update":"1181807696", "numofapproved":"1", "id":"2921"},
+{"last_update":"1181824523", "numofapproved":"1", "id":"2942"},
+{"last_update":"1181835089", "numofapproved":"1", "id":"2981"},
+{"last_update":"1182000147", "numofapproved":"1", "id":"3141"},
+{"last_update":"1182952133", "numofapproved":"1", "id":"3641"},
+{"last_update":"1188811518", "numofapproved":"1", "id":"8101"},
+{"last_update":"1188975549", "numofapproved":"1", "id":"8321"},
+{"last_update":"1190122760", "numofapproved":"1", "id":"9301"},
+{"last_update":"1190124712", "numofapproved":"1", "id":"9321"},
+{"last_update":"1194526560", "numofapproved":"1", "id":"15281"},
+{"last_update":"1195149112", "numofapproved":"1", "id":"16821"},
+{"last_update":"1179823256", "numofapproved":"1", "id":"1602"},
+{"last_update":"1186332011", "numofapproved":"1", "id":"6165"},
+{"last_update":"1187263451", "numofapproved":"1", "id":"6781"},
+{"last_update":"1190312346", "numofapproved":"1", "id":"9621"},
+{"last_update":"1193178728", "numofapproved":"1", "id":"13525"},
+{"last_update":"1193908534", "numofapproved":"1", "id":"14524"},
+{"last_update":"1194279992", "numofapproved":"1", "id":"15001"},
+{"last_update":"1194947169", "numofapproved":"1", "id":"16104"},
+{"last_update":"1195139978", "numofapproved":"1", "id":"16801"},
+{"last_update":"1195152323", "numofapproved":"1", "id":"16841"},
+{"last_update":"1188086146", "numofapproved":"1", "id":"7428"},
+{"last_update":"1192143475", "numofapproved":"1", "id":"12341"},
+{"last_update":"1192529949", "numofapproved":"1", "id":"12664"},
+{"last_update":"1192721072", "numofapproved":"1", "id":"13041"},
+{"last_update":"1193844156", "numofapproved":"1", "id":"14501"},
+{"last_update":"1177597683", "numofapproved":"1", "id":"1063"},
+{"last_update":"1180975406", "numofapproved":"1", "id":"2184"},
+{"last_update":"1184681435", "numofapproved":"1", "id":"4914"},
+{"last_update":"1187596457", "numofapproved":"1", "id":"6922"},
+{"last_update":"1190661113", "numofapproved":"1", "id":"10003"},
+{"last_update":"1192721357", "numofapproved":"1", "id":"13042"},
+{"last_update":"1193130120", "numofapproved":"1", "id":"13461"},
+{"last_update":"1193388868", "numofapproved":"1", "id":"13953"},
+{"last_update":"1194861534", "numofapproved":"1", "id":"15821"},
+{"last_update":"1182357592", "numofapproved":"1", "id":"3345"},
+{"last_update":"1183722862", "numofapproved":"1", "id":"4262"},
+{"last_update":"1186066354", "numofapproved":"1", "id":"6041"},
+{"last_update":"1192698982", "numofapproved":"1", "id":"12981"},
+{"last_update":"1181237191", "numofapproved":"1", "id":"2561"},
+{"last_update":"1184569090", "numofapproved":"1", "id":"4906"},
+{"last_update":"1185397555", "numofapproved":"1", "id":"5501"},
+{"last_update":"1185541935", "numofapproved":"1", "id":"5681"},
+{"last_update":"1193385832", "numofapproved":"1", "id":"13941"},
+{"last_update":"1185482424", "numofapproved":"1", "id":"5581"},
+{"last_update":"1195508796", "numofapproved":"1", "id":"17401"},
+{"last_update":"1178718386", "numofapproved":"1", "id":"1347"},
+{"last_update":"1178788813", "numofapproved":"1", "id":"1351"},
+{"last_update":"1178877332", "numofapproved":"1", "id":"1358"},
+{"last_update":"1183208679", "numofapproved":"1", "id":"3861"},
+{"last_update":"1187885439", "numofapproved":"1", "id":"7347"},
+{"last_update":"1188985190", "numofapproved":"1", "id":"8341"},
+{"last_update":"1189687132", "numofapproved":"1", "id":"8941"},
+{"last_update":"1189864330", "numofapproved":"1", "id":"9121"},
+{"last_update":"1190990605", "numofapproved":"1", "id":"10709"},
+{"last_update":"1192634449", "numofapproved":"1", "id":"12861"},
+{"last_update":"1194723756", "numofapproved":"1", "id":"15641"},
+{"last_update":"1194792428", "numofapproved":"1", "id":"15682"},
+{"last_update":"1194725734", "numofapproved":"1", "id":"15661"},
+{"last_update":"1194945618", "numofapproved":"1", "id":"16061"},
+{"last_update":"1194946006", "numofapproved":"1", "id":"16081"},
+{"last_update":"1194949774", "numofapproved":"1", "id":"16121"},
+{"last_update":"1194950925", "numofapproved":"1", "id":"16126"},
+{"last_update":"1194979238", "numofapproved":"1", "id":"16282"},
+{"last_update":"1195051013", "numofapproved":"1", "id":"16543"},
+{"last_update":"1195050956", "numofapproved":"1", "id":"16542"},
+{"last_update":"1195047036", "numofapproved":"1", "id":"16501"},
+{"last_update":"1195221919", "numofapproved":"1", "id":"16942"},
+{"last_update":"1178035892", "numofapproved":"1", "id":"1221"},
+{"last_update":"1178570265", "numofapproved":"1", "id":"1302"},
+{"last_update":"1178811921", "numofapproved":"1", "id":"1354"},
+{"last_update":"1182344326", "numofapproved":"1", "id":"3321"},
+{"last_update":"1184999048", "numofapproved":"1", "id":"5141"},
+{"last_update":"1188994511", "numofapproved":"1", "id":"8361"},
+{"last_update":"1189161726", "numofapproved":"1", "id":"8601"},
+{"last_update":"1190500875", "numofapproved":"1", "id":"9803"},
+{"last_update":"1190817424", "numofapproved":"1", "id":"10321"},
+{"last_update":"1191327796", "numofapproved":"1", "id":"11001"},
+{"last_update":"1191410544", "numofapproved":"1", "id":"11062"},
+{"last_update":"1192009739", "numofapproved":"1", "id":"12062"},
+{"last_update":"1193973669", "numofapproved":"1", "id":"14662"},
+{"last_update":"1194035149", "numofapproved":"1", "id":"14783"},
+{"last_update":"1194465519", "numofapproved":"1", "id":"15106"},
+{"last_update":"1194464336", "numofapproved":"1", "id":"15222"},
+{"last_update":"1194861398", "numofapproved":"1", "id":"15802"},
+{"last_update":"1194950791", "numofapproved":"1", "id":"16125"},
+{"last_update":"1195501394", "numofapproved":"1", "id":"17381"},
+{"last_update":"1195546583", "numofapproved":"1", "id":"17461"},
+{"last_update":"1177607652", "numofapproved":"1", "id":"1048"},
+{"last_update":"1182349136", "numofapproved":"1", "id":"3322"},
+{"last_update":"1184217665", "numofapproved":"1", "id":"4681"},
+{"last_update":"1185510733", "numofapproved":"1", "id":"5641"},
+{"last_update":"1187875988", "numofapproved":"1", "id":"7345"},
+{"last_update":"1188384227", "numofapproved":"1", "id":"7701"},
+{"last_update":"1188935650", "numofapproved":"1", "id":"8261"},
+{"last_update":"1188951982", "numofapproved":"1", "id":"8301"},
+{"last_update":"1190391010", "numofapproved":"1", "id":"9701"},
+{"last_update":"1191169581", "numofapproved":"1", "id":"10841"},
+{"last_update":"1194435269", "numofapproved":"1", "id":"15101"},
+{"last_update":"1171800457", "numofapproved":"1", "id":"86"},
+{"last_update":"1171968036", "numofapproved":"1", "id":"116"},
+{"last_update":"1171984640", "numofapproved":"1", "id":"129"},
+{"last_update":"1171987101", "numofapproved":"1", "id":"130"},
+{"last_update":"1172588327", "numofapproved":"1", "id":"213"},
+{"last_update":"1173736730", "numofapproved":"1", "id":"306"},
+{"last_update":"1174735009", "numofapproved":"1", "id":"463"},
+{"last_update":"1172314484", "numofapproved":"1", "id":"192"},
+{"last_update":"1172580739", "numofapproved":"1", "id":"212"},
+{"last_update":"1173889335", "numofapproved":"1", "id":"328"},
+{"last_update":"1171799339", "numofapproved":"1", "id":"79"},
+{"last_update":"1171882669", "numofapproved":"1", "id":"91"},
+{"last_update":"1172561300", "numofapproved":"1", "id":"207"},
+{"last_update":"1172565919", "numofapproved":"1", "id":"209"},
+{"last_update":"1172600401", "numofapproved":"1", "id":"217"},
+{"last_update":"1174040553", "numofapproved":"1", "id":"350"},
+{"last_update":"1174300376", "numofapproved":"1", "id":"365"},
+{"last_update":"1171800419", "numofapproved":"1", "id":"84"},
+{"last_update":"1171800471", "numofapproved":"1", "id":"87"},
+{"last_update":"1171904826", "numofapproved":"1", "id":"102"},
+{"last_update":"1171962248", "numofapproved":"1", "id":"110"},
+{"last_update":"1171968056", "numofapproved":"1", "id":"117"},
+{"last_update":"1172180757", "numofapproved":"1", "id":"174"},
+{"last_update":"1172249286", "numofapproved":"1", "id":"186"},
+{"last_update":"1172331355", "numofapproved":"1", "id":"194"},
+{"last_update":"1172838799", "numofapproved":"1", "id":"235"},
+{"last_update":"1173839361", "numofapproved":"1", "id":"316"},
+{"last_update":"1176141087", "numofapproved":"1", "id":"809"},
+{"last_update":"1176293168", "numofapproved":"1", "id":"827"},
+{"last_update":"1176314927", "numofapproved":"1", "id":"887"},
+{"last_update":"1172147490", "numofapproved":"1", "id":"169"},
+{"last_update":"1172673371", "numofapproved":"1", "id":"225"},
+{"last_update":"1175021309", "numofapproved":"1", "id":"539"},
+{"last_update":"1175719394", "numofapproved":"1", "id":"708"},
+{"last_update":"1175797177", "numofapproved":"1", "id":"741"},
+{"last_update":"1175797204", "numofapproved":"1", "id":"761"},
+{"last_update":"1173888948", "numofapproved":"1", "id":"323"},
+{"last_update":"1171050355", "numofapproved":"1", "id":"1"},
+{"last_update":"1171904868", "numofapproved":"1", "id":"104"},
+{"last_update":"1174301476", "numofapproved":"1", "id":"392"},
+{"last_update":"1174396679", "numofapproved":"1", "id":"401"},
+{"last_update":"1174735025", "numofapproved":"1", "id":"464"},
+{"last_update":"1171894147", "numofapproved":"1", "id":"94"},
+{"last_update":"1172226240", "numofapproved":"1", "id":"181"},
+{"last_update":"1172442130", "numofapproved":"1", "id":"195"},
+{"last_update":"1174300588", "numofapproved":"1", "id":"370"},
+{"last_update":"1174899082", "numofapproved":"1", "id":"490"},
+{"last_update":"1174899309", "numofapproved":"1", "id":"501"},
+{"last_update":"1173724444", "numofapproved":"1", "id":"304"},
+{"last_update":"1176314883", "numofapproved":"1", "id":"886"},
+{"last_update":"1173284377", "numofapproved":"1", "id":"259"},
+{"last_update":"1172244974", "numofapproved":"1", "id":"184"},
+{"last_update":"1173825356", "numofapproved":"1", "id":"315"},
+{"last_update":"1174898980", "numofapproved":"1", "id":"485"},
+{"last_update":"1175713133", "numofapproved":"1", "id":"706"},
+{"last_update":"1175872869", "numofapproved":"1", "id":"784"},
+{"last_update":"1174301161", "numofapproved":"1", "id":"380"},
+{"last_update":"1176710519", "numofapproved":"1", "id":"1002"},
+{"last_update":"1176776871", "numofapproved":"1", "id":"1006"},
+{"last_update":"1176383102", "numofapproved":"1", "id":"901"},
+{"last_update":"1176391153", "numofapproved":"1", "id":"902"},
+{"last_update":"1176562039", "numofapproved":"1", "id":"946"},
+{"last_update":"1175713172", "numofapproved":"1", "id":"668"},
+{"last_update":"1178045208", "numofapproved":"1", "id":"1204"},
+{"last_update":"1178648231", "numofapproved":"1", "id":"1307"},
+{"last_update":"1178876638", "numofapproved":"1", "id":"1362"},
+{"last_update":"1181120419", "numofapproved":"1", "id":"2341"},
+{"last_update":"1181217997", "numofapproved":"1", "id":"2462"},
+{"last_update":"1181292688", "numofapproved":"1", "id":"2622"},
+{"last_update":"1182246090", "numofapproved":"1", "id":"3205"},
+{"last_update":"1182982710", "numofapproved":"1", "id":"3681"},
+{"last_update":"1177496084", "numofapproved":"1", "id":"1021"},
+{"last_update":"1177496190", "numofapproved":"1", "id":"1022"},
+{"last_update":"1178310654", "numofapproved":"1", "id":"1261"},
+{"last_update":"1182861963", "numofapproved":"1", "id":"3582"},
+{"last_update":"1183392466", "numofapproved":"1", "id":"3981"},
+{"last_update":"1183971409", "numofapproved":"1", "id":"4404"},
+{"last_update":"1183984082", "numofapproved":"1", "id":"4421"},
+{"last_update":"1184101764", "numofapproved":"1", "id":"4581"},
+{"last_update":"1185805036", "numofapproved":"1", "id":"5821"},
+{"last_update":"1186071563", "numofapproved":"1", "id":"6061"},
+{"last_update":"1186331614", "numofapproved":"1", "id":"6221"},
+{"last_update":"1187103429", "numofapproved":"1", "id":"6623"},
+{"last_update":"1187359405", "numofapproved":"1", "id":"6901"},
+{"last_update":"1187764462", "numofapproved":"1", "id":"7121"},
+{"last_update":"1187765742", "numofapproved":"1", "id":"7181"},
+{"last_update":"1187821663", "numofapproved":"1", "id":"7281"},
+{"last_update":"1187851593", "numofapproved":"1", "id":"7301"},
+{"last_update":"1188829369", "numofapproved":"1", "id":"8141"},
+{"last_update":"1189006834", "numofapproved":"1", "id":"8401"},
+{"last_update":"1189656411", "numofapproved":"1", "id":"8901"},
+{"last_update":"1181824325", "numofapproved":"1", "id":"2961"},
+{"last_update":"1184699326", "numofapproved":"1", "id":"4922"},
+{"last_update":"1185981618", "numofapproved":"1", "id":"5981"},
+{"last_update":"1186476979", "numofapproved":"1", "id":"6169"},
+{"last_update":"1186501212", "numofapproved":"1", "id":"6301"},
+{"last_update":"1187111728", "numofapproved":"1", "id":"6624"},
+{"last_update":"1187275194", "numofapproved":"1", "id":"6821"},
+{"last_update":"1190232587", "numofapproved":"1", "id":"9501"},
+{"last_update":"1190379779", "numofapproved":"1", "id":"9661"},
+{"last_update":"1190500551", "numofapproved":"1", "id":"9801"},
+{"last_update":"1190555711", "numofapproved":"1", "id":"9861"},
+{"last_update":"1190664200", "numofapproved":"1", "id":"10061"},
+{"last_update":"1190662067", "numofapproved":"1", "id":"10021"},
+{"last_update":"1190887692", "numofapproved":"1", "id":"10461"},
+{"last_update":"1190887880", "numofapproved":"1", "id":"10462"},
+{"last_update":"1190924576", "numofapproved":"1", "id":"10581"},
+{"last_update":"1190990748", "numofapproved":"1", "id":"10713"},
+{"last_update":"1190990297", "numofapproved":"1", "id":"10703"},
+{"last_update":"1182792178", "numofapproved":"1", "id":"3541"},
+{"last_update":"1189505682", "numofapproved":"1", "id":"8781"},
+{"last_update":"1191410630", "numofapproved":"1", "id":"11081"},
+{"last_update":"1191431148", "numofapproved":"1", "id":"11141"},
+{"last_update":"1191446393", "numofapproved":"1", "id":"11181"},
+{"last_update":"1191559326", "numofapproved":"1", "id":"11481"},
+{"last_update":"1191860159", "numofapproved":"1", "id":"11861"},
+{"last_update":"1191933842", "numofapproved":"1", "id":"11901"},
+{"last_update":"1181765760", "numofapproved":"1", "id":"2901"},
+{"last_update":"1187098770", "numofapproved":"1", "id":"6622"},
+{"last_update":"1192155125", "numofapproved":"1", "id":"12382"},
+{"last_update":"1192449036", "numofapproved":"1", "id":"12601"},
+{"last_update":"1192604489", "numofapproved":"1", "id":"12781"},
+{"last_update":"1193265229", "numofapproved":"1", "id":"13681"},
+{"last_update":"1193304550", "numofapproved":"1", "id":"13781"},
+{"last_update":"1193401945", "numofapproved":"1", "id":"14101"},
+{"last_update":"1193305327", "numofapproved":"1", "id":"13801"},
+{"last_update":"1179912412", "numofapproved":"1", "id":"1722"},
+{"last_update":"1188295203", "numofapproved":"1", "id":"7621"},
+{"last_update":"1188580008", "numofapproved":"1", "id":"7881"},
+{"last_update":"1189115708", "numofapproved":"1", "id":"8521"},
+{"last_update":"1193864375", "numofapproved":"1", "id":"14522"},
+{"last_update":"1193973963", "numofapproved":"1", "id":"14666"},
+{"last_update":"1194003054", "numofapproved":"1", "id":"14701"},
+{"last_update":"1194262755", "numofapproved":"1", "id":"14885"},
+{"last_update":"1194262860", "numofapproved":"1", "id":"14886"},
+{"last_update":"1194366475", "numofapproved":"1", "id":"15042"},
+{"last_update":"1194505568", "numofapproved":"1", "id":"15108"},
+{"last_update":"1194507434", "numofapproved":"1", "id":"15109"},
+{"last_update":"1194625505", "numofapproved":"1", "id":"15542"},
+{"last_update":"1194635569", "numofapproved":"1", "id":"15583"},
+{"last_update":"1179319405", "numofapproved":"1", "id":"1394"},
+{"last_update":"1179409867", "numofapproved":"1", "id":"1441"},
+{"last_update":"1179431647", "numofapproved":"1", "id":"1481"},
+{"last_update":"1179842302", "numofapproved":"1", "id":"1667"},
+{"last_update":"1180710254", "numofapproved":"1", "id":"2081"},
+{"last_update":"1181855583", "numofapproved":"1", "id":"3041"},
+{"last_update":"1182100211", "numofapproved":"1", "id":"3182"},
+{"last_update":"1183377220", "numofapproved":"1", "id":"3921"},
+{"last_update":"1184677615", "numofapproved":"1", "id":"4910"},
+{"last_update":"1184679060", "numofapproved":"1", "id":"4911"},
+{"last_update":"1184679348", "numofapproved":"1", "id":"4912"},
+{"last_update":"1184749371", "numofapproved":"1", "id":"4943"},
+{"last_update":"1186734180", "numofapproved":"1", "id":"6381"},
+{"last_update":"1187012463", "numofapproved":"1", "id":"6501"},
+{"last_update":"1187209404", "numofapproved":"1", "id":"6741"},
+{"last_update":"1192687257", "numofapproved":"1", "id":"12941"},
+{"last_update":"1193385868", "numofapproved":"1", "id":"13942"},
+{"last_update":"1193386346", "numofapproved":"1", "id":"13943"},
+{"last_update":"1194937571", "numofapproved":"1", "id":"16042"},
+{"last_update":"1194855975", "numofapproved":"1", "id":"15761"},
+{"last_update":"1194960221", "numofapproved":"1", "id":"16161"},
+{"last_update":"1184058679", "numofapproved":"1", "id":"4541"},
+{"last_update":"1185865315", "numofapproved":"1", "id":"5842"},
+{"last_update":"1187178780", "numofapproved":"1", "id":"6681"},
+{"last_update":"1194884625", "numofapproved":"1", "id":"15921"},
+{"last_update":"1195134032", "numofapproved":"1", "id":"16721"},
+{"last_update":"1195164570", "numofapproved":"1", "id":"16901"},
+{"last_update":"1182336429", "numofapproved":"1", "id":"3301"},
+{"last_update":"1182415670", "numofapproved":"1", "id":"3353"},
+{"last_update":"1184575801", "numofapproved":"1", "id":"4907"},
+{"last_update":"1185483718", "numofapproved":"1", "id":"5601"},
+{"last_update":"1186402874", "numofapproved":"1", "id":"6166"},
+{"last_update":"1186750969", "numofapproved":"1", "id":"6383"},
+{"last_update":"1192725360", "numofapproved":"1", "id":"13061"},
+{"last_update":"1193314911", "numofapproved":"1", "id":"13822"},
+{"last_update":"1183448275", "numofapproved":"1", "id":"4062"},
+{"last_update":"1187321039", "numofapproved":"1", "id":"6861"},
+{"last_update":"1188287578", "numofapproved":"1", "id":"7601"},
+{"last_update":"1194464420", "numofapproved":"1", "id":"15224"},
+{"last_update":"1195139641", "numofapproved":"1", "id":"16781"},
+{"last_update":"1186147124", "numofapproved":"1", "id":"6107"},
+{"last_update":"1188821750", "numofapproved":"1", "id":"8122"},
+{"last_update":"1192531864", "numofapproved":"1", "id":"12665"},
+{"last_update":"1192984220", "numofapproved":"1", "id":"13223"},
+{"last_update":"1195225246", "numofapproved":"1", "id":"16982"},
+{"last_update":"1182410787", "numofapproved":"1", "id":"3351"},
+{"last_update":"1184531419", "numofapproved":"1", "id":"4901"},
+{"last_update":"1188801472", "numofapproved":"1", "id":"8081"},
+{"last_update":"1192524288", "numofapproved":"1", "id":"12661"},
+{"last_update":"1180950691", "numofapproved":"1", "id":"2181"},
+{"last_update":"1184016732", "numofapproved":"1", "id":"4501"},
+{"last_update":"1186074085", "numofapproved":"1", "id":"6081"},
+{"last_update":"1194937650", "numofapproved":"1", "id":"16043"},
+{"last_update":"1182937178", "numofapproved":"1", "id":"3623"},
+{"last_update":"1191419601", "numofapproved":"1", "id":"11101"},
+{"last_update":"1191856562", "numofapproved":"1", "id":"11843"},
+{"last_update":"1192525042", "numofapproved":"1", "id":"12681"},
+{"last_update":"1194625494", "numofapproved":"1", "id":"15541"},
+{"last_update":"1194982850", "numofapproved":"1", "id":"16361"},
+{"last_update":"1194989219", "numofapproved":"1", "id":"16401"},
+{"last_update":"1195066723", "numofapproved":"1", "id":"16641"},
+{"last_update":"1183971226", "numofapproved":"1", "id":"4403"},
+{"last_update":"1185526866", "numofapproved":"1", "id":"5661"},
+{"last_update":"1185741495", "numofapproved":"1", "id":"5741"},
+{"last_update":"1185905429", "numofapproved":"1", "id":"5881"},
+{"last_update":"1186137969", "numofapproved":"1", "id":"6104"},
+{"last_update":"1189267536", "numofapproved":"1", "id":"8701"},
+{"last_update":"1190115042", "numofapproved":"1", "id":"9261"},
+{"last_update":"1190664258", "numofapproved":"1", "id":"10062"},
+{"last_update":"1190774949", "numofapproved":"1", "id":"10201"},
+{"last_update":"1190965042", "numofapproved":"1", "id":"10641"},
+{"last_update":"1191493379", "numofapproved":"1", "id":"11301"},
+{"last_update":"1191578051", "numofapproved":"1", "id":"11501"},
+{"last_update":"1192188840", "numofapproved":"1", "id":"12421"},
+{"last_update":"1194000252", "numofapproved":"1", "id":"14682"},
+{"last_update":"1194622556", "numofapproved":"1", "id":"15462"},
+{"last_update":"1194981068", "numofapproved":"1", "id":"16341"},
+{"last_update":"1185795733", "numofapproved":"1", "id":"5782"},
+{"last_update":"1186646854", "numofapproved":"1", "id":"6341"},
+{"last_update":"1187087291", "numofapproved":"1", "id":"6621"},
+{"last_update":"1187951800", "numofapproved":"1", "id":"7401"},
+{"last_update":"1189170373", "numofapproved":"1", "id":"8642"},
+{"last_update":"1191007934", "numofapproved":"1", "id":"10781"},
+{"last_update":"1190985695", "numofapproved":"1", "id":"10681"},
+{"last_update":"1192009758", "numofapproved":"1", "id":"12063"},
+{"last_update":"1193062543", "numofapproved":"1", "id":"13321"},
+{"last_update":"1194950304", "numofapproved":"1", "id":"16123"},
+{"last_update":"1171882085", "numofapproved":"1", "id":"90"},
+{"last_update":"1171962264", "numofapproved":"1", "id":"111"},
+{"last_update":"1172646556", "numofapproved":"1", "id":"219"},
+{"last_update":"1174040139", "numofapproved":"1", "id":"349"},
+{"last_update":"1174059263", "numofapproved":"1", "id":"355"},
+{"last_update":"1174899063", "numofapproved":"1", "id":"489"},
+{"last_update":"1173797557", "numofapproved":"1", "id":"310"},
+{"last_update":"1174735191", "numofapproved":"1", "id":"468"},
+{"last_update":"1174899259", "numofapproved":"1", "id":"499"},
+{"last_update":"1174899354", "numofapproved":"1", "id":"502"},
+{"last_update":"1175254120", "numofapproved":"1", "id":"562"},
+{"last_update":"1171126391", "numofapproved":"1", "id":"4"},
+{"last_update":"1171800381", "numofapproved":"1", "id":"82"},
+{"last_update":"1171799224", "numofapproved":"1", "id":"75"},
+{"last_update":"1171972550", "numofapproved":"1", "id":"123"},
+{"last_update":"1174301165", "numofapproved":"1", "id":"381"},
+{"last_update":"1171904847", "numofapproved":"1", "id":"103"},
+{"last_update":"1172260956", "numofapproved":"1", "id":"190"},
+{"last_update":"1172803368", "numofapproved":"1", "id":"234"},
+{"last_update":"1173199576", "numofapproved":"1", "id":"250"},
+{"last_update":"1173206201", "numofapproved":"1", "id":"252"},
+{"last_update":"1175258941", "numofapproved":"1", "id":"563"},
+{"last_update":"1176232231", "numofapproved":"1", "id":"825"},
+{"last_update":"1176475088", "numofapproved":"1", "id":"921"},
+{"last_update":"1172082181", "numofapproved":"1", "id":"166"},
+{"last_update":"1172595205", "numofapproved":"1", "id":"216"},
+{"last_update":"1174898892", "numofapproved":"1", "id":"481"},
+{"last_update":"1174899696", "numofapproved":"1", "id":"518"},
+{"last_update":"1174924777", "numofapproved":"1", "id":"525"},
+{"last_update":"1175598588", "numofapproved":"1", "id":"682"},
+{"last_update":"1175602572", "numofapproved":"1", "id":"683"},
+{"last_update":"1175707879", "numofapproved":"1", "id":"666"},
+{"last_update":"1175710528", "numofapproved":"1", "id":"703"},
+{"last_update":"1175715728", "numofapproved":"1", "id":"707"},
+{"last_update":"1176137267", "numofapproved":"1", "id":"806"},
+{"last_update":"1176306491", "numofapproved":"1", "id":"883"},
+{"last_update":"1172069972", "numofapproved":"1", "id":"134"},
+{"last_update":"1173889144", "numofapproved":"1", "id":"324"},
+{"last_update":"1175502804", "numofapproved":"1", "id":"623"},
+{"last_update":"1175772530", "numofapproved":"1", "id":"711"},
+{"last_update":"1176297526", "numofapproved":"1", "id":"861"},
+{"last_update":"1171445818", "numofapproved":"1", "id":"47"},
+{"last_update":"1171884505", "numofapproved":"1", "id":"92"},
+{"last_update":"1172250708", "numofapproved":"1", "id":"187"},
+{"last_update":"1173749631", "numofapproved":"1", "id":"307"},
+{"last_update":"1173889164", "numofapproved":"1", "id":"325"},
+{"last_update":"1174301168", "numofapproved":"1", "id":"382"},
+{"last_update":"1171904807", "numofapproved":"1", "id":"101"},
+{"last_update":"1171970405", "numofapproved":"1", "id":"120"},
+{"last_update":"1172218677", "numofapproved":"1", "id":"179"},
+{"last_update":"1173125028", "numofapproved":"1", "id":"248"},
+{"last_update":"1171978122", "numofapproved":"1", "id":"126"},
+{"last_update":"1172676736", "numofapproved":"1", "id":"226"},
+{"last_update":"1173975473", "numofapproved":"1", "id":"344"},
+{"last_update":"1172072582", "numofapproved":"1", "id":"165"},
+{"last_update":"1173888774", "numofapproved":"1", "id":"322"},
+{"last_update":"1174560347", "numofapproved":"1", "id":"422"},
+{"last_update":"1174899242", "numofapproved":"1", "id":"498"},
+{"last_update":"1174735110", "numofapproved":"1", "id":"466"},
+{"last_update":"1176735630", "numofapproved":"1", "id":"1004"},
+{"last_update":"1175725931", "numofapproved":"1", "id":"670"},
+{"last_update":"1176498072", "numofapproved":"1", "id":"944"},
+{"last_update":"1178264233", "numofapproved":"1", "id":"1241"},
+{"last_update":"1178746727", "numofapproved":"1", "id":"1350"},
+{"last_update":"1178798992", "numofapproved":"1", "id":"1352"},
+{"last_update":"1180011647", "numofapproved":"1", "id":"1649"},
+{"last_update":"1180430823", "numofapproved":"1", "id":"1901"},
+{"last_update":"1180649952", "numofapproved":"1", "id":"2021"},
+{"last_update":"1180966506", "numofapproved":"1", "id":"2183"},
+{"last_update":"1180987142", "numofapproved":"1", "id":"2241"},
+{"last_update":"1181127788", "numofapproved":"1", "id":"2322"},
+{"last_update":"1181217668", "numofapproved":"1", "id":"2461"},
+{"last_update":"1182789542", "numofapproved":"1", "id":"3522"},
+{"last_update":"1182851714", "numofapproved":"1", "id":"3581"},
+{"last_update":"1179268837", "numofapproved":"1", "id":"1407"},
+{"last_update":"1179999486", "numofapproved":"1", "id":"1645"},
+{"last_update":"1180019568", "numofapproved":"1", "id":"1653"},
+{"last_update":"1180082061", "numofapproved":"1", "id":"1821"},
+{"last_update":"1184181871", "numofapproved":"1", "id":"4642"},
+{"last_update":"1184251955", "numofapproved":"1", "id":"4741"},
+{"last_update":"1184346893", "numofapproved":"1", "id":"4841"},
+{"last_update":"1184773981", "numofapproved":"1", "id":"5001"},
+{"last_update":"1185272905", "numofapproved":"1", "id":"5281"},
+{"last_update":"1185484083", "numofapproved":"1", "id":"5622"},
+{"last_update":"1185897961", "numofapproved":"1", "id":"5861"},
+{"last_update":"1186951708", "numofapproved":"1", "id":"6462"},
+{"last_update":"1187596311", "numofapproved":"1", "id":"6941"},
+{"last_update":"1187766852", "numofapproved":"1", "id":"7201"},
+{"last_update":"1188158133", "numofapproved":"1", "id":"7481"},
+{"last_update":"1188233835", "numofapproved":"1", "id":"7501"},
+{"last_update":"1188269273", "numofapproved":"1", "id":"7561"},
+{"last_update":"1177672684", "numofapproved":"1", "id":"1141"},
+{"last_update":"1178042016", "numofapproved":"1", "id":"1222"},
+{"last_update":"1181646022", "numofapproved":"1", "id":"2801"},
+{"last_update":"1181853920", "numofapproved":"1", "id":"3021"},
+{"last_update":"1183715836", "numofapproved":"1", "id":"4241"},
+{"last_update":"1183726859", "numofapproved":"1", "id":"4281"},
+{"last_update":"1189860355", "numofapproved":"1", "id":"9101"},
+{"last_update":"1189871747", "numofapproved":"1", "id":"9141"},
+{"last_update":"1190380660", "numofapproved":"1", "id":"9681"},
+{"last_update":"1190510808", "numofapproved":"1", "id":"9821"},
+{"last_update":"1190542013", "numofapproved":"1", "id":"9843"},
+{"last_update":"1190665412", "numofapproved":"1", "id":"10081"},
+{"last_update":"1190299519", "numofapproved":"1", "id":"9601"},
+{"last_update":"1191410594", "numofapproved":"1", "id":"11063"},
+{"last_update":"1191505786", "numofapproved":"1", "id":"11341"},
+{"last_update":"1191583652", "numofapproved":"1", "id":"11522"},
+{"last_update":"1191599712", "numofapproved":"1", "id":"11681"},
+{"last_update":"1191602931", "numofapproved":"1", "id":"11721"},
+{"last_update":"1191762572", "numofapproved":"1", "id":"11761"},
+{"last_update":"1191856256", "numofapproved":"1", "id":"11841"},
+{"last_update":"1191937041", "numofapproved":"1", "id":"11921"},
+{"last_update":"1179325639", "numofapproved":"1", "id":"1409"},
+{"last_update":"1179912165", "numofapproved":"1", "id":"1721"},
+{"last_update":"1181119430", "numofapproved":"1", "id":"2321"},
+{"last_update":"1184696743", "numofapproved":"1", "id":"4921"},
+{"last_update":"1192154847", "numofapproved":"1", "id":"12361"},
+{"last_update":"1192237071", "numofapproved":"1", "id":"12501"},
+{"last_update":"1178637394", "numofapproved":"1", "id":"1304"},
+{"last_update":"1178716778", "numofapproved":"1", "id":"1344"},
+{"last_update":"1182937057", "numofapproved":"1", "id":"3622"},
+{"last_update":"1183113642", "numofapproved":"1", "id":"3781"},
+{"last_update":"1183995467", "numofapproved":"1", "id":"4461"},
+{"last_update":"1184223331", "numofapproved":"1", "id":"4721"},
+{"last_update":"1190990692", "numofapproved":"1", "id":"10711"},
+{"last_update":"1193269310", "numofapproved":"1", "id":"13761"},
+{"last_update":"1193735756", "numofapproved":"1", "id":"14441"},
+{"last_update":"1194635738", "numofapproved":"1", "id":"15603"},
+{"last_update":"1194901721", "numofapproved":"1", "id":"15961"},
+{"last_update":"1194949951", "numofapproved":"1", "id":"16141"},
+{"last_update":"1194960695", "numofapproved":"1", "id":"16182"},
+{"last_update":"1194973974", "numofapproved":"1", "id":"16221"},
+{"last_update":"1194946810", "numofapproved":"1", "id":"16102"},
+{"last_update":"1194977452", "numofapproved":"1", "id":"16261"},
+{"last_update":"1195040385", "numofapproved":"1", "id":"16461"},
+{"last_update":"1195053483", "numofapproved":"1", "id":"16561"},
+{"last_update":"1195053518", "numofapproved":"1", "id":"16562"},
+{"last_update":"1195218698", "numofapproved":"1", "id":"16921"},
+{"last_update":"1195225049", "numofapproved":"1", "id":"16961"},
+{"last_update":"1195164270", "numofapproved":"1", "id":"16881"},
+{"last_update":"1195080947", "numofapproved":"1", "id":"16681"},
+{"last_update":"1195469884", "numofapproved":"1", "id":"17181"},
+{"last_update":"1185314804", "numofapproved":"1", "id":"5381"},
+{"last_update":"1188401767", "numofapproved":"1", "id":"7721"},
+{"last_update":"1190286841", "numofapproved":"1", "id":"9582"},
+{"last_update":"1190733096", "numofapproved":"1", "id":"10141"},
+{"last_update":"1190847451", "numofapproved":"1", "id":"10422"},
+{"last_update":"1190990526", "numofapproved":"1", "id":"10707"},
+{"last_update":"1192009711", "numofapproved":"1", "id":"12061"},
+{"last_update":"1192155478", "numofapproved":"1", "id":"12362"},
+{"last_update":"1192468382", "numofapproved":"1", "id":"12641"},
+{"last_update":"1193332032", "numofapproved":"1", "id":"13881"},
+{"last_update":"1195497290", "numofapproved":"1", "id":"17321"},
+{"last_update":"1195519935", "numofapproved":"1", "id":"17441"},
+{"last_update":"1195549826", "numofapproved":"1", "id":"17521"},
+{"last_update":"1177668131", "numofapproved":"1", "id":"1101"},
+{"last_update":"1186835348", "numofapproved":"1", "id":"6421"},
+{"last_update":"1191057903", "numofapproved":"1", "id":"10802"},
+{"last_update":"1193973906", "numofapproved":"1", "id":"14665"},
+{"last_update":"1171904780", "numofapproved":"1", "id":"100"},
+{"last_update":"1172677750", "numofapproved":"1", "id":"227"},
+{"last_update":"1172686704", "numofapproved":"1", "id":"229"},
+{"last_update":"1173101684", "numofapproved":"1", "id":"245"},
+{"last_update":"1173466151", "numofapproved":"1", "id":"282"},
+{"last_update":"1174301263", "numofapproved":"1", "id":"386"},
+{"last_update":"1174302366", "numofapproved":"1", "id":"399"},
+{"last_update":"1174501294", "numofapproved":"1", "id":"421"},
+{"last_update":"1174899635", "numofapproved":"1", "id":"515"},
+{"last_update":"1174924556", "numofapproved":"1", "id":"523"},
+{"last_update":"1175141200", "numofapproved":"1", "id":"541"},
+{"last_update":"1171799271", "numofapproved":"1", "id":"76"},
+{"last_update":"1171900163", "numofapproved":"1", "id":"97"},
+{"last_update":"1174301267", "numofapproved":"1", "id":"387"},
+{"last_update":"1174735156", "numofapproved":"1", "id":"467"},
+{"last_update":"1174899569", "numofapproved":"1", "id":"512"},
+{"last_update":"1174926970", "numofapproved":"1", "id":"531"},
+{"last_update":"1175502757", "numofapproved":"1", "id":"602"},
+{"last_update":"1175603425", "numofapproved":"1", "id":"663"},
+{"last_update":"1176194967", "numofapproved":"1", "id":"822"},
+{"last_update":"1171800398", "numofapproved":"1", "id":"83"},
+{"last_update":"1171968376", "numofapproved":"1", "id":"118"},
+{"last_update":"1172070063", "numofapproved":"1", "id":"135"},
+{"last_update":"1173821159", "numofapproved":"1", "id":"314"},
+{"last_update":"1176559052", "numofapproved":"1", "id":"964"},
+{"last_update":"1171299245", "numofapproved":"1", "id":"23"},
+{"last_update":"1171535160", "numofapproved":"1", "id":"57"},
+{"last_update":"1171564542", "numofapproved":"1", "id":"65"},
+{"last_update":"1172646592", "numofapproved":"1", "id":"220"},
+{"last_update":"1174899489", "numofapproved":"1", "id":"507"},
+{"last_update":"1174924890", "numofapproved":"1", "id":"528"},
+{"last_update":"1175687005", "numofapproved":"1", "id":"701"},
+{"last_update":"1176132888", "numofapproved":"1", "id":"805"},
+{"last_update":"1171286610", "numofapproved":"1", "id":"21"},
+{"last_update":"1172184441", "numofapproved":"1", "id":"176"},
+{"last_update":"1172187221", "numofapproved":"1", "id":"178"},
+{"last_update":"1173386668", "numofapproved":"1", "id":"261"},
+{"last_update":"1173809115", "numofapproved":"1", "id":"312"},
+{"last_update":"1175609126", "numofapproved":"1", "id":"685"},
+{"last_update":"1175791369", "numofapproved":"1", "id":"712"},
+{"last_update":"1176480434", "numofapproved":"1", "id":"942"},
+{"last_update":"1171503567", "numofapproved":"1", "id":"56"},
+{"last_update":"1171799204", "numofapproved":"1", "id":"74"},
+{"last_update":"1172236765", "numofapproved":"1", "id":"183"},
+{"last_update":"1175598013", "numofapproved":"1", "id":"681"},
+{"last_update":"1175610956", "numofapproved":"1", "id":"687"},
+{"last_update":"1175725436", "numofapproved":"1", "id":"710"},
+{"last_update":"1171905052", "numofapproved":"1", "id":"105"},
+{"last_update":"1172268920", "numofapproved":"1", "id":"191"},
+{"last_update":"1173264110", "numofapproved":"1", "id":"256"},
+{"last_update":"1173889179", "numofapproved":"1", "id":"326"},
+{"last_update":"1174301066", "numofapproved":"1", "id":"378"},
+{"last_update":"1174300399", "numofapproved":"1", "id":"366"},
+{"last_update":"1174387980", "numofapproved":"1", "id":"400"},
+{"last_update":"1176823766", "numofapproved":"1", "id":"1007"},
+{"last_update":"1171970585", "numofapproved":"1", "id":"122"},
+{"last_update":"1172071500", "numofapproved":"1", "id":"145"},
+{"last_update":"1172580279", "numofapproved":"1", "id":"211"},
+{"last_update":"1172658493", "numofapproved":"1", "id":"221"},
+{"last_update":"1174301611", "numofapproved":"1", "id":"397"},
+{"last_update":"1176900132", "numofapproved":"1", "id":"989"},
+{"last_update":"1171965754", "numofapproved":"1", "id":"114"},
+{"last_update":"1173797482", "numofapproved":"1", "id":"309"},
+{"last_update":"1174300513", "numofapproved":"1", "id":"367"},
+{"last_update":"1174301493", "numofapproved":"1", "id":"395"},
+{"last_update":"1174899124", "numofapproved":"1", "id":"492"},
+{"last_update":"1174899677", "numofapproved":"1", "id":"517"},
+{"last_update":"1174924235", "numofapproved":"1", "id":"522"},
+{"last_update":"1174925568", "numofapproved":"1", "id":"529"},
+{"last_update":"1174933088", "numofapproved":"1", "id":"533"},
+{"last_update":"1174933338", "numofapproved":"1", "id":"538"},
+{"last_update":"1174044629", "numofapproved":"1", "id":"352"},
+{"last_update":"1175713207", "numofapproved":"1", "id":"669"},
+{"last_update":"1178339569", "numofapproved":"1", "id":"1262"},
+{"last_update":"1178611427", "numofapproved":"1", "id":"1303"},
+{"last_update":"1178707269", "numofapproved":"1", "id":"1341"},
+{"last_update":"1179411388", "numofapproved":"1", "id":"1461"},
+{"last_update":"1180000879", "numofapproved":"1", "id":"1648"},
+{"last_update":"1180097993", "numofapproved":"1", "id":"1657"},
+{"last_update":"1180107947", "numofapproved":"1", "id":"1659"},
+{"last_update":"1180515935", "numofapproved":"1", "id":"1922"},
+{"last_update":"1180712418", "numofapproved":"1", "id":"2102"},
+{"last_update":"1180731895", "numofapproved":"1", "id":"2063"},
+{"last_update":"1180731763", "numofapproved":"1", "id":"2143"},
+{"last_update":"1180951519", "numofapproved":"1", "id":"2201"},
+{"last_update":"1180954763", "numofapproved":"1", "id":"2182"},
+{"last_update":"1181134185", "numofapproved":"1", "id":"2361"},
+{"last_update":"1181206368", "numofapproved":"1", "id":"2441"},
+{"last_update":"1181207556", "numofapproved":"1", "id":"2442"},
+{"last_update":"1183065868", "numofapproved":"1", "id":"3741"},
+{"last_update":"1183124436", "numofapproved":"1", "id":"3822"},
+{"last_update":"1183118631", "numofapproved":"1", "id":"3802"},
+{"last_update":"1183515629", "numofapproved":"1", "id":"4144"},
+{"last_update":"1184169495", "numofapproved":"1", "id":"4621"},
+{"last_update":"1184777700", "numofapproved":"1", "id":"5021"},
+{"last_update":"1185371099", "numofapproved":"1", "id":"5441"},
+{"last_update":"1185460060", "numofapproved":"1", "id":"5521"},
+{"last_update":"1185462514", "numofapproved":"1", "id":"5541"},
+{"last_update":"1185573050", "numofapproved":"1", "id":"5721"},
+{"last_update":"1185795586", "numofapproved":"1", "id":"5781"},
+{"last_update":"1185962181", "numofapproved":"1", "id":"5901"},
+{"last_update":"1185987024", "numofapproved":"1", "id":"6001"},
+{"last_update":"1186138150", "numofapproved":"1", "id":"6105"},
+{"last_update":"1186500528", "numofapproved":"1", "id":"6281"},
+{"last_update":"1187765075", "numofapproved":"1", "id":"7141"},
+{"last_update":"1188158263", "numofapproved":"1", "id":"7482"},
+{"last_update":"1189094579", "numofapproved":"1", "id":"8461"},
+{"last_update":"1189327635", "numofapproved":"1", "id":"8721"},
+{"last_update":"1182356521", "numofapproved":"1", "id":"3344"},
+{"last_update":"1185017921", "numofapproved":"1", "id":"5161"},
+{"last_update":"1185271167", "numofapproved":"1", "id":"5261"},
+{"last_update":"1190663796", "numofapproved":"1", "id":"10041"},
+{"last_update":"1190726728", "numofapproved":"1", "id":"10121"},
+{"last_update":"1190801144", "numofapproved":"1", "id":"10241"},
+{"last_update":"1190894441", "numofapproved":"1", "id":"10502"},
+{"last_update":"1190973098", "numofapproved":"1", "id":"10667"},
+{"last_update":"1190925124", "numofapproved":"1", "id":"10584"},
+{"last_update":"1191249884", "numofapproved":"1", "id":"10961"},
+{"last_update":"1187732431", "numofapproved":"1", "id":"7081"},
+{"last_update":"1189259179", "numofapproved":"1", "id":"8681"},
+{"last_update":"1191446517", "numofapproved":"1", "id":"11183"},
+{"last_update":"1191510643", "numofapproved":"1", "id":"11381"},
+{"last_update":"1191529640", "numofapproved":"1", "id":"11421"},
+{"last_update":"1191588726", "numofapproved":"1", "id":"11602"},
+{"last_update":"1191903050", "numofapproved":"1", "id":"11881"},
+{"last_update":"1181218459", "numofapproved":"1", "id":"2464"},
+{"last_update":"1187024536", "numofapproved":"1", "id":"6581"},
+{"last_update":"1192009094", "numofapproved":"1", "id":"12041"},
+{"last_update":"1192064048", "numofapproved":"1", "id":"12183"},
+{"last_update":"1192061973", "numofapproved":"1", "id":"12181"},
+{"last_update":"1193026780", "numofapproved":"1", "id":"13241"},
+{"last_update":"1193416409", "numofapproved":"1", "id":"14161"},
+{"last_update":"1186992495", "numofapproved":"1", "id":"6481"},
+{"last_update":"1191410811", "numofapproved":"1", "id":"11066"},
+{"last_update":"1193440748", "numofapproved":"1", "id":"14241"},
+{"last_update":"1194252005", "numofapproved":"1", "id":"14884"},
+{"last_update":"1194362364", "numofapproved":"1", "id":"14889"},
+{"last_update":"1179240103", "numofapproved":"1", "id":"1389"},
+{"last_update":"1181812262", "numofapproved":"1", "id":"2922"},
+{"last_update":"1182093916", "numofapproved":"1", "id":"3181"},
+{"last_update":"1182767688", "numofapproved":"1", "id":"3501"},
+{"last_update":"1184181747", "numofapproved":"1", "id":"4661"},
+{"last_update":"1186505570", "numofapproved":"1", "id":"6170"},
+{"last_update":"1186751068", "numofapproved":"1", "id":"6384"},
+{"last_update":"1187558925", "numofapproved":"1", "id":"6921"},
+{"last_update":"1188037477", "numofapproved":"1", "id":"7424"},
+{"last_update":"1194937530", "numofapproved":"1", "id":"16041"},
+{"last_update":"1179754250", "numofapproved":"1", "id":"1562"},
+{"last_update":"1183416194", "numofapproved":"1", "id":"4021"},
+{"last_update":"1185835616", "numofapproved":"1", "id":"5841"},
+{"last_update":"1192731190", "numofapproved":"1", "id":"13141"},
+{"last_update":"1193178120", "numofapproved":"1", "id":"13523"},
+{"last_update":"1193844805", "numofapproved":"1", "id":"14503"},
+{"last_update":"1193909242", "numofapproved":"1", "id":"14525"},
+{"last_update":"1195474767", "numofapproved":"1", "id":"17221"},
+{"last_update":"1177690781", "numofapproved":"1", "id":"1142"},
+{"last_update":"1185373614", "numofapproved":"1", "id":"5461"},
+{"last_update":"1192520088", "numofapproved":"1", "id":"12624"},
+{"last_update":"1193194444", "numofapproved":"1", "id":"13527"},
+{"last_update":"1193387684", "numofapproved":"1", "id":"13950"},
+{"last_update":"1193388786", "numofapproved":"1", "id":"13952"},
+{"last_update":"1194616895", "numofapproved":"1", "id":"15401"},
+{"last_update":"1195034817", "numofapproved":"1", "id":"16441"},
+{"last_update":"1183107374", "numofapproved":"1", "id":"3761"},
+{"last_update":"1183515040", "numofapproved":"1", "id":"4121"},
+{"last_update":"1184744160", "numofapproved":"1", "id":"4942"},
+{"last_update":"1192094830", "numofapproved":"1", "id":"12201"},
+{"last_update":"1193314411", "numofapproved":"1", "id":"13821"},
+{"last_update":"1193391901", "numofapproved":"1", "id":"13957"},
+{"last_update":"1193399824", "numofapproved":"1", "id":"14043"},
+{"last_update":"1194450353", "numofapproved":"1", "id":"15181"},
+{"last_update":"1194474719", "numofapproved":"1", "id":"15241"},
+{"last_update":"1194622799", "numofapproved":"1", "id":"15481"},
+{"last_update":"1194880827", "numofapproved":"1", "id":"15901"},
+{"last_update":"1182363929", "numofapproved":"1", "id":"3347"},
+{"last_update":"1182952243", "numofapproved":"1", "id":"3642"},
+{"last_update":"1183386876", "numofapproved":"1", "id":"3962"},
+{"last_update":"1193178314", "numofapproved":"1", "id":"13524"},
+{"last_update":"1195376577", "numofapproved":"1", "id":"17061"},
+{"last_update":"1179832847", "numofapproved":"1", "id":"1621"},
+{"last_update":"1184053269", "numofapproved":"1", "id":"4521"},
+{"last_update":"1185024744", "numofapproved":"1", "id":"5181"},
+{"last_update":"1186130324", "numofapproved":"1", "id":"6101"},
+{"last_update":"1192529640", "numofapproved":"1", "id":"12662"},
+{"last_update":"1193158482", "numofapproved":"1", "id":"13521"},
+{"last_update":"1194247788", "numofapproved":"1", "id":"14883"},
+{"last_update":"1182363717", "numofapproved":"1", "id":"3346"},
+{"last_update":"1193386824", "numofapproved":"1", "id":"13944"},
+{"last_update":"1193844655", "numofapproved":"1", "id":"14502"},
+{"last_update":"1180732326", "numofapproved":"1", "id":"2064"},
+{"last_update":"1182247493", "numofapproved":"1", "id":"3222"},
+{"last_update":"1183515318", "numofapproved":"1", "id":"4143"},
+{"last_update":"1184840285", "numofapproved":"1", "id":"5061"},
+{"last_update":"1188458821", "numofapproved":"1", "id":"7741"},
+{"last_update":"1188919582", "numofapproved":"1", "id":"8241"},
+{"last_update":"1190990231", "numofapproved":"1", "id":"10701"},
+{"last_update":"1190990557", "numofapproved":"1", "id":"10708"},
+{"last_update":"1191583611", "numofapproved":"1", "id":"11521"},
+{"last_update":"1192031263", "numofapproved":"1", "id":"12102"},
+{"last_update":"1192431349", "numofapproved":"1", "id":"12563"},
+{"last_update":"1192608972", "numofapproved":"1", "id":"12801"},
+{"last_update":"1193244196", "numofapproved":"1", "id":"13641"},
+{"last_update":"1193733530", "numofapproved":"1", "id":"14422"},
+{"last_update":"1194988770", "numofapproved":"1", "id":"16381"},
+{"last_update":"1195050890", "numofapproved":"1", "id":"16541"},
+{"last_update":"1195047262", "numofapproved":"1", "id":"16502"},
+{"last_update":"1195221672", "numofapproved":"1", "id":"16941"},
+{"last_update":"1195400016", "numofapproved":"1", "id":"17103"},
+{"last_update":"1178716622", "numofapproved":"1", "id":"1343"},
+{"last_update":"1183563126", "numofapproved":"1", "id":"4181"},
+{"last_update":"1183970953", "numofapproved":"1", "id":"4402"},
+{"last_update":"1190149151", "numofapproved":"1", "id":"9381"},
+{"last_update":"1190628937", "numofapproved":"1", "id":"9921"},
+{"last_update":"1190908511", "numofapproved":"1", "id":"10521"},
+{"last_update":"1191365468", "numofapproved":"1", "id":"11021"},
+{"last_update":"1192431054", "numofapproved":"1", "id":"12561"},
+{"last_update":"1188938163", "numofapproved":"1", "id":"8281"},
+{"last_update":"1192155298", "numofapproved":"1", "id":"12383"},
+{"last_update":"1193223714", "numofapproved":"1", "id":"13561"},
+{"last_update":"1171799359", "numofapproved":"1", "id":"80"},
+{"last_update":"1171962550", "numofapproved":"1", "id":"112"},
+{"last_update":"1171965210", "numofapproved":"1", "id":"113"},
+{"last_update":"1171980888", "numofapproved":"1", "id":"128"},
+{"last_update":"1174299174", "numofapproved":"1", "id":"361"},
+{"last_update":"1174301053", "numofapproved":"1", "id":"376"},
+{"last_update":"1174899661", "numofapproved":"1", "id":"516"},
+{"last_update":"1172646493", "numofapproved":"1", "id":"218"},
+{"last_update":"1174899018", "numofapproved":"1", "id":"487"},
+{"last_update":"1175091201", "numofapproved":"1", "id":"540"},
+{"last_update":"1175267243", "numofapproved":"1", "id":"564"},
+{"last_update":"1176293117", "numofapproved":"1", "id":"826"},
+{"last_update":"1171602873", "numofapproved":"1", "id":"67"},
+{"last_update":"1172568714", "numofapproved":"1", "id":"210"},
+{"last_update":"1174300556", "numofapproved":"1", "id":"369"},
+{"last_update":"1174301614", "numofapproved":"1", "id":"398"},
+{"last_update":"1174429050", "numofapproved":"1", "id":"404"},
+{"last_update":"1175547821", "numofapproved":"1", "id":"641"},
+{"last_update":"1175696551", "numofapproved":"1", "id":"702"},
+{"last_update":"1176223342", "numofapproved":"1", "id":"823"},
+{"last_update":"1176459077", "numofapproved":"1", "id":"905"},
+{"last_update":"1172169117", "numofapproved":"1", "id":"172"},
+{"last_update":"1172259821", "numofapproved":"1", "id":"189"},
+{"last_update":"1172847347", "numofapproved":"1", "id":"237"},
+{"last_update":"1176485274", "numofapproved":"1", "id":"961"},
+{"last_update":"1176739199", "numofapproved":"1", "id":"983"},
+{"last_update":"1171710108", "numofapproved":"1", "id":"72"},
+{"last_update":"1172147854", "numofapproved":"1", "id":"170"},
+{"last_update":"1172178657", "numofapproved":"1", "id":"173"},
+{"last_update":"1174933210", "numofapproved":"1", "id":"535"},
+{"last_update":"1175502973", "numofapproved":"1", "id":"626"},
+{"last_update":"1172071610", "numofapproved":"1", "id":"146"},
+{"last_update":"1172847402", "numofapproved":"1", "id":"240"},
+{"last_update":"1173282970", "numofapproved":"1", "id":"258"},
+{"last_update":"1175502729", "numofapproved":"1", "id":"621"},
+{"last_update":"1173889203", "numofapproved":"1", "id":"327"},
+{"last_update":"1174301604", "numofapproved":"1", "id":"396"},
+{"last_update":"1176738556", "numofapproved":"1", "id":"1005"},
+{"last_update":"1171287066", "numofapproved":"1", "id":"22"},
+{"last_update":"1171388951", "numofapproved":"1", "id":"46"},
+{"last_update":"1171645099", "numofapproved":"1", "id":"70"},
+{"last_update":"1174301489", "numofapproved":"1", "id":"394"},
+{"last_update":"1176109438", "numofapproved":"1", "id":"804"},
+{"last_update":"1173203622", "numofapproved":"1", "id":"251"},
+{"last_update":"1174300337", "numofapproved":"1", "id":"364"},
+{"last_update":"1174898999", "numofapproved":"1", "id":"486"},
+{"last_update":"1174899221", "numofapproved":"1", "id":"497"},
+{"last_update":"1174899505", "numofapproved":"1", "id":"508"},
+{"last_update":"1171905996", "numofapproved":"1", "id":"106"},
+{"last_update":"1172003938", "numofapproved":"1", "id":"131"},
+{"last_update":"1172134183", "numofapproved":"1", "id":"167"},
+{"last_update":"1178550080", "numofapproved":"1", "id":"1301"},
+{"last_update":"1178718229", "numofapproved":"1", "id":"1346"},
+{"last_update":"1178725187", "numofapproved":"1", "id":"1322"},
+{"last_update":"1179302219", "numofapproved":"1", "id":"1392"},
+{"last_update":"1180015260", "numofapproved":"1", "id":"1650"},
+{"last_update":"1180088452", "numofapproved":"1", "id":"1656"},
+{"last_update":"1180719498", "numofapproved":"1", "id":"2121"},
+{"last_update":"1180731930", "numofapproved":"1", "id":"2145"},
+{"last_update":"1180731601", "numofapproved":"1", "id":"2142"},
+{"last_update":"1181034337", "numofapproved":"1", "id":"2281"},
+{"last_update":"1181222113", "numofapproved":"1", "id":"2501"},
+{"last_update":"1181254636", "numofapproved":"1", "id":"2601"},
+{"last_update":"1181578682", "numofapproved":"1", "id":"2762"},
+{"last_update":"1181731051", "numofapproved":"1", "id":"2881"},
+{"last_update":"1177673345", "numofapproved":"1", "id":"1162"},
+{"last_update":"1183741680", "numofapproved":"1", "id":"4301"},
+{"last_update":"1183988623", "numofapproved":"1", "id":"4441"},
+{"last_update":"1184217947", "numofapproved":"1", "id":"4701"},
+{"last_update":"1186260146", "numofapproved":"1", "id":"6181"},
+{"last_update":"1186289860", "numofapproved":"1", "id":"6163"},
+{"last_update":"1186235477", "numofapproved":"1", "id":"6161"},
+{"last_update":"1186508996", "numofapproved":"1", "id":"6171"},
+{"last_update":"1187626570", "numofapproved":"1", "id":"6961"},
+{"last_update":"1187713755", "numofapproved":"1", "id":"7041"},
+{"last_update":"1187769208", "numofapproved":"1", "id":"7222"},
+{"last_update":"1187856827", "numofapproved":"1", "id":"7341"},
+{"last_update":"1188053850", "numofapproved":"1", "id":"7461"},
+{"last_update":"1188264856", "numofapproved":"1", "id":"7541"},
+{"last_update":"1188319841", "numofapproved":"1", "id":"7681"},
+{"last_update":"1188582632", "numofapproved":"1", "id":"7901"},
+{"last_update":"1188734330", "numofapproved":"1", "id":"8001"},
+{"last_update":"1189003562", "numofapproved":"1", "id":"8381"},
+{"last_update":"1179787121", "numofapproved":"1", "id":"1581"},
+{"last_update":"1181998896", "numofapproved":"1", "id":"3121"},
+{"last_update":"1182274782", "numofapproved":"1", "id":"3261"},
+{"last_update":"1186350397", "numofapproved":"1", "id":"6241"},
+{"last_update":"1187354512", "numofapproved":"1", "id":"6881"},
+{"last_update":"1188918086", "numofapproved":"1", "id":"8221"},
+{"last_update":"1190392989", "numofapproved":"1", "id":"9721"},
+{"last_update":"1190925022", "numofapproved":"1", "id":"10583"},
+{"last_update":"1190959571", "numofapproved":"1", "id":"10601"},
+{"last_update":"1190990357", "numofapproved":"1", "id":"10705"},
+{"last_update":"1190990656", "numofapproved":"1", "id":"10710"},
+{"last_update":"1191226364", "numofapproved":"1", "id":"10921"},
+{"last_update":"1180011741", "numofapproved":"1", "id":"1761"},
+{"last_update":"1180533694", "numofapproved":"1", "id":"1961"},
+{"last_update":"1180731839", "numofapproved":"1", "id":"2144"},
+{"last_update":"1181461876", "numofapproved":"1", "id":"2681"},
+{"last_update":"1181855690", "numofapproved":"1", "id":"3061"},
+{"last_update":"1189537687", "numofapproved":"1", "id":"8821"},
+{"last_update":"1189937430", "numofapproved":"1", "id":"9161"},
+{"last_update":"1190803903", "numofapproved":"1", "id":"10261"},
+{"last_update":"1190973051", "numofapproved":"1", "id":"10664"},
+{"last_update":"1191410739", "numofapproved":"1", "id":"11064"},
+{"last_update":"1191426697", "numofapproved":"1", "id":"11121"},
+{"last_update":"1191446459", "numofapproved":"1", "id":"11182"},
+{"last_update":"1191450891", "numofapproved":"1", "id":"11201"},
+{"last_update":"1191550000", "numofapproved":"1", "id":"11441"},
+{"last_update":"1191588714", "numofapproved":"1", "id":"11601"},
+{"last_update":"1191596815", "numofapproved":"1", "id":"11641"},
+{"last_update":"1191647971", "numofapproved":"1", "id":"11741"},
+{"last_update":"1191949660", "numofapproved":"1", "id":"11981"},
+{"last_update":"1180641844", "numofapproved":"1", "id":"2001"},
+{"last_update":"1188319710", "numofapproved":"1", "id":"7661"},
+{"last_update":"1189169640", "numofapproved":"1", "id":"8621"},
+{"last_update":"1192028009", "numofapproved":"1", "id":"12081"},
+{"last_update":"1192116783", "numofapproved":"1", "id":"12261"},
+{"last_update":"1192558715", "numofapproved":"1", "id":"12741"},
+{"last_update":"1192727702", "numofapproved":"1", "id":"13101"},
+{"last_update":"1193035517", "numofapproved":"1", "id":"13262"},
+{"last_update":"1193080239", "numofapproved":"1", "id":"13381"},
+{"last_update":"1193268912", "numofapproved":"1", "id":"13722"},
+{"last_update":"1193386894", "numofapproved":"1", "id":"13946"},
+{"last_update":"1193388087", "numofapproved":"1", "id":"13982"},
+{"last_update":"1179841973", "numofapproved":"1", "id":"1642"},
+{"last_update":"1179842066", "numofapproved":"1", "id":"1662"},
+{"last_update":"1185971695", "numofapproved":"1", "id":"5941"},
+{"last_update":"1186137440", "numofapproved":"1", "id":"6103"},
+{"last_update":"1192823224", "numofapproved":"1", "id":"13181"},
+{"last_update":"1193921116", "numofapproved":"1", "id":"14581"},
+{"last_update":"1193918035", "numofapproved":"1", "id":"14544"},
+{"last_update":"1193973759", "numofapproved":"1", "id":"14663"},
+{"last_update":"1194004166", "numofapproved":"1", "id":"14721"},
+{"last_update":"1194020795", "numofapproved":"1", "id":"14761"},
+{"last_update":"1194021069", "numofapproved":"1", "id":"14781"},
+{"last_update":"1194283444", "numofapproved":"1", "id":"14887"},
+{"last_update":"1194436909", "numofapproved":"1", "id":"15141"},
+{"last_update":"1194538247", "numofapproved":"1", "id":"15341"},
+{"last_update":"1180031440", "numofapproved":"1", "id":"1801"},
+{"last_update":"1181823965", "numofapproved":"1", "id":"2941"},
+{"last_update":"1182846565", "numofapproved":"1", "id":"3561"},
+{"last_update":"1185872587", "numofapproved":"1", "id":"5843"},
+{"last_update":"1186472951", "numofapproved":"1", "id":"6168"},
+{"last_update":"1189937606", "numofapproved":"1", "id":"9181"},
+{"last_update":"1193389026", "numofapproved":"1", "id":"13955"},
+{"last_update":"1192130592", "numofapproved":"1", "id":"12321"},
+{"last_update":"1194387386", "numofapproved":"1", "id":"15061"},
+{"last_update":"1179336536", "numofapproved":"1", "id":"1396"},
+{"last_update":"1182280246", "numofapproved":"1", "id":"3281"},
+{"last_update":"1183394591", "numofapproved":"1", "id":"4001"},
+{"last_update":"1184677502", "numofapproved":"1", "id":"4909"},
+{"last_update":"1186144184", "numofapproved":"1", "id":"6106"},
+{"last_update":"1187191683", "numofapproved":"1", "id":"6701"},
+{"last_update":"1193909594", "numofapproved":"1", "id":"14527"},
+{"last_update":"1194435747", "numofapproved":"1", "id":"15121"},
+{"last_update":"1184252278", "numofapproved":"1", "id":"4761"},
+{"last_update":"1194854996", "numofapproved":"1", "id":"15721"},
+{"last_update":"1194937730", "numofapproved":"1", "id":"16045"},
+{"last_update":"1193076864", "numofapproved":"1", "id":"13361"},
+{"last_update":"1194904087", "numofapproved":"1", "id":"15981"},
+{"last_update":"1181853751", "numofapproved":"1", "id":"3001"},
+{"last_update":"1182075529", "numofapproved":"1", "id":"3161"},
+{"last_update":"1184883226", "numofapproved":"1", "id":"5081"},
+{"last_update":"1186136013", "numofapproved":"1", "id":"6102"},
+{"last_update":"1193147983", "numofapproved":"1", "id":"13481"},
+{"last_update":"1194532658", "numofapproved":"1", "id":"15301"},
+{"last_update":"1194937763", "numofapproved":"1", "id":"16046"},
+{"last_update":"1195225183", "numofapproved":"1", "id":"16981"},
+{"last_update":"1180616624", "numofapproved":"1", "id":"1981"},
+{"last_update":"1183019269", "numofapproved":"1", "id":"3701"},
+{"last_update":"1188656338", "numofapproved":"1", "id":"7941"},
+{"last_update":"1178799062", "numofapproved":"1", "id":"1353"},
+{"last_update":"1178905809", "numofapproved":"1", "id":"1360"},
+{"last_update":"1179311575", "numofapproved":"1", "id":"1408"},
+{"last_update":"1182507595", "numofapproved":"1", "id":"3461"},
+{"last_update":"1184254004", "numofapproved":"1", "id":"4781"},
+{"last_update":"1187938257", "numofapproved":"1", "id":"7381"},
+{"last_update":"1188473327", "numofapproved":"1", "id":"7801"},
+{"last_update":"1189102174", "numofapproved":"1", "id":"8481"},
+{"last_update":"1191419747", "numofapproved":"1", "id":"11102"},
+{"last_update":"1193389169", "numofapproved":"1", "id":"14002"},
+{"last_update":"1194440930", "numofapproved":"1", "id":"15102"},
+{"last_update":"1194855848", "numofapproved":"1", "id":"15741"},
+{"last_update":"1194862162", "numofapproved":"1", "id":"15841"},
+{"last_update":"1194923605", "numofapproved":"1", "id":"16021"},
+{"last_update":"1194950051", "numofapproved":"1", "id":"16142"},
+{"last_update":"1194960554", "numofapproved":"1", "id":"16181"},
+{"last_update":"1194988868", "numofapproved":"1", "id":"16382"},
+{"last_update":"1195058276", "numofapproved":"1", "id":"16601"},
+{"last_update":"1195469960", "numofapproved":"1", "id":"17201"},
+{"last_update":"1178648361", "numofapproved":"1", "id":"1311"},
+{"last_update":"1183970840", "numofapproved":"1", "id":"4401"},
+{"last_update":"1184838534", "numofapproved":"1", "id":"5041"},
+{"last_update":"1190745858", "numofapproved":"1", "id":"10161"},
+{"last_update":"1191587968", "numofapproved":"1", "id":"11581"},
+{"last_update":"1189773687", "numofapproved":"1", "id":"9021"},
+{"last_update":"1192612866", "numofapproved":"1", "id":"12804"},
+{"last_update":"1193746024", "numofapproved":"1", "id":"14461"},
+{"last_update":"1193918117", "numofapproved":"1", "id":"14561"},
+{"last_update":"1194981013", "numofapproved":"1", "id":"16321"},
+{"last_update":"1195546695", "numofapproved":"1", "id":"17481"},
+{"last_update":"1177592107", "numofapproved":"1", "id":"1047"},
+{"last_update":"1183569612", "numofapproved":"1", "id":"4221"},
+{"last_update":"1186770649", "numofapproved":"1", "id":"6401"},
+{"last_update":"1187707518", "numofapproved":"1", "id":"7021"},
+{"last_update":"1187769297", "numofapproved":"1", "id":"7223"},
+{"last_update":"1187798945", "numofapproved":"1", "id":"7241"},
+{"last_update":"1187820883", "numofapproved":"1", "id":"7261"},
+{"last_update":"1190286816", "numofapproved":"1", "id":"9581"},
+{"last_update":"1190541964", "numofapproved":"1", "id":"9842"},
+{"last_update":"1190500569", "numofapproved":"1", "id":"9802"},
+{"last_update":"1190800190", "numofapproved":"1", "id":"10222"},
+{"last_update":"1190965460", "numofapproved":"1", "id":"10642"},
+{"last_update":"1192120899", "numofapproved":"1", "id":"12301"},
+{"last_update":"1193265675", "numofapproved":"1", "id":"13701"},
+{"last_update":"1194508196", "numofapproved":"1", "id":"15261"},
+{"last_update":"1172503197", "numofapproved":"1", "id":"196"},
+{"last_update":"1172847366", "numofapproved":"1", "id":"238"},
+{"last_update":"1173975764", "numofapproved":"1", "id":"347"},
+{"last_update":"1174301010", "numofapproved":"1", "id":"375"},
+{"last_update":"1174899614", "numofapproved":"1", "id":"514"},
+{"last_update":"1174924853", "numofapproved":"1", "id":"527"},
+{"last_update":"1175270318", "numofapproved":"1", "id":"567"},
+{"last_update":"1174933246", "numofapproved":"1", "id":"536"},
+{"last_update":"1176369900", "numofapproved":"1", "id":"889"},
+{"last_update":"1171102836", "numofapproved":"1", "id":"2"},
+{"last_update":"1171970451", "numofapproved":"1", "id":"121"},
+{"last_update":"1174898953", "numofapproved":"1", "id":"484"},
+{"last_update":"1175610845", "numofapproved":"1", "id":"664"},
+{"last_update":"1176313569", "numofapproved":"1", "id":"885"},
+{"last_update":"1171878648", "numofapproved":"1", "id":"89"},
+{"last_update":"1171897268", "numofapproved":"1", "id":"96"},
+{"last_update":"1172326187", "numofapproved":"1", "id":"193"},
+{"last_update":"1176106905", "numofapproved":"1", "id":"802"},
+{"last_update":"1176389540", "numofapproved":"1", "id":"891"},
+{"last_update":"1171318806", "numofapproved":"1", "id":"24"},
+{"last_update":"1171601548", "numofapproved":"1", "id":"66"},
+{"last_update":"1172148331", "numofapproved":"1", "id":"171"},
+{"last_update":"1172686680", "numofapproved":"1", "id":"228"},
+{"last_update":"1173793572", "numofapproved":"1", "id":"308"},
+{"last_update":"1174899594", "numofapproved":"1", "id":"513"},
+{"last_update":"1174898936", "numofapproved":"1", "id":"483"},
+{"last_update":"1175502773", "numofapproved":"1", "id":"622"},
+{"last_update":"1175722537", "numofapproved":"1", "id":"709"},
+{"last_update":"1175764633", "numofapproved":"1", "id":"672"},
+{"last_update":"1175797156", "numofapproved":"1", "id":"721"},
+{"last_update":"1175899070", "numofapproved":"1", "id":"785"},
+{"last_update":"1176106959", "numofapproved":"1", "id":"803"},
+{"last_update":"1176228460", "numofapproved":"1", "id":"824"},
+{"last_update":"1176488163", "numofapproved":"1", "id":"962"},
+{"last_update":"1172068869", "numofapproved":"1", "id":"133"},
+{"last_update":"1172847381", "numofapproved":"1", "id":"239"},
+{"last_update":"1173888657", "numofapproved":"1", "id":"320"},
+{"last_update":"1171449446", "numofapproved":"1", "id":"48"},
+{"last_update":"1175287424", "numofapproved":"1", "id":"581"},
+{"last_update":"1175502897", "numofapproved":"1", "id":"624"},
+{"last_update":"1175503020", "numofapproved":"1", "id":"605"},
+{"last_update":"1172848367", "numofapproved":"1", "id":"243"},
+{"last_update":"1174301060", "numofapproved":"1", "id":"377"},
+{"last_update":"1176824481", "numofapproved":"1", "id":"986"},
+{"last_update":"1171275893", "numofapproved":"1", "id":"6"},
+{"last_update":"1172546216", "numofapproved":"1", "id":"206"},
+{"last_update":"1175502705", "numofapproved":"1", "id":"601"},
+{"last_update":"1173962671", "numofapproved":"1", "id":"341"},
+{"last_update":"1173975403", "numofapproved":"1", "id":"342"},
+{"last_update":"1173816295", "numofapproved":"1", "id":"313"},
+{"last_update":"1174301256", "numofapproved":"1", "id":"384"},
+{"last_update":"1174933293", "numofapproved":"1", "id":"537"},
+{"last_update":"1176899419", "numofapproved":"1", "id":"988"},
+{"last_update":"1173975599", "numofapproved":"1", "id":"345"},
+{"last_update":"1174041960", "numofapproved":"1", "id":"351"},
+{"last_update":"1175759476", "numofapproved":"1", "id":"671"},
+{"last_update":"1178195644", "numofapproved":"1", "id":"1207"},
+{"last_update":"1178725318", "numofapproved":"1", "id":"1348"},
+{"last_update":"1179333492", "numofapproved":"1", "id":"1421"},
+{"last_update":"1179999737", "numofapproved":"1", "id":"1646"},
+{"last_update":"1180710770", "numofapproved":"1", "id":"2062"},
+{"last_update":"1182868347", "numofapproved":"1", "id":"3601"},
+{"last_update":"1182932927", "numofapproved":"1", "id":"3621"},
+{"last_update":"1183115054", "numofapproved":"1", "id":"3784"},
+{"last_update":"1180000741", "numofapproved":"1", "id":"1647"},
+{"last_update":"1181292582", "numofapproved":"1", "id":"2621"},
+{"last_update":"1184181581", "numofapproved":"1", "id":"4641"},
+{"last_update":"1185280501", "numofapproved":"1", "id":"5301"},
+{"last_update":"1185471699", "numofapproved":"1", "id":"5561"},
+{"last_update":"1185542771", "numofapproved":"1", "id":"5701"},
+{"last_update":"1186650650", "numofapproved":"1", "id":"6361"},
+{"last_update":"1186951065", "numofapproved":"1", "id":"6461"},
+{"last_update":"1187769080", "numofapproved":"1", "id":"7221"},
+{"last_update":"1187887905", "numofapproved":"1", "id":"7348"},
+{"last_update":"1188001607", "numofapproved":"1", "id":"7423"},
+{"last_update":"1188463414", "numofapproved":"1", "id":"7762"},
+{"last_update":"1188555813", "numofapproved":"1", "id":"7861"},
+{"last_update":"1188634622", "numofapproved":"1", "id":"7921"},
+{"last_update":"1189543954", "numofapproved":"1", "id":"8841"},
+{"last_update":"1177511009", "numofapproved":"1", "id":"1043"},
+{"last_update":"1181898808", "numofapproved":"1", "id":"3081"},
+{"last_update":"1182247483", "numofapproved":"1", "id":"3221"},
+{"last_update":"1187024005", "numofapproved":"1", "id":"6562"},
+{"last_update":"1189839471", "numofapproved":"1", "id":"9081"},
+{"last_update":"1190018380", "numofapproved":"1", "id":"9241"},
+{"last_update":"1190149586", "numofapproved":"1", "id":"9401"},
+{"last_update":"1190652684", "numofapproved":"1", "id":"9981"},
+{"last_update":"1190662296", "numofapproved":"1", "id":"10022"},
+{"last_update":"1190813509", "numofapproved":"1", "id":"10281"},
+{"last_update":"1190826005", "numofapproved":"1", "id":"10403"},
+{"last_update":"1190991166", "numofapproved":"1", "id":"10722"},
+{"last_update":"1191057700", "numofapproved":"1", "id":"10801"},
+{"last_update":"1191161241", "numofapproved":"1", "id":"10821"},
+{"last_update":"1191227885", "numofapproved":"1", "id":"10941"},
+{"last_update":"1182537005", "numofapproved":"1", "id":"3481"},
+{"last_update":"1185018401", "numofapproved":"1", "id":"5162"},
+{"last_update":"1186752963", "numofapproved":"1", "id":"6386"},
+{"last_update":"1190660077", "numofapproved":"1", "id":"10001"},
+{"last_update":"1191319062", "numofapproved":"1", "id":"10981"},
+{"last_update":"1191446097", "numofapproved":"1", "id":"11161"},
+{"last_update":"1191446587", "numofapproved":"1", "id":"11184"},
+{"last_update":"1191470824", "numofapproved":"1", "id":"11221"},
+{"last_update":"1191526821", "numofapproved":"1", "id":"11401"},
+{"last_update":"1191585471", "numofapproved":"1", "id":"11561"},
+{"last_update":"1191602213", "numofapproved":"1", "id":"11701"},
+{"last_update":"1191845720", "numofapproved":"1", "id":"11821"},
+{"last_update":"1191933874", "numofapproved":"1", "id":"11902"},
+{"last_update":"1191933897", "numofapproved":"1", "id":"11903"},
+{"last_update":"1177673238", "numofapproved":"1", "id":"1161"},
+{"last_update":"1181601542", "numofapproved":"1", "id":"2781"},
+{"last_update":"1182869532", "numofapproved":"1", "id":"3583"},
+{"last_update":"1183315879", "numofapproved":"1", "id":"3881"},
+{"last_update":"1187097870", "numofapproved":"1", "id":"6641"},
+{"last_update":"1190148660", "numofapproved":"1", "id":"9361"},
+{"last_update":"1192248648", "numofapproved":"1", "id":"12521"},
+{"last_update":"1192702958", "numofapproved":"1", "id":"13001"},
+{"last_update":"1193387721", "numofapproved":"1", "id":"13981"},
+{"last_update":"1193391276", "numofapproved":"1", "id":"14021"},
+{"last_update":"1193397051", "numofapproved":"1", "id":"14061"},
+{"last_update":"1193592081", "numofapproved":"1", "id":"14321"},
+{"last_update":"1188474438", "numofapproved":"1", "id":"7821"},
+{"last_update":"1190158372", "numofapproved":"1", "id":"9441"},
+{"last_update":"1193648459", "numofapproved":"1", "id":"14361"},
+{"last_update":"1193999834", "numofapproved":"1", "id":"14681"},
+{"last_update":"1194200119", "numofapproved":"1", "id":"14861"},
+{"last_update":"1194528747", "numofapproved":"1", "id":"15111"},
+{"last_update":"1179150787", "numofapproved":"1", "id":"1384"},
+{"last_update":"1179266496", "numofapproved":"1", "id":"1390"},
+{"last_update":"1179508139", "numofapproved":"1", "id":"1501"},
+{"last_update":"1179842157", "numofapproved":"1", "id":"1664"},
+{"last_update":"1179842347", "numofapproved":"1", "id":"1668"},
+{"last_update":"1181245388", "numofapproved":"1", "id":"2562"},
+{"last_update":"1181311044", "numofapproved":"1", "id":"2661"},
+{"last_update":"1181545818", "numofapproved":"1", "id":"2701"},
+{"last_update":"1181934881", "numofapproved":"1", "id":"3103"},
+{"last_update":"1187020798", "numofapproved":"1", "id":"6541"},
+{"last_update":"1187271377", "numofapproved":"1", "id":"6801"},
+{"last_update":"1196086904", "numofapproved":"1", "id":"17545"},
+{"last_update":"1196266437", "numofapproved":"2", "id":"17662"},
+{"last_update":"1196266638", "numofapproved":"2", "id":"17663"},
+{"last_update":"1197533251", "numofapproved":"1", "id":"17901"},
+{"last_update":"1197533384", "numofapproved":"1", "id":"17923"},
+{"last_update":"1197556776", "numofapproved":"2", "id":"17941"},
+{"last_update":"1200059354", "numofapproved":"1", "id":"17981"},
+{"last_update":"1200576144", "numofapproved":"1", "id":"18001"},
+{"last_update":"1200576230", "numofapproved":"1", "id":"18002"},
+{"last_update":"1200657266", "numofapproved":"1", "id":"18041"},
+{"last_update":"1201510556", "numofapproved":"1", "id":"18061"},
+{"last_update":"1196087136", "numofapproved":"1", "id":"17546"},
+{"last_update":"1196087269", "numofapproved":"1", "id":"17547"},
+{"last_update":"1196087335", "numofapproved":"1", "id":"17548"},
+{"last_update":"1196087379", "numofapproved":"1", "id":"17549"},
+{"last_update":"1196087427", "numofapproved":"1", "id":"17550"},
+{"last_update":"1196096347", "numofapproved":"1", "id":"17581"},
+{"last_update":"1196265997", "numofapproved":"2", "id":"17661"},
+{"last_update":"1196266785", "numofapproved":"1", "id":"17664"},
+{"last_update":"1196270058", "numofapproved":"1", "id":"17701"},
+{"last_update":"1196431875", "numofapproved":"1", "id":"17804"},
+{"last_update":"1197635044", "numofapproved":"1", "id":"17961"},
+{"last_update":"1202720206", "numofapproved":"2", "id":"18084"},
+{"last_update":"1196267153", "numofapproved":"1", "id":"17681"},
+{"last_update":"1196090749", "numofapproved":"1", "id":"17569"},
+{"last_update":"1196162163", "numofapproved":"2", "id":"17641"},
+{"last_update":"1196345846", "numofapproved":"1", "id":"17721"},
+{"last_update":"1196088254", "numofapproved":"1", "id":"17552"},
+{"last_update":"1196088437", "numofapproved":"1", "id":"17564"},
+{"last_update":"1196088477", "numofapproved":"1", "id":"17565"},
+{"last_update":"1196088537", "numofapproved":"1", "id":"17566"},
+{"last_update":"1196088894", "numofapproved":"1", "id":"17567"},
+{"last_update":"1196090414", "numofapproved":"1", "id":"17554"},
+{"last_update":"1196097621", "numofapproved":"1", "id":"17601"},
+{"last_update":"1196097710", "numofapproved":"1", "id":"17602"},
+{"last_update":"1196098047", "numofapproved":"1", "id":"17603"},
+{"last_update":"1196358376", "numofapproved":"2", "id":"17761"},
+{"last_update":"1196358647", "numofapproved":"1", "id":"17762"},
+{"last_update":"1196427604", "numofapproved":"1", "id":"17781"},
+{"last_update":"1196429856", "numofapproved":"1", "id":"17782"},
+{"last_update":"1196431068", "numofapproved":"2", "id":"17783"},
+{"last_update":"1196435953", "numofapproved":"2", "id":"17821"},
+{"last_update":"1204027277", "numofapproved":"1", "id":"18104"},
+{"last_update":"1196090201", "numofapproved":"1", "id":"17553"},
+{"last_update":"1196097095", "numofapproved":"1", "id":"17582"},
+{"last_update":"1196097215", "numofapproved":"1", "id":"17583"},
+{"last_update":"1196430140", "numofapproved":"2", "id":"17803"},
+{"last_update":"1196436411", "numofapproved":"2", "id":"17841"},
+{"last_update":"1196692298", "numofapproved":"1", "id":"17861"},
+{"last_update":"1196692342", "numofapproved":"2", "id":"17862"},
+{"last_update":"1196695231", "numofapproved":"2", "id":"17865"},
+{"last_update":"1197533316", "numofapproved":"1", "id":"17921"},
+{"last_update":"1201512744", "numofapproved":"1", "id":"18082"},
+{"last_update":"1201513438", "numofapproved":"2", "id":"18083"},
+{"last_update":"1196087540", "numofapproved":"1", "id":"17551"},
+{"last_update":"1196156416", "numofapproved":"2", "id":"17621"},
+{"last_update":"1196356717", "numofapproved":"1", "id":"17741"},
+{"last_update":"1196428544", "numofapproved":"2", "id":"17801"},
+{"last_update":"1196429000", "numofapproved":"2", "id":"17802"},
+{"last_update":"1196692578", "numofapproved":"1", "id":"17863"},
+{"last_update":"1196693445", "numofapproved":"2", "id":"17881"},
+{"last_update":"1196693804", "numofapproved":"2", "id":"17864"},
+{"last_update":"1197533347", "numofapproved":"1", "id":"17922"},
+{"last_update":"1200591782", "numofapproved":"1", "id":"18021"},
+{"last_update":"1201510930", "numofapproved":"1", "id":"18081"},
+{"last_update":"1192432005", "numofapproved":"1", "id":"12582"},
+{"last_update":"1192614291", "numofapproved":"1", "id":"12805"},
+{"last_update":"1192624421", "numofapproved":"1", "id":"12806"},
+{"last_update":"1192983623", "numofapproved":"1", "id":"13221"},
+{"last_update":"1193043248", "numofapproved":"1", "id":"13282"},
+{"last_update":"1193223892", "numofapproved":"1", "id":"13562"},
+{"last_update":"1193239943", "numofapproved":"1", "id":"13601"},
+{"last_update":"1193385960", "numofapproved":"1", "id":"13961"},
+{"last_update":"1193386863", "numofapproved":"1", "id":"13945"},
+{"last_update":"1193399770", "numofapproved":"1", "id":"14042"},
+{"last_update":"1193417684", "numofapproved":"1", "id":"14181"},
+{"last_update":"1193458402", "numofapproved":"1", "id":"14261"},
+{"last_update":"1193555071", "numofapproved":"1", "id":"14301"},
+{"last_update":"1185285506", "numofapproved":"1", "id":"5321"},
+{"last_update":"1188250869", "numofapproved":"1", "id":"7521"},
+{"last_update":"1191410480", "numofapproved":"1", "id":"11061"},
+{"last_update":"1193763056", "numofapproved":"1", "id":"14482"},
+{"last_update":"1193913886", "numofapproved":"1", "id":"14542"},
+{"last_update":"1194366001", "numofapproved":"1", "id":"14890"},
+{"last_update":"1194454607", "numofapproved":"1", "id":"15105"},
+{"last_update":"1194255904", "numofapproved":"1", "id":"14941"},
+{"last_update":"1179328986", "numofapproved":"1", "id":"1395"},
+{"last_update":"1180377628", "numofapproved":"1", "id":"1861"},
+{"last_update":"1181250011", "numofapproved":"1", "id":"2563"},
+{"last_update":"1181572386", "numofapproved":"1", "id":"2741"},
+{"last_update":"1183967114", "numofapproved":"1", "id":"4381"},
+{"last_update":"1192512712", "numofapproved":"1", "id":"12623"},
+{"last_update":"1193172621", "numofapproved":"1", "id":"13522"},
+{"last_update":"1193868932", "numofapproved":"1", "id":"14523"},
+{"last_update":"1194980345", "numofapproved":"1", "id":"16301"},
+{"last_update":"1182280312", "numofapproved":"1", "id":"3282"},
+{"last_update":"1184058726", "numofapproved":"1", "id":"4542"},
+{"last_update":"1188829875", "numofapproved":"1", "id":"8161"},
+{"last_update":"1190129857", "numofapproved":"1", "id":"9341"},
+{"last_update":"1190652687", "numofapproved":"1", "id":"9982"},
+{"last_update":"1193389082", "numofapproved":"1", "id":"13956"},
+{"last_update":"1195400591", "numofapproved":"1", "id":"17121"},
+{"last_update":"1184420846", "numofapproved":"1", "id":"4882"},
+{"last_update":"1184532219", "numofapproved":"1", "id":"4903"},
+{"last_update":"1192030476", "numofapproved":"1", "id":"12101"},
+{"last_update":"1192202239", "numofapproved":"1", "id":"12461"},
+{"last_update":"1192688302", "numofapproved":"1", "id":"12961"},
+{"last_update":"1192703266", "numofapproved":"1", "id":"13021"},
+{"last_update":"1193387096", "numofapproved":"1", "id":"13948"},
+{"last_update":"1193387200", "numofapproved":"1", "id":"13949"},
+{"last_update":"1193909837", "numofapproved":"1", "id":"14528"},
+{"last_update":"1181062093", "numofapproved":"1", "id":"2301"},
+{"last_update":"1182364431", "numofapproved":"1", "id":"3348"},
+{"last_update":"1182364589", "numofapproved":"1", "id":"3349"},
+{"last_update":"1184942429", "numofapproved":"1", "id":"5101"},
+{"last_update":"1192682522", "numofapproved":"1", "id":"12901"},
+{"last_update":"1184756287", "numofapproved":"1", "id":"4944"},
+{"last_update":"1190274411", "numofapproved":"1", "id":"9541"},
+{"last_update":"1193324229", "numofapproved":"1", "id":"13861"},
+{"last_update":"1195163999", "numofapproved":"1", "id":"16861"},
+{"last_update":"1181553321", "numofapproved":"1", "id":"2721"},
+{"last_update":"1178869453", "numofapproved":"1", "id":"1361"},
+{"last_update":"1181219788", "numofapproved":"1", "id":"2481"},
+{"last_update":"1178140002", "numofapproved":"1", "id":"1205"},
+{"last_update":"1178716891", "numofapproved":"1", "id":"1345"},
+{"last_update":"1180691957", "numofapproved":"1", "id":"2061"},
+{"last_update":"1182246242", "numofapproved":"1", "id":"3206"},
+{"last_update":"1182882314", "numofapproved":"1", "id":"3585"},
+{"last_update":"1183124192", "numofapproved":"1", "id":"3821"},
+{"last_update":"1183905634", "numofapproved":"1", "id":"4361"},
+{"last_update":"1191225755", "numofapproved":"1", "id":"10901"},
+{"last_update":"1192635977", "numofapproved":"1", "id":"12881"},
+{"last_update":"1193268752", "numofapproved":"1", "id":"13721"},
+{"last_update":"1193242245", "numofapproved":"1", "id":"13621"},
+{"last_update":"1193949751", "numofapproved":"1", "id":"14621"},
+{"last_update":"1194635892", "numofapproved":"1", "id":"15621"},
+{"last_update":"1194726918", "numofapproved":"1", "id":"15664"},
+{"last_update":"1194726371", "numofapproved":"1", "id":"15662"},
+{"last_update":"1194858043", "numofapproved":"1", "id":"15781"},
+{"last_update":"1194946522", "numofapproved":"1", "id":"16101"},
+{"last_update":"1195047359", "numofapproved":"1", "id":"16521"},
+{"last_update":"1195050812", "numofapproved":"1", "id":"16503"},
+{"last_update":"1195058811", "numofapproved":"1", "id":"16621"},
+{"last_update":"1195476161", "numofapproved":"1", "id":"17241"},
+{"last_update":"1178645683", "numofapproved":"1", "id":"1305"},
+{"last_update":"1183118619", "numofapproved":"1", "id":"3801"},
+{"last_update":"1186150376", "numofapproved":"1", "id":"6121"},
+{"last_update":"1189114226", "numofapproved":"1", "id":"8501"},
+{"last_update":"1190973079", "numofapproved":"1", "id":"10666"},
+{"last_update":"1190990329", "numofapproved":"1", "id":"10704"},
+{"last_update":"1191508485", "numofapproved":"1", "id":"11361"},
+{"last_update":"1183054560", "numofapproved":"1", "id":"3721"},
+{"last_update":"1185263889", "numofapproved":"1", "id":"5241"},
+{"last_update":"1187876083", "numofapproved":"1", "id":"7346"},
+{"last_update":"1189550218", "numofapproved":"1", "id":"8861"},
+{"last_update":"1190800088", "numofapproved":"1", "id":"10221"},
+{"last_update":"1193260528", "numofapproved":"1", "id":"13661"},
+{"last_update":"1172509002", "numofapproved":"1", "id":"199"},
+{"last_update":"1172509846", "numofapproved":"1", "id":"200"},
+{"last_update":"1172589855", "numofapproved":"1", "id":"214"},
+{"last_update":"1172847322", "numofapproved":"1", "id":"236"},
+{"last_update":"1172847433", "numofapproved":"1", "id":"242"},
+{"last_update":"1173607050", "numofapproved":"1", "id":"283"},
+{"last_update":"1173703535", "numofapproved":"1", "id":"301"},
+{"last_update":"1173719825", "numofapproved":"1", "id":"302"},
+{"last_update":"1174414845", "numofapproved":"1", "id":"403"},
+{"last_update":"1174650542", "numofapproved":"1", "id":"441"},
+{"last_update":"1171475944", "numofapproved":"1", "id":"52"},
+{"last_update":"1172746278", "numofapproved":"1", "id":"231"},
+{"last_update":"1173251095", "numofapproved":"1", "id":"254"},
+{"last_update":"1173259501", "numofapproved":"1", "id":"255"},
+{"last_update":"1174899183", "numofapproved":"1", "id":"495"},
+{"last_update":"1174924714", "numofapproved":"1", "id":"524"},
+{"last_update":"1171962179", "numofapproved":"1", "id":"108"},
+{"last_update":"1172522401", "numofapproved":"1", "id":"205"},
+{"last_update":"1174299349", "numofapproved":"1", "id":"362"},
+{"last_update":"1174899291", "numofapproved":"1", "id":"500"},
+{"last_update":"1175617661", "numofapproved":"1", "id":"688"},
+{"last_update":"1176302948", "numofapproved":"1", "id":"881"},
+{"last_update":"1176467393", "numofapproved":"1", "id":"893"},
+{"last_update":"1176737599", "numofapproved":"1", "id":"982"},
+{"last_update":"1171465517", "numofapproved":"1", "id":"50"},
+{"last_update":"1171924670", "numofapproved":"1", "id":"107"},
+{"last_update":"1173880505", "numofapproved":"1", "id":"317"},
+{"last_update":"1173889350", "numofapproved":"1", "id":"329"},
+{"last_update":"1173889557", "numofapproved":"1", "id":"332"},
+{"last_update":"1176391285", "numofapproved":"1", "id":"892"},
+{"last_update":"1176673529", "numofapproved":"1", "id":"981"},
+{"last_update":"1171643442", "numofapproved":"1", "id":"69"},
+{"last_update":"1172226841", "numofapproved":"1", "id":"182"},
+{"last_update":"1174899475", "numofapproved":"1", "id":"506"},
+{"last_update":"1174915327", "numofapproved":"1", "id":"521"},
+{"last_update":"1176194461", "numofapproved":"1", "id":"821"},
+{"last_update":"1172013837", "numofapproved":"1", "id":"132"},
+{"last_update":"1172184974", "numofapproved":"1", "id":"177"},
+{"last_update":"1175777908", "numofapproved":"1", "id":"674"},
+{"last_update":"1173460745", "numofapproved":"1", "id":"281"},
+{"last_update":"1174401746", "numofapproved":"1", "id":"402"},
+{"last_update":"1171274691", "numofapproved":"1", "id":"5"},
+{"last_update":"1171799314", "numofapproved":"1", "id":"78"},
+{"last_update":"1171979089", "numofapproved":"1", "id":"127"},
+{"last_update":"1172503571", "numofapproved":"1", "id":"197"},
+{"last_update":"1174301365", "numofapproved":"1", "id":"391"},
+{"last_update":"1174301259", "numofapproved":"1", "id":"385"},
+{"last_update":"1174899163", "numofapproved":"1", "id":"494"},
+{"last_update":"1174933167", "numofapproved":"1", "id":"534"},
+{"last_update":"1176139704", "numofapproved":"1", "id":"808"},
+{"last_update":"1175502855", "numofapproved":"1", "id":"603"},
+{"last_update":"1173721122", "numofapproved":"1", "id":"303"},
+{"last_update":"1173809079", "numofapproved":"1", "id":"311"},
+{"last_update":"1174734352", "numofapproved":"1", "id":"461"},
+{"last_update":"1174898917", "numofapproved":"1", "id":"482"},
+{"last_update":"1174899374", "numofapproved":"1", "id":"503"},
+{"last_update":"1176392495", "numofapproved":"1", "id":"903"},
+{"last_update":"1176829535", "numofapproved":"1", "id":"987"},
+{"last_update":"1173889385", "numofapproved":"1", "id":"330"},
+{"last_update":"1175869070", "numofapproved":"1", "id":"783"},
+{"last_update":"1177510634", "numofapproved":"1", "id":"1042"},
+{"last_update":"1177585810", "numofapproved":"1", "id":"1062"},
+{"last_update":"1178648303", "numofapproved":"1", "id":"1309"},
+{"last_update":"1178883682", "numofapproved":"1", "id":"1363"},
+{"last_update":"1179239792", "numofapproved":"1", "id":"1402"},
+{"last_update":"1179997715", "numofapproved":"1", "id":"1644"},
+{"last_update":"1180031289", "numofapproved":"1", "id":"1654"},
+{"last_update":"1180440758", "numofapproved":"1", "id":"1921"},
+{"last_update":"1180972413", "numofapproved":"1", "id":"2221"},
+{"last_update":"1181032741", "numofapproved":"1", "id":"2261"},
+{"last_update":"1181198104", "numofapproved":"1", "id":"2401"},
+{"last_update":"1181237541", "numofapproved":"1", "id":"2581"},
+{"last_update":"1181293731", "numofapproved":"1", "id":"2641"},
+{"last_update":"1182231158", "numofapproved":"1", "id":"3204"},
+{"last_update":"1177668412", "numofapproved":"1", "id":"1121"},
+{"last_update":"1178713554", "numofapproved":"1", "id":"1342"},
+{"last_update":"1179239886", "numofapproved":"1", "id":"1404"},
+{"last_update":"1184766561", "numofapproved":"1", "id":"4961"},
+{"last_update":"1185293883", "numofapproved":"1", "id":"5341"},
+{"last_update":"1185781181", "numofapproved":"1", "id":"5761"},
+{"last_update":"1185898126", "numofapproved":"1", "id":"5862"},
+{"last_update":"1186290486", "numofapproved":"1", "id":"6164"},
+{"last_update":"1186260193", "numofapproved":"1", "id":"6162"},
+{"last_update":"1186305362", "numofapproved":"1", "id":"6201"},
+{"last_update":"1187024035", "numofapproved":"1", "id":"6563"},
+{"last_update":"1187245873", "numofapproved":"1", "id":"6761"},
+{"last_update":"1187765176", "numofapproved":"1", "id":"7142"},
+{"last_update":"1187872548", "numofapproved":"1", "id":"7343"},
+{"last_update":"1188774634", "numofapproved":"1", "id":"8061"},
+{"last_update":"1188838929", "numofapproved":"1", "id":"8181"},
+{"last_update":"1189608461", "numofapproved":"1", "id":"8881"},
+{"last_update":"1189667694", "numofapproved":"1", "id":"8921"},
+{"last_update":"1179747423", "numofapproved":"1", "id":"1541"},
+{"last_update":"1181142187", "numofapproved":"1", "id":"2381"},
+{"last_update":"1185965227", "numofapproved":"1", "id":"5921"},
+{"last_update":"1190476977", "numofapproved":"1", "id":"9761"},
+{"last_update":"1190648889", "numofapproved":"1", "id":"9961"},
+{"last_update":"1190824195", "numofapproved":"1", "id":"10381"},
+{"last_update":"1190825530", "numofapproved":"1", "id":"10401"},
+{"last_update":"1190894398", "numofapproved":"1", "id":"10501"},
+{"last_update":"1178271031", "numofapproved":"1", "id":"1242"},
+{"last_update":"1178878052", "numofapproved":"1", "id":"1359"},
+{"last_update":"1178967516", "numofapproved":"1", "id":"1364"},
+{"last_update":"1180018261", "numofapproved":"1", "id":"1652"},
+{"last_update":"1180107922", "numofapproved":"1", "id":"1841"},
+{"last_update":"1180514196", "numofapproved":"1", "id":"1941"},
+{"last_update":"1181901023", "numofapproved":"1", "id":"3082"},
+{"last_update":"1182417878", "numofapproved":"1", "id":"3361"},
+{"last_update":"1182785340", "numofapproved":"1", "id":"3521"},
+{"last_update":"1183485766", "numofapproved":"1", "id":"4101"},
+{"last_update":"1189526136", "numofapproved":"1", "id":"8803"},
+{"last_update":"1191446636", "numofapproved":"1", "id":"11185"},
+{"last_update":"1191489743", "numofapproved":"1", "id":"11241"},
+{"last_update":"1191903141", "numofapproved":"1", "id":"11882"},
+{"last_update":"1191940049", "numofapproved":"1", "id":"11941"},
+{"last_update":"1179239857", "numofapproved":"1", "id":"1403"},
+{"last_update":"1185799202", "numofapproved":"1", "id":"5801"},
+{"last_update":"1190924823", "numofapproved":"1", "id":"10562"},
+{"last_update":"1191410783", "numofapproved":"1", "id":"11065"},
+{"last_update":"1192031578", "numofapproved":"1", "id":"12121"},
+{"last_update":"1192431234", "numofapproved":"1", "id":"12562"},
+{"last_update":"1192609228", "numofapproved":"1", "id":"12802"},
+{"last_update":"1192742243", "numofapproved":"1", "id":"13161"},
+{"last_update":"1192942532", "numofapproved":"1", "id":"13201"},
+{"last_update":"1193386303", "numofapproved":"1", "id":"13962"},
+{"last_update":"1193406158", "numofapproved":"1", "id":"14121"},
+{"last_update":"1193418273", "numofapproved":"1", "id":"14201"},
+{"last_update":"1193519213", "numofapproved":"1", "id":"14281"},
+{"last_update":"1193666593", "numofapproved":"1", "id":"14401"},
+{"last_update":"1193733296", "numofapproved":"1", "id":"14421"},
+{"last_update":"1193760981", "numofapproved":"1", "id":"14481"},
+{"last_update":"1182436569", "numofapproved":"1", "id":"3422"},
+{"last_update":"1184012598", "numofapproved":"1", "id":"4481"},
+{"last_update":"1189715279", "numofapproved":"1", "id":"8981"},
+{"last_update":"1192528903", "numofapproved":"1", "id":"12701"},
+{"last_update":"1194246273", "numofapproved":"1", "id":"14901"},
+{"last_update":"1194354217", "numofapproved":"1", "id":"14888"},
+{"last_update":"1194366787", "numofapproved":"1", "id":"14891"},
+{"last_update":"1194445768", "numofapproved":"1", "id":"15104"},
+{"last_update":"1194467580", "numofapproved":"1", "id":"15107"},
+{"last_update":"1194508237", "numofapproved":"1", "id":"15262"},
+{"last_update":"1194635341", "numofapproved":"1", "id":"15581"},
+{"last_update":"1194635508", "numofapproved":"1", "id":"15582"},
+{"last_update":"1179214538", "numofapproved":"1", "id":"1386"},
+{"last_update":"1186433530", "numofapproved":"1", "id":"6167"},
+{"last_update":"1187853435", "numofapproved":"1", "id":"7321"},
+{"last_update":"1187972012", "numofapproved":"1", "id":"7421"},
+{"last_update":"1188895906", "numofapproved":"1", "id":"8201"},
+{"last_update":"1190284020", "numofapproved":"1", "id":"9561"},
+{"last_update":"1190924163", "numofapproved":"1", "id":"10561"},
+{"last_update":"1192529770", "numofapproved":"1", "id":"12663"},
+{"last_update":"1192536538", "numofapproved":"1", "id":"12666"},
+{"last_update":"1193269090", "numofapproved":"1", "id":"13741"},
+{"last_update":"1193428819", "numofapproved":"1", "id":"14221"},
+{"last_update":"1193860091", "numofapproved":"1", "id":"14521"},
+{"last_update":"1193909426", "numofapproved":"1", "id":"14526"},
+{"last_update":"1194533708", "numofapproved":"1", "id":"15321"},
+{"last_update":"1179822723", "numofapproved":"1", "id":"1601"},
+{"last_update":"1179842248", "numofapproved":"1", "id":"1666"},
+{"last_update":"1182412362", "numofapproved":"1", "id":"3352"},
+{"last_update":"1185980065", "numofapproved":"1", "id":"5961"},
+{"last_update":"1186751100", "numofapproved":"1", "id":"6385"},
+{"last_update":"1187202714", "numofapproved":"1", "id":"6721"},
+{"last_update":"1187601864", "numofapproved":"1", "id":"6923"},
+{"last_update":"1191490727", "numofapproved":"1", "id":"11281"},
+{"last_update":"1194449840", "numofapproved":"1", "id":"15161"},
+{"last_update":"1180028166", "numofapproved":"1", "id":"1781"},
+{"last_update":"1185025939", "numofapproved":"1", "id":"5201"},
+{"last_update":"1192454400", "numofapproved":"1", "id":"12621"},
+{"last_update":"1193414234", "numofapproved":"1", "id":"14141"},
+{"last_update":"1194270682", "numofapproved":"1", "id":"14961"},
+{"last_update":"1184061669", "numofapproved":"1", "id":"4561"},
+{"last_update":"1186161284", "numofapproved":"1", "id":"6141"},
+{"last_update":"1187714492", "numofapproved":"1", "id":"7061"},
+{"last_update":"1187893562", "numofapproved":"1", "id":"7361"},
+{"last_update":"1190815311", "numofapproved":"1", "id":"10301"},
+{"last_update":"1193388120", "numofapproved":"1", "id":"13951"},
+{"last_update":"1195239956", "numofapproved":"1", "id":"17041"},
+{"last_update":"1179147467", "numofapproved":"1", "id":"1381"},
+{"last_update":"1182346611", "numofapproved":"1", "id":"3341"},
+{"last_update":"1184267506", "numofapproved":"1", "id":"4802"},
+{"last_update":"1192047087", "numofapproved":"1", "id":"12161"},
+{"last_update":"1192198948", "numofapproved":"1", "id":"12441"},
+{"last_update":"1193208717", "numofapproved":"1", "id":"13528"},
+{"last_update":"1194907182", "numofapproved":"1", "id":"16001"},
+{"last_update":"1179153020", "numofapproved":"1", "id":"1385"},
+{"last_update":"1179835655", "numofapproved":"1", "id":"1641"},
+{"last_update":"1181234739", "numofapproved":"1", "id":"2542"},
+{"last_update":"1182356477", "numofapproved":"1", "id":"3343"},
+{"last_update":"1182418583", "numofapproved":"1", "id":"3381"},
+{"last_update":"1184568502", "numofapproved":"1", "id":"4905"},
+{"last_update":"1189151603", "numofapproved":"1", "id":"8581"},
+{"last_update":"1191595695", "numofapproved":"1", "id":"11621"},
+{"last_update":"1193105000", "numofapproved":"1", "id":"13421"},
+{"last_update":"1195104657", "numofapproved":"1", "id":"16701"}],
+"request_timestamp":1206363392.08521, "request_call":"requestDetails",
+"instance":"tbedi", "call_time":"0.10059", "request_date":"2008-03-2412:56:32 UTC", "request_url":"http://cmsdoc.cern.ch/cms/test/aprom/phedex/dev/gowri/datasvc/tbedi/requestDetails?format=json"}}
+"""
+
+from jsonParser import jsonObject
+
+data = jsonObject.parseString(s)
+
+#~ from pprint import pprint
+#~ pprint( data[0].asList() )
+#~ print
+#~ print data.dump()
+print(data.phedex.call_time)
+print(data.phedex.instance)
+print(data.phedex.request_call)
+print(len(data.phedex.request))
+for req in data.phedex.request[:10]:
+ #~ print req.dump()
+ print("-", req.id, req.last_update)
diff --git a/trunk/src/examples/removeLineBreaks.py b/trunk/src/examples/removeLineBreaks.py
new file mode 100644
index 0000000..ba4b498
--- /dev/null
+++ b/trunk/src/examples/removeLineBreaks.py
@@ -0,0 +1,45 @@
+# removeLineBreaks.py
+#
+# Demonstration of the pyparsing module, converting text files
+# with hard line-breaks to text files with line breaks only
+# between paragraphs. (Helps when converting downloads from Project
+# Gutenberg - http://www.gutenberg.org - to import to word processing apps
+# that can reformat paragraphs once hard line-breaks are removed.)
+#
+# Uses parse actions and transformString to remove unwanted line breaks,
+# and to double up line breaks between paragraphs.
+#
+# Copyright 2006, by Paul McGuire
+#
+from pyparsing import *
+
+# define an expression for the body of a line of text - use a parse action to reject any
+# empty lines
+def mustBeNonBlank(s,l,t):
+ if not t[0]:
+ raise ParseException(s,l,"line body can't be empty")
+lineBody = SkipTo(lineEnd).setParseAction(mustBeNonBlank)
+
+# now define a line with a trailing lineEnd, to be replaced with a space character
+textLine = lineBody + Suppress(lineEnd).setParseAction(replaceWith(" "))
+
+# define a paragraph, with a separating lineEnd, to be replaced with a double newline
+para = OneOrMore(textLine) + Suppress(lineEnd).setParseAction(replaceWith("\n\n"))
+
+
+# run a test
+test = """
+ Now is the
+ time for
+ all
+ good men
+ to come to
+
+ the aid of their
+ country.
+"""
+print(para.transformString(test))
+
+# process an entire file
+z = para.transformString(file("Successful Methods of Public Speaking.txt").read())
+file("Successful Methods of Public Speaking(2).txt","w").write(z)
diff --git a/trunk/src/examples/romanNumerals.py b/trunk/src/examples/romanNumerals.py
new file mode 100644
index 0000000..27361f0
--- /dev/null
+++ b/trunk/src/examples/romanNumerals.py
@@ -0,0 +1,74 @@
+# romanNumerals.py
+#
+# Copyright (c) 2006, Paul McGuire
+#
+
+from pyparsing import *
+
+def romanNumeralLiteral(numeralString, value):
+ return Literal(numeralString).setParseAction(replaceWith(value))
+
+one = romanNumeralLiteral("I",1)
+four = romanNumeralLiteral("IV",4)
+five = romanNumeralLiteral("V",5)
+nine = romanNumeralLiteral("IX",9)
+ten = romanNumeralLiteral("X",10)
+forty = romanNumeralLiteral("XL",40)
+fifty = romanNumeralLiteral("L",50)
+ninety = romanNumeralLiteral("XC",90)
+onehundred = romanNumeralLiteral("C",100)
+fourhundred = romanNumeralLiteral("CD",400)
+fivehundred = romanNumeralLiteral("D",500)
+ninehundred = romanNumeralLiteral("CM",900)
+onethousand = romanNumeralLiteral("M",1000)
+
+numeral = ( onethousand | ninehundred | fivehundred | fourhundred |
+ onehundred | ninety | fifty | forty | ten | nine | five |
+ four | one ).leaveWhitespace()
+
+romanNumeral = OneOrMore(numeral).setParseAction( lambda s,l,t : sum(t) )
+
+# unit tests
+def makeRomanNumeral(n):
+ def addDigit(n,limit,c,s):
+ n -= limit
+ s += c
+ return n,s
+
+ ret = ""
+ while n >= 1000: n,ret = addDigit(n,1000,"M",ret)
+ while n >= 900: n,ret = addDigit(n, 900,"CM",ret)
+ while n >= 500: n,ret = addDigit(n, 500,"D",ret)
+ while n >= 400: n,ret = addDigit(n, 400,"CD",ret)
+ while n >= 100: n,ret = addDigit(n, 100,"C",ret)
+ while n >= 90: n,ret = addDigit(n, 90,"XC",ret)
+ while n >= 50: n,ret = addDigit(n, 50,"L",ret)
+ while n >= 40: n,ret = addDigit(n, 40,"XL",ret)
+ while n >= 10: n,ret = addDigit(n, 10,"X",ret)
+ while n >= 9: n,ret = addDigit(n, 9,"IX",ret)
+ while n >= 5: n,ret = addDigit(n, 5,"V",ret)
+ while n >= 4: n,ret = addDigit(n, 4,"IV",ret)
+ while n >= 1: n,ret = addDigit(n, 1,"I",ret)
+ return ret
+tests = " ".join(makeRomanNumeral(i) for i in range(1,5000+1))
+
+expected = 1
+for t,s,e in romanNumeral.scanString(tests):
+ if t[0] != expected:
+ print("{} {} {}".format("==>", t, tests[s:e]))
+ expected += 1
+
+def test(rn):
+ print("{} -> {}".format(rn, romanNumeral.parseString(rn)[0]))
+test("XVI")
+test("XXXIX")
+test("XIV")
+test("XIX")
+test("MCMLXXX")
+test("MMVI")
+
+
+
+
+
+
diff --git a/trunk/src/examples/scanExamples.py b/trunk/src/examples/scanExamples.py
new file mode 100644
index 0000000..24ae0e7
--- /dev/null
+++ b/trunk/src/examples/scanExamples.py
@@ -0,0 +1,75 @@
+#
+# scanExamples.py
+#
+# Illustration of using pyparsing's scanString,transformString, and searchString methods
+#
+# Copyright (c) 2004, 2006 Paul McGuire
+#
+from pyparsing import Word, alphas, alphanums, Literal, restOfLine, OneOrMore, \
+ empty, Suppress, replaceWith
+
+# simulate some C++ code
+testData = """
+#define MAX_LOCS=100
+#define USERNAME = "floyd"
+#define PASSWORD = "swordfish"
+
+a = MAX_LOCS;
+CORBA::initORB("xyzzy", USERNAME, PASSWORD );
+
+"""
+
+#################
+print("Example of an extractor")
+print("----------------------")
+
+# simple grammar to match #define's
+ident = Word(alphas, alphanums+"_")
+macroDef = Literal("#define") + ident.setResultsName("name") + "=" + restOfLine.setResultsName("value")
+for t,s,e in macroDef.scanString( testData ):
+ print(t.name,":", t.value)
+
+# or a quick way to make a dictionary of the names and values
+# (return only key and value tokens, and construct dict from key-value pairs)
+# - empty ahead of restOfLine advances past leading whitespace, does implicit lstrip during parsing
+macroDef = Suppress("#define") + ident + Suppress("=") + empty + restOfLine
+macros = dict(list(macroDef.searchString(testData)))
+print("macros =", macros)
+print()
+
+
+#################
+print("Examples of a transformer")
+print("----------------------")
+
+# convert C++ namespaces to mangled C-compatible names
+scopedIdent = ident + OneOrMore( Literal("::").suppress() + ident )
+scopedIdent.setParseAction(lambda t: "_".join(t))
+
+print("(replace namespace-scoped names with C-compatible names)")
+print(scopedIdent.transformString( testData ))
+
+
+# or a crude pre-processor (use parse actions to replace matching text)
+def substituteMacro(s,l,t):
+ if t[0] in macros:
+ return macros[t[0]]
+ident.setParseAction( substituteMacro )
+ident.ignore(macroDef)
+
+print("(simulate #define pre-processor)")
+print(ident.transformString( testData ))
+
+
+
+#################
+print("Example of a stripper")
+print("----------------------")
+
+from pyparsing import dblQuotedString, LineStart
+
+# remove all string macro definitions (after extracting to a string resource table?)
+stringMacroDef = Literal("#define") + ident + "=" + dblQuotedString + LineStart()
+stringMacroDef.setParseAction( replaceWith("") )
+
+print(stringMacroDef.transformString( testData ))
diff --git a/trunk/src/examples/scanYahoo.py b/trunk/src/examples/scanYahoo.py
new file mode 100644
index 0000000..825c169
--- /dev/null
+++ b/trunk/src/examples/scanYahoo.py
@@ -0,0 +1,14 @@
+from pyparsing import makeHTMLTags,SkipTo,htmlComment
+import urllib.request, urllib.parse, urllib.error
+
+serverListPage = urllib.request.urlopen( "http://www.yahoo.com" )
+htmlText = serverListPage.read()
+serverListPage.close()
+
+aStart,aEnd = makeHTMLTags("A")
+
+link = aStart + SkipTo(aEnd).setResultsName("link") + aEnd
+link.ignore(htmlComment)
+
+for toks,start,end in link.scanString(htmlText):
+ print(toks.link, "->", toks.startA.href) \ No newline at end of file
diff --git a/trunk/src/examples/searchParserAppDemo.py b/trunk/src/examples/searchParserAppDemo.py
new file mode 100644
index 0000000..259e0e3
--- /dev/null
+++ b/trunk/src/examples/searchParserAppDemo.py
@@ -0,0 +1,34 @@
+from searchparser import SearchQueryParser
+
+products = [ "grape juice", "grape jelly", "orange juice", "orange jujubees",
+ "strawberry jam", "prune juice", "prune butter", "orange marmalade",
+ "grapefruit juice" ]
+
+class FruitSearchParser(SearchQueryParser):
+ def GetWord(self, word):
+ return set( p for p in products if p.startswith(word + " ") )
+
+ def GetWordWildcard(self, word):
+ return set( p for p in products if p.startswith(word[:-1]) )
+
+ def GetQuotes(self, search_string, tmp_result):
+ result = Set()
+ # I have no idea how to use this feature...
+ return result
+
+ def GetNot(self, not_set):
+ return set( products ) - not_set
+
+
+parser = FruitSearchParser()
+
+tests = """\
+ grape or orange
+ grape*
+ not(grape*)
+ prune and grape""".splitlines()
+
+for t in tests:
+ print(t.strip())
+ print(parser.Parse(t))
+ print('') \ No newline at end of file
diff --git a/trunk/src/examples/searchparser.py b/trunk/src/examples/searchparser.py
new file mode 100644
index 0000000..e5b40a7
--- /dev/null
+++ b/trunk/src/examples/searchparser.py
@@ -0,0 +1,292 @@
+"""Search query parser
+
+version 2006-03-09
+
+This search query parser uses the excellent Pyparsing module
+(http://pyparsing.sourceforge.net/) to parse search queries by users.
+It handles:
+
+* 'and', 'or' and implicit 'and' operators;
+* parentheses;
+* quoted strings;
+* wildcards at the end of a search term (help*);
+
+Requirements:
+* Python
+* Pyparsing
+
+If you run this script, it will perform a number of tests. To use is as a
+module, you should use inheritance on the SearchQueryParser class and overwrite
+the Get... methods. The ParserTest class gives a very simple example of how this
+could work.
+
+-------------------------------------------------------------------------------
+Copyright (c) 2006, Estrate, the Netherlands
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+* Neither the name of Estrate nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+CONTRIBUTORS:
+- Steven Mooij
+- Rudolph Froger
+- Paul McGuire
+
+TODO:
+- add more docs
+- ask someone to check my English texts
+- add more kinds of wildcards ('*' at the beginning and '*' inside a word)?
+"""
+from pyparsing import Word, alphanums, Keyword, Group, Combine, Forward, Suppress, Optional, OneOrMore, oneOf
+
+class SearchQueryParser:
+
+ def __init__(self):
+ self._methods = {
+ 'and': self.evaluateAnd,
+ 'or': self.evaluateOr,
+ 'not': self.evaluateNot,
+ 'parenthesis': self.evaluateParenthesis,
+ 'quotes': self.evaluateQuotes,
+ 'word': self.evaluateWord,
+ 'wordwildcard': self.evaluateWordWildcard,
+ }
+ self._parser = self.parser()
+
+ def parser(self):
+ """
+ This function returns a parser.
+ The grammar should be like most full text search engines (Google, Tsearch, Lucene).
+
+ Grammar:
+ - a query consists of alphanumeric words, with an optional '*' wildcard
+ at the end of a word
+ - a sequence of words between quotes is a literal string
+ - words can be used together by using operators ('and' or 'or')
+ - words with operators can be grouped with parenthesis
+ - a word or group of words can be preceded by a 'not' operator
+ - the 'and' operator precedes an 'or' operator
+ - if an operator is missing, use an 'and' operator
+ """
+ operatorOr = Forward()
+
+ operatorWord = Group(Combine(Word(alphanums) + Suppress('*'))).setResultsName('wordwildcard') | \
+ Group(Word(alphanums)).setResultsName('word')
+
+ operatorQuotesContent = Forward()
+ operatorQuotesContent << (
+ (operatorWord + operatorQuotesContent) | operatorWord
+ )
+
+ operatorQuotes = Group(
+ Suppress('"') + operatorQuotesContent + Suppress('"')
+ ).setResultsName("quotes") | operatorWord
+
+ operatorParenthesis = Group(
+ (Suppress("(") + operatorOr + Suppress(")"))
+ ).setResultsName("parenthesis") | operatorQuotes
+
+ operatorNot = Forward()
+ operatorNot << (Group(
+ Suppress(Keyword("not", caseless=True)) + operatorNot
+ ).setResultsName("not") | operatorParenthesis)
+
+ operatorAnd = Forward()
+ operatorAnd << (Group(
+ operatorNot + Suppress(Keyword("and", caseless=True)) + operatorAnd
+ ).setResultsName("and") | Group(
+ operatorNot + OneOrMore(~oneOf("and or") + operatorAnd)
+ ).setResultsName("and") | operatorNot)
+
+ operatorOr << (Group(
+ operatorAnd + Suppress(Keyword("or", caseless=True)) + operatorOr
+ ).setResultsName("or") | operatorAnd)
+
+ return operatorOr.parseString
+
+ def evaluateAnd(self, argument):
+ return self.evaluate(argument[0]).intersection(self.evaluate(argument[1]))
+
+ def evaluateOr(self, argument):
+ return self.evaluate(argument[0]).union(self.evaluate(argument[1]))
+
+ def evaluateNot(self, argument):
+ return self.GetNot(self.evaluate(argument[0]))
+
+ def evaluateParenthesis(self, argument):
+ return self.evaluate(argument[0])
+
+ def evaluateQuotes(self, argument):
+ """Evaluate quoted strings
+
+ First is does an 'and' on the indidual search terms, then it asks the
+ function GetQuoted to only return the subset of ID's that contain the
+ literal string.
+ """
+ r = set()
+ search_terms = []
+ for item in argument:
+ search_terms.append(item[0])
+ if len(r) == 0:
+ r = self.evaluate(item)
+ else:
+ r = r.intersection(self.evaluate(item))
+ return self.GetQuotes(' '.join(search_terms), r)
+
+ def evaluateWord(self, argument):
+ return self.GetWord(argument[0])
+
+ def evaluateWordWildcard(self, argument):
+ return self.GetWordWildcard(argument[0])
+
+ def evaluate(self, argument):
+ return self._methods[argument.getName()](argument)
+
+ def Parse(self, query):
+ #print self._parser(query)[0]
+ return self.evaluate(self._parser(query)[0])
+
+ def GetWord(self, word):
+ return set()
+
+ def GetWordWildcard(self, word):
+ return set()
+
+ def GetQuotes(self, search_string, tmp_result):
+ return set()
+
+ def GetNot(self, not_set):
+ return set().difference(not_set)
+
+
+class ParserTest(SearchQueryParser):
+ """Tests the parser with some search queries
+ tests containts a dictionary with tests and expected results.
+ """
+ tests = {
+ 'help': set([1, 2, 4, 5]),
+ 'help or hulp': set([1, 2, 3, 4, 5]),
+ 'help and hulp': set([2]),
+ 'help hulp': set([2]),
+ 'help and hulp or hilp': set([2, 3, 4]),
+ 'help or hulp and hilp': set([1, 2, 3, 4, 5]),
+ 'help or hulp or hilp or halp': set([1, 2, 3, 4, 5, 6]),
+ '(help or hulp) and (hilp or halp)': set([3, 4, 5]),
+ 'help and (hilp or halp)': set([4, 5]),
+ '(help and (hilp or halp)) or hulp': set([2, 3, 4, 5]),
+ 'not help': set([3, 6, 7, 8]),
+ 'not hulp and halp': set([5, 6]),
+ 'not (help and halp)': set([1, 2, 3, 4, 6, 7, 8]),
+ '"help me please"': set([2]),
+ '"help me please" or hulp': set([2, 3]),
+ '"help me please" or (hulp and halp)': set([2]),
+ 'help*': set([1, 2, 4, 5, 8]),
+ 'help or hulp*': set([1, 2, 3, 4, 5]),
+ 'help* and hulp': set([2]),
+ 'help and hulp* or hilp': set([2, 3, 4]),
+ 'help* or hulp or hilp or halp': set([1, 2, 3, 4, 5, 6, 8]),
+ '(help or hulp*) and (hilp* or halp)': set([3, 4, 5]),
+ 'help* and (hilp* or halp*)': set([4, 5]),
+ '(help and (hilp* or halp)) or hulp*': set([2, 3, 4, 5]),
+ 'not help* and halp': set([6]),
+ 'not (help* and helpe*)': set([1, 2, 3, 4, 5, 6, 7]),
+ '"help* me please"': set([2]),
+ '"help* me* please" or hulp*': set([2, 3]),
+ '"help me please*" or (hulp and halp)': set([2]),
+ '"help me please" not (hulp and halp)': set([2]),
+ '"help me please" hulp': set([2]),
+ 'help and hilp and not holp': set([4]),
+ 'help hilp not holp': set([4]),
+ 'help hilp and not holp': set([4]),
+ }
+
+ docs = {
+ 1: 'help',
+ 2: 'help me please hulp',
+ 3: 'hulp hilp',
+ 4: 'help hilp',
+ 5: 'halp thinks he needs help',
+ 6: 'he needs halp',
+ 7: 'nothing',
+ 8: 'helper',
+ }
+
+ index = {
+ 'help': set((1, 2, 4, 5)),
+ 'me': set((2,)),
+ 'please': set((2,)),
+ 'hulp': set((2, 3,)),
+ 'hilp': set((3, 4,)),
+ 'halp': set((5, 6,)),
+ 'thinks': set((5,)),
+ 'he': set((5, 6,)),
+ 'needs': set((5, 6,)),
+ 'nothing': set((7,)),
+ 'helper': set((8,)),
+ }
+
+ def GetWord(self, word):
+ if (word in self.index):
+ return self.index[word]
+ else:
+ return set()
+
+ def GetWordWildcard(self, word):
+ result = set()
+ for item in list(self.index.keys()):
+ if word == item[0:len(word)]:
+ result = result.union(self.index[item])
+ return result
+
+ def GetQuotes(self, search_string, tmp_result):
+ result = set()
+ for item in tmp_result:
+ if self.docs[item].count(search_string):
+ result.add(item)
+ return result
+
+ def GetNot(self, not_set):
+ all = set(list(self.docs.keys()))
+ return all.difference(not_set)
+
+ def Test(self):
+ all_ok = True
+ for item in list(self.tests.keys()):
+ print(item)
+ r = self.Parse(item)
+ e = self.tests[item]
+ print('Result: %s' % r)
+ print('Expect: %s' % e)
+ if e == r:
+ print('Test OK')
+ else:
+ all_ok = False
+ print('>>>>>>>>>>>>>>>>>>>>>>Test ERROR<<<<<<<<<<<<<<<<<<<<<')
+ print('')
+ return all_ok
+
+if __name__=='__main__':
+ if ParserTest().Test():
+ print('All tests OK')
+ else:
+ print('One or more tests FAILED')
diff --git a/trunk/src/examples/select_parser.py b/trunk/src/examples/select_parser.py
new file mode 100644
index 0000000..da106ac
--- /dev/null
+++ b/trunk/src/examples/select_parser.py
@@ -0,0 +1,126 @@
+# select_parser.py
+# Copyright 2010, Paul McGuire
+#
+# a simple SELECT statement parser, taken from SQLite's SELECT statement
+# definition at http://www.sqlite.org/lang_select.html
+#
+from pyparsing import *
+ParserElement.enablePackrat()
+
+LPAR,RPAR,COMMA = map(Suppress,"(),")
+select_stmt = Forward().setName("select statement")
+
+# keywords
+(UNION, ALL, AND, INTERSECT, EXCEPT, COLLATE, ASC, DESC, ON, USING, NATURAL, INNER,
+ CROSS, LEFT, OUTER, JOIN, AS, INDEXED, NOT, SELECT, DISTINCT, FROM, WHERE, GROUP, BY,
+ HAVING, ORDER, BY, LIMIT, OFFSET, OR) = map(CaselessKeyword, """UNION, ALL, AND, INTERSECT,
+ EXCEPT, COLLATE, ASC, DESC, ON, USING, NATURAL, INNER, CROSS, LEFT, OUTER, JOIN, AS, INDEXED, NOT, SELECT,
+ DISTINCT, FROM, WHERE, GROUP, BY, HAVING, ORDER, BY, LIMIT, OFFSET, OR""".replace(",","").split())
+(CAST, ISNULL, NOTNULL, NULL, IS, BETWEEN, ELSE, END, CASE, WHEN, THEN, EXISTS,
+ COLLATE, IN, LIKE, GLOB, REGEXP, MATCH, ESCAPE, CURRENT_TIME, CURRENT_DATE,
+ CURRENT_TIMESTAMP) = map(CaselessKeyword, """CAST, ISNULL, NOTNULL, NULL, IS, BETWEEN, ELSE,
+ END, CASE, WHEN, THEN, EXISTS, COLLATE, IN, LIKE, GLOB, REGEXP, MATCH, ESCAPE,
+ CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP""".replace(",","").split())
+keyword = MatchFirst((UNION, ALL, INTERSECT, EXCEPT, COLLATE, ASC, DESC, ON, USING, NATURAL, INNER,
+ CROSS, LEFT, OUTER, JOIN, AS, INDEXED, NOT, SELECT, DISTINCT, FROM, WHERE, GROUP, BY,
+ HAVING, ORDER, BY, LIMIT, OFFSET, CAST, ISNULL, NOTNULL, NULL, IS, BETWEEN, ELSE, END, CASE, WHEN, THEN, EXISTS,
+ COLLATE, IN, LIKE, GLOB, REGEXP, MATCH, ESCAPE, CURRENT_TIME, CURRENT_DATE,
+ CURRENT_TIMESTAMP))
+
+identifier = ~keyword + Word(alphas, alphanums+"_")
+collation_name = identifier.copy()
+column_name = identifier.copy()
+column_alias = identifier.copy()
+table_name = identifier.copy()
+table_alias = identifier.copy()
+index_name = identifier.copy()
+function_name = identifier.copy()
+parameter_name = identifier.copy()
+database_name = identifier.copy()
+
+# expression
+expr = Forward().setName("expression")
+
+integer = Regex(r"[+-]?\d+")
+numeric_literal = Regex(r"\d+(\.\d*)?([eE][+-]?\d+)?")
+string_literal = QuotedString("'")
+blob_literal = Regex(r"[xX]'[0-9A-Fa-f]+'")
+literal_value = ( numeric_literal | string_literal | blob_literal |
+ NULL | CURRENT_TIME | CURRENT_DATE | CURRENT_TIMESTAMP )
+bind_parameter = (
+ Word("?",nums) |
+ Combine(oneOf(": @ $") + parameter_name)
+ )
+type_name = oneOf("TEXT REAL INTEGER BLOB NULL")
+
+expr_term = (
+ CAST + LPAR + expr + AS + type_name + RPAR |
+ EXISTS + LPAR + select_stmt + RPAR |
+ function_name.setName("function_name") + LPAR + Optional(delimitedList(expr)) + RPAR |
+ literal_value |
+ bind_parameter |
+ Combine(identifier+('.'+identifier)*(0,2)).setName("ident")
+ )
+
+UNARY,BINARY,TERNARY=1,2,3
+expr << infixNotation(expr_term,
+ [
+ (oneOf('- + ~') | NOT, UNARY, opAssoc.RIGHT),
+ (ISNULL | NOTNULL | NOT + NULL, UNARY, opAssoc.LEFT),
+ ('||', BINARY, opAssoc.LEFT),
+ (oneOf('* / %'), BINARY, opAssoc.LEFT),
+ (oneOf('+ -'), BINARY, opAssoc.LEFT),
+ (oneOf('<< >> & |'), BINARY, opAssoc.LEFT),
+ (oneOf('< <= > >='), BINARY, opAssoc.LEFT),
+ (oneOf('= == != <>') | IS | IN | LIKE | GLOB | MATCH | REGEXP, BINARY, opAssoc.LEFT),
+ ((BETWEEN,AND), TERNARY, opAssoc.LEFT),
+ (IN + LPAR + Group(select_stmt | delimitedList(expr)) + RPAR, UNARY, opAssoc.LEFT),
+ (AND, BINARY, opAssoc.LEFT),
+ (OR, BINARY, opAssoc.LEFT),
+ ])
+
+compound_operator = (UNION + Optional(ALL) | INTERSECT | EXCEPT)
+
+ordering_term = Group(expr('order_key') + Optional(COLLATE + collation_name('collate')) + Optional(ASC | DESC)('direction'))
+
+join_constraint = Group(Optional(ON + expr | USING + LPAR + Group(delimitedList(column_name)) + RPAR))
+
+join_op = COMMA | Group(Optional(NATURAL) + Optional(INNER | CROSS | LEFT + OUTER | LEFT | OUTER) + JOIN)
+
+join_source = Forward()
+single_source = ( (Group(database_name("database") + "." + table_name("table")) | table_name("table")) +
+ Optional(Optional(AS) + table_alias("table_alias")) +
+ Optional(INDEXED + BY + index_name("name") | NOT + INDEXED)("index") |
+ (LPAR + select_stmt + RPAR + Optional(Optional(AS) + table_alias)) |
+ (LPAR + join_source + RPAR) )
+
+join_source << (Group(single_source + OneOrMore(join_op + single_source + join_constraint)) |
+ single_source)
+
+result_column = "*" | table_name + "." + "*" | Group(expr + Optional(Optional(AS) + column_alias))
+select_core = (SELECT + Optional(DISTINCT | ALL) + Group(delimitedList(result_column))("columns") +
+ Optional(FROM + join_source("from")) +
+ Optional(WHERE + expr("where_expr")) +
+ Optional(GROUP + BY + Group(delimitedList(ordering_term)("group_by_terms")) +
+ Optional(HAVING + expr("having_expr"))))
+
+select_stmt << (select_core + ZeroOrMore(compound_operator + select_core) +
+ Optional(ORDER + BY + Group(delimitedList(ordering_term))("order_by_terms")) +
+ Optional(LIMIT + (Group(expr + OFFSET + expr) | Group(expr + COMMA + expr) | expr)("limit")))
+
+tests = """\
+ select * from xyzzy where z > 100
+ select * from xyzzy where z > 100 order by zz
+ select * from xyzzy
+ select z.* from xyzzy
+ select a, b from test_table where 1=1 and b='yes'
+ select a, b from test_table where 1=1 and b in (select bb from foo)
+ select z.a, b from test_table where 1=1 and b in (select bb from foo)
+ select z.a, b from test_table where 1=1 and b in (select bb from foo) order by b,c desc,d
+ select z.a, b from test_table left join test2_table where 1=1 and b in (select bb from foo)
+ select a, db.table.b as BBB from db.table where 1=1 and BBB='yes'
+ select a, db.table.b as BBB from test_table,db.table where 1=1 and BBB='yes'
+ select a, db.table.b as BBB from test_table,db.table where 1=1 and BBB='yes' limit 50
+ """
+
+select_stmt.runTests(tests)
diff --git a/trunk/src/examples/sexpParser.py b/trunk/src/examples/sexpParser.py
new file mode 100644
index 0000000..963d153
--- /dev/null
+++ b/trunk/src/examples/sexpParser.py
@@ -0,0 +1,167 @@
+# sexpParser.py
+#
+# Demonstration of the pyparsing module, implementing a simple S-expression
+# parser.
+#
+# Updates:
+# November, 2011 - fixed errors in precedence of alternatives in simpleString;
+# fixed exception raised in verifyLen to properly signal the input string
+# and exception location so that markInputline works correctly; fixed
+# definition of decimal to accept a single '0' and optional leading '-'
+# sign; updated tests to improve parser coverage
+#
+# Copyright 2007-2011, by Paul McGuire
+#
+"""
+BNF reference: http://theory.lcs.mit.edu/~rivest/sexp.txt
+
+<sexp> :: <string> | <list>
+<string> :: <display>? <simple-string> ;
+<simple-string> :: <raw> | <token> | <base-64> | <hexadecimal> |
+ <quoted-string> ;
+<display> :: "[" <simple-string> "]" ;
+<raw> :: <decimal> ":" <bytes> ;
+<decimal> :: <decimal-digit>+ ;
+ -- decimal numbers should have no unnecessary leading zeros
+<bytes> -- any string of bytes, of the indicated length
+<token> :: <tokenchar>+ ;
+<base-64> :: <decimal>? "|" ( <base-64-char> | <whitespace> )* "|" ;
+<hexadecimal> :: "#" ( <hex-digit> | <white-space> )* "#" ;
+<quoted-string> :: <decimal>? <quoted-string-body>
+<quoted-string-body> :: "\"" <bytes> "\""
+<list> :: "(" ( <sexp> | <whitespace> )* ")" ;
+<whitespace> :: <whitespace-char>* ;
+<token-char> :: <alpha> | <decimal-digit> | <simple-punc> ;
+<alpha> :: <upper-case> | <lower-case> | <digit> ;
+<lower-case> :: "a" | ... | "z" ;
+<upper-case> :: "A" | ... | "Z" ;
+<decimal-digit> :: "0" | ... | "9" ;
+<hex-digit> :: <decimal-digit> | "A" | ... | "F" | "a" | ... | "f" ;
+<simple-punc> :: "-" | "." | "/" | "_" | ":" | "*" | "+" | "=" ;
+<whitespace-char> :: " " | "\t" | "\r" | "\n" ;
+<base-64-char> :: <alpha> | <decimal-digit> | "+" | "/" | "=" ;
+<null> :: "" ;
+"""
+
+from pyparsing import *
+from base64 import b64decode
+import pprint
+
+def verifyLen(s,l,t):
+ t = t[0]
+ if t.len is not None:
+ t1len = len(t[1])
+ if t1len != t.len:
+ raise ParseFatalException(s,l,\
+ "invalid data of length %d, expected %s" % (t1len, t.len))
+ return t[1]
+
+# define punctuation literals
+LPAR, RPAR, LBRK, RBRK, LBRC, RBRC, VBAR = map(Suppress, "()[]{}|")
+
+decimal = Regex(r'0|[1-9]\d*').setParseAction(lambda t: int(t[0]))
+hexadecimal = ("#" + OneOrMore(Word(hexnums)) + "#")\
+ .setParseAction(lambda t: int("".join(t[1:-1]),16))
+bytes = Word(printables)
+raw = Group(decimal("len") + Suppress(":") + bytes).setParseAction(verifyLen)
+token = Word(alphanums + "-./_:*+=")
+base64_ = Group(Optional(decimal|hexadecimal,default=None)("len") + VBAR
+ + OneOrMore(Word( alphanums +"+/=" )).setParseAction(lambda t: b64decode("".join(t)))
+ + VBAR).setParseAction(verifyLen)
+
+qString = Group(Optional(decimal,default=None)("len") +
+ dblQuotedString.setParseAction(removeQuotes)).setParseAction(verifyLen)
+simpleString = base64_ | raw | decimal | token | hexadecimal | qString
+
+# extended definitions
+decimal = Regex(r'-?0|[1-9]\d*').setParseAction(lambda t: int(t[0]))
+real = Regex(r"[+-]?\d+\.\d*([eE][+-]?\d+)?").setParseAction(lambda tokens: float(tokens[0]))
+token = Word(alphanums + "-./_:*+=!<>")
+
+simpleString = real | base64_ | raw | decimal | token | hexadecimal | qString
+
+display = LBRK + simpleString + RBRK
+string_ = Optional(display) + simpleString
+
+sexp = Forward()
+sexpList = Group(LPAR + ZeroOrMore(sexp) + RPAR)
+sexp << ( string_ | sexpList )
+
+######### Test data ###########
+test00 = """(snicker "abc" (#03# |YWJj|))"""
+test01 = """(certificate
+ (issuer
+ (name
+ (public-key
+ rsa-with-md5
+ (e 15 |NFGq/E3wh9f4rJIQVXhS|)
+ (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|))
+ aid-committee))
+ (subject
+ (ref
+ (public-key
+ rsa-with-md5
+ (e |NFGq/E3wh9f4rJIQVXhS|)
+ (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|))
+ tom
+ mother))
+ (not-before "1997-01-01_09:00:00")
+ (not-after "1998-01-01_09:00:00")
+ (tag
+ (spend (account "12345678") (* numeric range "1" "1000"))))
+"""
+test02 = """(lambda (x) (* x x))"""
+test03 = """(def length
+ (lambda (x)
+ (cond
+ ((not x) 0)
+ ( t (+ 1 (length (cdr x))))
+ )
+ )
+)
+"""
+test04 = """(2:XX "abc" (#03# |YWJj|))"""
+test05 = """(if (is (window_name) "XMMS") (set_workspace 2))"""
+test06 = """(if
+ (and
+ (is (application_name) "Firefox")
+ (or
+ (contains (window_name) "Enter name of file to save to")
+ (contains (window_name) "Save As")
+ (contains (window_name) "Save Image")
+ ()
+ )
+ )
+ (geometry "+140+122")
+)
+"""
+test07 = """(defun factorial (x)
+ (if (zerop x) 1
+ (* x (factorial (- x 1)))))
+ """
+test51 = """(2:XX "abc" (#30# |YWJj|))"""
+test51error = """(3:XX "abc" (#30# |YWJj|))"""
+
+test52 = """
+ (and
+ (or (> uid 1000)
+ (!= gid 20)
+ )
+ (> quota 5.0e+03)
+ )
+ """
+
+# Run tests
+t = None
+alltests = [ locals()[t] for t in sorted(locals()) if t.startswith("test") ]
+
+for t in alltests:
+ print('-'*50)
+ print(t)
+ try:
+ sexpr = sexp.parseString(t, parseAll=True)
+ pprint.pprint(sexpr.asList())
+ except ParseFatalException as pfe:
+ print("Error:", pfe.msg)
+ print(pfe.markInputline('^'))
+ print()
diff --git a/trunk/src/examples/shapes.py b/trunk/src/examples/shapes.py
new file mode 100644
index 0000000..b5a0ebd
--- /dev/null
+++ b/trunk/src/examples/shapes.py
@@ -0,0 +1,64 @@
+# shapes.py
+#
+# A sample program showing how parse actions can convert parsed
+# strings into a data type or object.
+#
+# Copyright 2012, Paul T. McGuire
+#
+
+# define class hierarchy of Shape classes, with polymorphic area method
+class Shape(object):
+ def __init__(self, tokens):
+ self.__dict__.update(tokens.asDict())
+
+ def area(self):
+ raise NotImplementedException()
+
+ def __str__(self):
+ return "<%s>: %s" % (self.__class__.__name__, self.__dict__)
+
+class Square(Shape):
+ def area(self):
+ return self.side**2
+
+class Rectangle(Shape):
+ def area(self):
+ return self.width * self.height
+
+class Circle(Shape):
+ def area(self):
+ return 3.14159 * self.radius**2
+
+
+from pyparsing import *
+
+number = Regex(r'-?\d+(\.\d*)?').setParseAction(lambda t:float(t[0]))
+
+# Shape expressions:
+# square : S <centerx> <centery> <side>
+# rectangle: R <centerx> <centery> <width> <height>
+# circle : C <centerx> <centery> <diameter>
+
+squareDefn = "S" + number('centerx') + number('centery') + number('side')
+rectDefn = "R" + number('centerx') + number('centery') + number('width') + number('height')
+circleDefn = "C" + number('centerx') + number('centery') + number('diameter')
+
+squareDefn.setParseAction(Square)
+rectDefn.setParseAction(Rectangle)
+
+def computeRadius(tokens):
+ tokens['radius'] = tokens.diameter/2.0
+circleDefn.setParseAction(computeRadius, Circle)
+
+shapeExpr = squareDefn | rectDefn | circleDefn
+
+tests = """\
+C 0 0 100
+R 10 10 20 50
+S -1 5 10""".splitlines()
+
+for t in tests:
+ shape = shapeExpr.parseString(t)[0]
+ print(shape)
+ print("Area:", shape.area())
+ print()
diff --git a/trunk/src/examples/simpleArith.py b/trunk/src/examples/simpleArith.py
new file mode 100644
index 0000000..825956b
--- /dev/null
+++ b/trunk/src/examples/simpleArith.py
@@ -0,0 +1,67 @@
+#
+# simpleArith.py
+#
+# Example of defining an arithmetic expression parser using
+# the infixNotation helper method in pyparsing.
+#
+# Copyright 2006, by Paul McGuire
+#
+
+from pyparsing import *
+
+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('!')
+
+# To use the infixNotation helper:
+# 1. Define the "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 infixNotation 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
+# - 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 infixNotation 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.
+#
+expr = infixNotation( 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^2^2",
+ "(9! + -2) * 3^2^2",
+ "M*X + B",
+ "M*(X + B)",
+ "1+2*-3^4*5+-+-6",]
+for t in test:
+ print(t)
+ print(expr.parseString(t))
+ print('')
+
diff --git a/trunk/src/examples/simpleBool.py b/trunk/src/examples/simpleBool.py
new file mode 100644
index 0000000..5f355b7
--- /dev/null
+++ b/trunk/src/examples/simpleBool.py
@@ -0,0 +1,102 @@
+#
+# simpleBool.py
+#
+# Example of defining a boolean logic parser using
+# the operatorGrammar helper method in pyparsing.
+#
+# In this example, parse actions associated with each
+# operator expression will "compile" the expression
+# into BoolXXX class instances, which can then
+# later be evaluated for their boolean value.
+#
+# Copyright 2006, by Paul McGuire
+# Updated 2013-Sep-14 - improved Python 2/3 cross-compatibility
+#
+from pyparsing import infixNotation, opAssoc, Keyword, Word, alphas
+
+# define classes to be built at parse time, as each matching
+# expression type is parsed
+class BoolOperand(object):
+ def __init__(self,t):
+ self.label = t[0]
+ self.value = eval(t[0])
+ def __bool__(self):
+ return self.value
+ def __str__(self):
+ return self.label
+ __repr__ = __str__
+ __nonzero__ = __bool__
+
+class BoolBinOp(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)) + ")"
+ def __bool__(self):
+ return self.evalop(bool(a) for a in self.args)
+ __nonzero__ = __bool__
+ __repr__ = __str__
+
+class BoolAnd(BoolBinOp):
+ reprsymbol = '&'
+ evalop = all
+
+class BoolOr(BoolBinOp):
+ reprsymbol = '|'
+ evalop = any
+
+class BoolNot(object):
+ def __init__(self,t):
+ self.arg = t[0][1]
+ def __bool__(self):
+ v = bool(self.arg)
+ return not v
+ def __str__(self):
+ return "~" + str(self.arg)
+ __repr__ = __str__
+ __nonzero__ = __bool__
+
+TRUE = Keyword("True")
+FALSE = Keyword("False")
+boolOperand = TRUE | FALSE | Word(alphas,max=1)
+boolOperand.setParseAction(BoolOperand)
+
+# define expression, based on expression operand and
+# list of operations in precedence order
+boolExpr = infixNotation( boolOperand,
+ [
+ ("not", 1, opAssoc.RIGHT, BoolNot),
+ ("and", 2, opAssoc.LEFT, BoolAnd),
+ ("or", 2, opAssoc.LEFT, BoolOr),
+ ])
+
+
+if __name__ == "__main__":
+ p = True
+ q = False
+ r = True
+ tests = [("p", True),
+ ("q", False),
+ ("p and q", False),
+ ("p and not q", True),
+ ("not not p", True),
+ ("not(p and q)", True),
+ ("q or not p and r", False),
+ ("q or not p or not r", False),
+ ("q or not (p and r)", False),
+ ("p or q or r", True),
+ ("p or q or r and False", True),
+ ("(p or q or r) and False", False),
+ ]
+
+ print("p =", p)
+ print("q =", q)
+ print("r =", r)
+ print()
+ for t,expected in tests:
+ res = boolExpr.parseString(t)[0]
+ success = "PASS" if bool(res) == expected else "FAIL"
+ print (t,'\n', res, '=', bool(res),'\n', success, '\n')
+
+
diff --git a/trunk/src/examples/simpleSQL.py b/trunk/src/examples/simpleSQL.py
new file mode 100644
index 0000000..66dc18c
--- /dev/null
+++ b/trunk/src/examples/simpleSQL.py
@@ -0,0 +1,72 @@
+# simpleSQL.py
+#
+# simple demo of using the parsing library to do simple-minded SQL parsing
+# could be extended to include where clauses etc.
+#
+# Copyright (c) 2003,2016, Paul McGuire
+#
+from pyparsing import Literal, CaselessLiteral, Word, delimitedList, Optional, \
+ Combine, Group, alphas, nums, alphanums, ParseException, Forward, oneOf, quotedString, \
+ ZeroOrMore, restOfLine, Keyword, upcaseTokens
+
+# define SQL tokens
+selectStmt = Forward()
+SELECT = Keyword("select", caseless=True)
+FROM = Keyword("from", caseless=True)
+WHERE = Keyword("where", caseless=True)
+
+ident = Word( alphas, alphanums + "_$" ).setName("identifier")
+columnName = ( delimitedList( ident, ".", combine=True ) ).addParseAction(upcaseTokens)
+columnNameList = Group( delimitedList( columnName ) )
+tableName = ( delimitedList( ident, ".", combine=True ) ).addParseAction(upcaseTokens)
+tableNameList = Group( delimitedList( tableName ) )
+
+whereExpression = Forward()
+and_ = Keyword("and", caseless=True)
+or_ = Keyword("or", caseless=True)
+in_ = Keyword("in", caseless=True)
+
+E = CaselessLiteral("E")
+binop = oneOf("= != < > >= <= eq ne lt le gt ge", caseless=True)
+arithSign = Word("+-",exact=1)
+realNum = Combine( Optional(arithSign) + ( Word( nums ) + "." + Optional( Word(nums) ) |
+ ( "." + Word(nums) ) ) +
+ Optional( E + Optional(arithSign) + Word(nums) ) )
+intNum = Combine( Optional(arithSign) + Word( nums ) +
+ Optional( E + Optional("+") + Word(nums) ) )
+
+columnRval = realNum | intNum | quotedString | columnName # need to add support for alg expressions
+whereCondition = Group(
+ ( columnName + binop + columnRval ) |
+ ( columnName + in_ + "(" + delimitedList( columnRval ) + ")" ) |
+ ( columnName + in_ + "(" + selectStmt + ")" ) |
+ ( "(" + whereExpression + ")" )
+ )
+whereExpression << whereCondition + ZeroOrMore( ( and_ | or_ ) + whereExpression )
+
+# define the grammar
+selectStmt <<= (SELECT + ('*' | columnNameList)("columns") +
+ FROM + tableNameList( "tables" ) +
+ Optional(Group(WHERE + whereExpression), "")("where"))
+
+simpleSQL = selectStmt
+
+# define Oracle comment format, and ignore them
+oracleSqlComment = "--" + restOfLine
+simpleSQL.ignore( oracleSqlComment )
+
+if __name__ == "__main__":
+ simpleSQL.runTests("""\
+ SELECT * from XYZZY, ABC
+ select * from SYS.XYZZY
+ Select A from Sys.dual
+ Select A,B,C from Sys.dual
+ Select A, B, C from Sys.dual
+ Select A, B, C from Sys.dual, Table2
+ Xelect A, B, C from Sys.dual
+ Select A, B, C frox Sys.dual
+ Select
+ Select &&& frox Sys.dual
+ Select A from Sys.dual where a in ('RED','GREEN','BLUE')
+ Select A from Sys.dual where a in ('RED','GREEN','BLUE') and b in (10,20,30)
+ Select A,b from table1,table2 where table1.id eq table2.id -- test out comparison operators""")
diff --git a/trunk/src/examples/simpleWiki.py b/trunk/src/examples/simpleWiki.py
new file mode 100644
index 0000000..7a2a0ce
--- /dev/null
+++ b/trunk/src/examples/simpleWiki.py
@@ -0,0 +1,32 @@
+from pyparsing import *
+
+wikiInput = """
+Here is a simple Wiki input:
+ *This is in italics.*
+ **This is in bold!**
+ ***This is in bold italics!***
+ Here's a URL to {{Pyparsing's Wiki Page->http://pyparsing.wikispaces.com}}
+"""
+
+def convertToHTML(opening,closing):
+ def conversionParseAction(s,l,t):
+ return opening + t[0] + closing
+ return conversionParseAction
+
+italicized = QuotedString("*").setParseAction(convertToHTML("<I>","</I>"))
+bolded = QuotedString("**").setParseAction(convertToHTML("<B>","</B>"))
+boldItalicized = QuotedString("***").setParseAction(convertToHTML("<B><I>","</I></B>"))
+def convertToHTML_A(s,l,t):
+ try:
+ text,url=t[0].split("->")
+ except ValueError:
+ raise ParseFatalException(s,l,"invalid URL link reference: " + t[0])
+ return '<A href="%s">%s</A>' % (url,text)
+
+urlRef = QuotedString("{{",endQuoteChar="}}").setParseAction(convertToHTML_A)
+
+wikiMarkup = urlRef | boldItalicized | bolded | italicized
+
+print(wikiInput)
+print()
+print(wikiMarkup.transformString(wikiInput))
diff --git a/trunk/src/examples/snmp_api.h b/trunk/src/examples/snmp_api.h
new file mode 100644
index 0000000..d75cb12
--- /dev/null
+++ b/trunk/src/examples/snmp_api.h
@@ -0,0 +1,795 @@
+#ifndef SNMP_API_H
+#define SNMP_API_H
+
+/*
+ * snmp_api.h - API for access to snmp.
+ *
+ * Caution: when using this library in a multi-threaded application,
+ * the values of global variables "snmp_errno" and "snmp_detail"
+ * cannot be reliably determined. Suggest using snmp_error()
+ * to obtain the library error codes.
+ */
+
+#ifndef DONT_SHARE_ERROR_WITH_OTHER_THREADS
+#define SET_SNMP_ERROR(x) snmp_errno=(x)
+#else
+#define SET_SNMP_ERROR(x)
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/***********************************************************
+ Copyright 1989 by Carnegie Mellon University
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of CMU not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+******************************************************************/
+
+
+struct variable_list;
+struct timeval;
+
+
+ /*
+ * Mimic size and alignment of 'struct sockaddr_storage' (see RFC 2553)
+ * But retain field names of traditional 'struct sockaddr'
+ */
+
+#define _UCD_SS_MAXSIZE 92 /* <= sizeof( sockaddr_un ) */
+#define _UCD_SS_ALIGNSIZE (sizeof (long))
+
+#define _UCD_SS_PAD1SIZE (_UCD_SS_ALIGNSIZE - sizeof( unsigned short ))
+#define _UCD_SS_PAD2SIZE (_UCD_SS_MAXSIZE - \
+ (sizeof( unsigned short ) + _UCD_SS_PAD1SIZE + _UCD_SS_ALIGNSIZE ))
+
+typedef struct {
+
+#ifdef STRUCT_SOCKADDR_HAS_SA_UNION_SA_GENERIC_SA_FAMILY2
+ /*
+ * Certain systems (notably Irix 6.x) have a non-traditional
+ * socket structure, and #define the traditional field names.
+ * This local definition should reproduce this structure, and still
+ * be large enough to handle any necessary Unix domain addresses.
+ */
+ union {
+ struct {
+#ifdef _HAVE_SA_LEN
+ unsigned char sa_len2;
+ unsigned char sa_family2;
+#else
+ unsigned short sa_family2;
+#endif
+ char sa_data2[ _UCD_SS_PAD1SIZE ];
+ } sa_generic;
+ long sa_align;
+ char sa_pad2[ _UCD_SS_PAD2SIZE ];
+ } sa_union;
+
+#else
+
+#ifdef STRUCT_SOCKADDR_HAS_SA_LEN
+ unsigned char sa_len;
+ unsigned char sa_family;
+#else
+ unsigned short sa_family;
+#endif
+ char sa_data[ _UCD_SS_PAD1SIZE ];
+ long sa_align;
+ char sa_pad2[ _UCD_SS_PAD2SIZE ];
+#endif
+
+} snmp_ipaddr;
+
+#define USM_AUTH_KU_LEN 32
+#define USM_PRIV_KU_LEN 32
+
+struct snmp_pdu {
+
+ /*
+ * Protocol-version independent fields
+ */
+ long version;
+ int command; /* Type of this PDU */
+ long reqid; /* Request id - note: not incremented on retries */
+ long msgid; /* Message id for V3 messages
+ * note: incremented for each retry */
+ long transid; /* Unique ID for incoming transactions */
+ long sessid; /* Session id for AgentX messages */
+ long errstat; /* Error status (non_repeaters in GetBulk) */
+ long errindex; /* Error index (max_repetitions in GetBulk) */
+ u_long time; /* Uptime */
+ u_long flags;
+
+ int securityModel;
+ int securityLevel; /* noAuthNoPriv, authNoPriv, authPriv */
+ int msgParseModel;
+
+ snmp_ipaddr address; /* Address of peer or trap destination */
+
+ struct variable_list *variables;
+
+
+ /*
+ * SNMPv1 & SNMPv2c fields
+ */
+ u_char *community; /* community for outgoing requests. */
+ size_t community_len; /* Length of community name. */
+
+ /*
+ * Trap information
+ */
+ oid *enterprise; /* System OID */
+ size_t enterprise_length;
+ long trap_type; /* trap type */
+ long specific_type; /* specific type */
+ snmp_ipaddr agent_addr;
+
+ /*
+ * SNMPv3 fields
+ */
+ u_char *contextEngineID; /* context snmpEngineID */
+ size_t contextEngineIDLen; /* Length of contextEngineID */
+ char *contextName; /* authoritative contextName */
+ size_t contextNameLen; /* Length of contextName */
+ u_char *securityEngineID; /* authoritative snmpEngineID for security */
+ size_t securityEngineIDLen;/* Length of securityEngineID */
+ char *securityName; /* on behalf of this principal */
+ size_t securityNameLen; /* Length of securityName. */
+
+ /*
+ * AgentX fields
+ * (also uses SNMPv1 community field)
+ */
+ int priority;
+ int range_subid;
+
+ void * securityStateRef;
+};
+
+struct snmp_session;
+typedef int (*snmp_callback) (int, struct snmp_session *, int, struct snmp_pdu *, void *);
+
+struct snmp_session {
+ /*
+ * Protocol-version independent fields
+ */
+ long version;
+ int retries; /* Number of retries before timeout. */
+ long timeout; /* Number of uS until first timeout, then exponential backoff */
+ u_long flags;
+ struct snmp_session *subsession;
+ struct snmp_session *next;
+
+ char *peername; /* Domain name or dotted IP address of default peer */
+ u_short remote_port;/* UDP port number of peer. */
+ u_short local_port; /* My UDP port number, 0 for default, picked randomly */
+ /* Authentication function or NULL if null authentication is used */
+ u_char *(*authenticator) (u_char *, size_t *, u_char *, size_t);
+ snmp_callback callback; /* Function to interpret incoming data */
+ /* Pointer to data that the callback function may consider important */
+ void *callback_magic;
+
+ int s_errno; /* copy of system errno */
+ int s_snmp_errno; /* copy of library errno */
+ long sessid; /* Session id - AgentX only */
+
+ /*
+ * SNMPv1 & SNMPv2c fields
+ */
+ u_char *community; /* community for outgoing requests. */
+ size_t community_len; /* Length of community name. */
+
+ /*
+ * SNMPv3 fields
+ */
+ u_char isAuthoritative; /* are we the authoritative engine? */
+ u_char *contextEngineID; /* authoritative snmpEngineID */
+ size_t contextEngineIDLen; /* Length of contextEngineID */
+ u_int engineBoots; /* initial engineBoots for remote engine */
+ u_int engineTime; /* initial engineTime for remote engine */
+ char *contextName; /* authoritative contextName */
+ size_t contextNameLen; /* Length of contextName */
+ u_char *securityEngineID; /* authoritative snmpEngineID */
+ size_t securityEngineIDLen; /* Length of contextEngineID */
+ char *securityName; /* on behalf of this principal */
+ size_t securityNameLen; /* Length of securityName. */
+ oid *securityAuthProto; /* auth protocol oid */
+ size_t securityAuthProtoLen; /* Length of auth protocol oid */
+ u_char securityAuthKey[USM_AUTH_KU_LEN]; /* Ku for auth protocol XXX */
+ size_t securityAuthKeyLen; /* Length of Ku for auth protocol */
+ oid *securityPrivProto; /* priv protocol oid */
+ size_t securityPrivProtoLen; /* Length of priv protocol oid */
+ u_char securityPrivKey[USM_PRIV_KU_LEN]; /* Ku for privacy protocol XXX */
+ size_t securityPrivKeyLen; /* Length of Ku for priv protocol */
+ int securityModel;
+ int securityLevel; /* noAuthNoPriv, authNoPriv, authPriv */
+};
+
+/*
+ * A list of all the outstanding requests for a particular session.
+ */
+#ifdef SNMP_NEED_REQUEST_LIST
+struct request_list {
+ struct request_list *next_request;
+ long request_id; /* request id */
+ long message_id; /* message id */
+ snmp_callback callback; /* user callback per request (NULL if unused) */
+ void *cb_data; /* user callback data per request (NULL if unused) */
+ int retries; /* Number of retries */
+ u_long timeout; /* length to wait for timeout */
+ struct timeval time; /* Time this request was made */
+ struct timeval expire; /* time this request is due to expire */
+ struct snmp_session *session;
+ struct snmp_pdu *pdu; /* The pdu for this request
+ (saved so it can be retransmitted */
+};
+#endif /* SNMP_NEED_REQUEST_LIST */
+
+/*
+ * Set fields in session and pdu to the following to get a default or unconfigured value.
+ */
+#define SNMP_DEFAULT_COMMUNITY_LEN 0 /* to get a default community name */
+#define SNMP_DEFAULT_RETRIES -1
+#define SNMP_DEFAULT_TIMEOUT -1
+#define SNMP_DEFAULT_REMPORT 0
+#define SNMP_DEFAULT_REQID -1
+#define SNMP_DEFAULT_MSGID -1
+#define SNMP_DEFAULT_ERRSTAT -1
+#define SNMP_DEFAULT_ERRINDEX -1
+#define SNMP_DEFAULT_ADDRESS 0
+#define SNMP_DEFAULT_PEERNAME NULL
+#define SNMP_DEFAULT_ENTERPRISE_LENGTH 0
+#define SNMP_DEFAULT_TIME 0
+#define SNMP_DEFAULT_VERSION -1
+#define SNMP_DEFAULT_CONTEXT ""
+#define SNMP_DEFAULT_AUTH_PROTO usmHMACMD5AuthProtocol
+#define SNMP_DEFAULT_AUTH_PROTOLEN USM_LENGTH_OID_TRANSFORM
+#define SNMP_DEFAULT_PRIV_PROTO usmDESPrivProtocol
+#define SNMP_DEFAULT_PRIV_PROTOLEN USM_LENGTH_OID_TRANSFORM
+
+extern const char *snmp_api_errstring (int);
+extern void snmp_perror (const char *);
+extern void snmp_set_detail (const char *);
+
+#define SNMP_MAX_MSG_SIZE 1472 /* ethernet MTU minus IP/UDP header */
+#define SNMP_MAX_MSG_V3_HDRS (4+3+4+7+7+3+7+16) /* fudge factor=16 */
+#define SNMP_MAX_ENG_SIZE 32
+#define SNMP_MAX_SEC_NAME_SIZE 256
+#define SNMP_MAX_CONTEXT_SIZE 256
+#define SNMP_SEC_PARAM_BUF_SIZE 256
+
+/* set to one to ignore unauthenticated Reports */
+#define SNMPV3_IGNORE_UNAUTH_REPORTS 0
+
+/* authoritative engine definitions */
+#define SNMP_SESS_NONAUTHORITATIVE 0 /* should be 0 to default to this */
+#define SNMP_SESS_AUTHORITATIVE 1 /* don't learn engineIDs */
+#define SNMP_SESS_UNKNOWNAUTH 2 /* sometimes (like NRs) */
+
+/* to determine type of Report from varbind_list */
+#define REPORT_STATS_LEN 9
+#define REPORT_snmpUnknownSecurityModels_NUM 1
+#define REPORT_snmpInvalidMsgs_NUM 2
+#define REPORT_usmStatsUnsupportedSecLevels_NUM 1
+#define REPORT_usmStatsNotInTimeWindows_NUM 2
+#define REPORT_usmStatsUnknownUserNames_NUM 3
+#define REPORT_usmStatsUnknownEngineIDs_NUM 4
+#define REPORT_usmStatsWrongDigests_NUM 5
+#define REPORT_usmStatsDecryptionErrors_NUM 6
+
+#define SNMP_DETAIL_SIZE 512
+
+#define SNMP_FLAGS_DONT_PROBE 0x100 /* don't probe for an engineID */
+#define SNMP_FLAGS_STREAM_SOCKET 0x80
+#define SNMP_FLAGS_LISTENING 0x40 /* Server stream sockets only */
+#define SNMP_FLAGS_SUBSESSION 0x20
+#define SNMP_FLAGS_STRIKE2 0x02
+#define SNMP_FLAGS_STRIKE1 0x01
+
+#define CLEAR_SNMP_STRIKE_FLAGS(x) \
+ x &= ~(SNMP_FLAGS_STRIKE2|SNMP_FLAGS_STRIKE1)
+
+ /*
+ * returns '1' if the session is to be regarded as dead,
+ * otherwise set the strike flags appropriately, and return 0
+ */
+#define SET_SNMP_STRIKE_FLAGS(x) \
+ (( x & SNMP_FLAGS_STRIKE2 ) ? 1 : \
+ ((( x & SNMP_FLAGS_STRIKE1 ) ? ( x |= SNMP_FLAGS_STRIKE2 ) : \
+ ( x |= SNMP_FLAGS_STRIKE1 )), \
+ 0))
+
+/*
+ * Error return values.
+ *
+ * SNMPERR_SUCCESS is the non-PDU "success" code.
+ *
+ * XXX These should be merged with SNMP_ERR_* defines and confined
+ * to values < 0. ???
+ */
+#define SNMPERR_SUCCESS (0) /* XXX Non-PDU "success" code. */
+#define SNMPERR_GENERR (-1)
+#define SNMPERR_BAD_LOCPORT (-2)
+#define SNMPERR_BAD_ADDRESS (-3)
+#define SNMPERR_BAD_SESSION (-4)
+#define SNMPERR_TOO_LONG (-5)
+#define SNMPERR_NO_SOCKET (-6)
+#define SNMPERR_V2_IN_V1 (-7)
+#define SNMPERR_V1_IN_V2 (-8)
+#define SNMPERR_BAD_REPEATERS (-9)
+#define SNMPERR_BAD_REPETITIONS (-10)
+#define SNMPERR_BAD_ASN1_BUILD (-11)
+#define SNMPERR_BAD_SENDTO (-12)
+#define SNMPERR_BAD_PARSE (-13)
+#define SNMPERR_BAD_VERSION (-14)
+#define SNMPERR_BAD_SRC_PARTY (-15)
+#define SNMPERR_BAD_DST_PARTY (-16)
+#define SNMPERR_BAD_CONTEXT (-17)
+#define SNMPERR_BAD_COMMUNITY (-18)
+#define SNMPERR_NOAUTH_DESPRIV (-19)
+#define SNMPERR_BAD_ACL (-20)
+#define SNMPERR_BAD_PARTY (-21)
+#define SNMPERR_ABORT (-22)
+#define SNMPERR_UNKNOWN_PDU (-23)
+#define SNMPERR_TIMEOUT (-24)
+#define SNMPERR_BAD_RECVFROM (-25)
+#define SNMPERR_BAD_ENG_ID (-26)
+#define SNMPERR_BAD_SEC_NAME (-27)
+#define SNMPERR_BAD_SEC_LEVEL (-28)
+#define SNMPERR_ASN_PARSE_ERR (-29)
+#define SNMPERR_UNKNOWN_SEC_MODEL (-30)
+#define SNMPERR_INVALID_MSG (-31)
+#define SNMPERR_UNKNOWN_ENG_ID (-32)
+#define SNMPERR_UNKNOWN_USER_NAME (-33)
+#define SNMPERR_UNSUPPORTED_SEC_LEVEL (-34)
+#define SNMPERR_AUTHENTICATION_FAILURE (-35)
+#define SNMPERR_NOT_IN_TIME_WINDOW (-36)
+#define SNMPERR_DECRYPTION_ERR (-37)
+#define SNMPERR_SC_GENERAL_FAILURE (-38)
+#define SNMPERR_SC_NOT_CONFIGURED (-39)
+#define SNMPERR_KT_NOT_AVAILABLE (-40)
+#define SNMPERR_UNKNOWN_REPORT (-41)
+#define SNMPERR_USM_GENERICERROR (-42)
+#define SNMPERR_USM_UNKNOWNSECURITYNAME (-43)
+#define SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL (-44)
+#define SNMPERR_USM_ENCRYPTIONERROR (-45)
+#define SNMPERR_USM_AUTHENTICATIONFAILURE (-46)
+#define SNMPERR_USM_PARSEERROR (-47)
+#define SNMPERR_USM_UNKNOWNENGINEID (-48)
+#define SNMPERR_USM_NOTINTIMEWINDOW (-49)
+#define SNMPERR_USM_DECRYPTIONERROR (-50)
+#define SNMPERR_NOMIB (-51)
+#define SNMPERR_RANGE (-52)
+#define SNMPERR_MAX_SUBID (-53)
+#define SNMPERR_BAD_SUBID (-54)
+#define SNMPERR_LONG_OID (-55)
+#define SNMPERR_BAD_NAME (-56)
+#define SNMPERR_VALUE (-57)
+#define SNMPERR_UNKNOWN_OBJID (-58)
+#define SNMPERR_NULL_PDU (-59)
+#define SNMPERR_NO_VARS (-60)
+#define SNMPERR_VAR_TYPE (-61)
+#define SNMPERR_MALLOC (-62)
+
+#define SNMPERR_MAX (-62)
+
+#define non_repeaters errstat
+#define max_repetitions errindex
+
+
+struct variable_list {
+ struct variable_list *next_variable; /* NULL for last variable */
+ oid *name; /* Object identifier of variable */
+ size_t name_length; /* number of subid's in name */
+ u_char type; /* ASN type of variable */
+ union { /* value of variable */
+ long *integer;
+ u_char *string;
+ oid *objid;
+ u_char *bitstring;
+ struct counter64 *counter64;
+#ifdef OPAQUE_SPECIAL_TYPES
+ float *floatVal;
+ double *doubleVal;
+/* t_union *unionVal; */
+#endif /* OPAQUE_SPECIAL_TYPES */
+ } val;
+ size_t val_len;
+ oid name_loc[MAX_OID_LEN]; /* 90 percentile < 24. */
+ u_char buf[40]; /* 90 percentile < 40. */
+ void *data; /* (Opaque) hook for additional data */
+ int index;
+};
+
+
+
+/*
+ * struct snmp_session *snmp_open(session)
+ * struct snmp_session *session;
+ *
+ * Sets up the session with the snmp_session information provided
+ * by the user. Then opens and binds the necessary UDP port.
+ * A handle to the created session is returned (this is different than
+ * the pointer passed to snmp_open()). On any error, NULL is returned
+ * and snmp_errno is set to the appropriate error code.
+ */
+struct snmp_session *snmp_open (struct snmp_session *);
+
+/*
+ * int snmp_close(session)
+ * struct snmp_session *session;
+ *
+ * Close the input session. Frees all data allocated for the session,
+ * dequeues any pending requests, and closes any sockets allocated for
+ * the session. Returns 0 on error, 1 otherwise.
+ *
+ * snmp_close_sessions() does the same thing for all open sessions
+ */
+int snmp_close (struct snmp_session *);
+int snmp_close_sessions (void);
+
+
+/*
+ * int snmp_send(session, pdu)
+ * struct snmp_session *session;
+ * struct snmp_pdu *pdu;
+ *
+ * Sends the input pdu on the session after calling snmp_build to create
+ * a serialized packet. If necessary, set some of the pdu data from the
+ * session defaults. Add a request corresponding to this pdu to the list
+ * of outstanding requests on this session, then send the pdu.
+ * Returns the request id of the generated packet if applicable, otherwise 1.
+ * On any error, 0 is returned.
+ * The pdu is freed by snmp_send() unless a failure occured.
+ */
+int snmp_send (struct snmp_session *, struct snmp_pdu *);
+
+/*
+ * int snmp_async_send(session, pdu, callback, cb_data)
+ * struct snmp_session *session;
+ * struct snmp_pdu *pdu;
+ * snmp_callback callback;
+ * void *cb_data;
+ *
+ * Sends the input pdu on the session after calling snmp_build to create
+ * a serialized packet. If necessary, set some of the pdu data from the
+ * session defaults. Add a request corresponding to this pdu to the list
+ * of outstanding requests on this session and store callback and data,
+ * then send the pdu.
+ * Returns the request id of the generated packet if applicable, otherwise 1.
+ * On any error, 0 is returned.
+ * The pdu is freed by snmp_send() unless a failure occured.
+ */
+int snmp_async_send (struct snmp_session *, struct snmp_pdu *,
+ snmp_callback, void *);
+
+
+/*
+ * void snmp_read(fdset)
+ * fd_set *fdset;
+ *
+ * Checks to see if any of the fd's set in the fdset belong to
+ * snmp. Each socket with it's fd set has a packet read from it
+ * and snmp_parse is called on the packet received. The resulting pdu
+ * is passed to the callback routine for that session. If the callback
+ * routine returns successfully, the pdu and it's request are deleted.
+ */
+void snmp_read (fd_set *);
+
+
+
+/*
+ * void
+ * snmp_free_pdu(pdu)
+ * struct snmp_pdu *pdu;
+ *
+ * Frees the pdu and any malloc'd data associated with it.
+ */
+void snmp_free_pdu (struct snmp_pdu *);
+
+void snmp_free_var (struct variable_list *); /* frees just this one */
+
+void snmp_free_varbind(struct variable_list *var); /* frees all in list */
+
+/*
+ * int snmp_select_info(numfds, fdset, timeout, block)
+ * int *numfds;
+ * fd_set *fdset;
+ * struct timeval *timeout;
+ * int *block;
+ *
+ * Returns info about what snmp requires from a select statement.
+ * numfds is the number of fds in the list that are significant.
+ * All file descriptors opened for SNMP are OR'd into the fdset.
+ * If activity occurs on any of these file descriptors, snmp_read
+ * should be called with that file descriptor set.
+ *
+ * The timeout is the latest time that SNMP can wait for a timeout. The
+ * select should be done with the minimum time between timeout and any other
+ * timeouts necessary. This should be checked upon each invocation of select.
+ * If a timeout is received, snmp_timeout should be called to check if the
+ * timeout was for SNMP. (snmp_timeout is idempotent)
+ *
+ * Block is 1 if the select is requested to block indefinitely, rather than
+ * time out. If block is input as 1, the timeout value will be treated as
+ * undefined, but it must be available for setting in snmp_select_info. On
+ * return, if block is true, the value of timeout will be undefined.
+ *
+ * snmp_select_info returns the number of open sockets. (i.e. The number
+ * of sessions open)
+ */
+int snmp_select_info (int *, fd_set *, struct timeval *, int *);
+
+
+
+/*
+ * void snmp_timeout();
+ *
+ * snmp_timeout should be called whenever the timeout from snmp_select_info
+ * expires, but it is idempotent, so snmp_timeout can be polled (probably a
+ * cpu expensive proposition). snmp_timeout checks to see if any of the
+ * sessions have an outstanding request that has timed out. If it finds one
+ * (or more), and that pdu has more retries available, a new packet is formed
+ * from the pdu and is resent. If there are no more retries available, the
+ * callback for the session is used to alert the user of the timeout.
+ */
+
+void snmp_timeout (void);
+
+
+/*
+ * This routine must be supplied by the application:
+ *
+ * u_char *authenticator(pdu, length, community, community_len)
+ * u_char *pdu; The rest of the PDU to be authenticated
+ * int *length; The length of the PDU (updated by the authenticator)
+ * u_char *community; The community name to authenticate under.
+ * int community_len The length of the community name.
+ *
+ * Returns the authenticated pdu, or NULL if authentication failed.
+ * If null authentication is used, the authenticator in snmp_session can be
+ * set to NULL(0).
+ */
+
+
+
+/*
+ * This routine must be supplied by the application:
+ *
+ * int callback(operation, session, reqid, pdu, magic)
+ * int operation;
+ * struct snmp_session *session; The session authenticated under.
+ * int reqid; The request id of this pdu (0 for TRAP)
+ * struct snmp_pdu *pdu; The pdu information.
+ * void *magic A link to the data for this routine.
+ *
+ * Returns 1 if request was successful, 0 if it should be kept pending.
+ * Any data in the pdu must be copied because it will be freed elsewhere.
+ * Operations are defined below:
+ */
+
+#define RECEIVED_MESSAGE 1
+#define TIMED_OUT 2
+#define SEND_FAILED 3
+
+long snmp_get_next_msgid(void);
+long snmp_get_next_reqid(void);
+long snmp_get_next_sessid(void);
+long snmp_get_next_transid(void);
+/* provide for backwards compatibility */
+void snmp_set_dump_packet(int);
+int snmp_get_dump_packet(void);
+void snmp_set_quick_print(int);
+int snmp_get_quick_print(void);
+void snmp_set_suffix_only(int);
+int snmp_get_suffix_only(void);
+void snmp_set_full_objid(int);
+int snmp_get_full_objid(void);
+void snmp_set_random_access(int);
+int snmp_get_random_access(void);
+
+int snmp_oid_compare (const oid *, size_t, const oid *, size_t);
+void init_snmp (const char *);
+u_char *snmp_pdu_build (struct snmp_pdu *, u_char *, size_t *);
+#ifdef USE_REVERSE_ASNENCODING
+u_char *snmp_pdu_rbuild (struct snmp_pdu *, u_char *, size_t *);
+#endif
+int snmpv3_parse(struct snmp_pdu *, u_char *, size_t *, u_char **, struct snmp_session *);
+int snmpv3_dparse(struct snmp_pdu *, u_char *, size_t *, u_char **, int);
+int snmpv3_packet_build(struct snmp_pdu *pdu, u_char *packet, size_t *out_length, u_char *pdu_data, size_t pdu_data_len);
+int snmpv3_packet_rbuild(struct snmp_pdu *pdu, u_char *packet, size_t *out_length, u_char *pdu_data, size_t pdu_data_len);
+int snmpv3_make_report(struct snmp_pdu *pdu, int error);
+int snmpv3_get_report_type(struct snmp_pdu *pdu);
+int snmp_pdu_parse(struct snmp_pdu *pdu, u_char *data, size_t *length);
+int snmp_pdu_dparse(struct snmp_pdu *pdu, u_char *data, size_t *length, int);
+u_char* snmpv3_scopedPDU_parse(struct snmp_pdu *pdu, u_char *cp, size_t *length);
+u_char* snmpv3_scopedPDU_dparse(struct snmp_pdu *pdu, u_char *cp, size_t *length, int);
+void snmp_store(const char *type);
+void snmp_shutdown(const char *type);
+struct variable_list *snmp_pdu_add_variable (struct snmp_pdu *, oid *, size_t, u_char, u_char *, size_t);
+struct variable_list *snmp_varlist_add_variable(struct variable_list **varlist,
+ oid *name, size_t name_length, u_char type, u_char *value, size_t len);
+int hex_to_binary (const char *, u_char *);
+int ascii_to_binary (const char *, u_char *);
+int snmp_add_var (struct snmp_pdu *, oid*, size_t, char, const char *);
+oid *snmp_duplicate_objid(oid *objToCopy, size_t);
+u_int snmp_increment_statistic(int which);
+u_int snmp_increment_statistic_by(int which, int count);
+u_int snmp_get_statistic(int which);
+void snmp_init_statistics(void);
+int create_user_from_session(struct snmp_session *session);
+
+/* extended open */
+struct snmp_session *snmp_open_ex (struct snmp_session *,
+ int (*fpre_parse) (struct snmp_session *, snmp_ipaddr),
+ int (*fparse) (struct snmp_session *, struct snmp_pdu *, u_char *, size_t),
+ int (*fpost_parse) (struct snmp_session *, struct snmp_pdu *, int),
+ int (*fbuild) (struct snmp_session *, struct snmp_pdu *, u_char *, size_t *),
+ int (*fcheck) (u_char *, size_t)
+);
+
+/* provided for backwards compatability. Don't use these functions.
+ See snmp_debug.h and snmp_debug.c instead.
+*/
+#if HAVE_STDARG_H
+void DEBUGP (const char *, ...);
+#else
+void DEBUGP (va_alist);
+#endif
+void DEBUGPOID(oid *, size_t);
+void snmp_set_do_debugging (int);
+int snmp_get_do_debugging (void);
+
+#ifdef CMU_COMPATIBLE
+extern int snmp_dump_packet;
+extern int quick_print;
+#endif
+
+size_t snmp_socket_length (int family);
+
+/*
+ * snmp_error - return error data
+ * Inputs : address of errno, address of snmp_errno, address of string
+ * Caller must free the string returned after use.
+ */
+void snmp_error (struct snmp_session *, int *, int *, char **);
+/*
+ * single session API.
+ *
+ * These functions perform similar actions as snmp_XX functions,
+ * but operate on a single session only.
+ *
+ * Synopsis:
+
+ void * sessp;
+ struct snmp_session session, *ss;
+ struct snmp_pdu *pdu, *response;
+
+ snmp_sess_init(&session);
+ session.retries = ...
+ session.remote_port = ...
+ sessp = snmp_sess_open(&session);
+ ss = snmp_sess_session(sessp);
+ if (ss == NULL)
+ exit(1);
+ ...
+ if (ss->community) free(ss->community);
+ ss->community = strdup(gateway);
+ ss->community_len = strlen(gateway);
+ ...
+ snmp_sess_synch_response(sessp, pdu, &response);
+ ...
+ snmp_sess_close(sessp);
+
+ * See also:
+ * snmp_sess_synch_response, in snmp_client.h.
+
+ * Notes:
+ * 1. Invoke snmp_sess_session after snmp_sess_open.
+ * 2. snmp_sess_session return value is an opaque pointer.
+ * 3. Do NOT free memory returned by snmp_sess_session.
+ * 4. Replace snmp_send(ss,pdu) with snmp_sess_send(sessp,pdu)
+ */
+
+void snmp_sess_init (struct snmp_session *);
+void * snmp_sess_open (struct snmp_session *);
+struct snmp_session * snmp_sess_session (void *);
+
+/* use return value from snmp_sess_open as void * parameter */
+
+int snmp_sess_send (void *, struct snmp_pdu *);
+int snmp_sess_async_send (void *, struct snmp_pdu *,
+ snmp_callback, void *);
+int snmp_sess_select_info (void *, int *, fd_set *,
+ struct timeval *, int *);
+int snmp_sess_read (void *, fd_set *);
+void snmp_sess_timeout (void *);
+int snmp_sess_close (void *);
+
+void snmp_sess_error (void *, int *, int *, char **);
+void snmp_sess_perror (const char *prog_string, struct snmp_session *ss);
+
+/* end single session API */
+
+/* generic statistic counters */
+
+/* snmpv3 statistics */
+
+/* mpd stats */
+#define STAT_SNMPUNKNOWNSECURITYMODELS 0
+#define STAT_SNMPINVALIDMSGS 1
+#define STAT_SNMPUNKNOWNPDUHANDLERS 2
+#define STAT_MPD_STATS_START STAT_SNMPUNKNOWNSECURITYMODELS
+#define STAT_MPD_STATS_END STAT_SNMPUNKNOWNPDUHANDLERS
+
+/* usm stats */
+#define STAT_USMSTATSUNSUPPORTEDSECLEVELS 3
+#define STAT_USMSTATSNOTINTIMEWINDOWS 4
+#define STAT_USMSTATSUNKNOWNUSERNAMES 5
+#define STAT_USMSTATSUNKNOWNENGINEIDS 6
+#define STAT_USMSTATSWRONGDIGESTS 7
+#define STAT_USMSTATSDECRYPTIONERRORS 8
+#define STAT_USM_STATS_START STAT_USMSTATSUNSUPPORTEDSECLEVELS
+#define STAT_USM_STATS_END STAT_USMSTATSDECRYPTIONERRORS
+
+/* snmp counters */
+#define STAT_SNMPINPKTS 9
+#define STAT_SNMPOUTPKTS 10
+#define STAT_SNMPINBADVERSIONS 11
+#define STAT_SNMPINBADCOMMUNITYNAMES 12
+#define STAT_SNMPINBADCOMMUNITYUSES 13
+#define STAT_SNMPINASNPARSEERRS 14
+/* #define STAT_SNMPINBADTYPES 15 */
+#define STAT_SNMPINTOOBIGS 16
+#define STAT_SNMPINNOSUCHNAMES 17
+#define STAT_SNMPINBADVALUES 18
+#define STAT_SNMPINREADONLYS 19
+#define STAT_SNMPINGENERRS 20
+#define STAT_SNMPINTOTALREQVARS 21
+#define STAT_SNMPINTOTALSETVARS 22
+#define STAT_SNMPINGETREQUESTS 23
+#define STAT_SNMPINGETNEXTS 24
+#define STAT_SNMPINSETREQUESTS 25
+#define STAT_SNMPINGETRESPONSES 26
+#define STAT_SNMPINTRAPS 27
+#define STAT_SNMPOUTTOOBIGS 28
+#define STAT_SNMPOUTNOSUCHNAMES 29
+#define STAT_SNMPOUTBADVALUES 30
+/* #define STAT_SNMPOUTREADONLYS 31 */
+#define STAT_SNMPOUTGENERRS 32
+#define STAT_SNMPOUTGETREQUESTS 33
+#define STAT_SNMPOUTGETNEXTS 34
+#define STAT_SNMPOUTSETREQUESTS 35
+#define STAT_SNMPOUTGETRESPONSES 36
+#define STAT_SNMPOUTTRAPS 37
+/* AUTHTRAPENABLE 38 */
+#define STAT_SNMPSILENTDROPS 39
+#define STAT_SNMPPROXYDROPS 40
+#define STAT_SNMP_STATS_START STAT_SNMPINPKTS
+#define STAT_SNMP_STATS_END STAT_SNMPOUTTRAPS
+
+#define MAX_STATS 41
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SNMP_API_H */
diff --git a/trunk/src/examples/sparser.py b/trunk/src/examples/sparser.py
new file mode 100644
index 0000000..7c416da
--- /dev/null
+++ b/trunk/src/examples/sparser.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python
+
+"""
+NAME:
+ sparser.py
+
+SYNOPSIS:
+ sparser.py [options] filename
+
+DESCRIPTION:
+ The sparser.py script is a Specified PARSER. It is unique (as far as I can
+ tell) because it doesn't care about the delimiter(s). The user specifies
+ what is expected, and the order, for each line of text. All of the heavy
+ lifting is handled by pyparsing (http://pyparsing.sf.net).
+
+OPTIONS:
+ -h,--help this message
+ -v,--version version
+ -d,--debug turn on debug messages
+
+EXAMPLES:
+ 1. As standalone
+ sparser.py myfile
+ 2. As library
+ import sparser
+ ...
+
+#Copyright (C) 2006 Tim Cera timcera@earthlink.net
+#
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+#===imports======================
+import sys
+import os
+import getopt
+import re
+import gzip
+
+from pyparsing import *
+
+
+#===globals======================
+modname = "sparser"
+__version__ = "0.1"
+
+
+#--option args--
+debug_p = 0
+#opt_b=None #string arg, default is undefined
+
+
+#---positional args, default is empty---
+pargs = []
+
+
+#---other---
+
+
+#===utilities====================
+def msg(txt):
+ """Send message to stdout."""
+ sys.stdout.write(txt)
+ sys.stdout.flush()
+
+def debug(ftn, txt):
+ """Used for debugging."""
+ if debug_p:
+ sys.stdout.write("%s.%s:%s\n" % (modname, ftn, txt))
+ sys.stdout.flush()
+
+def fatal(ftn, txt):
+ """If can't continue."""
+ msg = "%s.%s:FATAL:%s\n" % (modname, ftn, txt)
+ raise SystemExit(msg)
+
+def usage():
+ """Prints the docstring."""
+ print(__doc__)
+
+
+
+#====================================
+class ToInteger(TokenConverter):
+ """Converter to make token into an integer."""
+ def postParse( self, instring, loc, tokenlist ):
+ return int(tokenlist[0])
+
+class ToFloat(TokenConverter):
+ """Converter to make token into a float."""
+ def postParse( self, instring, loc, tokenlist ):
+ return float(tokenlist[0])
+
+class ParseFileLineByLine:
+ """
+ Bring data from text files into a program, optionally parsing each line
+ according to specifications in a parse definition file.
+
+ ParseFileLineByLine instances can be used like normal file objects (i.e. by
+ calling readline(), readlines(), and write()), but can also be used as
+ sequences of lines in for-loops.
+
+ ParseFileLineByLine objects also handle compression transparently. i.e. it
+ is possible to read lines from a compressed text file as if it were not
+ compressed. Compression is deduced from the file name suffixes '.Z'
+ (compress/uncompress), '.gz' (gzip/gunzip), and '.bz2' (bzip2).
+
+ The parse definition file name is developed based on the input file name.
+ If the input file name is 'basename.ext', then the definition file is
+ 'basename_def.ext'. If a definition file specific to the input file is not
+ found, then the program searches for the file 'sparse.def' which would be
+ the definition file for all files in that directory without a file specific
+ definition file.
+
+ Finally, ParseFileLineByLine objects accept file names that start with '~'
+ or '~user' to indicate a home directory, as well as URLs (for reading
+ only).
+
+ Constructor:
+ ParseFileLineByLine(|filename|, |mode|='"r"'), where |filename| is the name
+ of the file (or a URL) and |mode| is one of '"r"' (read), '"w"' (write) or
+ '"a"' (append, not supported for .Z files).
+ """
+
+ def __init__(self, filename, mode = 'r'):
+ """Opens input file, and if available the definition file. If the
+ definition file is available __init__ will then create some pyparsing
+ helper variables. """
+ if mode not in ['r', 'w', 'a']:
+ raise IOError(0, 'Illegal mode: ' + repr(mode))
+
+ if string.find(filename, ':/') > 1: # URL
+ if mode == 'w':
+ raise IOError("can't write to a URL")
+ import urllib.request, urllib.parse, urllib.error
+ self.file = urllib.request.urlopen(filename)
+ else:
+ filename = os.path.expanduser(filename)
+ if mode == 'r' or mode == 'a':
+ if not os.path.exists(filename):
+ raise IOError(2, 'No such file or directory: ' + filename)
+ filen, file_extension = os.path.splitext(filename)
+ command_dict = {
+ ('.Z', 'r'):
+ "self.file = os.popen('uncompress -c ' + filename, mode)",
+ ('.gz', 'r'):
+ "self.file = gzip.GzipFile(filename, 'rb')",
+ ('.bz2', 'r'):
+ "self.file = os.popen('bzip2 -dc ' + filename, mode)",
+ ('.Z', 'w'):
+ "self.file = os.popen('compress > ' + filename, mode)",
+ ('.gz', 'w'):
+ "self.file = gzip.GzipFile(filename, 'wb')",
+ ('.bz2', 'w'):
+ "self.file = os.popen('bzip2 > ' + filename, mode)",
+ ('.Z', 'a'):
+ "raise IOError, (0, 'Can\'t append to .Z files')",
+ ('.gz', 'a'):
+ "self.file = gzip.GzipFile(filename, 'ab')",
+ ('.bz2', 'a'):
+ "raise IOError, (0, 'Can\'t append to .bz2 files')",
+ }
+
+ exec(command_dict.get((file_extension, mode),
+ 'self.file = open(filename, mode)'))
+
+ self.grammar = None
+
+ # Try to find a parse ('*_def.ext') definition file. First try to find
+ # a file specific parse definition file, then look for 'sparse.def'
+ # that would be the definition file for all files within the directory.
+
+ # The definition file is pure Python. The one variable that needs to
+ # be specified is 'parse'. The 'parse' variable is a list of tuples
+ # defining the name, type, and because it is a list, the order of
+ # variables on each line in the data file. The variable name is a
+ # string, the type variable is defined as integer, real, and qString.
+
+ # parse = [
+ # ('year', integer),
+ # ('month', integer),
+ # ('day', integer),
+ # ('value', real),
+ # ]
+
+ definition_file_one = filen + "_def" + file_extension
+ definition_file_two = os.path.dirname(filen) + os.sep + "sparse.def"
+ if os.path.exists(definition_file_one):
+ self.parsedef = definition_file_one
+ elif os.path.exists(definition_file_two):
+ self.parsedef = definition_file_two
+ else:
+ self.parsedef = None
+ return None
+
+ # Create some handy pyparsing constructs. I kept 'decimal_sep' so that
+ # could easily change to parse if the decimal separator is a ",".
+ decimal_sep = "."
+ sign = oneOf("+ -")
+ # part of printables without decimal_sep, +, -
+ special_chars = string.replace('!"#$%&\'()*,./:;<=>?@[\\]^_`{|}~',
+ decimal_sep, "")
+ integer = ToInteger(
+ Combine(Optional(sign) +
+ Word(nums))).setName("integer")
+ positive_integer = ToInteger(
+ Combine(Optional("+") +
+ Word(nums))).setName("integer")
+ negative_integer = ToInteger(
+ Combine("-" +
+ Word(nums))).setName("integer")
+ real = ToFloat(
+ Combine(Optional(sign) +
+ Word(nums) +
+ decimal_sep +
+ Optional(Word(nums)) +
+ Optional(oneOf("E e") +
+ Word(nums)))).setName("real")
+ positive_real = ToFloat(
+ Combine(Optional("+") +
+ Word(nums) +
+ decimal_sep +
+ Optional(Word(nums)) +
+ Optional(oneOf("E e") +
+ Word(nums)))).setName("real")
+ negative_real = ToFloat(
+ Combine("-" +
+ Word(nums) +
+ decimal_sep +
+ Optional(Word(nums)) +
+ Optional(oneOf("E e") +
+ Word(nums)))).setName("real")
+ qString = ( sglQuotedString | dblQuotedString ).setName("qString")
+
+ # add other characters we should skip over between interesting fields
+ integer_junk = Optional(
+ Suppress(
+ Word(alphas +
+ special_chars +
+ decimal_sep))).setName("integer_junk")
+ real_junk = Optional(
+ Suppress(
+ Word(alphas +
+ special_chars))).setName("real_junk")
+ qString_junk = SkipTo(qString).setName("qString_junk")
+
+ # Now that 'integer', 'real', and 'qString' have been assigned I can
+ # execute the definition file.
+ exec(compile(open(self.parsedef).read(), self.parsedef, 'exec'))
+
+ # Build the grammar, combination of the 'integer', 'real, 'qString',
+ # and '*_junk' variables assigned above in the order specified in the
+ # definition file.
+ grammar = []
+ for nam, expr in parse:
+ grammar.append( eval(expr.name + "_junk"))
+ grammar.append( expr.setResultsName(nam) )
+ self.grammar = And( grammar[1:] + [restOfLine] )
+
+ def __del__(self):
+ """Delete (close) the file wrapper."""
+ self.close()
+
+ def __getitem__(self, item):
+ """Used in 'for line in fp:' idiom."""
+ line = self.readline()
+ if not line:
+ raise IndexError
+ return line
+
+ def readline(self):
+ """Reads (and optionally parses) a single line."""
+ line = self.file.readline()
+ if self.grammar and line:
+ try:
+ return self.grammar.parseString(line).asDict()
+ except ParseException:
+ return self.readline()
+ else:
+ return line
+
+ def readlines(self):
+ """Returns a list of all lines (optionally parsed) in the file."""
+ if self.grammar:
+ tot = []
+ # Used this way instead of a 'for' loop against
+ # self.file.readlines() so that there wasn't two copies of the file
+ # in memory.
+ while 1:
+ line = self.file.readline()
+ if not line:
+ break
+ tot.append(line)
+ return tot
+ return self.file.readlines()
+
+ def write(self, data):
+ """Write to a file."""
+ self.file.write(data)
+
+ def writelines(self, list):
+ """Write a list to a file. Each item in the list is a line in the
+ file.
+ """
+ for line in list:
+ self.file.write(line)
+
+ def close(self):
+ """Close the file."""
+ self.file.close()
+
+ def flush(self):
+ """Flush in memory contents to file."""
+ self.file.flush()
+
+
+#=============================
+def main(pargs):
+ """This should only be used for testing. The primary mode of operation is
+ as an imported library.
+ """
+ input_file = sys.argv[1]
+ fp = ParseFileLineByLine(input_file)
+ for i in fp:
+ print(i)
+
+
+#-------------------------
+if __name__ == '__main__':
+ ftn = "main"
+ opts, pargs = getopt.getopt(sys.argv[1:], 'hvd',
+ ['help', 'version', 'debug', 'bb='])
+ for opt in opts:
+ if opt[0] == '-h' or opt[0] == '--help':
+ print(modname+": version="+__version__)
+ usage()
+ sys.exit(0)
+ elif opt[0] == '-v' or opt[0] == '--version':
+ print(modname+": version="+__version__)
+ sys.exit(0)
+ elif opt[0] == '-d' or opt[0] == '--debug':
+ debug_p = 1
+ elif opt[0] == '--bb':
+ opt_b = opt[1]
+
+ #---make the object and run it---
+ main(pargs)
+
+#===Revision Log===
+#Created by mkpythonproj:
+#2006-02-06 Tim Cera
+#
diff --git a/trunk/src/examples/sql2dot.py b/trunk/src/examples/sql2dot.py
new file mode 100644
index 0000000..1156207
--- /dev/null
+++ b/trunk/src/examples/sql2dot.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+
+# sql2dot.py
+#
+# Creates table graphics by parsing SQL table DML commands and
+# generating DOT language output.
+#
+# Adapted from a post at http://energyblog.blogspot.com/2006/04/blog-post_20.html.
+#
+sampleSQL = """
+create table student
+(
+student_id integer primary key,
+firstname varchar(20),
+lastname varchar(40),
+address1 varchar(80),
+address2 varchar(80),
+city varchar(30),
+state varchar(2),
+zipcode varchar(10),
+dob date
+);
+
+create table classes
+(
+class_id integer primary key,
+id varchar(8),
+maxsize integer,
+instructor varchar(40)
+);
+
+create table student_registrations
+(
+reg_id integer primary key,
+student_id integer,
+class_id integer
+);
+
+alter table only student_registrations
+ add constraint students_link
+ foreign key
+ (student_id) references students(student_id);
+
+alter table only student_registrations
+ add constraint classes_link
+ foreign key
+ (class_id) references classes(class_id);
+""".upper()
+
+from pyparsing import Literal, CaselessLiteral, Word, delimitedList \
+ ,Optional, Combine, Group, alphas, nums, alphanums, Forward \
+ , oneOf, sglQuotedString, OneOrMore, ZeroOrMore, CharsNotIn \
+ , replaceWith
+
+skobki = "(" + ZeroOrMore(CharsNotIn(")")) + ")"
+field_def = OneOrMore(Word(alphas,alphanums+"_\"':-") | skobki)
+
+def field_act(s,loc,tok):
+ return ("<"+tok[0]+"> " + " ".join(tok)).replace("\"","\\\"")
+
+field_def.setParseAction(field_act)
+
+field_list_def = delimitedList( field_def )
+def field_list_act(toks):
+ return " | ".join(toks)
+
+field_list_def.setParseAction(field_list_act)
+
+create_table_def = Literal("CREATE") + "TABLE" + Word(alphas,alphanums+"_").setResultsName("tablename") + \
+ "("+field_list_def.setResultsName("columns")+")"+ ";"
+
+def create_table_act(toks):
+ return """"%(tablename)s" [\n\t label="<%(tablename)s> %(tablename)s | %(columns)s"\n\t shape="record"\n];""" % toks
+create_table_def.setParseAction(create_table_act)
+
+add_fkey_def=Literal("ALTER")+"TABLE"+"ONLY" + Word(alphanums+"_").setResultsName("fromtable") + "ADD" \
+ + "CONSTRAINT" + Word(alphanums+"_") + "FOREIGN"+"KEY"+"("+Word(alphanums+"_").setResultsName("fromcolumn")+")" \
+ +"REFERENCES"+Word(alphanums+"_").setResultsName("totable")+"("+Word(alphanums+"_").setResultsName("tocolumn")+")"+";"
+
+def add_fkey_act(toks):
+ return """ "%(fromtable)s":%(fromcolumn)s -> "%(totable)s":%(tocolumn)s """ % toks
+add_fkey_def.setParseAction(add_fkey_act)
+
+other_statement_def = ( OneOrMore(CharsNotIn(";") ) + ";")
+other_statement_def.setParseAction( replaceWith("") )
+comment_def = "--" + ZeroOrMore(CharsNotIn("\n"))
+comment_def.setParseAction( replaceWith("") )
+
+statement_def = comment_def | create_table_def | add_fkey_def | other_statement_def
+defs = OneOrMore(statement_def)
+
+print("""digraph g { graph [ rankdir = "LR" ]; """)
+for i in defs.parseString(sampleSQL):
+ if i!="":
+ print(i)
+print("}") \ No newline at end of file
diff --git a/trunk/src/examples/stackish.py b/trunk/src/examples/stackish.py
new file mode 100644
index 0000000..f80b4d6
--- /dev/null
+++ b/trunk/src/examples/stackish.py
@@ -0,0 +1,81 @@
+# stackish.py
+#
+# Stackish is a data representation syntax, similar to JSON or YAML. For more info on
+# stackish, see http://www.savingtheinternetwithhate.com/stackish.html
+#
+# Copyright 2008, Paul McGuire
+#
+
+"""
+NUMBER A simple integer type that's just any series of digits.
+FLOAT A simple floating point type.
+STRING A string is double quotes with anything inside that's not a " or
+ newline character. You can include \n and \" to include these
+ characters.
+MARK Marks a point in the stack that demarcates the boundary for a nested
+ group.
+WORD Marks the root node of a group, with the other end being the nearest
+ MARK.
+GROUP Acts as the root node of an anonymous group.
+ATTRIBUTE Assigns an attribute name to the previously processed node.
+ This means that just about anything can be an attribute, unlike in XML.
+BLOB A BLOB is unique to Stackish and allows you to record any content
+ (even binary content) inside the structure. This is done by pre-
+ sizing the data with the NUMBER similar to Dan Bernstein's netstrings
+ setup.
+SPACE White space is basically ignored. This is interesting because since
+ Stackish is serialized consistently this means you can use \n as the
+ separation character and perform reasonable diffs on two structures.
+"""
+
+from pyparsing import Suppress,Word,nums,alphas,alphanums,Combine,oneOf,\
+ Optional,QuotedString,Forward,Group,ZeroOrMore,printables,srange
+
+MARK,UNMARK,AT,COLON,QUOTE = map(Suppress,"[]@:'")
+
+NUMBER = Word(nums)
+NUMBER.setParseAction(lambda t:int(t[0]))
+FLOAT = Combine(oneOf("+ -") + Word(nums) + "." + Optional(Word(nums)))
+FLOAT.setParseAction(lambda t:float(t[0]))
+STRING = QuotedString('"', multiline=True)
+WORD = Word(alphas,alphanums+"_:")
+ATTRIBUTE = Combine(AT + WORD)
+
+strBody = Forward()
+def setBodyLength(tokens):
+ strBody << Word(srange(r'[\0x00-\0xffff]'), exact=int(tokens[0]))
+ return ""
+BLOB = Combine(QUOTE + Word(nums).setParseAction(setBodyLength) +
+ COLON + strBody + QUOTE)
+
+item = Forward()
+def assignUsing(s):
+ def assignPA(tokens):
+ if s in tokens:
+ tokens[tokens[s]] = tokens[0]
+ del tokens[s]
+ return assignPA
+GROUP = (MARK +
+ Group( ZeroOrMore(
+ (item +
+ Optional(ATTRIBUTE)("attr")
+ ).setParseAction(assignUsing("attr"))
+ )
+ ) +
+ ( WORD("name") | UNMARK )
+ ).setParseAction(assignUsing("name"))
+item << (NUMBER | FLOAT | STRING | BLOB | GROUP )
+
+tests = """\
+[ '10:1234567890' @name 25 @age +0.45 @percentage person:zed
+[ [ "hello" 1 child root
+[ "child" [ 200 '4:like' "I" "hello" things root
+[ [ "data" [ 2 1 ] @numbers child root
+[ [ 1 2 3 ] @test 4 5 6 root
+""".splitlines()
+
+for test in tests:
+ if test:
+ print(test)
+ print(item.parseString(test).dump())
+ print()
diff --git a/trunk/src/examples/stateMachine2.py b/trunk/src/examples/stateMachine2.py
new file mode 100644
index 0000000..eb6633d
--- /dev/null
+++ b/trunk/src/examples/stateMachine2.py
@@ -0,0 +1,258 @@
+# stateMachine.py
+#
+# module to define .pystate import handler
+#
+#import imputil
+import sys
+import os
+import types
+import urllib.parse
+
+DEBUG = False
+
+from pyparsing import Word, Group, ZeroOrMore, alphas, \
+ alphanums, ParserElement, ParseException, ParseSyntaxException, \
+ Empty, LineEnd, OneOrMore, col, Keyword, pythonStyleComment, \
+ StringEnd, traceParseAction
+
+
+ident = Word(alphas+"_", alphanums+"_$")
+
+pythonKeywords = """and as assert break class continue def
+ del elif else except exec finally for from global if import
+ in is lambda None not or pass print raise return try while with
+ yield True False"""
+pythonKeywords = set(pythonKeywords.split())
+def no_keywords_allowed(s,l,t):
+ wd = t[0]
+ if wd in pythonKeywords:
+ errmsg = "cannot not use keyword '%s' " \
+ "as an identifier" % wd
+ raise ParseException(s,l,errmsg)
+ident.setParseAction(no_keywords_allowed)
+
+stateTransition = ident("fromState") + "->" + ident("toState")
+stateMachine = Keyword("statemachine") + \
+ ident("name") + ":" + \
+ OneOrMore(Group(stateTransition))("transitions")
+
+namedStateTransition = (ident("fromState") + \
+ "-(" + ident("transition") + ")->" + \
+ ident("toState"))
+namedStateMachine = Keyword("statemachine") + \
+ ident("name") + ":" + \
+ OneOrMore(Group(namedStateTransition))("transitions")
+
+def expand_state_definition(source, loc, tokens):
+ indent = " " * (col(loc,source)-1)
+ statedef = []
+
+ # build list of states
+ states = set()
+ fromTo = {}
+ for tn in tokens.transitions:
+ states.add(tn.fromState)
+ states.add(tn.toState)
+ fromTo[tn.fromState] = tn.toState
+
+ # define base class for state classes
+ baseStateClass = tokens.name + "State"
+ statedef.extend([
+ "class %s(object):" % baseStateClass,
+ " def __str__(self):",
+ " return self.__class__.__name__",
+ " def next_state(self):",
+ " return self._next_state_class()" ])
+
+ # define all state classes
+ statedef.extend(
+ "class %s(%s): pass" % (s,baseStateClass)
+ for s in states )
+ statedef.extend(
+ "%s._next_state_class = %s" % (s,fromTo[s])
+ for s in states if s in fromTo )
+
+ return indent + ("\n"+indent).join(statedef)+"\n"
+
+stateMachine.setParseAction(expand_state_definition)
+
+def expand_named_state_definition(source,loc,tokens):
+ indent = " " * (col(loc,source)-1)
+ statedef = []
+ # build list of states and transitions
+ states = set()
+ transitions = set()
+
+ baseStateClass = tokens.name + "State"
+
+ fromTo = {}
+ for tn in tokens.transitions:
+ states.add(tn.fromState)
+ states.add(tn.toState)
+ transitions.add(tn.transition)
+ if tn.fromState in fromTo:
+ fromTo[tn.fromState][tn.transition] = tn.toState
+ else:
+ fromTo[tn.fromState] = {tn.transition:tn.toState}
+
+ # add entries for terminal states
+ for s in states:
+ if s not in fromTo:
+ fromTo[s] = {}
+
+ # define state transition class
+ statedef.extend([
+ "class %sTransition:" % baseStateClass,
+ " def __str__(self):",
+ " return self.transitionName",
+ ])
+ statedef.extend(
+ "%s = %sTransition()" % (tn,baseStateClass)
+ for tn in transitions)
+ statedef.extend("%s.transitionName = '%s'" % (tn,tn)
+ for tn in transitions)
+
+ # define base class for state classes
+ excmsg = "'" + tokens.name + \
+ '.%s does not support transition "%s"' \
+ "'% (self, tn)"
+ statedef.extend([
+ "class %s(object):" % baseStateClass,
+ " def __str__(self):",
+ " return self.__class__.__name__",
+ " def next_state(self,tn):",
+ " try:",
+ " return self.tnmap[tn]()",
+ " except KeyError:",
+ " raise Exception(%s)" % excmsg,
+ " def __getattr__(self,name):",
+ " raise Exception(%s)" % excmsg,
+ ])
+
+ # define all state classes
+ for s in states:
+ statedef.append("class %s(%s): pass" %
+ (s,baseStateClass))
+
+ # define state transition maps and transition methods
+ for s in states:
+ trns = list(fromTo[s].items())
+ statedef.append("%s.tnmap = {%s}" %
+ (s, ",".join("%s:%s" % tn for tn in trns)) )
+ statedef.extend([
+ "%s.%s = staticmethod(lambda : %s())" %
+ (s,tn_,to_)
+ for tn_,to_ in trns
+ ])
+
+ return indent + ("\n"+indent).join(statedef) + "\n"
+
+namedStateMachine.setParseAction(
+ expand_named_state_definition)
+
+#======================================================================
+# NEW STUFF - Matt Anderson, 2009-11-26
+#======================================================================
+class SuffixImporter(object):
+
+ """An importer designed using the mechanism defined in :pep:`302`. I read
+ the PEP, and also used Doug Hellmann's PyMOTW article `Modules and
+ Imports`_, as a pattern.
+
+ .. _`Modules and Imports`: http://www.doughellmann.com/PyMOTW/sys/imports.html
+
+ Define a subclass that specifies a :attr:`suffix` attribute, and
+ implements a :meth:`process_filedata` method. Then call the classmethod
+ :meth:`register` on your class to actually install it in the appropriate
+ places in :mod:`sys`. """
+
+ scheme = 'suffix'
+ suffix = None
+ path_entry = None
+
+ @classmethod
+ def trigger_url(cls):
+ if cls.suffix is None:
+ raise ValueError('%s.suffix is not set' % cls.__name__)
+ return 'suffix:%s' % cls.suffix
+
+ @classmethod
+ def register(cls):
+ sys.path_hooks.append(cls)
+ sys.path.append(cls.trigger_url())
+
+ def __init__(self, path_entry):
+ pr = urllib.parse.urlparse(str(path_entry))
+ if pr.scheme != self.scheme or pr.path != self.suffix:
+ raise ImportError()
+ self.path_entry = path_entry
+ self._found = {}
+
+ def checkpath_iter(self, fullname):
+ for dirpath in sys.path:
+ # if the value in sys.path_importer_cache is None, then this
+ # path *should* be imported by the builtin mechanism, and the
+ # entry is thus a path to a directory on the filesystem;
+ # if it's not None, then some other importer is in charge, and
+ # it probably isn't even a filesystem path
+ if sys.path_importer_cache.get(dirpath,False) is None:
+ checkpath = os.path.join(
+ dirpath,'%s.%s' % (fullname,self.suffix))
+ yield checkpath
+
+ def find_module(self, fullname, path=None):
+ for checkpath in self.checkpath_iter(fullname):
+ if os.path.isfile(checkpath):
+ self._found[fullname] = checkpath
+ return self
+ return None
+
+ def load_module(self, fullname):
+ assert fullname in self._found
+ if fullname in sys.modules:
+ module = sys.modules[fullname]
+ else:
+ sys.modules[fullname] = module = types.ModuleType(fullname)
+ data = None
+ f = open(self._found[fullname])
+ try:
+ data = f.read()
+ finally:
+ f.close()
+
+ module.__dict__.clear()
+ module.__file__ = self._found[fullname]
+ module.__name__ = fullname
+ module.__loader__ = self
+ self.process_filedata(module, data)
+ return module
+
+ def process_filedata(self, module, data):
+ pass
+
+class PystateImporter(SuffixImporter):
+ suffix = 'pystate'
+
+ def process_filedata(self, module, data):
+ # MATT-NOTE: re-worked :func:`get_state_machine`
+
+ # convert any statemachine expressions
+ stateMachineExpr = (stateMachine |
+ namedStateMachine).ignore(
+ pythonStyleComment)
+ generated_code = stateMachineExpr.transformString(data)
+
+ if DEBUG: print(generated_code)
+
+ # compile code object from generated code
+ # (strip trailing spaces and tabs, compile doesn't like
+ # dangling whitespace)
+ COMPILE_MODE = 'exec'
+
+ codeobj = compile(generated_code.rstrip(" \t"),
+ module.__file__,
+ COMPILE_MODE)
+
+ exec(codeobj, module.__dict__)
+
+PystateImporter.register()
diff --git a/trunk/src/examples/test_bibparse.py b/trunk/src/examples/test_bibparse.py
new file mode 100644
index 0000000..7440a66
--- /dev/null
+++ b/trunk/src/examples/test_bibparse.py
@@ -0,0 +1,195 @@
+""" Test for bibparse grammar """
+
+from os.path import join as pjoin, dirname
+
+from pyparsing import ParseException
+from btpyparse import Macro
+import btpyparse as bp
+
+from nose.tools import assert_true, assert_false, assert_equal, assert_raises
+
+
+def test_names():
+ # check various types of names
+ # All names can contains alphas, but not some special chars
+ bad_chars = '"#%\'(),={}'
+ for name_type, dig1f in ((bp.macro_def, False),
+ (bp.field_name, False),
+ (bp.entry_type, False),
+ (bp.cite_key, True)):
+ if dig1f: # can start with digit
+ assert_equal(name_type.parseString('2t')[0], '2t')
+ else:
+ assert_raises(ParseException, name_type.parseString, '2t')
+ # All of the names cannot contain some characters
+ for char in bad_chars:
+ assert_raises(ParseException, name_type.parseString, char)
+ # standard strings all OK
+ assert_equal(name_type.parseString('simple_test')[0], 'simple_test')
+ # Test macro ref
+ mr = bp.macro_ref
+ # can't start with digit
+ assert_raises(ParseException, mr.parseString, '2t')
+ for char in bad_chars:
+ assert_raises(ParseException, mr.parseString, char)
+ assert_equal(mr.parseString('simple_test')[0].name, 'simple_test')
+
+
+def test_numbers():
+ assert_equal(bp.number.parseString('1066')[0], '1066')
+ assert_equal(bp.number.parseString('0')[0], '0')
+ assert_raises(ParseException, bp.number.parseString, '-4')
+ assert_raises(ParseException, bp.number.parseString, '+4')
+ assert_raises(ParseException, bp.number.parseString, '.4')
+ # something point something leaves a trailing .4 unmatched
+ assert_equal(bp.number.parseString('0.4')[0], '0')
+
+
+def test_parse_string():
+ # test string building blocks
+ assert_equal(bp.chars_no_quotecurly.parseString('x')[0], 'x')
+ assert_equal(bp.chars_no_quotecurly.parseString("a string")[0], 'a string')
+ assert_equal(bp.chars_no_quotecurly.parseString('a "string')[0], 'a ')
+ assert_equal(bp.chars_no_curly.parseString('x')[0], 'x')
+ assert_equal(bp.chars_no_curly.parseString("a string")[0], 'a string')
+ assert_equal(bp.chars_no_curly.parseString('a {string')[0], 'a ')
+ assert_equal(bp.chars_no_curly.parseString('a }string')[0], 'a ')
+ # test more general strings together
+ for obj in (bp.curly_string, bp.string, bp.field_value):
+ assert_equal(obj.parseString('{}').asList(), [])
+ assert_equal(obj.parseString('{a "string}')[0], 'a "string')
+ assert_equal(obj.parseString('{a {nested} string}').asList(),
+ ['a ', ['nested'], ' string'])
+ assert_equal(obj.parseString('{a {double {nested}} string}').asList(),
+ ['a ', ['double ', ['nested']], ' string'])
+ for obj in (bp.quoted_string, bp.string, bp.field_value):
+ assert_equal(obj.parseString('""').asList(), [])
+ assert_equal(obj.parseString('"a string"')[0], 'a string')
+ assert_equal(obj.parseString('"a {nested} string"').asList(),
+ ['a ', ['nested'], ' string'])
+ assert_equal(obj.parseString('"a {double {nested}} string"').asList(),
+ ['a ', ['double ', ['nested']], ' string'])
+ # check macro def in string
+ assert_equal(bp.string.parseString('someascii')[0], Macro('someascii'))
+ assert_raises(ParseException, bp.string.parseString, '%#= validstring')
+ # check number in string
+ assert_equal(bp.string.parseString('1994')[0], '1994')
+
+
+def test_parse_field():
+ # test field value - hashes included
+ fv = bp.field_value
+ # Macro
+ assert_equal(fv.parseString('aname')[0], Macro('aname'))
+ assert_equal(fv.parseString('ANAME')[0], Macro('aname'))
+ # String and macro
+ assert_equal(fv.parseString('aname # "some string"').asList(),
+ [Macro('aname'), 'some string'])
+ # Nested string
+ assert_equal(fv.parseString('aname # {some {string}}').asList(),
+ [Macro('aname'), 'some ', ['string']])
+ # String and number
+ assert_equal(fv.parseString('"a string" # 1994').asList(),
+ ['a string', '1994'])
+ # String and number and macro
+ assert_equal(fv.parseString('"a string" # 1994 # a_macro').asList(),
+ ['a string', '1994', Macro('a_macro')])
+
+
+def test_comments():
+ res = bp.comment.parseString('@Comment{about something}')
+ assert_equal(res.asList(), ['comment', '{about something}'])
+ assert_equal(
+ bp.comment.parseString('@COMMENT{about something').asList(),
+ ['comment', '{about something'])
+ assert_equal(
+ bp.comment.parseString('@comment(about something').asList(),
+ ['comment', '(about something'])
+ assert_equal(
+ bp.comment.parseString('@COMment about something').asList(),
+ ['comment', ' about something'])
+ assert_raises(ParseException, bp.comment.parseString,
+ '@commentabout something')
+ assert_raises(ParseException, bp.comment.parseString,
+ '@comment+about something')
+ assert_raises(ParseException, bp.comment.parseString,
+ '@comment"about something')
+
+
+def test_preamble():
+ res = bp.preamble.parseString('@preamble{"about something"}')
+ assert_equal(res.asList(), ['preamble', 'about something'])
+ assert_equal(bp.preamble.parseString(
+ '@PREamble{{about something}}').asList(),
+ ['preamble', 'about something'])
+ assert_equal(bp.preamble.parseString("""@PREamble{
+ {about something}
+ }""").asList(),
+ ['preamble', 'about something'])
+
+
+def test_macro():
+ res = bp.macro.parseString('@string{ANAME = "about something"}')
+ assert_equal(res.asList(), ['string', 'aname', 'about something'])
+ assert_equal(
+ bp.macro.parseString('@string{aname = {about something}}').asList(),
+ ['string', 'aname', 'about something'])
+
+
+def test_entry():
+ txt = """@some_entry{akey, aname = "about something",
+ another={something else}}"""
+ res = bp.entry.parseString(txt)
+ assert_equal(res.asList(),
+ ['some_entry', 'akey',
+ ['aname', 'about something'], ['another', 'something else']])
+ # Case conversion
+ txt = """@SOME_ENTRY{akey, ANAME = "about something",
+ another={something else}}"""
+ res = bp.entry.parseString(txt)
+ assert_equal(res.asList(),
+ ['some_entry', 'akey',
+ ['aname', 'about something'], ['another', 'something else']])
+
+
+def test_bibfile():
+ txt = """@some_entry{akey, aname = "about something",
+ another={something else}}"""
+ res = bp.bibfile.parseString(txt)
+ assert_equal(res.asList(),
+ [['some_entry', 'akey',
+ ['aname', 'about something'],
+ ['another', 'something else']]])
+
+
+def test_bib1():
+ # First pass whole bib-like tests
+ txt = """
+Some introductory text
+(implicit comment)
+
+ @ARTICLE{Brett2002marsbar,
+ author = {Matthew Brett and Jean-Luc Anton and Romain Valabregue and Jean-Baptise
+ Poline},
+ title = {{Region of interest analysis using an SPM toolbox}},
+ journal = {Neuroimage},
+ year = {2002},
+ volume = {16},
+ pages = {1140--1141},
+ number = {2}
+}
+
+@some_entry{akey, aname = "about something",
+another={something else}}
+"""
+ res = bp.bibfile.parseString(txt)
+ assert_equal(len(res), 3)
+ res2 = bp.parse_str(txt)
+ assert_equal(res.asList(), res2.asList())
+ res3 = [r.asList()[0] for r, start, end in bp.definitions.scanString(txt)]
+ assert_equal(res.asList(), res3)
+
+
+if __name__ == '__main__':
+ import nose
+ nose.main()
diff --git a/trunk/src/examples/urlExtractor.py b/trunk/src/examples/urlExtractor.py
new file mode 100644
index 0000000..2c66d78
--- /dev/null
+++ b/trunk/src/examples/urlExtractor.py
@@ -0,0 +1,33 @@
+# URL extractor
+# Copyright 2004, Paul McGuire
+from pyparsing import makeHTMLTags, SkipTo, pyparsing_common
+import urllib.request
+from contextlib import closing
+import pprint
+
+linkOpenTag, linkCloseTag = makeHTMLTags('a')
+
+linkBody = SkipTo(linkCloseTag)
+linkBody.setParseAction(pyparsing_common.stripHTMLTags)
+linkBody.addParseAction(lambda toks: ' '.join(toks[0].strip().split()))
+
+link = linkOpenTag + linkBody("body") + linkCloseTag.suppress()
+
+# Go get some HTML with some links in it.
+with closing(urllib.request.urlopen("http://www.yahoo.com")) as serverListPage:
+ htmlText = serverListPage.read().decode("UTF-8")
+
+# scanString is a generator that loops through the input htmlText, and for each
+# match yields the tokens and start and end locations (for this application, we are
+# not interested in the start and end values).
+for toks,strt,end in link.scanString(htmlText):
+ print(toks.asList())
+
+# Create dictionary from list comprehension, assembled from each pair of tokens returned
+# from a matched URL.
+pprint.pprint(
+ dict((toks.body, toks.href) for toks,strt,end in link.scanString(htmlText))
+ )
+
+
+
diff --git a/trunk/src/examples/urlExtractorNew.py b/trunk/src/examples/urlExtractorNew.py
new file mode 100644
index 0000000..0aac875
--- /dev/null
+++ b/trunk/src/examples/urlExtractorNew.py
@@ -0,0 +1,35 @@
+# URL extractor
+# Copyright 2004, Paul McGuire
+from pyparsing import Literal,Suppress,CharsNotIn,CaselessLiteral,\
+ Word,dblQuotedString,alphanums,SkipTo,makeHTMLTags
+import urllib.request, urllib.parse, urllib.error
+import pprint
+
+# Define the pyparsing grammar for a URL, that is:
+# URLlink ::= <a href= URL>linkText</a>
+# URL ::= doubleQuotedString | alphanumericWordPath
+# Note that whitespace may appear just about anywhere in the link. Note also
+# that it is not necessary to explicitly show this in the pyparsing grammar; by default,
+# pyparsing skips over whitespace between tokens.
+linkOpenTag,linkCloseTag = makeHTMLTags("a")
+link = linkOpenTag + SkipTo(linkCloseTag)("body") + linkCloseTag.suppress()
+
+# Go get some HTML with some links in it.
+serverListPage = urllib.request.urlopen( "http://www.google.com" )
+htmlText = serverListPage.read()
+serverListPage.close()
+
+# scanString is a generator that loops through the input htmlText, and for each
+# match yields the tokens and start and end locations (for this application, we are
+# not interested in the start and end values).
+for toks,strt,end in link.scanString(htmlText):
+ print(toks.startA.href,"->",toks.body)
+
+# Create dictionary from list comprehension, assembled from each pair of tokens returned
+# from a matched URL.
+pprint.pprint(
+ dict( [ (toks.body,toks.startA.href) for toks,strt,end in link.scanString(htmlText) ] )
+ )
+
+
+
diff --git a/trunk/src/examples/verilogParse.py b/trunk/src/examples/verilogParse.py
new file mode 100644
index 0000000..2b7fd35
--- /dev/null
+++ b/trunk/src/examples/verilogParse.py
@@ -0,0 +1,720 @@
+#
+# verilogParse.py
+#
+# an example of using the pyparsing module to be able to process Verilog files
+# uses BNF defined at http://www.verilog.com/VerilogBNF.html
+#
+# Copyright (c) 2004-2011 Paul T. McGuire. All rights reserved.
+#
+# 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.
+#
+# If you find this software to be useful, please make a donation to one
+# of the following charities:
+# - the Red Cross (http://www.redcross.org)
+# - Hospice Austin (http://www.hospiceaustin.org)
+#
+# DISCLAIMER:
+# THIS SOFTWARE IS PROVIDED BY PAUL T. McGUIRE ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL PAUL T. McGUIRE OR CO-CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OFUSE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# For questions or inquiries regarding this license, or commercial use of
+# this software, contact the author via e-mail: ptmcg@users.sourceforge.net
+#
+# Todo:
+# - add pre-process pass to implement compilerDirectives (ifdef, include, etc.)
+#
+# Revision History:
+#
+# 1.0 - Initial release
+# 1.0.1 - Fixed grammar errors:
+# . real declaration was incorrect
+# . tolerant of '=>' for '*>' operator
+# . tolerant of '?' as hex character
+# . proper handling of mintypmax_expr within path delays
+# 1.0.2 - Performance tuning (requires pyparsing 1.3)
+# 1.0.3 - Performance updates, using Regex (requires pyparsing 1.4)
+# 1.0.4 - Performance updates, enable packrat parsing (requires pyparsing 1.4.2)
+# 1.0.5 - Converted keyword Literals to Keywords, added more use of Group to
+# group parsed results tokens
+# 1.0.6 - Added support for module header with no ports list (thanks, Thomas Dejanovic!)
+# 1.0.7 - Fixed erroneous '<<' Forward definition in timCheckCond, omitting ()'s
+# 1.0.8 - Re-released under MIT license
+# 1.0.9 - Enhanced udpInstance to handle identifiers with leading '\' and subscripting
+# 1.0.10 - Fixed change added in 1.0.9 to work for all identifiers, not just those used
+# for udpInstance.
+# 1.0.11 - Fixed bug in inst_args, content alternatives were reversed
+#
+import pdb
+import time
+import pprint
+import sys
+
+__version__ = "1.0.11"
+
+from pyparsing import Literal, CaselessLiteral, Keyword, Word, OneOrMore, ZeroOrMore, \
+ Forward, NotAny, delimitedList, Group, Optional, Combine, alphas, nums, restOfLine, cStyleComment, \
+ alphanums, printables, dblQuotedString, empty, ParseException, ParseResults, MatchFirst, oneOf, GoToColumn, \
+ ParseResults,StringEnd, FollowedBy, ParserElement, And, Regex, cppStyleComment#,__version__
+import pyparsing
+usePackrat = False
+usePsyco = False
+
+packratOn = False
+psycoOn = False
+
+if usePackrat:
+ try:
+ ParserElement.enablePackrat()
+ except:
+ pass
+ else:
+ packratOn = True
+
+# comment out this section to disable psyco function compilation
+if usePsyco:
+ try:
+ import psyco
+ psyco.full()
+ except:
+ print("failed to import psyco Python optimizer")
+ else:
+ psycoOn = True
+
+
+def dumpTokens(s,l,t):
+ import pprint
+ pprint.pprint( t.asList() )
+
+verilogbnf = None
+def Verilog_BNF():
+ global verilogbnf
+
+ if verilogbnf is None:
+
+ # compiler directives
+ compilerDirective = Combine( "`" + \
+ oneOf("define undef ifdef else endif default_nettype "
+ "include resetall timescale unconnected_drive "
+ "nounconnected_drive celldefine endcelldefine") + \
+ restOfLine ).setName("compilerDirective")
+
+ # primitives
+ SEMI,COLON,LPAR,RPAR,LBRACE,RBRACE,LBRACK,RBRACK,DOT,COMMA,EQ = map(Literal,";:(){}[].,=")
+
+ identLead = alphas+"$_"
+ identBody = alphanums+"$_"
+ identifier1 = Regex( r"\.?["+identLead+"]["+identBody+"]*(\.["+identLead+"]["+identBody+"]*)*"
+ ).setName("baseIdent")
+ identifier2 = Regex(r"\\\S+").setParseAction(lambda t:t[0][1:]).setName("escapedIdent")#.setDebug()
+ identifier = identifier1 | identifier2
+ assert(identifier2 == r'\abc')
+
+ hexnums = nums + "abcdefABCDEF" + "_?"
+ base = Regex("'[bBoOdDhH]").setName("base")
+ basedNumber = Combine( Optional( Word(nums + "_") ) + base + Word(hexnums+"xXzZ"),
+ joinString=" ", adjacent=False ).setName("basedNumber")
+ #~ number = ( basedNumber | Combine( Word( "+-"+spacedNums, spacedNums ) +
+ #~ Optional( DOT + Optional( Word( spacedNums ) ) ) +
+ #~ Optional( e + Word( "+-"+spacedNums, spacedNums ) ) ).setName("numeric") )
+ number = ( basedNumber | \
+ Regex(r"[+-]?[0-9_]+(\.[0-9_]*)?([Ee][+-]?[0-9_]+)?") \
+ ).setName("numeric")
+ #~ decnums = nums + "_"
+ #~ octnums = "01234567" + "_"
+ expr = Forward().setName("expr")
+ concat = Group( LBRACE + delimitedList( expr ) + RBRACE )
+ multiConcat = Group("{" + expr + concat + "}").setName("multiConcat")
+ funcCall = Group(identifier + LPAR + Optional( delimitedList( expr ) ) + RPAR).setName("funcCall")
+
+ subscrRef = Group(LBRACK + delimitedList( expr, COLON ) + RBRACK)
+ subscrIdentifier = Group( identifier + Optional( subscrRef ) )
+ #~ scalarConst = "0" | (( FollowedBy('1') + oneOf("1'b0 1'b1 1'bx 1'bX 1'B0 1'B1 1'Bx 1'BX 1") ))
+ scalarConst = Regex("0|1('[Bb][01xX])?")
+ mintypmaxExpr = Group( expr + COLON + expr + COLON + expr ).setName("mintypmax")
+ primary = (
+ number |
+ (LPAR + mintypmaxExpr + RPAR ) |
+ ( LPAR + Group(expr) + RPAR ).setName("nestedExpr") |
+ multiConcat |
+ concat |
+ dblQuotedString |
+ funcCall |
+ subscrIdentifier
+ )
+
+ unop = oneOf( "+ - ! ~ & ~& | ^| ^ ~^" ).setName("unop")
+ binop = oneOf( "+ - * / % == != === !== && "
+ "|| < <= > >= & | ^ ^~ >> << ** <<< >>>" ).setName("binop")
+
+ expr << (
+ ( unop + expr ) | # must be first!
+ ( primary + "?" + expr + COLON + expr ) |
+ ( primary + Optional( binop + expr ) )
+ )
+
+ lvalue = subscrIdentifier | concat
+
+ # keywords
+ if_ = Keyword("if")
+ else_ = Keyword("else")
+ edge = Keyword("edge")
+ posedge = Keyword("posedge")
+ negedge = Keyword("negedge")
+ specify = Keyword("specify")
+ endspecify = Keyword("endspecify")
+ fork = Keyword("fork")
+ join = Keyword("join")
+ begin = Keyword("begin")
+ end = Keyword("end")
+ default = Keyword("default")
+ forever = Keyword("forever")
+ repeat = Keyword("repeat")
+ while_ = Keyword("while")
+ for_ = Keyword("for")
+ case = oneOf( "case casez casex" )
+ endcase = Keyword("endcase")
+ wait = Keyword("wait")
+ disable = Keyword("disable")
+ deassign = Keyword("deassign")
+ force = Keyword("force")
+ release = Keyword("release")
+ assign = Keyword("assign")
+
+ eventExpr = Forward()
+ eventTerm = ( posedge + expr ) | ( negedge + expr ) | expr | ( LPAR + eventExpr + RPAR )
+ eventExpr << (
+ Group( delimitedList( eventTerm, Keyword("or") ) )
+ )
+ eventControl = Group( "@" + ( ( LPAR + eventExpr + RPAR ) | identifier | "*" ) ).setName("eventCtrl")
+
+ delayArg = ( number |
+ Word(alphanums+"$_") | #identifier |
+ ( LPAR + Group( delimitedList( mintypmaxExpr | expr ) ) + RPAR )
+ ).setName("delayArg")#.setDebug()
+ delay = Group( "#" + delayArg ).setName("delay")#.setDebug()
+ delayOrEventControl = delay | eventControl
+
+ assgnmt = Group( lvalue + EQ + Optional( delayOrEventControl ) + expr ).setName( "assgnmt" )
+ nbAssgnmt = Group(( lvalue + "<=" + Optional( delay ) + expr ) |
+ ( lvalue + "<=" + Optional( eventControl ) + expr )).setName( "nbassgnmt" )
+
+ range = LBRACK + expr + COLON + expr + RBRACK
+
+ paramAssgnmt = Group( identifier + EQ + expr ).setName("paramAssgnmt")
+ parameterDecl = Group( "parameter" + Optional( range ) + delimitedList( paramAssgnmt ) + SEMI).setName("paramDecl")
+
+ inputDecl = Group( "input" + Optional( range ) + delimitedList( identifier ) + SEMI )
+ outputDecl = Group( "output" + Optional( range ) + delimitedList( identifier ) + SEMI )
+ inoutDecl = Group( "inout" + Optional( range ) + delimitedList( identifier ) + SEMI )
+
+ regIdentifier = Group( identifier + Optional( LBRACK + expr + COLON + expr + RBRACK ) )
+ regDecl = Group( "reg" + Optional("signed") + Optional( range ) + delimitedList( regIdentifier ) + SEMI ).setName("regDecl")
+ timeDecl = Group( "time" + delimitedList( regIdentifier ) + SEMI )
+ integerDecl = Group( "integer" + delimitedList( regIdentifier ) + SEMI )
+
+ strength0 = oneOf("supply0 strong0 pull0 weak0 highz0")
+ strength1 = oneOf("supply1 strong1 pull1 weak1 highz1")
+ driveStrength = Group( LPAR + ( ( strength0 + COMMA + strength1 ) |
+ ( strength1 + COMMA + strength0 ) ) + RPAR ).setName("driveStrength")
+ nettype = oneOf("wire tri tri1 supply0 wand triand tri0 supply1 wor trior trireg")
+ expandRange = Optional( oneOf("scalared vectored") ) + range
+ realDecl = Group( "real" + delimitedList( identifier ) + SEMI )
+
+ eventDecl = Group( "event" + delimitedList( identifier ) + SEMI )
+
+ blockDecl = (
+ parameterDecl |
+ regDecl |
+ integerDecl |
+ realDecl |
+ timeDecl |
+ eventDecl
+ )
+
+ stmt = Forward().setName("stmt")#.setDebug()
+ stmtOrNull = stmt | SEMI
+ caseItem = ( delimitedList( expr ) + COLON + stmtOrNull ) | \
+ ( default + Optional(":") + stmtOrNull )
+ stmt << Group(
+ ( begin + Group( ZeroOrMore( stmt ) ) + end ).setName("begin-end") |
+ ( if_ + Group(LPAR + expr + RPAR) + stmtOrNull + Optional( else_ + stmtOrNull ) ).setName("if") |
+ ( delayOrEventControl + stmtOrNull ) |
+ ( case + LPAR + expr + RPAR + OneOrMore( caseItem ) + endcase ) |
+ ( forever + stmt ) |
+ ( repeat + LPAR + expr + RPAR + stmt ) |
+ ( while_ + LPAR + expr + RPAR + stmt ) |
+ ( for_ + LPAR + assgnmt + SEMI + Group( expr ) + SEMI + assgnmt + RPAR + stmt ) |
+ ( fork + ZeroOrMore( stmt ) + join ) |
+ ( fork + COLON + identifier + ZeroOrMore( blockDecl ) + ZeroOrMore( stmt ) + end ) |
+ ( wait + LPAR + expr + RPAR + stmtOrNull ) |
+ ( "->" + identifier + SEMI ) |
+ ( disable + identifier + SEMI ) |
+ ( assign + assgnmt + SEMI ) |
+ ( deassign + lvalue + SEMI ) |
+ ( force + assgnmt + SEMI ) |
+ ( release + lvalue + SEMI ) |
+ ( begin + COLON + identifier + ZeroOrMore( blockDecl ) + ZeroOrMore( stmt ) + end ).setName("begin:label-end") |
+ # these *have* to go at the end of the list!!!
+ ( assgnmt + SEMI ) |
+ ( nbAssgnmt + SEMI ) |
+ ( Combine( Optional("$") + identifier ) + Optional( LPAR + delimitedList(expr|empty) + RPAR ) + SEMI )
+ ).setName("stmtBody")
+ """
+ x::=<blocking_assignment> ;
+ x||= <non_blocking_assignment> ;
+ x||= if ( <expression> ) <statement_or_null>
+ x||= if ( <expression> ) <statement_or_null> else <statement_or_null>
+ x||= case ( <expression> ) <case_item>+ endcase
+ x||= casez ( <expression> ) <case_item>+ endcase
+ x||= casex ( <expression> ) <case_item>+ endcase
+ x||= forever <statement>
+ x||= repeat ( <expression> ) <statement>
+ x||= while ( <expression> ) <statement>
+ x||= for ( <assignment> ; <expression> ; <assignment> ) <statement>
+ x||= <delay_or_event_control> <statement_or_null>
+ x||= wait ( <expression> ) <statement_or_null>
+ x||= -> <name_of_event> ;
+ x||= <seq_block>
+ x||= <par_block>
+ x||= <task_enable>
+ x||= <system_task_enable>
+ x||= disable <name_of_task> ;
+ x||= disable <name_of_block> ;
+ x||= assign <assignment> ;
+ x||= deassign <lvalue> ;
+ x||= force <assignment> ;
+ x||= release <lvalue> ;
+ """
+ alwaysStmt = Group( "always" + Optional(eventControl) + stmt ).setName("alwaysStmt")
+ initialStmt = Group( "initial" + stmt ).setName("initialStmt")
+
+ chargeStrength = Group( LPAR + oneOf( "small medium large" ) + RPAR ).setName("chargeStrength")
+
+ continuousAssign = Group(
+ assign + Optional( driveStrength ) + Optional( delay ) + delimitedList( assgnmt ) + SEMI
+ ).setName("continuousAssign")
+
+
+ tfDecl = (
+ parameterDecl |
+ inputDecl |
+ outputDecl |
+ inoutDecl |
+ regDecl |
+ timeDecl |
+ integerDecl |
+ realDecl
+ )
+
+ functionDecl = Group(
+ "function" + Optional( range | "integer" | "real" ) + identifier + SEMI +
+ Group( OneOrMore( tfDecl ) ) +
+ Group( ZeroOrMore( stmt ) ) +
+ "endfunction"
+ )
+
+ inputOutput = oneOf("input output")
+ netDecl1Arg = ( nettype +
+ Optional( expandRange ) +
+ Optional( delay ) +
+ Group( delimitedList( ~inputOutput + identifier ) ) )
+ netDecl2Arg = ( "trireg" +
+ Optional( chargeStrength ) +
+ Optional( expandRange ) +
+ Optional( delay ) +
+ Group( delimitedList( ~inputOutput + identifier ) ) )
+ netDecl3Arg = ( nettype +
+ Optional( driveStrength ) +
+ Optional( expandRange ) +
+ Optional( delay ) +
+ Group( delimitedList( assgnmt ) ) )
+ netDecl1 = Group(netDecl1Arg + SEMI).setName("netDecl1")
+ netDecl2 = Group(netDecl2Arg + SEMI).setName("netDecl2")
+ netDecl3 = Group(netDecl3Arg + SEMI).setName("netDecl3")
+
+ gateType = oneOf("and nand or nor xor xnor buf bufif0 bufif1 "
+ "not notif0 notif1 pulldown pullup nmos rnmos "
+ "pmos rpmos cmos rcmos tran rtran tranif0 "
+ "rtranif0 tranif1 rtranif1" )
+ gateInstance = Optional( Group( identifier + Optional( range ) ) ) + \
+ LPAR + Group( delimitedList( expr ) ) + RPAR
+ gateDecl = Group( gateType +
+ Optional( driveStrength ) +
+ Optional( delay ) +
+ delimitedList( gateInstance) +
+ SEMI )
+
+ udpInstance = Group( Group( identifier + Optional(range | subscrRef) ) +
+ LPAR + Group( delimitedList( expr ) ) + RPAR )
+ udpInstantiation = Group( identifier -
+ Optional( driveStrength ) +
+ Optional( delay ) +
+ delimitedList( udpInstance ) +
+ SEMI ).setName("udpInstantiation")
+
+ parameterValueAssignment = Group( Literal("#") + LPAR + Group( delimitedList( expr ) ) + RPAR )
+ namedPortConnection = Group( DOT + identifier + LPAR + expr + RPAR ).setName("namedPortConnection")#.setDebug()
+ assert(r'.\abc (abc )' == namedPortConnection)
+ modulePortConnection = expr | empty
+ #~ moduleInstance = Group( Group ( identifier + Optional(range) ) +
+ #~ ( delimitedList( modulePortConnection ) |
+ #~ delimitedList( namedPortConnection ) ) )
+ inst_args = Group( LPAR + (delimitedList( namedPortConnection ) |
+ delimitedList( modulePortConnection )) + RPAR).setName("inst_args")
+ moduleInstance = Group( Group ( identifier + Optional(range) ) + inst_args ).setName("moduleInstance")#.setDebug()
+
+ moduleInstantiation = Group( identifier +
+ Optional( parameterValueAssignment ) +
+ delimitedList( moduleInstance ).setName("moduleInstanceList") +
+ SEMI ).setName("moduleInstantiation")
+
+ parameterOverride = Group( "defparam" + delimitedList( paramAssgnmt ) + SEMI )
+ task = Group( "task" + identifier + SEMI +
+ ZeroOrMore( tfDecl ) +
+ stmtOrNull +
+ "endtask" )
+
+ specparamDecl = Group( "specparam" + delimitedList( paramAssgnmt ) + SEMI )
+
+ pathDescr1 = Group( LPAR + subscrIdentifier + "=>" + subscrIdentifier + RPAR )
+ pathDescr2 = Group( LPAR + Group( delimitedList( subscrIdentifier ) ) + "*>" +
+ Group( delimitedList( subscrIdentifier ) ) + RPAR )
+ pathDescr3 = Group( LPAR + Group( delimitedList( subscrIdentifier ) ) + "=>" +
+ Group( delimitedList( subscrIdentifier ) ) + RPAR )
+ pathDelayValue = Group( ( LPAR + Group( delimitedList( mintypmaxExpr | expr ) ) + RPAR ) |
+ mintypmaxExpr |
+ expr )
+ pathDecl = Group( ( pathDescr1 | pathDescr2 | pathDescr3 ) + EQ + pathDelayValue + SEMI ).setName("pathDecl")
+
+ portConditionExpr = Forward()
+ portConditionTerm = Optional(unop) + subscrIdentifier
+ portConditionExpr << portConditionTerm + Optional( binop + portConditionExpr )
+ polarityOp = oneOf("+ -")
+ levelSensitivePathDecl1 = Group(
+ if_ + Group(LPAR + portConditionExpr + RPAR) +
+ subscrIdentifier + Optional( polarityOp ) + "=>" + subscrIdentifier + EQ +
+ pathDelayValue +
+ SEMI )
+ levelSensitivePathDecl2 = Group(
+ if_ + Group(LPAR + portConditionExpr + RPAR) +
+ LPAR + Group( delimitedList( subscrIdentifier ) ) + Optional( polarityOp ) + "*>" +
+ Group( delimitedList( subscrIdentifier ) ) + RPAR + EQ +
+ pathDelayValue +
+ SEMI )
+ levelSensitivePathDecl = levelSensitivePathDecl1 | levelSensitivePathDecl2
+
+ edgeIdentifier = posedge | negedge
+ edgeSensitivePathDecl1 = Group(
+ Optional( if_ + Group(LPAR + expr + RPAR) ) +
+ LPAR + Optional( edgeIdentifier ) +
+ subscrIdentifier + "=>" +
+ LPAR + subscrIdentifier + Optional( polarityOp ) + COLON + expr + RPAR + RPAR +
+ EQ +
+ pathDelayValue +
+ SEMI )
+ edgeSensitivePathDecl2 = Group(
+ Optional( if_ + Group(LPAR + expr + RPAR) ) +
+ LPAR + Optional( edgeIdentifier ) +
+ subscrIdentifier + "*>" +
+ LPAR + delimitedList( subscrIdentifier ) + Optional( polarityOp ) + COLON + expr + RPAR + RPAR +
+ EQ +
+ pathDelayValue +
+ SEMI )
+ edgeSensitivePathDecl = edgeSensitivePathDecl1 | edgeSensitivePathDecl2
+
+ edgeDescr = oneOf("01 10 0x x1 1x x0").setName("edgeDescr")
+
+ timCheckEventControl = Group( posedge | negedge | (edge + LBRACK + delimitedList( edgeDescr ) + RBRACK ))
+ timCheckCond = Forward()
+ timCondBinop = oneOf("== === != !==")
+ timCheckCondTerm = ( expr + timCondBinop + scalarConst ) | ( Optional("~") + expr )
+ timCheckCond << ( ( LPAR + timCheckCond + RPAR ) | timCheckCondTerm )
+ timCheckEvent = Group( Optional( timCheckEventControl ) +
+ subscrIdentifier +
+ Optional( "&&&" + timCheckCond ) )
+ timCheckLimit = expr
+ controlledTimingCheckEvent = Group( timCheckEventControl + subscrIdentifier +
+ Optional( "&&&" + timCheckCond ) )
+ notifyRegister = identifier
+
+ systemTimingCheck1 = Group( "$setup" +
+ LPAR + timCheckEvent + COMMA + timCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck2 = Group( "$hold" +
+ LPAR + timCheckEvent + COMMA + timCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck3 = Group( "$period" +
+ LPAR + controlledTimingCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck4 = Group( "$width" +
+ LPAR + controlledTimingCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + expr + COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck5 = Group( "$skew" +
+ LPAR + timCheckEvent + COMMA + timCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck6 = Group( "$recovery" +
+ LPAR + controlledTimingCheckEvent + COMMA + timCheckEvent + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck7 = Group( "$setuphold" +
+ LPAR + timCheckEvent + COMMA + timCheckEvent + COMMA + timCheckLimit + COMMA + timCheckLimit +
+ Optional( COMMA + notifyRegister ) + RPAR +
+ SEMI )
+ systemTimingCheck = (FollowedBy('$') + ( systemTimingCheck1 | systemTimingCheck2 | systemTimingCheck3 |
+ systemTimingCheck4 | systemTimingCheck5 | systemTimingCheck6 | systemTimingCheck7 )).setName("systemTimingCheck")
+ sdpd = if_ + Group(LPAR + expr + RPAR) + \
+ ( pathDescr1 | pathDescr2 ) + EQ + pathDelayValue + SEMI
+
+ specifyItem = ~Keyword("endspecify") +(
+ specparamDecl |
+ pathDecl |
+ levelSensitivePathDecl |
+ edgeSensitivePathDecl |
+ systemTimingCheck |
+ sdpd
+ )
+ """
+ x::= <specparam_declaration>
+ x||= <path_declaration>
+ x||= <level_sensitive_path_declaration>
+ x||= <edge_sensitive_path_declaration>
+ x||= <system_timing_check>
+ x||= <sdpd>
+ """
+ specifyBlock = Group( "specify" + ZeroOrMore( specifyItem ) + "endspecify" ).setName("specifyBlock")
+
+ moduleItem = ~Keyword("endmodule") + (
+ parameterDecl |
+ inputDecl |
+ outputDecl |
+ inoutDecl |
+ regDecl |
+ netDecl3 |
+ netDecl1 |
+ netDecl2 |
+ timeDecl |
+ integerDecl |
+ realDecl |
+ eventDecl |
+ gateDecl |
+ parameterOverride |
+ continuousAssign |
+ specifyBlock |
+ initialStmt |
+ alwaysStmt |
+ task |
+ functionDecl |
+ # these have to be at the end - they start with identifiers
+ moduleInstantiation |
+ udpInstantiation
+ )
+ """ All possible moduleItems, from Verilog grammar spec
+ x::= <parameter_declaration>
+ x||= <input_declaration>
+ x||= <output_declaration>
+ x||= <inout_declaration>
+ ?||= <net_declaration> (spec does not seem consistent for this item)
+ x||= <reg_declaration>
+ x||= <time_declaration>
+ x||= <integer_declaration>
+ x||= <real_declaration>
+ x||= <event_declaration>
+ x||= <gate_declaration>
+ x||= <UDP_instantiation>
+ x||= <module_instantiation>
+ x||= <parameter_override>
+ x||= <continuous_assign>
+ x||= <specify_block>
+ x||= <initial_statement>
+ x||= <always_statement>
+ x||= <task>
+ x||= <function>
+ """
+ portRef = subscrIdentifier
+ portExpr = portRef | Group( LBRACE + delimitedList( portRef ) + RBRACE )
+ port = portExpr | Group( ( DOT + identifier + LPAR + portExpr + RPAR ) )
+
+ moduleHdr = Group ( oneOf("module macromodule") + identifier +
+ Optional( LPAR + Group( Optional( delimitedList(
+ Group(oneOf("input output") +
+ (netDecl1Arg | netDecl2Arg | netDecl3Arg) ) |
+ port ) ) ) +
+ RPAR ) + SEMI ).setName("moduleHdr")
+
+ module = Group( moduleHdr +
+ Group( ZeroOrMore( moduleItem ) ) +
+ "endmodule" ).setName("module")#.setDebug()
+
+ udpDecl = outputDecl | inputDecl | regDecl
+ #~ udpInitVal = oneOf("1'b0 1'b1 1'bx 1'bX 1'B0 1'B1 1'Bx 1'BX 1 0 x X")
+ udpInitVal = (Regex("1'[bB][01xX]") | Regex("[01xX]")).setName("udpInitVal")
+ udpInitialStmt = Group( "initial" +
+ identifier + EQ + udpInitVal + SEMI ).setName("udpInitialStmt")
+
+ levelSymbol = oneOf("0 1 x X ? b B")
+ levelInputList = Group( OneOrMore( levelSymbol ).setName("levelInpList") )
+ outputSymbol = oneOf("0 1 x X")
+ combEntry = Group( levelInputList + COLON + outputSymbol + SEMI )
+ edgeSymbol = oneOf("r R f F p P n N *")
+ edge = Group( LPAR + levelSymbol + levelSymbol + RPAR ) | \
+ Group( edgeSymbol )
+ edgeInputList = Group( ZeroOrMore( levelSymbol ) + edge + ZeroOrMore( levelSymbol ) )
+ inputList = levelInputList | edgeInputList
+ seqEntry = Group( inputList + COLON + levelSymbol + COLON + ( outputSymbol | "-" ) + SEMI ).setName("seqEntry")
+ udpTableDefn = Group( "table" +
+ OneOrMore( combEntry | seqEntry ) +
+ "endtable" ).setName("table")
+
+ """
+ <UDP>
+ ::= primitive <name_of_UDP> ( <name_of_variable> <,<name_of_variable>>* ) ;
+ <UDP_declaration>+
+ <UDP_initial_statement>?
+ <table_definition>
+ endprimitive
+ """
+ udp = Group( "primitive" + identifier +
+ LPAR + Group( delimitedList( identifier ) ) + RPAR + SEMI +
+ OneOrMore( udpDecl ) +
+ Optional( udpInitialStmt ) +
+ udpTableDefn +
+ "endprimitive" )
+
+ verilogbnf = OneOrMore( module | udp ) + StringEnd()
+
+ verilogbnf.ignore( cppStyleComment )
+ verilogbnf.ignore( compilerDirective )
+
+ return verilogbnf
+
+
+def test( strng ):
+ tokens = []
+ try:
+ tokens = Verilog_BNF().parseString( strng )
+ except ParseException as err:
+ print(err.line)
+ print(" "*(err.column-1) + "^")
+ print(err)
+ return tokens
+
+
+#~ if __name__ == "__main__":
+if 0:
+ import pprint
+ toptest = """
+ module TOP( in, out );
+ input [7:0] in;
+ output [5:0] out;
+ COUNT_BITS8 count_bits( .IN( in ), .C( out ) );
+ endmodule"""
+ pprint.pprint( test(toptest).asList() )
+
+else:
+ def main():
+ print("Verilog parser test (V %s)" % __version__)
+ print(" - using pyparsing version", pyparsing.__version__)
+ print(" - using Python version", sys.version)
+ if packratOn: print(" - using packrat parsing")
+ if psycoOn: print(" - using psyco runtime optimization")
+ print()
+
+ import os
+ import gc
+
+ failCount = 0
+ Verilog_BNF()
+ numlines = 0
+ startTime = time.clock()
+ fileDir = "verilog"
+ #~ fileDir = "verilog/new"
+ #~ fileDir = "verilog/new2"
+ #~ fileDir = "verilog/new3"
+ allFiles = [f for f in os.listdir(fileDir) if f.endswith(".v")]
+ #~ allFiles = [ "list_path_delays_test.v" ]
+ #~ allFiles = [ "escapedIdent.v" ]
+ #~ allFiles = filter( lambda f : f.startswith("a") and f.endswith(".v"), os.listdir(fileDir) )
+ #~ allFiles = filter( lambda f : f.startswith("c") and f.endswith(".v"), os.listdir(fileDir) )
+ #~ allFiles = [ "ff.v" ]
+
+ pp = pprint.PrettyPrinter( indent=2 )
+ totalTime = 0
+ for vfile in allFiles:
+ gc.collect()
+ fnam = fileDir + "/"+vfile
+ infile = open(fnam)
+ filelines = infile.readlines()
+ infile.close()
+ print(fnam, len(filelines), end=' ')
+ numlines += len(filelines)
+ teststr = "".join(filelines)
+ time1 = time.clock()
+ tokens = test( teststr )
+ time2 = time.clock()
+ elapsed = time2-time1
+ totalTime += elapsed
+ if ( len( tokens ) ):
+ print("OK", elapsed)
+ #~ print "tokens="
+ #~ pp.pprint( tokens.asList() )
+ #~ print
+
+ ofnam = fileDir + "/parseOutput/" + vfile + ".parsed.txt"
+ outfile = open(ofnam,"w")
+ outfile.write( teststr )
+ outfile.write("\n")
+ outfile.write("\n")
+ outfile.write(pp.pformat(tokens.asList()))
+ outfile.write("\n")
+ outfile.close()
+ else:
+ print("failed", elapsed)
+ failCount += 1
+ for i,line in enumerate(filelines,1):
+ print("%4d: %s" % (i,line.rstrip()))
+ endTime = time.clock()
+ print("Total parse time:", totalTime)
+ print("Total source lines:", numlines)
+ print("Average lines/sec:", ( "%.1f" % (float(numlines)/(totalTime+.05 ) ) ))
+ if failCount:
+ print("FAIL - %d files failed to parse" % failCount)
+ else:
+ print("SUCCESS - all files parsed")
+
+ return 0
+
+ #~ from line_profiler import LineProfiler
+ #~ from pyparsing import ParseResults
+ #~ lp = LineProfiler(ParseResults.__init__)
+
+ main()
+
+ #~ lp.print_stats()
+ #~ import hotshot
+ #~ p = hotshot.Profile("vparse.prof",1,1)
+ #~ p.start()
+ #~ main()
+ #~ p.stop()
+ #~ p.close()
diff --git a/trunk/src/examples/withAttribute.py b/trunk/src/examples/withAttribute.py
new file mode 100644
index 0000000..062c9ae
--- /dev/null
+++ b/trunk/src/examples/withAttribute.py
@@ -0,0 +1,24 @@
+#
+# withAttribute.py
+# Copyright, 2007 - Paul McGuire
+#
+# Simple example of using withAttribute parse action helper
+# to define
+#
+data = """\
+ <td align=right width=80><font size=2 face="New Times Roman,Times,Serif">&nbsp;49.950&nbsp;</font></td>
+ <td align=left width=80><font size=2 face="New Times Roman,Times,Serif">&nbsp;50.950&nbsp;</font></td>
+ <td align=right width=80><font size=2 face="New Times Roman,Times,Serif">&nbsp;51.950&nbsp;</font></td>
+ """
+
+from pyparsing import *
+
+tdS,tdE = makeHTMLTags("TD")
+fontS,fontE = makeHTMLTags("FONT")
+realNum = Combine( Word(nums) + "." + Word(nums) ).setParseAction(lambda t:float(t[0]))
+NBSP = Literal("&nbsp;")
+patt = tdS + fontS + NBSP + realNum("value") + NBSP + fontE + tdE
+
+tdS.setParseAction( withAttribute(align="right",width="80") )
+for s in patt.searchString(data):
+ print(s.value)
diff --git a/trunk/src/examples/wordsToNum.py b/trunk/src/examples/wordsToNum.py
new file mode 100644
index 0000000..60c7c3d
--- /dev/null
+++ b/trunk/src/examples/wordsToNum.py
@@ -0,0 +1,98 @@
+# wordsToNum.py
+# Copyright 2006, Paul McGuire
+#
+# Sample parser grammar to read a number given in words, and return the numeric value.
+#
+from pyparsing import *
+from operator import mul
+from functools import reduce
+
+def makeLit(s,val):
+ ret = CaselessLiteral(s).setName(s)
+ return ret.setParseAction( replaceWith(val) )
+
+unitDefinitions = [
+ ("zero", 0),
+ ("oh", 0),
+ ("zip", 0),
+ ("zilch", 0),
+ ("nada", 0),
+ ("bupkis", 0),
+ ("one", 1),
+ ("two", 2),
+ ("three", 3),
+ ("four", 4),
+ ("five", 5),
+ ("six", 6),
+ ("seven", 7),
+ ("eight", 8),
+ ("nine", 9),
+ ("ten", 10),
+ ("eleven", 11),
+ ("twelve", 12),
+ ("thirteen", 13),
+ ("fourteen", 14),
+ ("fifteen", 15),
+ ("sixteen", 16),
+ ("seventeen", 17),
+ ("eighteen", 18),
+ ("nineteen", 19),
+ ]
+units = Or(makeLit(s,v) for s,v in unitDefinitions)
+
+tensDefinitions = [
+ ("ten", 10),
+ ("twenty", 20),
+ ("thirty", 30),
+ ("forty", 40),
+ ("fourty", 40), # for the spelling-challenged...
+ ("fifty", 50),
+ ("sixty", 60),
+ ("seventy", 70),
+ ("eighty", 80),
+ ("ninety", 90),
+ ]
+tens = Or(makeLit(s,v) for s,v in tensDefinitions)
+
+hundreds = makeLit("hundred", 100)
+
+majorDefinitions = [
+ ("thousand", int(1e3)),
+ ("million", int(1e6)),
+ ("billion", int(1e9)),
+ ("trillion", int(1e12)),
+ ("quadrillion", int(1e15)),
+ ("quintillion", int(1e18)),
+ ]
+mag = Or(makeLit(s,v) for s,v in majorDefinitions)
+
+wordprod = lambda t: reduce(mul,t)
+wordsum = lambda t: sum(t)
+numPart = (((( units + Optional(hundreds) ).setParseAction(wordprod) +
+ Optional(tens)).setParseAction(wordsum)
+ ^ tens )
+ + Optional(units) ).setParseAction(wordsum)
+numWords = OneOrMore( (numPart + Optional(mag)).setParseAction(wordprod)
+ ).setParseAction(wordsum) + StringEnd()
+numWords.ignore(Literal("-"))
+numWords.ignore(CaselessLiteral("and"))
+
+def test(s,expected):
+ print ("Expecting %s" % expected)
+ numWords.runTests(s)
+
+test("one hundred twenty hundred", None)
+test("one hundred and twennty", None)
+test("one hundred and twenty", 120)
+test("one hundred and three", 103)
+test("one hundred twenty-three", 123)
+test("one hundred and twenty three", 123)
+test("one hundred twenty three million", 123000000)
+test("one hundred and twenty three million", 123000000)
+test("one hundred twenty three million and three", 123000003)
+test("fifteen hundred and sixty five", 1565)
+test("seventy-seven thousand eight hundred and nineteen", 77819)
+test("seven hundred seventy-seven thousand seven hundred and seventy-seven", 777777)
+test("zero", 0)
+test("forty two", 42)
+test("fourty two", 42) \ No newline at end of file
diff --git a/trunk/src/genEpydoc.bat b/trunk/src/genEpydoc.bat
new file mode 100644
index 0000000..988954b
--- /dev/null
+++ b/trunk/src/genEpydoc.bat
@@ -0,0 +1,2 @@
+xcopy /y ..\sourceforge\svn\trunk\src\pyparsing.py .
+c:\python27\python c:\python27\scripts\epydoc -v --name pyparsing -o htmldoc --inheritance listed --no-private pyparsing.py
diff --git a/trunk/src/makeRelease.bat b/trunk/src/makeRelease.bat
new file mode 100644
index 0000000..692e572
--- /dev/null
+++ b/trunk/src/makeRelease.bat
@@ -0,0 +1,24 @@
+copy ..\sourceforge\svn\trunk\src\CHANGES .
+copy ..\sourceforge\svn\trunk\src\setup.py .
+copy ..\sourceforge\svn\trunk\src\pyparsing.py .
+copy ..\sourceforge\svn\trunk\src\MANIFEST.in_bdist .
+copy ..\sourceforge\svn\trunk\src\MANIFEST.in_src .
+copy ..\sourceforge\svn\trunk\src\examples\* .\examples\
+
+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
+
+python setup.py bdist_wheel
+python setup.py bdist_wininst --target-version=2.6 --plat-name=win32
+python setup.py bdist_wininst --target-version=2.7 --plat-name=win32
+python setup.py bdist_wininst --target-version=3.3 --plat-name=win32
+python setup.py bdist_wininst --target-version=3.4 --plat-name=win32
+python setup.py bdist_wininst --target-version=3.5 --plat-name=win32
+
diff --git a/trunk/src/pyparsing.py b/trunk/src/pyparsing.py
new file mode 100644
index 0000000..6a3f582
--- /dev/null
+++ b/trunk/src/pyparsing.py
@@ -0,0 +1,5528 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2015 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.
+#
+
+__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 L{I{ParserElement.parseString}<ParserElement.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__ = "2.1.6"
+__versionTime__ = "07 Aug 2016 04:42 UTC"
+__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
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+ from _thread import RLock
+except ImportError:
+ from threading import RLock
+
+try:
+ from collections import OrderedDict as _OrderedDict
+except ImportError:
+ try:
+ from ordereddict import OrderedDict as _OrderedDict
+ except ImportError:
+ _OrderedDict = None
+
+#~ 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',
+'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', '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', 'infixNotation','locatedExpr', 'withClass',
+'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+ _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]
+
+else:
+ _MAX_INT = sys.maxint
+ range = xrange
+
+ 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:
+ # Else encode it
+ ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+ xmlcharref = Regex('&#\d+;')
+ xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+ return xmlcharref.transformString(ret)
+
+ # 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
+
+_generatorType = type((y for y in range(1)))
+
+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_uppercase + string.ascii_lowercase
+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
+ self.args = (pstr, loc, msg)
+
+ 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 "lineno col line".split() + dir(type(self))
+
+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
+
+ Example::
+ try:
+ Word(nums).setName("integer").parseString("ABC")
+ except ParseException as pe:
+ print(pe)
+ print("column: {}".format(pe.col))
+
+ prints::
+ Expected integer (at char 0), (line:1, col:1)
+ column: 1
+ """
+ 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>} - see L{ParserElement.setResultsName})
+
+ Example::
+ integer = Word(nums)
+ date_str = (integer.setResultsName("year") + '/'
+ + integer.setResultsName("month") + '/'
+ + integer.setResultsName("day"))
+ # equivalent form:
+ # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parseString("1999/12/31")
+ print(list(result))
+ print(result[0])
+ print(result['month'])
+ print(result.day)
+ print('month' in result)
+ print('minutes' in result)
+ print(result.dump())
+ prints::
+ ['1999', '/', '12', '/', '31']
+ 1999
+ 12
+ 31
+ True
+ False
+ ['1999', '/', '12', '/', '31']
+ - day: 31
+ - month: 12
+ - year: 1999
+ """
+ def __new__(cls, toklist=None, 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=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+ if self.__doinit:
+ self.__doinit = False
+ self.__name = None
+ self.__parent = None
+ self.__accumNames = {}
+ self.__asList = asList
+ self.__modal = modal
+ if toklist is None:
+ toklist = []
+ if isinstance(toklist, list):
+ self.__toklist = toklist[:]
+ elif isinstance(toklist, _generatorType):
+ self.__toklist = list(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 (isinstance(toklist, (type(None), basestring, list)) and 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,slice)):
+ 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,occurrences in self.__tokdict.items():
+ 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 ( not not self.__toklist )
+ __nonzero__ = __bool__
+ def __iter__( self ): return iter( self.__toklist )
+ def __reversed__( self ): return iter( self.__toklist[::-1] )
+ def _iterkeys( self ):
+ if hasattr(self.__tokdict, "iterkeys"):
+ return self.__tokdict.iterkeys()
+ else:
+ return iter(self.__tokdict)
+
+ def _itervalues( self ):
+ return (self[k] for k in self._iterkeys())
+
+ def _iteritems( self ):
+ return ((k, self[k]) for k in self._iterkeys())
+
+ if PY_3:
+ keys = _iterkeys
+ """Returns an iterator of all named result keys (Python 3.x only)."""
+
+ values = _itervalues
+ """Returns an iterator of all named result values (Python 3.x only)."""
+
+ items = _iteritems
+ """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+ else:
+ iterkeys = _iterkeys
+ """Returns an iterator of all named result keys (Python 2.x only)."""
+
+ itervalues = _itervalues
+ """Returns an iterator of all named result values (Python 2.x only)."""
+
+ iteritems = _iteritems
+ """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+ def keys( self ):
+ """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+ return list(self.iterkeys())
+
+ def values( self ):
+ """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+ return list(self.itervalues())
+
+ def items( self ):
+ """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+ return list(self.iteritems())
+
+ def haskeys( self ):
+ """Since keys() returns an iterator, this method is helpful in bypassing
+ code that looks for the existence of any defined results names."""
+ return bool(self.__tokdict)
+
+ def pop( self, *args, **kwargs):
+ """
+ Removes and returns item at specified index (default=C{last}).
+ Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+ argument or an integer argument, it will use C{list} semantics
+ and pop tokens from the list of parsed tokens. If passed a
+ non-integer argument (most likely a string), it will use C{dict}
+ semantics and pop the corresponding value from any defined
+ results names. A second default return value argument is
+ supported, just as in C{dict.pop()}.
+
+ Example::
+ def remove_first(tokens):
+ tokens.pop(0)
+ print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+ print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+ label = Word(alphas)
+ patt = label("LABEL") + OneOrMore(Word(nums))
+ print(patt.parseString("AAB 123 321").dump())
+
+ # Use pop() in a parse action to remove named result (note that corresponding value is not
+ # removed from list form of results)
+ def remove_LABEL(tokens):
+ tokens.pop("LABEL")
+ return tokens
+ patt.addParseAction(remove_LABEL)
+ print(patt.parseString("AAB 123 321").dump())
+ prints::
+ ['AAB', '123', '321']
+ - LABEL: AAB
+
+ ['AAB', '123', '321']
+ """
+ if not args:
+ args = [-1]
+ for k,v in kwargs.items():
+ if k == 'default':
+ args = (args[0], v)
+ else:
+ raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+ if (isinstance(args[0], int) or
+ len(args) == 1 or
+ args[0] in self):
+ index = args[0]
+ ret = self[index]
+ del self[index]
+ return ret
+ else:
+ defaultvalue = args[1]
+ return defaultvalue
+
+ 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.
+
+ Similar to C{dict.get()}.
+
+ Example::
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parseString("1999/12/31")
+ print(result.get("year")) # -> '1999'
+ print(result.get("hour", "not specified")) # -> 'not specified'
+ print(result.get("hour")) # -> None
+ """
+ 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.
+
+ Similar to C{list.insert()}.
+
+ Example::
+ print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+ # use a parse action to insert the parse location in the front of the parsed results
+ def insert_locn(locn, tokens):
+ tokens.insert(0, locn)
+ print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+ """
+ self.__toklist.insert(index, insStr)
+ # fixup indices in token dictionary
+ for name,occurrences in self.__tokdict.items():
+ for k, (value, position) in enumerate(occurrences):
+ occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+ def append( self, item ):
+ """
+ Add single element to end of ParseResults list of elements.
+
+ Example::
+ print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+ # use a parse action to compute the sum of the parsed integers, and add it to the end
+ def append_sum(tokens):
+ tokens.append(sum(map(int, tokens)))
+ print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+ """
+ self.__toklist.append(item)
+
+ def extend( self, itemseq ):
+ """
+ Add sequence of elements to end of ParseResults list of elements.
+
+ Example::
+ patt = OneOrMore(Word(alphas))
+
+ # use a parse action to append the reverse of the matched strings, to make a palindrome
+ def make_palindrome(tokens):
+ tokens.extend(reversed([t[::-1] for t in tokens]))
+ return ''.join(tokens)
+ print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+ """
+ if isinstance(itemseq, ParseResults):
+ self += itemseq
+ else:
+ self.__toklist.extend(itemseq)
+
+ def clear( self ):
+ """
+ Clear all elements and results names.
+ """
+ del self.__toklist[:]
+ self.__tokdict.clear()
+
+ def __getattr__( self, name ):
+ try:
+ return self[name]
+ except KeyError:
+ return ""
+
+ 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 ""
+
+ 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: offset if a<0 else 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:
+ # useful for merging many ParseResults using sum() builtin
+ return self.copy()
+ else:
+ # this may raise a TypeError - so be it
+ return other + self
+
+ def __repr__( self ):
+ return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+ def __str__( self ):
+ return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+ 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.
+
+ Example::
+ patt = OneOrMore(Word(alphas))
+ result = patt.parseString("sldkj lsdkj sldkj")
+ # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+ print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
+
+ # Use asList() to create an actual list
+ result_list = result.asList()
+ print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj']
+ """
+ return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+ def asDict( self ):
+ """
+ Returns the named parse results as a nested dictionary.
+
+ Example::
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parseString('12/31/1999')
+ print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+
+ result_dict = result.asDict()
+ print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'}
+
+ # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+ import json
+ print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+ print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+ """
+ if PY_3:
+ item_fn = self.items
+ else:
+ item_fn = self.iteritems
+
+ def toItem(obj):
+ if isinstance(obj, ParseResults):
+ if obj.haskeys():
+ return obj.asDict()
+ else:
+ return [toItem(v) for v in obj]
+ else:
+ return obj
+
+ return dict((k,toItem(v)) for k,v in item_fn())
+
+ 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 ):
+ """
+ (Deprecated) 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, ">" ]
+
+ for i,res in enumerate(self.__toklist):
+ 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. Useful when several
+ different expressions might match at a particular location.
+
+ Example::
+ integer = Word(nums)
+ ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+ house_number_expr = Suppress('#') + Word(nums, alphanums)
+ user_data = (Group(house_number_expr)("house_number")
+ | Group(ssn_expr)("ssn")
+ | Group(integer)("age"))
+ user_info = OneOrMore(user_data)
+
+ result = user_info.parseString("22 111-22-3333 #221B")
+ for item in result:
+ print(item.getName(), ':', item[0])
+ prints::
+ age : 22
+ ssn : 111-22-3333
+ house_number : 221B
+ """
+ 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, full=True):
+ """
+ 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.
+
+ Example::
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parseString('12/31/1999')
+ print(result.dump())
+ prints::
+ ['12', '/', '31', '/', '1999']
+ - day: 1999
+ - month: 31
+ - year: 12
+ """
+ out = []
+ NL = '\n'
+ out.append( indent+_ustr(self.asList()) )
+ if full:
+ if self.haskeys():
+ items = sorted(self.items())
+ for k,v in items:
+ if out:
+ out.append(NL)
+ out.append( "%s%s- %s: " % (indent,(' '*depth), k) )
+ if isinstance(v,ParseResults):
+ if v:
+ out.append( v.dump(indent,depth+1) )
+ else:
+ out.append(_ustr(v))
+ else:
+ out.append(_ustr(v))
+ elif any(isinstance(vv,ParseResults) for vv in self):
+ v = self
+ for i,vv in enumerate(v):
+ if isinstance(vv,ParseResults):
+ out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) ))
+ else:
+ out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv)))
+
+ return "".join(out)
+
+ def pprint(self, *args, **kwargs):
+ """
+ Pretty-printer for parsed results as a list, using the C{pprint} module.
+ Accepts additional positional or keyword args as defined for the
+ C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+ Example::
+ ident = Word(alphas, alphanums)
+ num = Word(nums)
+ func = Forward()
+ term = ident | num | Group('(' + func + ')')
+ func <<= ident + Group(Optional(delimitedList(term)))
+ result = func.parseString("fna a,b,(fnb c,d,200),100")
+ result.pprint(width=40)
+ prints::
+ ['fna',
+ ['a',
+ 'b',
+ ['(', 'fnb', ['c', 'd', '200'], ')'],
+ '100']]
+ """
+ pprint.pprint(self.asList(), *args, **kwargs)
+
+ # 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 __getnewargs__(self):
+ return self.__toklist, self.__name, self.__asList, self.__modal
+
+ def __dir__(self):
+ return (dir(type(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.
+ """
+ s = strg
+ return 1 if loc<len(s) and s[loc] == '\n' else loc - s.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
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+ #~ if func in singleArgBuiltins:
+ #~ return lambda s,l,t: func(t)
+ #~ limit = 0
+ #~ foundArity = False
+ #~ def wrapper(*args):
+ #~ nonlocal limit,foundArity
+ #~ while 1:
+ #~ try:
+ #~ ret = func(*args[limit:])
+ #~ foundArity = True
+ #~ return ret
+ #~ except TypeError:
+ #~ if limit == maxargs or foundArity:
+ #~ raise
+ #~ limit += 1
+ #~ continue
+ #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'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]
+ foundArity = [False]
+
+ # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+ if system_version[:2] >= (3,5):
+ def extract_stack():
+ # special handling for Python 3.5.0 - extra deep call stack by 1
+ offset = -3 if system_version == (3,5,0) else -2
+ frame_summary = traceback.extract_stack()[offset]
+ return [(frame_summary.filename, frame_summary.lineno)]
+ def extract_tb(tb):
+ frames = traceback.extract_tb(tb)
+ frame_summary = frames[-1]
+ return [(frame_summary.filename, frame_summary.lineno)]
+ else:
+ extract_stack = traceback.extract_stack
+ extract_tb = traceback.extract_tb
+
+ # synthesize what would be returned by traceback.extract_stack at the call to
+ # user's parse action 'func', so that we don't incur call penalty at parse time
+
+ LINE_DIFF = 6
+ # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND
+ # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+ this_line = extract_stack()[-1]
+ pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+ def wrapper(*args):
+ while 1:
+ try:
+ ret = func(*args[limit[0]:])
+ foundArity[0] = True
+ return ret
+ except TypeError:
+ # re-raise TypeErrors if they did not come from our arity testing
+ if foundArity[0]:
+ raise
+ else:
+ try:
+ tb = sys.exc_info()[-1]
+ if not extract_tb(tb)[-1][:2] == pa_call_line_synth:
+ raise
+ finally:
+ del tb
+
+ if limit[0] <= maxargs:
+ limit[0] += 1
+ continue
+ raise
+
+ # copy func name to wrapper for sensible debug output
+ func_name = "<parse action>"
+ try:
+ func_name = getattr(func, '__name__',
+ getattr(func, '__class__').__name__)
+ except Exception:
+ func_name = str(func)
+ wrapper.__name__ = func_name
+
+ return wrapper
+
+class ParserElement(object):
+ """Abstract base level parser element class."""
+ DEFAULT_WHITE_CHARS = " \n\t\r"
+ verbose_stacktrace = False
+
+ @staticmethod
+ def setDefaultWhitespaceChars( chars ):
+ r"""
+ Overrides the default whitespace chars
+
+ Example::
+ # default whitespace chars are space, <TAB> and newline
+ OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
+
+ # change to just treat newline as significant
+ ParserElement.setDefaultWhitespaceChars(" \t")
+ OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def']
+ """
+ ParserElement.DEFAULT_WHITE_CHARS = chars
+
+ @staticmethod
+ def inlineLiteralsUsing(cls):
+ """
+ Set class to be used for inclusion of string literals into a parser.
+
+ Example::
+ # default literal class used is Literal
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31']
+
+
+ # change to Suppress
+ ParserElement.inlineLiteralsUsing(Suppress)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ date_str.parseString("1999/12/31") # -> ['1999', '12', '31']
+ """
+ ParserElement._literalStringClass = cls
+
+ 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.
+
+ Example::
+ integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+ integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+ integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+
+ print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+ prints::
+ [5120, 100, 655360, 268435456]
+ Equivalent form of C{expr.copy()} is just C{expr()}::
+ integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+ """
+ 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, makes exception messages clearer.
+
+ Example::
+ Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+ Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1)
+ """
+ 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__>}.
+
+ Example::
+ date_str = (integer.setResultsName("year") + '/'
+ + integer.setResultsName("month") + '/'
+ + integer.setResultsName("day"))
+
+ # equivalent form:
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+ """
+ 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.
+
+ Optional keyword arguments:
+ - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+ 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.
+
+ Example::
+ integer = Word(nums)
+ date_str = integer + '/' + integer + '/' + integer
+
+ date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31']
+
+ # use parse action to convert to ints at parse time
+ integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+ date_str = integer + '/' + integer + '/' + integer
+
+ # note that integer fields are now ints, not strings
+ date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31]
+ """
+ self.parseAction = list(map(_trim_arity, list(fns)))
+ self.callDuringTry = kwargs.get("callDuringTry", False)
+ return self
+
+ def addParseAction( self, *fns, **kwargs ):
+ """
+ Add parse action to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}.
+
+ See examples in L{I{copy}<copy>}.
+ """
+ self.parseAction += list(map(_trim_arity, list(fns)))
+ self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+ return self
+
+ def addCondition(self, *fns, **kwargs):
+ """Add a boolean predicate function to expression's list of parse actions. See
+ L{I{setParseAction}<setParseAction>} for function call signatures. Unlike C{setParseAction},
+ functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+ Optional keyword arguments:
+ - message = define a custom message to be used in the raised exception
+ - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+
+ Example::
+ integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+ year_int = integer.copy()
+ year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+ date_str = year_int + '/' + integer + '/' + integer
+
+ result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+ """
+ msg = kwargs.get("message", "failed user-defined condition")
+ exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+ for fn in fns:
+ def pa(s,l,t):
+ if not bool(_trim_arity(fn)(s,l,t)):
+ raise exc_type(s,l,msg)
+ self.parseAction.append(pa)
+ self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+ 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)
+
+ def canParseNext(self, instring, loc):
+ try:
+ self.tryParse(instring, loc)
+ except (ParseException, IndexError):
+ return False
+ else:
+ return True
+
+ class _UnboundedCache(object):
+ def __init__(self):
+ cache = {}
+ self.not_in_cache = not_in_cache = object()
+
+ def get(self, key):
+ return cache.get(key, not_in_cache)
+
+ def set(self, key, value):
+ cache[key] = value
+
+ def clear(self):
+ cache.clear()
+
+ self.get = types.MethodType(get, self)
+ self.set = types.MethodType(set, self)
+ self.clear = types.MethodType(clear, self)
+
+ if _OrderedDict is not None:
+ class _FifoCache(object):
+ def __init__(self, size):
+ self.not_in_cache = not_in_cache = object()
+
+ cache = _OrderedDict()
+
+ def get(self, key):
+ return cache.get(key, not_in_cache)
+
+ def set(self, key, value):
+ cache[key] = value
+ if len(cache) > size:
+ cache.popitem(False)
+
+ def clear(self):
+ cache.clear()
+
+ self.get = types.MethodType(get, self)
+ self.set = types.MethodType(set, self)
+ self.clear = types.MethodType(clear, self)
+
+ else:
+ class _FifoCache(object):
+ def __init__(self, size):
+ self.not_in_cache = not_in_cache = object()
+
+ cache = {}
+ key_fifo = collections.deque([], size)
+
+ def get(self, key):
+ return cache.get(key, not_in_cache)
+
+ def set(self, key, value):
+ cache[key] = value
+ if len(cache) > size:
+ cache.pop(key_fifo.popleft(), None)
+ key_fifo.append(key)
+
+ def clear(self):
+ cache.clear()
+ key_fifo.clear()
+
+ self.get = types.MethodType(get, self)
+ self.set = types.MethodType(set, self)
+ self.clear = types.MethodType(clear, self)
+
+ # argument cache for optimizing repeated calls when backtracking through recursive expressions
+ packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+ packrat_cache_lock = RLock()
+ packrat_cache_stats = [0, 0]
+
+ # 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 ):
+ HIT, MISS = 0, 1
+ lookup = (self, instring, loc, callPreParse, doActions)
+ with ParserElement.packrat_cache_lock:
+ cache = ParserElement.packrat_cache
+ value = cache.get(lookup)
+ if value is cache.not_in_cache:
+ ParserElement.packrat_cache_stats[MISS] += 1
+ try:
+ value = self._parseNoCache(instring, loc, doActions, callPreParse)
+ except ParseBaseException as pe:
+ # cache a copy of the exception, without the traceback
+ cache.set(lookup, pe.__class__(*pe.args))
+ raise
+ else:
+ cache.set(lookup, (value[0], value[1].copy()))
+ return value
+ else:
+ ParserElement.packrat_cache_stats[HIT] += 1
+ if isinstance(value, Exception):
+ raise value
+ return (value[0], value[1].copy())
+
+ _parse = _parseNoCache
+
+ @staticmethod
+ def resetCache():
+ ParserElement.packrat_cache.clear()
+ ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+ _packratEnabled = False
+ @staticmethod
+ def enablePackrat(cache_size_limit=128):
+ """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.
+
+ Parameters:
+ - cache_size_limit - (default=C{128}) - if an integer value is provided
+ will limit the size of the packrat cache; if None is passed, then
+ the cache size will be unbounded; if 0 is passed, the cache will
+ be effectively disabled.
+
+ 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.
+
+ Example::
+ import pyparsing
+ pyparsing.ParserElement.enablePackrat()
+ """
+ if not ParserElement._packratEnabled:
+ ParserElement._packratEnabled = True
+ if cache_size_limit is None:
+ ParserElement.packrat_cache = ParserElement._UnboundedCache()
+ else:
+ ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+ ParserElement._parse = ParserElement._parseCache
+
+ 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}
+
+ Example::
+ Word('a').parseString('aaaaabaaa') # -> ['aaaaa']
+ Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text
+ """
+ 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.
+
+ Example::
+ source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+ print(source)
+ for tokens,start,end in Word(alphas).scanString(source):
+ print(' '*start + '^'*(end-start))
+ print(' '*start + tokens[0])
+
+ prints::
+
+ sldjf123lsdjjkf345sldkjf879lkjsfd987
+ ^^^^^
+ sldjf
+ ^^^^^^^
+ lsdjjkf
+ ^^^^^^
+ sldkjf
+ ^^^^^^
+ lkjsfd
+ """
+ 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.
+
+ Example::
+ wd = Word(alphas)
+ wd.setParseAction(lambda toks: toks[0].title())
+
+ print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+ Prints::
+ Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+ """
+ 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.
+
+ Example::
+ # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+ cap_word = Word(alphas.upper(), alphas.lower())
+
+ print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+ prints::
+ ['More', 'Iron', 'Lead', 'Gold', 'I']
+ """
+ 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 split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+ """
+ Generator method to split a string using the given expression as a separator.
+ May be called with optional C{maxsplit} argument, to limit the number of splits;
+ and the optional C{includeSeparators} argument (default=C{False}), if the separating
+ matching text should be included in the split results.
+
+ Example::
+ punc = oneOf(list(".,;:/-!?"))
+ print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+ prints::
+ ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+ """
+ splits = 0
+ last = 0
+ for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+ yield instring[last:s]
+ if includeSeparators:
+ yield t[0]
+ last = e
+ yield instring[last:]
+
+ 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=None):
+ """
+ Shortcut for C{L{setResultsName}}, with C{listAllMatches=default}.
+
+ If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+ passed as C{True}.
+
+ If C{name} is omitted, same as calling C{L{copy}}.
+
+ Example::
+ # these are equivalent
+ userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+ userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")
+ """
+ if name is not None:
+ return self.setResultsName(name)
+ else:
+ return self.copy()
+
+ 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.
+
+ Example::
+ patt = OneOrMore(Word(alphas))
+ patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+
+ patt.ignore(cStyleComment)
+ patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+ """
+ if isinstance(other, basestring):
+ other = Suppress(other)
+
+ if isinstance( other, Suppress ):
+ if other not in self.ignoreExprs:
+ self.ignoreExprs.append(other)
+ 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:
+ with open(file_or_filename, "r") as f:
+ file_contents = f.read()
+ 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 vars(self) == vars(other)
+ elif isinstance(other, basestring):
+ return self.matches(other)
+ 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)
+
+ def matches(self, testString, parseAll=True):
+ """
+ Method for quick testing of a parser against a test string. Good for simple
+ inline microtests of sub expressions while building up larger parser.0
+
+ Parameters:
+ - testString - to test against this expression for a match
+ - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+
+ Example::
+ expr = Word(nums)
+ assert expr.matches("100")
+ """
+ try:
+ self.parseString(_ustr(testString), parseAll=parseAll)
+ return True
+ except ParseBaseException:
+ return False
+
+ def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+ """
+ Execute the parse expression on a series of test strings, showing each
+ test, the parsed results or where the parse failed. Quick and easy way to
+ run a parse expression against a list of sample strings.
+
+ Parameters:
+ - tests - a list of separate test strings, or a multiline string of test strings
+ - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+ - comment - (default=C{'#'}) - expression for indicating embedded comments in the test
+ string; pass None to disable comment filtering
+ - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+ if False, only dump nested list
+ - printResults - (default=C{True}) prints test output to stdout
+ - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+ Returns: a (success, results) tuple, where success indicates that all tests succeeded
+ (or failed if C{failureTests} is True), and the results contain a list of lines of each
+ test's output
+
+ Example::
+ number_expr = pyparsing_common.number.copy()
+
+ result = number_expr.runTests('''
+ # unsigned integer
+ 100
+ # negative integer
+ -100
+ # float with scientific notation
+ 6.02e23
+ # integer with scientific notation
+ 1e-12
+ ''')
+ print("Success" if result[0] else "Failed!")
+
+ result = number_expr.runTests('''
+ # stray character
+ 100Z
+ # missing leading digit before '.'
+ -.100
+ # too many '.'
+ 3.14.159
+ ''', failureTests=True)
+ print("Success" if result[0] else "Failed!")
+ prints::
+ # unsigned integer
+ 100
+ [100]
+
+ # negative integer
+ -100
+ [-100]
+
+ # float with scientific notation
+ 6.02e23
+ [6.02e+23]
+
+ # integer with scientific notation
+ 1e-12
+ [1e-12]
+
+ Success
+
+ # stray character
+ 100Z
+ ^
+ FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+ # missing leading digit before '.'
+ -.100
+ ^
+ FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+ # too many '.'
+ 3.14.159
+ ^
+ FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+ Success
+ """
+ if isinstance(tests, basestring):
+ tests = list(map(str.strip, tests.rstrip().splitlines()))
+ if isinstance(comment, basestring):
+ comment = Literal(comment)
+ allResults = []
+ comments = []
+ success = True
+ for t in tests:
+ if comment is not None and comment.matches(t, False) or comments and not t:
+ comments.append(t)
+ continue
+ if not t:
+ continue
+ out = ['\n'.join(comments), t]
+ comments = []
+ try:
+ result = self.parseString(t, parseAll=parseAll)
+ out.append(result.dump(full=fullDump))
+ success = success and not failureTests
+ except ParseBaseException as pe:
+ fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+ if '\n' in t:
+ out.append(line(pe.loc, t))
+ out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+ else:
+ out.append(' '*pe.loc + '^' + fatal)
+ out.append("FAIL: " + str(pe))
+ success = success and failureTests
+ result = pe
+
+ if printResults:
+ if fullDump:
+ out.append('')
+ print('\n'.join(out))
+
+ allResults.append((t, result))
+
+ return success, allResults
+
+
+class Token(ParserElement):
+ """
+ Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+ """
+ def __init__( self ):
+ super(Token,self).__init__( savelist=False )
+
+
+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.
+
+ Example::
+ Literal('blah').parseString('blah') # -> ['blah']
+ Literal('blah').parseString('blahfooblah') # -> ['blah']
+ Literal('blah').parseString('bla') # -> Exception: Expected "blah"
+
+ For case-insensitive matching, use L{CaselessLiteral}.
+
+ For keyword matching (force word break before and after the matched string),
+ use L{Keyword} or L{CaselessKeyword}.
+ """
+ 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}}:
+ - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+ - C{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}.
+
+ Example::
+ Keyword("start").parseString("start") # -> ['start']
+ Keyword("start").parseString("starting") # -> Exception
+
+ For case-insensitive matching, use L{CaselessKeyword}.
+ """
+ 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
+
+ @staticmethod
+ def setDefaultKeywordChars( chars ):
+ """Overrides the default Keyword chars
+ """
+ Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+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.
+
+ Example::
+ OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+
+ (Contrast with example for L{CaselessKeyword}.)
+ """
+ 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):
+ """
+ Caseless version of L{Keyword}.
+
+ Example::
+ OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+
+ (Contrast with example for L{CaselessLiteral}.)
+ """
+ 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{excludeChars} 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.
+
+ L{srange} is useful for defining custom character set strings for defining
+ C{Word} expressions, using range notation from regular expression character sets.
+
+ A common mistake is to use C{Word} to match a specific literal string, as in
+ C{Word("Address")}. Remember that C{Word} uses the string argument to define
+ I{sets} of matchable characters. This expression would match "Add", "AAA",
+ "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+ To match an exact literal string, use L{Literal} or L{Keyword}.
+
+ pyparsing includes helper strings for building Words:
+ - L{alphas}
+ - L{nums}
+ - L{alphanums}
+ - L{hexnums}
+ - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+ - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+ - L{printables} (any non-whitespace character)
+
+ Example::
+ # a word composed of digits
+ integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+
+ # a word with a leading capital, and zero or more lowercase
+ capital_word = Word(alphas.upper(), alphas.lower())
+
+ # hostnames are alphanumeric, with leading alpha, and '-'
+ hostname = Word(alphas, alphanums+'-')
+
+ # roman numeral (not a strict parser, accepts invalid mix of characters)
+ roman = Word("IVXLCDM")
+
+ # any string of non-whitespace characters, except for ','
+ csv_value = Word(printables, excludeChars=",")
+ """
+ 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.initCharsOrig) == 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.
+
+ Example::
+ realnum = Regex(r"[+-]?\d+\.\d*")
+ ssn = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+ # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+ roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+ """
+ 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 not pattern:
+ 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):
+ r"""
+ Token for matching strings that are delimited by quoting characters.
+
+ 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=C{None})
+ - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{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)
+ - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+ Example::
+ qs = QuotedString('"')
+ print(qs.searchString('lsjdf "This is the quote" sldjf'))
+ complex_qs = QuotedString('{{', endQuoteChar='}}')
+ print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+ sql_qs = QuotedString('"', escQuote='""')
+ print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+ prints::
+ [['This is the quote']]
+ [['This is the "quote"']]
+ [['This is the quote with "embedded" quotes']]
+ """
+ def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+ super(QuotedString,self).__init__()
+
+ # remove white space from quote chars - wont work anyway
+ quoteChar = quoteChar.strip()
+ if not quoteChar:
+ warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+ raise SyntaxError()
+
+ if endQuoteChar is None:
+ endQuoteChar = quoteChar
+ else:
+ endQuoteChar = endQuoteChar.strip()
+ if not endQuoteChar:
+ 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
+ self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+ 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.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 whitespace
+ if '\\' in ret and self.convertWhitespaceEscapes:
+ ws_map = {
+ r'\t' : '\t',
+ r'\n' : '\n',
+ r'\f' : '\f',
+ r'\r' : '\r',
+ }
+ for wslit,wschar in ws_map.items():
+ ret = ret.replace(wslit, wschar)
+
+ # 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 (will
+ include whitespace in matched characters if not listed in the provided exclusion set - see example).
+ 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.
+
+ Example::
+ # define a comma-separated-value as anything that is not a ','
+ csv_value = CharsNotIn(',')
+ print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+ prints::
+ ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+ """
+ 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, _generatorType ):
+ exprs = list(exprs)
+
+ if isinstance( exprs, basestring ):
+ self.exprs = [ ParserElement._literalStringClass( exprs ) ]
+ elif isinstance( exprs, collections.Sequence ):
+ # if sequence of strings provided, wrap with Literal
+ if all(isinstance(expr, basestring) for expr in exprs):
+ exprs = map(ParserElement._literalStringClass, exprs)
+ self.exprs = list(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
+
+ self.errmsg = "Expected " + _ustr(self)
+
+ 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.
+ May also be constructed using the C{'-'} operator, which will suppress backtracking.
+
+ Example::
+ integer = Word(nums)
+ name_expr = OneOrMore(Word(alphas))
+
+ expr = And([integer("id"),name_expr("name"),integer("age")])
+ # more easily written as:
+ expr = integer("id") + name_expr("name") + integer("age")
+ """
+
+ class _ErrorStop(Empty):
+ def __init__(self, *args, **kwargs):
+ super(And._ErrorStop,self).__init__(*args, **kwargs)
+ self.name = '-'
+ self.leaveWhitespace()
+
+ def __init__( self, exprs, savelist = True ):
+ super(And,self).__init__(exprs, savelist)
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ self.setWhitespaceChars( self.exprs[0].whiteChars )
+ self.skipWhitespace = self.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.haskeys():
+ resultlist += exprtokens
+ return loc, resultlist
+
+ def __iadd__(self, other ):
+ if isinstance( other, basestring ):
+ other = ParserElement._literalStringClass( 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.
+
+ Example::
+ # construct Or using '^' operator
+
+ number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums))
+ print(number.searchString("123 3.1416 789"))
+ prints::
+ [['123'], ['3.1416'], ['789']]
+ """
+ def __init__( self, exprs, savelist = False ):
+ super(Or,self).__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ else:
+ self.mayReturnEmpty = True
+
+ def parseImpl( self, instring, loc, doActions=True ):
+ maxExcLoc = -1
+ maxException = None
+ matches = []
+ 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:
+ # save match among all matches, to retry longest to shortest
+ matches.append((loc2, e))
+
+ if matches:
+ matches.sort(key=lambda x: -x[0])
+ for _,e in matches:
+ try:
+ return e._parse( instring, loc, doActions )
+ except ParseException as err:
+ err.__traceback__ = None
+ if err.loc > maxExcLoc:
+ maxException = err
+ maxExcLoc = err.loc
+
+ if maxException is not None:
+ maxException.msg = self.errmsg
+ raise maxException
+ else:
+ raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+ 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.
+
+ Example::
+ # construct MatchFirst using '|' operator
+
+ # watch the order of expressions to match
+ number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+ print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+ # put more selective expression first
+ number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+ print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
+ """
+ def __init__( self, exprs, savelist = False ):
+ super(MatchFirst,self).__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ 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:
+ maxException.msg = self.errmsg
+ 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.
+
+ Example::
+ color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+ shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+ integer = Word(nums)
+ shape_attr = "shape:" + shape_type("shape")
+ posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+ color_attr = "color:" + color("color")
+ size_attr = "size:" + integer("size")
+
+ # use Each (using operator '&') to accept attributes in any order
+ # (shape and posn are required, color and size are optional)
+ shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+ shape_spec.runTests('''
+ shape: SQUARE color: BLACK posn: 100, 120
+ shape: CIRCLE size: 50 color: BLUE posn: 50,80
+ color:GREEN size:20 shape:TRIANGLE posn:20,40
+ '''
+ )
+ prints::
+ shape: SQUARE color: BLACK posn: 100, 120
+ ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+ - color: BLACK
+ - posn: ['100', ',', '120']
+ - x: 100
+ - y: 120
+ - shape: SQUARE
+
+
+ shape: CIRCLE size: 50 color: BLUE posn: 50,80
+ ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+ - color: BLUE
+ - posn: ['50', ',', '80']
+ - x: 50
+ - y: 80
+ - shape: CIRCLE
+ - size: 50
+
+
+ color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+ ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+ - color: GREEN
+ - posn: ['20', ',', '40']
+ - x: 20
+ - y: 40
+ - shape: TRIANGLE
+ - size: 20
+ """
+ def __init__( self, exprs, savelist = True ):
+ super(Each,self).__init__(exprs, savelist)
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ self.skipWhitespace = True
+ self.initExprGroups = True
+
+ def parseImpl( self, instring, loc, doActions=True ):
+ if self.initExprGroups:
+ self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+ opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+ opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+ 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(self.opt1map.get(id(e),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:
+ 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 = ParserElement._literalStringClass(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.
+
+ Example::
+ # use FollowedBy to match a label only if it is followed by a ':'
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+
+ OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+ prints::
+ [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+ """
+ 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.
+
+ Example::
+
+ """
+ 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 ):
+ if self.expr.canParseNext(instring, loc):
+ 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 OneOrMore(ParseElementEnhance):
+ """
+ Repetition of one or more of the given expression.
+
+ Parameters:
+ - expr - expression that must match one or more times
+ - stopOn - (default=C{None}) - expression for a terminating sentinel
+ (only required if the sentinel would ordinarily match the repetition
+ expression)
+
+ Example::
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+ text = "shape: SQUARE posn: upper left color: BLACK"
+ OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+ # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+ OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+
+ # could also be written as
+ (attr_expr * (1,)).parseString(text).pprint()
+ """
+ def __init__( self, expr, stopOn=None):
+ super(OneOrMore, self).__init__(expr)
+ ender = stopOn
+ if isinstance(ender, basestring):
+ ender = ParserElement._literalStringClass(ender)
+ self.not_ender = ~ender if ender is not None else None
+
+ def parseImpl( self, instring, loc, doActions=True ):
+ self_expr_parse = self.expr._parse
+ self_skip_ignorables = self._skipIgnorables
+ check_ender = self.not_ender is not None
+ if check_ender:
+ try_not_ender = self.not_ender.tryParse
+
+ # must be at least one (but first see if we are the stopOn sentinel;
+ # if so, fail)
+ if check_ender:
+ try_not_ender(instring, loc)
+ loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+ try:
+ hasIgnoreExprs = (not not self.ignoreExprs)
+ while 1:
+ if check_ender:
+ try_not_ender(instring, loc)
+ if hasIgnoreExprs:
+ preloc = self_skip_ignorables( instring, loc )
+ else:
+ preloc = loc
+ loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+ if tmptokens or tmptokens.haskeys():
+ 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 ZeroOrMore(OneOrMore):
+ """
+ Optional repetition of zero or more of the given expression.
+
+ Parameters:
+ - expr - expression that must match zero or more times
+ - stopOn - (default=C{None}) - expression for a terminating sentinel
+ (only required if the sentinel would ordinarily match the repetition
+ expression)
+
+ Example: similar to L{OneOrMore}
+ """
+ def __init__( self, expr, stopOn=None):
+ super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+ self.mayReturnEmpty = True
+
+ def parseImpl( self, instring, loc, doActions=True ):
+ try:
+ return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+ except (ParseException,IndexError):
+ 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 _NullToken(object):
+ def __bool__(self):
+ return False
+ __nonzero__ = __bool__
+ def __str__(self):
+ return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+ """
+ Optional matching of the given expression.
+
+ Parameters:
+ - expr - expression that must match zero or more times
+ - default (optional) - value to be returned if the optional expression is not found.
+
+ Example::
+ # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+ zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+ zip.runTests('''
+ # traditional ZIP code
+ 12345
+
+ # ZIP+4 form
+ 12101-0001
+
+ # invalid ZIP
+ 98765-
+ ''')
+ prints::
+ # traditional ZIP code
+ 12345
+ ['12345']
+
+ # ZIP+4 form
+ 12101-0001
+ ['12101-0001']
+
+ # invalid ZIP
+ 98765-
+ ^
+ FAIL: Expected end of text (at char 5), (line:1, col:6)
+ """
+ def __init__( self, expr, default=_optionalNotMatched ):
+ super(Optional,self).__init__( expr, 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.
+
+ Parameters:
+ - expr - target expression marking the end of the data to be skipped
+ - include - (default=C{False}) if True, the target expression is also parsed
+ (the skipped text and target expression are returned as a 2-element list).
+ - ignore - (default=C{None}) used to define grammars (typically quoted strings and
+ comments) that might contain false matches to the target expression
+ - failOn - (default=C{None}) define expressions that are not allowed to be
+ included in the skipped test; if found before the target expression is found,
+ the SkipTo is not a match
+
+ Example::
+ report = '''
+ Outstanding Issues Report - 1 Jan 2000
+
+ # | Severity | Description | Days Open
+ -----+----------+-------------------------------------------+-----------
+ 101 | Critical | Intermittent system crash | 6
+ 94 | Cosmetic | Spelling error on Login ('log|n') | 14
+ 79 | Minor | System slow when running too many reports | 47
+ '''
+ integer = Word(nums)
+ SEP = Suppress('|')
+ # use SkipTo to simply match everything up until the next SEP
+ # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+ # - parse action will call token.strip() for each matched token, i.e., the description body
+ string_data = SkipTo(SEP, ignore=quotedString)
+ string_data.setParseAction(tokenMap(str.strip))
+ ticket_expr = (integer("issue_num") + SEP
+ + string_data("sev") + SEP
+ + string_data("desc") + SEP
+ + integer("days_open"))
+
+ for tkt in ticket_expr.searchString(report):
+ print tkt.dump()
+ prints::
+ ['101', 'Critical', 'Intermittent system crash', '6']
+ - days_open: 6
+ - desc: Intermittent system crash
+ - issue_num: 101
+ - sev: Critical
+ ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+ - days_open: 14
+ - desc: Spelling error on Login ('log|n')
+ - issue_num: 94
+ - sev: Cosmetic
+ ['79', 'Minor', 'System slow when running too many reports', '47']
+ - days_open: 47
+ - desc: System slow when running too many reports
+ - issue_num: 79
+ - sev: Minor
+ """
+ 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 isinstance(failOn, basestring):
+ self.failOn = ParserElement._literalStringClass(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
+ expr_parse = self.expr._parse
+ self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+ self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+
+ tmploc = loc
+ while tmploc <= instrlen:
+ if self_failOn_canParseNext is not None:
+ # break if failOn expression matches
+ if self_failOn_canParseNext(instring, tmploc):
+ break
+
+ if self_ignoreExpr_tryParse is not None:
+ # advance past ignore expressions
+ while 1:
+ try:
+ tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+ except ParseBaseException:
+ break
+
+ try:
+ expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+ except (ParseException, IndexError):
+ # no match, advance loc in string
+ tmploc += 1
+ else:
+ # matched skipto expr, done
+ break
+
+ else:
+ # ran off the end of the input string without matching skipto expr, fail
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ # build up return values
+ loc = tmploc
+ skiptext = instring[startloc:loc]
+ skipresult = ParseResults(skiptext)
+
+ if self.includeMatch:
+ loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+ skipresult += mat
+
+ return loc, skipresult
+
+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.
+
+ See L{ParseResults.pprint} for an example of a recursive parser created using
+ C{Forward}.
+ """
+ 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.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 self
+
+ def __ilshift__(self, other):
+ return self << other
+
+ 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
+ return self.__class__.__name__ + ": ..."
+
+ # stubbed out for now - creates awful memory and perf issues
+ 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 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.
+
+ Example::
+ real = Word(nums) + '.' + Word(nums)
+ print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+ # will also erroneously match the following
+ print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+ real = Combine(Word(nums) + '.' + Word(nums))
+ print(real.parseString('3.1416')) # -> ['3.1416']
+ # no match when there are internal spaces
+ print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+ """
+ 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 retToks.haskeys():
+ 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.
+
+ Example::
+ ident = Word(alphas)
+ num = Word(nums)
+ term = ident | num
+ func = ident + Optional(delimitedList(term))
+ print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100']
+
+ func = ident + Group(Optional(delimitedList(term)))
+ print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']]
+ """
+ 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.
+
+ Example::
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+ text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+ attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+
+ # print attributes as plain groups
+ print(OneOrMore(attr_expr).parseString(text).dump())
+
+ # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+ result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+ print(result.dump())
+
+ # access named fields as dict entries, or output as dict
+ print(result['shape'])
+ print(result.asDict())
+ prints::
+ ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+ [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+ - color: light blue
+ - posn: upper left
+ - shape: SQUARE
+ - texture: burlap
+ SQUARE
+ {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+ See more examples at L{ParseResults} of accessing fields by results name.
+ """
+ def __init__( self, expr ):
+ super(Dict,self).__init__( expr )
+ 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.haskeys()):
+ 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.
+
+ Example::
+ source = "a, b, c,d"
+ wd = Word(alphas)
+ wd_list1 = wd + ZeroOrMore(',' + wd)
+ print(wd_list1.parseString(source))
+
+ # often, delimiters that are useful during parsing are just in the
+ # way afterward - use Suppress to keep them out of the parsed output
+ wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+ print(wd_list2.parseString(source))
+ prints::
+ ['a', ',', 'b', ',', 'c', ',', 'd']
+ ['a', 'b', 'c', 'd']
+ (See also L{delimitedList}.)
+ """
+ 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.
+
+ Example::
+ wd = Word(alphas)
+
+ @traceParseAction
+ def remove_duplicate_chars(tokens):
+ return ''.join(sorted(set(''.join(tokens)))
+
+ wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+ print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+ prints::
+ >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+ <<leaving remove_duplicate_chars (ret: 'dfjkls')
+ ['dfjkls']
+ """
+ f = _trim_arity(f)
+ def z(*paArgs):
+ thisFunc = f.__name__
+ s,l,t = paArgs[-3:]
+ if len(paArgs)>3:
+ thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+ sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\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: %r)\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.
+
+ Example::
+ delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc']
+ delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+ """
+ 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.
+
+ Example::
+ countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd']
+ """
+ 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 ).setName('(len) ' + _ustr(expr) + '...')
+
+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)
+ rep.setName('(prev) ' + _ustr(expr))
+ 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)
+ rep.setName('(prev) ' + _ustr(expr))
+ 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=C{False}) - treat all literals as caseless
+ - useRegex - (default=C{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)
+
+ Example::
+ comp_oper = oneOf("< = > <= >= !=")
+ var = Word(alphas)
+ number = Word(nums)
+ term = var | number
+ comparison_expr = term + comp_oper + term
+ print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12"))
+ prints::
+ [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+ """
+ 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
+
+ symbols = []
+ if isinstance(strs,basestring):
+ symbols = strs.split()
+ elif isinstance(strs, collections.Sequence):
+ symbols = list(strs[:])
+ elif isinstance(strs, _generatorType):
+ symbols = list(strs)
+ else:
+ warnings.warn("Invalid argument to oneOf, expected string or list",
+ SyntaxWarning, stacklevel=2)
+ if not symbols:
+ return NoMatch()
+
+ 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) ).setName(' | '.join(symbols))
+ else:
+ return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(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).setName(' | '.join(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.
+
+ Example::
+ text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+ attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+ print(OneOrMore(attr_expr).parseString(text).dump())
+
+ attr_label = label
+ attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+ # similar to Dict, but simpler call format
+ result = dictOf(attr_label, attr_value).parseString(text)
+ print(result.dump())
+ print(result['shape'])
+ print(result.shape) # object attribute access works too
+ print(result.asDict())
+ prints::
+ [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+ - color: light blue
+ - posn: upper left
+ - shape: SQUARE
+ - texture: burlap
+ SQUARE
+ SQUARE
+ {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+ """
+ 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. By default, returns astring 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.
+
+ Example::
+ src = "this is test <b> bold <i>text</i> </b> normal text "
+ for tag in ("b","i"):
+ opener,closer = makeHTMLTags(tag)
+ patt = originalTextFor(opener + SkipTo(closer) + closer)
+ print(patt.searchString(src)[0])
+ prints::
+ ['<b> bold <i>text</i> </b>']
+ ['<i>text</i>']
+ """
+ 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):
+ t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+ matchExpr.setParseAction(extractText)
+ matchExpr.ignoreExprs = expr.ignoreExprs
+ 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])
+
+def locatedExpr(expr):
+ """
+ Helper to decorate a returned token with its starting and ending locations in the input string.
+ This helper adds the following results names:
+ - locn_start = location where matched expression begins
+ - locn_end = location where matched expression ends
+ - value = the actual parsed results
+
+ Be careful if the input text contains C{<TAB>} characters, you may want to call
+ C{L{ParserElement.parseWithTabs}}
+
+ Example::
+ wd = Word(alphas)
+ for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+ print(match)
+ prints::
+ [[0, 'ljsdf', 5]]
+ [[8, 'lksdjjf', 15]]
+ [[18, 'lkkjj', 23]]
+ """
+ locator = Empty().setParseAction(lambda s,l,t: l)
+ return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# 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])
+_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, excludeChars=r'\]', exact=1) | Regex(r"\w", re.UNICODE)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+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 C{\-} or C{\]})
+ - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character)
+ (C{\0x##} is also supported for backwards compatibility)
+ - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+ - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+ - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+ """
+ _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+ 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>}()}.
+
+ Example::
+ num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+ na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+ term = na | num
+
+ OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+ """
+ return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+ """
+ Helper parse action for removing quotation marks from parsed quoted strings.
+
+ Example::
+ # by default, quotation marks are included in parsed results
+ quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+ # use removeQuotes to strip quotation marks from parsed results
+ quotedString.setParseAction(removeQuotes)
+ quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+ """
+ return t[0][1:-1]
+
+def tokenMap(func, *args):
+ """
+ Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional
+ args are passed, they are forwarded to the given function as additional arguments after
+ the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+ parsed data to an integer using base 16.
+
+ Example (compare the last to example in L{ParserElement.transformString}::
+ hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+ hex_ints.runTests('''
+ 00 11 22 aa FF 0a 0d 1a
+ ''')
+
+ upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+ OneOrMore(upperword).runTests('''
+ my kingdom for a horse
+ ''')
+
+ wd = Word(alphas).setParseAction(tokenMap(str.title))
+ OneOrMore(wd).setParseAction(' '.join).runTests('''
+ now is the winter of our discontent made glorious summer by this sun of york
+ ''')
+ prints::
+ 00 11 22 aa FF 0a 0d 1a
+ [0, 17, 34, 170, 255, 10, 13, 26]
+
+ my kingdom for a horse
+ ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+ now is the winter of our discontent made glorious summer by this sun of york
+ ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+ """
+ def pa(s,l,t):
+ return [func(tokn, *args) for tokn in t]
+
+ try:
+ func_name = getattr(func, '__name__',
+ getattr(func, '__class__').__name__)
+ except Exception:
+ func_name = str(func)
+ pa.__name__ = func_name
+
+ return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""Helper parse action to convert tokens to upper case."""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""Helper parse action to convert tokens to lower case."""
+
+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>" % resname)
+ closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname)
+ 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. Matches
+ tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+ Example::
+ text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>'
+ # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+ a,a_end = makeHTMLTags("A")
+ link_expr = a + SkipTo(a_end)("link_text") + a_end
+
+ for link in link_expr.searchString(text):
+ # attributes in the <A> tag (like "href" shown here) are also accessible as named results
+ print(link.link_text, '->', link.href)
+ prints::
+ pyparsing -> http://pyparsing.wikispaces.com
+ """
+ return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+ """
+ Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+ tags only in the given upper/lower case.
+
+ Example: similar to L{makeHTMLTags}
+ """
+ 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.
+
+ If just testing for C{class} (with or without a namespace), use C{L{withClass}}.
+
+ To verify that the attribute exists, but without specifying a value, pass
+ C{withAttribute.ANY_VALUE} as the value.
+
+ Example::
+ html = '''
+ <div>
+ Some text
+ <div type="grid">1 4 0 1 0</div>
+ <div type="graph">1,3 2,3 1,1</div>
+ <div>this has no type</div>
+ </div>
+
+ '''
+ div,div_end = makeHTMLTags("div")
+
+ # only match div tag having a type attribute with value "grid"
+ div_grid = div().setParseAction(withAttribute(type="grid"))
+ grid_expr = div_grid + SkipTo(div | div_end)("body")
+ for grid_header in grid_expr.searchString(html):
+ print(grid_header.body)
+
+ # construct a match with any div tag having a type attribute, regardless of the value
+ div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE))
+ div_expr = div_any_type + SkipTo(div | div_end)("body")
+ for div_header in div_expr.searchString(html):
+ print(div_header.body)
+ prints::
+ 1 4 0 1 0
+
+ 1 4 0 1 0
+ 1,3 2,3 1,1
+ """
+ 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()
+
+def withClass(classname, namespace=''):
+ """
+ Simplified version of C{L{withAttribute}} when matching on a div class - made
+ difficult because C{class} is a reserved word in Python.
+
+ Example::
+ html = '''
+ <div>
+ Some text
+ <div class="grid">1 4 0 1 0</div>
+ <div class="graph">1,3 2,3 1,1</div>
+ <div>this &lt;div&gt; has no class</div>
+ </div>
+
+ '''
+ div,div_end = makeHTMLTags("div")
+ div_grid = div().setParseAction(withClass("grid"))
+
+ grid_expr = div_grid + SkipTo(div | div_end)("body")
+ for grid_header in grid_expr.searchString(html):
+ print(grid_header.body)
+
+ div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE))
+ div_expr = div_any_type + SkipTo(div | div_end)("body")
+ for div_header in div_expr.searchString(html):
+ print(div_header.body)
+ prints::
+ 1 4 0 1 0
+
+ 1 4 0 1 0
+ 1,3 2,3 1,1
+ """
+ classattr = "%s:class" % namespace if namespace else "class"
+ return withAttribute(**{classattr : classname})
+
+opAssoc = _Constants()
+opAssoc.LEFT = object()
+opAssoc.RIGHT = object()
+
+def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ):
+ """
+ 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)
+ - lpar - expression for matching left-parentheses (default=C{Suppress('(')})
+ - rpar - expression for matching right-parentheses (default=C{Suppress(')')})
+
+ Example::
+ # simple example of four-function arithmetic with ints and variable names
+ integer = pyparsing_common.signedInteger
+ varname = pyparsing_common.identifier
+
+ arith_expr = infixNotation(integer | varname,
+ [
+ ('-', 1, opAssoc.RIGHT),
+ (oneOf('* /'), 2, opAssoc.LEFT),
+ (oneOf('+ -'), 2, opAssoc.LEFT),
+ ])
+
+ arith_expr.runTests('''
+ 5+3*6
+ (5+3)*6
+ -2--11
+ ''', fullDump=False)
+ prints::
+ 5+3*6
+ [[5, '+', [3, '*', 6]]]
+
+ (5+3)*6
+ [[[5, '+', 3], '*', 6]]
+
+ -2--11
+ [[['-', 2], '-', ['-', 11]]]
+ """
+ ret = Forward()
+ lastExpr = baseExpr | ( lpar + ret + rpar )
+ for i,operDef in enumerate(opList):
+ opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4]
+ termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr
+ 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(termName)
+ 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.setName(termName) | lastExpr )
+ lastExpr = thisExpr
+ ret <<= lastExpr
+ return ret
+
+operatorPrecedence = infixNotation
+"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release."""
+
+dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes")
+sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes")
+quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'|
+ Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes")
+unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal")
+
+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=C{"("}); can also be a pyparsing expression
+ - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression
+ - content - expression for items within the nested lists (default=C{None})
+ - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{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.
+
+ Example::
+ data_type = oneOf("void int short long char float double")
+ decl_data_type = Combine(data_type + Optional(Word('*')))
+ ident = Word(alphas+'_', alphanums+'_')
+ number = pyparsing_common.number
+ arg = Group(decl_data_type + ident)
+ LPAR,RPAR = map(Suppress, "()")
+
+ code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment))
+
+ c_function = (decl_data_type("type")
+ + ident("name")
+ + LPAR + Optional(delimitedList(arg), [])("args") + RPAR
+ + code_body("body"))
+ c_function.ignore(cStyleComment)
+
+ source_code = '''
+ int is_odd(int x) {
+ return (x%2);
+ }
+
+ int dec_to_hex(char hchar) {
+ if (hchar >= '0' && hchar <= '9') {
+ return (ord(hchar)-ord('0'));
+ } else {
+ return (10+ord(hchar)-ord('A'));
+ }
+ }
+ '''
+ for func in c_function.searchString(source_code):
+ print("%(name)s (%(type)s) args: %(args)s" % func)
+
+ prints::
+ is_odd (int) args: [['int', 'x']]
+ dec_to_hex (int) args: [['char', 'hchar']]
+ """
+ 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) )
+ ret.setName('nested %s%s expression' % (opener,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=C{True})
+
+ A valid block must contain at least one C{blockStatement}.
+
+ Example::
+ data = '''
+ def A(z):
+ A1
+ B = 100
+ G = A2
+ A2
+ A3
+ B
+ def BB(a,b,c):
+ BB1
+ def BBA():
+ bba1
+ bba2
+ bba3
+ C
+ D
+ def spam(x,y):
+ def eggs(z):
+ pass
+ '''
+
+
+ indentStack = [1]
+ stmt = Forward()
+
+ identifier = Word(alphas, alphanums)
+ funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":")
+ func_body = indentedBlock(stmt, indentStack)
+ funcDef = Group( funcDecl + func_body )
+
+ rvalue = Forward()
+ funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")")
+ rvalue << (funcCall | identifier | Word(nums))
+ assignment = Group(identifier + "=" + rvalue)
+ stmt << ( funcDef | assignment | identifier )
+
+ module_body = OneOrMore(stmt)
+
+ parseTree = module_body.parseString(data)
+ parseTree.pprint()
+ prints::
+ [['def',
+ 'A',
+ ['(', 'z', ')'],
+ ':',
+ [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]],
+ 'B',
+ ['def',
+ 'BB',
+ ['(', 'a', 'b', 'c', ')'],
+ ':',
+ [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]],
+ 'C',
+ 'D',
+ ['def',
+ 'spam',
+ ['(', 'x', 'y', ')'],
+ ':',
+ [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]]
+ """
+ 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)).setName('INDENT')
+ PEER = Empty().setParseAction(checkPeerIndent).setName('')
+ UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT')
+ 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.setName('indented block')
+
+alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]")
+punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
+
+anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag'))
+_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\''))
+commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity")
+def replaceHTMLEntity(t):
+ """Helper parser action to replace common HTML entities with their special characters"""
+ return _htmlEntityMap.get(t.entity)
+
+# it's easy to get these comment structures wrong - they're very common, so may as well make them available
+cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment")
+"Comment of the form C{/* ... */}"
+
+htmlComment = Regex(r"<!--[\s\S]*?-->").setName("HTML comment")
+"Comment of the form C{<!-- ... -->}"
+
+restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line")
+dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment")
+"Comment of the form C{// ... (to end of line)}"
+
+cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment")
+"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}"
+
+javaStyleComment = cppStyleComment
+"Same as C{L{cppStyleComment}}"
+
+pythonStyleComment = Regex(r"#.*").setName("Python style comment")
+"Comment of the form C{# ... (to end of line)}"
+
+_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') +
+ Optional( Word(" \t") +
+ ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem")
+commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList")
+"""Predefined expression of 1 or more printable words or quoted strings, separated by commas."""
+
+# some other useful expressions - using lower-case class name since we are really using this as a namespace
+class pyparsing_common:
+ """
+ Here are some common low-level expressions that may be useful in jump-starting parser development:
+ - numeric forms (L{integers<integer>}, L{reals<real>}, L{scientific notation<sciReal>})
+ - common L{programming identifiers<identifier>}
+ - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>}, L{IPv6<ipv6_address>})
+ - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>}
+ - L{UUID<uuid>}
+ Parse actions:
+ - C{L{convertToInteger}}
+ - C{L{convertToFloat}}
+ - C{L{convertToDate}}
+ - C{L{convertToDatetime}}
+ - C{L{stripHTMLTags}}
+
+ Example::
+ pyparsing_common.number.runTests('''
+ # any int or real number, returned as the appropriate type
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ ''')
+
+ pyparsing_common.fnumber.runTests('''
+ # any int or real number, returned as float
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ ''')
+
+ pyparsing_common.hex_integer.runTests('''
+ # hex numbers
+ 100
+ FF
+ ''')
+
+ pyparsing_common.fraction.runTests('''
+ # fractions
+ 1/2
+ -3/4
+ ''')
+
+ pyparsing_common.mixed_integer.runTests('''
+ # mixed fractions
+ 1
+ 1/2
+ -3/4
+ 1-3/4
+ ''')
+
+ import uuid
+ pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
+ pyparsing_common.uuid.runTests('''
+ # uuid
+ 12345678-1234-5678-1234-567812345678
+ ''')
+ prints::
+ # any int or real number, returned as the appropriate type
+ 100
+ [100]
+
+ -100
+ [-100]
+
+ +100
+ [100]
+
+ 3.14159
+ [3.14159]
+
+ 6.02e23
+ [6.02e+23]
+
+ 1e-12
+ [1e-12]
+
+ # any int or real number, returned as float
+ 100
+ [100.0]
+
+ -100
+ [-100.0]
+
+ +100
+ [100.0]
+
+ 3.14159
+ [3.14159]
+
+ 6.02e23
+ [6.02e+23]
+
+ 1e-12
+ [1e-12]
+
+ # hex numbers
+ 100
+ [256]
+
+ FF
+ [255]
+
+ # fractions
+ 1/2
+ [0.5]
+
+ -3/4
+ [-0.75]
+
+ # mixed fractions
+ 1
+ [1]
+
+ 1/2
+ [0.5]
+
+ -3/4
+ [-0.75]
+
+ 1-3/4
+ [1.75]
+
+ # uuid
+ 12345678-1234-5678-1234-567812345678
+ [UUID('12345678-1234-5678-1234-567812345678')]
+ """
+
+ convertToInteger = tokenMap(int)
+ """
+ Parse action for converting parsed integers to Python int
+ """
+
+ convertToFloat = tokenMap(float)
+ """
+ Parse action for converting parsed numbers to Python float
+ """
+
+ integer = Word(nums).setName("integer").setParseAction(convertToInteger)
+ """expression that parses an unsigned integer, returns an int"""
+
+ hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16))
+ """expression that parses a hexadecimal integer, returns an int"""
+
+ signedInteger = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger)
+ """expression that parses an integer with optional leading sign, returns an int"""
+
+ fraction = (signedInteger().setParseAction(convertToFloat) + '/' + signedInteger().setParseAction(convertToFloat)).setName("fraction")
+ """fractional expression of an integer divided by an integer, returns a float"""
+ fraction.addParseAction(lambda t: t[0]/t[-1])
+
+ mixed_integer = (fraction | signedInteger + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction")
+ """mixed integer of the form 'integer - fraction', with optional leading integer, returns float"""
+ mixed_integer.addParseAction(sum)
+
+ real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat)
+ """expression that parses a floating point number and returns a float"""
+
+ sciReal = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat)
+ """expression that parses a floating point number with optional scientific notation and returns a float"""
+
+ # streamlining this expression makes the docs nicer-looking
+ number = (sciReal | real | signedInteger).streamline()
+ """any numeric expression, returns the corresponding Python type"""
+
+ fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat)
+ """any int or real number, returned as float"""
+
+ identifier = Word(alphas+'_', alphanums+'_').setName("identifier")
+ """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')"""
+
+ ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address")
+ "IPv4 address (C{0.0.0.0 - 255.255.255.255})"
+
+ _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer")
+ _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address")
+ _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address")
+ _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8)
+ _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address")
+ ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address")
+ "IPv6 address (long, short, or mixed form)"
+
+ mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address")
+ "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)"
+
+ @staticmethod
+ def convertToDate(fmt="%Y-%m-%d"):
+ """
+ Helper to create a parse action for converting parsed date string to Python datetime.date
+
+ Params -
+ - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"})
+
+ Example::
+ date_expr = pyparsing_common.iso8601_date.copy()
+ date_expr.setParseAction(pyparsing_common.convertToDate())
+ print(date_expr.parseString("1999-12-31"))
+ prints::
+ [datetime.date(1999, 12, 31)]
+ """
+ return lambda s,l,t: datetime.strptime(t[0], fmt).date()
+
+ @staticmethod
+ def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"):
+ """
+ Helper to create a parse action for converting parsed datetime string to Python datetime.datetime
+
+ Params -
+ - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"})
+
+ Example::
+ dt_expr = pyparsing_common.iso8601_datetime.copy()
+ dt_expr.setParseAction(pyparsing_common.convertToDatetime())
+ print(dt_expr.parseString("1999-12-31T23:59:59.999"))
+ prints::
+ [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)]
+ """
+ return lambda s,l,t: datetime.strptime(t[0], fmt)
+
+ iso8601_date = Regex(r'(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601 date")
+ "ISO8601 date (C{yyyy-mm-dd})"
+
+ iso8601_datetime = Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime")
+ "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}"
+
+ uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID")
+ "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})"
+
+ _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress()
+ @staticmethod
+ def stripHTMLTags(s, l, tokens):
+ """
+ Parse action to remove HTML tags from web page HTML source
+
+ Example::
+ # strip HTML links from normal text
+ text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>'
+ td,td_end = makeHTMLTags("TD")
+ table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end
+
+ print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page'
+ """
+ return pyparsing_common._html_stripper.transformString(tokens[0])
+
+if __name__ == "__main__":
+
+ selectToken = CaselessLiteral("select")
+ fromToken = CaselessLiteral("from")
+
+ ident = Word(alphas, alphanums + "_$")
+
+ columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens)
+ columnNameList = Group(delimitedList(columnName)).setName("columns")
+ columnSpec = ('*' | columnNameList)
+
+ tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens)
+ tableNameList = Group(delimitedList(tableName)).setName("tables")
+
+ simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables")
+
+ # demo runTests method, including embedded comments in test string
+ simpleSQL.runTests("""
+ # '*' as column list and dotted table name
+ select * from SYS.XYZZY
+
+ # caseless match on "SELECT", and casts back to "select"
+ SELECT * from XYZZY, ABC
+
+ # list of column names, and mixed case SELECT keyword
+ Select AA,BB,CC from Sys.dual
+
+ # multiple tables
+ Select A, B, C from Sys.dual, Table2
+
+ # invalid SELECT keyword - should fail
+ Xelect A, B, C from Sys.dual
+
+ # incomplete command - should fail
+ Select
+
+ # invalid column name - should fail
+ Select ^^^ frox Sys.dual
+
+ """)
+
+ pyparsing_common.number.runTests("""
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ """)
+
+ # any int or real number, returned as float
+ pyparsing_common.fnumber.runTests("""
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ """)
+
+ pyparsing_common.hex_integer.runTests("""
+ 100
+ FF
+ """)
+
+ import uuid
+ pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
+ pyparsing_common.uuid.runTests("""
+ 12345678-1234-5678-1234-567812345678
+ """)
diff --git a/trunk/src/pyparsingClassDiagram.JPG b/trunk/src/pyparsingClassDiagram.JPG
new file mode 100644
index 0000000..ef10424
--- /dev/null
+++ b/trunk/src/pyparsingClassDiagram.JPG
Binary files differ
diff --git a/trunk/src/pyparsingClassDiagram.PNG b/trunk/src/pyparsingClassDiagram.PNG
new file mode 100644
index 0000000..f59baaf
--- /dev/null
+++ b/trunk/src/pyparsingClassDiagram.PNG
Binary files differ
diff --git a/trunk/src/setup.cfg b/trunk/src/setup.cfg
new file mode 100644
index 0000000..981e089
--- /dev/null
+++ b/trunk/src/setup.cfg
@@ -0,0 +1,5 @@
+[bdist_wheel]
+universal = 1
+
+[wheel]
+universal = 1
diff --git a/trunk/src/setup.py b/trunk/src/setup.py
new file mode 100644
index 0000000..82061c6
--- /dev/null
+++ b/trunk/src/setup.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+"""Setup script for the pyparsing module distribution."""
+from setuptools import setup
+
+import sys
+import os
+
+from pyparsing import __version__ as pyparsing_version
+
+modules = ["pyparsing",]
+
+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 :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ ]
+ )
diff --git a/trunk/src/test/__init__.py b/trunk/src/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trunk/src/test/__init__.py
diff --git a/trunk/src/test/jsonParserTests.py b/trunk/src/test/jsonParserTests.py
new file mode 100644
index 0000000..c8a42bb
--- /dev/null
+++ b/trunk/src/test/jsonParserTests.py
@@ -0,0 +1,360 @@
+# jsonParser.py
+#
+# Copyright (c) 2006, Paul McGuire
+#
+
+test1 = """
+{
+ "glossary": {
+ "title": "example glossary",
+ "GlossDiv": {
+ "title": "S",
+ "GlossList": [{
+ "ID": "SGML",
+ "SortAs": "SGML",
+ "GlossTerm": "Standard Generalized Markup Language",
+ "Acronym": "SGML",
+ "LargestPrimeLessThan100": 97,
+ "AvogadroNumber": 6.02E23,
+ "EvenPrimesGreaterThan2": null,
+ "PrimesLessThan10" : [2,3,5,7],
+ "WMDsFound" : false,
+ "IraqAlQaedaConnections" : null,
+ "Abbrev": "ISO 8879:1986",
+ "GlossDef":
+"A meta-markup language, used to create markup languages such as DocBook.",
+ "GlossSeeAlso": ["GML", "XML", "markup"],
+ "EmptyDict" : {},
+ "EmptyList" : []
+ }]
+ }
+ }
+}
+"""
+
+test2 = """
+{"menu": {
+ "id": "file",
+ "value": "File:",
+ "popup": {
+ "menuitem": [
+ {"value": "New", "onclick": "CreateNewDoc()"},
+ {"value": "Open", "onclick": "OpenDoc()"},
+ {"value": "Close", "onclick": "CloseDoc()"}
+ ]
+ }
+}}
+"""
+test3 = """
+{"widget": {
+ "debug": "on",
+ "window": {
+ "title": "Sample Konfabulator Widget", "name": "main_window", "width": 500, "height": 500
+ }, "image": {
+ "src": "Images/Sun.png",
+ "name": "sun1", "hOffset": 250, "vOffset": 250, "alignment": "center"
+ }, "text": {
+ "data": "Click Here",
+ "size": 36,
+ "style": "bold", "name": "text1", "hOffset": 250, "vOffset": 100, "alignment": "center",
+ "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
+ }
+}}
+"""
+test4 = """
+{"web-app": {
+ "servlet": [ // Defines the CDSServlet
+ {
+ "servlet-name": "cofaxCDS",
+ "servlet-class": "org.cofax.cds.CDSServlet",
+/*
+ Defines glossary variables that template designers
+ can use across the site. You can add new
+ variables to this set by creating a new init-param, with
+ the param-name prefixed with "configGlossary:".
+*/
+ "init-param": {
+ "configGlossary:installationAt": "Philadelphia, PA",
+ "configGlossary:adminEmail": "ksm@pobox.com",
+ "configGlossary:poweredBy": "Cofax",
+ "configGlossary:poweredByIcon": "/images/cofax.gif",
+ "configGlossary:staticPath": "/content/static",
+/*
+ Defines the template loader and template processor
+ classes. These are implementations of org.cofax.TemplateProcessor
+ and org.cofax.TemplateLoader respectively. Simply create new
+ implementation of these classes and set them here if the default
+ implementations do not suit your needs. Leave these alone
+ for the defaults.
+*/
+ "templateProcessorClass": "org.cofax.WysiwygTemplate",
+ "templateLoaderClass": "org.cofax.FilesTemplateLoader",
+ "templatePath": "templates",
+ "templateOverridePath": "",
+/*
+ Defines the names of the default templates to look for
+ when acquiring WYSIWYG templates. Leave these at their
+ defaults for most usage.
+*/
+ "defaultListTemplate": "listTemplate.htm",
+ "defaultFileTemplate": "articleTemplate.htm",
+/*
+ New! useJSP switches on JSP template processing.
+ jspListTemplate and jspFileTemplate are the names
+ of the default templates to look for when aquiring JSP
+ templates. Cofax currently in production at KR has useJSP
+ set to false, since our sites currently use WYSIWYG
+ templating exclusively.
+*/
+ "useJSP": false,
+ "jspListTemplate": "listTemplate.jsp",
+ "jspFileTemplate": "articleTemplate.jsp",
+/*
+ Defines the packageTag cache. This cache keeps
+ Cofax from needing to interact with the database
+ to look up packageTag commands.
+*/
+ "cachePackageTagsTrack": 200,
+ "cachePackageTagsStore": 200,
+ "cachePackageTagsRefresh": 60,
+/*
+ Defines the template cache. Keeps Cofax from needing
+ to go to the file system to load a raw template from
+ the file system.
+*/
+ "cacheTemplatesTrack": 100,
+ "cacheTemplatesStore": 50,
+ "cacheTemplatesRefresh": 15,
+/*
+ Defines the page cache. Keeps Cofax from processing
+ templates to deliver to users.
+*/
+ "cachePagesTrack": 200,
+ "cachePagesStore": 100,
+ "cachePagesRefresh": 10,
+ "cachePagesDirtyRead": 10,
+/*
+ Defines the templates Cofax will use when
+ being browsed by a search engine identified in
+ searchEngineRobotsDb
+*/
+ "searchEngineListTemplate": "forSearchEnginesList.htm",
+ "searchEngineFileTemplate": "forSearchEngines.htm",
+ "searchEngineRobotsDb": "WEB-INF/robots.db",
+/*
+ New! useDataStore enables/disables the Cofax database pool
+*/
+ "useDataStore": true,
+/*
+ Defines the implementation of org.cofax.DataStore that Cofax
+ will use. If this DataStore class does not suit your needs
+ simply implement a new DataStore class and set here.
+*/
+ "dataStoreClass": "org.cofax.SqlDataStore",
+/*
+ Defines the implementation of org.cofax.Redirection that
+ Cofax will use. If this Redirection class does not suit
+ your needs simply implenet a new Redirection class
+ and set here.
+*/
+ "redirectionClass": "org.cofax.SqlRedirection",
+/*
+ Defines the data store name. Keep this at the default
+*/
+ "dataStoreName": "cofax",
+/*
+ Defines the JDBC driver that Cofax's database pool will use
+*/
+ "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
+/*
+ Defines the JDBC connection URL to connect to the database
+*/
+ "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
+/*
+ Defines the user name to connect to the database
+*/
+ "dataStoreUser": "sa",
+/*
+ Defines the password to connect to the database
+*/
+ "dataStorePassword": "dataStoreTestQuery",
+/*
+ A query that will run to test the validity of the
+ connection in the pool.
+*/
+ "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
+/*
+ A log file to print out database information
+*/
+ "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
+/*
+ The number of connection to initialize on startup
+*/
+ "dataStoreInitConns": 10,
+/*
+ The maximum number of connection to use in the pool
+*/
+ "dataStoreMaxConns": 100,
+/*
+ The number of times a connection will be utilized from the
+ pool before disconnect
+*/
+ "dataStoreConnUsageLimit": 100,
+/*
+ The level of information to print to the log
+*/
+ "dataStoreLogLevel": "debug",
+/*
+ The maximum URL length allowable by the CDS Servlet
+ Helps to prevent hacking
+*/
+ "maxUrlLength": 500}},
+/*
+ Defines the Email Servlet
+*/
+ {
+ "servlet-name": "cofaxEmail",
+ "servlet-class": "org.cofax.cds.EmailServlet",
+ "init-param": {
+/*
+ The mail host to be used by the mail servlet
+*/
+ "mailHost": "mail1",
+/*
+ An override
+*/
+ "mailHostOverride": "mail2"}},
+/*
+ Defines the Admin Servlet - used to refresh cache on
+ demand and see statistics
+*/
+ {
+ "servlet-name": "cofaxAdmin",
+ "servlet-class": "org.cofax.cds.AdminServlet"},
+/*
+ Defines the File Servlet - used to display files like Apache
+*/
+ {
+ "servlet-name": "fileServlet",
+ "servlet-class": "org.cofax.cds.FileServlet"},
+ {
+ "servlet-name": "cofaxTools",
+ "servlet-class": "org.cofax.cms.CofaxToolsServlet",
+ "init-param": {
+/*
+ Path to the template folder relative to the tools tomcat installation.
+*/
+ "templatePath": "toolstemplates/",
+/*
+ Logging boolean 1 = on, 0 = off
+*/
+ "log": 1,
+/*
+ Location of log. If empty, log will be written System.out
+*/
+ "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
+/*
+ Max size of log in BITS. If size is empty, no limit to log.
+ If size is defined, log will be overwritten upon reaching defined size.
+*/
+ "logMaxSize": "",
+/*
+ DataStore logging boolean 1 = on, 0 = off
+*/
+ "dataLog": 1,
+/*
+ DataStore location of log. If empty, log will be written System.out
+*/
+ "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
+/*
+ Max size of log in BITS. If size is empty, no limit to log.
+ If size is defined, log will be overwritten upon reaching defined size.
+*/
+ "dataLogMaxSize": "",
+/*
+ Http string relative to server root to call for page cache
+ removal to Cofax Servlet.
+*/
+ "removePageCache": "/content/admin/remove?cache=pages&id=",
+/*
+ Http string relative to server root to call for template
+ cache removal to Cofax Servlet.
+*/
+ "removeTemplateCache": "/content/admin/remove?cache=templates&id=",
+/*
+ Location of folder from root of drive that will be used for
+ ftp transfer from beta server or user hard drive to live servers.
+ Note that Edit Article will not function without this variable
+ set correctly. MultiPart request relies upon access to this folder.
+*/
+ "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
+/*
+ Defines whether the Server should look in another path for
+ config files or variables.
+*/
+ "lookInContext": 1,
+/*
+ Number of the ID of the top level administration group in tblPermGroups.
+*/
+ "adminGroupID": 4,
+/*
+ Is the tools app running on the 'beta server'.
+*/
+ "betaServer": true}}],
+ "servlet-mapping": {
+/*
+ URL mapping for the CDS Servlet
+*/
+ "cofaxCDS": "/",
+/*
+ URL mapping for the Email Servlet
+*/
+ "cofaxEmail": "/cofaxutil/aemail/*",
+/*
+ URL mapping for the Admin servlet
+*/
+ "cofaxAdmin": "/admin/*",
+/*
+ URL mapping for the Files servlet
+*/
+ "fileServlet": "/static/*",
+ "cofaxTools": "/tools/*"},
+/*
+ New! The cofax taglib descriptor file
+*/
+ "taglib": {
+ "taglib-uri": "cofax.tld",
+ "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
+
+"""
+
+test5 = """
+{"menu": {
+ "header": "SVG Viewer",
+ "items": [
+ {"id": "Open"},
+ {"id": "OpenNew", "label": "Open New"},
+ null,
+ {"id": "ZoomIn", "label": "Zoom In"},
+ {"id": "ZoomOut", "label": "Zoom Out"},
+ {"id": "OriginalView", "label": "Original View"},
+ null,
+ {"id": "Quality"},
+ {"id": "Pause"},
+ {"id": "Mute"},
+ null,
+ {"id": "Find", "label": "Find..."},
+ {"id": "FindAgain", "label": "Find Again"},
+ {"id": "Copy"},
+ {"id": "CopyAgain", "label": "Copy Again"},
+ {"id": "CopySVG", "label": "Copy SVG"},
+ {"id": "ViewSVG", "label": "View SVG"},
+ {"id": "ViewSource", "label": "View Source"},
+ {"id": "SaveAs", "label": "Save As"},
+ null,
+ {"id": "Help"},
+ {"id": "About", "label": "About Adobe CVG Viewer..."}
+ ]
+}}
+"""
+
diff --git a/trunk/src/test/karthik.ini b/trunk/src/test/karthik.ini
new file mode 100644
index 0000000..0a7f594
--- /dev/null
+++ b/trunk/src/test/karthik.ini
@@ -0,0 +1,14 @@
+[users]
+source_dir = '/home/karthik/Projects/python'
+data_dir = '/home/karthik/Projects/data'
+result_dir = '/home/karthik/Projects/Results'
+param_file = $result_dir/param_file
+res_file = $result_dir/result_file
+comment = 'this is a comment'
+; a line starting with ';' is a comment
+K = 8
+simulate_K = 0
+N = 4000
+mod_scheme = 'QPSK'
+
+Na = K+2 \ No newline at end of file
diff --git a/trunk/src/test/parsefiletest_input_file.txt b/trunk/src/test/parsefiletest_input_file.txt
new file mode 100644
index 0000000..022837a
--- /dev/null
+++ b/trunk/src/test/parsefiletest_input_file.txt
@@ -0,0 +1 @@
+123 456 789 \ No newline at end of file
diff --git a/trunk/src/unitTests.py b/trunk/src/unitTests.py
new file mode 100644
index 0000000..a4c651f
--- /dev/null
+++ b/trunk/src/unitTests.py
@@ -0,0 +1,3169 @@
+# -*- coding: UTF-8 -*-
+from unittest import TestCase, TestSuite, TextTestRunner
+import unittest
+import datetime
+
+from pyparsing import ParseException
+#~ import HTMLTestRunner
+
+import sys
+import pdb
+
+PY_3 = sys.version.startswith('3')
+if PY_3:
+ import builtins
+ print_ = getattr(builtins, "print")
+
+ from io import StringIO
+else:
+ def _print(*args, **kwargs):
+ if 'end' in kwargs:
+ sys.stdout.write(' '.join(map(str,args)) + kwargs['end'])
+ else:
+ sys.stdout.write(' '.join(map(str,args)) + '\n')
+ print_ = _print
+ from cStringIO import StringIO
+
+
+# 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
+
+VERBOSE = False
+#~ VERBOSE = True
+
+# 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
+
+if 0:
+ 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,"...", end=' ')
+ #~ 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 examples.fourFn as 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)
+
+ from math import pi,exp
+ e = exp(1)
+
+ 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", pi*pi/10.0 )
+ test( "PI*PI/10", pi*pi/10.0 )
+ test( "6.02E23 * 8.048", 6.02E23 * 8.048 )
+ test( "e / 3", e/3.0 )
+ test( "sin(PI/2)", 1.0 )
+ test( "trunc(E)", 2.0 )
+ test( "E^PI", e**pi )
+ 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 examples.simpleSQL as 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 as 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):
+ from examples import configParse
+
+ def test(fnam,numToks,resCheckList):
+ print_("Parsing",fnam,"...", end=' ')
+ iniFileLines = "\n".join(open(fnam).read().splitlines())
+ 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_(list(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("test/karthik.ini", 23,
+ [ ("users.K","8"),
+ ("users.mod_scheme","'QPSK'"),
+ ("users.Na", "K+2") ]
+ )
+ test("examples/Setup.ini", 125,
+ [ ("Startup.audioinf", "M3i"),
+ ("Languages.key1", "0x0003"),
+ ("test.foo","bar") ] )
+
+class ParseJSONDataTest(ParseTestCase):
+ def runTest(self):
+ from examples.jsonParser import jsonObject
+ from test.jsonParserTests import test1,test2,test3,test4,test5
+ from test.jsonParserTests 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()
+ result.pprint()
+ 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+"\" ->", end=' ')
+ 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):
+ from examples 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( list(parsers.keys()) ))
+ assert len(list(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):
+ from examples import idlParse
+
+ def test( strng, numToks, errloc=0 ):
+ print_(strng)
+ try:
+ bnf = idlParse.CORBA_IDL_BNF()
+ tokens = bnf.parseString( strng )
+ print_("tokens = ")
+ tokens.pprint()
+ tokens = flatten( tokens.asList() )
+ print_(len(tokens))
+ assert len(tokens) == numToks, "error matching IDL string, %s -> %s" % (strng, str(tokens) )
+ except ParseException as 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,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])
+
+ print_("testing catastrophic RE backtracking in implementation of dblQuotedString")
+ for expr, test_string in [
+ (dblQuotedString, '"' + '\\xff' * 500),
+ (sglQuotedString, "'" + '\\xff' * 500),
+ (quotedString, '"' + '\\xff' * 500),
+ (quotedString, "'" + '\\xff' * 500),
+ (QuotedString('"'), '"' + '\\xff' * 500),
+ (QuotedString("'"), "'" + '\\xff' * 500),
+ ]:
+ expr.parseString(test_string+test_string[0])
+ try:
+ expr.parseString(test_string)
+ except Exception:
+ continue
+
+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&gt;&amp;&lt;</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 == list(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 == list(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", end=' ')
+ 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", end=' ')
+ 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]"),
+ (r"[\x21]"),
+ #(r"[а-ÑÐ-ЯёÐA-Z$_\041α-ω]".decode('utf-8')),
+ (u'[\u0430-\u044f\u0410-\u042f\u0451\u0401ABCDEFGHIJKLMNOPQRSTUVWXYZ$_\041\u03b1-\u03c9]'),
+ )
+ 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",
+ "!",
+ u"абвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑÐБВГДЕЖЗИЙКЛМÐОПРСТУФХЦЧШЩЪЫЬЭЮЯёÐABCDEFGHIJKLMNOPQRSTUVWXYZ$_!αβγδεζηθικλμνξοπÏςστυφχψω",
+ )
+ for test in zip( testCases, expectedResults ):
+ t,exp = test
+ res = pyparsing.srange(t)
+ #print_(t,"->",res)
+ assert res == exp, "srange error, srange(%r)->'%r', expected '%r'" % (t, res, exp)
+
+class SkipToParserTests(ParseTestCase):
+ def runTest(self):
+
+ from pyparsing import Literal, SkipTo, NotAny, cStyleComment, ParseBaseException
+
+ thingToFind = Literal('working')
+ testExpr = SkipTo(Literal(';'), include=True, ignore=cStyleComment) + thingToFind
+
+ def tryToParse (someText, fail_expected=False):
+ try:
+ print_(testExpr.parseString(someText))
+ assert not fail_expected, "expected failure but no exception raised"
+ except Exception as e:
+ print_("Exception %s while parsing string %s" % (e,repr(someText)))
+ assert fail_expected and isinstance(e,ParseBaseException), "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 previously failed, as there is text following the ignore expression, and before the SkipTo expression.
+ tryToParse('some text /* comment with ; in */some other stuff; working')
+
+ # tests for optional failOn argument
+ testExpr = SkipTo(Literal(';'), include=True, ignore=cStyleComment, failOn='other') + thingToFind
+ tryToParse('some text /* comment with ; in */; working')
+ tryToParse('some text /* comment with ; in */some other stuff; working', fail_expected=True)
+
+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])
+ print_(expected)
+ assert quoteExpr.searchString(testString)[0][0] == expected, \
+ "failed to match %s, expected '%s', got '%s'" % \
+ (quoteExpr,expected,quoteExpr.searchString(testString)[0])
+ print_()
+
+ 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 as 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, ParserElement
+
+ if ParserElement._packratEnabled:
+ print_("skipping this test, not compatible with packratting")
+ return
+
+ 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 InfixNotationGrammarTest1(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word,nums,alphas,Literal,oneOf,infixNotation,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 = infixNotation( 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 = [eval(x) for x in 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 infixNotation: got %s, expected %s" % (expr.parseString(t).asList(),e)
+
+class InfixNotationGrammarTest2(ParseTestCase):
+ def runTest(self):
+
+ from pyparsing import infixNotation, 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 __bool__(self):
+ for a in self.args:
+ if isinstance(a,str):
+ v = boolVars[a]
+ else:
+ v = bool(a)
+ if not v:
+ return False
+ return True
+
+ class BoolOr(BoolOperand):
+ reprsymbol = '|'
+ def __bool__(self):
+ for a in self.args:
+ if isinstance(a,str):
+ 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 __bool__(self):
+ if isinstance(self.arg,str):
+ v = boolVars[self.arg]
+ else:
+ v = bool(self.arg)
+ return not v
+
+ boolOperand = Word(alphas,max=1) | oneOf("True False")
+ boolExpr = infixNotation( 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 InfixNotationGrammarTest3(ParseTestCase):
+ def runTest(self):
+
+ from pyparsing import infixNotation, 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 = infixNotation( 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 InfixNotationGrammarTest4(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("!"), 1, pyparsing.opAssoc.RIGHT, lambda s, l, t: ["!", t[0][0]]),
+ (pyparsing.oneOf("= !="), 2, pyparsing.opAssoc.LEFT, ),
+ (supLiteral("&"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["&", t[0]]),
+ (supLiteral("|"), 2, pyparsing.opAssoc.LEFT, lambda s, l, t: ["|", t[0]])]
+ return pyparsing.infixNotation(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 PickleTest_Greeting():
+ def __init__(self, toks):
+ self.salutation = toks[0]
+ self.greetee = toks[1]
+
+ def __repr__(self):
+ return "%s: {%s}" % (self.__class__.__name__,
+ ', '.join('%r: %r' % (k, getattr(self,k)) for k in sorted(self.__dict__)))
+
+class ParseResultsPickleTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import makeHTMLTags, ParseResults
+ import pickle
+
+ # test 1
+ body = makeHTMLTags("BODY")[0]
+ result = body.parseString("<BODY BGCOLOR='#00FFBB' FGCOLOR=black>")
+ if VERBOSE:
+ print_(result.dump())
+ print_()
+
+ for protocol in range(pickle.HIGHEST_PROTOCOL+1):
+ print_("Test pickle dump protocol", protocol)
+ try:
+ pickleString = pickle.dumps(result, protocol)
+ except Exception as e:
+ print_("dumps exception:", e)
+ newresult = ParseResults()
+ else:
+ newresult = pickle.loads(pickleString)
+ if VERBOSE:
+ print_(newresult.dump())
+ print_()
+
+ assert result.dump() == newresult.dump(), "Error pickling ParseResults object (protocol=%d)" % protocol
+
+ # test 2
+ import pyparsing as pp
+
+ word = pp.Word(pp.alphas+"'.")
+ salutation = pp.OneOrMore(word)
+ comma = pp.Literal(",")
+ greetee = pp.OneOrMore(word)
+ endpunc = pp.oneOf("! ?")
+ greeting = salutation + pp.Suppress(comma) + greetee + pp.Suppress(endpunc)
+ greeting.setParseAction(PickleTest_Greeting)
+
+ string = 'Good morning, Miss Crabtree!'
+
+ result = greeting.parseString(string)
+
+ for protocol in range(pickle.HIGHEST_PROTOCOL+1):
+ print_("Test pickle dump protocol", protocol)
+ try:
+ pickleString = pickle.dumps(result, protocol)
+ except Exception as e:
+ print_("dumps exception:", e)
+ newresult = ParseResults()
+ else:
+ newresult = pickle.loads(pickleString)
+ print_(newresult.dump())
+ assert newresult.dump() == result.dump(), "failed to pickle/unpickle ParseResults: expected %r, got %r" % (result, newresult)
+
+
+
+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) = next(resIter)
+
+ 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
+ if PY_3:
+ unichr = chr
+ else:
+ from __builtin__ import unichr
+
+ a = '\u00bfC\u00f3mo esta usted?'
+ if not JYTHON_ENV:
+ ualphas = "".join( unichr(i) for i in range(sys.maxunicode)
+ if unichr(i).isalpha() )
+ else:
+ ualphas = "".join( unichr(i) for i in list(range(0xd800)) + list(range(0xe000,sys.maxunicode))
+ if unichr(i).isalpha() )
+ uword = pp.Word(ualphas).setParseAction(pp.upcaseTokens)
+
+ print_ = lambda *args: None
+ print_(uword.searchString(a))
+
+ uword = pp.Word(ualphas).setParseAction(pp.downcaseTokens)
+
+ print_(uword.searchString(a))
+
+ kw = pp.Keyword('mykey', caseless=True).setParseAction(pp.upcaseTokens).setResultsName('rname')
+ ret = kw.parseString('mykey')
+ print(ret.rname)
+ assert ret.rname=='MYKEY', "failed to upcase with named result"
+
+ 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 = 'Производитель, модель'
+ manufacturer = pp.Literal(text_manuf)
+
+ td_start, td_end = pp.makeHTMLTags("td")
+ manuf_body = td_start.suppress() + manufacturer + pp.SkipTo(td_end)("cells*") + 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_(list(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 as e:
+ print_("successfully rejected an invalid RE:", end=' ')
+ 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 CountedArrayTest3(ParseTestCase):
+ # test case where counter is not a decimal integer
+ def runTest(self):
+ from pyparsing import Word,nums,OneOrMore,countedArray,alphas
+ int_chars = "_"+alphas
+ array_counter = Word(int_chars).setParseAction(lambda t: int_chars.index(t[0]))
+
+ # 123456789012345678901234567890
+ testString = "B 5 7 F 0 1 2 3 4 5 _ C 5 4 3"
+
+ integer = Word(nums).setParseAction(lambda t: int(t[0]))
+ countedField = countedArray(integer, intExpr=array_counter)
+
+ 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 LineAndStringEndTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import OneOrMore,lineEnd,alphanums,Word,stringEnd,delimitedList,SkipTo
+
+ NLs = OneOrMore(lineEnd)
+ bnf1 = delimitedList(Word(alphanums).leaveWhitespace(), NLs)
+ 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 test,expected in tests:
+ res1 = bnf1.parseString(test)
+ print_(res1,'=?',expected)
+ assert res1.asList() == expected, "Failed lineEnd/stringEnd test (1): "+repr(test)+ " -> "+str(res1.asList())
+
+ res2 = bnf2.searchString(test)[0]
+ print_(res2.asList(),'=?',expected[-1:])
+ assert res2.asList() == expected[-1:], "Failed lineEnd/stringEnd test (2): "+repr(test)+ " -> "+str(res2.asList())
+
+ res3 = bnf3.parseString(test)
+ first = res3[0]
+ rest = res3[1]
+ #~ print res3.dump()
+ print_(repr(rest),'=?',repr(test[len(first)+1:]))
+ assert rest == test[len(first)+1:]#, "Failed lineEnd/stringEnd test (3): " +repr(test)+ " -> "+str(res3[1].asList())
+ print_()
+
+ 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('\\\\','\\'), end=' ')
+ try:
+ res = k.parseString(src, parseAll=True).asList()
+ except ParseException as 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_(list(map(str,res)))
+ assert list(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 as pbe:
+ print_("Received expected exception:", pbe)
+ raisedMsg = pbe.msg
+ assert raisedMsg == testMessage, "Failed to get correct exception message"
+
+
+class OriginalTextForTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import makeHTMLTags, originalTextFor
+
+ def rfn(t):
+ return "%s:%d" % (t.src, len("".join(t)))
+
+ makeHTMLStartTag = lambda tag: originalTextFor(makeHTMLTags(tag)[0], asString=False)
+
+ # 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)
+ if VERBOSE:
+ 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"
+
+ tag_fields = makeHTMLStartTag("IMG").searchString(text)[0]
+ if VERBOSE:
+ print_(sorted(tag_fields.keys()))
+ assert sorted(tag_fields.keys()) == ['alt', 'empty', 'height', 'src', 'startImg', 'tag', 'width'], 'failed to preserve results names in originalTextFor'
+
+
+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 = list(map(Keyword, "DO AA".split()))
+ LPAR,RPAR = list(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, withClass, 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>
+ <a class="boo">8</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>']],
+ [['a', ['b', 'x'], False, '2', '</a>'],
+ ['a', ['b', 'x'], False, '3', '</a>']],
+ [['a', ['class', 'boo'], False, '8', '</a>']],
+ )
+
+ for attrib, exp in zip([
+ withAttribute(b="x"),
+ #withAttribute(B="x"),
+ withAttribute(("b","x")),
+ #withAttribute(("B","x")),
+ withClass("boo"),
+ ], expected):
+
+ tagStart.setParseAction(attrib)
+ result = expr.searchString(data)
+
+ print_(result.dump())
+ assert result.asList() == exp, "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 = list(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 WordExcludeTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word, printables
+ allButPunc = Word(printables, excludeChars=".,:;-_!?")
+
+ test = "Hello, Mr. Ed, it's Wilbur!"
+ result = allButPunc.searchString(test).asList()
+ print_(result)
+ assert result == [['Hello'], ['Mr'], ['Ed'], ["it's"], ['Wilbur']]
+
+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 as 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 as 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 = [flatten(e.searchString(t).asList()) for e in [
+ leadingConsonant,
+ leadingVowel,
+ trailingConsonant,
+ trailingVowel,
+ internalVowel,
+ bnf,
+ ]]
+ print_(results)
+ assert results==expected,"Failed WordBoundaryTest, expected %s, got %s" % (expected,results)
+ print_()
+
+class RequiredEachTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Keyword
+
+ parser = Keyword('bam') & Keyword('boo')
+ try:
+ res1 = parser.parseString('bam boo')
+ print_(res1.asList())
+ res2 = parser.parseString('boo bam')
+ print_(res2.asList())
+ except ParseException:
+ failed = True
+ else:
+ failed = False
+ assert not failed, "invalid logic in Each"
+
+ assert set(res1) == set(res2), "Failed RequiredEachTest, expected " + \
+ str(res1.asList()) + " and " + str(res2.asList()) + "to contain same words in any order"
+
+class OptionalEachTest(ParseTestCase):
+ def runTest1(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())
+
+
+ def runTest2(self):
+ from pyparsing import Word, alphanums, Suppress, OneOrMore, Group, Regex, Optional
+
+ word = Word(alphanums + '_').setName("word")
+ with_stmt = 'with' + OneOrMore(Group(word('key') + '=' + word('value')))('overrides')
+ using_stmt = 'using' + Regex('id-[0-9a-f]{8}')('id')
+ modifiers = Optional(with_stmt('with_stmt')) & Optional(using_stmt('using_stmt'))
+
+ assert modifiers == "with foo=bar bing=baz using id-deadbeef"
+ assert not modifiers == "with foo=bar bing=baz using id-deadbeef using id-feedfeed"
+
+ def runTest(self):
+ self.runTest1()
+ self.runTest2()
+
+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 MarkInputLineTest(ParseTestCase):
+ def runTest(self):
+
+ samplestr1 = "DOB 100-10-2010;more garbage\nID PARI12345678;more garbage"
+
+ from pyparsing import Regex, Word, alphanums, restOfLine
+ dob_ref = "DOB" + Regex(r"\d{2}-\d{2}-\d{4}")("dob")
+
+ try:
+ res = dob_ref.parseString(samplestr1)
+ except ParseException as pe:
+ outstr = pe.markInputline()
+ print_(outstr)
+ assert outstr == "DOB >!<100-10-2010;more garbage", "did not properly create marked input line"
+ else:
+ assert False, "test construction failed - should have raised an exception"
+
+class LocatedExprTest(ParseTestCase):
+ def runTest(self):
+
+ # 012345678901234567890123456789012345678901234567890
+ samplestr1 = "DOB 10-10-2010;more garbage;ID PARI12345678 ;more garbage"
+
+ from pyparsing import Regex, Word, alphanums, restOfLine, locatedExpr
+ id_ref = locatedExpr("ID" + Word(alphanums,exact=12)("id"))
+
+ res = id_ref.searchString(samplestr1)[0][0]
+ print_(res.dump())
+ assert samplestr1[res.locn_start:res.locn_end] == 'ID PARI12345678', "incorrect location calculation"
+
+
+class PopTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word, alphas, nums
+
+ source = "AAA 123 456 789 234"
+ patt = Word(alphas)("name") + Word(nums)*(1,)
+
+ result = patt.parseString(source)
+ tests = [
+ (0, 'AAA', ['123', '456', '789', '234']),
+ (None, '234', ['123', '456', '789']),
+ ('name', 'AAA', ['123', '456', '789']),
+ (-1, '789', ['123', '456']),
+ ]
+ for test in tests:
+ idx, val, remaining = test
+ if idx is not None:
+ ret = result.pop(idx)
+ else:
+ ret = result.pop()
+ print_("EXP:", val, remaining)
+ print_("GOT:", ret, result.asList())
+ print_(ret, result.asList())
+ assert ret == val, "wrong value returned, got %r, expected %r" % (ret, val)
+ assert remaining == result.asList(), "list is in wrong state after pop, got %r, expected %r" % (result.asList(), remaining)
+ print_()
+
+ prevlist = result.asList()
+ ret = result.pop('name', default="noname")
+ print_(ret)
+ print_(result.asList())
+ assert ret == "noname", "default value not successfully returned, got %r, expected %r" % (ret, "noname")
+ assert result.asList() == prevlist, "list is in wrong state after pop, got %r, expected %r" % (result.asList(), remaining)
+
+
+class AddConditionTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word, alphas, nums, Suppress, ParseFatalException
+
+ numParser = Word(nums)
+ numParser.addParseAction(lambda s,l,t: int(t[0]))
+ numParser.addCondition(lambda s,l,t: t[0] % 2)
+ numParser.addCondition(lambda s,l,t: t[0] >= 7)
+
+ result = numParser.searchString("1 2 3 4 5 6 7 8 9 10")
+ print_(result.asList())
+ assert result.asList() == [[7],[9]], "failed to properly process conditions"
+
+ numParser = Word(nums)
+ numParser.addParseAction(lambda s,l,t: int(t[0]))
+ rangeParser = (numParser("from_") + Suppress('-') + numParser("to"))
+
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ print_(result.asList())
+ assert result.asList() == [[1, 4], [2, 4], [4, 3]], "failed to properly process conditions"
+
+ rangeParser.addCondition(lambda t: t.to > t.from_, message="from must be <= to", fatal=False)
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ print_(result.asList())
+ assert result.asList() == [[1, 4], [2, 4]], "failed to properly process conditions"
+
+ rangeParser = (numParser("from_") + Suppress('-') + numParser("to"))
+ rangeParser.addCondition(lambda t: t.to > t.from_, message="from must be <= to", fatal=True)
+ try:
+ result = rangeParser.searchString("1-4 2-4 4-3 5 6 7 8 9 10")
+ assert False, "failed to interrupt parsing on fatal condition failure"
+ except ParseFatalException:
+ print_("detected fatal condition")
+
+class PatientOrTest(ParseTestCase):
+ def runTest(self):
+ import pyparsing as pp
+
+ # Two expressions and a input string which could - syntactically - be matched against
+ # both expressions. The "Literal" expression is considered invalid though, so this PE
+ # should always detect the "Word" expression.
+ def validate(token):
+ if token[0] == "def":
+ raise pp.ParseException("signalling invalid token")
+ return token
+
+ a = pp.Word("de").setName("Word")#.setDebug()
+ b = pp.Literal("def").setName("Literal").setParseAction(validate)#.setDebug()
+ c = pp.Literal("d").setName("d")#.setDebug()
+
+ # The "Literal" expressions's ParseAction is not executed directly after syntactically
+ # detecting the "Literal" Expression but only after the Or-decision has been made
+ # (which is too late)...
+ try:
+ result = (a ^ b ^ c).parseString("def")
+ assert result.asList() == ['de'], "failed to select longest match, chose %s" % result
+ except ParseException:
+ failed = True
+ else:
+ failed = False
+ assert not failed, "invalid logic in Or, fails on longest match with exception in parse action"
+
+class EachWithOptionalWithResultsNameTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Optional
+
+ result = (Optional('foo')('one') & Optional('bar')('two')).parseString('bar foo')
+ print_(result.dump())
+ assert sorted(result.keys()) == ['one','two']
+
+class UnicodeExpressionTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Literal, ParseException
+
+ z = 'a' | Literal(u'\u1111')
+ z.streamline()
+ try:
+ z.parseString('b')
+ except ParseException as pe:
+ if not PY_3:
+ assert pe.msg == r'''Expected {"a" | "\u1111"}''', "Invalid error message raised, got %r" % pe.msg
+
+class SetNameTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import (oneOf,infixNotation,Word,nums,opAssoc,delimitedList,countedArray,
+ nestedExpr,makeHTMLTags,anyOpenTag,anyCloseTag,commonHTMLEntity,replaceHTMLEntity)
+
+ a = oneOf("a b c")
+ b = oneOf("d e f")
+ arith_expr = infixNotation(Word(nums),
+ [
+ (oneOf('* /'),2,opAssoc.LEFT),
+ (oneOf('+ -'),2,opAssoc.LEFT),
+ ])
+ arith_expr2 = infixNotation(Word(nums),
+ [
+ (('?',':'),3,opAssoc.LEFT),
+ ])
+
+ tests = [
+ a,
+ b,
+ (a | b),
+ arith_expr,
+ arith_expr.expr,
+ arith_expr2,
+ arith_expr2.expr,
+ delimitedList(Word(nums).setName("int")),
+ countedArray(Word(nums).setName("int")),
+ nestedExpr(),
+ makeHTMLTags('Z'),
+ (anyOpenTag,anyCloseTag),
+ commonHTMLEntity,
+ commonHTMLEntity.setParseAction(replaceHTMLEntity).transformString("lsdjkf &lt;lsdjkf&gt;&amp;&apos;&quot;&xyzzy;"),
+ ]
+
+ expected = map(str.strip, """\
+ a | b | c
+ d | e | f
+ {a | b | c | d | e | f}
+ Forward: ...
+ + | - term
+ Forward: ...
+ ?: term
+ int [, int]...
+ (len) int...
+ nested () expression
+ (<Z>, </Z>)
+ (<any tag>, </any tag>)
+ common HTML entity
+ lsdjkf <lsdjkf>&'"&xyzzy;""".splitlines())
+
+ for t,e in zip(tests, expected):
+ tname = str(t)
+ assert tname==e, "expression name mismatch, expected {} got {}".format(e, tname)
+
+class TrimArityExceptionMaskingTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word
+
+ invalid_message = [
+ "<lambda>() takes exactly 1 argument (0 given)",
+ "<lambda>() missing 1 required positional argument: 't'"
+ ][PY_3]
+ try:
+ Word('a').setParseAction(lambda t: t[0]+1).parseString('aaa')
+ except Exception as e:
+ exc_msg = str(e)
+ assert exc_msg != invalid_message, "failed to catch TypeError thrown in _trim_arity"
+
+class OneOrMoreStopTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import (Word, OneOrMore, alphas, Keyword, CaselessKeyword,
+ nums, alphanums)
+
+ test = "BEGIN aaa bbb ccc END"
+ BEGIN,END = map(Keyword, "BEGIN,END".split(','))
+ body_word = Word(alphas).setName("word")
+ for ender in (END, "END", CaselessKeyword("END")):
+ expr = BEGIN + OneOrMore(body_word, stopOn=ender) + END
+ assert test == expr, "Did not successfully stop on ending expression %r" % ender
+
+ number = Word(nums+',.()').setName("number with optional commas")
+ parser= (OneOrMore(Word(alphanums+'-/.'), stopOn=number)('id').setParseAction(' '.join)
+ + number('data'))
+ result = parser.parseString(' XXX Y/123 1,234.567890')
+ assert result.asList() == ['XXX Y/123', '1,234.567890'], "Did not successfully stop on ending expression %r" % number
+
+class ZeroOrMoreStopTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import (Word, ZeroOrMore, alphas, Keyword, CaselessKeyword)
+
+ test = "BEGIN END"
+ BEGIN,END = map(Keyword, "BEGIN,END".split(','))
+ body_word = Word(alphas).setName("word")
+ for ender in (END, "END", CaselessKeyword("END")):
+ expr = BEGIN + ZeroOrMore(body_word, stopOn=ender) + END
+ assert test == expr, "Did not successfully stop on ending expression %r" % ender
+
+class NestedAsDictTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Literal, Forward, alphanums, Group, delimitedList, Dict, Word, Optional
+
+ equals = Literal("=").suppress()
+ lbracket = Literal("[").suppress()
+ rbracket = Literal("]").suppress()
+ lbrace = Literal("{").suppress()
+ rbrace = Literal("}").suppress()
+
+ value_dict = Forward()
+ value_list = Forward()
+ value_string = Word(alphanums + "@. ")
+
+ value = value_list ^ value_dict ^ value_string
+ values = Group(delimitedList(value, ","))
+ #~ values = delimitedList(value, ",").setParseAction(lambda toks: [toks.asList()])
+
+ value_list << lbracket + values + rbracket
+
+ identifier = Word(alphanums + "_.")
+
+ assignment = Group(identifier + equals + Optional(value))
+ assignments = Dict(delimitedList(assignment, ';'))
+ value_dict << lbrace + assignments + rbrace
+
+ response = assignments
+
+ rsp = 'username=goat; errors={username=[already taken, too short]}; empty_field='
+ result_dict = response.parseString(rsp).asDict()
+ print_(result_dict)
+ assert result_dict['username'] == 'goat', "failed to process string in ParseResults correctly"
+ assert result_dict['errors']['username'] == ['already taken', 'too short'], "failed to process nested ParseResults correctly"
+
+class TraceParseActionDecoratorTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import traceParseAction, Word, nums
+
+ @traceParseAction
+ def convert_to_int(t):
+ return int(t[0])
+
+ class Z(object):
+ def __call__(self, other):
+ return other[0] * 1000
+
+ integer = Word(nums).addParseAction(convert_to_int)
+ integer.addParseAction(traceParseAction(lambda t: t[0]*10))
+ integer.addParseAction(traceParseAction(Z()))
+ integer.parseString("132")
+
+class RunTestsTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import Word, nums, delimitedList
+
+ integer = Word(nums).setParseAction(lambda t : int(t[0]))
+ intrange = integer("start") + '-' + integer("end")
+ intrange.addCondition(lambda t: t.end > t.start, message="invalid range, start must be <= end", fatal=True)
+ intrange.addParseAction(lambda t: list(range(t.start, t.end+1)))
+
+ indices = delimitedList(intrange | integer)
+ indices.addParseAction(lambda t: sorted(set(t)))
+
+ tests = """\
+ # normal data
+ 1-3,2-4,6,8-10,16
+
+ # lone integer
+ 11"""
+ results = indices.runTests(tests, printResults=False)[1]
+
+ expectedResults = [
+ [1, 2, 3, 4, 6, 8, 9, 10, 16],
+ [11],
+ ]
+ for res,expected in zip(results, expectedResults):
+ print_(res[1].asList())
+ print_(expected)
+ assert res[1].asList() == expected, "failed test: " + expected[0][2:]
+
+
+ tests = """\
+ # invalid range
+ 1-2, 3-1, 4-6, 7, 12
+ """
+ success = indices.runTests(tests, printResults=False, failureTests=True)[0]
+ assert success, "failed to raise exception on improper range test"
+
+class CommonExpressionsTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import pyparsing_common
+
+ success = pyparsing_common.mac_address.runTests("""
+ AA:BB:CC:DD:EE:FF
+ AA.BB.CC.DD.EE.FF
+ AA-BB-CC-DD-EE-FF
+ """)[0]
+ assert success, "error in parsing valid MAC address"
+
+ success = pyparsing_common.mac_address.runTests("""
+ # mixed delimiters
+ AA.BB:CC:DD:EE:FF
+ """, failureTests=True)[0]
+ assert success, "error in detecting invalid mac address"
+
+ success = pyparsing_common.ipv4_address.runTests("""
+ 0.0.0.0
+ 1.1.1.1
+ 127.0.0.1
+ 1.10.100.199
+ 255.255.255.255
+ """)[0]
+ assert success, "error in parsing valid IPv4 address"
+
+ success = pyparsing_common.ipv4_address.runTests("""
+ # out of range value
+ 256.255.255.255
+ """, failureTests=True)[0]
+ assert success, "error in detecting invalid IPv4 address"
+
+ success = pyparsing_common.ipv6_address.runTests("""
+ 2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ 2134::1234:4567:2468:1236:2444:2106
+ 0:0:0:0:0:0:A00:1
+ 1080::8:800:200C:417A
+ ::A00:1
+
+ # loopback address
+ ::1
+
+ # the null address
+ ::
+
+ # ipv4 compatibility form
+ ::ffff:192.168.0.1
+ """)[0]
+ assert success, "error in parsing valid IPv6 address"
+
+ success = pyparsing_common.ipv6_address.runTests("""
+ # too few values
+ 1080:0:0:0:8:800:200C
+
+ # too many ::'s, only 1 allowed
+ 2134::1234:4567::2444:2106
+ """, failureTests=True)[0]
+ assert success, "error in detecting invalid IPv6 address"
+
+ success = pyparsing_common.number.runTests("""
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ """)[0]
+ assert success, "error in parsing valid numerics"
+
+ # any int or real number, returned as float
+ success = pyparsing_common.fnumber.runTests("""
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ """)[0]
+ assert success, "error in parsing valid numerics"
+
+ success, results = pyparsing_common.iso8601_date.runTests("""
+ 1997
+ 1997-07
+ 1997-07-16
+ """)
+ assert success, "error in parsing valid iso8601_date"
+ expected = [
+ ('1997', None, None),
+ ('1997', '07', None),
+ ('1997', '07', '16'),
+ ]
+ for r,exp in zip(results, expected):
+ assert (r[1].year,r[1].month,r[1].day,) == exp, "failed to parse date into fields"
+
+ success, results = pyparsing_common.iso8601_date().addParseAction(pyparsing_common.convertToDate()).runTests("""
+ 1997-07-16
+ """)
+ assert success, "error in parsing valid iso8601_date with parse action"
+ assert results[0][1][0] == datetime.date(1997, 7, 16)
+
+ success, results = pyparsing_common.iso8601_datetime.runTests("""
+ 1997-07-16T19:20+01:00
+ 1997-07-16T19:20:30+01:00
+ 1997-07-16T19:20:30.45Z
+ 1997-07-16 19:20:30.45
+ """)
+ assert success, "error in parsing valid iso8601_datetime"
+
+ success, results = pyparsing_common.iso8601_datetime().addParseAction(pyparsing_common.convertToDatetime()).runTests("""
+ 1997-07-16T19:20:30.45
+ """)
+ assert success, "error in parsing valid iso8601_datetime"
+ assert results[0][1][0] == datetime.datetime(1997, 7, 16, 19, 20, 30, 450000)
+
+ success = pyparsing_common.uuid.runTests("""
+ 123e4567-e89b-12d3-a456-426655440000
+ """)[0]
+ assert success, "failed to parse valid uuid"
+
+
+ success = pyparsing_common.fraction.runTests("""
+ 1/2
+ -15/16
+ -3/-4
+ """)[0]
+ assert success, "failed to parse valid fraction"
+
+
+ success = pyparsing_common.mixed_integer.runTests("""
+ 1/2
+ -15/16
+ -3/-4
+ 1 1/2
+ 2 -15/16
+ 0 -3/-4
+ 12
+ """)[0]
+ assert success, "failed to parse valid mixed integer"
+
+ success, results = pyparsing_common.number.runTests("""
+ 100
+ -3
+ 1.732
+ -3.14159
+ 6.02e23""")
+ assert success, "failed to parse numerics"
+ for test,result in results:
+ expected = eval(test)
+ assert result[0] == expected, "numeric parse failed (wrong value) (%s should be %s)" % (result[0], expected)
+ assert type(result[0]) == type(expected), "numeric parse failed (wrong type) (%s should be %s)" % (type(result[0]), type(expected))
+
+
+class TokenMapTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import tokenMap, Word, hexnums, OneOrMore
+
+ parser = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+ success, results = parser.runTests("""
+ 00 11 22 aa FF 0a 0d 1a
+ """, printResults=False)
+ assert success, "failed to parse hex integers"
+ print_(results)
+ assert results[0][-1].asList() == [0, 17, 34, 170, 255, 10, 13, 26], "tokenMap parse action failed"
+
+
+class ParseFileTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import pyparsing_common, OneOrMore
+ s = """
+ 123 456 789
+ """
+ input_file = StringIO(s)
+ integer = pyparsing_common.integer
+
+ results = OneOrMore(integer).parseFile(input_file)
+ print_(results)
+
+ results = OneOrMore(integer).parseFile('test/parsefiletest_input_file.txt')
+ print_(results)
+
+
+class HTMLStripperTest(ParseTestCase):
+ def runTest(self):
+ from pyparsing import pyparsing_common, originalTextFor, OneOrMore, Word, printables
+
+ sample = """
+ <html>
+ Here is some sample <i>HTML</i> text.
+ </html>
+ """
+ read_everything = originalTextFor(OneOrMore(Word(printables)))
+ read_everything.addParseAction(pyparsing_common.stripHTMLTags)
+
+ result = read_everything.parseString(sample)
+ assert result[0].strip() == 'Here is some sample HTML text.'
+
+class ExprSplitterTest(ParseTestCase):
+ def runTest(self):
+
+ from pyparsing import Literal, quotedString, pythonStyleComment, Empty
+
+ expr = Literal(';') + Empty()
+ expr.ignore(quotedString)
+ expr.ignore(pythonStyleComment)
+
+
+ sample = """
+ def main():
+ this_semi_does_nothing();
+ neither_does_this_but_there_are_spaces_afterward();
+ a = "a;b"; return a # this is a comment; it has a semicolon!
+
+ def b():
+ if False:
+ z=1000;b("; in quotes"); c=200;return z
+ return ';'
+
+ class Foo(object):
+ def bar(self):
+ '''a docstring; with a semicolon'''
+ a = 10; b = 11; c = 12
+
+ # this comment; has several; semicolons
+ if self.spam:
+ x = 12; return x # so; does; this; one
+ x = 15;;; y += x; return y
+
+ def baz(self):
+ return self.bar
+ """
+ expected = [
+ [' this_semi_does_nothing()', ''],
+ [' neither_does_this_but_there_are_spaces_afterward()', ''],
+ [' a = "a;b"', 'return a # this is a comment; it has a semicolon!'],
+ [' z=1000', 'b("; in quotes")', 'c=200', 'return z'],
+ [" return ';'"],
+ [" '''a docstring; with a semicolon'''"],
+ [' a = 10', 'b = 11', 'c = 12'],
+ [' # this comment; has several; semicolons'],
+ [' x = 12', 'return x # so; does; this; one'],
+ [' x = 15', '', '', 'y += x', 'return y'],
+ ]
+
+ exp_iter = iter(expected)
+ for line in filter(lambda ll: ';' in ll, sample.splitlines()):
+ print_(str(list(expr.split(line)))+',')
+ assert list(expr.split(line)) == next(exp_iter), "invalid split on expression"
+
+ print_()
+
+ expected = [
+ [' this_semi_does_nothing()', ';', ''],
+ [' neither_does_this_but_there_are_spaces_afterward()', ';', ''],
+ [' a = "a;b"', ';', 'return a # this is a comment; it has a semicolon!'],
+ [' z=1000', ';', 'b("; in quotes")', ';', 'c=200', ';', 'return z'],
+ [" return ';'"],
+ [" '''a docstring; with a semicolon'''"],
+ [' a = 10', ';', 'b = 11', ';', 'c = 12'],
+ [' # this comment; has several; semicolons'],
+ [' x = 12', ';', 'return x # so; does; this; one'],
+ [' x = 15', ';', '', ';', '', ';', 'y += x', ';', 'return y'],
+ ]
+ exp_iter = iter(expected)
+ for line in filter(lambda ll: ';' in ll, sample.splitlines()):
+ print_(str(list(expr.split(line, includeSeparators=True)))+',')
+ assert list(expr.split(line, includeSeparators=True)) == next(exp_iter), "invalid split on expression"
+
+ print_()
+
+
+ expected = [
+ [' this_semi_does_nothing()', ''],
+ [' neither_does_this_but_there_are_spaces_afterward()', ''],
+ [' a = "a;b"', 'return a # this is a comment; it has a semicolon!'],
+ [' z=1000', 'b("; in quotes"); c=200;return z'],
+ [' a = 10', 'b = 11; c = 12'],
+ [' x = 12', 'return x # so; does; this; one'],
+ [' x = 15', ';; y += x; return y'],
+ ]
+ exp_iter = iter(expected)
+ for line in sample.splitlines():
+ pieces = list(expr.split(line, maxsplit=1))
+ print_(str(pieces)+',')
+ if len(pieces) == 2:
+ exp = next(exp_iter)
+ assert pieces == exp, "invalid split on expression with maxSplits=1"
+ elif len(pieces) == 1:
+ assert len(expr.searchString(line)) == 0, "invalid split with maxSplits=1 when expr not present"
+ else:
+ print_("\n>>> " + line)
+ assert False, "invalid split on expression with maxSplits=1, corner case"
+
+
+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 as 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 as 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 as 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 as 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():
+ import inspect
+ suite = TestSuite()
+ suite.addTest( PyparsingTestInit() )
+
+ test_case_classes = ParseTestCase.__subclasses__()
+ # put classes in order as they are listed in the source code
+ test_case_classes.sort(key=lambda cls: inspect.getsourcelines(cls)[1])
+
+ test_case_classes.remove(PyparsingTestInit)
+ # test_case_classes.remove(ParseASMLTest)
+ test_case_classes.remove(EnablePackratParsing)
+ if IRON_PYTHON_ENV:
+ test_case_classes.remove(OriginalTextForTest)
+
+ suite.addTests(T() for T in test_case_classes)
+
+ 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(classes):
+ suite = TestSuite()
+ suite.addTest( PyparsingTestInit() )
+ for cls in classes:
+ suite.addTest( cls() )
+ return suite
+
+console = False
+console = True
+
+#~ from line_profiler import LineProfiler
+#~ from pyparsing import ParseResults
+#~ lp = LineProfiler(ParseResults.__setitem__)
+lp = None
+
+#~ if __name__ == '__main__':
+ #~ unittest.main()
+if console:
+ #~ # console mode
+ testRunner = TextTestRunner()
+
+ testclasses = []
+ #~ testclasses.append(put_test_class_here)
+ #~ testclasses.append(RequiredEachTest)
+ if not testclasses:
+ testRunner.run( makeTestSuite() )
+ else:
+ if lp is None:
+ testRunner.run( makeTestSuiteTemp(testclasses) )
+ else:
+ lp.run("testRunner.run( makeTestSuite(%s) )" % testclass.__name__)
+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()
diff --git a/trunk/src/update_pyparsing_timestamp.py b/trunk/src/update_pyparsing_timestamp.py
new file mode 100644
index 0000000..80ea3d4
--- /dev/null
+++ b/trunk/src/update_pyparsing_timestamp.py
@@ -0,0 +1,17 @@
+from pyparsing import quotedString
+from datetime import datetime
+
+nw = datetime.utcnow()
+nowstring = '"%s"' % (nw.strftime("%d %b %Y %X")[:-3] + " UTC")
+print (nowstring)
+
+quoted_time = quotedString()
+quoted_time.setParseAction(lambda: nowstring)
+
+version_time = "__versionTime__ = " + quoted_time
+with open('pyparsing.py') as oldpp:
+ new_code = version_time.transformString(oldpp.read())
+
+with open('pyparsing.py','w') as newpp:
+ newpp.write(new_code)
+