Четвёртая часть рассказа.
9 Открываем капот.
Может быть, из академических соображений так поступать
не стоит, однако иногда бывает проще рассказать про причину,
чем про её следствия.
Давайте разберёмся с тем, как работает интерпретатор Scheme,
благо знания нам уже это позволяют сделать. После этого всё,
что было до сего момента неясно, уже станет очевидным.
Вот основной цикл работы интерпретатора:
- прочитать очередное выражение
- провести интерпретацию этого выражения
Если мы находимся в командном интерпретаторе, то после
последнего этапа ещё происходит печать получившегося выражения.
Вот собственно и всё! Теперь разберёмся с каждым шагом.
Как и многое другое, процесс интерпретации разделён
на несколько отдельных функций, что позволяет контролировать
его с точностью до миллиметра, если это конечно
надо.
Рассмотрим выражения:
9.1 Чтение
Прочитать выражение позволяет функция
read.
По умолчанию чтение происходит со стандартного ввода. Глядя
на примеры выражений и любовь языка к списковым
структурам в круглых скобках, несложно догадаться,
что мы получаем на выходе из
read ... правильно — именно списки и получаем, ну или просто константы, если скобок не было.
Первое – константа – число
3
Второе – константа – строка
“string”
Третье – список из констант:
- символ + (вот они символы-то! не спроста они в языке существуют ;))
- число 1
- число 3
Четвёртое – сами наверное уже догадались
Пятое – список из:
- константа – символ some-func
- константа – символ a
- константа – логическая ложь.
- ещё один список из: символа + и чисел 1 и 3.
Шестое – символ
a.
Вот вам и весь парсер ;)
9.2 Исполнение
Исполнить полученный список, можно с помощью функции
eval,
которой передаются два параметра: список и среда исполнения.
Что такое второй параметр — не будем пока заморачиваться,
а примем как данность.
Исполнитель рекурсивно пробегается по всем вложенным спискам,
большинство констант интерпретируются в них самих (строки
в строки, числа в числа), а вот наткнувшись
на символ, производится поиск соотв. переменной и в
результате подставляется то на что она ссылается (Note: здесь
описана так называемая подстановочная модель,
она не точная, зато простая и понятная, то что
происходит в реальном интерпретаторе — гораздо сложнее,
но суть остаётся примерно та же)
После того как все переменные разобраны, то если
в результате остаётся писок, то происходит запуск функции.
Первый элемент списка – собственно указатель на саму функцию,
например в одном из наших примеров – это стандартная
функция
string-append,
а все оставшиеся – это аргументы функции. Вычисляем указанную
функцию от данных аргументов (в том же примере –
это две константные строки
“str1” и
“str2”) ... и получаем результат исполнения.
Пройдёмся по нашим примерам:
3 --> собственно
3 и получим, никаких функций запускать не надо
“string” --> получим строку
(+ 1 3) --> выполним функцию сложения от аргументов
1 и
3, результат – число
4.
(string-append “str1” “str2”) – результат – строка
“str1str2”
(some-func a #t (+ 1 3)) – получим результат выполнения функцию от аргументов:
— значение переменной
a
— лочическая ложь
— результат сложения
1 и
3, то есть аргумент равен
4.
a --> значение переменной
a
9.3 Вывод результата на экран
Для вывода результата на экран есть множество функций, самая интересная из них это, пожалуй,
write.
Если в результате вы получили простую структуру, соcтоящую
из списков, возможно вложенных и каких-либо констант(строк,
чисел, символов), то
write напечатает их в таком виде, что потом
read может их обратно съесть.
Те кто знаком с языками типа Java узнает в этом знакомые
вещи называемые словами: сериализация и маршалинг ... только
сделанные лет за дцать до этого ;)
Константы будут выведены естественным образом:
7 напечатается как
7
“str” напечатается как
“str”
Списки опять напечатаются как знакомые выражения, окруженные скобками, например список из
1 2 и
3 будет напечатан как
(1 2 3)
9.4
Ну а теперь повторим пройдённое. Помните про функцию
quote, которая позволяла заполучить символы?
quote просто напросто говорит интерпретатору, что не надо исполнять
eval после
read.
Поэтому
'a – это просто символ
a
'(1 2 3) – это список из
1 2 3
10 Особые формы
Аргументы функции обрабатываются в некотором недокументированном порядке – зависит от реализации Схемы.
В отдельных случаях хочется заранее знать, как и когда
будут аргументы вычислены. Поскольку эти «функции» такие особые,
то и называются они «особые формы», вот основные,
которые нам потребуются на практике.
10.1 Условные выражения
(if условие команда-если-истина команда-если-ложь)
присутствие команда-если-ложь традиционно необязательно.
Сначала вычисляется условие, если оно истинно (то есть не
#f), то вычисляется
команда-если-истина, иначе вычисляется
команда-если-ложь.
Например,
- (if #f 3 4) – вернёт 4
- (if (+ 1 3) 5 6) – вернёт 5, поскольку результат (+ 1 3), то есть 4 — «не ложь».
- (if (string=? “aaa” “bbb”) 3 5) – вернёт 5, ибо строки не равны
- (if (number? 5) “number” “not number”) – вернёт строку “number”, ибо 5 — действительно число, а не что другое ;)
- (if #f 3) – поскольку «команда-если-ложь» отсутствует, то if
вернёт некоторое волшебную сущность, которую иногда называют
unspecific, иногда unspecified — в общем «то, не знаю что».
10.2 Множественное ветвление
Если в данной точке программы надо исследовать множество различных вариантов, то используйте
cond
Формат:
(cond вариант1 вариант2 ... )
вариант – оформляется в виде
(тест выражение1 выражение2 ... )
есть ещё специальный вариант
(else выражение1 выражение2 ... ), который применяется, если никакой другой вариант не прошёл.
else может остуствовать, и должен быть всегда последним вариантом в случае присутствия.
Например давайте попробуем понять, а что же пришло в функцию: строка или число, символ или что-то ещё?
10.3 последовательное исполнение
Иногда в
if допустима только одна команда
на условие истины и одна на условие лжи. А что
делать если хочется исполнить сразу много команд в случае
некоторого условия? Иногда можно конечно обойтись имеющимися
средствами, но гораздо проще воспользоваться особой формой
begin,
которая вычисляет свои аргументы строго последовательно слева направо.
В качестве результата возвращается результат работы последнего
выражения.
Пример:
сначала сложит
1 и
2, потом сложит
3 и
4 и вернёт в качестве результата
7.
Продолжение следует