results.py 27 KB

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