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

Глава 23 Компилятор

В качестве последнего примера программы на языке Пролог рассмотрим компилятор. Изложение согласовано с нисходящим принципом проектирования программы. В первом разделе главы в общих чертах излагается назначение компилятора и дается его определение. В следующих трех разделах описываются основные компоненты компилятора: синтаксический анализатор, генератор кода и ассемблер.

23.1. Обзор компилятора

Исходный язык компилятора PL (Programming Language)упрощенная версия языка Паскаль - разработан исключительно для целей этой главы, В него входят оператор присваивания, условный оператор, оператор цикла типа whileи простые операторы ввода-вывода. Возможности этого языка легко проиллюстрировать с помощью примера. На рис. 23.1представлена программа на PLдля вычисления факториала. Формальное определение синтаксиса языка неявно содержится в программе 23.1,которая является синтаксическим анализатором.

program factorial;

begin

read value;

count := 1;

result: = 1;

while count < value do

begin

count := count +1;

result: = result *count

end;

write result

end

Рис. 23.1. PL-программадля вычисления факториала.

Объектный язык-это машинный язык, типичный для ЭВМ с одним аккумулятором. Команды машины представлены на рис. 23.2.Каждая команда имеет один явный операнд, который может быть целочисленной константой, адресом ячейки памяти, адресом команды программы или игнорируемой величиной. Кроме того, имеется псевдокоманда Block, спомощью которой резервируется некоторое коли­чество ячеек памяти, специфицируемое ее целочисленным операндом.

Назначение компилятора ясно из рассмотрения нашего примера. На рис. 23.3 показан результат трансляции PL-программыдля вычисления факториала в программу на машинном языке. Компилятор вырабатывает команды, представленные на рис. 23.3двумя колонками, которые обозначены словами «команда» и «операнд» соответственно.

Процесс компиляции, как показано на рис. 23.4,может быть разбит на пять этапов. На первом этапе исходный текст программы преобразуется в список лексем.

Арифметические команды Команды управления Команды

ввода-вывода и др.

Литеральные С обращением

к памяти

jumpeq read

addc add jumpne write

subc sub jumplt halt

mulc mul jumpgt

divc div jumple

loadc load jumpge

store jump

Рис. 23.2,Команды объектного языка,

Символ Адрес Команда Операнд Символ

1 READ 21 VALUE

2 LOADC 1

3 STORE 19 COUNT

4 LOADC 20

5 STORE 20 RESULT

LABEL1 6 LOAD 19 COUNT

7 SUB 21 VALUE

8 JUMPGE 16 LABEL2

9 LOAD 19 COUNT

10 ADDC 1

11 STORE 19 COUNT

12 LOAD 20 RESULT

13 MUL 19 COUNT

14 STORE 20 RESULT

15 JUMP 6 LABEL1

LABEL2 16 LOAD 20 RESULT

17 WRITE 0

18 HALT 0

COUNT 19 BLOCK` 3

RESULT 20

VALUE 21

Рис. 23.3. Программы на языке ассемблера для вычисления факториала.

Рис. 23.4 Этапы компиляции

На этапе синтаксического анализа выполняется разбор списка лексем и вырабатывается некоторая исходная структура (внутреннее представление программы). На третьем этапе исходная структура преобразуется в перемещаемую объектную программу, на четвертом -с помощью ассемблера перемещаемая объектная про-

грамма преобразуется в абсолютную объектную программу. На последнем этапе осуществляется вывод объектной программы.

В нашем компиляторе реализованы второй, третий и четвертый этапы процесса компиляции. Как стадия лексического анализа, так и этап вывода готовой объектной программы сравнительно неинтересны и здесь не рассматриваются. На верхнем уровне компилятора выполняются синтаксический анализ, генерация кода и ассемблирование,

Основной предикат compile (Tokens, ObjectCode)связывает список лексем Tokensс объектной программой ObjectCode,соответствующей программе, представленной списком лексем. Компилятор корректно транслирует любую допустимую программу на языке PL,но не обрабатывает ошибки (этот вопрос в данной главе не рассматривается). Предполагается, что список лексем формируется на предыдущей стадии лексического анализа. Синтаксический анализатор, выполняющий синтакси­ческий анализ с помощью предиката parse,вырабатывает по списку лексем Tokens внутреннее дерево разбора Structure.Эта структура используется генератором кода (предикат encode)для формирования перемещаемой программы Code.Для генера­ции кода необходима таблица символов (словарь), содержащая имена переменных и соответствующие им адреса ячеек памяти. Кроме того, в таблице символов регистрируются метки, встречающиеся в программах. Она является вторым аргу­ментом предиката encode.Наконец, перемещаемая программа преобразуется в абсолютную объектную программу посредством предиката assembleс помощью сконструированной таблицы символов Dictionary.

compile (Tokens, ObjectCode)

Объектная программа ObjectCodeявляется результатом компиляции PL-программы, представленной списком символов Tokens.

compile(Tokens, ObjectCode) 

parse (Tokens, Structure),

encode(Structure, Dictionary, Code),

assemble(Code, Dictionary, ObjectCode).

Синтаксический анализатор

purse (Tokens, Structure)

Structure-результат успешного разбора списка Tokens.

parse(Source, Structure) 

pl_program (Structure, Source \ [ ]).

pl_program(S)  [program],identifier(X), [';'],statement(S).

statement((S;Ss)) 

[begin], statement (S),rest_statements(Ss).

statement(assign(X,V)) 

identifier(X), [':='], expression (V).

statement (if (T, S1, S2))

[if], test(T),[then], statements (S1), [else], statement(S2).

statement(while(T,S) 

[while], test (T), [do], statement(S).

statement(read(X)) 

[read],identifier(X).

statement(write(X)) 

[write], expression (X),

rest_statement ((S; Ss)) 

[';'], statement(S), rest_.statements(Ss).

rest_statements(void) [end].

Программа 23.1.Компилятор для языка PL.

expression(X)  pl_constant(X).

expression(expr(Op,X,Y))  pl_constant(X),arithmetic_op(Op), expression (Y).

arithmetic_op('+')  ['+'].

arithmetic_op('-') ['-'].

arithmetic_op('*')  ['*'].

arithmetic_op(‘7’)  ['/'].

pl_constant(name(X))  identiner(X).

pl_constant(number(X))  pl_integer(X).

identifier(X)  [X],{atom(X)}.

pl_nteger(X)  [X], {integer(X)}.

test(compare(Op, X, Y))  expression(X),comparison_op(Op),expression(Y).

comparison_op(‘=’)  [‘=’].

comparison_.op(‘’)  [‘’].

comparison_op(' >')  ['>'].

comparison_op(' <')  [' < '].

comparison_op(‘’)  [‘’].

comparison_op('')  [‘’].

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

encode (Structure, Dictionary, RelocatableCode)

Перемещаемая программа RelocatableCodeгенерируется исходя из промежуточного представления программы Structure;при этом строится словарь Dictionary,в котором содержатся имена переменных с соответствующими адресами.

encode((X; Xs),D,(Y; Ys)) 

encode (X, D, Y), encode (Xs, D, Ys).

encode (void, D, no_op).

encode (assign (Name, E), D,(Code; instr(store, Address))) 

lookup(Name, D, Address), encode expression (E, D, Code).

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

(TestCode; ThenCode; instr(jump,L2);label(Ll);

ElseCode; label(L2))) 

encode._test (Test,Ll; D, TestCode),

encode (Then, D, ThenCode),

encode (Else, D, ElseCode).

encode(while(Test, Do),D,

(label(L1); TestCode; DoCode; instr(jump, L 1); label(L2))) 

encode_test(Test,L2,D,TestCode),

encode(Do, D, DoCode).

encode(read(X), D, instr(read, Address)) 

lookup (X, D, Address).

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

encode_espression(E, D, Code).

encode expression (Expression,Dictionary,Code)

Программа Codeсоответствует арифметическому выражению Expression.

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

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

lookup(X,D, Address).

encode_expression (expr(Op, E1, E2), D,(Load; Instruction)) 

single_instruction (Op,E2,D, Instruction),

encode_expression(E1, D, Load).

encode_expression (expr(Op, E1, E2), D, Code) 

not_single_instruction (Op,E2,D, Instruction),

single_operation (Op, E1, D, E2Code, Code),

encode_expression(E2,D,E2Code).

single_instruction(Op, number(C), D, instr(OpCode,C)) 

literal_operation (Op, OpCode).

single_instruclion(Op, name(X), D, instr(OpCode,A))

memory_operation (Op, OpCode )),lookup(X, D, A).

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

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

single_operation (Op, E, D, Code,

(Code; instr (st ore, Address); Load;

instr(OpCode, Address))) 

not commutative(Op),

lookup('temp', D, Address),

encode_expression (E, D, Load),

op_code (Op, E, OpCode).

op_code (Op, number(C), OpCode)  literal_operation (Op, OpCode).

op_code(Op, name (X), OpCode)  memory_operation(Op, OpCode).

literal_operalion('+’, addc). memory_operation('+’,add).

literal operation(‘-‘, subc). memory_operation ('-',sub).

literal_operation('*', mulc). memory _operation('*', mul).

literal_operation(‘/’, dive). memory_operation(‘/'div).

commutative ('+'). commutative(‘*’).

encode_test (compare (Op, E 1, E2), Label, D,(Code; instr(OpCode, Label))) 

comparision_opcode(0p, OpCode),

encode_expression(expr('-',El,E2),D,Code).

comparison_opcode('=',jumpne).

comparison_opcode(‘’,jumpeq).

comparison_opcode(‘ > ‘jumple).

comparison_opcode('',jumplt).

comparison _opcode('<'jumpge).

comparison._opcode('',jumpgt).

lookup(Name, Dictionary, Address) См. программу 15.9.

Ассемблер

assemble (Code, Dictionary, TideCode)

TideCodeрезультат ассемблирования программы Code,в процессе которого производится удаление меток (label)и примитивовпо op,а также заполнение словаряDictionary.

assemble(Code, Diclionary,TidyCode) 

tidy_and_count(Code, l, TidyCode\ (instr (halt,0);

block (L))),

N1:= N+1,

allocate(Dictionary, N1 ,N2),

L:=N2-N1,!.

tidy_and_count ((Codel;Code2),M,N,TCodel \Code2) 

tidy_and_count (Code1, M, M1, TCode1 \ Rest),

tidy_and _count(Code2, M1, N, Rest\TCode2).

tidy_and_count(instr(X, Y), N, Nl, (instr(X,Y);Code)\Code) 

N1:=N+ 1.

tidy_and_count(label(N), N, N, Code \ Code).

tidy_and_count(no_op, N, N, Code \ Code).

allocate(void,N,N).

allocate(dict(Name, Nl, Before, After), N0, N) 

allocate(Before, N0, N1),

N2:=N1+1;

allocate(After, N2, N).

Для проверки компилятора используется программа 23.2,в которой содержатся необходимые тестовые данные и команды. Программа factorialна языке PLдля вычисления факториала (рис. 23.1)представлена здесь списком лексем. Еще две небольшие программы содержат по одному оператору и используются для провер­ки средств языка, не проверяемых тестовой программой вычисления факториала, Программой test1проверяется компиляция нетривиального арифметического выражения, а программой test2 -правильность компиляции условного оператораif_then_efse.

проверка._компилятора(Х,У) 

программа (X,P),compile(P,Y).

программа (test1,[program, test1, ';' ,begin, write, x,'+',у,'-',z,'/',2,end]).

пpoгpaммa (test2,[program,test2, ';' ,begin, if, a,' > ',b,then,max,1:= ', a, else, max,’:= ‘ b, end]).

программа (factorial,

[program, factorial, ';' ,

begin

,read , value,';'

,count ,': =', 1,';'

, result,': =',!,';' ,

, while, count,' < ‘, value, do

, begin

, count,’: =’.count,’+’,1,’;’

, result,’: = ‘, result,’ * ‘, count

, end,';'

, write, result

, end]).

Программа 23.2.Программа для тестирования компилятора.

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