Если сравнить расширяемое приложение с коробочкой, то плагины, это полезные вещи, которыми можно наполнить эту коробочку, придающие новое содержание нашему приложению. В этом ряду стоит и такая вещь как Script-fu. Но что если я скажу, что Script-fu так же может быть такой же "коробочкой" и может иметь расширения, давайте разбираться. Но сразу скажу, счастливы будут не все.

Плагины к tinyscheme

Изначальный интерпретатор на базе которого создавался Script-fu позволяет загружать специальным образом написанные для него плагины. Самих плагинов к тинисхеме написано немного. Да что там немного, раз два и обчёлся. Поэтому я рассмотрю плагин поставляемый с гимпом, tsx, он так и расшифровывается тинисхема экстеншен, т.е расширение.

Загрузка плагина в Script-fu

Предположим вы разместили плагин к тинисхеме в домашней директории "~/work/scheme/tiny/tsx/". Попробуем дать команду в консоли:

(getenv "HOME") ;;...../work/scheme/tiny 
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/")) 
(define p-ext-tsx (string-append p-ext "tsx/tsx"))

(load-extension p-ext-tsx) ;;Error: eval: unbound variable: load-extension

К моему ВЕЛИКОМУ сожалению, дорогие друзья, в стандартных сборках GIMP, из тинисхемы, на базе которой сделан script-fu, удален модуль dynload.c. Его просто нет даже в архивах сборок исходных кодов дистрибутивов GIMP. А именно он отвечает за загрузку расширений в тинисхеме. По какой причине это сделано я не знаю, но зато я знаю как этот модуль добавить в ваш GIMP.

Но я расскажу Вам, как проделал этот процесс на Линуксе, в принципе это можно сделать и на Виндовсе, но это не моя родная система, и у меня там нет никаких рабочих инструментов(MinGW есть обрезанный, обычную tinyscheme и плагины к ней я могу скомпилировать, но уже целый GIMP вряд ли).

Добавление возможности загружать плагины в Script-fu под Linux

Итак, нам надо пересобрать Script-fu, чтобы он поддерживал загрузку расширений. Первым делом вам понадобиться архив исходников вашей установленной сборки GIMP. Её можно скачать например здесь: https://download.gimp.org/gimp/v2.10/ . Качаем к себе и распаковываем. Запускаем ./configure. Эта процедура сгенерирует Makefile, в каждой поддиректории проекта, заодно проверив всё ли есть для сборки проекта GIMP. Нам ВЕСЬ гимп собирать не надо(но желательно, иначе сборка script-fu превратиться "в увлекательное занятие"!!

Далее мы идём в каталог: "gimp-2.10.30/plug-ins/script-fu" ищем строчку "-DUSE_INTERFACE=1" в makefile и под ней добавляем две строчки:

   ```
   -DSUN_DL=1      \
   -DUSE_DL=1      \
   ```

так что бы ниже ещё была строчка:

   ```
   -DUSE_STRLWR=0
   ```

В общем это параметры компиляции плагина script-fu(SUN_DL в данной директории не обзяателен).
Переходим в директорию tinyscheme ищем ту же строчку в файле makefile и опять добавляем:

   ```
 -DSUN_DL=1       \
 -DUSE_DL=1       \
   ```

Для систем виндовс надо вместо SUN_DL писать _WIN32.

Далее идем на гитхаб https://github.com/GNOME/gimp/tree/master/plug-ins/script-fu/libscriptfu/tinyscheme - зеркало исходников GIMP, и качаем от туда два файла dynload.c и dynload.h и размещаем их в директории tinyscheme. В makefile в той же директории надо провести ещё одну правку. Ищем строку:

 ```
  m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT)
 ```
  и добавляем компиляцию dynload

 ```
  m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT) dynload.$(OBJEXT)`
  ```

Опс. и чуть было не забыл, надо добавить линковку динамического загрузчика библиотек dl в makefile формирующего script-fu

LDADD = \
        -ldl            \
        $(libgimpui)            \
		....

Всё! Дальше идем в директорию выше и собираем плагин скрипт-фу с помощью команды make.

К сожалению для сборки этого небольшого плагина, требуются много сгенерированных и скомпилированных файлов из дистрибутива gimp, типа libgimpui-2.0.la, libgimpmodule-2.0.la и т.п. Их можно сгенерировать вручную прыгая из директории в директорию и давай команду make с нужным файлом, но я советую, всё таки собрать предварительно весь гимп командой make из верхней директории дистрибутива, это долго но проще, а уже после этого перебраться в директорию script-fu и там дать команду make.

Сам скомпилированный плагин находиться в директории ./.libs. Проверим на всякий случай, есть ли в собранном плагине строчка: load-extension, если нет, то что то у вас пошло не так.

Копируем этот плагин в место, где лежал оригинальный плагин, у меня это директория /usr/lib/gimp/2.0/plug-ins/script-fu, предварительно сохранив оригинальную версию плагина, ОБЯЗАТЕЛЬНО в другой директории(gimp может загрузить и старую переименованную версию плагина)!

Теперь можем грузить гимп, и плагин script-fu, если загрузилось поздравляю всё отлично.
Даже можно дать комманду в консоли скрип-фу:

load-extension
;;#

Мы это сделали!

Библиотеки расширения тинисхемы

Собственно библиотек расширения тинисхемы, хм.. раз два и обчёлся, о чём я уже говорил, вот что я нашёл:
https://github.com/mherasg/tsx - это и есть тинисхема экстеншен.
https://github.com/ignorabimus/tinyscheme-ffi - не пробовал, но по описанию эта библиотека позволяет загрузить любую динамическу библиотеку из тинисхемы, а не только её родное расширение и работать с функциями этой библиотеки как с обычными функциями тинисхемы.
https://sourceforge.net/projects/tinyscheme/files/ - тут лежит помимо самой тинисхемы, библиотека работы с регулярными выражениями из тинисхемы.
http://www.geonius.com/software/tsion/ - работа с сетью, директориями и пр.

Не думаю, что кому-то захочется строить сетевой веб сервер в консоли гимпа, а в принцпе чем чёрт не шутит? ))) Но данные библиотеки можно использовать для изучения принципов построения расширений тинисхемы.

Итак если у вас есть уже скомпилированная версия расширений тинисхемы, и вы попробуете её загрузить из консоли script-fu с помощью комманды load-extension, ваша попытка неминуемо окончиться крахом script-fu. Почему это происходит? Дело в том что разработчики scrip-fu существенно изменили тинисхему используемую с гимпом, и все расширения надо компилировать с учётом новых заголовочных файлов поставляемых с исходниками gimp.

Компилируем расширение тинисхемы

Берём проект tsx и распаковываем его.
Правим makefile

#SCHEME_H_DIR=..
SCHEME_H_DIR=<ВАШ ПУТЬ ДО ИСХОДНИКОВ ГИМП>/gimp-2.10.30/plug-ins/script-fu/tinyscheme
GLIB_H=pkg-config --cflags glib-2.0
GLIB_L=pkg-config --libs glib-2.0

CC=gcc
CFLAGS=-DUSE_DL=1 -I $(SCHEME_H_DIR) $(GLIB_H)

даём команду сборки:

make

При удачном завершении сборки мы получим файл: tsx.so который можно будет загружать в качестве расширения тинисхемы:

(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/"))
(define p-ext-tsx (string-append p-ext "tsx/tsx"))
(load-extension p-ext-tsx)

Теперь нам доступна интересная команда для работы:

(system "pwd")
(system (string-append "ls " (getenv "HOME") "/work/gimp"))

Вот только результаты её работы вы сможете увидеть, если запустили гимп из какой нибудь консоли, и будут они именно в ней! Команда не вернет никаких данных, кроме кода возврата команды, ну это уже вопросы к разработчику данного расширения, почему оно так работает. Но то, что теперь из тинисхемы мы можем выполнять системные команды уже хорошо.

Пишем собственное расширение ��инисхемы

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

Расширение размещаем в отдельной директории и загружаем с помощью комманд:

(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/"))
(define p-ext-my (string-append p-ext "my-ext/myext"))
(load-extension p-ext-my)

Начнем с самой простой функции, получения случайного числа:

pointer foreign_rand(scheme * sc, pointer args)
{
  pointer ret;
  unsigned long    rez;

  rez = rand();
  ret = sc->vptr->mk_integer(sc,rez);
  return ret;
}

Нам ничего не надо передавать в функцию, а просто получить случайное число с помощью функции rand и сформировать значение в схеме на основе этого числа, это можно сделать с помощью вызова функции sc->vptr->mk_integer(sc,rez).

Чтобы наша функция была доступна в тинисхеме, надо написать функцию инициализации, которую выполняет модуль dynload при загрузке динамической библиотеки расширения. В ней выполняются функции определения для схемы, связывающие код функции с именем функции в схеме. Там же инициализируем генератор случайных чисел: srand(time(NULL)); В эту же функцию будем добавлять определения для схемы и других функций расширения.

void init_myext (scheme * sc)
{
  printf("in run init_myext"); //просто печать для отладки
  ......
  srand(time(NULL));
  sc->vptr->scheme_define(sc,sc->global_env,
                               sc->vptr->mk_symbol(sc,"rand"),
                               sc->vptr->mk_foreign_func(sc, foreign_rand));
  .....
  printf("exit from init_myext");
}

Теперь разберёмся с передачей значений из схемы в функцию си, на примере написания битовой операции отритцания. Для выполнения этой операции нам надо передать одно целое значение в функцию си. Но поэксперементировав с этой функцией я решил, что лучше передавать еще одно значение, битовую маску нашего интереса, означающую сколько(а по факту какие) значащих битов мы хотим получить из функции.

pointer foreign_bitwise_not(scheme * sc, pointer args)
{
  pointer first_arg;
  unsigned long    var;
  pointer ret;
  pointer interest_arg;
  unsigned long    interest;
  unsigned long    rez;

  if(args == sc->NIL)
  {
    return sc->F;
  }

  first_arg = sc->vptr->pair_car(args);//получаем первое значение

  if(!sc->vptr->is_integer(first_arg))
  {
    return sc->F;
  }
  var = sc->vptr->ivalue(first_arg); //получаем первое значение

  args = sc->vptr->pair_cdr(args);   //получаем второе значение
  interest_arg = sc->vptr->pair_car(args);
  if(!sc->vptr->is_number(interest_arg)) {
    return sc->F;
  }
  interest = sc->vptr->ivalue(interest_arg); //получаем второе значение

  rez = (~var) & interest;      //выполняем операцию

  ret = sc->vptr->mk_integer(sc,rez);  //возвращаем значение
  return ret;
}

Аргументы передаваемые через args, представляют собой список из тинисхемы, нам надо просто его разобрать и преобразовать значения схемы в значения си.
Далее также надо зарегистрировать нашу функцию и сделать её доступной для схемы в функции init_myext

sc->vptr->scheme_define(sc,sc->global_env,
                           sc->vptr->mk_symbol(sc,"bitwise-not"),
                           sc->vptr->mk_foreign_func(sc, foreign_bitwise_not));
Давайте попробуем пример:
(bitwise-not #x0 #xFF)
;;255
(bitwise-not #x01 #xFF)
;;254
(bitwise-not #x01 #xFFFF)
;;65534
(number->string 5 2)
;;"101"
(number->string (bitwise-not  5 #xF) 2)
;;"1010"

Мы разобрали унарную функцию побитового отритцания. А следующие функции будут бинарные(имеющие два операнда) и ещё один дополнительный аргумент маску интересующих нас битов. Приведу полный пример операции побитового или.

pointer foreign_bitwise_or(scheme * sc, pointer args)
{
  pointer first_arg;   pointer second_arg;   unsigned long    var1, var2;
  pointer ret;
  pointer interest_arg;   unsigned long    interest;
  unsigned long    rez;

  if(args == sc->NIL)
  {
    return sc->F;
  }

  first_arg = sc->vptr->pair_car(args); //первый аргумент
  if(!sc->vptr->is_number(first_arg))
  {
    return sc->F;
  }
  var1 = sc->vptr->ivalue(first_arg);   //первый аргумент

  args = sc->vptr->pair_cdr(args);      //второй аргумент
  second_arg = sc->vptr->pair_car(args);
  if(!sc->vptr->is_number(second_arg)) {
    return sc->F;
  }
  var2 = sc->vptr->ivalue(second_arg);   //второй аргумент

  args = sc->vptr->pair_cdr(args);       //третий аргумент
  interest_arg = sc->vptr->pair_car(args);
  if(!sc->vptr->is_number(interest_arg)) {
    return sc->F;
  }
  interest = sc->vptr->ivalue(interest_arg);//третий аргумент


  rez = (var1 | var2) & interest;           //операция побитового или и наложение маски

  ret = sc->vptr->mk_integer(sc,rez);     //возврат значения
  return ret;
}

Операция побитового и отличается от или, только выполняемой над аргументами операцией.

rez = (var1 & var2) & interest;

Операция побитового исключающего или, аналогична:

rez = (var1 ^ var2) & interest;

Также надо прописать эти функции в операцию иницализацию расширения:

sc->vptr->scheme_define(sc,sc->global_env,
                             sc->vptr->mk_symbol(sc,"bitwise-or"),
                             sc->vptr->mk_foreign_func(sc, foreign_bitwise_or));
sc->vptr->scheme_define(sc,sc->global_env,
                             sc->vptr->mk_symbol(sc,"bitwise-and"),
                             sc->vptr->mk_foreign_func(sc, foreign_bitwise_and));
sc->vptr->scheme_define(sc,sc->global_env,
                             sc->vptr->mk_symbol(sc,"bitwise-xor"),
                             sc->vptr->mk_foreign_func(sc, foreign_bitwise_xor));
Проверим работу операций:
 (number->string (bitwise-or #b010101 
                             #b000111
			                 #b111111) 2)
;;"10111" 
 (number->string (bitwise-and #b010101 
                              #b000111
			                  #b111111) 2)
;;"101"
 (number->string (bitwise-xor #b010101 
                              #b000111
			                  #b111111) 2)
;;"10010"

Ну а теперь осталось написать аналог функции system из tsx

pointer foreign_cmd_run(scheme * sc, pointer args)
{
  pointer first_arg;
  char*   command;
  pointer ret;
  FILE*   fp;
  char    arr[LINESIZE];
  
  if(args == sc->NIL)
    return sc->F;

  first_arg = sc->vptr->pair_car(args); //принимаем первый аргумент
  if(!sc->vptr->is_string(first_arg))
    return sc->F;
  
  command = sc->vptr->string_value(first_arg);
  if(0 == command)
    return sc->F;                      //принимаем первый аргумент

  fp = popen(command,"r");             //запускаем на исполнение комманду, создавая канал.
  //чтение простое, не обрабатывает слишком длинные строки, надо бы проверять сколько прочитали
  //и если строка слишком длинная складывать её с остатком.
  ret = sc->NIL;
  while (fgets(arr, LINESIZE, fp) != NULL) {
    printf("read: %s", arr);
    ret = sc->vptr->cons(sc, sc->vptr->mk_string(sc, arr), ret);
  }
  pclose(fp);
  // возвращаем перевёрнутый список вывода, вертеть обратно надо уже в схеме
  return ret;
}

И эту функцию также надо зарегистрировать с схеме в функции инициализации расширения.

sc->vptr->scheme_define(sc,sc->global_env,
                             sc->vptr->mk_symbol(sc,"cmd-run"),
                             sc->vptr->mk_foreign_func(sc, foreign_cmd_run));
Пробуем:
(cmd-run "pwd") ;;("/usr/bin\n")#

(cmd-run (string-append "ls " (getenv "HOME")))
;;("work\n" "video\n" "tmp\n" "quicklisp\n" "inst\n"
;; "game\n" "doc\n" "distr\n" "bin\n" "basedraw01.png\n" "asdf\n"
;; "Videos\n" "Templates\n" "Public\n" "Pictures\n" "Photo\n" "Music\n"
;; "Downloads\n" "Documents\n" "Desktop\n")

(reverse (cmd-run (string-append "/sbin/ifconfig" " -a")))
;;("eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n" 
;;"        inet ...
;;...
;;"        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n" 
;;"\n")

Если мы разместим макросы которые мы написали раньше, в файле util.scm.

Продемонстрирую сеанс работы со скрипт фу:
;;определяем пути и загружаем библиотеку и расширение my-ext
(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"))
(define p-ext (string-append path-home "/work/scheme/tiny/gimp/"))
(define p-ext-my (string-append p-ext "my-ext/myext"))
(load-extension p-ext-my)

;;определяем функию получения случайного числа на основе функции из my-ext
(define (random i)
  (modulo (rand) i))

;;определяем функцию получения списка случайных чисел заданной длины и огр. m
(define (rand-lst m len)
    (let ((rez '()))
	   (for (i 1 len)
	      (set! rez (cons (random m)
		                  rez)))
	    rez))
		
(rand-lst 21 30)
;;(15 10 18 15 13 0 5 3 13 8 14 2 12 3 7 3 8 1 19 10 12 13 17 18 3 9 16 17 18 13)
(rand-lst 21 30)
;;(7 17 15 18 1 0 4 1 16 3 11 9 10 11 18 9 4 17 3 2 0 13 11 8 19 16 6 12 2 10)

Вот так вот легко и "непринужденно" мы добавили в Script-fu несколько функций, вернее написали расширение при загрузке которого в Script-fu добавляются несколько функций.

Таким образом поддержка загрузки расширений в тинисхеме является мощным средством расширения функциональности script-fu.