Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Layt_teor_osnovy_form_yazykov.doc
Скачиваний:
2
Добавлен:
01.04.2025
Размер:
1.17 Mб
Скачать

3.2. Проблемы нисходящего разбора и их решение.

Прямая левосторонняя рекурсия.

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

По этой причине правила грамматики пишут с применением правосторонней рекурсии вместо более привычной левосторонней. Лучший способ избавиться от прямой левосторонней рекурсии - записывать правила, используя итеративные и факультативные обозначения. Запишем правила

(3.1) E::=E+T|T как E::=T{T} и

T::=T*F|T/F|F как T::=F{*F|/F}

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

Факторизация. Если существуют правила вида U::=xy|xw|...|xz, то их надо заменить правилами U::= x(y|w|...|z), где скобки являются метасимволами.

Допустима факторизация и в более общей форме, такая, как в арифметических выражениях. Например, если y=y1y2 и w=y1w2, мы могли бы заменить U::=x(y|w|...|z) на U::=x(y1(y2|w2)|...|z).

Заметьте, что исходные правила U::=x|xy мы преобразуем к виду U::=x(y|), где  - пустая цепочка. Когда бы ни использовалось подобное преобразование,  всегда помещается как последняя альтернатива, так как мы принимаем условие, что если цель - , то эта цель всегда сопоставляется.

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

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

Пусть U::=x|y|...|z|Uv - правила, у которых осталась леворекурсивная правая часть. Эти правила означают, что членом синтаксического класса U является x, y или z, за которыми либо ничего не следует, либо следует сколько-то v. Тогда преобразуем эти правила к виду U::=(x|y|...|z) {v}.

Данное преобразование позволяет избавиться от ненужных скобок, заключающих T (см. выраж. 3.1). В качестве другого примера преобразуем A::=BC|BCD| Axz|Axy.

Рис.3.1. Деревья, использующие рекурсию и итерацию.

Применив правило факторизации, получим A::= BC(D|) |Ax(z|y); применив последующее преобразование, получим A::= (BC(D|){x(z|y)}.

Использование итерации вместо рекурсии отчасти меняет и структуру деревьев. Таким образом, рис.3.1, a должен был бы походить на рис.3.1,b. Мы утверждаем, что эти два дерева следует рассматривать как эквивалентные; операторы "плюс" должны выполняться слева направо.

Общая левосторонняя рекурсия

Мы не решили всей проблемы левосторонней рекурсии: с прямой левосторонней рекурсией покончено, но общая левосторонняя рекурсия еще осталась. Таким образом, правила U::=Vx и V::=Uy|v Дают вывод U=>+ Uyx. Избавиться от этого не так просто, но обнаружить такую ситуацию можно. Исключим из исходной грамматики все правила с прямой левосторонней рекурсией. Символ U, получившейся в результате этих преобразований грамматики, может быть леворекурсивным тогда и только тогда, когда U FIRST+ U.

5. ОБЗОР ПРОЦЕССА КОМПИЛЯЦИИ.

5.1. АССЕМБЛЕР.

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

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

ADD AX,COST

прибавляет содержимое ячейки памяти, ассоциируемой с идентификатором COST, к регистру АХ. Аббревиатура ADD является мнемоникой команды.

Директива

COST DW ?

Заставляет ассемблер зарезервировать слово и ассоциировать с ним идентификатор COST, но не порождает машинной команды.

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

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

[Метка:] Мнемокод [Операнд] [;Комментарии]

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

Чтобы разобраться каким образом происходит преобразование исходной программы на языке высокого уровня в объектную программу, рассмотрим мнемокоды (таблица 5.1).

Таблица 5.1

Мнемокоды ассемблера IBM PC

Мнемокод

Назначение

ААА

Скорректировать сложение для представления в кодах АSCII

AAD

Скорректировать деление для представления в кодах ASCII

ААМ

Скорректировать умножение для представления в кодах ASCII

AAS

Скорректировать вычитание для представления в кодах ASCII

ADC

Сложить с переносом

ADD

Сложить

AND

Выполнить операцию И

CALL

Вызвать процедуру

CBW

Преобразовать байт в слово

CMP

Сравнить значения

CMPS, CMPSB, CMPSW

Сравнить строки

CWD

Преобразовать слово в двойное слово

DIV

Поделить

ESC

Передать команду сопроцессору

HLT

Остановиться

IDIV

Разделить целые числа

IMUL

Умножить целые числа

IN

Считать значение из порта

INT

Прервать

LEA

Загрузить исполнительный адрес

LODS, LODSB, LODSW

Загрузить строку

LOOP

Повторять цикл до конца счетчика

MOV

Переслать значение

MOVS, MOVSB, MOVSW

Переслать строку

MUL

Умножить

NEG

Обратить знак

NOT

Обратить биты

OR

Выполнить операцию ИЛИ

OUT

Вывести значение в порт

POP

Поместить значение в стек

PUSH

Извлечь значение из стека

RET

Возвратиться в вызывающую процедуру

STOS,STOSB, STOSW

Сохранить строку

SUB

Вычесть

TEST

Проверить

XOR

Выполнить операцию ИСКЛЮЧАЮЩЕЕ ИЛИ

MOV – основная команда общего назначения, позволяющая пересылать байт или слово между регистром и ячейкой памяти или между двумя регистрами. Она может также пересылать непосредственно адресуемое значение в регистр или в ячейку памяти.

ADD – складывает содержимое операнда – источника и операнда – приемника и помещает результат в операнд – приемник.

SUB – вычитает операнд – источник из операнда – приемника и помещает результат в операнд – приемник.

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

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

Слова AH, AL, AX, BH, BL, BX, BP, CH, CL, CS, DH, DL, DX, DI, DS, ES, SI, SP, ST являются зарегистрированными именами регистров (аккумуляторы).

Представленные в таблице 5 мнемокоды, будем использовать далее в конкретных примерах.

Например, фрагмент программы, реализующий расчет выражения W=X+Y+24-Z может выглядеть следующим образом:

Директивы определения данных, команды ввода и др. X,Y,W,Z определены как переменные-слова.

MOV AX, X ;передать (X) в AX

ADD AX, Y ;прибавить (Y) к AX

ADD AX, 24 ;прибавить 24 к сумме

SUB AX,Z ;вычесть (Z) из (X)+(Y)+24

MOV W, AX ;запомнить результат в W

5.2. ОСНОВНЫЕ ЧАСТИ КОМПИЛЯТОРА.

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

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

Желательные качества транслятора таковы:

1) эффективность трансляции - время необходимое для обработки входной цепочки (программы) линейно зависит от ее длины;

2) небольшой объем;

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

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

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

(1) Лексический анализ.

(2) Работа с таблицами.

(3) Синтаксический анализ, или разбор.

(4) Генерация кода, или трансляция в промежуточный код (например, язык ассемблера).

(5) Оптимизация кода.

(6) Генерация объектного кода (например, ассемблирование).

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

Мы кратко опишем первые пять фаз компиляции. В реальном компиляторе они не обязательно разделены. Однако методически часто оказывается удобным расчленить компилятор на эти фазы, чтобы изолировать проблемы, присущие именно этим частям процесса компиляции.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]