Je fais un analyseur syntaxique avec Scala Combinators. C'est génial. Je termine avec une longue liste de classes de cas complexes, telles que: ClassDecl(Complex,List(VarDecl(Real,float), VarDecl(Imag,float)))
, juste 100 fois plus longtemps. Je me demandais s’il existait un bon moyen d’imprimer des classes de cas telles que celles-ci sous forme d’arborescence afin de faciliter la lecture ..? (ou une autre forme de Pretty Print)
ClassDecl
name = Complex
fields =
- VarDecl
name = Real
type = float
- VarDecl
name = Imag
type = float
^ Je veux finir avec quelque chose comme ça
edit: Question bonus
Est-il également possible d'afficher le nom du paramètre ..? Comme: ClassDecl(name=Complex, fields=List( ... )
?
Découvrez une petite bibliothèque d'extensions nommée sext . Il exporte ces deux fonctions à des fins similaires.
Voici comment cela peut être utilisé pour votre exemple:
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)
}
Voici le résultat de ce programme:
treeString output:
ClassDecl:
- Complex
- List:
| - VarDecl:
| | - 1
| | - abcd
| - VarDecl:
| | - 2
| | - efgh
valueTreeString output:
- kind:
- list:
| - - a:
| | | 1
| | - b:
| | | abcd
| - - a:
| | | 2
| | - b:
| | | efgh
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!")
}
}
Tout comme les combinateurs d’analyseurs, Scala contient déjà de jolis combinateurs d’imprimantes dans la bibliothèque standard. Vous ne le dites pas clairement dans votre question si vous avez besoin de la solution qui fait "réflexion" ou si vous souhaitez construire l'imprimante explicitement. (bien que votre "question bonus" suggère que vous souhaitiez probablement une solution "réfléchissante")
Quoi qu'il en soit, si vous souhaitez développer une imprimante simple et jolie à l’aide de la bibliothèque standard Scala, la voici. Le code suivant est 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)
))
Question bonus : place les imprimantes dans des classes de types pour une composition encore plus grande.
Utilisez la bibliothèque com.lihaoyi.pprint.
libraryDependencies += "com.lihaoyi" %% "pprint" % "0.4.1"
val data = ...
val str = pprint.tokenize(data).mkString
println(str)
vous pouvez également configurer la largeur, la hauteur, le retrait et les couleurs:
pprint.tokenize(data, width = 80).mkString
L’expérience la plus concise «hors du commun» que j’ai trouvée concerne la bibliothèque d’impressions Kiama . Cela n'imprime pas les noms des membres sans utiliser de combinateurs supplémentaires, mais avec seulement import org.kiama.output.PrettyPrinter._; pretty(any(data))
, vous avez un bon départ:
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)
Produit:
ClassDecl (
Complex (),
List (
VarDecl (
1,
"abcd"),
VarDecl (
2,
"efgh")))
Notez que ceci est juste l'exemple le plus fondamental. Kiama PrettyPrinter est une bibliothèque extrêmement puissante dotée d’un riche ensemble de combinateurs spécialement conçus pour l’espacement intelligent, le retour à la ligne, l’emboîtement et le regroupement. Il est très facile d'ajuster à vos besoins. A partir de cette publication, il est disponible en SBT avec:
libraryDependencies += "com.googlecode.kiama" %% "kiama" % "1.8.0"
Utiliser la réflexion
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
}
}
C'est un copier-coller sans scrupule de @F. P Librement, mais j'ai ajouté une fonctionnalité d'indentation et apporté quelques légères modifications afin que la sortie soit du style Scala correct (et compile pour tous les types primatifs)
import Java.lang.reflect.Field
def prettyPrint(a: Any, indentSize: Int = 0): 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))
val indent = List.fill(indentSize)(" ").mkString
(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(_, indentSize)).toString
case xs: Array[_] =>
s"Array(${xs.map(prettyPrint(_, indentSize)).mkString(", ")})"
// This covers case classes.
case None => "None"
case Some(x) => "Some(" + prettyPrint(x, indentSize) + ")"
case p: Product =>
s"${p.productPrefix}(\n${
getFields(p.getClass).map { f =>
f.setAccessible(true)
s" ${f.getName} = ${prettyPrint(f.get(p), indentSize + 2)}"
}
.mkString(",\n")
}\n)"
// General objects and primitives end up here.
case q =>
Option(q).map(_.toString).getOrElse("null")
})
.split("\n", -1).mkString("\n" + indent)
}
Par exemple.
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
)