在用 peg 写 parser 的时候碰到了一个问题,下面的代码中,前者可以正常匹配,后者却不行:
(defmacro t--parse-str (rule str &optional fail-fn)
`(with-work-buffer
(insert ,str)
(goto-char (point-min))
(peg-run (peg ,rule) ,fail-fn)))
(define-peg-rule t--cdo () "<!--" `(-- 'cdo))
(t--parse-str t--cdo "<!--")
(t--parse-str t--cdo "️<!--")
前者能够顺利输出 (cdo)
,后者却只能输出 nil
。我一开始还以为是 peg 的实现问题,后来仔细一看这两行代码似乎长度不一致(注意第二行的引号形状):
我测试了第二行的字符串的长度,居然是 5:
(length "️<!--")
;;=> 5
(aref "️<!--" 0)
;;=> 65039 #xfe0f
FE0F
在 Unicode 中是 Variation Selector-16 (VS16)
的点位,表示变体选择器,大致的解释如下:unicode - What does u'\ufe0f' in an emoji mean? Is it the same if I delete it? - Stack Overflow
In Unicode the value U+FE0F is called a variation selector. The variation selector in the case of emoji is to tell the system rendering the character how it should treat the value. That is, whether it should be treated as text, or as an image which could have additional properties, like color or animation.
For emoji there are two different variation selectors that can be applied, U+FE0E and U+FE0F. U+FE0E specifies that the emoji should be presented like text. U+FE0F specifies that it should be presented as an image, with color and possible animation.
我在编写测试匹配 /identifier/ 的规则时使用了一些 emoji 字符,在插入笑脸符号时我尝试性地使用了 C-8 e e
来选择笑脸,但此时 Emacs 为我插入的并不是 SMILING FACE WITH SMILING EYES
(#x1f60a) ,而是 Emoji > Smileys > affection
下的 f
选项:
两者仅有细微差距:(1f60a),
(9786,fe0f)。我能碰到这个问题估计是复制了某个使用带变体字符的字符串然后没有把里面的变体字符删除干净。想要复刻我遇到的问题很简单,首先在 Emacs 中插入
,然后尝试 (1) 在 emoji 前面添加
?
再在末尾 C-x C-e
(2) 把光标移至 emoji 后面按下 backspace
(3)把光标移至 emoji 上,按下 C-d
。对 (1) 你会得到求值错误,因为这实际上不是一个字符而是两个,对 (2) 和 (3) 你分别会看到(我这里开启了 whitespace-mode
):
我能遇到这个问题应该就是在 C-d
时没有把变体符号删干净,然后变体符号与双引号 "
结合就看不出来了。不知道某种意义上来说这算不算是操作的不统一,移动命令会把结合的码点当作一个单位,但删除的时候又分开对待了。
最后是一些普通文本字符加上 FE0F 的效果:
原始 | 修饰 |
---|---|
a | a️ |
( | (️ |
# | #️ |
! | !️ |
$ | $️ |
% | %️ |
& | &️ |
* | *️ |
+ | +️ |
’ | '️ |
" | "️ |