All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.nsc.ast.TreeBrowsers.scala Maven / Gradle / Ivy

The newest version!
/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package tools.nsc
package ast

import scala.language.implicitConversions
import java.awt.{List => _, _}
import java.awt.event._
import java.io.{StringWriter, Writer}
import javax.swing._
import javax.swing.event.TreeModelListener
import javax.swing.tree._
import java.util.concurrent.CountDownLatch
import scala.annotation.{nowarn, tailrec}

/**
 * Tree browsers can show the AST in a graphical and interactive
 * way, useful for debugging and understanding.
 *
 * @author Iulian Dragos
 */
abstract class TreeBrowsers {
  val global: Global
  import global._
  import nme.EMPTY
  import TreeBrowsers._

  val borderSize = 10

  def create(): SwingBrowser = new SwingBrowser()

  /** Pseudo tree class, so that all JTree nodes are treated uniformly */
  case class ProgramTree(units: List[UnitTree]) extends Tree {
    override def toString: String = "Program"
  }

  /** Pseudo tree class, so that all JTree nodes are treated uniformly */
  case class UnitTree(unit: CompilationUnit) extends Tree {
    override def toString: String = unit.toString
  }

  /**
   * Java Swing pretty printer for Scala abstract syntax trees.
   */
  class SwingBrowser {
    def browse(pName: String, units: Iterator[CompilationUnit]): Unit =
      browse(pName, units.toList)

    /** print the whole program */
    def browse(pName: String, units: List[CompilationUnit]): Unit = {
      val latch = new CountDownLatch(1)
      SwingUtilities.invokeAndWait {() =>
        val unitList = units.map(UnitTree(_))
        val tm = new ASTTreeModel(ProgramTree(unitList))
        val frame = new BrowserFrame(pName)
        frame.setTreeModel(tm)
        frame.createFrame(latch)
      }
      // wait for the frame to be closed
      latch.await()
    }
  }

  /** Tree model for abstract syntax trees */
  class ASTTreeModel(val program: ProgramTree) extends TreeModel {
    var listeners: List[TreeModelListener] = Nil

    /** Add a listener to this tree */
    def addTreeModelListener(l: TreeModelListener): Unit =
      listeners = l :: listeners

    /** Return the index'th child of parent */
    def getChild(parent: AnyRef, index: Int): AnyRef =
      packChildren(parent)(index)

    /** Return the number of children this 'parent' has */
    def getChildCount(parent: AnyRef): Int =
      packChildren(parent).length

    /** Return the index of the given child */
    def getIndexOfChild(parent: AnyRef, child: AnyRef): Int =
      packChildren(parent) indexOf child

    /** Return the root node */
    def getRoot(): AnyRef = program

    /** Test whether the given node is a leaf */
    def isLeaf(node: AnyRef): Boolean = packChildren(node).isEmpty

    def removeTreeModelListener(l: TreeModelListener): Unit =
      listeners = listeners filterNot (_ == l)

    /** we ignore this message for now */
    def valueForPathChanged(path: TreePath, newValue: AnyRef) = ()

    /**
     * Return a list of children for the given node.
     */
    def packChildren(t: AnyRef): List[AnyRef] = TreeInfo.children(t.asInstanceOf[Tree])
  }




  /**
   * A window that can host the Tree widget and provide methods for
   * displaying information
   *
   * @author Iulian Dragos
   */
  class BrowserFrame(phaseName: String = "unknown") {
    try {
      UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel")
    }
    catch {
      case _: Throwable => UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName())
    }

    val frame = new JFrame("Scala AST after " + phaseName + " phase")
    frame.setJMenuBar(new ASTMenuBar())
    val topLeftPane = new JPanel(new BorderLayout())
    val topRightPane = new JPanel(new BorderLayout())
    val bottomPane = new JPanel(new BorderLayout())
    var splitPane: JSplitPane = _
    var treeModel: ASTTreeModel = _
    var jTree: JTree = _
    val textArea: JTextArea = new JTextArea(30, 120)
    textArea.setBorder(BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))

    val infoPanel = new TextInfoPanel()


    private def setExpansionState(root: JTree, expand: Boolean): Unit = {
      def _setExpansionState(root: JTree, path: TreePath): Unit = {
        val last = path.getLastPathComponent
        for (i <- 0 until root.getModel.getChildCount(last)) {
          val child = root.getModel.getChild(last, i)
          val childPath = path pathByAddingChild child
          _setExpansionState(root, childPath)
        }
        if (expand) {jTree expandPath path}
        else {jTree collapsePath path}
      }
      _setExpansionState(root, new TreePath(root.getModel.getRoot))
    }

    def expandAll(subtree: JTree) = setExpansionState(subtree, expand = true)
    def collapseAll(subtree: JTree) = setExpansionState(subtree, expand = false)


    /** Create a frame that displays the AST.
     *
     * @param latch The latch is used in order to stop the compilation thread
     * until the user is done with the tree inspection. Swing creates its
     * own threads when the frame is packed, and therefore execution
     * would continue. However, this is not what we want, as the tree and
     * especially symbols/types would change while the window is visible.
     */
    def createFrame(latch: CountDownLatch): Unit = {

      frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)

      frame.addWindowListener(new WindowAdapter() {
        /** Release the lock, so compilation may resume after the window is closed. */
        override def windowClosed(e: WindowEvent): Unit = latch.countDown()
      })

      jTree = new JTree(treeModel) {
        /** Return the string for a tree node. */
        override def convertValueToText(value: Any, sel: Boolean,
                                        exp: Boolean, leaf: Boolean,
                                        row: Int, hasFocus: Boolean) = {
            val (cls, name) = TreeInfo.treeName(value.asInstanceOf[Tree])
            if (name != EMPTY)
              cls + "[" + name + "]"
            else
              cls
        }
      }

      jTree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
        def valueChanged(e: javax.swing.event.TreeSelectionEvent): Unit = {
          textArea.setText(e.getPath().getLastPathComponent().toString)
          infoPanel.update(e.getPath().getLastPathComponent())
        }
      })

      val topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, topLeftPane, topRightPane)
      topSplitPane.setResizeWeight(0.5)

      jTree.setBorder(
        BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))
      topLeftPane.add(new JScrollPane(jTree), BorderLayout.CENTER)
      topRightPane.add(new JScrollPane(infoPanel), BorderLayout.CENTER)
      bottomPane.add(new JScrollPane(textArea), BorderLayout.CENTER)
      textArea.setFont(new Font("monospaced", Font.PLAIN, 14))
      textArea.setEditable(false)

      splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topSplitPane, bottomPane)
      frame.getContentPane().add(splitPane)
      frame.pack()
      frame.setVisible(true)
      splitPane.setDividerLocation(0.5)
    }

    class ASTMenuBar extends JMenuBar {
      val menuKey = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(): @nowarn("cat=deprecation") // deprecated since JDK 10, replacement only available in 10+
      val shiftKey = InputEvent.SHIFT_DOWN_MASK
      val jmFile = new JMenu("File")
      // val jmiSaveImage = new JMenuItem(
      //   new AbstractAction("Save Tree Image") {
      //     putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, menuKey, false))
      //     override def actionPerformed(e: ActionEvent) {
      //       //TODO
      //     }
      //   }
      // )

      // jmFile add jmiSaveImage

      def closeWindow() = frame.getToolkit().getSystemEventQueue().postEvent(
        new WindowEvent(frame, WindowEvent.WINDOW_CLOSING))

      val jmiCancel = new JMenuItem (
        new AbstractAction("Cancel Compilation") {
          putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, menuKey + shiftKey, false))
          override def actionPerformed(e: ActionEvent): Unit = {
            closeWindow()
            global.currentRun.cancel()
          }
        }
      )
      jmFile add jmiCancel

      val jmiExit = new JMenuItem (
        new AbstractAction("Exit") {
          putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, menuKey, false))
          override def actionPerformed(e: ActionEvent) = closeWindow()
        }
      )
      jmFile add jmiExit
      add(jmFile)

      val jmView = new JMenu("View")
      val jmiExpand = new JMenuItem(
        new AbstractAction("Expand All Nodes") {
          putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E, menuKey, false))
          override def actionPerformed(e: ActionEvent): Unit = {
            expandAll(jTree)
          }
        }
      )
      jmView add jmiExpand
      val jmiCollapse = new JMenuItem(
        new AbstractAction("Collapse All Nodes") {
          putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_L, menuKey, false))
          override def actionPerformed(e: ActionEvent): Unit = {
            collapseAll(jTree)
          }
        }
      )
      jmView add jmiCollapse
      val jmiGoto = new JMenuItem(
        new AbstractAction("Go to unit") {
          putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, menuKey, false))
          override def actionPerformed(actionEvent: ActionEvent): Unit = {
            val query = JOptionPane.showInputDialog("Go to unit:", frame.getOwner)
            if (query ne null) { // "Cancel" returns null
              val units = treeModel.program.units
              units.find(_.unit.source.file.name startsWith query) foreach { unit =>
                // skip through 1-ary trees
                def expando(tree: Tree): List[Tree] = tree.children match {
                  case only :: Nil => only :: expando(only)
                  case _ => tree :: Nil
                }

                val path = new TreePath((treeModel.getRoot :: unit :: expando(unit.unit.body)).toArray[AnyRef]) // targ necessary to disambiguate Object and Object[] ctors
                jTree.expandPath(path)
                jTree.setSelectionPath(path)
              }
            }
          }
        }
      )
      jmView add jmiGoto
      add(jmView)
    }

    def setTreeModel(tm: ASTTreeModel): Unit = treeModel = tm
  }

  /**
   * Present detailed information about the selected tree node.
   */
  class TextInfoPanel extends JTextArea(20, 50) {

    setBorder(BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))
    setEditable(false)
    setFont(new Font("monospaced", Font.PLAIN, 12))

    def update(v: AnyRef): Unit = {
      val t: Tree = v.asInstanceOf[Tree]
      val str = new StringBuilder()
      var buf = new StringWriter()

      t match {
        case ProgramTree(_) => ()
        case UnitTree(_)    => ()
        case _ =>
          str.append("tree.id: ").append(t.id)
          str.append("\ntree.pos: ").append(t.pos)
          str.append(TreeInfo.attachments(t, "tree"))
          str.append("\nSymbol: ").append(TreeInfo.symbolText(t))
          str.append("\nSymbol owner: ").append(
            if ((t.symbol ne null) && t.symbol != NoSymbol)
              t.symbol.owner.toString
            else
              "NoSymbol has no owner")
          if ((t.symbol ne null) && t.symbol.isType) {
            str.append("\ntermSymbol: " + t.symbol.tpe.termSymbol
                     + "\ntypeSymbol: " + t.symbol.tpe.typeSymbol)
          if (t.symbol.isTypeSkolem)
            str.append("\nSkolem of: " + t.symbol.deSkolemize)
          }
          str.append("\nSymbol tpe: ")
          if (t.symbol ne null) {
            str.append(t.symbol.tpe).append("\n")
            buf = new StringWriter()
            TypePrinter.toDocument(t.symbol.tpe).format(getWidth() / getColumnWidth(), buf)
            str.append(buf.toString)
          }
          str.append("\n\nSymbol info: \n")
          TreeInfo.symbolTypeDoc(t).format(getWidth() / getColumnWidth(), buf)
          str.append(buf.toString)
          str.append("\n\nSymbol Attributes: \n").append(TreeInfo.symbolAttributes(t))
          str.append("\ntree.tpe: ")
          if (t.tpe ne null) {
            str.append(t.tpe.toString).append("\n")
            buf = new StringWriter()
            TypePrinter.toDocument(t.tpe).format(getWidth() / getColumnWidth(), buf)
            str.append(buf.toString)
          }
      }
      setText(str.toString)
    }
  }

  /** Computes different information about a tree node. It
   *  is used as central place to do all pattern matching against
   *  Tree.
   */
  object TreeInfo {
    /** Return the case class name and the Name, if the node defines one */
    def treeName(t: Tree): (String, Name) = ((t.productPrefix, t match {
      case UnitTree(unit)                  => newTermName("" + unit)
      case Super(_, mix)                   => newTermName("mix: " + mix)
      case This(qual)                      => qual
      case Select(_, selector)             => selector
      case Ident(name)                     => name
      case SelectFromTypeTree(_, selector) => selector
      case x: DefTree                      => x.name
      case _                               => EMPTY
    }))

    /** Return a list of children for the given tree node */
    def children(t: Tree): List[Tree] = t match {
      case ProgramTree(units) =>
        units

      case UnitTree(unit) =>
        List(unit.body)

      case DocDef(comment, definition) =>
        List(definition)

      case ClassDef(mods, name, tparams, impl) => {
        var children: List[Tree] = List()
        children = tparams ::: children
        mods.annotations ::: impl :: children
      }

      case PackageDef(pid, stats) =>
        stats

      case ModuleDef(mods, name, impl) =>
        mods.annotations ::: List(impl)

      case ValDef(mods, name, tpe, rhs) =>
        mods.annotations ::: List(tpe, rhs)

      case DefDef(mods, name, tparams, vparams, tpe, rhs) =>
        mods.annotations ::: tpe :: rhs :: vparams.flatten ::: tparams

      case TypeDef(mods, name, tparams, rhs) =>
        mods.annotations ::: rhs :: tparams // @M: was List(rhs, lobound)

      case Import(expr, selectors) =>
        List(expr)

      case CaseDef(pat, guard, body) =>
        List(pat, guard, body)

      case Template(parents, self, body) =>
        parents ::: List(self) ::: body

      case LabelDef(name, params, rhs) =>
        params ::: List(rhs)

      case Block(stats, expr) =>
        stats ::: List(expr)

      case Alternative(trees) =>
        trees

      case Bind(name, rhs) =>
        List(rhs)

      case UnApply(fun, args) =>
        fun :: args

      case Match(selector, cases) =>
        selector :: cases

      case Function(vparams, body) =>
        vparams ::: List(body)

      case Assign(lhs, rhs) =>
        List(lhs, rhs)

      case If(cond, thenp, elsep) =>
        List(cond, thenp, elsep)

      case Return(expr) =>
        List(expr)

      case Throw(expr) =>
        List(expr)

      case New(init) =>
        List(init)

      case Typed(expr, tpe) =>
        List(expr, tpe)

      case TypeApply(fun, args) =>
        List(fun) ::: args

      case Apply(fun, args) =>
        List(fun) ::: args

      case ApplyDynamic(qual, args) =>
        List(qual) ::: args

      case Super(qualif, mix) =>
        List(qualif)

      case This(_) =>
        Nil

      case Select(qualif, _) =>
        List(qualif)

      case Ident(name) =>
        Nil

      case Literal(value) =>
        Nil

      case TypeTree() =>
        Nil

      case Annotated(annot, arg) =>
        annot :: List(arg)

      case SingletonTypeTree(ref) =>
        List(ref)

      case SelectFromTypeTree(qualif, _) =>
        List(qualif)

      case CompoundTypeTree(templ) =>
        List(templ)

      case AppliedTypeTree(tpe, args) =>
        tpe :: args

      case TypeBoundsTree(lo, hi) =>
        List(lo, hi)

      case ExistentialTypeTree(tpt, whereClauses) =>
        tpt :: whereClauses

      case Try(block, catches, finalizer) =>
        block :: catches ::: List(finalizer)

      case ArrayValue(elemtpt, elems) =>
        elemtpt :: elems

      case EmptyTree =>
        Nil

      case Star(t) =>
        List(t)

      case x => throw new MatchError(x)
    }

    /** Return a textual representation of this t's symbol */
    def symbolText(t: Tree): String = {
      val prefix =
        if (t.hasSymbolField)  "[has] "
        else if (t.isDef) "[defines] "
        else ""

      prefix + t.symbol
    }

    /** Return t's symbol type  */
    def symbolTypeDoc(t: Tree): Document = {
      val s = t.symbol
      if (s ne null)
        TypePrinter.toDocument(s.info)
      else
        DocNil
    }

    /** Return a textual representation of (some of) the symbol's
     * attributes */
    def symbolAttributes(t: Tree): String = {
      val s = t.symbol

      if ((s ne null) && (s != NoSymbol)) {
        val str = new StringBuilder(s.flagString)
        if (s.isStaticMember) str ++= " isStatic "
        str ++= " annotations: "
        str ++= s.annotations.mkString("", " ", "")
        if (s.isTypeSkolem) {
          str ++= "\ndeSkolemized annotations: "
          str ++= s.deSkolemize.annotations.mkString("", " ", "")
        }
        str ++= attachments(s, "")
        str.toString
      }
      else ""
    }

    def attachments(t: Attachable, pre: String): String = {
      if (t.attachments.isEmpty) ""
      else t.attachments.all.mkString(s"\n$pre attachments:\n   ","\n   ","")
    }
  }

  object TypePrinter {

    ///////////////// Document pretty printer ////////////////

    implicit def view(n: String): Document = DocText(n)

    def toDocument(sym: Symbol): Document =
      toDocument(sym.info)

    def symsToDocument(syms: List[Symbol]): Document = syms match {
      case Nil => DocNil
      case s :: Nil => Document.group(toDocument(s))
      case _ =>
        Document.group(
          syms.tail.foldLeft (toDocument(syms.head) :: ", ") (
            (d: Document, s2: Symbol) => toDocument(s2) :: ", " :/: d) )
    }

    def toDocument(ts: List[Type]): Document = ts match {
      case Nil => DocNil
      case t :: Nil => Document.group(toDocument(t))
      case _ =>
        Document.group(
          ts.tail.foldLeft (toDocument(ts.head) :: ", ") (
            (d: Document, t2: Type) => toDocument(t2) :: ", " :/: d) )
    }

    def toDocument(t: Type): Document = t match {
      case ErrorType => "ErrorType()"
      case WildcardType => "WildcardType()"
      case NoType => "NoType()"
      case NoPrefix => "NoPrefix()"
      case ThisType(s) => "ThisType(" + s.nameString + ")"

      case SingleType(pre, sym) =>
        Document.group(
          Document.nest(4, "SingleType(" :/:
                      toDocument(pre) :: ", " :/: sym.nameString :: ")")
        )

      case ConstantType(value) =>
         "ConstantType(" + value + ")"

      case TypeRef(pre, sym, args) =>
        Document.group(
          Document.nest(4, "TypeRef(" :/:
                        toDocument(pre) :: ", " :/:
                        sym.nameString :: ", " :/:
                        "[ " :: toDocument(args) ::"]" :: ")")
        )

      case TypeBounds(lo, hi) =>
        Document.group(
          Document.nest(4, "TypeBounds(" :/:
                        toDocument(lo) :: ", " :/:
                        toDocument(hi) :: ")")
        )

       case RefinedType(parents, _) =>
        Document.group(
          Document.nest(4, "RefinedType(" :/:
                        toDocument(parents) :: ")")
        )

      case ClassInfoType(parents, _, clazz) =>
        Document.group(
          Document.nest(4,"ClassInfoType(" :/:
                        toDocument(parents) :: ", " :/:
                        clazz.nameString :: ")")
        )

      case MethodType(params, result) =>
        Document.group(
          Document.nest(4, "MethodType(" :/:
                        Document.group("(" :/:
                                       symsToDocument(params) :/:
                                       "), ") :/:
                        toDocument(result) :: ")")
        )

      case NullaryMethodType(result) =>
        Document.group(
          Document.nest(4,"NullaryMethodType(" :/:
                        toDocument(result) :: ")")
        )

      case PolyType(tparams, result) =>
        Document.group(
          Document.nest(4,"PolyType(" :/:
                        Document.group("(" :/:
                                       symsToDocument(tparams) :/:
                                       "), ") :/:
                        toDocument(result) :: ")")
        )

      case AnnotatedType(annots, tp) =>
        Document.group(
          Document.nest(4, "AnnotatedType(" :/:
                        annots.mkString("[", ",", "]") :/:
                        "," :/: toDocument(tp) :: ")")
        )

      case ExistentialType(tparams, result) =>
        Document.group(
            Document.nest(4, "ExistentialType(" :/:
                Document.group("(" :/: symsToDocument(tparams) :/: "), ") :/:
                toDocument(result) :: ")"))

      case ImportType(expr) =>
        "ImportType(" + expr.toString + ")"


      case SuperType(thistpe, supertpe) =>
        Document.group(
          Document.nest(4, "SuperType(" :/:
                        toDocument(thistpe) :/: ", " :/:
                        toDocument(supertpe) ::")"))

      case ErasedValueType(clazz, erased) =>
        Document.group(
          Document.nest(4, "ErasedValueType(" :/:
                        clazz.nameString :/: ", " :/:
                        toDocument(erased) :: ")"))

      case _ =>
        abort("Unknown case: " + t.toString +", "+ t.getClass)
    }
  }

}

object TreeBrowsers {
  case object DocNil                              extends Document
  case object DocBreak                            extends Document
  case class  DocText(txt: String)                extends Document
  case class  DocGroup(doc: Document)             extends Document
  case class  DocNest(indent: Int, doc: Document) extends Document
  case class  DocCons(hd: Document, tl: Document) extends Document

  /**
    * A basic pretty-printing library, based on Lindig's strict version
    * of Wadler's adaptation of Hughes' pretty-printer.
    *
    * @author Michel Schinz
    */
  sealed abstract class Document {
    def ::(hd: Document): Document  = DocCons(hd, this)
    def ::(hd: String): Document    = DocCons(DocText(hd), this)
    def :/:(hd: Document): Document = hd :: DocBreak :: this
    def :/:(hd: String): Document   = hd :: DocBreak :: this

    /**
      * Format this document on `writer` and try to set line
      * breaks so that the result fits in `width` columns.
      */
    def format(width: Int, writer: Writer): Unit = {
      type FmtState = (Int, Boolean, Document)

      @tailrec
      def fits(w: Int, state: List[FmtState]): Boolean = state match {
        case _ if w < 0                      => false
        case List()                          => true
        case (_,     _, DocNil)         :: z => fits(w, z)
        case (i,     b, DocCons(h, t))  :: z => fits(w, (i, b, h) :: (i, b, t) :: z)
        case (_,     _, DocText(t))     :: z => fits(w - t.length(), z)
        case (i,     b, DocNest(ii, d)) :: z => fits(w, (i + ii, b, d) :: z)
        case (_, false, DocBreak)       :: z => fits(w - 1, z)
        case (_,  true, DocBreak)       :: _ => true
        case (i,     _, DocGroup(d))    :: z => fits(w, (i, false, d) :: z)
      }

      def spaces(n: Int): Unit = {
        var rem = n
        while (rem >= 16) { writer.write("                ") ; rem -= 16 }
        if (rem >= 8)     { writer.write("        ")         ; rem -= 8  }
        if (rem >= 4)     { writer.write("    ")             ; rem -= 4  }
        if (rem >= 2)     { writer.write("  ")               ; rem -= 2  }
        if (rem == 1)     { writer.write(" ")                            }
      }

      @tailrec
      def fmt(k: Int, state: List[FmtState]): Unit = state match {
        case List()                          => ()
        case (_,     _, DocNil)         :: z => fmt(k, z)
        case (i,     b, DocCons(h, t))  :: z => fmt(k, (i, b, h) :: (i, b, t) :: z)
        case (_,     _, DocText(t))     :: z => writer.write(t) ; fmt(k + t.length(), z)
        case (i,     b, DocNest(ii, d)) :: z => fmt(k, (i + ii, b, d) :: z)
        case (i,  true, DocBreak)       :: z => writer.write("\n") ; spaces(i) ; fmt(i, z)
        case (_, false, DocBreak)       :: z => writer.write(" ") ; fmt(k + 1, z)
        case (i,     _, DocGroup(d))    :: z => fmt(k, (i, !fits(width - k, (i, false, d) :: z), d) :: z)
        case _                               => ()
      }

      fmt(0, (0, false, DocGroup(this)) :: Nil)
    }
  }

  object Document {
    /** The empty document */
    def empty = DocNil

    /** A break, which will either be turned into a space or a line break */
    def break = DocBreak

    /** A document consisting of some text literal */
    def text(s: String): Document = DocText(s)

    /**
      * A group, whose components will either be printed with all breaks
      * rendered as spaces, or with all breaks rendered as line breaks.
      */
    def group(d: Document): Document = DocGroup(d)

    /** A nested document, which will be indented as specified. */
    def nest(i: Int, d: Document): Document = DocNest(i, d)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy