lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК...

44
Министерство образования и науки Российской Федерации Федеральное агентство по образованию Муромский институт (филиал) Государственного образовательного учреждения высшего профессионального образования «Владимирский государственный университет» РАЗРАБОТКА КОМПИЛЯТОРОВ методические указания к лабораторному практикуму Часть 2 Составитель: Н. Е. Калинкина Муром 2008

Upload: others

Post on 01-Aug-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

Министерство образования и науки Российской Федерации Федеральное агентство по образованию

Муромский институт (филиал) Государственного образовательного учреждения

высшего профессионального образования «Владимирский государственный университет»

РАЗРАБОТКА КОМПИЛЯТОРОВ

методические указания к лабораторному практикуму

Часть 2

Составитель: Н. Е. Калинкина

Муром 2008

Page 2: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

УДК 681.3.06 (075.8) ББК 32.973.26-018

Р 17

Рецензент: доцент кафедры электроники и вычислительной техники

Муромского института (филиала) Владимирского государственного университета Г.П. Суворова

Печатается по решению редакционно-издательского совета

Муромского института ВлГУ Р 17 Ч.2 Разработка компиляторов: методические указания к лабораторному практикуму. В 2 ч. Ч.2. /Сост.: Калинкина Н.Е. – Муром: ИПЦ МИ ВлГУ, 2008.– 44 с.: ил.

В методических указаниях приведено описание восьми лабораторных работ, охватывающих основные этапы разработки компилятора. Каждая лабораторная работа содержит краткие теоретические сведения и рассчитана на четыре академических часа самостоятельной работы. Первая часть методических указаний содержит описание лабораторных работ №1-2, вторая часть работ № 3-8.

Методические указания предназначены для выполнения лабораторных работ по дисциплине «Системное программное обеспечение» для специальности 230101.65 «Вычислительные машины, системы, комплексы, сети» и «Теория языков программирования и методы трансляции» для специальности 230105.65 «Программное обеспечение вычислительной техники и автоматизированных систем», но может быть полезна всем, кто самостоятельно изучает программирование.

УДК 681.3.06 (075.8) ББК 32.973.26-018

© Муромский институт (филиал) Государственного образовательного учреждения высшего профессионального образования «Владимирский государственный университет», 2008

Page 3: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

3

Предисловие Пришла пора задуматься об устройстве основного инструмента

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

Все лабораторные работы, приведённые в методических указаниях связаны с реализацией составных частей компилятора. Все работы взаимосвязаны – каждая последующая использует материал предыдущей, и в сумме позволяют получить рабочий компилятор. Работы имеют разную сложность выполнения: она постепенно нарастает.

Методические указания предназначены для выполнения лабораторных работ по дисциплине «Системное программное обеспечение» для специальности 230101.65 «Вычислительные машины, системы, комплексы, сети» и «Теория языков программирования и методы трансляции» для специальности 230105.65 «Программное обеспечение вычислительной техники и автоматизированных систем», но может быть полезна и тем, кто самостоятельно изучает программирование.

Во второй части методических указаний приведено описание лабораторных работ №3-8, охватывающих вопросы построения контекстно-свободной грамматики подмножества языка программиро-вания, разработки таблиц для табличных методов синтаксического разбора, проектирования синтаксического и семантического анализа и генерации кода. Каждая лабораторная работа содержит краткие теоретические сведения и рассчитана на четыре академических часа самостоятельной работы.

Page 4: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

4

Лабораторная работа № 3 ПОСТРОЕНИЕ И ПРЕОБРАЗОВАНИЕ КОНТЕКСТНО-

СВОБОДНЫХ ГРАММАТИК Цель работы: Получить навыки в составлении грамматик, определении их

классов и преобразовании. Задача: Изучить табличные методы синтаксического анализа.

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

Лексические единицы языков программирования описываются регулярными выражениями и распознаются с помощью конечных автоматов в ходе лексического анализа. Дальнейшую их обработку выполняет синтаксический анализатор. Его работа основана на использовании правил контекстно-свободной грамматики (КСГ), описывающих конструкции исходного языка. Выходом лексического анализатора является таблица лексем (таблица стандартных символов). Эта таблица образует вход синтаксического анализатора. Синтаксический анализатор воспринимает выход лексического анализатора и разбирает его в соответствии с грамматикой входного языка.

Синтаксический анализатор – это часть компилятора, которая отвечает за выявление основных синтаксических конструкций входного языка. Его задачи: найти, выделить, проверить правильность каждой синтаксической конструкции и представить результат в форме удобной для дальнейшего применения на этапах оптимизации и генерации кода.

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

Page 5: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

5

Нас будут интересовать только детерминированные распознаватели. Среди всего их множества можно выделить следующие, наиболее часто используемые: − нисходящие распознаватели с подбором альтернатив (на основе LL(k)

грамматик, например метод рекурсивного спуска); − восходящие распознаватели на основе алгоритма сдвиг-свёртка (на

базе LR(k) грамматик или грамматик простого предшествования). В общем виде процесс построения синтаксического анализатора

можно описать следующим образом: 1. Построить грамматику языка. 2. Выполнить преобразования над построенной грамматикой. 3. Проверить принадлежность КСГ, получившейся в результате

преобразований одному из известных классов КСГ, для которых существуют линейные распознаватели.

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

5. Если соответствующий класс не найден или найденный класс не устраивает разработчика – попытаться выполнить преобразования под интересующий класс и вернуться к пункту 3.

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

7. Определить в какой форме будет синтаксический распознаватель передавать результаты своей работы другим фазам компилятора.

Построение грамматики заданного подмножества языка

1. Первым правилом задайте общую структуру программы. Элемент программы, обозначенный в примере <спис_опер> есть в любом языке программирования. Если в задании есть фраза "директивы описания переменных", то в первом правиле, вероятнее всего, должен быть <спис_опис>. Посмотрите есть ли в языке специальные служебные

Page 6: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

6

слова – ограничители блоков описания переменных или операторной части (в Паскаль это var и begin-end).

2. Далее последовательно надо объяснить структуру всех нетерминалов, введенных в первом и в следующих правилах. Обратите внимание на правила списков (2,4,6). Все эти правила содержат рекурсию, однотипно устроены и преследуют одну цель – размножить "элемент списка" в количестве один или более штук. В вашей грамматике хотя бы одно рекурсивное правило должно быть.

3. Если надо объяснить часть оператора, которая может принимать несколько возможных значений или может отсутствовать, то введите новый нетерминал на месте этой части, а затем, следующим правилом, объясняйте её структуру. Например в приведённом примере при построении описания выяснилось, что переменные могут быть двух различных типов. На языке грамматик такое ограничение можно было ввести при помощи правил (3,5) из примера или таким образом: <опис>::=<спис_перем> : integer | <спис_перем> : word

Пример 3. Построим грамматику подмножества языка Паскаль. 1. <прог> ::= var <спис_опис> ; begin <спис_опер>; end. 2. <спис_опис> ::= <опис>| <спис_опис>;<опис> 3. <опис> ::= <спис_перем> : <тип> 4. <спис_перем> ::= id | <спис_перем> , id 5. <тип> ::= integer | word 6. <спис_опер> ::= <опер> | <спис_опер> ; <опер> 7. <опер> ::= <назначение> | <ввод> |<вывод> 8. <назначение> ::= id := <выраж> 9. <ввод> ::= read ( <спис_перем> ) 10. <вывод> ::= write ( <спис_перем> ) Грамматика <выраж> не приведена, т.к. разбор сложных выражений (в соответствии с заданием) должен выполняться другим методом.

Page 7: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

7

Преобразование контекстно-свободных грамматик

Имея грамматику входного языка, необходимо выполнить ряд формальных преобразований над этой грамматикой, облегчающих построение распознавателя. После этого надо проверить относится ли полученная грамматика к одному из известных классов КС-языков, для которых существуют линейные распознаватели. Часто одна и та же КСГ может быть отнесена не к одному, а сразу к нескольким классам, допускающим построение линейных распознавателей. Тогда необходимо решить какой из нескольких возможных распознавателей выбрать для практической реализации. Желание использовать более простой класс грамматик для построения распознавателя может потребовать каких-то манипуляций с заданной грамматикой, необходимых для её преобразо-вания к требуемому классу. Всё усложняется тем, что проблема преобразования произвольной КСГ к требуемому классу алгоритмически неразрешима, но это не значит, что задача преобразования к нужному классу не решается в каждом конкретной случае. Зачастую удаётся найти такие преобразования, но начиная преобразования гарантировать результат невозможно.

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

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

Леворекурсивная грамматика не принадлежит классу LL(k) ни для какого k. Иногда удаётся преобразовать КСГ в эквивалентную ей LL(k)-грамматику с помощью устранения левой рекурсии и факторизации. Однако проблема существования эквивалентной LL(k)-грамматики для произвольной КСГ неразрешима.

Page 8: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

8

Устранение левой рекурсии

Правило называется леворекурсивным, если оно имеет вид U→Ux, где U∈N, а x∈(N∪T)+. От левой рекурсии всегда можно избавиться, заменив левую рекурсию правой. Алгоритм устранения прямой левой рекурсии.

Пусть A– леворекурсивный элемент, iα – цепочки, которые следуют

за леворекурсивным элементом A , и ни одна из цепочек βi не начинается с A . Тогда, выполним следующую замену:

⎪⎩

⎪⎨⎧

→⇒→

UUU

UUAAAAA

nn

mmmn αααα

ββββββααα

......

...... ......

11

11121

введя в эквивалентную грамматику вместо правил выводимых из элемента A , на котором есть левая рекурсия, правила в которых левая рекурсия заменена на правую.

Левая факторизация Основная идея левой факторизации в том, что, когда неясно, какую из

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

Среди всех правил, выводимых из нетерминала A ищем самый длинный префикс α , общий для двух или более его альтернатив. Если нетривиальный общий префикс существует, заменяем все A-правила:

δαβαβαβ nA ...21→ ⇒ ⎪⎩

⎪⎨⎧

→→

nUUA

βββδα

...21 , где U – новый нетерминал, а δ

все альтернативы, не начинающиеся с префикса α . Повторно применяем это преобразование, пока никакие две

альтернативы не будут иметь общего префикса.

Page 9: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

9

Нисходящий анализатор

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

Работу распознавателя с подбором альтернатив можно описать следующим образом:

1. Инициализация. Разбор начинается с порождающего символа грамматики. Это нетерминал из которого в итоге должна быть выведена анализируемая цепочка – вся компилируемая программа. Поместим его на вершину стека. Текущим символом входной цепочки выберем первую лексему.

2. Подбор альтернативы. Если на вершине стека находится нетермнал, то его нужно заменить на цепочку символов α, если в грамматике есть правило A→α, не перемещаясь при этом во входном потоке. Проблема в том, что в грамматике языка может оказаться более одного правила выводимого из нетерминала A, и тогда при замене содержимого вершины стека у распознавателя будет несколько альтернатив. Недетерминированная реализация распознавателя последовательно будет пытаться примерить к входной цепочке их все. Ошибка будет зафиксирована только после перебора всех возможных вариантов. Поэтому этот алгоритм относят к категории разборов с возвратом.

3. Сдвиг. Если на вершине стека находится терминальный символ a, и он совпадает с текущим символом входной цепочки, то символ удаляют из стека и перемещаются во входном потоке. Если он не совпадает с текущим символом, то на этом шаге вывода применяют другую альтернативу. Если других альтернатив на этом шаге нет, или это последняя из альтернатив, возвращаются в процессе порождений на шаг назад, меняют альтернативу и т.д.

Page 10: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

10

Нисходящие распознаватели без возвратов. Алгоритм нисходящего разбора можно улучшить, если найти метод,

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

Введём два множества FIRST и FOLLOW:

FIRSTk(α)={a | a∈T*, |a|≤k или |a|≥k и α⇒* aβ, α,β∈(N∪T)*}. Множество терминальных цепочек, выводимых из α, укороченных до k символов.

FOLLOWk(A)={a | a∈T*, S⇒* αAγ, a∈FIRSTk(γ), A∈N, α,γ∈(N∪T)*}. Множество терминальных цепочек, укороченных до k символов, которые могут следовать непосредственно за нетермналом A в цепочках вывода.

Доказано, что грамматика G(N,T,P,S) является LL(k) тогда и только тогда, когда: ∀ двух правил из P вида A→β и A→γ , где β≠γ выполнено FIRSTk(βω)∩FIRSTk(γω)=∅, для всех цепочек ω таких что S⇒* αAω.

Пример 4. Проверить, является ли приведённая грамматика LL(k), если нет, выполнить необходимые преобразования и проверить класс получившейся после преобразований грамматики.

aTTETE

→+→

Грамматика с приведёнными правилами не является LL(k), т.к. содержит левую рекурсию.

Устраним левую рекурсию (справа приведена формула):

⎩⎨⎧

′++→′′→

⇒+→ETT

ETTTETE

EE

⎨⎧

′→′′→

⇒→AA

AAααββ

αβAA

Для каждой пары полученных правил выполним левую факторизацию:

⎩⎨⎧

′→→

⇒′→E

TXETT

εXE

E ⎩

⎨⎧

′→+→′

⇒′++→′ETX

ETTεX

E E

Page 11: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

11

Так как правые части второго и третьего правил совпадают, для их обозначения в обоих случаях выбран одинаковый нетерминал Х.

В результате получили грамматику, не содержащую леворекурсивных правил:

aTEXE

→+→′

′→→

TXE

TXε

Докажем, что полученная после преобразований грамматика является LL(k): построим присоединённую грамматику, заполним таблицу правилами и вычислим первые терминалы, выводимые из каждого нетерминала для каждого правила грамматики, т.е. множество FIRSTk(A), в случае если нужная длина цепочки получена и множество FIRSTk(A)∪FOLLOWm(A), с таким значением m, чтобы полученная после конкатенации цепочка была не короче заданного значения k.

Правила грамматики FIRSTk(A)∪FOLLOWm(A)

$S E→ a E TX→ a

T a→ a

E′→→

XX ε

$ +

E TX+→′ +

Так как выбрать нужное правило из двух альтернативных выводимых из Х можно на основе анализа одного элемента входной строки грамматика является LL(1).

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

1. Для заданного языка построить контекстно-свободную грамматику. 2. Проверить принадлежность грамматики классу LL(k). 3. Выполнить необходимые преобразования контекстно-свободной

грамматики к классу LL(k)..

Page 12: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

12

4. Доказать что полученная после преобразований грамматика принадлежит классу LL(k) и определить k.

СОДЕРЖАНИЕ ОТЧЁТА

1. Тема, цель, задание. 2. Грамматика заданного подмножества языка. 3. Выводы о принадлежности построенной грамматики к классу LL(k). 4. Необходимые преобразования: устранение левой рекурсии

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

грамматики классу LL(k). 7. Выводы.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Любой ли язык может быть порожден LL(k) грамматикой? Поясните. 2. Укажите простые признаки нарушения свойств LL(1) грамматики,

приведите примеры. 3. Приведите формулы устранения левой рекурсии и покажите как ими

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

правил? 5. Приведите формулы левой факторизации и покажите как ими

пользоваться на частном примере. 6. Объясните принцип нисходящего разбора. Приведите пример

нисходящего разбора. 7. Дайте определение множеств FIRSTk(A) и FOLLOWm(A). Почему

множество FIRSTk(A)∪FOLLOWm(A) называют множеством k первых терминалов?

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

Контекстно-свободные грамматики были введены Хомским при изучении естественных языков. Бэкус и Наур независимо использовали контекстно свободные грамматики для описания языков Фортран и Алгол.

Page 13: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

13

О разработке грамматик и нисходящих методах синтаксического анализа см. [4, 127-158, 215-279], [7, 179-200].

Преобразования грамматики к LL(1) форме изучалось Фостером и Стирнзом. Класс грамматик, которые можно преобразовать к LL(1) форме изучен Розенкранцем и Льюисом. Возможность исключения левой рекурсии следует из результата Грейбах о том, что всякую грамматику, которая порождает язык, не содержащий пустой цепочки, можно преобразовать так, чтобы правые части всех правил начинались терминальными символами. Алгоритмы преобразования грамматик в LL(1) грамматики представлены в [1, 426-441].

Page 14: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

14

Лабораторная работа № 4 СИНТАКСИЧЕСКИЙ АНАЛИЗ. Построение решающих таблиц.

Цель работы: Изучить методы построения решающих таблиц LR анализатора.

Задача: Изучить табличные методы синтаксического анализа.

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

Восходящий разбор

Рассмотрим разбор снизу-вверх, при котором промежуточные выводы перемещаются по дереву в направление к корню. Промежуточный вывод имеет вид αbu, где α – цепочка терминалов и нетерминалов, из которой выводится просмотренная часть терминальной цепочки w, bu – не просмотренная часть терминальной цепочки, b – очередной символ. Чтобы продолжить разбор, можно либо добавить символ b к просмотренной части цепочки (выполнить «сдвиг»), либо выделить в конце α такую цепочку z (α=yz), что к z можно применить одно из правил грамматики B→z и заменить α на цепочку yB (выполнить «приведение» («свёртку»)).

Если приведение применять только к последним символам α, то мы будем получать правые выводы цепочки. Такой разбор получил название LR, где символ L (Left, левый) относится к просмотру цепочки слева направо, а R (Right, правый) относится к получаемым выводам.

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

LR(k)-грамматики.

Говорят о конфликте сдвига-приведения, если для одной цепочки u допустимы и сдвиг и приведение. Говорят о конфликте приведение-приведение, если допустимы свёртки по различным правилам.

Если в процессе LR-разбора принять детерминированное решение о сдвиге/приведении удаётся, рассматривая только цепочку α и первые k

Page 15: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

15

символов не просмотренной части входной цепочки u, говорят, что грамматика обладает LR(k)-свойством.

При разборе строки LR(0)-языка можно вообще не просматривать входную цепочку – выбор между сдвигом и приведением делается на основании цепочки α. Так как в процессе разбора она изменяется только с правого конца, её называют стеком. Грамматика называется LR(0), если для всех состояний стека в процессе LR-вывода нет конфликтов сдвиг-приведение или приведение-приведение.

Алгоритм построения таблицы LR-разбора.

Опишем множество цепочек из терминалов и нетерминалов, появляющихся в стеке в процессе всех LR-разборов (другими словами – всех правых выводов из грамматики). Будем отмечать маркером «●» символ, подлежащий анализу. 1. Построим присоединённую грамматику. 2. Построим граф состояний:

2.0. Добавим таблицу:

Состояние Предыдущее состояние Правила грамматики Переход

2.1. В нулевое состояние выпишем все правила выводимые из порождающего символа грамматики с маркером перед самым левым символом в правой части. 2.2. Для каждого нетерминала, отмеченного маркером выпишем порождаемые им правила с маркером перед самым левым символом в правой части. 2.3. Список правил, полученных выполнением п. 2.1-2.2 или 2.2, 2.4, 2.5 назовём состоянием. 2.4. Если в одном из правил состояния маркер находится после последнего символа правила, в колонку «переход» впишем признак «нет перехода», и отметим состояние как конечное. 2.5. Если в строке, относящейся к состоянию D клетка «правила» заполнена, а клетка «переход» свободна, то в эту клетку ставим номер следующего свободного состояния n. Запоминаем элемент, перед которым в этой строке стоит маркер. Анализируем остальные строки этого состояния. Если в этом

Page 16: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

16

состоянии есть другие строки, в которых маркером отмечен такой же элемент, то в клетки «переход» этих строк вписываем тот же номер n. 2.6. В следующее свободное состояние n выписываем все строки из текущего состояния D, в колонке перехода которых проставлено число n и переносим в них маркер на один символ вправо. В колонку «Предыдущее состояние» добавляем номер состояния, из которого выполнен переход (переход выполнен из состояния D). 2.7. Повторяем действия 2.2-2.6, пока все клетки колонки «Правила» не будут заполнены.

3. Построим решающую таблицу LR-анализатора: 3.0. Добавим таблицу:

Состояние Стек разбора Вход Действие

Заполнение таблицы выполняется на основе анализа строк первой таблицы (графа состояний), относящихся к одноимённому состоянию. 3.1. В колонке «Стек разбора» нулевого состояния должен быть порождающий символ грамматики с действием КОНЕЦ РАЗБОРА и строка ε с действием СДВИГ. Далее см 3.5. 3.2. Просматриваем все строки относящиеся к очередному анализируемому состоянию. В каждом состоянии, кроме нулевого, перед маркером есть элемент. Впишите этот элемент в колонку «Стек разбора». В колонку «действие» этой строки надо вписать базовое действие. Базовыми будут действия сдвиг или приведение. Анализируем все строки последней колонки текущего состояния графа состояний. Если в этих строках только переходы, то состояние промежуточное и базовое действие – СДВИГ, если все строки помечены как конечные базовое действие ПРИВЕДЕНИЕ. 3.3. Если в состоянии есть строки с переходами и строки с признаком «нет перехода», то в этом состоянии должны быть указаны два действия и СДВИГ и ПРИВЕДЕНИЕ. Если базовых действий два, то первым вписываем приведение, во второй строке дублируем содержимое стека разбора и вписываем действие сдвиг. Раз в этом состоянии выполняется два действия по одному содержимому стека разбора, то налицо конфликт сдвига-приведения. Конфликт может быть решён на основании анализа содержимого стека разбора (могут отличаться предыдущий или пред-предыдущий и т.п.

Page 17: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

17

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

3.4. Действие ПРИВЕДЕНИЕ имеет параметры: количество символов в правой части правила и целевой символ (левая часть правила).

3.5. Действие СДВИГ имеет последствия – переходы. Номер состояния, в которое осуществляется переход указан в последней колонке первой таблицы; основание перехода – элемент, расположенный после маркера. Впишите его в колонку «Стек разбора», а номер состояния, куда осуществлять переход, в колонку «Действие».

3.6. Повторяем действия 3.2-3.5 для каждого состояния первой таблицы.

Пример 5. Построим решающие таблицы LR-анализатора для грамматики G(A,{a,b},P,A) с правилами: A→aAA | b. 1. Построим присоединённую грамматику, добавив правило S→A$ 2. Построим граф состояний:

Состояние Предыдущие состояния

Правила Переход Пояснения

0 – S→●A$ 1

A→●aAA 2

A→●b 3

2.1

2.2, 2.3, 2.5

1 0 S→A●$ × 2.4

2 0 A→a●AA 4

A→●aAA 2

2.2, 2.3, 2.5

Page 18: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

18

Состояние Предыдущие состояния

Правила Переход Пояснения

A→●b 3

3 0,2,4 A→●b × 2.4

4 2 A→aA●A 5

A→●aAA 2

A→●b 3

2.2, 2.3, 2.5

5 4 A→aAA● × 2.4

3. Используя граф построим детерминированную таблицу действий для рассмотренной грамматики.

Сост. Стек Вход Действие

0 S конец разбора

ε сдвиг

a переход в состояние 2

b переход в состояние 3

A переход в состояние 1

1 A приведение (-1) к S

2 a a сдвиг, переход в состояние 2

a b сдвиг, переход в состояние 3

A переход в состояние 4

3 b приведение (-1) к A

Page 19: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

19

Сост. Стек Вход Действие

4 aA a сдвиг, переход в состояние 2

aA b сдвиг, переход в состояние 3

AA переход в состояние 5

5 A приведение (-3) к A

Эта грамматика является LR(0), хотя колонка «Вход» и заполнена (см. например состояние 2). Это один из способов решения проблемы времени: вначале выполняется сдвиг, в стеке появится элемент, вписанный в колонку «Вход», и будет выполнен переход в указанное состояние.

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

Переход Состояние Стек Действие

A a b

0 ε сдвиг 1 2 3

S конец разбора нет нет нет

1 A приведение (-1) к S нет нет нет

2 a сдвиг 4 2 3

3 b приведение (-1) к А нет нет нет

4 aA сдвиг 5 2 3

5 aAA Приведение (-3) к А нет нет нет

Page 20: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

20

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

Для грамматики, построенной в ходе лабораторной работы № 3 (до преобразований к классу LL(k) построить граф состояний и детерминированную таблицу действий (решающую таблицу LR(k)-анализатора).

СОДЕРЖАНИЕ ОТЧЁТА

1. Тема, цель, задание. 2. Присоединенная грамматика заданного языка. 3. Граф состояний. 4. Детерминированная таблица действий. 5. Выводы.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Дайте определение LL(k) и LR(k) грамматик. 2. Объясните принцип восходящего разбора. 3. Приведите пример восходящего разбора. 4. Покажите, как должен работать восходящий разбор на заданном

примере входной программы вашего языка. 5. Дайте сравнительную характеристику LL и LR методам разбора. 6. Объясните алгоритм построения графа состояний и решающей таблицы

LR-анализатора. 7. Приведите пример использования построенной таблицы для LR-

анализа предложения яхыка.

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

LR-грамматики и синтаксические анализаторы были введены Кнутом, который описал построение канонических таблиц LR-анализа. LR метод не рассматривался как практический до тех пор, пока Кореньяк не показал, что с его помощью можно разработать синтаксический анализатор вполне разумного размера для разбора грамматики языка программирования. После разработки деРемером SLR и LALR -методов LR-технология стала основным методом автоматической генерации синтаксических

Page 21: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

21

анализаторов. Алгоритмы LR(k) анализаторов приведены в [4, 280-318], [5, 217-240], [7, 220-250].

Page 22: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

22

Лабораторная работа № 5 СИНТАКСИЧЕСКИЙ АНАЛИЗ. Программирование.

Цель работы: Получить навык в программировании синтаксических анализаторов.

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

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

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

Синтаксический анализатор (англ. - parser) может быть построен для любой грамматики. Для любой контекстно свободной грамматики существует анализатор, требующий самое большее O(n3) времени для разбора строки из n лексем. Но для языков, которые встречаются на практике, хватает линейных алгоритмов. Анализаторы языков программирования почти всегда проходят один раз слева направо входной текст, считывая за раз одну лексему.

Большинство методов синтаксического разбора относятся к «восходящим» или «нисходящим» методам. В первом случае построение дерева начинается с корня и продолжается в направлении листьев, в то время как во втором – построение начинается с листьев и развивается по направлению к корню. Популярность анализаторов «сверху вниз» объясняется тем, что эффективный анализатор можно легко построить «вручную». Анализаторы «снизу-вверх» могут работать с большим классом грамматик и схем перевода, поэтому в программах-генераторах анализаторов всё больше используются восходящие алгоритмы разбора.

Разбор сверху вниз.

Текущую сканируемую лексему часто называют текущим символом. В начале текущий символ это первая (самая левая) лексема входной строки.

Если текущий узел дерева разбора помечен терминалом и этот терминал совпадает с текущим символом, мы должны продвинуться как в дереве разбора, так и во входной строке Как только в дереве

Page 23: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

23

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

Предсказывающий разбор

Метод рекурсивного спуска – это нисходящий метод синтаксического анализа, в котором входной поток лексем обрабатывается выполнением ряда рекурсивных процедур. Каждому нетерминалу грамматики соответствует одна подпрограмма, которая распознаёт цепочку, порождаемую этим нетерминалом. Последовательность вызовов процедур неявно определяет дерево разбора. Метод рекурсивного спуска применим в классе LL(k) грамматик.

При разработке подпрограмм, необходимо придерживаться следующих правил: 1. Каждому нетерминалу соответствует подпрограмма. Если одному

нетерминалу в левой части правила соответствует более одной правой части, то первый шаг – выбор нужного правила из списка альтернативных, для чего используется информация из второй колонки решающей таблицы, иначе первый шаг – пропустить. На этом этапе программируется анализ текущего элемента входного потока и следующих за ним (но не более k) и в случае совпадения элементов с эталонным набором из таблицы, программируется ветка, соответствующая этому эталону (см. правила 2 и 3). Смены текущего элемента при этом не происходит. Если нет совпадения ни с одним из возможных эталонных вариантов, то фиксируем ошибку и выходим из подпрограммы.

2. Если текущим символом правой части правила грамматики является нетерминал, ему соответствует вызов подпрограммы, имя которой

Page 24: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

24

совпадает с этим нетерминалом и которая распознаёт цепочку, порождаемую им.

3. Если текущим символом правой части правила грамматики является терминал, то проверяем текущий элемент цепочки на совпадение с этим терминалом. Если совпадения нет – фиксируем ошибку и выходим из подпрограммы, иначе перемещаемся к следующему символу входного потока (текущим становится следующий). Пример 6. Построить нисходящий анализатор методом рекурсивного

спуска для фрагмента грамматики подмножества языка Pascal: <тип> ::= <простой> | ^id | array [ <простой> ] of <тип> <простой> ::= integer | char | lit dotdot lit (Термин dotdot использован в грамматике вместо "..", чтобы подчеркнуть, что эта цепочка символов обрабатывается как одно целое). Заполним решающую таблицу: <тип> ::= <простой> <тип> ::= ^id <тип> ::= array [ <простой> ] of <тип>

integer, char, lit ^ array

<простой> ::= integer <простой> ::= char <простой> ::= lit dotdot lit

integer char lit

Пусть переменная Tek содержит текущий символ входного потока. Подпрограмма Next выполняет настройку переменной Tek на следующий элемент. Приведём псевдокод предсказывающего анализатора: Procedure p_тип(); begin if Tek in ['integer', 'char', 'lit'] then p_простой() else if Tek = '^' then begin Next;

if Tek ≠ 'id' then begin Error; exit; end; Next; end else if Tek = 'array' then begin Next;

Page 25: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

25

if Tek ≠ '[' then begin Error; exit; end; Next; p_простой();

if Tek ≠ ']' then begin Error; exit; end; Next;

if Tek ≠ 'of' then begin Error; exit; end; Next; p_тип(); end else Error; end;

Procedure p_простой() begin if (Tek = 'integer') or (Tek = 'char') then next else begin

if Tek ≠ 'num' begin Error; exit; end; Next;

if Tek ≠ 'dotdot' begin Error; exit; end; Next;

if Tek ≠ 'num' begin Error; exit; end; Next; end end;

При разработке нисходящего анализатора по приведённому примеру помните, что ваши входные данные – в таблице стандартных символов. Поэтому проверка на служебное слово может выглядеть например так: if TSS[i] ≠ 'T-1' then… . Там, где проверяем на идентификатор или литерал в анализируем только категорию лексемы, но не её номер в соответствующей таблице. Желательно чтобы каждому нетерминалу соответствовала функция с результатом: код ошибки или, 0 если ошибки не было. Поэтому в месте вызова подпрограммы необходимо будет проверять результат вызова (т. к. вызываем функцию) и продолжать разбор только если ошибки не было.

Page 26: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

26

Разбор снизу-вверх

Рассмотрим два варианта синтаксического анализатора, разбирающего предложения LR(k)-языка. Первый вариант (A) построен на первой форме детерминированной решающей таблицы, в основе второго (B) – вторая приведённая форма организации таблицы LR-анализатора. Пример А. стек_состояний.добавить (0) // начальное состояние стек_разбора.очистить цикл . выбор из стек_состояний. вершина 0: Если стек_ разбора. пуст то сдвиг

иначе если стек_ разбора. вершина = 'S' то стоп( "Успех" ) иначе если стек_ разбора. вершина = (перебираем другие

приведённые в таблице 2 значения) то переход(n) иначе стоп( "Ошибка" );

1: Если стек_ разбора. вершина = 'xxx' то приведение(?, 'yyy') иначе стоп( "Ошибка" );

… // Аналогично для каждого состояния таблицы 2. 30: … . конец выбора конец цикла Пример В. стек.очистить стек.добавить ( '#', начальное состояние ) цикл . выбор из (стек. вершина, состояние) .действие . . "сдвиг" => . . . прочитать очередной символ в ( новый символ ) . . "свертка" => . . . удалить правую часть правила из стека . . . новый символ := левая часть правила . . "успех" => . . . стоп( "успех" )

Page 27: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

27

. конец выбора

. старое состояние := V ( стек.вершина.состояние )

. новое состояние := старое состояние . переход [новый символ]

. если новое состояние = "Ошибка" то стоп( "ошибка" ) конец если

. стек.добавить ( новый символ, новое состояние ) конец цикла

Для выбора между сдвигом или свёрткой в LR(0) разборе используется только содержимое стека разбора. В LR(k) разборе учитывается также k-первых символов не просмотренной части входной цепочки.

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

Построить LL(k) и LR(k) анализаторы. Запрограммировать один из них (в соответствии с заданием) и проиллюстрировать на частном примере работу другого. LL(k)-анализатор оформляйте как систему функций. Входной аргумент – указатель (индекс) на текущий элемент таблицы стандартных символов, результат – код ошибки.

СОДЕРЖАНИЕ ОТЧЁТА

1. Тема, цель, задание. 2. LL или LR-анализатор и программа осуществляющая синтаксический

разбор любой программы написанной на заданном языке. 3. Тестовые примеры. 4. Результаты обработки тестовых примеров.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Какую роль выполняет синтаксический анализ в процессе компиляции? 2. Расскажите о задаче разбора. Что такое распознаватель языка? 3. Расскажите об общих принципах работы распознавателя языка. 4. Какие типы распознавателей для КСГ существуют? Расскажите

о недостатках и преимуществах различных типов распознавателей. 5. Дайте определение LL(k) и LR(k) грамматик. 6. Объясните принцип нисходящего (восходящего) разбора. 7. Дайте сравнительную характеристику LL и LR методам разбора.

Page 28: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

28

8. Расскажите как работает нисходящий разбор с возвратом. 9. Расскажите как работает нисходящий разбор без возвратов. Какие

требования предъявляются к грамматике на базе которой возможно построение такого распознавателя?

10. Расскажите как работает восходящий распознаватель.

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

Анализ методом рекурсивного спуска используется с начала 60-х годов. Благодаря своей гибкости метод рекурсивного спуска использовался во многих ранних системах разработки компиляторов. Алгоритм построения распознавателя по методу рекурсивного спуска см. [5, 178-199], [1, 463-486]. Алгоритм построения анализаторов LR(k) и SLR(1) языков представлены в [4, 287-317].

Page 29: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

29

Лабораторная работа № 6 ТРАНСЛЯЦИЯ АРИФМЕТИЧЕСКИХ ВЫРАЖЕНИЙ

Цель работы: Изучение методов трансляции арифметических и логических выражений.

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

Метод Бауэра - Замельзона.

В методе используются два стека и таблица переходов. Один стек, обозначим его E, используется для хранения операндов, другой стек – Т для хранения знаков операций.

Над стеком Е выполняются две операции: К(id) – выбрать элемент с именем id из входного потока, положить на

вершину стека Е, перейти к следующему элементу входного потока;

К(ОР) – извлечь два верхних операнда из стека Е, записать тройку: (ОР, операнд, операнд) в матрицу арифметического (логичес-кого) оператора; записать результат на вершину стека Е.

Приведём вид таблицы переходов для алгебраических выражений: Входной символ $ ( + - * / ) символ ε VI I I I V на ( V I I I III вершине +- IV I II I IV стека */ IV I IV II IV

В таблице переходов задаются действия, которые должен выполнить транслятор при разборе выражения. Возможны шесть действий при прочтении операции ОР из входной строки (ОР1 – операция на вершине стека Т): I Поместить операцию ОР на вершину стека Т; читать следующий

символ строки (Push(ОР); Next;); II Извлечь символ из стека Т (⇒ОР1), генерировать команду

К(ОР1); поместить операцию ОР на вершину стека Т; читать

Page 30: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

30

следующий символ строки (POP(ОР1); К(ОР1); Push(ОР); Next;); III Исключить символ из стека Т; читать следующий символ строки

(POP(); Next;); IV Извлечь символ из стека Т (⇒ОР1), генерировать команду

К(ОР1); поместить операцию ОР на вершину стека Т (POP(ОР1); К(ОР1); Push(ОР););

V Ошибка. Конец разбора; VI Успешное завершение разбора.

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

Пример. Для выражения A+(B-C)*D полученная последовательность команд имеет вид K(A)K(B)K(C)K(-)K(D)K(*)K(+) или опуская все К получится запись ABC-D*+, которая известна в литературе как польская инверсная запись.

Метод Дикстры перевода арифметического (логического) оператора в обратную польскую запись.

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

входного знака. После этого входной знак добавляется к вершине стека.

( [ if 0 = ) ] then 1

or 2 and 3 not 4

< = ≠ ≥ ... 5 + - 6 × ÷ / 7

^ 8

Page 31: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

31

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

После просмотра всех символов входной строки происходит выталкивание всех оставшихся в стеке символов и дописывание их к выходной строке.

Программируя приведённый алгоритм учтите, что нам не нужна обратная польская запись. Нам нудна матрица арифметического (логического) оператора. Есть несложный алгоритм, который переводит обратную польскую запись в матрицу, но лучше если вы объедините алгоритм перевода с методом Дейкстры и не будете порождать промежуточный вывод в виде обратной польской записи.

Алгоритм перевода обратной польской записи в матричную форму. Читаем обратную польскую запись слева направо. При чтении

операнда – он записывается в стек Е; при чтении операции – извлекаем из стека E два верхних операнда, формируем тройку (операция, операнды), записываем её в матрицу; в стек E записываем элемент матрицы, в котором будет результат выполнения операции.

Стековый метод с использованием таблиц предшествования.

Другим средством задания транслятору старшинства операций являются таблицы предшествования, которые устанавливают между операциями отношения предшествования. Отношение .=. устанавливается между операциями одного порядка старшинства. Отношение <⋅ устанавливается, если после данной операции в выражении следует более приоритетная операция. Например, если после операции "+" следует операция "*". Если же после данной операции следует менее приоритетная операция, то между ними устанавливается отношение ⋅>. Алгоритм использует три стека: стек операций, стек операндов и стек анализа (выделения основы). Выражение просматриваются слева направо,

Page 32: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

32

операнды и операции заносятся в стеки до тех пор, пока между символом на вершине стека и входным символом не выполнится отношение ⋅>. После этого перемещаясь по стеку основ в обратном направлении находим отношение .<⋅ Полная основа в стеке – можно выполнять приведение. Если в стеке тройка, то достаем из стека операндов два, а из стека операций один элемент. На вершину стека операндов заносится вспомогательная переменная, обозначающая результат, и описанные выше действия повторяются.

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

В соответствии с заданным вариантом дополнить программу синтаксического разбора подпрограммой разбора арифметического (логического) оператора. Результат разбора записать в матрицу арифметического (логического) оператора.

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

ПОРЯДОК ВЫПОЛНЕНИЯ ЛАБОРАТОРНОЙ РАБОТЫ:

1. Разработать необходимые для работы алгоритма таблицы переходов или предшествования;

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

3. подготовить тестовые примеры; 4. распечатать исходные тексты подпрограмм, входные данные

и результаты их обработки.

Page 33: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

33

СОДЕРЖАНИЕ ОТЧЁТА

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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Какие ошибки во входной строке фиксируют элементы таблицы «ошибка» ?

2. Почему в таблице переходов нет строки, соответствующей правой скобке ?

3. Существуют или нет преимущества от использования таблицы предшествования по сравнению с использованием таблиц переходов? в чем они заключаются?

4. Перечислите достоинства и недостатки используемого вами алгоритма.

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

Первоначальная формулировка методов предшествования и определение некоторых классов грамматик анализируемых этими методами, содержится в работах Флойда, Пэра, Вирта и Вебера. Различные классы грамматик предшествования широко рассмотрены в книге Ахо и Ульмана [7, 209-220]. Описание метода Бауэра-Замельзона взято из [11, с. 28-37], а метода Дейкстры из [12, с. 130-153]. Подробнее о внутренних формах арифметического (логического) оператора см. [1, 615-622], [7, 443-73], [8, с. 278-292].

Page 34: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

34

Лабораторная работа № 7 СЕМАНТИЧЕСКАЯ ИНТЕРПРЕТАЦИЯ.

Цель работы: Изучение алгоритмов замены конструкций языка высокого уровня соответствующими конструкциями на языке ассемблера.

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

В ходе семантического анализа проверяются отдельные правила записи исходных программ, которые носят контекстно-зависимый характер. Их называют семантическими соглашениями или контекстными условиями.

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

1. Проверку соблюдения семантических соглашений языка: сопоставление входных цепочек программы с требованиями семантики входного языка программирования. Каждый язык программирования имеет чётко заданные семантические соглашения, которые не могут быть проверены на этапе синтаксического разбора. Например:

− каждый идентификатор должен быть описан до его использования и только один раз (в блоке);

− каждая метка, на которую есть ссылка, должна один раз присутствовать в программе;

− все операнды в операции должны иметь типы допустимые для данной операции, эти типы должны быть согласованы между собой;

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

Page 35: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

35

Нарушение семантических требований влечёт выдачу сообщений об ошибке и прекращение трансляции. 2. Дополнение внутреннего представления программы операторами

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

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

− каждая переменная должна хотя бы раз использоваться в программе; − каждая переменная должна быть определена до её первого

использования при любом ходе выполнения программы; − результат функции должен быть определен при любом ходе

её выполнения; − операторы цикла должны предусматривать возможность завершения

цикла; − операторы условия и выбора должны предусматривать возможность

хода выполнения по каждой из своих ветвей. Необязательность соглашений такого типа накладывает особенность

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

Page 36: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

36

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

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

СОДЕРЖАНИЕ ОТЧЁТА

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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Является ли ваш язык программирования контекстно свободным языком? 2. Можно ли построить компилятор без семантического анализа? Если да, то какие условия должны при этом соблюдаться. 3. Перечислите основные действия, выполняемые на этапе семантического анализа. Приведите примеры. 4. Расскажите алгоритм реализации одного из семантических действий (на выбор преподавателя). 5. Объясните разницу между ошибкой и предупреждением. 6. Почему распределение памяти не может быть выполнено до семантического анализа?

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

Подробнее о назначении семантического анализа и его этапах читайте в [1, 588-597].

Page 37: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

37

Лабораторная работа № 8 ГЕНЕРАЦИЯ КОДА, СБОРКА.

Цель работы: Изучение основных принципов генерации объектного кода.

Теоретические сведения Генерация кода – это перевод транслятором внутреннего

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

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

Распределение памяти – это процесс, который ставит в соответствие лексическим единицам исходной программы адрес, размер и атрибуты области памяти, необходимой для этой лексической единицы. Входные данные – таблица идентификаторов, исправленная после синтаксического и семантического анализа декларативной части программы. К этому моменту должна быть известна вся информация по переменным, описанным в программе: тип памяти (статическая, стековая, динамическая), область видимости, объем требуемой памяти Если компилятор выполняет оптимизацию, то распределение памяти выполняется после оптимизирующих преобразований. Возможно память придется выделять дополнительно под хранение промежуточных результатов вычислений. Отдельные семантические программы занимается

Page 38: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

38

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

Как правило, транслятор выполняет генерацию кода поэтапно на основе анализа законченных синтаксических конструкций входной программы. Смысл синтаксической конструкции определяется исходя из её типа. Примеры синтаксических конструкций различных типов: операторы цикла, условные операторы, операторы выбора и т.д. Одни и те же типы синтаксических конструкций характерны для различных языков программирования, при этом они различаются синтаксисом, но имеют схожий смысл. Для семантически схожих конструкций различных входных языков программирования может порождаться типовой результирующий код.

Соответствие между операторами языка высокого уровня и операторами языка ассемблера оценивается как 1:N, т.е. один оператор алгоритмического языка заменяется N операторами языка ассемблера. Одним из наиболее простых подходов к решению задачи подстановок является представление операторов или операций алгоритмического языка в виде макрокоманд, а операндов в виде параметров макрокоманды. При обращении к такой макрокоманде выполняется замена её макроопреде-лением. Все макроопределения могут быть сведены в библиотеке данного транслятора.

Например, оператор присваивания S:=F + D может быть заменён последовательностью операторов ассемблера: MOV AX,F ADD AX, D MOV S,AX

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

Page 39: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

39

Макроопределения: Sum2 MACRO X,Y,Z ;; X+Y →Z MOV AX,X ADD AX, Y MOV Z,AX ENDM

Prisv2 MACRO X,Y ;; X→Y MOV AX,X MOV Y,AX ENDM

Макрокоманды: Sum2 F,D,M1 Prisv2 S,M1

Чтобы компилятор мог построить код результирующей программы для синтаксической конструкции входного языка, часто используется метод, называемый синтаксически управляемым переводом (СУ-переводом). СУ-перевод представляет собой КСГ в которой к каждому правилу добавлен элемент перевода (в некоторых правилах он может быть пустым). Всякий раз когда правило участвует в выводе входной цепочки, с помощью элемента перевода вычисляется часть выходной цепочки, соответствующая части входной цепочки, порождённой этим правилом.

В случае разработки кодовых образцов для нелинейных операторов, надо предусматривать необходимость обеспечения уникальности меток. Для этого имя метки может состоять из шаблонной части и счётчика. В ходе работы счётчик, после резервирования, должен храниться с стеке или в области локальных данных. В качестве первого символа имени метки желательно выбирать такой символ, который допустим в качестве первого символа метки в языке ассемблера, но не разрешён в алгоритмическом языке. Такими символами, например, могут быть: ? , . , @ , $. Например для условного оператора:

if <условие> then <оператор> [else <оператор>] может быть построена следующая СУ-схема:

if <условие> then «D1» <оператор>«D2» [else <оператор>] «D3», где D1 Зарезервировать метку (i) и сохранить её в стеке. Сгенерировать код:

«Если условие ложь перейти на метку Else_i» D2 Прочитать значение метки (i) из стека, сгенерировать код:

Page 40: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

40

«Переход на метку EndIf_i Определить метку Else_i»

D3 Извлечь из стека метку (i). Сгенерировать код: «Определить метку EndIf_i»

ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ

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

СОДЕРЖАНИЕ ОТЧЁТА

Грамматика, лежащая в основе синтаксического разбора, дополненная действиями с их расшифровкой. Примеры кодовых продукций для каждой конструкции вашего языка, тексты макроопределений, законченный текст программы – компилятора, Примеры работы компилятора на тестовых последовательностях, Листинги полученных asm - файлов.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Что такое транслятор, компилятор, интерпретатор? Расскажите об общей структуре компилятора.

2. Какую роль выполняет генерация объектного кода? Какие данные необходимы компилятору для генерации объектного кода? Какие действия выполняет компилятор перед генерацией кода?

3. Объясните, почему генерация объектного кода выполняется компилятором по отдельным синтаксическим конструкциям , а не для всей исходной программы в целом?

4. Расскажите что такое синтаксически-управляемый перевод.

ЗАМЕЧАНИЯ ПО ЛИТЕРАТУРЕ

Синтаксически-управляемые переводы впервые были использованы Айронсом. Математические модели синтаксически-управляемых переводов были изучены в работах Пола, Янгера, Льюиса, Стирнза, Ахо,

Page 41: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

41

Ульмана и Вебера. Атрибутные грамматики с синтезируемыми и наследуемыми атрибутами были введены и изучены Кнутом. Формальные методы описания перевода: СУ-схемы, атрибутные и транслирующие грамматики и методики разработки описаний перевода изложены в [4, 396-466]. Обзор синтаксически управляемой трансляции, применение синтезируемых и наследуемых атрибутов в нисходящих и восходящих алгоритмах синтаксического анализа см. [7, 279-334].

Page 42: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

42

Список используемых источников 1. Гордеев А.В., Молчанов А.Ю. Системное программное обеспечение: учебник. - СПб.: Питер, 2001.– 736 с.

2. Молчанов, А.Ю. Системное программное обеспечение: учебник. – СПб.: Питер, 2006. – 396 с.

3. Молчанов, А.Ю. Системное программное обеспечение: лабораторный практикум. – СПб.: Питер, 2005. – 284 с.

4. Опалёва Э.А., Самойленко В.П. Языки программирования и методы трансляции: уч. пособие. – СПб.: БХВ-Петербург, 2005. – 480 с.

5. Карпов, Ю.Г. Теория и технология программирования. Основы построе–ния трансляторов: уч. пособие. – СПб.: БХВ-Петербург, 2005. – 272 с.

6. Льюис Ф. и др. Теоретические основы проектирования компиляторов. / Ф. Льюис, Д. Розенкранц, Р. Стирнз. М.: Мир, 1979- 654.

7. Ахо А., Сети Р., Ульман Д. Компиляторы: принципы, технологии и инструменты. – М.: Вильямс, 2003. - 768.

8. Грис Д. Конструирование компиляторов для ЦВМ. М.: Мир, 1975- 544. 9. Хопкрофт и др. Введение в теорию автоматов, языков и вычислений. М: Диалектика, 2002. - 528с.

10.Рейуорд - Смит В.Дж. Теория формальных языков. Вводный курс: пер. с англ. - М.: Радио и связь, 1988. – 128 с..

11.Вайнгартен Ф. Трансляция языков программирования. М.: Мир, 1977 – 190 с.

12.Лебедев В.Н. Введение в системы программирования. М.: Статистика, 1975 – 312 с.

Page 43: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

43

Оглавление

Предисловие......................................................................................................... 3 Лабораторная работа № 3 ПОСТРОЕНИЕ И ПРЕОБРАЗОВАНИЕ

КОНТЕКСТНО-СВОБОДНЫХ ГРАММАТИК......................................................... 4

Лабораторная работа № 4 СИНТАКСИЧЕСКИЙ АНАЛИЗ. Построение решающих таблиц. .......................... 14

Лабораторная работа № 5 СИНТАКСИЧЕСКИЙ АНАЛИЗ. Программирование. .............................................. 22

Лабораторная работа № 6 ТРАНСЛЯЦИЯ АРИФМЕТИЧЕСКИХ ВЫРАЖЕНИЙ ...................................................... 29

Лабораторная работа № 7 СЕМАНТИЧЕСКАЯ ИНТЕРПРЕТАЦИЯ. ........ 34 Лабораторная работа № 8 ГЕНЕРАЦИЯ КОДА, СБОРКА. ........................ 37 Список используемых источников .................................................................. 42

Page 44: lb spo часть 2 8 - rfpro.rurfpro.ru/mfa/184880-90d5409c2626005dd77433e46a3bbfc2.pdf · УДК 681.3.06 (075.8) ББК 32.973.26-018 Р 17 Рецензент: доцент кафедры

Учебное издание

РАЗРАБОТКА КОМПИЛЯТОРОВ

методические указания к лабораторному практикуму Часть 2

Ответственный редактор – Калинкина Н.Е.

Составитель: Наталья Евгеньевна Калинкина

Подписано в печать 15.12.2008. Формат 60х84 1/16. Бумага для множительной техники. Гарнитура Times. Печать офсетная.

Усл. печ. л. 2,56. Уч.-изд. л. 1,42. Тираж 100 экз. Заказ № 1026. Муромский институт (филиал)

Государственного образовательного учреждения высшего профессионального образования

«Владимирский государственный университет» Адрес: 602264, Владимирская обл., г. Муром, ул. Орловская, 23

E-mail: с[email protected]