Лексеры против парсеров

Действительно ли лексеры и парсеры различны в теории?

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

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

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

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

Ответ 1

У всех парсеров и лексеров есть:

  • Они читают символы некоторого алфавита со своего ввода.
    Подсказка: алфавит не обязательно должен быть букв. Но это должен быть символ, который атомный для языка, понятного парсером/лексером.
    • Символы для lexer: символы ASCII.
    • Символы для синтаксического анализатора: конкретные маркеры, которые являются терминальными символами их грамматики.
  • Они анализируют эти символы и пытаются сопоставить их с грамматикой языка, который они понимают.
    И здесь, где обычно существует реальная разница. См. Ниже.
    • Грамматика, понятная лексеры: регулярная грамматика (уровень Хомского 3).
    • Грамматика, понятная парсерами: контекстно-свободная грамматика (уровень 2 Хомского).
  • Они придают семантике (то есть) языковые фрагменты, которые они находят.
    • Лексеры придают значение, классифицируя лексемы (строки символов из ввода) в качестве конкретных токенов. Например. Все эти лексемы: *, ==, <=, ^ будут классифицированы как "операторный" токен с помощью лексика C/С++.
    • Parsers присваивают значение, классифицируя строки токенов из ввода (предложений) как конкретные нетерминалы и создавая дерево разбора. Например. все эти токен строки: [number][operator][number], [id][operator][id], [id][operator][number][operator][number] будут классифицироваться как "выражение" нетерминалом парсером C/С++.
  • Они могут присоединить некоторые дополнительные значения (данные) к признанным элементам. Например. когда лексер распознает последовательность символов, составляющую правильное число, он может преобразовать его в свое двоичное значение и сохранить с помощью токена "число". Аналогично, когда парсер распознает выражение, он может вычислить его значение и сохранить с помощью "выражения" node дерева синтаксиса.
  • Все они производят на своем выходе правильные предложения распознаваемого языка.
    • Лексеры производят токены, которые являются предложениями обычного языка, который они распознают. Каждый токен может иметь внутренний синтаксис (хотя уровень 3, а не уровень 2), но это не имеет значения для выходных данных и для тех, которые их читают.
    • Парсеры производят деревья синтаксиса, которые являются представлениями предложений контекстно-свободного языка, который они распознают. Обычно это только одно большое дерево для всего документа/исходного файла, потому что весь документ/исходный файл является для них подходящим предложением. Но нет причин, по которым парсер не мог создать серию синтаксических деревьев на своем выходе. Например. это может быть синтаксический анализатор, который распознает теги SGML, прикрепленные к текстовому тексту. Таким образом, он будет документировать документ SGML в серии токенов: [TXT][TAG][TAG][TXT][TAG][TXT]....

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

Итак, что же все эти "грамматические уровни Хомского"? Ну, Ноам Чомский классифицировал грамматики на четыре уровня в зависимости от их сложности:

  • Уровень 3: Регулярные грамматики
    Они используют регулярные выражения, то есть они могут состоять только из символов алфавита (a, b), их конкатенаций (ab, aba, bbb etd.) или альтернативы (например, a|b).
    Они могут быть реализованы как автоматы с конечным состоянием (FSA), такие как NFA (недетерминированный конечный автомат) или лучше DFA (детерминированный конечный автомат).
    Регулярные грамматики не могут обрабатывать с вложенным синтаксисом, например правильно вложенные/совпадающие круглые скобки (()()(()())), вложенные теги HTML/BBcode, вложенные блоки и т.д. Это потому, что автоматы состояний, с которыми нужно иметь дело, должны иметь бесконечное число состояний для обработки бесконечного числа уровней вложенности.
  • Уровень 2: Контекстно-свободные грамматики
    Они могут иметь вложенные, рекурсивные, похожие на себя ветки в своих синтаксических деревьях, поэтому они могут хорошо обрабатывать вложенные структуры.
    Они могут быть реализованы как государственный автомат с стеком. Этот стек используется для представления уровня вложенности синтаксиса. На практике они обычно реализуются как парсер с нисходящим, рекурсивным спусками, который использует стеки вызовов машинных процедур для отслеживания уровня вложенности и использует рекурсивно называемые процедуры/функции для каждого нетерминального символа в своем синтаксисе.
    Но они не могут обрабатывать контекстно-зависимый синтаксис. Например. когда у вас есть выражение x+3, и в одном контексте это x может быть именем переменной, а в другом контексте это может быть имя функции и т.д.
  • Уровень 1: Контекстно-зависимые грамматики
  • Уровень 0: Неограниченные грамматики
    Также называются" грамматики фазовой структуры".

Ответ 2

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

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

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

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

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

Ответ 3

Когда достаточно лексирования, когда вам нужен 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, используя нулевую продукцию, то есть ту, которая может быть заменена пустой строкой (здесь обозначается только пустая постановка, другие используют 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*

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

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 s, одна и та же строка (но не тот же язык!) может быть выражена с помощью правильной рекурсии:

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 и B с обеих сторон. Лексеры (точнее говоря: автоматические автоматы конечных состояний, используемые лексерами) не могут рассчитывать на произвольное число (они конечны, помните?), Поэтому они не знают, сколько A должно было их равномерно сопоставлять с таким количеством B s, Граммары, подобные этому, называются контекстно-свободными грамматиками (по крайней мере), и им нужен парсер.

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

A R B --> A S B

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

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