За последние пару лет я написал токенизаторы и парсеры для пары небольших ориентированных на индентирование доменных языков, и то, что у вас есть, выглядит для меня вполне разумным, чего бы это ни стоило. Если я не ошибаюсь, ваш метод очень похож на то, что делает, например, Python, который, похоже, должен иметь некоторый вес.
Преобразование NEWLINE NEWLINE INDENT в просто INDENT до того, как он попадет в парсер, определенно кажется правильным способом сделать что-то - это боль (IME) - всегда смотреть вперед в парсере! Я фактически сделал этот шаг как отдельный слой в том, что в итоге получилось трехэтапным: первый объединил то, что делают ваш лексер и Layouter, за вычетом всего, что было сделано в NEWLINE (что делало его очень простым), второй (тоже очень простой) ) слой сложил последовательные NEWLINEs и преобразовал NEWLINE INDENT в просто INDENT (или, собственно, COLON NEWLINE INDENT в INDENT, поскольку в этом случае все блоки с отступом всегда предшествовали двоеточиями), затем парсер был третьим этапом поверх этого. Но для меня также имеет смысл делать вещи так, как вы их описали, особенно если вы хотите отделить лексер от Layouter, что, вероятно, вы захотите сделать, если бы вы использовали инструмент генерации кода например, сделать свой лексер, как это принято.
У меня действительно было одно приложение, которое должно было быть немного более гибким в отношении правил отступов, по сути оставляя парсеру принудительное применение их при необходимости - в некоторых контекстах, например, должно быть допустимо следующее:
this line introduces an indented block of literal text:
this line of the block is indented four spaces
but this line is only indented two spaces
, который не очень хорошо работает с токенами INDENT / DEDENT, так как в итоге вам нужно сгенерировать один INDENT для каждого столбца отступа и равное количество DEDENT на обратном пути, если только вы не заглянете далеко вперед, чтобы выяснить, где Уровни отступа в конечном итоге окажутся такими, какими, по-видимому, вы не хотели бы, чтобы токенизатор делал. В этом случае я попробовал несколько разных вещей и в итоге просто сохранил счетчик в каждом токене NEWLINE, который дал изменение отступа (положительное или отрицательное) для следующей логической строки. (Каждый токен также сохранял все завершающие пробелы, на случай, если его нужно было сохранить; для NEWLINE сохраненный пробел включал сам EOL, любые промежуточные пустые строки и отступ в следующей логической строке.) Никаких отдельных токенов INDENT или DEDENT вообще нет. Заставить парсер справиться с этим было немного больше, чем просто вкладывать INDENTs и DEDENTs, и, возможно, это была бы адская сложная грамматика, для которой требовался причудливый генератор парсера, но это было не так плохо, как я опасался, или. Опять же, нет необходимости, чтобы парсер смотрел вперед от NEWLINE, чтобы увидеть, есть ли INDENT в этой схеме.
Тем не менее, я думаю, что вы согласитесь с тем, что разрешение и сохранение всевозможных сумасшедших пробелов в токенизаторе / Layouter и разрешение синтаксическому анализатору решать, что является литералом, а какой код - немного необычным требованием! Вы, конечно, не хотели бы, чтобы ваш синтаксический анализатор был обременен этим счетчиком отступов, если вы просто хотите иметь возможность, например, разбирать код Python. То, как вы делаете вещи, почти наверняка является правильным подходом для вашего приложения и многих других. Хотя, если у кого-то еще есть мысли о том, как лучше всего делать подобные вещи, я бы с удовольствием их услышал ...