GIMP Script-Fu Первый Дан. Работа, Печать, Отладка
Процесс работы.
С консолью script-fu я работаю опосредованно, т.е весь код я пишу в текстовом редакторе, в моём случае это Emacs, а далее получившийся код копирую через буфер обмена в консоль, а результаты выведенные в консоль, если они чем то примечательны копирую через буфер обмена обратно в Emacs.
Обычно если я завершаю какую либо функционально обособленную группу функций я помещаю её в отдельный файл, библиотеку. А вот уже для загрузки библиотек я использую комманды загрузки:
;;задаём путь для загрузки библиотек
;;(define path-home "D:")
(define path-home (getenv "HOME"))
(define path-lib (string-append path-home "/work/gimp/lib/"))
(define path-work (string-append path-home "/work/gimp/"))
;;загружаем библиотеки
(load (string-append path-lib "util.scm"))
(load (string-append path-lib "defun.scm"))
(load (string-append path-lib "struct2.scm"))
Вывод данных.
Как только начинаешь работать, т.е писать более менее сложные функции, в интерпретаторе script-fu осознаешь необходимость иметь простое средство для вывода различных данных, без него просто не возможно что-то сделать, т.к. основным методом отладки здесь являтся вывод промежуточных результатов.
В script-fu есть несколько функций для перевода данных в текстовое представление и вывода его на консоль. Обычно пишут так:
(print 23)
23
#t
> (prin1 23)
23#t
(prin1 (string-append "number is " (number->string 23)))
"number is 23"#t
(print (string-append "list: " (apply string-append (map atom->string '(1 2 3)))))
"list: 123"
но вывод смешанных данных превращается в сущий кошмар из цепочек вызова функций преобразования данных в строку, объединения строк и печати, ну, а корректный вывод списка и вовсе невыполнимая задача.
Нам нужна функция которая инкапсулирует весь это процесс преобразований, за простым и понятным интерфейсом, хотелось бы иметь функцию на подобии printf или format, которая сразу может распечатать всё что угодно в одном вызове. Здесь нам не понадобятся макросы, а лишь пара вспомогательных функций которые переводят любой (ну практически любой) набор данных тинисхемы в строку:
(define (to-str elem)
(cond
((string? elem) elem)
((and (atom? elem)
(not (vector? elem))) (atom->string elem))
((list? elem)
(string-append "("
(apply string-append
(insert-between-elements
(map to-str elem) " "))
")"))
((vector? elem)
(string-append "#("
(apply string-append
(insert-between-elements
(map to-str
(vector->list elem)) " "))
")"))
))
;;полезная функция обработки списков, применяет функцию двух аргументов,
;;к списку, для первого применения используется начальный элемент инит
(define (fold f init lst)
(do ((rez init (f rez (car l)))
(l lst (cdr l)))
((null? l) rez)))
(define (insert-between-elements lst new-elem)
(reverse
(fold (lambda (prev elem)
(if (not (null? prev))
(cons elem (cons new-elem prev))
(cons elem prev)))
'()
lst))
)
(define (prn . args)
(display
(apply string-append
(map to-str
args))))
(to-str `((1 2 3 "hello" 'world 23 (q 3 e ,#(1 2 3) 4))))
;;"((1 2 3 hello (quote world) 23 (q 3 e #(1 2 3) 4)))"
(prn "Hello" " " "World!" "\n")
;;Hello World!
;;#t
(prn "x: " '(12 3 12 (23 q a b d) "next" "prev" q123) ", всё!" "\n")
;;x: (12 3 12 (23 q a b d) next prev q123), всё!
Это немного упрощённый вариант функции которой я пользуюсь, позволяющей печатать практически любые данные script-fu.
Не поймите меня не правильно, что тинисхема совсем «голая», нет! Например в файле инициализации определена функция foldr, аналог приведённой мной функции fold. Но я предпочитаю использовать итерационное определение обработки списка, а foldr имеет рекурсивное определение. Обратите внимание на цикл do в функции fold, у него отсутствует «тело» цикла. А что такое тело цикла? Это либо операции с «побочным эффектом», либо различные присваивания. Построение цикла в подобном стиле считается истинно функциональным. И с учётом того, что тинисхема нигде не обещает, что она имеет оптимизацию хвостовой рекурсии, постоянно использовать рекурсивные решения, это опрометчивый шаг.
Я поместил эти функции в файл util.scm. Кстати библиотеку функций я выложил на gitflic.ru
Отладка.
Отладка в Script-fu сводиться к распечатке промежуточных сообщений, позволяющих локализовать неисправность. А благодаря написанной выше функции, этот процесс значительно облегчается.
(define-m (reverse-str str)
(prn "run reverse-str with: " str "\n")
(list->string (reverse (string->list str))))
(reverse-str "смешарики")
;;run reverse-str with: смешарики
;;"икирашемс"#
Таким образом сегодня мы рассмотрели небольшую, но очень важную функцию универсальной печати, которая значительно облегчит нам дальнейшую работу со script-fu.