Джек Креншоу - Давайте создадим компилятор!
- Название:Давайте создадим компилятор!
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Джек Креншоу - Давайте создадим компилятор! краткое содержание
Эта серия, написанная в период с 1988 по 1995 года и состоящая из шестнадцати частей, является нетехническим введением в конструирование компиляторов. Серия является руководством по теории и практике разработки синтаксических анализаторов и компиляторов языков программирования. До того как вы закончите чтение этой книги, вы раскроете каждый аспект конструирования компиляторов, разработаете новый язык программирования и создадите работающий компилятор.
Давайте создадим компилятор! - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
MOVE X(PC),-(SP) ; Push X
MOVE Y(PC),-(SP) ; Push Y
BSR FOO ; Call FOO
Это конечно не выглядит слишком сложным!
Когда BSR выполнен центральный процессор помещает адрес возврата в стек и переходит к FOO. В этой точке стек будет выглядеть следующим образом:
.
.
Значение X (2 bytes)
Значение Y (2 bytes)
SP –> Адрес возврата (4 bytes)
Так что значения параметров имеют адреса с фиксированными смещениями от указателя стека. В этом примере адреса такие:
X: 6(SP)
Y: 4(SP)
Теперь рассмотрим, на что могла бы походить вызываемая процедура:
PROCEDURE FOO(A, B)
BEGIN
A = B
END
(Помните, что имена формальных параметров произвольные... учитываются только позиции).
Желаемый код мог бы выглядеть так:
FOO: MOVE 4(SP),D0
MOVE D0,6(SP)
RTS
Обратите внимание, что для адресации формальных параметров нам будет необходимо знать, какую позицию они занимают в списке параметров. Это подразумевает некоторые изменения в содержимом таблицы идентификаторов. Фактически, в нашем односимвольном случае лучше всего просто создать новую таблицу идентификаторов для формальных параметров.
Давайте начнем с объявления новой таблицы:
var Params: Array['A'..'Z'] of integer;
Нам также необходимо отслеживать, сколько параметров имеет данная процедура:
var NumParams: integer;
И мы должны инициализировать новую таблицу. Теперь, не забудьте, что список формальных параметров будет различным для каждой процедуры, которые мы обрабатываем, так что мы будем должны инициализировать эту таблицу заново для каждой процедуры. Вот инициализатор:
{–}
{ Initialize Parameter Table to Null }
procedure ClearParams;
var i: char;
begin
for i := 'A' to 'Z' do
Params[i] := 0;
NumParams := 0;
end;
{–}
Мы поместим обращение к этой процедуре в Init и также в конец DoProc:
{–}
{ Initialize }
procedure Init;
var i: char;
begin
GetChar;
SkipWhite;
for i := 'A' to 'Z' do
ST[i] := ' ';
ClearParams;
end;
{–}
.
.
.
{–}
{ Parse and Translate a Procedure Declaration }
procedure DoProc;
var N: char;
begin
Match('p');
N := GetName;
FormalList;
Fin;
if InTable(N) then Duplicate(N);
ST[N] := 'p';
PostLabel(N);
BeginBlock;
Return;
ClearParams;
end;
{–}
Обратите внимание, что вызов внутри DoProc гарантирует, что таблица будет чиста, когда мы в основной программе.
Хорошо, теперь нам нужны несколько процедур для работы с таблицей. Следующие несколько функций являются по существу копиями InTable, TypeOf и т.д.:
{–}
{ Find the Parameter Number }
function ParamNumber(N: char): integer;
begin
ParamNumber := Params[N];
end;
{–}
{ See if an Identifier is a Parameter }
function IsParam(N: char): boolean;
begin
IsParam := Params[N] <> 0;
end;
{–}
{ Add a New Parameter to Table }
procedure AddParam(Name: char);
begin
if IsParam(Name) then Duplicate(Name);
Inc(NumParams);
Params[Name] := NumParams;
end;
{–}
Наконец, нам понадобятся некоторые подпрограммы генерации кода:
{–}
{ Load a Parameter to the Primary Register }
procedure LoadParam(N: integer);
var Offset: integer;
begin
Offset := 4 + 2 * (NumParams – N);
Emit('MOVE ');
WriteLn(Offset, '(SP),D0');
end;
{–}
{ Store a Parameter from the Primary Register }
procedure StoreParam(N: integer);
var Offset: integer;
begin
Offset := 4 + 2 * (NumParams – N);
Emit('MOVE D0,');
WriteLn(Offset, '(SP)');
end;
{–}
{ Push The Primary Register to the Stack }
procedure Push;
begin
EmitLn('MOVE D0,-(SP)');
end;
{–}
(Последнюю подпрограмму мы уже видели прежде, но ее не было в этой остаточной версии программы.)
После этих приготовлений мы готовы работать с семантикой процедур со списками вызовов (помните, что код для работы с синтаксисом уже на месте).
Давайте начнем с обработки формальных параметров. Все что мы должны сделать – добавить каждый параметр в таблицу идентификаторов параметров:
{–}
{ Process a Formal Parameter }
procedure FormalParam;
begin
AddParam(GetName);
end;
{–}
Теперь, что делать с формальными параметрами, когда они появляются в теле процедуры? Это требует немного больше работы. Мы должны сначала определить, что это формальный параметр. Чтобы сделать это, я написал модифицированную версию TypeOf:
{–}
{ Get Type of Symbol }
function TypeOf(n: char): char;
begin
if IsParam(n) then
TypeOf := 'f'
else
TypeOf := ST[n];
end;
{–}
(Обратите внимание, что так как TypeOf теперь вызывает IsParam, возможно будет необходимо изменить ее местоположение в программе.)
Мы также должны изменить AssignOrProc для работы с этим новым типом:
{–}
{ Decide if a Statement is an Assignment or Procedure Call }
procedure AssignOrProc;
var Name: char;
begin
Name := GetName;
case TypeOf(Name) of
' ': Undefined(Name);
'v', 'f': Assignment(Name);
'p': CallProc(Name);
else Abort('Identifier ' + Name + ' Cannot Be Used Here');
end;
end;
{–}
Наконец, код для обработки операции присваивания и выражения должен быть расширен:
{–}
{ Parse and Translate an Expression }
{ Vestigial Version }
procedure Expression;
var Name: char;
begin
Name := GetName;
if IsParam(Name) then
LoadParam(ParamNumber(Name))
else
LoadVar(Name);
end;
{–}
{ Parse and Translate an Assignment Statement }
procedure Assignment(Name: char);
begin
Match('=');
Expression;
if IsParam(Name) then
StoreParam(ParamNumber(Name))
else
StoreVar(Name);
end;
{–}
Как вы можете видеть, эти процедуры обработают каждое встретившееся имя переменной или как формальный параметр или как глобальную переменную, в зависимости от того, появляется ли оно в таблице идентификаторов параметров. Запомните, что мы используем только остаточную форму Expression. В конечной программе изменения, показанные здесь, должны быть добавлены в Factor а не Expression.
Осталось самое простое. Мы должны только добавить семантику в фактический вызов процедуры, что мы можем сделать с помощъю одной новой строки кода:
{–}
{ Process an Actual Parameter }
procedure Param;
begin
Expression;
Push;
end;
{–}
Так вот. Добавьте эти изменения в вашу программу и испытайте ее. Попробуйте объявить одну или две процедуры, каждая со списком формальных параметров. Затем сделайте какие-нибудь присваивания, используя комбинации глобальных и формальных параметров. Вы можете вызывать одну процедуру из другой, но вы не можете объявлять вложенные процедуры. Вы можете даже передавать формальные параметры из одной процедуры в другую. Если бы мы имели здесь полный синтаксис языка, вы могли бы также читать и выводить формальные параметры или использовать их в сложных выражениях.
Что неправильно?
Тут вы могли бы подумать: Уверен, здесь должно быть что-то большее чем несколько сохранений и восстановлений из стека. Для передачи параметров здесь должно быть что-то большее чем тут есть.
Вы были бы правы. Фактически, код, который мы здесь генерируем, оставляет желать лучшего в нескольких случаях.
Самая явная оплошность в том, что он неправильный! Если вы оглянетесь на код для вызова процедур, вы увидите, что вызывающая подпрограмма помещает каждый фактический параметр в стек перед тем, как она вызывает процедуру. Процедура использует эту информацию, но она не изменяет указатель стека. Это означает, что содержимое все еще остается там когда мы возвращаемся. Кто-то должен очистить стек или мы скоро окажемся в очень трудной ситуации!
К счастью, это легко исправить. Все, что мы должны сделать – это увеличить указатель стека когда мы закончим.
Читать дальшеИнтервал:
Закладка: