From e2c4ec12a11cd3a8757d5584dea17d7902abebdd Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 8 Apr 2025 20:15:10 -0700 Subject: [PATCH 1/2] Improve printing of strings --- .../tools/dotc/printing/PlainPrinter.scala | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 0dcb06ae8c87..7fda42ebb147 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -17,6 +17,8 @@ import scala.annotation.switch import config.{Config, Feature} import cc.* +import java.lang.StringBuilder + class PlainPrinter(_ctx: Context) extends Printer { /** The context of all public methods in Printer and subclasses. @@ -668,22 +670,18 @@ class PlainPrinter(_ctx: Context) extends Printer { def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D" - private def escapedChar(ch: Char): String = (ch: @switch) match { - case '\b' => "\\b" - case '\t' => "\\t" - case '\n' => "\\n" - case '\f' => "\\f" - case '\r' => "\\r" - case '"' => "\\\"" - case '\'' => "\\\'" - case '\\' => "\\\\" - case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn - } + private def escapedChar(ch: Char): String = + if requiresFormat(ch) then + val b = StringBuilder().append('\'') + escapedChar(b, ch) + b.append('\'').toString + else + "'" + ch + "'" def toText(const: Constant): Text = const.tag match { - case StringTag => stringText("\"" + escapedString(const.value.toString) + "\"") + case StringTag => stringText(escapedString(const.value.toString, quoted = true)) case ClazzTag => "classOf[" ~ toText(const.typeValue) ~ "]" - case CharTag => literalText(s"'${escapedChar(const.charValue)}'") + case CharTag => literalText(escapedChar(const.charValue)) case LongTag => literalText(const.longValue.toString + "L") case DoubleTag => literalText(const.doubleValue.toString + "d") case FloatTag => literalText(const.floatValue.toString + "f") @@ -701,7 +699,57 @@ class PlainPrinter(_ctx: Context) extends Printer { ~ (if param.isTypeParam then "" else ": ") ~ toText(param.paramInfo) - protected def escapedString(str: String): String = str flatMap escapedChar + protected final def escapedString(str: String): String = escapedString(str, quoted = false) + + private def requiresFormat(c: Char): Boolean = (c: @switch) match + case '\b' | '\t' | '\n' | '\f' | '\r' | '"' | '\'' | '\\' => true + case c => c.isControl + + private def escapedString(text: String, quoted: Boolean): String = + def mustBuild: Boolean = + var i = 0 + while i < text.length do + if requiresFormat(text.charAt(i)) then return true + i += 1 + false + if mustBuild then + val b = StringBuilder(text.length + 16) + if quoted then + b.append('"') + var i = 0 + while i < text.length do + escapedChar(b, text.charAt(i)) + i += 1 + if quoted then + b.append('"') + b.toString + else if quoted then "\"" + text + "\"" + else text + + private def escapedChar(b: StringBuilder, c: Char): Unit = + def quadNibble(b: StringBuilder, x: Int, i: Int): Unit = + if i < 4 then + quadNibble(b, x >> 4, i + 1) + val n = x & 0xF + val c = if (n < 10) '0' + n else 'a' + (n - 10) + b.append(c.toChar) + val replace = (c: @switch) match + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '"' => "\\\"" + case '\'' => "\\\'" + case '\\' => "\\\\" + case c => + if c.isControl then + b.append("\\u") + quadNibble(b, c.toInt, 0) + else + b.append(c) + return + b.append(replace) def dclsText(syms: List[Symbol], sep: String): Text = Text(syms map dclText, sep) From 9f19b05658c486eac3ee61e91f15a5ceb6c066a0 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 9 Apr 2025 03:29:50 -0700 Subject: [PATCH 2/2] Improve SourceCode string printing --- .../runtime/impl/printers/SourceCode.scala | 84 ++++++++++++++----- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 7c7a688eccb2..8195697f1148 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1,8 +1,12 @@ package scala.quoted package runtime.impl.printers +import dotty.tools.dotc.util.Chars + import scala.annotation.switch +import java.lang.StringBuilder + /** Printer for fully elaborated representation of the source code */ object SourceCode { @@ -97,7 +101,7 @@ object SourceCode { this += lineBreak() += "}" } - def result(): String = sb.result() + def result(): String = sb.toString private def lineBreak(): String = "\n" + (" " * indent) private def doubleLineBreak(): String = "\n\n" + (" " * indent) @@ -438,7 +442,7 @@ object SourceCode { case _ => inParens { printTree(term) - this += (if (dotty.tools.dotc.util.Chars.isOperatorPart(sb.last)) " : " else ": ") + this += (if Chars.isOperatorPart(sb.charAt(sb.length - 1)) then " : " else ": ") def printTypeOrAnnots(tpe: TypeRepr): Unit = tpe match { case AnnotatedType(tp, annot) if tp == term.tpe => printAnnotation(annot) @@ -957,8 +961,8 @@ object SourceCode { } - inline private val qc = '\'' - inline private val qSc = '"' + inline private val qc = "\'" + inline private val qSc = "\"" def printConstant(const: Constant): this.type = const match { case UnitConstant() => this += highlightLiteral("()") @@ -970,8 +974,8 @@ object SourceCode { case LongConstant(v) => this += highlightLiteral(v.toString + "L") case FloatConstant(v) => this += highlightLiteral(v.toString + "f") case DoubleConstant(v) => this += highlightLiteral(v.toString) - case CharConstant(v) => this += highlightString(s"${qc}${escapedChar(v)}${qc}") - case StringConstant(v) => this += highlightString(s"${qSc}${escapedString(v)}${qSc}") + case CharConstant(v) => this += highlightString(escapedChar(v)) + case StringConstant(v) => this += highlightString(escapedString(v)) case ClassOfConstant(v) => this += "classOf" inSquare(printType(v)) @@ -1445,19 +1449,61 @@ object SourceCode { private def +=(x: Char): this.type = { sb.append(x); this } private def +=(x: String): this.type = { sb.append(x); this } - private def escapedChar(ch: Char): String = (ch: @switch) match { - case '\b' => "\\b" - case '\t' => "\\t" - case '\n' => "\\n" - case '\f' => "\\f" - case '\r' => "\\r" - case '"' => "\\\"" - case '\'' => "\\\'" - case '\\' => "\\\\" - case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn - } - - private def escapedString(str: String): String = str flatMap escapedChar + private def escapedChar(ch: Char): String = + if requiresFormat(ch) then + val b = StringBuilder().append(qc) + escapedChar(b, ch) + b.append(qc).toString + else + qc + ch + qc + + private def escapedChar(b: StringBuilder, c: Char): Unit = + def quadNibble(b: StringBuilder, x: Int, i: Int): Unit = + if i < 4 then + quadNibble(b, x >> 4, i + 1) + val n = x & 0xF + val c = if (n < 10) '0' + n else 'a' + (n - 10) + b.append(c.toChar) + val replace = (c: @switch) match + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '"' => "\\\"" + case '\'' => "\\\'" + case '\\' => "\\\\" + case c => + if c.isControl then + b.append("\\u") + quadNibble(b, c.toInt, 0) + else + b.append(c) + return + b.append(replace) + + private def requiresFormat(c: Char): Boolean = (c: @switch) match + case '\b' | '\t' | '\n' | '\f' | '\r' | '"' | '\'' | '\\' => true + case c => c.isControl + + private def escapedString(text: String): String = + def mustBuild: Boolean = + var i = 0 + while i < text.length do + if requiresFormat(text.charAt(i)) then return true + i += 1 + false + if mustBuild then + val b = StringBuilder(text.length + 16) + b.append(qSc) + var i = 0 + while i < text.length do + escapedChar(b, text.charAt(i)) + i += 1 + b.append(qSc) + b.toString + else + qSc + text + qSc private val names = collection.mutable.Map.empty[Symbol, String] private val namesIndex = collection.mutable.Map.empty[String, Int]