ВВЕДЕНИЕ В трех первых частях этой серии
мы рассмотрели синтаксический анализ и компиляцию математических выражений,
постепенно и методично пройдя от очень простых односимвольных "выражений",
состоящих из одного терма, через выражения в более общей форме и закончив
достаточно полным синтаксическим анализатором, способным анализировать
и транслировать операции присваивания с многосимвольными токенами, вложенными
пробелами и вызовами функций. Сейчас я собираюсь провести вас сквозь этот
процесс еще раз, но уже с целью интерпретации а не компиляции объектного
кода.
x = 2 * y + 3 В компиляторе мы хотим заставить центральный процессор
выполнить это присваивание во время выполнения. Сам транслятор не выполняет
никаких арифметических операций… он только выдает объектный код, который
заставит процессор сделать это когда код выполнится. В примере выше компилятор
выдал бы код для вычисления значения выражения и сохранения результата
в переменной x.
x = x + 3 - 2 - (5 - 4) наш компилятор будет покорно выплевывать поток из 18 инструкций для загрузки каждого параметра в регистры, выполнения арифметических действий и сохранения результата. Ленивая оценка распознала бы, что выражение, содержащее константы, может быть рассчитано во время компиляции и уменьшила бы выражение до x = x + 0 Даже ленивая оценка была бы затем достаточно умной, чтобы понять, что это эквивалентно x = x, что совсем не требует никаких действий. Мы смогли уменьшить
18 инструкций до нуля!
ИНТЕРПРЕТАТОР Итак, теперь, когда вы знаете
почему мы принялись за все это, давайте начнем. Просто для того,
чтобы дать вам практику, мы начнем с пустого Сradle и создадим транслятор
заново. На этот раз, конечно, мы сможем двигаться немного быстрее.
{--------------------------------------------------------------}
function GetNum: integer;
Затем напишите следующую версию Expression: {---------------------------------------------------------------}
function Expression: integer;
И, наконец, вставьте Writeln(Expression); в конец основной программы. Теперь откомпилируйте и протестируйте.
{---------------------------------------------------------------}
function Expression: integer;
Структура Expression, конечно,
схожа с тем, что мы делали ранее, так что мы не будем иметь слишком много
проблем при ее отладке. Тем не менее это была серьезная разработка, не
так ли? Процедуры Add и Subtract исчезли! Причина в том, что для выполнения
необходимых действий нужны оба аргумента операции. Я мог бы сохранить эти
процедуры и передавать в них значение выражения на данный момент, содержащееся
в Value. Но мне показалось более правильным оставить Value как строго локальную
переменную, что означает, что код для Add и Subtract должен быть помещен
вместе. Этот результат наводит на мысль, что хотя разработанная нами структура
была хорошей и проверенной для нашей бесхитростной схемы трансляции, она
возможно не могла бы использоваться с ленивой оценкой. Эту небольшую интересную
новость нам возможно необходимо иметь в виду в будущем.
{---------------------------------------------------------------}
function Term: integer;
Теперь испробуйте. Не забудьте
двух вещей: во-первых мы имеем дело с целочисленным делением, поэтому,
например, 1/3 выдаст ноль. Во-вторых, даже если мы можем получать на выходе
многозначные числа, входные числа все еще ограничены одиночной цифрой.
{--------------------------------------------------------------}
function GetNum: integer;
Если вы откомпилировали и протестировали эту версию интерпретатора, следующим шагом должна быть установка функции Factor, поддерживающей выражения в скобках. Мы задержимся немного дольше на именах переменных. Сначала измените ссылку на GetNum в функции Term, чтобы вместо нее вызывалась функция Factor. Теперь наберите следующую версию Factor: {---------------------------------------------------------------}
function Expression: integer; Forward; function Factor: integer;
Это было довольно легко, а? Мы быстро пришли к полезному интерпретатору. НЕМНОГО ФИЛОСОФИИ Прежде чем двинуться дальше,
я бы хотел обратить ваше внимание на кое-что. Я говорю о концепции, которую
мы использовали на всех этих уроках, но которую я явно не упомянул до сих
пор. Я думаю, что пришло время сделать это, так как эта концепция настолько
полезная и настолько мощная, что она стирает все различия между тривиально
простым синтаксическим анализатором и тем, который слишком сложен для того,
чтобы иметь с ним дело.
Table: Array['A'..'Z'] of integer; Мы также должны инициализировать массив, поэтому добавьте следующую процедуру: {---------------------------------------------------------------}
procedure InitTable;
Вы также должны вставить вызов InitTable в процедуру Init.
Не забудьте сделать это, иначе результат может удивить вас!
{---------------------------------------------------------------}
function Expression: integer; Forward; function Factor: integer;
Как всегда откомпилируйте и протестируйте
эту версию программы Даже притом, что все переменные сейчас равны нулю,
по крайней мере мы можем правильно анализировать законченные выражения,
так же как и отлавливать любые неправильно оформленные.
{--------------------------------------------------------------}
procedure Assignment;
Чтобы протестировать ее, я добавил
временный оператор write в основную программу для вывода значения A. Затем
я протестировал ее с различными присваиваниями.
{--------------------------------------------------------------}
procedure NewLine;
Вставьте эту процедуру в любом удобном месте… я поместил ее сразу после Match. Теперь перепишите основную программу, чтобы она выглядела следующим образом: {--------------------------------------------------------------}
begin
Обратите внимание, что проверка
на CR теперь исчезла и что также нет проверки на ошибку непосредственно
внутри NewLine. Это нормально… все оставшиеся фиктивные символы будут отловлены
в начале следующей операции присваивания.
{--------------------------------------------------------------}
procedure Input;
{--------------------------------------------------------------}
procedure Output;
Я полагаю они не очень причудливы…
например нет никакого символа приглашения при вводе… но они делают свою
работу.
{--------------------------------------------------------------}
begin
Теперь вы закончили создание
настоящего, работающего интерпретатора. Он довольно скудный, но работает
совсем как "большой мальчик". Он включает три вида операторов (и может
различить их!), 26 переменных и операторы ввода/вывода. Единственное, в
чем он действительно испытывает недостаток - это операторы управления,
подпрограммы и некоторые виды функций для редактирования программы. Функции
редактирования программ я собираюсь пропустить. В конце концов, мы здесь
не для того, чтобы создать готовый продукт, а чтобы учиться. Управляющие
операторы мы раскроем в следующей главе, а подпрограммы вскоре после нее.
Я стремлюсь продолжать дальше, поэтому мы оставим интерпретатор в его текущем
состоянии.
|