2013-03-30 7 views
35

Ich mache einen Parser mit Scala Combinators. Es ist beeindruckend. Am Ende steht eine lange Liste von Fallklassen, wie zB: ClassDecl(Complex,List(VarDecl(Real,float), VarDecl(Imag,float))), nur 100x länger. Ich frage mich, ob es eine gute Möglichkeit gibt, Fallklassen wie diese baumartig zu drucken, so dass es leichter zu lesen ist. (Oder eine andere Form von Ziemlich Drucke)Scala - wie Fall Klassen wie gedruckt (hübsch gedruckten) Baum

ClassDecl 
    name = Complex 
    fields = 
    - VarDecl 
     name = Real 
     type = float 
    - VarDecl 
     name = Imag 
     type = float 

^ich mit etwas will am Ende wie diese

bearbeiten: Bonus Frage

Gibt es auch eine Möglichkeit, die zeigt, Name des Parameters ..? Wie: ClassDecl(name=Complex, fields=List(...)?

Antwort

27

Überprüfen Sie eine kleine Erweiterungsbibliothek mit dem Namen sext. Es exportiert these two functions genau für solche Zwecke.

Hier ist, wie es für Ihr Beispiel verwendet werden:

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) 

} 

Folgenden sehen Sie die Ausgabe dieses Programms:

treeString output: 

ClassDecl: 
- Complex 
- List: 
| - VarDecl: 
| | - 1 
| | - abcd 
| - VarDecl: 
| | - 2 
| | - efgh 

valueTreeString output: 

- kind: 
- list: 
| - - a: 
| | | 1 
| | - b: 
| | | abcd 
| - - a: 
| | | 2 
| | - b: 
| | | efgh 
+0

funktioniert super! Danke :) – kornfridge

+0

Es funktioniert nicht für Scala 2.10 – Phil

+0

@Phil Travis sagt anders: [Die Bibliothek testet perfekt gegen Scala 2.10.0-2.10.3] (https://travi-ci.org/nikita-volkov/sext/ baut/14017002). Das Problem muss an deinem Ende liegen. –

4

Genau wie Parser Kombinatoren, Scala enthält bereits ziemlich Drucker combinators im Standard Bibliothek. Sie sagen es nicht klar in Ihrer Frage, wenn Sie die Lösung benötigen, die "Reflektion" ausführt, oder Sie möchten den Drucker explizit erstellen. (obwohl Ihre "Bonus Frage" Hinweise Sie wahrscheinlich "reflektierende" Lösung wünschen)

Wie auch immer, in dem Fall, dass Sie gerne einfache hübsche Drucker mit einfachen Scala-Bibliothek zu entwickeln, hier ist es. Der folgende Code ist ERSETZBAR.

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 Frage: für noch mehr composability die Drucker in Typklassen wickeln.

+3

Dieses Zeug ist in Scala 2.11 veraltet; siehe https://groups.google.com/forum/#!topic/scala-language/e7CqLqlxLts –

3

Die schönste, prägnanteste "Out-of-the-Box" -Erfahrung, die ich gefunden habe, ist mit der Kiama pretty printing library. Es wird nicht gedruckt Mitgliedernamen ohne zusätzliche combinators zu verwenden, aber mit nur import org.kiama.output.PrettyPrinter._; pretty(any(data)) haben Sie einen guten 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) 

Produziert:

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

Beachten Sie, dass dies nur das einfachste Beispiel. Kiama PrettyPrinter ist eine extrem leistungsfähige Bibliothek mit einer großen Auswahl an Kombinatoren, die speziell für intelligente Abstände, Zeilenumbruch, Schachtelung und Gruppierung entwickelt wurden. Es ist sehr einfach, an Ihre Bedürfnisse anzupassen. Ab diesem Posting, dann ist es in SBT erhältlich mit:

libraryDependencies += "com.googlecode.kiama" %% "kiama" % "1.8.0" 
+0

'hübsch' von sich selbst druckt nichts, es muss' println' oder etwas ähnliches zur Verfügung gestellt werden. Es druckt auch die Eigenschaftsnamen nicht, so dass die Ausgabe wenig Wert gegenüber dem Standard "toString" für Fallklassen bietet. –

1

Mit Reflexion

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 
    } 
} 
+1

Anwendungsbeispiel? –

2
import java.lang.reflect.Field 
... 

/** 
    * Pretty prints case classes with field names. 
    * Handles sequences and arrays of such values. 
    * Ideally, one could take the output and paste it into source code and have it compile. 
    */ 
def prettyPrint(a: Any): String = { 
    // 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)) 
    a match { 
    // Make Strings look similar to their literal form. 
    case s: String => 
     '"' + Seq("\n" -> "\\n", "\r" -> "\\r", "\t" -> "\\t", "\"" -> "\\\"", "\\" -> "\\\\").foldLeft(s) { 
     case (acc, (c, r)) => acc.replace(c, r) } + '"' 
    case xs: Seq[_] => 
     xs.map(prettyPrint).toString 
    case xs: Array[_] => 
     s"Array(${xs.map(prettyPrint) mkString ", "})" 
    // This covers case classes. 
    case p: Product => 
     s"${p.productPrefix}(${ 
     (getFields(p.getClass) map { f => 
      f setAccessible true 
      s"${f.getName} = ${prettyPrint(f.get(p))}" 
     }) mkString ", " 
     })" 
    // General objects and primitives end up here. 
    case q => 
     Option(q).map(_.toString).getOrElse("¡null!") 
    } 
} 
5

Verwenden Sie die com.lihaoyi.pprint Bibliothek.

libraryDependencies += "com.lihaoyi" %% "pprint" % "0.4.1" 

val data = ... 

val str = pprint.tokenize(data).mkString 
println(str) 

Sie können auch Breite, Höhe, Gedankenstrich und Farben konfigurieren:

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

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