Скрипты на Lua в С++

Lua отличный язык для встраеваемых сценариев. Продуманная структура языка, полный арсенал для процедурного и модульного программирования, а-ля функциональные возможности в виде функций-объектов и замыканий, работа со списками, кооперативная многопотоковость, зачатки объектно-ориентированности в достаточной мере для языка подобного рода, и вообще приятный синтаксис. Написан на стандартном С, поэтому отлично компилируется на разных платформах.

Есть великое множество оберток Lua для С++, но я не нашел ни одной, где не надо вообще вызывать С-шные функции Lua вручную из основной программы. Также для создания новых функций на С++, которые можно будет вызывать из Lua, должен быть только С++‘ый подход.

Моя идея была в создании чисто плюсого интерфейса для Lua с максимально простой интеграцией в рабочий проект.

То, что пока вышло называется luascript.

Для включения в свой проект надо скопировать библиотеку в подкаталог luascript/ и добавить в проект два файла: luascript/luascript.cpp и luascript/lua/lua-files.c.

После этого можно писать вот такие куски кода:

lua script;
script.set_variable<lua::string_arg_t>("a", "test");
script.exec("b = a .. '123';");
std::cout << script.get_variable<lua::string_arg_t>("b").value());

Данный простой скрипт принимает строку через переменню a, добавляет к ней 123 и записывает результат в переменную b, которая потом подхватывается из С++.

Если надо добавить свою функцию, например, для проверки существования файла, можно написать так:

class file_exists_func_t {
 public:
  // Регистрируем аргументы функции. В данном случае один аргумент типа "строка".
  static const lua::args_t* in_args() {
    lua::args_t* args = new lua::args_t();
    args->add(new lua::string_arg_t());
    return args;
  }

  // Регистрируем выходные параметры. В данном случае это просто bool.
  // Фукнция в Lua может возвращать не только одно значение, а несколько,
  // поэтому можно задать список типов выходных параметров.
  static const lua::args_t* out_args() {
    lua::args_t* args = new lua::args_t();
    args->add(new lua::bool_arg_t());
    return args;
  }

  // Задаем namespace и, собственно, имя фукнции. 
  // Получается "fs.file_exits()".
  static const std::string ns() { return "fs"; }
  static const std::string name() { return "file_exists"; }

  // Данный метод вычисляет значение функции.
  // Сначала надо разобрать входные параметры, вычислить функцию и
  // положить результы с массив выходных значений. Правильность
  // работы с типами аргументов, выходных данных и индексов в массивах,
  // их описывающих, лежит на плечах автора функции.
  static void calc(const lua::args_t& in, lua::args_t& out) {
    std::string filename = dynamic_cast<lua::string_arg_t&>(*in[0]).value();
    std::ifstream is(filename.c_str());
    dynamic_cast<lua::bool_arg_t&>(*out[0]).value() = is.good();
  }
};
...
try {
  // Создаем исполнителя скрипта.
  lua script;
  // Регистрируем нашу функцию "fs.file_exists()".
  script.register_function< file_exists_func_t >();
  // Устанавливаем переменную "fname" в "readme.txt".
  script.set_variable<lua::string_arg_t>("fname", "readme.txt");
  // Вызываем скрипт.
  script.exec("exists = fs.file_exists(fname);");
  // Получаем результат через переменную "exists".
  bool exists = script.get_variable<lua::bool_arg_t>("exists").value();
} catch (lua::exception& e) {
  std::cerr << "error: " << e.error() << ", line " << e.line();
}

Что пока не поддерживается, так это параметры типа таблица (хеш) для передачи их в функцию и получения их в качестве результата.

В каталоге lib лежат несколько мини примеров на Lua. Например, вот так можно вызвать внешнюю функцию для base64 кодирования или декодирования:

lua script;
script.exec("package.path = package.path .. ';./lib/?.lua'");
script.exec("require('base64'); a = base64.encode('test');");
// Данный пример напечатает "dGVzdA==".
std::cout << script.get_variable<lua::string_arg_t>("a").value();

Исходники доступны для просмотра в онлайне, или через Mercurial.

Сборка.

Пока я проверял только в Студии 2008. Тестовый проект включает в себя библиотеку, lua 5.1.4, Google Test 1.3.0 и несколько тестов, чтобы почувствовать вкус библиотеки. Все в одном флаконе.

Те, у кого есть SCons, могут собрать, набрав scons -Q. У кого нет, могут запустить скрипт compile-vs2008.cmd. Собранный runner для тестов luascript_unittest_vs2008.exe должен работать без ошибок. Посмотрев сами тесты в файле luascript_unittest.cpp можно в целом понять, как работать с библиотекой. Документация, конечно, будет, но пока так.

Общие замечания.

Забавно, в этих исходниках я попытался в качестве эксперимента максимально работать по стандарту кодирования Google. Из основного, что затронуло лично меня, это:

  • Отступ в 2 пробела (естественно, никаких табов). Для слов public, protected, private отступ в один пробел.
  • Максимальная экономия вертикального места (по возможности не лепить лишних пустых строк). Открывающая скобка { практически всегда на той же строке (для классов, функций, циклов, условий и т.д.). Я раньше так не делал для классов и функций.
  • Никаких cast‘ов в стиле С, даже для элементарных типов. Только приведения в стиле С++. Мне это очень нравится.
  • Забота о длинных строках. Как только можно избегать строк длинее 80 символов. В именах закрытых членов класса использовать не “_” в качестве префикса, а “” в качестве суффикса.

Это был снова эксперимент на Google Code и в opensource’e в целом. Если честно, то выкладывание исходников на публику страшно оздоравливает код, причем по всем статьям.

Данный проект не такой сухой как ранее выложенный SerialCom. Я с ним более менее активно работаю, так что должны быть точно улучшения. На работе, например, я его примастырил для гибко сконфирурированного фильтрования при журналировании. Есть, конечно, проблемы с производительностью (интерпретатор, все-таки, хоть и с виртуальной машиной), но есть пути улучшения.

Посты и ссылки по теме:


Оригинальный пост | Disclaimer

Комментарии