Parser Generator
Инструментарий для построения компиляторов. |
Главная О сайте Документация Download Ссылки Библиография О нас |
Алгоритмы и преобразования.
Во многих приложениях формальных грамматик используются различные
алгоритмы над формальными грамматиками и их преобразования. Для этих
целей была разработана библиотека классов FGAlgoritms. Экземпляры классов
этой библиотеки представляют собой функционалы или объекты функции. Т.е.
у них перегружена операция Преобразования формальных грамматикФормальные грамматики были разработаны для того, чтобы описывать некоторый язык. Основная их цель- давать формальной описание всем цепочкам, которые могут принадлежать некоторому языку. Однако, для одного языка могут существовать несколько формальных грамматик его описывающих. Причем их различие может заключаться на только в названиях термов или порядке правил, но и в самих правилах. Другими словами, грамматики могут "существенно" отличаться, т.е. никакая перестановка правил местами или переименование термов не приведут одну грамматику к другой. И несмотря на это языки, которые они порождают будут совпадать. В качестве простого примера рассмотрим две язык, состоящий из всех цепочек из одной или более единиц. L={ (1)+ }. Для него можно написать две различные грамматики: ФГ1: ( Vn={ "S" , "A" }Vt={ "1" } R={ "S::=1 A" , "A::=1 A" , "A::=" } S="S") ФГ2: ( Vn={ "S" }Vt={ "1" } R={ "S::=1 S" , "S::=1 " } S="S") Сразу видно, что эти две грамматики существенно отличаются. У них даже количество нетерминальных символов различно. Подобные различия грамматик порой очень сильно влияют на то, каким образом можно построить анализатор языка для данной грамматики. Для одного языка можно построить грамматики различных классов, с разным уровнем сложности разбора. Возникает задача- определить, порождают ли грамматики один и тот же язык, или нет. В общем случае эта задача не разрешима, однако есть некоторые преобразования грамматик, которые позволяют изменить грамматику, не изменив при этом язык, который она порождает.
Удаление бесполезных символовЧтобы определить, что такое "бесполезный" символ, надо сначала дать два других определения: Недостижимый символ- символ, который не может появится ни в одной сентенциальной форме. Производящий нетерминал- символ, из которого можно вывести терминальную цепочку. Т.е. A=>+x Теперь можно дать определение бесполезному символу. Символ называется бесполезным если он непроизводящий или недостижимый. Алгоритм удаления бесполезных символов разбивается на четыре части: удаление непроизводящих символов, удаление правил, содержащих эти символы, удаление недостижимых символов, удаление правил, содержащих эти символы.
Удаление e-правил (пустых правил)Пустое правило- это правило с пусто правой частью. Такие правила очень часто становятся большой проблемой для парсеров. Многие анализаторы не допускают использования в грамматиках таких правил. Для того, чтобы избавиться от таких правил можно применить алгоритм удаление пустых правил.
Для удаления этих пустых правил используется класс
Удаление цепных правил
Цепные правила- это правила вида :
Для удаления цепных правил используется класс
Удаление левой рекурсииЭтот алгоритм крайне важен, поскольку целый класс формальных грамматик не может содержать левую рекурсию. Это класс ЛЛ1-грамматик. Разбор этих грамматик крайне прост и в то же время многие конструкции могут быть представлены в виде ЛЛ1-грамматик. Сначала дадим определение прямой левой рекурсии.
Грамматика называется грамматикой с прямой левой рекурсией,
если содержит
хотя бы одно правило вида
Грамматика называется грамматикой с левой рекурсией,
если в ней возможен вывод Удаление левой рекурсии часто приводит к тому, что грамматика становится ЛЛ1-грамматикой. Однако у этого алгоритма есть один недостаток- несмотря на то, что язык, который описывает грамматика остается прежним, сама грамматика может стать неузнаваемой. Появляется много новых правил, нетерминалов, исчезает много старых правил.
Для удаления левой рекурсии используется класс
Алгоритмы над формальными грамматикамиФормальные грамматики сами по себе не представляют практического интереса. Интересны анализаторы построенные на их основе. Для построения этих алгоритмов необходимы некоторые вспомогательные результаты, которые могут быть получены с помощью алгоритмов, которые рассматриваются в этом разделе.
Построение множества укорачивающих символов
Укорачивающим символом называют такой символ для,
которого верно: Для получения этого множества используются объекты класса makeUnshortening. Его метод getShorteningTerms возвращает искомое множество термов.
Получение множества ПЕРВ (First).Множество ПЕРВ рассматривается для термов и правил. Неформально это множество представляет собой набор терминалов, которыми могут начинаться все сентенциальные формы, выводимые их терма A (для терма). Для правила это множество термов, которыми может начинаться цепочка выводимая из правой части правила. Формально множество ПЕРВ(A) для терма определяется так: ПЕРВ(A) = { a | a in Vt, A=>aw } Для правила множество ПЕРВ(R) определяется похожим образом: ПЕРВ(R) = { a | a in Vt, R=>aw } Для получения этого множества используются объекты класса getFirst.
Получение множества ПОСЛ (Last).Множество ПОСЛ рассматривается для термов и правил. Неформально это множество представляет собой набор терминалов, которыми могут заканчиваться все сентенциальные формы, выводимые их терма A (для терма). Для правила это множество термов, которыми может заканчиваться цепочка выводимая из правой части правила. Формально множество ПОСЛ(A) для терма определяется так: ПОСЛ(A) = { a | a in Vt, A=>wa } Для правила множество ПОСЛ(R) определяется похожим образом: ПОСЛ(R) = { a | a in Vt, R=>wa } Для получения этого множества используются объекты класса getLast.
Получение множества След (Follow).Множество СЛЕД рассматривается для термов. Неформально это множество представляет собой набор термов, которые могут встретиться следом за термом A в какой-нибудь сентенциальной форме. Формально множество СЛЕД(A) для терма определяется так: СЛЕД(A) = { a | a in Vt, S=>wAv, a in ПЕРВ(v) } Для получения этого множества используются объекты класса getFollow.
ЛЛ1-грамматикиЛЛ1 грамматики являются одними из самых простых грамматик с точки зрения сложности разбора. Однако очень много языковых конструкций могут быть описаны с их помощью. Анализ для ЛЛ1 грамматик интуитивно понятен и не требует сложных объяснений. Анализ ЛЛ1 грамматик строит дерево разбора сверху вниз и называется нисходящим. Это означает, что алгоритм пытается сначала построить верхушку синтаксического дерева, спускаясь постепенно к листьям. Неформально можно так описать ЛЛ1 грамматики. Грамматика, для которой на каждом шаге разбора можно сделать вывод о том, какое правило следует применить, зная лишь первый символ не прочитанной части входной цепочки называется ЛЛ1 грамматикой.
Это определение можно пояснить, рассмотрев алгоритм разбора ЛЛ1
грамматик. Для анализа цепочки используется стек, в который помещают
символы из полного словаря грамматики. Начальная конфигурация
анализатора подразумевает, что в стеке находится лишь стартовый символ
грамматики. Зная первый символ входной цепочки мы сразу должны
определить, чем мы заменим верхний символ в стеке (т.е. правой
частью какого правила мы заменим символ Если мы все-таки определили, каким правилом следует заменить начальный символ, то нужно извлечь S из стека, и положить туда всю правую часть правила (чтобы первый символ правила оказался наверху). На следующем шаге мы вновь смотрим на верхний символ стека, и первый символ оставшейся непрочитанной цепочки. Если на вершине стека терминал и он совпадает с первым символом цепочки, то мы просто удаляем его из стека. Если же это нетерминал, то снова заменяем его каким-нибудь правилом. В конце концов, если цепочка принадлежит искомому языку мы должны оказаться в состоянии, в котором стек пуст и прочитана вся входная цепочка. В противном случае, на каком-то шаге мы либо не сможем сделать вывод о том, на какое правило следует заменить нетерминал на вершине магазина, либо первый символ входной цепочки не совпадет с терминалом в стеке.
Определение принадлежности к классу ЛЛ1Итак выше было неформально дано определение ЛЛ1-грамматикам. Теперь дадим формальное определение. Грамматика называется ЛЛ1-грамматикой, если из существования двух левых выводов:
для которых ПЕРВ(x) = ПЕРВ(y), следует, что q=p (p,q- цепочки символов полного словаря). На практике, конечно такое определения сложно использовать, поэтому используют следующую теорему:
КС-грамматика G является ЛЛ1-грамматикой тогда и только тогда, когда для
двух различных правил Для неукорачивающих грамматик эту теорему можно сформулировать проще: G-ЛЛ1 <=> множества ПЕРВ на для каких двух правил одного нетерминала не пересекаются. Фактически это означает, что мы анализатор должен быть всегда в состоянии определить, какое правило необходимо применить на следующем шаге, зная лишь первый символ непрочитанной цепочки.
Для определения принадлежности грамматики к классу ЛЛ1 используется
класс
Если окажется, что грамматика не принадлежит к классу ЛЛ1, то после анализа можно вызвать метод errMsg, чтобы получить строку, содержащую "пояснения" почему это так. Это может помочь провести необходимые преобразования грамматики.
Алгоритм разбора для ЛЛ1 грамматикАлгоритм разбора для ЛЛ1 грамматик представляет собой автомат с магазинной памятью и входной лентой. Его работой управляет специальная таблица, которая в зависимости от верхнего символа магазина и первого символа непрочитанной цепочки предписывает автомату выполнить одно из следующих действий: Реализация такого алгоритма разбора очень проста. Поэтому ЛЛ1 грамматики получили распространение, несмотря на то, что их класс не слишком широк.
Построение управляющей таблицы для ЛЛ1-анализатора
Фактически алгоритм определения принадлежности грамматики к классу ЛЛ1
в процессе работы выполняет построение такой таблицы. После
успешного завершения его работы (грамматика оказалась ЛЛ1-грамматикой)
необходимо вызвать метод
Грамматики предшествованияГрамматики предшествования позволяют описывать более широкий класс языков чем ЛЛ1-грамматики, однако менее чем SLR-грамматики. Алгоритм разбора грамматик предшествования относится к восходящим методам разбора, т.е. дерево разбора строится снизу вверх (листья и "ветки" соединяются в более крупные "ветки"). Работает алгоритм по принципу "перенос-свертка".
Центральное место во всем, что касается грамматик предшествования
занимают отношения предшествования (бинарные отношения между символами
полного словаря грамматики и допустимыми символами магазина).
Фактически это три отношения:
Отношения < = определяется для множеств
^ < Y если
X = Y если есть правило
Отношение > определяется для множества
X > a если имеется правило
X > e если Вычисление матрицы предшествованияМатрица предшествования представляет собой таблицу следующего вида:
На пересечении столбцов и строк этой таблицы стоят соответствующие отношения предшествования. Фактически такая таблица служит двум целям: по ней можно определить принадлежность грамматики к классу грамматик предшествования и она управляет работой анализатора. Строится такая таблица с помощью тех правил вычисления отношений предшествования, которые были определены выше.
Класс
Определение принадлежности грамматики к классу простого предшествованияКласс простого предшествования определяется очень просто: грамматика является грамматикой простого предшествования, если для любых двух символов полного словаря существует не более одного отношения предшествования. Неформально, это значит, что в каждой клетке матрицы предшествования должно находится не более одного отношения. Проверку производит класс isPrecedence. Этот класс получает матрицу предшествования и по ней определяет является ли она грамматикой простого предшествования.
FormalGrammar::ptr a_fg = new FormalGrammar(a_fileName);
Определение принадлежности грамматики к классу слабого предшествования
Класс слабого предшествования
определяется чуть сложнее: грамматика
является грамматикой слабого предшествования, если
отношение > не пересекается с объединением отношений < = и
для Проверку производит класс isWeakPrecedence. Этот класс получает матрицу предшествования и по ней определяет является ли она грамматикой слабого предшествования.
FormalGrammar::ptr a_fg = new FormalGrammar(a_fileName);
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
© С. Григорчук 2001, Содержание, дизайн |
|