Популярные услуги

Все письменные КМ под ключ за 3 суток! (КМ-6 + КМ-7 + КМ-8 + КМ-9 + КМ-10)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
Любая задача на C/C++
Одно любое задание в mYsql
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Оба семинара по программированию под ключ! КМ-2. Разработка циклических алгоритмов + КМ-3. Функции и многофайловые программы в Си
Любой реферат по информатике
Главная » Лекции » Информатика и программирование » Компиляторы » Системы автоматизации построения трансляторов

Системы автоматизации построения трансляторов

2021-03-09СтудИзба

10. Лекция: Системы автоматизации построения трансляторов
В данной лекции рассматриваются системы автоматизации построения трансляторов на примере систем автоматизации построения трансляторов СУПЕР и YACC. Приведены структуры этих систем, основные термины и определения и части программного кода реализации систем автоматизации построения трансляторов.

Системы автоматизации построения трансляторов (САПТ) предназначены для автоматизации процесса разработки трансляторов. Очевидно, что для того, чтобы описать транслятор, необходимо иметь формализм для описания. Этот формализм затем реализуется в виде входного языка САПТ. Как правило, формализмы основаны на атрибутных грамматиках. Ниже описаны две САПТ, получившие распространение: СУПЕР [4] и Yacc. В основу первой системы положены LL(1)-грамматики и L-атрибутные вычислители, в основу второй - LALR(1)-грамматики и S-атрибутные вычислители.

Система СУПЕР

Программа на входном языке СУПЕР ("метапрограмма") состоит из следующих разделов:

  • Заголовок;
  • Раздел констант;
  • Раздел типов;
  • Алфавит;
  • Раздел файлов;
  • Раздел библиотеки;
  • Атрибутная схема.

Заголовок определяет имя атрибутной грамматики, первые три буквы имени задают расширение имени входного файла для реализуемого транслятора. Раздел констант содержит описание констант, раздел типов - описание типов.

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

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

Атрибутная схема состоит из списка синтаксических правил и сопоставленных им семантических правил. Для описания синтаксиса языка используется расширенная форма Бэкуса-Наура. Терминальные символы в правой части заключаются в кавычки, классы лексем и нетерминалы задаются их именами. Для задания в правой части необязательных символов используются скобки [ ], для задания повторяющихся конструкций используются скобки ( ). В этом случае может быть указан разделитель символов (после /). Например,

A ::= B [ C ] ( D ) ( E / ',' )

Рекомендуемые материалы

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

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

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

Суффикс A задает выполнение оператора перед каждым вхождением синтаксической конструкции, заключенной в скобки повторений ( ). Суффикс B задает выполнение оператора после каждого вхождения синтаксической конструкции, заключенной в скобки повторений ( ). Суффикс M задает выполнение оператора между вхождениями синтаксической конструкции, заключенной в скобки повторений ( ). Суффикс E задает выполнение оператора в том случае, когда конструкция, заключенная в скобки [ ], отсутствует.

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

D ::= 'd' =>

   $0.y:=$0.x+1.

A ::= B (C) [D] =>

   $2.x:=1;

2M: $2.x:=$2.x+1;

   $3.x:=$2.x;

3E: $3.y:=$3.x;

3: writeln($3.y).

Процедура writeln напечатает число вхождений символа C в C-список, если D опущено. В противном случае напечатанное число будет на единицу больше.

Система YACC

В основу системы YACC положен синтаксический анализатор типа LALR(1), генерируемый по входной (мета) программе. Эта программа состоит из трех частей:

%{

Си-текст

%}

%token Список имен лексем

%%

Список правил трансляции

%%

Служебные Си-подпрограммы

Си-текст (который вместе с окружающими скобками %{ и %} может отсутствовать) обычно содержит Си-объявления (включая #include и #define), используемые в тексте ниже. Этот Си-текст может содержать и объявления (или предобъявления) функций.

Список имен лексем содержит имена, которые преобразуются YACC-препроцессором в объявления констант (#define). Как правило, эти имена используются как имена классов лексем и служат для определения интерфейса с лексическим анализатором.

Каждое правило трансляции имеет вид

Левая"часть : альтернатива"1

   {семантические"действия"1}

 | альтернатива"2 {семантические"действия"2}

 |...

 | альтернатива"n {семантические"действия"n}

 ;

Каждое семантическое действие - это последовательность операторов Си. При этом каждому нетерминалу может быть сопоставлен один синтезируемый атрибут. На атрибут нетерминала левой части ссылка осуществляется посредством значка $$, на атрибуты символов правой части - посредством значков $1, $2 , . . . , $n, причем номер соответствует порядку элементов правой части, включая семантические действия. Каждое семантическое действие может вырабатывать значение в результате выполнения присваивания $$=Выражение. Исполнение текого оператора в последнем семантическом действии определяет значение атрибута символа левой части.

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

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

Например, объявление

%left '+' '-'

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

%right '^'

Бинарную операцию можно определить как неассоциативную (то есть не допускающую появления объединения двух подряд идущих знаков операции):

%nonassoc '<'

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

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

%prec терминал

Старшинство и ассоциативность правила в этом случае будут такими же, как у указанного терминала.

YACC не сообщает о конфликтах, разрешаемых с помощью ассоциативности и приоритетов. Восстановление после ошибок управляется пользователем с помощью введения в грамматику "правил ошибки" вида

A  error w:

Здесь error - ключевое слово YACC. Когда встречается синтаксическая ошибка, анализатор трактует состояние, набор ситуаций для которого содержит правило для error, некоторым специальным образом: символы из стека выталкиваются до тех пор, пока на верхушке стека не будет обнаружено состояние, для которого набор ситуаций содержит ситуацию вида [A :error w]. После чего в стек фиктивно помещается символ error, как если бы он встретился во входной строке.

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

Если w не пусто, просматривается входная строка и делается попытка свернуть w. Если w состоит только из терминалов, эта строка ищется во входном потоке.

Дополнительные материалы: Семантика контекстно-свободных языков

Введение

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

B  0 B  1

L  B L  LB

N  L N  L.L

Пример 1.1.

Здесь терминальными символами являются ".", "0" и "1", нетерминальными - B, L и N, обозначающие соответственно бит, список битов и число. Двоичным числом будем считать любую цепочку терминальных символов, выводимую из N при помощи правил (пример 1.1). Эта грамматика в действительности выражает тот факт, что двоичное число представляет собой последовательность из одного или более нулей и единиц, за которой может следовать точка и еще одна последовательность нулей и единиц. Кроме того, грамматика приписывает каждому двоичному числу определенную древовидную структуру. Например, цепочка 1101.01 получает следующую структуру:

1_2


Рис. 1.2.

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

forallбит B имеет целочисленный атрибут "значение", обозначаемый v(B).

forallсписок битов L имеет целочисленный атрибут "длина", обозначаемый l(L). forallсписок битов L имеет целочисленный атрибут "значение", обозначаемый v(L).

forallчисло N имеет атрибут "значение", являющийся рациональным числом и обозначаемый v(N).

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

Грамматику (пример 1.1) можно теперь расширить так, чтобы каждому синтаксическому правилу отвечали семантические правила.

B  0         v(B) = 0

B  1         v(B) = 1 

L  B         v(L) = v(B); l(L) = 1

L1  L2B      v(L1) = 2v(L2) + v(B); l(L1) = l(L2) + 1

N  L         v(N) = v(L)

N  L1:L2     v(N) = v(L1) + v(L2)=2l(L2)

Пример 1.3.

(Индексы в четвертом и шестом правилах применяются для того, чтобы различать вхождения одноименных нетерминалов.). В этих семантических правилах значения атрибутов всех нетерминалов определяются через значения атрибутов их непосредственных потомков, так что окончательные значения определены для всех атрибутов. Предполагается, что смысл обозначений, использованных для записи семантических правил, понятен. Отметим, например, что символ "0" в семантическом правиле v(B) = 0 понимается не так, как символ "0" в синтаксическом правиле B 0. B первом случае "0" обозначает математическое понятие, а именно число нуль, во втором - некоторый символ, имеющий эллиптическую форму. B каком-то смысле, то, что эти два символа выглядят одинаково, - не более чем простое совпадение.

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

1_4


Рис. 1.4.

Таким образом, "1101.01" обозначает 13.25 (в десятичной записи).

Такой способ определения семантики КС-языков в сущности хорошо известен, так как он уже использовался несколькими авторами. Однако существует важное расширение этого метода. Именно это расширение и представляет для нас интерес.

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

forallсимвол B имеет атрибут "значение", являющийся рациональным числом и обозначаемый v(B).

forallсимвол B имеет целочисленный атрибут "масштаб ", обозначаемый s(B).

forallсимвол L имеет атрибут "значение", являющийся рациональным числом и обозначаемый v(L).

forallсимвол L имеет целочисленный атрибут "длина", обозначаемый l(L).

forallсимвол L имеет целочисленный атрибут "масштаб", обозначаемый s(L).

forallсимвол N имеет атрибут "значение", принимающий в качестве значений рациональные числа и обозначаемый v(N).

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

Таблица 1.1.

Синтаксические правила

Семантические правила

B 0

v(B) = 0

B 1

v(B) = 2s(B)

L B

v(L) = v(B), s(B) = s(L), l(L) = 1

L1 L2B

v(L1) = v(L2) + v(B), s(B) = s(L1), s(L2) = s(L1) + 1, l(L1) = l(L2) + 1

N L

v(N) = v(L); s(L) = 0

N L1.L2

v(N) = v(L1) + v(L2), s(L1) = 0, s(L2) = -l(L2)

Здесь при записи семантических правил принято следующее соглашение. Правая часть каждого правила представляет собой определение левой части, таким образом, s(B) = s(L) означает, что сначала должно быть вычислено s(L), а затем полученное значение следует присвоить s(B).

Важным свойством грамматики (таблица 1.1) является то, что некоторые из атрибутов, которым присваиваются значения, приписаны нетерминалам, стоящим в правой части соответствующего синтаксического правила, в то время как в 1.3 атрибуты левых частей семантических правил относились только к нетерминалам, стоящим в левой части синтаксического правила. Здесь мы используем как синтезированные атрибуты (вычисляемые через атрибуты потомков данного нетерминала), так и унаследованные атрибуты (вычисляемые через атрибуты предков). Синтезированные атрибуты вычисляются в древовидной структуре снизу вверх, а унаследованные - сверху вниз. Грамматика (таблица 1.1) включает синтезированные атрибуты v(B), v(L), l(L), v(N) и унаследованные атрибуты s(B) и s(L), так что при их вычислении необходимо проходить по дереву в обоих направлениях. Вычисление на структуре, соответствующей цепочке 1101.01, имеет вид:

1_6


Рис. 1.6.

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

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

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

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

Формальные свойства

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

Пусть имеется КС-грамматика G = (V, N, S, P), где V - (конечный) алфавит терминальных и нетерминальных символов; N subseteq V- множество нетерминальных символов; S N - "начальный "символ, не входящий в правые части правил, и P - множество правил.

Семантические правила дополняют G следующим образом. С каждым символом X V связывается конечное множество атрибутов A(X). A(X) разбивается на два непересекающихся множества: множество синтезированных атрибутов A0(X) и множество унаследованных атрибутов A1(X). Множество A1(S) должно быть пустым (то есть начальный символ S не должен иметь унаследованных атрибутов); аналогично, множество A0(X) пусто, если X - терминальный символ. Каждый атрибут R из множества A(X) имеет (возможно, бесконечное) множество значений VR. Для каждого вхождения X в дерево вывода семантические правила позволяют определить одно значение из множества VR для соответствующего атрибута.

Пусть P состоит из m правил, и пусть p-е правило имеет вид

Xp0  Xp1Xp2...Xpnp ;

Пример 2.1.

где np > 0, Xp0 N и Xpj V для 1 j np. Семантическими правилами называются функции fpjR, определ_нные для всех 1 p m, 0 j np и некоторых A0(Xpj), если j = 0, или A1(Xpj), если j > 0. Каждая такая функция представляет собой отображение из V1 x V2 x ... x Vt в VR для некоторого t = t(p, j, ) > 0, где все i = i(p, j, ) являются атрибутами некоторых Xpki , при 0 ki = ki(p, j, ) np, 1 i t. Другими словами, каждое семантическое правило отображает значения некоторых атрибутов символов X_{p0}, X_{p1}, ldots X_{p_{n_p}}и значение некоторого атрибута символа X_{p_j}.

Грамматика (таблица 1.1), например, представляется в виде G = ({0, 1, ".", B, L, N}, {B, L, N}, N, {B 0, B 1, L B, L LB, N L, N L.L}).

Атрибутами здесь являются

A0(B) = {v},    A1(B) = {s};

A0(L) = {v, l},        A1(L) = {s};

A0(N) = {v},    A1(N) =

и A0(x) = A1(x) =

для x {0, 1, .}. Множествами значений атрибутов будут Vv ={рациональные числа}, Vs = Vl = {целые числа}. Типичным примером правил вывода служит четвeртое правило X40 X41X42 , где n4 = 2, X40 = X41 = L, X42 = B. Так же типично и семантическое правило f40v, соответствующее этому правилу вывода. Оно определяет v(X40) через другие атрибуты; в данном случае f40v отображает Vv x Vv в Vv согласно формуле f40v(x, y) = x + y. (Это есть не что иное, как правило v(L1) = v(L2) + v(B) из (таблица 1.1); используя довольно громоздкую запись, введeнную в предыдущем абзаце, получим:

t(4, 0, v) = 2, 1(4, 0, v) = 2(4, 0, v) = v, k1(4, 0, v) = 1, k2(4, 0, v) = 2).

Семантические правила используются для сопоставления цепочкам КС языка"значения" следующим образом1) . Для любого вывода терминальной цепочки t из S при помощи синтаксических правил построим обычное дерево вывода. А именно, корнем дерева будет S, а каждый узел помечается либо терминальным символом, либо нетерминалом Xp0, соответствующим применению p-го правила для некоторого p; в последнем случае у этого узла будет np непосредственных потомков.

2_2


Рис. 2.2.

Пусть теперь X - метка некоторого узла дерева и пусть R A(X) - атрибут символа X. Если A0(X), то X = Xp0 для некоторого p, если же A1(X), то X = Xpj для некоторых j и p. В обоих случаях дерево "в районе" этого узла имеет вид (рис. 2.2). По определению атрибут имеет в этом узле значение v, если в соответствующем семантическом правиле

fpj:V1x ... x VtV

все атрибуты 1, ... , t уже определены и имеют в узлах с метками Xpk1 , ... , Xpkt значения v1, ... , vt соответственно, а v = fpj(v1, ... , vt). Процесс вычисления атрибутов на дереве продолжается до тех пор, пока нельзя будет вычислить больше ни одного атрибута. Вычисленные в результате атрибуты корня дерева представляют собой "значение", соответствующее данному дереву вывода (рис. 1.6).

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

Отметим, что этот метод определения семантики обладает такой же мощностью, как и всякий другой возможный метод, в том смысле, что значение любого атрибута в любом узле может произвольным образом зависеть от структуры всего дерева. Предположим, например, что в КС грамматике всем символам, кроме S, приписано по два унаследованных атрибута: l ("положение") и t ("дерево"), а всем нетерминалам, кроме того, по одному синтезированному атрибуту s ("поддерево"). Значениями l будут конечные последовательности положительных целых чисел {a_1 cdot a_2 cdot ldots cdot a_k}, определяющие местонахождение узла в дереве в соответствии с системой обозначения Дьюи. Атрибуты t и s представляют собой множество упорядоченных пар (l, X), где l - положение узла, а X - символ грамматики, обозначающий метку узла с положением l. Семантическими правилами для каждого синтаксического правила (пример 2.1) служат

l(X_{pj})=&#10;left{&#10;begin{aligned}&#10;&amp;l(X_{p0})cdot j , &amp; text{ если } &amp; X_{p0}neq S;\&#10;&amp;j, &amp; text{ если } &amp; X_{p0}= S;\&#10;end{aligned}&#10;right. &#10;\l(X_{pj})=&#10;left{&#10;begin{aligned}&#10;&amp;t(X_{p0}), &amp; text{ если } &amp; X_{p0}neq S;\&#10;&amp;s(X_{p0}), &amp; text{ если } &amp; X_{p0}= S;\&#10;end{aligned}&#10;right.

(2.4)

s(X_{p0})={(l(X_{p0}),X_{p0}) mid X_{p0} neq S } cup bigcuplimits_{j=1}^{n_{p}}} {S(X_{pj}) mid X_{pj} in N }

Следовательно, для дерева (рис. 1.2), например, мы имеем

s(N) = {(1, L), (2, ⋅), (3, L),

   (1.1, L), (1.2, B), (3.1, L), (3.2, B),

   (1.1.1, L), (1.1.2, B), (1.2.1, 1), (3.1.1, B), (3.2.1, 1),

   (1.1.1.1, L), (1.1.1.2, B), (1.1.2.1, 0), (3.1.1.1, 0),

   (1.1.1.1.1, B), (1.1.1.2.1, 1), (1.1.1.1.2.1, 1)}.

Ясно, что эта запись содержит всю информацию о дереве вывода. Согласно семантическим правилам (2.4), атрибут t во всех узлах (кроме корня) представляет собой множество, характеризующее всe дерево вывода; атрибут l определяет местонахождение этих узлов. Отсюда сразу следует, что любая мыслимая функция, определ_нная на дереве вывода, может быть представлена как атрибут произвольного узла, поскольку эта функция имеет вид f(t, l), для некоторого f. Аналогично, можно показать, что для определения значения, связанного с произвольным деревом вывода, достаточно только синтезированных атрибутов, поскольку синтезированный атрибут w, вычисляемый по формуле

w(X_{p0})={(0, ; X_{p0})cup bigcuplimits_{j=1}^{n_{p}}{(jcdot alpha, X) mid \&#10;(alpha,X)in w (X_{pj}), ; X_{pj}in N}

(2.5)

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

Проверка на зацикленность

Рассмотрим теперь алгоритм, проверяющий, является ли корректной система семантических правил, определeнная в предыдущем разделе. Другими словами, мы хотим знать, когда семантические правила позволяют вычислить значение любого атрибута любого узла произвольного дерева вывода. Можно считать, что грамматика не содержит "бесполезных" правил вывода, то есть что каждое правило из P участвует в выводе хотя бы одной терминальной цепочки.

Пусть T - произвольное дерево вывода, соответствующее данной грамматике; метками концевых узлов могут быть только терминальные символы, корню же разрешим иметь меткой не только аксиому, но и любой символ из V . Тогда можно определить ориентированный граф D(T), соответствующий T, взяв в качестве его узлов упорядоченные пары (X, ), где X - узел дерева T, а - атрибут символа, служащего меткой узла X. Дуга из (X1; 1) в (X2, 2) проводится в том и только в том случае, когда семантическое правило, вычисляющее атрибут 2, непосредственно использует значение атрибута 1. Например, если T - дерево (рис. 1.2), а в качестве семантических правил взяты правила (таблица 1.1), то орграф D(T) будет иметь такой вид:

3_1


Рис. 3.1.

Другими словами, узлами графа D(T) служат атрибуты, значения которых нужно вычислить, а дуги определяют зависимости, подразумевающие, какие атрибуты вычисляются раньше, а какие позже (рис. 1.6).

Ясно, что семантические правила являются корректными тогда и только тогда, когда ни один из орграфов D(T) не содержит ориентированного цикла. Дело в том, что если в графе нет ориентированных циклов, то можно применить хорошо известную процедуру, позволяющую присвоить значения всем атрибутам. Если же в некотором графе D(T) есть ориентированный цикл, то ввиду того что грамматика не содержит бесполезных правил, можно утверждать, что существует ориентированный цикл в некотором графе D(T), у которого меткой корня дерева T служит S. Для такого дерева T все атрибуты вычислить не уда_тся. Таким образом, задача "Являются ли семантические правила корректным?" сводится к задаче "Содержат ли орграфы D(T) ориентированные циклы?"

Каждый орграф D(T) можно рассматривать как суперпозицию меньших орграфов Dp, соответствующих правилам Xp0 Xp1 ... Xpn грамматики, 1 p m. В обозначениях разд. 2 орграф Dp имеет узлы (Xpj, ) для 0 j np, A(Xpj) и дуги из (Xpki , ) в (Xpj , ) для 0 j np, A0(Xpj), если j = 0, A1(Xpj ), если j > 0, ki = ki(p, j, ), i = i(p, j, ), 1 i t(p, j, ). Другими словами, Dp отражает связи, которые порождают все семантические правила, соответствующие p-му синтаксическому правилу. Например, шести правилам грамматики (таблица 1.1) соответствуют шесть следующих орграфов:

3_2


Рис. 3.2.

Орграф (рис. 3.1) получается в результате "объединения" таких подграфов. Вообще, если T имеет в качестве метки корня терминал, D(T) не содержит дуг. Если корень дерева T помечен нетерминальным символом, T имеет вид

3_3


Рис. 3.3.

для некоторого p, где Tj - дерево вывода, у которого корень помечен символом Xpj, где 1 j np. В первом случае говорят, что T - дерево вывода типа 0, во втором случае T называется деревом вывода типа р. В соответствии с этим определением для того, чтобы по Dp, D(T1), ... , D(Tnp ) построить D(T), нужно для всех j, 1 j np совместить узлы, соответствующие атрибутам символа Xpj графа Dp с соответствующими узлами (отвечающими тем же атрибутам корня дерева Tj) в графе D(Tj ).

Для проверки того, содержит ли граф D(T) ориентированный цикл, нам понадобится ещe одно понятие. Пусть p - номер правила вывода. Обозначим через Gj произвольный орграф (1 j np), множество узлов которого является подмножеством множества A(Xpj) атрибутов символа Xpj. Пусть

Dp[G1,..., Gnp ]

Пример 3.4.

орграф, полученный из Dp добавлением дуг, идущих из (Xpj, ) в (Xpj, 0), если в графе Gj есть дуга из в 0

Например, если

3_31

и если D4 - ориентированный граф из (3.2), то D4(G1, G2) имеет вид

3_4


Рис. 3.4.

Теперь можно воспользоваться следующим алгоритмом. Для любого X V S(X) будет некоторым множеством ориентированных графов с узлами из A(X). Сначала для всех X N S(X) пусто, а для X = N S(X) состоит из единственного графа с множеством узлов A(X) и не содержащего дуг. Будем добавлять к множествам S(X) новые орграфы при помощи следующей процедуры до тех пор, пока в S(X) не перестанут появляться новые элементы. Выберем целое p; 1 p m и для каждого j, 1 j np, выберем орграф D0j S(Xpj). Затем добавим в S(Xp0) орграф с множеством узлов A(Xpo), обладающий тем свойством, что в нeм дуга от к 0 идeт тогда и только тогда, когда в орграфе

D_p[D'_1, ldots , D'_{n_p}]

(3.5)

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

3_6

Пусть T - дерево вывода с корнем X, и пусть D'(T) - ориентированный граф с множеством узлов A(X), у которого есть дуга из в ' тогда и только тогда, когда в D(T) существует ориентированный путь из (X, ) в (X, '). Покажем, что после окончания работы описанного выше алгоритма для всех X V S(X) - это множество всех D'(T), где T - дерево вывода с корнем X 4) . Действительно, построение не добавляет к S(X) новых ориентированных графов, не являющихся D'(T). Алгоритм можно даже легко обобщить так, чтобы для каждого графа из S(X) он печатал на выходе соответствующее дерево вывода T. Обратно, если T - дерево вывода, мы можем показать индукцией по числу узлов дерева T, что D'(T) принадлежит некоторому множеству S(X). В противном случае T должно иметь вид (3.3) и D(T) "составлен" из D_pD(T_1), ldots, D(T_{n_p} ). По индукции и вследствие того, что при j j' из D(Tj) в D(Tj' ) не проходит дуг вне Dp, дуги в D_pD(T_1), ldots, D(T_{n_p} ), составляющей рассматриваемый путь графа D(T), можно заменить соответствующими дугами в D_p[D'1, ldots D'_{n_p} ], где D'_j in S(X_{pj}), 1 leq j leq n_p. Поэтому ориентированный граф, включаемый в S(Xp0) на базе D_p[D'1, ldots , D'_{n_p} ], просто совпадает с D'(T).

Вышеприведeнный алгоритм решает задачу, поставленную в этом разделе.

Теорема. Семантические правила, добавленные к грамматике так, как это сделано в разд. 2, являются корректными тогда и только тогда, когда ни один из ориентированных графов (3.5) ни при каком выборе p и D'_1 in S(X_{p1}), ldots , D'_{n_p} in S(X_{n_p} )не содержит ориентированных циклов.

Доказательство. Если (3.5) содержит ориентированный цикл, то, как было показано выше, некоторый D(T) содержит ориентированный цикл. Наоборот, если T - дерево с наименьшим возможным числом улов, такое, что D(T) содержит ориентированный цикл, то T должно иметь вид (рис. 3.3), а D(T) "составляется" из D_p; D(T_1), ldots , D(T_{n_p} ). Из минимальности T следует, что ориентированный цикл включает по меньшей мере одну дугу графа Dp, и, следовательно, можно, рассуждая, как выше, все дуги, образующие этот цикл и лежащие в одном из графов D(T_1) , ldots , D(T_{n_p} ), заменить дугами графа (3.5).

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

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

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

Алфавит пробел, единица, нуль, точка;

печатать "точка"; перейти на выполнить;

тест: если символ на ленте _единицаї, то

{печатать "нуль";                                                    (4.1)

  выполнить: сдвинуться влево на одну клетку;

  перейти на тест};

печатать "единица";

возврат: сдвинуться вправо на одну клетку;

если символ на ленте "нуль", то перейти на возврат.

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

Поскольку каждый язык программирования нужно как- то называть, назовeм наш язык Тьюринголом. Всякая правильная программа на Тьюринголе определяет программу для машины Тьюринга; будем говорить, что программа для машины Тьюринга состоит из:

  • множества "состояний" Q,
  • множества "символов" Σ
  • начального "состояния" q0 Q
  • конечного "состояния" q1 Q
  • и "функции переходов" , отображающей множество

(Q-{q})xΣ в Σ{-1, 0, +1}xQ. Если (q, s) = (s', k', q'), то это означает, что если машина находится в состоянии q и обозревает символ s, то она печатает символ s', сдвигает читающее устройство на k клеток вправо (сдвигу на одну клетку влево соответствует случай k = 1) и переходит в состояние q'. Формально программа машины Тьюринга определяет вычисление для ленты с "любым начальным содержимым", то есть для любой бесконечной в обе стороны последовательности

...,a-3, a-2, a-1, a0, a1, a2, a3,...

Пример 4.2.

элементов алфавита Σ следующим образом. В произвольный момент вычисления существует "текущее состояние" q Q и целочисленная величина "положение читающего устройства" p. Вначале q = q0 и p = 0. Если q q и если (q, ap) = (s', k', q'), то следующим шагом вычисления будет замена значения p на p + k, q на q' и ap на s'. Если q = q, вычисление заканчивается. (Вычисление может не закончиться; для программы (4.1) это произойдeт тогда и только тогда, когда aj="единица" для всех j < 0.)

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

(1) Семантическое правило " включить х в B ", связанное с синтаксическим правилом, означает, что x должен стать элементом множества B, где B - атрибут аксиомы S грамматики. Значением B будет множество всех x, для которых существует такое семантическое правило, связанное с каждым применением соответствующего синтаксического правила в дереве вывода. (Это правило можно рассматривать как сокращeнную запись семантического правила

B(&amp;X_{p0})=bigcup limits_{j=1}^{n_p} B(X_{pj}) cup qquad qquad qquadqquad qquad qquadlet leqno relax (4.3) \&#10; &amp;cup ; {x mid text{&quot;textbf{включить} ; $x$ в $B$ &quot;связано с $p$ -м правилом} },

связанного с каждым синтаксическим правилом. Здесь B - синтезированный атрибут, имеющийся у всех нетерминальных символов. Для терминальных символов B(x) пусто. Ясно, что эти правила позволяют получить нужное B(S).)

(2) Семантическое правило "определить f(x) = y", связанное с синтаксическим правилом, означает, что значение функции f в точке x будет равно y; здесь f - атрибут аксиомы S грамматики. Если встречается два правила, задающих значение f(x) для одного и того же значения x, то возникает ситуация ошибки, а дерево вывода, в котором она возникла, называется неправильным. Далее, к функции f можно обращаться в других семантических правилах при условии, что f(x) будет использоваться только тогда, когда значение функции для аргумента x определено. Любое дерево вывода, для которого встретилось обращение к неопределeнной величине f(x), называется неправильным. (Правила такого типа важны, например, тогда, когда нужно обеспечить соответствие между описанием и использованием идентификаторов. В приведeнном ниже примере в соответствии с этим соглашением неправильными будут программы, в которых один и тот же идентификатор дважды встречается в качестве метки, или в операторе перехода используется идентификатор, не являющийся меткой оператора. В сущности, это правило можно представлять себе, аналогично (1), как " включить (x, y) в f", если рассматривать f как множество упорядоченных пар; необходимо соответственно ввести дополнительные проверки на зацикленность. Признак "правильно или неправильно" можно считать атрибутом аксиомы S. Построение соответствующих семантических правил, аналогичных (4.3), которые аккуратно определяют запись "определить f(x) = y", несложно и предоставляется читателю.)

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

Мы видели, что соглашения (1), (2) и (3) можно заменить другими семантическими конструкциями, не использующими таких соглашений, следовательно, они не являются "базисными" для семантики. Но они чрезвычайно полезны, так как соответствуют понятиям, которыми часто пользуются, поэтому их можно считать принципиальными для метода описания семантики, представленного в настоящей статье. Эффект от введения таких соглашений состоит в том, что уменьшается общее количество атрибутов, явно присутствующих в правилах, и в том, что удаeтся обойтись без неоправданно длинных правил. Теперь уже несложно дать формальное определение синтаксиса и семантики Тьюрингола.

Нетерминальные символы:

P (программа), S (оператор), L (список операторов), I (идентификатор), O (направление), A (символ алфавита), D (описание).

Терминальные символы:

a b c d e f g h i j k l m n o p q r s t u v w x y z . , : ; ' ' { }

алфавит перейти на печатать если символ на ленте то сдвинуться на одну клетку влево вправо

Начальный символ: p

Атрибуты:

Имя атрибута

Тип значения

Цель введения

Q

Множество

Состояния программы

Σ

Множество

Символы программы

q0

Элемент множества Q

Начальное состояние

q

Элемент множества Q

Конечное состояние

Функция, отображающая (Q - {q}) x Σ в Σ x {-1, 0, +1} x Q

Функция переходов

метка

Функция, отображающая цепочки букв в элементы мн-ва Q

Таблица состояний для операторных меток

символ

Функция, отображающая цепочки букв в элементы множества Σ

Таблица символов для символов ленты

следующее

Элемент множества Q

Состояние,непосредственно следующее за оператором или списком операторов

d

±1

Направление

текст

Цепочка букв

Идентификатор

начало

Элемент множества Q

Состояние в начале выполнения оператора или списка операторов (унаследованный атрибут)

Синтаксические и семантические правила см. в таблица 1.

Таблица 1.

Комментарий

Синтаксическое правило

Пример

Семантическое правило

Буквы

1.1 ... 1.26

A a ... A z

a ...(так же z

текст (A) = a для всех букв) текст (A) = z

Идентификаторы

2.1 2.2

I A I IA

m marilyn

текст (I) = текст (A) текст (I) = текст(I) текст

Описания

3.1

Dалфавит I

алфавит marilyn

определить символ (текст (I)) = новсимвол; включить символ (текст (I)) в Σ

Описания

3.2

D D, I

алфавит marilyn, jayne, brigitta

определить символ (текст (I)) = новсимвол; включить символ (текст (I)) в Σ

Оператор печати

4.1

Sпеча- тать I

печатать "jayne"

определить (начало (S), s)= символ(текст (I)), 0, следующий (S)) для всех s Σ; следующий (S) = новсимвол; включить следующий (S) в Q.

Оператор сдвига

4.2

S сдви- нуться О на одну клетку

сдвинуться влево на одну клетку

определить (начало (S), s) = (s, d(0), следующий (S) для всех s Σ; следующий (S) = новсимвол; включить следующий (S) в Q.

4.2.1

Овлево

влево

d(O) = -1.

4.2.2

О вправо

вправо

d(O) = +1.

Оператор перехода

4.3

S перейти на I

перейти на boston

определить (начало (S), s)= (s, 0, метка(текст (I)) для всех s Σ; следующий (S) = новсимвол; включить следующий (S) в Q.

Пустой оператор

4.4

S

следующий (S) = начало (S)

Условный оператор

5.1

S1 если символ на ленте I, то S2

еслисимвол на ленте marilyn , то печатать jayne

определить (начало (S1), s)= (s, 0, метка (следующий (S2)) для всех s Σ - символ (текст (I)); определить (начало (S1), s)= (s, 0, метка (следующий (S2)) для s = символ (текст (I)); начало (S2) = новсимвол; следующий (S1) = следующий (S2); включить начало (S2) в Q.

Помеченный оператор

5.2

S1 "I": S2

boston: сдвинуться влево на одну клетку

определить метка (текст (I))= начало (S1): начало (S2)=начало (S1); следующий (S1)= следующий (S2)

Составной оператор

5.3

S {L}

(печатать "jayne" перейти на boston

начало (L)=начало (S); следующий (S) = следующий (L).

Список опера- торов

6.1

L S

печатать "jayne"

начало (L)=начало (S); следующий (L) = следующий (S).

6.2

L1L2; S

печатать "jayne" перейти на boston

начало (L2)=начало (L1); начало (S) = следующий (L2). следующий (L1) = следующий (S).

Программа

7

Р D; L

алфавит marilyn, jayne, birgitta печатать "jayne"

q=новсимвол; включить q0 в Q; начало (L)=q0; q= следующий (L).

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

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

Наш пример мог бы быть проще, если бы мы использовали менее традиционную форму инструкций машины Тьюринга. Принятое нами определение требует, чтобы каждая инструкция включала действия чтения, печати и сдвига читающего устройства. Машина Тьюринга представляется при этом в виде некоей одно-плюс-одно- адресной вычислительной машины, в которой каждая инструкция определяет местоположение (состояние) следующей инструкции. Метод определения семантических правил, использованный в этом примере, где атрибут следующее (S) является синтезированным, а начало (S) - унаследованным, годится и для вычислительной машины или автомата, в котором n + 1-я инструкция выполняется после n-й. В этом случае (следующее (S) - начало (S)) есть число инструкций, "скомпилированных" для оператора S.

Создаeтся впечатление, что такое определение Тьюрингола приближает нас к желанной цели: придать точный смысл тем понятиям, которые встречаются в неформальном руководстве по языку для программиста, причeм сделать это нужно совершенно формально и однозначно. Другими словами, это определение, возможно, отвечает нашему образу мышления при изучении языка. Определение 4.1 оператора печати, например, можно легко перевести на естественный язык, написав

Оператор может иметь вид: печатать "I"

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

Обсуждение

Идея определения семантики с помощью синтезированных атрибутов, связанных с каждым нетерминальным символом и семантических правил, сопоставленных каждому правилу вывода, принадлежит Айронсу [6, 7]. Первоначально каждый нетерминальный символ имел ровно один атрибут, называвшийся его "трансляцией" . Эта идея использовалась Айронсом и позже другими авторами, особенно Маклюром [14] при построении "синтаксически управляемых компиляторов" , переводивших языки программирования в машинный код.

Как мы видели в разд. 2, синтезированных атрибутов достаточно (в принципе) для определения любой функции на дереве вывода. Но на практике применение наряду с синтезированными и унаследованных атрибутов, как описано в данной статье, приводит к значительным упрощениям. Определение Тьюрингола, например, показывает, что легко учитывается согласованность описаний и использований символов, а также между метками и операторами. Другой общей особенностью языков программирования, определение которой значительно упрощается в результате применения унаследованных атрибутов, является "блочная структура" . Вообще говоря, унаследованные атрибуты полезны всякий раз, когда часть значений некоторой конструкции определяется контекстом, в котором находится эта конструкция. Метод, приведeнный а разд. 2, показывает, как можно формально описывать унаследованные и синтезированные атрибуты, а в разд. 3 показано, что можно не принимать во внимание проблему зацикленности (являющуюся потенциальным источником трудностей при использовании атрибутов разных типов).

Автору к настоящему времени известно несколько работ, внесших принципиальный вклад в решение задачи формального описания семантики языков программирования. Это определение Алгола 60 средствами расширенного алгоритма Маркова, данное Дебаккером [1], определение Алгола 60 с помощью λ-исчисления, принадлежащее Ландину [9, 10, 11] (см. также Бeм [2, 3], определение Микро-Алгола с помощью рекурсивных функций, применяемых к программе и к _векторам состоянийї, принадлежащее Маккарти [12] (см. также Маккарти и Пэинтер [13]; определение языка Эйлер средствами семантических правил, применяемых во время синтаксического анализа программы, предложенное Виртом и Вебером [16], и определение языка PL=I, данное Венской лабораторией фирмы IBM и основанное на работе Маккарти и Ландина, а также на понятии абстрактной машины, введ_нном Элготом [4, 5]. Наиболее существенная разница между предшествующими методами и описанием языка Тьюрингол, привед_нным в таблица 1, состоит в том, что остальные определения представляют собой довольно сложные процессы, применяемые ко всей программе; можно сказать, что человек, прежде чем он поймeт описание языка, должен будет понять, как устроен его компилятор. Эта трудность особенно ощутима в работе Дебаккера, определяющего машину, подобную Марковским алгоритмам, но значительно более сложную. Эта машина имеет около 800 команд. На каждом шаге вычисления машины нужно выполнять последнюю применимую команду, так что мы не можем проверить, нужно ли выполнить команду номер 100, до тех пор, пока не убедимся, что остальные 700 команд неприменимы. Кроме того, в процессе работы машины список пополняется новыми командами. Ясно, что читателю чрезвычайно трудно понять работу такой машины или формально доказать ee основные свойства. Описание Тьюрингола, напротив, определяет каждую конструкцию языка только через еe "непосредственное окружение" , сводя тем самым к минимуму взаимосвязи между определениями разных частей языка. Определение составных операторов, операторов перехода и т.д. не влияет существенно на определение оператора печати; например, любое из правил 4.1, 4.2, 4.3, 4.4, 5.1, 5.3 можно выбросить, и получится строгое определение другого языка. Такая локализация и разделение семантических

правил помогает сделать определение более понятным и кратким. Хотя определения остальных авторов, упомянутые выше, не так сложны, как определение Дебаккера, в их работах всe-таки присутствуют относительно сложные зависимости между отдельными частями определения. Рассмотрим, например, формальное определение языка Эйлер, данное Виртом и Вебером [16]. Это краткое описание весьма сложного языка и потому, безусловно, оно является одним из наиболее удачных формальных определений. И всe же, несмотря на то, что Вирт и Вебер проверили своe определение с помощью моделирования на вычислительной машине, весьма вероятно, что некоторые черты Эйлера удивят его создателей. Следующая программа на Эйлере синтаксически и семантически правильна, хотя после метки L нигде не встречаются двоеточия:

begin{align*}&#10;&amp;perp begin ; label ; L; ; new ; A; ; A leftarrow 0;\&#10;&amp;if ; false ; then ; goto ; L ; else ;L;\&#10;&amp;out ;1; ;L; ;A leftarrow A + 1; ; out ; 2;\&#10;&amp;if ; false ; then ; go ; to ; L ; else\&#10;&amp;if ; A &lt; 2; then; go ;to ;L ;else ;out; 3; ;L ;end perp&#10;end{align*}

Результатом работы этой программы будет 1, 2, 2, 3! Промахи такого рода не являются неожиданностью при алгоритмическом определении языка. При использовании методов разд. 4 подобные ошибки менее вероятны.

Есть основания утверждать, что ни одна из предыдущих схем (формального определения семантики) не в состоянии дать такого же краткого и простого для понимания определения Тьюрингола, как то, которое представлено выше. Кроме того (хотя детали окончательно не проработаны), оказывается, что Алгол 60, Эйлер, Микро- Алгол и PL=1 также можно определить методами разд. 4, причeм все преимущества по сравнению с остальными методами сохраняются. Правда, здесь автор не может быть беспристрастным судьeй, поэтому для подтверждения такой точки зрения требуется некоторый дополнительный опыт.

Отметим, что семантические правила в том виде, в котором они даны в настоящей статье, не зависят от конкретно выбранного метода синтаксического анализа. На самом деле они привязаны даже к конкретным формам синтаксиса. Единственное от чего зависят семантические правила - это имя нетерминала в левой части синтаксического правила и имени нетерминалов в правой его части. Конкретные знаки пунктуации и порядок, в котором нетерминалы располагаются в правых частях правил, несущественны с точки зрения семантических правил. Таким образом, рассматриваемое здесь определение семантики хорошо сочетается с идеей Маккарти об "абстрактном синтаксисе" [12, 13].

Когда синтаксис неоднозначен в том смысле, что некоторые цепочки языка имеют более одного дерева вывода, семантические правила дают для каждого дерева вывода своe "значение" . Предположим, например, что к грамматике (1.3) добавлены правила

begin{align*}&#10;L_1 rightarrow BL_2&amp;,\&#10;&amp; v(L_1)=2^{l(L_{2})} v(B)+V(L_2),\&#10;&amp; l(L_1)=l(L_2)+1.&#10;end{align*}

Грамматика в результате становится синтаксически неоднозначной, но остаeтся по-прежнему семантически однозначной, поскольку атрибут v(N) имеет одно и то же значение для всех деревьев вывода. С другой стороны, если изменить правило 5.2 определения Тьюрингола с S I : S на S S : I, грамматика станет неоднозначной как синтаксически, так и семантически.

Рекомендуем посмотреть лекцию "6. Планирование процессов".

1) На самом деле значение здесь приписывается дереву вывода цепочки, а не ей самой. Если грамматика неоднозначна, это не одно и то же (см. последнюю страницу статьи). - Прим. перев.
2) В оригинале well defined. - Прим. ред.
3) В правой части формулы нужно добавить ещe член bigcup_{j=1}^{n_{p}}{(jcdot 0, x) mid xnotin N}. - Прим. ред.
4) Если, как определялось выше, концевыми узлами T могут быть только терминальные символы, то S(X) будет содержать все D'(T), а не совпадать со множеством всех D'(T). Чтобы не вносить в последующие построения несущественных исправлений, проще в этом абзаце считать T любым деревом вывода с корнем X (то есть не обязательно продолженным до терминалов). - Прим. ред.

Свежие статьи
Популярно сейчас
А знаете ли Вы, что из года в год задания практически не меняются? Математика, преподаваемая в учебных заведениях, никак не менялась минимум 30 лет. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5167
Авторов
на СтудИзбе
438
Средний доход
с одного платного файла
Обучение Подробнее