Я постараюсь рассказать о том как использовать интерпретатор 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
Скрипт суммирует значения всех элементов таблицы в переменной х, и возвращает результат.
Отличная статья! Очень познавательно было.
ОтветитьУдалитьА как сделать, чтобы моя функция, вызванная из луа скрипта возвращала в качестве результата такую таблицу?
алгоритм будет примерно такой:
ОтветитьУдалитьсначала создать таблицу в стеке с помощью lua_newtable, затем заполнить ее вызовами lua_push*** для ключа и для его значения в цикле, и потом lua_rawset :)
ну еще нужно не забыть вернуть 1, если функция возвращает интерпретатору только одну таблицу.
с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать? после него не нужно делать lua_push*чегонибудь*?
ОтветитьУдалитьпопробовал в качестве индекса передавать -1, но функция в такм случае ничего не возвращает -(
с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать?
ОтветитьУдалитьТуда нужно передавать индекс таблицы, в которую добавляется элемент. Таблицу нужно предварительно создать в стеке.
lua_rawset не просто создает пары ключ-значение, а добавляет их в таблицу созданую с помощью lua_newtable.
получается, в твоем примере после цикла нужно сделать: lua_rawset(L,-1); ?
ОтветитьУдалитьили созданные в цикле значения не удаляются с вершины стека после lua_rawset(), а каждый раз добавляются новые?
сорру за глупые вопросы, я еще только учусь :)
разобрался. в цикле нужно делать не lua_rawset а lua_settable(L,-3), а после цикла просто сделать return 1 - вернули одну таблицу :)
ОтветитьУдалитьпри таком вызове
ОтветитьУдалитьresult = lua_pcall(L, 0, LUA_MULTRET, 0); в стек возвращаемое значение не поместиться. А значит мы его не можем забрать оттуда
с заполнением в цикле понятно, а после цикла когда нужно вызывать lua_rawset, какой индекс ему в качестве второго параметра передавать? после него не нужно делать lua_push*чегонибудь*?
ОтветитьУдалитьпопробовал в качестве индекса передавать -1, но функция в такм случае ничего не возвращает -(
разобрался. в цикле нужно делать не lua_rawset а lua_settable(L,-3), а после цикла просто сделать return 1 - вернули одну таблицу :)
ОтветитьУдалить