Как всегда откомпилируйте и протестируйте эту версию программы. Даже притом, что все переменные сейчас равны нулю, по крайней мере мы можем правильно анализировать законченные выражения, так же как и отлавливать любые неправильно оформленные.
Я предполагаю вы уже знаете следующий шаг: мы должны добавить операции присваивания, чтобы мы могли помещать что-нибудь в переменные. Сейчас давайте будем "однострочниками", хотя скоро мы сможем обрабатывать множество операторов.
Операция присваивания похожа на то, что мы делали раньше:
Code: |
{ Parse and Translate an Assignment Statement } procedure Assignment; var Name: char; begin Name := GetName; Match('='); Table[Name] := Expression; end; |
Чтобы протестировать ее, я добавил временный оператор write в основную программу для вывода значения A. Затем я протестировал ее с различными присваиваниями.
Конечно, интерпретируемый язык, который может воспринимать только одну строку программы не имеет большой ценности. Поэтому нам нужно обрабатывать множество утверждений. Это просто означает что необходимо поместить цикл вокруг вызова Assignment. Давайте сделаем это сейчас. Но что должно быть критерием выхода из цикла? Рад, что вы спросили, потому что это поднимает вопрос, который мы были способны игнорировать до сих пор.
Одной из наиболее сложных вещей в любом трансляторе является определение момента когда необходимо выйти из данной конструкции и продолжить выполнение. Пока это не было для нас проблемой, потому что мы допускали только одну конструкцию… или выражение или операцию присваивания. Когда мы начинаем добавлять циклы и различные виды операторов, вы найдете, что мы должны быть очень осторожны, чтобы они завершались правильно. Если мы помещаем наш интерпретатор в цикл, то нам нужен способ для выхода из него. В прерывании по концу строки нет ничего хорошего, поскольку с его помощью мы переходим к следующей строке. Мы всегда могли позволить нераспознаваемым символам прерывать выполнение, но это приводило бы к завершению каждой программы сообщением об ошибке, что конечно выглядит несерьезно.
Нам нужен завершающий символ. Я выступаю за завершающую точку в Pascal ("."). Небольшое осложнение состоит в том, что Turbo Pascal завершает каждую нормальную строку двумя символами: возврат каретки (CR) и перевод строки (LF). В конце каждой строки мы должны "съедать" эти символы перед обработкой следующей. Естественным способом было бы сделать это в процедуре Match за исключением того, что сообщение об ошибке Match выводит ожидаемые символы, что для CR и LF не будет выглядеть так хорошо. Для этого нам нужна специальная процедура, которую мы, без сомнения, будем использовать много раз. Вот она:
Code: |
{ Recognize and Skip Over a Newline } procedure NewLine; begin if Look = CR then begin GetChar; if Look = LF then GetChar; end; end; |
Вставьте эту процедуру в любом удобном месте… я поместил ее сразу после Match. Теперь перепишите основную программу, чтобы она выглядела следующим образом:
Code: |
{ Main Program } begin Init; repeat Assignment; NewLine; until Look = '.'; end. |
Обратите внимание, что проверка на CR теперь исчезла и что также нет проверки на ошибку непосредственно внутри NewLine. Это нормально… все оставшиеся фиктивные символы будут отловлены в начале следующей операции присваивания.
Хорошо, сейчас мы имеем функционирующий интерпретатор. Однако, это не дает нам много хорошего, так как у нас нет никакого способа для ввода или вывода данных. Уверен что нам помогут несколько подпрограмм ввода/вывода!
Тогда давайте завершим этот урок добавив подпрограммы ввода/вывода. Так как мы придерживаемся одно-символьных токенов, я буду использовать знак "?" для замены операции чтения, знак "!" для операции записи и символ, немедленно следующий после них, который будет использоваться как одно-символьный "список параметров". Вот эти подпрограммы:
Code: |
{ Input Routine } procedure Input; begin Match('?'); Read(Table[GetName]); end;
{ Output Routine } procedure Output; begin Match('!'); WriteLn(Table[GetName]); end; |
Я полагаю они не очень причудливы… например нет никакого символа приглашения при вводе… но они делают свою работу.
Соответствующие изменения в основной программе показаны ниже. Обратите внимание, что мы используем обычный прием – оператор выбора по текущему предсказывающему символу, чтобы решить что делать.
Code: |
{ Main Program } begin Init; repeat case Look of '?': Input; '!': Output; else Assignment; end; NewLine; until Look = '.'; end. |
Теперь вы закончили создание настоящего, работающего интерпретатора. Он довольно скудный, но работает совсем как "большой мальчик". Он включает три вида операторов (и может различить их!), 26 переменных и операторы ввода/вывода. Единственное, в чем он действительно испытывает недостаток - это операторы управления, подпрограммы и некоторые виды функций для редактирования программы. Функции редактирования программ я собираюсь пропустить. В конце концов, мы здесь не для того, чтобы создать готовый продукт, а чтобы учиться. Управляющие операторы мы раскроем в следующей главе, а подпрограммы вскоре после нее. Я стремлюсь продолжать дальше, поэтому мы оставим интерпретатор в его текущем состоянии.
Я надеюсь, к настоящему времени вы убедились, что ограничение имен одним символом и обработка пробелов это вещи о которых легко позаботиться, как мы сделали это на последнем уроке. На этот раз, если вам захотелось поиграть с этими расширениями, будьте моим гостем… они "оставлены как упражнение для студента". Увидимся в следующий раз.
- << Назад
- Вперёд
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!