Управление стеком с помощью 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: сделано более ясным.



Какие-либо советы / ресурсы?
Есть ли здесь подобные вопросы? или какие-нибудь полезные ресурсы?

1006   2  

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_pcall will 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": это первый индекс стека из верхнего стека. Наша lua_pcall функция, как определено выше, будет выталкивать 2 элемента из стека (аргумент и функцию) и выталкивать 1 элемент (возвращаемое значение или ноль). Поэтому "-1" будет всегда ссылаться на наше возвращаемое значение.

Таким образом, мы проверяем, является ли индекс стека "-1" (верхняя часть стека) таблицей. Если это не так, то потерпите неудачу. Если это так, то мы можем разобрать наш список.

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

int len = lua_objlen(L, -1);
list_strings.reserve(len);

Второй - это просто тонкость, так что вы не выделяете кучу раз. Вы точно знаете, сколько струн вы собираетесь быть в этом списке, так что вы можете также сообщить список заранее, не так ли?

lua_objlen возвращает количество элементов массива в таблице. Обратите внимание, что это может вернуть ноль, но наш цикл будет обрабатывать этот случай.

Затем мы проходим по столу, вытаскивая струны.
for (int i=0; i < len; i++) {
    //Stuff from below.
}
Помните, что Lua использует 1-базовые индексы. Я лично предпочитаю использовать 0-базовые индексы в коде C / C++, даже в коде, который взаимодействует с Lua. Поэтому я делаю перевод так поздно, как возможный. Но тебе это и не нужно. Теперь перейдем к содержанию цикла. Первый шаг-получить запись таблицы из таблицы. Для этого нам нужно дать Lua индекс и сказать Lua, чтобы он получил этот индекс из таблицы:
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}
Теперь, когда мы получили значение, мы должны проверить, что это строка, а затем получить его значение.
size_t strLen = 0;
const char *theString = lua_tolstring(L, -1, &strLen);
Эта функция делает все это сразу. Если значение, полученное из таблицы, не является строкой (или числом, так как Lua автоматически преобразует числа в строки), то theString будет NULL. В противном случае theString будет иметь принадлежащий Lua указатель (не удалять) на строку. strLen также будет иметь длину строки.

Быстро в сторону: строки Lua Null-завершается, но они также могут содержать внутренние символы NULL. C-строки не могут этого делать, но C++ std::strings являются. Вот почему я не использую lua_tostring так, как это сделали вы; строки C++ могут хранить Lua-строки точно так, как они есть.

Теперь, когда у нас есть строковые данные из Lua, мы должны поместить их в наш список. Чтобы избежать ненужных копий, я предпочитаю следующий синтаксис:
list_strings.push_back();
list_strings.back().assign(theString, strLen);

Если бы я использовал стандартную библиотеку и компилятор с поддержкой C++11, я бы просто использовал list_strings.emplace_back(theString, strLen);, полагаясь на emplace_back функция для создания std::string на месте. Это позволяет аккуратно избежать создания большего количества копий строки, чем это необходимо.

Есть еще один заключительный этап очистки, который нам нужно сделать. В нашем стеке все еще есть два значения: строка и таблица. Мы закончили с веревкой, так что нам нужно избавиться от нее. Это делается путем выделения одной записи из стека Lua:
lua_pop(L, 1);

Здесь "1" - это число записей для pop, а не расположение стека.

А ты теперь вы понимаете, как работает управление стеком в Lua?


1) Просмотр состояния стека перед вызовом... luaL_loadfile помещает функцию в стек? Или это делает lua_pcall?

Предполагая, что вы ничего не сделали с состоянием Lua, кроме его создания, то стек пуст перед luaL_loadfile. И да, luaL_loadfile помещает функцию в стек. Эта функция представляет загруженный файл.

3) каков был бы результат стек будет, если после выполнения вызова функции он вернет значение ошибки?

Именно то, что говорится в документации.Теперь, когда вы понимаете, как работает стек, вы должны прочитать документы. Программирование в книге Lua также рекомендуется. Версия 5.0 доступна в интернете бесплатно, но книга 5.1 стоит денег. Книга 5.0 по-прежнему является полезной отправной точкой.

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

    Ничего не найдено.