Comments 38
передняя часть — серединка — задняя часть
заодно возможность контекстно-зависимых ключевых слов
Как связана интегрируемость токенизатора с возможностью использования контекстно-зависимых ключевых слов? Их и так можно использовать в контекстно свободном парсере на токенах.
--[[comment]]
--[=[com]]ment]=]
--[==[com]]=]ment]==]
--[===[com]]=]==]ment]===]
Количество символов '=' в начале и конце блока комментария должно совпадать. Как это реализовать не совмещая токенизацию и парсинг? А что делать, если в требуемом синтаксисе части с подобной зависимостью не находятся в пределах одного токена, а разделены кучей других?
Можно. В ANTLR для такого существуют семантические предикаты и действия, в которых можно писать код на целевом языке, который может делать что угодно, например, считать точное соответствие символов =
.
Каждое '=' — один токен, а уж парсер ищет один, два или сколько надо этих токенов подряд.
Кстати, это можно реализовать обычными регулярками
/\-\-\[(?P<comment>=+).*?(P=comment)\]
, а это всё же не полноценный LL\LR парсер, а ограниченная кс-граммтика.
- Алгоритмическую сложность это не меняет :)
- Единственная ответственность
- На PHP я реализовал токенайзер на генераторах и два прохода по сути идут параллельно :)
На PHP я реализовал токенайзер на генераторах и два прохода по сути идут параллельно :)
Тут есть несколько проблем:
- Нормальный (по скорости) лексер на пыхе возможен только с использованием preg_match_all/preg_replace_callback, т.е. с полным анализом всего и сразу.
- Даже если написать вручную это дело или используя preg_match, то нужен буфер для всяких lookahead в парсере.
А ещё я бы попросил ссылочкой на гитхаб в меня покидаться, не отказался бы посмотреть на это дело, можно?)
Я тоже реализовывал на ANTLR: php grammar. Поддерживаются интерполируемые строки, альтернативный синтаксис, Heredoc строки.
Кажется, я не очень удачно выразился. Это интерпретатор простенького языка, написанный на PHP в качестве тестового задания, а не интерпретатор PHP на PHP. Пока на гитхаб не выкладывал, чтоб конкуренты на вакансию не увели :)
Но совмещение лексера и парсера часто применяют при использовании парсерных комбинаторов. По моему опыту сильный недостаток этого подхода — в гармматике приходится тщательно прописывать, где разрешены комментарии.
Не совсем понимаю, в чем состояли Ваши затруднения при работе с комбинаторами. Вот пример их использования для игрушечного Си-подобного языка. Как можно видеть, комментарии поддерживаются без каких-либо специальных усилий:
github.com/true-grue/PigletC/blob/master/src/parse.py
Примерно так:
val functionCallArgs: P[Seq[EXPR]] = comment ~ baseExpr.rep(sep = comment ~ "," ~ comment) ~ comment
Комбинаторы — удобный способ создавать простые eDSL на языке, который поддерживает механизм лексических замыканий. Соответственно, комбинаторами мы можем описать БНФ-грамматику, разбор для которой может быть на деле совершенно произвольным.
По-настоящему популярным подход без выделенной стадии лексического анализа стал с появлением работ по PEG (напомню, что одна из сильных сторон scannerless-подхода — удобное описание вложенных грамматик). Соответственно, далее я буду рассматривать PEG-комбинаторы. Если мы посмотрим на классическую уже статью Форда, то увидим в одном из примеров набор правил, часть которых относится к уровню лексического разбора, а другая — к синтаксическому разбору.
...
DOT <- ’.’ Spacing
Spacing <- (Space / Comment)*
Comment <- ’#’ (!EndOfLine .)* EndOfLine
...
Здесь сразу становится понятно, что как отдельную сущность, в духе Вашего примера на Scala, комментарии выносить на уровень синтаксических правил совершенно излишне. Но остается Spacing, который действительно повторяется достаточно часто.
В таких системах, как Ometa и Ohm, Spacing неявно вызывается при указании правила для соотв. токена. Например, в Ometa Spacing будет вызван, если Вы в качестве имени правила указали класс токена в кавычках. Комбинаторы же сильны своей программируемостью и мы всегда можем задать удобную для нас функцию высшего порядка, в духе:
seq(kw("if"), op("("), expr, op(")") ...
Здесь, kw и op занимаются, в том числе, разбором пробельных элементов. Ну а Ваш пример будет выглядеть следующим образом:
args = list_of(expr, op(","))
В целом, я бы порекомендовал перед использованием готовых комбинаторов поупражняться в создании игрушечной системы на основе PEG. Поскольку, как я уже сказал выше, комбинаторы хороши своей программируемостью и Вы мало выиграете, если будете только пассивно пользоваться готовыми возможностями.
При использовании генераторов парсеров это приведет к раздуванию таблиц, сильному увеличению времени рабты генератора и увеличению выходного файла.
Тут можно поспорить, насколько сильное это увеличение времени. Зато генераторы парсеров дают: быстрая разработка, возможность генерации под разные языки, обработка ошибок из коробки, как и обработка скрытых токенов (как комментарии).
Я имел в виду не генераторы парсеров, а потоковый лексер/токенайзер, использующий генераторы как сахар для потока. Думаю над потоковым парсером, но ещё не понял возможно ли AST в принципе выдавать как поток для Си-подобных языков и имеет ли это какой-то смысл.
lexer->getNextToken()
лексер прочитал следующий токен и остановился, попутно сохранив offset
. В итоге это просто инкапсуляция чтобы не думать о токенизации во время написания синтаксического анализатора.Просто как небольшое примечание.
Википедия, конечно, не истина в последней инстанции, но в моих глазах она довольно авторитетна, по крайней мере по околопрограммистским и околоматематическим темам.
По поводу предметности несогласия о терминологии: язык меняется со временем (я написал предыдущей комментарий на основании субъективных наблюдений — со временем «парсинг» попадается чаще и чаще в местах, где раньше я бы ожидал прочитать «синтаксический анализ»). «Абсолютная истина» может быть определена в узких (по тематике и времени) контекстах; более предметным спор становится в кругу экспертов (мне же с парсингом / синтаксическим анализом не так часто приходится иметь дело — большей ясности хотелось для себя лично).
В условиях неоднозначности я, если мне это сильно занятно, пытаюсь выяснить наиболее частое словоупотребление, и использовать именно его. Пример — ошибочно использовал везде слово «компонента» в женского роде (как в «компонента связности» и в прочих математических и физических «компонентах»), удивляясь, всё чаще встречая его в мужском роде (учился я на математическом факультете, а работать стал программистом). Постепенно осознал, что при употреблении в инженерном контексте большинство собеседников употребляют слово «компонент» в мужском роде, так что ввиду этого мне следует себя переучить — и вообще, это два разных слова, которые я принимал за одно (да, так и есть, ещё раз заглянул в Википедию, русскоязычную уже на этот раз). Возможное различие в словоупотреблении «парсинг» и «синтаксический анализ» беру на заметку, но пока — не убеждён.
foo* bar;
bar.b = 3;
Хех, найди ошибку, что называется)
Краткий и бодрый обзор архитектуры компиляторов