Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pascal_Unkn.doc
Скачиваний:
8
Добавлен:
03.11.2018
Размер:
1.63 Mб
Скачать

Объявление типов

Распределение памяти различных размеров не сложнее чем изменение процедуры TopDecl для распознавания более чем одного ключевого слова. Здесь необходимо принять ряд решений, с точки зрения того, каков должен быть синтаксис и т.п., но сейчас я собираюсь отложить  все эти вопросы и просто объявить не подлежащий утверждению указ что наш синтаксис будет таким:

    <data decl> ::= <typename>  <identifier>

где:

    <typename> ::= BYTE | WORD | LONG

(По удивительному совпадению, первые буквы этих наименований оказались те же самыми что и спецификации длины ассемблерного кода 68000, так что такой выбор сэкономит нам немного работы.)

Мы можем создать код, который позаботится об этих объявлениях, внеся всего лишь небольшие изменения. Обратите внимание, что в подпрограммах, показанных ниже, я отделил генерацию код в Alloc от логической части. Это соответствует нашему желанию изолировать машинно-зависимую часть компилятора.

{--------------------------------------------------------------}

{ Generate Code for Allocation of a Variable }

procedure AllocVar(N, T: char); begin    WriteLn(N, ':', TAB, 'DC.', T, ' 0'); end;

{--------------------------------------------------------------} { Allocate Storage for a Variable }

procedure Alloc(N, T: char); begin    AddEntry(N, T);    AllocVar(N, T); end;

{--------------------------------------------------------------} { Parse and Translate a Data Declaration }

procedure Decl; var Typ: char; begin    Typ := GetName;    Alloc(GetName, Typ); end;

{--------------------------------------------------------------} { Parse and Translate Global Declarations }

procedure TopDecls; begin    while Look <> '.' do begin       case Look of         'b', 'w', 'l': Decl;       else Abort('Unrecognized Keyword ' + Look);       end;       Fin;    end; end;

{--------------------------------------------------------------}

Внесите показанные изменения в эти процедуры и испытайте программу. Используйте одиночные символы "b", "w" и "l" как ключевые слова (сейчас они должны быть в нижнем регистре). Вы увидите, что в каждом случае мы выделяем память соответствующего объема. Обратите внимание, глядя на дамп таблицы идентификаторов, что размеры также сохранены для использования позже. Какого использования? Хорошо, это тема остальной части этой главы.

Присваивания

Теперь, когда мы можем объявлять переменные различных размеров, очевидно что мы должны иметь возможность что-то с ними делать. На первый раз, давайте просто попробуем загружать их в наш рабочий регистр D0. Имеет смысл использовать ту же самую идею, которую мы использовали для Alloc, т.е. сделаем процедуру загрузки, которая может загружать переменные нескольких размеров. Нам также необходимо продолжать изолировать машинно-зависимое содержимое. Процедура загрузки выглядит так:

{---------------------------------------------------------------}

{ Load a Variable to Primary Register }

procedure LoadVar(Name, Typ: char); begin    Move(Typ, Name + '(PC)', 'D0'); end;

{---------------------------------------------------------------}

По крайней мере для 68000, многие команды оказываются командами MOVE. Было бы полезно создать отдельный генератор кода только для этих инструкций и затем вызывать его когда необходимо:

{---------------------------------------------------------------}

{ Generate a Move Instruction }

procedure Move(Size: char; Source, Dest: String); begin    EmitLn('MOVE.' + Size + ' ' + Source + ',' + Dest); end;

{---------------------------------------------------------------}

Обратите внимание, что эти две подпрограммы - строго генераторы кода; они не имеют проверки ошибок и другой логики. Чтобы завершить картинку, нам необходим еще один программный уровень, который предоставляет эти функции.

Прежде всего, мы должны удостовериться, что типы, с которыми мы работаем - загружаемого типа. Это звучит как работа для другого распознавателя:

{--------------------------------------------------------------}

{ Recognize a Legal Variable Type }

function IsVarType(c: char): boolean; begin    IsVarType := c in ['B', 'W', 'L']; end;

{--------------------------------------------------------------}

Затем, было бы хорошо иметь подпрограмму, которая извлечет тип переменной из таблицы идентификаторов в то же время проверяя его на допустимость:

{--------------------------------------------------------------}

{ Get a Variable Type from the Symbol Table }

function VarType(Name: char): char; var Typ: char; begin    Typ := TypeOf(Name);   if not IsVarType(Typ) then Abort('Identifier ' + Name +

' is not a variable');    VarType := Typ; end;

{--------------------------------------------------------------}

Вооруженная этими инструментами, процедура, выполняющая загрузку переменной, становится тривиальной:

{--------------------------------------------------------------}

{ Load a Variable to the Primary Register }

procedure Load(Name: char); begin   LoadVar(Name, VarType(Name)); end;

{--------------------------------------------------------------}

(Примечание для обеспокоившихся: я знаю, знаю, все это очень неэффективно. В промышленной программы мы, возможно, предприняли бы шаги чтобы избежать такого глубокого вложения вызовов процедур. Не волнуйтесь об этом. Это упражнение, помните? Более важно сделать его правильно и понять его, чем получить неправильный ответ но быстро. Если вы закончите свой компилятор и обнаружите, что вы несчастны от его быстродействия, вы вольны вернуться и доработать код для более быстрой работы).

Было бы хорошей идеей протестировать программу сейчас. Так как мы пока не имеем процедуры для работы с операциями присваивания, я просто добавил строки:

    Load('A');

    Load('B');

    Load('C');

    Load('X');

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

Я уверен, что вы не будете удивлены, узнав, что сохранение переменных во многом подобно их загрузке. Необходимые процедуры показаны дальше:

{---------------------------------------------------------------}

{ Store Primary to Variable }

procedure StoreVar(Name, Typ: char); begin    EmitLn('LEA ' + Name + '(PC),A0');    Move(Typ, 'D0', '(A0)'); end;

{--------------------------------------------------------------} { Store a Variable from the Primary Register }

procedure Store(Name: char); begin    StoreVar(Name, VarType(Name)); end;

{--------------------------------------------------------------}

Вы можете проверить их таким же образом, что и загрузку.

Теперь, конечно, достаточно легко использовать их для обработки операций присваивания. Что мы сделаем - создадим специальную версию процедуры Block, которая поддерживает только операции присваивания, а также специальную версию Expression, которая поддерживает в качестве допустимых выражений только одиночные переменные. Вот они:

{---------------------------------------------------------------}

{ Parse and Translate an Expression }

procedure Expression; var Name: char; begin    Load(GetName); end;

{--------------------------------------------------------------} { Parse and Translate an Assignment Statement }

procedure Assignment; var Name: char; begin    Name := GetName;    Match('=');    Expression;    Store(Name); end;

{--------------------------------------------------------------} { Parse and Translate a Block of Statements }

procedure Block; begin    while Look <> '.' do begin       Assignment;       Fin;    end; end;

{--------------------------------------------------------------}

(Стоит заметить, что новые процедуры, которые позволяют нам манипулировать типами, даже проще и яснее чем те, что мы видели ранее. Это в основном благодаря нашим усилиям по изоляции подпрограмм генерации кода.)

Есть одна небольшая назойливая проблема. Прежде мы использовали завершающую точку Паскаля чтобы выбраться из процедуры TopDecl. Теперь это неправильный символ... он использован для завершения Block. В предыдущих программах мы использовали для выхода символ BEGIN (сокращенно "b"). Но он теперь используется как символ типа.

Решение, хотя и является отчасти клуджем, достаточно простое. Для обозначения BEGIN мы будем использовать 'B' в верхнем регистре. Так что измените символ в цикле WHILE внутри TopDecl с "." на "B" и все будет прекрасно.

Теперь мы можем завершить задачу, изменив основную программу следующим образом:

{--------------------------------------------------------------}

{ Main Program }

begin    Init;    TopDecls;    Match('B');    Fin;    Block;    DumpTable; end.

{--------------------------------------------------------------}

(Обратите внимание, что я должен был расставить несколько обращений к Fin чтобы избежать проблем переносов строк.)

ОК, запустите эту программу. Попробуйте ввести:

    ba        { byte a }   *** НЕ НАБИРАЙТЕ КОММЕНТАРИИ!!! ***

    wb        { word b }     lc        { long c }     B         { begin  }     a=a     a=b     a=c     b=a     b=b     b=c     c=a     c=b     c=c

    .

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

Есть только одна небольшая проблема: сгенерированный код неправильный!

Взгляните на код для a=c:

    MOVE.L    C(PC),D0

    LEA       A(PC),A0

    MOVE.B    D0,(A0)

Этот код корректный. Он приведет к сохранению младших восьми бит C в A, что является приемлемым поведением. Это почти все, что мы можем ожидать.

Но теперь, взгляните на противоположный случай. Для c=a генерируется такой код:

    MOVE.B A(PC),D0

    LEA  C(PC),A0

    MOVE.L D0,(A0)

Это не правильно. Он приведет к сохранению байтовой переменной A в младших восьми битах D0. Согласно правилам для процессора 68000 старшие 24 бита останутся неизменными. Это означаем, что когда мы сохраняем все 32 бита в C, любой мусор, который был в этих старших разрядах, также будет сохранен. Нехорошо.

То, с чем мы сейчас столкнулись, называется проблемой преобразования типов или приведением.

Прежде, чем мы сделаем что-либо с переменными различных типов, даже если это просто их копирование, мы должны быть готовы встретиться с этой проблемой.     Это не самая простая часть компилятора. Большинство ошибок, которые я видел в промышленных компиляторах, имели отношение к ошибкам преобразования типов для некоторой неизвестной комбинации аргументов. Как обычно, существует компромисс между сложностью компилятора и потенциальным качеством сгенерированного кода, и, как обычно, мы выберем путь, который сохранит компилятор простым. Я думаю вы найдете, что с таким подходом мы можем удерживать потенциальную сложность под достаточным контролем.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]