LL-анализатор

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

[1]

См. также LL(1)

Синтаксический LL-анализатор (LL parser) — нисходящий синтаксический анализатор для подмножества контекстно-свободных грамматик. Он анализирует входной поток слева направо, и строит левый вывод грамматики. Класс грамматик, для которых можно построить LL-анализатор, известен как LL-грамматики.

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

LL-анализатор называется LL(k)-анализатором, если данный анализатор использует предпросмотр на k токенов (лексем) при разборе входного потока. Грамматика, которая может быть распознана LL(k)-анализатором без возвратов к предыдущим символам, называется LL(k)-грамматикой. Язык, который может быть представлен в виде LL(k)-грамматики, называется LL(k)-языком. Существуют LL(k+n)-языки, которые не являются LL(k)-языками, то есть не все контекстно-свободные языки являются LL(k)-языками.

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

LL-анализатор называется LL(*)-анализатором, если нет строгого ограничения для k и анализатор может распознавать язык, если токены принадлежат какому-либо регулярному множеству (например, используя детерминированные конечные автоматы).

Существуют противоречия между так называемой «Европейской школой» построения языков, которая основывается на LL-грамматиках, и «Американской школой», которая предпочитает LR-грамматики. Такие противоречия обусловлены традициями преподавания и деталями описания различных методов и инструментов в конкретных учебниках; кроме того, свое влияние оказал Н. Вирт из ETHZ, чьи исследования описывают различные методы оптимизации LL(1) распознавателей и компиляторов.

Общий случай[править | править вики-текст]

Синтаксический анализатор предназначен для разрешения проблемы принадлежности строки заданной грамматике и построения дерева вывода в случае принадлежности.

Синтаксический анализатор состоит из:

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

В строках таблицы располагаются символы магазинного алфавита, а в столбцах — символы терминального алфавита.

При запуске синтаксического анализа, стек уже содержит два символа:

 [ S, $ ]

Где '$' — специальный терминал, служащий для указания конца стека, а 'S' — аксиома грамматики. Синтаксический анализатор пытается при помощи правил грамматики заменить в стеке аксиому на цепочку символов, аналогичную цепочке, находящейся на входной ленте, и затем полностью прочитать ленту, опустошая стек.

Пример[править | править вики-текст]

Таблица синтаксического анализа[править | править вики-текст]

Чтобы объяснить принцип работы синтаксического LL анализатора, рассмотрим следующую грамматику:

  1. S → F
  2. S → (S + F)
  3. F → 1

И проанализируем разбор на примере строки:

(1 + 1)

Таблица синтаксического анализа для этой грамматики выглядит следующим образом:

( ) 1 + $
S 2  — 1 - -
F  —  — 3 - -

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

Процедура синтаксического анализа[править | править вики-текст]

Синтаксический анализатор просматривает первый символ '(' из входного потока, в этот момент на вершине стека находится символ 'S', на пересечении этих значений в таблице синтаксического разбора находится правило из списка грамматики. В данном примере это правило номер 2, которое предписывает заменить в стеке символ 'S' на цепочку '(S + F)'. Состояние стека после применения этого правила:

[ (, S, +, F, ), $ ]

На следующем шаге анализатор читает символ '(' из входного потока. Так как на вершине стека находится аналогичный символ '(', то происходит чтение данного символа с ленты и его удаление из стека. Состояние стека после применения этого правила:

[ S, +, F, ), $ ]

Далее на ленте находится символ '1', а на вершине стека 'S', на пересечении этих символов в таблице находится правило 1. После применения этого правила, согласно таблице, анализатор применяет правило 3. Состояние стека после применения правил:

[ F, +, F, ), $ ]
[ 1, +, F, ), $ ]

Далее синтаксический анализатор читает '1' и '+' из входного потока и, так как они соответствуют следующим двум элементам на стеке, также удаляет их из стека. В результате чего стек имеет вид:

[ F, ), $ ]

В следующих трёх шагах символ 'F' в стеке будет заменен на '1', после чего символы '1' и ')' будут прочитаны с ленты и удалены из стека. В результате на вершине стека и на ленте будет находиться символ '$', согласно определению это означает успешный разбор цепочки.

В таком случае анализатор сообщит об успешном завершении и выдаст список правил, которые были применены в процессе вывода:

[ 2, 1, 3, 3 ]

Данные правила действительно являются крайним левым выводом:

S → (S + F) → (F + F) → (1 + F) → (1 + 1)

Комментарии[править | править вики-текст]

Как можно заметить исходя из примера — синтаксический анализатор выполняет три различных вида действий в зависимости от того, находится ли на вершине стека нетерминал, терминал или специальный символ $[2]:

  • Если вершина — нетерминал, то в таблице синтаксического анализа ищется правило грамматики, находящееся на пересечении столбца и строки, соответствующих нетерминалу на вершине стека и текущему символу на ленте (на который указывает головка МП-автомата). Если же в указанной ячейке таблицы правило отсутствует — синтаксический анализатор останавливается и сообщает об ошибке.
  • Если вершина — терминал, то синтаксический анализатор сравнивает его с текущим символом на ленте. Если они равны, то символ с ленты прочитывается, а соответствующий символ из вершины стека — выталкивается.
  • Если вершина — $ и текущий символ на ленте так же $, то синтаксический анализатор сообщает об успешном распознавании цепочки, в противном случае — сообщает об ошибке. В обоих случаях происходит останов анализатора.

Эти шаги повторяются до тех пор, пока не произойдёт останов. После останова мы получим или сообщение об ошибке, или сообщение об успешном распознавании цепочки.

Построение LL (1) таблицы синтаксического анализа[править | править вики-текст]

Чтобы заполнить таблицу синтаксического анализа, необходимо установить, какое правило грамматики синтаксический анализатор должен выбрать, если нетерминальное A находится на вершине его стека и символ a - во входном потоке. Легко заметить, что такое правило должно иметь форму Aw и что у языка, соответствующего w , должна быть по крайней мере одна строка, начинающаяся с a. С этой целью мы определяем Первый набор w, написанного здесь как Fi(w), как набор терминалов, которые могут быть найдены в начале любой строки в w, и ε, если пустая строка также принадлежит w. Учитывая грамматику с правилами A1w1, …, Anwn, можно вычислить Fi(wi) и Fi(Ai) для каждого правила следующим образом:

  1. инициалировать каждый Fi(wi) и Fi(Ai) пустым множеством
  2. добавить Fi(wi) к Fi(Ai) для каждого правила Aiwi, где Fi определён следующим образом:
    • Fi(a w' ) = { a } для каждого терминала a
    • Fi(A w' ) = Fi(A) для каждого нетерминального A с ε не в Fi(A)
    • Fi(A w' ) = Fi(A) \ { ε } ∪ Fi(w' ) для каждого нетерминального A с ε in Fi(A)
    • Fi(ε) = { ε }
  3. добавить Fi(wi) к Fi(Ai) для каждого правила Aiwi
  4. сделайте шаги 2 и 3, пока все наборы Fi не будут перечислены.

К сожалению, первых наборов недостаточно, чтобы построить таблицу синтаксического анализа. Так происходит, потому что правая сторона w правила могла бы в конечном счете быть приведена к пустой строке. Таким образом синтаксический анализатор должен также использовать правило Aw если ε находится в Fi(w) и во входном потоке символ, который может следовать за A. Поэтому также необходимо построить Следующий набор A (здесь будет записываться как Fo(A)) который определен как набор терминалов a таких, что существует строка символов αAaβ которая может быть получена из начального символа. Вычисление Следующих наборов для нетерминалов в грамматике может быть сделано следующим образом:

  1. инициализировать каждый Fo(Ai) пустым множеством
  2. если есть правило формы AjwAiw' , тогда
    • если терминал a в Fi(w' ), то добавить a к Fo(Ai)
    • если ε находится в Fi(w' ), тогда добавить Fo(Aj) к Fo(Ai)
  3. повторите шаг 2 до всего, пока не будут перечислены все наборы Fo.

Теперь можно определить точно, какие правила будут содержаться в таблице синтаксического анализа. Если T[A, a] обозначает вход в таблице для нетерминального A и терминала a, то

T[A,a] содержит правило Aw если и только если
a в Fi(w) или
ε находится в Fi(w) и a в Fo(A).

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

Построение таблиц синтаксического анализа для LL(k) анализаторов[править | править вики-текст]

Размер таблицы синтаксического анализа в худшем случае имеет показательную сложность в зависимости от k. Однако, после выпуска набора инструментальных средств конструирования компиляторов (PCCTS, теперь известный как ANTLR) приблизительно в 1992 году было показано, что размер таблицы синтаксического анализа для большинства языков программирования не имеет тенденции к показательному росту, так как не является худшим вариантом. Кроме того, в определенных случаях было возможно создание даже LL(*)-анализаторов. Однако, несмотря на это, традиционные генераторы синтаксических анализаторов, такие как yacc/GNU bison, используют LALR(1) таблицы синтаксического анализа, чтобы создать ограниченный LR-анализатор.

LL генераторы синтаксических анализаторов[править | править вики-текст]

Современные генераторы синтаксических анализаторов, для LL грамматик с мультиэстафетным предвидением, включают:

Ссылки[править | править вики-текст]

Примечания[править | править вики-текст]

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