лексеры против парсеров - PullRequest
292 голосов
/ 16 мая 2010

Действительно ли лексеры и парсеры теоретически отличаются друг от друга?

Кажется модным ненавидеть регулярные выражения: ужас кодирования , еще одно сообщение в блоге .

Однако, популярные инструменты на основе лексинга: pygments , geshi или prettify , все используют регулярные выражения Кажется, они лекс что-нибудь ...

Когда достаточно лексизма, когда вам нужен EBNF?

Кто-нибудь использовал токены, сгенерированные этими лексерами, с генераторами бизонов или анализаторов antlr?

Ответы [ 5 ]

450 голосов
/ 01 сентября 2010

Что общего у парсеров и лексеров:

  1. Они читают символов некоторого алфавита из своего ввода.

    • Подсказка: алфавит не обязательно должен состоять из букв. Но это должен состоять из символов, атомных для языка понимается парсером / лексером.
    • Символы для лексера: символы ASCII.
    • Символы для синтаксического анализатора: определенные токены, которые являются терминальными символами их грамматики.
  2. Они анализируют эти символы и пытаются сопоставить их с грамматикой языка, который они понимают.

    • Вот здесь и заключается реальная разница. Подробнее см. Ниже.
    • Грамматика, понятная лексерам: обычная грамматика (уровень 3 Хомского).
    • Грамматика, понятная парсерам: грамматика без контекста (уровень 2 Хомского).
  3. Они прикрепляют семантику (значение) к языковым фрагментам, которые они находят.

    • Лексеры придают смысл, классифицируя лексемы (строки символов из входных данных) как конкретные токены . Например. Все эти лексемы: *, ==, <=, ^ будут классифицироваться лексером C / C ++ как маркер "оператора".
    • Синтаксические анализаторы придают смысл, классифицируя строки токенов из входных данных (предложений) как конкретные нетерминалы и создавая дерево разбора . Например. все эти строки токенов: [number][operator][number], [id][operator][id], [id][operator][number][operator][number] будут классифицироваться как нетерминал "expression" синтаксическим анализатором C / C ++.
  4. Они могут придавать некоторое дополнительное значение (данные) распознаваемым элементам.

    • Когда лексер распознает последовательность символов, составляющую правильное число, он может преобразовать ее в двоичное значение и сохранить с токеном «число».
    • Аналогично, когда синтаксический анализатор распознает выражение, он может вычислить его значение и сохранить его с помощью узла «expression» синтаксического дерева.
  5. Все они выдают на выходе правильные предложения языка, который они распознают.

    • Lexer создает токенов , которые предложений из обычного языка они распознают. Каждый токен может иметь внутренний синтаксис (хотя уровень 3, а не уровень 2), но это не имеет значения для выходных данных и для того, который их читает.
    • Синтаксические анализаторы создают синтаксических деревьев , которые являются представлениями предложений языка без контекста , который они распознают. Обычно это всего лишь одно большое дерево для всего документа / исходного файла, потому что весь документ / исходный файл является подходящим предложением для них. Но нет никаких причин, по которым парсер не может создать серию синтаксических деревьев на своем выходе. Например. это может быть парсер, который распознает теги SGML, вставленные в обычный текст. Таким образом, токенизирует документ SGML в серию токенов: [TXT][TAG][TAG][TXT][TAG][TXT]....

Как видите, парсеры и токенизаторы имеют много общего. Один синтаксический анализатор может быть токенизатором для другого синтаксического анализатора, который считывает свои входные токены как символы из своего собственного алфавита (токены являются просто символами некоторого алфавита) так же, как предложения одного языка могут быть буквенными символами некоторого другого, более высокого уровня язык. Например, если * и - являются символами алфавита M (как «символы азбуки Морзе»), то вы можете создать синтаксический анализатор, который распознает строки этих точек и строк как буквы, закодированные в коде Морзе. , Предложения на языке «азбуки Морзе» могут быть токенами для некоторого другого синтаксического анализатора, для которого эти токены являются атомными символами его языка (например, язык «английских слов»). И эти «английские слова» могут быть токенами (символами алфавита) для некоего парсера более высокого уровня, который понимает язык «английских предложений». И все эти языки отличаются только сложностью грамматики . Ничего больше.

Так что же это за "грамматические уровни Хомского"? Нуам Хомский разделил грамматику на четыре уровня в зависимости от их сложности:

  • Уровень 3: Обычные грамматики

    Они используют регулярные выражения, то есть они могут состоять только из символов алфавита (a, b), их конкатенаций (ab, aba, bbb и т. Д.) Или альтернатив (например, a|b).
    Они могут быть реализованы как конечные автоматы (FSA), например, NFA (недетерминированный конечный автомат) или лучше DFA (детерминированный конечный автомат).
    Обычные грамматики не справляются с вложенный синтаксис , например правильно вложенные / совпавшие круглые скобки (()()(()())), вложенные теги HTML / BBcode, вложенные блоки и т. д. Это связано с тем, что автоматы состояний должны иметь бесконечно много состояний, чтобы обрабатывать бесконечно много уровней вложенности.
  • Уровень 2: контекстно-свободные грамматики

    Они могут иметь вложенные, рекурсивные, самоподобные ветви в своих синтаксических деревьях, поэтому они могут хорошо обрабатывать вложенные структуры.
    Их можно реализовать как автомат состояний со стеком. Этот стек используется для представления уровня вложенности синтаксиса. На практике они обычно реализуются как анализатор рекурсивного спуска сверху вниз, который использует стек вызовов машинных процедур для отслеживания уровня вложенности и использует рекурсивно вызываемые процедуры / функции для каждого нетерминального символа в их синтаксисе.
    Но они не могут справиться с контекстно-зависимым синтаксисом . Например. когда у вас есть выражение x+3 и в одном контексте это x может быть именем переменной, а в другом контексте это может быть имя функции и т. д.
  • Уровень 1: контекстно-зависимая грамматика

  • Уровень 0: Неограниченные грамматики
    Также называемые рекурсивно перечислимыми грамматиками.

98 голосов
/ 18 мая 2010

Да, они очень разные в теории и в реализации.

Лексеры используются для распознавания «слов», составляющих языковые элементы, потому что структура таких слов, как правило, проста. Регулярные выражения очень хорошо справляются с этой более простой структурой, и для реализации лексеров используются очень высокопроизводительные механизмы сопоставления регулярных выражений.

Парсеры используются для распознавания "структуры" языковых фраз. Такая структура, как правило, намного превосходит то, что могут распознавать «регулярные выражения», поэтому необходимо «контекстно-зависимые» парсеры для извлечения такой структуры. Контекстно-зависимые парсеры сложны в построении, поэтому инженерный компромисс состоит в использовании «контекстно-свободных» грамматик и добавить хаки в анализаторы («таблицы символов» и т. д.) для обработки контекстно-зависимой части.

Ни лексинг, ни технология синтаксического анализа, скорее всего, скоро исчезнут.

Они могут быть объединены, решив использовать технологию "разбора" для распознавания "слов", как это в настоящее время исследуют так называемые анализаторы GLR без сканирования. Это требует затрат времени выполнения, поскольку вы применяете более общие механизмы для решения часто не нуждающейся проблемы, и обычно вы платите за это накладными расходами. Там, где у вас много свободных циклов, эти накладные расходы могут не иметь значения. Если вы обрабатываете много текста, тогда издержки имеют значение, и классические парсеры регулярных выражений будут продолжать использоваться.

30 голосов
/ 02 августа 2012

Когда достаточно лексизма, когда вам нужен EBNF?

EBNF действительно мало что добавляет к мощности грамматик. Это просто удобная / сокращенная запись / "синтаксический сахар" по сравнению со стандартными правилами грамматики Хомского в нормальной форме (CNF). Например, альтернатива EBNF:

S --> A | B

Вы можете достичь в CNF, перечислив каждую альтернативную продукцию отдельно:

S --> A      // `S` can be `A`,
S --> B      // or it can be `B`.

Необязательный элемент из EBNF:

S --> X?

вы можете достичь в CNF, используя nullable продукцию, то есть ту, которая может быть заменена пустой строкой (обозначается здесь просто пустой продукцией; другие используют epsilon или лямбда или перечеркнутый круг):

S --> B       // `S` can be `B`,
B --> X       // and `B` can be just `X`,
B -->         // or it can be empty.

Производство в форме, подобной последней B выше, называется «стиранием», потому что оно может стереть все, что обозначает в других производствах (создать пустую строку вместо чего-то еще).

Повторение нуля или более от EBNF:

S --> A*

вы можете обтанить, используя рекурсивную продукцию, то есть ту, которая встраивается где-то в нее. Это можно сделать двумя способами. Первый из них - левая рекурсия (чего обычно следует избегать, потому что анализаторы нисходящего рекурсивного спуска не могут его проанализировать):

S --> S A    // `S` is just itself ended with `A` (which can be done many times),
S -->        // or it can begin with empty-string, which stops the recursion.

Зная, что он генерирует только пустую строку (в конечном итоге), за которой следует ноль или более A с, эту же строку (, но не тот же язык! ) можно выразить, используя right- рекурсия

S --> A S    // `S` can be `A` followed by itself (which can be done many times),
S -->        // or it can be just empty-string end, which stops the recursion.

И когда дело доходит до + за одно или несколько повторений от EBNF:

S --> A+

это можно сделать, выделив один A и используя *, как и раньше:

S --> A A*

, который вы можете выразить в CNF как таковой (здесь я использую правильную рекурсию; попробуйте выяснить другую в качестве упражнения):

S --> A S   // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A     // or it could be just one single `A`.

Зная это, теперь вы, вероятно, можете распознать грамматику для регулярного выражения (то есть регулярная грамматика ) как грамматику, которая может быть выражена в единственном произведении EBNF, состоящем только из терминальных символов. В более общем смысле вы можете распознать обычные грамматики, когда видите произведения, подобные этим:

A -->        // Empty (nullable) production (AKA erasure).
B --> x      // Single terminal symbol.
C --> y D    // Simple state change from `C` to `D` when seeing input `y`.
E --> F z    // Simple state change from `E` to `F` when seeing input `z`.
G --> G u    // Left recursion.
H --> v H    // Right recursion.

То есть, используя только пустые строки, терминальные символы, простые нетерминалы для подстановок и изменений состояния, и используя рекурсию только для достижения повторения (итерация, которая является просто линейной рекурсией - та, которая не ветвь древовидная). Ничего более сложного, кроме этого, вы уверены, что это обычный синтаксис, и вы можете использовать только лексер для этого.

Но когда ваш синтаксис использует рекурсию нетривиальным образом, для создания древовидных, самоподобных, вложенных структур, как показано ниже:

S --> a S b    // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S -->          // or it could be (ultimately) empty, which ends recursion.

тогда вы можете легко увидеть, что это невозможно сделать с помощью регулярного выражения, потому что вы никак не можете преобразовать его в одну единицу EBNF; в конечном итоге вы замените S на неопределенный срок, что всегда добавит еще a s и b s с обеих сторон. Лексеры (точнее: конечные автоматы, используемые лексерами) не могут сосчитать до произвольного числа (они конечны, помните?), Поэтому они не знают, сколько было a с, чтобы сопоставить их равномерно с таким количеством b s. Грамматики, подобные этой, называются контекстно-свободными грамматиками (как минимум), и для них требуется синтаксический анализатор.

Общеизвестно, что контекстно-зависимые грамматики разбираются, поэтому они широко используются для описания синтаксиса языков программирования. Но это еще не все. Иногда требуется более общая грамматика - когда у вас есть много вещей, чтобы сосчитать одновременно, независимо. Например, когда вы хотите описать язык, в котором можно использовать круглые скобки и квадратные скобки с чередованием, но они должны быть правильно спарены друг с другом (фигурные скобки с круглыми скобками). Этот вид грамматики называется контекстно-зависимым . Вы можете распознать его по тому, что у него более одного символа слева (перед стрелкой). Например:

A R B --> A S B

Вы можете рассматривать эти дополнительные символы слева как «контекст» для применения правила. Могут быть некоторые предварительные условия, постусловия и т. Д. Например, вышеприведенное правило будет заменять R на S, но только когда оно находится между A и B, оставляя эти A и B самими неизменными. , Этот вид синтаксиса действительно трудно разобрать, потому что ему нужна полноценная машина Тьюринга. Это совсем другая история, поэтому на этом я закончу.

11 голосов
/ 11 июня 2014

Чтобы ответить на вопрос в том виде, в котором он был задан (без чрезмерного повторения того, что появляется в другие ответы)

Лексеры и парсеры не очень отличаются, как это предлагается принятый ответ. Оба основаны на простых языковых формализмах: регулярный языки для лексеров и, почти всегда, контекстно-свободные (CF) языки для парсеров. Они оба связаны с довольно простыми вычислительными модели, конечный автомат и стековый автомат. Обычные языки - это особый случай контекстно-свободных языков, поэтому что лексеры могут быть получены с несколько более сложным CF технология. Но это не очень хорошая идея по крайней мере по двум причинам.

Основополагающим моментом в программировании является то, что системный компонент должен быть в курсе самых подходящих технологий, чтобы легко производить, понимать и поддерживать. Технология не должна быть излишнее (использование методов, намного более сложных и дорогостоящих, чем необходимо), и при этом это не должно быть на пределе его власти, таким образом, требуя технического извращения для достижения желаемой цели.

Вот почему "Кажется модным ненавидеть регулярные выражения". Хотя они могут сделать многое, иногда они требуют очень нечитаемого кодирование для достижения этого, не говоря уже о том, что различные расширения и ограничения в реализации несколько снижают их теоретические простота. Лексеры обычно не делают этого, и, как правило, простые, эффективная и соответствующая технология для разбора токена. Использование парсеров CF для токена было бы излишним, хотя это возможно.

Другая причина не использовать формализм CF для лексеров заключается в том, что он может тогда будьте заманчивы использовать всю мощь CF. Но это может поднять структурные проблемы с чтением программ.

По сути, большая часть структуры текста программы, из которой смысл извлекается, это древовидная структура. Выражает, как разбирать предложение (программа) генерируется из правил синтаксиса. Семантика это получены композиционными методами (гомоморфизм для математически ориентированный) от того, как синтаксические правила составлены для построить дерево разбора. Следовательно, древовидная структура имеет важное значение. Тот факт, что токены идентифицируются с помощью регулярного набора на основе лексера не меняет ситуацию, потому что CF состоит из регулярных еще дает CF (я говорю очень свободно о регулярных преобразователях, что преобразовать поток символов в поток токена).

Однако CF состоит из CF (через преобразователи CF ... извините за математика), не обязательно дает CF, и может сделать вещи более в общем, но менее податливый на практике. Так что CF не подходит инструмент для лексеров, хотя его можно использовать.

Одним из основных отличий между обычным и МВ является то, что регулярный языки (и преобразователи) очень хорошо сочетаются практически с любыми формализм различными способами, в то время как языки CF (и преобразователи) делают нет, даже с собой (за некоторыми исключениями).

(Обратите внимание, что обычные преобразователи могут иметь другое использование, например, формализация некоторых методов обработки синтаксических ошибок.)

BNF - это просто особый синтаксис для представления грамматик CF.

EBNF - синтаксический сахар для BNF , использующий средства регулярного обозначение, чтобы дать более краткую версию грамматик БНФ. Это всегда может быть превращается в эквивалент чистого БНФ.

Однако регулярные обозначения часто используются в EBNF только для того, чтобы подчеркнуть части синтаксиса, соответствующие структуре лексического элементы, и должны быть распознаны с лексером, а остальные с быть довольно представленным в прямой BNF. Но это не абсолютное правило.

Подводя итог, более простая структура токена лучше анализируется с помощью более простая технология обычных языков, в то время как дерево ориентировано структура языка (синтаксиса программы) лучше обрабатывается CF грамматик.

Я бы предложил также посмотреть AHR-ответ .

Но это оставляет открытым вопрос: Почему деревья?

Деревья являются хорошей основой для определения синтаксиса, потому что

  • они дают тексту простую структуру

  • там очень удобно связывать семантику с текстом на основе этой структуры, с математически хорошо понимаемая технология (композиционность через гомоморфизмы), как указано выше. Это фундаментальный алгебраический инструмент для определения семантика математических формализмов.

Следовательно, это хорошее промежуточное представление, как показано успех абстрактных синтаксических деревьев (AST). Обратите внимание, что АСТ часто отличается от дерева разбора, потому что технология синтаксического анализа, используемая многими профессионалы (такие как LL или LR) относятся только к подмножеству CF грамматики, таким образом вызывая грамматические искажения, которые позже исправлено в АСТ. Этого можно избежать с помощью более общего анализа технология (основанная на динамическом программировании), которая принимает любую грамматику CF.

Заявление о том, что языки программирования контекстно-зависимые (CS), а не CF произвольны и спорны.

Проблема в том, что разделение синтаксиса и семантики произвольны. Проверка деклараций или типового соглашения может рассматриваться как либо часть синтаксиса, либо часть семантики. То же самое относится и к гендерное и числовое соглашение на естественных языках. Но есть естественные языки, где множественное число зависит от фактической семантики значение слов, чтобы оно не вписывалось в синтаксис.

Множество определений языков программирования в денотационной семантике размещать объявления и проверку типов в семантике. Так заявляя как сделано Ира Бакстер , что парсеры CF взламываются, чтобы получить контекст чувствительность, требуемая синтаксисом, является в лучшем случае произвольным представлением ситуация. Это может быть организовано как взломать в некоторых компиляторах, но это не должно быть.

Кроме того, не только то, что парсеры CS (в том смысле, в котором они используются в других ответах здесь) сложны для построения эффективный. Они также неадекватно выражают Вид чувствительности к контексту, который может быть необходим. И они не естественно создавать синтаксическую структуру (такую ​​как деревья разбора), которая удобно выводить семантику программы, т.е. генерировать скомпилированный код.

6 голосов
/ 10 марта 2014

Есть ряд причин, по которым часть анализа компилятора обычно разделены на фазы лексического анализа и синтаксического анализа (синтаксического анализа).

  1. Простота дизайна является наиболее важным фактором. Разделение лексического и синтаксического анализа часто позволяет упростить хотя бы одну из этих задач. Например, парсер, который должен был иметь дело с комментариями и пробелами как синтаксическими единицами. Значительно более сложный, чем тот, который может предполагать комментарии и пробелы, уже был удален лексическим анализатором. Если мы разрабатываем новый язык, разделение лексических и синтаксических проблем может привести к более четкой общей структуре языка.
  2. Улучшена эффективность компилятора. Отдельный лексический анализатор позволяет нам применять специализированные методики, которые служат только лексической задаче, а не разбору. Кроме того, специальные методы буферизации для чтения вводимых символов могут значительно ускорить компилятор.
  3. Улучшена переносимость компилятора. Специфичные для устройства ввода особенности могут быть ограничены лексическим анализатором.

ресурс ___ Компиляторы (2-е издание) написано- Альфред В. Або Колумбийский университет Моника С. Лам Стэндфордский Университет Рави Сетхи Avaya Джеффри Д. Уллман Стэнфордский университет

...