Code archives/Miscellaneous/Type Access in Lua via Reflection
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
Please head to http://github.com/nilium/bmax-misc/ to get the latest version of this code. It's not public domain anymore, but it's MIT license, so it's close enough. The point of this code was to test an idea I had about using reflection to automate the use of BMax types/objects in Lua. As luck would have it, it works. It's probably not very speedy, since I haven't optimized this much, but I'm pretty happy to know that it works, which is the important point for me. [:: Function Reference ::] # lua_implementtypes( state:lua_State ) This will iterate over all types and expose any with the {expose} attribute to Lua. See the other attributes below. # lua_implementtype( state:lua_State, typeid:TTypeId, expose%=-1, static%=-1, noclass%=-1, hidefields%=-1 ) This will implement a specific type, specified by typeid. The remaining arguments, expose, static, noclass, and hidefields, are used to override any metadata settings on the type. Keep in mind that it is possible to implement a type twice, and if I know my Lua right it ought to just over-write the old type constructor/methods and Lua's GC will pick up the scraps. I do not recommend trying this, just because you should never have to implement a type twice for the same Lua state. # lua_pushbmaxobject( state:lua_State, obj:Object, excludeMethods%=False ) This pushes a BMax object onto the stack using the same method that the rest of this system does. If you pass True to excludeMethods, this will more or less circumvent any use of the reflection and only push a new table with the object onto the stack. This can possibly make things run faster, especially if you don't need Lua to know what that object is (there are plenty of good reasons to do this), but I have not tested whether or not pushing objects method-less works well, so that's up to you. # lua_tobmaxobject:Object( state:lua_State, index% ) This will retrieve a BlitzMax object from the stack. If the location in the stack specified by index is a table with an object field, you (should) get an object back. If it's not a table, not an object, nil, etc. then you will simply get Null. Errors may occur due to this being used in less than pristine conditions, so consider yourself warned. # lua_pushbmaxarray( state:lua_State, arr:Object, excludeMethods%=False ) This pushes a BMax array onto the stack as a table. excludeMethods behaves the same way as with lua_pushbmaxobjects, and is only applicable to arrays of BMax objects. # lua_tobmaxarray:Object[]( state:lua_State, index% ) This converts a table at index to a BMax array. This only concerns itself with the length of a table returned by lua_objlen. This is iffy at best, and I do not recommend using this extensively, if at all, for the time being. # lua_pushbmaxtlist( state:lua_State, list:TList, excludeMethods%=False ) More or less the same as lua_pushbmaxarray, but this takes a TList rather than an array. # lua_pushbmaxtmap( state:lua_State, map:TMap, excludeMethods%=False ) Again, more or less the same as lua_pushbmaxarray, the difference being that TMap is likely the closest in use to a Lua table. The table key for each field is the result of calling the map key's ToString(). [:: Attributes ::] {expose} - When applied to a type, this will result in the type being exposed to Lua when ImplementTypes or ImplementType is called on it and any BMax objects pushed onto the stack will have their methods and field metatable attached to the object unless specified otherwise. Regular object instances can have their methods accessed via varname.methodName() or varname:methodName(). For instances, it is better to use the second unless you are calling the method as if it were a delegate. {static} - When applied to a type, the type acts as a static class or namespace in Lua. An instance of the type is created and the type is created as a global table in Lua. These are accessed either via TypeName.methodName() or TypeName:methodName() - either works, but I recommend using the first. The fields of the instance this static object is based off of are accessible. {noclass} - Requires {static}. When applied to a type, the behavior is the same as {static}, except that the functions are pushed as global variables/functions in Lua rather than as fields of a table. Only methodName() is required to call them. Fields are inaccessible using this attribute, even if static is not set. {rename="newName"} - A form of aliasing. Renames a method, such that if a method is named lua_Print and it has the attribute {rename="Print"}, the function in Lua will be Print, not lua_Print. This can only be applied to methods, currently. I may change this later. This attribute does not apply to fields. {hidden} - When applied to a method or field, will not expose the method/field to Lua. This is useful if you would like to only expose methods of a type to Lua. {hidefields} - When applied to a type, not a field, this will hide all fields without exception. As a result, no metatable is set on instances of objects created with the type this is applied to. A quick example to test with: And a script: Finally, the code: | |||||
Please head to http://github.com/nilium/bmax-misc/ to get the latest version of this code. It's not public domain anymore, but it's MIT license, so it's close enough. |
Comments
| ||
Why do you always post stuff that I can't understand? |
| ||
Maybe he doesn't like you? ;P |
| ||
Maybe he doesn't like you either. :) |
| ||
Psh. Whatever. You can't dislike me. :| :P |
| ||
Changes:June 22, 2008 - 8:16 PM - Fixed a brainfart where hidefields did the opposite of what it was supposed to do. June 22, 2008 - 7:46 PM - Support for get/setting type fields has been added. The {hidden} attribute applies to these as well, since there are obviously fields that you do not want to be modifiable in Lua. Because you may not want any fields exposed, I've included {hidefields} as an attribute that will cause all fields to be hidden (it's not that they're really hidden, it's just that the object does not get a metatable). June 22, 2008 - 6:16 PM - Changed all use of lua_rawset/rawget to lua_settable/gettable. The original intention for using rawset/get over set/gettable was that I wished to avoid the effects of metatables on BMax objects. However, I feel this is not in the interest of preserving the features and flow that Lua is known for. - Fixed a bug in lua_tobmaxobject, where relative indices were not used properly. This has since been fixed, and should no longer be an issue. |
| ||
Changes:June 26, 2008 - 6:16 PM - Changed back from pushing method names to pushing the method object. Was mainly for debugging, and I think I've got most of the issues there ironed out now. June 26, 2008 - 6:00 PM - Removed debugging code since I no longer intend to use it. - Added ILREFException class for exception throwing. Currently only used for constructor call errors. - Removed LREF_DumpStackMsg, replaced by LREF_DumpStack. No longer takes a message argument, instead opting to choose whether or not the information is printed on the spot or not. The stack string is always returned. - Fixed a bug in LREF_TypeCall where if you used Type:Method calling, the closure would attempt to get one more argument than it should have. This was because I'd forgotten to apply the use of argOffset properly to the loop where arguments are retrieved from the stack. - Renamed ImplementType/s to lua_implementtype/s to match Lua's naming scheme as well as the lua_pushbmaxobject/tobmaxobject functions. June 23, 2008 - 12:12 AM - Switched set/gettable back to rawset/get where the object field is being modified after deciding I did not want metatables affecting that field. Other fields remain metatable'd. - Changed object handle type in Lua from number to lightuserdata, since it's just better suited for this task. - Increased the amount of debugging code for LREF_DEBUG_HEAVY dramatically. Setting debug info to this mode is no longer recommended unless you intend to fix bugs in the code or want to see in detail what occurs. - Other bug-fixes, focusing mainly on pushing/popping objects where some data was not being popped from the stack, resulting in incorrect data being passed between functions. |
| ||
Changes:July 4, 2008 - 11:30 PM - The method call closure no longer provides basic default arguments. I decided that, ultimately, this does not benefit any sort of system in the long run and is better off removed. - I have not tested the array code for arguments, fields, etc., much at all, so that's still highly experimented. I do not recommend trying to use it, since for all I know every condition other than my own will break it. - Prepended the object field string with 'LREF_'. - Added some support for garbage collection of BMax objects in Lua. This is done through the finalizer metamethod of userdata. Objects that are 'forcefully' deleted via the Delete method now rely upon this. The handle for an object, as this is how Lua accesses objects in case you didn't find that out just by looking, is not released until the userdata is collected by Lua's GC. More on this below. - There is a new metatable for garbage collection, as mentioned above. - Metatables are created on-demand and stored in the Lua registry. The metatables are no longer created over and over for each and every object pushed onto the stack. - Due to Lua's design, meta tables cannot be assigned to light userdata. Because of this, all use of light user data for object handles (not methods and type ids) has been moved to full userdata. In order to prevent the chance that an object will be garbage-collected by Lua while still in use (because each push creates a new userdata), generation of object handles is now simply an ever-increasing (and eventually looping around) Long. Handles are stored in the LREF_objectHandles TMap. Whenever a BMax object is pushed, a new handle is 'allocated,' if you will. This means that at any time, an object may have as many handles as a Long can represent (that's quite a lot). If a handle is taken, LREF_CreateHandle will continually increase the handle counter until it hits a handle that is not currently in use. If it loops around and reaches the handle it started at, an exception is thrown. Should you ever get this exception, you probably have more things to worry about than running out of handles. - The delete method no longer has the object as an upvalue. This means you /must/ use the table:method() syntax to call it. As a side-effect, you can call the destructor as table.Delete(otherTable). Calling :Delete does not mean the object's handle is freed immediately, as this is left up to Lua's garbage collector. What this means is that an object is not freed until you have removed all traces of it, including any method pointers from the object's table. Because of this, however, I'm pondering removing the Delete method entirely, since it no longer really serves a purpose. You do not have to call the Delete method to release an object as well, but Delete may decrease the amount of time before the object is garbage collected. The most important reason to use Delete is to wipe out the object's table, thereby removing the chance that you might accidentally use the object again. - I have just realized that a good chunk of this source code is whitespace and documentation. July 4, 2008 - 12:48 PM - Introduced the LREF_USE_EXCEPTIONS constant, which, as the name suggests, allows one to toggle whether or not LREF uses exceptions. This does not mean that errors go unhandled. See the next note for why. Turning this off will only disable exceptions in functions where lua_error can be used. - lua_error calls added after all exceptions for when LREF_USE_EXCEPTIONS is set to 0. Either way, the code is producing an error, letting it go without notice is not permissible, so you have the choice of handling an exception or letting Lua handle the error (which you can, in turn, handle through Lua if you've read the docs). - All error messages are now string constants at the head of functions for the sake of organization. June 26, 2008 - 6:49 PM - Added lua_pushbmaxarray, lua_tobmaxarray, lua_pushbmaxtmap, and lua_pushbmaxtlist as utility functions, since it's something you would likely want to do at some point. |
| ||
Updated to replace spaces with tabs and to fix a bug in LREF_GetValue where, after converting the table in the stack to an array, the value previously gotten to identify whether or not the table was an object was not being popped. It probably will not make any fundamental difference, but I can still imagine it causing a problem down the road if the stack isn't clean. Also removed all one-line if statements. |
Code Archives Forum