results.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  1. # results.py
  2. from __future__ import annotations
  3. import collections
  4. from collections.abc import (
  5. MutableMapping,
  6. Mapping,
  7. MutableSequence,
  8. Iterator,
  9. Iterable,
  10. )
  11. import pprint
  12. from typing import Any
  13. from .util import deprecate_argument
  14. str_type: tuple[type, ...] = (str, bytes)
  15. _generator_type = type((_ for _ in ()))
  16. class _ParseResultsWithOffset:
  17. tup: tuple[ParseResults, int]
  18. __slots__ = ["tup"]
  19. def __init__(self, p1: ParseResults, p2: int) -> None:
  20. self.tup: tuple[ParseResults, int] = (p1, p2)
  21. def __getitem__(self, i):
  22. return self.tup[i]
  23. def __getstate__(self):
  24. return self.tup
  25. def __setstate__(self, *args):
  26. self.tup = args[0]
  27. class ParseResults:
  28. """Structured parse results, to provide multiple means of access to
  29. the parsed data:
  30. - as a list (``len(results)``)
  31. - by list index (``results[0], results[1]``, etc.)
  32. - by attribute (``results.<results_name>`` - see :class:`ParserElement.set_results_name`)
  33. Example:
  34. .. testcode::
  35. integer = Word(nums)
  36. date_str = (integer.set_results_name("year") + '/'
  37. + integer.set_results_name("month") + '/'
  38. + integer.set_results_name("day"))
  39. # equivalent form:
  40. # date_str = (integer("year") + '/'
  41. # + integer("month") + '/'
  42. # + integer("day"))
  43. # parse_string returns a ParseResults object
  44. result = date_str.parse_string("1999/12/31")
  45. def test(s, fn=repr):
  46. print(f"{s} -> {fn(eval(s))}")
  47. test("list(result)")
  48. test("result[0]")
  49. test("result['month']")
  50. test("result.day")
  51. test("'month' in result")
  52. test("'minutes' in result")
  53. test("result.dump()", str)
  54. prints:
  55. .. testoutput::
  56. list(result) -> ['1999', '/', '12', '/', '31']
  57. result[0] -> '1999'
  58. result['month'] -> '12'
  59. result.day -> '31'
  60. 'month' in result -> True
  61. 'minutes' in result -> False
  62. result.dump() -> ['1999', '/', '12', '/', '31']
  63. - day: '31'
  64. - month: '12'
  65. - year: '1999'
  66. """
  67. _null_values: tuple[Any, ...] = (None, [], ())
  68. _name: str
  69. _parent: ParseResults
  70. _all_names: set[str]
  71. _modal: bool
  72. _toklist: list[Any]
  73. _tokdict: dict[str, Any]
  74. __slots__ = (
  75. "_name",
  76. "_parent",
  77. "_all_names",
  78. "_modal",
  79. "_toklist",
  80. "_tokdict",
  81. )
  82. class List(list):
  83. """
  84. Simple wrapper class to distinguish parsed list results that should be preserved
  85. as actual Python lists, instead of being converted to :class:`ParseResults`:
  86. .. testcode::
  87. import pyparsing as pp
  88. ppc = pp.common
  89. LBRACK, RBRACK, LPAR, RPAR = pp.Suppress.using_each("[]()")
  90. element = pp.Forward()
  91. item = ppc.integer
  92. item_list = pp.DelimitedList(element)
  93. element_list = LBRACK + item_list + RBRACK | LPAR + item_list + RPAR
  94. element <<= item | element_list
  95. # add parse action to convert from ParseResults
  96. # to actual Python collection types
  97. @element_list.add_parse_action
  98. def as_python_list(t):
  99. return pp.ParseResults.List(t.as_list())
  100. element.run_tests('''
  101. 100
  102. [2,3,4]
  103. [[2, 1],3,4]
  104. [(2, 1),3,4]
  105. (2,3,4)
  106. ([2, 3], 4)
  107. ''', post_parse=lambda s, r: (r[0], type(r[0]))
  108. )
  109. prints:
  110. .. testoutput::
  111. :options: +NORMALIZE_WHITESPACE
  112. 100
  113. (100, <class 'int'>)
  114. [2,3,4]
  115. ([2, 3, 4], <class 'list'>)
  116. [[2, 1],3,4]
  117. ([[2, 1], 3, 4], <class 'list'>)
  118. [(2, 1),3,4]
  119. ([[2, 1], 3, 4], <class 'list'>)
  120. (2,3,4)
  121. ([2, 3, 4], <class 'list'>)
  122. ([2, 3], 4)
  123. ([[2, 3], 4], <class 'list'>)
  124. (Used internally by :class:`Group` when `aslist=True`.)
  125. """
  126. def __new__(cls, contained=None):
  127. if contained is None:
  128. contained = []
  129. if not isinstance(contained, list):
  130. raise TypeError(
  131. f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}"
  132. )
  133. return list.__new__(cls)
  134. def __new__(cls, toklist=None, name=None, **kwargs):
  135. if isinstance(toklist, ParseResults):
  136. return toklist
  137. self = object.__new__(cls)
  138. self._name = None
  139. self._parent = None
  140. self._all_names = set()
  141. if toklist is None:
  142. self._toklist = []
  143. elif isinstance(toklist, (list, _generator_type)):
  144. self._toklist = (
  145. [toklist[:]]
  146. if isinstance(toklist, ParseResults.List)
  147. else list(toklist)
  148. )
  149. else:
  150. self._toklist = [toklist]
  151. self._tokdict = dict()
  152. return self
  153. # Performance tuning: we construct a *lot* of these, so keep this
  154. # constructor as small and fast as possible
  155. def __init__(
  156. self,
  157. toklist=None,
  158. name=None,
  159. aslist=True,
  160. modal=True,
  161. isinstance=isinstance,
  162. **kwargs,
  163. ) -> None:
  164. asList = deprecate_argument(kwargs, "asList", True, new_name="aslist")
  165. asList = asList and aslist
  166. self._tokdict: dict[str, _ParseResultsWithOffset]
  167. self._modal = modal
  168. if name is None or name == "":
  169. return
  170. if isinstance(name, int):
  171. name = str(name)
  172. if not modal:
  173. self._all_names = {name}
  174. self._name = name
  175. if toklist in self._null_values:
  176. return
  177. if isinstance(toklist, (str_type, type)):
  178. toklist = [toklist]
  179. if asList:
  180. if isinstance(toklist, ParseResults):
  181. self[name] = _ParseResultsWithOffset(ParseResults(toklist._toklist), 0)
  182. else:
  183. self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0)
  184. self[name]._name = name
  185. return
  186. try:
  187. self[name] = toklist[0]
  188. except (KeyError, TypeError, IndexError):
  189. if toklist is not self:
  190. self[name] = toklist
  191. else:
  192. self._name = name
  193. def __getitem__(self, i):
  194. if isinstance(i, (int, slice)):
  195. return self._toklist[i]
  196. if i not in self._all_names:
  197. return self._tokdict[i][-1][0]
  198. return ParseResults([v[0] for v in self._tokdict[i]])
  199. def __setitem__(self, k, v, isinstance=isinstance):
  200. if isinstance(v, _ParseResultsWithOffset):
  201. self._tokdict[k] = self._tokdict.get(k, list()) + [v]
  202. sub = v[0]
  203. elif isinstance(k, (int, slice)):
  204. self._toklist[k] = v
  205. sub = v
  206. else:
  207. self._tokdict[k] = self._tokdict.get(k, []) + [
  208. _ParseResultsWithOffset(v, 0)
  209. ]
  210. sub = v
  211. if isinstance(sub, ParseResults):
  212. sub._parent = self
  213. def __delitem__(self, i):
  214. if not isinstance(i, (int, slice)):
  215. del self._tokdict[i]
  216. return
  217. mylen = len(self._toklist)
  218. del self._toklist[i]
  219. # convert int to slice
  220. if isinstance(i, int):
  221. if i < 0:
  222. i += mylen
  223. i = slice(i, i + 1)
  224. # get removed indices
  225. removed = list(range(*i.indices(mylen)))
  226. removed.reverse()
  227. # fixup indices in token dictionary
  228. for occurrences in self._tokdict.values():
  229. for j in removed:
  230. for k, (value, position) in enumerate(occurrences):
  231. occurrences[k] = _ParseResultsWithOffset(
  232. value, position - (position > j)
  233. )
  234. def __contains__(self, k) -> bool:
  235. return k in self._tokdict
  236. def __len__(self) -> int:
  237. return len(self._toklist)
  238. def __bool__(self) -> bool:
  239. return not not (self._toklist or self._tokdict)
  240. def __iter__(self) -> Iterator:
  241. return iter(self._toklist)
  242. def __reversed__(self) -> Iterator:
  243. return iter(self._toklist[::-1])
  244. def keys(self):
  245. return iter(self._tokdict)
  246. def values(self):
  247. return (self[k] for k in self.keys())
  248. def items(self):
  249. return ((k, self[k]) for k in self.keys())
  250. def haskeys(self) -> bool:
  251. """
  252. Since ``keys()`` returns an iterator, this method is helpful in bypassing
  253. code that looks for the existence of any defined results names."""
  254. return not not self._tokdict
  255. def pop(self, *args, **kwargs):
  256. """
  257. Removes and returns item at specified index (default= ``last``).
  258. Supports both ``list`` and ``dict`` semantics for ``pop()``. If
  259. passed no argument or an integer argument, it will use ``list``
  260. semantics and pop tokens from the list of parsed tokens. If passed
  261. a non-integer argument (most likely a string), it will use ``dict``
  262. semantics and pop the corresponding value from any defined results
  263. names. A second default return value argument is supported, just as in
  264. ``dict.pop()``.
  265. Example:
  266. .. doctest::
  267. >>> numlist = Word(nums)[...]
  268. >>> print(numlist.parse_string("0 123 321"))
  269. ['0', '123', '321']
  270. >>> def remove_first(tokens):
  271. ... tokens.pop(0)
  272. ...
  273. >>> numlist.add_parse_action(remove_first)
  274. [W:(0-9)]...
  275. >>> print(numlist.parse_string("0 123 321"))
  276. ['123', '321']
  277. >>> label = Word(alphas)
  278. >>> patt = label("LABEL") + Word(nums)[1, ...]
  279. >>> print(patt.parse_string("AAB 123 321").dump())
  280. ['AAB', '123', '321']
  281. - LABEL: 'AAB'
  282. >>> # Use pop() in a parse action to remove named result
  283. >>> # (note that corresponding value is not
  284. >>> # removed from list form of results)
  285. >>> def remove_LABEL(tokens):
  286. ... tokens.pop("LABEL")
  287. ... return tokens
  288. ...
  289. >>> patt.add_parse_action(remove_LABEL)
  290. {W:(A-Za-z) {W:(0-9)}...}
  291. >>> print(patt.parse_string("AAB 123 321").dump())
  292. ['AAB', '123', '321']
  293. """
  294. if not args:
  295. args = [-1]
  296. for k, v in kwargs.items():
  297. if k == "default":
  298. args = (args[0], v)
  299. else:
  300. raise TypeError(f"pop() got an unexpected keyword argument {k!r}")
  301. if isinstance(args[0], int) or len(args) == 1 or args[0] in self:
  302. index = args[0]
  303. ret = self[index]
  304. del self[index]
  305. return ret
  306. else:
  307. defaultvalue = args[1]
  308. return defaultvalue
  309. def get(self, key, default_value=None):
  310. """
  311. Returns named result matching the given key, or if there is no
  312. such name, then returns the given ``default_value`` or ``None`` if no
  313. ``default_value`` is specified.
  314. Similar to ``dict.get()``.
  315. Example:
  316. .. doctest::
  317. >>> integer = Word(nums)
  318. >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
  319. >>> result = date_str.parse_string("1999/12/31")
  320. >>> result.get("year")
  321. '1999'
  322. >>> result.get("hour", "not specified")
  323. 'not specified'
  324. >>> result.get("hour")
  325. """
  326. if key in self:
  327. return self[key]
  328. else:
  329. return default_value
  330. def insert(self, index, ins_string):
  331. """
  332. Inserts new element at location index in the list of parsed tokens.
  333. Similar to ``list.insert()``.
  334. Example:
  335. .. doctest::
  336. >>> numlist = Word(nums)[...]
  337. >>> print(numlist.parse_string("0 123 321"))
  338. ['0', '123', '321']
  339. >>> # use a parse action to insert the parse location
  340. >>> # in the front of the parsed results
  341. >>> def insert_locn(locn, tokens):
  342. ... tokens.insert(0, locn)
  343. ...
  344. >>> numlist.add_parse_action(insert_locn)
  345. [W:(0-9)]...
  346. >>> print(numlist.parse_string("0 123 321"))
  347. [0, '0', '123', '321']
  348. """
  349. self._toklist.insert(index, ins_string)
  350. # fixup indices in token dictionary
  351. for occurrences in self._tokdict.values():
  352. for k, (value, position) in enumerate(occurrences):
  353. occurrences[k] = _ParseResultsWithOffset(
  354. value, position + (position > index)
  355. )
  356. def append(self, item):
  357. """
  358. Add single element to end of ``ParseResults`` list of elements.
  359. Example:
  360. .. doctest::
  361. >>> numlist = Word(nums)[...]
  362. >>> print(numlist.parse_string("0 123 321"))
  363. ['0', '123', '321']
  364. >>> # use a parse action to compute the sum of the parsed integers,
  365. >>> # and add it to the end
  366. >>> def append_sum(tokens):
  367. ... tokens.append(sum(map(int, tokens)))
  368. ...
  369. >>> numlist.add_parse_action(append_sum)
  370. [W:(0-9)]...
  371. >>> print(numlist.parse_string("0 123 321"))
  372. ['0', '123', '321', 444]
  373. """
  374. self._toklist.append(item)
  375. def extend(self, itemseq):
  376. """
  377. Add sequence of elements to end of :class:`ParseResults` list of elements.
  378. Example:
  379. .. testcode::
  380. patt = Word(alphas)[1, ...]
  381. # use a parse action to append the reverse of the matched strings,
  382. # to make a palindrome
  383. def make_palindrome(tokens):
  384. tokens.extend(reversed([t[::-1] for t in tokens]))
  385. return ''.join(tokens)
  386. patt.add_parse_action(make_palindrome)
  387. print(patt.parse_string("lskdj sdlkjf lksd"))
  388. prints:
  389. .. testoutput::
  390. ['lskdjsdlkjflksddsklfjkldsjdksl']
  391. """
  392. if isinstance(itemseq, ParseResults):
  393. self.__iadd__(itemseq)
  394. else:
  395. self._toklist.extend(itemseq)
  396. def clear(self):
  397. """
  398. Clear all elements and results names.
  399. """
  400. del self._toklist[:]
  401. self._tokdict.clear()
  402. def __getattr__(self, name):
  403. try:
  404. return self[name]
  405. except KeyError:
  406. if name.startswith("__"):
  407. raise AttributeError(name)
  408. return ""
  409. def __add__(self, other: ParseResults) -> ParseResults:
  410. ret = self.copy()
  411. ret += other
  412. return ret
  413. def __iadd__(self, other: ParseResults) -> ParseResults:
  414. if not other:
  415. return self
  416. if other._tokdict:
  417. offset = len(self._toklist)
  418. addoffset = lambda a: offset if a < 0 else a + offset
  419. otheritems = other._tokdict.items()
  420. otherdictitems = [
  421. (k, _ParseResultsWithOffset(v[0], addoffset(v[1])))
  422. for k, vlist in otheritems
  423. for v in vlist
  424. ]
  425. for k, v in otherdictitems:
  426. self[k] = v
  427. if isinstance(v[0], ParseResults):
  428. v[0]._parent = self
  429. self._toklist += other._toklist
  430. self._all_names |= other._all_names
  431. return self
  432. def __radd__(self, other) -> ParseResults:
  433. if isinstance(other, int) and other == 0:
  434. # useful for merging many ParseResults using sum() builtin
  435. return self.copy()
  436. else:
  437. # this may raise a TypeError - so be it
  438. return other + self
  439. def __repr__(self) -> str:
  440. return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})"
  441. def __str__(self) -> str:
  442. return (
  443. "["
  444. + ", ".join(
  445. [
  446. str(i) if isinstance(i, ParseResults) else repr(i)
  447. for i in self._toklist
  448. ]
  449. )
  450. + "]"
  451. )
  452. def _asStringList(self, sep=""):
  453. out = []
  454. for item in self._toklist:
  455. if out and sep:
  456. out.append(sep)
  457. if isinstance(item, ParseResults):
  458. out += item._asStringList()
  459. else:
  460. out.append(str(item))
  461. return out
  462. def as_list(self, *, flatten: bool = False) -> list:
  463. """
  464. Returns the parse results as a nested list of matching tokens, all converted to strings.
  465. If ``flatten`` is True, all the nesting levels in the returned list are collapsed.
  466. Example:
  467. .. doctest::
  468. >>> patt = Word(alphas)[1, ...]
  469. >>> result = patt.parse_string("sldkj lsdkj sldkj")
  470. >>> # even though the result prints in string-like form,
  471. >>> # it is actually a pyparsing ParseResults
  472. >>> type(result)
  473. <class 'pyparsing.results.ParseResults'>
  474. >>> print(result)
  475. ['sldkj', 'lsdkj', 'sldkj']
  476. .. doctest::
  477. >>> # Use as_list() to create an actual list
  478. >>> result_list = result.as_list()
  479. >>> type(result_list)
  480. <class 'list'>
  481. >>> print(result_list)
  482. ['sldkj', 'lsdkj', 'sldkj']
  483. .. versionchanged:: 3.2.0
  484. New ``flatten`` argument.
  485. """
  486. def flattened(pr):
  487. to_visit = collections.deque([*self])
  488. while to_visit:
  489. to_do = to_visit.popleft()
  490. if isinstance(to_do, ParseResults):
  491. to_visit.extendleft(to_do[::-1])
  492. else:
  493. yield to_do
  494. if flatten:
  495. return [*flattened(self)]
  496. else:
  497. return [
  498. res.as_list() if isinstance(res, ParseResults) else res
  499. for res in self._toklist
  500. ]
  501. def as_dict(self) -> dict:
  502. """
  503. Returns the named parse results as a nested dictionary.
  504. Example:
  505. .. doctest::
  506. >>> integer = pp.Word(pp.nums)
  507. >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
  508. >>> result = date_str.parse_string('1999/12/31')
  509. >>> type(result)
  510. <class 'pyparsing.results.ParseResults'>
  511. >>> result
  512. ParseResults(['1999', '/', '12', '/', '31'], {'year': '1999', 'month': '12', 'day': '31'})
  513. >>> result_dict = result.as_dict()
  514. >>> type(result_dict)
  515. <class 'dict'>
  516. >>> result_dict
  517. {'year': '1999', 'month': '12', 'day': '31'}
  518. >>> # even though a ParseResults supports dict-like access,
  519. >>> # sometime you just need to have a dict
  520. >>> import json
  521. >>> print(json.dumps(result))
  522. Traceback (most recent call last):
  523. TypeError: Object of type ParseResults is not JSON serializable
  524. >>> print(json.dumps(result.as_dict()))
  525. {"year": "1999", "month": "12", "day": "31"}
  526. """
  527. def to_item(obj):
  528. if isinstance(obj, ParseResults):
  529. return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj]
  530. else:
  531. return obj
  532. return dict((k, to_item(v)) for k, v in self.items())
  533. def copy(self) -> ParseResults:
  534. """
  535. Returns a new shallow copy of a :class:`ParseResults` object.
  536. :class:`ParseResults` items contained within the source are
  537. shared with the copy. Use :meth:`ParseResults.deepcopy` to
  538. create a copy with its own separate content values.
  539. """
  540. ret = ParseResults(self._toklist)
  541. ret._tokdict = self._tokdict.copy()
  542. ret._parent = self._parent
  543. ret._all_names |= self._all_names
  544. ret._name = self._name
  545. return ret
  546. def deepcopy(self) -> ParseResults:
  547. """
  548. Returns a new deep copy of a :class:`ParseResults` object.
  549. .. versionadded:: 3.1.0
  550. """
  551. ret = self.copy()
  552. # replace values with copies if they are of known mutable types
  553. for i, obj in enumerate(self._toklist):
  554. if isinstance(obj, ParseResults):
  555. ret._toklist[i] = obj.deepcopy()
  556. elif isinstance(obj, (str, bytes)):
  557. pass
  558. elif isinstance(obj, MutableMapping):
  559. ret._toklist[i] = dest = type(obj)()
  560. for k, v in obj.items():
  561. dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v
  562. elif isinstance(obj, Iterable):
  563. ret._toklist[i] = type(obj)(
  564. v.deepcopy() if isinstance(v, ParseResults) else v for v in obj # type: ignore[call-arg]
  565. )
  566. return ret
  567. def get_name(self) -> str | None:
  568. r"""
  569. Returns the results name for this token expression.
  570. Useful when several different expressions might match
  571. at a particular location.
  572. Example:
  573. .. testcode::
  574. integer = Word(nums)
  575. ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
  576. house_number_expr = Suppress('#') + Word(nums, alphanums)
  577. user_data = (Group(house_number_expr)("house_number")
  578. | Group(ssn_expr)("ssn")
  579. | Group(integer)("age"))
  580. user_info = user_data[1, ...]
  581. result = user_info.parse_string("22 111-22-3333 #221B")
  582. for item in result:
  583. print(item.get_name(), ':', item[0])
  584. prints:
  585. .. testoutput::
  586. age : 22
  587. ssn : 111-22-3333
  588. house_number : 221B
  589. """
  590. if self._name:
  591. return self._name
  592. elif self._parent:
  593. par: ParseResults = self._parent
  594. parent_tokdict_items = par._tokdict.items()
  595. return next(
  596. (
  597. k
  598. for k, vlist in parent_tokdict_items
  599. for v, loc in vlist
  600. if v is self
  601. ),
  602. None,
  603. )
  604. elif (
  605. len(self) == 1
  606. and len(self._tokdict) == 1
  607. and next(iter(self._tokdict.values()))[0][1] in (0, -1)
  608. ):
  609. return next(iter(self._tokdict.keys()))
  610. else:
  611. return None
  612. def dump(self, indent="", full=True, include_list=True, _depth=0) -> str:
  613. """
  614. Diagnostic method for listing out the contents of
  615. a :class:`ParseResults`. Accepts an optional ``indent`` argument so
  616. that this string can be embedded in a nested display of other data.
  617. Example:
  618. .. testcode::
  619. integer = Word(nums)
  620. date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
  621. result = date_str.parse_string('1999/12/31')
  622. print(result.dump())
  623. prints:
  624. .. testoutput::
  625. ['1999', '/', '12', '/', '31']
  626. - day: '31'
  627. - month: '12'
  628. - year: '1999'
  629. """
  630. out = []
  631. NL = "\n"
  632. out.append(indent + str(self.as_list()) if include_list else "")
  633. if not full:
  634. return "".join(out)
  635. if self.haskeys():
  636. items = sorted((str(k), v) for k, v in self.items())
  637. for k, v in items:
  638. if out:
  639. out.append(NL)
  640. out.append(f"{indent}{(' ' * _depth)}- {k}: ")
  641. if not isinstance(v, ParseResults):
  642. out.append(repr(v))
  643. continue
  644. if not v:
  645. out.append(str(v))
  646. continue
  647. out.append(
  648. v.dump(
  649. indent=indent,
  650. full=full,
  651. include_list=include_list,
  652. _depth=_depth + 1,
  653. )
  654. )
  655. if not any(isinstance(vv, ParseResults) for vv in self):
  656. return "".join(out)
  657. v = self
  658. incr = " "
  659. nl = "\n"
  660. for i, vv in enumerate(v):
  661. if isinstance(vv, ParseResults):
  662. vv_dump = vv.dump(
  663. indent=indent,
  664. full=full,
  665. include_list=include_list,
  666. _depth=_depth + 1,
  667. )
  668. out.append(
  669. f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv_dump}"
  670. )
  671. else:
  672. out.append(
  673. f"{nl}{indent}{incr * _depth}[{i}]:{nl}{indent}{incr * (_depth + 1)}{vv}"
  674. )
  675. return "".join(out)
  676. def pprint(self, *args, **kwargs):
  677. """
  678. Pretty-printer for parsed results as a list, using the
  679. `pprint <https://docs.python.org/3/library/pprint.html>`_ module.
  680. Accepts additional positional or keyword args as defined for
  681. `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ .
  682. Example:
  683. .. testcode::
  684. ident = Word(alphas, alphanums)
  685. num = Word(nums)
  686. func = Forward()
  687. term = ident | num | Group('(' + func + ')')
  688. func <<= ident + Group(Optional(DelimitedList(term)))
  689. result = func.parse_string("fna a,b,(fnb c,d,200),100")
  690. result.pprint(width=40)
  691. prints:
  692. .. testoutput::
  693. ['fna',
  694. ['a',
  695. 'b',
  696. ['(', 'fnb', ['c', 'd', '200'], ')'],
  697. '100']]
  698. """
  699. pprint.pprint(self.as_list(), *args, **kwargs)
  700. # add support for pickle protocol
  701. def __getstate__(self):
  702. return (
  703. self._toklist,
  704. (
  705. self._tokdict.copy(),
  706. None,
  707. self._all_names,
  708. self._name,
  709. ),
  710. )
  711. def __setstate__(self, state):
  712. self._toklist, (self._tokdict, par, inAccumNames, self._name) = state
  713. self._all_names = set(inAccumNames)
  714. self._parent = None
  715. def __getnewargs__(self):
  716. return self._toklist, self._name
  717. def __dir__(self):
  718. return dir(type(self)) + list(self.keys())
  719. @classmethod
  720. def from_dict(cls, other, name=None) -> ParseResults:
  721. """
  722. Helper classmethod to construct a :class:`ParseResults` from a ``dict``, preserving the
  723. name-value relations as results names. If an optional ``name`` argument is
  724. given, a nested :class:`ParseResults` will be returned.
  725. """
  726. def is_iterable(obj):
  727. try:
  728. iter(obj)
  729. except Exception:
  730. return False
  731. # str's are iterable, but in pyparsing, we don't want to iterate over them
  732. else:
  733. return not isinstance(obj, str_type)
  734. ret = cls([])
  735. for k, v in other.items():
  736. if isinstance(v, Mapping):
  737. ret += cls.from_dict(v, name=k)
  738. else:
  739. ret += cls([v], name=k, aslist=is_iterable(v))
  740. if name is not None:
  741. ret = cls([ret], name=name)
  742. return ret
  743. asList = as_list
  744. """
  745. .. deprecated:: 3.0.0
  746. use :meth:`as_list`
  747. """
  748. asDict = as_dict
  749. """
  750. .. deprecated:: 3.0.0
  751. use :meth:`as_dict`
  752. """
  753. getName = get_name
  754. """
  755. .. deprecated:: 3.0.0
  756. use :meth:`get_name`
  757. """
  758. MutableMapping.register(ParseResults)
  759. MutableSequence.register(ParseResults)