unicode 字符转义序列如何在 Scala 和 Java 中工作,当用于命名时
How do unicode character escape sequences work in Scala and Java, when used in naming
我最近了解到在 Scala 中可以使用 Unicode 转义序列进行命名。
例如:
val B\u0041\u0044 = 1
允许:
scala> BAD
res0: Int = 1
我的直觉是它的功能有点类似于 C 的 #define <entity> <new_entity>
的工作方式,它只是用呈现的字符替换 unicode 转义序列,这就是 BAD
在上面的示例中工作的原因。
在 Java 中也是可能的。所以我想知道它与 Java 的关系是否比与 Scala 的关系更大。
在 Java 中,以下是可能的:
double \u03C0 = Math.PI;
这允许:
System.out.println(π)
这实际上是如何工作的?
您的困惑来自于将两个完全不同的级别混为一谈:
Unicode 转义序列在单个字符级别处理。严格来说,它甚至发生在词法分析之前。这意味着 Unicode 转义序列对 "naming" 或 "names" 一无所知 - 当它们被扩展时,没有关于当前处理的内容的信息:名称(标识符),或保留关键字,或一些完全不同的语言结构。
关于什么是 "names" 的信息要晚得多,只有在词法分析之后,一旦输入字符序列被分成标记。
Scala 所做的只是用相应的 Unicode 字符替换转义序列 \uXXXX
。例如 (run ScalaFiddle):
fo\u0072 (i <- 1 to 10) pri\u006Etln\u0028"hello, world\u0022)
是一个完全有效的 Scala 程序,它打印 hello, world
十次。所以你看到了:
- 关键字中间的
\u0072
扩展为r
(for
是保留关键字,不是标识符)
\u006E
在另一个标识符中间展开(println
)
\u0028
和 \u0022
分别替换为 (
和 "
。这些字符甚至不是标识符的有效部分(除非包含在反引号中)。
它与"names"根本没有任何关系。这都是关于单个字符的,它发生在 "names" 或 "string literals" 或 "comments" 之前,这就是为什么在字符串内部使用 Unicode 转义序列时会出现 代码高亮器错误处理的文字或注释。
所有这些似乎与 C 预处理器中的宏所发生的事情几乎无关。
使用 #define
创建的宏必须有一个适当的标识符名称(即由字符、数字、下划线组成),如果宏有参数,则预处理器会在调用时用实际参数逐字替换这些参数地点。对于 Unicode 转义序列,这一切都是不可能的。此外,C 预处理器不会拆分标记:例如,如果您 #define u0072 r
,预处理器将不会用 for
关键字替换所有 fou0072
标识符。它的工作方式完全不同,目的也完全不同。
更新:更多细节
如果你看一下 Scanners.scala, you can see that there is a method getUEscape, which processes the escape sequences, and pushes single characters into a buffer. The only place where this method is used is in another helper method getLitChar, so that all Unicode escape sequences are always transformed into characters, before those characters arrive in the "higher level" methods like fetchToken。这就是我所说的 Unicode 转义序列的处理甚至发生在词法分析之前的意思。
正如 Alexey Romanov 在下面的评论中提到的那样,转义很快将被区别对待,并在更少的上下文中扩展 - this or this 提交中的更改似乎是相关的。
一个Java程序由一串Unicode字符组成。
支持 \uNNNN 表示法来表示无法在您使用的特定输入设备上输入的单个字符,例如,如果您只有一个 ASCII 终端或典型的 US电脑键盘.
Java 编译器通过首先处理 Unicode 转义将其输入流转换为标准 Unicode 格式。有关详细信息,请参阅 the language specification。
我最近了解到在 Scala 中可以使用 Unicode 转义序列进行命名。
例如:
val B\u0041\u0044 = 1
允许:
scala> BAD
res0: Int = 1
我的直觉是它的功能有点类似于 C 的 #define <entity> <new_entity>
的工作方式,它只是用呈现的字符替换 unicode 转义序列,这就是 BAD
在上面的示例中工作的原因。
在 Java 中也是可能的。所以我想知道它与 Java 的关系是否比与 Scala 的关系更大。
在 Java 中,以下是可能的:
double \u03C0 = Math.PI;
这允许:
System.out.println(π)
这实际上是如何工作的?
您的困惑来自于将两个完全不同的级别混为一谈:
Unicode 转义序列在单个字符级别处理。严格来说,它甚至发生在词法分析之前。这意味着 Unicode 转义序列对 "naming" 或 "names" 一无所知 - 当它们被扩展时,没有关于当前处理的内容的信息:名称(标识符),或保留关键字,或一些完全不同的语言结构。
关于什么是 "names" 的信息要晚得多,只有在词法分析之后,一旦输入字符序列被分成标记。
Scala 所做的只是用相应的 Unicode 字符替换转义序列 \uXXXX
。例如 (run ScalaFiddle):
fo\u0072 (i <- 1 to 10) pri\u006Etln\u0028"hello, world\u0022)
是一个完全有效的 Scala 程序,它打印 hello, world
十次。所以你看到了:
- 关键字中间的
\u0072
扩展为r
(for
是保留关键字,不是标识符) \u006E
在另一个标识符中间展开(println
)\u0028
和\u0022
分别替换为(
和"
。这些字符甚至不是标识符的有效部分(除非包含在反引号中)。
它与"names"根本没有任何关系。这都是关于单个字符的,它发生在 "names" 或 "string literals" 或 "comments" 之前,这就是为什么在字符串内部使用 Unicode 转义序列时会出现
所有这些似乎与 C 预处理器中的宏所发生的事情几乎无关。
使用 #define
创建的宏必须有一个适当的标识符名称(即由字符、数字、下划线组成),如果宏有参数,则预处理器会在调用时用实际参数逐字替换这些参数地点。对于 Unicode 转义序列,这一切都是不可能的。此外,C 预处理器不会拆分标记:例如,如果您 #define u0072 r
,预处理器将不会用 for
关键字替换所有 fou0072
标识符。它的工作方式完全不同,目的也完全不同。
更新:更多细节
如果你看一下 Scanners.scala, you can see that there is a method getUEscape, which processes the escape sequences, and pushes single characters into a buffer. The only place where this method is used is in another helper method getLitChar, so that all Unicode escape sequences are always transformed into characters, before those characters arrive in the "higher level" methods like fetchToken。这就是我所说的 Unicode 转义序列的处理甚至发生在词法分析之前的意思。
正如 Alexey Romanov 在下面的评论中提到的那样,转义很快将被区别对待,并在更少的上下文中扩展 - this or this 提交中的更改似乎是相关的。
一个Java程序由一串Unicode字符组成。
支持 \uNNNN 表示法来表示无法在您使用的特定输入设备上输入的单个字符,例如,如果您只有一个 ASCII 终端或典型的 US电脑键盘.
Java 编译器通过首先处理 Unicode 转义将其输入流转换为标准 Unicode 格式。有关详细信息,请参阅 the language specification。