使用Lua和C ++管理堆栈 [英] Managing stack with Lua and C++

查看:212
本文介绍了使用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函数,



在解析之后,它应该返回一个包含其中的字符串的表(你可以把它当成搜索一个特定字符串的函数,我想)



编辑:更清楚。



任何建议/资源?
这里有类似的问题吗?或任何有用的资源?

解决方案

我想确保我明白你在做什么,走错了。你有一个Lua脚本文件。你想要执行这个脚本,传递一个单一的字符串参数。它会做一些东西,然后返回零个或多个字符串作为返回值。



好的,让我们从顶部开始:

  if((error = lua_pcall(L,1,LUA_MULTRET,0))== 0)

通常,当你执行 lua_pcall 时,第三个参数告诉Lua 多少个返回值。如果被调用的函数返回超过此数字,那些返回值将被丢弃。如果返回的数量少于此数量,则使用额外的NIL值填充计数。



LUA_MULTRET告诉Lua不要这样做。当使用这个时,所有的结果都被压入堆栈。



现在,由于你忽略了发布你的脚本,我必须猜测你的脚本喜欢。你返回多个字符串,但你永远不会说这是怎么回事。 Lua作为一种语言允许多个返回值:

  returnstring1,string2 

这会导致2个字符串被压入堆栈。这不同于:

  return {string1,string2}; 

这会将一个对象放入堆栈:该表包含2个字符串。看到差异?



看看你的代码,看起来你希望Lua脚本返回一个的字符串,而不是多个返回值



在这种情况下,您应该像这样调用您的Lua脚本:

  if((error = lua_pcall(L,1,1,0))== 0)

这告诉Lua,你期望一个返回值,如果用户不提供一个,Lua会将一个NIL推入堆栈。



现在让我们谈谈关于堆栈。在发出函数调用之前,堆栈的状态为:

  2- {string:path.c_str $ b 1- {function:从文件src / test.lua加载} 

堆栈的顶部到底部。如果你使用我给你的 lua_pcall ,你会在你的堆栈上得到以下:

  1- {return value} 

lua_pcall 将从堆栈中删除参数和函数。因此,它将从堆栈中弹出N + 1个项目,其中N是由 lua_pcall (第二个参数)指定的Lua函数的参数数量。因此,Lua会从堆栈中弹出2个东西。然后它将正确的1值推送到堆栈:返回值(如果没有返回值,则为NIL)。



这样我们就可以通过函数调用。如果一切顺利,我们现在期望堆栈包含:

  1- {table:从函数返回} 

但是,所有可能都没有很好。脚本可能已返回NIL。或者是其他东西;不能保证它是一个表。所以,下一步是验证返回值(注意:这是你的代码停止有意义,所以这是所有新的)。

  if(lua_istable(L,-1))

lua_istable 完全正确的名字建议:确定给定的项目是否是一个表。但是,-1是什么意思,为什么不是你的代码中的1?



这个参数是一个位置的引用堆栈。 Lua的堆栈也是Lua的寄存器文件。这意味着,与 real 堆栈不同,您可以在堆栈上的任何元素达到峰值。堆栈上的元素在堆栈上具有绝对位置。现在,这里是我们的堆栈看起来再次:

  1- {return value} 

我写的1是这个值的堆栈上的绝对位置。我可以推送值和pop值,但除非我弹出这个值,它的位置将总是为1。



只有1,因为我们的堆栈开始为空。这是有点粗鲁的假设这(因为它可以真正咬你,如果堆栈不是空的。当你可以假设堆栈真的是空的,或者如果它的不是,已经在堆栈)。因此,您可以使用相对位置。



这就是-1:它是从顶部的第一个堆栈索引堆栈。我们的 lua_pcall 函数将从堆栈中弹出2个项(参数和函数),然后push 1项(返回值或NIL)。因此,我们检查堆栈索引是否为-1,因为-1总是指向我们的返回值。



< (堆栈的顶部)是一个表。如果不是,则失败。如果是,那么我们可以解析我们的列表。



这是我们得到列表解析的地方。第一步是获取列表中的项目数:

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

第二个只是一个nicety,所以你不分配一堆次。你知道列表中有多少个字符串,所以你可以提前知道列表。



lua_objlen 获取表中数组元素的数量。请注意,这可以返回 zero ,但是我们的循环会处理这种情况。



接下来, / p>

  for(int i = 0; i  // 
}

请记住,Lua使用1个索引。我个人更喜欢使用0基础索引,而在C / C ++代码,甚至与Lua接口的代码。所以我做翻译尽可能晚。但你不必。



现在,对于循环的内容。第一步是从表中获取表项。为了做到这一点,我们需要给Lua一个索引并告诉Lua从表中获取索引:

  lua_pushinteger i + 1); 
lua_gettable(L,-2);

现在,第一个函数将索引推入堆栈。之后,我们的堆栈看起来像这样:

  2- {integer:i + 1} 
1- {table :从函数返回}

lua_gettable 函数值得更多的解释。它需要一个键(记住:Lua中的表键不必是整数)和一个表,并返回该表中与该键相关联的值。或者NIL,如果没有值相关联。但它的工作方式有点奇怪。



它假设堆栈的顶部是键。因此,它需要的参数是键将索引的的堆栈位置。我们使用-2,因为,好,看看堆栈。该表是从顶部的2,因为我们推一个整数;因此我们使用-2。



之后,我们的堆栈看起来像这样:

  2- {value:from table [i + 1]} 
1- {table:从函数返回}

现在我们已经得到一个值,我们必须验证它是一个字符串,然后得到它的值。

  size_t strLen = 0; 
const char * theString = lua_tolstring(L,-1,& strLen);

此函数立即执行所有这些操作。如果从表中获得的值不是字符串(或数字,因为Lua会自动将数字转换为字符串),那么 theString 将为NULL。否则, theString 将有一个Lua拥有的指针(不要删除)到字符串。 strLen 也会有字符串的长度。



快速拨号:Lua字符串以NULL结尾,也可以在内部包含NULL字符。 C字符串不允许这样做,但是C ++ std :: string s 。这就是为什么我不使用 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是要弹出的条目数,而不是堆栈位置。 p>

你知道堆栈管理如何在Lua中工作吗?







1)查看堆栈在调用之前的状态... luaL_loadfile将函数推送到堆栈?或者lua_pcall?


假设除了创建Lua状态之外没有做任何事情,那么在luaL_loadfile之前stack是空的。是的,luaL_loadfile将一个函数推送到堆栈。这个函数代表加载的文件。


3)堆栈的结果是什么,如果在调用函数后返回错误值? =http://www.lua.org/manual/5.1/manual。 html#lua_pcall>文档说。现在,您了解堆栈是如何工作的,您应该阅读文档。也建议在Lua书中编程。版本5.0是免费的在线免费,但5.1书费钱。 5.0本书仍然是一个有用的起点。


4)list_strings.reserve(len);至于这...这个lua脚本实际上嵌入在一个小的C程序,通过代码库递归,并将收集所有的字符串,lua脚本从所有的文件返回...我不知道究竟如何保留工作,但是我说的是,我将使用许多表来添加字符串到这个列表...应该保留只是不使用在这种情况下?或仍在使用...


std :: vector :: reserve std :: vector 将至少包含足够的空间用于X元素,其中X是您传递的值。我这样做是因为Lua告诉你表中有多少个元素,所以没有必要让 std :: vector 自己展开。你可以让它为一切做一个内存分配,而不是让 std :: vector :: push_back 函数根据需要分配更多的内存。



这是有用的,只要你一次调用你的Lua脚本一次。也就是说,它从Lua获取单个返回值。无论表返回多大,这将工作。如果你多次调用你的Lua脚本(从C ++),那么没有办法预先知道要保留多少内存。您可以为每个返回的表保留空间,但是可以通过 std :: vector 的默认分配方案在大数据集的分配数量上击败您。所以在这种情况下,我不会打扰 reserve



但是,这不是不明智的从健康的保留开始,作为默认情况。选择一个你认为足够大的数字,并保留很多空间。


I need to pass a lua script a single string (path to file), and return 0 to many strings.

int error = 0;
lua_State *L = lua_open();
luaL_openlibs(L);

std::vector<string> list_strings;

is used to push the string onto the stack, before I load and call the source file

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);
            }
        }
    }
}

As it stands, I've just been copying code from examples so I'm not really sure if what I'm doing is what I want to do... I want push the path onto the stack, and call a lua function which takes that value off of the stack, and will parse the file which associates to that path.

After parsing, it should return a table containing the strings that are inside of it (you can just think of it as a function that searches for a specific string, I suppose)

edit: made more clear.

Any advice/resources? Any similar questions here? or any helpful resources?

解决方案

I want to make sure I understand what you're doing before we see where you seem to be going wrong. You have a Lua script file. You want to execute this script, passing it a single string argument. It will do some stuff, then return zero or more strings as return values. And you want to fetch these values in your code.

OK, let's start from the top:

if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)

Normally, when you perform a lua_pcall, the third parameter tells Lua exactly how many return values are expected. If the function being called returns more than this number, those return values are discarded. If it returns fewer than this number, then additional NIL values are used to fill out the count.

LUA_MULTRET tells Lua not to do this. When this is used, all results are pushed onto the stack.

Now, since you neglected to post your script, I have to make some guesses as to what your script looks like. You are returning multiple strings, but you never say how this happens. Lua, as a language, allows multiple return values:

return "string1", "string2";

This results in 2 strings being pushed onto the stack. This is different from:

return {"string1", "string2"};

This puts one object onto the stack: a table. The table contains 2 strings. See the difference?

Looking at your code, it seems like you expect the Lua script to return a table of strings, not multiple return values.

In which case, you should call your Lua script like this:

if ((error = lua_pcall(L, 1, 1, 0)) == 0)

This tells Lua that you expect a single return value, and if the user doesn't provide one, Lua will push a NIL onto the stack.

Now let's talk about the stack. The state of the stack was this before issuing the function call:

2- {string: path.c_str()}
1- {function: loaded from file "src/test.lua"}

This is from the top of the stack to the "bottom". If you use the lua_pcall that I gave you, you will get the following on your stack:

1- {return value}

The lua_pcall will remove the argument(s) and function from the stack. So it will pop N + 1 items from the stack, where N is the number of arguments to the Lua function as specified by lua_pcall (the second parameter). Therefore, Lua will pop 2 things off the stack. It will then push exactly 1 value onto the stack: the return value (or NIL if there was no return value).

So that gets us past the function call. If all has gone well, we now expect the stack to contain:

1- {table: returned from function}

However, all may not have gone well. The script may have returned NIL. Or something else; there's no guarantee that it was a table. So, the next step is to verify the return value (note: this is where your code stops making sense, so this is all new).

if(lua_istable(L, -1))

lua_istable does exactly what the name suggests: determine if the given item is a table. But what does that "-1" mean, and why isn't it the "1" you had in your code?

This argument is a reference to a location on the stack. Lua's stack is also Lua's register file. This means that, unlike a real stack, you are allowed to peak at any element on the stack. Elements on the stack have an absolute location on the stack. Now, here's what our stack looks like again:

1- {return value}

That "1" I wrote is the absolute location on the stack of this value. I can push values and pop values, but unless I pop this value, it's location will always be "1".

However, it is only "1" because our stack started out empty. It's somewhat rude to assume this (as it can really bite you if the stack isn't empty. The Lua docs do helpfully state when you can assume the stack really is empty, or if its not, what is already on the stack). Therefore, you can use relative locations.

And that's what "-1" is: it is the first stack index from the top of the stack. Our lua_pcall function as defined above will pop 2 items from the stack (the argument and the function), and push 1 item (the return value or NIL). Therefore, "-1" will always refer to our return value.

Thus, we check to see if the stack index "-1" (the top of the stack) is a table. If it isn't, then fail. If it is, then we can parse our list.

And this is where we get to list parsing. The first step is to get the number of items in the list:

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

The second one is just a nicety, so that you're not allocating a bunch of times. You know exactly how many strings are going to be in that list, so you may as well let the list know ahead of time, right?

lua_objlen gets the number of array elements in the table. Note that this can return zero, but our loop will handle that case.

Next, we walk the table, pulling out the strings.

for (int i=0; i < len; i++) {
    //Stuff from below.
}

Remember that Lua uses 1-base indices. I personally prefer using 0-base indices while in C/C++ code, even code that interfaces with Lua. So I do the translation as late as possible. But you don't have to.

Now, for the contents of the loop. The first step is to get the table entry from the table. To do that, we need to give Lua an index and tell Lua to get that index from the table:

lua_pushinteger(L, i + 1);
lua_gettable(L, -2);

Now, the first function pushes the index onto the stack. After that, our stack looks like this:

2- {integer: i + 1}
1- {table: returned from function}

The lua_gettable function deserves more explanation. It takes a key (remember: table keys in Lua do not have to be integers) and a table, and returns the value associated with that key in that table. Or NIL, if no value is associated there. But the way it works is a bit odd.

It assumes that the top of the stack is the key. So the parameter it takes is the stack location of the table that the key will index into. We use "-2" because, well, look at the stack. The table is 2 from the top since we pushed an integer; therefore we use "-2".

After this, our stack looks like this:

2- {value: from table[i + 1]}
1- {table: returned from function}

Now that we have gotten a value, we must verify that it is a string, and then get its value.

size_t strLen = 0;
const char *theString = lua_tolstring(L, -1, &strLen);

This function does all of these at once. If the value we got from the table is not a string (or a number, since Lua will auto-convert numbers to strings), then theString will be NULL. Otherwise, theString will have a Lua-owned pointer (do not delete) to the string. strLen will also have the length of the string.

Quick aside: Lua strings are NULL-terminated, but they also can internally contain NULL characters. C-strings aren't allowed to do this, but C++ std::strings are. That's why I don't use lua_tostring the way you did; C++ strings can store Lua strings exactly as they are.

Now that we have the string data from Lua, we need to put it into our list. To avoid unnecessary copies, I prefer this syntax:

list_strings.push_back();
list_strings.back().assign(theString, strLen);

If I were using a C++11-enabled standard library and compiler, I would have just used list_strings.emplace_back(theString, strLen);, relying on the emplace_back function to construc the std::string in-place. This neatly avoids making more copies of the string than necessary.

There is one final bit of cleanup we need to do. Our stack still has two values on it: the string and the table. We're done with the string, so we need to get rid of it. This is done by poping one entry from the Lua stack:

lua_pop(L, 1);

Here, the "1" is the number of entries to pop, not the stack location.

Do you understand how stack management works in Lua now?


1) Looking at the state of the stack before the call... luaL_loadfile pushes a function to the stack? Or does lua_pcall?

Assuming you haven't done anything with the Lua state besides create it, then stack is empty before luaL_loadfile. And yes, luaL_loadfile pushes a function onto the stack. This function represents the file that was loaded.

3) What would the result of the stack be, if after making the function call it returned an error value?

Exactly what the documentation says. Now that you understand how the stack works, you should read through the docs. The Programming in Lua book is also recommended. Version 5.0 is available online for free, but the 5.1 book costs money. The 5.0 book is still a useful starting point.

4) list_strings.reserve(len); As for this... This lua script is actually embedded in a small C program that recurses through a code base and will collect ALL of the strings that the lua script returns from ALL of the files... I don't know exactly how reserve works, but What I'm saying is that I will be using many tables to add strings to this list... Should reserve just be not used in that case? or still used...

std::vector::reserve ensures that the std::vector will contain at least enough space for X elements, where X is the value you pass it. I did this because Lua tells you how many elements are in the table, so there is no need to let the std::vector expand on its own. You can make it do one memory allocation for everything, rather than letting the std::vector::push_back function allocate more memory as needed.

This is useful so long as you call your Lua script once. That is, it gets a single return value from Lua. No matter how large the table returned is, this will work. If you call your Lua script (from C++) multiple times, then there's no way to know ahead of time how much memory to reserve. You could reserve space for each table you get back, but it's possible for the std::vector's default allocation scheme to beat you in number of allocations for large datasets. So in that case, I wouldn't bother with reserve.

However, it wouldn't be unwise to start off with a healthy-sized reserve, as kind of a default case. Pick a number that you think would be "big enough", and reserve that much space.

这篇关于使用Lua和C ++管理堆栈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆