diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/printing')
10 files changed, 2129 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala new file mode 100644 index 000000000..b321d3736 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -0,0 +1,258 @@ +package dotty.tools.dotc +package printing + +import core._ +import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Contexts._ +import collection.mutable +import collection.Map +import Decorators._ +import scala.annotation.switch +import scala.util.control.NonFatal +import reporting.diagnostic.MessageContainer +import util.DiffUtil +import Highlighting._ +import SyntaxHighlighting._ + +object Formatting { + + /** General purpose string formatter, with the following features: + * + * 1) On all Showables, `show` is called instead of `toString` + * 2) Exceptions raised by a `show` are handled by falling back to `toString`. + * 3) Sequences can be formatted using the desired separator between two `%` signs, + * eg `i"myList = (${myList}%, %)"` + * 4) Safe handling of multi-line margins. Left margins are skipped om the parts + * of the string context *before* inserting the arguments. That way, we guard + * against accidentally treating an interpolated value as a margin. + */ + class StringFormatter(protected val sc: StringContext) { + + protected def showArg(arg: Any)(implicit ctx: Context): String = arg match { + case arg: Showable => + try arg.show + catch { + case NonFatal(ex) => s"[cannot display due to $ex, raw string = $toString]" + } + case _ => arg.toString + } + + private def treatArg(arg: Any, suffix: String)(implicit ctx: Context): (Any, String) = arg match { + case arg: Seq[_] if suffix.nonEmpty && suffix.head == '%' => + val (rawsep, rest) = suffix.tail.span(_ != '%') + val sep = StringContext.treatEscapes(rawsep) + if (rest.nonEmpty) (arg.map(showArg).mkString(sep), rest.tail) + else (arg, suffix) + case _ => + (showArg(arg), suffix) + } + + def assemble(args: Seq[Any])(implicit ctx: Context): String = { + def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak + def stripTrailingPart(s: String) = { + val (pre, post) = s.span(c => !isLineBreak(c)) + pre ++ post.stripMargin + } + val (prefix, suffixes) = sc.parts.toList match { + case head :: tail => (head.stripMargin, tail map stripTrailingPart) + case Nil => ("", Nil) + } + val (args1, suffixes1) = (args, suffixes).zipped.map(treatArg(_, _)).unzip + new StringContext(prefix :: suffixes1.toList: _*).s(args1: _*) + } + } + + /** The `em` string interpolator works like the `i` string interpolator, but marks nonsensical errors + * using `<nonsensical>...</nonsensical>` tags. + * Note: Instead of these tags, it would be nicer to return a data structure containing the message string + * and a boolean indicating whether the message is sensical, but then we cannot use string operations + * like concatenation, stripMargin etc on the values returned by em"...", and in the current error + * message composition methods, this is crucial. + */ + class ErrorMessageFormatter(sc: StringContext) extends StringFormatter(sc) { + override protected def showArg(arg: Any)(implicit ctx: Context): String = + wrapNonSensical(arg, super.showArg(arg)) + } + + class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { + override protected def showArg(arg: Any)(implicit ctx: Context): String = + arg match { + case arg: Showable if ctx.settings.color.value != "never" => + val highlighted = + SyntaxHighlighting(wrapNonSensical(arg, super.showArg(arg))) + new String(highlighted.toArray) + case hl: Highlight => + hl.show + case hb: HighlightBuffer => + hb.toString + case str: String if ctx.settings.color.value != "never" => + new String(SyntaxHighlighting(str).toArray) + case _ => super.showArg(arg) + } + } + + private def wrapNonSensical(arg: Any /* Type | Symbol */, str: String)(implicit ctx: Context): String = { + import MessageContainer._ + def isSensical(arg: Any): Boolean = arg match { + case tpe: Type => + tpe.exists && !tpe.isErroneous + case sym: Symbol if sym.isCompleted => + sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists + case _ => true + } + + if (isSensical(arg)) str + else nonSensicalStartTag + str + nonSensicalEndTag + } + + private type Recorded = AnyRef /*Symbol | PolyParam*/ + + private class Seen extends mutable.HashMap[String, List[Recorded]] { + + override def default(key: String) = Nil + + def record(str: String, entry: Recorded)(implicit ctx: Context): String = { + def followAlias(e1: Recorded): Recorded = e1 match { + case e1: Symbol if e1.isAliasType => + val underlying = e1.typeRef.underlyingClassRef(refinementOK = false).typeSymbol + if (underlying.name == e1.name) underlying else e1 + case _ => e1 + } + lazy val dealiased = followAlias(entry) + var alts = apply(str).dropWhile(alt => dealiased ne followAlias(alt)) + if (alts.isEmpty) { + alts = entry :: apply(str) + update(str, alts) + } + str + "'" * (alts.length - 1) + } + } + + private class ExplainingPrinter(seen: Seen)(_ctx: Context) extends RefinedPrinter(_ctx) { + override def simpleNameString(sym: Symbol): String = + if ((sym is ModuleClass) && sym.sourceModule.exists) simpleNameString(sym.sourceModule) + else seen.record(super.simpleNameString(sym), sym) + + override def polyParamNameString(param: PolyParam): String = + seen.record(super.polyParamNameString(param), param) + } + + /** Create explanation for single `Recorded` type or symbol */ + def explanation(entry: AnyRef)(implicit ctx: Context): String = { + def boundStr(bound: Type, default: ClassSymbol, cmp: String) = + if (bound.isRef(default)) "" else i"$cmp $bound" + + def boundsStr(bounds: TypeBounds): String = { + val lo = boundStr(bounds.lo, defn.NothingClass, ">:") + val hi = boundStr(bounds.hi, defn.AnyClass, "<:") + if (lo.isEmpty) hi + else if (hi.isEmpty) lo + else s"$lo and $hi" + } + + def addendum(cat: String, info: Type): String = info match { + case bounds @ TypeBounds(lo, hi) if bounds ne TypeBounds.empty => + if (lo eq hi) i" which is an alias of $lo" + else i" with $cat ${boundsStr(bounds)}" + case _ => + "" + } + + entry match { + case param: PolyParam => + s"is a type variable${addendum("constraint", ctx.typeComparer.bounds(param))}" + case sym: Symbol => + s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", sym.info)}" + } + } + + /** Turns a `Seen` into a `String` to produce an explanation for types on the + * form `where: T is...` + * + * @return string disambiguating types + */ + private def explanations(seen: Seen)(implicit ctx: Context): String = { + def needsExplanation(entry: Recorded) = entry match { + case param: PolyParam => ctx.typerState.constraint.contains(param) + case _ => false + } + + val toExplain: List[(String, Recorded)] = seen.toList.flatMap { + case (str, entry :: Nil) => + if (needsExplanation(entry)) (str, entry) :: Nil else Nil + case (str, entries) => + entries.map(alt => (seen.record(str, alt), alt)) + }.sortBy(_._1) + + def columnar(parts: List[(String, String)]): List[String] = { + lazy val maxLen = parts.map(_._1.length).max + parts.map { + case (leader, trailer) => + val variable = hl"$leader" + s"""$variable${" " * (maxLen - leader.length)} $trailer""" + } + } + + val explainParts = toExplain.map { case (str, entry) => (str, explanation(entry)) } + val explainLines = columnar(explainParts) + if (explainLines.isEmpty) "" else i"where: $explainLines%\n %\n" + } + + /** Context with correct printer set for explanations */ + private def explainCtx(seen: Seen)(implicit ctx: Context): Context = ctx.printer match { + case dp: ExplainingPrinter => + ctx // re-use outer printer and defer explanation to it + case _ => ctx.fresh.setPrinterFn(ctx => new ExplainingPrinter(seen)(ctx)) + } + + /** Entrypoint for explanation string interpolator: + * + * ``` + * ex"disambiguate $tpe1 and $tpe2" + * ``` + */ + def explained2(op: Context => String)(implicit ctx: Context): String = { + val seen = new Seen + op(explainCtx(seen)) ++ explanations(seen) + } + + /** When getting a type mismatch it is useful to disambiguate placeholders like: + * + * ``` + * found: List[Int] + * required: List[T] + * where: T is a type in the initalizer of value s which is an alias of + * String + * ``` + * + * @return the `where` section as well as the printing context for the + * placeholders - `("T is a...", printCtx)` + */ + def disambiguateTypes(args: Type*)(implicit ctx: Context): (String, Context) = { + val seen = new Seen + val printCtx = explainCtx(seen) + args.foreach(_.show(printCtx)) // showing each member will put it into `seen` + (explanations(seen), printCtx) + } + + /** This method will produce a colored type diff from the given arguments. + * The idea is to do this for known cases that are useful and then fall back + * on regular syntax highlighting for the cases which are unhandled. + * + * Please not that if used in combination with `disambiguateTypes` the + * correct `Context` for printing should also be passed when calling the + * method. + * + * @return the (found, expected, changePercentage) with coloring to + * highlight the difference + */ + def typeDiff(found: Type, expected: Type)(implicit ctx: Context): (String, String) = { + val fnd = wrapNonSensical(found, found.show) + val exp = wrapNonSensical(expected, expected.show) + + DiffUtil.mkColoredTypeDiff(fnd, exp) match { + case _ if ctx.settings.color.value == "never" => (fnd, exp) + case (fnd, exp, change) if change < 0.5 => (fnd, exp) + case _ => (fnd, exp) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/printing/Highlighting.scala b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala new file mode 100644 index 000000000..3bda7fb7a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala @@ -0,0 +1,77 @@ +package dotty.tools +package dotc +package printing + +import scala.collection.mutable +import core.Contexts.Context + +object Highlighting { + + implicit def highlightShow(h: Highlight)(implicit ctx: Context): String = + h.show + + abstract class Highlight(private val highlight: String) { + def text: String + + def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") text + else highlight + text + Console.RESET + + override def toString = + highlight + text + Console.RESET + + def +(other: Highlight)(implicit ctx: Context): HighlightBuffer = + new HighlightBuffer(this) + other + + def +(other: String)(implicit ctx: Context): HighlightBuffer = + new HighlightBuffer(this) + other + } + + abstract class Modifier(private val mod: String, text: String) extends Highlight(Console.RESET) { + override def show(implicit ctx: Context) = + if (ctx.settings.color.value == "never") "" + else mod + super.show + } + + case class HighlightBuffer(hl: Highlight)(implicit ctx: Context) { + val buffer = new mutable.ListBuffer[String] + + buffer += hl.show + + def +(other: Highlight): HighlightBuffer = { + buffer += other.show + this + } + + def +(other: String): HighlightBuffer = { + buffer += other + this + } + + override def toString = + buffer.mkString + } + + case class NoColor(text: String) extends Highlight(Console.RESET) + + case class Red(text: String) extends Highlight(Console.RED) + case class Blue(text: String) extends Highlight(Console.BLUE) + case class Cyan(text: String) extends Highlight(Console.CYAN) + case class Black(text: String) extends Highlight(Console.BLACK) + case class Green(text: String) extends Highlight(Console.GREEN) + case class White(text: String) extends Highlight(Console.WHITE) + case class Yellow(text: String) extends Highlight(Console.YELLOW) + case class Magenta(text: String) extends Highlight(Console.MAGENTA) + + case class RedB(text: String) extends Highlight(Console.RED_B) + case class BlueB(text: String) extends Highlight(Console.BLUE_B) + case class CyanB(text: String) extends Highlight(Console.CYAN_B) + case class BlackB(text: String) extends Highlight(Console.BLACK_B) + case class GreenB(text: String) extends Highlight(Console.GREEN_B) + case class WhiteB(text: String) extends Highlight(Console.WHITE_B) + case class YellowB(text: String) extends Highlight(Console.YELLOW_B) + case class MagentaB(text: String) extends Highlight(Console.MAGENTA_B) + + case class Bold(text: String) extends Modifier(Console.BOLD, text) + case class Underlined(text: String) extends Modifier(Console.UNDERLINED, text) +} diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala new file mode 100644 index 000000000..15c382bb0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -0,0 +1,500 @@ +package dotty.tools.dotc +package printing + +import core._ +import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, Denotations._ +import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation +import StdNames.{nme, tpnme} +import ast.Trees._, ast._ +import config.Config +import java.lang.Integer.toOctalString +import config.Config.summarizeDepth +import scala.annotation.switch + +class PlainPrinter(_ctx: Context) extends Printer { + protected[this] implicit def ctx: Context = _ctx.addMode(Mode.Printing) + + private var openRecs: List[RecType] = Nil + + protected def maxToTextRecursions = 100 + + protected final def controlled(op: => Text): Text = + if (ctx.toTextRecursions < maxToTextRecursions && ctx.toTextRecursions < maxSummarized) + try { + ctx.toTextRecursions += 1 + op + } finally { + ctx.toTextRecursions -= 1 + } + else { + if (ctx.toTextRecursions >= maxToTextRecursions) + recursionLimitExceeded() + "..." + } + + protected def recursionLimitExceeded() = { + ctx.warning("Exceeded recursion depth attempting to print.") + if (ctx.debug) Thread.dumpStack() + } + + /** If true, tweak output so it is the same before and after pickling */ + protected def homogenizedView: Boolean = ctx.settings.YtestPickler.value + + def homogenize(tp: Type): Type = + if (homogenizedView) + tp match { + case tp: ThisType if tp.cls.is(Package) && !tp.cls.isEffectiveRoot => + ctx.requiredPackage(tp.cls.fullName).termRef + case tp: TypeVar if tp.isInstantiated => + homogenize(tp.instanceOpt) + case AndType(tp1, tp2) => + homogenize(tp1) & homogenize(tp2) + case OrType(tp1, tp2) => + homogenize(tp1) | homogenize(tp2) + case tp: SkolemType => + homogenize(tp.info) + case tp: LazyRef => + homogenize(tp.ref) + case HKApply(tycon, args) => + tycon.dealias.appliedTo(args) + case _ => + tp + } + else tp + + private def selfRecName(n: Int) = s"z$n" + + /** Render elements alternating with `sep` string */ + protected def toText(elems: Traversable[Showable], sep: String) = + Text(elems map (_ toText this), sep) + + /** Render element within highest precedence */ + protected def toTextLocal(elem: Showable): Text = + atPrec(DotPrec) { elem.toText(this) } + + /** Render element within lowest precedence */ + protected def toTextGlobal(elem: Showable): Text = + atPrec(GlobalPrec) { elem.toText(this) } + + protected def toTextLocal(elems: Traversable[Showable], sep: String) = + atPrec(DotPrec) { toText(elems, sep) } + + protected def toTextGlobal(elems: Traversable[Showable], sep: String) = + atPrec(GlobalPrec) { toText(elems, sep) } + + /** 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.isTypeName) "/T" else "/V" + else "" + } + + def toText(name: Name): Text = Str(nameString(name)) + + /** 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 + + protected def argText(arg: Type): Text = arg match { + case arg: TypeBounds => "_" ~ toTextGlobal(arg) + case _ => toTextGlobal(arg) + } + + /** The longest sequence of refinement types, starting at given type + * and following parents. + */ + private def refinementChain(tp: Type): List[Type] = + tp :: (tp match { + case tp: RefinedType => refinementChain(tp.parent.stripTypeVar) + case _ => Nil + }) + + def toText(tp: Type): Text = controlled { + homogenize(tp) match { + case tp: TypeType => + toTextRHS(tp) + case tp: TermRef + if !tp.denotationIsCurrent && !homogenizedView || // always print underyling when testing picklers + tp.symbol.is(Module) || + tp.symbol.name.isImportName => + toTextRef(tp) ~ ".type" + case tp: TermRef if tp.denot.isOverloaded => + "<overloaded " ~ toTextRef(tp) ~ ">" + case tp: SingletonType => + toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")" + case tp: TypeRef => + toTextPrefix(tp.prefix) ~ selectionString(tp) + case tp: RefinedType => + val parent :: (refined: List[RefinedType @unchecked]) = + refinementChain(tp).reverse + toTextLocal(parent) ~ "{" ~ Text(refined map toTextRefinement, "; ").close ~ "}" + case tp: RecType => + try { + openRecs = tp :: openRecs + "{" ~ selfRecName(openRecs.length) ~ " => " ~ toTextGlobal(tp.parent) ~ "}" + } + finally openRecs = openRecs.tail + case AndType(tp1, tp2) => + changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) } + case OrType(tp1, tp2) => + changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) } + case ErrorType => + "<error>" + case tp: WildcardType => + if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?" + case NoType => + "<notype>" + case NoPrefix => + "<noprefix>" + case tp: MethodType => + def paramText(name: TermName, tp: Type) = toText(name) ~ ": " ~ toText(tp) + changePrec(GlobalPrec) { + (if (tp.isImplicit) "(implicit " else "(") ~ + Text((tp.paramNames, tp.paramTypes).zipped map paramText, ", ") ~ + ")" ~ toText(tp.resultType) + } + case tp: ExprType => + changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) } + case tp: PolyType => + def paramText(variance: Int, name: Name, bounds: TypeBounds): Text = + varianceString(variance) ~ name.toString ~ toText(bounds) + changePrec(GlobalPrec) { + "[" ~ Text((tp.variances, tp.paramNames, tp.paramBounds).zipped.map(paramText), ", ") ~ + "] => " ~ toTextGlobal(tp.resultType) + } + case tp: PolyParam => + polyParamNameString(tp) ~ polyHash(tp.binder) + case AnnotatedType(tpe, annot) => + toTextLocal(tpe) ~ " " ~ toText(annot) + case HKApply(tycon, args) => + toTextLocal(tycon) ~ "[" ~ Text(args.map(argText), ", ") ~ "]" + case tp: TypeVar => + if (tp.isInstantiated) + toTextLocal(tp.instanceOpt) ~ "^" // debug for now, so that we can see where the TypeVars are. + else { + val constr = ctx.typerState.constraint + val bounds = + if (constr.contains(tp)) constr.fullBounds(tp.origin)(ctx.addMode(Mode.Printing)) + else TypeBounds.empty + if (ctx.settings.YshowVarBounds.value) "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")" + else toText(tp.origin) + } + case tp: LazyRef => + "LazyRef(" ~ toTextGlobal(tp.ref) ~ ")" // TODO: only print this during debug mode? + case _ => + tp.fallbackToText(this) + } + }.close + + protected def polyParamNameString(name: TypeName): String = name.toString + + protected def polyParamNameString(param: PolyParam): String = polyParamNameString(param.binder.paramNames(param.paramNum)) + + /** 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) + + /** If -uniqid is set, the hashcode of the polytype, after a # */ + protected def polyHash(pt: PolyType): Text = + if (ctx.settings.uniqid.value) "#" + pt.hashCode else "" + + /** If -uniqid is set, 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) // + "<" + (if (sym.exists) sym.owner else "") + ">" + + def fullNameString(sym: Symbol): String = + if (sym.isRoot || sym == NoSymbol || sym.owner.isEffectiveRoot) + nameString(sym) + else + fullNameString(fullNameOwner(sym)) + "." + nameString(sym) + + protected def fullNameOwner(sym: Symbol): Symbol = sym.effectiveOwner.enclosingClass + + protected def objectPrefix = "object " + protected def packagePrefix = "package " + + protected def trimPrefix(text: Text) = + text.stripPrefix(objectPrefix).stripPrefix(packagePrefix) + + protected def selectionString(tp: NamedType) = { + val sym = if (homogenizedView) tp.symbol else tp.currentSymbol + if (sym.exists) nameString(sym) else nameString(tp.name) + } + + /** The string representation of this type used as a prefix */ + protected def toTextRef(tp: SingletonType): Text = controlled { + tp match { + case tp: TermRef => + toTextPrefix(tp.prefix) ~ selectionString(tp) + case tp: ThisType => + nameString(tp.cls) + ".this" + case SuperType(thistpe: SingletonType, _) => + toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super")) + case SuperType(thistpe, _) => + "Super(" ~ toTextGlobal(thistpe) ~ ")" + case tp @ ConstantType(value) => + toText(value) + case MethodParam(mt, idx) => + nameString(mt.paramNames(idx)) + case tp: RecThis => + val idx = openRecs.reverse.indexOf(tp.binder) + if (idx >= 0) selfRecName(idx + 1) + else "{...}.this" // TODO move underlying type to an addendum, e.g. ... z3 ... where z3: ... + case tp: SkolemType => + if (homogenizedView) toText(tp.info) else tp.repr + } + } + + /** The string representation of this type used as a prefix */ + protected def toTextPrefix(tp: Type): Text = controlled { + homogenize(tp) match { + case NoPrefix => "" + case tp: SingletonType => toTextRef(tp) ~ "." + case tp => trimPrefix(toTextLocal(tp)) ~ "#" + } + } + + protected def isOmittablePrefix(sym: Symbol): Boolean = + defn.UnqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym) + + protected def isEmptyPrefix(sym: Symbol): Boolean = + sym.isEffectiveRoot || sym.isAnonymousClass || sym.name.isReplWrapperName + + /** String representation of a definition's type following its name, + * if symbol is completed, "?" otherwise. + */ + protected def toTextRHS(optType: Option[Type]): Text = optType match { + case Some(tp) => toTextRHS(tp) + case None => "?" + } + + /** String representation of a definition's type following its name */ + protected def toTextRHS(tp: Type): Text = controlled { + homogenize(tp) match { + case tp @ TypeBounds(lo, hi) => + if (lo eq hi) { + val eql = + if (tp.variance == 1) " =+ " + else if (tp.variance == -1) " =- " + else " = " + eql ~ toText(lo) + } + else + (if (lo isRef defn.NothingClass) Text() else " >: " ~ toText(lo)) ~ + (if (hi isRef defn.AnyClass) Text() else " <: " ~ toText(hi)) + case tp @ ClassInfo(pre, cls, cparents, decls, selfInfo) => + val preText = toTextLocal(pre) + val (tparams, otherDecls) = decls.toList partition treatAsTypeParam + val tparamsText = + if (tparams.isEmpty) Text() else ("[" ~ dclsText(tparams) ~ "]").close + val selfText: Text = selfInfo match { + case NoType => Text() + case sym: Symbol if !sym.isCompleted => "this: ? =>" + case _ => "this: " ~ atPrec(InfixPrec) { toText(tp.selfType) } ~ " =>" + } + val trueDecls = otherDecls.filterNot(treatAsTypeArg) + val declsText = + if (trueDecls.isEmpty || !ctx.settings.debug.value) Text() + else dclsText(trueDecls) + tparamsText ~ " extends " ~ toTextParents(tp.parents) ~ "{" ~ selfText ~ declsText ~ + "} at " ~ preText + case tp => + ": " ~ toTextGlobal(tp) + } + } + + protected def toTextParents(parents: List[Type]): Text = Text(parents.map(toTextLocal), " with ") + + protected def treatAsTypeParam(sym: Symbol): Boolean = false + protected def treatAsTypeArg(sym: Symbol): Boolean = false + + /** 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.is(Method)) "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 JavaTrait) "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 is Method) "def" + else if (sym.isTerm && (!(flags is Param))) "val" + else "" + } + + /** String representation of symbol's flags */ + protected def toTextFlags(sym: Symbol): Text = + Text(sym.flagsUNSAFE.flagStrings map stringToText, " ") + + /** String representation of symbol's variance or "" if not applicable */ + protected def varianceString(sym: Symbol): String = varianceString(sym.variance) + + protected def varianceString(v: Int): String = v match { + case -1 => "-" + case 1 => "+" + case _ => "" + } + + def annotsText(sym: Symbol): Text = Text(sym.annotations.map(toText)) + + def dclText(sym: Symbol): Text = dclTextWithInfo(sym, sym.unforcedInfo) + + def dclText(d: SingleDenotation): Text = dclTextWithInfo(d.symbol, Some(d.info)) + + private def dclTextWithInfo(sym: Symbol, info: Option[Type]): Text = + (toTextFlags(sym) ~~ keyString(sym) ~~ + (varianceString(sym) ~ nameString(sym)) ~ toTextRHS(info)).close + + def toText(sym: Symbol): Text = + (kindString(sym) ~~ { + if (sym.isAnonymousClass) toText(sym.info.parents, " with ") ~ "{...}" + else if (hasMeaninglessName(sym)) simpleNameString(sym.owner) + idString(sym) + else nameString(sym) + }).close + + def locationText(sym: Symbol): Text = + if (!sym.exists) "" + else { + val ownr = sym.effectiveOwner + if (ownr.isClass && !isEmptyPrefix(ownr)) " in " ~ toText(ownr) else Text() + } + + def locatedText(sym: Symbol): Text = + (toText(sym) ~ locationText(sym)).close + + def extendedLocationText(sym: Symbol): Text = + if (!sym.exists) "" + else { + def recur(ownr: Symbol, innerLocation: String): Text = { + def nextOuter(innerKind: String): Text = + recur(ownr.effectiveOwner, + if (!innerLocation.isEmpty) innerLocation + else s" in an anonymous $innerKind") + def showLocation(ownr: Symbol, where: String): Text = + innerLocation ~ " " ~ where ~ " " ~ toText(ownr) + if (ownr.isAnonymousClass) nextOuter("class") + else if (ownr.isAnonymousFunction) nextOuter("function") + else if (isEmptyPrefix(ownr)) "" + else if (ownr.isLocalDummy) showLocation(ownr.owner, "locally defined in") + else if (ownr.isTerm && !ownr.is(Module | Method)) showLocation(ownr, "in the initalizer of") + else showLocation(ownr, "in") + } + recur(sym.owner, "") + } + + 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 => "\"" + escapedString(const.value.toString) + "\"" + case ClazzTag => "classOf[" ~ toText(const.typeValue.classSymbol) ~ "]" + 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 + + protected def escapedString(str: String): String = str flatMap escapedChar + + def dclsText(syms: List[Symbol], sep: String): Text = 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(this) + 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) + " | " ~ toText(tree.typeOpt) + else + Text() + + nodeName ~ "(" ~ elems ~ tpSuffix ~ ")" ~ node.pos.toString + case _ => + tree.fallbackToText(this) + } + }.close // todo: override in refined printer + + private var maxSummarized = Int.MaxValue + + def summarized[T](depth: Int)(op: => T): T = { + val saved = maxSummarized + maxSummarized = ctx.toTextRecursions + depth + try op + finally maxSummarized = depth + } + + def summarized[T](op: => T): T = summarized(summarizeDepth)(op) + + def plain = this +} + diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala new file mode 100644 index 000000000..14b63012e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -0,0 +1,105 @@ +package dotty.tools.dotc +package printing + +import core._ +import Texts._, ast.Trees._ +import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant, + Names.Name, Denotations._, Annotations.Annotation + +/** The base class of all printers + */ +abstract class Printer { + + private[this] var prec: Precedence = GlobalPrec + + /** The current precedence level */ + def currentPrecedence = prec + + /** Generate text using `op`, assuming a given precedence level `prec`. */ + def atPrec(prec: Precedence)(op: => Text): Text = { + val outerPrec = this.prec + this.prec = prec + try op + finally this.prec = outerPrec + } + + /** Generate text using `op`, assuming a given precedence level `prec`. + * If new level `prec` is lower than previous level, put text in parentheses. + */ + def changePrec(prec: Precedence)(op: => Text): Text = + if (prec < this.prec) atPrec(prec) ("(" ~ op ~ ")") else atPrec(prec)(op) + + /** 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 + + /** The name as a text */ + def toText(name: Name): Text + + /** 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 + + /** Textual representation of single denotation's declaration */ + def dclText(sd: SingleDenotation): 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 + + /** A description of sym's location */ + def extendedLocationText(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 */ + def toText(tp: Type): 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 + + /** Perform string or text-producing operation `op` so that only a + * summarized text with given recursion depth is shown + */ + def summarized[T](depth: Int)(op: => T): T + + /** A plain printer without any embellishments */ + def plain: Printer +} + diff --git a/compiler/src/dotty/tools/dotc/printing/Printers.scala b/compiler/src/dotty/tools/dotc/printing/Printers.scala new file mode 100644 index 000000000..36043a4ff --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Printers.scala @@ -0,0 +1,14 @@ +package dotty.tools.dotc +package printing + +import core.Contexts.Context + +trait Printers { this: Context => + + /** A function creating a printer */ + def printer = { + val pr = printerFn(this) + if (this.settings.YplainPrinter.value) pr.plain else pr + } +} + diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala new file mode 100644 index 000000000..29e1d4869 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -0,0 +1,652 @@ +package dotty.tools.dotc +package printing + +import core._ +import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._ +import TypeErasure.ErasedValueType +import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation +import StdNames.{nme, tpnme} +import ast.{Trees, untpd, tpd} +import typer.{Namer, Inliner} +import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType} +import Trees._ +import TypeApplications._ +import Decorators._ +import config.Config +import scala.annotation.switch +import language.implicitConversions + +class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { + + /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ + private var enclosingDef: untpd.Tree = untpd.EmptyTree + private var myCtx: Context = _ctx + private var printPos = ctx.settings.Yprintpos.value + override protected[this] implicit def ctx: Context = myCtx + + def withEnclosingDef(enclDef: Tree[_ >: Untyped])(op: => Text): Text = { + val savedCtx = myCtx + if (enclDef.hasType && enclDef.symbol.exists) + myCtx = ctx.withOwner(enclDef.symbol) + val savedDef = enclosingDef + enclosingDef = enclDef + try op finally { + myCtx = savedCtx + enclosingDef = savedDef + } + } + + def inPattern(op: => Text): Text = { + val savedCtx = myCtx + myCtx = ctx.addMode(Mode.Pattern) + try op finally myCtx = savedCtx + } + + def withoutPos(op: => Text): Text = { + val savedPrintPos = printPos + printPos = false + try op finally printPos = savedPrintPos + } + + private def enclDefIsClass = enclosingDef match { + case owner: TypeDef[_] => owner.isClassDef + case owner: untpd.ModuleDef => true + case _ => false + } + + override protected def recursionLimitExceeded() = {} + + protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags + + override def nameString(name: Name): String = name.decode.toString + + override protected def simpleNameString(sym: Symbol): String = { + val name = sym.originalName + nameString(if (sym is ExpandedTypeParam) name.asTypeName.unexpandedName else name) + } + + override def fullNameString(sym: Symbol): String = + if (isEmptyPrefix(sym.maybeOwner)) nameString(sym) + else super.fullNameString(sym) + + override protected def fullNameOwner(sym: Symbol) = { + val owner = super.fullNameOwner(sym) + if (owner is ModuleClass) owner.sourceModule else owner + } + + override def toTextRef(tp: SingletonType): Text = controlled { + tp match { + case tp: ThisType => + if (tp.cls.isAnonymousClass) return "this" + if (tp.cls is ModuleClass) return fullNameString(tp.cls.sourceModule) + case _ => + } + super.toTextRef(tp) + } + + override def toTextPrefix(tp: Type): Text = controlled { + def isOmittable(sym: Symbol) = + if (ctx.settings.verbose.value) false + else if (homogenizedView) isEmptyPrefix(sym) // drop <root> and anonymous classes, but not scala, Predef. + else isOmittablePrefix(sym) + tp match { + case tp: ThisType => + if (isOmittable(tp.cls)) return "" + case tp @ TermRef(pre, _) => + val sym = tp.symbol + if (sym.isPackageObject) return toTextPrefix(pre) + if (isOmittable(sym)) return "" + case _ => + } + super.toTextPrefix(tp) + } + + override protected def refinementNameString(tp: RefinedType): String = + if (tp.parent.isInstanceOf[WildcardType] || tp.refinedName == nme.WILDCARD) + super.refinementNameString(tp) + else { + val tsym = tp.parent.member(tp.refinedName).symbol + if (!tsym.exists) super.refinementNameString(tp) + else simpleNameString(tsym) + } + + override def toText(tp: Type): Text = controlled { + def toTextTuple(args: List[Type]): Text = + "(" ~ Text(args.map(argText), ", ") ~ ")" + def toTextFunction(args: List[Type]): Text = + changePrec(GlobalPrec) { + val argStr: Text = + if (args.length == 2 && !defn.isTupleType(args.head)) + atPrec(InfixPrec) { argText(args.head) } + else + toTextTuple(args.init) + argStr ~ " => " ~ argText(args.last) + } + homogenize(tp) match { + case AppliedType(tycon, args) => + val cls = tycon.typeSymbol + if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*" + if (defn.isFunctionClass(cls)) return toTextFunction(args) + if (defn.isTupleClass(cls)) return toTextTuple(args) + return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close + case tp: TypeRef => + val hideType = tp.symbol is AliasPreferred + if (hideType && !ctx.phase.erasedTypes && !tp.symbol.isCompleting) { + tp.info match { + case TypeAlias(alias) => return toText(alias) + case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol) + } + } + else if (tp.symbol.isAnonymousClass && !ctx.settings.uniqid.value) + return toText(tp.info) + case ExprType(result) => + return "=> " ~ toText(result) + case ErasedValueType(tycon, underlying) => + return "ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")" + case tp: ClassInfo => + return toTextParents(tp.parentsWithArgs) ~ "{...}" + case JavaArrayType(elemtp) => + return toText(elemtp) ~ "[]" + case tp: AnnotatedType if homogenizedView => + // Positions of annotations in types are not serialized + // (they don't need to because we keep the original type tree with + // the original annotation anyway. Therefore, there will always be + // one version of the annotation tree that has the correct positions). + withoutPos(super.toText(tp)) + case tp: SelectionProto => + return "?{ " ~ toText(tp.name) ~ (" " provided !tp.name.decode.last.isLetterOrDigit) ~ + ": " ~ toText(tp.memberProto) ~ " }" + case tp: ViewProto => + return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType) + case tp @ FunProto(args, resultType, _) => + val argsText = args match { + case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp) + case _ => toTextGlobal(args, ", ") + } + return "FunProto(" ~ argsText ~ "):" ~ toText(resultType) + case tp: IgnoredProto => + return "?" + case _ => + } + super.toText(tp) + } + + def blockText[T >: Untyped](trees: List[Tree[T]]): Text = + ("{" ~ toText(trees, "\n") ~ "}").close + + override def toText[T >: Untyped](tree: Tree[T]): Text = controlled { + + import untpd.{modsDeco => _, _} + + /** Print modifiers from symbols if tree has type, overriding the untpd behavior. */ + implicit def modsDeco(mdef: untpd.MemberDef)(implicit ctx: Context): untpd.ModsDecorator = + new untpd.ModsDecorator { + def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods + } + + def Modifiers(sym: Symbol)(implicit ctx: Context): Modifiers = untpd.Modifiers( + sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags), + if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY, + sym.annotations map (_.tree)) + + def isLocalThis(tree: Tree) = tree.typeOpt match { + case tp: ThisType => tp.cls == ctx.owner.enclosingClass + case _ => false + } + + def optDotPrefix(tree: This) = optText(tree.qual)(_ ~ ".") provided !isLocalThis(tree) + + def optAscription(tpt: untpd.Tree) = optText(tpt)(": " ~ _) + // Dotty deviation: called with an untpd.Tree, so cannot be a untpd.Tree[T] (seems to be a Scala2 problem to allow this) + // More deviations marked below as // DD + + def tparamsText[T >: Untyped](params: List[Tree]): Text = + "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty + + def addVparamssText(txt: Text, vparamss: List[List[ValDef]]): Text = + (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")") + + def caseBlockText(tree: Tree): Text = tree match { + case Block(stats, expr) => toText(stats :+ expr, "\n") + case expr => toText(expr) + } + + def enumText(tree: untpd.Tree) = tree match { // DD + case _: untpd.GenFrom | _: untpd.GenAlias => toText(tree) + case _ => "if " ~ toText(tree) + } + + def forText(enums: List[untpd.Tree], expr: untpd.Tree, sep: String): Text = // DD + changePrec(GlobalPrec) { "for " ~ Text(enums map enumText, "; ") ~ sep ~ toText(expr) } + + def cxBoundToText(bound: untpd.Tree): Text = bound match { // DD + case AppliedTypeTree(tpt, _) => " : " ~ toText(tpt) + case untpd.Function(_, tpt) => " <% " ~ toText(tpt) + } + + def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix("new ") // DD + + def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD + + def useSymbol = + tree.hasType && tree.symbol.exists && ctx.settings.YprintSyms.value + + def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param + var flagMask = + if (ctx.settings.debugFlags.value) AnyFlags + else if (suppressKw) PrintableFlags &~ Private + else PrintableFlags + if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes + val flagsText = (mods.flags & flagMask).toString + Text(mods.annotations.map(annotText), " ") ~~ flagsText ~~ (kw provided !suppressKw) + } + + def varianceText(mods: untpd.Modifiers) = + if (mods is Covariant) "+" + else if (mods is Contravariant) "-" + else "" + + def argText(arg: Tree): Text = arg match { + case arg: TypeBoundsTree => "_" ~ toTextGlobal(arg) + case arg: TypeTree => + arg.typeOpt match { + case tp: TypeBounds => "_" ~ toTextGlobal(arg) + case _ => toTextGlobal(arg) + } + case _ => toTextGlobal(arg) + } + + def dclTextOr(treeText: => Text) = + if (useSymbol) + annotsText(tree.symbol) ~~ dclText(tree.symbol) ~ + ( " <in " ~ toText(tree.symbol.owner) ~ ">" provided ctx.settings.debugOwners.value) + else treeText + + def idText(tree: untpd.Tree): Text = { + if (ctx.settings.uniqid.value && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else "" + } + + def nameIdText(tree: untpd.NameTree): Text = + if (tree.hasType && tree.symbol.exists) nameString(tree.symbol) + else toText(tree.name) ~ idText(tree) + + def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { + val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl + val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } + val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil + val prefix: Text = + if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt + else { + var modsText = modText(constr.mods, "") + if (!modsText.isEmpty) modsText = " " ~ modsText + if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" + withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } + } + val parentsText = Text(parents map constrText, " with ") + val selfText = { + val selfName = if (self.name == nme.WILDCARD) "this" else self.name.toString + (selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close + } provided !self.isEmpty + val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: impl.body, "\n") ~ "}" + prefix ~ (" extends" provided !ofNew) ~~ parentsText ~~ bodyText + } + + def toTextPackageId(pid: Tree): Text = + if (homogenizedView && pid.hasType) toTextLocal(pid.tpe) + else toTextLocal(pid) + + def toTextCore(tree: Tree): Text = tree match { + case id: Trees.BackquotedIdent[_] if !homogenizedView => + "`" ~ toText(id.name) ~ "`" + case Ident(name) => + tree.typeOpt match { + case tp: NamedType if name != nme.WILDCARD => + val pre = if (tp.symbol is JavaStatic) tp.prefix.widen else tp.prefix + toTextPrefix(pre) ~ selectionString(tp) + case _ => + toText(name) + } + case tree @ Select(qual, name) => + if (qual.isType) toTextLocal(qual) ~ "#" ~ toText(name) + else toTextLocal(qual) ~ ("." ~ nameIdText(tree) provided name != nme.CONSTRUCTOR) + case tree: This => + optDotPrefix(tree) ~ "this" ~ idText(tree) + case Super(qual: This, mix) => + optDotPrefix(qual) ~ "super" ~ optText(mix)("[" ~ _ ~ "]") + case Apply(fun, args) => + if (fun.hasType && fun.symbol == defn.throwMethod) + changePrec (GlobalPrec) { + "throw " ~ toText(args.head) + } + else + toTextLocal(fun) ~ "(" ~ toTextGlobal(args, ", ") ~ ")" + case TypeApply(fun, args) => + toTextLocal(fun) ~ "[" ~ toTextGlobal(args, ", ") ~ "]" + case Literal(c) => + tree.typeOpt match { + case ConstantType(tc) => toText(tc) + case _ => toText(c) + } + case New(tpt) => + "new " ~ { + tpt match { + case tpt: Template => toTextTemplate(tpt, ofNew = true) + case _ => + if (tpt.hasType) + toTextLocal(tpt.typeOpt.underlyingClassRef(refinementOK = false)) + else + toTextLocal(tpt) + } + } + case Typed(expr, tpt) => + changePrec(InfixPrec) { toText(expr) ~ ": " ~ toText(tpt) } + case NamedArg(name, arg) => + toText(name) ~ " = " ~ toText(arg) + case Assign(lhs, rhs) => + changePrec(GlobalPrec) { toTextLocal(lhs) ~ " = " ~ toText(rhs) } + case Block(stats, expr) => + blockText(stats :+ expr) + case If(cond, thenp, elsep) => + changePrec(GlobalPrec) { + "if " ~ toText(cond) ~ (" then" provided !cond.isInstanceOf[Parens]) ~~ toText(thenp) ~ optText(elsep)(" else " ~ _) + } + case Closure(env, ref, target) => + "closure(" ~ (toTextGlobal(env, ", ") ~ " | " provided env.nonEmpty) ~ + toTextGlobal(ref) ~ (":" ~ toText(target) provided !target.isEmpty) ~ ")" + case Match(sel, cases) => + if (sel.isEmpty) blockText(cases) + else changePrec(GlobalPrec) { toText(sel) ~ " match " ~ blockText(cases) } + case CaseDef(pat, guard, body) => + "case " ~ inPattern(toText(pat)) ~ optText(guard)(" if " ~ _) ~ " => " ~ caseBlockText(body) + case Return(expr, from) => + changePrec(GlobalPrec) { "return" ~ optText(expr)(" " ~ _) } + case Try(expr, cases, finalizer) => + changePrec(GlobalPrec) { + "try " ~ toText(expr) ~ optText(cases)(" catch " ~ _) ~ optText(finalizer)(" finally " ~ _) + } + case Throw(expr) => + changePrec(GlobalPrec) { + "throw " ~ toText(expr) + } + case SeqLiteral(elems, elemtpt) => + "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" + case tree @ Inlined(call, bindings, body) => + (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView) ~ + blockText(bindings :+ body) + case tpt: untpd.DerivedTypeTree => + "<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">" + case TypeTree() => + toText(tree.typeOpt) + case SingletonTypeTree(ref) => + toTextLocal(ref) ~ ".type" + case AndTypeTree(l, r) => + changePrec(AndPrec) { toText(l) ~ " & " ~ toText(r) } + case OrTypeTree(l, r) => + changePrec(OrPrec) { toText(l) ~ " | " ~ toText(r) } + case RefinedTypeTree(tpt, refines) => + toTextLocal(tpt) ~ " " ~ blockText(refines) + case AppliedTypeTree(tpt, args) => + toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]" + case PolyTypeTree(tparams, body) => + changePrec(GlobalPrec) { + tparamsText(tparams) ~ " -> " ~ toText(body) + } + case ByNameTypeTree(tpt) => + "=> " ~ toTextLocal(tpt) + case TypeBoundsTree(lo, hi) => + optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _) + case Bind(name, body) => + changePrec(InfixPrec) { toText(name) ~ " @ " ~ toText(body) } + case Alternative(trees) => + changePrec(OrPrec) { toText(trees, " | ") } + case UnApply(fun, implicits, patterns) => + val extractor = fun match { + case Select(extractor, nme.unapply) => extractor + case _ => fun + } + toTextLocal(extractor) ~ + "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~ + ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty) + case tree @ ValDef(name, tpt, _) => + dclTextOr { + modText(tree.mods, if (tree.mods is Mutable) "var" else "val") ~~ + nameIdText(tree) ~ optAscription(tpt) ~ + withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } + } + case tree @ DefDef(name, tparams, vparamss, tpt, _) => + dclTextOr { + val prefix = modText(tree.mods, "def") ~~ nameIdText(tree) + withEnclosingDef(tree) { + addVparamssText(prefix ~ tparamsText(tparams), vparamss) ~ optAscription(tpt) ~ + optText(tree.rhs)(" = " ~ _) + } + } + case tree @ TypeDef(name, rhs) => + def typeDefText(tparamsText: => Text, rhsText: => Text) = + dclTextOr { + modText(tree.mods, "type") ~~ (varianceText(tree.mods) ~ nameIdText(tree)) ~ + withEnclosingDef(tree) { + if (tree.hasType) toText(tree.symbol.info) // TODO: always print RHS, once we pickle/unpickle type trees + else tparamsText ~ rhsText + } + } + def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { + case impl: Template => + modText(tree.mods, if ((tree).mods is Trait) "trait" else "class") ~~ + nameIdText(tree) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ + (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") + case rhs: TypeBoundsTree => + typeDefText(tparamsTxt, toText(rhs)) + case PolyTypeTree(tparams, body) => + recur(body, tparamsText(tparams)) + case rhs => + typeDefText(tparamsTxt, optText(rhs)(" = " ~ _)) + } + recur(rhs, "") + case Import(expr, selectors) => + def selectorText(sel: Tree): Text = sel match { + case Thicket(l :: r :: Nil) => toTextGlobal(l) ~ " => " ~ toTextGlobal(r) + case _ => toTextGlobal(sel) + } + val selectorsText: Text = selectors match { + case id :: Nil => toText(id) + case _ => "{" ~ Text(selectors map selectorText, ", ") ~ "}" + } + "import " ~ toTextLocal(expr) ~ "." ~ selectorsText + case PackageDef(pid, stats) => + val statsText = stats match { + case (pdef: PackageDef) :: Nil => toText(pdef) + case _ => toTextGlobal(stats, "\n") + } + val bodyText = + if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}" + "package " ~ toTextPackageId(pid) ~ bodyText + case tree: Template => + toTextTemplate(tree) + case Annotated(arg, annot) => + toTextLocal(arg) ~~ annotText(annot) + case EmptyTree => + "<empty>" + case TypedSplice(t) => + toText(t) + case tree @ ModuleDef(name, impl) => + withEnclosingDef(tree) { + modText(tree.mods, "object") ~~ nameIdText(tree) ~ toTextTemplate(impl) + } + case SymbolLit(str) => + "'" + str + case InterpolatedString(id, segments) => + def strText(str: Literal) = Str(escapedString(str.const.stringValue)) + def segmentText(segment: Tree) = segment match { + case Thicket(List(str: Literal, expr)) => strText(str) ~ "{" ~ toTextGlobal(expr) ~ "}" + case str: Literal => strText(str) + } + toText(id) ~ "\"" ~ Text(segments map segmentText, "") ~ "\"" + case Function(args, body) => + var implicitSeen: Boolean = false + def argToText(arg: Tree) = arg match { + case arg @ ValDef(name, tpt, _) => + val implicitText = + if ((arg.mods is Implicit) && !implicitSeen) { implicitSeen = true; "implicit " } + else "" + implicitText ~ toText(name) ~ optAscription(tpt) + case _ => + toText(arg) + } + val argsText = args match { + case (arg @ ValDef(_, tpt, _)) :: Nil if tpt.isEmpty => argToText(arg) + case _ => "(" ~ Text(args map argToText, ", ") ~ ")" + } + changePrec(GlobalPrec) { + argsText ~ " => " ~ toText(body) + } + case InfixOp(l, op, r) => + val opPrec = parsing.precedence(op) + changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) } + case PostfixOp(l, op) => + changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) } + case PrefixOp(op, r) => + changePrec(DotPrec) { toText(op) ~ " " ~ toText(r) } + case Parens(t) => + "(" ~ toTextGlobal(t) ~ ")" + case Tuple(ts) => + "(" ~ toTextGlobal(ts, ", ") ~ ")" + case WhileDo(cond, body) => + changePrec(GlobalPrec) { "while " ~ toText(cond) ~ " do " ~ toText(body) } + case DoWhile(cond, body) => + changePrec(GlobalPrec) { "do " ~ toText(body) ~ " while " ~ toText(cond) } + case ForYield(enums, expr) => + forText(enums, expr, " yield ") + case ForDo(enums, expr) => + forText(enums, expr, " do ") + case GenFrom(pat, expr) => + toText(pat) ~ " <- " ~ toText(expr) + case GenAlias(pat, expr) => + toText(pat) ~ " = " ~ toText(expr) + case ContextBounds(bounds, cxBounds) => + (toText(bounds) /: cxBounds) {(t, cxb) => + t ~ cxBoundToText(cxb) + } + case PatDef(mods, pats, tpt, rhs) => + modText(mods, "val") ~~ toText(pats, ", ") ~ optAscription(tpt) ~ + optText(rhs)(" = " ~ _) + case ParsedTry(expr, handler, finalizer) => + changePrec(GlobalPrec) { + "try " ~ toText(expr) ~ " catch {" ~ toText(handler) ~ "}" ~ optText(finalizer)(" finally " ~ _) + } + case Thicket(trees) => + "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" + case _ => + tree.fallbackToText(this) + } + + var txt = toTextCore(tree) + + def suppressTypes = + tree.isType || tree.isDef || // don't print types of types or defs + homogenizedView && ctx.mode.is(Mode.Pattern) + // When comparing pickled info, disregard types of patterns. + // The reason is that GADT matching can rewrite types of pattern trees + // without changing the trees themselves. (see Typer.typedCase.indexPatterns.transform). + // But then pickling and unpickling the original trees will yield trees + // with the original types before they are rewritten, which causes a discrepancy. + + def suppressPositions = tree match { + case _: WithoutTypeOrPos[_] | _: TypeTree => true // TypeTrees never have an interesting position + case _ => false + } + + if (ctx.settings.printtypes.value && tree.hasType) { + // add type to term nodes; replace type nodes with their types unless -Yprintpos is also set. + def tp = tree.typeOpt match { + case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying + case tp => tp + } + if (!suppressTypes) + txt = ("<" ~ txt ~ ":" ~ toText(tp) ~ ">").close + else if (tree.isType && !homogenizedView) + txt = toText(tp) + } + if (printPos && !suppressPositions) { + // add positions + val pos = + if (homogenizedView && !tree.isInstanceOf[MemberDef]) tree.pos.toSynthetic + else tree.pos + val clsStr = ""//if (tree.isType) tree.getClass.toString else "" + txt = (txt ~ "@" ~ pos.toString ~ clsStr).close + } + tree match { + case Block(_, _) | Template(_, _, _, _) => txt + case _ => txt.close + } + } + + def optText(name: Name)(encl: Text => Text): Text = + if (name.isEmpty) "" else encl(toText(name)) + + def optText[T >: Untyped](tree: Tree[T])(encl: Text => Text): Text = + if (tree.isEmpty) "" else encl(toText(tree)) + + def optText[T >: Untyped](tree: List[Tree[T]])(encl: Text => Text): Text = + if (tree.exists(!_.isEmpty)) encl(blockText(tree)) else "" + + override protected def polyParamNameString(name: TypeName): String = + name.unexpandedName.toString + + 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 def toText(sym: Symbol): Text = { + if (sym.isImport) { + def importString(tree: untpd.Tree) = s"import ${tree.show}" + sym.infoOrCompleter match { + case info: Namer#Completer => return importString(info.original) + case info: ImportType => return importString(info.expr) + case _ => + } + } + if (sym.is(ModuleClass)) + kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) + else + super.toText(sym) + } + + 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 && sym.owner.isTerm) "" + else super.keyString(sym) + } + + override def toTextFlags(sym: Symbol) = + if (ctx.settings.debugFlags.value) + super.toTextFlags(sym) + else { + var flags = sym.flagsUNSAFE + if (flags is TypeParam) flags = flags &~ Protected + Text((flags & PrintableFlags).flagStrings map stringToText, " ") + } + + override def toText(denot: Denotation): Text = denot match { + case denot: MultiDenotation => Text(denot.alternatives.map(dclText), " <and> ") + case NoDenotation => "NoDenotation" + case _ => + if (denot.symbol.exists) toText(denot.symbol) + else "some " ~ toText(denot.info) + } + + override def plain = new PlainPrinter(_ctx) +} diff --git a/compiler/src/dotty/tools/dotc/printing/Showable.scala b/compiler/src/dotty/tools/dotc/printing/Showable.scala new file mode 100644 index 000000000..efddb26f7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Showable.scala @@ -0,0 +1,34 @@ +package dotty.tools.dotc +package printing + +import core._ + +import Contexts._, Texts._, Decorators._ +import config.Config.summarizeDepth +import scala.util.control.NonFatal + +trait Showable extends Any { + + /** The text representation of this showable element. + * This normally dispatches to a pattern matching + * method in Printers. + */ + def toText(printer: Printer): Text + + /** A fallback text representation, if the pattern matching + * in Printers does not have a case for this showable element + */ + def fallbackToText(printer: Printer): Text = toString + + /** The string representation of this showable element. */ + def show(implicit ctx: Context): String = toText(ctx.printer).show + + /** The summarized string representation of this showable element. + * Recursion depth is limited to some smallish value. Default is + * Config.summarizeDepth. + */ + def showSummary(depth: Int)(implicit ctx: Context): String = + ctx.printer.summarized(depth)(show) + + def showSummary(implicit ctx: Context): String = showSummary(summarizeDepth) +} diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala new file mode 100644 index 000000000..86f34e64d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -0,0 +1,304 @@ +package dotty.tools +package dotc +package printing + +import parsing.Tokens._ +import scala.annotation.switch +import scala.collection.mutable.StringBuilder +import core.Contexts.Context +import Highlighting.{Highlight, HighlightBuffer} + +/** This object provides functions for syntax highlighting in the REPL */ +object SyntaxHighlighting { + + val NoColor = Console.RESET + val CommentColor = Console.BLUE + val KeywordColor = Console.YELLOW + val ValDefColor = Console.CYAN + val LiteralColor = Console.RED + val TypeColor = Console.MAGENTA + val AnnotationColor = Console.MAGENTA + + private def none(str: String) = str + private def keyword(str: String) = KeywordColor + str + NoColor + private def typeDef(str: String) = TypeColor + str + NoColor + private def literal(str: String) = LiteralColor + str + NoColor + private def valDef(str: String) = ValDefColor + str + NoColor + private def operator(str: String) = TypeColor + str + NoColor + private def annotation(str: String) = + if (str.trim == "@") str else AnnotationColor + str + NoColor + private val tripleQs = Console.RED_B + "???" + NoColor + + private val keywords: Seq[String] = for { + index <- IF to INLINE // All alpha keywords + } yield tokenString(index) + + private val interpolationPrefixes = + 'A' :: 'B' :: 'C' :: 'D' :: 'E' :: 'F' :: 'G' :: 'H' :: 'I' :: 'J' :: 'K' :: + 'L' :: 'M' :: 'N' :: 'O' :: 'P' :: 'Q' :: 'R' :: 'S' :: 'T' :: 'U' :: 'V' :: + 'W' :: 'X' :: 'Y' :: 'Z' :: '$' :: '_' :: 'a' :: 'b' :: 'c' :: 'd' :: 'e' :: + 'f' :: 'g' :: 'h' :: 'i' :: 'j' :: 'k' :: 'l' :: 'm' :: 'n' :: 'o' :: 'p' :: + 'q' :: 'r' :: 's' :: 't' :: 'u' :: 'v' :: 'w' :: 'x' :: 'y' :: 'z' :: Nil + + private val typeEnders = + '{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' :: + '\n' :: Nil + + def apply(chars: Iterable[Char]): Iterable[Char] = { + var prev: Char = 0 + var remaining = chars.toStream + val newBuf = new StringBuilder + var lastToken = "" + + @inline def keywordStart = + prev == 0 || prev == ' ' || prev == '{' || prev == '(' || + prev == '\n' || prev == '[' || prev == ',' + + @inline def numberStart(c: Char) = + c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000') + + def takeChar(): Char = takeChars(1).head + def takeChars(x: Int): Seq[Char] = { + val taken = remaining.take(x) + remaining = remaining.drop(x) + taken + } + + while (remaining.nonEmpty) { + val n = takeChar() + if (interpolationPrefixes.contains(n)) { + // Interpolation prefixes are a superset of the keyword start chars + val next = remaining.take(3).mkString + if (next.startsWith("\"")) { + newBuf += n + prev = n + if (remaining.nonEmpty) takeChar() // drop 1 for appendLiteral + appendLiteral('"', next == "\"\"\"") + } else { + if (n.isUpper && keywordStart) { + appendWhile(n, !typeEnders.contains(_), typeDef) + } else if (keywordStart) { + append(n, keywords.contains(_), { kw => + if (kw == "new") typeDef(kw) else keyword(kw) + }) + } else { + newBuf += n + prev = n + } + } + } else { + (n: @switch) match { + case '/' => + if (remaining.nonEmpty) { + remaining.head match { + case '/' => + takeChar() + eolComment() + case '*' => + takeChar() + blockComment() + case x => + newBuf += '/' + } + } else newBuf += '/' + case '=' => + append('=', _ == "=>", operator) + case '<' => + append('<', { x => x == "<-" || x == "<:" || x == "<%" }, operator) + case '>' => + append('>', { x => x == ">:" }, operator) + case '#' => + if (prev != ' ' && prev != '.') newBuf append operator("#") + else newBuf += n + prev = '#' + case '@' => + appendWhile('@', !typeEnders.contains(_), annotation) + case '\"' => + appendLiteral('\"', multiline = remaining.take(2).mkString == "\"\"") + case '\'' => + appendLiteral('\'') + case '`' => + appendTo('`', _ == '`', none) + case _ => { + if (n == '?' && remaining.take(2).mkString == "??") { + takeChars(2) + newBuf append tripleQs + prev = '?' + } else if (n.isUpper && keywordStart) + appendWhile(n, !typeEnders.contains(_), typeDef) + else if (numberStart(n)) + appendWhile(n, { x => x.isDigit || x == '.' || x == '\u0000'}, literal) + else + newBuf += n; prev = n + } + } + } + } + + def eolComment() = { + newBuf append (CommentColor + "//") + var curr = '/' + while (curr != '\n' && remaining.nonEmpty) { + curr = takeChar() + newBuf += curr + } + prev = curr + newBuf append NoColor + } + + def blockComment() = { + newBuf append (CommentColor + "/*") + var curr = '*' + var open = 1 + while (open > 0 && remaining.nonEmpty) { + curr = takeChar() + newBuf += curr + + if (curr == '*' && remaining.nonEmpty) { + curr = takeChar() + newBuf += curr + if (curr == '/') open -= 1 + } else if (curr == '/' && remaining.nonEmpty) { + curr = takeChar() + newBuf += curr + if (curr == '*') open += 1 + } + } + prev = curr + newBuf append NoColor + } + + def appendLiteral(delim: Char, multiline: Boolean = false) = { + var curr: Char = 0 + var continue = true + var closing = 0 + val inInterpolation = interpolationPrefixes.contains(prev) + newBuf append (LiteralColor + delim) + + def shouldInterpolate = + inInterpolation && curr == '$' && prev != '$' && remaining.nonEmpty + + def interpolate() = { + val next = takeChar() + if (next == '$') { + newBuf += curr + newBuf += next + prev = '$' + } else if (next == '{') { + var open = 1 // keep track of open blocks + newBuf append (ValDefColor + curr) + newBuf += next + while (remaining.nonEmpty && open > 0) { + var c = takeChar() + newBuf += c + if (c == '}') open -= 1 + else if (c == '{') open += 1 + } + newBuf append LiteralColor + } else { + newBuf append (ValDefColor + curr) + newBuf += next + var c: Char = 'a' + while (c.isLetterOrDigit && remaining.nonEmpty) { + c = takeChar() + if (c != '"') newBuf += c + } + newBuf append LiteralColor + if (c == '"') { + newBuf += c + continue = false + } + } + closing = 0 + } + + while (continue && remaining.nonEmpty) { + curr = takeChar() + if (curr == '\\' && remaining.nonEmpty) { + val next = takeChar() + newBuf append (KeywordColor + curr) + if (next == 'u') { + val code = "u" + takeChars(4).mkString + newBuf append code + } else newBuf += next + newBuf append LiteralColor + closing = 0 + } else if (shouldInterpolate) { + interpolate() + } else if (curr == delim && multiline) { + closing += 1 + if (closing == 3) continue = false + newBuf += curr + } else if (curr == delim) { + continue = false + newBuf += curr + } else { + newBuf += curr + closing = 0 + } + } + newBuf append NoColor + prev = curr + } + + def append(c: Char, shouldHL: String => Boolean, highlight: String => String) = { + var curr: Char = 0 + val sb = new StringBuilder(s"$c") + + def delim(c: Char) = (c: @switch) match { + case ' ' => true + case '\n' => true + case '(' => true + case '[' => true + case ':' => true + case '@' => true + case _ => false + } + + while (remaining.nonEmpty && !delim(curr)) { + curr = takeChar() + if (!delim(curr)) sb += curr + } + + val str = sb.toString + val toAdd = + if (shouldHL(str)) + highlight(str) + else if (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken)) + valDef(str) + else str + val suffix = if (delim(curr)) s"$curr" else "" + newBuf append (toAdd + suffix) + lastToken = str + prev = curr + } + + def appendWhile(c: Char, pred: Char => Boolean, highlight: String => String) = { + var curr: Char = 0 + val sb = new StringBuilder(s"$c") + while (remaining.nonEmpty && pred(curr)) { + curr = takeChar() + if (pred(curr)) sb += curr + } + + val str = sb.toString + val suffix = if (!pred(curr)) s"$curr" else "" + newBuf append (highlight(str) + suffix) + prev = curr + } + + def appendTo(c: Char, pred: Char => Boolean, highlight: String => String) = { + var curr: Char = 0 + val sb = new StringBuilder(s"$c") + while (remaining.nonEmpty && !pred(curr)) { + curr = takeChar() + sb += curr + } + + newBuf append highlight(sb.toString) + prev = curr + } + + newBuf.toIterable + } +} diff --git a/compiler/src/dotty/tools/dotc/printing/Texts.scala b/compiler/src/dotty/tools/dotc/printing/Texts.scala new file mode 100644 index 000000000..db81cab7a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/Texts.scala @@ -0,0 +1,168 @@ +package dotty.tools.dotc +package printing +import core.Contexts.Context +import language.implicitConversions + +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) + + def provided(pred: Boolean) = if (pred) this else Str("") + } + + object Text { + + /** The empty text */ + def apply(): Text = Str("") + + /** A concatenation of elements in `xs` and interspersed with + * separator strings `sep`. + */ + def apply(xs: Traversable[Text], sep: String = " "): Text = { + if (sep == "\n") lines(xs) + else { + val ys = xs filterNot (_.isEmpty) + if (ys.isEmpty) Str("") + else ys reduce (_ ~ sep ~ _) + } + } + + /** The given texts `xs`, each on a separate line */ + 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) +} diff --git a/compiler/src/dotty/tools/dotc/printing/package.scala b/compiler/src/dotty/tools/dotc/printing/package.scala new file mode 100644 index 000000000..814eb2ad0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/package.scala @@ -0,0 +1,17 @@ +package dotty.tools.dotc + +import core.StdNames.nme +import parsing.{precedence, minPrec, maxPrec, minInfixPrec} + +package object printing { + + type Precedence = Int + + val DotPrec = parsing.maxPrec + val AndPrec = parsing.precedence(nme.raw.AMP) + val OrPrec = parsing.precedence(nme.raw.BAR) + val InfixPrec = parsing.minInfixPrec + val GlobalPrec = parsing.minPrec + val TopLevelPrec = parsing.minPrec - 1 + +} |