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
| 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: ' test.bmx Type Foo {expose} Method Bazzle( obj:Bar ) If obj = Null Then Print "Null obj" Else Print obj.ToString() EndIf End Method End Type Type Bar {expose} Method ToString:String() Return "Woopertonville" End Method End Type Type FooBar Extends Foo {expose} End Type Type Common {expose static noclass} ' Some wrapper functions are necessary, since I cannot really do anything about exposing regular functions Method luaOpenFile:TStream( file:String, mode:String ) {rename="OpenStream"} Local read:Int, write:Int read = False write = False If mode.Contains("r") Then read = True EndIf If mode.Contains("w") Then write = True EndIf Return OpenFile( file, read, write ) End Method End Type ' Exec Local vm:Byte Ptr = luaL_newstate() ' Call this to implement any types with the proper attributes/metadata lua_implementtypes(vm) ' Example of forcing exposure of a type (handy when you don't want to or can't modify source) lua_implementtype( vm, TTypeId.ForName("TStream"), True, False, False ) If lua_dofile( vm, "test.lua" ) <> 0 Then Print "[ERROR] "+lua_tostring( vm, -1 ) EndIf lua_close(vm) vm = Null Input("Done.") And a script: -- test.lua -- Random stuff local foo,bar foo = NewFooBar() bar = NewBar() foo:Bazzle(bar) foo:Bazzle(nil) bar:Delete() foo:Delete() -- Once :Delete() is called, the object is no longer usable, as the table is -- cleared out. If you have any data being stored inside the object's table, -- make damn sure you've got it stored elsewhere. I don't know why you'd -- attach anything to the object, it's a bad idea, but whatever. -- TStream local mystream = OpenStream("woop.txt","w") mystream:WriteString("Wooperton, yo.") mystream:Close() -- You may still call mystream:Delete(), but it is not required mystream = nil Finally, the code: |
SuperStrict
Import brl.Reflection
Import axe.Lua
Rem '=======================================================================
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.
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.
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.
June 20, 2008 - 11:56 PM
- Renamed BMX_OBJECT_FIELD to LREF_OBJECT_FIELD to be consistent with other
variable names.
June 20, 2008 - 10:10 PM
- Replaced use of lua_tointeger with lua_tonumber. Lua uses ptrdiff_t when
you push an integer, but it does not store the number as an actual integer.
I would rather cast it to an integer myself than have it cast the double to
ptrdiff_t and then to int. Changing LUA_INTEGER to int fixes conversion
problems, but requiring a modification to the library seems unreasonable to
me.
- New metadata attributes that apply to types are {static} and {noclass}.
Static means an instance of the object is created upon being added to Lua,
however the type itself acts similar to a static class in C#. The type
does not have a delete method, and is accessed via <TypeName> as a global
variable.
This allows something similar to Lua's standard libraries to be implemented
in the program without writing glue functions.
A noclass type causes the methods to be global without requiring you to
access them via <TypeName>.<MethodName>. This is more convenient for widely
used functions where you don't want to specify its class each and every
time.
- Methods no longer use {expose}- instead, they are exposed unless they
have the {hidden} attribute. Methods may be aliased via the {rename}
attribute, such as [Method Foo() {rename="Bar"}]. I'd have used {alias},
but that's reserved in BlitzMax for something. Nobody seems to know what
for though.
- Metadata booleans are now stored in type constructors for when their
specs are overridden by the expose, static, and noclass arguments for
ImplementType.
- Some debug information is available by setting the LREF_DEBUG_MODE global
to one of the LREF_DEBUG_ constants.
- Other bug fixes, of course, but I can't remember what.
June 19, 2008
- Initial version
EndRem '=======================================================================
Rem '==========================================================================
To-do
6/20/08 - Optimize the way objects are pushed onto the stack. Given that
methods are pushed with an object as they come, some changes may be
required to improve speed at the cost of losing method pointers of sorts.
If this had a preprocessor I could just shoehorn it in with an ifdef,
but...
EndRem '=======================================================================
Rem '==========================================================================
Notes
This has not been extensively tested and I have no doubts that there are
still a significant amount of bugs, so I cannot guarantee that there will
be no errors. All the work presented in this code should be treated as an
experiment and nothing more. Of course, I expect all those using this to
understand this and, if possible, provide useful reports of errors. If you
cannot provide that much, I am not interested in your problems.
EndRem '=======================================================================
Private
Const LREF_OBJECT_FIELD:String = "LREF_bmxObject" ' The field of BMax object tables in Lua: table = { <LREF_OBJECT_FIELD> = HANDLE }
Const LREF_METATABLE_FIELDS:String = "LREF_metatable_fields" ' Field access metatable
Const LREF_METATABLE_OBJECTS:String = "LREF_metatable_objects" ' Userdata collection metatable
Const LREF_USE_EXCEPTIONS:Int = True
' Object handles are used for full userdata objects (not methods/type ids and such, since those do not need to have their IDs freed)
Global LREF_objectHandles:TMap = New TMap
Type LREF_ObjRef
Field m_obj:Object
Field m_handle:Long
Global s_nextHandle:Long = 0
Method Set:LREF_ObjRef( obj:Object )
m_handle = s_nextHandle
s_nextHandle :+ 1
m_obj = obj
Return Self
End Method
Method Get:LREF_ObjRef( handle:Long )
m_handle = handle
Return Self
End Method
Method Dispose()
m_obj = Null
m_handle = 0
End Method
Method Compare:Int( o:Object )
Local oh:Long = LREF_ObjRef(o).m_handle
If oh < m_handle Then
Return -1
ElseIf oh > m_handle Then
Return 1
Else
Return 0
EndIf
End Method
End Type
Function LREF_CreateHandle:Long(obj:Object)
Local ref:LREF_ObjRef = New LREF_ObjRef.Set(obj)
Local initHandle:Long = ref.m_handle
While LREF_objectHandles.Contains( ref )
ref.Set(obj)
If initHandle = ref.m_handle Then
' For reference, this should never, ever happen - you shouldn't be able to use up all the references, you're
' more likely to run out of memory before that I'd guess
Throw LREF_Exception( "No object handles available", Null )
EndIf
Wend
LREF_objectHandles.Insert( ref, ref )
Return ref.m_handle
End Function
Function LREF_HandleToObject:Object( h:Long )
Local ref:LREF_ObjRef = LREF_ObjRef(LREF_objectHandles.ValueForKey(New LREF_ObjRef.Get(h)))
If ref Then
Return ref.m_obj
Else
Return Null
EndIf
End Function
Function LREF_ReleaseHandle( h:Long )
Local ref:LREF_ObjRef = LREF_ObjRef(LREF_objectHandles.ValueForKey(New LREF_ObjRef.Get(h)))
If ref Then
ref.Dispose()
EndIf
End Function
Public
' Exception when an error occurs
Type ILREFException Final
Field m_stack:String
Field m_msg:String
Method Stack:String()
Return m_stack
End Method
Method Message:String()
Return m_msg
End Method
Method ToString:String()
Return m_msg
End Method
End Type
Function LREF_Exception:ILREFException( msg$="", state:Byte Ptr )
Local ex:ILREFException = New ILREFException
If state <> Null Then
ex.m_stack = LREF_DumpStack( state, False )
Else
ex.m_stack = ""
EndIf
ex.m_msg = msg
Return ex
End Function
Private
' Currently not implemented in axe.lua
Function LREF_lua_upvalueindex:Int(idx:Int) NoDebug
Return LUA_GLOBALSINDEX-idx
End Function
Function LREF_ToObjectHandle:Long( state:Byte Ptr, idx:Int )
Local p:Long Ptr
Local handle:Long
p = Long Ptr lua_touserdata( state, idx )
handle = p[0]
Return handle
End Function
Function LREF_AttachMetatable( state:Byte Ptr, idx:Int, metatable$ )
If idx < 1 And idx > LUA_REGISTRYINDEX Then
idx = lua_gettop(state) - (idx+1)
EndIf
lua_pushstring( state, metatable )
lua_gettable( state, LUA_REGISTRYINDEX )
If lua_type( state, -1 ) = LUA_TNIL Then
lua_pop( state, 1 )
LREF_CreateMetaTables(state)
lua_pushstring( state, metatable )
lua_gettable( state, LUA_REGISTRYINDEX )
EndIf
lua_setmetatable( state, idx )
End Function
' TypeNew([object])
Function LREF_TypeNew:Int(state:Byte Ptr)
Const ERROR_NO_TYPEID$ = "Unable to construct object: no TTypeId attached to class constructor"
Const ERROR_OBJ_NULL$ = "Unable to construct object: object passed to constructor is Null"
Const ERROR_CANNOT_ALLOCATE_OBJ$ = "Unable to construct object: object could not be allocated"
Local typeid:TTypeId
Local obj:Object
Local expose:Int, noclass:Int, static:Int, hidefields:Int
Local objIdx:Int = -1
typeid = TTypeId(HandleToObject(Int lua_touserdata( state, LREF_lua_upvalueindex(1) )))
If typeid = Null Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_NO_TYPEID, state )
EndIf
lua_pushstring( state, ERROR_NO_TYPEID )
lua_error(state)
Return 0
Else
If lua_gettop(state) = 1 And lua_type( state, 1 ) = LUA_TUSERDATA Then
objIdx = 1
obj = LREF_HandleToObject(LREF_ToObjectHandle( state, objIdx ))
If obj = Null Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_OBJ_NULL, state )
EndIf
lua_pushstring( state, ERROR_OBJ_NULL )
lua_error(state)
Return 0
EndIf
Else
obj = typeid.NewObject()
EndIf
If obj = Null Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_CANNOT_ALLOCATE_OBJ, state )
EndIf
lua_pushstring( state, ERROR_CANNOT_ALLOCATE_OBJ )
lua_error(state)
Return 0
Else
expose = lua_toboolean( state, LREF_lua_upvalueindex(2) )
static = lua_toboolean( state, LREF_lua_upvalueindex(3) )
noclass = lua_toboolean( state, LREF_lua_upvalueindex(4) )
hidefields = lua_toboolean( state, LREF_lua_upvalueindex(5) )
LREF_PushBMaxObject( state, obj, typeid, expose, static, noclass, hidefields, objIdx )
Return 1
EndIf
EndIf
End Function
' TypeFieldSet( table, key, value ) [obj.field = newvalue]
Function LREF_TypeFieldSet:Int(state:Byte Ptr)
Local obj:Object
Local rfield:TField
Local name:String
If lua_type( state, 2 ) <> LUA_TSTRING Then
lua_rawset( state, 1 )
Return 0
EndIf
name = lua_tostring( state, 2 )
obj = LREF_GetValue( state, 1 )
If obj Then
rfield = TTypeId.ForObject(obj).FindField(name)
EndIf
If rfield <> Null And (Not rfield.MetaData("hidden")) Then
rfield.Set( obj, LREF_GetValue( state, 3 ) ) ' Is a type field
Else
lua_rawset( state, 1 ) ' Not a type field
EndIf
Return 0
End Function
' TypeFieldGet( table, key ) [someVar = obj.field]
Function LREF_TypeFieldGet:Int(state:Byte Ptr)
Local obj:Object
Local typeid:TTypeId = Null
Local rfield:TField = Null
Local name:String
If lua_type( state, 2 ) <> LUA_TSTRING Then
lua_rawget( state, 1 ) ' Not a string (name), so not a field
Return 1
EndIf
name = lua_tostring( state, 2 )
obj = LREF_GetValue( state, 1 )
If obj Then
typeid = TTypeId.ForObject(obj)
If typeid Then
rfield = typeid.FindField(lua_tostring( state, -1 ))
EndIf
EndIf
If rfield <> Null And (Not rfield.MetaData("hidden")) Then
Select rfield.TypeId()
Case FloatTypeId
lua_pushnumber( state, rfield.GetFloat(obj) )
Case DoubleTypeId
lua_pushnumber( state, rfield.GetDouble(obj) )
Case ByteTypeId, ShortTypeId, IntTypeId
lua_pushnumber( state, Long rfield.GetInt(obj) )
Case LongTypeId
lua_pushnumber( state, Long rfield.GetLong(obj) )
Case StringTypeId
lua_pushnumber( state, Long rfield.GetString(obj) )
Case ArrayTypeId
lua_pushbmaxarray( state, rfield.Get(obj), False )
Default
LREF_ConstructBMaxObject( state, rfield.Get(obj), typeid )
End Select
Else
lua_rawget( state, 1 )
EndIf
Return 1
End Function
' TypeDelete()
Function LREF_TypeDelete:Int(state:Byte Ptr)
Const ERROR_NOT_AN_OBJECT$ = "Error calling type destructor: destructor called from non-object"
Const ERROR_NO_OBJECT$ = "Error calling type destructor: invalid arguments to destructor"
Local objIdx:Int
Local typeid:TTypeId
If lua_gettop(state) <> 1 Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_NO_OBJECT, state )
EndIf
lua_pushstring( state, ERROR_NO_OBJECT )
lua_error(state)
Return 0
EndIf
If lua_type( state, 1 ) <> LUA_TTABLE Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_NOT_AN_OBJECT, state )
EndIf
lua_pushstring( state, ERROR_NOT_AN_OBJECT )
lua_error(state)
Return 0
EndIf
lua_pushstring( state, LREF_OBJECT_FIELD )
lua_rawget( state, 1 )
If lua_type( state, 2 ) <> LUA_TUSERDATA Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_NOT_AN_OBJECT, state )
EndIf
lua_pushstring( state, ERROR_NOT_AN_OBJECT )
lua_error(state)
Return 0
EndIf
lua_pop( state, 1 )
LREF_ClearTable( state, 1 )
Return 0
End Function
' This is only called if the type's field
Function LREF_TypeCollection:Int(state:Byte Ptr)
Local udata:Long = LREF_ToObjectHandle( state, 1 )
LREF_ReleaseHandle(udata)
Return 0
End Function
Function LREF_TypeCall:Int(state:Byte Ptr)
Const ERROR_NULL_METHOD$ = "Unable to call method: Null TMethod object attached to closure"
Const ERROR_NUM_ARGUMENTS$ = "Error calling $1::$2(): Expected $3 arguments, only received $4"
Local obj:Object
Local meth:TMethod
Local typeid:TTypeId
Local result:Object
Local argOffset:Int
obj = LREF_HandleToObject(LREF_ToObjectHandle( state, LREF_lua_upvalueindex(2) ))
typeid = TTypeId.ForObject(obj)
meth = TMethod(HandleToObject(Int lua_touserdata( state, LREF_lua_upvalueindex(1) )))
If meth = Null Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( ERROR_NULL_METHOD, state )
EndIf
lua_pushstring( state, ERROR_NULL_METHOD )
lua_error(state)
Return 0
EndIf
Local argTypes:TTypeId[] = meth.ArgTypes()
Local args:Object[meth.ArgTypes().Length]
Local argIdx:Int
Local numArgs:Int = args.Length
If numArgs > 0 And lua_type( state, 1 ) = LUA_TTABLE And LREF_GetValue( state, 1 ) = obj Then
argOffset = 2
Else
argOffset = 1
EndIf
If numArgs > lua_gettop(state)-(argOffset-1) Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( "Error calling "+typeid.Name()+"::"+meth.Name()+"(): Expected "+numArgs+" arguments, only received "+(lua_gettop(state)-(argOffset-1)), state )
' this happens to be the only exception to storing the error as a constant in the function header
EndIf
lua_pushstring( state, "Error calling "+typeid.Name()+"::"+meth.Name()+"(): Expected "+numArgs+" arguments, only received "+(lua_gettop(state)-(argOffset-1)) )
lua_error(state)
' numArgs = lua_gettop(state)-(argOffset-1) ' No more default arguments
EndIf
For argIdx = 0 To numArgs-1
args[argIdx] = LREF_GetValue( state, argOffset+argIdx )
Next
result = meth.Invoke( obj, args )
If result <> Null Then
Select meth.TypeId()
Case LongTypeId,IntTypeId,ByteTypeId,ShortTypeId
lua_pushinteger( state, result.ToString().ToLong() )
Return 1
Case StringTypeId
lua_pushstring( state, result.ToString() )
Return 1
Case FloatTypeId,DoubleTypeId
lua_pushnumber( state, result.ToString().ToDouble() )
Return 1
Case ArrayTypeId
lua_pushbmaxarray( state, result, False )
Return 1
Default
LREF_ConstructBMaxObject( state, result, Null )
Return 1
End Select
Else
lua_pushnil(state)
Return 1
EndIf
End Function
Function LREF_ConstructBMaxObject( state:Byte Ptr, obj:Object, typeId:TTypeId )
If typeId = Null Then
typeId = TTypeId.ForObject(obj)
EndIf
lua_pushstring( state, "New"+typeId.Name() )
lua_gettable( state, LUA_GLOBALSINDEX )
If lua_type( state, -1 ) = LUA_TFUNCTION Then
' In the event that a constructor for the object's type already exists, use that
Print "Using existing constructor"
Local p:Long Ptr = Long Ptr lua_newuserdata( state, 8 )
p[0] = LREF_CreateHandle(obj)
LREF_AttachMetaTable( state, -1, LREF_METATABLE_OBJECTS )
If lua_pcall( state, 1, 1, 0 ) <> 0 Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( "Error calling constructor for "+typeId.Name()+"~nLua error: "+lua_tostring( state, -1 ), state )
EndIf
' I understand there is some pointlessness to handling an error here only to produce another error.
lua_pushstring( state, "Error calling constructor for "+typeId.Name()+"~nLua error: "+lua_tostring( state, -1 ) )
lua_error(state)
Return
EndIf
Else
lua_pop( state, 1 )
If typeId.SuperType() Then
' If the object extends another class, check to see if its base class has been implemented
LREF_ConstructBMaxObject( state, obj, typeId.SuperType() )
Else
' If no constructor for the class exists, push it as just an object without methods
lua_pop( state, 1 )
LREF_PushBMaxObject( state, obj, Null, False, False, False, True, -1 )
EndIf
EndIf
End Function
Function LREF_ClearTable( state:Byte Ptr, idx:Int )
Local top:Int = lua_gettop(state)
lua_pushnil(state)
While lua_next( state, idx ) <> 0
lua_pop( state, 1 )
lua_pushvalue( state, -1 )
lua_pushnil(state)
lua_rawset( state, idx ) ' Do not want metatables here.
Wend
top = lua_gettop(state)-top
If top > 0 Then
lua_pop( state, top )
EndIf
End Function
Function LREF_GetValue:Object( state:Byte Ptr, idx:Int )
Local obj:Object
Select lua_type( state, idx )
Case LUA_TNIL
Return Null
Case LUA_TSTRING
Return lua_tostring( state, idx )
Case LUA_TNUMBER
Return String(lua_tonumber( state, idx ))
Case LUA_TFUNCTION
Return Null
Case LUA_TTABLE
' check if the table is an object
lua_pushstring( state, LREF_OBJECT_FIELD )
lua_rawget( state, idx )
If lua_type( state, -1 ) <> LUA_TUSERDATA Then ' Treat value as an array
Local arr:Object[] = lua_tobmaxarray( state, idx )
lua_pop( state, 1 )
Return arr
ElseIf lua_type(state, -1) = LUA_TUSERDATA Then
' Otherwise, it's an object
obj = LREF_HandleToObject(LREF_ToObjectHandle( state, -1 ))
lua_pop( state, 1 )
Return obj
Else
lua_pop(state, 1)
Return Null
EndIf
Case LUA_TBOOLEAN
Return String(lua_toboolean( state, idx ))
Case LUA_TUSERDATA,LUA_TLIGHTUSERDATA
Return String(Int(lua_topointer( state, idx )))
Default
Return Null
End Select
End Function
' NOTE: consider rewriting this such that the method table is an upvalue to the
' New*() function that is then copied and modified for the object. (May pose
' issues for method-pointer style functions.)
Function LREF_PushBMaxObject( state:Byte Ptr, obj:Object, from:TTypeId, expose:Int=-1, static:Int=-1, noclass:Int=-1, hidefields:Int=-1, objidx:Int=-1 )
Local methIter:TMethod
Local tableIdx:Int
Local rename:String = Null
Local name:String = Null
Local ownObjIdx:Int = False
If from = Null Then
from = TTypeId.ForObject(obj)
EndIf
If expose = -1 And from.MetaData("expose") Then
expose = True
ElseIf expose = -1 Then
expose = False
EndIf
If static = -1 And from.MetaData("static") Then
static = True
ElseIf static = -1 Then
static = False
EndIf
If noclass = -1 And from.MetaData("noclass") Then
noclass = True
ElseIf noclass = -1 Then
noclass = False
EndIf
If hidefields = -1 And from.MetaData("hidefields") Then
hidefields = True
ElseIf hidefields = -1 Then
hidefields = False
EndIf
If objIdx = -1 Then
Local p:Long Ptr = Long Ptr lua_newuserdata( state, 8 )
p[0] = LREF_CreateHandle(obj)
objIdx = lua_gettop(state)
ownObjIdx = True
LREF_AttachMetaTable( state, objIdx, LREF_METATABLE_OBJECTS )
EndIf
If noclass And static Then
tableIdx = LUA_GLOBALSINDEX
hidefields = True ' No exceptions
Else
' Create the new object table if it's an instance or regular static class
tableIdx = -3
lua_createtable( state, 0, 1 )
lua_pushstring( state, LREF_OBJECT_FIELD )
lua_pushvalue( state, objIdx )
lua_settable(state, tableIdx)
EndIf
If from <> Null And expose Then
' This is not something I'm particularly fond of, and I would like to
' see if I can do this better. Currently, the methods are iterated
' over and pushed onto the stack each time you push an object. This
' doesn't apply to unexposed objects, but this may be a problem if you
' rely heavily on Lua.
For methIter = EachIn from.EnumMethods()
name = methIter.Name()
If from.MetaData("hidden") Or name.ToLower() = "delete" Or name.ToLower() = "new" Then
Continue
EndIf
rename = methIter.MetaData("rename")
If rename Then
name = rename
EndIf
rename = Null
' {Type}::{Name}
lua_pushstring( state, name )
'lua_pushstring( state, methIter.Name() )
lua_pushlightuserdata( state, Byte Ptr HandleFromObject(methIter) )
lua_pushvalue( state, objIdx )
lua_pushcclosure( state, LREF_TypeCall, 2 )
lua_settable( state, tableIdx )
' NOTE: An unintended side-effect of the object being passed as an
' upvalue is that you sort of have delegates in Lua...
Next
EndIf
If Not static Then
' {Type}::Delete method
lua_pushstring( state, "Delete" )
lua_pushcclosure( state, LREF_TypeDelete, 0 )
lua_settable( state, tableIdx )
EndIf
If expose = True And hidefields = False And noclass = False Then ' If the type is not exposed or if it's regular functions, fields will be inaccessible
LREF_AttachMetaTable( state, -2, LREF_METATABLE_FIELDS )
EndIf
If ownObjIdx Then
lua_remove( state, objIdx )
EndIf
End Function
Function LREF_CreateMetaTables(state:Byte Ptr)
' Fields
lua_pushstring( state, LREF_METATABLE_FIELDS )
lua_createtable( state, 0, 2 )
lua_pushstring( state, "__newindex" )
lua_pushcclosure( state, LREF_TypeFieldSet, 0 )
lua_rawset( state, -3 )
lua_pushstring( state, "__index" )
lua_pushcclosure( state, LREF_TypeFieldGet, 0 )
lua_rawset( state, -3 )
lua_settable( state, LUA_REGISTRYINDEX )
' Userdata collection
lua_pushstring( state, LREF_METATABLE_OBJECTS )
lua_createtable( state, 0, 1 )
lua_pushstring( state, "__gc" )
lua_pushcclosure( state, LREF_TypeCollection, 0 )
lua_rawset( state, -3 )
lua_settable( state, LUA_REGISTRYINDEX )
End Function
' Debugging code
Function LREF_DumpStack$( lua:Byte Ptr, output%=True ) NoDebug
Local sout:String = ""
Local lout:String
Local idx:Int
For idx = 1 To lua_gettop(lua)
sout :+ " "+("["+idx+"]")[..5]+" "
Select lua_type( lua, idx )
Case LUA_TBOOLEAN
sout :+ "boolean "+lua_toboolean( lua, idx )
Case LUA_TNIL
sout :+ "nil"
Case LUA_TNUMBER
sout :+ "number "+lua_tonumber( lua, idx )
Case LUA_TFUNCTION
sout :+ "function"
Case LUA_TUSERDATA,LUA_TLIGHTUSERDATA
sout :+ "userdata 0x"+Hex(Int(lua_topointer( lua, idx )))
Case LUA_TSTRING
sout :+ "string "+lua_tostring( lua, idx )
Case LUA_TTABLE
sout :+ "table "+LREF_TableToString( lua, idx, 6 )
Default
sout :+"object/unknown/null"
End Select
sout :+ "~n"
Next
If output Then
Print sout
EndIf
Return sout
End Function
Function LREF_TableToString$( lua:Byte Ptr, idx:Int, indent%=0 ) NoDebug
Local out$ = "{ "
Local idn$ = " "[..indent]
Local top% = lua_gettop(lua)
Local ran% = 0
Local key$
Local value$
lua_pushnil(lua)
While lua_next( lua, idx ) <> 0
ran = True
key = lua_tostring( lua, -2 )
If lua_type( lua, -1 ) = LUA_TFUNCTION Then
value = "function"
ElseIf lua_type( lua, -1 ) = LUA_TNIL Then
value = "nil"
ElseIf lua_type( lua, -1 ) = LUA_TNUMBER Then
value = lua_tonumber(lua, -1)
ElseIf lua_type( lua, -1 ) = LUA_TBOOLEAN Then
If lua_toboolean(lua, -1) Then
value="true"
Else
value="false"
EndIf
ElseIf lua_type( lua, -1 ) = LUA_TUSERDATA Then
value = "userdata"
ElseIf lua_type( lua, -1 ) = LUA_TLIGHTUSERDATA Then
value = "0x"+Hex(Int(lua_touserdata(lua,-1)))
Else
lua_tostring( lua, -1 )
EndIf
out :+ idn+key+"="+value+", ~n"
lua_pop( lua, 1 )
Wend
If ran Then
out = out[..out.Length-3]
EndIf
If lua_gettop(lua)-top > 0 Then
lua_pop(lua, lua_gettop(lua)-top)
EndIf
out :+ " }"
Return out
End Function
Public
Function lua_implementtype( state:Byte Ptr, from:TTypeID, expose:Int=-1, static:Int=-1, noclass:Int=-1, hidefields%=-1 )
If expose = -1 And from.MetaData("expose") Then
expose = True
ElseIf expose = -1 Then
expose = False
EndIf
If static = -1 And from.MetaData("static") Then
static = True
ElseIf static = -1 Then
static = False
EndIf
If noclass = -1 And from.MetaData("noclass") Then
noclass = True
ElseIf noclass = -1 Then
noclass = False
EndIf
If hidefields = -1 And from.MetaData("hidefields") Then
hidefields = True
ElseIf hidefields = -1 Then
hidefields = False
EndIf
If expose Then
If static Then
If noclass Then
LREF_PushBMaxObject( state, from.NewObject(), from )
Else
lua_pushstring( state, from.Name() )
LREF_PushBMaxObject( state, from.NewObject(), from, expose, static, noclass )
lua_settable( state, LUA_GLOBALSINDEX )
EndIf
Else
' function NewName()
lua_pushstring( state, "New"+from.Name() )
' upvalues - a lot of them
lua_pushlightuserdata( state, Byte Ptr HandleFromObject(from) ) ' uv 1
lua_pushboolean( state, expose )
lua_pushboolean( state, static )
lua_pushboolean( state, noclass )
lua_pushboolean( state, hidefields )
' closure
lua_pushcclosure( state, LREF_TypeNew, 5 )
lua_settable( state, LUA_GLOBALSINDEX )
EndIf
EndIf
End Function
Function lua_implementtypes(state:Byte Ptr)
Local typeIter:TTypeId
For typeIter = EachIn TTypeId.EnumTypes()
lua_implementtype( state, typeIter )
Next
End Function
' Convenience functions
' Passing true to excludeMethods may be faster, at the expense of, well, not having methods
Function lua_pushbmaxobject( state:Byte Ptr, obj:Object, excludeMethods:Int=False )
If excludeMethods Then
LREF_PushBMaxObject( state, obj, Null, False, False, False, True )
Else
LREF_ConstructBMaxObject( state, obj, Null )
EndIf
End Function
Function lua_tobmaxobject:Object( state:Byte Ptr, idx:Int )
Local obj:Object = Null
If idx < 1 And idx > LUA_REGISTRYINDEX Then
idx = lua_gettop(state) - (idx+1)
EndIf
If lua_type( state, idx ) <> LUA_TTABLE Then
Return Null
Else
lua_pushstring( state, LREF_OBJECT_FIELD )
lua_rawget( state, idx )
If lua_type( state, -1 ) = LUA_TUSERDATA Then
obj = LREF_HandleToObject(LREF_ToObjectHandle( state, -1 ))
lua_pop( state, 1 )
Return obj
Else
lua_pop( state, 1 )
Return Null
EndIf
EndIf
End Function
' Currently undecided on how I'll get arrays /from/ Lua, but this works on giving them to Lua
Function lua_pushbmaxarray( state:Byte Ptr, obj:Object, excludeMethods:Int = False ) ' Can be an array of objects, so excludeMethods is still an argument here
Local typeid:TTypeId = TTypeId.ForObject(obj)
Local idx:Int
Local arrLen:Int
If typeid <> ArrayTypeId Then
If LREF_USE_EXCEPTIONS Then
Throw LREF_Exception( "lua_pushbmaxarray: obj is not an array", Null )
EndIf
lua_pushstring( state, "Error calling lua_pushbmaxarray: obj is not an array" )
lua_error(state)
Return
EndIf
arrLen = typeid.ArrayLength(obj)
lua_createtable( state, arrLen, 0 )
Select typeid.ElementType()
Case FloatTypeId, DoubleTypeId, LongTypeId, IntTypeId, ShortTypeId, ByteTypeId ' These are all numbers! ('-'\) .('-')/ (.'-')/
For idx = 0 To arrLen-1
lua_pushnumber( state, idx )
lua_pushnumber( state, String(typeid.GetArrayElement( obj, idx )).ToDouble() )
lua_settable( state, -3 )
Next
Case StringTypeId ' And this is a ball of yarn.
For idx = 0 To arrLen-1
lua_pushnumber( state, idx )
lua_pushstring( state, String(typeid.GetArrayElement( obj, idx )) )
lua_settable( state, -3 )
Next
Case ArrayTypeId
For idx = 0 To arrLen-1
lua_pushnumber( state, idx )
lua_pushbmaxarray( state, typeid.GetArrayElement( obj, idx ) )
lua_settable( state, -3 )
Next
Default ' This is el presidente.
For idx = 0 To arrLen-1
lua_pushnumber( state, idx )
lua_pushbmaxobject( state, typeid.GetArrayElement( obj, idx ), excludeMethods )
lua_settable( state, -3 )
Next
End Select
End Function
Function lua_tobmaxarray:Object[]( state:Byte Ptr, idx:Int )
Local tableLen:Int
Local tableInner:Int
Local arr:Object[]
If idx < 1 And idx > LUA_REGISTRYINDEX Then
idx = lua_gettop(state) - (idx+1)
EndIf
If lua_type( state, idx ) <> LUA_TTABLE Then
Return Null
EndIf
tableLen = lua_objlen( state, idx )
If tableLen = 0 Then
Return New Object[0]
EndIf
arr = New Object[tableLen]
For tableInner = 1 To tableLen
lua_pushnumber( state, tableInner )
lua_gettable( state, idx )
arr[tableInner - 1] = LREF_GetValue( state, idx )
Next
Return arr
End Function
' Your key must either BE a string or override ToString to have any meaningful key
Function lua_pushbmaxtmap( state:Byte Ptr, map:TMap, excludeMethods:Int = False )
Local keyval:TNode
lua_newtable(state)
For keyval = EachIn map
lua_pushstring( state, keyval.Key().ToString() )
If keyval.Value() = Null Then
lua_pushnil(state)
ElseIf TTypeId.ForObject(keyval.Value()) = StringTypeId Then
lua_pushstring( state, String(keyval.Value()) )
ElseIf TTypeId.ForObject(keyval.Value()) = ArrayTypeId Then
lua_pushbmaxarray( state, keyval.Value(), excludeMethods )
Else
lua_pushbmaxobject( state, keyval.Value(), excludeMethods )
EndIf
lua_settable( state, -3 )
Next
End Function
Function lua_pushbmaxtlist( state:Byte Ptr, list:TList, excludeMethods:Int = False )
Local item:Object
lua_createtable( state, list.Count(), 0 )
Local idx:Int = 1
For item = EachIn list
lua_pushnumber( state, idx )
If item = Null Then
lua_pushnil(state)
ElseIf TTypeId.ForObject(item) = StringTypeId Then
lua_pushstring( state, String(item) )
ElseIf TTypeId.ForObject(item) = ArrayTypeId Then
lua_pushbmaxarray( state, item, excludeMethods )
Else
lua_pushbmaxobject( state, item, excludeMethods )
EndIf
lua_settable( state, -3 )
idx :+ 1
Next
End Function |