Особенности компиляции управляющих структур программы
Основным отличием компиляции управляющих структур программы (прежде всего, операторов) является то, что машинный код имеет обычно последовательный характер (то есть основан на использовании операции перехода 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:”
}
}