Stack management with Lua and C ++

I need to pass lua script one line (file path) and return 0 to many lines.

int error = 0; lua_State *L = lua_open(); luaL_openlibs(L); std::vector<string> list_strings; 

used to push a line onto the stack before loading and calling 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); } } } } 

Be that as it may, I just copied the code from the examples, so I'm not sure what what I am doing is what I want to do ... I want the path to the lua stack that drops this value from stack and will parse the file that is associated with this path.

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

edit: made more clear.

Any tips / resources? Any similar questions here? or any useful resources?

+6
source share
2 answers

I want to make sure that I understand what you are doing before we see where you seem to be mistaken. You have a Lua script file. You want to execute this script by passing it one string argument. It will do some things and then return zero or more rows as return values. And you want to get these values ​​in your code.

OK, start at the top:

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

Usually, when you execute lua_pcall , the third parameter tells Lua exactly how many expected return values. If the called function returns more than this number, these return values ​​are discarded. If it returns less than this number, then additional NIL values ​​are used to fill the account.

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

Now, since you neglected to submit your script, I have to make some guesses about what your script looks like. You return a few lines, but never say how this happens. Lua, as a language, allows several return values:

 return "string1", "string2"; 

This results in two lines being pushed onto the stack. This is different from:

 return {"string1", "string2"}; 

One object is pushed onto the stack: a table. The table contains 2 rows. See the difference?

Looking at your code, it seems like you are expecting a Lua script to return a table of strings, rather than a few return values.

In this case, you should invoke the Lua script as follows:

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

This tells Lua that you expect a single return value, and if the user does not provide it, Lua pushes NIL onto the stack.

Now let's talk about the stack. The state of the stack was 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 in your stack:

 1- {return value} 

lua_pcall will remove the argument and function from the stack. Thus, it will push N + 1 elements from the stack, where N is the number of arguments to the Lua function, as indicated by lua_pcall (second parameter). Therefore, Lua will pull 2 ​​things from the stack. Then it pushes exactly 1 value onto the stack: the return value (or NIL if there was no return value).

So, we will skip the function call. If everything went well, now we expect the stack to contain:

 1- {table: returned from function} 

However, perhaps everything went well. The script may have returned NIL. Or something different; there is no guarantee that this is a table. So, the next step is to check the return value (note: this is where your code no longer makes sense, so this is all new).

 if(lua_istable(L, -1)) 

lua_istable does exactly what the name suggests: determine if this element is a table. But what does this β€œ-1” mean, and why is it not β€œ1” in your code?

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

 1- {return value} 

What "1" I wrote is the absolute location on the stack of this value. I can click values ​​and pop values, but if I didn’t pull that value, this location will always be β€œ1”.

However, this is only "1" because our stack is started empty. This is somewhat crude to suggest this (since it can really bite you if the stack is not empty. In Lua docs it is useful to indicate when you can assume that the stack is really empty, or if it is not, then what is already on the stack) . Therefore, you can use relative locations.

And this is what β€œ-1” is: this is the first stack index from the top of the stack. Our lua_pcall function, as defined above, lua_pcall 2 items from the stack (argument and function) and presses 1 item (return value or NIL). Therefore, "-1" will always refer to our return value.

Thus, we check if the stack index "-1" (the top of the stack) is a table. If this is not the case, then fail. If so, then we can analyze our list.

And here we move on to 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 is just subtlety, so you do not select a bunch of times. You know exactly how many lines will be in this list, so you can also tell the list in advance, right?

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

Then we will walk around the table, stretching the rows.

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

Remember that Lua uses 1-base indexes. I personally prefer to use 0-base indexes in C / C ++ code, even code that interacts with Lua. Therefore, I am doing the translation as late as possible. But you do not need it.

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

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

Now the first function pushes the pointer 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 the key (remember: the keys of the table in Lua do not have to be integers) and the table, and returns the value associated with this key in this table. Or NIL if there is no value. But the way it works is a little strange.

The top of the stack is assumed to be the key. Thus, the parameter that it takes is the location of the stack of the table into which the key will be indexed. We use "-2" because, well, look at the stack. Table 2 above, as we pressed an integer; therefore we use "-2".

After that, our stack looks like this:

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

Now that we have the value, we need to check 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 it all at once. If the value we got from the table is not a string (or a number, since Lua will automatically convert numbers to strings), then theString will be NULL. Otherwise, theString will contain a Lua pointer (do not delete) to the string. strLen will also contain the length of the string.

Quick side: Lua strings have a NULL end, but can also contain NULL characters. C-lines are not allowed, but C ++ is std::string . This is why I do not use lua_tostring way you did it; C ++ strings can store Lua strings exactly as they are.

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

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

If I were to use the standard library and compiler with C ++ 11 support, I would just use list_strings.emplace_back(theString, strLen); , relying on the emplace_back function, construct std::string in place. This neatly avoids making more copies of the string than necessary.

There is one last bit of cleaning we need to do. Our stack still has two values: row and table. We are done with the line, so we need to get rid of it. This is done by entering one entry from the Lua stack:

 lua_pop(L, 1); 

Here, β€œ1” is the number of entries for pop, not the location of the stack.

Do you understand how stack management works in Lua?


1) Considering the state of the stack before calling ... does luaL_loadfile push the function onto the stack? Or lua_pcall?

Assuming you have done nothing with the Lua state other than creating it, then the stack will be empty before luaL_loadfile. And yes, luaL_loadfile pushes the function onto the stack. This function represents the file that has been downloaded.

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

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

4) list_strings.reserve (len); As for this ... This lua script is actually built into a small C program that recurses through the code base and will collect ALL the lines that the lua script returns from ALL files ... I don’t know, I know exactly how the reserve works, but I say that I will use many tables to add rows to this list ... If the reserve simply will not be used in this case? or still in use ...

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

This is useful if you call your Lua script once. That is, it receives a single return value from Lua. No matter how big the table is, this will work. If you call your Lua script (from C ++) several times, then there is no way to know in advance how much memory to reserve. You can reserve a place for each table that you return, but it is possible for the default distribution scheme std::vector to beat you in the number of distributions for large data sets. Therefore, in this case, I would not worry about reserve .

However, it would not be wise to start with a healthy reserve size, as a kind of default case. Choose a number that you think will be β€œbig enough” and reserve so much space.

+30
source

There is no stack on the Lua side. Values ​​inserted on the C side are sent to Lua as arguments to invoke. If you execute the whole script, and not a specific function, the arguments are available as ... So you can do local myarg = ... to get the first argument. Or local arg ={...} so that they all fall into the table.

+3
source

Source: https://habr.com/ru/post/891579/


All Articles