Давайте создадим компилятор!

ОглавлениеДобавить в закладки К обложке

Затем давайте заставим GetClass делать что-нибудь стоящее. Объявите глобальную переменную

var Class: char;

и измените GetClass

{–}

{ Get a Storage Class Specifier }

Procedure GetClass;

begin

if Look in ['a', 'x', 's'] then begin

Class := Look;

GetChar;

end

else Class := 'a';

end;

{–}

Здесь я использовал три одиночных символа для представления трех классов памяти «auto», «extern» и «static». Это не единственные три возможных класса... есть также «register» и «typedef», но это должно дать вам представление. Заметьте, что класс по умолчанию «auto».

Мы можем сделать подобную вещь для типов. Введите следующую процедуру:

{–}

{ Get a Type Specifier }

procedure GetType;

begin

Typ := ' ';

if Look = 'u' then begin

Sign := 'u';

Typ := 'i';

GetChar;

end

else Sign := 's';

if Look in ['i', 'l', 'c'] then begin

Typ := Look;

GetChar;

end;

end;

{–}

Обратите внимание, что вы должны добавить еще две глобальные переменные Sign и Typ.

С этими двумя процедурами компилятор будет обрабатывать определение классов и типов и сохранять их результаты. Мы можем сейчас обрабатывать остальные объявления.

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

Вставьте следующую версию TopDecl:

{–}

{ Process a Top-Level Declaration }

procedure TopDecl;

var Name: char;

begin

Name := Getname;

if Look = '(' then

DoFunc(Name)

else

DoData(Name);

end;

{–}

(Заметьте, что так как мы уже прочитали имя, мы должны передать его соответствующей подпрограмме.)

Наконец, добавьте две процедуры DoFunc и DoData:

{–}

{ Process a Function Definition }

procedure DoFunc(n: char);

begin

Match('(');

Match(')');

Match('{');

Match('}');

if Typ = ' ' then Typ := 'i';

Writeln(Class, Sign, Typ, ' function ', n);

end;

{–}

{ Process a Data Declaration }

procedure DoData(n: char);

begin

if Typ = ' ' then Expected('Type declaration');

Writeln(Class, Sign, Typ, ' data ', n);

while Look = ',' do begin

Match(',');

n := GetName;

WriteLn(Class, Sign, Typ, ' data ', n);

end;

Match(';');

end;

{–}

Так как мы еще далеки от получения выполнимого кода, я решил чтобы эти две подпрограммы только сообщали нам, что они нашли.

Протестируйте эту программу. Для объявления данных дайте список, разделенный запятыми. Мы не можем пока еще обрабатывать инициализаторы. Мы также не можем обрабатывать списки параметров функций но символы «(){}» должны быть.

Мы все еще очень далеко от того, чтобы иметь компилятор C, но то что у нас есть обрабатывает правильные виды входных данных и распознает и хорошие и плохие входных данные. В процессе этого естественная структура компилятора начинает принимать форму.

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

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

Мы могли бы развивать обе эти структуры намного дальше, но помните, что наша цель здесь не в том, чтобы построить компилятор C или Pascal, а скорее изучать компиляторы вообще. Для тех из вас, кто хотят иметь дело с Pascal или C, я надеюсь, что дал вам достаточно начал чтобы вы могли взять их отсюда (хотя вам скоро понадобятся некоторые вещи, которые мы еще не охватили здесь, такие как типы и вызовы процедур). Остальные будьте со мной в следующей главе. Там я проведу вас через разработку законченного компилятора для TINY, подмножества KISS.


Логин
Пароль
Запомнить меня