- •Лекции по построению компилятора на 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. Конструирование модулей введение
- •Совсем как классический?
- •Расширение синтаксического анализатора
- •Термы и выражения
- •Присваивания
- •Булева алгебра
Что будет дальше?
Прежде чем мы погрузимся в это занятие, я думаю вам бы хотелось знать что мы сейчас собираемся делать... особенно после того, как прошло столько много времени с прошлой главы.
Тем временем я не бездействовал. Я разбил компилятор на модули. Одна из проблем, с которыми я столкнулся, в том, что так как мы охватывали новые области и вследствие этого расширяли возможности компилятора TINY, он становился все больше и больше. Я понял пару глав назад, что это приводило к затруднениям и именно поэтому я возвратился к использованию только фрагментов компилятора в последней и этой главах. Кажется просто глупо заново воспроизводить код для, скажем, обработки булевых исключающих ИЛИ, когда тема дискуссии - передача параметров.
Очевидным способом получит свой пирог и съесть его также является разбиение компилятора на раздельно компилируемые модули и, конечно, модули Turbo Pascal являются для этого идеальным средством. Это позволит нам скрыть некоторый довольно сложный код (такой как полный синтаксический анализ арифметических и булевых выражений) в одиночный модуль и просто вытаскивать его всякий раз когда он необходим. При таком способе единственным кодом, который я должен буду воспроизводить в этих главах, будет код который непосредственно касается обсуждаемого вопроса.
Я также игрался с Turbo 5.5 который, конечно, включает Борландовские объектно-ориентированные расширения Паскаля. Я не решил, использовать ли эти возможности, по двум причинам. Прежде всего, многие из вас, кто следовал за этой серией, могут все еще не иметь 5.5 и я конечно не хочу вынуждать кого-либо пойти и купить новый компилятор только для того, чтобы завершить эту серию. Во-вторых, я не убежден, что ОО расширения имеют такое большое значение для этого приложения. Мы обсуждали кое-что из этого на форуме CLM на CompuServe, и пока что мы не нашли никакой убедительной причины для использования ОО конструкции. Это одна из тех областей, где я мог бы использовать некоторую обратную связь с читателями. Кто-нибудь хочет проголосовать за Turbo 5.5 и ООП? В любом случае после следующих нескольких глав этой серии я планирую предоставить вам законченный набор модулей а также законченные функционирующие компиляторы. Планом фактически предусмотрено три компилятора: один для одно-символьной версии TINY (для использования в наших экспериментах), один для TINY и один для KISS. Я достаточно четко выделил различия между TINY и KISS:
-
TINY будет поддерживать только два типа данных: символьный и 16-разрядное целое число. Я могу также попробовать сделать что-нибудь со строками, так как без них компилятор был бы довольно бесполезным. KISS будет поддерживать все обычные простые типы, включая массивы и даже числа с плавающей точкой.
-
TINY будет иметь только две управляющие конструкции IF и WHILE. KISS будет поддерживать очень богатый набор конструкций включая одну, которую мы не обсуждали здесь ранее... CASE.
-
KISS будет поддерживать раздельно компилируемые модули.
Одно предостережение: так как я все еще не знаю достаточно об ассемблере для 80x86, все эти модули компилятора все еще будут написаны для поддержки кода 68000. Однако в программах, которые я планирую вам представить, вся генерация кода была тщательно изолирована в отдельном модуле, так что любой предприимчивый студент смог бы перенастроить их на любой другой процессор. Эта задача "оставлена как упражнение для студента". Я сделаю предложение прямо здесь и сейчас: с человеком, который предоставит нам первый надежный перевод для 80x86, я буду счастлив обсудить коллективные авторские права и авторские отчисления от предстоящей книги.
Но хватит говорить. Давайте приступим к изучению типов. Как я сказал ранее, мы будем делать это как и в последней главе: выполняя эксперименты с использованием одно-символьных токенов.
ТАБЛИЦА ИДЕНТИФИКАТОРОВ
Должно быть очевидным, что если мы собираемся работать с переменными различных типов, нам понадобится какое-то место для записи их типов. Очевидным средством для этого является таблица идентификаторов и мы уже использовали ее например для различия между локальными и глобальными переменными и между переменными и процедурами. Структура таблицы идентификаторов для одно-символьных токенов особенно проста и мы использовали ее прежде несколько раз. Для работы с ней, мы возьмем некоторые процедуры, которые мы использовали раньше.
Сначала, нам необходимо объявить саму таблицу идентификаторов:
{--------------------------------------------------------------}
{ Variable Declarations }
var Look: char; { Lookahead Character }
ST: Array['A'..'Z'] of char; { *** ДОБАВЬТЕ ЭТУ СТРОКУ ***} {--------------------------------------------------------------}
Затем мы должны удостовериться, что она инициализируется в процедуре Init:
{--------------------------------------------------------------} { Initialize }
procedure Init; var i: char; begin for i := 'A' to 'Z' do ST[i] := '?'; GetChar; end;
{--------------------------------------------------------------}
Следующая процедура в действительности нам не нужна, но она будет полезна для отладки. Все, что она делает, это формирует дамп содержимого таблицы идентификаторов:
{--------------------------------------------------------------}
{ Dump the Symbol Table }
procedure DumpTable; var i: char; begin for i := 'A' to 'Z' do WriteLn(i, ' ', ST[i]); end;
{--------------------------------------------------------------}
В действительности не имеет значения, где вы поместите эту процедуру... я планирую группировать все подпрограммы таблицы идентификаторов вместе, так что я поместил ее сразу после процедур сообщений об ошибках.
Если вы осторожный тип (как я), вам возможно захотелось бы начать с тестовой программы, которая ничего не делает а просто инициализирует таблицу и затем создает ее дамп. Только для того, чтобы быть уверенным, что все мы находимся на одной волне, ниже я воспроизвожу всю программу, дополненную новыми процедурами. Заметьте, что эта версия включает поддержку пробелов:
{--------------------------------------------------------------}
program Types;
{--------------------------------------------------------------} { Constant Declarations }
const TAB = ^I; CR = ^M; LF = ^J;
{--------------------------------------------------------------} { Variable Declarations }
var Look: char; { Lookahead Character }
ST: Array['A'..'Z'] of char;
{--------------------------------------------------------------} { Read New Character From Input Stream }
procedure GetChar; begin Read(Look); end;
{--------------------------------------------------------------} { Report an Error }
procedure Error(s: string); begin WriteLn; WriteLn(^G, 'Error: ', s, '.'); end;
{--------------------------------------------------------------} { Report Error and Halt }
procedure Abort(s: string); begin Error(s); Halt; end;
{--------------------------------------------------------------} { Report What Was Expected }
procedure Expected(s: string); begin Abort(s + ' Expected'); end;
{--------------------------------------------------------------} { Dump the Symbol Table }
procedure DumpTable; var i: char; begin for i := 'A' to 'Z' do WriteLn(i, ' ', ST[i]); end;
{--------------------------------------------------------------} { Recognize an Alpha Character }
function IsAlpha(c: char): boolean; begin IsAlpha := UpCase(c) in ['A'..'Z']; end;
{--------------------------------------------------------------} { Recognize a Decimal Digit }
function IsDigit(c: char): boolean; begin IsDigit := c in ['0'..'9']; end;
{--------------------------------------------------------------} { Recognize an AlphaNumeric Character }
function IsAlNum(c: char): boolean; begin IsAlNum := IsAlpha(c) or IsDigit(c); end;
{--------------------------------------------------------------} { Recognize an Addop }
function IsAddop(c: char): boolean; begin IsAddop := c in ['+', '-']; end;
{--------------------------------------------------------------} { Recognize a Mulop }
function IsMulop(c: char): boolean; begin IsMulop := c in ['*', '/']; end;
{--------------------------------------------------------------} { Recognize a Boolean Orop }
function IsOrop(c: char): boolean; begin IsOrop := c in ['|', '~']; end;
{--------------------------------------------------------------} { Recognize a Relop }
function IsRelop(c: char): boolean; begin IsRelop := c in ['=', '#', '<', '>']; end;
{--------------------------------------------------------------} { Recognize White Space }
function IsWhite(c: char): boolean; begin IsWhite := c in [' ', TAB]; end;
{--------------------------------------------------------------} { Skip Over Leading White Space }
procedure SkipWhite; begin while IsWhite(Look) do GetChar; end;
{--------------------------------------------------------------} { Skip Over an End-of-Line }
procedure Fin; begin if Look = CR then begin GetChar; if Look = LF then GetChar; end; end;
{--------------------------------------------------------------} { Match a Specific Input Character }
procedure Match(x: char); begin if Look = x then GetChar else Expected('''' + x + ''''); SkipWhite; end;
{--------------------------------------------------------------} { Get an Identifier }
function GetName: char; begin if not IsAlpha(Look) then Expected('Name'); GetName := UpCase(Look); GetChar; SkipWhite; end;
{--------------------------------------------------------------} { Get a Number }
function GetNum: char; begin if not IsDigit(Look) then Expected('Integer'); GetNum := Look; GetChar; SkipWhite; end;
{--------------------------------------------------------------} { Output a String with Tab }
procedure Emit(s: string); begin Write(TAB, s); end;
{--------------------------------------------------------------} { Output a String with Tab and CRLF }
procedure EmitLn(s: string); begin Emit(s); WriteLn; end;
{--------------------------------------------------------------} { Initialize }
procedure Init; var i: char; begin for i := 'A' to 'Z' do ST[i] := '?'; GetChar; SkipWhite; end;
{--------------------------------------------------------------} { Main Program }
begin Init; DumpTable; end.
{--------------------------------------------------------------}
ОК, запустите эту программу. Вы должны получить (очень быстро) распечатку всех букв алфавита (потенциальных идентификаторов) сопровождаемых вопросительным знаком. Не очень захватывающе, но это только начало.
Конечно, вообще-то мы хотим видеть типы только тех переменных, которые были определены. Мы можем устранить другие добавив в DumpTable условие IF. Измените цикл следующим образом:
for i := 'A' to 'Z' do
if ST[i] <> '?' then
WriteLn(i, ' ', ST[i]);
Теперь запустите программу снова. Что вы получили?
Хорошо, это даже более скучно чем раньше! Сейчас вообще ничего не выводится, так как в данный момент ни одно из имен не было объявлено. Мы можем немного приправить результат вставив в основную программу несколько операторов, объявляющих несколько записей. Попробуйте такие:
ST['A'] := 'a';
ST['P'] := 'b';
ST['X'] := 'c';
На этот раз, когда вы запустите программу, вы должны получить распечатку, показывающую, что таблица идентификаторов работает правильно.