根据我对Lua的了解(并根据我在Lua手册中所读的内容),我一直给人的印象是Lua中的标识符仅限于AZ&az&_&digits(并且不能开始使用数字,也不能保留关键字,即local local = 123)。
现在,我遇到了一些(混淆的)Lua程序,该程序使用各种奇怪的字符作为标识符:
https://i.imgur.com/HPLKMxp.png
-- Most likely, copy+paste won't work. Download the file from https://tknk.io/7HHZ
print(_VERSION .. " " .. (jit and "JIT" or "non-JIT"))
local T = {}
T.math = T.math or {}
T.math.?»?​â®â€‹â?â®â€‹?»?â€?â€âŽ = math.sin
T.math.â¬â€‹ââ¬ââ«â®â€â€¬ = math.cos
for k, v in pairs(T.math) do print(k, v) end
Run Code Online (Sandbox Code Playgroud)
输出:
Lua 5.1 JIT的
“功能”:builtin#45
????? ▲功能:内建#44
我不清楚,为什么标识符允许使用这组字符?
换句话说,为什么它是一个完全有效的Lua程序?
与某些语言不同,Lua并不是由正式规范真正定义的,它涵盖了所有偶然性,并完全解释了Lua的所有行为。Lua的文档中并没有真正解释诸如“ Lua文件编码了什么字符集”这样的简单内容。
Lua中的名称(也称为标识符)可以是字母,数字和下划线的任何字符串,不是以数字开头也不是保留字。
但是,没有人真正说出“字母”是什么。Lua所使用的字符集甚至都没有定义。因此,它本质上是依赖于实现的。一个“字母”就是……无论实现如何实现。
因此,假设您正在编写Lua实现。而你希望用户能够提供的Unicode编码的字符串(即,字符串中的Lua的文本)。Lua 5.3要求这样做。但是,您也不希望他们的文件使用UTF-16编码(也是因为lua_load获取字节序列,而不是短裤)。因此,您的Lua实现假定传入的字节序列lua_load采用UTF-8编码,以便用户可以编写使用Unicode字符的字符串。
在编写此实现的词法分析器/解析器部分时,如何处理?处理UTF-8的最简单,最简单的方法是... 不处理UTF-8。确实,这就是编码的重点。由于Lua用特定符号定义的所有内容均以ASCII编码,并且ASCII文本也是具有相同含义的UTF-8文本,因此您基本上可以将UTF-8字符串视为ASCII字符串。对于In-Lua字符串,您只需要在字符串的开始和结束字符之间复制字节序列即可。
那么,如何处理词法标识符呢?好吧,你可以问上面的问题。或者您可以问一个更简单的问题:字符是空格,控制字符,数字还是符号?“信”只是其中之一而已。
Lua定义了它认为什么是“符号”。ASCII可以告诉您什么是控制字符,空格和数字。在这样的实现中,任何ASCII值之外的UTF-8代码单元都是一个letter。即使从技术上讲,这些代码单元解码为Unicode认为是“符号”的东西,您的词法分析器也只是威胁要以字母表示。
UTF-8词汇表的这种简单形式为您提供了快速的性能和较低的内存开销。您不必将UTF-8解码为Unicode代码点,也不需要庞大的Unicode表来告诉您代码点是“符号”还是“空格”或其他。当然,这也是许多基于ASCII的Lua实现自然所不具备的。
因此,即使只是偶然,大多数Lua实现都会以这种方式进行操作。做更多的事情需要刻意的努力。
它还允许用户使用Unicode字符序列作为标识符。这意味着某人可以轻松地以其母语(关键字之外)编写代码。
但这也意味着混淆器可以通过多种方式来创建“标识符”,这些标识符只是无意义的字节串。确实,由于Unicode有多种方式可以“拼写”相同的表观Unicode字符串(除非您直接检查字节),混淆器可以将在文本编辑器中呈现时出现的标识符装配为相同的文本,而实际上不同的字符串。