Основным отличием компиляции управляющих структур программы (прежде всего, операторов) является то, что машинный код имеет обычно последовательный характер (то есть основан на использовании операции перехода GOTO), а для управляющих структур характерен принцип вложенности. Поэтому с каждой компонентой управляющей структуры может быть связан сколь угодно длинный код, представляющий собой результат компиляции вложенных компонент. При этом возможны следующие решения:
- при восходящем СА необходимо использовать принцип сохранения частей генерируемого кода. То есть с каждым нетерминалом, соответствующим оператору, связывается некоторый временный файл (или массив), содержащий сгенерированный код этой компоненты. Когда производится “свертка” по некоторому правилу, то сгенерированные коды для нетерминалов правой части правила переписываются в общий код, связанный с нетерминалом левой части. Здесь приведен пример для условного оператора:
.
IF::=if (W) OP1 else OP2
| | |__файл OP2
| |____________файл OP1
|________________файл W
.
IF à x=(файл W);
IF NOT X GOTO M1;
(файл OP1); GOTO M2;
M1: (файл OP2);
M2:
- При нисходящем разборе наряду с определенным выше методом сохранения частей управляющего кода возможна также генерация “меток вперед”. В этом случае возможна запись генерируемого кода в обычный последовательный файл. Проиллюстрируем, как будет выглядеть метод рекурсивного спуска для условного оператора:
void IF()
{
if (s[i]!=’(‘) { error(); return; }
i++;
// Анализ условия и генерация кода
W();
if (s[i]!=’)’) { error(); return; }
i++;
// Генерация кода для проверки условия
cout << “IF NOT A THEN GOTO M1;”
// Анализ оператора и генерация кода
OP();
if (s[i]!=’e’)
// генерация кода для if без else
cout << “M1:”;
else
// генерация кода для else
{ i++;
cout << “GOTO M2; M1:”;
// Анализ оператора и генерация кода
OP();
cout << “M2:”
}
}