verbnet.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. # Natural Language Toolkit: Verbnet Corpus Reader
  2. #
  3. # Copyright (C) 2001-2020 NLTK Project
  4. # Author: Edward Loper <edloper@gmail.com>
  5. # URL: <http://nltk.org/>
  6. # For license information, see LICENSE.TXT
  7. """
  8. An NLTK interface to the VerbNet verb lexicon
  9. For details about VerbNet see:
  10. https://verbs.colorado.edu/~mpalmer/projects/verbnet.html
  11. """
  12. import re
  13. import textwrap
  14. from collections import defaultdict
  15. from nltk.corpus.reader.xmldocs import XMLCorpusReader
  16. class VerbnetCorpusReader(XMLCorpusReader):
  17. """
  18. An NLTK interface to the VerbNet verb lexicon.
  19. From the VerbNet site: "VerbNet (VN) (Kipper-Schuler 2006) is the largest
  20. on-line verb lexicon currently available for English. It is a hierarchical
  21. domain-independent, broad-coverage verb lexicon with mappings to other
  22. lexical resources such as WordNet (Miller, 1990; Fellbaum, 1998), XTAG
  23. (XTAG Research Group, 2001), and FrameNet (Baker et al., 1998)."
  24. For details about VerbNet see:
  25. https://verbs.colorado.edu/~mpalmer/projects/verbnet.html
  26. """
  27. # No unicode encoding param, since the data files are all XML.
  28. def __init__(self, root, fileids, wrap_etree=False):
  29. XMLCorpusReader.__init__(self, root, fileids, wrap_etree)
  30. self._lemma_to_class = defaultdict(list)
  31. """A dictionary mapping from verb lemma strings to lists of
  32. VerbNet class identifiers."""
  33. self._wordnet_to_class = defaultdict(list)
  34. """A dictionary mapping from wordnet identifier strings to
  35. lists of VerbNet class identifiers."""
  36. self._class_to_fileid = {}
  37. """A dictionary mapping from class identifiers to
  38. corresponding file identifiers. The keys of this dictionary
  39. provide a complete list of all classes and subclasses."""
  40. self._shortid_to_longid = {}
  41. # Initialize the dictionaries. Use the quick (regexp-based)
  42. # method instead of the slow (xml-based) method, because it
  43. # runs 2-30 times faster.
  44. self._quick_index()
  45. _LONGID_RE = re.compile(r"([^\-\.]*)-([\d+.\-]+)$")
  46. """Regular expression that matches (and decomposes) longids"""
  47. _SHORTID_RE = re.compile(r"[\d+.\-]+$")
  48. """Regular expression that matches shortids"""
  49. _INDEX_RE = re.compile(
  50. r'<MEMBER name="\??([^"]+)" wn="([^"]*)"[^>]+>|' r'<VNSUBCLASS ID="([^"]+)"/?>'
  51. )
  52. """Regular expression used by ``_index()`` to quickly scan the corpus
  53. for basic information."""
  54. def lemmas(self, vnclass=None):
  55. """
  56. Return a list of all verb lemmas that appear in any class, or
  57. in the ``classid`` if specified.
  58. """
  59. if vnclass is None:
  60. return sorted(self._lemma_to_class.keys())
  61. else:
  62. # [xx] should this include subclass members?
  63. if isinstance(vnclass, str):
  64. vnclass = self.vnclass(vnclass)
  65. return [member.get("name") for member in vnclass.findall("MEMBERS/MEMBER")]
  66. def wordnetids(self, vnclass=None):
  67. """
  68. Return a list of all wordnet identifiers that appear in any
  69. class, or in ``classid`` if specified.
  70. """
  71. if vnclass is None:
  72. return sorted(self._wordnet_to_class.keys())
  73. else:
  74. # [xx] should this include subclass members?
  75. if isinstance(vnclass, str):
  76. vnclass = self.vnclass(vnclass)
  77. return sum(
  78. [
  79. member.get("wn", "").split()
  80. for member in vnclass.findall("MEMBERS/MEMBER")
  81. ],
  82. [],
  83. )
  84. def classids(self, lemma=None, wordnetid=None, fileid=None, classid=None):
  85. """
  86. Return a list of the VerbNet class identifiers. If a file
  87. identifier is specified, then return only the VerbNet class
  88. identifiers for classes (and subclasses) defined by that file.
  89. If a lemma is specified, then return only VerbNet class
  90. identifiers for classes that contain that lemma as a member.
  91. If a wordnetid is specified, then return only identifiers for
  92. classes that contain that wordnetid as a member. If a classid
  93. is specified, then return only identifiers for subclasses of
  94. the specified VerbNet class.
  95. If nothing is specified, return all classids within VerbNet
  96. """
  97. if fileid is not None:
  98. return [c for (c, f) in self._class_to_fileid.items() if f == fileid]
  99. elif lemma is not None:
  100. return self._lemma_to_class[lemma]
  101. elif wordnetid is not None:
  102. return self._wordnet_to_class[wordnetid]
  103. elif classid is not None:
  104. xmltree = self.vnclass(classid)
  105. return [
  106. subclass.get("ID")
  107. for subclass in xmltree.findall("SUBCLASSES/VNSUBCLASS")
  108. ]
  109. else:
  110. return sorted(self._class_to_fileid.keys())
  111. def vnclass(self, fileid_or_classid):
  112. """Returns VerbNet class ElementTree
  113. Return an ElementTree containing the xml for the specified
  114. VerbNet class.
  115. :param fileid_or_classid: An identifier specifying which class
  116. should be returned. Can be a file identifier (such as
  117. ``'put-9.1.xml'``), or a VerbNet class identifier (such as
  118. ``'put-9.1'``) or a short VerbNet class identifier (such as
  119. ``'9.1'``).
  120. """
  121. # File identifier: just return the xml.
  122. if fileid_or_classid in self._fileids:
  123. return self.xml(fileid_or_classid)
  124. # Class identifier: get the xml, and find the right elt.
  125. classid = self.longid(fileid_or_classid)
  126. if classid in self._class_to_fileid:
  127. fileid = self._class_to_fileid[self.longid(classid)]
  128. tree = self.xml(fileid)
  129. if classid == tree.get("ID"):
  130. return tree
  131. else:
  132. for subclass in tree.findall(".//VNSUBCLASS"):
  133. if classid == subclass.get("ID"):
  134. return subclass
  135. else:
  136. assert False # we saw it during _index()!
  137. else:
  138. raise ValueError("Unknown identifier {}".format(fileid_or_classid))
  139. def fileids(self, vnclass_ids=None):
  140. """
  141. Return a list of fileids that make up this corpus. If
  142. ``vnclass_ids`` is specified, then return the fileids that make
  143. up the specified VerbNet class(es).
  144. """
  145. if vnclass_ids is None:
  146. return self._fileids
  147. elif isinstance(vnclass_ids, str):
  148. return [self._class_to_fileid[self.longid(vnclass_ids)]]
  149. else:
  150. return [
  151. self._class_to_fileid[self.longid(vnclass_id)]
  152. for vnclass_id in vnclass_ids
  153. ]
  154. def frames(self, vnclass):
  155. """Given a VerbNet class, this method returns VerbNet frames
  156. The members returned are:
  157. 1) Example
  158. 2) Description
  159. 3) Syntax
  160. 4) Semantics
  161. :param vnclass: A VerbNet class identifier; or an ElementTree
  162. containing the xml contents of a VerbNet class.
  163. :return: frames - a list of frame dictionaries
  164. """
  165. if isinstance(vnclass, str):
  166. vnclass = self.vnclass(vnclass)
  167. frames = []
  168. vnframes = vnclass.findall("FRAMES/FRAME")
  169. for vnframe in vnframes:
  170. frames.append(
  171. {
  172. "example": self._get_example_within_frame(vnframe),
  173. "description": self._get_description_within_frame(vnframe),
  174. "syntax": self._get_syntactic_list_within_frame(vnframe),
  175. "semantics": self._get_semantics_within_frame(vnframe),
  176. }
  177. )
  178. return frames
  179. def subclasses(self, vnclass):
  180. """Returns subclass ids, if any exist
  181. Given a VerbNet class, this method returns subclass ids (if they exist)
  182. in a list of strings.
  183. :param vnclass: A VerbNet class identifier; or an ElementTree
  184. containing the xml contents of a VerbNet class.
  185. :return: list of subclasses
  186. """
  187. if isinstance(vnclass, str):
  188. vnclass = self.vnclass(vnclass)
  189. subclasses = [
  190. subclass.get("ID") for subclass in vnclass.findall("SUBCLASSES/VNSUBCLASS")
  191. ]
  192. return subclasses
  193. def themroles(self, vnclass):
  194. """Returns thematic roles participating in a VerbNet class
  195. Members returned as part of roles are-
  196. 1) Type
  197. 2) Modifiers
  198. :param vnclass: A VerbNet class identifier; or an ElementTree
  199. containing the xml contents of a VerbNet class.
  200. :return: themroles: A list of thematic roles in the VerbNet class
  201. """
  202. if isinstance(vnclass, str):
  203. vnclass = self.vnclass(vnclass)
  204. themroles = []
  205. for trole in vnclass.findall("THEMROLES/THEMROLE"):
  206. themroles.append(
  207. {
  208. "type": trole.get("type"),
  209. "modifiers": [
  210. {"value": restr.get("Value"), "type": restr.get("type")}
  211. for restr in trole.findall("SELRESTRS/SELRESTR")
  212. ],
  213. }
  214. )
  215. return themroles
  216. ######################################################################
  217. # { Index Initialization
  218. ######################################################################
  219. def _index(self):
  220. """
  221. Initialize the indexes ``_lemma_to_class``,
  222. ``_wordnet_to_class``, and ``_class_to_fileid`` by scanning
  223. through the corpus fileids. This is fast if ElementTree
  224. uses the C implementation (<0.1 secs), but quite slow (>10 secs)
  225. if only the python implementation is available.
  226. """
  227. for fileid in self._fileids:
  228. self._index_helper(self.xml(fileid), fileid)
  229. def _index_helper(self, xmltree, fileid):
  230. """Helper for ``_index()``"""
  231. vnclass = xmltree.get("ID")
  232. self._class_to_fileid[vnclass] = fileid
  233. self._shortid_to_longid[self.shortid(vnclass)] = vnclass
  234. for member in xmltree.findall("MEMBERS/MEMBER"):
  235. self._lemma_to_class[member.get("name")].append(vnclass)
  236. for wn in member.get("wn", "").split():
  237. self._wordnet_to_class[wn].append(vnclass)
  238. for subclass in xmltree.findall("SUBCLASSES/VNSUBCLASS"):
  239. self._index_helper(subclass, fileid)
  240. def _quick_index(self):
  241. """
  242. Initialize the indexes ``_lemma_to_class``,
  243. ``_wordnet_to_class``, and ``_class_to_fileid`` by scanning
  244. through the corpus fileids. This doesn't do proper xml parsing,
  245. but is good enough to find everything in the standard VerbNet
  246. corpus -- and it runs about 30 times faster than xml parsing
  247. (with the python ElementTree; only 2-3 times faster
  248. if ElementTree uses the C implementation).
  249. """
  250. # nb: if we got rid of wordnet_to_class, this would run 2-3
  251. # times faster.
  252. for fileid in self._fileids:
  253. vnclass = fileid[:-4] # strip the '.xml'
  254. self._class_to_fileid[vnclass] = fileid
  255. self._shortid_to_longid[self.shortid(vnclass)] = vnclass
  256. for m in self._INDEX_RE.finditer(self.open(fileid).read()):
  257. groups = m.groups()
  258. if groups[0] is not None:
  259. self._lemma_to_class[groups[0]].append(vnclass)
  260. for wn in groups[1].split():
  261. self._wordnet_to_class[wn].append(vnclass)
  262. elif groups[2] is not None:
  263. self._class_to_fileid[groups[2]] = fileid
  264. vnclass = groups[2] # for <MEMBER> elts.
  265. self._shortid_to_longid[self.shortid(vnclass)] = vnclass
  266. else:
  267. assert False, "unexpected match condition"
  268. ######################################################################
  269. # { Identifier conversion
  270. ######################################################################
  271. def longid(self, shortid):
  272. """Returns longid of a VerbNet class
  273. Given a short VerbNet class identifier (eg '37.10'), map it
  274. to a long id (eg 'confess-37.10'). If ``shortid`` is already a
  275. long id, then return it as-is"""
  276. if self._LONGID_RE.match(shortid):
  277. return shortid # it's already a longid.
  278. elif not self._SHORTID_RE.match(shortid):
  279. raise ValueError("vnclass identifier %r not found" % shortid)
  280. try:
  281. return self._shortid_to_longid[shortid]
  282. except KeyError:
  283. raise ValueError("vnclass identifier %r not found" % shortid)
  284. def shortid(self, longid):
  285. """Returns shortid of a VerbNet class
  286. Given a long VerbNet class identifier (eg 'confess-37.10'),
  287. map it to a short id (eg '37.10'). If ``longid`` is already a
  288. short id, then return it as-is."""
  289. if self._SHORTID_RE.match(longid):
  290. return longid # it's already a shortid.
  291. m = self._LONGID_RE.match(longid)
  292. if m:
  293. return m.group(2)
  294. else:
  295. raise ValueError("vnclass identifier %r not found" % longid)
  296. ######################################################################
  297. # { Frame access utility functions
  298. ######################################################################
  299. def _get_semantics_within_frame(self, vnframe):
  300. """Returns semantics within a single frame
  301. A utility function to retrieve semantics within a frame in VerbNet
  302. Members of the semantics dictionary:
  303. 1) Predicate value
  304. 2) Arguments
  305. :param vnframe: An ElementTree containing the xml contents of
  306. a VerbNet frame.
  307. :return: semantics: semantics dictionary
  308. """
  309. semantics_within_single_frame = []
  310. for pred in vnframe.findall("SEMANTICS/PRED"):
  311. arguments = [
  312. {"type": arg.get("type"), "value": arg.get("value")}
  313. for arg in pred.findall("ARGS/ARG")
  314. ]
  315. semantics_within_single_frame.append(
  316. {"predicate_value": pred.get("value"), "arguments": arguments}
  317. )
  318. return semantics_within_single_frame
  319. def _get_example_within_frame(self, vnframe):
  320. """Returns example within a frame
  321. A utility function to retrieve an example within a frame in VerbNet.
  322. :param vnframe: An ElementTree containing the xml contents of
  323. a VerbNet frame.
  324. :return: example_text: The example sentence for this particular frame
  325. """
  326. example_element = vnframe.find("EXAMPLES/EXAMPLE")
  327. if example_element is not None:
  328. example_text = example_element.text
  329. else:
  330. example_text = ""
  331. return example_text
  332. def _get_description_within_frame(self, vnframe):
  333. """Returns member description within frame
  334. A utility function to retrieve a description of participating members
  335. within a frame in VerbNet.
  336. :param vnframe: An ElementTree containing the xml contents of
  337. a VerbNet frame.
  338. :return: description: a description dictionary with members - primary and secondary
  339. """
  340. description_element = vnframe.find("DESCRIPTION")
  341. return {
  342. "primary": description_element.attrib["primary"],
  343. "secondary": description_element.get("secondary", ""),
  344. }
  345. def _get_syntactic_list_within_frame(self, vnframe):
  346. """Returns semantics within a frame
  347. A utility function to retrieve semantics within a frame in VerbNet.
  348. Members of the syntactic dictionary:
  349. 1) POS Tag
  350. 2) Modifiers
  351. :param vnframe: An ElementTree containing the xml contents of
  352. a VerbNet frame.
  353. :return: syntax_within_single_frame
  354. """
  355. syntax_within_single_frame = []
  356. for elt in vnframe.find("SYNTAX"):
  357. pos_tag = elt.tag
  358. modifiers = dict()
  359. modifiers["value"] = elt.get("value") if "value" in elt.attrib else ""
  360. modifiers["selrestrs"] = [
  361. {"value": restr.get("Value"), "type": restr.get("type")}
  362. for restr in elt.findall("SELRESTRS/SELRESTR")
  363. ]
  364. modifiers["synrestrs"] = [
  365. {"value": restr.get("Value"), "type": restr.get("type")}
  366. for restr in elt.findall("SYNRESTRS/SYNRESTR")
  367. ]
  368. syntax_within_single_frame.append(
  369. {"pos_tag": pos_tag, "modifiers": modifiers}
  370. )
  371. return syntax_within_single_frame
  372. ######################################################################
  373. # { Pretty Printing
  374. ######################################################################
  375. def pprint(self, vnclass):
  376. """Returns pretty printed version of a VerbNet class
  377. Return a string containing a pretty-printed representation of
  378. the given VerbNet class.
  379. :param vnclass: A VerbNet class identifier; or an ElementTree
  380. containing the xml contents of a VerbNet class.
  381. """
  382. if isinstance(vnclass, str):
  383. vnclass = self.vnclass(vnclass)
  384. s = vnclass.get("ID") + "\n"
  385. s += self.pprint_subclasses(vnclass, indent=" ") + "\n"
  386. s += self.pprint_members(vnclass, indent=" ") + "\n"
  387. s += " Thematic roles:\n"
  388. s += self.pprint_themroles(vnclass, indent=" ") + "\n"
  389. s += " Frames:\n"
  390. s += self.pprint_frames(vnclass, indent=" ")
  391. return s
  392. def pprint_subclasses(self, vnclass, indent=""):
  393. """Returns pretty printed version of subclasses of VerbNet class
  394. Return a string containing a pretty-printed representation of
  395. the given VerbNet class's subclasses.
  396. :param vnclass: A VerbNet class identifier; or an ElementTree
  397. containing the xml contents of a VerbNet class.
  398. """
  399. if isinstance(vnclass, str):
  400. vnclass = self.vnclass(vnclass)
  401. subclasses = self.subclasses(vnclass)
  402. if not subclasses:
  403. subclasses = ["(none)"]
  404. s = "Subclasses: " + " ".join(subclasses)
  405. return textwrap.fill(
  406. s, 70, initial_indent=indent, subsequent_indent=indent + " "
  407. )
  408. def pprint_members(self, vnclass, indent=""):
  409. """Returns pretty printed version of members in a VerbNet class
  410. Return a string containing a pretty-printed representation of
  411. the given VerbNet class's member verbs.
  412. :param vnclass: A VerbNet class identifier; or an ElementTree
  413. containing the xml contents of a VerbNet class.
  414. """
  415. if isinstance(vnclass, str):
  416. vnclass = self.vnclass(vnclass)
  417. members = self.lemmas(vnclass)
  418. if not members:
  419. members = ["(none)"]
  420. s = "Members: " + " ".join(members)
  421. return textwrap.fill(
  422. s, 70, initial_indent=indent, subsequent_indent=indent + " "
  423. )
  424. def pprint_themroles(self, vnclass, indent=""):
  425. """Returns pretty printed version of thematic roles in a VerbNet class
  426. Return a string containing a pretty-printed representation of
  427. the given VerbNet class's thematic roles.
  428. :param vnclass: A VerbNet class identifier; or an ElementTree
  429. containing the xml contents of a VerbNet class.
  430. """
  431. if isinstance(vnclass, str):
  432. vnclass = self.vnclass(vnclass)
  433. pieces = []
  434. for themrole in self.themroles(vnclass):
  435. piece = indent + "* " + themrole.get("type")
  436. modifiers = [
  437. modifier["value"] + modifier["type"]
  438. for modifier in themrole["modifiers"]
  439. ]
  440. if modifiers:
  441. piece += "[{}]".format(" ".join(modifiers))
  442. pieces.append(piece)
  443. return "\n".join(pieces)
  444. def pprint_frames(self, vnclass, indent=""):
  445. """Returns pretty version of all frames in a VerbNet class
  446. Return a string containing a pretty-printed representation of
  447. the list of frames within the VerbNet class.
  448. :param vnclass: A VerbNet class identifier; or an ElementTree
  449. containing the xml contents of a VerbNet class.
  450. """
  451. if isinstance(vnclass, str):
  452. vnclass = self.vnclass(vnclass)
  453. pieces = []
  454. for vnframe in self.frames(vnclass):
  455. pieces.append(self._pprint_single_frame(vnframe, indent))
  456. return "\n".join(pieces)
  457. def _pprint_single_frame(self, vnframe, indent=""):
  458. """Returns pretty printed version of a single frame in a VerbNet class
  459. Returns a string containing a pretty-printed representation of
  460. the given frame.
  461. :param vnframe: An ElementTree containing the xml contents of
  462. a VerbNet frame.
  463. """
  464. frame_string = self._pprint_description_within_frame(vnframe, indent) + "\n"
  465. frame_string += self._pprint_example_within_frame(vnframe, indent + " ") + "\n"
  466. frame_string += (
  467. self._pprint_syntax_within_frame(vnframe, indent + " Syntax: ") + "\n"
  468. )
  469. frame_string += indent + " Semantics:\n"
  470. frame_string += self._pprint_semantics_within_frame(vnframe, indent + " ")
  471. return frame_string
  472. def _pprint_example_within_frame(self, vnframe, indent=""):
  473. """Returns pretty printed version of example within frame in a VerbNet class
  474. Return a string containing a pretty-printed representation of
  475. the given VerbNet frame example.
  476. :param vnframe: An ElementTree containing the xml contents of
  477. a Verbnet frame.
  478. """
  479. if vnframe["example"]:
  480. return indent + " Example: " + vnframe["example"]
  481. def _pprint_description_within_frame(self, vnframe, indent=""):
  482. """Returns pretty printed version of a VerbNet frame description
  483. Return a string containing a pretty-printed representation of
  484. the given VerbNet frame description.
  485. :param vnframe: An ElementTree containing the xml contents of
  486. a VerbNet frame.
  487. """
  488. description = indent + vnframe["description"]["primary"]
  489. if vnframe["description"]["secondary"]:
  490. description += " ({})".format(vnframe["description"]["secondary"])
  491. return description
  492. def _pprint_syntax_within_frame(self, vnframe, indent=""):
  493. """Returns pretty printed version of syntax within a frame in a VerbNet class
  494. Return a string containing a pretty-printed representation of
  495. the given VerbNet frame syntax.
  496. :param vnframe: An ElementTree containing the xml contents of
  497. a VerbNet frame.
  498. """
  499. pieces = []
  500. for element in vnframe["syntax"]:
  501. piece = element["pos_tag"]
  502. modifier_list = []
  503. if "value" in element["modifiers"] and element["modifiers"]["value"]:
  504. modifier_list.append(element["modifiers"]["value"])
  505. modifier_list += [
  506. "{}{}".format(restr["value"], restr["type"])
  507. for restr in (
  508. element["modifiers"]["selrestrs"]
  509. + element["modifiers"]["synrestrs"]
  510. )
  511. ]
  512. if modifier_list:
  513. piece += "[{}]".format(" ".join(modifier_list))
  514. pieces.append(piece)
  515. return indent + " ".join(pieces)
  516. def _pprint_semantics_within_frame(self, vnframe, indent=""):
  517. """Returns a pretty printed version of semantics within frame in a VerbNet class
  518. Return a string containing a pretty-printed representation of
  519. the given VerbNet frame semantics.
  520. :param vnframe: An ElementTree containing the xml contents of
  521. a VerbNet frame.
  522. """
  523. pieces = []
  524. for predicate in vnframe["semantics"]:
  525. arguments = [argument["value"] for argument in predicate["arguments"]]
  526. pieces.append(
  527. "{}({})".format(predicate["predicate_value"], ", ".join(arguments))
  528. )
  529. return "\n".join("{}* {}".format(indent, piece) for piece in pieces)