Skip to content

Improve printing of strings #22945

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 62 additions & 14 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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)

Expand Down
84 changes: 65 additions & 19 deletions compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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("()")
Expand All @@ -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))
Expand Down Expand Up @@ -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]
Expand Down
Loading