Понятие L-value
К семантическому анализу имеет отношение и понятие l-value, характеризующее некоторый “тонкий” семантический смысл выражения, которое касается способа формирования его значения. Если происходит анализ и свертка правила, соответствующего некоторой операции, то у транслятора существуют два способа формирования ее результата:
- в виде нового объекта-значения. В таком случае транслятором должна быть “заведена” его семантика в виде некоторого внутреннего объекта, создаваемого в процессе работы программы;
- в виде объекта-операнда или его части. В таком случае семантика этого объекта-результата включает в себя неявный указатель (ссылку) на “исходный” объект. Такой результат операции называется l-value, от слова LEFT, что означает, что данное выражение может стоять в левой части операции (оператора) присваивания.
Общая стратегия транслятора должна состоять в том, что он должен сохранять результат в виде l-value до тех пор, пока не встретится операция, в которой он не в состоянии это сделать. Тогда уже он может переходить к значениям - промежуточным объектам. Рассмотрим ряд примеров для Си-компилятора, проиллюстрировав внутреннее представление выражения через l-value средствами того же Си.
.
Выражение
Код компилятора Примечание
B[i] &B[i] l-value
B[i].money &(B[i].name) l-value
B[i].money+5 x=(*&B[i].money)+5 транслятор поддерживает
признак l-value для
выражения до операции “+”
5. Генерация кода. Интерпретация
Последняя фаза трансляции - генерация кода или интерпретация - по своему способу включения в транслятор аналогична семантическому анализу: при выполнении шага синтаксического разбора ("свертке" правила в восходящем или подборе правила в нисходящем разборе) вызывается семантическая процедура, по результатам которой вызывается аналогичная процедура для генерации кода и интерпретации. В принципе эти две процедуры можно объединить.
Использование стека для компиляции выражений при восходящем разборе
Поскольку всех восходящих методах СА используется стек, то любая “свертка” может генерировать команды, рассчитанные на размещение операндов (или их адресов) в аппаратном стеке процессора. Пусть некоторый условный процессор имеет следующий набор команд для работы со стеком:
- PUSH(V) – загрузить значение V в стек;
- V=POP() – извлечь значение из вершины стека;
- V=GET(n) – получить значение n-го элемента относительно вершины стека, не удаляя его оттуда.
Тогда “свертка” общеизвестных правил может сопровождаться генерацией неизменного кода относительно текущего состояния “аппаратного” стека:
.
E ::= E + T a=POP(); a=a+POP(); PUSH(a);
F ::= E[c] a=POP(); a=a*sizeof(x);
a=a+POP(); PUSH(*a);
F ::=c PUSH(c);