【问题标题】:Parser to get start and end positions of a token解析器获取令牌的开始和结束位置
【发布时间】:2020-07-05 16:23:27
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 您究竟为什么需要这些信息?它将如何使用? (我并不是要窥探,或者暗示您的问题是不必要的。开始和结束值的使用方式会影响它们的收集方式。

标签: python parsing tokenize abstract-syntax-tree acorn


【解决方案1】:

原则上,将行号/偏移量对转换为字节偏移量到文档中所需的只是每行起始字节偏移量的列表。因此,一种简单的方法是在读取文件时累积信息。这相当简单,因为您可以为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 意味着读取的字符数与文件中的字符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-13
    • 2019-12-08
    相关资源
    最近更新 更多