Парсер для получения начальной и конечной позиций токена
Я пытаюсь воспроизвести программное обеспечение для обнаружения ошибок, созданное для файла JavaScript, чтобы использовать его для поиска ошибок в файлах Python.
Процесс включает поиск начальной и конечной позиций токена на основе номера столбца.
Ниже приведен результат использования JS-анализатора acorn в файле.js:
На изображении выше начальное и конечное расположение токена - это номера столбцов во всем документе.
Я проверил токенизатор Python, который дает только значения loc.start и loc.end, эквивалентные значениям на рисунке выше.
Но как получить начальное и конечное значения для токенов 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()
Заметки
- Но это не так просто, как кажется. Если вы не откроете файл в двоичном режиме, то, что возвращается из
readline
это строка, а неbytes
объект, поэтому его длина измеряется в символах, а не в байтах; кроме того, на платформах (таких как Windows), в которых конец строки не является одиночным символом, замена конца строки на\n
означает, что количество прочитанных символов не соответствует количеству символов в файле.