Парсер для получения начальной и конечной позиций токена

Я пытаюсь воспроизвести программное обеспечение для обнаружения ошибок, созданное для файла JavaScript, чтобы использовать его для поиска ошибок в файлах Python.

Процесс включает поиск начальной и конечной позиций токена на основе номера столбца.

Ниже приведен результат использования JS-анализатора acorn в файле.js:

вывод синтаксического анализа желудя

На изображении выше начальное и конечное расположение токена - это номера столбцов во всем документе.

Я проверил токенизатор Python, который дает только значения loc.start и loc.end, эквивалентные значениям на рисунке выше.

вывод токенизатора python

Но как получить начальное и конечное значения для токенов pythons точно так же, как изображение вывода желудя?

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 означает, что количество прочитанных символов не соответствует количеству символов в файле.
Другие вопросы по тегам