REBOL: Медитация на интерпретацию
Рабочие материалы по почти функциональному и почти объектному языку программирования

Медитация на интерпретацию

© Ladislav Mecir — основа статьи
© «Бакава» Анохин — перевод, дополнения и изменения

Содержание: 1. Фаза создания
2. Фаза загрузки
3. Фаза исполнения
4. Симуляция (простой интерпретатор Ребола, написанный на Реболе)
5. Вычисление слов
6. Рекурсивные вычисления

Интерпретация исходного кода в Реболе состоит из трёх основных фаз:

1. Фаза создания

В первой фазе функция make создаёт блок. Созданный блок заполняется указателями на значения, полученные разбором исходного кода согласно правилам соответствующих типов данных.

Все слова (т.е. все значения типа any-word!), на которые ссылается новый блок, являются несвязанными, т.е. не имеют никакой информации о контексте. О контексте мы поговорим в статье Контекст против разума.

2. Фаза загрузки

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

3. Фаза исполнения

Функции исполнения DO плевать на то, является ли интерпретируемый блок результатом предыдущей фазы, или это результат любых других телодвижений. Эта модель описывает поведение функции DO при обработке блоков и скобок.

Функция DO обрабатывает значения из интерпретирумого блока по очереди. Встретив в блоке очередное значение, она первым делом проверяет его тип. Для некоторых значений это подразумевает довольно хитроумное поведение — значение обрабатывается таким образом, чтобы получить результат — а в иных случаях дополнительных вычислений не требуется и значение представляет собой результат вычисления самого себя.

Значения первого рода (сложные) называют активными значениями:

active?: func [
{finds out, if a Rebol value is active}
value [any-type!]
] [
parse head insert/only copy [] get/any ’value [
any-function! | get-word! | lit-word! |
set-word! | word! | lit-path! | paren! |
path! | set-path!
]
]

Неактивные значения ведут себя просто, а вот с активными посложнее. Заметим важную вещь, отличающую Ребол от других языков. Значения типа block! не являются активными. Тем не менее, в Реболе есть активный аналог типа block!. Догадаетесь, какой?

Опишем поведение значений с типом any-function!. Для того, чтобы вычислить их, функция DO должна собрать их аргументы и передать их вычисляемой функции.

Как вы могли уже догадаться, значения типа paren! в Реболе являются активными аналогами блокам.

Когда функция DO завершает вычисления всех значений в блоке, последнее полученное значение становится результатом вычислений.

Исключение: если функция DO вычисляет значение типа error! и это значение не используется в качестве аргумента для функции, обрабатывающей такие аргументы, интерпретатор выдаёт сообщение об ошибке.

4. Симуляция (простой интерпретатор Ребола, написанный на Реболе)

Ниже приведена функция, являющаяся простым интерпретатором, и способная проинтерпретировать одну строку текста, введённого с клавиатуры.

simulation: func [/local input-line created-block loaded-block] [
; step #0, INPUT
input-line: ask «!>>»
; step #1, MAKE
created-block: make block! input-line
; step #2, LOAD
loaded-block: load created-block
; step #3, DO
do loaded-block
; done
]

5. Вычисление слов

Слова в Реболе (а это все значения типа word!) ведут себя посложнее. Когда слово вычисляется, функция DO сперва выбирает значение, на которое ссылается слово. Если вычисляемое слово не имеет контекста, функция DO не может выбрать значение, и прекращает вычисления, вываливая в консоль ошибку:

>> b: make block! «a» ; == [a]
== [a]
>> do b
** Script Error: a word has no context
** Near: a

Дальнейшие действия зависят от типа выбранного значения. Если значение не установлено (т.е. принадлежит типу unset!), этими действиями будет остановка вычислений.

>> do [a]
** Script Error: a has no value
** Near: a

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

Типичными представителями словоактивного типа являются значения типа any-function!. Когда такое значение является значением вычисляемого слова, функция DO собирает все аргументы для этой функции и вызывает её.

В постепенно уменьшающемся списке словоактивных типов данных всё ещё числятся типы lit-word! и lit-path! (и, по-моему, напрасно). С другой стороны, поведение значений типа unset! можно сделать более напоминающим функции:

word-active?: func [
{finds out, if a Rebol value is word-active}
value [any-type!]
] [
parse head insert/only copy [] get/any ’value [
unset! | any-function! | lit-word! | lit-path!
]
]

6. Рекурсивные вычисления

Словоактивные значения могут вести себя рекурсивно. Например:

; рекурсивная функция
factorial: func [n] [
either n <= 1 [1] [n * factorial n - 1]
]

>> factorial 5
== 120

На самом деле, мы можем ввести рекурсию даже в неактивные значения, если правильно воспользуемся активными:

; рекурсивный блок
factorial-block: [
either n <= 1 [1] [n: n - 1 (n + 1) * do factorial-block]
]
n: 5

>> do factorial-block
== 120
Formatted with MakeDoc2 by REBOL - 21-Apr-2007