- •Содержание
- •1.Рабочая программа
- •2.Модуль Вводный
- •3.Модуль Формальные грамматики и языки
- •3.1.Языки и цепочки символов. Способы задания языков
- •3.1.1.Цепочки символов. Операции над цепочками символов
- •3.1.2.Понятие языка. Формальное определение языка
- •3.1.3.Способы задания языков
- •3.1.4.Синтаксис и семантика языка
- •3.2.Определение грамматики
- •3.2.1.Особенности языков программирования
- •3.2.2.Определение грамматики. Форма Бэкуса—Наура
- •3.2.3.Принцип рекурсии в правилах грамматики
- •3.2.4.Другие способы задания грамматик
- •3.3.Классификация языков и грамматик
- •3.3.1.Классификация грамматик
- •3.3.2.Классификация языков
- •3.4.Контроль
- •4.Модуль Распознаватели, механизм вывода цепочек символов
- •4.1.Цепочки вывода. Сентенциальная форма.
- •4.1.1.Сентенциальная форма грамматики. Язык, заданный грамматикой
- •4.1.2.Левосторонний и правосторонний выводы
- •4.1.3.Однозначные и неоднозначные грамматики
- •4.1.4.Эквивалентность и преобразование грамматик
- •4.2.Распознаватели. Задача разбора
- •4.2.1.Общая схема распознавателя
- •4.2.2.Виды распознавателей
- •4.2.3.Классификация распознавателей по типам языков
- •4.3.Контроль
- •5.Модуль Регулярные грамматики и языки
- •5.1.Регулярные языки и грамматики
- •5.2.Леволинейные и праволинейные грамматики. Автоматные грамматики
- •5.3.Алгоритм преобразования регулярной грамматики к автоматному виду
- •5.4.Конечные автоматы
- •5.4.1.Определение конечного автомата
- •5.4.2.Детерминированные и недетерминированные конечные автоматы
- •5.4.3.Преобразование конечного автомата к детерминированному виду
- •5.5.Контроль
- •6.Модуль Контекстно-свободные грамматики и языки
- •6.1.Контекстно-свободные языки
- •6.1.1.Распознаватели кс-языков. Автоматы с магазинной памятью. Определение мп-автомата
- •6.2.Классы кс-языков и грамматик. Класс ll(k) грамматик.
- •6.3.Принципы построения распознавателей для ll(k)-грамматик
- •6.4.Левая факторизация
- •6.5.Удаление левой рекурсии
- •6.6.Алгоритм разбора для ll(1)-грамматик
- •6.7.Алгоритм построения множества first(1,a)
- •6.8.Алгоритм построения множества follow(1,a)
- •6.9.Восходящие распознаватели кс-языков без возвратов
- •6.9.1.Определение lr(k)-грамматики
- •6.10.Принципы построения распознавателей для lr(k)-грамматик
- •6.10.1.Грамматики простого предшествования
- •6.11.Распознаватели для lr(0) и lr(1) грамматик
- •6.11.1.Распознаватель для lr(0)-грамматики
- •6.11.2.Распознаватель для lr(1) грамматики
- •6.12.Контроль
- •7.Модуль Инструментальные средства для построения трансляторов
- •7.1.Инструментальные средства для построения компиляторов
- •7.1.1.Построитель лексических анализаторов Lex
- •7.2.Контроль
- •8.Модуль Особенности программирование трансляторов
- •8.1.Использование значений произвольных типов, алгоритм разбора
- •8.1.1.Алгоритм синтаксического разбора
- •8.1.2.Семантический стек
- •8.2.Неоднозначности и конфликты
- •8.3.Старшинство операций
- •8.4.Дополнительные возможности программ yacc и lex
- •8.4.1.Обработка ошибок
- •8.5.Совместное использование lex и yacc
- •8.5.1.Кодировка лексем и интерфейс
- •8.5.2.Сборка yacc-программ
- •8.6.Советы по подготовке спецификаций
- •8.6.1.Стиль
- •8.6.2.Использование левой рекурсии
- •8.6.3.Уловки анализа лексики
- •8.6.4.Входной синтаксис yacc'а
- •8.7.Контроль
- •9.Модуль Заключение
- •10.Обеспечение лабораторного практикума
- •11.Дополнительная информация. Примеры
- •11.4.Пример простейшего интерпретатора формул
- •11.5.Простой пример
- •11.6.Более сложный пример
- •11.7.Генераторы лексических и синтаксических анализаторов
- •11.8.Генераторы лексических и синтаксических анализаторов на java
- •11.9.Пакеты для разработки компиляторов
- •Список сокращений
- •Литература
- •Приложения Приложение 1. Учебно–методическая карта дисциплины “Системное программное обеспечение. Синтаксические анализаторы”
- •Приложение 2. Вопросы для зачета по дисциплине “Системное программное обеспечение. Синтаксические анализаторы”
- •Приложение 3. Методические указания к лабораторным работам по дисциплине «Системное программное обеспечение. Синтаксические анализаторы»
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex. Анализ структуры программ
- •Краткая теория:
- •Рассмотрим примеры:
- •Краткая теория:
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex, синтаксический анализатор yacc. Алгебраические вычисления
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex и синтаксический анализатор yacc. Изображение геометрических фигур
- •Краткая теория:
- •Создание метафайла и работа сним
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Приложение 4. Организация рейтингового контроля по дисциплине «Системное программное обеспечение. Синтаксические анализаторы»
6.7.Алгоритм построения множества first(1,a)
Алгоритм строит множества FIRST(1,A) сразу для всех нетерминальных символов грамматики G(VT,VN,P,S), А VN. Для выполнения алгоритма надо предварительно преобразовать исходную грамматику G(VT,VN,P,S) в грамматику G'(VT,VN',P',S'), не содержащую -правил.
На основании полученной грамматики G' и выполняется построение множеств FIRST(1,A) для всех AVN (если A VN, то согласно алгоритму преобразования также справедливо AVN'). Множества строятся методом последовательного приближения. Если в результате преобразования грамматики G в грамматику G' множество VN' содержит новый символ S', то при построении множества FIRST(l.A) он не учитывается.
Алгоритм состоит из нескольких шагов.
Шаг 1. Для всех AVN: FIRST0(1,A) = X | АХ Р, X (VTVN), (VTVN)'} (первоначально вносим во множество первых символов для каждого нетерминального символа А все символы, стоянию в начале правых частей правил для этого символа A); i:=0.
Шаг 2. Для всех AVN: FIRSTi+1(1,A) = FIRSTi(1,A) FIRSTi(1,B), для всех нетерминальных символов B (FIRSTi(l,A) VN).
Шаг 3. Если AVN: FIRSTi+1(1,A) FIRSTi(1,A), то i:=i+l и вернуться к шагу 2, иначе перейти к шагу 4.
Шаг 4. Для всех AVN: FIRST(l,A) = FIRSTi,(1,A) \ VN (исключаем из построенных множеств все нетерминальные символы).
6.8.Алгоритм построения множества follow(1,a)
Алгоритм строит множества FOLLOW(l,A) сразу для всех нетерминальных символов грамматики G(VT,VN,P,S), AVN. Для выполнения алгоритма предварительно надо построить все множества FIRST(l,A), AVN. Множества строятся методом последовательного приближения. Алгоритм состоит из нескольких шагов.
Шаг 1. Для всех AVN: FOLLOW0(1,A) = X│В αAXβР, ВÎVN, X (VTVN), β (VTVN)*}, (первоначально вносим во множество последующих символов для каждого нетерминального символа А все символы, которые в правых частях правил встречаются непосредственно за символом A); i:=0.
Шаг 2. FOLLOW0(l,S) = FOLLOW0(l,S) {} (вносим пустую цепочку во множество последующих символов для целевого символа S — это означает, что в конце разбора за целевым символом цепочка кончается, иногда для этой цели используется специальный символ конца цепочки: K).
Шаг 3. Для всех AÎVN: FOLLOW’i(l,A) = FOLLOWi(l,A) FIRST(1,B), Для всех нетерминальных символов B Î (FOLLOWi(l,A) VN).
Шаг 4. Для всех AÎVN: FOLLOW”i(l,A) = FOLLOW’i(l,A) FOLLOW’i(l,B), для всех нетерминальных символов B Î (FOLLOW’i(l,A) VN) и существует правило В→.
Шаг5. Для всех A ÎVN: FOLLOWi+1(l,A) = FOLLOW’i(l,A) FOLLOW’i(l,B), для всех нетерминальных символов B ÎVN, если существует правило B→αA, α Î(VT VN)*.
Шаг 6. Если A ÎVN: FOLLOWi+1(l,A) ≠ FOLLOWi(l,A), то i:=i+l и вернуться к шагу 3, иначе перейти к шагу 7.
Шаг 7. Для всех AÎVN: FOLLOW(1,A) = FOLLOWi(l,A) \ VN (исключаем из построенных множеств все нетерминальные символы).
6.9.Восходящие распознаватели кс-языков без возвратов
6.9.1.Определение lr(k)-грамматики
Восходящие распознаватели выполняют построение дерева вывода снизу вверх. Результатом их работы является правосторонний вывод. Функционирование таких распознавателей основано на модификациях алгоритма «сдвиг-свертка».
Идея состоит в том, чтобы модифицировать этот алгоритм таким образом, чтобы на каждом шаге его работы можно было однозначно дать ответ на следующие вопросы:
что следует выполнять: сдвиг (перенос) или свертку;
какую цепочку символов выбрать из стека для выполнения свертки;
какое правило выбрать для выполнения свертки (в том случае, если существует несколько правил вида А1, А2, .. Аn).
Тогда восходящий алгоритм распознавания цепочек КС-языка не требовал бы выполнения возвратом, поскольку сам язык мог бы быть задан детерминированным расширенным МП-автоматом. Конечно, как уже было сказано, это нельзя сделать в общем случае, для всех КС-языков (поскольку даже сам класс детерминированных КС-языков более узкий, чем весь класс КС-языков). Но, вероятно, среди всех КС-языков можно выделить такой класс (или классы), для которых подобная реализация распознающего алгоритма станет возможной.
В первую очередь можно использовать ют же самый подход, который был положен в основу определения LL(к)-грамматик. Тогда мы получим другой класс КС-грамматик, который носит название LL(к)-грамматик.
КС-грамматика обладает свойством LR(k), k 0, если на каждом шаге вывода для однозначного решения вопроса о выполняемом действии в алгоритме «сдвиг-свертка» («перенос-свертка») расширенному МП-автомату достаточно знать содержимое верхней части стека и рассмотреть первые k символов от текущего положения считывающей головки автомата во входной цепочке символов.
Грамматика называется LR(k)-грамматикой, если она обладает свойством LR(k) для некоторого k01.
Название LR(k), как и рассмотренное выше LL(k), также несет определенный смысл. Первая литера «L» также обозначает порядок чтения входной цепочки символов: слева— направо. Вторая литера «R» происходит от слова «right» и, по аналогии с LL(k), означает, что в результате работы распознавателя получается правосторонний вывод. Вместо «k» в названии грамматики стоит число, которое показывает, сколько символов входной цепочки надо рассмотреть, чтобы принять решение о действии на каждом шаге алгоритма «сдвиг-свертка». Так, существуют LR(0)-грамматики, LR(1)-грамматики и другие классы.
В совокупности все LR(k)- грамматики для всех k0 образуют класс LR-грамматик.
На рис. 6.3 схематично показано частичное дерево вывода для некоторой LR(k)-грамматики. В нем обозначает уже разобранную часть входной цепочки а, на основе которой построена левая часть дерева у. Правая часть дерева х — это еще не разобранная часть, а А — это нетерминальный символ, к которому на очередном шаге будет свернута цепочка символов z, находящаяся на верхушке стека МП-автомата. В эту цепочку уже входит прочитанная, но еще не разобранная часть входной цепочки . Правая часть дерева х будет построена на основе части входной цепочки . Свойство LR(k) предполагает, что однозначный выбор действия, выполняемого на каждом шаге алгоритма «сдвиг-свертка», может быть сделан на основе цепочки и k первых символов цепочки , являющихся частью входной цепочки . Этим очередным действием может быть свертка цепочки z к символу А или перенос первого символа из цепочки и добавление его к цепочке z.
Рис. 6.3. Схема построения дерева вывода для LR(k)-грамматики
Рассмотрев схему построения дерева вывода для LR(k)-грамматики на рис. 6.3 и сравнив ее с приведенной выше на рис. 6.2 схемой для LL(k)-грамматики, можно предположить, что класс LR-грамматик является более широким, чем класс LL-грамматик. Основанием для такого предположения служит тот факт, что на каждом шаге работы распознавателя LR(k)-грамматики обрабатывается больше информации, чем на шаге работы распознавателя LL(k)-грамматики. Действительно, для принятия решения на каждом шаге алгоритма распознавания LL(k)-грамматики используются первые k символов из цепочки , а для принятия решения на шаге распознавания LR(k)-грамматики — вся цепочка и еще первые k символов из цепочки . Очевидно, что по втором случае можно проанализировать больший объем информации и, таким образом, построить вывод для более широкого класса КС-языков.
Приведенное выше довольно нестрогое утверждение имеет строго обоснованное доказательство. Доказано, что класс LR-грамматик является более широким, чем класс LL-грамматик. То есть для каждого КС-языка, заданного LL-грамматикой, может быть построена LR-грамматика, задающая тот же язык, но не наоборот.
Существуют также языки, заданные LR-грамматиками, для которых невозможно построить LL-грамматику, задающую тот же язык. Иначе говоря, для всякой LL-грамматики существует эквивалентная ей LR-грамматика, но не для всякой LR-грамматики существует эквивалентная ей LL-грамматика1.
Для LR(k)-грамматик известны следующие полезные свойства:
всякая LR(k)-грамматика для любого k≥0 является однозначной;
существует алгоритм, позволяющий проверить, является ли заданная грамматика LR(k)- грамматикой для строго определенного числа k.
Есть, однако, неразрешимые проблемы для произвольных КС-грамматик (они аналогичны таким же проблемам для других классов КС-грамматик):
не существует алгоритма, который бы мог проверить, является ли заданная КС-грамматика LR(k)- грамматикой для некоторого произвольного числа k;
не существует алгоритма, который бы мог преобразовать (или доказать, что преобразование невозможно) произвольную КС-грамматику к виду LR(k)-грамматики для некоторого k.
Кроме того, для LR-грамматик доказано еще одно очень интересное свойство - класс LR-грамматик полностью совпадает с классом детерминированных КС-языков. То есть, во-первых, любая LR(k)-грамматика задает детерминированный КС-язык (это очевидно следует из однозначности всех LR-грамматик), а во-вторых, для любого детерминированного КС-языка можно построить LR-грамматику, задающую этот язык. Второе утверждение уже не столь очевидно, но доказано в теории формальных языков1.
В принципе класс LR- грамматик очень удобен для построения распознавателей детерминированных КС-языков (а все языки программирования, безусловно, относятся к этому классу). Но тот факт, что для каждого детерминированного КС-языка существует задающая его LR- грамматика, еще ни о чем не говорит, поскольку из-за неразрешимости проблемы преобразования отсутствует алгоритм, который позволил бы эту грамматику построить всегда. Данный детерминированный КС-язык может быть изначально задан грамматикой, которая не относится к классу LR-грамматик. В таком случае совсем не очевидно, что для этого языка удастся построить распознаватель на основе LR-грамматики, потому что в общем случае нет алгоритма, который бы позволил эту грамматику получить, хотя и известно, что она существует. То, что проблема не разрешима в общем случае, совсем не означает, что ее не удастся решить в конкретной ситуации. И здесь факт существования LR-грамматики для каждого детерминированного КС-языка играет важную роль — всегда есть смысл в каждом конкретном случае пытаться построить такую грамматику.
