After some more testing, there is still a missing piece, to support opCall calls on class members, typically:
myObject.functor();
I have managed to find out the path that I had missed, and I thought that adding similar code would solve it, but the problem is that the script compiles, and when executed, it crashes in asCContext::CallInterfaceMethod crashes. So obviously the generated method call has not the right stack...
The only modification that I have made compared to the previous iteration (diff attached), consists in generating a function call on member when it is detected that this is a member (see code in bold below):
int asCCompiler::CompileFunctionCall(asCScriptNode *node, asSExprContext *ctx, asCObjectType *objectType, bool objIsConst, const asCString &scope,const asCString& functionName)
{
asCString name;
asCTypeInfo tempObj;
asCArray<int> funcs;
int localVar = -1;
bool initializeMembers = false;
bool opCallOnObject=false;
asCTypeInfo opCallObjectType;
// if no function name, use the current node's name (default)
if(functionName.GetLength()==0)
{
asCScriptNode *nm = node->lastChild->prev;
name.Assign(&script->code[nm->tokenPos], nm->tokenLength);
}
else
name=functionName;
// First check for a local variable of a function type as it would take precedence
// Must not allow function names, nor global variables to be returned in this instance
// If objectType is set then this is a post op expression and we shouldn't look for local variables
asSExprContext funcPtr(engine);
if( objectType == 0 )
{
// check if a global or local variable with object type can be found
localVar = CompileVariableAccess(name, scope, &funcPtr, node, true, true, false);
if( localVar >= 0 && !funcPtr.type.dataType.GetFuncDef() && funcPtr.methodName == "" )
{
// if the variable is an object type, could be an opCall
if(funcPtr.type.dataType.IsObject()||funcPtr.type.dataType.IsObjectHandle())
{
// actually compile the variable access
CompileVariableAccess(name, scope, ctx, node);
// set operation to opCall on our object
name="opCall";
objectType = ctx->type.dataType.GetObjectType();
opCallOnObject=true;
opCallObjectType=ctx->type;
// continue
localVar = -1;
}
else
{
// The variable is not a function
asCString msg;
msg.Format(TXT_NOT_A_FUNC_s_IS_VAR, name.AddressOf());
Error(msg, node);
return -1;
}
}
// If the name matches a method name, then reset the indicator that nothing was found
if( funcPtr.methodName != "" )
localVar = -1;
}
if( localVar < 0 )
{
// If this is an expression post op, or if a class method is
// being compiled, then we should look for matching class methods
if( objectType || (outFunc && outFunc->objectType && scope != "::") )
{
// If we're compiling a constructor and the name of the function is super then
// the constructor of the base class is being called.
// super cannot be prefixed with a scope operator
if( scope == "" && m_isConstructor && name == SUPER_TOKEN )
{
// If the class is not derived from anyone else, calling super should give an error
if( outFunc && outFunc->objectType->derivedFrom )
funcs = outFunc->objectType->derivedFrom->beh.constructors;
// Must not allow calling base class' constructor multiple times
if( continueLabels.GetLength() > 0 )
{
// If a continue label is set we are in a loop
Error(TXT_CANNOT_CALL_CONSTRUCTOR_IN_LOOPS, node);
}
else if( breakLabels.GetLength() > 0 )
{
// TODO: inheritance: Should eventually allow constructors in switch statements
// If a break label is set we are either in a loop or a switch statements
Error(TXT_CANNOT_CALL_CONSTRUCTOR_IN_SWITCH, node);
}
else if( m_isConstructorCalled )
{
Error(TXT_CANNOT_CALL_CONSTRUCTOR_TWICE, node);
}
m_isConstructorCalled = true;
// We need to initialize the class members, but only after all the deferred arguments have been completed
initializeMembers = true;
}
else
{
// The scope is can be used to specify the base class
builder->GetObjectMethodDescriptions(name.AddressOf(), objectType ? objectType : outFunc->objectType, funcs, objIsConst, scope);
}
// It is still possible that there is a class member of a function type of of object type that
if( funcs.GetLength() == 0 )
{
int r = CompileVariableAccess(name, scope, &funcPtr, node, true, true, true, objectType);
if( r >= 0 && !funcPtr.type.dataType.GetFuncDef() && funcPtr.methodName == "" )
{
// if this is an opCall, compile the variable access and opCall
if((funcPtr.type.dataType.IsObject()||funcPtr.type.dataType.IsObjectHandle()))
{
int r = CompileVariableAccess(name, scope, ctx, node, true, true, true, objectType);
if(r<0)
return r;
bool isConst = false;
if( ctx->type.dataType.IsObjectHandle() )
isConst = ctx->type.dataType.IsHandleToConst();
else
isConst = ctx->type.dataType.IsReadOnly();
asCObjectType *trueObj = ctx->type.dataType.GetObjectType();
asCTypeInfo objType = ctx->type;
// Compile function call
r = CompileFunctionCall(node, ctx, trueObj, isConst,scope,"opCall");
if( r < 0 )
return r;
return r;
}
else
{
// The variable is not a function
asCString msg;
msg.Format(TXT_NOT_A_FUNC_s_IS_VAR, name.AddressOf());
Error(msg, node);
return -1;
}
}
}
// If a class method is being called implicitly, then add the this pointer for the call
if( funcs.GetLength() && !objectType )
{
objectType = outFunc->objectType;
asCDataType dt = asCDataType::CreateObject(objectType, false);
// The object pointer is located at stack position 0
ctx->bc.InstrSHORT(asBC_PSF, 0);
ctx->type.SetVariable(dt, 0, false);
ctx->type.dataType.MakeReference(true);
Dereference(ctx, true);
}
}
// If it is not a class method or member function pointer,
// then look for global functions or global function pointers,
// unless this is an expression post op, incase only member
// functions are expected
if( objectType == 0 && funcs.GetLength() == 0 && funcPtr.type.dataType.GetFuncDef() == 0 )
{
// The scope is used to define the namespace
asSNameSpace *ns = DetermineNameSpace(scope);
if( ns )
{
// Search recursively in parent namespaces
while( ns && funcs.GetLength() == 0 && funcPtr.type.dataType.GetFuncDef() == 0 )
{
builder->GetFunctionDescriptions(name.AddressOf(), funcs, ns);
if( funcs.GetLength() == 0 )
{
int r = CompileVariableAccess(name, scope, &funcPtr, node, true, true);
if( r >= 0 && !funcPtr.type.dataType.GetFuncDef() )
{
// The variable is not a function
asCString msg;
msg.Format(TXT_NOT_A_FUNC_s_IS_VAR, name.AddressOf());
Error(msg, node);
return -1;
}
}
ns = builder->GetParentNameSpace(ns);
}
}
else
{
asCString msg;
msg.Format(TXT_NAMESPACE_s_DOESNT_EXIST, scope.AddressOf());
Error(msg, node);
return -1;
}
}
}
if( funcs.GetLength() == 0 && funcPtr.type.dataType.GetFuncDef() )
{
funcs.PushLast(funcPtr.type.dataType.GetFuncDef()->id);
}
// Compile the arguments
asCArray<asSExprContext *> args;
asCArray<asCTypeInfo> temporaryVariables;
[...]
After debugging for a while it seems that it is very similar to what is generated when myObject.functor.opCall() is called, but there is obviously something different that I cannot figure out. Does anyone have an idea?
[attachment=19508:diff.txt]
I am pretty sure that it is related to stack cleanup (I haev tried adding the following code after CompileFunction Call, but it just crashes differently and I must admit I do not understand everything of it yet...):
// If the method returned a reference, then we can't release the original
// object yet, because the reference may be to a member of it
if( !objType.isTemporary ||
!(ctx->type.dataType.IsReference() || (ctx->type.dataType.IsObject() && !ctx->type.dataType.IsObjectHandle())) ||
ctx->type.isVariable ) // If the resulting type is a variable, then the reference is not a member
{
// As the method didn't return a reference to a member
// we can safely release the original object now
ReleaseTemporaryVariable(objType, &ctx->bc);
}