*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

пятница, 12 июня 2009 г.

Скрипты на 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. Я с ним более менее активно работаю, так что должны быть точно улучшения. На работе, например, я его примастырил для гибко сконфирурированного фильтрования при журналировании. Есть, конечно, проблемы с производительностью (интерпретатор, все-таки, хоть и с виртуальной машиной), но есть пути улучшения.


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

6 комментариев:

  1. Хм.... А чем Luabind не устроил? Если ответ - "зависимость от Boost" - то от Буста он не более пары хедеров тянет и все. Пользуюсь и очень доволен.

    ОтветитьУдалить
  2. Точно так, ибо когда в списке поддержки обязательно стоят HP-UX, AIX да Tru64, причем с не самыми новыми компиляторами, с бустом бывает туго, хотя, конечно, luabind находится на ином уровне, чем мой простецкий luascript.

    ОтветитьУдалить
  3. Спасибо за наводку. SWIG до этого не видел, надо будет посмотреть.

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

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

    А что по поводу LuaJIT? http://luajit.org/index.html
    Сам не использовал, но судя по тестам, выигрыш должен быт значительный.

    ОтветитьУдалить
  5. Про LuaJIT я в курсе. Мы тут используем Lua для парсинга FIX протокола налету, чтобы не хардкодить под каждый случай, но пока чистый Lua, без JIT. Поглядим, как пойдет.

    ОтветитьУдалить