如果没有一些自定义样板,JavaScript 无法正确处理BMP之外的 Unicode 字符/代码点,即编码需要超过 16 位的字符/代码点。
这个限制似乎延续到 PEG.js,如下面的例子所示。
特别是,我希望能够指定诸如[\u1D400-\u1D419]
(目前变成[ᵀ0-ᵁ9]
)或等效的[𝐀-𝐙]
(抛出“无效字符范围”)等范围错误)。 (并且使用新的 ES6 符号[\u{1D400}-\u{1D419}]
导致以下错误: SyntaxError: Expected "!", "$", "&", "(", ".", character class, comment, end of line, identifier, literal, or whitespace but "[" found.
。)
可能有一种方法可以使这项工作不需要更改 PEG.js 吗?
示例代码:
这个语法:
//MathUpper = [𝐀-𝐙]+
MathUpperEscaped = [\u1D400-\u1D419]+
预期行为:
从给定语法生成的解析器成功解析,例如“𝐀𝐁𝐂”。
实际行为:
解析错误: Line 1, column 1: Expected [ᵀ0-ᵁ9] but "
(或者,在取消注释其他规则时,会出现“无效字符范围”错误。)
老实说,除了更新对PEG.js 语法解析器和JavaScript 示例的Unicode 支持之外,我对 Unicode 知之甚少,因此目前无法解决此问题(这两个语法中都明确说明:_Non -BMP 字符完全被忽略_)。
目前,在处理更紧急的个人和工作相关项目(包括 _PEG.js 0.x_)时,我将继续等待更了解 Unicode 的人提供一些 PR 😆,或者最终在 _PEG 之后解决它。 js v1_,对不起,伙计。
仅供参考,代理对似乎有效。 语法start = result:[\uD83D\uDCA9]+ {return result.join('')}
解析 💩 是 u+1F4A9。 请注意 result.join('') 将代理对重新组合在一起,否则您将获得['\uD83D','\uDCA9']
而不是 hankey。 范围会有问题。
更多关于代理对: https ://en.wikipedia.org/wiki/UTF-16#U +010000_to_U+10FFFF
这绝不能取代 OP 的要求。
@drewnolan谢谢提醒👍
不幸的是,该语法也解析\uD83D\uD83D
。
对于遇到此问题的其他人:我很幸运,因为我只需要处理 BMP 之外的一小部分代码点,所以我最终在解析和反转此映射之前将它们映射到 BMP 的专用区域.
这个解决方案在一般情况下显然充满了问题,但它在我的问题域中运行良好。
@futagoza - 我会尽力解释。 您在这里面临几个问题。
utf-16
、 ucs4
等的东西。 这些是作为预期数据的codepoints
被编码为字节的方式。 通过示例utf-16-le
可以将大多数字母编码为称为code units
两字节对,但使用代码单元组来表达高达0x10ffff
高价值字符。我也想要这个,但实际上,这不会发生
天啊,大约一年前有人提交了一个完整的字符串解析器替换,他们认识到了开销,所以他们通常让我们使用标准的 JS 字符串
为什么没有合并
@StoneCypher我爱你心中的火焰! 但是为什么要给当前的维护者带来困难呢? 没有人欠下任何东西。 为什么不维护自己的fork呢?
当前没有维护者。 接手 PEG 的人从未发布过任何东西。 他为下一个未成年人工作了三年,然后说他不喜欢它的外观,正在扔掉所有peg.js
,并从他用另一种语言从头开始写的东西重新开始,与不兼容的AST。
该工具已经失去了一半的用户群,他们在这个人身上等了三年才能提交其他人编写的一行修复程序,例如 es6 模块支持、打字稿支持、箭头支持、扩展的 unicode 等。
有十几个人要求他合并,他一直说“不,这是我现在的爱好项目,我不喜欢它”
很多人都有基于这个解析器的公司。 他们完全搞砸了。
这个人答应做一个极其重要的工具的维护者,并没有做任何维护。 现在是时候让其他人保持这个图书馆的良好信誉了。
为什么不维护自己的fork呢?
我已经三年了。 我的peg
修复了几乎三分之一的问题跟踪器。
我不得不克隆它,重命名它,并制作一个新的叉子来修复大小问题以尝试提交它,因为我的已经漂移太多了
是时候让其他所有人以及自 2017 年以来一直位于跟踪器中的修复程序接收这些修复程序了。
这家伙没有保持联系; 他让它死去。
是时候改变了。
@drewnolan - 所以,我不确定这是否有趣,但是,代理对实际上不起作用。 只是,巧合的是,他们通常会这样做。
为了理解潜在的问题,您必须考虑编码级别的位模式,而不是表示级别的第一。
也就是说,如果你的 unicode 值为 240,大多数人会认为“哦,他的意思是0b1111 0000
。 但实际上,Unicode 并不是这样表示 240 的; 超过 127 用两个字节表示,因为最高位是标志位,而不是值位。 所以 Unicode 中的 240 实际上是0b0000 0001 0111 0000
存储(除了在 utf-7 中,它是真实的而不是打字错误,事情变得非常奇怪。是的,我知道维基百科说它没有被使用。维基百科是错误的.这是发送短信的内容;它可能是总流量中最常见的字符编码。)
所以问题来了。
如果你写一个字节 STUV WXYZ,然后在 utf16 中,从 ucs4 数据,如果你的东西被切成两半,通常你可以把它装回去。
对于超过两个字节编码的字符本机,您不能使用 128 次。 (听起来像是一个非常具体的数字,不是吗?)
因为当使用第二个字节位置的最高位时,将其切成两半会在本应为 1 的地方添加一个零。 将它们并排装在一起作为二进制数据并不会再次消除价值概念。 解码后的值不等于编码后的值,您附加的是解码,而不是编码。
碰巧大多数表情符号都在这个范围之外。 然而,大部分语言都不是,包括罕见的中文、大多数数学和音乐符号。
诚然,您的方法对于几乎所有表情符号以及每种人类语言都足够好,可以通过 Unicode 6 进入,这是对现状的巨大改进
但是这个 PR 真的应该合并,一旦它的正确性和意外性能问题都经过充分测试(记住,unicode 性能问题是 php 死的原因)
似乎. (dot character)
表达式也需要 Unicode 模式。 相比:
const string = '-🐎-👱-';
const symbols = (string.match(/./gu));
console.log(JSON.stringify(symbols, null, ' '));
const pegResult = require('pegjs/')
.generate('root = .+')
.parse(string);
console.log(JSON.stringify(pegResult, null, ' '));
输出:
[
"-",
"🐎",
"-",
"👱",
"-"
]
[
"-",
"\ud83d",
"\udc0e",
"-",
"\ud83d",
"\udc71",
"-"
]
我最近在做这方面的工作,使用 #616 作为基础并将其修改为使用 ES6 语法\u{hhhhhhh}
,我将在几个小时内创建一个 PR。
计算由代理分割的 UTF-16 正则表达式范围有点复杂,我为此使用了https://github.com/mathiasbynens/regenerate ; 这将是 pegjs 包的第一个依赖项,我希望这是可能的(还有用于 Unicode 属性的 polyfills 可以作为依赖项添加,参见 #648)。 如果您不知道 UTF-16 代理,请参阅维基百科。
为了使 PEG.js 与整个 Unicode 兼容,有多个级别:
.
以捕获 1 或 2 个代码单元。对于大多数点,我们可以设法向后兼容并生成与旧的解析器非常相似的解析器,除了点 5,因为解析的结果可能取决于点规则是否捕获了一个或两个代码单元。 为此,我建议添加一个运行时选项,让用户在两个或三个选项之间进行选择:
可以在解析器生成期间静态分析 Regex 类,以检查它们是否具有固定长度(以代码单元数表示)。 有 3 种情况:1. 只有一个 BMP 或单个代码单元,或 2. 只有两个代码单元,或 3. 一两个代码单元,具体取决于运行时。 现在,字节码假定正则表达式类始终是一个代码单元(请参阅此处)。 通过静态分析,对于前两种情况,我们可以将此字节码指令的此参数更改为 1 或 2。 但是对于第三种情况,我想应该添加一个新的字节码指令,以便在运行时获取匹配的代码单元数并相应地增加光标。 没有新字节码指令的其他选项将是: 1. 始终计算匹配的代码单元的数量,但在仅 BMP 解析器的解析过程中这是性能损失,因此我不喜欢此选项; 2. 计算当前代码单元是否是高代理,然后是低代理以递增 1 或 2,但这将假设语法始终具有格式良好的 UTF-16 代理,而无法编写具有单独代理的语法(见下一点),这也是 BMP-only 解析器的性能损失。
存在单独代理的问题(高代理没有低代理,或者低代理没有高代理)。 我对此的看法是,正则表达式类应该是唯一的:要么带有单独的代理,要么带有格式良好的 UTF-16 Unicode 字符(BMP 或高代理后跟低代理),否则存在语法作者不知道的危险UTF-16 的微妙之处混合了两者,无法理解结果,想要管理自己的 UTF-16 代理的语法作者可以使用 PEG 规则来描述特定高低代理之间的联系。 我建议在解析器生成期间添加一个执行此规则的访问者。
总而言之,在 PEG 中处理单独代理的问题可能比在正则表达式中更容易,因为 PEG 解析器总是在前进,因此要么识别下一个代码单元要么不识别,与正则表达式相反,其中可能有一些回溯可能关联或将高代理与低代理分离,从而更改匹配的 Unicode 字符数等。
用于星形 Unicode 字符的 ES6 语法的 PR 是基于 #616 的 #651,类的开发是https://github.com/Seb35/pegjs/commit/0d33a7a4e13b0ac7c55a9cfaadc16fc0a5dd5f0c实现了我上面的评论中的第 2 点和第 3 点,光标增量的快速破解(第 4 点),现在没有规则点.
(第 5 点)。
我目前关于这个问题的开发基本完成,更高级的工作在https://github.com/Seb35/pegjs/tree/dev-astral-classes-final。 上面提到的所有五点都得到了处理,并且全局行为试图模仿关于边缘情况的 JS 正则表达式(并且有很多)。
全局行为由选项unicode
类似于 JS 正则表达式中的标志unicode
:光标根据实际文本(例如[^a]
匹配文本“💯”,光标增加2个代码单元)。 When the option unicode
is false the cursor is always increased of 1 code unit.
关于输入,我不确定我们是否以与 JS 正则表达式相同的方式设计 PEG.js:我们是否应该在非 Unicode 模式的语法中授权[\u{1F4AD}-\u{1F4AF}]
(相当于[\uD83D\uDCAD-\uD83D\uDCAF]
) ? 我们可以区分“输入 Unicode”和“输出 Unicode”:
就我个人而言,我想我更希望我们永久授权输入 Unicode,或者使用带有默认true
的选项,因为没有显着的开销,默认情况下这将为每个人启用这种可能性,但输出 Unicode 应保持false
因为生成的解析器的性能更好(游标增量始终为 1)。
关于这个问题(以及关于输出 Unicode 默认为false
),我们应该记住,已经有可能在我们的语法中编码 Unicode 字符,代价是理解 UTF-16 的功能:
// rule matching [\u{1F4AD}-\u{1F4AF}]
my_class = "\uD83D" [\uDCAD-\uDCAF]
// rule matching any Unicode character
my_strict_unicode_dot_rule = $( [\u0000-\uD7FF\uE000-\uFFFF] / [\uD800-\uDBFF] [\uDC00-\uDFFF] )
// rule matching any Unicode character or a lone surrogate
my_loose_unicode_dot_rule = $( [\uD800-\uDBFF] [\uDC00-\uDFFF]? / [\u0000-\uFFFF] )
因此,想要快速解析器并能够识别她/他的语法特定部分中的 Unicode 字符的语法作者可以使用这样的规则。 因此,这个问题仅仅是关于简化 Unicode 管理,而不是深入研究 UTF-16 内部。
关于实现,我在第一次尝试时考虑到语法文本是用 Unicode 字符编码的,而 PEG.js 解析器的“点”规则可以识别 Unicode 字符。 第二次也是最后一次尝试恢复了这一点(点规则总是 1 个代码单元以便更快地解析)并且访问者 prepare-unicode-classes.js 中有一个小算法来重建字符类中的拆分 Unicode 字符(例如[\uD83D\uDCAD-\uD83D\uDCAF]
在语法上被识别为[ "\uD83D", [ "\uDCAD", "\uD83D" ], "\uDCAF" ]
并且该算法将其转换为[ [ "\uD83D\uDCAD", "\uD83D\uDCAF" ] ]
)。 我想在语法本身中写这个,但它会很长,更重要的是有多种方法来编码字符(“💯”,“uD83DuDCAF”,“u{1F4AF}”),所以写起来更容易在访客中。
我在第二次尝试中添加了两个操作码:
(input.charCodeAt(currPos) & 0xFC00) === 0xD800 && input.length > currPos + 1 && (input.charCodeAt(currPos+1) & 0xFC00) === 0xDC00
classes[c].test(input.substring(currPos, currPos+2)
ACCEPT_N
将光标增加 1 或 2 个代码单元,并且字符类被分成两部分固定长度的正则表达式(1 或 2 个代码单元)。我对“匹配”功能进行了一些优化,在生成过程中根据模式(Unicode 与否)和字符类消除了“死代码”路径。
另请注意,此实现中的正则表达式始终为正:反转的正则表达式返回相反的结果,从而产生字节码。 这更容易避免代理周围的边缘情况。
我写了一些文档,但我可能会添加更多(也许是一个指南,可以快速解释选项的详细信息 unicode 和带有自制 Unicode 点规则的片段)。 我将在将其作为 PR 提交之前添加测试。