Базовая форма для записи тетрад выглядит следующим образом: <операция> (<операнд1> <операнд2> <результат>). Опера-
ция заданная тетрадой, выполняется над ее операндами и результат помещается в переменную, заданную результатом тетра-
ды. Тетрады представляют собой линейную последовательность команд. Если операнд отсутствует, то он либо опускается,
либо заменяется пустым операндом. Результат тетрады не может быть опущен. Порядок вычисления тетрад может быть из-
менен, только если есть специальные тетрады, целенаправленно его изменяющие. Для тетрад легко написать тривиальный
алгоритм перевода в результирующую программу. Недостаток тетрад в том, что их сложно преобразовывать в машинный
код, так как в наборах команд современных процессоров редко используются операции с тремя операндами.
Особенностью триад является то, что операнды могут быть ссылками на другую триаду. Они также представляют собой ли-
нейную последовательность команд. Результат выполнения триады нужно хранить во временной памяти, так как он может
использоваться по ссылке из другой триады. В остальном они подобны тетрадам. При реализации алгоритма перевода до-
полнительно требуется распределение памяти.
Рассмотрим простейшую схему СУ-компиляции для перевода выражения в обратную польскую запись. Будем считать, что
имеется выходная цепочка символов R и известно текущее положение указателя в ней p. Распознаватель, выполняя свертку к
очередному нетерминалу или подбор альтернативы по правилу грамматики, может записывать символы в выходную цепочку
и менять положение указателя. Пример:
S → S+T R(p)=”+” shift(p)
S → S-T R(p)=”-” shift(p)
S → T
T → T*E R(p)=”*” shift(p)
T → T/E R(p)=”/” shift(p)
T → E
E → (S)
E → a R(p)=”a” shift(p)
E → b R(p)=”b” shift(p)
При генерации кода по дереву необходимо определять тип узла дерева. Он соответствует типу операции, которой помечен
узел. Кроме того, необходимо различать четыре комбинации нижележащих узлов: оба они – листья, только левый – лист,
только правый – лист, оба они не являются листьями. Пусть функция, которая реализует перевод узла дерева в последова-
тельность команд ассемблера, называется NodeGen, параметром для нее является узел, который ей необходимо перевести.
Тогда для 4 вариантов узлов для операции XXX будут представлены следующим результатом:
1. Оба нижележащих узла – операнды (op1 - левый, op2 - правый). Результат: mov ax, op1; XXX ax, op2
2. Правый нижележащий узел – не операнд (node), левый – операнд op1. Результат: NodeGen(node); mov dx,ax; mov ax, op1;
XXX ax, dx
3. Левый нижележащий узел – не операнд (node), правый – операнд op1. Результат: NodeGen(node); XXX ax, op2
4. Оба нижележащих узла – не операнды (node1 - левый, node 2 - правый). Результат: NodeGen(node1); push(ax); Node-
Gen(node2); mov dx, ax; pop ax; XXX ax, dx.
В качестве операции XXX может использоваться любая, например, *, /, + , - (mul, div, add, sub).
Генерация кода для операции присваивания (=) несколько отличается. Здесь возможны два варианта.
1. Оба нижележащих узла – операнды (op1 – левый, op2- правый). Результат: mov ax, op2; mov op1, ax;
2. Левый нижележащий узел – операнд op1, правый – не операнд (node). Результат: NodeGen(node); mov
op1, ax.
Для выражения z=(a+a)*b дерево имеет вид:
Тогда генерация выглядит следующим образом:
NodeGen (node2) NodeGen(node3) mov ax, a
mov z, ax mov dx, ax add ax, a
mov ax, b mov dx, ax
mul ax, dx mov ax, b
mov z, ax mul ax, dx
mov z, ax
В примере было сделано несколько допущений. Во-первых, ассемблер должен воспринимать строку mul ax, op2. Поскольку
для х86 платформы первый множитель всегда должен находиться в ах, то правильный синтаксис команды следующий: mul
op2. Т.е. в реальных условиях необходимо учитывать особенности мнемоник ассемблера реальной платформы. Кроме того, в
зависимости от типа операндов могут использоваться различные регистры процессора или даже требоваться иная серия ко-
манд, например, для чисел с плавающей запятой. Эти аппаратные зависимости устраняются, если порождать код в виде три-
ад либо тетрад.
СУ-схемой называется пятерка D=(T, N, Δ, R, S), где Т – конечный входной алфавит (терминалы), N – конечное множество
нетерминалов, Δ - конечный выходной алфавит, R – конечное множество правил вида A→α,β, где α∈V
*
, β∈ (N∪Δ)
*
; S - ак-
сиома схемы. СУ-схема называется простой, если в каждом правиле A→α,β одноименные нетерминалы встречаются в α и β
в одном и том же порядке. СУ-схема называется постфиксной, если β∈N
*
Δ
*
в каждом правиле (A → α,β) ∈ R. СУ-
переводом τ, определяемым СУ-схемой D=(T, N, Δ, R, S), называется множество пар τ(D)={(ω,y) | (S,S)⇒
*
(ω,y), ω∈T
*
,
y∈Δ
*
}. Грамматика G
вх
=(T,N,P,S), где P={A→ α | (A → α,β)∈ R }, называется входной грамматикой СУ-схемы. Грамматика
G
вых
=(Δ,N,P’,S’), где P’={A→ β | (A → α,β)∈ R }, называется выходной грамматикой СУ-схемы. МП-преобразователем
называют восьмерку вида M=(Q, A, Z, Δ, δ, q
0
, z
0
, F), где Q – конечное множество состояний преобразователя ,A – конечный
входной алфавит, Z – конечный магазинный алфавит, Δ - конечный выходной алфавит, δ - отображение множества
(Q×(A∪{λ}×Z) в множество всех подмножеств множества (Q×Z
*
×Δ
*
), т.е. δ:(Q×(A∪{λ}×Z)→M(Q×Z
*
×Δ
*
), q
0
– начальное
состояние преобразователя, q
0
∈Q, z
0
– начальное содержимое магазина, z
0
∈Z, F – множество заключительных состояний