Скачиваний:
38
Добавлен:
01.05.2014
Размер:
765.95 Кб
Скачать

23.3. Генератор кода

Основное отношение генератора кода encode(Structure,Dictionary,Code)обеспечивает генерацию кода Codeиз структуры Structure,произведенной синтаксическим анализатором. Поэтому настоящий раздел является прямым продолжением предыдущего раздела. Здесь описывается генерируемый код для каждой структуры. произведенной синтаксическим анализатором для различных операторов языка PL.

Словарь Dictionaryсвязывает PL-переменныес ячейками памяти, а метки- с адресами команд. Ассемблер использует словарь для определения адресов меток и идентификаторов программы. В данном разделе словарь обозначается буквой D. Для реализации словаря используется неполная структура данных - упорядоченное двоичное дерево, описанное в разд. 15.3.Для доступа к неполному двоичному дереву используется предикат lookup (Name, D. Value)(см. программу 15.9).

Структура, соответствующая составному оператору, представляет собой последовательность составляющих его структур. Она транслируется в последовательность блоков кода, рекурсивно определяемых отношением encode.Для установления последовательности используется функтор «;».Пустой оператор voidтранслируется в «пустую» операцию (null),имеющую обозначениепо_ор.При ассемблировании перемещаемого кода эта «псевдокоманда» исключается из программы.

Структура, вырабатываемая синтаксическим анализатором для оператора присваивания языка PL,имеет вид assign (Name, Expression),где Expression -выражение, значение которого присваивается PL-переменной Name.Выражение вычисляется соответствующей откомпилированной формой, за которой следует команда store, чей аргумент адрес, соответствующий переменной Name.Отдельные команды в откомпилированном коде представляются структурой вида instr(X,Y),где X-команда, аY-операнд. Следовательно, соответствующая трансляция структурыassignдаст структуру (Code: instr(store. Address)),где Code-откомпилированная форма выражения, после выполнения которой значение выражения остается в сумматоре. Такая форма генерируется с помощью предиката encode_expression(Expression, D, Expression Сode).Кодирование оператора присваивания выполняется предложением

encode (assign (Name,Expression), D,

(Code; instr(store. Address)))  lookup(Name, D, Address),

encode_expression(Expression, D, Code).

Это предложение хороший пример Пролог - программы, которая декларативно воспринимается легко, но скрывает усложненную процедурную интерпретацию. Логически необходимо специфицировать отношения между Nameи Address,а также между Expressionи Code.С точки зрения программиста, безразлично, когда конструируется окончательная структура, и действительно, порядок двух целей в теле этою предложения может быть изменен, что не приведет к изменению поведения всей программы. Более того, цель lookup,связывающая имя Nameс адресом Address,на стадии ассемблирования либо делает новую запись в словарь, либо осуществляет поиск существующей записи с соответствующим значениемName.Все эти действия не требуют явного внимания программиста. Они корректно выполняются как фоновые.

Существует несколько ситуаций, возникающих при компиляции выражений, которые требуют отдельного рассмотрения. Константы загружаются непосредственно; для этого используется соответствующая машинная команда loadc C,где С - константа. Подобным образом идентификаторы компилируются в командуload А,гдеА-адрес идентификатора. Соответствующие этим случаям предложенияencode_expressionимеют вид

encode_expression (number(C), D, instr(loadc,С)).

encode_expression(name(X), D, instr(load, Address)) 

lookup (X,D, Address).

Обычное выражение представляется структурой ехрr(Ор,Е1,Е2),гдеОр -оператор,Elконстанта языка PLиЕ2-выражение. Вид откомпилированного кода зависит отЕ2.Если выражение является PL-константой,то сгенерированный код состоит из двух операторов: соответствующей команды load,рекурсивно определяемой предложением encode_expression,и отдельной команды, соответствующей операторуОр. Вновь не имеет значения, в каком порядке определяются эти две команды. Предложение encode_.expressionзапишется следующим образом:

encode_expression (expr(Op,El,E2), D, (Load; Instruction))  single_instruction(Op, E2, D, Instruction), encode_expression (E 1, D, Load).

Вид отдельной команды зависит от оператора и от того, является ли PL-константачислом или идентификатором. Если константа-число, то используются литеральные операции, в то время как идентификаторы связаны с операциями обраще­ния к памяти. Следующие предложения учитывают эти варианты:

single_instruction(Op, number(C), D, instr(Opcode,С)) 

literal_operation(Op, Opcode).

single_instruction(Op, name(X), D, instr(Opcode, A)) 

memory_operation(0p, Opcode), lookup(X,D,A).

Для каждого вида операций требуется своя таблица фактов. Соответствующие формы фактов иллюстрируются для операции «+»:

literal_operation(+,addc). memory _operation(+, add).

Специального рассмотрения требует случай, когда выражение Е2в структуреехрrне является константой и не может быть закодировано одной командой. Форма откомпилированного кода будет определяться откомпилированным кодом для вычисления выраженияЕ2и отдельной операцией, определяемой операторомОри константой E1.Соответствующие предложения запишутся следующим образом:

encode_expression (expr(Op,Е1,Е2), D, Code) 

not single_instruction(Op, E2, D, Instruction), single_operation (Op, E1, D, E2Code, Code), encode_expression(E2, D, E2Code).

Вообще, результат вычисления выражения Е2должен быть записан в некоторую временную ячейку памяти, которую обозначим ниже как "$temp".Таким образом, последовательность команд будет состоять из кода для выраженияЕ2,командыstore,команды loadдля константы E1и соответствующей операции обращения к памяти, адресующей сохраняемое содержимое. Окончательно с использованием определенных выше предикатов получим фрагмент программы генерации кода для выражения

single_operation(Ор, E, D, Code,

(Code;

instr(store, Address);

Load;

instr(OpCode.Address))

) 

not commutative(Op),

lookup(‘$temp’, D, Address),

encode_expression(E, D, Load),

op_code (Op, E, OpCode).

Если операция коммутативна, например сложение или умножение, то возможна некоторая оптимизация кода, благодаря которой можно избежать использования временной переменной. Предполагая, что результат вычисления Е2остается в сумматоре, литеральная операция или операция обращения к памяти в этом случае может быть выполнена с использованием E1 :

single_operation (Op, E, D, Code, (Code; Instruction)) 

commutative(Op), single_instruction(Op, E, D, Instruction).

Рассмотрим теперь компиляцию условного оператора if-then-elseпосле синтаксического разбора структурой if (Test, Then, Else).Для компиляции этой структуры необходимо ввести метки, используемые при реализации команд перехода. Требуются две метки, отмечающие начало и конец else - частиусловного оператора соответственно. Эти метки имеют вид label(N),где N -адрес команды. Значение N определяется на стадии ассемблирования, когда само предложение labelудаляется. Схема получаемой программы дается третьим аргументом следующего пред­ложения encode:

encode(if(Test, Then, Else), D,

(TestCode;

ThenCode;

instr(jump,L2);

label(Ll);

ElseCode;

label (L2))

) 

encode_test(Test, L1, D, TestCode),

encode (Then, D, ThenCode),

encode(Else, D, ElseCode).

Для сравнения двух арифметических выражений второе выражение вычитается из первого, а затем выполняется операция перехода, соответствующая конкретному оператору сравнения. Например, при проверке двух выражений на равенство программа будет введена в заблуждение, если результат вычитания двух выражений не равен нулю. Поэтому в генератор кода должен быть включен факт comparison_opcode(‘=’ ,jumpne).Отметим, что метка, являющаяся вторым аргументом предиката encode_test,является адресом первой команды программы после проверки.

encode .test (compare(Op, El, E2, Label, D,(Code; instr(OpCode, Label)))

comparison_.opcode (Op.OpCode),

encode_expression(expr(‘-‘,E1, E2), D, Code).

Следующим рассмотрим оператор цикла while.Синтаксический разбор этого оператора завершается построением структуры while(Test, Statements).В данном случае метка необходима перед проверкой. Код для проверки генерируется так же, как и в случае условного оператора. Затем генерируются тело программы для оператора while,соответствующее параметру Statements ипереход на повторное выполнение проверки. После команды перехода jumpнеобходимо разместить метку, на которую будет передаваться управление, когда проверка примет значение «ложно». Предложение encodeдля оператора whileпримет вид

encode(while(Test. Do),D,

(label (LI);

TestCode;

DoCode;

instr(jump, Ll);

label(L2))

)

encode_test(Test, L2, D, TestCode), encode(Do, D, DoCode).

Операторы ввода-вывода языка PLкомпилируются просто. В результате разбора оператора ввода формируется структура read(X),которая компилируется в одну команду read. При этом для получения корректного адреса переменнойХиспользуется предикат lookup (X, D, Address).В данном случае предложение encodeбудет выглядеть так:

encode (read (X), D, inst r (read, Address)) 

lookup(X, D, Address).

При компиляции оператора вывода сначала компилируется входящее в оператор выражение, а затем генерируется команда вывода write.Для этого предусматривается следующее предложение encode:

encode(write(E),D,(Code; instr(write,0))) 

encode_expression (E, D, Code).

Для каждого из трех примеров, содержащихся в программе 23.2,на рис. 23.6 представлен перемещаемый код, произведенный генератором кода и готовый к ассемблированию. Для облегчения восприятия здесь использованы мнемонические имена переменных.

Программа test1:

((((instr(load, Z);instr(divc, 2));instr(store, Temp);

instr(Load, Y);inslr(sub, Temp));instr(add, X));

instr(wnte,0));no_op

Программа test2:

(((instr(load, A):instr(sub, B));instr(jumple, Ll));

(instr(load, A);instr(store, Max));instr(jump,L2);

label(Ll );(instr(load, B);instr(store, Max));

label(L2));no_op

Программа factorial:

instr(read, Value);(instr(loadc, l);instr(store, Count));

(instr(loadc, l);instr(slore, Result));(label(Ll);

((instr(1oad, Count);inslr(sub, Value));

instr(jumpge,L2));(((instr(1oad. Count): instr(addc. 1));

inslr(store,Count));inslr(load, Result);

instr(mul, Count));instr(store, Result)): no_op);

instr(jump, L1):.label(L2));(inslr(load,Result);

inslr(wrile,0));no_op

Рис. 23.6 Сгенерированный код

Соседние файлы в папке prolog14_end