@@ -13,13 +13,16 @@ import scala.annotation.internal.sharable
1313object NameTransformer {
1414
1515 private val nops = 128
16+ private val ncodes = 26 * 26
1617
17- @ sharable private val op2code = new Array [String ](nops)
18- @ sharable private val str2op = new mutable.HashMap [String , Char ]
18+ private class OpCodes (val op : Char , val code : String , val next : OpCodes )
1919
20+ @ sharable private val op2code = new Array [String ](nops)
21+ @ sharable private val code2op = new Array [OpCodes ](ncodes)
2022 private def enterOp (op : Char , code : String ) = {
21- op2code(op) = code
22- str2op(code) = op
23+ op2code(op.toInt) = code
24+ val c = (code.charAt(1 ) - 'a' ) * 26 + code.charAt(2 ) - 'a'
25+ code2op(c.toInt) = new OpCodes (op, code, code2op(c))
2326 }
2427
2528 /* Note: decoding assumes opcodes are only ever lowercase. */
@@ -42,99 +45,107 @@ object NameTransformer {
4245 enterOp('?' , " $qmark" )
4346 enterOp('@' , " $at" )
4447
45- /** Expand characters that are illegal as JVM method names by `$u`, followed
46- * by the character's unicode expansion.
47- */
48- def avoidIllegalChars (name : SimpleName ): SimpleName = {
49- var i = name.length - 1
50- while (i >= 0 && Chars .isValidJVMMethodChar(name(i))) i -= 1
51- if (i >= 0 )
52- termName(
53- name.toString.flatMap(ch =>
54- if (Chars .isValidJVMMethodChar(ch)) ch.toString else " $u%04X" .format(ch.toInt)))
55- else name
56- }
57-
58- /** Decode expanded characters starting with `$u`, followed by the character's unicode expansion. */
59- def decodeIllegalChars (name : String ): String =
60- if (name.contains(" $u" )) {
61- val sb = new mutable.StringBuilder ()
62- var i = 0
63- while (i < name.length)
64- if (i < name.length - 5 && name(i) == '$' && name(i + 1 ) == 'u' ) {
65- val numbers = name.substring(i + 2 , i + 6 )
66- try sb.append(Integer .valueOf(name.substring(i + 2 , i + 6 ), 16 ).toChar)
67- catch {
68- case _ : java.lang.NumberFormatException =>
69- sb.append(" $u" ).append(numbers)
70- }
71- i += 6
72- }
73- else {
74- sb.append(name(i))
75- i += 1
76- }
77- sb.result()
78- }
79- else name
80-
81- /** Replace operator symbols by corresponding expansion strings.
82- *
83- * @param name the string to encode
84- * @return the string with all recognized opchars replaced with their encoding
85- *
86- * Operator symbols are only recognized if they make up the whole name, or
87- * if they make up the last part of the name which follows a `_`.
48+ /** Replace operator symbols by corresponding expansion strings, and replace
49+ * characters that are not valid Java identifiers by "$u" followed by the
50+ * character's unicode expansion.
51+ * Note that no attempt is made to escape the use of '$' in `name`: blindly
52+ * escaping them might make it impossible to call some platform APIs. This
53+ * unfortunately means that `decode(encode(name))` might not be equal to
54+ * `name`, this is considered acceptable since '$' is a reserved character in
55+ * the Scala spec as well as the Java spec.
8856 */
8957 def encode (name : SimpleName ): SimpleName = {
90- def loop (len : Int , ops : List [String ]): SimpleName = {
91- def convert =
92- if (ops.isEmpty) name
93- else {
94- val buf = new java.lang.StringBuilder
95- buf.append(chrs, name.start, len)
96- for (op <- ops) buf.append(op)
97- termName(buf.toString)
58+ var buf : StringBuilder = null
59+ val len = name.length
60+ var i = 0
61+ while (i < len) {
62+ val c = name(i)
63+ if (c < nops && (op2code(c.toInt) ne null )) {
64+ if (buf eq null ) {
65+ buf = new StringBuilder ()
66+ buf.append(name.sliceToString(0 , i))
67+ }
68+ buf.append(op2code(c.toInt))
69+ /* Handle glyphs that are not valid Java/JVM identifiers */
70+ }
71+ else if (! Character .isJavaIdentifierPart(c)) {
72+ if (buf eq null ) {
73+ buf = new StringBuilder ()
74+ buf.append(name.sliceToString(0 , i))
9875 }
99- if (len == 0 || name(len - 1 ) == '_' ) convert
100- else {
101- val ch = name(len - 1 )
102- if (ch <= nops && op2code(ch) != null )
103- loop(len - 1 , op2code(ch) :: ops)
104- else if (Chars .isSpecial(ch))
105- loop(len - 1 , ch.toString :: ops)
106- else name
76+ buf.append(" $u%04X" .format(c.toInt))
10777 }
78+ else if (buf ne null ) {
79+ buf.append(c)
80+ }
81+ i += 1
10882 }
109- loop( name.length, Nil )
83+ if (buf eq null ) name else termName(buf.toString )
11084 }
11185
112- /** Replace operator expansions by the operators themselves.
113- * Operator expansions are only recognized if they make up the whole name, or
114- * if they make up the last part of the name which follows a `_`.
86+ /** Replace operator expansions by the operators themselves,
87+ * and decode `$u....` expansions into unicode characters.
11588 */
11689 def decode (name : SimpleName ): SimpleName = {
117- def loop (len : Int , ops : List [Char ]): SimpleName = {
118- def convert =
119- if (ops.isEmpty) name
120- else {
121- val buf = new java.lang.StringBuilder
122- buf.append(chrs, name.start, len)
123- for (op <- ops) buf.append(op)
124- termName(buf.toString)
125- }
126- if (len == 0 || name(len - 1 ) == '_' ) convert
127- else if (Chars .isSpecial(name(len - 1 ))) loop(len - 1 , name(len - 1 ) :: ops)
128- else {
129- val idx = name.lastIndexOf('$' , len - 1 )
130- if (idx >= 0 && idx + 2 < len)
131- str2op.get(name.sliceToString(idx, len)) match {
132- case Some (ch) => loop(idx, ch :: ops)
133- case None => name
90+ // System.out.println("decode: " + name);//DEBUG
91+ var buf : StringBuilder = null
92+ val len = name.length
93+ var i = 0
94+ while (i < len) {
95+ var ops : OpCodes = null
96+ var unicode = false
97+ val c = name(i)
98+ if (c == '$' && i + 2 < len) {
99+ val ch1 = name(i + 1 )
100+ if ('a' <= ch1 && ch1 <= 'z' ) {
101+ val ch2 = name(i + 2 )
102+ if ('a' <= ch2 && ch2 <= 'z' ) {
103+ ops = code2op((ch1 - 'a' ) * 26 + ch2 - 'a' )
104+ while ((ops ne null ) && ! name.startsWith(ops.code, i)) ops = ops.next
105+ if (ops ne null ) {
106+ if (buf eq null ) {
107+ buf = new StringBuilder ()
108+ buf.append(name.sliceToString(0 , i))
109+ }
110+ buf.append(ops.op)
111+ i += ops.code.length()
112+ }
113+ /* Handle the decoding of Unicode glyphs that are
114+ * not valid Java/JVM identifiers */
115+ } else if ((len - i) >= 6 && // Check that there are enough characters left
116+ ch1 == 'u' &&
117+ ((Character .isDigit(ch2)) ||
118+ ('A' <= ch2 && ch2 <= 'F' ))) {
119+ /* Skip past "$u", next four should be hexadecimal */
120+ val hex = name.sliceToString(i+ 2 , i+ 6 )
121+ try {
122+ val str = Integer .parseInt(hex, 16 ).toChar
123+ if (buf eq null ) {
124+ buf = new StringBuilder ()
125+ buf.append(name.sliceToString(0 , i))
126+ }
127+ buf.append(str)
128+ /* 2 for "$u", 4 for hexadecimal number */
129+ i += 6
130+ unicode = true
131+ } catch {
132+ case _:NumberFormatException =>
133+ /* `hex` did not decode to a hexadecimal number, so
134+ * do nothing. */
135+ }
134136 }
135- else name
137+ }
138+ }
139+ /* If we didn't see an opcode or encoded Unicode glyph, and the
140+ buffer is non-empty, write the current character and advance
141+ one */
142+ if ((ops eq null ) && ! unicode) {
143+ if (buf ne null )
144+ buf.append(c)
145+ i += 1
136146 }
137147 }
138- loop(name.length, Nil )
148+ // System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG
149+ if (buf eq null ) name else termName(buf.toString)
139150 }
140151}
0 commit comments