Разбор блоков как Python

Я пишу парсер lexer + в JFlex + CUP, и я хотел иметь подобный Python синтаксис относительно блоков; то есть отступ обозначает уровень блока.

Я не уверен, как с этим справиться, и нужно ли это делать на лексическом или синтаксическом уровне.

Мой текущий подход состоит в том, чтобы решить проблему на лексическом уровне - новые строки анализируются как разделители инструкций, и когда один из них обрабатывается, я перемещаю лексер в специальное состояние, которое проверяет, сколько символов находится перед новой строкой, и запоминает, в каком столбце последняя строка начинается, и соответственно вводит и открывает блок или закрывает блок символа.

Тем не менее, я сталкиваюсь со всеми видами неприятностей. Например:

  1. JFlex не может соответствовать пустым строкам, поэтому в моих инструкциях должен быть хотя бы один пробел после каждой новой строки.
  2. Я не могу закрыть два блока одновременно с этим подходом.

Мой подход правильный? Должен ли я делать разные вещи?

1 ответ

Решение

Ваш подход к обработке отступов в лексере, а не в парсере, правильный. Ну, это выполнимо в любом случае, но обычно это более простой способ, и именно так это делает сам Python (или, по крайней мере, CPython и PyPy).

Я не знаю много о JFlex, и вы не дали нам никакого кода для работы, но я могу объяснить в общих чертах.

Для вашей первой проблемы вы уже переводите лексер в специальное состояние после новой строки, так что "захват 0 или более пробелов" должен быть выполним, сбегая от обычного потока вещей и просто выполняя регулярное выражение в строке.

Для вашей второй проблемы самое простое решение (и то, которое использует Python) - сохранить стек отступов. Я продемонстрирую кое-что более простое, чем то, что делает Python.

Первый:

indents = [0]

После каждого перехода на новую строку выбирайте 0 или более пробелов как spaces, Затем:

if len(spaces) == indents[-1]:
    pass
elif len(spaces) > indents[-1]:
    indents.append(len(spaces))
    emit(INDENT_TOKEN)
else:
    while len(spaces) != indents[-1]:
        indents.pop()
        emit(DEDENT_TOKEN)

Теперь ваш парсер просто видит INDENT_TOKEN а также DEDENT_TOKEN, которые ничем не отличаются от, скажем, OPEN_BRACE_TOKEN а также CLOSE_BRACE_TOKEN на C-подобном языке.

Вы бы хотели лучше обрабатывать ошибки - поднимать какую-то ошибку токенизатора, а не неявную IndexError, может быть, использовать < вместо != таким образом, вы можете обнаружить, что вы зашли слишком далеко, вместо того, чтобы исчерпать стек (для лучшего восстановления после ошибок, если вы хотите продолжать выдавать дальнейшие ошибки вместо загрузки первой) и т. д.

Для примера реального кода (с обработкой ошибок, табуляцией, пробелами и экранированием новой строки с обратной косой чертой, а также обработкой несинтаксического отступа внутри выражений в скобках и т. Д.) См. tokenize документы и источник в stdlib.

Другие вопросы по тегам