фиксная и постфиксная нотации. В префиксной нотации каждый
знак операции появляется перед своими опреандами, а в пост-
фиксной - после. В этом и состоит их отличие от обычной ( ин-
фиксной ) нотации, в которой обозначения двухместных операций
появляются между своими операндами. Например, инфиксное выра-
жение a+b в префиксной нотации примет вид + ab , а в пост-
фиксной - вид ab +.
Префиксная нотация известна также как польская запись, а
постфиксная - как обратная польская запись. С помощью этих
нотаций можно записывать более сложные выражения. Например,
выражение (a+b)*(c+d) в префиксной форме записывается следую-
щим образом: *+ab+cd
а в постфиксной так: ab+cd+*
Каждый знак опреации в префиксной нотации ставится не-
посредственно перед своими операндами, а в постфиксной после
них.
В префиксной и постфиксной нотациях скобки уже не требу-
ются, так здесь никогда не возникает сомнений относительно
того, какие операнды принадлежат к тем или иным знакам опера-
ций. В этих нотациях не существует приоритета знаков опера-
ций, хотя при преобразовании инфиксных выражений в префиксные
или постфиксные их приоритет, несомненно, нужно учитывать.
Перегруппировку в результате преобразования
(a+b)*(c+d)
в
ab+cd+*
можно осуществить с помощью стека. Алгоритм такого преобразо-
вания хорошо известен. Это преобразование можно выполнить
также на основании грамматики инфиксных выражений. В данном
случае оно сведется к трем действиям:
1) напечатать идентификатор, когда он встретится при
чтении инфиксного выражения слева направо;
2) поместить в стек знак операции, когда он встретится;
3) когда встретится конец выражения ( или подвыражения ),
выдать на печать тот знак операций, который находится в вер-
шине стека.
Этот метод подобен методу, который применяется для полу-
чения четверок. Префиксные и постфиксные выражения можно так-
же получить из представления выражения в виде бинарного дере-
ва. Чтобы получить представление префиксного выражения, дере-
во обходят сверху в порядке, определенном Кнутом:
посещение корня;
обход левого поддерева сверху;
обход правого поддерева сверху,
что дает
+*+abcd
Для получения постфиксного представления дерево обходят
снизу. По Кнуту это выглядит так:
обход левого поддерева снизу;
обход правого поддерева снизу;
посещение корня.
В результате имеем: ab+c*d+
Далее будем рассуждать в терминах промежуточного языка (
или обьектного ), состоящего из команд вида
тип-команды параметры
Тип-команды может быть, например, вызовом стандартного
обозначения операции, тогда параметрами могут быть имя знака
операции, адреса опреандов и адрес результата. Например,
STANDOP II+,A,B,C
Здесь II+ обозначает сложение двух целых чисел, а A, B,
C cлужат во время прогона адресами двух операндов и результа-
та. Для того чтобы в промежуточном коде можно было воспользо-
ваться адресами во время прогона, распределение памяти к это-
му времени должно быть уже закончено. При распределении памя-
ти необходимо знать, какой обьем памяти занимает целое, ве-
щественное и другие значения на той машине, для которой выда-
ется обьектный код. Это означает, что промежуточный код не
является в строгом смысле интерфецсом между не зависящей и
зависящей от машины частями компилятора. Тем не менее если
речь идет о переводе фронтальной части компилятора ( т.е.
части, транслирующей исходный код в промежуточный ) с одной
машины на другую, то единственное, что здесь может потребо-
ваться, - это изменение нескольких констант.
Промежуточный код пишется на относительно низком уровне.
Он аналогичен коду, использованному для реализации Алгола 68.
Обычно выдвигается условие, чтобы промежуточный код отражал
структуру реализуемого языка.
Промежуточный код напоминает префиксную нотацию в том
смысле, что знак операции всегда предшествует своим операн-
дам. Но он имеет менее общий характер, так как сами операнды
не могут быть префиксными выражениями. При получении промежу-
точного кода для хранения адресов операндов до тех пор, пока
не будет напечатан знак операции, используется стек. Посколь-
ку знак операции можно установит ( во многих языках ) лишь
после того, как станут известны его опреанды, стек служит
также для хранения каждого знака операции на то время, пока
не определены оба операнда.
Адрес на время прогона обычно соотносится со стеком, и
каждый такой адрес можно представить тройкой вида
( тип-адреса, номер блока, смещение ).
Тип-адреса может быть прямым или косвенным ( т.е. адрес
может содержать значение или указатель на значение ) и ссы-
латься на рабочий стек или стек идентификаторов. Он может
быть также литералом или константой. Номер блока позволяет
найти номер уровня блока в таблице блоков, что обеспечивает
доступ к конкретной рамке стека через диспдей. В сдучае лите-
рала или константы номер блока не используется. Смещение (
для адреса стека ) показывает смещение значения конкретной
рамки по отношению к началу стека идентификаторов или рабоче-
го стека. Если тип-адрес представляет собой литерал, то сме-
щение выражается самим значением, а если тип-адреса - конс-
танта, то смещение нужно найти в таблице констант по заданно-
му им адресу. В том случае, когда в каждой рамке стека рабо-
чий стек помещается сразу же над стеком идентификаторов, сме-
щения адресов рабочего стека по отношению к началу рамки мож-
но рассчитывать, как только станет известным размер стека
идентификаторов для конкретной рамки ( т.е. во время прохода,
следующего за проходом, при котором происходит распределение
памяти ).
Адреса во время прогона для идентификаторов определяются
в процессе распределения памяти и хранятся в таблице символов
вместе с информацией о типе и т.п.
Кроме рассмотренных, существуют и другие команды проме-
жуточного кода ( ICI по Бранкару ):
SETLABEL L1
для установки метки и
ASSIGN type, add1, add2
для присваивания. Тип необходим как параметр, чтобы опреде-
лить размер значения, переписываемого из add1 в add2. В Алго-
ле 68 может потребоваться просмотр типа ( вида ) при трансля-
ции этой команды в фактический код машины, если значения бу-
дут содержать динамические части, поэтому во время генерации
машинного кода нужна таблица видов.
Структуры данных для генерации кода
Как упоминалось выше, для хранения адресов операндов на
то время, пока их нельзя будет выдать как параметры ICI, не-
обходим стек значений. В этом стеке, который Бранкар называет
нижним стеком, можно хранить также и другую информацию. Нап-
ример, значение может быть связано со своими
а) адресом времени прогона;
б) типом;
в) областью действия,
помимо той информации, которая имеет значение для диагности-
ки. Это - статическая информация, так как ( по крайней мере,
для большинства языков ) ее можно получить во время компиля-
ции. Так, при компиляции может быть известно если не факти-
ческое значение, то во всяком случае адрес целого числа.
При трансляции А + В первыми помещаются в нижний стек
статические свойства А. Любой элемент нижнего стека можно
представить в виде структуры, имеющей поле для каждой из сво-
их статических характеристик. В случае идентификаторов стати-
ческие характеристики находятся из таблицы символов. Затем в
стек знаков операции помещается знак операции +, и в нижний
стек добавляются статические характеристики В. Знак операции
берется из стека знаков операций, а его два операнда - из
нижнего стека. Типыоперандов используются для идентификации
знака операции, после чего генерируется код. И наконец, в
нижний стек помещаются статические характеристики результата.
Этот процесс можно распространить и на более сложные вы-
ражения, например нп те, которые генерируются грамматикой с
правилами
EXP -> TERM |
EXP + TERM |
EXP - TERM
TERM -> FACT |
TERM * FACT |
TERM / FACT
FACT -> constant |
identifier |
(EXP)
После чтения идентификатора или константы, знака опера-
ции и второго операнда необходимо выполнить следующие дейс-
твия:
А1. Послечтения идентификатора или константы ( т.е. лис-
та синтаксического дерева ) поместить в нижний стек
соответствующие статические характеристики.
А2. После чтения оператора поместить символ операции в
стек знаков операций.
А3. После чтения правого операнда ( который может быть
выражением ) извлечь из стеков знак операции и его два опе-
ранда, генерировать соответствующий код, так как знак опера-
ции идентифицирован, и поместить в стек статические характе-
ристики результата. Тип результата становится известным во
время идентификации знака операции, например сложение двух
целых чисел всегда дает целое число.
При включении в грамматику этих действий она примет сле-
дующий вид:
EXP -> TERM
EXP+<A2>TERM<A3>
EXP-<A2>TERM<A3>
TERM -> FACT
TERM*<A2>FACT<A3>
TERM/<A2>FACT<A3>
FACT -> constant<A1>
identifier<A1>
(EXP)
Нижний стек частично используется для передачи информа-
ции о типе вверх по синтаксическому дереву. Рассмотрим син-
таксическое дерево, соответствующее выражению:
+
^
/ \
/ \
/ \
* / \ *
/\ /\
/ \ / \
/ \ / \
a b x y
a * b + x * y
Если значения a и b имеют тип целого, а х и у - тип ве-
щественного значения, компилятор может заключить,
воспользовавшись информацией нижнего стека, что "+" в вершине
дерева представляет сложение целого и вещественного значений.
Мы можем переписать выражение, расставив действия А1, А2 и А3
в том порядке, в каком они будут вызываться при трансляции