class Tree:
    """
    A tree structure.

    A tree is essentially a node that contains other nodes, but this
    Tree class is useful to contain any metadata and to provide
    tree-based methods.
    """

    __slots__ = 'node', 'metadata'

    def __init__(self,
                 node: Node,
                 metadata: Mapping[str, str] = None):
        self.node = node
        self.metadata = metadata or {}

    def __eq__(self, other) -> bool:
        if isinstance(other, Tree):
            other = other.node
        return self.node == other

    def __repr__(self) -> str:
        return f'Tree({self.node!r})'

    def __str__(self) -> str:
        s = _format(self.node, 2)
        return f'Tree(\n  {s})'

    def nodes(self) -> List[Node]:
        """
        Return the nodes in the tree as a flat list.
        """
        return _nodes(self.node)

    def walk(self) -> Iterator[_Step]:
        """
        Iterate over branches in the tree.

        This function yields pairs of (*path*, *branch*) where each
        *path* is a tuple of 0-based indices of branches to get to
        *branch*. For example, the path (2, 0) is the concept branch
        `('/', 'bark-01')` in the tree for the following PENMAN
        string, traversing first to the third (index 2) branch of the
        top node, then to the first (index 0) branch of that node::

            (t / try-01
               :ARG0 (d / dog)
               :ARG1 (b / bark-01
                        :ARG0 d))

        The (*path*, *branch*) pairs are yielded in depth-first order
        of the tree traversal.
        """
        yield from _walk(self.node, ())

    def reset_variables(self, fmt='{prefix}{j}') -> None:
        """
        Recreate node variables formatted using *fmt*.

        The *fmt* string can be formatted with the following values:

        - ``prefix``: first alphabetic character in the node's concept
        - ``i``: 0-based index of the current occurrence of the prefix
        - ``j``: 1-based index starting from the second occurrence
        """
        varmap: Dict[Variable, Variable] = {}
        used: Set[Variable] = set()
        for var, branches in self.nodes():
            if var not in varmap:
                concept = next((tgt for role, tgt in branches if role == '/'),
                               None)
                pre = _default_variable_prefix(concept)
                i = 0
                newvar = None
                while newvar is None or newvar in used:
                    newvar = fmt.format(
                        prefix=pre,
                        i=i,
                        j='' if i == 0 else i + 1)
                    i += 1
                used.add(newvar)
                varmap[var] = newvar

        self.node = _map_vars(self.node, varmap) class Graph(object):
    """
    A basic class for modeling a rooted, directed acyclic graph.

    A Graph is defined by a list of triples, which can be divided into
    two parts: a list of graph edges where both the source and target
    are variables (node identifiers), and a list of node attributes
    where only the source is a variable and the target is a
    constant. The raw triples are available via the :attr:`triples`
    attribute, while the :meth:`instances`, :meth:`edges` and
    :meth:`attributes` methods return only those that are concept
    relations, relations between nodes, or relations between a node
    and a constant, respectively.

    Args:
        triples: an iterable of triples (:class:`Triple` or 3-tuples)
        top: the variable of the top node; if unspecified, the source
            of the first triple is used
        epidata: a mapping of triples to epigraphical markers
        metadata: a mapping of metadata types to descriptions
    Example:
        >>> from penman.graph import Graph
        >>> Graph([('b', ':instance', 'bark-01'),
        ...        ('d', ':instance', 'dog'),
        ...        ('b', ':ARG0', 'd')])
        <Graph object (top=b) at ...>
    """

    def __init__(self,
                 triples: Triples = None,
                 top: Variable = None,
                 epidata: Mapping[BasicTriple, Epidata] = None,
                 metadata: Mapping[str, str] = None):
        if not triples:
            triples = []
        if not epidata:
            epidata = {}
        if not metadata:
            metadata = {}

        # the following (a) creates a new list (b) validates that
        # they are triples, and (c) ensures roles begin with :
        self.triples = [(src, _ensure_colon(role), tgt)
                        for src, role, tgt in triples]
        self._top = top
        self.epidata = dict(epidata)
        self.metadata = dict(metadata)

    def __repr__(self):
        name = self.__class__.__name__
        return f'<{name} object (top={self.top}) at {id(self)}>'

    def __str__(self):
        triples = '[{}]'.format(',\n   '.join(map(repr, self.triples)))
        epidata = '{{{}}}'.format(',\n    '.join(
            map('{0[0]!r}: {0[1]!r}'.format, self.epidata.items())))
        return f'Graph(\n  {triples},\n  epidata={epidata})'

    def __eq__(self, other):
        if not isinstance(other, Graph):
            return NotImplemented
        return (self.top == other.top
                and len(self.triples) == len(other.triples)
                and set(self.triples) == set(other.triples))

    def __or__(self, other):
        if isinstance(other, Graph):
            g = copy.deepcopy(self)
            g.metadata.clear()
            g |= other
            return g
        else:
            return NotImplemented

    def __ior__(self, other):
        if isinstance(other, Graph):
            new = set(other.triples) - set(self.triples)
            self.triples.extend(t for t in other.triples if t in new)
            for t in new:
                if t in other.epidata:
                    self.epidata[t] = list(other.epidata[t])
            self.epidata.update(other.epidata)
            return self
        else:
            return NotImplemented

    def __sub__(self, other):
        if isinstance(other, Graph):
            g = copy.deepcopy(self)
            g.metadata.clear()
            g -= other
            return g
        else:
            return NotImplemented

    def __isub__(self, other):
        if isinstance(other, Graph):
            removed = set(other.triples)
            self.triples[:] = [t for t in self.triples if t not in removed]
            for t in removed:
                if t in self.epidata:
                    del self.epidata[t]
            possible_variables = set(v for t in self.triples for v in t[::2])
            if self._top not in possible_variables:
                self._top = None
            return self
        else:
            return NotImplemented

    @property
    def top(self) -> Union[Variable, None]:
        """
        The top variable.
        """
        top = self._top
        if top is None and len(self.triples) > 0:
            top = self.triples[0][0]  # implicit top
        return top

    @top.setter
    def top(self, top: Union[Variable, None]):
        if top is not None and top not in self.variables():
            raise GraphError('top must be a valid node')
        self._top = top  # check if top is valid variable?

    def variables(self) -> Set[Variable]:
        """
        Return the set of variables (nonterminal node identifiers).
        """
        vs = set(src for src, _, _ in self.triples)
        if self._top is not None:
            vs.add(self._top)
        return vs

    def instances(self) -> List[Instance]:
        """
        Return instances (concept triples).
        """
        return [Instance(*t)
                for t in self._filter_triples(None, CONCEPT_ROLE, None)]

    def edges(self,
              source: Optional[Variable] = None,
              role: Role = None,
              target: Variable = None) -> List[Edge]:
        """
        Return edges filtered by their *source*, *role*, or *target*.

        Edges don't include terminal triples (concepts or attributes).
        """
        variables = self.variables()
        return [Edge(*t)
                for t in self._filter_triples(source, role, target)
                if t[1] != CONCEPT_ROLE and t[2] in variables]

    def attributes(self,
                   source: Optional[Variable] = None,
                   role: Role = None,
                   target: Constant = None) -> List[Attribute]:
        """
        Return attributes filtered by their *source*, *role*, or *target*.

        Attributes don't include concept triples or those where the
        target is a nonterminal.
        """
        variables = self.variables()
        return [Attribute(*t)
                for t in self._filter_triples(source, role, target)
                if t[1] != CONCEPT_ROLE and t[2] not in variables]

    def _filter_triples(self,
                        source: Optional[Variable],
                        role: Optional[Role],
                        target: Optional[Target]) -> List[BasicTriple]:
        """
        Filter triples based on their source, role, and/or target.
        """
        if source is role is target is None:
            triples = list(self.triples)
        else:
            triples = [
                t for t in self.triples
                if ((source is None or source == t[0])
                    and (role is None or role == t[1])
                    and (target is None or target == t[2]))
            ]
        return triples

    def reentrancies(self) -> Dict[Variable, int]:
        """
        Return a mapping of variables to their re-entrancy count.

        A re-entrancy is when more than one edge selects a node as its
        target. These graphs are rooted, so the top node always has an
        implicit entrancy. Only nodes with re-entrancies are reported,
        and the count is only for the entrant edges beyond the first.
        Also note that these counts are for the interpreted graph, not
        for the linearized form, so inverted edges are always
        re-entrant.
        """
        entrancies: Dict[Variable, int] = defaultdict(int)
        if self.top is not None:
            entrancies[self.top] += 1  # implicit entrancy to top
        for t in self.edges():
            entrancies[t.target] += 1
        return dict((v, cnt - 1) for v, cnt in entrancies.items() if cnt >= 2) class Model(object):
    """
    A semantic model for Penman graphs.

    The model defines things like valid roles and transformations.

    Args:
        top_variable: the variable of the graph's top
        top_role: the role linking the graph's top to the top node
        concept_role: the role associated with node concepts
        roles: a mapping of roles to associated data
        normalizations: a mapping of roles to normalized roles
        reifications: a list of 4-tuples used to define reifications
    """
    def __init__(self,
                 top_variable: Variable = 'top',
                 top_role: Role = ':TOP',
                 concept_role: Role = CONCEPT_ROLE,
                 roles: Mapping[Role, Any] = None,
                 normalizations: Mapping[Role, Role] = None,
                 reifications: Iterable[_ReificationSpec] = None):
        self.top_variable = top_variable
        self.top_role = top_role
        self.concept_role = concept_role

        if roles:
            roles = dict(roles)
        self.roles = roles or {}
        self._role_re = re.compile(
            '^({})$'.format(
                '|'.join(list(self.roles) + [top_role, concept_role])))

        if normalizations:
            normalizations = dict(normalizations)
        self.normalizations = normalizations or {}

        reifs: Dict[Role, List[_Reified]] = defaultdict(list)
        deifs: Dict[Constant, List[_Dereified]] = defaultdict(list)
        if reifications:
            for role, concept, source, target in reifications:
                reifs[role].append((concept, source, target))
                deifs[concept].append((role, source, target))
        self.reifications = dict(reifs)
        self.dereifications = dict(deifs)

    def __eq__(self, other):
        if not isinstance(other, Model):
            return NotImplemented
        return (self.top_variable == other.top_variable
                and self.top_role == other.top_role
                and self.concept_role == other.concept_role
                and self.roles == other.roles
                and self.normalizations == other.normalizations
                and self.reifications == other.reifications)

    @classmethod
    def from_dict(cls, d):
        """Instantiate a model from a dictionary."""
        return cls(**d)

    def has_role(self, role: Role) -> bool:
        """
        Return ``True`` if *role* is defined by the model.

        If *role* is not in the model but a single deinversion of
        *role* is in the model, then ``True`` is returned. Otherwise
        ``False`` is returned, even if something like
        :meth:`canonicalize_role` could return a valid role.
        """
        return (self._has_role(role)
                or (role.endswith('-of') and self._has_role(role[:-3])))

    def _has_role(self, role: Role) -> bool:
        return self._role_re.match(role) is not None

    def is_role_inverted(self, role: Role) -> bool:
        """Return ``True`` if *role* is inverted."""
        return not self._has_role(role) and role.endswith('-of')

    def invert_role(self, role: Role) -> Role:
        """Invert *role*."""
        if not self._has_role(role) and role.endswith('-of'):
            inverse = role[:-3]
        else:
            inverse = role + '-of'
        return inverse

    def invert(self, triple: BasicTriple) -> BasicTriple:
        """
        Invert *triple*.

        This will invert or deinvert a triple regardless of its
        current state. :meth:`deinvert` will deinvert a triple only if
        it is already inverted. Unlike :meth:`canonicalize`, this will
        not perform multiple inversions or replace the role with a
        normalized form.
        """
        source, role, target = triple
        inverse = self.invert_role(role)
        # casting is just for the benefit of the type checker; it does
        # not actually check that target is a valid variable type
        target = cast(Variable, target)
        return (target, inverse, source)

    def deinvert(self, triple: BasicTriple) -> BasicTriple:
        """
        De-invert *triple* if it is inverted.

        Unlike :meth:`invert`, this only inverts a triple if the model
        considers it to be already inverted, otherwise it is left
        alone. Unlike :meth:`canonicalize`, this will not normalize
        multiple inversions or replace the role with a normalized
        form.
        """
        if self.is_role_inverted(triple[1]):
            triple = self.invert(triple)
        return triple

    def canonicalize_role(self, role: Role) -> Role:
        """
        Canonicalize *role*.

        Role canonicalization will do the following:

        * Ensure the role starts with `':'`

        * Normalize multiple inversions (e.g., ``ARG0-of-of`` becomes
          ``ARG0``), but it does *not* change the direction of the role

        * Replace the resulting role with a normalized form if one is
          defined in the model
        """
        if role != '/' and not role.startswith(':'):
            role = ':' + role
        role = self._canonicalize_inversion(role)
        role = self.normalizations.get(role, role)
        return role

    def _canonicalize_inversion(self, role: Role) -> Role:
        invert = self.invert_role
        if not self._has_role(role):
            while True:
                prev = role
                inverse = invert(role)
                role = invert(inverse)
                if prev == role:
                    break
        return role

    def canonicalize(self, triple: BasicTriple) -> BasicTriple:
        """
        Canonicalize *triple*.

        See :meth:`canonicalize_role` for a description of how the
        role is canonicalized. Unlike :meth:`invert`, this does not
        swap the source and target of *triple*.
        """
        source, role, target = triple
        canonical = self.canonicalize_role(role)
        return (source, canonical, target)

    def is_role_reifiable(self, role: Role) -> bool:
        """Return ``True`` if *role* can be reified."""
        return role in self.reifications

    def reify(self,
              triple: BasicTriple,
              variables: Set[Variable] = None) -> _Reification:
        """
        Return the three triples that reify *triple*.

        Note that, unless *variables* is given, the node variable
        for the reified node is not necessarily valid for the target
        graph. When incorporating the reified triples, this variable
        should then be replaced.

        If the role of *triple* does not have a defined reification, a
        :exc:`~penman.exceptions.ModelError` is raised.

        Args:
            triple: the triple to reify
            variables: a set of variables that should not be used for
                the reified node's variable
        Returns:
            The 3-tuple of triples that reify *triple*.
        """
        source, role, target = triple
        if role not in self.reifications:
            raise ModelError(f"'{role}' cannot be reified")
        concept, source_role, target_role = next(iter(self.reifications[role]))

        var = '_'
        if variables:
            i = 2
            while var in variables:
                var = f'_{i}'
                i += 1

        return ((var, source_role, source),
                (var, CONCEPT_ROLE, concept),
                (var, target_role, target))

    def is_concept_dereifiable(self, concept: Target) -> bool:
        """Return ``True`` if *concept* can be dereified."""
        return concept in self.dereifications

    def dereify(self,
                instance_triple: BasicTriple,
                source_triple: BasicTriple,
                target_triple: BasicTriple) -> BasicTriple:
        """
        Return the triple that dereifies the three argument triples.

        If the target of *instance_triple* does not have a defined
        dereification, or if the roles of *source_triple* and
        *target_triple* do not match those for the dereification of
        the concept, a :exc:`~penman.exceptions.ModelError` is
        raised. A :exc:`ValueError` is raised if *instance_triple* is
        not an instance triple or any triple does not have the same
        source variable as the others.

        Args:
            instance_triple: the triple containing the node's concept
            source_triple: the source triple from the node
            target_triple: the target triple from the node
        Returns:
            The triple that dereifies the three argument triples.
        """
        if instance_triple[1] != CONCEPT_ROLE:
            raise ValueError('second argument is not an instance triple')
        if not (instance_triple[0] == source_triple[0] == target_triple[0]):
            raise ValueError('triples do not share the same source')

        concept = instance_triple[2]
        source_role = source_triple[1]
        target_role = target_triple[1]

        if concept not in self.dereifications:
            raise ModelError(f"{concept!r} cannot be dereified")
        for role, source, target in self.dereifications[concept]:
            if source == source_role and target == target_role:
                return (cast(Variable, source_triple[2]),
                        role,
                        target_triple[2])
            elif target == source_role and source == target_role:
                return (cast(Variable, target_triple[2]),
                        role,
                        source_triple[2])

        raise ModelError(f'{source_role!r} and {target_role!r} '
                         f'are not valid roles to dereify {concept!r}')

    def original_order(self, role: Role):
        """Role sorting key that does not change the order."""
        return True

    def alphanumeric_order(self, role: Role):
        """Role sorting key for alphanumeric order."""
        m = re.match(r'(.*\D)(\d+)$', role)
        if m:
            rolename = m.group(1)
            roleno = int(m.group(2))
        else:
            rolename, roleno = role, 0
        return rolename, roleno

    def canonical_order(self, role: Role):
        """Role sorting key that finds a canonical order."""
        return (self.is_role_inverted(role), self.alphanumeric_order(role))

    def random_order(self, role: Role):
        """Role sorting key that randomizes the order."""
        return random.random()

    def errors(self, graph: Graph) -> Dict[Optional[BasicTriple], List[str]]:
        """
        Return a description of model errors detected in *graph*.

        The description is a dictionary mapping a context to a list of
        errors. A context is a triple if the error is relevant for the
        triple, or ``None`` for general graph errors.

        Example:

            >>> from penman.models.amr import model
            >>> from penman.graph import Graph
            >>> g = Graph([('a', ':instance', 'alpha'),
            ...            ('a', ':foo', 'bar'),
            ...            ('b', ':instance', 'beta')])
            >>> for context, errors in model.errors(g).items():
            ...     print(context, errors)
            ...
            ('a', ':foo', 'bar') ['invalid role']
            ('b', ':instance', 'beta') ['unreachable']
        """
        err: Dict[Optional[BasicTriple], List[str]] = defaultdict(list)
        if len(graph.triples) == 0:
            err[None].append('graph is empty')
        else:
            g: Dict[Variable, List[BasicTriple]] = {}
            for triple in graph.triples:
                var, role, tgt = triple
                if not self.has_role(role):
                    err[triple].append('invalid role')
                if var not in g:
                    g[var] = []
                g[var].append(triple)
            if not graph.top:
                err[None].append('top is not set')
            elif graph.top not in g:
                err[None].append('top is not a variable in the graph')
            else:
                reachable = _dfs(g, graph.top)
                unreachable = set(g).difference(reachable)
                for uvar in sorted(unreachable):
                    for triple in g[uvar]:
                        err[triple].append('unreachable')
        return dict(err) def parse(s: str) -> Tree:
    """
    Parse PENMAN-notation string *s* into its tree structure.

    Args:
        s: a string containing a single PENMAN-serialized graph
    Returns:
        The tree structure described by *s*.
    Example:
        >>> import penman
        >>> penman.parse('(b / bark-01 :ARG0 (d / dog))')  # noqa
        Tree(('b', [('/', 'bark-01'), (':ARG0', ('d', [('/', 'dog')]))]))

    """
    tokens = lex(s, pattern=PENMAN_RE)
    return _parse(tokens) def iterparse(lines: Union[Iterable[str], str]) -> Iterator[Tree]:
    """
    Yield trees parsed from *lines*.

    Args:
        lines: a string or open file with PENMAN-serialized graphs
    Returns:
        The :class:`~penman.tree.Tree` object described in *lines*.
    Example:
        >>> import penman
        >>> for t in penman.iterparse('(a / alpha) (b / beta)'):
        ...     print(repr(t))
        ...
        Tree(('a', [('/', 'alpha')]))
        Tree(('b', [('/', 'beta')]))

    """
    tokens = lex(lines, pattern=PENMAN_RE)
    while tokens and tokens.peek().type in ('COMMENT', 'LPAREN'):
        yield _parse(tokens) def parse_triples(s: str) -> List[BasicTriple]:
    """
    Parse a triple conjunction from *s*.

    Example:
        >>> import penman
        >>> for triple in penman.parse_triples('''
        ...         instance(b, bark) ^
        ...         ARG0(b, d) ^
        ...         instance(d, dog)'''):
        ...     print(triple)
        ('b', ':instance', 'bark')
        ('b', ':ARG0', 'd')
        ('d', ':instance', 'dog')

        """
    tokens = lex(s, pattern=TRIPLE_RE)
    return _parse_triples(tokens) def format(tree: Tree,
           indent: Union[int, None] = -1,
           compact: bool = False) -> str:
    """
    Format *tree* into a PENMAN string.

    Args:
        tree: a Tree object
        indent: how to indent formatted strings
        compact: if ``True``, put initial attributes on the first line
    Returns:
        the PENMAN-serialized string of the Tree *t*
    Example:
        >>> import penman
        >>> print(penman.format(
        ...     ('b', [('/', 'bark-01'),
        ...            (':ARG0', ('d', [('/', 'dog')]))])))
        (b / bark-01
           :ARG0 (d / dog))
    """
    if not isinstance(tree, Tree):
        tree = Tree(tree)
    vars = [var for var, _ in tree.nodes()] if compact else []
    parts = ['# ::{}{}'.format(key, ' ' + value if value else value)
             for key, value in tree.metadata.items()]
    parts.append(_format_node(tree.node, indent, 0, set(vars)))
    return '\n'.join(parts) def format_triples(triples: Iterable[BasicTriple], indent: bool = True) -> str:
    """
    Return the formatted triple conjunction of *triples*.

    Args:
        triples: an iterable of triples
        indent: how to indent formatted strings
    Returns:
        the serialized triple conjunction of *triples*
    Example:
        >>> import penman
        >>> g = penman.decode('(b / bark-01 :ARG0 (d / dog))')
        >>> print(penman.format_triples(g.triples))
        instance(b, bark-01) ^
        ARG0(b, d) ^
        instance(d, dog)

    """
    delim = ' ^\n' if indent else ' ^ '
    # need to remove initial : on roles for triples
    conjunction = [f'{role.lstrip(":")}({source}, {target})'
                   for source, role, target in triples]
    return delim.join(conjunction) class LayoutMarker(Epidatum):
class Push(LayoutMarker):
class Pop(LayoutMarker):
    def __init__(self, variable):
    def __repr__(self):
    def __repr__(self):
def interpret(t: Tree, model: Model = None) -> Graph:
def _interpret_node(t: Node, variables: Set[Variable], model: Model):
def _process_role(role):
def _process_atomic(target):
def configure(g: Graph,
              top: Variable = None,
              model: Model = None) -> Tree:
def _configure(g, top, model):
def _preconfigure(g, model):
def _configure_node(var, data, nodemap, model):
def _find_next(data, nodemap):
def _get_or_establish_site(var, nodemap):
def _process_epigraph(node):
def reconfigure(g: Graph,
                top: Variable = None,
                model: Model = None,
                key: Callable[[Role], Any] = None) -> Tree:
        def _key(triple):
def rearrange(t: Tree,
              key: Callable[[Role], Any] = None,
              attributes_first: bool = False) -> None:
    def sort_key(branch: Branch):
def _rearrange(node: Node, key: Callable[[Branch], Any]) -> None:
def get_pushed_variable(g: Graph,
                        triple: BasicTriple) -> Union[Variable, None]:
def appears_inverted(g: Graph, triple: BasicTriple) -> bool:
def node_contexts(g: Graph) -> List[Union[Variable, None]]:
POP = Pop()
from typing import Union, Iterable, Iterator, List, IO
from pathlib import Path
from penman.types import (
    Variable,
    BasicTriple,
)
from penman.tree import Tree
from penman.graph import Graph
from penman.model import Model
from penman._parse import (
    parse,
    iterparse,
    parse_triples,
)
from penman._format import (
    format,
    format_triples,
)
from penman import layout

    def decode(self, s: str) -> Graph:
        """
        Deserialize PENMAN-notation string *s* into its Graph object.

        Args:
            s: a string containing a single PENMAN-serialized graph
        Returns:
            The :class:`~penman.graph.Graph` object described by *s*.
        Example:
            >>> from penman.codec import PENMANCodec
            >>> codec = PENMANCodec()
            >>> codec.decode('(b / bark-01 :ARG0 (d / dog))')
            <Graph object (top=b) at ...>
        """
        tree = parse(s)
        return layout.interpret(tree, self.model)

    def iterdecode(self,
                   lines: Union[Iterable[str], str]) -> Iterator[Graph]:
        """
        Yield graphs parsed from *lines*.

        Args:
            lines: a string or open file with PENMAN-serialized graphs
        Returns:
            The :class:`~penman.graph.Graph` objects described in
            *lines*.
        """
        for tree in iterparse(lines):
            yield layout.interpret(tree, self.model)

    def iterparse(self, lines: Union[Iterable[str], str]) -> Iterator[Tree]:
        """
        Yield trees parsed from *lines*.

        Args:
            lines: a string or open file with PENMAN-serialized graphs
        Returns:
            The :class:`~penman.tree.Tree` object described in
            *lines*.
        """
        yield from iterparse(lines)

    def parse(self, s: str) -> Tree:
        """
        Parse PENMAN-notation string *s* into its tree structure.

        Args:
            s: a string containing a single PENMAN-serialized graph
        Returns:
            The tree structure described by *s*.
        Example:
            >>> from penman.codec import PENMANCodec
            >>> codec = PENMANCodec()
            >>> codec.parse('(b / bark-01 :ARG0 (d / dog))')  # noqa
            Tree(('b', [('/', 'bark-01'), (':ARG0', ('d', [('/', 'dog')]))]))
        """
        return parse(s)

    def parse_triples(self, s: str) -> List[BasicTriple]:
        """ Parse a triple conjunction from *s*."""
        return parse_triples(s)

    def encode(self,
               g: Graph,
               top: Variable = None,
               indent: Union[int, None] = -1,
               compact: bool = False) -> str:
        """
        Serialize the graph *g* into PENMAN notation.

        Args:
            g: the Graph object
            top: if given, the node to use as the top in serialization
            indent: how to indent formatted strings
            compact: if ``True``, put initial attributes on the first line
        Returns:
            the PENMAN-serialized string of the Graph *g*
        Example:
            >>> from penman.graph import Graph
            >>> from penman.codec import PENMANCodec
            >>> codec = PENMANCodec()
            >>> codec.encode(Graph([('h', 'instance', 'hi')]))
            '(h / hi)'

        """
        tree = layout.configure(g, top=top, model=self.model)
        return self.format(tree, indent=indent, compact=compact)

    def format(self,
               tree: Tree,
               indent: Union[int, None] = -1,
               compact: bool = False) -> str:
        """
        Format *tree* into a PENMAN string.
        """
        return format(tree, indent=indent, compact=compact)

    def format_triples(self,
                       triples: Iterable[BasicTriple],
                       indent: bool = True) -> str:
        """
        Return the formatted triple conjunction of *triples*.

        Args:
            triples: an iterable of triples
            indent: how to indent formatted strings
        Returns:
            the serialized triple conjunction of *triples*
        Example:
            >>> from penman.codec import PENMANCodec
            >>> codec = PENMANCodec()
            >>> codec.format_triples([('a', ':instance', 'alpha'),
            ...                       ('a', ':ARG0', 'b'),
            ...                       ('b', ':instance', 'beta')])
            ...
            'instance(a, alpha) ^\\nARG0(a, b) ^\\ninstance(b, beta)'

        """

        return format_triples(triples, indent=indent)