获取令牌开始和结束位置的解析器

Parser to get start and end positions of a token

我正在尝试复制为 JavaScript 文件构建的错误检测软件,以使用它来查找 Python 文件中的错误。

该过程涉及根据列号查找令牌的开始和结束位置。

以下是在 .js 文件上使用 acorn JS 解析器的输出:

在上图中,token的开始和结束位置是整个文档中的列号。

我已经检查了 Python 分词器,它只给出了与上图相同的 loc.start 和 loc.end 值。

但是如何像橡子输出图片一样获取pythons令牌的起始值和结束值?

原则上,将 linenumber/offset 对转换为字节偏移量到文档中所需要做的就是每行的起始字节偏移量列表。因此,一种简单的方法是在读取文件时积累信息。这相当简单,因为您可以为 tokenize 自己的函数提供 returns 输入行。因此,您可以收集从行号到文件位置的映射,然后将 tokenize 包装在一个使用该映射添加开始和结束索引的函数中。

在下面的例子中,我使用file.tell来提取当前文件位置。但如果输入不是可搜索的文件,那将不起作用;在这种情况下,您需要想出一些替代方案,例如跟踪返回的字节数 [注 1]。根据您需要索引的用途,这可能重要也可能不重要:例如,如果您只需要唯一的数字,则每行的字符串长度总计 运行 就足够了。

import tokenize
from collections import namedtuple
MyToken = namedtuple('MyToken', 'type string startpos endpos start end')

def my_tokenize(infile):
    '''Generator which requires one argument, typically an io.ioBase
       object, with `tell` and `readline` member functions.
    ''' 
    # Used to track starting position of each line.
    # Note that tokenize starts line numbers at 1 and column numbers at 0
    offsets = [0]
    # Function used to wrap calls to infile.readline(); stores current
    # stream position at the beginning of each line.
    def wrapped_readline():
        offsets.append(infile.tell())
        return infile.readline()

    # For each returned token, substitute type with exact_type and
    # add token boundaries as stream positions
    for t in tokenize.tokenize(wrapped_readline):
        startline, startcol = t.start
        endline, endcol = t.end
        yield MyToken(t.exact_type, t.string,
                      offsets[startline] + startcol,
                      offsets[endline] + endcol,
                      t.start, t.end)

# Adapted from tokenize.tokenize.main(). Errors are mine.
def main():
    import sys
    from token import tok_name

    def print_tokens(gen):
        for t in gen:
            rangepos = f'{t.startpos}-{t.endpos}'
            range = f'{t.start[0]},{t.start[1]}-{t.end[0]},{t.end[1]}'
            print(f'{rangepos:<10} {range:<20} '
                  f'{tok_name[t.type]:<15}{t.string!r}')

    if len(sys.argv) <= 1:
        print_tokens(my_tokenize(sys.stdin.buffer))
    else:
        for filename in sys.argv[1:]:
            with open(filename, 'rb') as infile:
                print_tokens(my_tokenize(infile))

if __name__ == '__main__':
    main()

备注

  1. 但这并不像听起来那么容易。除非您以二进制模式打开文件,否则从 readline 返回的是一个字符串,而不是 bytes 对象,因此它的长度以字符而不是字节为单位;此外,在行尾不是单个字符的平台(例如 Windows)上,将行尾替换为 \n 意味着读取的字符数不与文件中的字符数不对应。