diff options
author | Martin Odersky <odersky@gmail.com> | 2013-05-14 12:22:35 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2013-05-14 12:22:35 +0200 |
commit | fec318dedaa10bf7ffaebd7aaf4a99e05ac0312a (patch) | |
tree | 8d5f9560806325c922eceb847f5c13b096aa7013 /src/dotty/tools/dotc/printing | |
parent | b866c49bde62ee8ee52358dee746db64741b5891 (diff) | |
download | dotty-fec318dedaa10bf7ffaebd7aaf4a99e05ac0312a.tar.gz dotty-fec318dedaa10bf7ffaebd7aaf4a99e05ac0312a.tar.bz2 dotty-fec318dedaa10bf7ffaebd7aaf4a99e05ac0312a.zip |
Refactored Printers, Showable and Text into new package dotc.printing.
Diffstat (limited to 'src/dotty/tools/dotc/printing')
-rw-r--r-- | src/dotty/tools/dotc/printing/Printers.scala | 542 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/Showable.scala | 14 | ||||
-rw-r--r-- | src/dotty/tools/dotc/printing/Texts.scala | 153 |
3 files changed, 709 insertions, 0 deletions
diff --git a/src/dotty/tools/dotc/printing/Printers.scala b/src/dotty/tools/dotc/printing/Printers.scala new file mode 100644 index 000000000..6a2f98250 --- /dev/null +++ b/src/dotty/tools/dotc/printing/Printers.scala @@ -0,0 +1,542 @@ +package dotty.tools.dotc +package printing + +import core._ +import Types._, Symbols._, Contexts._, Scopes._, Names._, NameOps._, Flags._ +import Constants._, Annotations._, StdNames._, Denotations._, SymDenotations._, Trees._ +import Texts._ +import java.lang.Integer.toOctalString +import scala.annotation.switch + +trait Printers { this: Context => + + import Printers._ + + def printer = if (this.debug) plainPrinter else refinedPrinter + +} + +object Printers { + + class Precedence(val value: Int) extends AnyVal { + def parenthesize(nested: Precedence)(text: Text) = + if (nested.value < value) "(" ~ text ~ ")" else text + } + + val DotPrec = new Precedence(4) + val AndPrec = new Precedence(3) + val OrPrec = new Precedence(2) + val WithPrec = new Precedence(1) + val LeftArrowPrec = new Precedence(1) + val GlobalPrec = new Precedence(0) + + abstract class Printer { + + /** The name, possibley with with namespace suffix if debugNames is set: + * /L for local names, /V for other term names, /T for type names + */ + def nameString(name: Name): String + + /** The name of the given symbol. + * If !settings.debug, the original name where + * expansions of operators are translated back to operator symbol. + * E.g. $eq => =. + * If settings.uniqid, adds id. + */ + def nameString(sym: Symbol): String + + /** The fully qualified name of the symbol */ + def fullNameString(sym: Symbol): String + + /** The kind of the symbol */ + def kindString(sym: Symbol): String + + /** Textual representation, including symbol's kind e.g., "class Foo", "method Bar". + * If hasMeaninglessName is true, uses the owner's name to disambiguate identity. + */ + def toText(sym: Symbol): Text + + /** Textual representation of symbol's declaration */ + def dclText(sym: Symbol): Text + + /** If symbol's owner is a printable class C, the text "in C", otherwise "" */ + def locationText(sym: Symbol): Text + + /** Textual representation of symbol and its location */ + def locatedText(sym: Symbol): Text + + /** Textual representation of denotation */ + def toText(denot: Denotation): Text + + /** Textual representation of constant */ + def toText(const: Constant): Text + + /** Textual representation of annotation */ + def toText(annot: Annotation): Text + + /** Textual representation of type in context with given precedence */ + def toText(tp: Type, precedence: Precedence): Text + + /** Textual representation of all symbols in given list, + * using `dclText` for displaying each. + */ + def dclsText(syms: List[Symbol], sep: String = "\n"): Text + + /** Textual representation of all definitions in a scope using `dclText` for each */ + def toText(sc: Scope): Text + + /** Textual representation of tree */ + def toText[T >: Untyped](tree: Tree[T]): Text + } + + class PlainPrinter(_ctx: Context) extends Printer { + protected[this] implicit val ctx = _ctx + + def controlled(op: => Text): Text = + if (ctx.toTextRecursions < maxToTextRecursions) + try { + ctx.toTextRecursions += 1 + op + } finally { + ctx.toTextRecursions -= 1 + } + else { + recursionLimitExceeeded() + "..." + } + + protected def recursionLimitExceeeded() = { + ctx.warning("Exceeded recursion depth attempting to print type.") + (new Throwable).printStackTrace + } + + /** If the name of the symbol's owner should be used when you care about + * seeing an interesting name: in such cases this symbol is e.g. a method + * parameter with a synthetic name, a constructor named "this", an object + * "package", etc. The kind string, if non-empty, will be phrased relative + * to the name of the owner. + */ + protected def hasMeaninglessName(sym: Symbol) = ( + (sym is Param) && sym.owner.isSetter // x$1 + || sym.isClassConstructor // this + || (sym.name == nme.PACKAGE) // package + ) + + def nameString(name: Name): String = name.toString + { + if (ctx.settings.debugNames.value) + if (name.isLocalName) "/L" + else if (name.isTypeName) "/T" + else "/V" + else "" + } + + /** String representation of a name used in a refinement + * In refined printing this undoes type parameter expansion + */ + protected def refinementNameString(tp: RefinedType) = nameString(tp.refinedName) + + /** String representation of a refinement */ + protected def toTextRefinement(rt: RefinedType) = + (refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close + + /** The longest sequence of refinement types, starting at given type + * and following parents. + */ + private def refinementChain(tp: Type): List[Type] = + tp :: (tp match { + case RefinedType(parent, _) => refinementChain(parent) + case _ => Nil + }) + + def toText(tp: Type, prec: Precedence): Text = controlled { + tp match { + case tp: TypeType => + toTextRHS(tp) + case tp: SingletonType => + val pre = toTextPrefix(tp) + if (pre.lastLine.endsWith(".")) pre ~ "type" + else fullNameString(tp.typeSymbol.skipPackageObject) ~ ".type" + case TypeRef(pre, name) => + toTextPrefix(pre) ~ nameString(tp.typeSymbol) + case tp: RefinedType => + // return tp.toString // !!! DEBUG + val parent :: (refined: List[RefinedType]) = + refinementChain(tp).reverse + toTextLocal(parent) ~ "{" ~ + Text(refined.map(toTextRefinement), "; ").close ~ "}" + case AndType(tp1, tp2) => + (prec parenthesize AndPrec) { + toText(tp1, AndPrec) ~ " & " ~ toText(tp2, AndPrec) + } + case OrType(tp1, tp2) => + (prec parenthesize OrPrec) { + toText(tp1, OrPrec) ~ " | " ~ toText(tp2, OrPrec) + } + case ErrorType => + "<error>" + case WildcardType => + "?" + case NoType => + "<notype>" + case NoPrefix => + "<noprefix>" + case tp: MethodType => + (prec parenthesize GlobalPrec) { + (if (tp.isImplicit) "(implicit " else "(") ~ + Text( + (tp.paramNames, tp.paramTypes).zipped + .map((name, tp) => nameString(name) ~ ": " ~ toTextGlobal(tp)), + ", ") ~ + ")" ~ toTextGlobal(tp.resultType) + } + case tp: ExprType => + (prec parenthesize GlobalPrec) { + "=> " ~ toTextGlobal(tp.resultType) + } + case tp: PolyType => + (prec parenthesize GlobalPrec) { + "[" ~ + Text( + (tp.paramNames, tp.paramBounds).zipped + .map((name, bounds) => + nameString(polyParamName(name)) ~ toTextGlobal(bounds)), + ", ") ~ + "]" ~ toTextGlobal(tp.resultType) + } + case PolyParam(pt, n) => + nameString(polyParamName(pt.paramNames(n))) + case AnnotatedType(annot, tpe) => + toTextLocal(tpe) ~ " " ~ toText(annot) + } + }.close + + protected def polyParamName(name: TypeName): TypeName = name + + /** Render type within highest precedence */ + protected def toTextLocal(tp: Type) = toText(tp, DotPrec) + + /** Render type within lowest precedence */ + protected def toTextGlobal(tp: Type) = toText(tp, GlobalPrec) + + /** The name of the symbol without a unique id. Under refined printing, + * the decoded original name. + */ + protected def simpleNameString(sym: Symbol): String = nameString(sym.name) + + /** The unique id of symbol, after a # */ + protected def idString(sym: Symbol): String = + if (ctx.settings.uniqid.value) "#" + sym.id else "" + + def nameString(sym: Symbol): String = simpleNameString(sym) + idString(sym) + + def fullNameString(sym: Symbol): String = + if (sym.isRoot || sym == NoSymbol || sym.owner.isEffectiveRoot) + nameString(sym) + else + fullNameString(sym.effectiveOwner.enclosingClass) + "." + nameString(sym) + + protected def objectPrefix = "object " + protected def packagePrefix = "package " + + protected def trimPrefix(text: Text) = + text.stripPrefix(objectPrefix).stripPrefix(packagePrefix) + + /** The string representation of this type used as a prefix */ + protected def toTextPrefix(tp: Type): Text = controlled { + tp match { + case tp @ TermRef(pre, name) => + toTextPrefix(pre) ~ nameString(tp.symbol) ~ "." + case ThisType(cls) => + nameString(cls) + ".this." + case SuperType(thistpe, _) => + toTextPrefix(thistpe).map(_.replaceAll("""\bthis\.$""", "super.")) + case tp @ ConstantType(value) => + toTextLocal(tp.underlying) ~ "(" ~ toText(value) ~ ")." + case MethodParam(mt, idx) => + nameString(mt.paramNames(idx)) + "." + case RefinedThis(_) => + "this." + case NoPrefix => + "" + case _ => + trimPrefix(toTextLocal(tp)) ~ "#" + } + } + + protected def isOmittablePrefix(sym: Symbol) = + (defn.UnqualifiedOwners contains sym) || isEmptyPrefix(sym) + + protected def isEmptyPrefix(sym: Symbol) = + sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName + + /** String representation of a definition's type following its name */ + protected def toTextRHS(tp: Type): Text = controlled { + tp match { + case TypeBounds(lo, hi) => + if (lo eq hi) + " = " ~ lo.toText + else + (if (lo == defn.NothingType) Text() else " >: " ~ lo.toText) ~ + (if (hi == defn.AnyType) Text() else " <: " ~ hi.toText) + case ClassInfo(pre, cls, cparents, decls, optSelfType) => + val preText = toTextLocal(pre) + val (tparams, otherDecls) = decls.toList partition treatAsTypeParam + val tparamsText = + if (tparams.isEmpty) Text() else ("[" ~ dclsText(tparams) ~ "]").close + val selfText = + if (optSelfType.exists) + "this: " ~ toText(optSelfType, LeftArrowPrec) ~ " =>" + else Text() + val parentsText = Text(cparents.map(p => + toText(reconstituteParent(cls, p), WithPrec)), " with ") + val trueDecls = otherDecls.filterNot(treatAsTypeArg) + val declsText = if (trueDecls.isEmpty) Text() else dclsText(trueDecls) + tparamsText ~ " extends " ~ parentsText ~ "{" ~ selfText ~ declsText ~ + "} at " ~ preText + case _ => + ": " ~ toTextGlobal(tp) + } + } + + protected def treatAsTypeParam(sym: Symbol): Boolean = false + protected def treatAsTypeArg(sym: Symbol): Boolean = false + protected def reconstituteParent(cls: ClassSymbol, parent: Type): Type = parent + + /** String representation of symbol's kind. */ + def kindString(sym: Symbol): String = { + val flags = sym.flagsUNSAFE + if (flags is PackageClass) "package class" + else if (flags is PackageVal) "package" + else if (sym.isPackageObject) + if (sym.isClass) "package object class" + else "package object" + else if (sym.isAnonymousClass) "anonymous class" + else if (flags is ModuleClass) "module class" + else if (flags is ModuleVal) "module" + else if (flags is ImplClass) "implementation class" + else if (flags is Trait) "trait" + else if (sym.isClass) "class" + else if (sym.isType) "type" + else if (sym.isGetter) "getter" + else if (sym.isSetter) "setter" + else if (flags is Lazy) "lazy value" + else if (flags is Mutable) "variable" + else if (sym.isClassConstructor && sym.isPrimaryConstructor) "primary constructor" + else if (sym.isClassConstructor) "constructor" + else if (sym.isSourceMethod) "method" + else if (sym.isTerm) "value" + else "" + } + + /** String representation of symbol's definition key word */ + protected def keyString(sym: Symbol): String = { + val flags = sym.flagsUNSAFE + if (flags is JavaInterface) "interface" + else if ((flags is Trait) && !(flags is ImplClass)) "trait" + else if (sym.isClass) "class" + else if (sym.isType) "type" + else if (flags is Mutable) "var" + else if (flags is Package) "package" + else if (flags is Module) "object" + else if (sym.isSourceMethod) "def" + else if (sym.isTerm && (!(flags is Param))) "val" + else "" + } + + /** String representation of symbol's flags */ + protected def toTextFlags(sym: Symbol): Text = + Text(sym.flags.flagStrings map stringToText, " ") + + /** String representation of symbol's variance or "" if not applicable */ + protected def varianceString(sym: Symbol): String = sym.variance match { + case -1 => "-" + case 1 => "+" + case _ => "" + } + + def dclText(sym: Symbol): Text = + (toTextFlags(sym) ~~ keyString(sym) ~~ + (varianceString(sym) ~ nameString(sym)) ~ toTextRHS(sym.info)).close + + def toText(sym: Symbol): Text = + (kindString(sym) ~~ { + if (hasMeaninglessName(sym)) simpleNameString(sym.owner) + idString(sym) + else nameString(sym) + }).close + + def locationText(sym: Symbol): Text = { + val owns = sym.effectiveOwner + if (owns.isClass && !isEmptyPrefix(owns)) " in " ~ toText(owns) else Text() + } + + def locatedText(sym: Symbol): Text = + (toText(sym) ~ locationText(sym)).close + + def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D" + + @switch private def escapedChar(ch: Char): String = ch match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '"' => "\\\"" + case '\'' => "\\\'" + case '\\' => "\\\\" + case _ => if (ch.isControl) "\\0" + toOctalString(ch) else String.valueOf(ch) + } + + def toText(const: Constant): Text = const.tag match { + case StringTag => "\"" + (const.value.toString flatMap escapedChar) + "\"" + case ClazzTag => "classOf[" ~ const.tpe.toText ~ "]" + case CharTag => s"'${escapedChar(const.charValue)}'" + case LongTag => const.longValue.toString + "L" + case EnumTag => const.symbolValue.name.toString + case _ => String.valueOf(const.value) + } + + def toText(annot: Annotation): Text = s"@${annot.symbol.name}" // for now + + def dclsText(syms: List[Symbol], sep: String): Text = + if (sep == "\n") Text.lines(syms map dclText) + else Text(syms map dclText, sep) + + def toText(sc: Scope): Text = + ("Scope{" ~ dclsText(sc.toList) ~ "}").close + + def toText[T >: Untyped](tree: Tree[T]): Text = { + tree match { + case node: Positioned => + def toTextElem(elem: Any): Text = elem match { + case elem: Showable => elem.toText + case elem: List[_] => "List(" ~ Text(elem map toTextElem, ",") ~ ")" + case elem => elem.toString + } + val nodeName = node.productPrefix + val elems = + Text(node.productIterator.map(toTextElem).toList, ", ") + val tpSuffix = + if (ctx.settings.printtypes.value && tree.hasType) + " | " ~ tree.tpe.asInstanceOf[Type].toText + else + Text() + + nodeName ~ "(" ~ elems ~ tpSuffix ~ ")" ~ node.pos.toString + case _ => + tree.toString: Text + } + }.close // todo: override in refined printer + } + + class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { + override protected def recursionLimitExceeeded() = {} + + override def nameString(name: Name): String = name.toString + + override protected def simpleNameString(sym: Symbol): String = { + var name = sym.originalName + if (sym is ModuleClass) name = name.stripModuleClassSuffix + name.decode.toString + } + + override def toTextPrefix(tp: Type): Text = controlled { + tp match { + case ThisType(cls) => + if (cls.isAnonymousClass) return "this." + if (isOmittablePrefix(cls)) return "" + if (cls is ModuleClass) return fullNameString(cls) + "." + case tp @ TermRef(pre, name) => + val sym = tp.symbol + if (sym.isPackageObject) return toTextPrefix(pre) + if (isOmittablePrefix(sym)) return "" + case _ => + } + super.toTextPrefix(tp) + } + + override protected def refinementNameString(tp: RefinedType): String = { + val tsym = tp.member(tp.refinedName).symbol + val name = tsym.originalName + nameString(if (tsym is ExpandedTypeParam) name.asTypeName.unexpandedName() else name) + } + + override def toText(tp: Type, prec: Precedence): Text = controlled { + def toTextFunction(args: List[Type]): Text = + (prec parenthesize GlobalPrec) { + val argStr: Text = + if (args.length == 2 && + !(defn.TupleClasses contains args.head.typeSymbol)) toText(args.head, LeftArrowPrec) + else + "(" ~ Text(args.init.map(toTextGlobal(_)), ", ") ~ ")" + argStr ~ " => " ~ toTextGlobal(args.last) + } + def toTextTuple(args: List[Type]): Text = + "(" ~ Text(args.map(toTextGlobal(_)), ", ") ~ ")" + tp match { + case tp: RefinedType => + val args = tp.typeArgs + if (args.nonEmpty) { + val tycon = tp.unrefine + val cls = tycon.typeSymbol + if (cls.typeParams.length == args.length) { + if (cls == defn.RepeatedParamClass) return toTextLocal(args.head) ~ "*" + if (cls == defn.ByNameParamClass) return "=> " ~ toTextGlobal(args.head) + if (defn.FunctionClasses contains cls) return toTextFunction(args) + if (defn.TupleClasses contains cls) return toTextTuple(args) + } + return (toTextLocal(tycon) ~ "[" ~ + Text(args.map(toTextGlobal(_)), ", ") ~ "]").close + } + case tp @ TypeRef(pre, name) => + if (tp.symbol is TypeParam) return nameString(tp.symbol) + case _ => + } + super.toText(tp, prec) + } + + override def toText[T >: Untyped](tree: Tree[T]): Text = super.toText(tree) + + override protected def polyParamName(name: TypeName): TypeName = + name.unexpandedName() + + override protected def treatAsTypeParam(sym: Symbol): Boolean = sym is TypeParam + + override protected def treatAsTypeArg(sym: Symbol) = + sym.isType && (sym is ProtectedLocal) && + (sym.allOverriddenSymbols exists (_ is TypeParam)) + + override protected def reconstituteParent(cls: ClassSymbol, parent: Type): Type = + (parent /: parent.classSymbol.typeParams) { (parent, tparam) => + val targSym = cls.decls.lookup(tparam.name) + if (targSym.exists) RefinedType(parent, targSym.name, targSym.info) + else parent + } + + override def kindString(sym: Symbol) = { + val flags = sym.flagsUNSAFE + if (flags is Package) "package" + else if (sym.isPackageObject) "package object" + else if (flags is Module) "object" + else if (flags is ImplClass) "class" + else if (sym.isClassConstructor) "constructor" + else super.kindString(sym) + } + + override protected def keyString(sym: Symbol): String = { + val flags = sym.flagsUNSAFE + if (sym.isType && (flags is ExpandedTypeParam)) "" + else super.keyString(sym) + } + + override def toTextFlags(sym: Symbol) = { + var flags = sym.flags + if (flags is TypeParam) flags = flags &~ Protected + Text(flags.flagStrings.filterNot(_.startsWith("<")) map stringToText, " ") + } + + override def toText(denot: Denotation): Text = toText(denot.symbol) + } + + final val maxToTextRecursions = 100 + +} diff --git a/src/dotty/tools/dotc/printing/Showable.scala b/src/dotty/tools/dotc/printing/Showable.scala new file mode 100644 index 000000000..73d320019 --- /dev/null +++ b/src/dotty/tools/dotc/printing/Showable.scala @@ -0,0 +1,14 @@ +package dotty.tools.dotc +package printing + +import core._ + +import Contexts._, Texts._, Decorators._ + +trait Showable { + + def toText(implicit ctx: Context): Text + + def show(implicit ctx: Context): String = toText.show + +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/printing/Texts.scala b/src/dotty/tools/dotc/printing/Texts.scala new file mode 100644 index 000000000..2ff4edc9a --- /dev/null +++ b/src/dotty/tools/dotc/printing/Texts.scala @@ -0,0 +1,153 @@ +package dotty.tools.dotc +package printing + +object Texts { + + abstract class Text { + + protected def indentMargin = 2 + + def relems: List[Text] + + def isEmpty: Boolean = this match { + case Str(s) => s.isEmpty + case Fluid(relems) => relems forall (_.isEmpty) + case Vertical(relems) => relems.isEmpty + } + + def isVertical = isInstanceOf[Vertical] + def isClosed = isVertical || isInstanceOf[Closed] + def isFluid = isInstanceOf[Fluid] + def isSplittable = isFluid && !isClosed + + def close = new Closed(relems) + + def remaining(width: Int): Int = this match { + case Str(s) => + width - s.length + case Fluid(Nil) => + width + case Fluid(last :: prevs) => + val r = last remaining width + if (r < 0) r else Fluid(prevs) remaining r + case Vertical(_) => + -1 + } + + def lastLine: String = this match { + case Str(s) => s + case _ => relems.head.lastLine + } + + def appendToLastLine(that: Text): Text = that match { + case Str(s2) => + this match { + case Str(s1) => Str(s1 + s2) + case Fluid(Str(s1) :: prev) => Fluid(Str(s1 + s2) :: prev) + case Fluid(relems) => Fluid(that :: relems) + } + case Fluid(relems) => + (this /: relems.reverse)(_ appendToLastLine _) + } + + private def appendIndented(that: Text)(width: Int): Text = + Vertical(that.layout(width - indentMargin).indented :: this.relems) + + private def append(width: Int)(that: Text): Text = { + if (this.isEmpty) that.layout(width) + else if (that.isEmpty) this + else if (that.isVertical) appendIndented(that)(width) + else if (this.isVertical) Fluid(that.layout(width) :: this.relems) + else if (that.remaining(width - lastLine.length) >= 0) appendToLastLine(that) + else if (that.isSplittable) (this /: that.relems.reverse)(_.append(width)(_)) + else appendIndented(that)(width) + } + + def layout(width: Int): Text = this match { + case Str(_) => + this + case Fluid(relems) => + ((Str(""): Text) /: relems.reverse)(_.append(width)(_)) + case Vertical(relems) => + Vertical(relems map (_ layout width)) + } + + def map(f: String => String): Text = this match { + case Str(s) => Str(f(s)) + case Fluid(relems) => Fluid(relems map (_ map f)) + case Vertical(relems) => Vertical(relems map (_ map f)) + } + + def stripPrefix(pre: String): Text = this match { + case Str(s) => + if (s.startsWith(pre)) s drop pre.length else s + case Fluid(relems) => + val elems = relems.reverse + val head = elems.head.stripPrefix(pre) + if (head eq elems.head) this else Fluid((head :: elems.tail).reverse) + case Vertical(relems) => + val elems = relems.reverse + val head = elems.head.stripPrefix(pre) + if (head eq elems.head) this else Vertical((head :: elems.tail).reverse) + } + + private def indented: Text = this match { + case Str(s) => Str((" " * indentMargin) + s) + case Fluid(relems) => Fluid(relems map (_.indented)) + case Vertical(relems) => Vertical(relems map (_.indented)) + } + + def print(sb: StringBuilder): Unit = this match { + case Str(s) => + sb.append(s) + case _ => + var follow = false + for (elem <- relems.reverse) { + if (follow) sb.append("\n") + elem.print(sb) + follow = true + } + } + + def mkString(width: Int): String = { + val sb = new StringBuilder + layout(width).print(sb) + sb.toString + } + + def ~ (that: Text) = + if (this.isEmpty) that + else if (that.isEmpty) this + else Fluid(that :: this :: Nil) + + def ~~ (that: Text) = + if (this.isEmpty) that + else if (that.isEmpty) this + else Fluid(that :: Str(" ") :: this :: Nil) + + def over (that: Text) = + if (this.isVertical) Vertical(that :: this.relems) + else Vertical(that :: this :: Nil) + } + + object Text { + def apply(): Text = Str("") + def apply(xs: Traversable[Text], sep: String = " "): Text = { + val ys = xs filterNot (_.isEmpty) + if (ys.isEmpty) Str("") + else ys reduce (_ ~ sep ~ _) + } + def lines(xs: Traversable[Text]) = Vertical(xs.toList.reverse) + } + + case class Str(s: String) extends Text { + override def relems: List[Text] = List(this) + } + + case class Vertical(relems: List[Text]) extends Text + case class Fluid(relems: List[Text]) extends Text + + class Closed(relems: List[Text]) extends Fluid(relems) + + implicit def stringToText(s: String): Text = Str(s) +}
\ No newline at end of file |