Создание скриптов при помощи Guile

Увеличиваем возможности C и Scheme

Scheme – это язык программирования, а Guile – интерпретатор и библиотека для Scheme, преобразующие его во встроенный язык сценариев, что делает его идеальным средством вдохнуть новую динамичную жизнь в ваши старые статичные приложения. Познакомьтесь с кратким обзором Guile и изучите его широкие возможности для создания гибких приложений.

M. Тим Джонс, инженер-консультант, Emulex Corp.

М. Тим ДжонсМ. Тим Джонс - архитектор встроенного ПО и, кроме того, автор книг Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (выдержавшей на данный момент второе издание), AI Application Programming (второе издание) и BSD Sockets Programming from a Multilanguage Perspective. Он имеет обширный опыт разработки ПО в самых разных предметных областях - от ядер специальных ОС для геосинхронных космических аппаратов до архитектур встраиваемых систем и сетевых протоколов. Тим - инженер-консультант Emulex Corp., Лонгмонт, Колорадо.



21.05.2009

Guile появился в 1995 году в качестве интерпретатора для языка Scheme, упрощенного потомка языка Lisp, созданного Джоном МакКарти в 1958 году. Однако Guile делает Scheme встраиваемым, что позволяет использовать этот интерпретатор для встроенных скриптов. Guile – это не просто одно из расширений языка, это официальное расширение языка в рамках проекта GNU. Скрипты на Guile можно обнаружить во множестве приложений с открытым исходным кодом – от утилит gEDA CAD до Scheme Constraints Window Manager (Scwm), который обеспечивает гибкость настройки при помощи скриптов Scheme (ссылки приведены в разделе Ресурсы). У Guile очень успешная история использования в качестве встроенного языка скриптов таких приложений, как GNU Emacs, GIMP и Apache Web Server.

Ключевое свойство Guile - это расширяемость (см. рисунок 1). При помощи Guile вы можете обрабатывать скрипты Scheme, динамически связывать скрипты со скомпилированными приложениями на C и даже интегрировать скомпилированные функции C в скрипты Scheme. Это означает, что пользователи смогут перекраивать или изменять ваши приложения, добавляя в них свои параметры.

Рисунок 1. Модель использования скриптов Guile
Рисунок 1. Модель использования скриптов Guile

Одним из лучших примеров индивидуальной настройки приложений является индустрия видеоигр. Видеоигры предоставляют громадные возможности для индивидуальной настройки при помощи скриптов. Многие разработчики игр используют скрипты даже для создания основного кода, реализуя с их помощью отдельные аспекты (такие как системные параметры).

Простой пример

Теперь давайте взглянем на простой пример интеграции Guile в приложение на языке C. В данном случае я использую программу на языке C, которая вызывает скрипт Scheme. Исходный код первого примера приведен в листингах 1 и 2.

Использование скриптов в играх

Современные игры обычно включают в себя скриптовые языки, от традиционных, вроде Python и Ruby, до специализированных, как UnrealScript (ссылки приведены в разделе Ресурсы). В играх эти скриптовые языки могут использоваться для настройки системных характеристик и даже для настройки параметров объектов, использующихся в игре. Скрипты наиболее подходят для разработки игр, поскольку для введения новых свойств отпадает необходимость длительной компиляции. Если вы просмотрите подкаталоги вашей любимой игры для PC, скорее всего вы обнаружите скрипты.

В листинге 1 показано приложение на языке C, которое вызывает скрипт Scheme. Для начала обратите внимание на включение файла заголовка libguile, которое делает доступными необходимые символы Guile. Затем обратите внимание на определение нового типа: SCM. Это абстрактный тип данных языка C, который представляет все объекты Scheme, содержащиеся внутри Guile. Здесь объявляется функция Scheme, вызываемая позже.

Первое, что нужно сделать для любого потока, использующего Guile, - это выполнить вызов scm_init_guile. Эта функция инициализирует глобальный статус Guile и должна быть выполнена раньше любой другой функции Scheme. Затем, до вызова функции Scheme, должен быть загружен файл, в котором находится эта функция. Сделайте это при помощи функции scm_c_primitive_load. Обратите внимание на название: _c_ в названии обозначает, что функция содержит переменную языка C (в отличие от переменной Scheme).

Далее выполняется поиск и возврат символьного имени переменной (модель функций Scheme) с помощью scm_c_lookup, затем оно разыменовывается при помощи scm_variable_ref и сохраняется в переменной Scheme func. Затем при помощи scm_call_0вызывается функция Scheme. Эта функция Guile вызывает ранее объявленную функцию Scheme без аргументов.

Листинг 1. Программа на языке C, вызывающая скрипт Scheme
#include <stdio.h>
#include <libguile.h>

int main( int argc, char **arg )
{
  SCM func;

  scm_init_guile();

  scm_c_primitive_load( "script.scm" );

  func = scm_variable_ref( scm_c_lookup( "simple-script" ) );

  scm_call_0( func );

  return 0;
}

В листинге 2 приведен скрипт Scheme, вызываемый внутри приложения на языке C. Скрипт использует процедуру display для вывода на экран строки. После этой функции следует вызов процедуры newline, которая выполняет возврат каретки.

Листинг 2. Скрипт Scheme, вызываемый приложением на языке C (script.scm)
(define simple-script
  (lambda ()
    (display "script called") (newline)))

Интересно то, что здесь скрипт связан с программой на языке C не статически, а динамически. Скрипт Scheme можно изменить, и тогда при выполнении ранее скомпилированной программы на языке C будут выполняться новые команды из скрипта. В этом заключается мощь встроенных скриптов: вы сочетаете скорость скомпилированных приложений и гибкость динамических скриптов.

Теперь, когда мы разобрались с простым примером, давайте копнем немного глубже, чтобы исследовать некоторые другие элементы использования скриптов Scheme в приложениях на языке C.


Краткое введение в Scheme

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

Для этих примеров я использовал интерпретатор Guile, который позволяет работать с Scheme в режиме реального времени, обеспечивает работу с кодом Scheme и отображает результат немедленно.

Переменные

Scheme является языком, использующим динамические типы данных, следовательно, тип переменной, как правило, неизвестен до момента исполнения. Переменные Scheme - это просто контейнеры, тип которых может быть задан позже.

Переменные создаются при помощи примитива define, а затем изменяются при помощи примитива set!. Здесь это сделано вот так:

guile> (define my-var 3)
guile> (begin (display my-var) (newline))
guile> (set! my-var (* my-var my-var))

Процедуры

Способ создания процедур в Scheme также не будет неожиданным: при помощи примитива define. Процедуры могут быть анонимными (лямбда-процедуры) или именованными. Именованные процедуры хранятся в переменной, как показано ниже:

(define (square val) (* val val))

Эта форма отличается от традиционного синтаксиса Lisp, если вы с ним знакомы, но она проще для чтения. Теперь можно использовать эту новую процедуру так же, как и другие примитивы:

guile> (square 5)
25

Условные операторы

В Scheme существует много вариантов работы с условными операторами. Самый простой и распространенный - это условный оператор if. Он определяет проверку условия, истинное выражение и необязательное ложное выражение. В следующем примере вы можете видеть, как в Scheme делается обработка списков. Список начинается с if и заканчивается (display "less"). Вспомните, что Scheme является потомком Lisp и, соответственно, в его основе лежат списки. Scheme обрабатывает код и данные в виде списков, что размывает границы между данными и кодом.

guile> (define my-var 3)
guile> (if (> my-var 20) (display "more") (display "less"))
less

Циклы

В Scheme циклы реализуются при помощи рекурсии, что требует определенной осмотрительности при использовании. Тем не менее это естественный способ итерирования. В следующем примере показан скрипт, который выполняет итерирование от 0 до 9, а затем выводит сообщение done (готово). В этом примере используется то, что в Scheme называется концевая рекурсия. Обратите внимание, что в конце цикла, который многократно вызывается с одной и той же функцией и аргументом, увеличивающимся с каждым повтором на единицу, выполняется итерация цикла. В традиционных языках программирования такая рекурсия обращается за пределы стека для обработки истории вызовов, но в Scheme это делается иначе. Последний вызов (tail) просто вызывает функцию без дополнительных вызовов или издержек на обслуживание стека.

(let countup ((i 0))
  (if (= i 10) (begin (display "done") (newline))
    (begin
      (display i)
      (newline)
      (countup (+ i 1)))))

Другой интересный способ использования циклов в Scheme - это процедура map. Эта команда просто применяет (или привязывает (maps)) процедуру к списку, как показано в следующем примере. Такой подход одновременно является простым и наглядным.

guile> (define my-list '(1 2 3 4 5))
guile> (define (square val) (* val val))
guile> (map square my-list)
(1 4 9 16 25)

Использование скриптов Scheme в приложениях на языке C

Как вы видели в листинге 1, добавление скрипта Scheme в приложение на языке C не составляет особой проблемы. Теперь рассмотрим пример, в котором для переноса кода Scheme в C используются другие программные интерфейсы приложений (application programming interfaces, API). В большинстве приложений вам необходимо не только выполнять вызовы элементов Scheme, но и передавать аргументы функций Scheme, получать возвращенные значения и использовать переменные в обеих средах. Для этого в Guile содержится богатый набор средств.

Guile пытается преодолеть границу между двумя средами и придать языку C мощь Scheme. Этой цели служат динамические типы, продолжения, сборка мусора и другие концепции Scheme, расширяющие возможности языка C при помощи API-интерфейсов Guile.

Одним из примеров использования концепций Scheme в языке C является возможность динамического создания новых переменных Scheme из среды C. Для создания переменных Scheme используется функция C scm_c_define. Напоминаю, _c_ обозначает, что в качестве аргумента используется тип данных языка C. Если у вас уже есть переменная Scheme (полученная при помощи функции scm_c_lookup), вместо этого вы можете использовать scm_define. Наряду с созданием переменных Scheme в среде C вы можете разыменовывать переменные Scheme и конвертировать значения между двумя средами. Соответствующий пример приведен в листинге 3.

В листингах 3 и 4 приведены два примера взаимодействия между C и Scheme. В первом примере показан вызов функции Scheme из C, передача аргумента и перехват возвращенного значения. Во втором примере создается переменная Scheme для передачи аргументу. В листинге 4 представлены одинаковые функции Scheme, но первая из них использует аргумент, а вторая – статическую переменную.

Ограничения вызова scm_call

В Guile возможно пять вариантов вызова scm_call. Функцию Scheme можно вызвать без аргументов (scm_call_0) или с несколькими аргументам (не более четырех, scm_call_4), ограниченными количеством переменных Scheme, передаваемых при помощи Guile (четыре). Также не поддерживаются функции с переменным числом аргументов. Если необходимо передать более четырех аргументов или использовать переменное количество аргументов, можно использовать список объектов Scheme с необходимым количеством аргументов.

В первом примере, в листинге 3, для вызова функции Scheme с единственным аргументом просто используется функция scm_call_1. Обратите внимание, что здесь вы должны передать Scheme значения функции. Функция scm_int2num используется для конвертирования целого числа C в числовой тип данных Scheme. Для обратного преобразования переменной Scheme ret_val в целое число C используйте scm_num2int.

Второй пример в листинге 3 начинается с создания новой переменной Scheme при помощи scm_c_define, определенной в строковой переменной C (sc_arg). Эта переменная назначается автоматически при помощи функции преобразования типов scm_int2num. Теперь, когда создана переменная Scheme, вы можете просто вызвать функцию Scheme square2 (на этот раз без аргументов) и использовать тот же метод для получения и разыменования возвращенного значения.

Листинг 3. Обращение к функциям и переменным Scheme при помощи C
#include <stdio.h>
#include <libguile.h>

int main( int argc, char *argv[] )
{
  SCM func;
  SCM ret_val;
  int sqr_result;

  scm_init_guile();

  /* Вызов скрипта square с переданным аргументом */

  scm_c_primitive_load( "script.scm" );

  func = scm_variable_ref( scm_c_lookup( "square" ) );

  ret_val = scm_call_1( func, scm_int2num(7) );

  sqr_result = scm_num2int( ret_val, 0, NULL );

  printf( "result of square is %d\n", sqr_result );

  /* Вызов скрипта square2 с использованием переменной Scheme */

  scm_c_define( "sc_arg", scm_int2num(9) );

  func = scm_variable_ref( scm_c_lookup( "square2" ) );

  ret_val = scm_call_0( func );

  sqr_result = scm_num2int( ret_val, 0, NULL );

  printf( "result of square2 is %d\n", sqr_result );

  return 0;
}

В листинге 4 показаны две процедуры Scheme, использующиеся приложением на языке C, приведенным в листинге 3. Первая процедура square – это обычная функция Scheme, которая получает один аргумент и возвращает результат. Вторая процедура - square2 - не получает аргументов, но зато обрабатывает переменную Scheme (sc_arg). Как и в предыдущем случае, эта переменная также возвращает результат.

Листинг 4. Скрипты Scheme, вызываемые в листинге 3 (script.scm)
(define square
  (lambda (x) (* x x)))

(define square2
  (lambda () (* sc_arg sc_arg)))

Использование функций C в скриптах Scheme

В последнем примере рассмотрим процесс вызова функций C из скриптов Scheme. Начнем с функции, вызываемой при помощи Scheme, в листинге 5. Для начала вы должны запомнить, что хотя это функция языка C, она получает и возвращает объекты Scheme (тип SCM). Начнем с создания переменной языка C, которая используется для получения аргумента SCM при помощи функции scm_num2int (конвертирует числовой тип данных Scheme в int языка С). Полученный аргумент возводится в квадрат и возвращает результат при помощи другого вызова в scm_from_int.

Оставшийся код программы в листинге 5 определяет параметры среды для загрузки в Scheme. После инициализации среды Guile при помощи вызова scm_c_define_gsubr в Scheme экспортируется функция языка C, которая принимает в качестве аргументов имя функции в Scheme, количество аргументов (обязательные, дополнительные, другие) и собственно экспортируемую функцию C. Остальное вы видели ранее. Загружается скрипт Scheme, получается ссылка на соответствующую функцию Scheme, и она вызывается без аргументов.

Listing 5. C program for setting up the environment for Scheme
#include <stdio.h>
#include <libguile.h>

SCM c_square( SCM arg)
{
  int c_arg = scm_num2int( arg, 0, NULL );

  return scm_from_int( c_arg * c_arg );
}

int main( int argc, char *argv[] )
{
  SCM func;

  scm_init_guile();

  scm_c_define_gsubr( "c_square", 1, 0, 0, c_square );

  scm_c_primitive_load( "script.scm" );

  func = scm_variable_ref( scm_c_lookup("main-script") );

  scm_call_0( func );

  return 0;
}

В листинге 6 содержится скрипт Scheme. Этот скрипт отображает ответ на вызов c_square, являющийся функцией, экспортированной в приложение на языке C из листинга 5.

Listing 6. Scheme script that calls the C function (script.scm)
(define main-script
  (lambda ()
    (begin (display (c_square 8))
           (newline))))

Как ни прост этот пример, он тем не менее демонстрирует легкость, с которой вы можете совместно использовать код и переменные в двух различных программных средах.


Эпилог

Время создания и распространения статических программ ушло. Сегодня пользователи ожидают от программ гибкости и удобной индивидуальной настройки. Хотя такая эволюция сопряжена с новыми сложностями, она в конечном счете позволяет пользователям раскрыть нам самим новые возможности наших приложений. Надеюсь, что эта статья поможет вам ощутить всю силу Guile. Scheme, возможно, один из самых старых используемых ныне языков программирования, но он остается одним из самых мощных. Guile делает его еще мощнее и удобнее.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=390839
ArticleTitle=Создание скриптов при помощи Guile
publish-date=05212009