С полным Си все не так просто. Проблема возникает потому, что в полном Си функции могут также иметь типы. Так что когда компилятор видит ключевое слово типа «int» он все еще не знает ожидать ли объявления данных или определение функции. Дела становятся более сложными так как следующим токеном может быть не имя... он может начинаться с "*" или "(" или комбинаций этих двух.
Точнее говоря, БНФ для полного Си начинается с:
::= [
Теперь вы можете увидеть проблему: первые две части обьявлений для данных и функций могут быть одинаковыми. Из-за неоднозначности в этой грамматике выше, она является неподходящей для рекурсивного синтаксического анализатора. Можем ли мы преобразовать ее в одну из подходящих? Да, с небольшой работой. Предположим мы запишем ее таким образом:
Мы можем написать подпрограмму синтаксичесого анализа для определений классов и типов и позволять им отложить их сведения и продолжать выполнение даже не зная обрабатывается ли функция или объявление данных.
Для начала, наберите следующую версию основной программы:
{–}
{ Main Program }
begin
Init;
while Look <> ^Z do begin
GetClass;
GetType;
TopDecl;
end;
end.
{–}
На первый раз просто сделайте три процедуры-заглушки которые ничего не делают, а только вызывают GetChar.
Работает ли эта программа? Хорошо, было бы трудно не сделать это, так как мы в действительности не требовали от нее какой-либо работы. Уже говорилось, что компилятор Си примет практически все без отказа. Несомненно это правда для этого компилятора, потому что в действительности все, что он делает, это съедает входные символы до тех пор, пока не найдет ^Z.
Затем давайте заставим 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: