Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
results.py929 linesDownload Raw Back to pyparsing
1# results.py2 3from __future__ import annotations4 5import collections6from collections.abc import (7    MutableMapping,8    Mapping,9    MutableSequence,10    Iterator,11    Iterable,12)13import pprint14from typing import Any15 16from .util import deprecate_argument, _is_iterable, _flatten17 18 19str_type: tuple[type, ...] = (str, bytes)20_generator_type = type((_ for _ in ()))21NULL_SLICE: slice = slice(None)22 23 24class _ParseResultsWithOffset:25    tup: tuple[ParseResults, int]26    __slots__ = ["tup"]27 28    def __init__(self, p1: ParseResults, p2: int) -> None:29        self.tup: tuple[ParseResults, int] = (p1, p2)30 31    def __getitem__(self, i):32        return self.tup[i]33 34    def __getstate__(self):35        return self.tup36 37    def __setstate__(self, *args):38        self.tup = args[0]39 40 41class ParseResults:42    """Structured parse results, to provide multiple means of access to43    the parsed data:44 45    - as a list (``len(results)``)46    - by list index (``results[0], results[1]``, etc.)47    - by attribute (``results.<results_name>`` - see :class:`ParserElement.set_results_name`)48 49    Example:50 51    .. testcode::52 53       integer = Word(nums)54       date_str = (integer.set_results_name("year") + '/'55                   + integer.set_results_name("month") + '/'56                   + integer.set_results_name("day"))57       # equivalent form:58       # date_str = (integer("year") + '/'59       #             + integer("month") + '/'60       #             + integer("day"))61 62       # parse_string returns a ParseResults object63       result = date_str.parse_string("1999/12/31")64 65       def test(s, fn=repr):66           print(f"{s} -> {fn(eval(s))}")67 68       test("list(result)")69       test("result[0]")70       test("result['month']")71       test("result.day")72       test("'month' in result")73       test("'minutes' in result")74       test("result.dump()", str)75 76    prints:77 78    .. testoutput::79 80       list(result) -> ['1999', '/', '12', '/', '31']81       result[0] -> '1999'82       result['month'] -> '12'83       result.day -> '31'84       'month' in result -> True85       'minutes' in result -> False86       result.dump() -> ['1999', '/', '12', '/', '31']87       - day: '31'88       - month: '12'89       - year: '1999'90 91    """92 93    _null_values: tuple[Any, ...] = (None, [], ())94 95    _name: str96    _parent: ParseResults97    _all_names: set[str]98    _modal: bool99    _toklist: list[Any]100    _tokdict: dict[str, Any]101 102    __slots__ = (103        "_name",104        "_parent",105        "_all_names",106        "_modal",107        "_toklist",108        "_tokdict",109    )110 111    class List(list):112        """113        Simple wrapper class to distinguish parsed list results that should be preserved114        as actual Python lists, instead of being converted to :class:`ParseResults`:115 116        .. testcode::117 118           import pyparsing as pp119           ppc = pp.common120 121           LBRACK, RBRACK, LPAR, RPAR = pp.Suppress.using_each("[]()")122           element = pp.Forward()123           item = ppc.integer124           item_list = pp.DelimitedList(element)125           element_list = LBRACK + item_list + RBRACK | LPAR + item_list + RPAR126           element <<= item | element_list127 128           # add parse action to convert from ParseResults129           # to actual Python collection types130           @element_list.add_parse_action131           def as_python_list(t):132               return pp.ParseResults.List(t.as_list())133 134           element.run_tests('''135               100136               [2,3,4]137               [[2, 1],3,4]138               [(2, 1),3,4]139               (2,3,4)140               ([2, 3], 4)141               ''', post_parse=lambda s, r: (r[0], type(r[0]))142           )143 144        prints:145 146        .. testoutput::147           :options: +NORMALIZE_WHITESPACE148 149 150           100151           (100, <class 'int'>)152 153           [2,3,4]154           ([2, 3, 4], <class 'list'>)155 156           [[2, 1],3,4]157           ([[2, 1], 3, 4], <class 'list'>)158 159           [(2, 1),3,4]160           ([[2, 1], 3, 4], <class 'list'>)161 162           (2,3,4)163           ([2, 3, 4], <class 'list'>)164 165           ([2, 3], 4)166           ([[2, 3], 4], <class 'list'>)167 168        (Used internally by :class:`Group` when `aslist=True`.)169        """170 171        def __new__(cls, contained=None):172            if contained is None:173                contained = []174 175            if not isinstance(contained, list):176                raise TypeError(177                    f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}"178                )179 180            return list.__new__(cls)181 182    def __new__(cls, toklist=None, name=None, **kwargs):183        if isinstance(toklist, ParseResults):184            return toklist185        self = object.__new__(cls)186        self._name = None187        self._parent = None188        self._all_names = set()189 190        if toklist is None:191            self._toklist = []192        elif isinstance(toklist, (list, _generator_type)):193            self._toklist = (194                [toklist[:]]195                if isinstance(toklist, ParseResults.List)196                else list(toklist)197            )198        else:199            self._toklist = [toklist]200        self._tokdict = dict()201        return self202 203    # Performance tuning: we construct a *lot* of these, so keep this204    # constructor as small and fast as possible205    def __init__(206        self,207        toklist=None,208        name=None,209        aslist=True,210        modal=True,211        isinstance=isinstance,212        **kwargs,213    ) -> None:214        asList = deprecate_argument(kwargs, "asList", True, new_name="aslist")215 216        asList = asList and aslist217        self._tokdict: dict[str, _ParseResultsWithOffset]218        self._modal = modal219 220        if name is None or name == "":221            return222 223        if isinstance(name, int):224            name = str(name)225 226        if not modal:227            self._all_names = {name}228 229        self._name = name230 231        if toklist in self._null_values:232            return233 234        if isinstance(toklist, (str_type, type)):235            toklist = [toklist]236 237        if asList:238            if isinstance(toklist, ParseResults):239                self[name] = _ParseResultsWithOffset(ParseResults(toklist._toklist), 0)240            else:241                self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0)242            self[name]._name = name243            return244 245        try:246            self[name] = toklist[0]247        except (KeyError, TypeError, IndexError):248            if toklist is not self:249                self[name] = toklist250            else:251                self._name = name252 253    def __getitem__(self, i):254        if isinstance(i, (int, slice)):255            return self._toklist[i]256 257        if i not in self._all_names:258            return self._tokdict[i][-1][0]259 260        return ParseResults([v[0] for v in self._tokdict[i]])261 262    def __setitem__(self, k, v, isinstance=isinstance):263        if isinstance(v, _ParseResultsWithOffset):264            self._tokdict[k] = self._tokdict.get(k, list()) + [v]265            sub = v[0]266        elif isinstance(k, (int, slice)):267            self._toklist[k] = v268            sub = v269        else:270            self._tokdict[k] = self._tokdict.get(k, []) + [271                _ParseResultsWithOffset(v, 0)272            ]273            sub = v274        if isinstance(sub, ParseResults):275            sub._parent = self276 277    def __delitem__(self, i):278        if not isinstance(i, (int, slice)):279            del self._tokdict[i]280            return281 282        # slight optimization if del results[:]283        if i == NULL_SLICE:284            self._toklist.clear()285            return286 287        mylen = len(self._toklist)288        del self._toklist[i]289 290        # convert int to slice291        if isinstance(i, int):292            if i < 0:293                i += mylen294            i = slice(i, i + 1)295        # get removed indices296        removed = list(range(*i.indices(mylen)))297        removed.reverse()298        # fixup indices in token dictionary299        for occurrences in self._tokdict.values():300            for j in removed:301                for k, (value, position) in enumerate(occurrences):302                    occurrences[k] = _ParseResultsWithOffset(303                        value, position - (position > j)304                    )305 306    def __contains__(self, k) -> bool:307        return k in self._tokdict308 309    def __len__(self) -> int:310        return len(self._toklist)311 312    def __bool__(self) -> bool:313        return not not (self._toklist or self._tokdict)314 315    def __iter__(self) -> Iterator:316        return iter(self._toklist)317 318    def __reversed__(self) -> Iterator:319        return iter(self._toklist[::-1])320 321    def keys(self):322        return iter(self._tokdict)323 324    def values(self):325        return (self[k] for k in self.keys())326 327    def items(self):328        return ((k, self[k]) for k in self.keys())329 330    def haskeys(self) -> bool:331        """332        Since ``keys()`` returns an iterator, this method is helpful in bypassing333        code that looks for the existence of any defined results names."""334        return not not self._tokdict335 336    def pop(self, *args, **kwargs):337        """338        Removes and returns item at specified index (default= ``last``).339        Supports both ``list`` and ``dict`` semantics for ``pop()``. If340        passed no argument or an integer argument, it will use ``list``341        semantics and pop tokens from the list of parsed tokens. If passed342        a non-integer argument (most likely a string), it will use ``dict``343        semantics and pop the corresponding value from any defined results344        names. A second default return value argument is supported, just as in345        ``dict.pop()``.346 347        Example:348 349        .. doctest::350 351           >>> numlist = Word(nums)[...]352           >>> print(numlist.parse_string("0 123 321"))353           ['0', '123', '321']354 355           >>> def remove_first(tokens):356           ...     tokens.pop(0)357           ...358           >>> numlist.add_parse_action(remove_first)359           [W:(0-9)]...360           >>> print(numlist.parse_string("0 123 321"))361           ['123', '321']362 363           >>> label = Word(alphas)364           >>> patt = label("LABEL") + Word(nums)[1, ...]365           >>> print(patt.parse_string("AAB 123 321").dump())366           ['AAB', '123', '321']367           - LABEL: 'AAB'368 369           >>> # Use pop() in a parse action to remove named result370           >>> # (note that corresponding value is not371           >>> # removed from list form of results)372           >>> def remove_LABEL(tokens):373           ...     tokens.pop("LABEL")374           ...     return tokens375           ...376           >>> patt.add_parse_action(remove_LABEL)377           {W:(A-Za-z) {W:(0-9)}...}378           >>> print(patt.parse_string("AAB 123 321").dump())379           ['AAB', '123', '321']380 381        """382        if not args:383            args = [-1]384        for k, v in kwargs.items():385            if k == "default":386                args = (args[0], v)387            else:388                raise TypeError(f"pop() got an unexpected keyword argument {k!r}")389        if isinstance(args[0], int) or len(args) == 1 or args[0] in self:390            index = args[0]391            ret = self[index]392            del self[index]393            return ret394        else:395            defaultvalue = args[1]396            return defaultvalue397 398    def get(self, key, default_value=None):399        """400        Returns named result matching the given key, or if there is no401        such name, then returns the given ``default_value`` or ``None`` if no402        ``default_value`` is specified.403 404        Similar to ``dict.get()``.405 406        Example:407 408        .. doctest::409 410           >>> integer = Word(nums)411           >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day")412 413           >>> result = date_str.parse_string("1999/12/31")414           >>> result.get("year")415           '1999'416           >>> result.get("hour", "not specified")417           'not specified'418           >>> result.get("hour")419 420        """421        if key in self:422            return self[key]423        else:424            return default_value425 426    def insert(self, index, ins_string):427        """428        Inserts new element at location index in the list of parsed tokens.429 430        Similar to ``list.insert()``.431 432        Example:433 434        .. doctest::435 436           >>> numlist = Word(nums)[...]437           >>> print(numlist.parse_string("0 123 321"))438           ['0', '123', '321']439 440           >>> # use a parse action to insert the parse location441           >>> # in the front of the parsed results442           >>> def insert_locn(locn, tokens):443           ...     tokens.insert(0, locn)444           ...445           >>> numlist.add_parse_action(insert_locn)446           [W:(0-9)]...447           >>> print(numlist.parse_string("0 123 321"))448           [0, '0', '123', '321']449 450        """451        self._toklist.insert(index, ins_string)452        # fixup indices in token dictionary453        for occurrences in self._tokdict.values():454            for k, (value, position) in enumerate(occurrences):455                occurrences[k] = _ParseResultsWithOffset(456                    value, position + (position > index)457                )458 459    def append(self, item):460        """461        Add single element to end of ``ParseResults`` list of elements.462 463        Example:464 465        .. doctest::466 467           >>> numlist = Word(nums)[...]468           >>> print(numlist.parse_string("0 123 321"))469           ['0', '123', '321']470 471           >>> # use a parse action to compute the sum of the parsed integers,472           >>> # and add it to the end473           >>> def append_sum(tokens):474           ...     tokens.append(sum(map(int, tokens)))475           ...476           >>> numlist.add_parse_action(append_sum)477           [W:(0-9)]...478           >>> print(numlist.parse_string("0 123 321"))479           ['0', '123', '321', 444]480        """481        self._toklist.append(item)482 483    def extend(self, itemseq):484        """485        Add sequence of elements to end of :class:`ParseResults` list of elements.486 487        Example:488 489        .. testcode::490 491           patt = Word(alphas)[1, ...]492 493           # use a parse action to append the reverse of the matched strings,494           # to make a palindrome495           def make_palindrome(tokens):496               tokens.extend(reversed([t[::-1] for t in tokens]))497               return ''.join(tokens)498 499           patt.add_parse_action(make_palindrome)500           print(patt.parse_string("lskdj sdlkjf lksd"))501 502        prints:503 504        .. testoutput::505 506           ['lskdjsdlkjflksddsklfjkldsjdksl']507        """508        if isinstance(itemseq, ParseResults):509            self.__iadd__(itemseq)510        else:511            self._toklist.extend(itemseq)512 513    def clear(self):514        """515        Clear all elements and results names.516        """517        del self._toklist[:]518        self._tokdict.clear()519 520    def __getattr__(self, name):521        try:522            return self[name]523        except KeyError:524            if name.startswith("__"):525                raise AttributeError(name)526            return ""527 528    def __add__(self, other: ParseResults) -> ParseResults:529        ret = self.copy()530        ret += other531        return ret532 533    def __iadd__(self, other: ParseResults) -> ParseResults:534        if not other:535            return self536 537        if other._tokdict:538            offset = len(self._toklist)539            addoffset = lambda a: offset if a < 0 else a + offset540            otheritems = other._tokdict.items()541            otherdictitems = [542                (k, _ParseResultsWithOffset(v[0], addoffset(v[1])))543                for k, vlist in otheritems544                for v in vlist545            ]546            for k, v in otherdictitems:547                self[k] = v548                if isinstance(v[0], ParseResults):549                    v[0]._parent = self550 551        self._toklist += other._toklist552        self._all_names |= other._all_names553        return self554 555    def __radd__(self, other) -> ParseResults:556        if isinstance(other, int) and other == 0:557            # useful for merging many ParseResults using sum() builtin558            return self.copy()559        else:560            # this may raise a TypeError - so be it561            return other + self562 563    def __repr__(self) -> str:564        return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})"565 566    def __str__(self) -> str:567        return (568            "["569            + ", ".join(570                [571                    str(i) if isinstance(i, ParseResults) else repr(i)572                    for i in self._toklist573                ]574            )575            + "]"576        )577 578    def _asStringList(self, sep=""):579        out = []580        for item in self._toklist:581            if out and sep:582                out.append(sep)583            if isinstance(item, ParseResults):584                out += item._asStringList()585            else:586                out.append(str(item))587        return out588 589    def as_list(self, *, flatten: bool = False) -> list:590        """591        Returns the parse results as a nested list of matching tokens, all converted to strings.592        If ``flatten`` is True, all the nesting levels in the returned list are collapsed.593 594        Example:595 596        .. doctest::597 598           >>> patt = Word(alphas)[1, ...]599           >>> result = patt.parse_string("sldkj lsdkj sldkj")600           >>> # even though the result prints in string-like form,601           >>> # it is actually a pyparsing ParseResults602           >>> type(result)603           <class 'pyparsing.results.ParseResults'>604           >>> print(result)605           ['sldkj', 'lsdkj', 'sldkj']606 607        .. doctest::608 609           >>> # Use as_list() to create an actual list610           >>> result_list = result.as_list()611           >>> type(result_list)612           <class 'list'>613           >>> print(result_list)614           ['sldkj', 'lsdkj', 'sldkj']615 616        .. versionchanged:: 3.2.0617           New ``flatten`` argument.618        """619 620        if flatten:621            return [*_flatten(self)]622        else:623            return [624                res.as_list() if isinstance(res, ParseResults) else res625                for res in self._toklist626            ]627 628    def as_dict(self) -> dict:629        """630        Returns the named parse results as a nested dictionary.631 632        Example:633 634        .. doctest::635 636           >>> integer = pp.Word(pp.nums)637           >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day")638 639           >>> result = date_str.parse_string('1999/12/31')640           >>> type(result)641           <class 'pyparsing.results.ParseResults'>642           >>> result643           ParseResults(['1999', '/', '12', '/', '31'], {'year': '1999', 'month': '12', 'day': '31'})644 645           >>> result_dict = result.as_dict()646           >>> type(result_dict)647           <class 'dict'>648           >>> result_dict649           {'year': '1999', 'month': '12', 'day': '31'}650 651           >>> # even though a ParseResults supports dict-like access,652           >>> # sometime you just need to have a dict653           >>> import json654           >>> print(json.dumps(result))655           Traceback (most recent call last):656           TypeError: Object of type ParseResults is not JSON serializable657           >>> print(json.dumps(result.as_dict()))658           {"year": "1999", "month": "12", "day": "31"}659        """660 661        def to_item(obj):662            if isinstance(obj, ParseResults):663                return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj]664            else:665                return obj666 667        return dict((k, to_item(v)) for k, v in self.items())668 669    def copy(self) -> ParseResults:670        """671        Returns a new shallow copy of a :class:`ParseResults` object.672        :class:`ParseResults` items contained within the source are673        shared with the copy. Use :meth:`ParseResults.deepcopy` to674        create a copy with its own separate content values.675        """676        ret = ParseResults(self._toklist)677        ret._tokdict = self._tokdict.copy()678        ret._parent = self._parent679        ret._all_names |= self._all_names680        ret._name = self._name681        return ret682 683    def deepcopy(self) -> ParseResults:684        """685        Returns a new deep copy of a :class:`ParseResults` object.686 687        .. versionadded:: 3.1.0688        """689        ret = self.copy()690        # replace values with copies if they are of known mutable types691        for i, obj in enumerate(self._toklist):692            if isinstance(obj, ParseResults):693                ret._toklist[i] = obj.deepcopy()694            elif isinstance(obj, (str, bytes)):695                pass696            elif isinstance(obj, MutableMapping):697                ret._toklist[i] = dest = type(obj)()698                for k, v in obj.items():699                    dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v700            elif isinstance(obj, Iterable):701                ret._toklist[i] = type(obj)(702                    v.deepcopy() if isinstance(v, ParseResults) else v for v in obj  # type: ignore[call-arg]703                )704        return ret705 706    def get_name(self) -> str | None:707        r"""708        Returns the results name for this token expression.709 710        Useful when several different expressions might match711        at a particular location.712 713        Example:714 715        .. testcode::716 717           integer = Word(nums)718           ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")719           house_number_expr = Suppress('#') + Word(nums, alphanums)720           user_data = (Group(house_number_expr)("house_number")721                       | Group(ssn_expr)("ssn")722                       | Group(integer)("age"))723           user_info = user_data[1, ...]724 725           result = user_info.parse_string("22 111-22-3333 #221B")726           for item in result:727               print(item.get_name(), ':', item[0])728 729        prints:730 731        .. testoutput::732 733           age : 22734           ssn : 111-22-3333735           house_number : 221B736 737        """738        if self._name:739            return self._name740        elif self._parent:741            par: ParseResults = self._parent742            parent_tokdict_items = par._tokdict.items()743            return next(744                (745                    k746                    for k, vlist in parent_tokdict_items747                    for v, loc in vlist748                    if v is self749                ),750                None,751            )752        elif (753            len(self) == 1754            and len(self._tokdict) == 1755            and next(iter(self._tokdict.values()))[0][1] in (0, -1)756        ):757            return next(iter(self._tokdict.keys()))758        else:759            return None760 761    def dump(self, indent="", full=True, include_list=True, _depth=0) -> str:762        """763        Diagnostic method for listing out the contents of764        a :class:`ParseResults`. Accepts an optional ``indent`` argument so765        that this string can be embedded in a nested display of other data.766 767        Example:768 769        .. testcode::770 771           integer = Word(nums)772           date_str = integer("year") + '/' + integer("month") + '/' + integer("day")773 774           result = date_str.parse_string('1999/12/31')775           print(result.dump())776 777        prints:778 779        .. testoutput::780 781           ['1999', '/', '12', '/', '31']782           - day: '31'783           - month: '12'784           - year: '1999'785        """786        out = []787        NL = "\n"788        out.append(indent + str(self.as_list()) if include_list else "")789 790        if not full:791            return "".join(out)792 793        if self.haskeys():794            items = sorted((str(k), v) for k, v in self.items())795            for k, v in items:796                if out:797                    out.append(NL)798                out.append(f"{indent}{('  ' * _depth)}- {k}: ")799                if not isinstance(v, ParseResults):800                    out.append(repr(v))801                    continue802 803                if not v:804                    out.append(str(v))805                    continue806 807                out.append(808                    v.dump(809                        indent=indent,810                        full=full,811                        include_list=include_list,812                        _depth=_depth + 1,813                    )814                )815        if not any(isinstance(vv, ParseResults) for vv in self):816            return "".join(out)817 818        v = self819        incr = "  "820        nl = "\n"821        for i, vv in enumerate(v):822            if isinstance(vv, ParseResults):823                vv_dump = vv.dump(824                    indent=indent,825                    full=full,826                    include_list=include_list,827                    _depth=_depth + 1,828                )829                out.append(830                    f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv_dump}"831                )832            else:833                out.append(834                    f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv}"835                )836 837        return "".join(out)838 839    def pprint(self, *args, **kwargs):840        """841        Pretty-printer for parsed results as a list, using the842        `pprint <https://docs.python.org/3/library/pprint.html>`_ module.843        Accepts additional positional or keyword args as defined for844        `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ .845 846        Example:847 848        .. testcode::849 850           ident = Word(alphas, alphanums)851           num = Word(nums)852           func = Forward()853           term = ident | num | Group('(' + func + ')')854           func <<= ident + Group(Optional(DelimitedList(term)))855           result = func.parse_string("fna a,b,(fnb c,d,200),100")856           result.pprint(width=40)857 858        prints:859 860        .. testoutput::861 862           ['fna',863            ['a',864             'b',865             ['(', 'fnb', ['c', 'd', '200'], ')'],866             '100']]867        """868        pprint.pprint(self.as_list(), *args, **kwargs)869 870    # add support for pickle protocol871    def __getstate__(self):872        return (873            self._toklist,874            (875                self._tokdict.copy(),876                None,877                self._all_names,878                self._name,879            ),880        )881 882    def __setstate__(self, state):883        self._toklist, (self._tokdict, par, inAccumNames, self._name) = state884        self._all_names = set(inAccumNames)885        self._parent = None886 887    def __getnewargs__(self):888        return self._toklist, self._name889 890    def __dir__(self):891        return dir(type(self)) + list(self.keys())892 893    @classmethod894    def from_dict(cls, other, name=None) -> ParseResults:895        """896        Helper classmethod to construct a :class:`ParseResults` from a ``dict``, preserving the897        name-value relations as results names. If an optional ``name`` argument is898        given, a nested :class:`ParseResults` will be returned.899        """900        ret = cls([])901        for k, v in other.items():902            if isinstance(v, Mapping):903                ret += cls.from_dict(v, name=k)904            else:905                ret += cls([v], name=k, aslist=_is_iterable(v))906        if name is not None:907            ret = cls([ret], name=name)908        return ret909 910    asList = as_list911    """912    .. deprecated:: 3.0.0913       use :meth:`as_list`914    """915    asDict = as_dict916    """917    .. deprecated:: 3.0.0918       use :meth:`as_dict`919    """920    getName = get_name921    """922    .. deprecated:: 3.0.0923       use :meth:`get_name`924    """925 926 927MutableMapping.register(ParseResults)928MutableSequence.register(ParseResults)929 
codekingpro/portable-devtools · Team Ai