Лекции по построению компилятора на Pascal

       

ВВОД/ВЫВОД


Теперь  у нас есть полный, работающий язык, за исключением одного небольшого смущающего факта: у нас нет никакого способа получить или вывести данные. Нам нужны подпрограммы ввода/вывода.

Современное соглашение, установленное в C и продолженное в Ada и Modula-2, состоит в том, чтобы вывести I/O операторы из самого языка и просто включить их в библиотеку подпрограмм. Это было бы прекрасно, за исключением того, что мы пока не имеем никаких средств поддержки подпрограмм. В любом случае, с этим подходом вы столкнетесь с проблемой переменной длины списка параметров. В Паскале I/O операторы встроены в язык, поэтому это единственные операторы, для которых список параметров может иметь переменное число элементов. В C мы примиряемся с клуджами типа scanf и printf и должны передавать количество параметров в вызываемую процедуру. В Ada и Modula-2 мы должны использовать неудобный (и медленный!) способ отдельного вызова для каждого аргумента.

Так что я думаю, что предпочитаю Паскалевский подход встраивания подпрограмм ввода/вывода, даже если мы не нуждаемся в этом.

Как обычно, для этого нам нужны еще несколько подпрограмм генерации кода. Они, оказывается, самые простые из всех, потому что все, что мы делаем это вызываем библиотечные процедуры для выполнения работы.

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

{ Read Variable to Primary Register }

procedure ReadVar;

begin

   EmitLn('BSR READ');

   Store(Value);

end;

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

{ Write Variable from Primary Register }



procedure WriteVar;

begin

   EmitLn('BSR WRITE');

end;

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

Идея состоит в том, что READ загружает значение из входного потока в D0, а WRITE выводит его оттуда.

Эти две процедуры представляют собой нашу первую встречу с потребностью в библиотечных процедурах... компонентах Run Time Library (RTL). Конечно кто-то (а именно мы) должен написать эти подпрограммы, но они не являются непосредственно частью компилятора. Я даже не буду беспокоиться о том, чтобы показать здесь эти подпрограммы, так как они очевидно очень ОС-зависимы. Я просто скажу, что для SK*DOS они особенно просты... почти тривиальны. Одна из причин, по которым я не буду показывать их здесь в том, что вы можете добавлять новые виды возможностей, например приглашение в READ или возможность пользователю повторить ошибочный ввод.


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

Так как нам теперь нужно загружать ее, мы должны добавить ее загрузку в процедуру Header:

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

{ Write Header Info }

procedure Header;

begin

   WriteLn('WARMST', TAB, 'EQU $A01E');

   EmitLn('LIB TINYLIB');

end;

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

Она возьмет на себя эту часть работы. Теперь нам также необходимо распознавать команды ввода и вывода. Мы можем сделать это добавив еще два ключевых слова в наш список:

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

{ Definition of Keywords and Token Types }

const NKW =   11;

      NKW1 = 12;

const KWlist: array[1..NKW] of Symbol =

              ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',

               'READ',    'WRITE',    'VAR',    'BEGIN',   'END',

'PROGRAM');

const KWcode: string[NKW1] = 'xileweRWvbep';

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

 (Обратите внимание, что здесь я использую кода в верхнем регистре чтобы избежать конфликта с 'w' из WHILE.)

Затем нам нужны процедуры для обработки оператора ввода/вывода и его списка параметров:

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

{ Process a Read Statement }

procedure DoRead;

begin

   Match('(');

   GetName;

   ReadVar;

   while Look = ',' do begin

      Match(',');

      GetName;

      ReadVar;

   end;

   Match(')');

end;

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



{ Process a Write Statement }

procedure DoWrite;

begin

   Match('(');

   Expression;

   WriteVar;

   while Look = ',' do begin

      Match(',');

      Expression;

      WriteVar;

   end;

   Match(')');

end;

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

Наконец, мы должны расширить процедуру Block для поддержки новых типов операторов:

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

{ Parse and Translate a Block of Statements }

procedure Block;

begin

   Scan;

   while not(Token in ['e', 'l']) do begin

      case Token of

       'i': DoIf;

       'w': DoWhile;

       'R': DoRead;

       'W': DoWrite;

      else Assignment;

      end;

      Scan;

   end;

end;

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

На этом все. Теперь у нас есть язык!


Содержание раздела