[devel] I: alterator internals - 4
Stanislav Ievlev
=?iso-8859-1?q?inger_=CE=C1_altlinux=2Eorg?=
Чт Июн 16 17:54:19 MSD 2005
Четвёртая часть рассказа.
Часть информации в ней будет касаться alterator которого пока ещё нет в
Сизифе, но он скоро до него доберётся.
2.10 Открываем капот.
Может быть из академических соображений так поступать не стоит, однако
иногда бывает проще рассказать про причину, чем про её следствия.
Давайте разберёмся с тем как работает интерпретатор схемы, благо знания
нам уже это позволяют сделать. После этого всё что было до сего момента
неясно уже станет очевидным.
Вот основной цикл работы интерпретатора:
* прочитать очередное выражение
* провести интерпретацию этого выражения
Если мы находимся в командном интерпретаторе, то после последнего этапа
ещё происходит печать получившегося выражения.
Вот собственно и всё! Теперь разберёмся с каждым шагом. Как и многое
другое процесс интерпретации разделён на несколько отдельных функций, что
позволяет контролировать его с точностью до миллиметра, если это конечно
надо.
Рассмотрим выражения:
3
"string"
(+ 1 3)
(string-append "str1" "str2")
(some-func a #t (+ 1 3))
a
2.10.1 Чтение
Прочитать выражение позволяет функция read. По-умолчанию, чтение
происходит со стандартного ввода. Глядя на примеры выражений и любовь
языка к списковым структурам в круглых скобках не сложно догадаться что мы получаем на
выходе из read ... правильно именно списки и получаем ну или просто
константы, если скобок не было.
Первое - константа - число 3
Второе - константа - строка "string"
Третье - список из констант:
-- символ '+' (вот они символы-то! не спроста они в языке существуют ;))
-- число 1
-- число 3
Четвёртое - сами наверное уже догадались
Пятое - список из:
-- константа - символ some-func
-- константа - символ a
-- константа - логическая ложь.
-- ещё один список из: символа + и чисел 1 и 3.
Шестое - символ a.
Вот вам и весь парсер ;)
2.10.2 Исполнение
Исполнить полученный список, можно с помощью функции eval, 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
2.10.3 Вывод результата на экран
Для вывода результата на экран есть множество функций, самая интересная из
них это, пожалуй, write.
Если в результате вы получили простую структуру, соcтоящую из списков,
возможно вложенных и каких-либо констант(строк, чисел, символов), то write
напечатает их в таком виде, что потом read может их обратно съесть.
Те кто знаком с языками типа Java узнает в этом знакомые вещи называемые
словами: сериализация и маршалинг ... только сделанные лет за дцать до этого ;)
Константы будут выведенны естественным образом
7 напечатается как 7
"str" напечатается как "str"
Списки опять напечатаются как знакомые выражения, окруженные скобками,
например список из 1 2 и 3 будет напечатан как (1 2 3)
2.10.4
Ну а теперь повторим пройдённое. Помните про функцию quote, которая
позволяла заполучить символы?
quote просто напросто говорит интерпретатору, что не надо исполнять eval
после read.
Поэтому 'a - это просто символ a
'(1 2 3) - это список из 1 2 3
2.11 Особые формы
Аргументы функции обрабатываются в некотором недокументированном порядке -
зависит от реализации scheme.
В отдельных случаях хочется заранее знать как и когда будут аргументы
вычисленны. Поскольку эти "функции" такие особые, то и называются они
"особые формы", вот основные, которые нам потребуются при работе с
alterator.
2.11.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 в общем "то не знаю что".
2.11.2 Множественное ветвление
Если в данной точке программы надо исследовать множество различных
вариантов, то используйте cond
Формат: (cond вариант1 вариант2 ... )
вариант - оформляется в виде (тест выражение1 выражение2 ... )
есть ещё специальный вариант (else выражение1 выражение2 ... ) который
применяется если никакой другой вариант не прошёл. else может
остуствовать, и должен быть всегда последним вариантом в случае
присутствия.
Например давайте попробуем понять, а что же пришло в функцию: строка или
число, символ или что-то ещё?
(define (func x)
(cond
((string? x) "строка")
((number? x) "число")
((symbol? x) "символ")
(else "не знаю что такое")))
2.11.3 последовательное исполнение
Иногда в if допустима только одна команда на условие истины и одна на
условие лжи. А что делать если хочется исполнить сразу много команд в
случае некоторого условия? Иногда можно конечно обойтись имеющимися средствами,
но гораздо проще воспользоваться особой формой begin, которая вычисляет
свои аргументы строго последовательно слева направо. В качестве результата
возвращается результат работы последнего выражения.
Пример: (begin (+ 1 2)
(+ 3 4))
Сначала сложит 1 и 2, потом сложит 3 и 4 и вернёт в качестве результата 7.
2.12 Вернёмся к alterator
Попробуем применить полученные знания к alterator.
Попробуем сделать форму, состоящую из:
- поля ввода (edit)
- метки (label)
- checkbox (не знаю как это сказать по-русски)
При вводе чего-либо в edit метка получит текст "something changed"
При нажатии Enter метка получит текст "enter pressed"
checkbox будет переключать режим отображения edit - нормальный и
звездочками (как при вводе пароля).
Раздадим имена виджетам: edit - editor
label - lbl
checkbox - setter.
На этом краткое введение в Scheme завершается, но не заканчивается. В ходе
дальнейшего рассказа про alterator мы попутно будем совершенствоваться в
этом языке и изучать новые подробности.
Если хотим узнать выставленна ли галочка у checkbox по именни setter,
переформулируем эту фразу на языке alterator следующим образом: (setter checked)
В результате получаем или #t или #f.
Итак, вот какое описание формы получается, если собрать всё воедино:
--
(id 'editor (edit ""
(on-change (lbl text "something changed"))
(on-return (lbl text "enter pressed"))))
(id 'lbl (label "some label"))
(id 'setter (checkbox "Password mode"
(on-click (if (setter checked)
(editor echo stars)
(editor echo yes)))))
--
Подробная информация о списке рассылки Devel