- •Лекции по построению компилятора на Pascal Автор неизвестен Оглавление
- •1. Введение введение
- •2. Синтаксический анализ выражений начало
- •Одиночные цифры
- •Выражения с двумя цифрами
- •Общая форма выражения
- •Использование стека
- •Умножение и деление
- •Круглые скобки
- •Унарный минус
- •Слово об оптимизации
- •3. Снова выражения введение
- •Переменные
- •Функции
- •Подробнее об обработке ошибок
- •Присваивание
- •Многосимвольные токены.
- •Пробелы
- •4. Интерпретаторы введение
- •Интерпретатор
- •Немного философии
- •5. Управляющие конструкции введение
- •Немного основ
- •Оператор if
- •Оператор while
- •Оператор loop
- •Цикл for
- •Оператор do
- •Оператор break
- •Заключение
- •6. Булевы выражения введение
- •Грамматика
- •Операторы отношений
- •Исправление грамматики
- •Синтаксический анализатор
- •Объединение с управляющими конструкциями
- •Добавление присваиваний
- •7. Лексический анализ введение
- •Лексический анализ
- •Конечные автоматы и альтернативы
- •Эксперименты по сканированию
- •Конечные автоматы
- •Новые строки
- •Операторы
- •Списки, запятые и командные строки.
- •Становится интересней
- •Возвращение символа
- •Распределенные сканеры против централизованных
- •Объединение сканера и парсера
- •Пара комментариев:
- •Заключение
- •8. Немного философии введение
- •Дорога домой
- •Почему это так просто?
- •Здесь нет ничего сложного!
- •Заключение
- •9. Вид сверху введение
- •Верхний уровень
- •Структура паскаля
- •Расширение
- •Объявления
- •Структура си
- •10. Представление "tiny" введение
- •Подготовка
- •Объявления
- •Объявления и идентификаторы
- •Инициализаторы
- •Выполнимые утверждения
- •Булева логика
- •Управляющие структуры
- •Лексический анализ
- •Многосимвольные имена переменных
- •Снова операторы отношений
- •Ввод/вывод
- •Заключение
- •11. Пересмотр лексического анализа введение
- •Предпосылка
- •Проблема
- •Решение
- •Исправление компилятора
- •Заключение
- •12. Разное введение
- •Точки с запятой
- •Синтаксический сахар
- •Работа с точками с запятой
- •Компромисс
- •Комментарии
- •Односимвольные разделители
- •Многосимвольные разделители
- •Односторонние комментарии
- •Заключение
- •13. Процедуры введение
- •Последнее отклонение
- •Основа для экспериментов
- •Объявление процедуры
- •Вызов процедуры
- •Передача параметров
- •Семантика параметров
- •Передача по значению
- •Что неправильно?
- •Передача по ссылке
- •Локальные переменные
- •Заключение
- •14. Типы введение
- •Что будет дальше?
- •Добавление записей
- •Распределение памяти
- •Объявление типов
- •Присваивания
- •Трусливый выход
- •Более приемлемое решение
- •Литеральные аргументы
- •Аддитивные выражения
- •Почему так много процедур?
- •Мультипликативные выражения
- •Умножение
- •Деление
- •Завершение
- •Приводить или не приводить
- •Заключение
- •15. Назад в будущее введение
- •Новое начало, старое направление
- •Начинаем заново?
- •Модуль input
- •Модуль output
- •Модуль error
- •Лексический и синтаксический анализ
- •Модуль scanner
- •Решения, решения
- •Синтаксический анализ
- •16. Конструирование модулей введение
- •Совсем как классический?
- •Расширение синтаксического анализатора
- •Термы и выражения
- •Присваивания
- •Булева алгебра
Снова операторы отношений
У нас осталось последнее одно-символьное ограничение - ограничение операторов отношений. Некоторые из операторов отношений действительно состоят из одиночных символов, но другие требуют двух. Это '<=' и '>='. Я также предпочитаю Паскалевское '<>' для "не равно" вместо '#'.
Как вы помните, в главе 7 я указал, что стандартный способ работы с операторами отношений - включить их в список ключевых слов и позволить лексическому анализатору отыскивать их. Но, опять, это требует выполнение полного анализа выражения, тогда как до этого мы у нас была возможность ограничить использование сканера началом утверждения.
Я упомянул тогда, что мы все же можем избежать неприятностей с этим, так как много символьных операторов отношений немного и они ограничены в применении. Было бы легко обрабатывать их просто как специальные случаи и поддерживать их специальным способом.
Требуемые изменения влияют только на подпрограммы генерации кода и процедуры Relation и ее друзей. С начала, нам понадобятся еще две подпрограммы генерации кода:
{---------------------------------------------------------------}
{ Set D0 If Compare was <= }
procedure SetLessOrEqual; begin EmitLn('SGE D0'); EmitLn('EXT D0'); end;
{---------------------------------------------------------------} { Set D0 If Compare was >= }
procedure SetGreaterOrEqual; begin EmitLn('SLE D0'); EmitLn('EXT D0'); end;
{---------------------------------------------------------------}
Затем измените подпрограммы анализа отношений как показано ниже:
{---------------------------------------------------------------}
{ Recognize and Translate a Relational "Less Than or Equal" }
procedure LessOrEqual; begin Match('='); Expression; PopCompare; SetLessOrEqual; end;
{---------------------------------------------------------------} { Recognize and Translate a Relational "Not Equals" }
procedure NotEqual; begin Match('>'); Expression; PopCompare; SetNEqual; end;
{---------------------------------------------------------------} { Recognize and Translate a Relational "Less Than" }
procedure Less; begin Match('<'); case Look of '=': LessOrEqual; '>': NotEqual; else begin Expression; PopCompare; SetLess; end; end; end;
{---------------------------------------------------------------} { Recognize and Translate a Relational "Greater Than" }
procedure Greater; begin Match('>'); if Look = '=' then begin Match('='); Expression; PopCompare; SetGreaterOrEqual; end else begin Expression; PopCompare; SetGreater; end; end;
{---------------------------------------------------------------}
Это все, что требуется. Теперь вы можете обрабатывать все операторы отношений. Попробуйте.
Ввод/вывод
Теперь у нас есть полный, работающий язык, за исключением одного небольшого смущающего факта: у нас нет никакого способа получить или вывести данные. Нам нужны подпрограммы ввода/вывода.
Современное соглашение, установленное в 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;
{--------------------------------------------------------------}
На этом все. Теперь у нас есть язык!