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

Type Access in Lua via Reflection by N2008
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

markcw2008
Why do you always post stuff that I can't understand?


Ked2008
Maybe he doesn't like you? ;P


markcw2008
Maybe he doesn't like you either. :)


Ked2008
Psh. Whatever. You can't dislike me. :|

:P


N2008
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.



N2008
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.



N2008
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.



N2009
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