-1

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

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

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

橡子解析输出

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

我检查了 Python 标记器,它只给出了与上图中相同的 loc.start 和 loc.end 值。

python分词器输出

但是如何像橡子输出图片一样获取 python 标记的开始和结束值?

4

1 回答 1

0

原则上,将行号/偏移量对转换为文档中的字节偏移量所需的只是每行起始字节偏移量的列表。因此,一种简单的方法是在读取文件时累积信息。这相当简单,因为您可以提供tokenize自己的函数来返回输入行。因此,您可以收集从行号到文件位置的映射,然后包装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意味着读取的字符数与行尾的字符数不对应文件。
于 2020-07-06T17:17:04.897 回答