Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GOS / Дисциплины программистского цикла.doc
Скачиваний:
53
Добавлен:
09.05.2015
Размер:
1.51 Mб
Скачать

24. Методы описания языков программирования. Бнф. Классы грамматик языков. Общая схема трансляции. Функции отдельных блоков транслятора.

Формальный язык определяется как множество строк, в котором каждая строка представляет собой последовательность символов. Все языки состоят из бесконечного множества строк, поэтому нужен краткий способ, позволяющий охарактеризовать это множество. Для этого служит грамматика. В качестве способа оформления грамматик приняли формальную систему, называемую формой Бэкуса—Наура (Backus—Naur form — BNF). Грамматика BNF состоит из четырех компонентов, перечисленных ниже.

•    Множество терминальных символов. Это — символы или слова, из которых состоят строки языка. В качестве таких символов могут использоваться буквы (А, В, С, ...) или слова (a, aardvark, abacus, ...).

•    Множество нетерминальных символов, которые обозначают компоненты предложений языка. Например, нетерминальный символ NounPhrase (именное словосочетание) в английском языке обозначает бесконечное множество строк, которое включает "you" и "the big slobbery dog".

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

• Множество правил подстановки

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

Алфавит – это некоторое конечное множество символов.

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

Если α и β – цепочки, то цепочка αβ называется конкатенацией (или сцеплением) цепочек α и β. Если α = ab и β = cd, то αβ = abcd. Для любой цепочки α выполняется: αλ = λα = α.

Обращением (или реверсом) цепочки α называется цепочка, символы которой записаны в обратном порядке. Обращение цепочки α будем обозначать αR.

n-ой степенью цепочки α (обозначается αn) называется конкатенация n цепочек α.: α0 = λ; αn = ααn-1 = αn-1α.

Длина цепочки – число составляющих ее символов. Обозначается |α|. Язык в алфавите V – это подмножество цепочек конечной длины в этом алфавите.

Обозначим через V* множество, содержащее все цепочки в алфавите V, включая пустую цепочку. Т.е., если V={0,1}, то V* = {λ,0,1,00,11,01,10,000,001, 011,…}.

Обозначим через V+ множество, содержащее все цепочки в алфавите V,

исключая пустую цепочку. Следовательно, V* = V+ {λ}. Каждый язык в алфавите V является подмножеством множества V*.

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

Порождающая грамматика G – это четверка (VT, VN, P, S), где:

VT – алфавит терминальных символов (терминалов),

VN – алфавит нетерминальных символов (нетерминалов), не пересекающийся с VT,

P – конечное подмножество множества (VT VN)+ х (VTVN)*; элемент (α, β) множества P называется правилом вывода и записывается в виде α → β,

S – начальный символ (цель) грамматики, S VN. Для записи правил вывода с одинаковыми левыми частями:

α → β1, α → β2, ..., α → βn

можно пользоваться сокращенной записью:

α → β1 | β2 |...| βn.

Каждое βi , i= 1, 2, ... ,n , называется альтернативой правила вывода из цепочки α.

Пример грамматики: G1 = ({0,1}, {A,S}, P, S), где P состоит из правил:

S → 0A1, 0A → 00A1, A → λ.

Цепочка β ∈ (VT ∪ VN)* непосредственно выводима из цепочки α ∈ (VT

∪ VN)+ в грамматике G = (VT, VN, P, S) (обозначим α → β), если α = ξ1γξ2,

β = ξ1δξ2, где ξ1, ξ2, δ ∈ (VT ∪ VN)*, γ ∈ (VT ∪ VN)+ и правило вывода

γ → δ содержится в P. Например, цепочка 00A11 непосредственно выводима из

0A1 в грамматике G1.

Цепочка β ∈ (VT ∪ VN)* выводима из цепочки α ∈ (VT ∪ VN)+ в грам-

матике G = (VT, VN, P, S) (обозначим α ⇒ β), если существуют цепочки

γ0, γ1, ... , γn (n>=0), такие, что α = γ0 → γ1 → ... → γn= β.

Последовательность γ0, γ1, ... , γn называется выводом длины n. Напри-

мер, S ⇒ 000A111 в грамматике G1 (см. пример выше), т.к. существует вывод

S → 0A1 → 00A11 → 000A111. Длина вывода равна 3.

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

Классификация грамматик и языков по Хомскому

В данной классификации грамматики классифицируются по виду их правил вывода.

ТИП 0: Грамматика G = (VT, VN, P, S) называется грамматикой типа 0, если на правила вывода не накладывается никаких ограничений.

ТИП 1: Грамматика G = (VT, VN, P, S) называется неукорачивающей, если каждое правило из P имеет вид: α → β, где α (VTVN), β(VTVN) и | α | ≤ | β |.

Грамматика G = (VT, VN, P, S) называется контекстно-зависимой (КЗ), если

каждое правило из P имеет вид: α → β, где α = ξ1Aξ2; β = ξ1γξ2; A VN;

γ (VTVN)+; ξ1,ξ2(VTVN)*.

Грамматику типа 1 можно определить как неукорачивающую либо как контекстно-зависимую. Выбор определения не влияет на множество языков, порождаемых грамматиками этого класса, поскольку доказано, что множество языков, порождаемых неукорачивающими грамматиками, совпадает с множеством языков, порождаемых КЗ-грамматиками.

ТИП 2: Грамматика G = (VT, VN, P, S) называется контекстно-свободной (КС), если каждое правило из Р имеет вид: A → β, где A VN, β(VTVN)+. Грамматика G = (VT, VN, P, S) называется укорачивающей контекстно-свободной (УКС), если каждое правило из Р имеет вид: A → β, где AVN, β(VTVN)*.

Грамматику типа 2 можно определить как контекстно-свободную либо

как укорачивающую контекстно-свободную. Возможность выбора обусловлена

тем, что для каждой УКС-грамматики существует почти эквивалентная

КС-грамматика.

ТИП 3: Грамматика G = (VT, VN, P, S) называется праволинейной, если каждое правило

из Р имеет вид: A → tB либо A → t, где A VN, BVN, tVT.

Грамматика G = (VT, VN, P, S) называется леволинейной, если каждое правило

из Р имеет вид: A → Bt либо A → t, где A VN, BVN, tVT.

Грамматику типа 3 (регулярную, Р-грамматику) можно определить как

праволинейную либо как леволинейную. Выбор определения не влияет на мно-

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

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

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

Соотношения между типами грамматик:

(1) любая регулярная грамматика является КС-грамматикой;

(2) любая регулярная грамматика является УКС-грамматикой;

(3) любая КС-грамматика является КЗ-грамматикой;

(4) любая КС-грамматика является неукорачивающей грамматикой;

(5) любая КЗ-грамматика является грамматикой типа 0;

(6) любая неукорачивающая грамматика является грамматикой типа 0.

Замечание: УКС-грамматика, содержащая правила вида A → λ, не являет-

ся КЗ-грамматикой и не является неукорачивающей грамматикой.

Общая схема работы транслятора

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

  1. 1. Сам транслятор является программой;

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

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

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

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

Общая схема компиляции

25. Методы синтаксического анализа при трансляции. Использование грамматик. Нисходящий и восходящий анализ. Перевод программы в промежуточную форму (обратная польская запись).

Пусть дана КС-грамматика G = (N, T, P, S). Рассмотрим предсказывающий разбор (или разбор сверху-вниз) для грамматики G.

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

Фрагменты недостроенного дерева соответствуют сентенциальным формам. Вначале дерево состоит только из одной вершины, соответствующей аксиоме S. В этот момент по первому символу входной цепочки предсказывающий анализатор должен определить правило S -> X1X2... , которое должно быть применено к S. Затем необходимо определить правило, которое должно быть применено к X1, и т.д., до тех пор, пока в процессе такого построения сентенциальной формы, соответствующей левому выводу, не будет применено правило Y -> a... . Этот процесс затем применяется для следующего самого левого нетерминального символа сентенциальной формы.

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

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

Входная лента содержит анализируемую строку, заканчивающуюся символом $ - маркером конца строки. Выходная лента содержит последовательность примененных правил вывода.

Таблица анализа - это двумерный массив M[A, a], где A - нетерминал, и a - терминал или символ $. Значением M[A, a] может быть некоторое правило грамматики или элемент «ошибка».

Магазин может содержать последовательность символов грамматики с $ на дне. В начальный момент магазин содержит только начальный символ грамматики на верхушке и $ на дне.

Анализатор работает следующим образом. Вначале анализатор находится в конфигурации, в которой магазин содержит S$, на входной ленте w$ (w - анализируемая цепочка), выходная лента пуста. На каждом такте анализатор рассматривает X - символ на верхушке магазина и a - текущий входной символ. Эти два символа определяют действия анализатора. Имеются следующие возможности.

1. Если X = a = $, анализатор останавливается, сообщает об успешном окончании разбора и выдает содержимое выходной ленты.

2. Если X = a<>$, анализатор удаляет X из магазина и продвигает указатель входа на следующий входной символ.

3. Если X - терминал, и X<>a, то анализатор останавливается и сообщает о том, что входная цепочка не принадлежит языку.

4. Если X - нетерминал, анализатор заглядывает в таблицу M[X, a]. Возможны два случая:

  1. Значением M[X, a] является правило для X. В этом случае анализатор заменяет X на верхушке магазина на правую часть данного правила, а само правило помещает на выходную ленту. Указатель входа не передвигается.

  2. Значением M[X, a] является «ошибка». В этом случае анализатор останавливается и сообщает о том, что входная цепочка не принадлежит языку.

Разбор снизу-вверх типа сдвиг-сверстка

В процессе разбора снизу-вверх типа сдвиг-свертка строится дерево разбора входной цепочки, начиная с листьев (снизу) к корню (вверх). Этот процесс можно рассматривать как «свертку» цепочки w к начальному символу грамматики. На каждом шаге свертки подцепочка, которую можно сопоставить правой части некоторого правила вывода, заменяется символом левой части этого правила вывода, и если на каждом шаге выбирается правильная подцепочка, то в обратном порядке прослеживается правосторонний вывод (рис. 4.7). Здесь ко входной цепочке, так же как и при анализе LL(1)-грамматик, приписан концевой маркер $.

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

Формально, основа правой сентенциальной формы z - это правило вывода A-> и позиция в z, в которой может быть найдена цепочкатакие, что в результате заменына A получается предыдущая сентенциальная форма в правостороннем выводе z. Таким образом, если

, то A -> в позиции, следующей за, это основа цепочки. Подцепочкасправа от основы содержит только терминальные символы.

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

Чтобы восстановить этот вывод в обратном порядке, выделяем основу n вn и заменяем n на левую часть некоторого правила вывода An ->n, получая (n - 1)-ю правую сентенциальную форму n-1. Затем повторяем этот процесс, т.е. выделяем основу n-1 в n-1 и сворачиваем эту основу, получая правую сентенциальную форму n-2. Если, повторяя этот процесс, мы получаем правую сентенциальную форму, состоящую только из начального символа S, то останавливаемся и сообщаем об успешном завершении разбора. Обращение последовательности правил, использованных в свертках, есть правый вывод входной строки.

Таким образом, главная задача анализатора типа сдвиг-свертка - это выделение и отсечение основы.

Чтобы дать индуктивное определение постфиксной нотации[1], обозначим выражения в инфиксной нотации E, E1, E2, эквивалентные им выражения в постфиксной нотации E‘ , E‘1, E‘2 соответственно; o - произвольный бинарный оператор, тогда:

1. Если E – переменная или константа, то E‘ есть E.

2. Если E - выражение вида E1 o E2, то E‘ есть E‘1E‘2o.

3. Если E- выражение вида (E1), то E‘ есть E‘1.

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

  • Запись набора операций состоит из последовательности операндов и знаков операций. Операнды в выражении при письменной записи разделяются пробелами.

  • Выражение читается слева направо. Когда в выражении встречается знак операции, выполняется соответствующая операция над двумя последними встретившимися перед ним операндами в порядке их записи. Результат операции заменяет в выражении последовательность её операндов и её знак, после чего выражение вычисляется дальше по тому же правилу.

  • Результатом вычисления выражения становится результат последней вычисленной операции.

Например, рассмотрим вычисление выражения 7 2 3 * - (эквивалентное выражение в инфиксной нотации: 7-2*3).

  1. Первый по порядку знак операции — «*», поэтому первой выполняется операция умножения над операндами 2 и 3 (они стоят последними перед знаком). Выражение при этом преобразуется к виду 7 6 - (результат умножения — 6, — заменяет тройку «2 3 *»).

  2. Второй знак операции — «-». Выполняется операция вычитания над операндами 7 и 6.

  3. Вычисление закончено. Результат последней операции равен 1, это и есть результат вычисления выражения.

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

Особенности обратной польской записи следующие:

  • Порядок выполнения операций однозначно задаётся порядком следования знаков операций в выражении, поэтому отпадает необходимость использования скобок и введения приоритетов и ассоциативности операций.

  • В отличие от инфиксной записи, невозможно использовать одни и те же знаки для записи унарных и бинарных операций. Так, в инфиксной записи выражение 5 * (-3 + 8) использует знак «минус» как символ унарной операции (изменение знака числа), а выражение (10 - 15) * 3 применяет этот же знак для обозначения бинарной операции (вычитание). Конкретная операция определяется тем, в какой позиции находится знак. Обратная польская запись не позволяет этого: запись 5 3 - 8 + * (условный аналог первого выражения) будет интерпретирована как ошибочная, поскольку невозможно определить, что «минус» после 5 и 3 обозначает не вычитание; в результате будет сделана попытка вычислить сначала 5 - 3, затем 2 + 8, после чего выяснится, что для операции умножения не хватает операндов. Чтобы всё же записать это выражение, придётся либо переформулировать его, либо ввести для операции изменения знака отдельное обозначение, например, «±»: 5 3 ± 8 + *.

  • Так же, как и в инфиксной нотации, в ОПН одно и то же вычисление может быть записано в нескольких разных вариантах. Например, выражение (10 - 15) * 3 в ОПН можно записать как 10 15 - 3 *, а можно — как 3 10 15 - *

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

Преобразование выражения в ОПЗ с использованием стека

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

Рассматриваем поочередно каждый символ:

1. Если этот символ - число (или переменная), то просто помещаем его в выходную строку.

2. Если символ - знак операции (+, -, *, / ), то проверяем приоритет данной операции. Операции умножения и деления имеют наивысший приоритет (допустим он равен 3). Операции сложения и вычитания имеют меньший приоритет (равен 2). Наименьший приоритет (равен 1) имеет открывающая скобка.

Получив один из этих символов, мы должны проверить стек:

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

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

3. Если текущий символ - открывающая скобка, то помещаем ее в стек.

4. Если текущий символ - закрывающая скобка, то извлекаем символы из стека в выходную строку до тех пор, пока не встретим в стеке открывающую скобку (т.е. символ с приоритетом, равным 1), которую следует просто уничтожить. Закрывающая скобка также уничтожается.

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