Разбор блоков как Python
Я пишу парсер lexer + в JFlex + CUP, и я хотел иметь подобный Python синтаксис относительно блоков; то есть отступ обозначает уровень блока.
Я не уверен, как с этим справиться, и нужно ли это делать на лексическом или синтаксическом уровне.
Мой текущий подход состоит в том, чтобы решить проблему на лексическом уровне - новые строки анализируются как разделители инструкций, и когда один из них обрабатывается, я перемещаю лексер в специальное состояние, которое проверяет, сколько символов находится перед новой строкой, и запоминает, в каком столбце последняя строка начинается, и соответственно вводит и открывает блок или закрывает блок символа.
Тем не менее, я сталкиваюсь со всеми видами неприятностей. Например:
- JFlex не может соответствовать пустым строкам, поэтому в моих инструкциях должен быть хотя бы один пробел после каждой новой строки.
- Я не могу закрыть два блока одновременно с этим подходом.
Мой подход правильный? Должен ли я делать разные вещи?
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.