Scala - how to print case classes like (pretty printed) tree

前端 未结 10 1824
情歌与酒
情歌与酒 2020-12-07 22:35

I\'m making a parser with Scala Combinators. It is awesome. What I end up with is a long list of entagled case classes, like: ClassDecl(Complex,List(VarDecl(Real,float

相关标签:
10条回答
  • 2020-12-07 22:42

    The nicest, most concise "out-of-the" box experience I've found is with the Kiama pretty printing library. It doesn't print member names without using additional combinators, but with only import org.kiama.output.PrettyPrinter._; pretty(any(data)) you have a great start:

    case class ClassDecl( kind : Kind, list : List[ VarDecl ] )
    sealed trait Kind
    case object Complex extends Kind
    case class VarDecl( a : Int, b : String )
    
    val data = ClassDecl(Complex,List(VarDecl(1, "abcd"), VarDecl(2, "efgh")))
    import org.kiama.output.PrettyPrinter._
    
    // `w` is the wrapping width. `1` forces wrapping all components.
    pretty(any(data), w=1)
    

    Produces:

    ClassDecl (
        Complex (),
        List (
            VarDecl (
                1,
                "abcd"),
            VarDecl (
                2,
                "efgh")))
    

    Note that this is just the most basic example. Kiama PrettyPrinter is an extremely powerful library with a rich set of combinators specifically designed for intelligent spacing, line wrapping, nesting, and grouping. It's very easy to tweak to suit your needs. As of this posting, it's available in SBT with:

    libraryDependencies += "com.googlecode.kiama" %% "kiama" % "1.8.0"
    
    0 讨论(0)
  • 2020-12-07 22:50

    Check out a small extensions library named sext. It exports these two functions exactly for purposes like that.

    Here's how it can be used for your example:

    object Demo extends App {
    
      import sext._
    
      case class ClassDecl( kind : Kind, list : List[ VarDecl ] )
      sealed trait Kind
      case object Complex extends Kind
      case class VarDecl( a : Int, b : String )
    
    
      val data = ClassDecl(Complex,List(VarDecl(1, "abcd"), VarDecl(2, "efgh")))
      println("treeString output:\n")
      println(data.treeString)
      println()
      println("valueTreeString output:\n")
      println(data.valueTreeString)
    
    }
    

    Following is the output of this program:

    treeString output:
    
    ClassDecl:
    - Complex
    - List:
    | - VarDecl:
    | | - 1
    | | - abcd
    | - VarDecl:
    | | - 2
    | | - efgh
    
    valueTreeString output:
    
    - kind:
    - list:
    | - - a:
    | | | 1
    | | - b:
    | | | abcd
    | - - a:
    | | | 2
    | | - b:
    | | | efgh
    
    0 讨论(0)
  • 2020-12-07 22:58

    This is a shamless copy paste of @F. P Freely, but

    • I've added an indentation feature
    • slight modifications so that the output will be of correct Scala style (and will compile for all primative types)
    • Fixed string literal bug
    • Added support for java.sql.Timestamp (as I use this with Spark a lot)

    Tada!

    // Recursively get all the fields; this will grab vals declared in parents of case classes.
      def getFields(cls: Class[_]): List[Field] =
        Option(cls.getSuperclass).map(getFields).getOrElse(Nil) ++
          cls.getDeclaredFields.toList.filterNot(f =>
            f.isSynthetic || java.lang.reflect.Modifier.isStatic(f.getModifiers))
    
      // FIXME fix bug where indent seems to increase too much
      def prettyfy(a: Any, indentSize: Int = 0): String = {
        val indent = List.fill(indentSize)(" ").mkString
    
        val newIndentSize = indentSize + 2
        (a match {
          // Make Strings look similar to their literal form.
          case string: String =>
            val conversionMap = Map('\n' -> "\\n", '\r' -> "\\r", '\t' -> "\\t", '\"' -> "\\\"", '\\' -> "\\\\")
            string.map(c => conversionMap.getOrElse(c, c)).mkString("\"", "", "\"")
          case xs: Seq[_] =>
            xs.map(prettyfy(_, newIndentSize)).toString
          case xs: Array[_] =>
            s"Array(${xs.map(prettyfy(_, newIndentSize)).mkString(", ")})"
          case map: Map[_, _] =>
            s"Map(\n" + map.map {
              case (key, value) => "  " + prettyfy(key, newIndentSize) + " -> " + prettyfy(value, newIndentSize)
            }.mkString(",\n") + "\n)"
          case None => "None"
          case Some(x) => "Some(" + prettyfy(x, newIndentSize) + ")"
          case timestamp: Timestamp => "new Timestamp(" + timestamp.getTime + "L)"
          case p: Product =>
            s"${p.productPrefix}(\n${
              getFields(p.getClass)
                .map { f =>
                  f.setAccessible(true)
                  s"  ${f.getName} = ${prettyfy(f.get(p), newIndentSize)}"
                }
                .mkString(",\n")
            }\n)"
          // General objects and primitives end up here.
          case q =>
            Option(q).map(_.toString).getOrElse("null")
        })
          .split("\n", -1).mkString("\n" + indent)
      }
    

    E.g.

    case class Foo(bar: String, bob: Int)
    
    case class Alice(foo: Foo, opt: Option[String], opt2: Option[String])
    
    scala> prettyPrint(Alice(Foo("hello world", 10), Some("asdf"), None))
    res6: String =
    Alice(
      foo = Foo(
        bar = "hello world",
        bob = 10
      ),
      opt = Some("asdf"),
      opt2 = None
    )
    
    0 讨论(0)
  • 2020-12-07 23:01

    Just like parser combinators, Scala already contains pretty printer combinators in the standard library. You are not saying it plainly in your question if you need the solution that does "reflection" or you'd like to build the printer explicitly. (though your "bonus question" hints you probably want "reflective" solution)

    Anyway, in the case you'd like to develop simple pretty printer using plain Scala library, here it is. The following code is REPLable.

    case class VarDecl(name: String, `type`: String)
    case class ClassDecl(name: String, fields: List[VarDecl])
    
    import scala.text._
    import Document._
    
    def varDoc(x: VarDecl) =
      nest(4, text("- VarDecl") :/:
        group("name = " :: text(x.name)) :/:
        group("type = " :: text(x.`type`))
      )
    
    def classDoc(x: ClassDecl) = {
      val docs = ((empty:Document) /: x.fields) { (d, f) => varDoc(f) :/: d }
      nest(2, text("ClassDecl") :/:
        group("name = " :: text(x.name)) :/:
        group("fields =" :/: docs))
    }
    
    def prettyPrint(d: Document) = {
      val writer = new java.io.StringWriter
      d.format(1, writer)
      writer.toString
    }
    
    prettyPrint(classDoc(
      ClassDecl("Complex", VarDecl("Real","float") :: VarDecl("Imag","float") :: Nil)
    ))
    

    Bonus question: wrap the printers into type classes for even greater composability.

    0 讨论(0)
  • 2020-12-07 23:02

    Use the com.lihaoyi.pprint library.

    libraryDependencies += "com.lihaoyi" %% "pprint" % "0.4.1"
    
    val data = ...
    
    val str = pprint.tokenize(data).mkString
    println(str)
    

    you can also configure width, height, indent and colors:

    pprint.tokenize(data, width = 80).mkString
    

    Docs: http://www.lihaoyi.com/PPrint/

    0 讨论(0)
  • 2020-12-07 23:02

    Using reflection

    import scala.reflect.ClassTag
    import scala.reflect.runtime.universe._
    
    object CaseClassBeautifier  {
      def getCaseAccessors[T: TypeTag] = typeOf[T].members.collect {
        case m: MethodSymbol if m.isCaseAccessor => m
      }.toList
    
      def nice[T:TypeTag](x: T)(implicit classTag: ClassTag[T]) : String = {
        val instance = x.asInstanceOf[T]
        val mirror = runtimeMirror(instance.getClass.getClassLoader)
        val accessors = getCaseAccessors[T]
        var res = List.empty[String]
        accessors.foreach { z ⇒
          val instanceMirror = mirror.reflect(instance)
          val fieldMirror = instanceMirror.reflectField(z.asTerm)
          val s = s"${z.name} = ${fieldMirror.get}"
          res = s :: res
        }
        val beautified = x.getClass.getSimpleName + "(" + res.mkString(", ") + ")"
        beautified
      }
    }
    
    0 讨论(0)
提交回复
热议问题