Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 [ 79 ] 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

используется (что легко может случиться при наличии всего 26 вариантов!). Для поддержки всего это введите следующие новые процедуры:

{ Report Type of a Variable } function TypeOf(N: char): char; begin

TypeOf := ST[N]; end;

{ Report if a Variable is in the Table } function InTable(N: char): boolean; begin

InTable := TypeOf(N) <> ?; end;

{ Check for a Duplicate Variable Name }

procedure CheckDup(N: char);

begin

if InTable(N) then Abort(Duplicate Name + N); end;

{ Add Entry to Table }

procedure AddEntry(N, T: char); begin

CheckDup(N);

ST[N] := T; end;

Теперь измените три строки в основной программе следующим образом:

AddEntryCA, a); AddEntryCP, b); AddEntryCX, c);

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

РАСПРЕДЕЛЕНИЕ ПАМЯТИ

В других программах, подобных этой, включая сам компилятор TINY, мы уже обращались к вопросу объявления глобальных переменных и кода, генерируемого для них. Давайте создадим здесь урезанную версию "компилятора", чья единственная функция - позволить нам объявлять переменные. Помните, синтаксис для объявления:

<data decl> ::= VAR <identifier>

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

{ Allocate Storage for a Variable }

procedure Alloc(N: char);

begin

AddEntry(N, v);

WriteLn(N, TAB, DC 0);

end;



{ Parse and Translate a Data Declaration } procedure Decl; var Name: char; begin

Match(v);

Alloc(GetName); end;

{ Parse and Translate Global Declarations }

procedure TopDecls;

begin

while Look <> . do begin case Look of v: Decl;

else Abort(Unrecognized Keyword + Look); end; Fin; end; end;

Теперь, в основной программе добавьте вызов TopDecl и запустите программу. Попробуйте распределить несколько переменных и обратите внимание на полученный сгенерированный код. Для вас это пройденный этап, поэтому результат должен выглядеть знакомым. Заметьте из кода для TopDecls что программа завершается точкой.

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

ОБЪЯВЛЕНИЕ ТИПОВ

Распределение памяти различных размеров не сложнее чем изменение процедуры 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, Ч: 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;



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 [ 79 ] 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97