Render the given expression into a string that can be evaluated in an
    appropriate context to re-instantiate an identical expression. If
    `indented` is False (default), the resulting string is a single line.
    Otherwise, the result is a multiline string, and each positional and
    keyword argument of each `Expression` is on a separate line, recursively
    indented to produce a tree-like output. The `cache` may be used to generate
    more readable expressions.

    Example:

        >>> hs = LocalSpace('1')
        >>> A = OperatorSymbol('A', hs=hs); B = OperatorSymbol('B', hs=hs)
        >>> expr = A + B
        >>> srepr(expr)
        "OperatorPlus(OperatorSymbol('A', hs=LocalSpace('1')), OperatorSymbol('B', hs=LocalSpace('1')))"
        >>> eval(srepr(expr)) == expr
        True
        >>> srepr(expr, cache={hs:'hs'})
        "OperatorPlus(OperatorSymbol('A', hs=hs), OperatorSymbol('B', hs=hs))"
        >>> eval(srepr(expr, cache={hs:'hs'})) == expr
        True
        >>> print(srepr(expr, indented=True))
        OperatorPlus(
            OperatorSymbol(
                'A',
                hs=LocalSpace(
                    '1')),
            OperatorSymbol(
                'B',
                hs=LocalSpace(
                    '1')))
        >>> eval(srepr(expr, indented=True)) == expr
        True

    See also:
        :func:`~qnet.printing.tree.print_tree`, respectively
        :func:`qnet.printing.tree.tree`, produces an output similar to
        the indented :func:`srepr`, for interactive use. Their result
        cannot be evaluated and the exact output depends on
        :func:`init_printing`.

        :func:`~qnet.printing.dot.dotprint` provides a way to graphically
        explore the tree structure of an expression.

def srepr(expr, indented=False, cache=None):
    """Render the given expression into a string that can be evaluated in an
    appropriate context to re-instantiate an identical expression. If
    `indented` is False (default), the resulting string is a single line.
    Otherwise, the result is a multiline string, and each positional and
    keyword argument of each `Expression` is on a separate line, recursively
    indented to produce a tree-like output. The `cache` may be used to generate
    more readable expressions.

    Example:

        >>> hs = LocalSpace('1')
        >>> A = OperatorSymbol('A', hs=hs); B = OperatorSymbol('B', hs=hs)
        >>> expr = A + B
        >>> srepr(expr)
        "OperatorPlus(OperatorSymbol('A', hs=LocalSpace('1')), OperatorSymbol('B', hs=LocalSpace('1')))"
        >>> eval(srepr(expr)) == expr
        True
        >>> srepr(expr, cache={hs:'hs'})
        "OperatorPlus(OperatorSymbol('A', hs=hs), OperatorSymbol('B', hs=hs))"
        >>> eval(srepr(expr, cache={hs:'hs'})) == expr
        True
        >>> print(srepr(expr, indented=True))
        OperatorPlus(
            OperatorSymbol(
                'A',
                hs=LocalSpace(
                    '1')),
            OperatorSymbol(
                'B',
                hs=LocalSpace(
                    '1')))
        >>> eval(srepr(expr, indented=True)) == expr
        True

    See also:
        :func:`~qnet.printing.tree.print_tree`, respectively
        :func:`qnet.printing.tree.tree`, produces an output similar to
        the indented :func:`srepr`, for interactive use. Their result
        cannot be evaluated and the exact output depends on
        :func:`init_printing`.

        :func:`~qnet.printing.dot.dotprint` provides a way to graphically
        explore the tree structure of an expression.
    """
    if indented:
        printer = IndentedSReprPrinter(cache=cache)
    else:
        printer = QnetSReprPrinter(cache=cache)
    return printer.doprint(expr)