Python pygments词法分析器状态保存

10

在以下C++文本上运行Pygments默认的词法分析器:class foo{};,得到的结果如下:

(Token.Keyword, 'class')
(Token.Text, ' ')
(Token.Name.Class, 'foo')
(Token.Punctuation, '{')
(Token.Punctuation, '}')
(Token.Punctuation, ';')
请注意,toke foo 的类型为 Token.Name.Class
如果我将类名更改为 foobar,我希望只能在触摸到的标记上运行默认词法分析器,例如原始标记foo{
问:如何保存词法分析器状态,以便对 foobar{ 进行标记化时得到类型为Token.Name.Class的标记?
有了这个功能,可以优化大型源文件的语法高亮,例如用户在文件中间输入文本。似乎没有记录这样做的方法,也没有关于如何使用默认的pygments词法分析器进行此操作的信息。
还有其他支持此行为的语法突出显示系统吗?
编辑:
关于性能,这是一个例子:http://tpcg.io/ESYjiF

你有考虑过性能影响吗?我的意思是,大多数词法分析器都会进行完整的解析,因此即使是这么小的差异,也可能意味着完全的更改或破坏其他标记。比如将 foo 改为 foo {,这会引入另一个括号,整个代码的含义实际上会发生变化。因此,在任何情况下,这可能不是一个好主意。 - Tarun Lalwani
@Tarun Lalwani 在一台性能不错的机器上,处理一个200 KB的文件(确实很大),我得到了0.5毫秒的词法分析时间。但使用代码格式化工具后,我得到了0.5秒的处理时间。虽然词法分析的时间是“可以接受的”,但总体的处理性能却不符合我的标准。 - Raxvan
@Tarun Lalwani 我还添加了一个测试代码,大小为33 kb。词法分析器的结果似乎是一个生成器,因此初始词法分析时间非常短,但迭代标记会揭示解析代码所花费的总时间。 - Raxvan
你想要实现的功能叫做“重命名符号”,在按下F2键后,你可以在VS Code中找到它。如果你使用类似Flex的东西,可以通过重命名全局字符串表中的条目来完成。 - obgnaw
1个回答

6
根据我对源代码的理解,您想要的是不可能的。我不会深入挖掘并解释每一行相关的代码,但基本上,情况是这样的: 最后,RegexLexer.get_tokens_unprocessed 循环遍历定义的标记类型(类似于 (("function", ('pattern-to-find-c-function',)), ("class", ('function-to-find-c-class',)))),对于每个类型(functionclasscomment...)在源文本中查找所有匹配项,然后处理下一个类型。
这种行为使得你想要的结果不可能实现,因为它循环遍历的是标记类型,而不是文本。
为了更加明确我的观点,我在库中添加了两行代码,文件:pygments/lexer.py,行号:628
for rexmatch, action, new_state in statetokens:
    print('looking for {}'.format(action))
    m = rexmatch(text, pos)
    print('found: {}'.format(m))

并使用以下代码运行它:

import pygments
import pygments.lexers

lexer = pygments.lexers.get_lexer_for_filename("foo.h")
sample="""
class foo{};
"""
print(list(lexer.get_tokens(sample)))

输出:

[...]
looking for Token.Keyword.Reserved
found: None
looking for Token.Name.Builtin
found: None
looking for <function bygroups.<locals>.callback at 0x7fb1f29b52f0>
found: None
looking for Token.Name
found: <_sre.SRE_Match object; span=(6, 9), match='foo'>
[...]

正如您所看到的,令牌类型是代码迭代的内容。

根据Tarun Lalwani在评论中提到的,一个新字符可能会破坏整个源代码结构,因此您最好在每次更新时重新对整个文本进行词法分析。


在检查实现后,您是正确的,但更改的令牌永远不会影响其他先前令牌的类型。更改后的令牌确实可以更改,但这也可以检查以最小化正则表达式匹配。每次都对所有内容进行正则表达式匹配似乎非常浪费时间。此外,似乎一些IDE(如CLion)确实存在问题,语法高亮需要数小时(至少在CLion上)。 - Raxvan
你说得对。这很有趣,让我深入挖掘一下,然后再回来给你答复。 - Arount
我没有时间尝试,但是除了标记类型之外,将堆栈保存起来应该可以解决更改前的标记问题。对于更改后的标记,情况会更加复杂。我正在考虑为标记值后剩余文本内容保留一个哈希值。使用该哈希值,当你遇到与“原始”标记匹配的哈希值时,你应该能够停止正则表达式匹配。如果你能提供一个可行的原型,奖金就属于你:)。 - Raxvan
在现实世界中,它们仅在遇到行结束时存储状态,然后如果行发生更改,就从上一行不发生更改的位置开始。@Raxvan - obgnaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接