为什么以下 Lua 代码是完全有效的?

从我的 Lua 知识(以及 Lua 手册中所述),我一直以来认为 Lua 中的标识符仅限于 A-Z、a-z、_ 和数字(不能以数字开头,也不能是保留关键字,即 local local = 123)。

现在我遇到了一些(混淆的)Lua 程序,它使用各种奇怪的字符作为标识符:

https://i.imgur.com/HPLKMxp.png

-- 可能无法复制粘贴。从 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

输出:

Lua 5.1 JIT

â¬â€‹â­â¬â­â«â®â€­â€¬ function: builtin#45

​â®â€‹âŞâ®â€‹­ď»żâ€Śâ€­âŽ­ function: builtin#44

我不清楚为什么标识符允许使用这组字符?

换句话说,为什么它是一个完全有效的 Lua 程序?

点赞
用户7396148
用户7396148

为了澄清,只有一个标识符 T

T.mathT["math"] 的糖语法,这也适用于混淆的字符串。拥有包含任何字符或以数字开头的“键”是完全有效的。

现在能够使用 . 而不是 [ ] 与不符合标识符限制的字符串无法使用。请查看Nicol Bolas的答案以了解这些限制的详细分析。

2019-04-14 16:39:02
用户734069
用户734069

与某些语言不同,Lua没有正式的规范来覆盖每个情况并完全解释Lua的所有行为。像“Lua文件编码是什么字符集”这样的简单问题在Lua的文档中并没有详细说明。

关于标识符的所有内容,文档中只有:

在Lua中,_名称_(也称为_标识符_)可以是任何由字母、数字和下划线组成的字符串,不以数字开头,也不是保留字。

但是,没有任何东西真正说明“字母”是什么。甚至还没有定义Lua使用什么字符集。因此,它基本上是取决于实现的。一个“字母”是......取决于实现需要什么。

所以,假设你正在编写一个Lua实现。你希望用户能够提供Unicode编码的字符串(也就是说,在Lua文本内的字符串)。Lua 5.3要求这样。但你也不希望它们必须使用UTF-16编码文件(也因为lua_load获得的是字节序列,而不是short)。因此,你的Lua实现假定lua_load得到的字节序列以UTF-8编码,以便用户可以编写使用Unicode字符的字符串。

当涉及到实现分析器的部分,你如何处理?处理UTF-8的最简单、最容易的方法是......不处理UTF-8。实际上,这就是该编码的整个意义所在。由于Lua定义的所有特定符号都是使用ASCII编码的,而ASCII文本也是具有相同含义的UTF-8文本,你可以将UTF-8字符串基本上看作ASCII字符串。对于Lua字符串,只需将字符串的开始和结束字符之间的字节序列复制即可。

那么,如何识别标识符?好吧,你可以问上面的问题。或者你可以问一个更简单的问题:这个字符是空格、控制字符、数字还是符号?一个“字母”只是一个不属于这些中的任何一个的东西。

Lua定义了它认为是“符号”的东西。ASCII可以告诉你哪些是控制字符、空格和数字。在这样的实现中,任何值在ASCII之外的UTF-8代码单元都是_字母_。即使从技术上讲,这些代码单元解码为Unicode认为是“符号”的东西,你的分词器也将其视为字母。

这种简单形式的UTF-8分析可提供快速的性能和低内存开销。你不需要将UTF-8解码为Unicode代码点,也不需要一个巨大的Unicode表来告诉你一个代码点是“符号”还是“空格”或其他类型。当然,它也是许多基于ASCII的Lua实现中自然获得的东西。

因此,大多数Lua实现都会这样做,即使只是偶然的结果。要做更多的事情需要有意识的努力。

它还允许用户使用Unicode字符序列作为标识符。这意味着某人可以轻松地用他们的母语编写代码(除了关键字)。

但它也意味着混淆器可以有很多方法来创建仅是无意义字节串的“标识符”。实际上,由于在Unicode中有多种“拼写”相同的Unicode字符串的方法(除非直接检查字节),混淆器可以搭配出看起来是相同文本的标识符,而实际上是不同的字符串。

2019-04-14 17:05:41