
338
Глава 4. Метаязыковая абстракция
4.1. Метациклический интерпретатор
Наш интерпретатор Лиспа будет р еализован как программа на Лиспе. Может по-
казаться, что размышления о выполнении Лисп-программ при помощи интерпретатора,
который сам написан на Лиспе, составляют порочн ый круг. Однако вычисление есть
процесс, так что вполне логично описывать процесс вычисления с помощью Лиспа — в
конце концов, это наш инструмент для описания процессов
3
. Интерпретатор, написанный
на языке, который он сам реализует, называется метациклическим (metacircular).
В сущности, метациклический интерпретатор является формулировкой на языке
Scheme модели вычислений с окружениями, описанной в разделе 3.2. Напомн им, ч то
в этой модели было две основные части:
• Чтобы выполнить комбинацию (составное выражение, не являющееся особой фор-
мой), нужно вычислить его подвыражения и затем применить значение подвыражения-
оператора к значениям подвыражений-операндов.
• Чтобы применить составную процедуру к набору аргументов, нужно выполнить
тело процедуры в новом окружении. Для того, чтобы построить это окружение, нуж-
но расширить окружение объекта-процедуры кадром, в котор ом формальные параметры
процедуры связаны с аргументами, к которым процедура применяется.
Эти два правила описывают сущность процесса вычисления, основной цикл, в ко-
тором выражения, которые тре буется выполнить в окружении, сводятся к процедурам,
которые нужно применить к аргументам, а те, в свою очередь, сводятся к новым вы-
ражениям, которые нужно выполнить в новых окружениях, и так дале е, пока мы не
доберемся до символов, чьи значения достаточно найти в окружении , и элементарных
процедур, которые применяю тся напрямую (см. рис. 4.1)
4
. Этот цикл вычисления бу-
дет построен в виде взаимодействия двух основных процедур интерпретатора, eval и
apply, опис анных в разде ле 4.1.1 (см. рис. 4.1).
3
Даже с учетом этого, остаются важные стороны процесса вычисления, которые в нашем интерпретаторе
не проясняются. Самая важная из ни х — точные механизмы того, как одни процедуры вызывают другие
и возвращают значения процедурам, которые их вызвали. Эти вопросы мы рассмотрим в главе 5, где мы
исследуем процесс вычисления более внимательно, реализуя вычислитель как простую регистровую машину.
4
Если нам дается возможность применять примитивы, то что остается сделать для реализации интерпрета-
тора? Задача интерпретатора состоит не в том, чтобы определить примитивы языка, а в том, чтобы обеспечить
связующие элементы — средства комбинирования и абстракции, — которые превращают набор примитивов в
язык. А именно:
• Интерпретатор позволяет работать с вложенными выражениями. Например, чтобы вычислить значение
выражения (+ 1 6), достаточно применения примитивов, но этого недостаточно для работы с выражением
(+ 1 (* 2 3)). Сама по себе элементарная процедура + способна работать только с числами, и если пере-
дать ей аргумент — выражение (* 2 3), она сломается. Одна из важных задач интерпретатора — устроить
вычисление так, чтобы (* 2 3) свелось к значению 6, прежде чем оно будет передано + как аргумент.
• Интерпретатор позволяет использовать переменные. Например, элементарная процедура сложения не
знает, как работать с выражениями вроде (+ x 1). Нам нужен интерпретатор, чтобы следить за переменными
и получать их значения, прежде чем запускать элементарные процедуры.
• Интерпретатор позволяет определять составные процедуры. При этом нужно хранить определения про-
цедур, знать, как эти определения используются при вычислении выражений, и обеспечивать механизм, кото-
рый позволяет процедурам прин имать аргументы.
• Интерпретатор дает особые формы, вычисляющиеся иначе, чем вызовы процедур.