From 7f927dabbae79caeaa9527222ec4b7f9544f75e0 Mon Sep 17 00:00:00 2001 From: L2501 Date: Sat, 9 Dec 2023 08:58:26 +0000 Subject: [PATCH] [script.module.pyparsing] 3.1.1 --- script.module.pyparsing/LICENSE.txt | 18 + script.module.pyparsing/addon.xml | 18 + .../lib/pyparsing/__init__.py | 325 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 8220 bytes .../__pycache__/actions.cpython-311.pyc | Bin 0 -> 9108 bytes .../__pycache__/common.cpython-311.pyc | Bin 0 -> 14855 bytes .../__pycache__/core.cpython-311.pyc | Bin 0 -> 297087 bytes .../__pycache__/exceptions.cpython-311.pyc | Bin 0 -> 13695 bytes .../__pycache__/helpers.cpython-311.pyc | Bin 0 -> 54114 bytes .../__pycache__/results.cpython-311.pyc | Bin 0 -> 37835 bytes .../__pycache__/testing.cpython-311.pyc | Bin 0 -> 19685 bytes .../__pycache__/unicode.cpython-311.pyc | Bin 0 -> 15174 bytes .../__pycache__/util.cpython-311.pyc | Bin 0 -> 16769 bytes .../lib/pyparsing/actions.py | 217 + .../lib/pyparsing/common.py | 432 ++ script.module.pyparsing/lib/pyparsing/core.py | 6159 +++++++++++++++++ .../lib/pyparsing/diagram/__init__.py | 656 ++ .../lib/pyparsing/exceptions.py | 299 + .../lib/pyparsing/helpers.py | 1100 +++ .../lib/pyparsing/results.py | 796 +++ .../lib/pyparsing/testing.py | 258 + .../lib/pyparsing/unicode.py | 361 + script.module.pyparsing/lib/pyparsing/util.py | 284 + script.module.pyparsing/resources/icon.png | Bin 0 -> 7237 bytes 24 files changed, 10923 insertions(+) create mode 100644 script.module.pyparsing/LICENSE.txt create mode 100644 script.module.pyparsing/addon.xml create mode 100644 script.module.pyparsing/lib/pyparsing/__init__.py create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/__init__.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/actions.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/common.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/core.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/exceptions.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/helpers.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/results.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/testing.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/unicode.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/__pycache__/util.cpython-311.pyc create mode 100644 script.module.pyparsing/lib/pyparsing/actions.py create mode 100644 script.module.pyparsing/lib/pyparsing/common.py create mode 100644 script.module.pyparsing/lib/pyparsing/core.py create mode 100644 script.module.pyparsing/lib/pyparsing/diagram/__init__.py create mode 100644 script.module.pyparsing/lib/pyparsing/exceptions.py create mode 100644 script.module.pyparsing/lib/pyparsing/helpers.py create mode 100644 script.module.pyparsing/lib/pyparsing/results.py create mode 100644 script.module.pyparsing/lib/pyparsing/testing.py create mode 100644 script.module.pyparsing/lib/pyparsing/unicode.py create mode 100644 script.module.pyparsing/lib/pyparsing/util.py create mode 100644 script.module.pyparsing/resources/icon.png diff --git a/script.module.pyparsing/LICENSE.txt b/script.module.pyparsing/LICENSE.txt new file mode 100644 index 000000000..1bf98523e --- /dev/null +++ b/script.module.pyparsing/LICENSE.txt @@ -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/script.module.pyparsing/addon.xml b/script.module.pyparsing/addon.xml new file mode 100644 index 000000000..41afe3212 --- /dev/null +++ b/script.module.pyparsing/addon.xml @@ -0,0 +1,18 @@ + + + + + + + + Classes and methods to define and execute parsing grammars + Classes and methods to define and execute parsing grammars + MIT + all + https://github.com/pyparsing/pyparsing/ + https://github.com/pyparsing/pyparsing/ + + resources/icon.png + + + diff --git a/script.module.pyparsing/lib/pyparsing/__init__.py b/script.module.pyparsing/lib/pyparsing/__init__.py new file mode 100644 index 000000000..3dbc3cf83 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/__init__.py @@ -0,0 +1,325 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2022 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 +``", !"``), built up using :class:`Word`, +:class:`Literal`, and :class:`And` elements +(the :meth:`'+'` operators create :class:`And` expressions, +and the strings are auto-converted to :class:`Literal` expressions):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word(alphas) + "," + Word(alphas) + "!" + + hello = "Hello, World!" + print(hello, "->", greet.parse_string(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 :class:`'+'`, +:class:`'|'`, :class:`'^'` and :class:`'&'` operators. + +The :class:`ParseResults` object returned from +:class:`ParserElement.parse_string` 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 + + +Getting Started - +----------------- +Visit the classes :class:`ParserElement` and :class:`ParseResults` to +see the base classes that most other pyparsing +classes inherit from. Use the docstrings for examples of how to: + + - construct literal match expressions from :class:`Literal` and + :class:`CaselessLiteral` classes + - construct character word-group expressions using the :class:`Word` + class + - see how to create repetitive expressions using :class:`ZeroOrMore` + and :class:`OneOrMore` classes + - use :class:`'+'`, :class:`'|'`, :class:`'^'`, + and :class:`'&'` operators to combine simple expressions into + more complex ones + - associate names with your parsed results using + :class:`ParserElement.set_results_name` + - access the parsed data, which is returned as a :class:`ParseResults` + object + - find some helpful expression short-cuts like :class:`DelimitedList` + and :class:`one_of` + - find more useful common expressions in the :class:`pyparsing_common` + namespace class +""" +from typing import NamedTuple + + +class version_info(NamedTuple): + major: int + minor: int + micro: int + releaselevel: str + serial: int + + @property + def __version__(self): + return ( + f"{self.major}.{self.minor}.{self.micro}" + + ( + f"{'r' if self.releaselevel[0] == 'c' else ''}{self.releaselevel[0]}{self.serial}", + "", + )[self.releaselevel == "final"] + ) + + def __str__(self): + return f"{__name__} {self.__version__} / {__version_time__}" + + def __repr__(self): + return f"{__name__}.{type(self).__name__}({', '.join('{}={!r}'.format(*nv) for nv in zip(self._fields, self))})" + + +__version_info__ = version_info(3, 1, 1, "final", 1) +__version_time__ = "29 Jul 2023 22:27 UTC" +__version__ = __version_info__.__version__ +__versionTime__ = __version_time__ +__author__ = "Paul McGuire " + +from .util import * +from .exceptions import * +from .actions import * +from .core import __diag__, __compat__ +from .results import * +from .core import * # type: ignore[misc, assignment] +from .core import _builtin_exprs as core_builtin_exprs +from .helpers import * # type: ignore[misc, assignment] +from .helpers import _builtin_exprs as helper_builtin_exprs + +from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode +from .testing import pyparsing_test as testing +from .common import ( + pyparsing_common as common, + _builtin_exprs as common_builtin_exprs, +) + +# define backward compat synonyms +if "pyparsing_unicode" not in globals(): + pyparsing_unicode = unicode # type: ignore[misc] +if "pyparsing_common" not in globals(): + pyparsing_common = common # type: ignore[misc] +if "pyparsing_test" not in globals(): + pyparsing_test = testing # type: ignore[misc] + +core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs + + +__all__ = [ + "__version__", + "__version_time__", + "__author__", + "__compat__", + "__diag__", + "And", + "AtLineStart", + "AtStringStart", + "CaselessKeyword", + "CaselessLiteral", + "CharsNotIn", + "CloseMatch", + "Combine", + "DelimitedList", + "Dict", + "Each", + "Empty", + "FollowedBy", + "Forward", + "GoToColumn", + "Group", + "IndentedBlock", + "Keyword", + "LineEnd", + "LineStart", + "Literal", + "Located", + "PrecededBy", + "MatchFirst", + "NoMatch", + "NotAny", + "OneOrMore", + "OnlyOnce", + "OpAssoc", + "Opt", + "Optional", + "Or", + "ParseBaseException", + "ParseElementEnhance", + "ParseException", + "ParseExpression", + "ParseFatalException", + "ParseResults", + "ParseSyntaxException", + "ParserElement", + "PositionToken", + "QuotedString", + "RecursiveGrammarException", + "Regex", + "SkipTo", + "StringEnd", + "StringStart", + "Suppress", + "Token", + "TokenConverter", + "White", + "Word", + "WordEnd", + "WordStart", + "ZeroOrMore", + "Char", + "alphanums", + "alphas", + "alphas8bit", + "any_close_tag", + "any_open_tag", + "autoname_elements", + "c_style_comment", + "col", + "common_html_entity", + "condition_as_parse_action", + "counted_array", + "cpp_style_comment", + "dbl_quoted_string", + "dbl_slash_comment", + "delimited_list", + "dict_of", + "empty", + "hexnums", + "html_comment", + "identchars", + "identbodychars", + "infix_notation", + "java_style_comment", + "line", + "line_end", + "line_start", + "lineno", + "make_html_tags", + "make_xml_tags", + "match_only_at_col", + "match_previous_expr", + "match_previous_literal", + "nested_expr", + "null_debug_action", + "nums", + "one_of", + "original_text_for", + "printables", + "punc8bit", + "pyparsing_common", + "pyparsing_test", + "pyparsing_unicode", + "python_style_comment", + "quoted_string", + "remove_quotes", + "replace_with", + "replace_html_entity", + "rest_of_line", + "sgl_quoted_string", + "srange", + "string_end", + "string_start", + "token_map", + "trace_parse_action", + "ungroup", + "unicode_set", + "unicode_string", + "with_attribute", + "with_class", + # pre-PEP8 compatibility names + "__versionTime__", + "anyCloseTag", + "anyOpenTag", + "cStyleComment", + "commonHTMLEntity", + "conditionAsParseAction", + "countedArray", + "cppStyleComment", + "dblQuotedString", + "dblSlashComment", + "delimitedList", + "dictOf", + "htmlComment", + "indentedBlock", + "infixNotation", + "javaStyleComment", + "lineEnd", + "lineStart", + "locatedExpr", + "makeHTMLTags", + "makeXMLTags", + "matchOnlyAtCol", + "matchPreviousExpr", + "matchPreviousLiteral", + "nestedExpr", + "nullDebugAction", + "oneOf", + "opAssoc", + "originalTextFor", + "pythonStyleComment", + "quotedString", + "removeQuotes", + "replaceHTMLEntity", + "replaceWith", + "restOfLine", + "sglQuotedString", + "stringEnd", + "stringStart", + "tokenMap", + "traceParseAction", + "unicodeString", + "withAttribute", + "withClass", + "common", + "unicode", + "testing", +] diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/__init__.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abc1d522b48de5a866140b241ae33a453acaec70 GIT binary patch literal 8220 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 TU_V<*c7cIrxs#)>(E0xXN=bzh literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/actions.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/actions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d36f6138fe28be8e2e578ed7401562701540aff GIT binary patch literal 9108 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 M`cDLJXb_A4XI`Lavj6}9 literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/common.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f010b42a0321e5d2df1c190d6ff71c09a48a5e71 GIT binary patch literal 14855 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 QLIhQ=)o(cPkI3hL05Rwq2mk;8 literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/core.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f3ee70a27c6a91ea8e194ffd28756b083f1ec9f GIT binary patch literal 297087 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 literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/exceptions.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/exceptions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4c2fa23519b4b87c245888cc5591df9549d20ae GIT binary patch literal 13695 zcmeHOUu+x6dEezfQ4}Rg7WIdcY_D~;CHf@U@_*~jzSw8^Y+p`yIs0;n?h<;%T}hPr zFT12|G1OJpxUF(Adc0JD6+{QHfN_=KJh(s}?jb<^(1&{nTGC))b^#X#iarD=3L17$ z!01DN-z>RYl2Y2VP0@$0=-JtsZ@&5Fo0)HZ^Ue4#T3cNlo-g+%WGvj$^f%@z(tWz9ihG^X_h zkQNr>svJ-gN+>!PNW=n)7@d<SlB?31iW`lQ)8LJQ0dT-;XKMdwTgPzg2Sv0#Pv{2LhTa5QxO2 zWSHXaK;U~xF|6m@<(^R$FW%ihAB)KQukJq`kE{FNj7g#W5HmKa29;1eq3+k;V>**D zL)!h}(9Hhoms}A<1&PuE#w_SSM5SS~Z2T2%&gHWT4If64vJ8Kaa((Cf<0Ioom;C9m zn$|l(Mb9&*#oYS$Fz~0XvQ4&2mRFH2I}o!PF&m3DEmTT#$hDW#DpQEc*yN@KHrc2O z+2NxXduFZGILbPxtkg8!(#+AHv_rDV4#^_hB6MPA@$#En(l*M^tKK_PyH|o z19D&O^q00b)V*D5#<<_5ZF>h(Mm{5|vM~Wt$7b*;7~@a~hDB8sW@CyVhQmU6jtNRK zng~TqbgE_z$AX$QqRwfyIHs)DMzpdLki&9BjwUplsLZK%?F!i}rHg`Y3P?vvF9jHz z+*Htxu=0?ZqJPg42ySy}OPUK*^0>5h*^}mxQkmNr&G;fN?1{Fet>#=tYviVF$RpnD zL7pXToh5o(B~9YUHcV-pLZcW~%BITUSzTzO4VsObr;^YDJqpcKG8>_2C=m#x`WrPo zR>>Rzsd^s4GFNKjyge(nTh3xj+sZ4&p8l1yx6T)P`|!H3a^Xo2Mesd9-^Cq+E4G5O zzu4tlJCwENyt`4bc?EyW8vL3}LeOX@K4|_jEq)hm!*a(Ndt!T)wob#2#LjA7+MecH05<-}QnjSD5}oBT{CD<$ z2%RrAEjiOpiLX*FHK&^w41kfPq1o_A&61@~I#*5qy3*F#eC0>rYQU0(&I-${0Y)k* z^p;$;@~Zr;Zzo=^Ql#th(~fJL!r!`@c4WysG(5kpY;8?ESJT&Z^(?h)HO|s4RlALG z(1LICxNxUk7#H^Xx}q~_*T;u{A2l|P!RUy&2bSDvceNL!R_M=?;cvOOY@dm`z7=U6 z^9mMuiBI#Yh3BHgX@^5TmAr zMT{Z?h)xZQ30X+Y%fdU0ppFWmXhK$^VwiG+@{Aa~N=Y$66v81jVepR4vO)%S0_(UU zt13{OiX1T^1jVE(qY|*Lu4iC0BMXWcLe`ke;08MlpofapiE82~l@nN~!^3hpK}r%N zHCDsnsEm4|u>`Re1!nV&6?FvC3u-bRk118*1p(rh+iS>H7CRD}n`fMKA*r~?ssisI zfmFXC^{600uz6uvl4r$aIB|0Ps2}o1laU!&p(?`iH94#bWhF9Is0q?2(=Bxj>C~h; zgSCL(l7!i0lvODZ*i~N#s3@Tyzu5ec8W7nhAZhBWvBo%bJC`6!x&?s@L z6d@s~4mQ3RAeq$h2*{(-e!!aACX%*6Ye;IyFr=EBI%W{)utl+Ac3>OQ%KBa@*YScE zUhv;dStbNzH3efB*f$c&0Fhd><~WISArcR3tpOIFioGrd=j9jtSjviC1WnU*MU2N~ zCFOio-#DCl!MprEttk{$<8lz7Di~8_WfwSVR&`O;I_BhrA_tS$2|?kZNGPGXLf93d zm!d&gA!DsMh-Fk#2()MpY_pP)sAfwnq8?hzv^MYvMWeDZOH)FHbOB=~V)tWJ<%HHO zV~oiPX0GO(4FLIxVIV*h)Eu+exk)lLm+hT27wT3L>h(}!UbBQG%_>TgLaU=@TZo0C z{#MNjC2Mvzl9hePD7Qxew$p+_Ta~e?{j|uJ-q&rG4e( zV{cEv>(6^%E_h#FnJl)ouUH?qbY+5n*FTzX87s7ml{icDk)qr4>BUbjX4KUidACq- z3pux7q^$+l7dFO!8qN2N-nZs^4&~j43+}@?_u-%oxz2I? zHeT1?dFR1`^I*<-5RzoZKRNNk6UFZBkGe-5c8_daDvX}`%AV^U$#+i`x~Ep&c--0j z*^$*FD`y}32D8UDCO3|59NRcnc594vPCu63-+8S4|@ z!1}-=pZ}rHzp?lJ?!50 z(|PZ3!8@Gu4*!#|JNwN)ci(YmE)=~zw=e$qVzGaBp?`m__Z#@#_ZJSH)uUf^>u*%p zMHP0TLL+c;-2=ILE?d(>l2x?z~`&! z+|a395B>hWS9qe!vzAy-=e;8Z??}!&@}#|Mt>ZIS#$N0hC~7mW`h&Q!?Lpjt+=x>t_Iv@D zdd~pzSe*IP&keI>EV8p$Q)0@ZfW@!8RI4G_q|of55L9H58BbVzfhHJRw8&~rR@@W@ zzgZ;~fklnMb}F!4;vQiXZ(8X4cpjz>^Kb5Rk-S{7goXTzaj0%kZ81BsfPVLMG( zHCXy>b(<}=SAY&w z+~}rSM9sS94Wr%qa>iCHwvrLh!MY(_hiY}8WLPjE7KE)=2+1%@hrn-ThF#xg6NJ9Y zvlT}rLG=A8BS`gZq93a7Pe7*<-dpB=fS^zsQV|#dsbCSXXfzg z@wLnA-_Co63LaV-e=kZ=E|J8SDX8zn10?Kbov15noyxpN0d0tD<}Zx|Hh(DxUM8+; zW-np(Okn~sCskdIT5H^lCsR7?uLTn|6nDU0rLE*L_r%j#@C@dhgSs?^T|g$qnKZPW zhBV~ULQH?iC#8G~apl_x{1!H|6>=t+ZJ{!ag#TEc4Jt-$hCF4TQqI|rU!#i1?ZSlm zLny0OMarLY4s3`)Z+5^e!Eu(hrXY3JBzc)FUJ_j0b&~N>i`0Zo zHMd~6!+wUYg?qtit_$DI_%f7P4c@or(qyP1|^=Z;+G zzT&{pqk++f1EU*ae&9f1;6P?FGx=mdSig{!?#$o!{-XEG(| z0=h^2Cm!~n_;Sw!Z@&Lrq5m9J^`ytQa=y5&dv!SHk=M>6z>gT{u?J`W`b|BSd;f#N z`!nS@sGM@+^rNZArk{XFJ;JCR)@A#E{iYTAtGANmnMNOxUN`ql>zThqH7cEyEpv}O z+cJlLxOi(Z=j>&IrL3oRy$bejeW!kj?aZ}S{i}Rdr(8u@1J2TvqhZflKz&A5?PE)t zkHYJYNo%sVOYp!~5tC#gAMcVaZBd3LYu0Qf!9{=4Fd}K2d3us3KW&qEbJ}d39~bpl zvm{T$^954w8L60Rw$zV|Wq48__vEKByObD8V*r@ab#Dfg59F)+F-SL6SW7gtVJ1%w zo47T+XqzmE0`2ZYaDl0dk(pSyiteV^>}FFLvx*EIn_&uVQZ+kO8u#1vRUu57)DfDK z1f;IfT3Ae95$g{vJR~O0E#Qj@5Ls$709{A(S75J35U?W*|Iys=>C94Q=|OtMUF_^x zJ+a~}cK5Ab&7S@&w$f5;>A}vgxvkjayM6t~*NYv#wUM<~)M{J-v;{D0q{An03#|7G8wF<2&Vh}bL36DFTAEn(z|7ETKB_!!+M z7{(jkGSx5a6Al?gi7ufmPShk+3Xd`1Shl9agX0Ge35Smy6;2;Jeqyld{ne@I<^om4 z)=6VyE{LiSmm9gqVU`lZak=8>g69|x>H9W8nwPrW+4nx5~1>Zw?)vqQP)dGJLj#$&$QFis09oz?MPPn4}TE>tEnlpdMDLF+e`lrb`-$e^a=q%4R~CN1>+*%=3m?M) z-M4x%XWM~a-nOIYYQHs-QP*D2cHh}~zq7FGNUrBd!F4oeqhH>3v}AL1IF>Jz`Z#Y_ zarbbswWHMHvF|By2%fZclVq=jbR|?Dp4690JPgxcPye(8f91N*0^lp9z*Yys80_wcY#{%Y-*n zCwqC#N)#_w0~6WH2H9}t5>X6S0~6VBl}zWh6`3v@Z+{8?a{EgL2TE{oj?RMJSF)IM z`-~Jvn|+pF>nw5f-Xs)wlm*jr;N*^+C|TR=Lh&2lWN#`Wu%Nj8rS$-gOSX^e1aD5U zH^xHAUZJT{mxMj`LnRJDR)4X$DR_tH9Njs4S0sJEfyBIm(^B#tSI?1Qq{^mQkYchL z9ZJBcVVubFwn%uLCrv&%AEJG5R8-o3EA{*gvJ*g#7iI_+v_=pdG`tK@(QeL`|S;f(rbOn(#7BTv`MKyirxfGuDiY zNnCsghj1-rf-O4}I#Q@r+m#Vv2I#V^9U3HbGo{=(eM&`*@{@`ZQ!YU#ubz%dV*%sy z)D>ZPXmm)Rl}v=!%n$4%g#fb3hg}SOlAh_T4B92=0xp6{nLu;wHVcl}gk+Sia}aj9 zN}b3Wqp~InSO#&UVIEUw2FK-kH|frU%{yX+I)Z|p%{szP9Z?hL9YFzw3Tj_?c9d~YRxa;bW?66!8&G)W;P2rmS2Cf+Zr&RmrUs#ij?~3+k77b0aZmnWE zF&vVh%`1e$0g+kpIh`v;ZZa&f>nQSUVzhiW;+p(Q1wj&EWY>3~bvbG%Gz@Bh=JHJn z;Ckwtnt@_eua^o?2{_lvC0?x+aZ{R8I^;AQWl-LK(*1vzF1)jYSp71~Wx6R<#;aPp zIp^z`#%A!!uvcnuOXVB4Q-^3o=%)k4UE_4)bVrW;^f53+V*n(^Lu0^-f6wU5k#3oq z{d%}DN9&bkD%HPb%@cxh3q|WuF2i-HU0W7|!0>DZ0{ci_y59nD>zUlRTo`>l=b5CR zbCQWX5N|PaG_*F-cbwKeG%B4Uc{%6|+rH2{VKOH*Y^k0HYi@c>6hqN9axI zTZg(jsRsXmX2E5i5vp+!fo4l4LSg-oj(PeR`X&6%aIMx<{*Mj{oeyZ%cs!{&z79wo z9WTF6m)~bJ`x}ZZU)7wiFDkg46$-NdGN3h!;rP5rYqqwn;?oaQ({O_)6332MxkCJ# zjrpj9=O2(9%#br9!t_+DyBEf#CFOGv(_m5m0b$9?^E?n8?^*VMsdR5b>;w(^vcpI7sSZA97_KDjYTmXMp2q9uVYc}x%Xz*1W^(Jh%b@U%MvMxlqiXkEZGv}2W2+}5}-th1n3J; z4;t)ddYm%lQl??AJ*FLN8_v2DdfKk2Q{5FM)!tF2IvZ~$slnymU7}E>Elo{%liu2y zqRZ~2%Ue6M-+#`%z`Xz|%k9q0RLv!LaB=T>{O3Qf|NO7>M^0ynfa_j!Ec6fmLJ25lkpl~T3fkh4y`6@ta}@~u#d303Aw3{{RB zbH9S+p{ig7{wwich5u^&Q%R-48kAfetOfLS_@`$@!FrTdgOVPV?J?-}S-G!KR?WD1Hz(2p(vljicabvLc6~pkxVA~ocmV`EioO;O6O6{nn z=NrWDK>W?frzyCj2Y|4S6D&FSzp+A!NAXS5scuuHGeE)I06BzO9Z3`rpH?fU_;QE4Vw^u1%%hhg$VeP+fw1 zSdDZouuhHk>QfJQ1fO_C7~WwNjtRkiuL!~Yq0T=v0W>n z+i_EFd<=IZ?xs+8XlJNP*SB`%;*6Vf_i9{i9@@Nzce5V0SzjCWtV#b2y{-rSwKudU zv^$r7@BrY+9|;H7ct+#3BzWi*%kbWy>ym)n`p}1;KuP;pJN4^J3m$&#mN=qMIlRA^ zdTCp5K>vK5c08(&HQb}5KBj-3>{Z)Q`>pSB>y&U@pVROG2Hl|k={jjo=wl5ZWY9eV zW1)kyR*cEV>A@%Uv4#&73!y{9eM

EEsc-;?@S!~K|xPrpL=Kb(h~BU-$v_xd;m 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 literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/unicode.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/unicode.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f0356a1f6662bf54b26f983b3034367fad0985a GIT binary patch literal 15174 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 literal 0 HcmV?d00001 diff --git a/script.module.pyparsing/lib/pyparsing/__pycache__/util.cpython-311.pyc b/script.module.pyparsing/lib/pyparsing/__pycache__/util.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21643e76b8a8d39e80978dd45aabac9bae447ce8 GIT binary patch literal 16769 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 (). + + Example:: + + num = Word(nums).set_parse_action(lambda toks: int(toks[0])) + na = one_of("N/A NA").set_parse_action(replace_with(math.nan)) + term = na | num + + term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234] + """ + return lambda s, l, t: [repl_str] + + +def remove_quotes(s, l, t): + """ + Helper parse action for removing quotation marks from parsed + quoted strings. + + Example:: + + # by default, quotation marks are included in parsed results + quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] + + # use remove_quotes to strip quotation marks from parsed results + quoted_string.set_parse_action(remove_quotes) + quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] + """ + return t[0][1:-1] + + +def with_attribute(*args, **attr_dict): + """ + Helper to create a validating parse action to be used with start + tags created with :class:`make_xml_tags` or + :class:`make_html_tags`. Use ``with_attribute`` to qualify + a starting tag with a required attribute value, to avoid false + matches on common tags such as ```` or ``
``. + + Call ``with_attribute`` with a series of attribute names and + values. Specify the list of filter attributes names and values as: + + - keyword arguments, as in ``(align="right")``, or + - as an explicit dict with ``**`` operator, when an attribute + name is also a Python reserved word, as in ``**{"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 ``class`` (with or without a namespace), use + :class:`with_class`. + + To verify that the attribute exists, but without specifying a value, + pass ``with_attribute.ANY_VALUE`` as the value. + + Example:: + + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = make_html_tags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().set_parse_action(with_attribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().set_parse_action(with_attribute(type=with_attribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(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 = attr_dict.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 != with_attribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException( + s, + l, + f"attribute {attrName!r} has value {tokens[attrName]!r}, must be {attrValue!r}", + ) + + return pa + + +with_attribute.ANY_VALUE = object() # type: ignore [attr-defined] + + +def with_class(classname, namespace=""): + """ + Simplified version of :class:`with_attribute` when + matching on a div class - made difficult because ``class`` is + a reserved word in Python. + + Example:: + + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = make_html_tags("div") + div_grid = div().set_parse_action(with_class("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + div_any_type = div().set_parse_action(with_class(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(html): + print(div_header.body) + + prints:: + + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = f"{namespace}:class" if namespace else "class" + return with_attribute(**{classattr: classname}) + + +# pre-PEP8 compatibility symbols +# fmt: off +@replaced_by_pep8(replace_with) +def replaceWith(): ... + +@replaced_by_pep8(remove_quotes) +def removeQuotes(): ... + +@replaced_by_pep8(with_attribute) +def withAttribute(): ... + +@replaced_by_pep8(with_class) +def withClass(): ... + +@replaced_by_pep8(match_only_at_col) +def matchOnlyAtCol(): ... + +# fmt: on diff --git a/script.module.pyparsing/lib/pyparsing/common.py b/script.module.pyparsing/lib/pyparsing/common.py new file mode 100644 index 000000000..7a666b276 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/common.py @@ -0,0 +1,432 @@ +# common.py +from .core import * +from .helpers import DelimitedList, any_open_tag, any_close_tag +from datetime import datetime + + +# 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 (:class:`integers`, :class:`reals`, + :class:`scientific notation`) + - common :class:`programming identifiers` + - network addresses (:class:`MAC`, + :class:`IPv4`, :class:`IPv6`) + - ISO8601 :class:`dates` and + :class:`datetime` + - :class:`UUID` + - :class:`comma-separated list` + - :class:`url` + + Parse actions: + + - :class:`convert_to_integer` + - :class:`convert_to_float` + - :class:`convert_to_date` + - :class:`convert_to_datetime` + - :class:`strip_html_tags` + - :class:`upcase_tokens` + - :class:`downcase_tokens` + + Example:: + + pyparsing_common.number.run_tests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.run_tests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.run_tests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.run_tests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.run_tests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.set_parse_action(token_map(uuid.UUID)) + pyparsing_common.uuid.run_tests(''' + # 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')] + """ + + convert_to_integer = token_map(int) + """ + Parse action for converting parsed integers to Python int + """ + + convert_to_float = token_map(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).set_name("integer").set_parse_action(convert_to_integer) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = ( + Word(hexnums).set_name("hex integer").set_parse_action(token_map(int, 16)) + ) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = ( + Regex(r"[+-]?\d+") + .set_name("signed integer") + .set_parse_action(convert_to_integer) + ) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = ( + signed_integer().set_parse_action(convert_to_float) + + "/" + + signed_integer().set_parse_action(convert_to_float) + ).set_name("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.add_parse_action(lambda tt: tt[0] / tt[-1]) + + mixed_integer = ( + fraction | signed_integer + Opt(Opt("-").suppress() + fraction) + ).set_name("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.add_parse_action(sum) + + real = ( + Regex(r"[+-]?(?:\d+\.\d*|\.\d+)") + .set_name("real number") + .set_parse_action(convert_to_float) + ) + """expression that parses a floating point number and returns a float""" + + sci_real = ( + Regex(r"[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)") + .set_name("real number with scientific notation") + .set_parse_action(convert_to_float) + ) + """expression that parses a floating point number with optional + scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).setName("number").streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = ( + Regex(r"[+-]?\d+\.?\d*([eE][+-]?\d+)?") + .set_name("fnumber") + .set_parse_action(convert_to_float) + ) + """any int or real number, returned as float""" + + identifier = Word(identchars, identbodychars).set_name("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}" + ).set_name("IPv4 address") + "IPv4 address (``0.0.0.0 - 255.255.255.255``)" + + _ipv6_part = Regex(r"[0-9a-fA-F]{1,4}").set_name("hex_integer") + _full_ipv6_address = (_ipv6_part + (":" + _ipv6_part) * 7).set_name( + "full IPv6 address" + ) + _short_ipv6_address = ( + Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6)) + + "::" + + Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6)) + ).set_name("short IPv6 address") + _short_ipv6_address.add_condition( + lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8 + ) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).set_name("mixed IPv6 address") + ipv6_address = Combine( + (_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).set_name( + "IPv6 address" + ) + ).set_name("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}" + ).set_name("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convert_to_date(fmt: str = "%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= ``"%Y-%m-%d"``) + + Example:: + + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.set_parse_action(pyparsing_common.convert_to_date()) + print(date_expr.parse_string("1999-12-31")) + + prints:: + + [datetime.date(1999, 12, 31)] + """ + + def cvt_fn(ss, ll, tt): + try: + return datetime.strptime(tt[0], fmt).date() + except ValueError as ve: + raise ParseException(ss, ll, str(ve)) + + return cvt_fn + + @staticmethod + def convert_to_datetime(fmt: str = "%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= ``"%Y-%m-%dT%H:%M:%S.%f"``) + + Example:: + + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.set_parse_action(pyparsing_common.convert_to_datetime()) + print(dt_expr.parse_string("1999-12-31T23:59:59.999")) + + prints:: + + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + + def cvt_fn(s, l, t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + + return cvt_fn + + iso8601_date = Regex( + r"(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?" + ).set_name("ISO8601 date") + "ISO8601 date (``yyyy-mm-dd``)" + + iso8601_datetime = Regex( + r"(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?" + ).set_name("ISO8601 datetime") + "ISO8601 datetime (``yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)``) - trailing seconds, milliseconds, and timezone optional; accepts separating ``'T'`` or ``' '``" + + uuid = Regex(r"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}").set_name("UUID") + "UUID (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``)" + + _html_stripper = any_open_tag.suppress() | any_close_tag.suppress() + + @staticmethod + def strip_html_tags(s: str, l: int, tokens: ParseResults): + """Parse action to remove HTML tags from web page HTML source + + Example:: + + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td, td_end = make_html_tags("TD") + table_text = td + SkipTo(td_end).set_parse_action(pyparsing_common.strip_html_tags)("body") + td_end + print(table_text.parse_string(text).body) + + Prints:: + + More info at the pyparsing wiki page + """ + return pyparsing_common._html_stripper.transform_string(tokens[0]) + + _commasepitem = ( + Combine( + OneOrMore( + ~Literal(",") + + ~LineEnd() + + Word(printables, exclude_chars=",") + + Opt(White(" \t") + ~FollowedBy(LineEnd() | ",")) + ) + ) + .streamline() + .set_name("commaItem") + ) + comma_separated_list = DelimitedList( + Opt(quoted_string.copy() | _commasepitem, default="") + ).set_name("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcase_tokens = staticmethod(token_map(lambda t: t.upper())) + """Parse action to convert tokens to upper case.""" + + downcase_tokens = staticmethod(token_map(lambda t: t.lower())) + """Parse action to convert tokens to lower case.""" + + # fmt: off + url = Regex( + # https://mathiasbynens.be/demo/url-regex + # https://gist.github.com/dperini/729294 + r"(?P" + + # protocol identifier (optional) + # short syntax // still required + r"(?:(?:(?Phttps?|ftp):)?\/\/)" + + # user:pass BasicAuth (optional) + r"(?:(?P\S+(?::\S*)?)@)?" + + r"(?P" + + # IP address exclusion + # private & local networks + r"(?!(?:10|127)(?:\.\d{1,3}){3})" + + r"(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})" + + r"(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})" + + # IP address dotted notation octets + # excludes loopback network 0.0.0.0 + # excludes reserved space >= 224.0.0.0 + # excludes network & broadcast addresses + # (first & last IP address of each class) + r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" + + r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}" + + r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" + + r"|" + + # host & domain names, may end with dot + # can be replaced by a shortest alternative + # (?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.)+ + r"(?:" + + r"(?:" + + r"[a-z0-9\u00a1-\uffff]" + + r"[a-z0-9\u00a1-\uffff_-]{0,62}" + + r")?" + + r"[a-z0-9\u00a1-\uffff]\." + + r")+" + + # TLD identifier name, may end with dot + r"(?:[a-z\u00a1-\uffff]{2,}\.?)" + + r")" + + # port number (optional) + r"(:(?P\d{2,5}))?" + + # resource path (optional) + r"(?P\/[^?# ]*)?" + + # query string (optional) + r"(\?(?P[^#]*))?" + + # fragment (optional) + r"(#(?P\S*))?" + + r")" + ).set_name("url") + """URL (http/https/ftp scheme)""" + # fmt: on + + # pre-PEP8 compatibility names + convertToInteger = convert_to_integer + """Deprecated - use :class:`convert_to_integer`""" + convertToFloat = convert_to_float + """Deprecated - use :class:`convert_to_float`""" + convertToDate = convert_to_date + """Deprecated - use :class:`convert_to_date`""" + convertToDatetime = convert_to_datetime + """Deprecated - use :class:`convert_to_datetime`""" + stripHTMLTags = strip_html_tags + """Deprecated - use :class:`strip_html_tags`""" + upcaseTokens = upcase_tokens + """Deprecated - use :class:`upcase_tokens`""" + downcaseTokens = downcase_tokens + """Deprecated - use :class:`downcase_tokens`""" + + +_builtin_exprs = [ + v for v in vars(pyparsing_common).values() if isinstance(v, ParserElement) +] diff --git a/script.module.pyparsing/lib/pyparsing/core.py b/script.module.pyparsing/lib/pyparsing/core.py new file mode 100644 index 000000000..73514ed09 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/core.py @@ -0,0 +1,6159 @@ +# +# core.py +# + +from collections import deque +import os +import typing +from typing import ( + Any, + Callable, + Generator, + List, + NamedTuple, + Sequence, + Set, + TextIO, + Tuple, + Union, + cast, +) +from abc import ABC, abstractmethod +from enum import Enum +import string +import copy +import warnings +import re +import sys +from collections.abc import Iterable +import traceback +import types +from operator import itemgetter +from functools import wraps +from threading import RLock +from pathlib import Path + +from .util import ( + _FifoCache, + _UnboundedCache, + __config_flags, + _collapse_string_to_ranges, + _escape_regex_range_chars, + _bslash, + _flatten, + LRUMemo as _LRUMemo, + UnboundedMemo as _UnboundedMemo, + replaced_by_pep8, +) +from .exceptions import * +from .actions import * +from .results import ParseResults, _ParseResultsWithOffset +from .unicode import pyparsing_unicode + +_MAX_INT = sys.maxsize +str_type: Tuple[type, ...] = (str, bytes) + +# +# Copyright (c) 2003-2022 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. +# + + +if sys.version_info >= (3, 8): + from functools import cached_property +else: + + class cached_property: + def __init__(self, func): + self._func = func + + def __get__(self, instance, owner=None): + ret = instance.__dict__[self._func.__name__] = self._func(instance) + return ret + + +class __compat__(__config_flags): + """ + A cross-version compatibility configuration for pyparsing features that will be + released in a future version. By setting values in this configuration to True, + those features can be enabled in prior versions for compatibility development + and testing. + + - ``collect_all_And_tokens`` - flag to enable fix for Issue #63 that fixes erroneous grouping + of results names when an :class:`And` expression is nested within an :class:`Or` or :class:`MatchFirst`; + maintained for compatibility, but setting to ``False`` no longer restores pre-2.3.1 + behavior + """ + + _type_desc = "compatibility" + + collect_all_And_tokens = True + + _all_names = [__ for __ in locals() if not __.startswith("_")] + _fixed_names = """ + collect_all_And_tokens + """.split() + + +class __diag__(__config_flags): + _type_desc = "diagnostic" + + warn_multiple_tokens_in_named_alternation = False + warn_ungrouped_named_tokens_in_collection = False + warn_name_set_on_empty_Forward = False + warn_on_parse_using_empty_Forward = False + warn_on_assignment_to_Forward = False + warn_on_multiple_string_args_to_oneof = False + warn_on_match_first_with_lshift_operator = False + enable_debug_on_named_expressions = False + + _all_names = [__ for __ in locals() if not __.startswith("_")] + _warning_names = [name for name in _all_names if name.startswith("warn")] + _debug_names = [name for name in _all_names if name.startswith("enable_debug")] + + @classmethod + def enable_all_warnings(cls) -> None: + for name in cls._warning_names: + cls.enable(name) + + +class Diagnostics(Enum): + """ + Diagnostic configuration (all default to disabled) + + - ``warn_multiple_tokens_in_named_alternation`` - flag to enable warnings when a results + name is defined on a :class:`MatchFirst` or :class:`Or` expression with one or more :class:`And` subexpressions + - ``warn_ungrouped_named_tokens_in_collection`` - flag to enable warnings when a results + name is defined on a containing expression with ungrouped subexpressions that also + have results names + - ``warn_name_set_on_empty_Forward`` - flag to enable warnings when a :class:`Forward` is defined + with a results name, but has no contents defined + - ``warn_on_parse_using_empty_Forward`` - flag to enable warnings when a :class:`Forward` is + defined in a grammar but has never had an expression attached to it + - ``warn_on_assignment_to_Forward`` - flag to enable warnings when a :class:`Forward` is defined + but is overwritten by assigning using ``'='`` instead of ``'<<='`` or ``'<<'`` + - ``warn_on_multiple_string_args_to_oneof`` - flag to enable warnings when :class:`one_of` is + incorrectly called with multiple str arguments + - ``enable_debug_on_named_expressions`` - flag to auto-enable debug on all subsequent + calls to :class:`ParserElement.set_name` + + Diagnostics are enabled/disabled by calling :class:`enable_diag` and :class:`disable_diag`. + All warnings can be enabled by calling :class:`enable_all_warnings`. + """ + + warn_multiple_tokens_in_named_alternation = 0 + warn_ungrouped_named_tokens_in_collection = 1 + warn_name_set_on_empty_Forward = 2 + warn_on_parse_using_empty_Forward = 3 + warn_on_assignment_to_Forward = 4 + warn_on_multiple_string_args_to_oneof = 5 + warn_on_match_first_with_lshift_operator = 6 + enable_debug_on_named_expressions = 7 + + +def enable_diag(diag_enum: Diagnostics) -> None: + """ + Enable a global pyparsing diagnostic flag (see :class:`Diagnostics`). + """ + __diag__.enable(diag_enum.name) + + +def disable_diag(diag_enum: Diagnostics) -> None: + """ + Disable a global pyparsing diagnostic flag (see :class:`Diagnostics`). + """ + __diag__.disable(diag_enum.name) + + +def enable_all_warnings() -> None: + """ + Enable all global pyparsing diagnostic warnings (see :class:`Diagnostics`). + """ + __diag__.enable_all_warnings() + + +# hide abstract class +del __config_flags + + +def _should_enable_warnings( + cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str] +) -> bool: + enable = bool(warn_env_var) + for warn_opt in cmd_line_warn_options: + w_action, w_message, w_category, w_module, w_line = (warn_opt + "::::").split( + ":" + )[:5] + if not w_action.lower().startswith("i") and ( + not (w_message or w_category or w_module) or w_module == "pyparsing" + ): + enable = True + elif w_action.lower().startswith("i") and w_module in ("pyparsing", ""): + enable = False + return enable + + +if _should_enable_warnings( + sys.warnoptions, os.environ.get("PYPARSINGENABLEALLWARNINGS") +): + enable_all_warnings() + + +# build list of single arg builtins, that can be used as parse actions +_single_arg_builtins = { + sum, + len, + sorted, + reversed, + list, + tuple, + set, + any, + all, + min, + max, +} + +_generatorType = types.GeneratorType +ParseImplReturnType = Tuple[int, Any] +PostParseReturnType = Union[ParseResults, Sequence[ParseResults]] +ParseAction = Union[ + Callable[[], Any], + Callable[[ParseResults], Any], + Callable[[int, ParseResults], Any], + Callable[[str, int, ParseResults], Any], +] +ParseCondition = Union[ + Callable[[], bool], + Callable[[ParseResults], bool], + Callable[[int, ParseResults], bool], + Callable[[str, int, ParseResults], bool], +] +ParseFailAction = Callable[[str, int, "ParserElement", Exception], None] +DebugStartAction = Callable[[str, int, "ParserElement", bool], None] +DebugSuccessAction = Callable[ + [str, int, int, "ParserElement", ParseResults, bool], None +] +DebugExceptionAction = Callable[[str, int, "ParserElement", Exception, bool], None] + + +alphas = string.ascii_uppercase + string.ascii_lowercase +identchars = pyparsing_unicode.Latin1.identchars +identbodychars = pyparsing_unicode.Latin1.identbodychars +nums = "0123456789" +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +printables = "".join([c for c in string.printable if c not in string.whitespace]) + +_trim_arity_call_line: traceback.StackSummary = None # type: ignore[assignment] + + +def _trim_arity(func, max_limit=3): + """decorator to trim function calls to match the arity of the target""" + global _trim_arity_call_line + + if func in _single_arg_builtins: + return lambda s, l, t: func(t) + + limit = 0 + found_arity = False + + # 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 + + # fmt: off + LINE_DIFF = 7 + # 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!!!! + _trim_arity_call_line = (_trim_arity_call_line or traceback.extract_stack(limit=2)[-1]) + pa_call_line_synth = (_trim_arity_call_line[0], _trim_arity_call_line[1] + LINE_DIFF) + + def wrapper(*args): + nonlocal found_arity, limit + while 1: + try: + ret = func(*args[limit:]) + found_arity = True + return ret + except TypeError as te: + # re-raise TypeErrors if they did not come from our arity testing + if found_arity: + raise + else: + tb = te.__traceback__ + frames = traceback.extract_tb(tb, limit=2) + frame_summary = frames[-1] + trim_arity_type_error = ( + [frame_summary[:2]][-1][:2] == pa_call_line_synth + ) + del tb + + if trim_arity_type_error: + if limit < max_limit: + limit += 1 + continue + + raise + # fmt: on + + # copy func name to wrapper for sensible debug output + # (can't use functools.wraps, since that messes with function signature) + func_name = getattr(func, "__name__", getattr(func, "__class__").__name__) + wrapper.__name__ = func_name + wrapper.__doc__ = func.__doc__ + + return wrapper + + +def condition_as_parse_action( + fn: ParseCondition, message: typing.Optional[str] = None, fatal: bool = False +) -> ParseAction: + """ + Function to convert a simple predicate function that returns ``True`` or ``False`` + into a parse action. Can be used in places when a parse action is required + and :class:`ParserElement.add_condition` cannot be used (such as when adding a condition + to an operator level in :class:`infix_notation`). + + Optional keyword arguments: + + - ``message`` - define a custom message to be used in the raised exception + - ``fatal`` - if True, will raise :class:`ParseFatalException` to stop parsing immediately; + otherwise will raise :class:`ParseException` + + """ + msg = message if message is not None else "failed user-defined condition" + exc_type = ParseFatalException if fatal else ParseException + fn = _trim_arity(fn) + + @wraps(fn) + def pa(s, l, t): + if not bool(fn(s, l, t)): + raise exc_type(s, l, msg) + + return pa + + +def _default_start_debug_action( + instring: str, loc: int, expr: "ParserElement", cache_hit: bool = False +): + cache_hit_str = "*" if cache_hit else "" + print( + ( + f"{cache_hit_str}Match {expr} at loc {loc}({lineno(loc, instring)},{col(loc, instring)})\n" + f" {line(loc, instring)}\n" + f" {' ' * (col(loc, instring) - 1)}^" + ) + ) + + +def _default_success_debug_action( + instring: str, + startloc: int, + endloc: int, + expr: "ParserElement", + toks: ParseResults, + cache_hit: bool = False, +): + cache_hit_str = "*" if cache_hit else "" + print(f"{cache_hit_str}Matched {expr} -> {toks.as_list()}") + + +def _default_exception_debug_action( + instring: str, + loc: int, + expr: "ParserElement", + exc: Exception, + cache_hit: bool = False, +): + cache_hit_str = "*" if cache_hit else "" + print(f"{cache_hit_str}Match {expr} failed, {type(exc).__name__} raised: {exc}") + + +def null_debug_action(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + + +class ParserElement(ABC): + """Abstract base level parser element class.""" + + DEFAULT_WHITE_CHARS: str = " \n\t\r" + verbose_stacktrace: bool = False + _literalStringClass: type = None # type: ignore[assignment] + + @staticmethod + def set_default_whitespace_chars(chars: str) -> None: + r""" + Overrides the default whitespace chars + + Example:: + + # default whitespace chars are space, and newline + Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] + + # change to just treat newline as significant + ParserElement.set_default_whitespace_chars(" \t") + Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def'] + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + + # update whitespace all parse expressions defined in this module + for expr in _builtin_exprs: + if expr.copyDefaultWhiteChars: + expr.whiteChars = set(chars) + + @staticmethod + def inline_literals_using(cls: type) -> None: + """ + 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.parse_string("1999/12/31") # -> ['1999', '/', '12', '/', '31'] + + + # change to Suppress + ParserElement.inline_literals_using(Suppress) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + date_str.parse_string("1999/12/31") # -> ['1999', '12', '31'] + """ + ParserElement._literalStringClass = cls + + @classmethod + def using_each(cls, seq, **class_kwargs): + """ + Yields a sequence of class(obj, **class_kwargs) for obj in seq. + + Example:: + + LPAR, RPAR, LBRACE, RBRACE, SEMI = Suppress.using_each("(){};") + + """ + yield from (cls(obj, **class_kwargs) for obj in seq) + + class DebugActions(NamedTuple): + debug_try: typing.Optional[DebugStartAction] + debug_match: typing.Optional[DebugSuccessAction] + debug_fail: typing.Optional[DebugExceptionAction] + + def __init__(self, savelist: bool = False): + self.parseAction: List[ParseAction] = list() + self.failAction: typing.Optional[ParseFailAction] = None + self.customName: str = None # type: ignore[assignment] + self._defaultName: typing.Optional[str] = None + self.resultsName: str = None # type: ignore[assignment] + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS) + self.copyDefaultWhiteChars = True + # used when checking for left-recursion + self.mayReturnEmpty = False + self.keepTabs = False + self.ignoreExprs: List["ParserElement"] = list() + self.debug = False + self.streamlined = False + # optimize exception handling for subclasses that don't advance parse index + self.mayIndexError = True + self.errmsg = "" + # mark results names as modal (report only last) or cumulative (list all) + self.modalResults = True + # custom debug actions + self.debugActions = self.DebugActions(None, None, None) + # avoid redundant calls to preParse + self.callPreparse = True + self.callDuringTry = False + self.suppress_warnings_: List[Diagnostics] = [] + + def suppress_warning(self, warning_type: Diagnostics) -> "ParserElement": + """ + Suppress warnings emitted for a particular diagnostic on this expression. + + Example:: + + base = pp.Forward() + base.suppress_warning(Diagnostics.warn_on_parse_using_empty_Forward) + + # statement would normally raise a warning, but is now suppressed + print(base.parse_string("x")) + + """ + self.suppress_warnings_.append(warning_type) + return self + + def visit_all(self): + """General-purpose method to yield all expressions and sub-expressions + in a grammar. Typically just for internal use. + """ + to_visit = deque([self]) + seen = set() + while to_visit: + cur = to_visit.popleft() + + # guard against looping forever through recursive grammars + if cur in seen: + continue + seen.add(cur) + + to_visit.extend(cur.recurse()) + yield cur + + def copy(self) -> "ParserElement": + """ + Make a copy of this :class:`ParserElement`. Useful for defining + different parse actions for the same parsing pattern, using copies of + the original parse element. + + Example:: + + integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) + integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K") + integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") + + print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M")) + + prints:: + + [5120, 100, 655360, 268435456] + + Equivalent form of ``expr.copy()`` is just ``expr()``:: + + integerM = integer().add_parse_action(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 = set(ParserElement.DEFAULT_WHITE_CHARS) + return cpy + + def set_results_name( + self, name: str, list_all_matches: bool = False, *, listAllMatches: bool = False + ) -> "ParserElement": + """ + Define name for referencing matching tokens as a nested attribute + of the returned parse results. + + Normally, results names are assigned as you would assign keys in a dict: + any existing value is overwritten by later values. If it is necessary to + keep all values captured for a particular results name, call ``set_results_name`` + with ``list_all_matches`` = True. + + NOTE: ``set_results_name`` returns a *copy* of the original :class:`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, + ``expr("name")`` in place of ``expr.set_results_name("name")`` + - see :class:`__call__`. If ``list_all_matches`` is required, use + ``expr("name*")``. + + Example:: + + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + + # equivalent form: + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + """ + listAllMatches = listAllMatches or list_all_matches + return self._setResultsName(name, listAllMatches) + + def _setResultsName(self, name, listAllMatches=False): + if name is None: + return self + newself = self.copy() + if name.endswith("*"): + name = name[:-1] + listAllMatches = True + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def set_break(self, break_flag: bool = True) -> "ParserElement": + """ + Method to invoke the Python pdb debugger when this element is + about to be parsed. Set ``break_flag`` to ``True`` to enable, ``False`` to + disable. + """ + if break_flag: + _parseMethod = self._parse + + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + + # this call to pdb.set_trace() is intentional, not a checkin error + pdb.set_trace() + return _parseMethod(instring, loc, doActions, callPreParse) + + breaker._originalParseMethod = _parseMethod # type: ignore [attr-defined] + self._parse = breaker # type: ignore [assignment] + else: + if hasattr(self._parse, "_originalParseMethod"): + self._parse = self._parse._originalParseMethod # type: ignore [attr-defined, assignment] + return self + + def set_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement": + """ + Define one or more actions to perform when successfully matching parse element definition. + + Parse actions can be called to perform data conversions, do extra validation, + update external data structures, or enhance or replace the parsed tokens. + Each parse action ``fn`` is a callable method with 0-3 arguments, called as + ``fn(s, loc, toks)`` , ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where: + + - ``s`` = the original string being parsed (see note below) + - ``loc`` = the location of the matching substring + - ``toks`` = a list of the matched tokens, packaged as a :class:`ParseResults` object + + The parsed tokens are passed to the parse action as ParseResults. They can be + modified in place using list-style append, extend, and pop operations to update + the parsed list elements; and with dictionary-style item set and del operations + to add, update, or remove any named results. If the tokens are modified in place, + it is not necessary to return them with a return statement. + + Parse actions can also completely replace the given tokens, with another ``ParseResults`` + object, or with some entirely different object (common for parse actions that perform data + conversions). A convenient way to build a new parse result is to define the values + using a dict, and then create the return value using :class:`ParseResults.from_dict`. + + If None is passed as the ``fn`` parse action, all previously added parse actions for this + expression are cleared. + + Optional keyword arguments: + + - ``call_during_try`` = (default= ``False``) indicate if parse action should be run during + lookaheads and alternate testing. For parse actions that have side effects, it is + important to only call the parse action once it is determined that it is being + called as part of a successful parse. For parse actions that perform additional + validation, then call_during_try should be passed as True, so that the validation + code is included in the preliminary "try" parses. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See :class:`parse_string` for more + information on parsing strings containing ```` 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:: + + # parse dates in the form YYYY/MM/DD + + # use parse action to convert toks from str to int at parse time + def convert_to_int(toks): + return int(toks[0]) + + # use a parse action to verify that the date is a valid date + def is_valid_date(instring, loc, toks): + from datetime import date + year, month, day = toks[::2] + try: + date(year, month, day) + except ValueError: + raise ParseException(instring, loc, "invalid date given") + + integer = Word(nums) + date_str = integer + '/' + integer + '/' + integer + + # add parse actions + integer.set_parse_action(convert_to_int) + date_str.set_parse_action(is_valid_date) + + # note that integer fields are now ints, not strings + date_str.run_tests(''' + # successful parse - note that integer fields were converted to ints + 1999/12/31 + + # fail - invalid date + 1999/13/31 + ''') + """ + if list(fns) == [None]: + self.parseAction = [] + else: + if not all(callable(fn) for fn in fns): + raise TypeError("parse actions must be callable") + self.parseAction = [_trim_arity(fn) for fn in fns] + self.callDuringTry = kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def add_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement": + """ + Add one or more parse actions to expression's list of parse actions. See :class:`set_parse_action`. + + See examples in :class:`copy`. + """ + self.parseAction += [_trim_arity(fn) for fn in fns] + self.callDuringTry = self.callDuringTry or kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def add_condition(self, *fns: ParseCondition, **kwargs) -> "ParserElement": + """Add a boolean predicate function to expression's list of parse actions. See + :class:`set_parse_action` for function call signatures. Unlike ``set_parse_action``, + functions passed to ``add_condition`` 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 + - ``call_during_try`` = boolean to indicate if this method should be called during internal tryParse calls, + default=False + + Example:: + + integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) + year_int = integer.copy() + year_int.add_condition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") + date_str = year_int + '/' + integer + '/' + integer + + result = date_str.parse_string("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), + (line:1, col:1) + """ + for fn in fns: + self.parseAction.append( + condition_as_parse_action( + fn, + message=str(kwargs.get("message")), + fatal=bool(kwargs.get("fatal", False)), + ) + ) + + self.callDuringTry = self.callDuringTry or kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def set_fail_action(self, fn: ParseFailAction) -> "ParserElement": + """ + Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + ``fn(s, loc, expr, err)`` where: + + - ``s`` = string being parsed + - ``loc`` = location where expression match was attempted and failed + - ``expr`` = the parse expression that failed + - ``err`` = the exception thrown + + The function returns no value. It may throw :class:`ParseFatalException` + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables(self, instring: str, loc: int) -> int: + if not self.ignoreExprs: + return loc + exprsFound = True + ignore_expr_fns = [e._parse for e in self.ignoreExprs] + last_loc = loc + while exprsFound: + exprsFound = False + for ignore_fn in ignore_expr_fns: + try: + while 1: + loc, dummy = ignore_fn(instring, loc) + exprsFound = True + except ParseException: + pass + # check if all ignore exprs matched but didn't actually advance the parse location + if loc == last_loc: + break + last_loc = loc + return loc + + def preParse(self, instring: str, loc: int) -> int: + if self.ignoreExprs: + loc = self._skipIgnorables(instring, loc) + + if self.skipWhitespace: + instrlen = len(instring) + white_chars = self.whiteChars + while loc < instrlen and instring[loc] in white_chars: + 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 + ) -> Tuple[int, ParseResults]: + TRY, MATCH, FAIL = 0, 1, 2 + debugging = self.debug # and doActions) + len_instring = len(instring) + + if debugging or self.failAction: + # print("Match {} at loc {}({}, {})".format(self, loc, lineno(loc, instring), col(loc, instring))) + try: + if callPreParse and self.callPreparse: + pre_loc = self.preParse(instring, loc) + else: + pre_loc = loc + tokens_start = pre_loc + if self.debugActions.debug_try: + self.debugActions.debug_try(instring, tokens_start, self, False) + if self.mayIndexError or pre_loc >= len_instring: + try: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except IndexError: + raise ParseException(instring, len_instring, self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except Exception as err: + # print("Exception raised:", err) + if self.debugActions.debug_fail: + self.debugActions.debug_fail( + instring, tokens_start, self, err, False + ) + if self.failAction: + self.failAction(instring, tokens_start, self, err) + raise + else: + if callPreParse and self.callPreparse: + pre_loc = self.preParse(instring, loc) + else: + pre_loc = loc + tokens_start = pre_loc + if self.mayIndexError or pre_loc >= len_instring: + try: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except IndexError: + raise ParseException(instring, len_instring, self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + + tokens = self.postParse(instring, loc, tokens) + + ret_tokens = 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: + try: + tokens = fn(instring, tokens_start, ret_tokens) # type: ignore [call-arg, arg-type] + except IndexError as parse_action_exc: + exc = ParseException("exception raised in parse action") + raise exc from parse_action_exc + + if tokens is not None and tokens is not ret_tokens: + ret_tokens = ParseResults( + tokens, + self.resultsName, + asList=self.saveAsList + and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults, + ) + except Exception as err: + # print "Exception raised in user parse action:", err + if self.debugActions.debug_fail: + self.debugActions.debug_fail( + instring, tokens_start, self, err, False + ) + raise + else: + for fn in self.parseAction: + try: + tokens = fn(instring, tokens_start, ret_tokens) # type: ignore [call-arg, arg-type] + except IndexError as parse_action_exc: + exc = ParseException("exception raised in parse action") + raise exc from parse_action_exc + + if tokens is not None and tokens is not ret_tokens: + ret_tokens = ParseResults( + tokens, + self.resultsName, + asList=self.saveAsList + and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults, + ) + if debugging: + # print("Matched", self, "->", ret_tokens.as_list()) + if self.debugActions.debug_match: + self.debugActions.debug_match( + instring, tokens_start, loc, self, ret_tokens, False + ) + + return loc, ret_tokens + + def try_parse( + self, + instring: str, + loc: int, + *, + raise_fatal: bool = False, + do_actions: bool = False, + ) -> int: + try: + return self._parse(instring, loc, doActions=do_actions)[0] + except ParseFatalException: + if raise_fatal: + raise + raise ParseException(instring, loc, self.errmsg, self) + + def can_parse_next(self, instring: str, loc: int, do_actions: bool = False) -> bool: + try: + self.try_parse(instring, loc, do_actions=do_actions) + except (ParseException, IndexError): + return False + else: + return True + + # cache for left-recursion in Forward references + recursion_lock = RLock() + recursion_memos: typing.Dict[ + Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]] + ] = {} + + class _CacheType(dict): + """ + class to help type checking + """ + + not_in_cache: bool + + def get(self, *args): + ... + + def set(self, *args): + ... + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + packrat_cache = ( + _CacheType() + ) # set later by enable_packrat(); this is here so that reset_cache() 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 + ) -> Tuple[int, ParseResults]: + HIT, MISS = 0, 1 + TRY, MATCH, FAIL = 0, 1, 2 + 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(), loc)) + return value + else: + ParserElement.packrat_cache_stats[HIT] += 1 + if self.debug and self.debugActions.debug_try: + try: + self.debugActions.debug_try(instring, loc, self, cache_hit=True) # type: ignore [call-arg] + except TypeError: + pass + if isinstance(value, Exception): + if self.debug and self.debugActions.debug_fail: + try: + self.debugActions.debug_fail( + instring, loc, self, value, cache_hit=True # type: ignore [call-arg] + ) + except TypeError: + pass + raise value + + value = cast(Tuple[int, ParseResults, int], value) + loc_, result, endloc = value[0], value[1].copy(), value[2] + if self.debug and self.debugActions.debug_match: + try: + self.debugActions.debug_match( + instring, loc_, endloc, self, result, cache_hit=True # type: ignore [call-arg] + ) + except TypeError: + pass + + return loc_, result + + _parse = _parseNoCache + + @staticmethod + def reset_cache() -> None: + ParserElement.packrat_cache.clear() + ParserElement.packrat_cache_stats[:] = [0] * len( + ParserElement.packrat_cache_stats + ) + ParserElement.recursion_memos.clear() + + _packratEnabled = False + _left_recursion_enabled = False + + @staticmethod + def disable_memoization() -> None: + """ + Disables active Packrat or Left Recursion parsing and their memoization + + This method also works if neither Packrat nor Left Recursion are enabled. + This makes it safe to call before activating Packrat nor Left Recursion + to clear any previous settings. + """ + ParserElement.reset_cache() + ParserElement._left_recursion_enabled = False + ParserElement._packratEnabled = False + ParserElement._parse = ParserElement._parseNoCache + + @staticmethod + def enable_left_recursion( + cache_size_limit: typing.Optional[int] = None, *, force=False + ) -> None: + """ + Enables "bounded recursion" parsing, which allows for both direct and indirect + left-recursion. During parsing, left-recursive :class:`Forward` elements are + repeatedly matched with a fixed recursion depth that is gradually increased + until finding the longest match. + + Example:: + + import pyparsing as pp + pp.ParserElement.enable_left_recursion() + + E = pp.Forward("E") + num = pp.Word(pp.nums) + # match `num`, or `num '+' num`, or `num '+' num '+' num`, ... + E <<= E + '+' - num | num + + print(E.parse_string("1+2+3")) + + Recursion search naturally memoizes matches of ``Forward`` elements and may + thus skip reevaluation of parse actions during backtracking. This may break + programs with parse actions which rely on strict ordering of side-effects. + + Parameters: + + - ``cache_size_limit`` - (default=``None``) - memoize at most this many + ``Forward`` elements during matching; if ``None`` (the default), + memoize all ``Forward`` elements. + + Bounded Recursion parsing works similar but not identical to Packrat parsing, + thus the two cannot be used together. Use ``force=True`` to disable any + previous, conflicting settings. + """ + if force: + ParserElement.disable_memoization() + elif ParserElement._packratEnabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") + if cache_size_limit is None: + ParserElement.recursion_memos = _UnboundedMemo() # type: ignore[assignment] + elif cache_size_limit > 0: + ParserElement.recursion_memos = _LRUMemo(capacity=cache_size_limit) # type: ignore[assignment] + else: + raise NotImplementedError("Memo size of %s" % cache_size_limit) + ParserElement._left_recursion_enabled = True + + @staticmethod + def enable_packrat( + cache_size_limit: Union[int, None] = 128, *, force: bool = False + ) -> None: + """ + 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= ``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 :class:`ParserElement.enable_packrat`. + For best results, call ``enable_packrat()`` immediately after + importing pyparsing. + + Example:: + + import pyparsing + pyparsing.ParserElement.enable_packrat() + + Packrat parsing works similar but not identical to Bounded Recursion parsing, + thus the two cannot be used together. Use ``force=True`` to disable any + previous, conflicting settings. + """ + if force: + ParserElement.disable_memoization() + elif ParserElement._left_recursion_enabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + if cache_size_limit is None: + ParserElement.packrat_cache = _UnboundedCache() + else: + ParserElement.packrat_cache = _FifoCache(cache_size_limit) # type: ignore[assignment] + ParserElement._parse = ParserElement._parseCache + + def parse_string( + self, instring: str, parse_all: bool = False, *, parseAll: bool = False + ) -> ParseResults: + """ + Parse a string with respect to the parser definition. This function is intended as the primary interface to the + client code. + + :param instring: The input string to be parsed. + :param parse_all: If set, the entire input string must match the grammar. + :param parseAll: retained for pre-PEP8 compatibility, will be removed in a future release. + :raises ParseException: Raised if ``parse_all`` is set and the input string does not match the whole grammar. + :returns: the parsed data as a :class:`ParseResults` object, which may be accessed as a `list`, a `dict`, or + an object with attributes if the given parser includes results names. + + If the input string is required to match the entire grammar, ``parse_all`` flag must be set to ``True``. This + is also equivalent to ending the grammar with :class:`StringEnd`\\ (). + + To report proper column numbers, ``parse_string`` operates on a copy of the input string where all tabs are + converted to spaces (8 spaces per tab, as per the default in ``string.expandtabs``). If the input string + contains tabs and the grammar uses parse actions that use the ``loc`` argument to index into the string + being parsed, one can ensure a consistent view of the input string by doing one of the following: + + - calling ``parse_with_tabs`` on your grammar before calling ``parse_string`` (see :class:`parse_with_tabs`), + - define your parse action using the full ``(s,loc,toks)`` signature, and reference the input string using the + parse action's ``s`` argument, or + - explicitly expand the tabs in your input string before calling ``parse_string``. + + Examples: + + By default, partial matches are OK. + + >>> res = Word('a').parse_string('aaaaabaaa') + >>> print(res) + ['aaaaa'] + + The parsing behavior varies by the inheriting class of this abstract class. Please refer to the children + directly to see more examples. + + It raises an exception if parse_all flag is set and instring does not match the whole grammar. + + >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True) + Traceback (most recent call last): + ... + pyparsing.ParseException: Expected end of text, found 'b' (at char 5), (line:1, col:6) + """ + parseAll = parse_all or parseAll + + ParserElement.reset_cache() + if not self.streamlined: + self.streamline() + 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, clearing out pyparsing internal stack trace + raise exc.with_traceback(None) + else: + return tokens + + def scan_string( + self, + instring: str, + max_matches: int = _MAX_INT, + overlap: bool = False, + *, + debug: bool = False, + maxMatches: int = _MAX_INT, + ) -> Generator[Tuple[ParseResults, int, int], None, None]: + """ + Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + ``max_matches`` argument, to clip scanning after 'n' matches are found. If + ``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 :class:`parse_string` for more information on parsing + strings with embedded tabs. + + Example:: + + source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" + print(source) + for tokens, start, end in Word(alphas).scan_string(source): + print(' '*start + '^'*(end-start)) + print(' '*start + tokens[0]) + + prints:: + + sldjf123lsdjjkf345sldkjf879lkjsfd987 + ^^^^^ + sldjf + ^^^^^^^ + lsdjjkf + ^^^^^^ + sldkjf + ^^^^^^ + lkjsfd + """ + maxMatches = min(maxMatches, max_matches) + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = str(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: int = preparseFn(instring, loc) + nextLoc: int + tokens: ParseResults + nextLoc, tokens = parseFn(instring, preloc, callPreParse=False) + except ParseException: + loc = preloc + 1 + else: + if nextLoc > loc: + matches += 1 + if debug: + print( + { + "tokens": tokens.asList(), + "start": preloc, + "end": nextLoc, + } + ) + 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.with_traceback(None) + + def transform_string(self, instring: str, *, debug: bool = False) -> str: + """ + Extension to :class:`scan_string`, to modify matching text with modified tokens that may + be returned from a parse action. To use ``transform_string``, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking ``transform_string()`` on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. ``transform_string()`` returns the resulting transformed string. + + Example:: + + wd = Word(alphas) + wd.set_parse_action(lambda toks: toks[0].title()) + + print(wd.transform_string("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: List[str] = [] + lastE = 0 + # force preservation of s, to minimize unwanted transformation of string, and to + # keep string locs straight between transform_string and scan_string + self.keepTabs = True + try: + for t, s, e in self.scan_string(instring, debug=debug): + out.append(instring[lastE:s]) + if t: + if isinstance(t, ParseResults): + out += t.as_list() + elif isinstance(t, Iterable) and not isinstance(t, str_type): + out.extend(t) + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + out = [o for o in out if o] + return "".join([str(s) for s in _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.with_traceback(None) + + def search_string( + self, + instring: str, + max_matches: int = _MAX_INT, + *, + debug: bool = False, + maxMatches: int = _MAX_INT, + ) -> ParseResults: + """ + Another extension to :class:`scan_string`, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + ``max_matches`` 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.search_string("More than Iron, more than Lead, more than Gold I need Electricity")) + + # the sum() builtin can be used to merge results into a single ParseResults object + print(sum(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity"))) + + prints:: + + [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] + ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] + """ + maxMatches = min(maxMatches, max_matches) + try: + return ParseResults( + [t for t, s, e in self.scan_string(instring, maxMatches, debug=debug)] + ) + 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.with_traceback(None) + + def split( + self, + instring: str, + maxsplit: int = _MAX_INT, + include_separators: bool = False, + *, + includeSeparators=False, + ) -> Generator[str, None, None]: + """ + Generator method to split a string using the given expression as a separator. + May be called with optional ``maxsplit`` argument, to limit the number of splits; + and the optional ``include_separators`` argument (default= ``False``), if the separating + matching text should be included in the split results. + + Example:: + + punc = one_of(list(".,;:/-!?")) + print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) + + prints:: + + ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] + """ + includeSeparators = includeSeparators or include_separators + last = 0 + for t, s, e in self.scan_string(instring, max_matches=maxsplit): + yield instring[last:s] + if includeSeparators: + yield t[0] + last = e + yield instring[last:] + + def __add__(self, other) -> "ParserElement": + """ + Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement` + converts them to :class:`Literal`\\ s by default. + + Example:: + + greet = Word(alphas) + "," + Word(alphas) + "!" + hello = "Hello, World!" + print(hello, "->", greet.parse_string(hello)) + + prints:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + + ``...`` may be used as a parse expression as a short form of :class:`SkipTo`:: + + Literal('start') + ... + Literal('end') + + is equivalent to:: + + Literal('start') + SkipTo('end')("_skipped*") + Literal('end') + + Note that the skipped text is returned with '_skipped' as a results name, + and to support having multiple skips in the same parser, the value returned is + a list of all skipped text. + """ + if other is Ellipsis: + return _PendingSkip(self) + + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return And([self, other]) + + def __radd__(self, other) -> "ParserElement": + """ + Implementation of ``+`` operator when left operand is not a :class:`ParserElement` + """ + if other is Ellipsis: + return SkipTo(self)("_skipped*") + self + + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return other + self + + def __sub__(self, other) -> "ParserElement": + """ + Implementation of ``-`` operator, returns :class:`And` with error stop + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return self + And._ErrorStop() + other + + def __rsub__(self, other) -> "ParserElement": + """ + Implementation of ``-`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return other - self + + def __mul__(self, other) -> "ParserElement": + """ + Implementation of ``*`` operator, allows use of ``expr * 3`` in place of + ``expr + expr + expr``. Expressions may also be multiplied by a 2-integer + tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples + may also include ``None`` as in: + + - ``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`` + """ + if other is Ellipsis: + other = (0, None) + elif isinstance(other, tuple) and other[:1] == (Ellipsis,): + other = ((0,) + other[1:] + (None,))[:2] + + if isinstance(other, int): + minElements, optElements = other, 0 + elif isinstance(other, tuple): + other = tuple(o if o is not Ellipsis else None for o in other) + 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: + return NotImplemented + else: + return NotImplemented + + 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: + return And([]) + + if optElements: + + def makeOptionalList(n): + if n > 1: + return Opt(self + makeOptionalList(n - 1)) + else: + return Opt(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) -> "ParserElement": + return self.__mul__(other) + + def __or__(self, other) -> "ParserElement": + """ + Implementation of ``|`` operator - returns :class:`MatchFirst` + """ + if other is Ellipsis: + return _PendingSkip(self, must_skip=True) + + if isinstance(other, str_type): + # `expr | ""` is equivalent to `Opt(expr)` + if other == "": + return Opt(self) + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return MatchFirst([self, other]) + + def __ror__(self, other) -> "ParserElement": + """ + Implementation of ``|`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return other | self + + def __xor__(self, other) -> "ParserElement": + """ + Implementation of ``^`` operator - returns :class:`Or` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return Or([self, other]) + + def __rxor__(self, other) -> "ParserElement": + """ + Implementation of ``^`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return other ^ self + + def __and__(self, other) -> "ParserElement": + """ + Implementation of ``&`` operator - returns :class:`Each` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return Each([self, other]) + + def __rand__(self, other) -> "ParserElement": + """ + Implementation of ``&`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return other & self + + def __invert__(self) -> "ParserElement": + """ + Implementation of ``~`` operator - returns :class:`NotAny` + """ + return NotAny(self) + + # disable __iter__ to override legacy use of sequential access to __getitem__ to + # iterate over a sequence + __iter__ = None + + def __getitem__(self, key): + """ + use ``[]`` indexing notation as a short form for expression repetition: + + - ``expr[n]`` is equivalent to ``expr*n`` + - ``expr[m, n]`` is equivalent to ``expr*(m, n)`` + - ``expr[n, ...]`` or ``expr[n,]`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` + (read as "at least n instances of ``expr``") + - ``expr[..., n]`` is equivalent to ``expr*(0, n)`` + (read as "0 to n instances of ``expr``") + - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)`` + - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)`` + + ``None`` may be used in place of ``...``. + + Note that ``expr[..., n]`` and ``expr[m, n]`` do not raise an exception + if more than ``n`` ``expr``\\ s exist in the input stream. If this behavior is + desired, then write ``expr[..., n] + ~expr``. + + For repetition with a stop_on expression, use slice notation: + + - ``expr[...: end_expr]`` and ``expr[0, ...: end_expr]`` are equivalent to ``ZeroOrMore(expr, stop_on=end_expr)`` + - ``expr[1, ...: end_expr]`` is equivalent to ``OneOrMore(expr, stop_on=end_expr)`` + + """ + + stop_on_defined = False + stop_on = NoMatch() + if isinstance(key, slice): + key, stop_on = key.start, key.stop + if key is None: + key = ... + stop_on_defined = True + elif isinstance(key, tuple) and isinstance(key[-1], slice): + key, stop_on = (key[0], key[1].start), key[1].stop + stop_on_defined = True + + # convert single arg keys to tuples + if isinstance(key, str_type): + key = (key,) + try: + iter(key) + except TypeError: + key = (key, key) + + if len(key) > 2: + raise TypeError( + f"only 1 or 2 index arguments supported ({key[:5]}{f'... [{len(key)}]' if len(key) > 5 else ''})" + ) + + # clip to 2 elements + ret = self * tuple(key[:2]) + ret = typing.cast(_MultipleMatch, ret) + + if stop_on_defined: + ret.stopOn(stop_on) + + return ret + + def __call__(self, name: typing.Optional[str] = None) -> "ParserElement": + """ + Shortcut for :class:`set_results_name`, with ``list_all_matches=False``. + + If ``name`` is given with a trailing ``'*'`` character, then ``list_all_matches`` will be + passed as ``True``. + + If ``name`` is omitted, same as calling :class:`copy`. + + Example:: + + # these are equivalent + userdata = Word(alphas).set_results_name("name") + Word(nums + "-").set_results_name("socsecno") + userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") + """ + if name is not None: + return self._setResultsName(name) + else: + return self.copy() + + def suppress(self) -> "ParserElement": + """ + Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress(self) + + def ignore_whitespace(self, recursive: bool = True) -> "ParserElement": + """ + Enables the skipping of whitespace before matching the characters in the + :class:`ParserElement`'s defined pattern. + + :param recursive: If ``True`` (the default), also enable whitespace skipping in child elements (if any) + """ + self.skipWhitespace = True + return self + + def leave_whitespace(self, recursive: bool = True) -> "ParserElement": + """ + Disables the skipping of whitespace before matching the characters in the + :class:`ParserElement`'s defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + + :param recursive: If true (the default), also disable whitespace skipping in child elements (if any) + """ + self.skipWhitespace = False + return self + + def set_whitespace_chars( + self, chars: Union[Set[str], str], copy_defaults: bool = False + ) -> "ParserElement": + """ + Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = set(chars) + self.copyDefaultWhiteChars = copy_defaults + return self + + def parse_with_tabs(self) -> "ParserElement": + """ + Overrides default behavior to expand ```` s to spaces before parsing the input string. + Must be called before ``parse_string`` when the input grammar contains elements that + match ```` characters. + """ + self.keepTabs = True + return self + + def ignore(self, other: "ParserElement") -> "ParserElement": + """ + 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 = Word(alphas)[1, ...] + patt.parse_string('ablaj /* comment */ lskjd') + # -> ['ablaj'] + + patt.ignore(c_style_comment) + patt.parse_string('ablaj /* comment */ lskjd') + # -> ['ablaj', 'lskjd'] + """ + import typing + + if isinstance(other, str_type): + 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 set_debug_actions( + self, + start_action: DebugStartAction, + success_action: DebugSuccessAction, + exception_action: DebugExceptionAction, + ) -> "ParserElement": + """ + Customize display of debugging messages while doing pattern matching: + + - ``start_action`` - method to be called when an expression is about to be parsed; + should have the signature ``fn(input_string: str, location: int, expression: ParserElement, cache_hit: bool)`` + + - ``success_action`` - method to be called when an expression has successfully parsed; + should have the signature ``fn(input_string: str, start_location: int, end_location: int, expression: ParserELement, parsed_tokens: ParseResults, cache_hit: bool)`` + + - ``exception_action`` - method to be called when expression fails to parse; + should have the signature ``fn(input_string: str, location: int, expression: ParserElement, exception: Exception, cache_hit: bool)`` + """ + self.debugActions = self.DebugActions( + start_action or _default_start_debug_action, # type: ignore[truthy-function] + success_action or _default_success_debug_action, # type: ignore[truthy-function] + exception_action or _default_exception_debug_action, # type: ignore[truthy-function] + ) + self.debug = True + return self + + def set_debug(self, flag: bool = True, recurse: bool = False) -> "ParserElement": + """ + Enable display of debugging messages while doing pattern matching. + Set ``flag`` to ``True`` to enable, ``False`` to disable. + Set ``recurse`` to ``True`` to set the debug flag on this expression and all sub-expressions. + + Example:: + + wd = Word(alphas).set_name("alphaword") + integer = Word(nums).set_name("numword") + term = wd | integer + + # turn on debugging for wd + wd.set_debug() + + term[1, ...].parse_string("abc 123 xyz 890") + + prints:: + + Match alphaword at loc 0(1,1) + Matched alphaword -> ['abc'] + Match alphaword at loc 3(1,4) + Exception raised:Expected alphaword (at char 4), (line:1, col:5) + Match alphaword at loc 7(1,8) + Matched alphaword -> ['xyz'] + Match alphaword at loc 11(1,12) + Exception raised:Expected alphaword (at char 12), (line:1, col:13) + Match alphaword at loc 15(1,16) + Exception raised:Expected alphaword (at char 15), (line:1, col:16) + + The output shown is that produced by the default debug actions - custom debug actions can be + specified using :class:`set_debug_actions`. Prior to attempting + to match the ``wd`` expression, the debugging message ``"Match at loc (,)"`` + is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"`` + message is shown. Also note the use of :class:`set_name` to assign a human-readable name to the expression, + which makes debugging and exception messages easier to understand - for instance, the default + name created for the :class:`Word` expression without calling ``set_name`` is ``"W:(A-Za-z)"``. + """ + if recurse: + for expr in self.visit_all(): + expr.set_debug(flag, recurse=False) + return self + + if flag: + self.set_debug_actions( + _default_start_debug_action, + _default_success_debug_action, + _default_exception_debug_action, + ) + else: + self.debug = False + return self + + @property + def default_name(self) -> str: + if self._defaultName is None: + self._defaultName = self._generateDefaultName() + return self._defaultName + + @abstractmethod + def _generateDefaultName(self) -> str: + """ + Child classes must define this method, which defines how the ``default_name`` is set. + """ + + def set_name(self, name: str) -> "ParserElement": + """ + Define name for this expression, makes debugging and exception messages clearer. + + Example:: + + Word(nums).parse_string("ABC") # -> Exception: Expected W:(0-9) (at char 0), (line:1, col:1) + Word(nums).set_name("integer").parse_string("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) + """ + self.customName = name + self.errmsg = "Expected " + self.name + if __diag__.enable_debug_on_named_expressions: + self.set_debug() + return self + + @property + def name(self) -> str: + # This will use a user-defined name if available, but otherwise defaults back to the auto-generated name + return self.customName if self.customName is not None else self.default_name + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return str(self) + + def streamline(self) -> "ParserElement": + self.streamlined = True + self._defaultName = None + return self + + def recurse(self) -> List["ParserElement"]: + return [] + + def _checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] + for e in self.recurse(): + e._checkRecursion(subRecCheckList) + + def validate(self, validateTrace=None) -> None: + """ + Check defined expressions for valid structure, check for infinite recursive definitions. + """ + warnings.warn( + "ParserElement.validate() is deprecated, and should not be used to check for left recursion", + DeprecationWarning, + stacklevel=2, + ) + self._checkRecursion([]) + + def parse_file( + self, + file_or_filename: Union[str, Path, TextIO], + encoding: str = "utf-8", + parse_all: bool = False, + *, + parseAll: bool = False, + ) -> ParseResults: + """ + 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. + """ + parseAll = parseAll or parse_all + try: + file_or_filename = typing.cast(TextIO, file_or_filename) + file_contents = file_or_filename.read() + except AttributeError: + file_or_filename = typing.cast(str, file_or_filename) + with open(file_or_filename, "r", encoding=encoding) as f: + file_contents = f.read() + try: + return self.parse_string(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.with_traceback(None) + + def __eq__(self, other): + if self is other: + return True + elif isinstance(other, str_type): + return self.matches(other, parse_all=True) + elif isinstance(other, ParserElement): + return vars(self) == vars(other) + return False + + def __hash__(self): + return id(self) + + def matches( + self, test_string: str, parse_all: bool = True, *, parseAll: bool = True + ) -> bool: + """ + Method for quick testing of a parser against a test string. Good for simple + inline microtests of sub expressions while building up larger parser. + + Parameters: + + - ``test_string`` - to test against this expression for a match + - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests + + Example:: + + expr = Word(nums) + assert expr.matches("100") + """ + parseAll = parseAll and parse_all + try: + self.parse_string(str(test_string), parse_all=parseAll) + return True + except ParseBaseException: + return False + + def run_tests( + self, + tests: Union[str, List[str]], + parse_all: bool = True, + comment: typing.Optional[Union["ParserElement", str]] = "#", + full_dump: bool = True, + print_results: bool = True, + failure_tests: bool = False, + post_parse: typing.Optional[Callable[[str, ParseResults], str]] = None, + file: typing.Optional[TextIO] = None, + with_line_numbers: bool = False, + *, + parseAll: bool = True, + fullDump: bool = True, + printResults: bool = True, + failureTests: bool = False, + postParse: typing.Optional[Callable[[str, ParseResults], str]] = None, + ) -> Tuple[bool, List[Tuple[str, Union[ParseResults, Exception]]]]: + """ + 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 + - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests + - ``comment`` - (default= ``'#'``) - expression for indicating embedded comments in the test + string; pass None to disable comment filtering + - ``full_dump`` - (default= ``True``) - dump results as list followed by results names in nested outline; + if False, only dump nested list + - ``print_results`` - (default= ``True``) prints test output to stdout + - ``failure_tests`` - (default= ``False``) indicates if these tests are expected to fail parsing + - ``post_parse`` - (default= ``None``) optional callback for successful parse results; called as + `fn(test_string, parse_results)` and returns a string to be added to the test output + - ``file`` - (default= ``None``) optional file-like object to which test output will be written; + if None, will default to ``sys.stdout`` + - ``with_line_numbers`` - default= ``False``) show test strings with line and column numbers + + Returns: a (success, results) tuple, where success indicates that all tests succeeded + (or failed if ``failure_tests`` 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.run_tests(''' + # 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.run_tests(''' + # stray character + 100Z + # missing leading digit before '.' + -.100 + # too many '.' + 3.14.159 + ''', failure_tests=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 + + Each test string must be on a single line. If you want to test a string that spans multiple + lines, create a test like this:: + + expr.run_tests(r"this is a test\\n of strings that spans \\n 3 lines") + + (Note that this is a raw string literal, you must include the leading ``'r'``.) + """ + from .testing import pyparsing_test + + parseAll = parseAll and parse_all + fullDump = fullDump and full_dump + printResults = printResults and print_results + failureTests = failureTests or failure_tests + postParse = postParse or post_parse + if isinstance(tests, str_type): + tests = typing.cast(str, tests) + line_strip = type(tests).strip + tests = [line_strip(test_line) for test_line in tests.rstrip().splitlines()] + comment_specified = comment is not None + if comment_specified: + if isinstance(comment, str_type): + comment = typing.cast(str, comment) + comment = Literal(comment) + comment = typing.cast(ParserElement, comment) + if file is None: + file = sys.stdout + print_ = file.write + + result: Union[ParseResults, Exception] + allResults: List[Tuple[str, Union[ParseResults, Exception]]] = [] + comments: List[str] = [] + success = True + NL = Literal(r"\n").add_parse_action(replace_with("\n")).ignore(quoted_string) + BOM = "\ufeff" + for t in tests: + if comment_specified and comment.matches(t, False) or comments and not t: + comments.append( + pyparsing_test.with_line_numbers(t) if with_line_numbers else t + ) + continue + if not t: + continue + out = [ + "\n" + "\n".join(comments) if comments else "", + pyparsing_test.with_line_numbers(t) if with_line_numbers else t, + ] + comments = [] + try: + # convert newline marks to actual newlines, and strip leading BOM if present + t = NL.transform_string(t.lstrip(BOM)) + result = self.parse_string(t, parse_all=parseAll) + except ParseBaseException as pe: + fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" + out.append(pe.explain()) + out.append("FAIL: " + str(pe)) + if ParserElement.verbose_stacktrace: + out.extend(traceback.format_tb(pe.__traceback__)) + success = success and failureTests + result = pe + except Exception as exc: + out.append(f"FAIL-EXCEPTION: {type(exc).__name__}: {exc}") + if ParserElement.verbose_stacktrace: + out.extend(traceback.format_tb(exc.__traceback__)) + success = success and failureTests + result = exc + else: + success = success and not failureTests + if postParse is not None: + try: + pp_value = postParse(t, result) + if pp_value is not None: + if isinstance(pp_value, ParseResults): + out.append(pp_value.dump()) + else: + out.append(str(pp_value)) + else: + out.append(result.dump()) + except Exception as e: + out.append(result.dump(full=fullDump)) + out.append( + f"{postParse.__name__} failed: {type(e).__name__}: {e}" + ) + else: + out.append(result.dump(full=fullDump)) + out.append("") + + if printResults: + print_("\n".join(out)) + + allResults.append((t, result)) + + return success, allResults + + def create_diagram( + self, + output_html: Union[TextIO, Path, str], + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, + embed: bool = False, + **kwargs, + ) -> None: + """ + Create a railroad diagram for the parser. + + Parameters: + + - ``output_html`` (str or file-like object) - output target for generated + diagram HTML + - ``vertical`` (int) - threshold for formatting multiple alternatives vertically + instead of horizontally (default=3) + - ``show_results_names`` - bool flag whether diagram should show annotations for + defined results names + - ``show_groups`` - bool flag whether groups should be highlighted with an unlabeled surrounding box + - ``embed`` - bool flag whether generated HTML should omit , , and tags to embed + the resulting HTML in an enclosing HTML source + - ``head`` - str containing additional HTML to insert into the section of the generated code; + can be used to insert custom CSS styling + - ``body`` - str containing additional HTML to insert at the beginning of the section of the + generated code + + Additional diagram-formatting keyword arguments can also be included; + see railroad.Diagram class. + """ + + try: + from .diagram import to_railroad, railroad_to_html + except ImportError as ie: + raise Exception( + "must ``pip install pyparsing[diagrams]`` to generate parser railroad diagrams" + ) from ie + + self.streamline() + + railroad = to_railroad( + self, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + diagram_kwargs=kwargs, + ) + if isinstance(output_html, (str, Path)): + with open(output_html, "w", encoding="utf-8") as diag_file: + diag_file.write(railroad_to_html(railroad, embed=embed, **kwargs)) + else: + # we were passed a file-like object, just write to it + output_html.write(railroad_to_html(railroad, embed=embed, **kwargs)) + + # Compatibility synonyms + # fmt: off + @staticmethod + @replaced_by_pep8(inline_literals_using) + def inlineLiteralsUsing(): ... + + @staticmethod + @replaced_by_pep8(set_default_whitespace_chars) + def setDefaultWhitespaceChars(): ... + + @replaced_by_pep8(set_results_name) + def setResultsName(self): ... + + @replaced_by_pep8(set_break) + def setBreak(self): ... + + @replaced_by_pep8(set_parse_action) + def setParseAction(self): ... + + @replaced_by_pep8(add_parse_action) + def addParseAction(self): ... + + @replaced_by_pep8(add_condition) + def addCondition(self): ... + + @replaced_by_pep8(set_fail_action) + def setFailAction(self): ... + + @replaced_by_pep8(try_parse) + def tryParse(self): ... + + @staticmethod + @replaced_by_pep8(enable_left_recursion) + def enableLeftRecursion(): ... + + @staticmethod + @replaced_by_pep8(enable_packrat) + def enablePackrat(): ... + + @replaced_by_pep8(parse_string) + def parseString(self): ... + + @replaced_by_pep8(scan_string) + def scanString(self): ... + + @replaced_by_pep8(transform_string) + def transformString(self): ... + + @replaced_by_pep8(search_string) + def searchString(self): ... + + @replaced_by_pep8(ignore_whitespace) + def ignoreWhitespace(self): ... + + @replaced_by_pep8(leave_whitespace) + def leaveWhitespace(self): ... + + @replaced_by_pep8(set_whitespace_chars) + def setWhitespaceChars(self): ... + + @replaced_by_pep8(parse_with_tabs) + def parseWithTabs(self): ... + + @replaced_by_pep8(set_debug_actions) + def setDebugActions(self): ... + + @replaced_by_pep8(set_debug) + def setDebug(self): ... + + @replaced_by_pep8(set_name) + def setName(self): ... + + @replaced_by_pep8(parse_file) + def parseFile(self): ... + + @replaced_by_pep8(run_tests) + def runTests(self): ... + + canParseNext = can_parse_next + resetCache = reset_cache + defaultName = default_name + # fmt: on + + +class _PendingSkip(ParserElement): + # internal placeholder class to hold a place were '...' is added to a parser element, + # once another ParserElement is added, this placeholder will be replaced with a SkipTo + def __init__(self, expr: ParserElement, must_skip: bool = False): + super().__init__() + self.anchor = expr + self.must_skip = must_skip + + def _generateDefaultName(self) -> str: + return str(self.anchor + Empty()).replace("Empty", "...") + + def __add__(self, other) -> "ParserElement": + skipper = SkipTo(other).set_name("...")("_skipped*") + if self.must_skip: + + def must_skip(t): + if not t._skipped or t._skipped.as_list() == [""]: + del t[0] + t.pop("_skipped", None) + + def show_skip(t): + if t._skipped.as_list()[-1:] == [""]: + t.pop("_skipped") + t["_skipped"] = "missing <" + repr(self.anchor) + ">" + + return ( + self.anchor + skipper().add_parse_action(must_skip) + | skipper().add_parse_action(show_skip) + ) + other + + return self.anchor + skipper + other + + def __repr__(self): + return self.defaultName + + def parseImpl(self, *args): + raise Exception( + "use of `...` expression without following SkipTo target expression" + ) + + +class Token(ParserElement): + """Abstract :class:`ParserElement` subclass, for defining atomic + matching patterns. + """ + + def __init__(self): + super().__init__(savelist=False) + + def _generateDefaultName(self) -> str: + return type(self).__name__ + + +class NoMatch(Token): + """ + A token that will never match. + """ + + def __init__(self): + super().__init__() + 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').parse_string('blah') # -> ['blah'] + Literal('blah').parse_string('blahfooblah') # -> ['blah'] + Literal('blah').parse_string('bla') # -> Exception: Expected "blah" + + For case-insensitive matching, use :class:`CaselessLiteral`. + + For keyword matching (force word break before and after the matched string), + use :class:`Keyword` or :class:`CaselessKeyword`. + """ + + def __new__(cls, match_string: str = "", *, matchString: str = ""): + # Performance tuning: select a subclass with optimized parseImpl + if cls is Literal: + match_string = matchString or match_string + if not match_string: + return super().__new__(Empty) + if len(match_string) == 1: + return super().__new__(_SingleCharLiteral) + + # Default behavior + return super().__new__(cls) + + # Needed to make copy.copy() work correctly if we customize __new__ + def __getnewargs__(self): + return (self.match,) + + def __init__(self, match_string: str = "", *, matchString: str = ""): + super().__init__() + match_string = matchString or match_string + self.match = match_string + self.matchLen = len(match_string) + self.firstMatchChar = match_string[:1] + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + + def _generateDefaultName(self) -> str: + return repr(self.match) + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar and instring.startswith( + self.match, loc + ): + return loc + self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) + + +class Empty(Literal): + """ + An empty token, will always match. + """ + + def __init__(self, match_string="", *, matchString=""): + super().__init__("") + self.mayReturnEmpty = True + self.mayIndexError = False + + def _generateDefaultName(self) -> str: + return "Empty" + + def parseImpl(self, instring, loc, doActions=True): + return loc, [] + + +class _SingleCharLiteral(Literal): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar: + return loc + 1, self.match + raise ParseException(instring, loc, self.errmsg, self) + + +ParserElement._literalStringClass = Literal + + +class Keyword(Token): + """ + Token to exactly match a specified string as a keyword, that is, + it must be immediately preceded and followed by whitespace or + non-keyword characters. Compare with :class:`Literal`: + + - ``Literal("if")`` will match the leading ``'if'`` in + ``'ifAndOnlyIf'``. + - ``Keyword("if")`` will not; it will only match the leading + ``'if'`` in ``'if x=1'``, or ``'if(y==2)'`` + + Accepts two optional constructor arguments in addition to the + keyword string: + + - ``ident_chars`` is a string of characters that would be valid + identifier characters, defaulting to all alphanumerics + "_" and + "$" + - ``caseless`` allows case-insensitive matching, default is ``False``. + + Example:: + + Keyword("start").parse_string("start") # -> ['start'] + Keyword("start").parse_string("starting") # -> Exception + + For case-insensitive matching, use :class:`CaselessKeyword`. + """ + + DEFAULT_KEYWORD_CHARS = alphanums + "_$" + + def __init__( + self, + match_string: str = "", + ident_chars: typing.Optional[str] = None, + caseless: bool = False, + *, + matchString: str = "", + identChars: typing.Optional[str] = None, + ): + super().__init__() + identChars = identChars or ident_chars + if identChars is None: + identChars = Keyword.DEFAULT_KEYWORD_CHARS + match_string = matchString or match_string + self.match = match_string + self.matchLen = len(match_string) + try: + self.firstMatchChar = match_string[0] + except IndexError: + raise ValueError("null string passed to Keyword; use Empty() instead") + self.errmsg = f"Expected {type(self).__name__} {self.name}" + self.mayReturnEmpty = False + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = match_string.upper() + identChars = identChars.upper() + self.identChars = set(identChars) + + def _generateDefaultName(self) -> str: + return repr(self.match) + + def parseImpl(self, instring, loc, doActions=True): + errmsg = self.errmsg + errloc = loc + if self.caseless: + if instring[loc : loc + self.matchLen].upper() == self.caselessmatch: + if loc == 0 or instring[loc - 1].upper() not in self.identChars: + if ( + loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen].upper() not in self.identChars + ): + return loc + self.matchLen, self.match + else: + # followed by keyword char + errmsg += ", was immediately followed by keyword character" + errloc = loc + self.matchLen + else: + # preceded by keyword char + errmsg += ", keyword was immediately preceded by keyword character" + errloc = loc - 1 + # else no match just raise plain exception + + else: + if ( + instring[loc] == self.firstMatchChar + and self.matchLen == 1 + or instring.startswith(self.match, loc) + ): + if loc == 0 or instring[loc - 1] not in self.identChars: + if ( + loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen] not in self.identChars + ): + return loc + self.matchLen, self.match + else: + # followed by keyword char + errmsg += ( + ", keyword was immediately followed by keyword character" + ) + errloc = loc + self.matchLen + else: + # preceded by keyword char + errmsg += ", keyword was immediately preceded by keyword character" + errloc = loc - 1 + # else no match just raise plain exception + + raise ParseException(instring, errloc, errmsg, self) + + @staticmethod + def set_default_keyword_chars(chars) -> None: + """ + Overrides the default characters used by :class:`Keyword` expressions. + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + + setDefaultKeywordChars = set_default_keyword_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:: + + CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10") + # -> ['CMD', 'CMD', 'CMD'] + + (Contrast with example for :class:`CaselessKeyword`.) + """ + + def __init__(self, match_string: str = "", *, matchString: str = ""): + match_string = matchString or match_string + super().__init__(match_string.upper()) + # Preserve the defining literal. + self.returnString = match_string + 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 :class:`Keyword`. + + Example:: + + CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10") + # -> ['CMD', 'CMD'] + + (Contrast with example for :class:`CaselessLiteral`.) + """ + + def __init__( + self, + match_string: str = "", + ident_chars: typing.Optional[str] = None, + *, + matchString: str = "", + identChars: typing.Optional[str] = None, + ): + identChars = identChars or ident_chars + match_string = matchString or match_string + super().__init__(match_string, identChars, caseless=True) + + +class CloseMatch(Token): + """A variation on :class:`Literal` which matches "close" matches, + that is, strings with at most 'n' mismatching characters. + :class:`CloseMatch` takes parameters: + + - ``match_string`` - string to be matched + - ``caseless`` - a boolean indicating whether to ignore casing when comparing characters + - ``max_mismatches`` - (``default=1``) maximum number of + mismatches allowed to count as a match + + The results from a successful parse will contain the matched text + from the input string and the following named results: + + - ``mismatches`` - a list of the positions within the + match_string where mismatches were found + - ``original`` - the original match_string used to compare + against the input string + + If ``mismatches`` is an empty list, then the match was an exact + match. + + Example:: + + patt = CloseMatch("ATCATCGAATGGA") + patt.parse_string("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) + patt.parse_string("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) + + # exact match + patt.parse_string("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) + + # close match allowing up to 2 mismatches + patt = CloseMatch("ATCATCGAATGGA", max_mismatches=2) + patt.parse_string("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) + """ + + def __init__( + self, + match_string: str, + max_mismatches: typing.Optional[int] = None, + *, + maxMismatches: int = 1, + caseless=False, + ): + maxMismatches = max_mismatches if max_mismatches is not None else maxMismatches + super().__init__() + self.match_string = match_string + self.maxMismatches = maxMismatches + self.errmsg = f"Expected {self.match_string!r} (with up to {self.maxMismatches} mismatches)" + self.caseless = caseless + self.mayIndexError = False + self.mayReturnEmpty = False + + def _generateDefaultName(self) -> str: + return f"{type(self).__name__}:{self.match_string!r}" + + def parseImpl(self, instring, loc, doActions=True): + start = loc + instrlen = len(instring) + maxloc = start + len(self.match_string) + + if maxloc <= instrlen: + match_string = self.match_string + match_stringloc = 0 + mismatches = [] + maxMismatches = self.maxMismatches + + for match_stringloc, s_m in enumerate( + zip(instring[loc:maxloc], match_string) + ): + src, mat = s_m + if self.caseless: + src, mat = src.lower(), mat.lower() + + if src != mat: + mismatches.append(match_stringloc) + if len(mismatches) > maxMismatches: + break + else: + loc = start + match_stringloc + 1 + results = ParseResults([instring[start:loc]]) + results["original"] = match_string + results["mismatches"] = mismatches + return loc, results + + raise ParseException(instring, loc, self.errmsg, self) + + +class Word(Token): + """Token for matching words composed of allowed character sets. + + Parameters: + + - ``init_chars`` - string of all characters that should be used to + match as a word; "ABC" will match "AAA", "ABAB", "CBAC", etc.; + if ``body_chars`` is also specified, then this is the string of + initial characters + - ``body_chars`` - string of characters that + can be used for matching after a matched initial character as + given in ``init_chars``; if omitted, same as the initial characters + (default=``None``) + - ``min`` - minimum number of characters to match (default=1) + - ``max`` - maximum number of characters to match (default=0) + - ``exact`` - exact number of characters to match (default=0) + - ``as_keyword`` - match as a keyword (default=``False``) + - ``exclude_chars`` - characters that might be + found in the input ``body_chars`` string but which should not be + accepted for matching ;useful to define a word of all + printables except for one or two characters, for instance + (default=``None``) + + :class:`srange` is useful for defining custom character set strings + for defining :class:`Word` expressions, using range notation from + regular expression character sets. + + A common mistake is to use :class:`Word` to match a specific literal + string, as in ``Word("Address")``. Remember that :class:`Word` + uses the string argument to define *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 :class:`Literal` or :class:`Keyword`. + + pyparsing includes helper strings for building Words: + + - :class:`alphas` + - :class:`nums` + - :class:`alphanums` + - :class:`hexnums` + - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255 + - accented, tilded, umlauted, etc.) + - :class:`punc8bit` (non-alphabetic characters in ASCII range + 128-255 - currency, symbols, superscripts, diacriticals, etc.) + - :class:`printables` (any non-whitespace character) + + ``alphas``, ``nums``, and ``printables`` are also defined in several + Unicode sets - see :class:`pyparsing_unicode``. + + 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, exclude_chars=",") + """ + + def __init__( + self, + init_chars: str = "", + body_chars: typing.Optional[str] = None, + min: int = 1, + max: int = 0, + exact: int = 0, + as_keyword: bool = False, + exclude_chars: typing.Optional[str] = None, + *, + initChars: typing.Optional[str] = None, + bodyChars: typing.Optional[str] = None, + asKeyword: bool = False, + excludeChars: typing.Optional[str] = None, + ): + initChars = initChars or init_chars + bodyChars = bodyChars or body_chars + asKeyword = asKeyword or as_keyword + excludeChars = excludeChars or exclude_chars + super().__init__() + if not initChars: + raise ValueError( + f"invalid {type(self).__name__}, initChars cannot be empty string" + ) + + initChars_set = set(initChars) + if excludeChars: + excludeChars_set = set(excludeChars) + initChars_set -= excludeChars_set + if bodyChars: + bodyChars = "".join(set(bodyChars) - excludeChars_set) + self.initChars = initChars_set + self.initCharsOrig = "".join(sorted(initChars_set)) + + if bodyChars: + self.bodyChars = set(bodyChars) + self.bodyCharsOrig = "".join(sorted(bodyChars)) + else: + self.bodyChars = initChars_set + self.bodyCharsOrig = self.initCharsOrig + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError( + "cannot specify a minimum length < 1; use Opt(Word()) if zero-length word is permitted" + ) + + if self.maxSpecified and min > max: + raise ValueError( + f"invalid args, if min and max both specified min must be <= max (min={min}, max={max})" + ) + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + min = max = exact + self.maxLen = exact + self.minLen = exact + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asKeyword = asKeyword + if self.asKeyword: + self.errmsg += " as a keyword" + + # see if we can make a regex for this Word + if " " not in (self.initChars | self.bodyChars): + if len(self.initChars) == 1: + re_leading_fragment = re.escape(self.initCharsOrig) + else: + re_leading_fragment = f"[{_collapse_string_to_ranges(self.initChars)}]" + + if self.bodyChars == self.initChars: + if max == 0 and self.minLen == 1: + repeat = "+" + elif max == 1: + repeat = "" + else: + if self.minLen != self.maxLen: + repeat = f"{{{self.minLen},{'' if self.maxLen == _MAX_INT else self.maxLen}}}" + else: + repeat = f"{{{self.minLen}}}" + self.reString = f"{re_leading_fragment}{repeat}" + else: + if max == 1: + re_body_fragment = "" + repeat = "" + else: + re_body_fragment = f"[{_collapse_string_to_ranges(self.bodyChars)}]" + if max == 0 and self.minLen == 1: + repeat = "*" + elif max == 2: + repeat = "?" if min <= 1 else "" + else: + if min != max: + repeat = f"{{{min - 1 if min > 0 else ''},{max - 1 if max > 0 else ''}}}" + else: + repeat = f"{{{min - 1 if min > 0 else ''}}}" + + self.reString = f"{re_leading_fragment}{re_body_fragment}{repeat}" + + if self.asKeyword: + self.reString = rf"\b{self.reString}\b" + + try: + self.re = re.compile(self.reString) + except re.error: + self.re = None # type: ignore[assignment] + else: + self.re_match = self.re.match + self.parseImpl = self.parseImpl_regex # type: ignore[assignment] + + def _generateDefaultName(self) -> str: + def charsAsStr(s): + max_repr_len = 16 + s = _collapse_string_to_ranges(s, re_escape=False) + if len(s) > max_repr_len: + return s[: max_repr_len - 3] + "..." + else: + return s + + if self.initChars != self.bodyChars: + base = f"W:({charsAsStr(self.initChars)}, {charsAsStr(self.bodyChars)})" + else: + base = f"W:({charsAsStr(self.initChars)})" + + # add length specification + if self.minLen > 1 or self.maxLen != _MAX_INT: + if self.minLen == self.maxLen: + if self.minLen == 1: + return base[2:] + else: + return base + f"{{{self.minLen}}}" + elif self.maxLen == _MAX_INT: + return base + f"{{{self.minLen},...}}" + else: + return base + f"{{{self.minLen},{self.maxLen}}}" + return base + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not 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 + elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + elif 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 parseImpl_regex(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + return loc, result.group() + + +class Char(Word): + """A short-cut class for defining :class:`Word` ``(characters, exact=1)``, + when defining a match of any single character in a string of + characters. + """ + + def __init__( + self, + charset: str, + as_keyword: bool = False, + exclude_chars: typing.Optional[str] = None, + *, + asKeyword: bool = False, + excludeChars: typing.Optional[str] = None, + ): + asKeyword = asKeyword or as_keyword + excludeChars = excludeChars or exclude_chars + super().__init__( + charset, exact=1, as_keyword=asKeyword, exclude_chars=excludeChars + ) + + +class Regex(Token): + r"""Token for matching strings that match a given regular + expression. Defined with string specifying the regular expression in + a form recognized by the stdlib Python `re module `_. + If the given regex contains named groups (defined using ``(?P...)``), + these will be preserved as named :class:`ParseResults`. + + If instead of the Python stdlib ``re`` module you wish to use a different RE module + (such as the ``regex`` module), you can do so by building your ``Regex`` object with + a compiled RE that was compiled using ``regex``. + + Example:: + + realnum = Regex(r"[+-]?\d+\.\d*") + # ref: https://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?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") + + # named fields in a regex will be returned as named results + date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)') + + # the Regex class will accept re's compiled using the regex module + import regex + parser = pp.Regex(regex.compile(r'[0-9]')) + """ + + def __init__( + self, + pattern: Any, + flags: Union[re.RegexFlag, int] = 0, + as_group_list: bool = False, + as_match: bool = False, + *, + asGroupList: bool = False, + asMatch: bool = False, + ): + """The parameters ``pattern`` and ``flags`` are passed + to the ``re.compile()`` function as-is. See the Python + `re module `_ module for an + explanation of the acceptable patterns and flags. + """ + super().__init__() + asGroupList = asGroupList or as_group_list + asMatch = asMatch or as_match + + if isinstance(pattern, str_type): + if not pattern: + raise ValueError("null string passed to Regex; use Empty() instead") + + self._re = None + self.reString = self.pattern = pattern + self.flags = flags + + elif hasattr(pattern, "pattern") and hasattr(pattern, "match"): + self._re = pattern + self.pattern = self.reString = pattern.pattern + self.flags = flags + + else: + raise TypeError( + "Regex may only be constructed with a string or a compiled RE object" + ) + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asGroupList = asGroupList + self.asMatch = asMatch + if self.asGroupList: + self.parseImpl = self.parseImplAsGroupList # type: ignore [assignment] + if self.asMatch: + self.parseImpl = self.parseImplAsMatch # type: ignore [assignment] + + @cached_property + def re(self): + if self._re: + return self._re + else: + try: + return re.compile(self.pattern, self.flags) + except re.error: + raise ValueError(f"invalid pattern ({self.pattern!r}) passed to Regex") + + @cached_property + def re_match(self): + return self.re.match + + @cached_property + def mayReturnEmpty(self): + return self.re_match("") is not None + + def _generateDefaultName(self) -> str: + return "Re:({})".format(repr(self.pattern).replace("\\\\", "\\")) + + 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() + ret = ParseResults(result.group()) + d = result.groupdict() + if d: + for k, v in d.items(): + ret[k] = v + return loc, ret + + def parseImplAsGroupList(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.groups() + return loc, ret + + def parseImplAsMatch(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result + return loc, ret + + def sub(self, repl: str) -> ParserElement: + r""" + Return :class:`Regex` with an attached parse action to transform the parsed + result as if called using `re.sub(expr, repl, string) `_. + + Example:: + + make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2") + print(make_html.transform_string("h1:main title:")) + # prints "

main title

" + """ + if self.asGroupList: + raise TypeError("cannot use sub() with Regex(as_group_list=True)") + + if self.asMatch and callable(repl): + raise TypeError( + "cannot use sub() with a callable with Regex(as_match=True)" + ) + + if self.asMatch: + + def pa(tokens): + return tokens[0].expand(repl) + + else: + + def pa(tokens): + return self.re.sub(repl, tokens[0]) + + return self.add_parse_action(pa) + + +class QuotedString(Token): + r""" + Token for matching strings that are delimited by quoting characters. + + Defined with the following parameters: + + - ``quote_char`` - string of one or more characters defining the + quote delimiting string + - ``esc_char`` - character to re_escape quotes, typically backslash + (default= ``None``) + - ``esc_quote`` - special quote sequence to re_escape an embedded quote + string (such as SQL's ``""`` to re_escape an embedded ``"``) + (default= ``None``) + - ``multiline`` - boolean indicating whether quotes can span + multiple lines (default= ``False``) + - ``unquote_results`` - boolean indicating whether the matched text + should be unquoted (default= ``True``) + - ``end_quote_char`` - string of one or more characters defining the + end of the quote delimited string (default= ``None`` => same as + quote_char) + - ``convert_whitespace_escapes`` - convert escaped whitespace + (``'\t'``, ``'\n'``, etc.) to actual whitespace + (default= ``True``) + + Example:: + + qs = QuotedString('"') + print(qs.search_string('lsjdf "This is the quote" sldjf')) + complex_qs = QuotedString('{{', end_quote_char='}}') + print(complex_qs.search_string('lsjdf {{This is the "quote"}} sldjf')) + sql_qs = QuotedString('"', esc_quote='""') + print(sql_qs.search_string('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']] + """ + ws_map = dict(((r"\t", "\t"), (r"\n", "\n"), (r"\f", "\f"), (r"\r", "\r"))) + + def __init__( + self, + quote_char: str = "", + esc_char: typing.Optional[str] = None, + esc_quote: typing.Optional[str] = None, + multiline: bool = False, + unquote_results: bool = True, + end_quote_char: typing.Optional[str] = None, + convert_whitespace_escapes: bool = True, + *, + quoteChar: str = "", + escChar: typing.Optional[str] = None, + escQuote: typing.Optional[str] = None, + unquoteResults: bool = True, + endQuoteChar: typing.Optional[str] = None, + convertWhitespaceEscapes: bool = True, + ): + super().__init__() + esc_char = escChar or esc_char + esc_quote = escQuote or esc_quote + unquote_results = unquoteResults and unquote_results + end_quote_char = endQuoteChar or end_quote_char + convert_whitespace_escapes = ( + convertWhitespaceEscapes and convert_whitespace_escapes + ) + quote_char = quoteChar or quote_char + + # remove white space from quote chars + quote_char = quote_char.strip() + if not quote_char: + raise ValueError("quote_char cannot be the empty string") + + if end_quote_char is None: + end_quote_char = quote_char + else: + end_quote_char = end_quote_char.strip() + if not end_quote_char: + raise ValueError("end_quote_char cannot be the empty string") + + self.quote_char: str = quote_char + self.quote_char_len: int = len(quote_char) + self.first_quote_char: str = quote_char[0] + self.end_quote_char: str = end_quote_char + self.end_quote_char_len: int = len(end_quote_char) + self.esc_char: str = esc_char or "" + self.has_esc_char: bool = esc_char is not None + self.esc_quote: str = esc_quote or "" + self.unquote_results: bool = unquote_results + self.convert_whitespace_escapes: bool = convert_whitespace_escapes + self.multiline = multiline + self.re_flags = re.RegexFlag(0) + + # fmt: off + # build up re pattern for the content between the quote delimiters + inner_pattern = [] + + if esc_quote: + inner_pattern.append(rf"(?:{re.escape(esc_quote)})") + + if esc_char: + inner_pattern.append(rf"(?:{re.escape(esc_char)}.)") + + if len(self.end_quote_char) > 1: + inner_pattern.append( + "(?:" + + "|".join( + f"(?:{re.escape(self.end_quote_char[:i])}(?!{re.escape(self.end_quote_char[i:])}))" + for i in range(len(self.end_quote_char) - 1, 0, -1) + ) + + ")" + ) + + if self.multiline: + self.re_flags |= re.MULTILINE | re.DOTALL + inner_pattern.append( + rf"(?:[^{_escape_regex_range_chars(self.end_quote_char[0])}" + rf"{(_escape_regex_range_chars(esc_char) if self.has_esc_char else '')}])" + ) + else: + inner_pattern.append( + rf"(?:[^{_escape_regex_range_chars(self.end_quote_char[0])}\n\r" + rf"{(_escape_regex_range_chars(esc_char) if self.has_esc_char else '')}])" + ) + + self.pattern = "".join( + [ + re.escape(self.quote_char), + "(?:", + '|'.join(inner_pattern), + ")*", + re.escape(self.end_quote_char), + ] + ) + + if self.unquote_results: + if self.convert_whitespace_escapes: + self.unquote_scan_re = re.compile( + rf"({'|'.join(re.escape(k) for k in self.ws_map)})" + rf"|({re.escape(self.esc_char)}.)" + rf"|(\n|.)", + flags=self.re_flags, + ) + else: + self.unquote_scan_re = re.compile( + rf"({re.escape(self.esc_char)}.)" + rf"|(\n|.)", + flags=self.re_flags + ) + # fmt: on + + try: + self.re = re.compile(self.pattern, self.re_flags) + self.reString = self.pattern + self.re_match = self.re.match + except re.error: + raise ValueError(f"invalid pattern {self.pattern!r} passed to Regex") + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def _generateDefaultName(self) -> str: + if self.quote_char == self.end_quote_char and isinstance( + self.quote_char, str_type + ): + return f"string enclosed in {self.quote_char!r}" + + return f"quoted string, starting with {self.quote_char} ending with {self.end_quote_char}" + + def parseImpl(self, instring, loc, doActions=True): + # check first character of opening quote to see if that is a match + # before doing the more complicated regex match + result = ( + instring[loc] == self.first_quote_char + and self.re_match(instring, loc) + or None + ) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + # get ending loc and matched string from regex matching result + loc = result.end() + ret = result.group() + + if self.unquote_results: + # strip off quotes + ret = ret[self.quote_char_len : -self.end_quote_char_len] + + if isinstance(ret, str_type): + # fmt: off + if self.convert_whitespace_escapes: + # as we iterate over matches in the input string, + # collect from whichever match group of the unquote_scan_re + # regex matches (only 1 group will match at any given time) + ret = "".join( + # match group 1 matches \t, \n, etc. + self.ws_map[match.group(1)] if match.group(1) + # match group 2 matches escaped characters + else match.group(2)[-1] if match.group(2) + # match group 3 matches any character + else match.group(3) + for match in self.unquote_scan_re.finditer(ret) + ) + else: + ret = "".join( + # match group 1 matches escaped characters + match.group(1)[-1] if match.group(1) + # match group 2 matches any character + else match.group(2) + for match in self.unquote_scan_re.finditer(ret) + ) + # fmt: on + + # replace escaped quotes + if self.esc_quote: + ret = ret.replace(self.esc_quote, self.end_quote_char) + + return loc, ret + + +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 ``min`` is + 1 (a minimum value < 1 is not valid); the default values for + ``max`` and ``exact`` are 0, meaning no maximum or exact + length restriction. + + Example:: + + # define a comma-separated-value as anything that is not a ',' + csv_value = CharsNotIn(',') + print(DelimitedList(csv_value).parse_string("dkls,lsdkjf,s12 34,@!#,213")) + + prints:: + + ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] + """ + + def __init__( + self, + not_chars: str = "", + min: int = 1, + max: int = 0, + exact: int = 0, + *, + notChars: str = "", + ): + super().__init__() + self.skipWhitespace = False + self.notChars = not_chars or notChars + self.notCharsSet = set(self.notChars) + + if min < 1: + raise ValueError( + "cannot specify a minimum length < 1; use " + "Opt(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.errmsg = "Expected " + self.name + self.mayReturnEmpty = self.minLen == 0 + self.mayIndexError = False + + def _generateDefaultName(self) -> str: + not_chars_str = _collapse_string_to_ranges(self.notChars) + if len(not_chars_str) > 16: + return f"!W:({self.notChars[: 16 - 3]}...)" + else: + return f"!W:({self.notChars})" + + def parseImpl(self, instring, loc, doActions=True): + notchars = self.notCharsSet + if instring[loc] in notchars: + raise ParseException(instring, loc, self.errmsg, self) + + start = loc + loc += 1 + 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] + + +class White(Token): + """Special matching class for matching whitespace. Normally, + whitespace is ignored by pyparsing grammars. This class is included + when some whitespace structures are significant. Define with + a string containing the whitespace characters to be matched; default + is ``" \\t\\r\\n"``. Also takes optional ``min``, + ``max``, and ``exact`` arguments, as defined for the + :class:`Word` class. + """ + + whiteStrs = { + " ": "", + "\t": "", + "\n": "", + "\r": "", + "\f": "", + "\u00A0": "", + "\u1680": "", + "\u180E": "", + "\u2000": "", + "\u2001": "", + "\u2002": "", + "\u2003": "", + "\u2004": "", + "\u2005": "", + "\u2006": "", + "\u2007": "", + "\u2008": "", + "\u2009": "", + "\u200A": "", + "\u200B": "", + "\u202F": "", + "\u205F": "", + "\u3000": "", + } + + def __init__(self, ws: str = " \t\r\n", min: int = 1, max: int = 0, exact: int = 0): + super().__init__() + self.matchWhite = ws + self.set_whitespace_chars( + "".join(c for c in self.whiteStrs if c not in self.matchWhite), + copy_defaults=True, + ) + # self.leave_whitespace() + 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 _generateDefaultName(self) -> str: + return "".join(White.whiteStrs[c] for c in self.matchWhite) + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not 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().__init__() + 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: int): + super().__init__() + self.col = colno + + def preParse(self, instring: str, loc: int) -> int: + 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): + r"""Matches if current position is at the beginning of a line within + the parse string + + Example:: + + test = '''\ + AAA this line + AAA and this line + AAA but not this one + B AAA and definitely not this one + ''' + + for t in (LineStart() + 'AAA' + rest_of_line).search_string(test): + print(t) + + prints:: + + ['AAA', ' this line'] + ['AAA', ' and this line'] + + """ + + def __init__(self): + super().__init__() + self.leave_whitespace() + self.orig_whiteChars = set() | self.whiteChars + self.whiteChars.discard("\n") + self.skipper = Empty().set_whitespace_chars(self.whiteChars) + self.errmsg = "Expected start of line" + + def preParse(self, instring: str, loc: int) -> int: + if loc == 0: + return loc + else: + ret = self.skipper.preParse(instring, loc) + if "\n" in self.orig_whiteChars: + while instring[ret : ret + 1] == "\n": + ret = self.skipper.preParse(instring, ret + 1) + return ret + + def parseImpl(self, instring, loc, doActions=True): + if col(loc, instring) == 1: + return loc, [] + raise ParseException(instring, loc, self.errmsg, self) + + +class LineEnd(PositionToken): + """Matches if current position is at the end of a line within the + parse string + """ + + def __init__(self): + super().__init__() + self.whiteChars.discard("\n") + self.set_whitespace_chars(self.whiteChars, copy_defaults=False) + 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().__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().__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 + :class:`Word`, and is not preceded by any character in a given + set of ``word_chars`` (default= ``printables``). To emulate the + ``\b`` behavior of regular expressions, use + ``WordStart(alphanums)``. ``WordStart`` will also match at + the beginning of the string being parsed, or at the beginning of + a line. + """ + + def __init__(self, word_chars: str = printables, *, wordChars: str = printables): + wordChars = word_chars if wordChars == printables else wordChars + super().__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 :class:`Word`, + and is not followed by any character in a given set of ``word_chars`` + (default= ``printables``). To emulate the ``\b`` behavior of + regular expressions, use ``WordEnd(alphanums)``. ``WordEnd`` + will also match at the end of the string being parsed, or at the end + of a line. + """ + + def __init__(self, word_chars: str = printables, *, wordChars: str = printables): + wordChars = word_chars if wordChars == printables else wordChars + super().__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: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(savelist) + self.exprs: List[ParserElement] + if isinstance(exprs, _generatorType): + exprs = list(exprs) + + if isinstance(exprs, str_type): + self.exprs = [self._literalStringClass(exprs)] + elif isinstance(exprs, ParserElement): + self.exprs = [exprs] + elif isinstance(exprs, Iterable): + exprs = list(exprs) + # if sequence of strings provided, wrap with Literal + if any(isinstance(expr, str_type) for expr in exprs): + exprs = ( + self._literalStringClass(e) if isinstance(e, str_type) else e + for e in exprs + ) + self.exprs = list(exprs) + else: + try: + self.exprs = list(exprs) + except TypeError: + self.exprs = [exprs] + self.callPreparse = False + + def recurse(self) -> List[ParserElement]: + return self.exprs[:] + + def append(self, other) -> ParserElement: + self.exprs.append(other) + self._defaultName = None + return self + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + """ + Extends ``leave_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on + all contained expressions. + """ + super().leave_whitespace(recursive) + + if recursive: + self.exprs = [e.copy() for e in self.exprs] + for e in self.exprs: + e.leave_whitespace(recursive) + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + """ + Extends ``ignore_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on + all contained expressions. + """ + super().ignore_whitespace(recursive) + if recursive: + self.exprs = [e.copy() for e in self.exprs] + for e in self.exprs: + e.ignore_whitespace(recursive) + return self + + def ignore(self, other) -> ParserElement: + if isinstance(other, Suppress): + if other not in self.ignoreExprs: + super().ignore(other) + for e in self.exprs: + e.ignore(self.ignoreExprs[-1]) + else: + super().ignore(other) + for e in self.exprs: + e.ignore(self.ignoreExprs[-1]) + return self + + def _generateDefaultName(self) -> str: + return f"{self.__class__.__name__}:({str(self.exprs)})" + + def streamline(self) -> ParserElement: + if self.streamlined: + return self + + super().streamline() + + for e in self.exprs: + e.streamline() + + # collapse nested :class:`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 :class:`Or`'s and :class:`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._defaultName = 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._defaultName = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + self.errmsg = "Expected " + str(self) + + return self + + def validate(self, validateTrace=None) -> None: + warnings.warn( + "ParserElement.validate() is deprecated, and should not be used to check for left recursion", + DeprecationWarning, + stacklevel=2, + ) + tmp = (validateTrace if validateTrace is not None else [])[:] + [self] + for e in self.exprs: + e.validate(tmp) + self._checkRecursion([]) + + def copy(self) -> ParserElement: + ret = super().copy() + ret = typing.cast(ParseExpression, ret) + ret.exprs = [e.copy() for e in self.exprs] + return ret + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_ungrouped_named_tokens_in_collection + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in self.suppress_warnings_ + ): + for e in self.exprs: + if ( + isinstance(e, ParserElement) + and e.resultsName + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in e.suppress_warnings_ + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "collides with {!r} on contained expression".format( + "warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + # Compatibility synonyms + # fmt: off + @replaced_by_pep8(leave_whitespace) + def leaveWhitespace(self): ... + + @replaced_by_pep8(ignore_whitespace) + def ignoreWhitespace(self): ... + # fmt: on + + +class And(ParseExpression): + """ + Requires all given :class:`ParseExpression` s to be found in the given order. + Expressions may be separated by whitespace. + May be constructed using the ``'+'`` operator. + May also be constructed using the ``'-'`` operator, which will + suppress backtracking. + + Example:: + + integer = Word(nums) + name_expr = Word(alphas)[1, ...] + + 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().__init__(*args, **kwargs) + self.leave_whitespace() + + def _generateDefaultName(self) -> str: + return "-" + + def __init__( + self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True + ): + exprs: List[ParserElement] = list(exprs_arg) + if exprs and Ellipsis in exprs: + tmp = [] + for i, expr in enumerate(exprs): + if expr is Ellipsis: + if i < len(exprs) - 1: + skipto_arg: ParserElement = typing.cast( + ParseExpression, (Empty() + exprs[i + 1]) + ).exprs[-1] + tmp.append(SkipTo(skipto_arg)("_skipped*")) + else: + raise Exception( + "cannot construct And with sequence ending in ..." + ) + else: + tmp.append(expr) + exprs[:] = tmp + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + if not isinstance(self.exprs[0], White): + self.set_whitespace_chars( + self.exprs[0].whiteChars, + copy_defaults=self.exprs[0].copyDefaultWhiteChars, + ) + self.skipWhitespace = self.exprs[0].skipWhitespace + else: + self.skipWhitespace = False + else: + self.mayReturnEmpty = True + self.callPreparse = True + + def streamline(self) -> ParserElement: + # collapse any _PendingSkip's + if self.exprs: + if any( + isinstance(e, ParseExpression) + and e.exprs + and isinstance(e.exprs[-1], _PendingSkip) + for e in self.exprs[:-1] + ): + deleted_expr_marker = NoMatch() + for i, e in enumerate(self.exprs[:-1]): + if e is deleted_expr_marker: + continue + if ( + isinstance(e, ParseExpression) + and e.exprs + and isinstance(e.exprs[-1], _PendingSkip) + ): + e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1] + self.exprs[i + 1] = deleted_expr_marker + self.exprs = [e for e in self.exprs if e is not deleted_expr_marker] + + super().streamline() + + # link any IndentedBlocks to the prior expression + prev: ParserElement + cur: ParserElement + for prev, cur in zip(self.exprs, self.exprs[1:]): + # traverse cur or any first embedded expr of cur looking for an IndentedBlock + # (but watch out for recursive grammar) + seen = set() + while True: + if id(cur) in seen: + break + seen.add(id(cur)) + if isinstance(cur, IndentedBlock): + prev.add_parse_action( + lambda s, l, t, cur_=cur: setattr( + cur_, "parent_anchor", col(l, s) + ) + ) + break + subs = cur.recurse() + next_first = next(iter(subs), None) + if next_first is None: + break + cur = typing.cast(ParserElement, next_first) + + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + return self + + def parseImpl(self, instring, loc, doActions=True): + # pass False as callPreParse 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): + if type(e) is 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._from_exception(pe) + except IndexError: + raise ParseSyntaxException( + instring, len(instring), self.errmsg, self + ) + else: + loc, exprtokens = e._parse(instring, loc, doActions) + resultlist += exprtokens + return loc, resultlist + + def __iadd__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + 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 _generateDefaultName(self) -> str: + inner = " ".join(str(e) for e in self.exprs) + # strip off redundant inner {}'s + while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}": + inner = inner[1:-1] + return "{" + inner + "}" + + +class Or(ParseExpression): + """Requires that at least one :class:`ParseExpression` is found. If + two expressions match, the expression that matches the longest + string will be used. May be constructed using the ``'^'`` + operator. + + Example:: + + # construct Or using '^' operator + + number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) + print(number.search_string("123 3.1416 789")) + + prints:: + + [['123'], ['3.1416'], ['789']] + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + else: + self.mayReturnEmpty = True + + def streamline(self) -> ParserElement: + super().streamline() + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.saveAsList = any(e.saveAsList for e in self.exprs) + self.skipWhitespace = all( + e.skipWhitespace and not isinstance(e, White) for e in self.exprs + ) + else: + self.saveAsList = False + return self + + def parseImpl(self, instring, loc, doActions=True): + maxExcLoc = -1 + maxException = None + matches = [] + fatals = [] + if all(e.callPreparse for e in self.exprs): + loc = self.preParse(instring, loc) + for e in self.exprs: + try: + loc2 = e.try_parse(instring, loc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parser_element = e + fatals.append(pfe) + maxException = None + maxExcLoc = -1 + except ParseException as err: + if not fatals: + 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: + # re-evaluate all matches in descending order of length of match, in case attached actions + # might change whether or how much they match of the input. + matches.sort(key=itemgetter(0), reverse=True) + + if not doActions: + # no further conditions or parse actions to change the selection of + # alternative, so the first match will be the best match + best_expr = matches[0][1] + return best_expr._parse(instring, loc, doActions) + + longest = -1, None + for loc1, expr1 in matches: + if loc1 <= longest[0]: + # already have a longer match than this one will deliver, we are done + return longest + + try: + loc2, toks = expr1._parse(instring, loc, doActions) + except ParseException as err: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + else: + if loc2 >= loc1: + return loc2, toks + # didn't match as much as before + elif loc2 > longest[0]: + longest = loc2, toks + + if longest != (-1, None): + return longest + + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parser_element)))) + max_fatal = fatals[0] + raise max_fatal + + if maxException is not None: + # infer from this check that all alternatives failed at the current position + # so emit this collective error message instead of any single error message + if maxExcLoc == loc: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException( + instring, loc, "no defined alternatives to match", self + ) + + def __ixor__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return self.append(other) # Or([self, other]) + + def _generateDefaultName(self) -> str: + return "{" + " ^ ".join(str(e) for e in self.exprs) + "}" + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_multiple_tokens_in_named_alternation + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in self.suppress_warnings_ + ): + if any( + isinstance(e, And) + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in e.suppress_warnings_ + for e in self.exprs + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "will return a list of all parsed tokens in an And alternative, " + "in prior versions only the first token was returned; enclose " + "contained argument in Group".format( + "warn_multiple_tokens_in_named_alternation", + name, + type(self).__name__, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class MatchFirst(ParseExpression): + """Requires that at least one :class:`ParseExpression` is found. If + more than one expression matches, the first one listed is the one that will + match. May be constructed using the ``'|'`` operator. + + Example:: + + # construct MatchFirst using '|' operator + + # watch the order of expressions to match + number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) + print(number.search_string("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] + + # put more selective expression first + number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) + print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + else: + self.mayReturnEmpty = True + + def streamline(self) -> ParserElement: + if self.streamlined: + return self + + super().streamline() + if self.exprs: + self.saveAsList = any(e.saveAsList for e in self.exprs) + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all( + e.skipWhitespace and not isinstance(e, White) for e in self.exprs + ) + else: + self.saveAsList = False + self.mayReturnEmpty = True + return self + + def parseImpl(self, instring, loc, doActions=True): + maxExcLoc = -1 + maxException = None + + for e in self.exprs: + try: + return e._parse( + instring, + loc, + doActions, + ) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parser_element = e + raise + 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) + + if maxException is not None: + # infer from this check that all alternatives failed at the current position + # so emit this collective error message instead of any individual error message + if maxExcLoc == loc: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException( + instring, loc, "no defined alternatives to match", self + ) + + def __ior__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return self.append(other) # MatchFirst([self, other]) + + def _generateDefaultName(self) -> str: + return "{" + " | ".join(str(e) for e in self.exprs) + "}" + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_multiple_tokens_in_named_alternation + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in self.suppress_warnings_ + ): + if any( + isinstance(e, And) + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in e.suppress_warnings_ + for e in self.exprs + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "will return a list of all parsed tokens in an And alternative, " + "in prior versions only the first token was returned; enclose " + "contained argument in Group".format( + "warn_multiple_tokens_in_named_alternation", + name, + type(self).__name__, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class Each(ParseExpression): + """Requires all given :class:`ParseExpression` s to be found, but in + any order. Expressions may be separated by whitespace. + + May be constructed using the ``'&'`` operator. + + Example:: + + color = one_of("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") + shape_type = one_of("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 & Opt(color_attr) & Opt(size_attr) + + shape_spec.run_tests(''' + 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: typing.Iterable[ParserElement], savelist: bool = True): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + self.skipWhitespace = True + self.initExprGroups = True + self.saveAsList = True + + def __iand__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + return NotImplemented + return self.append(other) # Each([self, other]) + + def streamline(self) -> ParserElement: + super().streamline() + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + return self + + 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, Opt) + ) + opt1 = [e.expr for e in self.exprs if isinstance(e, Opt)] + opt2 = [ + e + for e in self.exprs + if e.mayReturnEmpty and not isinstance(e, (Opt, Regex, ZeroOrMore)) + ] + self.optionals = opt1 + opt2 + self.multioptionals = [ + e.expr.set_results_name(e.resultsName, list_all_matches=True) + for e in self.exprs + if isinstance(e, _MultipleMatch) + ] + self.multirequired = [ + e.expr.set_results_name(e.resultsName, list_all_matches=True) + for e in self.exprs + if isinstance(e, OneOrMore) + ] + self.required = [ + e for e in self.exprs if not isinstance(e, (Opt, ZeroOrMore, OneOrMore)) + ] + self.required += self.multirequired + self.initExprGroups = False + + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + multis = self.multioptionals[:] + matchOrder = [] + + keepMatching = True + failed = [] + fatals = [] + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + multis + failed.clear() + fatals.clear() + for e in tmpExprs: + try: + tmpLoc = e.try_parse(instring, tmpLoc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parser_element = e + fatals.append(pfe) + failed.append(e) + 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 + + # look for any ParseFatalExceptions + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parser_element)))) + max_fatal = fatals[0] + raise max_fatal + + if tmpReqd: + missing = ", ".join([str(e) for e in tmpReqd]) + raise ParseException( + instring, + loc, + f"Missing one or more required elements ({missing})", + ) + + # add any unmatched Opts, in case they have default values defined + matchOrder += [e for e in self.exprs if isinstance(e, Opt) and e.expr in tmpOpt] + + total_results = ParseResults([]) + for e in matchOrder: + loc, results = e._parse(instring, loc, doActions) + total_results += results + + return loc, total_results + + def _generateDefaultName(self) -> str: + return "{" + " & ".join(str(e) for e in self.exprs) + "}" + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of :class:`ParserElement`, for combining and + post-processing parsed tokens. + """ + + def __init__(self, expr: Union[ParserElement, str], savelist: bool = False): + super().__init__(savelist) + if isinstance(expr, str_type): + expr_str = typing.cast(str, expr) + if issubclass(self._literalStringClass, Token): + expr = self._literalStringClass(expr_str) # type: ignore[call-arg] + elif issubclass(type(self), self._literalStringClass): + expr = Literal(expr_str) + else: + expr = self._literalStringClass(Literal(expr_str)) # type: ignore[assignment, call-arg] + expr = typing.cast(ParserElement, expr) + self.expr = expr + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.set_whitespace_chars( + expr.whiteChars, copy_defaults=expr.copyDefaultWhiteChars + ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def recurse(self) -> List[ParserElement]: + return [self.expr] if self.expr is not None else [] + + def parseImpl(self, instring, loc, doActions=True): + if self.expr is not None: + try: + return self.expr._parse(instring, loc, doActions, callPreParse=False) + except ParseBaseException as pbe: + if not isinstance(self, Forward) or self.customName is not None: + pbe.msg = self.errmsg + raise + else: + raise ParseException(instring, loc, "No expression defined", self) + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + super().leave_whitespace(recursive) + + if recursive: + if self.expr is not None: + self.expr = self.expr.copy() + self.expr.leave_whitespace(recursive) + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + super().ignore_whitespace(recursive) + + if recursive: + if self.expr is not None: + self.expr = self.expr.copy() + self.expr.ignore_whitespace(recursive) + return self + + def ignore(self, other) -> ParserElement: + if isinstance(other, Suppress): + if other not in self.ignoreExprs: + super().ignore(other) + if self.expr is not None: + self.expr.ignore(self.ignoreExprs[-1]) + else: + super().ignore(other) + if self.expr is not None: + self.expr.ignore(self.ignoreExprs[-1]) + return self + + def streamline(self) -> ParserElement: + super().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=None) -> None: + warnings.warn( + "ParserElement.validate() is deprecated, and should not be used to check for left recursion", + DeprecationWarning, + stacklevel=2, + ) + if validateTrace is None: + validateTrace = [] + tmp = validateTrace[:] + [self] + if self.expr is not None: + self.expr.validate(tmp) + self._checkRecursion([]) + + def _generateDefaultName(self) -> str: + return f"{self.__class__.__name__}:({str(self.expr)})" + + # Compatibility synonyms + # fmt: off + @replaced_by_pep8(leave_whitespace) + def leaveWhitespace(self): ... + + @replaced_by_pep8(ignore_whitespace) + def ignoreWhitespace(self): ... + # fmt: on + + +class IndentedBlock(ParseElementEnhance): + """ + Expression to match one or more expressions at a given indentation level. + Useful for parsing text where structure is implied by indentation (like Python source code). + """ + + class _Indent(Empty): + def __init__(self, ref_col: int): + super().__init__() + self.errmsg = f"expected indent at column {ref_col}" + self.add_condition(lambda s, l, t: col(l, s) == ref_col) + + class _IndentGreater(Empty): + def __init__(self, ref_col: int): + super().__init__() + self.errmsg = f"expected indent at column greater than {ref_col}" + self.add_condition(lambda s, l, t: col(l, s) > ref_col) + + def __init__( + self, expr: ParserElement, *, recursive: bool = False, grouped: bool = True + ): + super().__init__(expr, savelist=True) + # if recursive: + # raise NotImplementedError("IndentedBlock with recursive is not implemented") + self._recursive = recursive + self._grouped = grouped + self.parent_anchor = 1 + + def parseImpl(self, instring, loc, doActions=True): + # advance parse position to non-whitespace by using an Empty() + # this should be the column to be used for all subsequent indented lines + anchor_loc = Empty().preParse(instring, loc) + + # see if self.expr matches at the current location - if not it will raise an exception + # and no further work is necessary + self.expr.try_parse(instring, anchor_loc, do_actions=doActions) + + indent_col = col(anchor_loc, instring) + peer_detect_expr = self._Indent(indent_col) + + inner_expr = Empty() + peer_detect_expr + self.expr + if self._recursive: + sub_indent = self._IndentGreater(indent_col) + nested_block = IndentedBlock( + self.expr, recursive=self._recursive, grouped=self._grouped + ) + nested_block.set_debug(self.debug) + nested_block.parent_anchor = indent_col + inner_expr += Opt(sub_indent + nested_block) + + inner_expr.set_name(f"inner {hex(id(inner_expr))[-4:].upper()}@{indent_col}") + block = OneOrMore(inner_expr) + + trailing_undent = self._Indent(self.parent_anchor) | StringEnd() + + if self._grouped: + wrapper = Group + else: + wrapper = lambda expr: expr + return (wrapper(block) + Optional(trailing_undent)).parseImpl( + instring, anchor_loc, doActions + ) + + +class AtStringStart(ParseElementEnhance): + """Matches if expression matches at the beginning of the parse + string:: + + AtStringStart(Word(nums)).parse_string("123") + # prints ["123"] + + AtStringStart(Word(nums)).parse_string(" 123") + # raises ParseException + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.callPreparse = False + + def parseImpl(self, instring, loc, doActions=True): + if loc != 0: + raise ParseException(instring, loc, "not found at string start") + return super().parseImpl(instring, loc, doActions) + + +class AtLineStart(ParseElementEnhance): + r"""Matches if an expression matches at the beginning of a line within + the parse string + + Example:: + + test = '''\ + AAA this line + AAA and this line + AAA but not this one + B AAA and definitely not this one + ''' + + for t in (AtLineStart('AAA') + rest_of_line).search_string(test): + print(t) + + prints:: + + ['AAA', ' this line'] + ['AAA', ' and this line'] + + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.callPreparse = False + + def parseImpl(self, instring, loc, doActions=True): + if col(loc, instring) != 1: + raise ParseException(instring, loc, "not found at line start") + return super().parseImpl(instring, loc, doActions) + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. + ``FollowedBy`` does *not* advance the parsing position within + the input string, it only verifies that the specified parse + expression matches at the current position. ``FollowedBy`` + always returns a null token list. If any results names are defined + in the lookahead expression, those *will* be returned for access by + name. + + 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, stop_on=label).set_parse_action(' '.join)) + + attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint() + + prints:: + + [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + # by using self._expr.parse and deleting the contents of the returned ParseResults list + # we keep any named results that were defined in the FollowedBy expression + _, ret = self.expr._parse(instring, loc, doActions=doActions) + del ret[:] + + return loc, ret + + +class PrecededBy(ParseElementEnhance): + """Lookbehind matching of the given parse expression. + ``PrecededBy`` does not advance the parsing position within the + input string, it only verifies that the specified parse expression + matches prior to the current position. ``PrecededBy`` always + returns a null token list, but if a results name is defined on the + given expression, it is returned. + + Parameters: + + - ``expr`` - expression that must match prior to the current parse + location + - ``retreat`` - (default= ``None``) - (int) maximum number of characters + to lookbehind prior to the current parse location + + If the lookbehind expression is a string, :class:`Literal`, + :class:`Keyword`, or a :class:`Word` or :class:`CharsNotIn` + with a specified exact or maximum length, then the retreat + parameter is not required. Otherwise, retreat must be specified to + give a maximum number of characters to look back from + the current parse position for a lookbehind match. + + Example:: + + # VB-style variable names with type prefixes + int_var = PrecededBy("#") + pyparsing_common.identifier + str_var = PrecededBy("$") + pyparsing_common.identifier + + """ + + def __init__( + self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None + ): + super().__init__(expr) + self.expr = self.expr().leave_whitespace() + self.mayReturnEmpty = True + self.mayIndexError = False + self.exact = False + if isinstance(expr, str_type): + expr = typing.cast(str, expr) + retreat = len(expr) + self.exact = True + elif isinstance(expr, (Literal, Keyword)): + retreat = expr.matchLen + self.exact = True + elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT: + retreat = expr.maxLen + self.exact = True + elif isinstance(expr, PositionToken): + retreat = 0 + self.exact = True + self.retreat = retreat + self.errmsg = "not preceded by " + str(expr) + self.skipWhitespace = False + self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None))) + + def parseImpl(self, instring, loc=0, doActions=True): + if self.exact: + if loc < self.retreat: + raise ParseException(instring, loc, self.errmsg) + start = loc - self.retreat + _, ret = self.expr._parse(instring, start) + else: + # retreat specified a maximum lookbehind window, iterate + test_expr = self.expr + StringEnd() + instring_slice = instring[max(0, loc - self.retreat) : loc] + last_expr = ParseException(instring, loc, self.errmsg) + for offset in range(1, min(loc, self.retreat + 1) + 1): + try: + # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:])) + _, ret = test_expr._parse( + instring_slice, len(instring_slice) - offset + ) + except ParseBaseException as pbe: + last_expr = pbe + else: + break + else: + raise last_expr + return loc, ret + + +class Located(ParseElementEnhance): + """ + Decorates 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 ```` characters, you + may want to call :class:`ParserElement.parse_with_tabs` + + Example:: + + wd = Word(alphas) + for match in Located(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [0, ['ljsdf'], 5] + [8, ['lksdjjf'], 15] + [18, ['lkkjj'], 23] + + """ + + def parseImpl(self, instring, loc, doActions=True): + start = loc + loc, tokens = self.expr._parse(instring, start, doActions, callPreParse=False) + ret_tokens = ParseResults([start, tokens, loc]) + ret_tokens["locn_start"] = start + ret_tokens["value"] = tokens + ret_tokens["locn_end"] = loc + if self.resultsName: + # must return as a list, so that the name will be attached to the complete group + return loc, [ret_tokens] + else: + return loc, ret_tokens + + +class NotAny(ParseElementEnhance): + """ + Lookahead to disallow matching with the given parse expression. + ``NotAny`` does *not* advance the parsing position within the + input string, it only verifies that the specified parse expression + does *not* match at the current position. Also, ``NotAny`` does + *not* skip over leading whitespace. ``NotAny`` always returns + a null token list. May be constructed using the ``'~'`` operator. + + Example:: + + AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split()) + + # take care not to mistake keywords for identifiers + ident = ~(AND | OR | NOT) + Word(alphas) + boolean_term = Opt(NOT) + ident + + # very crude boolean expression - to support parenthesis groups and + # operation hierarchy, use infix_notation + boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...] + + # integers that are followed by "." are actually floats + integer = Word(nums) + ~Char(".") + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + # do NOT use self.leave_whitespace(), don't want to propagate to exprs + # self.leave_whitespace() + self.skipWhitespace = False + + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, " + str(self.expr) + + def parseImpl(self, instring, loc, doActions=True): + if self.expr.can_parse_next(instring, loc, do_actions=doActions): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + def _generateDefaultName(self) -> str: + return "~{" + str(self.expr) + "}" + + +class _MultipleMatch(ParseElementEnhance): + def __init__( + self, + expr: Union[str, ParserElement], + stop_on: typing.Optional[Union[ParserElement, str]] = None, + *, + stopOn: typing.Optional[Union[ParserElement, str]] = None, + ): + super().__init__(expr) + stopOn = stopOn or stop_on + self.saveAsList = True + ender = stopOn + if isinstance(ender, str_type): + ender = self._literalStringClass(ender) + self.stopOn(ender) + + def stopOn(self, ender) -> ParserElement: + if isinstance(ender, str_type): + ender = self._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + return self + + 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.try_parse + + # 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) + 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) + tokens += tmptokens + except (ParseException, IndexError): + pass + + return loc, tokens + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_ungrouped_named_tokens_in_collection + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in self.suppress_warnings_ + ): + for e in [self.expr] + self.expr.recurse(): + if ( + isinstance(e, ParserElement) + and e.resultsName + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in e.suppress_warnings_ + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "collides with {!r} on contained expression".format( + "warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class OneOrMore(_MultipleMatch): + """ + Repetition of one or more of the given expression. + + Parameters: + + - ``expr`` - expression that must match one or more times + - ``stop_on`` - (default= ``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).set_parse_action(' '.join)) + + text = "shape: SQUARE posn: upper left color: BLACK" + attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] + + # use stop_on attribute for OneOrMore to avoid reading label string as part of the data + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + OneOrMore(attr_expr).parse_string(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] + + # could also be written as + (attr_expr * (1,)).parse_string(text).pprint() + """ + + def _generateDefaultName(self) -> str: + return "{" + str(self.expr) + "}..." + + +class ZeroOrMore(_MultipleMatch): + """ + Optional repetition of zero or more of the given expression. + + Parameters: + + - ``expr`` - expression that must match zero or more times + - ``stop_on`` - expression for a terminating sentinel + (only required if the sentinel would ordinarily match the repetition + expression) - (default= ``None``) + + Example: similar to :class:`OneOrMore` + """ + + def __init__( + self, + expr: Union[str, ParserElement], + stop_on: typing.Optional[Union[ParserElement, str]] = None, + *, + stopOn: typing.Optional[Union[ParserElement, str]] = None, + ): + super().__init__(expr, stopOn=stopOn or stop_on) + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + try: + return super().parseImpl(instring, loc, doActions) + except (ParseException, IndexError): + return loc, ParseResults([], name=self.resultsName) + + def _generateDefaultName(self) -> str: + return "[" + str(self.expr) + "]..." + + +class DelimitedList(ParseElementEnhance): + def __init__( + self, + expr: Union[str, ParserElement], + delim: Union[str, ParserElement] = ",", + combine: bool = False, + min: typing.Optional[int] = None, + max: typing.Optional[int] = None, + *, + allow_trailing_delim: bool = False, + ): + """Helper to define a delimited list of expressions - the delimiter + defaults to ','. By default, the list elements and delimiters can + have intervening whitespace, and comments, but this can be + overridden by passing ``combine=True`` in the constructor. If + ``combine`` is set to ``True``, the matching tokens are + returned as a single token string, with the delimiters included; + otherwise, the matching tokens are returned as a list of tokens, + with the delimiters suppressed. + + If ``allow_trailing_delim`` is set to True, then the list may end with + a delimiter. + + Example:: + + DelimitedList(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc'] + DelimitedList(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] + """ + if isinstance(expr, str_type): + expr = ParserElement._literalStringClass(expr) + expr = typing.cast(ParserElement, expr) + + if min is not None: + if min < 1: + raise ValueError("min must be greater than 0") + if max is not None: + if min is not None and max < min: + raise ValueError("max must be greater than, or equal to min") + + self.content = expr + self.raw_delim = str(delim) + self.delim = delim + self.combine = combine + if not combine: + self.delim = Suppress(delim) + self.min = min or 1 + self.max = max + self.allow_trailing_delim = allow_trailing_delim + + delim_list_expr = self.content + (self.delim + self.content) * ( + self.min - 1, + None if self.max is None else self.max - 1, + ) + if self.allow_trailing_delim: + delim_list_expr += Opt(self.delim) + + if self.combine: + delim_list_expr = Combine(delim_list_expr) + + super().__init__(delim_list_expr, savelist=True) + + def _generateDefaultName(self) -> str: + return "{0} [{1} {0}]...".format(self.content.streamline(), self.raw_delim) + + +class _NullToken: + def __bool__(self): + return False + + def __str__(self): + return "" + + +class Opt(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) + Opt('-' + Word(nums, exact=4))) + zip.run_tests(''' + # 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) + """ + + __optionalNotMatched = _NullToken() + + def __init__( + self, expr: Union[ParserElement, str], default: Any = __optionalNotMatched + ): + super().__init__(expr, savelist=False) + self.saveAsList = self.expr.saveAsList + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + self_expr = self.expr + try: + loc, tokens = self_expr._parse(instring, loc, doActions, callPreParse=False) + except (ParseException, IndexError): + default_value = self.defaultValue + if default_value is not self.__optionalNotMatched: + if self_expr.resultsName: + tokens = ParseResults([default_value]) + tokens[self_expr.resultsName] = default_value + else: + tokens = [default_value] + else: + tokens = [] + return loc, tokens + + def _generateDefaultName(self) -> str: + inner = str(self.expr) + # strip off redundant inner {}'s + while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}": + inner = inner[1:-1] + return "[" + inner + "]" + + +Optional = Opt + + +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`` - if ``True``, the target expression is also parsed + (the skipped text and target expression are returned as a 2-element + list) (default= ``False``). + - ``ignore`` - (default= ``None``) used to define grammars (typically quoted strings and + comments) that might contain false matches to the target expression + - ``fail_on`` - (default= ``None``) define expressions that are not allowed to be + included in the skipped test; if found before the target expression is found, + the :class:`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=quoted_string) + string_data.set_parse_action(token_map(str.strip)) + ticket_expr = (integer("issue_num") + SEP + + string_data("sev") + SEP + + string_data("desc") + SEP + + integer("days_open")) + + for tkt in ticket_expr.search_string(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: Union[ParserElement, str], + include: bool = False, + ignore: typing.Optional[Union[ParserElement, str]] = None, + fail_on: typing.Optional[Union[ParserElement, str]] = None, + *, + failOn: typing.Optional[Union[ParserElement, str]] = None, + ): + super().__init__(other) + failOn = failOn or fail_on + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.saveAsList = False + if isinstance(failOn, str_type): + self.failOn = self._literalStringClass(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for " + str(self.expr) + self.ignorer = Empty().leave_whitespace() + self._update_ignorer() + + def _update_ignorer(self): + # rebuild internal ignore expr from current ignore exprs and assigned ignoreExpr + self.ignorer.ignoreExprs.clear() + for e in self.expr.ignoreExprs: + self.ignorer.ignore(e) + if self.ignoreExpr: + self.ignorer.ignore(self.ignoreExpr) + + def ignore(self, expr): + super().ignore(expr) + self._update_ignorer() + + def parseImpl(self, instring, loc, doActions=True): + startloc = loc + instrlen = len(instring) + self_expr_parse = self.expr._parse + self_failOn_canParseNext = ( + self.failOn.canParseNext if self.failOn is not None else None + ) + ignorer_try_parse = self.ignorer.try_parse if self.ignorer.ignoreExprs 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 ignorer_try_parse is not None: + # advance past ignore expressions + prev_tmploc = tmploc + while 1: + try: + tmploc = ignorer_try_parse(instring, tmploc) + except ParseBaseException: + break + # see if all ignorers matched, but didn't actually ignore anything + if tmploc == prev_tmploc: + break + prev_tmploc = tmploc + + try: + self_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 = self_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 ``Forward`` + variable using the ``'<<'`` operator. + + Note: take care when assigning to ``Forward`` not to overlook + precedence of operators. + + Specifically, ``'|'`` has a lower precedence than ``'<<'``, so that:: + + fwd_expr << a | b | c + + will actually be evaluated as:: + + (fwd_expr << a) | b | c + + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the ``Forward``:: + + fwd_expr << (a | b | c) + + Converting to use the ``'<<='`` operator instead will avoid this problem. + + See :class:`ParseResults.pprint` for an example of a recursive + parser created using ``Forward``. + """ + + def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None): + self.caller_frame = traceback.extract_stack(limit=2)[0] + super().__init__(other, savelist=False) # type: ignore[arg-type] + self.lshift_line = None + + def __lshift__(self, other) -> "Forward": + if hasattr(self, "caller_frame"): + del self.caller_frame + if isinstance(other, str_type): + other = self._literalStringClass(other) + + if not isinstance(other, ParserElement): + return NotImplemented + + self.expr = other + self.streamlined = other.streamlined + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.set_whitespace_chars( + self.expr.whiteChars, copy_defaults=self.expr.copyDefaultWhiteChars + ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + self.lshift_line = traceback.extract_stack(limit=2)[-2] # type: ignore[assignment] + return self + + def __ilshift__(self, other) -> "Forward": + if not isinstance(other, ParserElement): + return NotImplemented + + return self << other + + def __or__(self, other) -> "ParserElement": + caller_line = traceback.extract_stack(limit=2)[-2] + if ( + __diag__.warn_on_match_first_with_lshift_operator + and caller_line == self.lshift_line + and Diagnostics.warn_on_match_first_with_lshift_operator + not in self.suppress_warnings_ + ): + warnings.warn( + "using '<<' operator with '|' is probably an error, use '<<='", + stacklevel=2, + ) + ret = super().__or__(other) + return ret + + def __del__(self): + # see if we are getting dropped because of '=' reassignment of var instead of '<<=' or '<<' + if ( + self.expr is None + and __diag__.warn_on_assignment_to_Forward + and Diagnostics.warn_on_assignment_to_Forward not in self.suppress_warnings_ + ): + warnings.warn_explicit( + "Forward defined here but no expression attached later using '<<=' or '<<'", + UserWarning, + filename=self.caller_frame.filename, + lineno=self.caller_frame.lineno, + ) + + def parseImpl(self, instring, loc, doActions=True): + if ( + self.expr is None + and __diag__.warn_on_parse_using_empty_Forward + and Diagnostics.warn_on_parse_using_empty_Forward + not in self.suppress_warnings_ + ): + # walk stack until parse_string, scan_string, search_string, or transform_string is found + parse_fns = ( + "parse_string", + "scan_string", + "search_string", + "transform_string", + ) + tb = traceback.extract_stack(limit=200) + for i, frm in enumerate(reversed(tb), start=1): + if frm.name in parse_fns: + stacklevel = i + 1 + break + else: + stacklevel = 2 + warnings.warn( + "Forward expression was never assigned a value, will not parse any input", + stacklevel=stacklevel, + ) + if not ParserElement._left_recursion_enabled: + return super().parseImpl(instring, loc, doActions) + # ## Bounded Recursion algorithm ## + # Recursion only needs to be processed at ``Forward`` elements, since they are + # the only ones that can actually refer to themselves. The general idea is + # to handle recursion stepwise: We start at no recursion, then recurse once, + # recurse twice, ..., until more recursion offers no benefit (we hit the bound). + # + # The "trick" here is that each ``Forward`` gets evaluated in two contexts + # - to *match* a specific recursion level, and + # - to *search* the bounded recursion level + # and the two run concurrently. The *search* must *match* each recursion level + # to find the best possible match. This is handled by a memo table, which + # provides the previous match to the next level match attempt. + # + # See also "Left Recursion in Parsing Expression Grammars", Medeiros et al. + # + # There is a complication since we not only *parse* but also *transform* via + # actions: We do not want to run the actions too often while expanding. Thus, + # we expand using `doActions=False` and only run `doActions=True` if the next + # recursion level is acceptable. + with ParserElement.recursion_lock: + memo = ParserElement.recursion_memos + try: + # we are parsing at a specific recursion expansion - use it as-is + prev_loc, prev_result = memo[loc, self, doActions] + if isinstance(prev_result, Exception): + raise prev_result + return prev_loc, prev_result.copy() + except KeyError: + act_key = (loc, self, True) + peek_key = (loc, self, False) + # we are searching for the best recursion expansion - keep on improving + # both `doActions` cases must be tracked separately here! + prev_loc, prev_peek = memo[peek_key] = ( + loc - 1, + ParseException( + instring, loc, "Forward recursion without base case", self + ), + ) + if doActions: + memo[act_key] = memo[peek_key] + while True: + try: + new_loc, new_peek = super().parseImpl(instring, loc, False) + except ParseException: + # we failed before getting any match – do not hide the error + if isinstance(prev_peek, Exception): + raise + new_loc, new_peek = prev_loc, prev_peek + # the match did not get better: we are done + if new_loc <= prev_loc: + if doActions: + # replace the match for doActions=False as well, + # in case the action did backtrack + prev_loc, prev_result = memo[peek_key] = memo[act_key] + del memo[peek_key], memo[act_key] + return prev_loc, prev_result.copy() + del memo[peek_key] + return prev_loc, prev_peek.copy() + # the match did get better: see if we can improve further + else: + if doActions: + try: + memo[act_key] = super().parseImpl(instring, loc, True) + except ParseException as e: + memo[peek_key] = memo[act_key] = (new_loc, e) + raise + prev_loc, prev_peek = memo[peek_key] = new_loc, new_peek + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + self.skipWhitespace = False + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + self.skipWhitespace = True + return self + + def streamline(self) -> ParserElement: + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate(self, validateTrace=None) -> None: + warnings.warn( + "ParserElement.validate() is deprecated, and should not be used to check for left recursion", + DeprecationWarning, + stacklevel=2, + ) + if validateTrace is None: + validateTrace = [] + + if self not in validateTrace: + tmp = validateTrace[:] + [self] + if self.expr is not None: + self.expr.validate(tmp) + self._checkRecursion([]) + + def _generateDefaultName(self) -> str: + # Avoid infinite recursion by setting a temporary _defaultName + self._defaultName = ": ..." + + # Use the string representation of main expression. + retString = "..." + try: + if self.expr is not None: + retString = str(self.expr)[:1000] + else: + retString = "None" + finally: + return self.__class__.__name__ + ": " + retString + + def copy(self) -> ParserElement: + if self.expr is not None: + return super().copy() + else: + ret = Forward() + ret <<= self + return ret + + def _setResultsName(self, name, list_all_matches=False): + if ( + __diag__.warn_name_set_on_empty_Forward + and Diagnostics.warn_name_set_on_empty_Forward + not in self.suppress_warnings_ + ): + if self.expr is None: + warnings.warn( + "{}: setting results name {!r} on {} expression " + "that has no contained expression".format( + "warn_name_set_on_empty_Forward", name, type(self).__name__ + ), + stacklevel=3, + ) + + return super()._setResultsName(name, list_all_matches) + + # Compatibility synonyms + # fmt: off + @replaced_by_pep8(leave_whitespace) + def leaveWhitespace(self): ... + + @replaced_by_pep8(ignore_whitespace) + def ignoreWhitespace(self): ... + # fmt: on + + +class TokenConverter(ParseElementEnhance): + """ + Abstract subclass of :class:`ParseExpression`, for converting parsed results. + """ + + def __init__(self, expr: Union[ParserElement, str], savelist=False): + super().__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 + ``'adjacent=False'`` in the constructor. + + Example:: + + real = Word(nums) + '.' + Word(nums) + print(real.parse_string('3.1416')) # -> ['3', '.', '1416'] + # will also erroneously match the following + print(real.parse_string('3. 1416')) # -> ['3', '.', '1416'] + + real = Combine(Word(nums) + '.' + Word(nums)) + print(real.parse_string('3.1416')) # -> ['3.1416'] + # no match when there are internal spaces + print(real.parse_string('3. 1416')) # -> Exception: Expected W:(0123...) + """ + + def __init__( + self, + expr: ParserElement, + join_string: str = "", + adjacent: bool = True, + *, + joinString: typing.Optional[str] = None, + ): + super().__init__(expr) + joinString = joinString if joinString is not None else join_string + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leave_whitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + self.callPreparse = True + + def ignore(self, other) -> ParserElement: + if self.adjacent: + ParserElement.ignore(self, other) + else: + super().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 :class:`ZeroOrMore` and :class:`OneOrMore` expressions. + + The optional ``aslist`` argument when set to True will return the + parsed tokens as a Python list instead of a pyparsing ParseResults. + + Example:: + + ident = Word(alphas) + num = Word(nums) + term = ident | num + func = ident + Opt(DelimitedList(term)) + print(func.parse_string("fn a, b, 100")) + # -> ['fn', 'a', 'b', '100'] + + func = ident + Group(Opt(DelimitedList(term))) + print(func.parse_string("fn a, b, 100")) + # -> ['fn', ['a', 'b', '100']] + """ + + def __init__(self, expr: ParserElement, aslist: bool = False): + super().__init__(expr) + self.saveAsList = True + self._asPythonList = aslist + + def postParse(self, instring, loc, tokenlist): + if self._asPythonList: + return ParseResults.List( + tokenlist.asList() + if isinstance(tokenlist, ParseResults) + else list(tokenlist) + ) + else: + 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. + + The optional ``asdict`` argument when set to True will return the + parsed tokens as a Python dict instead of a pyparsing ParseResults. + + Example:: + + data_word = Word(alphas) + label = data_word + FollowedBy(':') + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + + # print attributes as plain groups + print(attr_expr[1, ...].parse_string(text).dump()) + + # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names + result = Dict(Group(attr_expr)[1, ...]).parse_string(text) + print(result.dump()) + + # access named fields as dict entries, or output as dict + print(result['shape']) + print(result.as_dict()) + + 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 :class:`ParseResults` of accessing fields by results name. + """ + + def __init__(self, expr: ParserElement, asdict: bool = False): + super().__init__(expr) + self.saveAsList = True + self._asPythonDict = asdict + + 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 = str(ikey).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: + try: + dictvalue = tok.copy() # ParseResults(i) + except Exception: + exc = TypeError( + "could not extract dict values from parsed results" + " - Dict expression must contain Grouped expressions" + ) + raise exc from None + + 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._asPythonDict: + return [tokenlist.as_dict()] if self.resultsName else tokenlist.as_dict() + else: + return [tokenlist] if self.resultsName else 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 + (',' + wd)[...] + print(wd_list1.parse_string(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 + (Suppress(',') + wd)[...] + print(wd_list2.parse_string(source)) + + # Skipped text (using '...') can be suppressed as well + source = "lead in START relevant text END trailing text" + start_marker = Keyword("START") + end_marker = Keyword("END") + find_body = Suppress(...) + start_marker + ... + end_marker + print(find_body.parse_string(source) + + prints:: + + ['a', ',', 'b', ',', 'c', ',', 'd'] + ['a', 'b', 'c', 'd'] + ['START', 'relevant text ', 'END'] + + (See also :class:`DelimitedList`.) + """ + + def __init__(self, expr: Union[ParserElement, str], savelist: bool = False): + if expr is ...: + expr = _PendingSkip(NoMatch()) + super().__init__(expr) + + def __add__(self, other) -> "ParserElement": + if isinstance(self.expr, _PendingSkip): + return Suppress(SkipTo(other)) + other + else: + return super().__add__(other) + + def __sub__(self, other) -> "ParserElement": + if isinstance(self.expr, _PendingSkip): + return Suppress(SkipTo(other)) - other + else: + return super().__sub__(other) + + def postParse(self, instring, loc, tokenlist): + return [] + + def suppress(self) -> ParserElement: + return self + + +def trace_parse_action(f: ParseAction) -> ParseAction: + """Decorator for debugging parse actions. + + When the parse action is called, this decorator will print + ``">> entering method-name(line:, , )"``. + When the parse action completes, the decorator will print + ``"<<"`` followed by the returned value, or any exception that the parse action raised. + + Example:: + + wd = Word(alphas) + + @trace_parse_action + def remove_duplicate_chars(tokens): + return ''.join(sorted(set(''.join(tokens)))) + + wds = wd[1, ...].set_parse_action(remove_duplicate_chars) + print(wds.parse_string("slkdjs sld sldd sdlf sdljf")) + + prints:: + + >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) + < 3: + thisFunc = paArgs[0].__class__.__name__ + "." + thisFunc + sys.stderr.write(f">>entering {thisFunc}(line: {line(l, s)!r}, {l}, {t!r})\n") + try: + ret = f(*paArgs) + except Exception as exc: + sys.stderr.write(f"< str: + r"""Helper to easily define string ranges for use in :class:`Word` + construction. Borrows syntax from regexp ``'[]'`` string range + definitions:: + + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + + The input string must be enclosed in []'s, and the returned string + is the expanded character set joined into a single string. The + values enclosed in the []'s may be: + + - a single character + - an escaped character with a leading backslash (such as ``\-`` + or ``\]``) + - an escaped hex character with a leading ``'\x'`` + (``\x21``, which is a ``'!'`` character) (``\0x##`` + is also supported for backwards compatibility) + - an escaped octal character with a leading ``'\0'`` + (``\041``, which is a ``'!'`` character) + - a range of any of the above, separated by a dash (``'a-z'``, + etc.) + - any combination of the above (``'aeiouy'``, + ``'a-zA-Z0-9_$'``, etc.) + """ + _expanded = ( + lambda p: p + if not isinstance(p, ParseResults) + else "".join(chr(c) for c in range(ord(p[0]), ord(p[1]) + 1)) + ) + try: + return "".join(_expanded(part) for part in _reBracketExpr.parse_string(s).body) + except Exception as e: + return "" + + +def token_map(func, *args) -> ParseAction: + """Helper to define a parse action by mapping a function to all + elements of a :class:`ParseResults` list. If any additional args are passed, + they are forwarded to the given function as additional arguments + after the token, as in + ``hex_integer = Word(hexnums).set_parse_action(token_map(int, 16))``, + which will convert the parsed data to an integer using base 16. + + Example (compare the last to example in :class:`ParserElement.transform_string`:: + + hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16)) + hex_ints.run_tests(''' + 00 11 22 aa FF 0a 0d 1a + ''') + + upperword = Word(alphas).set_parse_action(token_map(str.upper)) + upperword[1, ...].run_tests(''' + my kingdom for a horse + ''') + + wd = Word(alphas).set_parse_action(token_map(str.title)) + wd[1, ...].set_parse_action(' '.join).run_tests(''' + 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] + + func_name = getattr(func, "__name__", getattr(func, "__class__").__name__) + pa.__name__ = func_name + + return pa + + +def autoname_elements() -> None: + """ + Utility to simplify mass-naming of parser elements, for + generating railroad diagram with named subdiagrams. + """ + calling_frame = sys._getframe().f_back + if calling_frame is None: + return + calling_frame = typing.cast(types.FrameType, calling_frame) + for name, var in calling_frame.f_locals.items(): + if isinstance(var, ParserElement) and not var.customName: + var.set_name(name) + + +dbl_quoted_string = Combine( + Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' +).set_name("string enclosed in double quotes") + +sgl_quoted_string = Combine( + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'" +).set_name("string enclosed in single quotes") + +quoted_string = Combine( + (Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').set_name( + "double quoted string" + ) + | (Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").set_name( + "single quoted string" + ) +).set_name("quoted string using single or double quotes") + +python_quoted_string = Combine( + (Regex(r'"""(?:[^"\\]|""(?!")|"(?!"")|\\.)*', flags=re.MULTILINE) + '"""').set_name( + "multiline double quoted string" + ) + ^ ( + Regex(r"'''(?:[^'\\]|''(?!')|'(?!'')|\\.)*", flags=re.MULTILINE) + "'''" + ).set_name("multiline single quoted string") + ^ (Regex(r'"(?:[^"\n\r\\]|(?:\\")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').set_name( + "double quoted string" + ) + ^ (Regex(r"'(?:[^'\n\r\\]|(?:\\')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").set_name( + "single quoted string" + ) +).set_name("Python quoted string") + +unicode_string = Combine("u" + quoted_string.copy()).set_name("unicode string literal") + + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + +# backward compatibility names +# fmt: off +sglQuotedString = sgl_quoted_string +dblQuotedString = dbl_quoted_string +quotedString = quoted_string +unicodeString = unicode_string +lineStart = line_start +lineEnd = line_end +stringStart = string_start +stringEnd = string_end + +@replaced_by_pep8(null_debug_action) +def nullDebugAction(): ... + +@replaced_by_pep8(trace_parse_action) +def traceParseAction(): ... + +@replaced_by_pep8(condition_as_parse_action) +def conditionAsParseAction(): ... + +@replaced_by_pep8(token_map) +def tokenMap(): ... +# fmt: on diff --git a/script.module.pyparsing/lib/pyparsing/diagram/__init__.py b/script.module.pyparsing/lib/pyparsing/diagram/__init__.py new file mode 100644 index 000000000..267f34474 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/diagram/__init__.py @@ -0,0 +1,656 @@ +# mypy: ignore-errors +import railroad +import pyparsing +import typing +from typing import ( + List, + NamedTuple, + Generic, + TypeVar, + Dict, + Callable, + Set, + Iterable, +) +from jinja2 import Template +from io import StringIO +import inspect + + +jinja2_template_source = """\ +{% if not embed %} + + + +{% endif %} + {% if not head %} + + {% else %} + {{ head | safe }} + {% endif %} +{% if not embed %} + + +{% endif %} +{{ body | safe }} +{% for diagram in diagrams %} +
+

{{ diagram.title }}

+
{{ diagram.text }}
+
+ {{ diagram.svg }} +
+
+{% endfor %} +{% if not embed %} + + +{% endif %} +""" + +template = Template(jinja2_template_source) + +# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet +NamedDiagram = NamedTuple( + "NamedDiagram", + [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)], +) +""" +A simple structure for associating a name with a railroad diagram +""" + +T = TypeVar("T") + + +class EachItem(railroad.Group): + """ + Custom railroad item to compose a: + - Group containing a + - OneOrMore containing a + - Choice of the elements in the Each + with the group label indicating that all must be matched + """ + + all_label = "[ALL]" + + def __init__(self, *items): + choice_item = railroad.Choice(len(items) - 1, *items) + one_or_more_item = railroad.OneOrMore(item=choice_item) + super().__init__(one_or_more_item, label=self.all_label) + + +class AnnotatedItem(railroad.Group): + """ + Simple subclass of Group that creates an annotation label + """ + + def __init__(self, label: str, item): + super().__init__(item=item, label="[{}]".format(label) if label else label) + + +class EditablePartial(Generic[T]): + """ + Acts like a functools.partial, but can be edited. In other words, it represents a type that hasn't yet been + constructed. + """ + + # We need this here because the railroad constructors actually transform the data, so can't be called until the + # entire tree is assembled + + def __init__(self, func: Callable[..., T], args: list, kwargs: dict): + self.func = func + self.args = args + self.kwargs = kwargs + + @classmethod + def from_call(cls, func: Callable[..., T], *args, **kwargs) -> "EditablePartial[T]": + """ + If you call this function in the same way that you would call the constructor, it will store the arguments + as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3) + """ + return EditablePartial(func=func, args=list(args), kwargs=kwargs) + + @property + def name(self): + return self.kwargs["name"] + + def __call__(self) -> T: + """ + Evaluate the partial and return the result + """ + args = self.args.copy() + kwargs = self.kwargs.copy() + + # This is a helpful hack to allow you to specify varargs parameters (e.g. *args) as keyword args (e.g. + # args=['list', 'of', 'things']) + arg_spec = inspect.getfullargspec(self.func) + if arg_spec.varargs in self.kwargs: + args += kwargs.pop(arg_spec.varargs) + + return self.func(*args, **kwargs) + + +def railroad_to_html(diagrams: List[NamedDiagram], embed=False, **kwargs) -> str: + """ + Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams + :params kwargs: kwargs to be passed in to the template + """ + data = [] + for diagram in diagrams: + if diagram.diagram is None: + continue + io = StringIO() + try: + css = kwargs.get('css') + diagram.diagram.writeStandalone(io.write, css=css) + except AttributeError: + diagram.diagram.writeSvg(io.write) + title = diagram.name + if diagram.index == 0: + title += " (root)" + data.append({"title": title, "text": "", "svg": io.getvalue()}) + + return template.render(diagrams=data, embed=embed, **kwargs) + + +def resolve_partial(partial: "EditablePartial[T]") -> T: + """ + Recursively resolves a collection of Partials into whatever type they are + """ + if isinstance(partial, EditablePartial): + partial.args = resolve_partial(partial.args) + partial.kwargs = resolve_partial(partial.kwargs) + return partial() + elif isinstance(partial, list): + return [resolve_partial(x) for x in partial] + elif isinstance(partial, dict): + return {key: resolve_partial(x) for key, x in partial.items()} + else: + return partial + + +def to_railroad( + element: pyparsing.ParserElement, + diagram_kwargs: typing.Optional[dict] = None, + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, +) -> List[NamedDiagram]: + """ + Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram + creation if you want to access the Railroad tree before it is converted to HTML + :param element: base element of the parser being diagrammed + :param diagram_kwargs: kwargs to pass to the Diagram() constructor + :param vertical: (optional) - int - limit at which number of alternatives should be + shown vertically instead of horizontally + :param show_results_names - bool to indicate whether results name annotations should be + included in the diagram + :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled + surrounding box + """ + # Convert the whole tree underneath the root + lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) + _to_diagram_element( + element, + lookup=lookup, + parent=None, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + root_id = id(element) + # Convert the root if it hasn't been already + if root_id in lookup: + if not element.customName: + lookup[root_id].name = "" + lookup[root_id].mark_for_extraction(root_id, lookup, force=True) + + # Now that we're finished, we can convert from intermediate structures into Railroad elements + diags = list(lookup.diagrams.values()) + if len(diags) > 1: + # collapse out duplicate diags with the same name + seen = set() + deduped_diags = [] + for d in diags: + # don't extract SkipTo elements, they are uninformative as subdiagrams + if d.name == "...": + continue + if d.name is not None and d.name not in seen: + seen.add(d.name) + deduped_diags.append(d) + resolved = [resolve_partial(partial) for partial in deduped_diags] + else: + # special case - if just one diagram, always display it, even if + # it has no name + resolved = [resolve_partial(partial) for partial in diags] + return sorted(resolved, key=lambda diag: diag.index) + + +def _should_vertical( + specification: int, exprs: Iterable[pyparsing.ParserElement] +) -> bool: + """ + Returns true if we should return a vertical list of elements + """ + if specification is None: + return False + else: + return len(_visible_exprs(exprs)) >= specification + + +class ElementState: + """ + State recorded for an individual pyparsing Element + """ + + # Note: this should be a dataclass, but we have to support Python 3.5 + def __init__( + self, + element: pyparsing.ParserElement, + converted: EditablePartial, + parent: EditablePartial, + number: int, + name: str = None, + parent_index: typing.Optional[int] = None, + ): + #: The pyparsing element that this represents + self.element: pyparsing.ParserElement = element + #: The name of the element + self.name: typing.Optional[str] = name + #: The output Railroad element in an unconverted state + self.converted: EditablePartial = converted + #: The parent Railroad element, which we store so that we can extract this if it's duplicated + self.parent: EditablePartial = parent + #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram + self.number: int = number + #: The index of this inside its parent + self.parent_index: typing.Optional[int] = parent_index + #: If true, we should extract this out into a subdiagram + self.extract: bool = False + #: If true, all of this element's children have been filled out + self.complete: bool = False + + def mark_for_extraction( + self, el_id: int, state: "ConverterState", name: str = None, force: bool = False + ): + """ + Called when this instance has been seen twice, and thus should eventually be extracted into a sub-diagram + :param el_id: id of the element + :param state: element/diagram state tracker + :param name: name to use for this element's text + :param force: If true, force extraction now, regardless of the state of this. Only useful for extracting the + root element when we know we're finished + """ + self.extract = True + + # Set the name + if not self.name: + if name: + # Allow forcing a custom name + self.name = name + elif self.element.customName: + self.name = self.element.customName + else: + self.name = "" + + # Just because this is marked for extraction doesn't mean we can do it yet. We may have to wait for children + # to be added + # Also, if this is just a string literal etc, don't bother extracting it + if force or (self.complete and _worth_extracting(self.element)): + state.extract_into_diagram(el_id) + + +class ConverterState: + """ + Stores some state that persists between recursions into the element tree + """ + + def __init__(self, diagram_kwargs: typing.Optional[dict] = None): + #: A dictionary mapping ParserElements to state relating to them + self._element_diagram_states: Dict[int, ElementState] = {} + #: A dictionary mapping ParserElement IDs to subdiagrams generated from them + self.diagrams: Dict[int, EditablePartial[NamedDiagram]] = {} + #: The index of the next unnamed element + self.unnamed_index: int = 1 + #: The index of the next element. This is used for sorting + self.index: int = 0 + #: Shared kwargs that are used to customize the construction of diagrams + self.diagram_kwargs: dict = diagram_kwargs or {} + self.extracted_diagram_names: Set[str] = set() + + def __setitem__(self, key: int, value: ElementState): + self._element_diagram_states[key] = value + + def __getitem__(self, key: int) -> ElementState: + return self._element_diagram_states[key] + + def __delitem__(self, key: int): + del self._element_diagram_states[key] + + def __contains__(self, key: int): + return key in self._element_diagram_states + + def generate_unnamed(self) -> int: + """ + Generate a number used in the name of an otherwise unnamed diagram + """ + self.unnamed_index += 1 + return self.unnamed_index + + def generate_index(self) -> int: + """ + Generate a number used to index a diagram + """ + self.index += 1 + return self.index + + def extract_into_diagram(self, el_id: int): + """ + Used when we encounter the same token twice in the same tree. When this + happens, we replace all instances of that token with a terminal, and + create a new subdiagram for the token + """ + position = self[el_id] + + # Replace the original definition of this element with a regular block + if position.parent: + ret = EditablePartial.from_call(railroad.NonTerminal, text=position.name) + if "item" in position.parent.kwargs: + position.parent.kwargs["item"] = ret + elif "items" in position.parent.kwargs: + position.parent.kwargs["items"][position.parent_index] = ret + + # If the element we're extracting is a group, skip to its content but keep the title + if position.converted.func == railroad.Group: + content = position.converted.kwargs["item"] + else: + content = position.converted + + self.diagrams[el_id] = EditablePartial.from_call( + NamedDiagram, + name=position.name, + diagram=EditablePartial.from_call( + railroad.Diagram, content, **self.diagram_kwargs + ), + index=position.number, + ) + + del self[el_id] + + +def _worth_extracting(element: pyparsing.ParserElement) -> bool: + """ + Returns true if this element is worth having its own sub-diagram. Simply, if any of its children + themselves have children, then its complex enough to extract + """ + children = element.recurse() + return any(child.recurse() for child in children) + + +def _apply_diagram_item_enhancements(fn): + """ + decorator to ensure enhancements to a diagram item (such as results name annotations) + get applied on return from _to_diagram_element (we do this since there are several + returns in _to_diagram_element) + """ + + def _inner( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, + ) -> typing.Optional[EditablePartial]: + ret = fn( + element, + parent, + lookup, + vertical, + index, + name_hint, + show_results_names, + show_groups, + ) + + # apply annotation for results name, if present + if show_results_names and ret is not None: + element_results_name = element.resultsName + if element_results_name: + # add "*" to indicate if this is a "list all results" name + element_results_name += "" if element.modalResults else "*" + ret = EditablePartial.from_call( + railroad.Group, item=ret, label=element_results_name + ) + + return ret + + return _inner + + +def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]): + non_diagramming_exprs = ( + pyparsing.ParseElementEnhance, + pyparsing.PositionToken, + pyparsing.And._ErrorStop, + ) + return [ + e + for e in exprs + if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs)) + ] + + +@_apply_diagram_item_enhancements +def _to_diagram_element( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, +) -> typing.Optional[EditablePartial]: + """ + Recursively converts a PyParsing Element to a railroad Element + :param lookup: The shared converter state that keeps track of useful things + :param index: The index of this element within the parent + :param parent: The parent of this element in the output tree + :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default), + it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never + do so + :param name_hint: If provided, this will override the generated name + :param show_results_names: bool flag indicating whether to add annotations for results names + :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed + :param show_groups: bool flag indicating whether to show groups using bounding box + """ + exprs = element.recurse() + name = name_hint or element.customName or element.__class__.__name__ + + # Python's id() is used to provide a unique identifier for elements + el_id = id(element) + + element_results_name = element.resultsName + + # Here we basically bypass processing certain wrapper elements if they contribute nothing to the diagram + if not element.customName: + if isinstance( + element, + ( + # pyparsing.TokenConverter, + # pyparsing.Forward, + pyparsing.Located, + ), + ): + # However, if this element has a useful custom name, and its child does not, we can pass it on to the child + if exprs: + if not exprs[0].customName: + propagated_name = name + else: + propagated_name = None + + return _to_diagram_element( + element.expr, + parent=parent, + lookup=lookup, + vertical=vertical, + index=index, + name_hint=propagated_name, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # If the element isn't worth extracting, we always treat it as the first time we say it + if _worth_extracting(element): + if el_id in lookup: + # If we've seen this element exactly once before, we are only just now finding out that it's a duplicate, + # so we have to extract it into a new diagram. + looked_up = lookup[el_id] + looked_up.mark_for_extraction(el_id, lookup, name=name_hint) + ret = EditablePartial.from_call(railroad.NonTerminal, text=looked_up.name) + return ret + + elif el_id in lookup.diagrams: + # If we have seen the element at least twice before, and have already extracted it into a subdiagram, we + # just put in a marker element that refers to the sub-diagram + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + return ret + + # Recursively convert child elements + # Here we find the most relevant Railroad element for matching pyparsing Element + # We use ``items=[]`` here to hold the place for where the child elements will go once created + if isinstance(element, pyparsing.And): + # detect And's created with ``expr*N`` notation - for these use a OneOrMore with a repeat + # (all will have the same name, and resultsName) + if not exprs: + return None + if len(set((e.name, e.resultsName) for e in exprs)) == 1: + ret = EditablePartial.from_call( + railroad.OneOrMore, item="", repeat=str(len(exprs)) + ) + elif _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Stack, items=[]) + else: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)): + if not exprs: + return None + if _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Choice, 0, items=[]) + else: + ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[]) + elif isinstance(element, pyparsing.Each): + if not exprs: + return None + ret = EditablePartial.from_call(EachItem, items=[]) + elif isinstance(element, pyparsing.NotAny): + ret = EditablePartial.from_call(AnnotatedItem, label="NOT", item="") + elif isinstance(element, pyparsing.FollowedBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="") + elif isinstance(element, pyparsing.PrecededBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="") + elif isinstance(element, pyparsing.Group): + if show_groups: + ret = EditablePartial.from_call(AnnotatedItem, label="", item="") + else: + ret = EditablePartial.from_call(railroad.Group, label="", item="") + elif isinstance(element, pyparsing.TokenConverter): + label = type(element).__name__.lower() + if label == "tokenconverter": + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + ret = EditablePartial.from_call(AnnotatedItem, label=label, item="") + elif isinstance(element, pyparsing.Opt): + ret = EditablePartial.from_call(railroad.Optional, item="") + elif isinstance(element, pyparsing.OneOrMore): + ret = EditablePartial.from_call(railroad.OneOrMore, item="") + elif isinstance(element, pyparsing.ZeroOrMore): + ret = EditablePartial.from_call(railroad.ZeroOrMore, item="") + elif isinstance(element, pyparsing.Group): + ret = EditablePartial.from_call( + railroad.Group, item=None, label=element_results_name + ) + elif isinstance(element, pyparsing.Empty) and not element.customName: + # Skip unnamed "Empty" elements + ret = None + elif isinstance(element, pyparsing.ParseElementEnhance): + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif len(exprs) > 0 and not element_results_name: + ret = EditablePartial.from_call(railroad.Group, item="", label=name) + elif len(exprs) > 0: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName) + ret = terminal + + if ret is None: + return + + # Indicate this element's position in the tree so we can extract it if necessary + lookup[el_id] = ElementState( + element=element, + converted=ret, + parent=parent, + parent_index=index, + number=lookup.generate_index(), + ) + if element.customName: + lookup[el_id].mark_for_extraction(el_id, lookup, element.customName) + + i = 0 + for expr in exprs: + # Add a placeholder index in case we have to extract the child before we even add it to the parent + if "items" in ret.kwargs: + ret.kwargs["items"].insert(i, None) + + item = _to_diagram_element( + expr, + parent=ret, + lookup=lookup, + vertical=vertical, + index=i, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # Some elements don't need to be shown in the diagram + if item is not None: + if "item" in ret.kwargs: + ret.kwargs["item"] = item + elif "items" in ret.kwargs: + # If we've already extracted the child, don't touch this index, since it's occupied by a nonterminal + ret.kwargs["items"][i] = item + i += 1 + elif "items" in ret.kwargs: + # If we're supposed to skip this element, remove it from the parent + del ret.kwargs["items"][i] + + # If all this items children are none, skip this item + if ret and ( + ("items" in ret.kwargs and len(ret.kwargs["items"]) == 0) + or ("item" in ret.kwargs and ret.kwargs["item"] is None) + ): + ret = EditablePartial.from_call(railroad.Terminal, name) + + # Mark this element as "complete", ie it has all of its children + if el_id in lookup: + lookup[el_id].complete = True + + if el_id in lookup and lookup[el_id].extract and lookup[el_id].complete: + lookup.extract_into_diagram(el_id) + if ret is not None: + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + + return ret diff --git a/script.module.pyparsing/lib/pyparsing/exceptions.py b/script.module.pyparsing/lib/pyparsing/exceptions.py new file mode 100644 index 000000000..12219f124 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/exceptions.py @@ -0,0 +1,299 @@ +# exceptions.py + +import re +import sys +import typing + +from .util import ( + col, + line, + lineno, + _collapse_string_to_ranges, + replaced_by_pep8, +) +from .unicode import pyparsing_unicode as ppu + + +class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic): + pass + + +_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums) +_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.") + + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + + loc: int + msg: str + pstr: str + parser_element: typing.Any # "ParserElement" + args: typing.Tuple[str, int, typing.Optional[str]] + + __slots__ = ( + "loc", + "msg", + "pstr", + "parser_element", + "args", + ) + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, + pstr: str, + loc: int = 0, + msg: typing.Optional[str] = None, + elem=None, + ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parser_element = elem + self.args = (pstr, loc, msg) + + @staticmethod + def explain_exception(exc, depth=16): + """ + Method to take an exception and translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - exc - exception raised during parsing (need not be a ParseException, in support + of Python exceptions that might be raised in a parse action) + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + """ + import inspect + from .core import ParserElement + + if depth is None: + depth = sys.getrecursionlimit() + ret = [] + if isinstance(exc, ParseBaseException): + ret.append(exc.line) + ret.append(" " * (exc.column - 1) + "^") + ret.append(f"{type(exc).__name__}: {exc}") + + if depth > 0: + callers = inspect.getinnerframes(exc.__traceback__, context=depth) + seen = set() + for i, ff in enumerate(callers[-depth:]): + frm = ff[0] + + f_self = frm.f_locals.get("self", None) + if isinstance(f_self, ParserElement): + if not frm.f_code.co_name.startswith( + ("parseImpl", "_parseNoCache") + ): + continue + if id(f_self) in seen: + continue + seen.add(id(f_self)) + + self_type = type(f_self) + ret.append( + f"{self_type.__module__}.{self_type.__name__} - {f_self}" + ) + + elif f_self is not None: + self_type = type(f_self) + ret.append(f"{self_type.__module__}.{self_type.__name__}") + + else: + code = frm.f_code + if code.co_name in ("wrapper", ""): + continue + + ret.append(code.co_name) + + depth -= 1 + if not depth: + break + + return "\n".join(ret) + + @classmethod + def _from_exception(cls, pe): + """ + internal factory method to simplify creating one type of ParseException + from another - avoids having __init__ signature conflicts among subclasses + """ + return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element) + + @property + def line(self) -> str: + """ + Return the line of text where the exception occurred. + """ + return line(self.loc, self.pstr) + + @property + def lineno(self) -> int: + """ + Return the 1-based line number of text where the exception occurred. + """ + return lineno(self.loc, self.pstr) + + @property + def col(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + @property + def column(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + # pre-PEP8 compatibility + @property + def parserElement(self): + return self.parser_element + + @parserElement.setter + def parserElement(self, elem): + self.parser_element = elem + + def __str__(self) -> str: + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ", found end of text" + else: + # pull out next word at error location + found_match = _exception_word_extractor.match(self.pstr, self.loc) + if found_match is not None: + found = found_match.group(0) + else: + found = self.pstr[self.loc : self.loc + 1] + foundstr = (", found %r" % found).replace(r"\\", "\\") + else: + foundstr = "" + return f"{self.msg}{foundstr} (at char {self.loc}), (line:{self.lineno}, col:{self.column})" + + def __repr__(self): + return str(self) + + def mark_input_line( + self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<" + ) -> str: + """ + Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + markerString = marker_string if marker_string is not None else markerString + 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 explain(self, depth=16) -> str: + """ + Method to translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + + Example:: + + expr = pp.Word(pp.nums) * 3 + try: + expr.parse_string("123 456 A789") + except pp.ParseException as pe: + print(pe.explain(depth=0)) + + prints:: + + 123 456 A789 + ^ + ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9) + + Note: the diagnostic output will include string representations of the expressions + that failed to parse. These representations will be more helpful if you use `set_name` to + give identifiable names to your expressions. Otherwise they will use the default string + forms, which may be cryptic to read. + + Note: pyparsing's default truncation of exception tracebacks may also truncate the + stack of expressions that are displayed in the ``explain`` output. To get the full listing + of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True`` + """ + return self.explain_exception(self, depth) + + # fmt: off + @replaced_by_pep8(mark_input_line) + def markInputline(self): ... + # fmt: on + + +class ParseException(ParseBaseException): + """ + Exception thrown when a parse expression doesn't match the input string + + Example:: + + try: + Word(nums).set_name("integer").parse_string("ABC") + except ParseException as pe: + print(pe) + print("column: {}".format(pe.column)) + + prints:: + + Expected integer (at char 0), (line:1, col:1) + column: 1 + + """ + + +class ParseFatalException(ParseBaseException): + """ + User-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately + """ + + +class ParseSyntaxException(ParseFatalException): + """ + Just like :class:`ParseFatalException`, but thrown internally + when an :class:`ErrorStop` ('-' operator) indicates + that parsing is to stop immediately because an unbacktrackable + syntax error has been found. + """ + + +class RecursiveGrammarException(Exception): + """ + Exception thrown by :class:`ParserElement.validate` if the + grammar could be left-recursive; parser may need to enable + left recursion using :class:`ParserElement.enable_left_recursion` + """ + + def __init__(self, parseElementList): + self.parseElementTrace = parseElementList + + def __str__(self) -> str: + return f"RecursiveGrammarException: {self.parseElementTrace}" diff --git a/script.module.pyparsing/lib/pyparsing/helpers.py b/script.module.pyparsing/lib/pyparsing/helpers.py new file mode 100644 index 000000000..018f0d6ac --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/helpers.py @@ -0,0 +1,1100 @@ +# helpers.py +import html.entities +import re +import sys +import typing + +from . import __diag__ +from .core import * +from .util import ( + _bslash, + _flatten, + _escape_regex_range_chars, + replaced_by_pep8, +) + + +# +# global helpers +# +def counted_array( + expr: ParserElement, + int_expr: typing.Optional[ParserElement] = None, + *, + intExpr: typing.Optional[ParserElement] = None, +) -> ParserElement: + """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. + + If ``int_expr`` is specified, it should be a pyparsing expression + that produces an integer value. + + Example:: + + counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd'] + + # in this parser, the leading integer value is given in binary, + # '10' indicating that 2 values are in the array + binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) + counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd'] + + # if other fields must be parsed after the count but before the + # list items, give the fields results names and they will + # be preserved in the returned ParseResults: + count_with_metadata = integer + Word(alphas)("type") + typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items") + result = typed_array.parse_string("3 bool True True False") + print(result.dump()) + + # prints + # ['True', 'True', 'False'] + # - items: ['True', 'True', 'False'] + # - type: 'bool' + """ + intExpr = intExpr or int_expr + array_expr = Forward() + + def count_field_parse_action(s, l, t): + nonlocal array_expr + n = t[0] + array_expr <<= (expr * n) if n else Empty() + # clear list contents, but keep any named results + del t[:] + + if intExpr is None: + intExpr = Word(nums).set_parse_action(lambda t: int(t[0])) + else: + intExpr = intExpr.copy() + intExpr.set_name("arrayLen") + intExpr.add_parse_action(count_field_parse_action, call_during_try=True) + return (intExpr + array_expr).set_name("(len) " + str(expr) + "...") + + +def match_previous_literal(expr: ParserElement) -> ParserElement: + """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 = match_previous_literal(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches a previous literal, will also match the leading + ``"1:1"`` in ``"1:10"``. If this is not desired, use + :class:`match_previous_expr`. Do *not* use with packrat parsing + enabled. + """ + rep = Forward() + + def copy_token_to_repeater(s, l, t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.as_list()) + rep << And(Literal(tt) for tt in tflat) + else: + rep << Empty() + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def match_previous_expr(expr: ParserElement) -> ParserElement: + """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 = match_previous_expr(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches by expressions, will *not* match the leading ``"1:1"`` + in ``"1:10"``; the expressions are evaluated first, and then + compared, so ``"1"`` is compared with ``"10"``. Do *not* use + with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep <<= e2 + + def copy_token_to_repeater(s, l, t): + matchTokens = _flatten(t.as_list()) + + def must_match_these_tokens(s, l, t): + theseTokens = _flatten(t.as_list()) + if theseTokens != matchTokens: + raise ParseException( + s, l, f"Expected {matchTokens}, found{theseTokens}" + ) + + rep.set_parse_action(must_match_these_tokens, callDuringTry=True) + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def one_of( + strs: Union[typing.Iterable[str], str], + caseless: bool = False, + use_regex: bool = True, + as_keyword: bool = False, + *, + useRegex: bool = True, + asKeyword: bool = False, +) -> ParserElement: + """Helper to quickly define a set of alternative :class:`Literal` s, + and makes sure to do longest-first testing when there is a conflict, + regardless of the input order, but returns + a :class:`MatchFirst` for best performance. + + Parameters: + + - ``strs`` - a string of space-delimited literals, or a collection of + string literals + - ``caseless`` - treat all literals as caseless - (default= ``False``) + - ``use_regex`` - as an optimization, will + generate a :class:`Regex` object; otherwise, will generate + a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if + creating a :class:`Regex` raises an exception) - (default= ``True``) + - ``as_keyword`` - enforce :class:`Keyword`-style matching on the + generated expressions - (default= ``False``) + - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility, + but will be removed in a future release + + Example:: + + comp_oper = one_of("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) + + prints:: + + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + """ + asKeyword = asKeyword or as_keyword + useRegex = useRegex and use_regex + + if ( + isinstance(caseless, str_type) + and __diag__.warn_on_multiple_string_args_to_oneof + ): + warnings.warn( + "More than one string argument passed to one_of, pass" + " choices as a list or space-delimited string", + stacklevel=2, + ) + + if caseless: + isequal = lambda a, b: a.upper() == b.upper() + masks = lambda a, b: b.upper().startswith(a.upper()) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral + else: + isequal = lambda a, b: a == b + masks = lambda a, b: b.startswith(a) + parseElementClass = Keyword if asKeyword else Literal + + symbols: List[str] = [] + if isinstance(strs, str_type): + strs = typing.cast(str, strs) + symbols = strs.split() + elif isinstance(strs, Iterable): + symbols = list(strs) + else: + raise TypeError("Invalid argument to one_of, expected string or iterable") + if not symbols: + return NoMatch() + + # reorder given symbols to take care to avoid masking longer choices with shorter ones + # (but only if the given symbols are not just single characters) + if any(len(sym) > 1 for sym in symbols): + 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) + break + else: + i += 1 + + if useRegex: + re_flags: int = re.IGNORECASE if caseless else 0 + + try: + if all(len(sym) == 1 for sym in symbols): + # symbols are just single characters, create range regex pattern + patt = f"[{''.join(_escape_regex_range_chars(sym) for sym in symbols)}]" + else: + patt = "|".join(re.escape(sym) for sym in symbols) + + # wrap with \b word break markers if defining as keywords + if asKeyword: + patt = rf"\b(?:{patt})\b" + + ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols)) + + if caseless: + # add parse action to return symbols as specified, not in random + # casing as found in input string + symbol_map = {sym.lower(): sym for sym in symbols} + ret.add_parse_action(lambda s, l, t: symbol_map[t[0].lower()]) + + return ret + + except re.error: + warnings.warn( + "Exception creating Regex for one_of, building MatchFirst", stacklevel=2 + ) + + # last resort, just use MatchFirst + return MatchFirst(parseElementClass(sym) for sym in symbols).set_name( + " | ".join(symbols) + ) + + +def dict_of(key: ParserElement, value: ParserElement) -> ParserElement: + """Helper to easily and clearly define a dictionary by specifying + the respective patterns for the key and value. Takes care of + defining the :class:`Dict`, :class:`ZeroOrMore`, and + :class:`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 :class:`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, stop_on=label).set_parse_action(' '.join)) + print(attr_expr[1, ...].parse_string(text).dump()) + + attr_label = label + attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + + # similar to Dict, but simpler call format + result = dict_of(attr_label, attr_value).parse_string(text) + print(result.dump()) + print(result['shape']) + print(result.shape) # object attribute access works too + print(result.as_dict()) + + 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(OneOrMore(Group(key + value))) + + +def original_text_for( + expr: ParserElement, as_string: bool = True, *, asString: bool = True +) -> ParserElement: + """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 a string containing the original parsed text. + + If the optional ``as_string`` argument is passed as + ``False``, then the return value is + a :class:`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 + :class:`original_text_for` contains expressions with defined + results names, you must set ``as_string`` to ``False`` if you + want to preserve those results name values. + + The ``asString`` pre-PEP8 argument is retained for compatibility, + but will be removed in a future release. + + Example:: + + src = "this is test bold text normal text " + for tag in ("b", "i"): + opener, closer = make_html_tags(tag) + patt = original_text_for(opener + ... + closer) + print(patt.search_string(src)[0]) + + prints:: + + [' bold text '] + ['text'] + """ + asString = asString and as_string + + locMarker = Empty().set_parse_action(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.set_parse_action(extractText) + matchExpr.ignoreExprs = expr.ignoreExprs + matchExpr.suppress_warning(Diagnostics.warn_ungrouped_named_tokens_in_collection) + return matchExpr + + +def ungroup(expr: ParserElement) -> ParserElement: + """Helper to undo pyparsing's default grouping of And expressions, + even if all but one are non-empty. + """ + return TokenConverter(expr).add_parse_action(lambda t: t[0]) + + +def locatedExpr(expr: ParserElement) -> ParserElement: + """ + (DEPRECATED - future code should use the :class:`Located` class) + 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 ```` characters, you + may want to call :class:`ParserElement.parse_with_tabs` + + Example:: + + wd = Word(alphas) + for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] + """ + locator = Empty().set_parse_action(lambda ss, ll, tt: ll) + return Group( + locator("locn_start") + + expr("value") + + locator.copy().leaveWhitespace()("locn_end") + ) + + +def nested_expr( + opener: Union[str, ParserElement] = "(", + closer: Union[str, ParserElement] = ")", + content: typing.Optional[ParserElement] = None, + ignore_expr: ParserElement = quoted_string(), + *, + ignoreExpr: ParserElement = quoted_string(), +) -> ParserElement: + """Helper method for defining nested lists enclosed in opening and + closing delimiters (``"("`` and ``")"`` are the default). + + Parameters: + + - ``opener`` - opening character for a nested list + (default= ``"("``); can also be a pyparsing expression + - ``closer`` - closing character for a nested list + (default= ``")"``); can also be a pyparsing expression + - ``content`` - expression for items within the nested lists + (default= ``None``) + - ``ignore_expr`` - expression for ignoring opening and closing delimiters + (default= :class:`quoted_string`) + - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility + but will be removed in a future release + + 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 ``ignore_expr`` 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 quoted_string or + a comment expression. Specify multiple expressions using an + :class:`Or` or :class:`MatchFirst`. The default is + :class:`quoted_string`, but if no expressions are to be ignored, then + pass ``None`` for this argument. + + Example:: + + data_type = one_of("void int short long char float double") + decl_data_type = Combine(data_type + Opt(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR, RPAR = map(Suppress, "()") + + code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(c_style_comment) + + 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.search_string(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 ignoreExpr != ignore_expr: + ignoreExpr = ignore_expr if ignoreExpr == quoted_string() else ignoreExpr + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener, str_type) and isinstance(closer, str_type): + opener = typing.cast(str, opener) + closer = typing.cast(str, closer) + 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, + ) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = empty.copy() + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS + ).set_parse_action(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) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = Combine( + OneOrMore( + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(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.set_name("nested %s%s expression" % (opener, closer)) + return ret + + +def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr, str_type): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dbl_quoted_string.copy().set_parse_action(remove_quotes) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + else: + tagAttrValue = quoted_string.copy().set_parse_action(remove_quotes) | Word( + printables, exclude_chars=">" + ) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict( + ZeroOrMore( + Group( + tagAttrName.set_parse_action(lambda t: t[0].lower()) + + Opt(Suppress("=") + tagAttrValue) + ) + ) + ) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + closeTag = Combine(Literal("", adjacent=False) + + openTag.set_name("<%s>" % resname) + # add start results name in parse action now that ungrouped names are not reported at two levels + openTag.add_parse_action( + lambda t: t.__setitem__( + "start" + "".join(resname.replace(":", " ").title().split()), t.copy() + ) + ) + closeTag = closeTag( + "end" + "".join(resname.replace(":", " ").title().split()) + ).set_name("" % resname) + openTag.tag = resname + closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) + return openTag, closeTag + + +def make_html_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """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 = 'More info at the pyparsing wiki page' + # make_html_tags returns pyparsing expressions for the opening and + # closing tags as a 2-tuple + a, a_end = make_html_tags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end + + for link in link_expr.search_string(text): + # attributes in the tag (like "href" shown here) are + # also accessible as named results + print(link.link_text, '->', link.href) + + prints:: + + pyparsing -> https://github.com/pyparsing/pyparsing/wiki + """ + return _makeTags(tag_str, False) + + +def make_xml_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """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 :class:`make_html_tags` + """ + return _makeTags(tag_str, True) + + +any_open_tag: ParserElement +any_close_tag: ParserElement +any_open_tag, any_close_tag = make_html_tags( + Word(alphas, alphanums + "_:").set_name("any tag") +) + +_htmlEntityMap = {k.rstrip(";"): v for k, v in html.entities.html5.items()} +common_html_entity = Regex("&(?P" + "|".join(_htmlEntityMap) + ");").set_name( + "common HTML entity" +) + + +def replace_html_entity(s, l, t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + + +class OpAssoc(Enum): + """Enumeration of operator associativity + - used in constructing InfixNotationOperatorSpec for :class:`infix_notation`""" + + LEFT = 1 + RIGHT = 2 + + +InfixNotationOperatorArgType = Union[ + ParserElement, str, Tuple[Union[ParserElement, str], Union[ParserElement, str]] +] +InfixNotationOperatorSpec = Union[ + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + typing.Optional[ParseAction], + ], + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + ], +] + + +def infix_notation( + base_expr: ParserElement, + op_list: List[InfixNotationOperatorSpec], + lpar: Union[str, ParserElement] = Suppress("("), + rpar: Union[str, ParserElement] = Suppress(")"), +) -> ParserElement: + """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. The generated parser will also + recognize the use of parentheses to override operator precedences + (see example below). + + Note: if you define a deep operator list, you may see performance + issues when using infix_notation. See + :class:`ParserElement.enable_packrat` for a mechanism to potentially + improve your parser performance. + + Parameters: + + - ``base_expr`` - expression representing the most basic operand to + be used in the expression + - ``op_list`` - list of tuples, one for each operator precedence level + in the expression grammar; each tuple is of the form ``(op_expr, + num_operands, right_left_assoc, (optional)parse_action)``, where: + + - ``op_expr`` is the pyparsing expression for the operator; may also + be a string, which will be converted to a Literal; if ``num_operands`` + is 3, ``op_expr`` is a tuple of two expressions, for the two + operators separating the 3 terms + - ``num_operands`` is the number of terms for this operator (must be 1, + 2, or 3) + - ``right_left_assoc`` is the indicator whether the operator is right + or left associative, using the pyparsing-defined constants + ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. + - ``parse_action`` is the parse action to be associated with + expressions matching this operator expression (the parse action + tuple member may be omitted); if the parse action is passed + a tuple or list of functions, this is equivalent to calling + ``set_parse_action(*fn)`` + (:class:`ParserElement.set_parse_action`) + - ``lpar`` - expression for matching left-parentheses; if passed as a + str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as + an expression (such as ``Literal('(')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress('(')``) + - ``rpar`` - expression for matching right-parentheses; if passed as a + str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as + an expression (such as ``Literal(')')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress(')')``) + + Example:: + + # simple example of four-function arithmetic with ints and + # variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infix_notation(integer | varname, + [ + ('-', 1, OpAssoc.RIGHT), + (one_of('* /'), 2, OpAssoc.LEFT), + (one_of('+ -'), 2, OpAssoc.LEFT), + ]) + + arith_expr.run_tests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', full_dump=False) + + prints:: + + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + (5+x)*y + [[[5, '+', 'x'], '*', 'y']] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + + # captive version of FollowedBy that does not do parse actions or capture results names + class _FB(FollowedBy): + def parseImpl(self, instring, loc, doActions=True): + self.expr.try_parse(instring, loc) + return loc, [] + + _FB.__name__ = "FollowedBy>" + + ret = Forward() + if isinstance(lpar, str): + lpar = Suppress(lpar) + if isinstance(rpar, str): + rpar = Suppress(rpar) + + # if lpar and rpar are not suppressed, wrap in group + if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)): + lastExpr = base_expr | Group(lpar + ret + rpar) + else: + lastExpr = base_expr | (lpar + ret + rpar) + + arity: int + rightLeftAssoc: opAssoc + pa: typing.Optional[ParseAction] + opExpr1: ParserElement + opExpr2: ParserElement + for i, operDef in enumerate(op_list): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] # type: ignore[assignment] + if isinstance(opExpr, str_type): + opExpr = ParserElement._literalStringClass(opExpr) + opExpr = typing.cast(ParserElement, opExpr) + if arity == 3: + if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2: + raise ValueError( + "if numterms=3, opExpr must be a tuple or list of two expressions" + ) + opExpr1, opExpr2 = opExpr + term_name = f"{opExpr1}{opExpr2} term" + else: + term_name = f"{opExpr} term" + + if not 1 <= arity <= 3: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + + if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): + raise ValueError("operator must indicate right or left associativity") + + thisExpr: ParserElement = Forward().set_name(term_name) + thisExpr = typing.cast(Forward, thisExpr) + if rightLeftAssoc is OpAssoc.LEFT: + if arity == 1: + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( + lastExpr + (opExpr + lastExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr[2, ...]) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr + ) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr)) + elif rightLeftAssoc is OpAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Opt): + opExpr = Opt(opExpr) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( + lastExpr + (opExpr + thisExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + thisExpr) + Group( + lastExpr + thisExpr[1, ...] + ) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr + ) + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.set_parse_action(*pa) + else: + matchExpr.set_parse_action(pa) + thisExpr <<= (matchExpr | lastExpr).setName(term_name) + lastExpr = thisExpr + ret <<= lastExpr + return ret + + +def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): + """ + (DEPRECATED - use :class:`IndentedBlock` class instead) + 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 current level; set to ``False`` for block of left-most statements + (default= ``True``) + + A valid block must contain at least one ``blockStatement``. + + (Note that indentedBlock uses internal parse actions which make it + incompatible with packrat parsing.) + + 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("(" + Opt(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) + + module_body = stmt[1, ...] + + 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']]]]]]] + """ + backup_stacks.append(indentStack[:]) + + def reset_stack(): + indentStack[:] = backup_stacks[-1] + + def checkPeerIndent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseException(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 in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() + + NL = OneOrMore(LineEnd().set_whitespace_chars("\t ").suppress()) + INDENT = (Empty() + Empty().set_parse_action(checkSubIndent)).set_name("INDENT") + PEER = Empty().set_parse_action(checkPeerIndent).set_name("") + UNDENT = Empty().set_parse_action(checkUnindent).set_name("UNINDENT") + if indent: + smExpr = Group( + Opt(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + UNDENT + ) + else: + smExpr = Group( + Opt(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + Opt(UNDENT) + ) + + # add a parse action to remove backup_stack from list of backups + smExpr.add_parse_action( + lambda: backup_stacks.pop(-1) and None if backup_stacks else None + ) + smExpr.set_fail_action(lambda a, b, c, d: reset_stack()) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.set_name("indented block") + + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +c_style_comment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").set_name( + "C style comment" +) +"Comment of the form ``/* ... */``" + +html_comment = Regex(r"").set_name("HTML comment") +"Comment of the form ````" + +rest_of_line = Regex(r".*").leave_whitespace().set_name("rest of line") +dbl_slash_comment = Regex(r"//(?:\\\n|[^\n])*").set_name("// comment") +"Comment of the form ``// ... (to end of line)``" + +cpp_style_comment = Combine( + Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/" | dbl_slash_comment +).set_name("C++ style comment") +"Comment of either form :class:`c_style_comment` or :class:`dbl_slash_comment`" + +java_style_comment = cpp_style_comment +"Same as :class:`cpp_style_comment`" + +python_style_comment = Regex(r"#.*").set_name("Python style comment") +"Comment of the form ``# ... (to end of line)``" + + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + + +# compatibility function, superseded by DelimitedList class +def delimited_list( + expr: Union[str, ParserElement], + delim: Union[str, ParserElement] = ",", + combine: bool = False, + min: typing.Optional[int] = None, + max: typing.Optional[int] = None, + *, + allow_trailing_delim: bool = False, +) -> ParserElement: + """(DEPRECATED - use :class:`DelimitedList` class)""" + return DelimitedList( + expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim + ) + + +# pre-PEP8 compatible names +# fmt: off +opAssoc = OpAssoc +anyOpenTag = any_open_tag +anyCloseTag = any_close_tag +commonHTMLEntity = common_html_entity +cStyleComment = c_style_comment +htmlComment = html_comment +restOfLine = rest_of_line +dblSlashComment = dbl_slash_comment +cppStyleComment = cpp_style_comment +javaStyleComment = java_style_comment +pythonStyleComment = python_style_comment + +@replaced_by_pep8(DelimitedList) +def delimitedList(): ... + +@replaced_by_pep8(DelimitedList) +def delimited_list(): ... + +@replaced_by_pep8(counted_array) +def countedArray(): ... + +@replaced_by_pep8(match_previous_literal) +def matchPreviousLiteral(): ... + +@replaced_by_pep8(match_previous_expr) +def matchPreviousExpr(): ... + +@replaced_by_pep8(one_of) +def oneOf(): ... + +@replaced_by_pep8(dict_of) +def dictOf(): ... + +@replaced_by_pep8(original_text_for) +def originalTextFor(): ... + +@replaced_by_pep8(nested_expr) +def nestedExpr(): ... + +@replaced_by_pep8(make_html_tags) +def makeHTMLTags(): ... + +@replaced_by_pep8(make_xml_tags) +def makeXMLTags(): ... + +@replaced_by_pep8(replace_html_entity) +def replaceHTMLEntity(): ... + +@replaced_by_pep8(infix_notation) +def infixNotation(): ... +# fmt: on diff --git a/script.module.pyparsing/lib/pyparsing/results.py b/script.module.pyparsing/lib/pyparsing/results.py new file mode 100644 index 000000000..031304976 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/results.py @@ -0,0 +1,796 @@ +# results.py +from collections.abc import ( + MutableMapping, + Mapping, + MutableSequence, + Iterator, + Sequence, + Container, +) +import pprint +from typing import Tuple, Any, Dict, Set, List + +str_type: Tuple[type, ...] = (str, bytes) +_generator_type = type((_ for _ in ())) + + +class _ParseResultsWithOffset: + tup: Tuple["ParseResults", int] + __slots__ = ["tup"] + + def __init__(self, p1: "ParseResults", p2: int): + self.tup: Tuple[ParseResults, int] = (p1, p2) + + def __getitem__(self, i): + return self.tup[i] + + def __getstate__(self): + return self.tup + + def __setstate__(self, *args): + self.tup = args[0] + + +class ParseResults: + """Structured parse results, to provide multiple means of access to + the parsed data: + + - as a list (``len(results)``) + - by list index (``results[0], results[1]``, etc.) + - by attribute (``results.`` - see :class:`ParserElement.set_results_name`) + + Example:: + + integer = Word(nums) + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + # equivalent form: + # date_str = (integer("year") + '/' + # + integer("month") + '/' + # + integer("day")) + + # parse_string returns a ParseResults object + result = date_str.parse_string("1999/12/31") + + def test(s, fn=repr): + print(f"{s} -> {fn(eval(s))}") + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) + + prints:: + + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + + _null_values: Tuple[Any, ...] = (None, [], ()) + + _name: str + _parent: "ParseResults" + _all_names: Set[str] + _modal: bool + _toklist: List[Any] + _tokdict: Dict[str, Any] + + __slots__ = ( + "_name", + "_parent", + "_all_names", + "_modal", + "_toklist", + "_tokdict", + ) + + class List(list): + """ + Simple wrapper class to distinguish parsed list results that should be preserved + as actual Python lists, instead of being converted to :class:`ParseResults`:: + + LBRACK, RBRACK = map(pp.Suppress, "[]") + element = pp.Forward() + item = ppc.integer + element_list = LBRACK + pp.DelimitedList(element) + RBRACK + + # add parse actions to convert from ParseResults to actual Python collection types + def as_python_list(t): + return pp.ParseResults.List(t.as_list()) + element_list.add_parse_action(as_python_list) + + element <<= item | element_list + + element.run_tests(''' + 100 + [2,3,4] + [[2, 1],3,4] + [(2, 1),3,4] + (2,3,4) + ''', post_parse=lambda s, r: (r[0], type(r[0]))) + + prints:: + + 100 + (100, ) + + [2,3,4] + ([2, 3, 4], ) + + [[2, 1],3,4] + ([[2, 1], 3, 4], ) + + (Used internally by :class:`Group` when `aslist=True`.) + """ + + def __new__(cls, contained=None): + if contained is None: + contained = [] + + if not isinstance(contained, list): + raise TypeError( + f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}" + ) + + return list.__new__(cls) + + def __new__(cls, toklist=None, name=None, **kwargs): + if isinstance(toklist, ParseResults): + return toklist + self = object.__new__(cls) + self._name = None + self._parent = None + self._all_names = set() + + if toklist is None: + self._toklist = [] + elif isinstance(toklist, (list, _generator_type)): + self._toklist = ( + [toklist[:]] + if isinstance(toklist, ParseResults.List) + else list(toklist) + ) + else: + self._toklist = [toklist] + self._tokdict = dict() + return self + + # 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 + ): + self._tokdict: Dict[str, _ParseResultsWithOffset] + self._modal = modal + if name is not None and name != "": + if isinstance(name, int): + name = str(name) + if not modal: + self._all_names = {name} + self._name = name + if toklist not in self._null_values: + if isinstance(toklist, (str_type, type)): + toklist = [toklist] + if asList: + if isinstance(toklist, ParseResults): + self[name] = _ParseResultsWithOffset( + ParseResults(toklist._toklist), 0 + ) + else: + self[name] = _ParseResultsWithOffset( + ParseResults(toklist[0]), 0 + ) + self[name]._name = name + else: + try: + self[name] = toklist[0] + except (KeyError, TypeError, IndexError): + if toklist is not self: + self[name] = toklist + else: + self._name = name + + def __getitem__(self, i): + if isinstance(i, (int, slice)): + return self._toklist[i] + else: + if i not in self._all_names: + 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 = 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) -> bool: + return k in self._tokdict + + def __len__(self) -> int: + return len(self._toklist) + + def __bool__(self) -> bool: + return not not (self._toklist or self._tokdict) + + def __iter__(self) -> Iterator: + return iter(self._toklist) + + def __reversed__(self) -> Iterator: + return iter(self._toklist[::-1]) + + def keys(self): + return iter(self._tokdict) + + def values(self): + return (self[k] for k in self.keys()) + + def items(self): + return ((k, self[k]) for k in self.keys()) + + def haskeys(self) -> bool: + """ + Since ``keys()`` returns an iterator, this method is helpful in bypassing + code that looks for the existence of any defined results names.""" + return not not self._tokdict + + def pop(self, *args, **kwargs): + """ + Removes and returns item at specified index (default= ``last``). + 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()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + def remove_first(tokens): + tokens.pop(0) + numlist.add_parse_action(remove_first) + print(numlist.parse_string("0 123 321")) # -> ['123', '321'] + + label = Word(alphas) + patt = label("LABEL") + Word(nums)[1, ...] + print(patt.parse_string("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.add_parse_action(remove_LABEL) + print(patt.parse_string("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(f"pop() got an unexpected keyword argument {k!r}") + 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, default_value=None): + """ + Returns named result matching the given key, or if there is no + such name, then returns the given ``default_value`` or ``None`` if no + ``default_value`` is specified. + + Similar to ``dict.get()``. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string("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 default_value + + def insert(self, index, ins_string): + """ + Inserts new element at location index in the list of parsed tokens. + + Similar to ``list.insert()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("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) + numlist.add_parse_action(insert_locn) + print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321'] + """ + self._toklist.insert(index, ins_string) + # 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:: + + numlist = Word(nums)[...] + print(numlist.parse_string("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))) + numlist.add_parse_action(append_sum) + print(numlist.parse_string("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 = Word(alphas)[1, ...] + + # 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) + patt.add_parse_action(make_palindrome) + print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + """ + if isinstance(itemseq, ParseResults): + self.__iadd__(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: + if name.startswith("__"): + raise AttributeError(name) + return "" + + def __add__(self, other: "ParseResults") -> "ParseResults": + ret = self.copy() + ret += other + return ret + + def __iadd__(self, other: "ParseResults") -> "ParseResults": + if not other: + return self + + 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 = self + + self._toklist += other._toklist + self._all_names |= other._all_names + return self + + def __radd__(self, other) -> "ParseResults": + 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) -> str: + return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})" + + def __str__(self) -> str: + return ( + "[" + + ", ".join( + [ + str(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(str(item)) + return out + + def as_list(self) -> list: + """ + Returns the parse results as a nested list of matching tokens, all converted to strings. + + Example:: + + patt = Word(alphas)[1, ...] + result = patt.parse_string("sldkj lsdkj sldkj") + # even though the result prints in string-like form, it is actually a pyparsing ParseResults + print(type(result), result) # -> ['sldkj', 'lsdkj', 'sldkj'] + + # Use as_list() to create an actual list + result_list = result.as_list() + print(type(result_list), result_list) # -> ['sldkj', 'lsdkj', 'sldkj'] + """ + return [ + res.as_list() if isinstance(res, ParseResults) else res + for res in self._toklist + ] + + def as_dict(self) -> dict: + """ + Returns the named parse results as a nested dictionary. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('12/31/1999') + print(type(result), repr(result)) # -> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + + result_dict = result.as_dict() + print(type(result_dict), repr(result_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.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"} + """ + + def to_item(obj): + if isinstance(obj, ParseResults): + return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj] + else: + return obj + + return dict((k, to_item(v)) for k, v in self.items()) + + def copy(self) -> "ParseResults": + """ + Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults` + items contained within the source are shared with the copy. Use + :class:`ParseResults.deepcopy()` to create a copy with its own separate + content values. + """ + ret = ParseResults(self._toklist) + ret._tokdict = self._tokdict.copy() + ret._parent = self._parent + ret._all_names |= self._all_names + ret._name = self._name + return ret + + def deepcopy(self) -> "ParseResults": + """ + Returns a new deep copy of a :class:`ParseResults` object. + """ + ret = self.copy() + # replace values with copies if they are of known mutable types + for i, obj in enumerate(self._toklist): + if isinstance(obj, ParseResults): + self._toklist[i] = obj.deepcopy() + elif isinstance(obj, (str, bytes)): + pass + elif isinstance(obj, MutableMapping): + self._toklist[i] = dest = type(obj)() + for k, v in obj.items(): + dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v + elif isinstance(obj, Container): + self._toklist[i] = type(obj)( + v.deepcopy() if isinstance(v, ParseResults) else v for v in obj + ) + return ret + + def get_name(self): + r""" + 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 = user_data[1, ...] + + result = user_info.parse_string("22 111-22-3333 #221B") + for item in result: + print(item.get_name(), ':', item[0]) + + prints:: + + age : 22 + ssn : 111-22-3333 + house_number : 221B + """ + if self._name: + return self._name + elif self._parent: + par: "ParseResults" = self._parent + parent_tokdict_items = par._tokdict.items() + return next( + ( + k + for k, vlist in parent_tokdict_items + for v, loc in vlist + if v is self + ), + None, + ) + elif ( + len(self) == 1 + and len(self._tokdict) == 1 + and next(iter(self._tokdict.values()))[0][1] in (0, -1) + ): + return next(iter(self._tokdict.keys())) + else: + return None + + def dump(self, indent="", full=True, include_list=True, _depth=0) -> str: + """ + Diagnostic method for listing out the contents of + a :class:`ParseResults`. Accepts an optional ``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.parse_string('1999/12/31') + print(result.dump()) + + prints:: + + ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + out = [] + NL = "\n" + out.append(indent + str(self.as_list()) if include_list else "") + + if full: + if self.haskeys(): + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: + if out: + out.append(NL) + out.append(f"{indent}{(' ' * _depth)}- {k}: ") + if isinstance(v, ParseResults): + if v: + out.append( + v.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ) + ) + else: + out.append(str(v)) + else: + out.append(repr(v)) + if any(isinstance(vv, ParseResults) for vv in self): + v = self + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append( + "\n{}{}[{}]:\n{}{}{}".format( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + vv.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ), + ) + ) + else: + out.append( + "\n%s%s[%d]:\n%s%s%s" + % ( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + str(vv), + ) + ) + + return "".join(out) + + def pprint(self, *args, **kwargs): + """ + Pretty-printer for parsed results as a list, using the + `pprint `_ module. + Accepts additional positional or keyword args as defined for + `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.parse_string("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) + + prints:: + + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] + """ + pprint.pprint(self.as_list(), *args, **kwargs) + + # add support for pickle protocol + def __getstate__(self): + return ( + self._toklist, + ( + self._tokdict.copy(), + None, + self._all_names, + self._name, + ), + ) + + def __setstate__(self, state): + self._toklist, (self._tokdict, par, inAccumNames, self._name) = state + self._all_names = set(inAccumNames) + self._parent = None + + def __getnewargs__(self): + return self._toklist, self._name + + def __dir__(self): + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None) -> "ParseResults": + """ + Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the + name-value relations as results names. If an optional ``name`` argument is + given, a nested ``ParseResults`` will be returned. + """ + + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + # str's are iterable, but in pyparsing, we don't want to iterate over them + else: + return not isinstance(obj, str_type) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret + + asList = as_list + """Deprecated - use :class:`as_list`""" + asDict = as_dict + """Deprecated - use :class:`as_dict`""" + getName = get_name + """Deprecated - use :class:`get_name`""" + + +MutableMapping.register(ParseResults) +MutableSequence.register(ParseResults) diff --git a/script.module.pyparsing/lib/pyparsing/testing.py b/script.module.pyparsing/lib/pyparsing/testing.py new file mode 100644 index 000000000..5caa5ffe8 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/testing.py @@ -0,0 +1,258 @@ +# testing.py + +from contextlib import contextmanager +import typing + +from .core import ParserElement, ParseException, Keyword, __diag__, __compat__ + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - bounded recursion parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example:: + + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + + self._save_context["literal_string_class"] = ParserElement._literalStringClass + + self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace + + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + if ParserElement._packratEnabled: + self._save_context["packrat_cache_size"] = ParserElement.packrat_cache.size + else: + self._save_context["packrat_cache_size"] = None + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context["recursion_enabled"] = ParserElement._left_recursion_enabled + + self._save_context["__diag__"] = {name: getattr(__diag__, name) for name in __diag__._all_names} + + self._save_context["__compat__"] = {"collect_all_And_tokens": __compat__.collect_all_And_tokens} + + return self + + def restore(self): + # reset pyparsing global state + if ParserElement.DEFAULT_WHITE_CHARS != self._save_context["default_whitespace"]: + ParserElement.set_default_whitespace_chars(self._save_context["default_whitespace"]) + + ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"] + + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing(self._save_context["literal_string_class"]) + + for name, value in self._save_context["__diag__"].items(): + (__diag__.enable if value else __diag__.disable)(name) + + ParserElement._packratEnabled = False + if self._save_context["packrat_enabled"]: + ParserElement.enable_packrat(self._save_context["packrat_cache_size"]) + else: + ParserElement._parse = self._save_context["packrat_parse"] + ParserElement._left_recursion_enabled = self._save_context["recursion_enabled"] + + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + return self + + def copy(self): + ret = type(self)() + ret._save_context.update(self._save_context) + return ret + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + + def assertParseResultsEquals(self, result, expected_list=None, expected_dict=None, msg=None): + """ + Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``, + and compare any defined results names with an optional ``expected_dict``. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.as_list(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.as_dict(), msg=msg) + + def assertParseAndCheckList(self, expr, test_string, expected_list, msg=None, verbose=True): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asList()`` is equal to the ``expected_list``. + """ + result = expr.parse_string(test_string, parse_all=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict(self, expr, test_string, expected_dict, msg=None, verbose=True): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``. + """ + result = expr.parse_string(test_string, parseAll=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults(self, run_tests_report, expected_parse_results=None, msg=None): + """ + Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of + list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped + with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``. + Finally, asserts that the overall ``runTests()`` success value is ``True``. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [(*rpt, expected) for rpt, expected in zip(run_test_results, expected_parse_results)] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next((exp for exp in expected if isinstance(exp, str)), None) + expected_exception = next( + (exp for exp in expected if isinstance(exp, type) and issubclass(exp, Exception)), None + ) + if expected_exception is not None: + with self.assertRaises(expected_exception=expected_exception, msg=fail_msg or msg): + if isinstance(result, Exception): + raise result + else: + expected_list = next((exp for exp in expected if isinstance(exp, list)), None) + expected_dict = next((exp for exp in expected if isinstance(exp, dict)), None) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, expected_list=expected_list, expected_dict=expected_dict, msg=fail_msg or msg + ) + else: + # warning here maybe? + print(f"no validation for {test_string!r}") + + # do this last, in case some specific test results can be reported instead + self.assertTrue(run_test_success, msg=msg if msg is not None else "failed runTests") + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield + + @staticmethod + def with_line_numbers( + s: str, + start_line: typing.Optional[int] = None, + end_line: typing.Optional[int] = None, + expand_tabs: bool = True, + eol_mark: str = "|", + mark_spaces: typing.Optional[str] = None, + mark_control: typing.Optional[str] = None, + ) -> str: + """ + Helpful method for debugging a parser - prints a string with line and column numbers. + (Line and column numbers are 1-based.) + + :param s: tuple(bool, str - string to be printed with line and column numbers + :param start_line: int - (optional) starting line number in s to print (default=1) + :param end_line: int - (optional) ending line number in s to print (default=len(s)) + :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default + :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|") + :param mark_spaces: str - (optional) special character to display in place of spaces + :param mark_control: str - (optional) convert non-printing control characters to a placeholding + character; valid values: + - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊" + - any single character string - replace control characters with given string + - None (default) - string is displayed as-is + + :return: str - input string with leading line numbers and column number headers + """ + if expand_tabs: + s = s.expandtabs() + if mark_control is not None: + mark_control = typing.cast(str, mark_control) + if mark_control == "unicode": + transtable_map = {c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))} + transtable_map[127] = 0x2421 + tbl = str.maketrans(transtable_map) + eol_mark = "" + else: + ord_mark_control = ord(mark_control) + tbl = str.maketrans({c: ord_mark_control for c in list(range(0, 32)) + [127]}) + s = s.translate(tbl) + if mark_spaces is not None and mark_spaces != " ": + if mark_spaces == "unicode": + tbl = str.maketrans({9: 0x2409, 32: 0x2423}) + s = s.translate(tbl) + else: + s = s.replace(" ", mark_spaces) + if start_line is None: + start_line = 1 + if end_line is None: + end_line = len(s) + end_line = min(end_line, len(s)) + start_line = min(max(1, start_line), end_line) + + if mark_control != "unicode": + s_lines = s.splitlines()[start_line - 1 : end_line] + else: + s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]] + if not s_lines: + return "" + + lineno_width = len(str(end_line)) + max_line_len = max(len(line) for line in s_lines) + lead = " " * (lineno_width + 1) + if max_line_len >= 99: + header0 = lead + "".join(f"{' ' * 99}{(i + 1) % 100}" for i in range(max(max_line_len // 100, 1))) + "\n" + else: + header0 = "" + header1 = header0 + lead + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10))) + "\n" + header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n" + return ( + header1 + + header2 + + "\n".join(f"{i:{lineno_width}d}:{line}{eol_mark}" for i, line in enumerate(s_lines, start=start_line)) + + "\n" + ) diff --git a/script.module.pyparsing/lib/pyparsing/unicode.py b/script.module.pyparsing/lib/pyparsing/unicode.py new file mode 100644 index 000000000..b0a87b235 --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/unicode.py @@ -0,0 +1,361 @@ +# unicode.py + +import sys +from itertools import filterfalse +from typing import List, Tuple, Union + + +class _lazyclassproperty: + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + self.__name__ = fn.__name__ + + def __get__(self, obj, cls): + if cls is None: + cls = type(obj) + if not hasattr(cls, "_intern") or any( + cls._intern is getattr(superclass, "_intern", []) + for superclass in cls.__mro__[1:] + ): + cls._intern = {} + attrname = self.fn.__name__ + if attrname not in cls._intern: + cls._intern[attrname] = self.fn(cls) + return cls._intern[attrname] + + +UnicodeRangeList = List[Union[Tuple[int, int], Tuple[int]]] + + +class unicode_set: + """ + A set of Unicode characters, for language-specific strings for + ``alphas``, ``nums``, ``alphanums``, and ``printables``. + A unicode_set is defined by a list of ranges in the Unicode character + set, in a class attribute ``_ranges``. Ranges can be specified using + 2-tuples or a 1-tuple, such as:: + + _ranges = [ + (0x0020, 0x007e), + (0x00a0, 0x00ff), + (0x0100,), + ] + + Ranges are left- and right-inclusive. A 1-tuple of (x,) is treated as (x, x). + + A unicode set can also be defined using multiple inheritance of other unicode sets:: + + class CJK(Chinese, Japanese, Korean): + pass + """ + + _ranges: UnicodeRangeList = [] + + @_lazyclassproperty + def _chars_for_ranges(cls): + ret = [] + for cc in cls.__mro__: + if cc is unicode_set: + break + for rr in getattr(cc, "_ranges", ()): + ret.extend(range(rr[0], rr[-1] + 1)) + return [chr(c) for c in sorted(set(ret))] + + @_lazyclassproperty + def printables(cls): + """all non-whitespace characters in this range""" + return "".join(filterfalse(str.isspace, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphas(cls): + """all alphabetic characters in this range""" + return "".join(filter(str.isalpha, cls._chars_for_ranges)) + + @_lazyclassproperty + def nums(cls): + """all numeric digit characters in this range""" + return "".join(filter(str.isdigit, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphanums(cls): + """all alphanumeric characters in this range""" + return cls.alphas + cls.nums + + @_lazyclassproperty + def identchars(cls): + """all characters in this range that are valid identifier characters, plus underscore '_'""" + return "".join( + sorted( + set( + "".join(filter(str.isidentifier, cls._chars_for_ranges)) + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªµº" + + "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ" + + "_" + ) + ) + ) + + @_lazyclassproperty + def identbodychars(cls): + """ + all characters in this range that are valid identifier body characters, + plus the digits 0-9, and · (Unicode MIDDLE DOT) + """ + return "".join( + sorted( + set( + cls.identchars + + "0123456789·" + + "".join( + [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()] + ) + ) + ) + ) + + @_lazyclassproperty + def identifier(cls): + """ + a pyparsing Word expression for an identifier using this range's definitions for + identchars and identbodychars + """ + from pyparsing import Word + + return Word(cls.identchars, cls.identbodychars) + + +class pyparsing_unicode(unicode_set): + """ + A namespace class for defining common language unicode_sets. + """ + + # fmt: off + + # define ranges in language character sets + _ranges: UnicodeRangeList = [ + (0x0020, sys.maxunicode), + ] + + class BasicMultilingualPlane(unicode_set): + """Unicode set for the Basic Multilingual Plane""" + _ranges: UnicodeRangeList = [ + (0x0020, 0xFFFF), + ] + + class Latin1(unicode_set): + """Unicode set for Latin-1 Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0020, 0x007E), + (0x00A0, 0x00FF), + ] + + class LatinA(unicode_set): + """Unicode set for Latin-A Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0100, 0x017F), + ] + + class LatinB(unicode_set): + """Unicode set for Latin-B Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0180, 0x024F), + ] + + class Greek(unicode_set): + """Unicode set for Greek Unicode Character Ranges""" + _ranges: UnicodeRangeList = [ + (0x0342, 0x0345), + (0x0370, 0x0377), + (0x037A, 0x037F), + (0x0384, 0x038A), + (0x038C,), + (0x038E, 0x03A1), + (0x03A3, 0x03E1), + (0x03F0, 0x03FF), + (0x1D26, 0x1D2A), + (0x1D5E,), + (0x1D60,), + (0x1D66, 0x1D6A), + (0x1F00, 0x1F15), + (0x1F18, 0x1F1D), + (0x1F20, 0x1F45), + (0x1F48, 0x1F4D), + (0x1F50, 0x1F57), + (0x1F59,), + (0x1F5B,), + (0x1F5D,), + (0x1F5F, 0x1F7D), + (0x1F80, 0x1FB4), + (0x1FB6, 0x1FC4), + (0x1FC6, 0x1FD3), + (0x1FD6, 0x1FDB), + (0x1FDD, 0x1FEF), + (0x1FF2, 0x1FF4), + (0x1FF6, 0x1FFE), + (0x2129,), + (0x2719, 0x271A), + (0xAB65,), + (0x10140, 0x1018D), + (0x101A0,), + (0x1D200, 0x1D245), + (0x1F7A1, 0x1F7A7), + ] + + class Cyrillic(unicode_set): + """Unicode set for Cyrillic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0400, 0x052F), + (0x1C80, 0x1C88), + (0x1D2B,), + (0x1D78,), + (0x2DE0, 0x2DFF), + (0xA640, 0xA672), + (0xA674, 0xA69F), + (0xFE2E, 0xFE2F), + ] + + class Chinese(unicode_set): + """Unicode set for Chinese Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x2E80, 0x2E99), + (0x2E9B, 0x2EF3), + (0x31C0, 0x31E3), + (0x3400, 0x4DB5), + (0x4E00, 0x9FEF), + (0xA700, 0xA707), + (0xF900, 0xFA6D), + (0xFA70, 0xFAD9), + (0x16FE2, 0x16FE3), + (0x1F210, 0x1F212), + (0x1F214, 0x1F23B), + (0x1F240, 0x1F248), + (0x20000, 0x2A6D6), + (0x2A700, 0x2B734), + (0x2B740, 0x2B81D), + (0x2B820, 0x2CEA1), + (0x2CEB0, 0x2EBE0), + (0x2F800, 0x2FA1D), + ] + + class Japanese(unicode_set): + """Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges""" + + class Kanji(unicode_set): + "Unicode set for Kanji Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x4E00, 0x9FBF), + (0x3000, 0x303F), + ] + + class Hiragana(unicode_set): + """Unicode set for Hiragana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3041, 0x3096), + (0x3099, 0x30A0), + (0x30FC,), + (0xFF70,), + (0x1B001,), + (0x1B150, 0x1B152), + (0x1F200,), + ] + + class Katakana(unicode_set): + """Unicode set for Katakana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3099, 0x309C), + (0x30A0, 0x30FF), + (0x31F0, 0x31FF), + (0x32D0, 0x32FE), + (0xFF65, 0xFF9F), + (0x1B000,), + (0x1B164, 0x1B167), + (0x1F201, 0x1F202), + (0x1F213,), + ] + + 漢字 = Kanji + カタカナ = Katakana + ひらがな = Hiragana + + _ranges = ( + Kanji._ranges + + Hiragana._ranges + + Katakana._ranges + ) + + class Hangul(unicode_set): + """Unicode set for Hangul (Korean) Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x1100, 0x11FF), + (0x302E, 0x302F), + (0x3131, 0x318E), + (0x3200, 0x321C), + (0x3260, 0x327B), + (0x327E,), + (0xA960, 0xA97C), + (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), + (0xD7CB, 0xD7FB), + (0xFFA0, 0xFFBE), + (0xFFC2, 0xFFC7), + (0xFFCA, 0xFFCF), + (0xFFD2, 0xFFD7), + (0xFFDA, 0xFFDC), + ] + + Korean = Hangul + + class CJK(Chinese, Japanese, Hangul): + """Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range""" + + class Thai(unicode_set): + """Unicode set for Thai Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0E01, 0x0E3A), + (0x0E3F, 0x0E5B) + ] + + class Arabic(unicode_set): + """Unicode set for Arabic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0600, 0x061B), + (0x061E, 0x06FF), + (0x0700, 0x077F), + ] + + class Hebrew(unicode_set): + """Unicode set for Hebrew Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0591, 0x05C7), + (0x05D0, 0x05EA), + (0x05EF, 0x05F4), + (0xFB1D, 0xFB36), + (0xFB38, 0xFB3C), + (0xFB3E,), + (0xFB40, 0xFB41), + (0xFB43, 0xFB44), + (0xFB46, 0xFB4F), + ] + + class Devanagari(unicode_set): + """Unicode set for Devanagari Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0900, 0x097F), + (0xA8E0, 0xA8FF) + ] + + BMP = BasicMultilingualPlane + + # add language identifiers using language Unicode + العربية = Arabic + 中文 = Chinese + кириллица = Cyrillic + Ελληνικά = Greek + עִברִית = Hebrew + 日本語 = Japanese + 한국어 = Korean + ไทย = Thai + देवनागरी = Devanagari + + # fmt: on diff --git a/script.module.pyparsing/lib/pyparsing/util.py b/script.module.pyparsing/lib/pyparsing/util.py new file mode 100644 index 000000000..d8d3f414c --- /dev/null +++ b/script.module.pyparsing/lib/pyparsing/util.py @@ -0,0 +1,284 @@ +# util.py +import inspect +import warnings +import types +import collections +import itertools +from functools import lru_cache, wraps +from typing import Callable, List, Union, Iterable, TypeVar, cast + +_bslash = chr(92) +C = TypeVar("C", bound=Callable) + + +class __config_flags: + """Internal class for defining compatibility and debugging flags""" + + _all_names: List[str] = [] + _fixed_names: List[str] = [] + _type_desc = "configuration" + + @classmethod + def _set(cls, dname, value): + if dname in cls._fixed_names: + warnings.warn( + f"{cls.__name__}.{dname} {cls._type_desc} is {str(getattr(cls, dname)).upper()}" + f" and cannot be overridden", + stacklevel=3, + ) + return + if dname in cls._all_names: + setattr(cls, dname, value) + else: + raise ValueError(f"no such {cls._type_desc} {dname!r}") + + enable = classmethod(lambda cls, name: cls._set(name, True)) + disable = classmethod(lambda cls, name: cls._set(name, False)) + + +@lru_cache(maxsize=128) +def col(loc: int, strg: str) -> int: + """ + 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 + :class:`ParserElement.parse_string` for more + information on parsing strings containing ```` 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 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc) + + +@lru_cache(maxsize=128) +def lineno(loc: int, strg: str) -> int: + """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 :class:`ParserElement.parse_string` + for more information on parsing strings containing ```` 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 + + +@lru_cache(maxsize=128) +def line(loc: int, strg: str) -> str: + """ + Returns the line of text containing loc within a string, counting newlines as line separators. + """ + last_cr = strg.rfind("\n", 0, loc) + next_cr = strg.find("\n", loc) + return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :] + + +class _UnboundedCache: + def __init__(self): + cache = {} + cache_get = cache.get + self.not_in_cache = not_in_cache = object() + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + + def clear(_): + cache.clear() + + self.size = None + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class _FifoCache: + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + cache = {} + keyring = [object()] * size + cache_get = cache.get + cache_pop = cache.pop + keyiter = itertools.cycle(range(size)) + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + i = next(keyiter) + cache_pop(keyring[i], None) + keyring[i] = key + + def clear(_): + cache.clear() + keyring[:] = [object()] * size + + self.size = size + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class LRUMemo: + """ + A memoizing mapping that retains `capacity` deleted items + + The memo tracks retained items by their access order; once `capacity` items + are retained, the least recently used item is discarded. + """ + + def __init__(self, capacity): + self._capacity = capacity + self._active = {} + self._memory = collections.OrderedDict() + + def __getitem__(self, key): + try: + return self._active[key] + except KeyError: + self._memory.move_to_end(key) + return self._memory[key] + + def __setitem__(self, key, value): + self._memory.pop(key, None) + self._active[key] = value + + def __delitem__(self, key): + try: + value = self._active.pop(key) + except KeyError: + pass + else: + while len(self._memory) >= self._capacity: + self._memory.popitem(last=False) + self._memory[key] = value + + def clear(self): + self._active.clear() + self._memory.clear() + + +class UnboundedMemo(dict): + """ + A memoizing mapping that retains all deleted items + """ + + def __delitem__(self, key): + pass + + +def _escape_regex_range_chars(s: str) -> str: + # 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 str(s) + + +def _collapse_string_to_ranges( + s: Union[str, Iterable[str]], re_escape: bool = True +) -> str: + def is_consecutive(c): + c_int = ord(c) + is_consecutive.prev, prev = c_int, is_consecutive.prev + if c_int - prev > 1: + is_consecutive.value = next(is_consecutive.counter) + return is_consecutive.value + + is_consecutive.prev = 0 # type: ignore [attr-defined] + is_consecutive.counter = itertools.count() # type: ignore [attr-defined] + is_consecutive.value = -1 # type: ignore [attr-defined] + + def escape_re_range_char(c): + return "\\" + c if c in r"\^-][" else c + + def no_escape_re_range_char(c): + return c + + if not re_escape: + escape_re_range_char = no_escape_re_range_char + + ret = [] + s = "".join(sorted(set(s))) + if len(s) > 3: + for _, chars in itertools.groupby(s, key=is_consecutive): + first = last = next(chars) + last = collections.deque( + itertools.chain(iter([last]), chars), maxlen=1 + ).pop() + if first == last: + ret.append(escape_re_range_char(first)) + else: + sep = "" if ord(last) == ord(first) + 1 else "-" + ret.append( + f"{escape_re_range_char(first)}{sep}{escape_re_range_char(last)}" + ) + else: + ret = [escape_re_range_char(c) for c in s] + + return "".join(ret) + + +def _flatten(ll: list) -> list: + ret = [] + for i in ll: + if isinstance(i, list): + ret.extend(_flatten(i)) + else: + ret.append(i) + return ret + + +def _make_synonym_function(compat_name: str, fn: C) -> C: + # In a future version, uncomment the code in the internal _inner() functions + # to begin emitting DeprecationWarnings. + + # Unwrap staticmethod/classmethod + fn = getattr(fn, "__func__", fn) + + # (Presence of 'self' arg in signature is used by explain_exception() methods, so we take + # some extra steps to add it if present in decorated function.) + if "self" == list(inspect.signature(fn).parameters)[0]: + + @wraps(fn) + def _inner(self, *args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(self, *args, **kwargs) + + else: + + @wraps(fn) + def _inner(*args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(*args, **kwargs) + + _inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`""" + _inner.__name__ = compat_name + _inner.__annotations__ = fn.__annotations__ + if isinstance(fn, types.FunctionType): + _inner.__kwdefaults__ = fn.__kwdefaults__ + elif isinstance(fn, type) and hasattr(fn, "__init__"): + _inner.__kwdefaults__ = fn.__init__.__kwdefaults__ + else: + _inner.__kwdefaults__ = None + _inner.__qualname__ = fn.__qualname__ + return cast(C, _inner) + + +def replaced_by_pep8(fn: C) -> Callable[[Callable], C]: + """ + Decorator for pre-PEP8 compatibility synonyms, to link them to the new function. + """ + return lambda other: _make_synonym_function(other.__name__, fn) diff --git a/script.module.pyparsing/resources/icon.png b/script.module.pyparsing/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ca4c53adbfa8d06d0e282d45f9cad195e89d571e GIT binary patch literal 7237 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 literal 0 HcmV?d00001