четверг, 17 января 2008 г.

Lua и C++

Я постараюсь рассказать о том как использовать интерпретатор lua в программе написанной на C++. Это может понадобиться в тех случаях, когда в программу нужно встроить поддержку скриптов, либо написать библиотеку для интерпретатора Lua. Луа – это язык предназначенный для написания расширений. Основа Луа – это библиотека написанная на ANSI С, поэтому она собирается на любой платформе. Библиотеку можно собрать в разных конфигурациях, например исключить парсер, auxiliary library или что нибудь еще. Парсер луа не использует универсальные инструменты для генерации кода, вроде yacc или lex, а написан разработчиками с нуля. Так как библиотека работает очень быстро, и использует очень мало памяти для этого (да и сама имеет очень скромные размеры), Луа может работать на самом разнообразном железе. Приступим. В первую очередь нужно скачать исходники lua с оф сайта www.lua.org. Затем вам не составит труда их собрать. Для того чтобы использовать Lua API, нужно подключить несколько заголовочных файлов:
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}
lua.h – Lua API lauxlib.h – дополнительный API lualib.h – стандартные библиотеки. Луа использует следующее соглашение об именах функций: lua_### - функция(или макрос, или определение типа) из базового интерфейса, luaL_### - функция из auxiliary библиотеки. Чтобы иметь возможность использовать API, нужно, впервую очередь, создать lua_State. Эта структура содержит состояние виртуальной машины луа, помимо, это стек, через который lua общается с вашей программой.
lua_State *L;
    L = lua_open();//создать стек
    lua_close(L); //закрыть стек
Весь обмен информацией между программой и скриптом происходит через стэк. Например если ваша программа попросит lua создать какой нибудь объект, то он в результате будет находится на вершине стека. Если программа должна что-нибудь передать скрипту, то это что-то то-же должно быть передано через стэк (примерно так: lua_pushnumber(L, 0) ). Ваша программа может создать несколько экземпляров lua_State. К элементам стека можно обращаться по индексу, положительные значения соответствуют абсолютным индексам – элемент помещенный в стэк первым имеет индекс равный 1, следующий 2 и тд, абсолютные индексы элементов стека не изменяются при добавлении новых элементов в стэк. Отрицательные значения используются для индексирования относительно вершины стека, верхний элемент имеет индекс -1, следующий за ним -2 и тд. При добавлении нового элемента в стэк относительные(отрицательные) индексы всех его элементов изменяются. В общем стек луа, из 3х элементов, выглядит примерно так: [abs][rel] [-1][3] <--Top [-2][2] [-3][1]<—Bottom Ок. В первом примере показывается как создать экземпляр виртуальной машины lua, загрузить скрипт, передать в него значения (через глобальную переменную), выполнить скрипт и получить результат его работы.
#include "stdafx.h"

extern "C" {
#    include "lua.h"
#    include "lauxlib.h"
#    include "lualib.h"
}
#include <stdlib.h>
#include <stdio.h>



int main(void)
{
    int status, result, i;
    double sum;
    lua_State *L;

    L = lua_open();//Создаем стэк
    
    luaL_openlibs(L);//подключить стандартные библиотеки   
    
    status = luaL_loadfile(L, "script.lua");//загрузить файл
    //содержимое файла обрабатывается, создается функция, которая помещается на вершину стэка
    /* Состояние стэка:
        [-1]main <--top of the stack
     */
    if (status)
    {
        (void)fprintf(stderr, "file not found\n");
        exit(1);
    }

    lua_newtable(L);//создать таблицу, поместить ее на вершину стэка
        /* Состояние стэка:
            [-1]table <--top of the stack
            [-2]main
         */

    //В этом цикле таблица заполняется значениями
    for (i = 1; i <= 5; i++) 
    {
        /* Состояние стэка:
            [-1]table <--top of the stack
            [-2]main
         */
        lua_pushnumber(L, i);//кладем в стэк число (key)   
        /* Состояние стэка:
            [-1]key <--top of the stack
            [-2]table
            [-3]main
         */
        lua_pushnumber(L, i*2);//добавляем значение ключа (value)
        /* Состояние стэка:
            [-1]value <--top of the stack
            [-2]key
            [-3]table
            [-4]main
         */
        lua_rawset(L, -3);//добавить к таблице пару ключ-значение: table[key] = value
        //таблица (table) должна распологаться по индексу -3, value - на вершине стэка
        //key - после value
        /* Состояние стэка:
            [-1]table <--top of the stack
            [-2]main
         */
    }
    
    lua_setglobal(L, "foo");//добавить глобальную переменную "foo" в скрипт
    //значение переменной (в данном случае это будет таблица)
    //должно находится на вершине стэка
    /* Состояние стэка:
        [-1]main <--top of the stack
     */    

    result = lua_pcall(L, 0, LUA_MULTRET, 0);//Выполняет функцию, функция должна находится
    //на вершине стэка (функция находящаяся на вершине стэка была создана через luaL_loadfile).
    //В данном случае ф-я не получает аргументов.

    if (result) {
        fprintf(stdout, "runtime error\n");
        exit(1);
    }

    sum = lua_tonumber(L, lua_gettop(L));//Получаем через стэк значение которое вернул скрипт
    if (!sum) {
        fprintf(stdout, "lua_tonumber() failed!\n");
        exit(1);
    }

    fprintf(stdout, "script exec result: %.0f\n", sum);

    lua_pop(L, 1);//удаляем число которое вернул скрипт из стэка 
    lua_close(L);//удаляем стэк и интерпретатор 

    return 0;
}
Файл script.lua должен выглядеть так:
-- script.lua
x = 0
for i = 1, #foo do
print(i, foo[i])
x = x + foo[i]
end
return x
Скрипт суммирует значения всех элементов таблицы в переменной х, и возвращает результат.

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

Xternalx комментирует...

Отличная статья! Очень познавательно было.

А как сделать, чтобы моя функция, вызванная из луа скрипта возвращала в качестве результата такую таблицу?

Unknown комментирует...

алгоритм будет примерно такой:
сначала создать таблицу в стеке с помощью lua_newtable, затем заполнить ее вызовами lua_push*** для ключа и для его значения в цикле, и потом lua_rawset :)
ну еще нужно не забыть вернуть 1, если функция возвращает интерпретатору только одну таблицу.

Xternalx комментирует...

с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать? после него не нужно делать lua_push*чегонибудь*?

попробовал в качестве индекса передавать -1, но функция в такм случае ничего не возвращает -(

Unknown комментирует...

с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать?
Туда нужно передавать индекс таблицы, в которую добавляется элемент. Таблицу нужно предварительно создать в стеке.
lua_rawset не просто создает пары ключ-значение, а добавляет их в таблицу созданую с помощью lua_newtable.

Xternalx комментирует...

получается, в твоем примере после цикла нужно сделать: lua_rawset(L,-1); ?
или созданные в цикле значения не удаляются с вершины стека после lua_rawset(), а каждый раз добавляются новые?

сорру за глупые вопросы, я еще только учусь :)

Xternalx комментирует...

разобрался. в цикле нужно делать не lua_rawset а lua_settable(L,-3), а после цикла просто сделать return 1 - вернули одну таблицу :)

Виталий комментирует...

при таком вызове
result = lua_pcall(L, 0, LUA_MULTRET, 0); в стек возвращаемое значение не поместиться. А значит мы его не можем забрать оттуда

Xternalx комментирует...

с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать? после него не нужно делать lua_push*чегонибудь*?

попробовал в качестве индекса передавать -1, но функция в такм случае ничего не возвращает -(

Xternalx комментирует...

разобрался. в цикле нужно делать не lua_rawset а lua_settable(L,-3), а после цикла просто сделать return 1 - вернули одну таблицу :)