Skip to content

Commit e2c4ec1

Browse files
committed
Improve printing of strings
1 parent 5b4b5c2 commit e2c4ec1

File tree

1 file changed

+62
-14
lines changed

1 file changed

+62
-14
lines changed

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

+62-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import scala.annotation.switch
1717
import config.{Config, Feature}
1818
import cc.*
1919

20+
import java.lang.StringBuilder
21+
2022
class PlainPrinter(_ctx: Context) extends Printer {
2123

2224
/** The context of all public methods in Printer and subclasses.
@@ -668,22 +670,18 @@ class PlainPrinter(_ctx: Context) extends Printer {
668670

669671
def toText(denot: Denotation): Text = toText(denot.symbol) ~ "/D"
670672

671-
private def escapedChar(ch: Char): String = (ch: @switch) match {
672-
case '\b' => "\\b"
673-
case '\t' => "\\t"
674-
case '\n' => "\\n"
675-
case '\f' => "\\f"
676-
case '\r' => "\\r"
677-
case '"' => "\\\""
678-
case '\'' => "\\\'"
679-
case '\\' => "\\\\"
680-
case _ => if ch.isControl then f"${"\\"}u${ch.toInt}%04x" else String.valueOf(ch).nn
681-
}
673+
private def escapedChar(ch: Char): String =
674+
if requiresFormat(ch) then
675+
val b = StringBuilder().append('\'')
676+
escapedChar(b, ch)
677+
b.append('\'').toString
678+
else
679+
"'" + ch + "'"
682680

683681
def toText(const: Constant): Text = const.tag match {
684-
case StringTag => stringText("\"" + escapedString(const.value.toString) + "\"")
682+
case StringTag => stringText(escapedString(const.value.toString, quoted = true))
685683
case ClazzTag => "classOf[" ~ toText(const.typeValue) ~ "]"
686-
case CharTag => literalText(s"'${escapedChar(const.charValue)}'")
684+
case CharTag => literalText(escapedChar(const.charValue))
687685
case LongTag => literalText(const.longValue.toString + "L")
688686
case DoubleTag => literalText(const.doubleValue.toString + "d")
689687
case FloatTag => literalText(const.floatValue.toString + "f")
@@ -701,7 +699,57 @@ class PlainPrinter(_ctx: Context) extends Printer {
701699
~ (if param.isTypeParam then "" else ": ")
702700
~ toText(param.paramInfo)
703701

704-
protected def escapedString(str: String): String = str flatMap escapedChar
702+
protected final def escapedString(str: String): String = escapedString(str, quoted = false)
703+
704+
private def requiresFormat(c: Char): Boolean = (c: @switch) match
705+
case '\b' | '\t' | '\n' | '\f' | '\r' | '"' | '\'' | '\\' => true
706+
case c => c.isControl
707+
708+
private def escapedString(text: String, quoted: Boolean): String =
709+
def mustBuild: Boolean =
710+
var i = 0
711+
while i < text.length do
712+
if requiresFormat(text.charAt(i)) then return true
713+
i += 1
714+
false
715+
if mustBuild then
716+
val b = StringBuilder(text.length + 16)
717+
if quoted then
718+
b.append('"')
719+
var i = 0
720+
while i < text.length do
721+
escapedChar(b, text.charAt(i))
722+
i += 1
723+
if quoted then
724+
b.append('"')
725+
b.toString
726+
else if quoted then "\"" + text + "\""
727+
else text
728+
729+
private def escapedChar(b: StringBuilder, c: Char): Unit =
730+
def quadNibble(b: StringBuilder, x: Int, i: Int): Unit =
731+
if i < 4 then
732+
quadNibble(b, x >> 4, i + 1)
733+
val n = x & 0xF
734+
val c = if (n < 10) '0' + n else 'a' + (n - 10)
735+
b.append(c.toChar)
736+
val replace = (c: @switch) match
737+
case '\b' => "\\b"
738+
case '\t' => "\\t"
739+
case '\n' => "\\n"
740+
case '\f' => "\\f"
741+
case '\r' => "\\r"
742+
case '"' => "\\\""
743+
case '\'' => "\\\'"
744+
case '\\' => "\\\\"
745+
case c =>
746+
if c.isControl then
747+
b.append("\\u")
748+
quadNibble(b, c.toInt, 0)
749+
else
750+
b.append(c)
751+
return
752+
b.append(replace)
705753

706754
def dclsText(syms: List[Symbol], sep: String): Text = Text(syms map dclText, sep)
707755

0 commit comments

Comments
 (0)