Управление стеком с помощью Lua и C++
Мне нужно передать скрипту lua одну строку (путь к файлу) и вернуть 0 для многих строк.
int error = 0;
lua_State *L = lua_open();
luaL_openlibs(L);
std::vector<string> list_strings;
Используется для перемещения строки в стек, прежде чем я загружу и вызову исходный файл
if ((error = luaL_loadfile(L, "src/test.lua")) == 0)
{
lua_pushstring(L, path.c_str());
if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)
{
lua_gettable(L, LUA_GLOBALSINDEX);
lua_pcall(L,1,1,0);
if (lua_gettop(L) == 1 && lua_istable(L,1))
{
int len = lua_objlen(L,1);
for (int i=1;i =< len; i++)
{
lua_pushinteger(L,i);
lua_gettable(L,1);
const char *s = lua_tostring(L,-1);
if (s)
{
list_strings.push_back(s);
}
lua_pop(L,1);
}
}
}
}
Как бы то ни было, я просто копировал код из примеров, поэтому я не совсем уверен, что делаю то, что я хочу делать... Я хочу протолкнуть путь в стек и вызвать функцию lua, которая заберет это значение из стека и проанализирует файл, который ассоциируется с этим путь.
После синтаксического анализа он должен вернуть таблицу, содержащую строки, которые находятся внутри нее (вы можете просто думать о ней как о функции, которая ищет определенную строку, я полагаю)
Edit: сделано более ясным.
Какие-либо советы / ресурсы?
Есть ли здесь подобные вопросы? или какие-нибудь полезные ресурсы?
2 ответов:
Я хочу убедиться, что понимаю, что вы делаете, прежде чем мы увидим, где вы, кажется, идете не так. У вас есть файл сценария Lua. Вы хотите выполнить этот сценарий, передав ему один строковый аргумент. Он будет делать некоторые вещи, а затем возвращать ноль или больше строк в качестве возвращаемых значений. И вы хотите получить эти значения в своем коде.
Ладно, начнем сверху:
if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)Обычно, когда вы выполняете
lua_pcall, третий параметр сообщает Lua точно , сколько возвращается значения ожидаются. Если вызываемая функция возвращает больше этого числа, то эти возвращаемые значения отбрасываются. Если он возвращает меньше, чем это число, то для заполнения счетчика используются дополнительные нулевые значения.LUA_MULTRET говорит Lua не делать этого. Когда это используется, все результаты помещаются в стек.
Теперь, поскольку вы забыли опубликовать свой сценарий, я должен сделать некоторые предположения о том, как выглядит ваш сценарий. Вы возвращаете несколько строк, но никогда не говорите как это происходит. Lua, как язык, допускает несколько возвращаемых значений:return "string1", "string2";Это приводит к тому, что 2 строки помещаются в стек. Это отличается от:
return {"string1", "string2"};Это помещает один объект в стек: таблицу. Таблица содержит 2 строки. Видите разницу?
Глядя на ваш код, кажется, что вы ожидаете, что сценарий Lua вернеттаблицу строк, а не несколько возвращаемых значений.
В таком случае, вы должны позвонить своему Lua сценарий такой:
if ((error = lua_pcall(L, 1, 1, 0)) == 0)Это говорит Lua, что вы ожидаете одно возвращаемое значение, и если пользователь не предоставляет его, Lua будет помещать ноль в стек.
Теперь давайте поговорим о стеке. Состояние стека было таким перед вызовом функции:2- {string: path.c_str()} 1- {function: loaded from file "src/test.lua"}Это от верха стека до "низа". Если вы используете
lua_pcall, который я вам дал, Вы получите следующее в своем стеке:1- {return value}The
lua_pcallwill remove the argument(s) и функция из стека. Таким образом, он будет выводить N + 1 элементов из стека, где N-число аргументов функции Lua, указанное вlua_pcall(второй параметр). Таким образом, Lua выбросит 2 вещи из стека. Затем он будет помещать в стек ровно 1 значение: возвращаемое значение (или ноль, если возвращаемого значения не было).Таким образом, мы проходим мимо вызова функции. Если все прошло хорошо, теперь мы ожидаем, что стек будет содержать:
Однако, возможно, все пошло не так хорошо. Сценарий, возможно, вернул ноль. Или что-то еще; нет никакой гарантии, что это был стол. Итак, следующий шаг-это проверка возвращаемого значения (Примечание: именно здесь ваш код перестает иметь смысл, так что это все новое).1- {table: returned from function}if(lua_istable(L, -1))
lua_istableделает именно то, что подсказывает название: определяет, является ли данный элемент таблицей. Но что означает это "-1", и почему это не" 1", которое вы имели в своем коде?Этот аргумент является ссылкой на местоположение в стеке. Стек Луа-это также стек Луа. регистровый файл. Это означает, что, в отличие от реального стека , вы можете достичь пика в любом элементе стека. Элементы в стеке имеют абсолютное расположение в стеке. Теперь, вот как снова выглядит наш стек:
1- {return value}То, что я написал "1", является абсолютным местоположением в стеке этого значения. Я могу нажимать значения и поп-значения, но если я не поп - это значение, его местоположение будет всегда быть "1".
Однако, это только "1", потому что наш стек начался пусто . Это несколько грубо предполагать, что это (так как это действительно может укусить вас, если стек не пуст. Документы Lua действительно услужливо указывают, когда вы можете предположить, что стек действительно пуст, или если его нет, то что уже находится в стеке). Поэтому можно использовать относительные местоположения.
И вот что такое "-1": это первый индекс стека из верхнего стека. Наша
Таким образом, мы проверяем, является ли индекс стека "-1" (верхняя часть стека) таблицей. Если это не так, то потерпите неудачу. Если это так, то мы можем разобрать наш список.lua_pcallфункция, как определено выше, будет выталкивать 2 элемента из стека (аргумент и функцию) и выталкивать 1 элемент (возвращаемое значение или ноль). Поэтому "-1" будет всегда ссылаться на наше возвращаемое значение.И здесь мы переходим к разбору списков. Первым шагом является получение количества элементов в списке:
int len = lua_objlen(L, -1); list_strings.reserve(len);Второй - это просто тонкость, так что вы не выделяете кучу раз. Вы точно знаете, сколько струн вы собираетесь быть в этом списке, так что вы можете также сообщить список заранее, не так ли?
Затем мы проходим по столу, вытаскивая струны.
lua_objlenвозвращает количество элементов массива в таблице. Обратите внимание, что это может вернуть ноль, но наш цикл будет обрабатывать этот случай.Помните, что Lua использует 1-базовые индексы. Я лично предпочитаю использовать 0-базовые индексы в коде C / C++, даже в коде, который взаимодействует с Lua. Поэтому я делаю перевод так поздно, как возможный. Но тебе это и не нужно. Теперь перейдем к содержанию цикла. Первый шаг-получить запись таблицы из таблицы. Для этого нам нужно дать Lua индекс и сказать Lua, чтобы он получил этот индекс из таблицы:for (int i=0; i < len; i++) { //Stuff from below. }Теперь первая функция помещает индекс в стек. После этого наш стек выглядит следующим образом:lua_pushinteger(L, i + 1); lua_gettable(L, -2);Функция2- {integer: i + 1} 1- {table: returned from function}
lua_gettableзаслуживает более подробного объяснения. Он принимает ключ (помните: ключи таблицы в Lua не обязательно должны быть целыми числами) и таблицу, и возвращает значение, связанное с этим ключом в этой таблице. Или ноль, если с ним не связана никакая ценность. Но то, как это работает, немного странно.Предполагается, что вершина стека является ключом. Таким образом, параметр, который он принимает, - это расположение стека таблицы , в которую будет индексироваться ключ. Мы используем "-2", потому что, ну, посмотрите на стек. Таблица - это 2 сверху, так как мы толкнули целое число; поэтому мы используем "-2".
После этого наш стек выглядит следующим образом: это:
Теперь, когда мы получили значение, мы должны проверить, что это строка, а затем получить его значение.2- {value: from table[i + 1]} 1- {table: returned from function}Эта функция делает все это сразу. Если значение, полученное из таблицы, не является строкой (или числом, так как Lua автоматически преобразует числа в строки), тоsize_t strLen = 0; const char *theString = lua_tolstring(L, -1, &strLen);theStringбудет NULL. В противном случаеtheStringбудет иметь принадлежащий Lua указатель (не удалять) на строку.strLenтакже будет иметь длину строки.Быстро в сторону: строки Lua Null-завершается, но они также могут содержать внутренние символы NULL. C-строки не могут этого делать, но C++
Теперь, когда у нас есть строковые данные из Lua, мы должны поместить их в наш список. Чтобы избежать ненужных копий, я предпочитаю следующий синтаксис:std::strings являются. Вот почему я не используюlua_tostringтак, как это сделали вы; строки C++ могут хранить Lua-строки точно так, как они есть.list_strings.push_back(); list_strings.back().assign(theString, strLen);Если бы я использовал стандартную библиотеку и компилятор с поддержкой C++11, я бы просто использовал
Есть еще один заключительный этап очистки, который нам нужно сделать. В нашем стеке все еще есть два значения: строка и таблица. Мы закончили с веревкой, так что нам нужно избавиться от нее. Это делается путем выделения одной записи из стека Lua:list_strings.emplace_back(theString, strLen);, полагаясь наemplace_backфункция для созданияstd::stringна месте. Это позволяет аккуратно избежать создания большего количества копий строки, чем это необходимо.lua_pop(L, 1);Здесь "1" - это число записей для pop, а не расположение стека.
А ты теперь вы понимаете, как работает управление стеком в Lua?
1) Просмотр состояния стека перед вызовом... luaL_loadfile помещает функцию в стек? Или это делает lua_pcall?Предполагая, что вы ничего не сделали с состоянием Lua, кроме его создания, то стек пуст перед luaL_loadfile. И да, luaL_loadfile помещает функцию в стек. Эта функция представляет загруженный файл.
3) каков был бы результат стек будет, если после выполнения вызова функции он вернет значение ошибки?
4) list_strings.резерв (len); что касается этого... Этот сценарий lua является фактически встроен в небольшую программу на языке Си, которая рекурсирует через кодовую базу и собирает все строки, возвращаемые сценарием lua из всех файлов... Я не знаю точно, как работает резерв, но я говорю, что буду использовать много таблиц для добавления строк в этот список... Должен ли резерв просто не использоваться в этом случае? или все еще используется...
std::vector::reserveгарантирует, чтоstd::vectorбудет содержать по крайней мере достаточно места для X элементов, где X-это передаваемое значение. Я сделал это потому, что Lua говорит вам, сколько элементов находится в таблице, поэтому нет необходимости позволятьstd::vectorрасширяться самостоятельно. Вы можете заставить его сделать одно выделение памяти для всего, а не позволять функцииstd::vector::push_backвыделять больше памяти по мере необходимости.Это полезно до тех пор, пока вы вызываете свой сценарий Lua один раз. То есть он получает одно возвращаемое значение от Lua. Независимо от того, насколько велик возвращаемый стол, это будет работать. Если вы вызываете Lua-скрипт (из C++) несколько раз, то невозможно заранее узнать, сколько памяти нужно сохранить. Вы можете зарезервировать место для каждой возвращаемой таблицы, но схема распределения по умолчанию
Тем не менее, было бы неразумно начать с здорового размераstd::vectorможет превзойти вас по количеству выделений для больших наборов данных. Так что в этом случае я бы не стал утруждать себяreserve.reserve, как своего рода случай по умолчанию. Выберите число, которое вы считаете "достаточно большим", и зарезервируйте столько места.
На стороне Lua нет стека. Значения, переданные на стороне C, передаются Lua в качестве аргументов для вызова. Если вы выполняете весь сценарий, а не конкретную функцию, аргументы доступны в виде
.... Таким образом, вы можете сделатьlocal myarg = ..., чтобы получить первый аргумент. Илиlocal arg ={...}собрать их всех в таблицу.
Comments