Ишакова Е.Н. Разработка компиляторов - Методические указания к курсовой работе (1082246), страница 4
Текст из файла (страница 4)
end
else ERR
end;
Аналогично составляются оставшиеся процедуры.
2.5 Семантический анализатор программы
В ходе семантического анализа проверяются отдельные правила записи исходных программ, которые не описываются КС-грамматикой. Эти правила носят контекстно-зависимый характер, их называют семантическими соглашениями или контекстными условиями.
Рассмотрим пример построения семантического анализатора (СеА) для программы на модельном языке М. Соблюдение контекстных условий для языка М предполагает три типа проверок:
1) обработка описаний;
2) анализ выражений;
3) проверка правильности операторов.
В оптимизированном варианте СиА и СеА совмещены и осуществляются параллельно. Поэтому процедуры СеА будем внедрять в ранее разработанные процедуры СиА.
Вход: файл лексем в числовом представлении.
Выход: заключение о семантической правильности программы или о типе обнаруженной семантической ошибке.
Обработка описаний
Задача обработки описаний - проверить, все ли переменные программы описаны правильно и только один раз. Эта задача решается следующим образом.
Таблица идентификаторов, введенная на этапе лексического анализа, расширяется, приобретая вид таблицы 2.1. Описание таблицы идентификаторов будет иметь вид:
type
tabid = record
id :string;
descrid :byte;
typid :string[4];
addrid :word
end;
var
TI: array[1.. n] of tabid;
Таблица 2.1 – Таблица идентификаторов на этапе СеА
Номер | Идентификатор | Описан | Тип | Адрес |
1 | K | 1 | Int | … |
2 | Sum | 0 | … | … |
Поле «описан» таблицы на этапе лексического анализа заполняется нулем, а при правильном описании переменных на этапе семантического анализа заменяется единицей.
При выполнении процедуры D вводится стековая переменная-массив, в которую заносится контрольное число 0. По мере успешного выполнения процедуры I в стек заносятся номера считываемых из файла лексем, под которыми они записаны в таблице идентификаторов. Как только при считывании лексем встречается лексема «:», из стека извлекаются записанные номера и по ним в таблице идентификаторов проставляется 1 в поле «описан» (к этому моменту там должен быть 0). Если очередная лексема будет «int » или «bool », то попутно в таблице идентификаторов поле «тип» заполняется соответствующим типом.
Пример 2.11. Пусть фрагмент описания на модельном языке имеет вид: var k, sum: int … Тогда соответствующий фрагмент файла лексем: (1, 2) (4, 1) (2, 3) (4, 2)…Содержимое стека при выполнении процедуры D представлено на рисунке 2.7.
0
1
2



Рисунок 2.7 – Содержимое стека при выполнении процедуры D
Для реализации обработки описаний введем следующие обозначения переменных и процедур:
1) LEX – переменная, хранящая значение очередной лексемы, представляющая собой одномерный массив размером 2, т.е. для лексемы (n, k) LEX[1]=n, LEX[2]=k;
2) gl – процедура считывания очередной лексемы в переменную LEX;
3) inst(l) - процедура записи в стек числа l;
4) outst(l) – процедура вывод из стека числа l;
5) instl – процедура записи в стек номера, под которым лексема хранится в таблице идентификаторов, т.е. inst(LEX[2]);
6) dec(t) - процедура вывода всех чисел из стека и вызова процедуры decid(1, t);
7) decid(l, t) – процедура проверки и заполнения поля «описан» и «тип» таблицы идентификаторов для лексемы с номером l и типа t.
Процедуры dec и decid имеют вид:
procedure decid (l:..; t:...);
begin
if TI[l].descrid =1 then ERR
else begin
TI[l].descrid: = 1;
TI[l].typid:= t
end
end;
procedure dec(t: ...);
begin
outst(l);
while l<>0 do
begin
decid(l, t);
outst(l)
end
end;
Правило и процедура D с учетом семантических проверок принимает вид:
D <inst(0)> I <instl> {, I <instl> } : ( int <deс(‘int’)> | bool <dec(‘bool’)> )
procedure D;
begin
inst(0);
I;
instl;
while EQ(‘,’) do
begin
gl; I; instl
end;
if EQ(‘:’) then gl else ERR;
if EQ(‘int’) then
begin
gl; dec(‘int’)
end
else
if EQ(‘bool’)
then
begin
gl; dec(‘bool’)
end
else ERR
end;
Анализ выражений
Задача анализа выражений - проверить описаны ли переменные, встречающиеся в выражениях, и соответствуют ли типы операндов друг другу и типу операции.
Эти задачи решаются следующим образом. Вводится таблица двуместных операций (таблица 2.2) и стек, в который в соответствии с разбором выражения E заносятся типы операндов и знак операции. После семантической проверки в стеке оставляется только тип результата операции. В результате разбора всего выражения в стеке остается тип этого выражения.
Для реализации анализа выражений введем следующие обозначения процедур и функций:
1) checkid - процедура, которая для лексемы LEX, являющейся идентификатором, проверяет по таблице идентификаторов TI, описан ли он, и, если описан, то помещает его тип в стек;
2) checkop – процедура, выводящая из стека типы операндов и знак операции, вызывающая процедуру gettype(op, t1, t2, t), проверяющая соответствие типов и записывающая в стек тип результата операции;
3) gettype(ор, t1, t2, t) – процедура, которая по таблице операций TOP для операции ор выдает тип t результата и типы t1, t2 операндов;
4) checknot – процедура проверки типа для одноместной операции «».
Таблица 2.2 – Фрагмент таблицы двуместных операций TOP
Операция | Тип 1 | Тип 2 | Тип результата |
+ > … | int int … | int int … | int bool … |
Перечисленные процедуры имеют следующий вид:
procedure checkid;
begin
k:=LEX[2];
if TI[k].descrid = 0 then ERR;
inst(TI[k].typid)
end;
procedure checkop;
begin
outst(top2); outst(op); outst(top1);
gettype(op, t1, t2, t);
if (top1<>t1) or (top2<>t2) then ERR;
inst(t)
end;
procedure checknot;
begin
outst(t);
if t<> bool then ERR;
inst(t)
end;
Правила, описывающие выражения языка М, расширенные процедурами семантического анализа, принимают вид.
Е Е1 {( > | < | = ) <instl> E1 <checkop>}
E1 Т {(+ | - | ) <instl> T <checkop>}
T F {( * | / | ) <instl> F<checkop>}
F I <checkid>| N<inst(‘int’)> | L <inst(‘bool’)>| F <checknot>|(E)
Пример 2.12. Дано выражение a+5*b. Дерево разбора выражения и динамика содержимого стека представлены на рисунке 2.8.
int | + | int | * | int |
1)
int | + | int |
2)
int |
3)
Рисунок 2.8 – Анализ выражения a+5*b
Проверка правильности операторов
Задачи проверки правильности операторов:
1) выяснить, все ли переменные, встречающиеся в операторах, описаны;
2) установить соответствие типов в операторе присваивания слева и справа от символа «:=»;
3) определить, является ли выражение Е в операторах условия и цикла булевым.
Данные задачи решаются путем включения в правило S ранее рассмотренной процедуры checkid, а также новых процедур eqtype и eqbool, имеющих следующий вид:
procedure eqtype;
begin
outst(t2); outst(t1);
if t1<>t2 then ERR
end;
procedure eqbool;
begin
outst(t);
if t<>bool then ERR
end;
Правило S с учетом процедур СеА примет вид:
S I <checkid> := E <eqtype> | if E <eqbool> then S else S
while E <egbool> do S | write (E) | read (I <checkid> )
2.6 Генерация внутреннего представления программы
Результатом СиА должно быть некоторое внутреннее представление исходной цепочки лексем, которое отражает ее синтаксическую структуру. Программа в таком виде может либо транслироваться в объектный код, либо интерпретироваться.
Выделяют следующие общепринятые способы внутреннего представления программы:
-
постфиксная запись;
-
многоадресный код с явно именуемым результатом (тетрады);
-
многоадресный код с неявно именуемым результатом (триады);
-
синтаксические деревья;
-
машинные команды или ассемблерный код.
В качестве языка для представления промежуточной программы выберем постфиксную запись – ПОЛИЗ (польская инверсная запись).
Перевод в ПОЛИЗ выражений
В ПОЛИЗе операнды записаны слева направо в порядке использования. Знаки операций следуют таким образом, что знаку операции непосредственно предшествуют его операнды.
Пример 2.13. Для выражения в обычной (инфиксной записи) a*(b+c)-(d-e)/f ПОЛИЗ будет иметь вид: abc+*de-f/-.
Справедливы следующие формальные определения.
Определение 2.5. Если Е является единственным операндом, то ПОЛИЗ выражения Е – это этот операнд.
Определение 2.6. ПОЛИЗ выражения Е1 Е2, где - знак бинарной операции, Е1 и Е2 – операнды для , является запись , где
- ПОЛИЗ выражений Е1 и Е2 соответственно.
Определение 2.7. ПОЛИЗ выражения Е, где - знак унарной операции, а Е – операнд , есть запись , где
- ПОЛИЗ выражения Е.
Определение 2.8. ПОЛИЗ выражения (Е) есть ПОЛИЗ выражения Е.
Перевод в ПОЛИЗ операторов
Каждый оператор языка программирования может быть представлен как n-местная операция с семантикой, соответствующей семантике оператора.
Оператор присваивания I:=E в ПОЛИЗе записывается: