Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[177]

так что последовательность, которая является телом, компилируется в этом расширенном окружении. В каждой точке компиляции compile-variable и compile-assignment используют окружение времени компиляции для порождения соответствующих лексических адресов.

Упражнения с 5.39 по 5.43 описывают, как завершить этот набросок лексической адресации и включить в компилятор лексический поиск. В упражнении 5.44 описывается еще один способ использовать окружение времени компиляции.

Упражнение 5.39.

Напишите процедуру lexical-address-lookup, которая реализует новую операцию поиска. Она должна брать два аргумента - лексический адрес и окружение времени компиляции, - и возвращать значение переменной, находящейся по указанному лексическому адресу. Lexical-address-lookup должна сообщать об ошибке, если значением переменной является символ *unassigned*.46 Кроме того, напишите процедуру lexical-address-set!, реализующую операцию, которая изменяет значение переменной по указанному лексическому адресу.

Упражнение 5.40.

Модифицируйте компилятор так, чтобы он поддерживал окружение времени компиляции, как описано выше. А именно, добавьте аргумент-окружение к compile и всем генераторам кода, и расширяйте его в compile-lambda-body.

Упражнение 5.41.

Напишите процедуру find-variable, которая в качестве аргументов принимает переменную и окружение времени компиляции, а возвращает лексический адрес переменной по отношению к этому окружению. Например, во фрагменте программы, который приведен выше, окружение времени компиляции при обработке выражения (e1) равно ((y z) (a b c d e) (x y)). Find-variable должна давать

(find-variable c ((y z) (a b c d e) (x y))) (1 2)

(find-variable x ((y z) (a b c d e) (x y))) (2 0)

(find-variable w ((y z) (a b c d e) (x y)))

not-found

Упражнение 5.42.

С помощью find-variable из упражнения 5.41 перепишите compile-variable и compile-assignment так, чтобы они порождали команды лексической адресации. В случаях, когда find-variable возвращает not-found (то есть, когда переменной нет в окружении времени компиляции), нужно заставлять генераторы кода использовать, как и раньше, операции вычислителя для поиска связывания. (Единственное место, где может оказаться переменная, не найденная во время компиляции - это глобальное окружение, которое является частью окружения времени выполнения, но не окружения

46Эта модификация в поиске переменной требуется в том случае, если мы реализуем просмотр текста и уничтожение внутренних определений (упражнение 5.43). Чтобы лексическая адресация работала, их следует уничтожить.


времени компиляции.47 Поэтому, если хотите, можете заставить операции вычислителя искать сразу в глобальном окружении, которое можно получить с помощью операции (op get-global-environment) , а не в полном локальном окружении, которое хранится в env.) Проверьте измененный компилятор на нескольких простых примерах, например, на вложенной комбинации lambda из начала этого раздела.

Упражнение 5.43.

В разделе 4.1.6 мы показали, что определения внутри блочной структуры не следует рассматривать как «настоящие» define. Вместо этого тело процедуры следует интерпретировать так, как будто внутренние переменные, определяемые через define, были введены как обыкновенные переменные lambda, а их настоящее значение было им присвоено через set! . В разделе 4.1.6 и упражнении 4.16 показывалось, как можно изменить метациклический интерпретатор и добиться этого просмотром внутренних определений. Измените компилятор так, чтобы он проводил такое же преобразование, прежде чем компилировать тело процедуры.

Упражнение 5.44.

В этом разделе мы в основном говорили о том, как с помощью окружения времени компиляции порождать лексические адреса. Однако такие окружения можно использовать и другими способами. Например, в упражнении 5.38 мы повысили эффективность скомпилированного кода путем явного кодирования элементарных процедур. Наша реализация обрабатывала имена явно кодируемых процедур как зарезервированные слова. Если бы какая-либо программа переопределяла такое имя, механизм, описанный в упражнении 5.38, продолжал бы явно кодировать его как примитив и игнорировал бы новое связывание. Рассмотрим, например, процедуру

(lambda (+ * a b x y)

(+ (* a x) (* b y)))

которая вычисляет линейную комбинацию x и y. Мы могли бы вызвать такую процедуру с аргументами +matrix, *matrix и четырьмя матрицами, но явно кодирующий компилятор по-прежнему вставлял бы код для + и * в (+ (* a x) (* b y)) как для примитивов + и *. Измените компилятор с явным кодированием так, чтобы он проверял окружение времени компиляции и на его основе порождал правильный код для выражений, в которых встречаются имена элементарных процедур. (Код будет работать правильно, пока программа не применяет к этим именам define или set! .)

5.5.7 Связь скомпилированного кода с вычислителем

Пока что мы не объяснили, как загружать скомпилированный код в машину-вычислитель и как его запускать. Предположим, что машина-вычислитель с явным управлением определена как в разделе 5.4.4 с дополнительными операциями из примечания 38. Мы реализуем процедуру compile-and-go, которая компилирует выражение на Scheme, загружает получившийся код в машину-вычислитель, а затем заставляет машину исполнить код в глобальном окружении вычислителя, напечатать результат и войти в управляющий цикл. Вычислитель мы изменим так, чтобы интерпретируемые выражения могли вызывать

47Для доступа к переменным в глобальном окружении нельзя использовать лексические адреса, поскольку эти имена можно определять и переопределять интерактивно когда угодно. Если внутренние определения вычищены, как в упражнении 5.43, то компилятор видит только определения верхнего уровня, которые действуют на глобальное окружение. Компиляция определения не приводит к тому, что определяемое имя вводится в окружение времени компиляции.


не только интерпретируемые, но и скомпилированные процедуры. После этого мы можем поместить скомпилированную процедуру в машину и вызвать ее с помощью интерпретатора:

(compile-and-go (define (factorial n)

(if (= n 1) 1

(* (factorial (- n 1)) n))))

;;; Значение EC-Eval: ok

; ;; Ввод EC-Eval:

(factorial 5)

;;; Значение EC-Eval:

120

Для того, чтобы вычислитель мог обрабатывать скомпилированные процедуры (например, выполнить вызов factorial, как показано выше), нужно изменить код apply-dispatch (раздел 5.4.1), чтобы он распознавал их (в отличие от составных и элементарных процедур) и передавал управление прямо во входную точку скомпилированного кода:48

apply-dispatch

(test (op primitive-procedure?) (reg proc))

(branch (label primitive-apply))

(test (op compound-procedure?) (reg proc))

(branch (label compound-apply))

(test (op compiled-procedure?) (reg proc))

(branch (label compiled-apply))

(goto (label unknown-procedure-type))

compiled-apply

(restore continue)

(assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val))

Обратите внимание на восстановление continue в compiled-apply. Вспомним, что вычислитель был так устроен, что при выполнении apply-dis-patch продолжение находилось на вершине стека. С другой стороны, входная точка скомпилированного кода ожидает, что продолжение будет находиться в continue, так что этот регистр надо восстановить, прежде чем передать управление скомпилированному коду.

Для того, чтобы позволить нам запускать скомпилированный код при запуске вычислителя, мы в начало машины добавляем команду branch, которая переводит машину на новую точку входа, если установлен регистр flag.49

48 Разумеется, скомпилированные процедуры являются составными (неэлементарными) точно так же, как и интерпретируемые. Однако ради совместимости с терминологией, которая используется при обсуждении вычислителя с явным управлением, мы в этом разделе будем считать, что слово «составная» означает «интерпретируемая» (а не скомпилированная).

49Теперь, когда код вычислителя начинается с branch, нам перед запуском машины всегда нужно устанавливать значение flag. Для того, чтобы запустить машину в обычном управляющем цикле, можно использовать



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]