luapi An api extension for Lua 5.0 (C) Marcelo Nicolet 2003 mnicolet@satlink.com MANIFEST doc/luapi.txt : this text doc/luapi.howto : some recipes ( not very complete ) include/luaex.h : declaration of some 'primary' Lua's api extensions include/luapi.h : declaration of types, macros and functions src/lapiex.c : extension to the 'primary' Lua's api src/luapi.c : luapi module src/luaval.c : luaval ( luapi values support module ) host/host.c : host C test program. host/udtest.c : userdata C test program host/1.lua : some data to play with host/2.lua : some data to play with host/4.lua : some data to play with these scripts are intended above all, to play with function execution and parameter passing, and table operations ( retrieving, updating, etc ) host/ud.lua : test some userdata operations host/ud2.lua : test some userdata operations host/ud3.lua : test some userdata operations host/makefile : makefile for the host test program LICENSE The license terms for luapi are exactly the same as for Lua 5.0 INSTALLATION This installation method is the one I used, and have the merit of no large scale changes to the original Lua distribution. WARNING: the Makefile in the src directory is overwriten, but the modified one cleanly separates the additions, so one can back to the original one easily by hand. Also, if one have the rcs system, it can be cheked out. 1 - Unpack this tarball in the same directory where your Lua 5.0 sources reside, so the .h, and .c main files go to include and src respectively, and host will be made as a sibling of the previous. 2 - Go to src directory and make. The new liblua will include the luapi object modules. 3 - To build the host test program Alter the host/makefile to suit your compiler needs. As is, this makefile will copy some src modules to the host directory to compile them with debugging info. Your linker must be able to cope with explicit object listing, even if some of them are in the libraries. I will not document host usage in detail ( please see the source for that purpose - it's very simple ), but it allows lots of interactive Lua manipulation such as - loading of lua modules - get, set and deletion of globals on global deletions, it also recognizes global table deletions, and queries the user regarding which mechanism to use. This is great to test luapi_deltable and its options. - Explicit table creation Being the most convoluted topic, this is a separate option, and allows ( see the code to learn exactly how ) to recursively set a table member to a subsidiary one, or a pre-existent global, etc - Table operations Every time a table is 'in focus', it allows to - get a 'row' by key - add a 'row' ( index, value ) the 'row value' can be itself a table created 'on the fly' - delete a 'row' by key if the 'row value' is itself a table, all the options regarding table deletions are allowed - browsing it you can 'filter' the type of the elements to be showed during the browse - Function execution Every time a function is 'in focus', it allows you to execute it, passing as many parameters as you like, and showing the results. The execution itself can be made using the name of the function ( if it's global ) or the reference to allow test both methods. - Userdata exemplification Tables and functions are 'in focus' as a result of every operation that retrieves a Lua object, i.e, getting a global, browsing or examining a table, and as a function result. The only case which is not considered 'active' is the C function parameters show, simply to not mess the display. Last but not least, one special argument sintax allows you to execute a set of commands stored in a file, repeating them n times. This is @n,commandfile, where the '@' signals the special option, n is the number of iterations you want, and commandfile is ... that. To create a command file, the best way is to start host with an argument of =filename ( the equal sign followed by a filename - be careful to give a 'foo' one - it will be overwritten ), under such condition, all what you type will be stored in 'filename'. This feature is useful twofold: a) to check memory leaks. Initially, I wonder regarding the exact mechanism required to delete a table which allows the gc to collect those deleted objects ... In fact, I am not sure if the laborious mechanism used in deltable ( restarting the browsing until no more elements are left ) is really needed ... but it works You can create ( 'manually' or loading a module which does ) and delete a table repeteadly but the data segment of the program stabilizes after a few iterations. b) to 'time' some path of execution. My apologies for the bizarre argument convention, but it was developed on the fly. Any other argument to the executable is interpreted as a lua module to load. The whole thing is, in this respect, a nice demonstration of the reflectivity of Lua, and how luapi follows that paradigm. You can see how there are a short handful of functions which serve all purposes. That is not the result of careful design, rather the program was grown as new luapi functions and features went functional, and then, some code chunks reorganized because common functionality, put into focus by the api new capabilities. Enjoy ! MOTIVATION AND PURPOSE I was using Lua 3.2 as the configuring language for a very ambitious general accounting system. That was a success, even if the lua's usage is 'in extension' and not 'in deep'; i.e I was a Lua 'newbie' and limited its uses to the most simplistic ones. Also, there was the user question: you cannot face it with sophisticated and carefully sintax without risk of a module being rejected over and over. But this is another, generic question. Because I am focusing on Lua for some even more ambitious projects, it seems a Good Thing to migrate to Lua 4.0, at least because of separate 'interpreters' ( lua_States ). And there is a lot more ... When I saw the api differences among versions, and how I interspersed Lua api functions into my production code, and the code changes involved, I decided it was time to 'officialize' two main things - the first one, to generalize and standardize some ideas I used in the accounting system, mainly, some data wrappers. - the second, to make some draft ideas present in that system a 'second stage' generic Lua's api. Both aims are intimately related, and follows a single direction, mainly - to simplify the Lua interface from C. This has mainly to do with shortened lua stuff code ( which sometimes are a lot of lines without clear relation with the code purpose ) - to hide future changes in the api. This may be broken by future big changes not only in the api, but in the concepts underlying, but Waldemar, Roberto, and Luiz surprise us with very cool changes, so .. it's a matter of bet. - to retain ( surprisingly for me, also ) some 3.2 functionality The luapi code is surprisingly simple. This is not a merit of mine; the main credit must be for Lua authors. I simply put it to work, after a week of carefully study of some Lua internals, coding and testing. The fast change from lua-4.0.1 to lua-5.0-alpha was done because userdata. I like the userdata whole idea, because it provides an additional way to interface lua with host 'functionality'; specifically, to query some 'host states' I think it's more clear to directly interrogate some variable than to call a function. In fact, my current 'lua enhanced' system is doing so: it's setting some globals. But that is cumbersome: the code must explicitly call lua_setglobal ( or luapi equivalent: luapi_setglob ) for every state to publish. It would be better if there were a permanent 'binding' between the host variables and the language. The present luapi for 5.0 final is a complete reworking, to take into account the new 'architecture' of a lua_State, mainly, the 'spaces', i.e Globals and Registry tables. Documentation What it provides a) a comfortable mechanism to pass values from/to lua interpreter. This is mainly accomplished by the luav_ functions and macros, and the proper luapi functions luapi_push luapi_spval luapi_pop luapi_stack. The luav_ family relies on a circular buffer to obtain ( if needed ) memory for transparently pass values from/to lua, allowing you to pass NULL pointers from your C code, if you like. The main interest of this mechanism is that it is flexible: for some of the 'objects' involved, you can elect among allocate in the stack of your C function, or allows luapi to obtain the required memory. In this last case, the very important thing is that you are not concerned with heap management. Of course, care must be taken: this memory is as 'volatile' as a stack variable. Note that 'string values' are always 'copied' to 'allocated' memory ( this simplifies the bussiness ), and that, when the 'value' comes from lua ( luapi_spval( ) ), lua_strlen( ) is used to get the length ( honoring the 4.0 ability to store arbitrary byte sequences in a string ); so if you are using lots of big strings, it may be wise to set CBUF_SIZE to some bigger than the current value, which is calculated to store 3000 luaval_t no matter how much padding your compiler puts between structure members. At a second level, the transparent value passing ( luapi_push() and luapi_spval() ) does two important things a.1) conversion 'type' from luav types to Lua types and viceversa a.2) access a special api extension function ( lapiex.c and luaex.h ) to push function, table and userdata 'objects' to the Lua stack. This allows you to reference 'anonymous' functions and tables from C code without worry. b) Full stack control. At the expense of some extra code and function calls, the luapi functions warrants a correct stack manipulation, so freeing the programmer from this chore. c) Complete front ends for c.1) Global and Registry entries manipulation c.2) Function call and result retrieving using a single api call c.3) Full table manipulation c.4) Simple and complete userdata support ... c.5) Some powerful constructs Usability ========= Whether to encapsulate the official lua api and how is mainly a matter of taste. Of course, I like the style luapi promotes: declarations and inicializations clearly delimited from usage, some more code and time to hide details ( stack management, by example ), and some independence in the application code, even if names and exact functionality changed, the main concepts remains there. In fact, to 'port' host.c from the previous api to the new was no more than two hours. The main disadvantage is the hack in the lapiex.c code. I am intrigued why the lua team don't provided the two functions I devised. lua_pushobject is a dangerous function, and may be this was the reason. But, with caution ( see warnings below ), it can be used safely. Aside from true use, I think luapi is interesting for learning. Because it lacks all elegance, it may be clearer how to use the api. Warnings ======== 1) Some lua data types are not supported at this time. They are LUA_TBOOLEAN LUA_TLIGHTUSERDATA LUA_TTHREAD Care must be taken when calling a host function to avoid passing these types as arguments, because their interpretation by luapi_spval is undefined 2) Some lua object types must be treated with care. They are LUA_TTABLE ( LAPI_TAB ) LUA_TFUNCTION ( LAPI_FUN ) LUA_TUSERDATA ( LAPI_U_D ) any luaval_t reference to those must be treated as an opaque type, and passed again to luapi functions without any intervention. That is because the possibility of re-pushing such values depends heavily on the layout of these objects, which may change between versions/releases. The api for the re-push is an extension to the official lua api. Also, it's not wise to store these references for future use. Data support ============ All luapi functions depends on some data structures and their fair primary allocation scheme. The allocation scheme relies on a circular buffer, statically allocated and sized so it can yield 3000 luaval_t objects ( 36000 bytes if no padding is added by the compiler ). But this is only a hint, because the strings' content requires their own memory. Moreover, some architectures may require a revision of the allocation algorithm. In other words: all luaval_t ( or luapair_t ) objects must be considered highly volatile. To compensate for this volatility, there are some circumvents. a) any of the luav_set... functions can take a pointer as the region to set. If the pointer is NULL, the required memory is taken from the buffer. But be aware that luav_setstring allways allocates the memory for the string bytes from the buffer. All remaining types are contained in an internal union, so no extra memory is required. luav_setto( ) simply copies types and pointers. b) a family of complimentary functions allows for CB conservation. All of these functions expect a pointer to luaval_t. If this pointer is NULL, memory is allocated from the heap. b.1) luav_hstr is useful for management of 'string' luavals allocated in the host data space. The last parameter controls whether the char * passed may be simply copied ( string constants or stack allocated ) or must be allocated. In this case, the memory is obtained from the heap. b.2) luav_hnum sets the value of the number member. b.3) luav_hobj sets the value of the pointer member. b.4) luav_hcp copies it second parameter to the first one. If the destination pointer is NULL, space is allocated from the stack. If the source and destination are LAPI_STR, and the length of the destination is big enough, the source string is copied, else the required space is allocated WITHOUT freeing the previuos one, because there is no way to know where that memory is. WARNING: if you are allocating space from the heap, you are responsible for it management. There is an special luav type: LAPI_NONE, whose value was determined so any luaval with all bytes zeroed, is of type LAPI_NONE. The best way to initialize a luaval is to memset it to zero. luav_setnone( ) only sets the type member ( may be it could do the memset ). Also, don't hack into the members of luaval_t. Use the functions or macros. Functions by usage ------------------ all luapi_ functions receives a lua_State as the first parameter. stack manipulation luapi_push : pushes a luaval_t into the lua stack with the necessary conversion to lua 'type' NOTE: of course, the stack is left alone !!! Params: luaval_t - reference to value to be pushed if the NULL pointer, a nil is pushed on the stack Return: the new stack top as an int luapi_pushrow : pushes the two luavals received, using _push Params: luaval_t luaval_t Return: the new stack top as an int luapi_pushpair : pushes the two members of the luapair_t received, using _push Params: luapair_t Return: the new stack top as an int Caveat: no check is made for a NULL pointer luapi_spval : gets a 'lua value' from one designated stack position without popping the stack. Params: luaval_t : place to put the retrieved value ( may be NULL ) int : stack position Return: luaval_t, same pointer as passed, or an allocated one Caveat: if the type of the stack pos has no equivalent to LAPI, a warn is issued, and nothing is done. So, the luaval passed will return intact. luapi_pop : calls luapi_spval and pops the stack Params: same as spval Return: same as spval Caveat: sp would be the top of the stack luapi_stack : pops the entire stack, returning an array of luaval_t which mimics the stack content Params: none Return: an array with the stack content, whose size is one more the number of entries, to signal the end with a luav_isnone( ) use luav_tsize( ) to get the number of entries luapi_cfstack : copies the stack content, without popping it, into an array of luaval_t Params: none Return: an array with the stack content, whose size is one more the number of entries, to signal the end with a luav_isnone( ) use luav_tsize( ) to get the number of entries It's the same as luapi_stack, without popping anything luapi_cfreturn : In cases where the host program retrieved the stack without popping it ( using _cfstack, by example ) and later pushed some more values ( may be results for a function call ), this function removes anything in the stack below the indicated number of values. Params: the number of stack entries to preserve Return: the same int argument received, so it can be used as luapi_push( ... ) r ++; luapi_push( ... ) r ++; ............. return cfreturn( L, r ); see host.c, host_Prot for an usage example This stack management policy may be useful whenever one may be dealing with objects created in the stack, to preserve them from collection. luapi_methodstack : like luapi_stack, but only pops an explicit number of entries, returning an error condition if there are not at least the requested entries Params: the expected number of entries Return: like _stack, but limiting the entries popped to the expected number. May be NULL to signal the condition where there are less entries than expected. global and table space manipulation luapi_getGt returns a luaval_t referencing the globals table Params: luaval_t pointer to an area where to put the info (may be NULL) Return: luaval_t, the same passed or an allocated one luapi_getRg returns a luaval_t referencing the registry table params and return as getGt luapi_solvename given a luaval_t which represents a table in the lua_State, and a const char *, this function tries the passed text as an index into the table, and if it exists, returns a luaval_t reference to the associated value. If the 'table' is passed a NULL pointer, the global table is substituted. Two important things which made this routine special - if the first byte of the text is a digit, the index is tried as numeric. - the most interesting: the text is parsed at some punctuation marks ( '.' and ':' ), and every token is recursivelly tried in the most recent table space until no more tokens left, or the most recent 'solution' is not a table. An example would clarify the thing suppose you have a global table named Mytable, which has 5 entries indexed by numbers, which in turn are tables with indexes 'a', 'b' and 'c' solvename( L, NULL, "Mytable.2.b" ) will try to get Mytable from the global space; being a table and having a next token, ( '2' ), this will be searched in the Mytable space; being this new reference itself a table, and having one more token, it will in turn tried. The process will end when a) there are no more tokens b) the last reference obtained is not a table even if more tokens remains ( in which case, the reference returned will be a luav_setnil one ) Params: luaval_t pointer to the space to start with. May be NULL ( getGt ) const char * expression Return: luaval_t : the reference to the object, luav_isnil if no expression or expression the empty string, or tokens not searchable because some intermediate is not a table luapi_getname given a space and a text pointer, acts as a front end for solvename Params: same as solvename, plus an optional luaval to get the result Return: same as solvename luapi_getgname front end for getname, passing getGt as the space Params: same as getname, exception of space, which is provided as getGt Return: same as solvename luapi_getrname like the previous, but with getRg as the space luapi_setname given a space, a name ( which may be 'compound' ) and a luaval_t reference to a 'value' it parses the name to split it into a 'path' and a 'stem', i.e, the last ( or unique ) token is the 'stem', all previous ( if any ) the 'path'. If there is a 'path', the space reference is 'corrected' to the possible resulting table, using solvename service. Then, using an even more elementary primitive, it sets the entry keyed by the 'stem' into the space to the value indicated by the third argument. Params: luaval_t space ( required ) const char * name ( required ) luaval_t newval ( required ) Return: int, > 0 success, 0 error: some of the pointers NULL, or 'path' solving to non table object luapi_setgname same as setname, but defaults to space = getGt luapi_setrname same as setname, but defaults to space = getRg luapi_delname given a space and a possible compound text, it's a front end to setname( space, text, luav_setnil( ) ) Params: luaval_t space ( required ) const char * name ( required ) luaval_t newval ( required ) Return: int, > 0 success, 0 error: some of the pointers NULL, or 'path' solving to non table object luapi_delgname front end to delname, using getGt luapi_delrname front end to delname, using getRg table manipulation, primitive level luapi_setelem given a table reference, a reference for a key, and a reference for a value, it tries to set the key to the value into the table returns 1 if successfull, 0 otherwise Params: luaval_t as table luaval_t as key luaval_t as value Return: int > 0 success 0 the table is NULL or not a table luapi_getelem given a table reference, a reference for a key, and an optional pointer to a luaval_t memory chunk, it tries to get the value of the key from the table If successfull, returns a pointer to the associated value ( which may be stored in the optional pointer ) otherwise if the table reference is not such, luav_setnone( ) is returned; if the key is not present in the table, luav_setnil( ) ( which is the exact lua equivalent ) is returned. Params: luaval_t table luaval_t key luaval_t value ( optional, one will be assigned ) Return: luaval_t as described luapi_delelem calls setelem with a luav_setnil Params: luaval_t as table luaval_t as key Return: int > 0 success 0 the table is NULL or not a table luapi_setpair front end to setelem, but sourcing from a luapair_t ( which is a convenience structure comprising two luaval_t ) luapi_getpair front end to getelem luapi_delpair front end to delelem luapi_setelems like setelem, but assuming the key and value pointers are actually arrays, whose size may be given ( the int argument ) or marked by setting the 'last key' to luav_setnone( ) Params: luaval_t table luaval_t keys ( array of ) luaval_t values ( array of ) int n >= 0 number of valid entries n < 0, iterates the arrays until luav_isnone( key ) Return: int, the number of entries iterated luapi_getelems like getelem, but assuming the key and value pointers are actually arrays, whose size may be given ( the int argument ) or marked by setting the 'last key' to luav_setnone( ) iterates over the array of keys, setting the corrsponding values, using the same logic for array size as setelems Params: luaval_t table luaval_t keys ( array of ) luaval_t values ( array of ) int n >= 0 number of valid entries n < 0, iterates the arrays until luav_isnone( key ) Return: int, the number of entries iterated, which is not the same as the entries succesfully retrieved luapi_delelems taking only an array of keys, and counting as the previous deletes the entries in the array Params: luaval_t table luaval_t keys ( array of ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( key ) Return: int, the number of entries iterated, which is not the same as the entries succesfully deleted luapi_getpairs front end to getelem, but using an array of luapair_t, using the same logic as getelems to find the end of the array Params: luaval_t table luapair_t pairs ( array of ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( pair.key ) Return: int, the number of entries iterated, which is not the same as the entries succesfully retrieved luapi_setpairs front end to setelem, but using an array of luapair_t, using the same logic as setelems to find the end of the array Params: luaval_t table luapair_t pairs ( array of ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( pair.key ) Return: int, the number of entries iterated, which is not the same as the entries succesfully setted up luapi_delpairs front end to setelem, but using an array of luapair_t, using the same logic as delelems to find the end of the array Params: luaval_t table luapair_t pairs ( array of ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( pair.key ) Return: int, the number of entries iterated, which is not the same as the entries succesfully deleted luapi_newelemtable creates a new lua table, populating it from the optional arrays of keys and values, calling setelems with the new table. If those arrays are NULL, the function simply creates the table. Returns the table 'reference' after removing it from the stack, or the NULL pointer if something goes wrong. NOTE: the created table is 'anonymous' !!! Params: luaval_t keys ( array - optional ) luaval_t values ( array - optional ) int n >= 0 number of valid entries n < 0, iterates the arrays until luav_isnone( pair.key ) Return: luaval_t table reference or NULL if some error in the lua_newtable function call luapi_newpairtable creates a new lua table, populating it from the optional arrays of pairs, calling setpairs with the new table. If the pairs array is NULL, the function simply creates the table. Returns the table 'reference' after removing it from the stack, or the NULL pointer if something goes wrong. NOTE: the created table is 'anonymous' !!! Params: luapair_t pairs ( array, optional ) int n >= 0 number of valid entries n < 0, iterates the arrays until luav_isnone( pair.key ) Return: luaval_t table reference or NULL if some error in the lua_newtable function call table manipulation, high level luapi_ptable a front end to newpairtable, which optionally binds a name to the new table in a given space. If no space and name are given, the table remains 'anonymous' the pairs and n arguments are passed to newpairtable. Returns the table 'reference' which can be NULL. Params: luaval_t space ( optional ) const char * name ( optional ) luapair_t pairs ( optional ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( pair.key ) Return: luaval_t table reference or NULL, as returned by newpairtable luapi_etable a front end to newelemtable, which optionally binds a name to the new table in a given space. If no space and name are given, the table remains 'anonymous' the pairs and n arguments are passed to newpairtable. Returns the table 'reference' which can be NULL. NOTE: these two functions does not evaluate the name argument to see if it's numeric ... luaval_t space ( optional ) const char * name ( optional ) luaval_t keys ( array - optional ) luaval_t values ( array - optional ) int n >= 0 number of valid entries n < 0, exploit the arrays until luav_isnone( key ) luapi_deltable given a table reference, browses all its elements, setting each to nil. If some of the element values are tables, and the deep argument is not zero, the son tables are recursively browsed and deleted. The main purpose of this function is to get rid from nested tables. NOTE: the table reference itself is not marked nil, because the function does not expect a 'space' !!! To delete the table itself, one must resort to delgname ( if global ) or to delelem, if one knows the parent This function could be better named emptytable, but now it's too late. Params: luaval_t table reference int > 0, recursively delete son tables Return: void luapi_browtable given a table reference and a luapair_t, it behaves like a front end to lua_next, getting the next pair from the table. To start browsing a table, one can give a NULL pointer for the luapair_t argument, or manually set the key member to luav_setnil. Passing a NULL pointer will cause to get one from the circular buffer. The new pair ( key, value ) is stored in the passed pair, and a pointer to it is returned. If some error occurs or the table is exhausted, the function returns NULL. Params: luaval_t table reference luapair_t current key value pair Return: luapair_t or NULL function calling luapi_setcallobj this function expects a luaval_t which references a function ( a _lua_ function, whether it's a C function registered in the lua context, or a function defined in the lua space ) NOTE this function pushes the reference, and returns the new stack 'pointer' Params: luaval_t function reference Return: int, the new stack top, or same if not a luav_isfun( ) luapi_callexec assumes that the current 'bottom' of the stack is a function object. If the array of luaval_t is not NULL, pushes them until the count reaches the n argument ( being >= 0 ) or one of the arguments is luav_isnone ... after that, calls luapi_call, which in turns calls lua_pcall. luapi_call and luapi_callexec returns the stack content ( luapi_stack ) as their result. Params: luaval_t arg ( array - optional ) int n >= 0, the number of args, < 0, iterate the array until luav_isnone( ) Return: luaval_t array of results ( luapi_stack ) luapi_callobj given a function reference and an array of arguments, this function calls setcallobj, and callexec, returning the result of the last. If the 'function' luaval_t argument is not such, it returns NULL. Params: luaval_t function reference luaval_t arg ( array - optional ) int n >= 0, the number of args, < 0, iterate the array until luav_isnone( ) Return: luaval_t array of results ( luapi_stack ) luapi_callfunc given a space ( which may be NULL, substituted by getGt ) and a name, it tries to solve the name to a function object calling solvename ... If the name resolves into a function object, it calls callobj returning it result, else returns the NULL pointer Params: luaval_t space ( table ref - optional = getGt ) const char * name luaval_t arg ( array - optional ) int n >= 0, the number of args, < 0, iterate the array until luav_isnone( ) Return: luaval_t array of results ( luapi_stack ) or NULL if name cannot be found in space, or is not luav_fun( ) luapi_ecallobj like callobj, but the arguments are arrays of keys and values, which are used to populate a table ( newelemtable ) It's intended for functions that expect a table as an argument. Params: luaval_t function reference luaval_t keys ( array - optional ) luaval_t values ( array - optional ) int n >= 0, the number of args, < 0, iterate the array until luav_isnone( ) Return: luaval_t array of results ( luapi_stack ) luapi_pcallobj like ecallobj, but the table is populated from an array of pairs. Params: luaval_t function reference luapair_t pairs ( array - optional ) int n >= 0, the number of args, < 0, iterate the array until luav_isnone( ) Return: luaval_t array of results ( luapi_stack ) luapi_ecallfunc combines the functionality of ecallobj and callfunc Return: the result array or NULL luapi_pcallfunc combines the functionality of pcallobj and callfunc Return: the result array or NULL userdata support luapi_pushmethods given a table reference and an array of pairs name - cfunction ( luapirh_t ) adds every of the pairs to the table the array end is marked with a NULL name Params: luaval_t table reference luapirh_t array of pairs name - function Return: the number of entries in the luapirh array or 0 if table or array are NULL luapi_newmetatable given a name, and an array of luapirh_t creates an empty table in the registry ( luapi_ptable ) and populates it using luapi_pushmethods an optional argument of luaval_t type is a pointer to host memory where to copy the table reference obtained from luapi_ptable Params: const char * name luaval_t place ( optional ) luapirh_t array of methods Return: luaval_t table reference or NULL ( newpairtable failure ) luapi_registermetatable given a name and a table reference name may well be the name used for the previous function table reference would be the reference obtained as a result a) creates an entry in the table, using as key a luapi string constant ( ___udId ) and the name as a value b) creates an entry in the registry, with the name pointing to the metatable reference This second step may be redundant with the previous function if name is the same, but does not disturb and is here for the case where one may prefer to use two names. Params: const char * name luaval_t metatable reference Return: int allways 1 luapi_getmetatable given the name used for the previous function, this function is a shortcut to luapi_getrname, thus returning the table reference Params: const char * name luaval_t place ( optional ) Return: luaval_t table reference or NULL if name don't bind to a table luapi_getudtype given a metatable reference, it gets the ___udId member, so extracting the name of the table Params: luaval_t metatable luaval_t place ( optional ) Return: luaval_t reference to a lua string which is the name of the metatable NULL if metatable is NULL or ! luav_istab( ) luapi_isudtype to check if a userdata reference has a metatable whose id is the same as given Params: luaval_t userdata reference const char * name Return: int 1 all operations ok and the id of the userdata metatable same as passed 0 some operations are not possible or the names does not match luapi_getobjmetatable given a userdata reference, retrieves the metatable Params: luaval_t userdata reference luaval_t place ( optional ) Return: luaval_t with the result: a metatable reference if all went ok, or luav_isnil( ) otherwise luapi_methodstack already mentioned luapi_rawudobj first step to userdata creation. It allocates a userdata of given size, and returns a reference to it Params: int userdata size luaval_t place ( optional ) Return: luaval_t reference to the new userdata ( may be luav_isnil if something goes wrong ) the stack is cleared, so the host program can call any lua function without worry The new userdata is binded to a variable in the lua interpreter globals space, so it's preserved from collection until the construction process completes ( see below ) The global name used is "___borningud" **** check the error conditions of lua_newuserdata luapi_pushudobj second step to userdata creation. It pushes again the userdata reference, binding it to the given metatable Params: luaval_t userdata luaval_t metatable Return: int, the current stack position ... CAUTION: The stack is left alone, so the host can do something with it Assuming a two step userdata construction, the global "___borningud" is nil'ed so allowing ud collection when the object truly goes out of scope. **** check the stack !!! or document that this routine can only be used exactly before a return to lua machine !!! luapi_holdgc( L ) This function sets the GC threshold to a very high value, in fact precluding gc execution. The current threshold is stored in the global context of the interpreter luapi_contgc( L, int ) This function sets the gc threshold. If the int argument is >= 0, it's passed as the new threshold, else, it tries to get a previous threshold stored into the interpreter by holdgc The main usage of these functions is during userdata creation, to block a possible collection of the 'orphan' ud. A word regarding LAPI_ types: LAPI_NONE is defined as zero, so it's the 'default' type when a luaval_t object is allocated from the circular buffer, or one does a 'memset( lv, 0, sizeof lv )' on a C allocated array. All remaining 'types' are a 'function' of LUA 'types' to avoid clashing with LAPI_NONE, and are != 0. When pushing a LAPI_NONE luaval to the stack, it's interpreted as LUA_TNIL ... some useful stubs Error displaying/logging luapi_Alert_Stub is defined as void (*f)( const char * ) and is intended to be a function wich deals with error messages, but not required to know anything about lua. When a lua error happens, and the program is an interactive one, it's nice to get the user informed. But being the process interactive or not, your host program must know that something went wrong. If you are calling lua apis directly from your host code, you can deal with the errors directly, but when using some extra apis or deeply nesting the lua interaction, your main code must resort to another way of error information. luapi fullfill this service twofold 1) every lua context can have its own error display stub, and its own error counter 2) the whole module can have a 'global' error display stub and a correlated 'global' error counter The full mechanism is as follows Whenever you open a new lua context, you can pass a luapi_Alert_Stub pointer. If you do, luapi_open will try to set a table into the Registry of the new context, with two entries: 1) a string valued one, which is the string representation of the error stub function address. See note. 2) a number valued, which will maintain the count of alert calls. Also, if by the time luapi_open is called, the 'global' stub is NULL, this one is set to the passed function pointer. Also, the _ALERT context is set to a static luapi function. Every time _ALERT is called, the luapi static stub will try to access the lua context registry table, and if successfull, will a) call that stub, converting the string value to a function pointer - See note. b) increment the counter. If it cannot, will use the 'global' error stub, and increment the global error counter. Also, if the error stub is triggered by lua_panic, the lua context registry table will not be tried, because a panic condition does not warrant any more access to the lua context, being the only action possible an exit() call. When it comes to the host asking if some error happened, the luapi_alerted( L ) function will try, in turn, to access the context registry error counter ( resetting it ), or if that fails, will resort to the 'global' error counter. I think the error management is not fully 'context compliant' at the native api level. But I don't like to change the context definition to cope with this. Not this time. Note: the mechanism devised to store the error stub pointer is a little bizarre. But this was required because being the signature 'void (*f)( const char * )' I cannot use the normal C function register apis. Also, I don't mastered the ligthuserdata concept. But I think that for most compilers/platforms, the printf directive "%p" must work both ways: to store a pointer in readable form, and to retrieve it in a machine usable form, using scanf. If not, one could try to store the whole pointer as a lua string ( remember that lua strings can contain anything, including zero valued bytes ). At the end of this text there is a snippet of a test program regarding this issue. _ALERT and panic functions Every interpreter created using luapi_open gets these functions redirected to luapi functions. The alert function behaves as explained above. luapi_open( lua_State **, luapi_Alert_Stub alertdisplay ) receives a pointer to a lua_State pointer, and - checks whether it's null ( support for redundant opens ) if it's, - opens a new interpreter - opens all lua standard libraries - if alertdisplay is not null, tries to set this context error stub to that function - _ALERT and lua_panic are directed to luapi functions - if some extra 'libraries' are registered ( see luapi_regals ) the new context is passed to every of them returns the pointer if a non null alertdisplay is passed, and the module 'global' stub is NULL, Alert_Stub is set to this one. lua_State my_Lua = NULL; my_Lua = luapi_open( & my_Lua, ... ) luapi_alert_stub( luapi_Alert_Stub ) the function alert_stub sets the 'global' message displayer to any void function you may want, without requiring it to know and manage the lua protocol ( it receives the bare message ) This function returns the current Stub ... This function pointer is used if the interpreter has no 'self stubs' or in panic situations. luapi_alerted when Lua calls the alert function, may be the end user sees the message, but your code does not. luapi_alerted returns the current count of alert calls, and resets it to zero. it's good advice to call alerted at the end of every critical lua function ( dofile, call, etc ) to see if something wrong happened and to take appropiate actions ... luapi_regals( char * name, luapi_LStart lfun ) luapi_LStart is void (*f)( lua_State *, char * name ) registers a name and a function to be called for every new lua interpreter created using luapi_open This is a 'construction' mechanism, so every interpreter may have some 'libraries' added. Up to 30 such functions may be registered. luapi_close front end to lua_close, but with pointer checking, and returning NULL, my_lua = luapi_close( my_lua ); if ( my_lua ) ... BUGS AND IMPROVEMENTS All are welcome .. mnicolet@satlink.com mnicolet@speedy.com.ar --------------------- code to check "%p" usage ------------------- #include #include #include char * tostr( void * p ) { static char r[ 31 ]; sprintf( r, "%p", p ); return r; } void * fromstr( char * s ) { void * r; sscanf( s, "%p", & r ); return r; } char buffer[ ] = { "harmless" }; typedef void (* fp )( char * ); void func( char * t ) { printf( "func=%s\n", t ); } main( int argc, char ** argv ) { void * s = buffer; char * t; void * q; int i; int r = 1; int show = 0; if ( argc > 1 ) r = atoi( argv[ 1 ] ); for ( i = 0; i < r; i ++ ) { show = ( ( r - i ) == 1 ); if ( show ) printf( "%p = %p ? \n", s, buffer ); t = tostr( s ); if ( show ) printf( "as str %s\n", t ); q = fromstr( t ); if ( show ) { printf( "and back %p\n", q ); printf( "=%s\n", (char *)q ); } } s = func; for ( i = 0; i < r; i ++ ) { show = ( ( r - i ) == 1 ); if ( show ) printf( "%p = %p ? \n", s, func ); t = tostr( s ); if ( show ) printf( "as str %s\n", t ); q = fromstr( t ); if ( show ) { printf( "and back %p\n", q ); ( * (fp) q )( buffer ); } } }