Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
840 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/source/angelscript/source/as_builder.cpp b/source/angelscript/source/as_builder.cpp
index 5c555f0..012735d 100644
--- a/source/angelscript/source/as_builder.cpp
+++ b/source/angelscript/source/as_builder.cpp
@@ -1,5923 +1,5926 @@
/*
AngelCode Scripting Library
Copyright (c) 2003-2016 Andreas Jonsson
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
The original version of this library can be located at:
http://www.angelcode.com/angelscript/
Andreas Jonsson
andreas@angelcode.com
*/
//
// as_builder.cpp
//
// This is the class that manages the compilation of the scripts
//
#include "as_config.h"
#include "as_builder.h"
#include "as_parser.h"
#include "as_compiler.h"
#include "as_tokendef.h"
#include "as_string_util.h"
#include "as_outputbuffer.h"
#include "as_texts.h"
#include "as_scriptobject.h"
#include "as_debug.h"
BEGIN_AS_NAMESPACE
#ifndef AS_NO_COMPILER
// asCSymbolTable template specializations for sGlobalVariableDescription entries
template<>
void asCSymbolTable<sGlobalVariableDescription>::GetKey(const sGlobalVariableDescription *entry, asSNameSpaceNamePair &key) const
{
asSNameSpace *ns = entry->ns;
asCString name = entry->name;
key = asSNameSpaceNamePair(ns, name);
}
// Comparator for exact variable search
class asCCompGlobVarType : public asIFilter
{
public:
const asCDataType &m_type;
asCCompGlobVarType(const asCDataType &type) : m_type(type) {}
bool operator()(const void *p) const
{
const sGlobalVariableDescription* desc = reinterpret_cast<const sGlobalVariableDescription*>(p);
return desc->datatype == m_type;
}
private:
// The assignment operator is required for MSVC9, otherwise it will complain that it is not possible to auto generate the operator
asCCompGlobVarType &operator=(const asCCompGlobVarType &) {return *this;}
};
#endif
asCBuilder::asCBuilder(asCScriptEngine *_engine, asCModule *_module)
{
this->engine = _engine;
this->module = _module;
silent = false;
}
asCBuilder::~asCBuilder()
{
#ifndef AS_NO_COMPILER
asUINT n;
// Free all functions
for( n = 0; n < functions.GetLength(); n++ )
{
if( functions[n] )
{
if( functions[n]->node )
functions[n]->node->Destroy(engine);
asDELETE(functions[n],sFunctionDescription);
}
functions[n] = 0;
}
// Free all global variables
asCSymbolTable<sGlobalVariableDescription>::iterator it = globVariables.List();
while( it )
{
if( (*it)->declaredAtNode )
(*it)->declaredAtNode->Destroy(engine);
if( (*it)->initializationNode )
(*it)->initializationNode->Destroy(engine);
asDELETE((*it),sGlobalVariableDescription);
it++;
}
globVariables.Clear();
// Free all the loaded files
for( n = 0; n < scripts.GetLength(); n++ )
{
if( scripts[n] )
asDELETE(scripts[n],asCScriptCode);
scripts[n] = 0;
}
// Free all class declarations
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
if( classDeclarations[n] )
{
if( classDeclarations[n]->node )
classDeclarations[n]->node->Destroy(engine);
asDELETE(classDeclarations[n],sClassDeclaration);
classDeclarations[n] = 0;
}
}
for( n = 0; n < interfaceDeclarations.GetLength(); n++ )
{
if( interfaceDeclarations[n] )
{
if( interfaceDeclarations[n]->node )
interfaceDeclarations[n]->node->Destroy(engine);
asDELETE(interfaceDeclarations[n],sClassDeclaration);
interfaceDeclarations[n] = 0;
}
}
for( n = 0; n < namedTypeDeclarations.GetLength(); n++ )
{
if( namedTypeDeclarations[n] )
{
if( namedTypeDeclarations[n]->node )
namedTypeDeclarations[n]->node->Destroy(engine);
asDELETE(namedTypeDeclarations[n],sClassDeclaration);
namedTypeDeclarations[n] = 0;
}
}
for( n = 0; n < funcDefs.GetLength(); n++ )
{
if( funcDefs[n] )
{
if( funcDefs[n]->node )
funcDefs[n]->node->Destroy(engine);
asDELETE(funcDefs[n],sFuncDef);
funcDefs[n] = 0;
}
}
for( n = 0; n < mixinClasses.GetLength(); n++ )
{
if( mixinClasses[n] )
{
if( mixinClasses[n]->node )
mixinClasses[n]->node->Destroy(engine);
asDELETE(mixinClasses[n],sMixinClass);
mixinClasses[n] = 0;
}
}
#endif // AS_NO_COMPILER
}
void asCBuilder::Reset()
{
numErrors = 0;
numWarnings = 0;
engine->preMessage.isSet = false;
#ifndef AS_NO_COMPILER
// Clear the cache of known types
hasCachedKnownTypes = false;
knownTypes.EraseAll();
#endif
}
#ifndef AS_NO_COMPILER
int asCBuilder::AddCode(const char *name, const char *code, int codeLength, int lineOffset, int sectionIdx, bool makeCopy)
{
asCScriptCode *script = asNEW(asCScriptCode);
if( script == 0 )
return asOUT_OF_MEMORY;
int r = script->SetCode(name, code, codeLength, makeCopy);
if( r < 0 )
{
asDELETE(script, asCScriptCode);
return r;
}
script->lineOffset = lineOffset;
script->idx = sectionIdx;
scripts.PushLast(script);
return 0;
}
void asCBuilder::EvaluateTemplateInstances(asUINT startIdx, bool keepSilent)
{
// Backup the original message stream
bool msgCallback = engine->msgCallback;
asSSystemFunctionInterface msgCallbackFunc = engine->msgCallbackFunc;
void *msgCallbackObj = engine->msgCallbackObj;
// Set the new temporary message stream
asCOutputBuffer outBuffer;
if( keepSilent )
engine->SetMessageCallback(asMETHOD(asCOutputBuffer, Callback), &outBuffer, asCALL_THISCALL);
// Evaluate each of the template instances that have been created since the start of the build
// TODO: This is not exactly correct, since another thread may have created template instances in parallel
for( asUINT n = startIdx; n < engine->templateInstanceTypes.GetLength(); n++ )
{
bool dontGarbageCollect = false;
asCObjectType *tmpl = engine->templateInstanceTypes[n];
asCScriptFunction *callback = engine->scriptFunctions[tmpl->beh.templateCallback];
if( callback && !engine->CallGlobalFunctionRetBool(tmpl, &dontGarbageCollect, callback->sysFuncIntf, callback) )
{
asCString sub = tmpl->templateSubTypes[0].Format(engine->nameSpaces[0]);
for( asUINT m = 1; m < tmpl->templateSubTypes.GetLength(); m++ )
{
sub += ",";
sub += tmpl->templateSubTypes[m].Format(engine->nameSpaces[0]);
}
asCString str;
str.Format(TXT_INSTANCING_INVLD_TMPL_TYPE_s_s, tmpl->name.AddressOf(), sub.AddressOf());
WriteError(tmpl->scriptSectionIdx >= 0 ? engine->scriptSectionNames[tmpl->scriptSectionIdx]->AddressOf() : "", str, tmpl->declaredAt&0xFFFFF, (tmpl->declaredAt>>20)&0xFFF);
}
else
{
// If the callback said this template instance won't be garbage collected then remove the flag
if( dontGarbageCollect )
tmpl->flags &= ~asOBJ_GC;
}
}
// Restore message callback
if( keepSilent )
{
engine->msgCallback = msgCallback;
engine->msgCallbackFunc = msgCallbackFunc;
engine->msgCallbackObj = msgCallbackObj;
}
}
int asCBuilder::Build()
{
Reset();
// The template callbacks must only be called after the subtypes have a known structure,
// otherwise the callback may think it is not possible to create the template instance,
// even though it is.
// TODO: This flag shouldn't be set globally in the engine, as it would mean that another
// thread requesting a template instance in parallel to the compilation wouldn't
// evaluate the template instance.
engine->deferValidationOfTemplateTypes = true;
asUINT numTempl = (asUINT)engine->templateInstanceTypes.GetLength();
ParseScripts();
// Compile the types first
CompileInterfaces();
CompileClasses(numTempl);
// Evaluate the template instances one last time, this time with error messages, as we know
// all classes have been fully built and it is known which ones will need garbage collection.
EvaluateTemplateInstances(numTempl, false);
engine->deferValidationOfTemplateTypes = false;
// Then the global variables. Here the variables declared with auto
// will be resolved, so they can be accessed properly in the functions
CompileGlobalVariables();
// Finally the global functions and class methods
CompileFunctions();
// TODO: Attempt to reorder the initialization of global variables so that
// they do not access other uninitialized global variables out-of-order
// The builder needs to check for each of the global variable, what functions
// that are accessed, and what global variables are access by these functions.
if( numWarnings > 0 && engine->ep.compilerWarnings == 2 )
WriteError(TXT_WARNINGS_TREATED_AS_ERROR, 0, 0);
if( numErrors > 0 )
return asERROR;
// Make sure something was compiled, otherwise return an error
//if( module->IsEmpty() )
//{
// WriteError(TXT_NOTHING_WAS_BUILT, 0, 0);
// return asERROR;
//}
return asSUCCESS;
}
int asCBuilder::CompileGlobalVar(const char *sectionName, const char *code, int lineOffset)
{
Reset();
// Add the string to the script code
asCScriptCode *script = asNEW(asCScriptCode);
if( script == 0 )
return asOUT_OF_MEMORY;
script->SetCode(sectionName, code, true);
script->lineOffset = lineOffset;
script->idx = engine->GetScriptSectionNameIndex(sectionName ? sectionName : "");
scripts.PushLast(script);
// Parse the string
asCParser parser(this);
if( parser.ParseScript(scripts[0]) < 0 )
return asERROR;
asCScriptNode *node = parser.GetScriptNode();
// Make sure there is nothing else than the global variable in the script code
if( node == 0 ||
node->firstChild == 0 ||
node->firstChild != node->lastChild ||
node->firstChild->nodeType != snDeclaration )
{
WriteError(TXT_ONLY_ONE_VARIABLE_ALLOWED, script, 0);
return asERROR;
}
node = node->firstChild;
node->DisconnectParent();
RegisterGlobalVar(node, script, module->defaultNamespace);
CompileGlobalVariables();
// It is possible that the global variable initialization included anonymous functions that must be compiled too
for( asUINT n = 0; n < functions.GetLength(); n++ )
{
asCCompiler compiler(engine);
asCScriptFunction *func = engine->scriptFunctions[functions[n]->funcId];
int r = compiler.CompileFunction(this, functions[n]->script, func->parameterNames, functions[n]->node, func, 0);
if( r < 0 )
break;
}
if( numWarnings > 0 && engine->ep.compilerWarnings == 2 )
WriteError(TXT_WARNINGS_TREATED_AS_ERROR, 0, 0);
// None of the functions should be added to the module if any error occurred,
// or it was requested that the functions wouldn't be added to the scope
if( numErrors > 0 )
{
for( asUINT n = 0; n < functions.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[functions[n]->funcId];
if( module->globalFunctions.GetIndex(func) >= 0 )
{
module->globalFunctions.Erase(module->globalFunctions.GetIndex(func));
module->scriptFunctions.RemoveValue(func);
func->ReleaseInternal();
}
}
}
if( numErrors > 0 )
{
// Remove the variable from the module, if it was registered
if( globVariables.GetSize() > 0 )
module->RemoveGlobalVar(module->GetGlobalVarCount()-1);
return asERROR;
}
return 0;
}
#endif
int asCBuilder::ValidateDefaultArgs(asCScriptCode *script, asCScriptNode *node, asCScriptFunction *func)
{
int firstArgWithDefaultValue = -1;
for( asUINT n = 0; n < func->defaultArgs.GetLength(); n++ )
{
if( func->defaultArgs[n] )
firstArgWithDefaultValue = n;
else if( firstArgWithDefaultValue >= 0 )
{
asCString str;
str.Format(TXT_DEF_ARG_MISSING_IN_FUNC_s, func->GetDeclaration());
WriteError(str, script, node);
return asINVALID_DECLARATION;
}
}
return 0;
}
#ifndef AS_NO_COMPILER
// This function will verify if the newly created function will conflict another overload due to having
// identical function arguments that are not default args, e.g: foo(int) and foo(int, int=0)
int asCBuilder::CheckForConflictsDueToDefaultArgs(asCScriptCode *script, asCScriptNode *node, asCScriptFunction *func, asCObjectType *objType)
{
// TODO: Implement for global functions too
if( func->objectType == 0 || objType == 0 ) return 0;
asCArray<int> funcs;
GetObjectMethodDescriptions(func->name.AddressOf(), objType, funcs, false);
for( asUINT n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *func2 = engine->scriptFunctions[funcs[n]];
if( func == func2 )
continue;
if( func->IsReadOnly() != func2->IsReadOnly() )
continue;
bool match = true;
asUINT p = 0;
for( ; p < func->parameterTypes.GetLength() && p < func2->parameterTypes.GetLength(); p++ )
{
// Only verify until the first argument with default args
if( (func->defaultArgs.GetLength() > p && func->defaultArgs[p]) ||
(func2->defaultArgs.GetLength() > p && func2->defaultArgs[p]) )
break;
if( func->parameterTypes[p] != func2->parameterTypes[p] ||
func->inOutFlags[p] != func2->inOutFlags[p] )
{
match = false;
break;
}
}
if( match )
{
if( !((p >= func->parameterTypes.GetLength() && p < func2->defaultArgs.GetLength() && func2->defaultArgs[p]) ||
(p >= func2->parameterTypes.GetLength() && p < func->defaultArgs.GetLength() && func->defaultArgs[p])) )
{
// The argument lists match for the full length of the shorter, but the next
// argument on the longer does not have a default arg so there is no conflict
match = false;
}
}
if( match )
{
WriteWarning(TXT_OVERLOAD_CONFLICTS_DUE_TO_DEFAULT_ARGS, script, node);
WriteInfo(func->GetDeclaration(), script, node);
WriteInfo(func2->GetDeclaration(), script, node);
break;
}
}
return 0;
}
int asCBuilder::CompileFunction(const char *sectionName, const char *code, int lineOffset, asDWORD compileFlags, asCScriptFunction **outFunc)
{
asASSERT(outFunc != 0);
Reset();
// Add the string to the script code
asCScriptCode *script = asNEW(asCScriptCode);
if( script == 0 )
return asOUT_OF_MEMORY;
script->SetCode(sectionName, code, true);
script->lineOffset = lineOffset;
script->idx = engine->GetScriptSectionNameIndex(sectionName ? sectionName : "");
scripts.PushLast(script);
// Parse the string
asCParser parser(this);
if( parser.ParseScript(scripts[0]) < 0 )
return asERROR;
asCScriptNode *node = parser.GetScriptNode();
// Make sure there is nothing else than the function in the script code
if( node == 0 ||
node->firstChild == 0 ||
node->firstChild != node->lastChild ||
node->firstChild->nodeType != snFunction )
{
WriteError(TXT_ONLY_ONE_FUNCTION_ALLOWED, script, 0);
return asERROR;
}
// Find the function node
node = node->firstChild;
// Create the function
bool isConstructor, isDestructor, isPrivate, isProtected, isFinal, isOverride, isShared;
asCScriptFunction *func = asNEW(asCScriptFunction)(engine, compileFlags & asCOMP_ADD_TO_MODULE ? module : 0, asFUNC_SCRIPT);
if( func == 0 )
return asOUT_OF_MEMORY;
GetParsedFunctionDetails(node, scripts[0], 0, func->name, func->returnType, func->parameterNames, func->parameterTypes, func->inOutFlags, func->defaultArgs, func->isReadOnly, isConstructor, isDestructor, isPrivate, isProtected, isFinal, isOverride, isShared, module->defaultNamespace);
func->id = engine->GetNextScriptFunctionId();
func->scriptData->scriptSectionIdx = engine->GetScriptSectionNameIndex(sectionName ? sectionName : "");
int row, col;
scripts[0]->ConvertPosToRowCol(node->tokenPos, &row, &col);
func->scriptData->declaredAt = (row & 0xFFFFF)|((col & 0xFFF)<<20);
func->nameSpace = module->defaultNamespace;
// Make sure the default args are declared correctly
int r = ValidateDefaultArgs(script, node, func);
if( r < 0 )
{
func->ReleaseInternal();
return asERROR;
}
// Tell the engine that the function exists already so the compiler can access it
if( compileFlags & asCOMP_ADD_TO_MODULE )
{
r = CheckNameConflict(func->name.AddressOf(), node, scripts[0], module->defaultNamespace);
if( r < 0 )
{
func->ReleaseInternal();
return asERROR;
}
module->globalFunctions.Put(func);
module->AddScriptFunction(func);
}
else
engine->AddScriptFunction(func);
// Fill in the function info for the builder too
node->DisconnectParent();
sFunctionDescription *funcDesc = asNEW(sFunctionDescription);
if( funcDesc == 0 )
{
func->ReleaseInternal();
return asOUT_OF_MEMORY;
}
functions.PushLast(funcDesc);
funcDesc->script = scripts[0];
funcDesc->node = node;
funcDesc->name = func->name;
funcDesc->funcId = func->id;
funcDesc->paramNames = func->parameterNames;
funcDesc->isExistingShared = false;
// This must be done in a loop, as it is possible that additional functions get declared as lambda's in the code
for( asUINT n = 0; n < functions.GetLength(); n++ )
{
asCCompiler compiler(engine);
asCScriptFunction *f = engine->scriptFunctions[functions[n]->funcId];
r = compiler.CompileFunction(this, functions[n]->script, f->parameterNames, functions[n]->node, f, 0);
if( r < 0 )
break;
}
if( numWarnings > 0 && engine->ep.compilerWarnings == 2 )
WriteError(TXT_WARNINGS_TREATED_AS_ERROR, 0, 0);
// None of the functions should be added to the module if any error occurred,
// or it was requested that the functions wouldn't be added to the scope
if( !(compileFlags & asCOMP_ADD_TO_MODULE) || numErrors > 0 )
{
for( asUINT n = 0; n < functions.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[functions[n]->funcId];
if( module->globalFunctions.GetIndex(f) >= 0 )
{
module->globalFunctions.Erase(module->globalFunctions.GetIndex(f));
module->scriptFunctions.RemoveValue(f);
f->ReleaseInternal();
}
}
}
if( numErrors > 0 )
{
// Release the function pointer that would otherwise be returned if no errors occured
func->ReleaseInternal();
return asERROR;
}
// Return the function
*outFunc = func;
return asSUCCESS;
}
void asCBuilder::ParseScripts()
{
TimeIt("asCBuilder::ParseScripts");
asCArray<asCParser*> parsers((int)scripts.GetLength());
// Parse all the files as if they were one
asUINT n = 0;
for( n = 0; n < scripts.GetLength(); n++ )
{
asCParser *parser = asNEW(asCParser)(this);
if( parser != 0 )
{
parsers.PushLast(parser);
// Parse the script file
parser->ParseScript(scripts[n]);
}
}
if (numErrors == 0)
{
// Find all type declarations
for (n = 0; n < scripts.GetLength(); n++)
{
asCScriptNode *node = parsers[n]->GetScriptNode();
RegisterTypesFromScript(node, scripts[n], engine->nameSpaces[0]);
}
// Before moving forward the builder must establish the relationship between types
// so that a derived type can see the child types of the parent type.
DetermineTypeRelations();
// Complete function definitions (defining returntype and parameters)
for( n = 0; n < funcDefs.GetLength(); n++ )
CompleteFuncDef(funcDefs[n]);
// Register script methods found in the interfaces
for( n = 0; n < interfaceDeclarations.GetLength(); n++ )
{
sClassDeclaration *decl = interfaceDeclarations[n];
asCScriptNode *node = decl->node->firstChild->next;
// Skip list of inherited interfaces
while( node && node->nodeType == snIdentifier )
node = node->next;
while( node )
{
asCScriptNode *next = node->next;
if( node->nodeType == snFunction )
{
node->DisconnectParent();
RegisterScriptFunctionFromNode(node, decl->script, decl->typeInfo->CastToObjectType(), true, false, 0, decl->isExistingShared);
}
else if( node->nodeType == snVirtualProperty )
{
node->DisconnectParent();
RegisterVirtualProperty(node, decl->script, decl->typeInfo->CastToObjectType(), true, false, 0, decl->isExistingShared);
}
node = next;
}
}
// Register script methods found in the classes
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
sClassDeclaration *decl = classDeclarations[n];
asCScriptNode *node = decl->node->firstChild->next;
// Skip list of classes and interfaces
while( node && node->nodeType == snIdentifier )
node = node->next;
while( node )
{
asCScriptNode *next = node->next;
if( node->nodeType == snFunction )
{
node->DisconnectParent();
RegisterScriptFunctionFromNode(node, decl->script, decl->typeInfo->CastToObjectType(), false, false, 0, decl->isExistingShared);
}
else if( node->nodeType == snVirtualProperty )
{
node->DisconnectParent();
RegisterVirtualProperty(node, decl->script, decl->typeInfo->CastToObjectType(), false, false, 0, decl->isExistingShared);
}
node = next;
}
// Make sure the default factory & constructor exists for classes
asCObjectType *ot = decl->typeInfo->CastToObjectType();
if( ot->beh.construct == engine->scriptTypeBehaviours.beh.construct )
{
if( ot->beh.constructors.GetLength() == 1 || engine->ep.alwaysImplDefaultConstruct )
{
AddDefaultConstructor(ot, decl->script);
}
else
{
// As the class has another constructor we shouldn't provide the default constructor
if( ot->beh.construct )
{
engine->scriptFunctions[ot->beh.construct]->ReleaseInternal();
ot->beh.construct = 0;
ot->beh.constructors.RemoveIndex(0);
}
if( ot->beh.factory )
{
engine->scriptFunctions[ot->beh.factory]->ReleaseInternal();
ot->beh.factory = 0;
ot->beh.factories.RemoveIndex(0);
}
// Only remove the opAssign method if the script hasn't provided one
if( ot->beh.copy == engine->scriptTypeBehaviours.beh.copy )
{
engine->scriptFunctions[ot->beh.copy]->ReleaseInternal();
ot->beh.copy = 0;
}
}
}
}
// Find other global nodes
for( n = 0; n < scripts.GetLength(); n++ )
{
// Find other global nodes
asCScriptNode *node = parsers[n]->GetScriptNode();
RegisterNonTypesFromScript(node, scripts[n], engine->nameSpaces[0]);
}
}
for( n = 0; n < parsers.GetLength(); n++ )
{
asDELETE(parsers[n],asCParser);
}
}
void asCBuilder::RegisterTypesFromScript(asCScriptNode *node, asCScriptCode *script, asSNameSpace *ns)
{
asASSERT(node->nodeType == snScript);
// Find structure definitions first
node = node->firstChild;
while( node )
{
asCScriptNode *next = node->next;
if( node->nodeType == snNamespace )
{
// Recursively register the entities defined in the namespace
asCString nsName;
nsName.Assign(&script->code[node->firstChild->tokenPos], node->firstChild->tokenLength);
if( ns->name != "" )
nsName = ns->name + "::" + nsName;
asSNameSpace *nsChild = engine->AddNameSpace(nsName.AddressOf());
RegisterTypesFromScript(node->lastChild, script, nsChild);
}
else
{
if( node->nodeType == snClass )
{
node->DisconnectParent();
RegisterClass(node, script, ns);
}
else if( node->nodeType == snInterface )
{
node->DisconnectParent();
RegisterInterface(node, script, ns);
}
else if( node->nodeType == snEnum )
{
node->DisconnectParent();
RegisterEnum(node, script, ns);
}
else if( node->nodeType == snTypedef )
{
node->DisconnectParent();
RegisterTypedef(node, script, ns);
}
else if( node->nodeType == snFuncDef )
{
node->DisconnectParent();
RegisterFuncDef(node, script, ns, 0);
}
else if( node->nodeType == snMixin )
{
node->DisconnectParent();
RegisterMixinClass(node, script, ns);
}
}
node = next;
}
}
void asCBuilder::RegisterNonTypesFromScript(asCScriptNode *node, asCScriptCode *script, asSNameSpace *ns)
{
node = node->firstChild;
while( node )
{
asCScriptNode *next = node->next;
if( node->nodeType == snNamespace )
{
// Determine the name of the namespace
asCString nsName;
nsName.Assign(&script->code[node->firstChild->tokenPos], node->firstChild->tokenLength);
if( ns->name != "" )
nsName = ns->name + "::" + nsName;
// Declare the namespace, then add the entities
asSNameSpace *nsChild = engine->AddNameSpace(nsName.AddressOf());
RegisterNonTypesFromScript(node->lastChild, script, nsChild);
}
else
{
node->DisconnectParent();
if( node->nodeType == snFunction )
RegisterScriptFunctionFromNode(node, script, 0, false, true, ns);
else if( node->nodeType == snDeclaration )
RegisterGlobalVar(node, script, ns);
else if( node->nodeType == snVirtualProperty )
RegisterVirtualProperty(node, script, 0, false, true, ns);
else if( node->nodeType == snImport )
RegisterImportedFunction(module->GetNextImportedFunctionId(), node, script, ns);
else
{
// Unused script node
int r, c;
script->ConvertPosToRowCol(node->tokenPos, &r, &c);
WriteWarning(script->name, TXT_UNUSED_SCRIPT_NODE, r, c);
node->Destroy(engine);
}
}
node = next;
}
}
void asCBuilder::CompileFunctions()
{
// Compile each function
for( asUINT n = 0; n < functions.GetLength(); n++ )
{
sFunctionDescription *current = functions[n];
if( current == 0 ) continue;
// Don't compile the function again if it was an existing shared function
if( current->isExistingShared ) continue;
asCCompiler compiler(engine);
asCScriptFunction *func = engine->scriptFunctions[current->funcId];
// Find the class declaration for constructors
sClassDeclaration *classDecl = 0;
if( current->objType && current->name == current->objType->name )
{
for( asUINT c = 0; c < classDeclarations.GetLength(); c++ )
{
if( classDeclarations[c]->typeInfo == current->objType )
{
classDecl = classDeclarations[c];
break;
}
}
asASSERT( classDecl );
}
if( current->node )
{
int r, c;
current->script->ConvertPosToRowCol(current->node->tokenPos, &r, &c);
asCString str = func->GetDeclarationStr();
str.Format(TXT_COMPILING_s, str.AddressOf());
WriteInfo(current->script->name, str, r, c, true);
// When compiling a constructor need to pass the class declaration for member initializations
compiler.CompileFunction(this, current->script, current->paramNames, current->node, func, classDecl);
engine->preMessage.isSet = false;
}
else if( current->objType && current->name == current->objType->name )
{
asCScriptNode *node = classDecl->node;
int r = 0, c = 0;
if( node )
current->script->ConvertPosToRowCol(node->tokenPos, &r, &c);
asCString str = func->GetDeclarationStr();
str.Format(TXT_COMPILING_s, str.AddressOf());
WriteInfo(current->script->name, str, r, c, true);
// This is the default constructor that is generated
// automatically if not implemented by the user.
compiler.CompileDefaultConstructor(this, current->script, node, func, classDecl);
engine->preMessage.isSet = false;
}
else
{
asASSERT( false );
}
}
}
#endif
// Called from module and engine
int asCBuilder::ParseDataType(const char *datatype, asCDataType *result, asSNameSpace *implicitNamespace, bool isReturnType)
{
Reset();
asCScriptCode source;
source.SetCode("", datatype, true);
asCParser parser(this);
int r = parser.ParseDataType(&source, isReturnType);
if( r < 0 )
return asINVALID_TYPE;
// Get data type and property name
asCScriptNode *dataType = parser.GetScriptNode()->firstChild;
*result = CreateDataTypeFromNode(dataType, &source, implicitNamespace, true);
if( isReturnType )
*result = ModifyDataTypeFromNode(*result, dataType->next, &source, 0, 0);
if( numErrors > 0 )
return asINVALID_TYPE;
return asSUCCESS;
}
int asCBuilder::ParseTemplateDecl(const char *decl, asCString *name, asCArray<asCString> &subtypeNames)
{
Reset();
asCScriptCode source;
source.SetCode("", decl, true);
asCParser parser(this);
int r = parser.ParseTemplateDecl(&source);
if( r < 0 )
return asINVALID_TYPE;
// Get the template name and subtype names
asCScriptNode *node = parser.GetScriptNode()->firstChild;
name->Assign(&decl[node->tokenPos], node->tokenLength);
while( (node = node->next) != 0 )
{
asCString subtypeName;
subtypeName.Assign(&decl[node->tokenPos], node->tokenLength);
subtypeNames.PushLast(subtypeName);
}
// TODO: template: check for name conflicts
if( numErrors > 0 )
return asINVALID_DECLARATION;
return asSUCCESS;
}
int asCBuilder::VerifyProperty(asCDataType *dt, const char *decl, asCString &name, asCDataType &type, asSNameSpace *ns)
{
// Either datatype or namespace must be informed
asASSERT( dt || ns );
Reset();
if( dt )
{
// Verify that the object type exist
if( dt->GetTypeInfo()->CastToObjectType() == 0 )
return asINVALID_OBJECT;
}
// Check property declaration and type
asCScriptCode source;
source.SetCode(TXT_PROPERTY, decl, true);
asCParser parser(this);
int r = parser.ParsePropertyDeclaration(&source);
if( r < 0 )
return asINVALID_DECLARATION;
// Get data type
asCScriptNode *dataType = parser.GetScriptNode()->firstChild;
// Check if the property is declared 'by reference'
bool isReference = (dataType->next->tokenType == ttAmp);
// Get the name of the property
asCScriptNode *nameNode = isReference ? dataType->next->next : dataType->next;
// If an object property is registered, then use the
// object's namespace, otherwise use the specified namespace
type = CreateDataTypeFromNode(dataType, &source, dt ? dt->GetTypeInfo()->nameSpace : ns);
name.Assign(&decl[nameNode->tokenPos], nameNode->tokenLength);
type.MakeReference(isReference);
// Validate that the type really can be a registered property
// We cannot use CanBeInstantiated, as it is allowed to register
// properties of type that cannot otherwise be instantiated
if( type.IsFuncdef() && !type.IsObjectHandle() )
{
// Function definitions must always be handles
return asINVALID_DECLARATION;
}
// Verify property name
if( dt )
{
if( CheckNameConflictMember(dt->GetTypeInfo(), name.AddressOf(), nameNode, &source, true) < 0 )
return asNAME_TAKEN;
}
else
{
if( CheckNameConflict(name.AddressOf(), nameNode, &source, ns) < 0 )
return asNAME_TAKEN;
}
if( numErrors > 0 )
return asINVALID_DECLARATION;
return asSUCCESS;
}
#ifndef AS_NO_COMPILER
asCObjectProperty *asCBuilder::GetObjectProperty(asCDataType &obj, const char *prop)
{
asASSERT(obj.GetTypeInfo()->CastToObjectType() != 0);
// TODO: optimize: Improve linear search
asCArray<asCObjectProperty *> &props = obj.GetTypeInfo()->CastToObjectType()->properties;
for( asUINT n = 0; n < props.GetLength(); n++ )
{
if( props[n]->name == prop )
{
if( module->accessMask & props[n]->accessMask )
return props[n];
else
return 0;
}
}
return 0;
}
#endif
bool asCBuilder::DoesGlobalPropertyExist(const char *prop, asSNameSpace *ns, asCGlobalProperty **outProp, sGlobalVariableDescription **outDesc, bool *isAppProp)
{
if( outProp ) *outProp = 0;
if( outDesc ) *outDesc = 0;
if( isAppProp ) *isAppProp = false;
// Check application registered properties
asCString name(prop);
asCGlobalProperty *globProp = engine->registeredGlobalProps.GetFirst(ns, name);
if( globProp )
{
if( isAppProp ) *isAppProp = true;
if( outProp ) *outProp = globProp;
return true;
}
#ifndef AS_NO_COMPILER
// Check properties being compiled now
sGlobalVariableDescription* desc = globVariables.GetFirst(ns, prop);
if( desc && !desc->isEnumValue )
{
if( outProp ) *outProp = desc->property;
if( outDesc ) *outDesc = desc;
return true;
}
#endif
// Check previously compiled global variables
if( module )
{
globProp = module->scriptGlobals.GetFirst(ns, prop);
if( globProp )
{
if( outProp ) *outProp = globProp;
return true;
}
globProp = module->importedGlobals.GetFirst(ns, prop);
if( globProp )
{
if( outProp ) *outProp = globProp;
return true;
}
}
return false;
}
asCGlobalProperty *asCBuilder::GetGlobalProperty(const char *prop, asSNameSpace *ns, bool *isCompiled, bool *isPureConstant, asQWORD *constantValue, bool *isAppProp)
{
if( isCompiled ) *isCompiled = true;
if( isPureConstant ) *isPureConstant = false;
if( isAppProp ) *isAppProp = false;
if( constantValue ) *constantValue = 0;
asCGlobalProperty *globProp = 0;
sGlobalVariableDescription *globDesc = 0;
if( DoesGlobalPropertyExist(prop, ns, &globProp, &globDesc, isAppProp) )
{
#ifndef AS_NO_COMPILER
if( globDesc )
{
// The property was declared in this build call, check if it has been compiled successfully already
if( isCompiled ) *isCompiled = globDesc->isCompiled;
if( isPureConstant ) *isPureConstant = globDesc->isPureConstant;
if( constantValue ) *constantValue = globDesc->constantValue;
}
else
#endif
if( isAppProp )
{
// Don't return the property if the module doesn't have access to it
if( !(module->accessMask & globProp->accessMask) )
globProp = 0;
}
return globProp;
}
return 0;
}
int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *decl, asCScriptFunction *func, bool isSystemFunction, asCArray<bool> *paramAutoHandles, bool *returnAutoHandle, asSNameSpace *ns, asCScriptNode **listPattern, asCObjectType **outParentClass)
{
asASSERT( objType || ns );
if (listPattern)
*listPattern = 0;
if (outParentClass)
*outParentClass = 0;
// TODO: Can't we use GetParsedFunctionDetails to do most of what is done in this function?
Reset();
asCScriptCode source;
source.SetCode(TXT_SYSTEM_FUNCTION, decl, true);
asCParser parser(this);
int r = parser.ParseFunctionDefinition(&source, listPattern != 0);
if( r < 0 )
return asINVALID_DECLARATION;
asCScriptNode *node = parser.GetScriptNode();
// Determine scope
asCScriptNode *n = node->firstChild->next->next;
asCObjectType *parentClass = 0;
func->nameSpace = GetNameSpaceFromNode(n, &source, ns, &n, &parentClass);
if( func->nameSpace == 0 && parentClass == 0 )
return asINVALID_DECLARATION;
if (parentClass && func->funcType != asFUNC_FUNCDEF)
return asINVALID_DECLARATION;
if (outParentClass)
*outParentClass = parentClass;
// Find name
func->name.Assign(&source.code[n->tokenPos], n->tokenLength);
// Initialize a script function object for registration
bool autoHandle;
// Scoped reference types are allowed to use handle when returned from application functions
func->returnType = CreateDataTypeFromNode(node->firstChild, &source, objType ? objType->nameSpace : ns, true, parentClass ? parentClass : objType);
func->returnType = ModifyDataTypeFromNode(func->returnType, node->firstChild->next, &source, 0, &autoHandle);
if( autoHandle && (!func->returnType.IsObjectHandle() || func->returnType.IsReference()) )
return asINVALID_DECLARATION;
if( returnAutoHandle ) *returnAutoHandle = autoHandle;
// Reference types cannot be returned by value from system functions
if( isSystemFunction &&
(func->returnType.GetTypeInfo() &&
(func->returnType.GetTypeInfo()->flags & asOBJ_REF)) &&
!(func->returnType.IsReference() ||
func->returnType.IsObjectHandle()) )
return asINVALID_DECLARATION;
// Count number of parameters
int paramCount = 0;
asCScriptNode *paramList = n->next;
n = paramList->firstChild;
while( n )
{
paramCount++;
n = n->next->next;
if( n && n->nodeType == snIdentifier )
n = n->next;
if( n && n->nodeType == snExpression )
n = n->next;
}
// Preallocate memory
func->parameterTypes.Allocate(paramCount, false);
func->parameterNames.SetLength(paramCount);
func->inOutFlags.Allocate(paramCount, false);
func->defaultArgs.Allocate(paramCount, false);
if( paramAutoHandles ) paramAutoHandles->Allocate(paramCount, false);
n = paramList->firstChild;
asUINT index = 0;
while( n )
{
asETypeModifiers inOutFlags;
asCDataType type = CreateDataTypeFromNode(n, &source, objType ? objType->nameSpace : ns, false, parentClass ? parentClass : objType);
type = ModifyDataTypeFromNode(type, n->next, &source, &inOutFlags, &autoHandle);
// Reference types cannot be passed by value to system functions
if( isSystemFunction &&
(type.GetTypeInfo() &&
(type.GetTypeInfo()->flags & asOBJ_REF)) &&
!(type.IsReference() ||
type.IsObjectHandle()) )
return asINVALID_DECLARATION;
// Store the parameter type
func->parameterTypes.PushLast(type);
func->inOutFlags.PushLast(inOutFlags);
// Don't permit void parameters
if( type.GetTokenType() == ttVoid )
return asINVALID_DECLARATION;
if( autoHandle && (!type.IsObjectHandle() || type.IsReference()) )
return asINVALID_DECLARATION;
if( paramAutoHandles ) paramAutoHandles->PushLast(autoHandle);
// Make sure that var type parameters are references
if( type.GetTokenType() == ttQuestion &&
!type.IsReference() )
return asINVALID_DECLARATION;
// Move to next parameter
n = n->next->next;
if( n && n->nodeType == snIdentifier )
{
func->parameterNames[index] = asCString(&source.code[n->tokenPos], n->tokenLength);
n = n->next;
}
++index;
if( n && n->nodeType == snExpression )
{
// Strip out white space and comments to better share the string
asCString *defaultArgStr = asNEW(asCString);
if( defaultArgStr )
{
*defaultArgStr = GetCleanExpressionString(n, &source);
func->defaultArgs.PushLast(defaultArgStr);
}
n = n->next;
}
else
func->defaultArgs.PushLast(0);
}
// Set the read-only flag if const is declared after parameter list
n = paramList->next;
if( n && n->nodeType == snUndefined && n->tokenType == ttConst )
{
if( objType == 0 )
return asINVALID_DECLARATION;
func->isReadOnly = true;
n = n->next;
}
else
func->isReadOnly = false;
// If the caller expects a list pattern, check for the existence, else report an error if not
if( listPattern )
{
if( n == 0 || n->nodeType != snListPattern )
return asINVALID_DECLARATION;
else
{
*listPattern = n;
n->DisconnectParent();
}
}
else
{
if( n )
return asINVALID_DECLARATION;
}
// Make sure the default args are declared correctly
ValidateDefaultArgs(&source, node, func);
if( numErrors > 0 || numWarnings > 0 )
return asINVALID_DECLARATION;
return 0;
}
int asCBuilder::ParseVariableDeclaration(const char *decl, asSNameSpace *implicitNamespace, asCString &outName, asSNameSpace *&outNamespace, asCDataType &outDt)
{
Reset();
asCScriptCode source;
source.SetCode(TXT_VARIABLE_DECL, decl, true);
asCParser parser(this);
int r = parser.ParsePropertyDeclaration(&source);
if( r < 0 )
return asINVALID_DECLARATION;
asCScriptNode *node = parser.GetScriptNode();
// Determine the scope from declaration
asCScriptNode *n = node->firstChild->next;
// TODO: child funcdef: The parentType will be set if the scope is actually a type rather than a namespace
outNamespace = GetNameSpaceFromNode(n, &source, implicitNamespace, &n);
if( outNamespace == 0 )
return asINVALID_DECLARATION;
// Find name
outName.Assign(&source.code[n->tokenPos], n->tokenLength);
// Initialize a script variable object for registration
outDt = CreateDataTypeFromNode(node->firstChild, &source, implicitNamespace);
if( numErrors > 0 || numWarnings > 0 )
return asINVALID_DECLARATION;
return 0;
}
int asCBuilder::CheckNameConflictMember(asCTypeInfo *t, const char *name, asCScriptNode *node, asCScriptCode *code, bool isProperty)
{
// It's not necessary to check against object types
asCObjectType *ot = t->CastToObjectType();
if (!ot)
return 0;
// TODO: optimize: Improve linear search
asCArray<asCObjectProperty *> &props = ot->properties;
for( asUINT n = 0; n < props.GetLength(); n++ )
{
if( props[n]->name == name )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_OBJ_PROPERTY, name);
WriteError(str, code, node);
}
return -1;
}
}
asCArray<asCFuncdefType*> &funcdefs = ot->childFuncDefs;
for (asUINT n = 0; n < funcdefs.GetLength(); n++)
{
if (funcdefs[n]->name == name)
{
if (code)
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_IS_FUNCDEF, name);
WriteError(str, code, node);
}
return -1;
}
}
// Property names must be checked against method names
if( isProperty )
{
asCArray<int> methods = ot->methods;
for( asUINT n = 0; n < methods.GetLength(); n++ )
{
if( engine->scriptFunctions[methods[n]]->name == name )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_METHOD, name);
WriteError(str, code, node);
}
return -1;
}
}
}
return 0;
}
int asCBuilder::CheckNameConflict(const char *name, asCScriptNode *node, asCScriptCode *code, asSNameSpace *ns)
{
// Check against registered object types
// TODO: Must check against registered funcdefs too
if( engine->GetRegisteredType(name, ns) != 0 )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_EXTENDED_TYPE, name);
WriteError(str, code, node);
}
return -1;
}
// Check against global properties
if( DoesGlobalPropertyExist(name, ns) )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_GLOBAL_PROPERTY, name);
WriteError(str, code, node);
}
return -1;
}
// TODO: Property names must be checked against function names
#ifndef AS_NO_COMPILER
// Check against class types
asUINT n;
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
if( classDeclarations[n]->name == name &&
classDeclarations[n]->typeInfo->nameSpace == ns )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_STRUCT, name);
WriteError(str, code, node);
}
return -1;
}
}
// Check against named types
for( n = 0; n < namedTypeDeclarations.GetLength(); n++ )
{
if( namedTypeDeclarations[n]->name == name &&
namedTypeDeclarations[n]->typeInfo->nameSpace == ns )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_IS_NAMED_TYPE, name);
WriteError(str, code, node);
}
return -1;
}
}
// Must check for name conflicts with funcdefs
for( n = 0; n < funcDefs.GetLength(); n++ )
{
if( funcDefs[n]->name == name &&
module->funcDefs[funcDefs[n]->idx]->nameSpace == ns )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_IS_FUNCDEF, name);
WriteError(str, code, node);
}
return -1;
}
}
// Check against mixin classes
if( GetMixinClass(name, ns) )
{
if( code )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_IS_MIXIN, name);
WriteError(str, code, node);
}
return -1;
}
#endif
return 0;
}
#ifndef AS_NO_COMPILER
sMixinClass *asCBuilder::GetMixinClass(const char *name, asSNameSpace *ns)
{
for( asUINT n = 0; n < mixinClasses.GetLength(); n++ )
if( mixinClasses[n]->name == name &&
mixinClasses[n]->ns == ns )
return mixinClasses[n];
return 0;
}
int asCBuilder::RegisterFuncDef(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns, asCObjectType *parent)
{
// namespace and parent are exclusively mutual
asASSERT((ns == 0 && parent) || (ns && parent == 0));
// TODO: redesign: Allow funcdefs to be explicitly declared as 'shared'. In this case
// an error should be given if any of the arguments/return type is not
// shared.
// Find the name
asASSERT( node->firstChild->nodeType == snDataType );
asCScriptNode *n = node->firstChild->next->next;
asCString name;
name.Assign(&file->code[n->tokenPos], n->tokenLength);
// Check for name conflict with other types
if (ns)
{
int r = CheckNameConflict(name.AddressOf(), node, file, ns);
if (asSUCCESS != r)
{
node->Destroy(engine);
return r;
}
}
else
{
int r = CheckNameConflictMember(parent, name.AddressOf(), node, file, false);
if (asSUCCESS != r)
{
node->Destroy(engine);
return r;
}
}
// The function definition should be stored as a asCScriptFunction so that the application
// can use the asIScriptFunction interface to enumerate the return type and parameters
// The return type and parameter types aren't determined in this function. A second pass is
// necessary after all type declarations have been identified. The second pass is implemented
// in CompleteFuncDef().
sFuncDef *fd = asNEW(sFuncDef);
if( fd == 0 )
{
node->Destroy(engine);
return asOUT_OF_MEMORY;
}
fd->name = name;
fd->node = node;
fd->script = file;
fd->idx = module->AddFuncDef(name, ns, parent);
funcDefs.PushLast(fd);
return 0;
}
void asCBuilder::CompleteFuncDef(sFuncDef *funcDef)
{
asCArray<asCString *> defaultArgs;
bool isConstMethod;
bool isConstructor;
bool isDestructor;
bool isProtected;
bool isPrivate;
bool isOverride;
bool isFinal;
bool isShared;
asCFuncdefType *fdt = module->funcDefs[funcDef->idx];
asASSERT( fdt );
asCScriptFunction *func = fdt->funcdef;
// TODO: It should be possible to declare funcdef as shared. In this case a compiler error will be given if any of the types it uses are not shared
asSNameSpace *implicitNs = func->nameSpace ? func->nameSpace : fdt->parentClass->nameSpace;
GetParsedFunctionDetails(funcDef->node, funcDef->script, fdt->parentClass, funcDef->name, func->returnType, func->parameterNames, func->parameterTypes, func->inOutFlags, defaultArgs, isConstMethod, isConstructor, isDestructor, isPrivate, isProtected, isOverride, isFinal, isShared, implicitNs);
// There should not be any defaultArgs, but if there are any we need to delete them to avoid leaks
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
// All funcdefs are shared, unless one of the parameter types or return type is not shared
isShared = true;
if( func->returnType.GetTypeInfo() && !func->returnType.GetTypeInfo()->IsShared() )
isShared = false;
for( asUINT n = 0; isShared && n < func->parameterTypes.GetLength(); n++ )
if( func->parameterTypes[n].GetTypeInfo() && !func->parameterTypes[n].GetTypeInfo()->IsShared() )
isShared = false;
func->isShared = isShared;
// Check if there is another identical funcdef from another module and if so reuse that instead
if( func->isShared )
{
for( asUINT n = 0; n < engine->funcDefs.GetLength(); n++ )
{
asCFuncdefType *fdt2 = engine->funcDefs[n];
if( fdt2 == 0 || fdt == fdt2 )
continue;
if( !fdt2->funcdef->isShared )
continue;
if( fdt2->name == fdt->name &&
fdt2->nameSpace == fdt->nameSpace &&
fdt2->funcdef->IsSignatureExceptNameEqual(func) )
{
// Replace our funcdef for the existing one
funcDef->idx = fdt2->funcdef->id;
module->funcDefs[module->funcDefs.IndexOf(fdt)] = fdt2;
fdt2->AddRefInternal();
engine->funcDefs.RemoveValue(fdt);
fdt->ReleaseInternal();
break;
}
}
}
}
int asCBuilder::RegisterGlobalVar(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
// Has the application disabled global vars?
if( engine->ep.disallowGlobalVars )
WriteError(TXT_GLOBAL_VARS_NOT_ALLOWED, file, node);
// What data type is it?
asCDataType type = CreateDataTypeFromNode(node->firstChild, file, ns);
if( !type.CanBeInstantiated() )
{
asCString str;
if( type.IsAbstractClass() )
str.Format(TXT_ABSTRACT_CLASS_s_CANNOT_BE_INSTANTIATED, type.Format(ns).AddressOf());
else if( type.IsInterface() )
str.Format(TXT_INTERFACE_s_CANNOT_BE_INSTANTIATED, type.Format(ns).AddressOf());
else
// TODO: Improve error message to explain why
str.Format(TXT_DATA_TYPE_CANT_BE_s, type.Format(ns).AddressOf());
WriteError(str, file, node);
}
asCScriptNode *n = node->firstChild->next;
while( n )
{
// Verify that the name isn't taken
asCString name(&file->code[n->tokenPos], n->tokenLength);
CheckNameConflict(name.AddressOf(), n, file, ns);
// Register the global variable
sGlobalVariableDescription *gvar = asNEW(sGlobalVariableDescription);
if( gvar == 0 )
{
node->Destroy(engine);
return asOUT_OF_MEMORY;
}
gvar->script = file;
gvar->name = name;
gvar->isCompiled = false;
gvar->datatype = type;
gvar->isEnumValue = false;
gvar->ns = ns;
// TODO: Give error message if wrong
asASSERT(!gvar->datatype.IsReference());
// Allocation is done when the variable is compiled, to allow for autos
gvar->property = 0;
gvar->index = 0;
globVariables.Put(gvar);
gvar->declaredAtNode = n;
n = n->next;
gvar->declaredAtNode->DisconnectParent();
gvar->initializationNode = 0;
if( n &&
( n->nodeType == snAssignment ||
n->nodeType == snArgList ||
n->nodeType == snInitList ) )
{
gvar->initializationNode = n;
n = n->next;
gvar->initializationNode->DisconnectParent();
}
}
node->Destroy(engine);
return 0;
}
int asCBuilder::RegisterMixinClass(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
asCScriptNode *cl = node->firstChild;
asASSERT( cl->nodeType == snClass );
asCScriptNode *n = cl->firstChild;
// Skip potential 'final' and 'shared' tokens
while( n->tokenType == ttIdentifier &&
(file->TokenEquals(n->tokenPos, n->tokenLength, FINAL_TOKEN) ||
file->TokenEquals(n->tokenPos, n->tokenLength, SHARED_TOKEN)) )
{
// Report error, because mixin class cannot be final or shared
asCString msg;
msg.Format(TXT_MIXIN_CANNOT_BE_DECLARED_AS_s, asCString(&file->code[n->tokenPos], n->tokenLength).AddressOf());
WriteError(msg, file, n);
asCScriptNode *tmp = n;
n = n->next;
// Remove the invalid node, so compilation can continue as if it wasn't there
tmp->DisconnectParent();
tmp->Destroy(engine);
}
asCString name(&file->code[n->tokenPos], n->tokenLength);
int r, c;
file->ConvertPosToRowCol(n->tokenPos, &r, &c);
CheckNameConflict(name.AddressOf(), n, file, ns);
sMixinClass *decl = asNEW(sMixinClass);
if( decl == 0 )
{
node->Destroy(engine);
return asOUT_OF_MEMORY;
}
mixinClasses.PushLast(decl);
decl->name = name;
decl->ns = ns;
decl->node = cl;
decl->script = file;
// Clean up memory
cl->DisconnectParent();
node->Destroy(engine);
// Check that the mixin class doesn't contain any child types
// TODO: Add support for child types in mixin classes
n = cl->firstChild;
while (n)
{
if (n->nodeType == snFuncDef)
{
WriteError(TXT_MIXIN_CANNOT_HAVE_CHILD_TYPES, file, n);
break;
}
n = n->next;
}
return 0;
}
int asCBuilder::RegisterClass(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
asCScriptNode *n = node->firstChild;
bool isFinal = false;
bool isTidy = false;
bool isShared = false;
bool isAbstract = false;
// Check the class modifiers
while( n->tokenType == ttIdentifier )
{
if( file->TokenEquals(n->tokenPos, n->tokenLength, FINAL_TOKEN) )
{
if( isAbstract )
WriteError(TXT_CLASS_CANT_BE_FINAL_AND_ABSTRACT, file, n);
else
{
if( isFinal )
{
asCString msg;
msg.Format(TXT_ATTR_s_INFORMED_MULTIPLE_TIMES, asCString(&file->code[n->tokenPos], n->tokenLength).AddressOf());
WriteWarning(msg, file, n);
}
isFinal = true;
}
}
else if( file->TokenEquals(n->tokenPos, n->tokenLength, SHARED_TOKEN) )
{
if( isShared )
{
asCString msg;
msg.Format(TXT_ATTR_s_INFORMED_MULTIPLE_TIMES, asCString(&file->code[n->tokenPos], n->tokenLength).AddressOf());
WriteWarning(msg, file, n);
}
isShared = true;
}
else if( file->TokenEquals(n->tokenPos, n->tokenLength, ABSTRACT_TOKEN) )
{
if( isFinal )
WriteError(TXT_CLASS_CANT_BE_FINAL_AND_ABSTRACT, file, n);
else
{
if( isAbstract )
{
asCString msg;
msg.Format(TXT_ATTR_s_INFORMED_MULTIPLE_TIMES, asCString(&file->code[n->tokenPos], n->tokenLength).AddressOf());
WriteWarning(msg, file, n);
}
isAbstract = true;
}
}
else if( file->TokenEquals(n->tokenPos, n->tokenLength, TIDY_TOKEN) )
{
isTidy = true;
}
else
{
// This is the name of the class
break;
}
n = n->next;
}
asCString name(&file->code[n->tokenPos], n->tokenLength);
int r, c;
file->ConvertPosToRowCol(n->tokenPos, &r, &c);
CheckNameConflict(name.AddressOf(), n, file, ns);
sClassDeclaration *decl = asNEW(sClassDeclaration);
if( decl == 0 )
{
node->Destroy(engine);
return asOUT_OF_MEMORY;
}
classDeclarations.PushLast(decl);
decl->name = name;
decl->script = file;
decl->node = node;
decl->isTidy = isTidy;
asCObjectType *st = 0;
// If this type is shared and there already exist another shared
// type of the same name, then that one should be used instead of
// creating a new one.
if( isShared )
{
for( asUINT i = 0; i < engine->sharedScriptTypes.GetLength(); i++ )
{
st = engine->sharedScriptTypes[i]->CastToObjectType();
if( st &&
st->IsShared() &&
st->name == name &&
st->nameSpace == ns &&
!st->IsInterface() )
{
// We'll use the existing type
decl->isExistingShared = true;
decl->typeInfo = st;
module->classTypes.PushLast(st);
st->AddRefInternal();
break;
}
}
}
if (!decl->isExistingShared)
{
// Create a new object type for this class
st = asNEW(asCObjectType)(engine);
if (st == 0)
return asOUT_OF_MEMORY;
// By default all script classes are marked as garbage collected.
// Only after the complete structure and relationship between classes
// is known, can the flag be cleared for those objects that truly cannot
// form circular references. This is important because a template
// callback may be called with a script class before the compilation
// completes, and until it is known, the callback must assume the class
// is garbage collected.
st->flags = asOBJ_REF | asOBJ_SCRIPT_OBJECT | asOBJ_GC;
if (isShared)
st->flags |= asOBJ_SHARED;
if (isFinal)
st->flags |= asOBJ_NOINHERIT;
if (isAbstract)
st->flags |= asOBJ_ABSTRACT;
if (node->tokenType == ttHandle)
st->flags |= asOBJ_IMPLICIT_HANDLE;
st->size = sizeof(asCScriptObject);
st->name = name;
st->nameSpace = ns;
st->module = module;
module->classTypes.PushLast(st);
if (isShared)
{
engine->sharedScriptTypes.PushLast(st);
st->AddRefInternal();
}
decl->typeInfo = st;
// Use the default script class behaviours
st->beh = engine->scriptTypeBehaviours.beh;
// TODO: Move this to asCObjectType so that the asCRestore can reuse it
engine->scriptFunctions[st->beh.addref]->AddRefInternal();
engine->scriptFunctions[st->beh.release]->AddRefInternal();
engine->scriptFunctions[st->beh.gcEnumReferences]->AddRefInternal();
engine->scriptFunctions[st->beh.gcGetFlag]->AddRefInternal();
engine->scriptFunctions[st->beh.gcGetRefCount]->AddRefInternal();
engine->scriptFunctions[st->beh.gcReleaseAllReferences]->AddRefInternal();
engine->scriptFunctions[st->beh.gcSetFlag]->AddRefInternal();
engine->scriptFunctions[st->beh.copy]->AddRefInternal();
engine->scriptFunctions[st->beh.factory]->AddRefInternal();
engine->scriptFunctions[st->beh.construct]->AddRefInternal();
// TODO: weak: Should not do this if the class has been declared with noweak
engine->scriptFunctions[st->beh.getWeakRefFlag]->AddRefInternal();
// Skip to the content of the class
while (n && n->nodeType == snIdentifier)
n = n->next;
}
// Register possible child types
while (n)
{
node = n->next;
if (n->nodeType == snFuncDef)
{
n->DisconnectParent();
if (!decl->isExistingShared)
RegisterFuncDef(n, file, 0, st);
else
{
// Destroy the node, since it won't be used
// TODO: Should verify that the funcdef is identical to the one in the existing shared class
n->Destroy(engine);
}
}
n = node;
}
return 0;
}
int asCBuilder::RegisterInterface(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
asCScriptNode *n = node->firstChild;
asCString name(&file->code[n->tokenPos], n->tokenLength);
bool isShared = false;
if( name == SHARED_TOKEN )
{
isShared = true;
n = n->next;
name.Assign(&file->code[n->tokenPos], n->tokenLength);
}
int r, c;
file->ConvertPosToRowCol(n->tokenPos, &r, &c);
CheckNameConflict(name.AddressOf(), n, file, ns);
sClassDeclaration *decl = asNEW(sClassDeclaration);
if( decl == 0 )
{
node->Destroy(engine);
return asOUT_OF_MEMORY;
}
interfaceDeclarations.PushLast(decl);
decl->name = name;
decl->script = file;
decl->node = node;
// If this type is shared and there already exist another shared
// type of the same name, then that one should be used instead of
// creating a new one.
if( isShared )
{
for( asUINT i = 0; i < engine->sharedScriptTypes.GetLength(); i++ )
{
asCObjectType *st = engine->sharedScriptTypes[i]->CastToObjectType();
if( st &&
st->IsShared() &&
st->name == name &&
st->nameSpace == ns &&
st->IsInterface() )
{
// We'll use the existing type
decl->isExistingShared = true;
decl->typeInfo = st;
module->classTypes.PushLast(st);
st->AddRefInternal();
return 0;
}
}
}
// Register the object type for the interface
asCObjectType *st = asNEW(asCObjectType)(engine);
if( st == 0 )
return asOUT_OF_MEMORY;
st->flags = asOBJ_REF | asOBJ_SCRIPT_OBJECT;
if( isShared )
st->flags |= asOBJ_SHARED;
st->size = 0; // Cannot be instantiated
st->name = name;
st->nameSpace = ns;
st->module = module;
module->classTypes.PushLast(st);
if( isShared )
{
engine->sharedScriptTypes.PushLast(st);
st->AddRefInternal();
}
decl->typeInfo = st;
// Use the default script class behaviours
st->beh.construct = 0;
st->beh.addref = engine->scriptTypeBehaviours.beh.addref;
engine->scriptFunctions[st->beh.addref]->AddRefInternal();
st->beh.release = engine->scriptTypeBehaviours.beh.release;
engine->scriptFunctions[st->beh.release]->AddRefInternal();
st->beh.copy = 0;
return 0;
}
void asCBuilder::CompileGlobalVariables()
{
bool compileSucceeded = true;
// Store state of compilation (errors, warning, output)
int currNumErrors = numErrors;
int currNumWarnings = numWarnings;
// Backup the original message stream
bool msgCallback = engine->msgCallback;
asSSystemFunctionInterface msgCallbackFunc = engine->msgCallbackFunc;
void *msgCallbackObj = engine->msgCallbackObj;
// Set the new temporary message stream
asCOutputBuffer outBuffer;
engine->SetMessageCallback(asMETHOD(asCOutputBuffer, Callback), &outBuffer, asCALL_THISCALL);
asCOutputBuffer finalOutput;
asCScriptFunction *initFunc = 0;
asCSymbolTable<asCGlobalProperty> initOrder;
// We first try to compile all the primitive global variables, and only after that
// compile the non-primitive global variables. This permits the constructors
// for the complex types to use the already initialized variables of primitive
// type. Note, we currently don't know which global variables are used in the
// constructors, so we cannot guarantee that variables of complex types are
// initialized in the correct order, so we won't reorder those.
bool compilingPrimitives = true;
// Compile each global variable
while( compileSucceeded )
{
compileSucceeded = false;
int accumErrors = 0;
int accumWarnings = 0;
// Restore state of compilation
finalOutput.Clear();
asCSymbolTable<sGlobalVariableDescription>::iterator it = globVariables.List();
for( ; it; it++ )
{
sGlobalVariableDescription *gvar = *it;
if( gvar->isCompiled )
continue;
asCByteCode init(engine);
numWarnings = 0;
numErrors = 0;
outBuffer.Clear();
// Skip this for now if we're not compiling complex types yet
if( compilingPrimitives && !gvar->datatype.IsPrimitive() )
continue;
if( gvar->declaredAtNode )
{
int r, c;
gvar->script->ConvertPosToRowCol(gvar->declaredAtNode->tokenPos, &r, &c);
asCString str = gvar->datatype.Format(gvar->ns);
str += " " + gvar->name;
str.Format(TXT_COMPILING_s, str.AddressOf());
WriteInfo(gvar->script->name, str, r, c, true);
}
if( gvar->isEnumValue )
{
int r;
if( gvar->initializationNode )
{
asCCompiler comp(engine);
asCScriptFunction func(engine, module, asFUNC_SCRIPT);
// Set the namespace that should be used during the compilation
func.nameSpace = gvar->datatype.GetTypeInfo()->nameSpace;
// Temporarily switch the type of the variable to int so it can be compiled properly
asCDataType saveType;
saveType = gvar->datatype;
gvar->datatype = asCDataType::CreatePrimitive(ttInt, true);
r = comp.CompileGlobalVariable(this, gvar->script, gvar->initializationNode, gvar, &func);
gvar->datatype = saveType;
// Make the function a dummy so it doesn't try to release objects while destroying the function
func.funcType = asFUNC_DUMMY;
}
else
{
r = 0;
// When there is no assignment the value is the last + 1
int enumVal = 0;
asCSymbolTable<sGlobalVariableDescription>::iterator prev_it = it;
prev_it--;
if( prev_it )
{
sGlobalVariableDescription *gvar2 = *prev_it;
if(gvar2->datatype == gvar->datatype )
{
// The integer value is stored in the lower bytes
enumVal = (*(int*)&gvar2->constantValue) + 1;
if( !gvar2->isCompiled )
{
int row, col;
gvar->script->ConvertPosToRowCol(gvar->declaredAtNode->tokenPos, &row, &col);
asCString str = gvar->datatype.Format(gvar->ns);
str += " " + gvar->name;
str.Format(TXT_COMPILING_s, str.AddressOf());
WriteInfo(gvar->script->name, str, row, col, true);
str.Format(TXT_UNINITIALIZED_GLOBAL_VAR_s, gvar2->name.AddressOf());
WriteError(gvar->script->name, str, row, col);
r = -1;
}
}
}
// The integer value is stored in the lower bytes
*(int*)&gvar->constantValue = enumVal;
}
if( r >= 0 )
{
// Set the value as compiled
gvar->isCompiled = true;
compileSucceeded = true;
}
}
else
{
// Compile the global variable
initFunc = asNEW(asCScriptFunction)(engine, module, asFUNC_SCRIPT);
if( initFunc == 0 )
{
// Out of memory
return;
}
// Set the namespace that should be used for this function
initFunc->nameSpace = gvar->ns;
asCCompiler comp(engine);
int r = comp.CompileGlobalVariable(this, gvar->script, gvar->initializationNode, gvar, initFunc);
if( r >= 0 )
{
// Compilation succeeded
gvar->isCompiled = true;
compileSucceeded = true;
}
else
{
// Compilation failed
initFunc->funcType = asFUNC_DUMMY;
asDELETE(initFunc, asCScriptFunction);
initFunc = 0;
}
}
if( gvar->isCompiled )
{
// Add warnings for this constant to the total build
if( numWarnings )
{
currNumWarnings += numWarnings;
if( msgCallback )
outBuffer.SendToCallback(engine, &msgCallbackFunc, msgCallbackObj);
}
// Determine order of variable initializations
if( gvar->property && !gvar->isEnumValue )
initOrder.Put(gvar->property);
// Does the function contain more than just a SUSPEND followed by a RET instruction?
if( initFunc && initFunc->scriptData->byteCode.GetLength() > 2 )
{
// Create the init function for this variable
initFunc->id = engine->GetNextScriptFunctionId();
engine->AddScriptFunction(initFunc);
// Finalize the init function for this variable
initFunc->returnType = asCDataType::CreatePrimitive(ttVoid, false);
initFunc->scriptData->scriptSectionIdx = engine->GetScriptSectionNameIndex(gvar->script->name.AddressOf());
if( gvar->declaredAtNode )
{
int row, col;
gvar->script->ConvertPosToRowCol(gvar->declaredAtNode->tokenPos, &row, &col);
initFunc->scriptData->declaredAt = (row & 0xFFFFF)|((col & 0xFFF)<<20);
}
gvar->property->SetInitFunc(initFunc);
initFunc->ReleaseInternal();
initFunc = 0;
}
else if( initFunc )
{
// Destroy the function as it won't be used
initFunc->funcType = asFUNC_DUMMY;
asDELETE(initFunc, asCScriptFunction);
initFunc = 0;
}
// Convert enums to true enum values, so subsequent compilations can access it as an enum
if( gvar->isEnumValue )
{
asCEnumType *enumType = gvar->datatype.GetTypeInfo()->CastToEnumType();
asASSERT(NULL != enumType);
asSEnumValue *e = asNEW(asSEnumValue);
if( e == 0 )
{
// Out of memory
numErrors++;
return;
}
e->name = gvar->name;
e->value = *(int*)&gvar->constantValue;
enumType->enumValues.PushLast(e);
}
}
else
{
// Add output to final output
finalOutput.Append(outBuffer);
accumErrors += numErrors;
accumWarnings += numWarnings;
}
engine->preMessage.isSet = false;
}
if( !compileSucceeded )
{
if( compilingPrimitives )
{
// No more primitives could be compiled, so
// switch to compiling the complex variables
compilingPrimitives = false;
compileSucceeded = true;
}
else
{
// No more variables can be compiled
// Add errors and warnings to total build
currNumWarnings += accumWarnings;
currNumErrors += accumErrors;
if( msgCallback )
finalOutput.SendToCallback(engine, &msgCallbackFunc, msgCallbackObj);
}
}
}
// Restore states
engine->msgCallback = msgCallback;
engine->msgCallbackFunc = msgCallbackFunc;
engine->msgCallbackObj = msgCallbackObj;
numWarnings = currNumWarnings;
numErrors = currNumErrors;
// Set the correct order of initialization
if( numErrors == 0 )
{
// If the length of the arrays are not the same, then this is the compilation
// of a single variable, in which case the initialization order of the previous
// variables must be preserved.
if( module->scriptGlobals.GetSize() == initOrder.GetSize() )
module->scriptGlobals.SwapWith(initOrder);
}
// Delete the enum expressions
asCSymbolTableIterator<sGlobalVariableDescription> it = globVariables.List();
while( it )
{
sGlobalVariableDescription *gvar = *it;
if( gvar->isEnumValue )
{
// Remove from symboltable. This has to be done prior to freeing the memeory
globVariables.Erase(it.GetIndex());
// Destroy the gvar property
if( gvar->declaredAtNode )
{
gvar->declaredAtNode->Destroy(engine);
gvar->declaredAtNode = 0;
}
if( gvar->initializationNode )
{
gvar->initializationNode->Destroy(engine);
gvar->initializationNode = 0;
}
if( gvar->property )
{
asDELETE(gvar->property, asCGlobalProperty);
gvar->property = 0;
}
asDELETE(gvar, sGlobalVariableDescription);
}
else
it++;
}
}
int asCBuilder::GetNamespaceAndNameFromNode(asCScriptNode *n, asCScriptCode *script, asSNameSpace *implicitNs, asSNameSpace *&outNs, asCString &outName)
{
// TODO: child funcdef: The node might be a snScope now
asASSERT( n->nodeType == snIdentifier );
// Get the optional scope from the node
// TODO: child funcdef: The parentType will be set if the scope is actually a type rather than a namespace
asSNameSpace *ns = GetNameSpaceFromNode(n->firstChild, script, implicitNs, 0);
if( ns == 0 )
return -1;
// Get the name
asCString name(&script->code[n->lastChild->tokenPos], n->lastChild->tokenLength);
outNs = ns;
outName = name;
return 0;
}
void asCBuilder::AddInterfaceFromMixinToClass(sClassDeclaration *decl, asCScriptNode *errNode, sMixinClass *mixin)
{
// Determine what interfaces that the mixin implements
asCScriptNode *node = mixin->node;
asASSERT(node->nodeType == snClass);
// Skip the name of the mixin
node = node->firstChild->next;
while( node && node->nodeType == snIdentifier )
{
bool ok = true;
asSNameSpace *ns;
asCString name;
if( GetNamespaceAndNameFromNode(node, mixin->script, mixin->ns, ns, name) < 0 )
ok = false;
else
{
// Find the object type for the interface
asCObjectType *objType = GetObjectType(name.AddressOf(), ns);
// Check that the object type is an interface
if( objType && objType->IsInterface() )
{
// Only add the interface if the class doesn't already implement it
if( !decl->typeInfo->Implements(objType) )
AddInterfaceToClass(decl, errNode, objType);
}
else
{
WriteError(TXT_MIXIN_CLASS_CANNOT_INHERIT, mixin->script, node);
ok = false;
}
}
if( !ok )
{
// Remove this node so the error isn't reported again
asCScriptNode *delNode = node;
node = node->prev;
delNode->DisconnectParent();
delNode->Destroy(engine);
}
node = node->next;
}
}
void asCBuilder::AddInterfaceToClass(sClassDeclaration *decl, asCScriptNode *errNode, asCObjectType *intfType)
{
// A shared type may only implement from shared interfaces
if( decl->typeInfo->IsShared() && !intfType->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_IMPLEMENT_NON_SHARED_s, intfType->name.AddressOf());
WriteError(msg, decl->script, errNode);
return;
}
if( decl->isExistingShared )
{
// If the class is an existing shared class, then just check if the
// interface exists in the original declaration too
if( !decl->typeInfo->Implements(intfType) )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, decl->typeInfo->GetName());
WriteError(str, decl->script, errNode);
return;
}
}
else
{
// If the interface is already in the class then don't add it again
if( decl->typeInfo->Implements(intfType) )
return;
// Add the interface to the class
decl->typeInfo->CastToObjectType()->interfaces.PushLast(intfType);
// Add the inherited interfaces too
// For interfaces this will be done outside to handle out-of-order declarations
if( !decl->typeInfo->CastToObjectType()->IsInterface() )
{
for( asUINT n = 0; n < intfType->interfaces.GetLength(); n++ )
AddInterfaceToClass(decl, errNode, intfType->interfaces[n]);
}
}
}
void asCBuilder::CompileInterfaces()
{
asUINT n;
// Order the interfaces with inheritances so that the inherited
// of inherited interfaces can be added properly
for( n = 0; n < interfaceDeclarations.GetLength(); n++ )
{
sClassDeclaration *intfDecl = interfaceDeclarations[n];
asCObjectType *intfType = intfDecl->typeInfo->CastToObjectType();
if( intfType->interfaces.GetLength() == 0 ) continue;
// If any of the derived interfaces are found after this interface, then move this to the end of the list
for( asUINT m = n+1; m < interfaceDeclarations.GetLength(); m++ )
{
if( intfType->Implements(interfaceDeclarations[m]->typeInfo) )
{
interfaceDeclarations.RemoveIndex(n);
interfaceDeclarations.PushLast(intfDecl);
// Decrease index so that we don't skip an entry
n--;
break;
}
}
}
// Now recursively add the additional inherited interfaces
for( n = 0; n < interfaceDeclarations.GetLength(); n++ )
{
sClassDeclaration *intfDecl = interfaceDeclarations[n];
asCObjectType *intfType = intfDecl->typeInfo->CastToObjectType();
// TODO: Is this really at the correct place? Hasn't the vfTableIdx already been set here?
// Co-opt the vfTableIdx value in our own methods to indicate the
// index the function should have in the table chunk for this interface.
for( asUINT d = 0; d < intfType->methods.GetLength(); d++ )
{
asCScriptFunction *func = GetFunctionDescription(intfType->methods[d]);
func->vfTableIdx = d;
asASSERT(func->objectType == intfType);
}
// As new interfaces will be added to the end of the list, all
// interfaces will be traversed the same as recursively
for( asUINT m = 0; m < intfType->interfaces.GetLength(); m++ )
{
asCObjectType *base = intfType->interfaces[m];
// Add any interfaces not already implemented
for( asUINT l = 0; l < base->interfaces.GetLength(); l++ )
AddInterfaceToClass(intfDecl, intfDecl->node, base->interfaces[l]);
// Add the methods from the implemented interface
for( asUINT l = 0; l < base->methods.GetLength(); l++ )
{
// If the derived interface implements the same method, then don't add the base interface' method
asCScriptFunction *baseFunc = GetFunctionDescription(base->methods[l]);
asCScriptFunction *derivedFunc = 0;
bool found = false;
for( asUINT d = 0; d < intfType->methods.GetLength(); d++ )
{
derivedFunc = GetFunctionDescription(intfType->methods[d]);
if( derivedFunc->IsSignatureEqual(baseFunc) )
{
found = true;
break;
}
}
if( !found )
{
// Add the method
intfType->methods.PushLast(baseFunc->id);
baseFunc->AddRefInternal();
}
}
}
}
}
void asCBuilder::DetermineTypeRelations()
{
// Determine inheritance between interfaces
for (asUINT n = 0; n < interfaceDeclarations.GetLength(); n++)
{
sClassDeclaration *intfDecl = interfaceDeclarations[n];
asCObjectType *intfType = intfDecl->typeInfo->CastToObjectType();
asCScriptNode *node = intfDecl->node;
asASSERT(node && node->nodeType == snInterface);
node = node->firstChild;
// Skip the 'shared' keyword
if (intfType->IsShared())
node = node->next;
// Skip the name
node = node->next;
// Verify the inherited interfaces
while (node && node->nodeType == snIdentifier)
{
asSNameSpace *ns;
asCString name;
if (GetNamespaceAndNameFromNode(node, intfDecl->script, intfType->nameSpace, ns, name) < 0)
{
node = node->next;
continue;
}
// Find the object type for the interface
asCObjectType *objType = 0;
while (ns)
{
objType = GetObjectType(name.AddressOf(), ns);
if (objType) break;
ns = engine->GetParentNameSpace(ns);
}
// Check that the object type is an interface
bool ok = true;
if (objType && objType->IsInterface())
{
// Check that the implemented interface is shared if the base interface is shared
if (intfType->IsShared() && !objType->IsShared())
{
asCString str;
str.Format(TXT_SHARED_CANNOT_IMPLEMENT_NON_SHARED_s, objType->GetName());
WriteError(str, intfDecl->script, node);
ok = false;
}
}
else
{
WriteError(TXT_INTERFACE_CAN_ONLY_IMPLEMENT_INTERFACE, intfDecl->script, node);
ok = false;
}
if (ok)
{
// Make sure none of the implemented interfaces implement from this one
asCObjectType *base = objType;
while (base != 0)
{
if (base == intfType)
{
WriteError(TXT_CANNOT_IMPLEMENT_SELF, intfDecl->script, node);
ok = false;
break;
}
// At this point there is at most one implemented interface
if (base->interfaces.GetLength())
base = base->interfaces[0];
else
break;
}
}
if (ok)
AddInterfaceToClass(intfDecl, node, objType);
// Remove the nodes so they aren't parsed again
asCScriptNode *delNode = node;
node = node->next;
delNode->DisconnectParent();
delNode->Destroy(engine);
}
}
// Determine class inheritances and interfaces
for (asUINT n = 0; n < classDeclarations.GetLength(); n++)
{
sClassDeclaration *decl = classDeclarations[n];
asCScriptCode *file = decl->script;
// Find the base class that this class inherits from
bool multipleInheritance = false;
asCScriptNode *node = decl->node->firstChild;
while (file->TokenEquals(node->tokenPos, node->tokenLength, FINAL_TOKEN) ||
file->TokenEquals(node->tokenPos, node->tokenLength, SHARED_TOKEN) ||
file->TokenEquals(node->tokenPos, node->tokenLength, TIDY_TOKEN) ||
file->TokenEquals(node->tokenPos, node->tokenLength, ABSTRACT_TOKEN))
{
node = node->next;
}
// Skip the name of the class
asASSERT(node->tokenType == ttIdentifier);
node = node->next;
while (node && node->nodeType == snIdentifier)
{
asSNameSpace *ns;
asCString name;
if (GetNamespaceAndNameFromNode(node, file, decl->typeInfo->nameSpace, ns, name) < 0)
{
node = node->next;
continue;
}
// Find the object type for the interface
asCObjectType *objType = 0;
sMixinClass *mixin = 0;
asSNameSpace *origNs = ns;
while (ns)
{
objType = GetObjectType(name.AddressOf(), ns);
if (objType == 0)
mixin = GetMixinClass(name.AddressOf(), ns);
if (objType || mixin)
break;
ns = engine->GetParentNameSpace(ns);
}
if (objType == 0 && mixin == 0)
{
asCString str;
if (origNs->name == "")
str.Format(TXT_IDENTIFIER_s_NOT_DATA_TYPE_IN_GLOBAL_NS, name.AddressOf());
else
str.Format(TXT_IDENTIFIER_s_NOT_DATA_TYPE_IN_NS_s, name.AddressOf(), origNs->name.AddressOf());
WriteError(str, file, node);
}
else if (mixin)
{
AddInterfaceFromMixinToClass(decl, node, mixin);
}
else if (!(objType->flags & asOBJ_SCRIPT_OBJECT) ||
(objType->flags & asOBJ_NOINHERIT))
{
// Either the class is not a script class or interface
// or the class has been declared as 'final'
asCString str;
str.Format(TXT_CANNOT_INHERIT_FROM_s_FINAL, objType->name.AddressOf());
WriteError(str, file, node);
}
else if (objType->size != 0)
{
// The class inherits from another script class
if (!decl->isExistingShared && decl->typeInfo->CastToObjectType()->derivedFrom != 0)
{
if (!multipleInheritance)
{
WriteError(TXT_CANNOT_INHERIT_FROM_MULTIPLE_CLASSES, file, node);
multipleInheritance = true;
}
}
else
{
// Make sure none of the base classes inherit from this one
asCObjectType *base = objType;
bool error = false;
while (base != 0)
{
if (base == decl->typeInfo)
{
WriteError(TXT_CANNOT_INHERIT_FROM_SELF, file, node);
error = true;
break;
}
base = base->derivedFrom;
}
if (!error)
{
// A shared type may only inherit from other shared types
if ((decl->typeInfo->IsShared()) && !(objType->IsShared()))
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_INHERIT_FROM_NON_SHARED_s, objType->name.AddressOf());
WriteError(msg, file, node);
error = true;
}
}
if (!error)
{
if (decl->isExistingShared)
{
// Verify that the base class is the same as the original shared type
if (decl->typeInfo->CastToObjectType()->derivedFrom != objType)
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, decl->typeInfo->GetName());
WriteError(str, file, node);
}
}
else
{
// Set the base class
decl->typeInfo->CastToObjectType()->derivedFrom = objType;
objType->AddRefInternal();
}
}
}
}
else
{
// The class implements an interface
AddInterfaceToClass(decl, node, objType);
}
node = node->next;
}
}
}
// numTempl is the number of template instances that existed in the engine before the build begun
void asCBuilder::CompileClasses(asUINT numTempl)
{
asUINT n;
asCArray<sClassDeclaration*> toValidate((int)classDeclarations.GetLength());
// Order class declarations so that base classes are compiled before derived classes.
// This will allow the derived classes to copy properties and methods in the next step.
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
sClassDeclaration *decl = classDeclarations[n];
asCObjectType *derived = decl->typeInfo->CastToObjectType();
asCObjectType *base = derived->derivedFrom;
if( base == 0 ) continue;
// If the base class is found after the derived class, then move the derived class to the end of the list
for( asUINT m = n+1; m < classDeclarations.GetLength(); m++ )
{
sClassDeclaration *declBase = classDeclarations[m];
if( base == declBase->typeInfo )
{
classDeclarations.RemoveIndex(n);
classDeclarations.PushLast(decl);
// Decrease index so that we don't skip an entry
n--;
break;
}
}
}
// Go through each of the classes and register the object type descriptions
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
sClassDeclaration *decl = classDeclarations[n];
asCObjectType *ot = decl->typeInfo->CastToObjectType();
if( decl->isExistingShared )
{
// Set the declaration as validated already, so that other
// types that contain this will accept this type
decl->validState = 1;
// We'll still validate the declaration to make sure nothing new is
// added to the shared class that wasn't there in the previous
// compilation. We do not care if something that is there in the previous
// declaration is not included in the new declaration though.
asASSERT( ot->interfaces.GetLength() == ot->interfaceVFTOffsets.GetLength() );
}
// Methods included from mixin classes should take precedence over inherited methods
IncludeMethodsFromMixins(decl);
// Add all properties and methods from the base class
if( !decl->isExistingShared && ot->derivedFrom )
{
asCObjectType *baseType = ot->derivedFrom;
// The derived class inherits all interfaces from the base class
for( unsigned int m = 0; m < baseType->interfaces.GetLength(); m++ )
{
if( !ot->Implements(baseType->interfaces[m]) )
ot->interfaces.PushLast(baseType->interfaces[m]);
}
// TODO: Need to check for name conflict with new class methods
// Copy properties from base class to derived class
for( asUINT p = 0; p < baseType->properties.GetLength(); p++ )
{
asCObjectProperty *prop = AddPropertyToClass(decl, baseType->properties[p]->name, baseType->properties[p]->type, baseType->properties[p]->isPrivate, baseType->properties[p]->isProtected, true);
// The properties must maintain the same offset
asASSERT(prop && prop->byteOffset == baseType->properties[p]->byteOffset); UNUSED_VAR(prop);
}
// Copy methods from base class to derived class
for( asUINT m = 0; m < baseType->methods.GetLength(); m++ )
{
// If the derived class implements the same method, then don't add the base class' method
asCScriptFunction *baseFunc = GetFunctionDescription(baseType->methods[m]);
asCScriptFunction *derivedFunc = 0;
bool found = false;
for( asUINT d = 0; d < ot->methods.GetLength(); d++ )
{
derivedFunc = GetFunctionDescription(ot->methods[d]);
if( baseFunc->name == "opConv" || baseFunc->name == "opImplConv" ||
baseFunc->name == "opCast" || baseFunc->name == "opImplCast" )
{
// For the opConv and opCast methods, the return type can differ if they are different methods
if( derivedFunc->name == baseFunc->name &&
derivedFunc->IsSignatureExceptNameEqual(baseFunc) )
{
if( baseFunc->IsFinal() )
{
asCString msg;
msg.Format(TXT_METHOD_CANNOT_OVERRIDE_s, baseFunc->GetDeclaration());
WriteError(msg, decl->script, decl->node);
}
// Move the function from the methods array to the virtualFunctionTable
ot->methods.RemoveIndex(d);
ot->virtualFunctionTable.PushLast(derivedFunc);
found = true;
break;
}
}
else
{
if( derivedFunc->name == baseFunc->name &&
derivedFunc->IsSignatureExceptNameAndReturnTypeEqual(baseFunc) )
{
if( baseFunc->returnType != derivedFunc->returnType )
{
asCString msg;
msg.Format(TXT_DERIVED_METHOD_MUST_HAVE_SAME_RETTYPE_s, baseFunc->GetDeclaration());
WriteError(msg, decl->script, decl->node);
}
if( baseFunc->IsFinal() )
{
asCString msg;
msg.Format(TXT_METHOD_CANNOT_OVERRIDE_s, baseFunc->GetDeclaration());
WriteError(msg, decl->script, decl->node);
}
// Move the function from the methods array to the virtualFunctionTable
ot->methods.RemoveIndex(d);
ot->virtualFunctionTable.PushLast(derivedFunc);
found = true;
break;
}
}
}
if( !found )
{
// Push the base class function on the virtual function table
ot->virtualFunctionTable.PushLast(baseType->virtualFunctionTable[m]);
baseType->virtualFunctionTable[m]->AddRefInternal();
CheckForConflictsDueToDefaultArgs(decl->script, decl->node, baseType->virtualFunctionTable[m], ot);
}
ot->methods.PushLast(baseType->methods[m]);
engine->scriptFunctions[baseType->methods[m]]->AddRefInternal();
}
}
if( !decl->isExistingShared )
{
// Move this class' methods into the virtual function table
for( asUINT m = 0; m < ot->methods.GetLength(); m++ )
{
asCScriptFunction *func = GetFunctionDescription(ot->methods[m]);
if( func->funcType != asFUNC_VIRTUAL )
{
// Move the reference from the method list to the virtual function list
ot->methods.RemoveIndex(m);
ot->virtualFunctionTable.PushLast(func);
// Substitute the function description in the method list for a virtual method
// Make sure the methods are in the same order as the virtual function table
ot->methods.PushLast(CreateVirtualFunction(func, (int)ot->virtualFunctionTable.GetLength() - 1));
m--;
}
}
// Make virtual function table chunks for each implemented interface
for( asUINT m = 0; m < ot->interfaces.GetLength(); m++ )
{
asCObjectType *intf = ot->interfaces[m];
// Add all the interface's functions to the virtual function table
asUINT offset = asUINT(ot->virtualFunctionTable.GetLength());
ot->interfaceVFTOffsets.PushLast(offset);
for( asUINT j = 0; j < intf->methods.GetLength(); j++ )
{
asCScriptFunction *intfFunc = GetFunctionDescription(intf->methods[j]);
// Only create the table for functions that are explicitly from this interface,
// inherited interface methods will be put in that interface's table.
if( intfFunc->objectType != intf )
continue;
asASSERT((asUINT)intfFunc->vfTableIdx == j);
//Find the interface function in the list of methods
asCScriptFunction *realFunc = 0;
for( asUINT p = 0; p < ot->methods.GetLength(); p++ )
{
asCScriptFunction *func = GetFunctionDescription(ot->methods[p]);
if( func->signatureId == intfFunc->signatureId )
{
if( func->funcType == asFUNC_VIRTUAL )
{
realFunc = ot->virtualFunctionTable[func->vfTableIdx];
}
else
{
// This should not happen, all methods were moved into the virtual table
asASSERT(false);
}
break;
}
}
// If realFunc is still null, the interface was not
// implemented and we error out later in the checks.
ot->virtualFunctionTable.PushLast(realFunc);
if( realFunc )
realFunc->AddRefInternal();
}
}
}
// Enumerate each of the declared properties
asCScriptNode *node = decl->node->firstChild->next;
// Skip list of classes and interfaces
while( node && node->nodeType == snIdentifier )
node = node->next;
while( node )
{
if( node->nodeType == snDeclaration )
{
asCScriptNode *nd = node->firstChild;
// Is the property declared as private or protected?
bool isPrivate = false, isProtected = false;
if( nd && nd->tokenType == ttPrivate )
{
isPrivate = true;
nd = nd->next;
}
else if( nd && nd->tokenType == ttProtected )
{
isProtected = true;
nd = nd->next;
}
// Determine the type of the property
asCScriptCode *file = decl->script;
asCDataType dt = CreateDataTypeFromNode(nd, file, ot->nameSpace, false, ot);
if( ot->IsShared() && dt.GetTypeInfo() && !dt.GetTypeInfo()->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, dt.GetTypeInfo()->name.AddressOf());
WriteError(msg, file, node);
}
if( dt.IsReadOnly() )
WriteError(TXT_PROPERTY_CANT_BE_CONST, file, node);
// Multiple properties can be declared separated by ,
nd = nd->next;
while( nd )
{
asCString name(&file->code[nd->tokenPos], nd->tokenLength);
if( !decl->isExistingShared )
{
CheckNameConflictMember(ot, name.AddressOf(), nd, file, true);
AddPropertyToClass(decl, name, dt, isPrivate, isProtected, false, file, nd);
}
else
{
// Verify that the property exists in the original declaration
bool found = false;
for( asUINT p = 0; p < ot->properties.GetLength(); p++ )
{
asCObjectProperty *prop = ot->properties[p];
if( prop->isPrivate == isPrivate &&
prop->isProtected == isProtected &&
prop->name == name &&
prop->type.IsEqualExceptRef(dt) )
{
found = true;
break;
}
}
if( !found )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, ot->GetName());
WriteError(str, file, nd);
}
}
// Skip the initialization node
if( nd->next && nd->next->nodeType != snIdentifier )
nd = nd->next;
nd = nd->next;
}
}
else
asASSERT(false);
node = node->next;
}
// Add properties from included mixin classes that don't conflict with existing properties
IncludePropertiesFromMixins(decl);
if( !decl->isExistingShared )
toValidate.PushLast(decl);
asASSERT( ot->interfaces.GetLength() == ot->interfaceVFTOffsets.GetLength() );
}
// TODO: Warn if a method overrides a base method without marking it as 'override'.
// It must be possible to turn off this warning through engine property.
// TODO: A base class should be able to mark a method as 'abstract'. This will
// allow a base class to provide a partial implementation, but still force
// derived classes to implement specific methods.
// Verify that all interface methods are implemented in the classes
// We do this here so the base class' methods have already been inherited
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
sClassDeclaration *decl = classDeclarations[n];
if( decl->isExistingShared ) continue;
asCObjectType *ot = decl->typeInfo->CastToObjectType();
asCArray<bool> overrideValidations(ot->GetMethodCount());
for( asUINT k = 0; k < ot->methods.GetLength(); k++ )
overrideValidations.PushLast( !static_cast<asCScriptFunction*>(ot->GetMethodByIndex(k, false))->IsOverride() );
for( asUINT m = 0; m < ot->interfaces.GetLength(); m++ )
{
asCObjectType *objType = ot->interfaces[m];
for( asUINT i = 0; i < objType->methods.GetLength(); i++ )
{
// Only check the interface methods that was explicitly declared in this interface
// Methods that was inherited from other interfaces will be checked in those interfaces
if( objType != engine->scriptFunctions[objType->methods[i]]->objectType )
continue;
asUINT overrideIndex;
if( !DoesMethodExist(ot, objType->methods[i], &overrideIndex) )
{
asCString str;
str.Format(TXT_MISSING_IMPLEMENTATION_OF_s,
engine->GetFunctionDeclaration(objType->methods[i]).AddressOf());
WriteError(str, decl->script, decl->node);
}
else
overrideValidations[overrideIndex] = true;
}
}
bool hasBaseClass = ot->derivedFrom != 0;
for( asUINT j = 0; j < overrideValidations.GetLength(); j++ )
{
if( !overrideValidations[j] && (!hasBaseClass || !DoesMethodExist(ot->derivedFrom, ot->methods[j])) )
{
asCString msg;
msg.Format(TXT_METHOD_s_DOES_NOT_OVERRIDE, ot->GetMethodByIndex(j, false)->GetDeclaration());
WriteError(msg, decl->script, decl->node);
}
}
}
// Verify that the declared structures are valid, e.g. that the structure
// doesn't contain a member of its own type directly or indirectly
while( toValidate.GetLength() > 0 )
{
asUINT numClasses = (asUINT)toValidate.GetLength();
asCArray<sClassDeclaration*> toValidateNext((int)toValidate.GetLength());
while( toValidate.GetLength() > 0 )
{
sClassDeclaration *decl = toValidate[toValidate.GetLength()-1];
asCObjectType *ot = decl->typeInfo->CastToObjectType();
int validState = 1;
for( n = 0; n < ot->properties.GetLength(); n++ )
{
// A valid structure is one that uses only primitives or other valid objects
asCObjectProperty *prop = ot->properties[n];
asCDataType dt = prop->type;
// TODO: Add this check again, once solving the issues commented below
/*
if( dt.IsTemplate() )
{
// TODO: This must verify all sub types, not just the first one
// TODO: Just because the subtype is not a handle doesn't mean the template will actually instance the object
// this it shouldn't automatically raise an error for this, e.g. weakref<Object> should be legal as member
// of the Object class
asCDataType sub = dt;
while( sub.IsTemplate() && !sub.IsObjectHandle() )
sub = sub.GetSubType();
dt = sub;
}
*/
if( dt.IsObject() && !dt.IsObjectHandle() )
{
// Find the class declaration
sClassDeclaration *pdecl = 0;
for( asUINT p = 0; p < classDeclarations.GetLength(); p++ )
{
if( classDeclarations[p]->typeInfo == dt.GetTypeInfo() )
{
pdecl = classDeclarations[p];
break;
}
}
if( pdecl )
{
if( pdecl->typeInfo == decl->typeInfo )
{
WriteError(TXT_ILLEGAL_MEMBER_TYPE, decl->script, decl->node);
validState = 2;
break;
}
else if( pdecl->validState != 1 )
{
validState = pdecl->validState;
break;
}
}
}
}
if( validState == 1 )
{
decl->validState = 1;
toValidate.PopLast();
}
else if( validState == 2 )
{
decl->validState = 2;
toValidate.PopLast();
}
else
{
toValidateNext.PushLast(toValidate.PopLast());
}
}
toValidate = toValidateNext;
toValidateNext.SetLength(0);
if( numClasses == toValidate.GetLength() )
{
WriteError(TXT_ILLEGAL_MEMBER_TYPE, toValidate[0]->script, toValidate[0]->node);
break;
}
}
if( numErrors > 0 ) return;
// Verify which script classes can really form circular references, and mark only those as garbage collected.
// This must be done in the correct order, so that a class that contains another class isn't needlessly marked
// as garbage collected, just because the contained class was evaluated afterwards.
// TODO: runtime optimize: This algorithm can be further improved by checking the types that inherits from
// a base class. If the base class is not shared all the classes that derive from it
// are known at compile time, and can thus be checked for potential circular references too.
//
// Observe, that doing this would conflict with another potential future feature, which is to
// allow incremental builds, i.e. allow application to add or replace classes in an
// existing module. However, the applications that want to use that should use a special
// build flag to not finalize the module.
asCArray<asCObjectType*> typesToValidate;
for( n = 0; n < classDeclarations.GetLength(); n++ )
{
// Existing shared classes won't need evaluating, nor interfaces
sClassDeclaration *decl = classDeclarations[n];
if( decl->isExistingShared ) continue;
asCObjectType *ot = decl->typeInfo->CastToObjectType();
if( ot->IsInterface() ) continue;
if( decl->isTidy ) {
ot->flags &= ~asOBJ_GC;
continue;
}
typesToValidate.PushLast(ot);
}
asUINT numReevaluations = 0;
while( typesToValidate.GetLength() )
{
if( numReevaluations > typesToValidate.GetLength() )
{
// No types could be completely evaluated in the last iteration so
// we consider the remaining types in the array as garbage collected
break;
}
asCObjectType *type = typesToValidate[0];
typesToValidate.RemoveIndex(0);
// If the type inherits from another type that is yet to be validated, then reinsert it at the end
if( type->derivedFrom && typesToValidate.Exists(type->derivedFrom) )
{
typesToValidate.PushLast(type);
numReevaluations++;
continue;
}
// If the type inherits from a known garbage collected type, then this type must also be garbage collected
if( type->derivedFrom && (type->derivedFrom->flags & asOBJ_GC) )
{
type->flags |= asOBJ_GC;
continue;
}
// Evaluate template instances (silently) before verifying each of the classes, since it is possible that
// a class will be marked as non-garbage collected, which in turn will mark the template instance that uses
// it as non-garbage collected, which in turn means the class that contains the array also do not have to be
// garbage collected
EvaluateTemplateInstances(numTempl, true);
// Is there some path in which this structure is involved in circular references?
// If the type contains a member of a type that is yet to be validated, then reinsert it at the end
bool mustReevaluate = false;
bool gc = false;
for( asUINT p = 0; p < type->properties.GetLength(); p++ )
{
asCDataType dt = type->properties[p]->type;
if (dt.IsFuncdef())
{
// If a class holds a function pointer as member then the class must be garbage collected as the
// function pointer can form circular references with the class through use of a delegate. Example:
//
// class A { B @b; void f(); }
// class B { F @f; }
// funcdef void F();
//
// A a;
// @a.b = B(); // instance of A refers to instance of B
// @a.b.f = F(a.f); // instance of B refers to delegate that refers to instance of A
//
gc = true;
break;
}
if( !dt.IsObject() )
continue;
if( typesToValidate.Exists(dt.GetTypeInfo()->CastToObjectType()) )
mustReevaluate = true;
else
{
if( dt.IsTemplate() )
{
// Check if any of the subtypes are yet to be evaluated
bool skip = false;
for( asUINT s = 0; s < dt.GetTypeInfo()->GetSubTypeCount(); s++ )
{
asCObjectType *t = reinterpret_cast<asCObjectType*>(dt.GetTypeInfo()->GetSubType(s));
if( typesToValidate.Exists(t) )
{
mustReevaluate = true;
skip = true;
break;
}
}
if( skip )
continue;
}
if( dt.IsObjectHandle() )
{
// If it is known that the handle can't be involved in a circular reference
// then this object doesn't need to be marked as garbage collected.
asCObjectType *prop = dt.GetTypeInfo()->CastToObjectType();
if( prop->flags & asOBJ_SCRIPT_OBJECT )
{
// For script objects, treat non-final classes as if they can contain references
// as it is not known what derived classes might do. For final types, check all
// properties to determine if any of those can cause a circular reference with this
// class.
if( prop->flags & asOBJ_NOINHERIT )
{
//If this object doesn't need to GC, then we don't neither,
// it is impossible for an object to refer to its
// containing object if it is already determined to
// be unable to refer to itself.
if(prop->flags & asOBJ_GC) {
//Check for possible loops
for( asUINT sp = 0; sp < prop->properties.GetLength(); sp++ )
{
asCDataType sdt = prop->properties[sp]->type;
if( sdt.IsObject() )
{
if( sdt.IsObjectHandle() )
{
// TODO: runtime optimize: If the handle is again to a final class, then we can recursively check if the circular reference can occur
if( (sdt.GetTypeInfo()->flags & (asOBJ_SCRIPT_OBJECT | asOBJ_GC)) == (asOBJ_SCRIPT_OBJECT | asOBJ_GC) )
{
gc = true;
break;
}
}
else if( sdt.GetTypeInfo()->flags & asOBJ_GC )
{
// TODO: runtime optimize: Just because the member type is a potential circle doesn't mean that this one is.
// Only if the object is of a type that can reference this type, either directly or indirectly
gc = true;
break;
}
}
}
}
if( gc )
break;
}
else
{
// Assume it is garbage collected as it is not known at compile time what might inherit from this type
gc = true;
break;
}
}
else if( prop->flags & asOBJ_GC )
{
// If a type is not a script object, adopt its GC flag
// TODO: runtime optimize: Just because an application registered class is garbage collected, doesn't mean it
// can form a circular reference with this script class. Perhaps need a flag to tell
// if the script classes that contains the type should be garbage collected or not.
gc = true;
break;
}
}
else if( dt.GetTypeInfo()->flags & asOBJ_GC )
{
// TODO: runtime optimize: Just because the member type is a potential circle doesn't mean that this one is.
// Only if the object is of a type that can reference this type, either directly or indirectly
gc = true;
break;
}
}
}
// If the class wasn't found to require garbage collection, but it
// contains another type that has yet to be evaluated then it must be
// re-evaluated.
if( !gc && mustReevaluate )
{
typesToValidate.PushLast(type);
numReevaluations++;
continue;
}
// Update the flag in the object type
if( gc )
type->flags |= asOBJ_GC;
else
type->flags &= ~asOBJ_GC;
// Reset the counter
numReevaluations = 0;
}
}
void asCBuilder::IncludeMethodsFromMixins(sClassDeclaration *decl)
{
asCScriptNode *node = decl->node->firstChild;
// Skip the class attributes
while( node->nodeType == snIdentifier &&
!decl->script->TokenEquals(node->tokenPos, node->tokenLength, decl->name.AddressOf()) )
node = node->next;
// Skip the name of the class
node = node->next;
// Find the included mixin classes
while( node && node->nodeType == snIdentifier )
{
asSNameSpace *ns;
asCString name;
if( GetNamespaceAndNameFromNode(node, decl->script, decl->typeInfo->nameSpace, ns, name) < 0 )
{
node = node->next;
continue;
}
sMixinClass *mixin = 0;
while( ns )
{
// Need to make sure the name is not an object type
asCObjectType *objType = GetObjectType(name.AddressOf(), ns);
if( objType == 0 )
mixin = GetMixinClass(name.AddressOf(), ns);
if( objType || mixin )
break;
ns = engine->GetParentNameSpace(ns);
}
if( mixin )
{
// Find methods from mixin declaration
asCScriptNode *n = mixin->node->firstChild;
// Skip to the member declarations
// Possible keywords 'final' and 'shared' are removed in RegisterMixinClass so we don't need to worry about those here
while( n && n->nodeType == snIdentifier )
n = n->next;
// Add methods from the mixin that are not already existing in the class
while( n )
{
if( n->nodeType == snFunction )
{
// Instead of disconnecting the node, we need to clone it, otherwise other
// classes that include the same mixin will not see the methods
asCScriptNode *copy = n->CreateCopy(engine);
// Register the method, but only if it doesn't already exist in the class
RegisterScriptFunctionFromNode(copy, mixin->script, decl->typeInfo->CastToObjectType(), false, false, mixin->ns, false, true);
}
else if( n->nodeType == snVirtualProperty )
{
// TODO: mixin: Support virtual properties too
WriteError("The virtual property syntax is currently not supported for mixin classes", mixin->script, n);
//RegisterVirtualProperty(node, decl->script, decl->objType, false, false);
}
n = n->next;
}
}
node = node->next;
}
}
void asCBuilder::IncludePropertiesFromMixins(sClassDeclaration *decl)
{
asCScriptNode *node = decl->node->firstChild;
// Skip the class attributes
while( node->nodeType == snIdentifier &&
!decl->script->TokenEquals(node->tokenPos, node->tokenLength, decl->name.AddressOf()) )
node = node->next;
// Skip the name of the class
node = node->next;
// Find the included mixin classes
while( node && node->nodeType == snIdentifier )
{
asSNameSpace *ns;
asCString name;
if( GetNamespaceAndNameFromNode(node, decl->script, decl->typeInfo->nameSpace, ns, name) < 0 )
{
node = node->next;
continue;
}
sMixinClass *mixin = 0;
while( ns )
{
// Need to make sure the name is not an object type
asCObjectType *objType = GetObjectType(name.AddressOf(), ns);
if( objType == 0 )
mixin = GetMixinClass(name.AddressOf(), ns);
if( objType || mixin )
break;
ns = engine->GetParentNameSpace(ns);
}
if( mixin )
{
// Find properties from mixin declaration
asCScriptNode *n = mixin->node->firstChild;
// Skip to the member declarations
// Possible keywords 'final' and 'shared' are removed in RegisterMixinClass so we don't need to worry about those here
while( n && n->nodeType == snIdentifier )
n = n->next;
// Add properties from the mixin that are not already existing in the class
while( n )
{
if( n->nodeType == snDeclaration )
{
asCScriptNode *n2 = n->firstChild;
bool isPrivate = false, isProtected = false;
if( n2 && n2->tokenType == ttPrivate )
{
isPrivate = true;
n2 = n2->next;
}
else if( n2 && n2->tokenType == ttProtected )
{
isProtected = true;
n2 = n2->next;
}
asCScriptCode *file = mixin->script;
asCDataType dt = CreateDataTypeFromNode(n2, file, mixin->ns);
if( decl->typeInfo->IsShared() && dt.GetTypeInfo() && !dt.GetTypeInfo()->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, dt.GetTypeInfo()->name.AddressOf());
WriteError(msg, file, n);
WriteInfo(TXT_WHILE_INCLUDING_MIXIN, decl->script, node);
}
if( dt.IsReadOnly() )
WriteError(TXT_PROPERTY_CANT_BE_CONST, file, n);
n2 = n2->next;
while( n2 )
{
name.Assign(&file->code[n2->tokenPos], n2->tokenLength);
// Add the property only if it doesn't already exist in the class
bool exists = false;
asCObjectType *ot = decl->typeInfo->CastToObjectType();
for( asUINT p = 0; p < ot->properties.GetLength(); p++ )
if( ot->properties[p]->name == name )
{
exists = true;
break;
}
if( !exists )
{
if( !decl->isExistingShared )
{
// It must not conflict with the name of methods
int r = CheckNameConflictMember(ot, name.AddressOf(), n2, file, true);
if( r < 0 )
WriteInfo(TXT_WHILE_INCLUDING_MIXIN, decl->script, node);
AddPropertyToClass(decl, name, dt, isPrivate, isProtected, false, file, n2);
}
else
{
// Verify that the property exists in the original declaration
bool found = false;
for( asUINT p = 0; p < ot->properties.GetLength(); p++ )
{
asCObjectProperty *prop = ot->properties[p];
if( prop->isPrivate == isPrivate &&
prop->isProtected == isProtected &&
prop->name == name &&
prop->type == dt )
{
found = true;
break;
}
}
if( !found )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, ot->GetName());
WriteError(str, decl->script, decl->node);
WriteInfo(TXT_WHILE_INCLUDING_MIXIN, decl->script, node);
}
}
}
// Skip the initialization expression
if( n2->next && n2->next->nodeType != snIdentifier )
n2 = n2->next;
n2 = n2->next;
}
}
n = n->next;
}
}
node = node->next;
}
}
int asCBuilder::CreateVirtualFunction(asCScriptFunction *func, int idx)
{
asCScriptFunction *vf = asNEW(asCScriptFunction)(engine, module, asFUNC_VIRTUAL);
if( vf == 0 )
return asOUT_OF_MEMORY;
vf->name = func->name;
vf->returnType = func->returnType;
vf->parameterTypes = func->parameterTypes;
vf->inOutFlags = func->inOutFlags;
vf->id = engine->GetNextScriptFunctionId();
vf->isReadOnly = func->isReadOnly;
vf->objectType = func->objectType;
vf->objectType->AddRefInternal();
vf->signatureId = func->signatureId;
vf->isPrivate = func->isPrivate;
vf->isProtected = func->isProtected;
vf->isFinal = func->isFinal;
vf->isOverride = func->isOverride;
vf->vfTableIdx = idx;
// It is not necessary to copy the default args, as they have no meaning in the virtual function
module->AddScriptFunction(vf);
// Add a dummy to the builder so that it doesn't mix up function ids
functions.PushLast(0);
return vf->id;
}
asCObjectProperty *asCBuilder::AddPropertyToClass(sClassDeclaration *decl, const asCString &name, const asCDataType &dt, bool isPrivate, bool isProtected, bool isInherited, asCScriptCode *file, asCScriptNode *node)
{
if( node )
{
asASSERT(!isInherited);
// Check if the property is allowed
if( !dt.CanBeInstantiated() )
{
if( file && node )
{
asCString str;
if( dt.IsAbstractClass() )
str.Format(TXT_ABSTRACT_CLASS_s_CANNOT_BE_INSTANTIATED, dt.Format(decl->typeInfo->nameSpace).AddressOf());
else if( dt.IsInterface() )
str.Format(TXT_INTERFACE_s_CANNOT_BE_INSTANTIATED, dt.Format(decl->typeInfo->nameSpace).AddressOf());
else
// TODO: Improve error message to explain why
str.Format(TXT_DATA_TYPE_CANT_BE_s, dt.Format(decl->typeInfo->nameSpace).AddressOf());
WriteError(str, file, node);
}
return 0;
}
// Register the initialization expression (if any) to be compiled later
asCScriptNode *declNode = node;
asCScriptNode *initNode = 0;
if( node->next && node->next->nodeType != snIdentifier )
{
asASSERT( node->next->nodeType == snAssignment );
initNode = node->next;
}
sPropertyInitializer p(name, declNode, initNode, file);
decl->propInits.PushLast(p);
}
else
{
// If the declaration node is not given, then
// this property is inherited from a base class
asASSERT(isInherited);
}
// Add the property to the object type
return decl->typeInfo->CastToObjectType()->AddPropertyToClass(name, dt, isPrivate, isProtected, isInherited);
}
bool asCBuilder::DoesMethodExist(asCObjectType *objType, int methodId, asUINT *methodIndex)
{
asCScriptFunction *method = GetFunctionDescription(methodId);
for( asUINT n = 0; n < objType->methods.GetLength(); n++ )
{
asCScriptFunction *m = GetFunctionDescription(objType->methods[n]);
if( m->name != method->name ) continue;
if( m->returnType != method->returnType ) continue;
if( m->isReadOnly != method->isReadOnly ) continue;
if( m->parameterTypes != method->parameterTypes ) continue;
if( m->inOutFlags != method->inOutFlags ) continue;
if( methodIndex )
*methodIndex = n;
return true;
}
return false;
}
void asCBuilder::AddDefaultConstructor(asCObjectType *objType, asCScriptCode *file)
{
int funcId = engine->GetNextScriptFunctionId();
asCDataType returnType = asCDataType::CreatePrimitive(ttVoid, false);
asCArray<asCDataType> parameterTypes;
asCArray<asETypeModifiers> inOutFlags;
asCArray<asCString *> defaultArgs;
asCArray<asCString> parameterNames;
// Add the script function
// TODO: declaredAt should be set to where the class has been declared
module->AddScriptFunction(file->idx, 0, funcId, objType->name, returnType, parameterTypes, parameterNames, inOutFlags, defaultArgs, false, objType, false, false, false, false, false, false, false, objType->nameSpace);
// Set it as default constructor
if( objType->beh.construct )
engine->scriptFunctions[objType->beh.construct]->ReleaseInternal();
objType->beh.construct = funcId;
objType->beh.constructors[0] = funcId;
engine->scriptFunctions[funcId]->AddRefInternal();
// The bytecode for the default constructor will be generated
// only after the potential inheritance has been established
sFunctionDescription *func = asNEW(sFunctionDescription);
if( func == 0 )
{
// Out of memory
return;
}
functions.PushLast(func);
func->script = file;
func->node = 0;
func->name = objType->name;
func->objType = objType;
func->funcId = funcId;
func->isExistingShared = false;
// Add a default factory as well
funcId = engine->GetNextScriptFunctionId();
if( objType->beh.factory )
engine->scriptFunctions[objType->beh.factory]->ReleaseInternal();
objType->beh.factory = funcId;
objType->beh.factories[0] = funcId;
returnType = asCDataType::CreateObjectHandle(objType, false);
// TODO: should be the same as the constructor
module->AddScriptFunction(file->idx, 0, funcId, objType->name, returnType, parameterTypes, parameterNames, inOutFlags, defaultArgs, false);
functions.PushLast(0);
asCCompiler compiler(engine);
compiler.CompileFactory(this, file, engine->scriptFunctions[funcId]);
engine->scriptFunctions[funcId]->AddRefInternal();
// If the object is shared, then the factory must also be marked as shared
if( objType->flags & asOBJ_SHARED )
engine->scriptFunctions[funcId]->isShared = true;
}
int asCBuilder::RegisterEnum(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
// Is it a shared enum?
bool isShared = false;
asCEnumType *existingSharedType = 0;
asCScriptNode *tmp = node->firstChild;
if( tmp->nodeType == snIdentifier && file->TokenEquals(tmp->tokenPos, tmp->tokenLength, SHARED_TOKEN) )
{
isShared = true;
tmp = tmp->next;
}
// Grab the name of the enumeration
asCString name;
asASSERT(snDataType == tmp->nodeType);
asASSERT(snIdentifier == tmp->firstChild->nodeType);
name.Assign(&file->code[tmp->firstChild->tokenPos], tmp->firstChild->tokenLength);
if( isShared )
{
// Look for a pre-existing shared enum with the same signature
for( asUINT n = 0; n < engine->sharedScriptTypes.GetLength(); n++ )
{
asCTypeInfo *o = engine->sharedScriptTypes[n];
if( o &&
o->IsShared() &&
(o->flags & asOBJ_ENUM) &&
o->name == name &&
o->nameSpace == ns )
{
existingSharedType = o->CastToEnumType();
break;
}
}
}
// Check the name and add the enum
int r = CheckNameConflict(name.AddressOf(), tmp->firstChild, file, ns);
if( asSUCCESS == r )
{
asCEnumType *st;
if( existingSharedType )
{
st = existingSharedType;
st->AddRefInternal();
}
else
{
st = asNEW(asCEnumType)(engine);
if( st == 0 )
return asOUT_OF_MEMORY;
st->flags = asOBJ_ENUM;
if( isShared )
st->flags |= asOBJ_SHARED;
st->size = 4;
st->name = name;
st->nameSpace = ns;
st->module = module;
}
module->enumTypes.PushLast(st);
if( !existingSharedType && isShared )
{
engine->sharedScriptTypes.PushLast(st);
st->AddRefInternal();
}
// Store the location of this declaration for reference in name collisions
sClassDeclaration *decl = asNEW(sClassDeclaration);
if( decl == 0 )
return asOUT_OF_MEMORY;
decl->name = name;
decl->script = file;
decl->typeInfo = st;
namedTypeDeclarations.PushLast(decl);
asCDataType type = CreateDataTypeFromNode(tmp, file, ns);
asASSERT(!type.IsReference());
// Register the enum values
tmp = tmp->next;
while( tmp )
{
asASSERT(snIdentifier == tmp->nodeType);
name.Assign(&file->code[tmp->tokenPos], tmp->tokenLength);
if( existingSharedType )
{
// If this is a pre-existent shared enum, then just double check
// that the value is already defined in the original declaration
bool found = false;
for( asUINT n = 0; n < st->enumValues.GetLength(); n++ )
if( st->enumValues[n]->name == name )
{
found = true;
break;
}
if( !found )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, st->GetName());
WriteError(str, file, tmp);
break;
}
tmp = tmp->next;
if( tmp && tmp->nodeType == snAssignment )
tmp = tmp->next;
continue;
}
else
{
// Check for name conflict errors with other values in the enum
if( globVariables.GetFirst(ns, name, asCCompGlobVarType(type)) )
{
asCString str;
str.Format(TXT_NAME_CONFLICT_s_ALREADY_USED, name.AddressOf());
WriteError(str, file, tmp);
tmp = tmp->next;
if( tmp && tmp->nodeType == snAssignment )
tmp = tmp->next;
continue;
}
// Check for assignment
asCScriptNode *asnNode = tmp->next;
if( asnNode && snAssignment == asnNode->nodeType )
asnNode->DisconnectParent();
else
asnNode = 0;
// Create the global variable description so the enum value can be evaluated
sGlobalVariableDescription *gvar = asNEW(sGlobalVariableDescription);
if( gvar == 0 )
return asOUT_OF_MEMORY;
gvar->script = file;
gvar->declaredAtNode = tmp;
tmp = tmp->next;
gvar->declaredAtNode->DisconnectParent();
gvar->initializationNode = asnNode;
gvar->name = name;
gvar->datatype = type;
gvar->ns = ns;
// No need to allocate space on the global memory stack since the values are stored in the asCObjectType
// Set the index to a negative to allow compiler to diferentiate from ordinary global var when compiling the initialization
gvar->index = -1;
gvar->isCompiled = false;
gvar->isPureConstant = true;
gvar->isEnumValue = true;
gvar->constantValue = 0xdeadbeef;
// Allocate dummy property so we can compile the value.
// This will be removed later on so we don't add it to the engine.
gvar->property = asNEW(asCGlobalProperty);
if( gvar->property == 0 )
return asOUT_OF_MEMORY;
gvar->property->name = name;
gvar->property->nameSpace = ns;
gvar->property->type = gvar->datatype;
gvar->property->id = 0;
globVariables.Put(gvar);
}
}
}
node->Destroy(engine);
return r;
}
int asCBuilder::RegisterTypedef(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
// Get the native data type
asCScriptNode *tmp = node->firstChild;
asASSERT(NULL != tmp && snDataType == tmp->nodeType);
asCDataType dataType;
dataType.CreatePrimitive(tmp->tokenType, false);
dataType.SetTokenType(tmp->tokenType);
tmp = tmp->next;
// Grab the name of the typedef
asASSERT(NULL != tmp && NULL == tmp->next);
asCString name;
name.Assign(&file->code[tmp->tokenPos], tmp->tokenLength);
// If the name is not already in use add it
int r = CheckNameConflict(name.AddressOf(), tmp, file, ns);
asCTypedefType *st = 0;
if( asSUCCESS == r )
{
// Create the new type
st = asNEW(asCTypedefType)(engine);
if( st == 0 )
r = asOUT_OF_MEMORY;
}
if( asSUCCESS == r )
{
st->flags = asOBJ_TYPEDEF;
st->size = dataType.GetSizeInMemoryBytes();
st->name = name;
st->nameSpace = ns;
st->aliasForType = dataType;
st->module = module;
module->typeDefs.PushLast(st);
// Store the location of this declaration for reference in name collisions
sClassDeclaration *decl = asNEW(sClassDeclaration);
if( decl == 0 )
r = asOUT_OF_MEMORY;
else
{
decl->name = name;
decl->script = file;
decl->typeInfo = st;
namedTypeDeclarations.PushLast(decl);
}
}
node->Destroy(engine);
return r;
}
void asCBuilder::GetParsedFunctionDetails(asCScriptNode *node, asCScriptCode *file, asCObjectType *objType, asCString &name, asCDataType &returnType, asCArray<asCString> &parameterNames, asCArray<asCDataType> &parameterTypes, asCArray<asETypeModifiers> &inOutFlags, asCArray<asCString *> &defaultArgs, bool &isConstMethod, bool &isConstructor, bool &isDestructor, bool &isPrivate, bool &isProtected, bool &isOverride, bool &isFinal, bool &isShared, asSNameSpace *implicitNamespace)
{
node = node->firstChild;
// Is the function a private or protected class method?
isPrivate = false, isProtected = false;
if( node->tokenType == ttPrivate )
{
isPrivate = true;
node = node->next;
}
else if( node->tokenType == ttProtected )
{
isProtected = true;
node = node->next;
}
// Is the function shared?
isShared = false;
if( node->tokenType == ttIdentifier && file->TokenEquals(node->tokenPos, node->tokenLength, SHARED_TOKEN) )
{
isShared = true;
node = node->next;
}
// Find the name
isConstructor = false;
isDestructor = false;
asCScriptNode *n = 0;
if( node->nodeType == snDataType )
n = node->next->next;
else
{
// If the first node is a ~ token, then we know it is a destructor
if( node->tokenType == ttBitNot )
{
n = node->next;
isDestructor = true;
}
else
{
n = node;
isConstructor = true;
}
}
name.Assign(&file->code[n->tokenPos], n->tokenLength);
// Initialize a script function object for registration
if( !isConstructor && !isDestructor )
{
returnType = CreateDataTypeFromNode(node, file, implicitNamespace, false, objType);
returnType = ModifyDataTypeFromNode(returnType, node->next, file, 0, 0);
if( engine->ep.disallowValueAssignForRefType &&
returnType.GetTypeInfo() &&
(returnType.GetTypeInfo()->flags & asOBJ_REF) &&
!(returnType.GetTypeInfo()->flags & asOBJ_SCOPED) &&
!returnType.IsReference() &&
!returnType.IsObjectHandle() )
{
WriteError(TXT_REF_TYPE_CANT_BE_RETURNED_BY_VAL, file, node);
}
}
else
returnType = asCDataType::CreatePrimitive(ttVoid, false);
isConstMethod = false;
isFinal = false;
isOverride = false;
if( objType && n->next->next )
{
asCScriptNode *decorator = n->next->next;
// Is this a const method?
if( decorator->tokenType == ttConst )
{
isConstMethod = true;
decorator = decorator->next;
}
while( decorator )
{
if( decorator->tokenType == ttIdentifier && file->TokenEquals(decorator->tokenPos, decorator->tokenLength, FINAL_TOKEN) )
isFinal = true;
else if( decorator->tokenType == ttIdentifier && file->TokenEquals(decorator->tokenPos, decorator->tokenLength, OVERRIDE_TOKEN) )
isOverride = true;
decorator = decorator->next;
}
}
// Count the number of parameters
int count = 0;
asCScriptNode *c = n->next->firstChild;
while( c )
{
count++;
c = c->next->next;
if( c && c->nodeType == snIdentifier )
c = c->next;
if( c && c->nodeType == snExpression )
c = c->next;
}
// Get the parameter types
parameterNames.Allocate(count, false);
parameterTypes.Allocate(count, false);
inOutFlags.Allocate(count, false);
defaultArgs.Allocate(count, false);
n = n->next->firstChild;
while( n )
{
asETypeModifiers inOutFlag;
asCDataType type = CreateDataTypeFromNode(n, file, implicitNamespace, false, objType);
type = ModifyDataTypeFromNode(type, n->next, file, &inOutFlag, 0);
if( engine->ep.disallowValueAssignForRefType &&
type.GetTypeInfo() &&
(type.GetTypeInfo()->flags & asOBJ_REF) &&
!(type.GetTypeInfo()->flags & asOBJ_SCOPED) &&
!type.IsReference() &&
!type.IsObjectHandle() )
{
WriteError(TXT_REF_TYPE_CANT_BE_PASSED_BY_VAL, file, node);
}
// Store the parameter type
parameterTypes.PushLast(type);
inOutFlags.PushLast(inOutFlag);
// Move to next parameter
n = n->next->next;
if( n && n->nodeType == snIdentifier )
{
asCString paramName(&file->code[n->tokenPos], n->tokenLength);
parameterNames.PushLast(paramName);
n = n->next;
}
else
{
// No name was given for the parameter
parameterNames.PushLast(asCString());
}
if( n && n->nodeType == snExpression )
{
// Strip out white space and comments to better share the string
asCString *defaultArgStr = asNEW(asCString);
if( defaultArgStr )
*defaultArgStr = GetCleanExpressionString(n, file);
defaultArgs.PushLast(defaultArgStr);
n = n->next;
}
else
defaultArgs.PushLast(0);
}
}
#endif
asCString asCBuilder::GetCleanExpressionString(asCScriptNode *node, asCScriptCode *file)
{
asASSERT(node && node->nodeType == snExpression);
asCString str;
str.Assign(file->code + node->tokenPos, node->tokenLength);
asCString cleanStr;
for( asUINT n = 0; n < str.GetLength(); )
{
asUINT len = 0;
asETokenClass tok = engine->ParseToken(str.AddressOf() + n, str.GetLength() - n, &len);
if( tok != asTC_COMMENT && tok != asTC_WHITESPACE )
{
if( cleanStr.GetLength() ) cleanStr += " ";
cleanStr.Concatenate(str.AddressOf() + n, len);
}
n += len;
}
return cleanStr;
}
#ifndef AS_NO_COMPILER
int asCBuilder::RegisterScriptFunctionFromNode(asCScriptNode *node, asCScriptCode *file, asCObjectType *objType, bool isInterface, bool isGlobalFunction, asSNameSpace *ns, bool isExistingShared, bool isMixin)
{
asCString name;
asCDataType returnType;
asCArray<asCString> parameterNames;
asCArray<asCDataType> parameterTypes;
asCArray<asETypeModifiers> inOutFlags;
asCArray<asCString *> defaultArgs;
bool isConstMethod;
bool isOverride;
bool isFinal;
bool isConstructor;
bool isDestructor;
bool isPrivate;
bool isProtected;
bool isShared;
asASSERT( (objType && ns == 0) || isGlobalFunction || isMixin );
// Set the default namespace
if( ns == 0 )
{
if( objType )
ns = objType->nameSpace;
else
ns = engine->nameSpaces[0];
}
GetParsedFunctionDetails(node, file, objType, name, returnType, parameterNames, parameterTypes, inOutFlags, defaultArgs, isConstMethod, isConstructor, isDestructor, isPrivate, isProtected, isOverride, isFinal, isShared, ns);
return RegisterScriptFunction(node, file, objType, isInterface, isGlobalFunction, ns, isExistingShared, isMixin, name, returnType, parameterNames, parameterTypes, inOutFlags, defaultArgs, isConstMethod, isConstructor, isDestructor, isPrivate, isProtected, isOverride, isFinal, isShared);
}
asCScriptFunction *asCBuilder::RegisterLambda(asCScriptNode *node, asCScriptCode *file, asCScriptFunction *funcDef, const asCString &name, asSNameSpace *ns)
{
// Get the parameter names from the node
asCArray<asCString> parameterNames;
asCArray<asCString*> defaultArgs;
asCScriptNode *args = node->firstChild;
while( args && args->nodeType == snIdentifier )
{
asCString argName;
argName.Assign(&file->code[args->tokenPos], args->tokenLength);
parameterNames.PushLast(argName);
defaultArgs.PushLast(0);
args = args->next;
}
// The statement block for the function must be disconnected, as the builder is going to be the owner of it
args->DisconnectParent();
// Get the return and parameter types from the funcDef
asCString funcName = name;
int r = RegisterScriptFunction(args, file, 0, 0, true, ns, false, false, funcName, funcDef->returnType, parameterNames, funcDef->parameterTypes, funcDef->inOutFlags, defaultArgs, false, false, false, false, false, false, false, false);
if( r < 0 )
return 0;
// Return the function that was just created (but that will be compiled later)
return engine->scriptFunctions[functions[functions.GetLength()-1]->funcId];
}
int asCBuilder::RegisterScriptFunction(asCScriptNode *node, asCScriptCode *file, asCObjectType *objType, bool isInterface, bool isGlobalFunction, asSNameSpace *ns, bool isExistingShared, bool isMixin, asCString &name, asCDataType &returnType, asCArray<asCString> &parameterNames, asCArray<asCDataType> &parameterTypes, asCArray<asETypeModifiers> &inOutFlags, asCArray<asCString *> &defaultArgs, bool isConstMethod, bool isConstructor, bool isDestructor, bool isPrivate, bool isProtected, bool isOverride, bool isFinal, bool isShared)
{
// Determine default namespace if not specified
if( ns == 0 )
{
if( objType )
ns = objType->nameSpace;
else
ns = engine->nameSpaces[0];
}
if( isExistingShared )
{
asASSERT( objType );
// Should validate that the function really exists in the class/interface
bool found = false;
if( isConstructor || isDestructor )
{
// TODO: shared: Should check the existance of these too
found = true;
}
else
{
for( asUINT n = 0; n < objType->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[objType->methods[n]];
if( func->name == name &&
func->IsSignatureExceptNameEqual(returnType, parameterTypes, inOutFlags, objType, isConstMethod) )
{
// Add the shared function in this module too
module->AddScriptFunction(func);
found = true;
break;
}
}
}
if( !found )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, objType->GetName());
WriteError(str, file, node);
}
// Free the default args
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
node->Destroy(engine);
return 0;
}
// Check for name conflicts
if( !isConstructor && !isDestructor )
{
if( objType )
{
CheckNameConflictMember(objType, name.AddressOf(), node, file, false);
if( name == objType->name )
WriteError(TXT_METHOD_CANT_HAVE_NAME_OF_CLASS, file, node);
}
else
CheckNameConflict(name.AddressOf(), node, file, ns);
}
else
{
if( isMixin )
{
// Mixins cannot implement constructors/destructors
WriteError(TXT_MIXIN_CANNOT_HAVE_CONSTRUCTOR, file, node);
// Free the default args
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
node->Destroy(engine);
return 0;
}
// Verify that the name of the constructor/destructor is the same as the class
if( name != objType->name )
{
asCString str;
if( isDestructor )
str.Format(TXT_DESTRUCTOR_s_s_NAME_ERROR, objType->name.AddressOf(), name.AddressOf());
else
str.Format(TXT_METHOD_s_s_HAS_NO_RETURN_TYPE, objType->name.AddressOf(), name.AddressOf());
WriteError(str, file, node);
}
if( isDestructor )
name = "~" + name;
}
isExistingShared = false;
int funcId = engine->GetNextScriptFunctionId();
if( !isInterface )
{
sFunctionDescription *func = asNEW(sFunctionDescription);
if( func == 0 )
{
// Free the default args
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
return asOUT_OF_MEMORY;
}
functions.PushLast(func);
func->script = file;
func->node = node;
func->name = name;
func->objType = objType;
func->funcId = funcId;
func->isExistingShared = false;
func->paramNames = parameterNames;
if( isShared )
{
// Look for a pre-existing shared function with the same signature
for( asUINT n = 0; n < engine->scriptFunctions.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[n];
if( f &&
f->isShared &&
f->name == name &&
f->nameSpace == ns &&
f->objectType == objType &&
f->IsSignatureExceptNameEqual(returnType, parameterTypes, inOutFlags, 0, false) )
{
funcId = func->funcId = f->id;
isExistingShared = func->isExistingShared = true;
break;
}
}
}
}
// Destructors may not have any parameters
if( isDestructor && parameterTypes.GetLength() > 0 )
WriteError(TXT_DESTRUCTOR_MAY_NOT_HAVE_PARM, file, node);
// If a function, class, or interface is shared then only shared types may be used in the signature
if( (objType && objType->IsShared()) || isShared )
{
asCTypeInfo *ti = returnType.GetTypeInfo();
if( ti && !ti->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, ti->name.AddressOf());
WriteError(msg, file, node);
}
for( asUINT p = 0; p < parameterTypes.GetLength(); ++p )
{
ti = parameterTypes[p].GetTypeInfo();
if( ti && !ti->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, ti->name.AddressOf());
WriteError(msg, file, node);
}
}
}
// Check that the same function hasn't been registered already in the namespace
asCArray<int> funcs;
if( objType )
GetObjectMethodDescriptions(name.AddressOf(), objType, funcs, false);
else
GetFunctionDescriptions(name.AddressOf(), funcs, ns);
if( objType && (name == "opConv" || name == "opImplConv" || name == "opCast" || name == "opImplCast") && parameterTypes.GetLength() == 0 )
{
// opConv and opCast are special methods used for type casts
for( asUINT n = 0; n < funcs.GetLength(); ++n )
{
asCScriptFunction *func = GetFunctionDescription(funcs[n]);
if( func->IsSignatureExceptNameEqual(returnType, parameterTypes, inOutFlags, objType, isConstMethod) )
{
// TODO: clean up: Reuse the same error handling for both opConv and normal methods
if( isMixin )
{
// Clean up the memory, as the function will not be registered
if( node )
node->Destroy(engine);
sFunctionDescription *funcDesc = functions.PopLast();
asDELETE(funcDesc, sFunctionDescription);
// Free the default args
for( n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
return 0;
}
WriteError(TXT_FUNCTION_ALREADY_EXIST, file, node);
break;
}
}
}
else
{
for( asUINT n = 0; n < funcs.GetLength(); ++n )
{
asCScriptFunction *func = GetFunctionDescription(funcs[n]);
if( func->IsSignatureExceptNameAndReturnTypeEqual(parameterTypes, inOutFlags, objType, isConstMethod) )
{
if( isMixin )
{
// Clean up the memory, as the function will not be registered
if( node )
node->Destroy(engine);
sFunctionDescription *funcDesc = functions.PopLast();
asDELETE(funcDesc, sFunctionDescription);
// Free the default args
for( n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
return 0;
}
WriteError(TXT_FUNCTION_ALREADY_EXIST, file, node);
break;
}
}
}
// Register the function
if( isExistingShared )
{
// Delete the default args as they won't be used anymore
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
asDELETE(defaultArgs[n], asCString);
asCScriptFunction *f = engine->scriptFunctions[funcId];
module->AddScriptFunction(f);
// TODO: clean up: This should be done by AddScriptFunction() itself
module->globalFunctions.Put(f);
}
else
{
int row = 0, col = 0;
if( node )
file->ConvertPosToRowCol(node->tokenPos, &row, &col);
module->AddScriptFunction(file->idx, (row&0xFFFFF)|((col&0xFFF)<<20), funcId, name, returnType, parameterTypes, parameterNames, inOutFlags, defaultArgs, isInterface, objType, isConstMethod, isGlobalFunction, isPrivate, isProtected, isFinal, isOverride, isShared, ns);
}
// Make sure the default args are declared correctly
ValidateDefaultArgs(file, node, engine->scriptFunctions[funcId]);
CheckForConflictsDueToDefaultArgs(file, node, engine->scriptFunctions[funcId], objType);
if( objType )
{
asASSERT( !isExistingShared );
engine->scriptFunctions[funcId]->AddRefInternal();
if( isConstructor )
{
int factoryId = engine->GetNextScriptFunctionId();
if( parameterTypes.GetLength() == 0 )
{
// Overload the default constructor
engine->scriptFunctions[objType->beh.construct]->ReleaseInternal();
objType->beh.construct = funcId;
objType->beh.constructors[0] = funcId;
// Register the default factory as well
engine->scriptFunctions[objType->beh.factory]->ReleaseInternal();
objType->beh.factory = factoryId;
objType->beh.factories[0] = factoryId;
}
else
{
objType->beh.constructors.PushLast(funcId);
// Register the factory as well
objType->beh.factories.PushLast(factoryId);
}
// We must copy the default arg strings to avoid deleting the same object multiple times
for( asUINT n = 0; n < defaultArgs.GetLength(); n++ )
if( defaultArgs[n] )
defaultArgs[n] = asNEW(asCString)(*defaultArgs[n]);
asCDataType dt = asCDataType::CreateObjectHandle(objType, false);
module->AddScriptFunction(file->idx, engine->scriptFunctions[funcId]->scriptData->declaredAt, factoryId, name, dt, parameterTypes, parameterNames, inOutFlags, defaultArgs, false);
// If the object is shared, then the factory must also be marked as shared
if( objType->flags & asOBJ_SHARED )
engine->scriptFunctions[factoryId]->isShared = true;
// Add a dummy function to the builder so that it doesn't mix up the fund Ids
functions.PushLast(0);
// Compile the factory immediately
asCCompiler compiler(engine);
compiler.CompileFactory(this, file, engine->scriptFunctions[factoryId]);
engine->scriptFunctions[factoryId]->AddRefInternal();
}
else if( isDestructor )
objType->beh.destruct = funcId;
else
{
// If the method is the assignment operator we need to replace the default implementation
asCScriptFunction *f = engine->scriptFunctions[funcId];
if( f->name == "opAssign" && f->parameterTypes.GetLength() == 1 &&
f->parameterTypes[0].GetTypeInfo() == f->objectType &&
(f->inOutFlags[0] & asTM_INREF) )
{
engine->scriptFunctions[objType->beh.copy]->ReleaseInternal();
objType->beh.copy = funcId;
f->AddRefInternal();
}
objType->methods.PushLast(funcId);
}
}
// We need to delete the node already if this is an interface method
if( isInterface && node )
node->Destroy(engine);
return 0;
}
int asCBuilder::RegisterVirtualProperty(asCScriptNode *node, asCScriptCode *file, asCObjectType *objType, bool isInterface, bool isGlobalFunction, asSNameSpace *ns, bool isExistingShared)
{
if( engine->ep.propertyAccessorMode != 2 )
{
WriteError(TXT_PROPERTY_ACCESSOR_DISABLED, file, node);
node->Destroy(engine);
return 0;
}
asASSERT( (objType && ns == 0) || isGlobalFunction );
if( ns == 0 )
{
if( objType )
ns = objType->nameSpace;
else
ns = engine->nameSpaces[0];
}
bool isPrivate = false, isProtected = false;
asCString emulatedName;
asCDataType emulatedType;
asCScriptNode *mainNode = node;
node = node->firstChild;
if( !isGlobalFunction && node->tokenType == ttPrivate )
{
isPrivate = true;
node = node->next;
}
else if( !isGlobalFunction && node->tokenType == ttProtected )
{
isProtected = true;
node = node->next;
}
emulatedType = CreateDataTypeFromNode(node, file, ns);
emulatedType = ModifyDataTypeFromNode(emulatedType, node->next, file, 0, 0);
node = node->next->next;
emulatedName.Assign(&file->code[node->tokenPos], node->tokenLength);
if( node->next == 0 )
WriteError(TXT_PROPERTY_WITHOUT_ACCESSOR, file, node);
node = node->next;
while( node )
{
asCScriptNode *next = node->next;
asCScriptNode *funcNode = 0;
bool success = false;
bool isConst = false;
bool isFinal = false;
bool isOverride = false;
asCDataType returnType;
asCArray<asCString> paramNames;
asCArray<asCDataType> paramTypes;
asCArray<asETypeModifiers> paramModifiers;
asCArray<asCString*> defaultArgs;
asCString name;
// TODO: getset: Allow private for individual property accessors
// TODO: getset: If the accessor uses its own name, then the property should be automatically declared
if( node->firstChild->nodeType == snIdentifier && file->TokenEquals(node->firstChild->tokenPos, node->firstChild->tokenLength, GET_TOKEN) )
{
funcNode = node->firstChild->next;
if( funcNode && funcNode->tokenType == ttConst )
{
isConst = true;
funcNode = funcNode->next;
}
while( funcNode && funcNode->nodeType != snStatementBlock )
{
if( funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, FINAL_TOKEN) )
isFinal = true;
else if( funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, OVERRIDE_TOKEN) )
isOverride = true;
funcNode = funcNode->next;
}
if( funcNode )
funcNode->DisconnectParent();
if( funcNode == 0 && (objType == 0 || !objType->IsInterface()) )
{
// TODO: getset: If no implementation is supplied the builder should provide an automatically generated implementation
// The compiler needs to be able to handle the different types, primitive, value type, and handle
// The code is also different for global property accessors
WriteError(TXT_PROPERTY_ACCESSOR_MUST_BE_IMPLEMENTED, file, node);
}
// Setup the signature for the get accessor method
returnType = emulatedType;
name = "get_" + emulatedName;
success = true;
}
else if( node->firstChild->nodeType == snIdentifier && file->TokenEquals(node->firstChild->tokenPos, node->firstChild->tokenLength, SET_TOKEN) )
{
funcNode = node->firstChild->next;
if( funcNode && funcNode->tokenType == ttConst )
{
isConst = true;
funcNode = funcNode->next;
}
while( funcNode && funcNode->nodeType != snStatementBlock )
{
if( funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, FINAL_TOKEN) )
isFinal = true;
else if( funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, OVERRIDE_TOKEN) )
isOverride = true;
funcNode = funcNode->next;
}
if( funcNode )
funcNode->DisconnectParent();
if( funcNode == 0 && (objType == 0 || !objType->IsInterface()) )
WriteError(TXT_PROPERTY_ACCESSOR_MUST_BE_IMPLEMENTED, file, node);
// Setup the signature for the set accessor method
returnType = asCDataType::CreatePrimitive(ttVoid, false);
paramModifiers.PushLast(asTM_NONE);
paramNames.PushLast("value");
paramTypes.PushLast(emulatedType);
defaultArgs.PushLast(0);
name = "set_" + emulatedName;
success = true;
}
else
WriteError(TXT_UNRECOGNIZED_VIRTUAL_PROPERTY_NODE, file, node);
if( success )
{
if( !isExistingShared )
RegisterScriptFunction(funcNode, file, objType, isInterface, isGlobalFunction, ns, false, false, name, returnType, paramNames, paramTypes, paramModifiers, defaultArgs, isConst, false, false, isPrivate, isProtected, isOverride, isFinal, false);
else
{
// Free the funcNode as it won't be used
if( funcNode ) funcNode->Destroy(engine);
// Should validate that the function really exists in the class/interface
bool found = false;
for( asUINT n = 0; n < objType->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[objType->methods[n]];
if( func->name == name &&
func->IsSignatureExceptNameEqual(returnType, paramTypes, paramModifiers, objType, isConst) )
{
found = true;
break;
}
}
if( !found )
{
asCString str;
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, objType->GetName());
WriteError(str, file, node);
}
}
}
node = next;
};
mainNode->Destroy(engine);
return 0;
}
int asCBuilder::RegisterImportedFunction(int importID, asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns)
{
asCString name;
asCDataType returnType;
asCArray<asCString> parameterNames;
asCArray<asCDataType> parameterTypes;
asCArray<asETypeModifiers> inOutFlags;
asCArray<asCString *> defaultArgs;
bool isConstMethod, isOverride, isFinal, isConstructor, isDestructor, isPrivate, isProtected, isShared;
if( ns == 0 )
ns = engine->nameSpaces[0];
GetParsedFunctionDetails(node->firstChild, file, 0, name, returnType, parameterNames, parameterTypes, inOutFlags, defaultArgs, isConstMethod, isConstructor, isDestructor, isPrivate, isProtected, isOverride, isFinal, isShared, ns);
CheckNameConflict(name.AddressOf(), node, file, ns);
// Check that the same function hasn't been registered already in the namespace
asCArray<int> funcs;
GetFunctionDescriptions(name.AddressOf(), funcs, ns);
for( asUINT n = 0; n < funcs.GetLength(); ++n )
{
asCScriptFunction *func = GetFunctionDescription(funcs[n]);
if( func->IsSignatureExceptNameAndReturnTypeEqual(parameterTypes, inOutFlags, 0, false) )
{
WriteError(TXT_FUNCTION_ALREADY_EXIST, file, node);
break;
}
}
// Read the module name as well
asCScriptNode *nd = node->lastChild;
asASSERT( nd->nodeType == snConstant && nd->tokenType == ttStringConstant );
asCString moduleName;
moduleName.Assign(&file->code[nd->tokenPos+1], nd->tokenLength-2);
node->Destroy(engine);
// Register the function
module->AddImportedFunction(importID, name, returnType, parameterTypes, inOutFlags, defaultArgs, ns, moduleName);
return 0;
}
asCScriptFunction *asCBuilder::GetFunctionDescription(int id)
{
// TODO: import: This should be improved when the imported functions are removed
// Get the description from the engine
if( (id & FUNC_IMPORTED) == 0 )
return engine->scriptFunctions[id];
else
return engine->importedFunctions[id & ~FUNC_IMPORTED]->importedFunctionSignature;
}
void asCBuilder::GetFunctionDescriptions(const char *name, asCArray<int> &funcs, asSNameSpace *ns)
{
asUINT n;
// Get the script declared global functions
{
const asCArray<unsigned int> &idxs = module->globalFunctions.GetIndexes(ns, name);
for( n = 0; n < idxs.GetLength(); n++ )
{
const asCScriptFunction *f = module->globalFunctions.Get(idxs[n]);
asASSERT( f->objectType == 0 );
funcs.PushLast(f->id);
}
}
{
const asCArray<unsigned int> &idxs = module->importedGlobalFunctions.GetIndexes(ns, name);
for( n = 0; n < idxs.GetLength(); n++ )
{
const asCScriptFunction *f = module->importedGlobalFunctions.Get(idxs[n]);
asASSERT( f->objectType == 0 );
funcs.PushLast(f->id);
}
}
// Add the imported functions
// TODO: optimize: Linear search: This is probably not that critial. Also bindInformation will probably be removed in near future
for( n = 0; n < module->bindInformations.GetLength(); n++ )
{
if( module->bindInformations[n]->importedFunctionSignature->name == name &&
module->bindInformations[n]->importedFunctionSignature->nameSpace == ns )
funcs.PushLast(module->bindInformations[n]->importedFunctionSignature->id);
}
// Add the registered global functions
const asCArray<unsigned int> &idxs2 = engine->registeredGlobalFuncs.GetIndexes(ns, name);
for( n = 0; n < idxs2.GetLength(); n++ )
{
asCScriptFunction *f = engine->registeredGlobalFuncs.Get(idxs2[n]);
// Verify if the module has access to the function
if( module->accessMask & f->accessMask )
{
funcs.PushLast(f->id);
}
}
}
// scope is only informed when looking for a base class' method
void asCBuilder::GetObjectMethodDescriptions(const char *name, asCObjectType *objectType, asCArray<int> &methods, bool objIsConst, const asCString &scope, asCScriptNode *errNode, asCScriptCode *script)
{
if( scope != "" )
{
// If searching with a scope informed, then the node and script must also be informed for potential error reporting
asASSERT( errNode && script );
// If the scope contains ::identifier, then use the last identifier as the class name and the rest of it as the namespace
// TODO: child funcdef: A scope can include a template type, e.g. array<ns::type>
int n = scope.FindLast("::");
asCString className = n >= 0 ? scope.SubString(n+2) : scope;
asCString nsName = n >= 0 ? scope.SubString(0, n) : "";
// If a namespace was specifically defined, then this must be used
asSNameSpace *ns = 0;
if (n >= 0)
{
if (nsName == "")
ns = engine->nameSpaces[0];
else
ns = GetNameSpaceByString(nsName, objectType->nameSpace, errNode, script, 0, false);
// If the namespace isn't found return silently and let the calling
// function report the error if it cannot resolve the symbol
if (ns == 0)
return;
}
// Find the base class with the specified scope
while (objectType)
{
// If the name and namespace matches it is the correct class. If no
// specific namespace was given, then don't compare the namespace
if (objectType->name == className && (ns == 0 || objectType->nameSpace == ns))
break;
objectType = objectType->derivedFrom;
}
// If the scope is not any of the base classes, then return no methods
if( objectType == 0 )
return;
}
// Find the methods in the object that match the name
// TODO: optimize: Improve linear search
for( asUINT n = 0; n < objectType->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[objectType->methods[n]];
if( func->name == name &&
(!objIsConst || func->isReadOnly) &&
(func->accessMask & module->accessMask) )
{
// When the scope is defined the returned methods should be the true methods, not the virtual method stubs
if( scope == "" )
methods.PushLast(engine->scriptFunctions[objectType->methods[n]]->id);
else
{
asCScriptFunction *virtFunc = engine->scriptFunctions[objectType->methods[n]];
asCScriptFunction *realFunc = objectType->virtualFunctionTable[virtFunc->vfTableIdx];
methods.PushLast(realFunc->id);
}
}
}
}
#endif
void asCBuilder::WriteInfo(const asCString &scriptname, const asCString &message, int r, int c, bool pre)
{
// Need to store the pre message in a structure
if( pre )
{
engine->preMessage.isSet = true;
engine->preMessage.c = c;
engine->preMessage.r = r;
engine->preMessage.message = message;
engine->preMessage.scriptname = scriptname;
}
else
{
engine->preMessage.isSet = false;
if( !silent )
engine->WriteMessage(scriptname.AddressOf(), r, c, asMSGTYPE_INFORMATION, message.AddressOf());
}
}
void asCBuilder::WriteInfo(const asCString &message, asCScriptCode *file, asCScriptNode *node)
{
int r = 0, c = 0;
if( node )
file->ConvertPosToRowCol(node->tokenPos, &r, &c);
WriteInfo(file->name, message, r, c, false);
}
void asCBuilder::WriteError(const asCString &message, asCScriptCode *file, asCScriptNode *node)
{
int r = 0, c = 0;
if( node && file )
file->ConvertPosToRowCol(node->tokenPos, &r, &c);
WriteError(file ? file->name : asCString(""), message, r, c);
}
void asCBuilder::WriteError(const asCString &scriptname, const asCString &message, int r, int c)
{
numErrors++;
if( !silent )
engine->WriteMessage(scriptname.AddressOf(), r, c, asMSGTYPE_ERROR, message.AddressOf());
}
void asCBuilder::WriteWarning(const asCString &scriptname, const asCString &message, int r, int c)
{
if( engine->ep.compilerWarnings )
{
numWarnings++;
if( !silent )
engine->WriteMessage(scriptname.AddressOf(), r, c, asMSGTYPE_WARNING, message.AddressOf());
}
}
void asCBuilder::WriteWarning(const asCString &message, asCScriptCode *file, asCScriptNode *node)
{
int r = 0, c = 0;
if( node && file )
file->ConvertPosToRowCol(node->tokenPos, &r, &c);
WriteWarning(file ? file->name : asCString(""), message, r, c);
}
// TODO: child funcdef: Should try to eliminate this function. GetNameSpaceFromNode is more complete
asCString asCBuilder::GetScopeFromNode(asCScriptNode *node, asCScriptCode *script, asCScriptNode **next)
{
if (node->nodeType != snScope)
{
if (next)
*next = node;
return "";
}
asCString scope;
asCScriptNode *sn = node->firstChild;
if( sn->tokenType == ttScope )
{
scope = "::";
sn = sn->next;
}
// TODO: child funcdef: A scope can have a template type as the innermost
while( sn && sn->next && sn->next->tokenType == ttScope )
{
asCString tmp;
tmp.Assign(&script->code[sn->tokenPos], sn->tokenLength);
if( scope != "" && scope != "::" )
scope += "::";
scope += tmp;
sn = sn->next->next;
}
if( next )
*next = node->next;
return scope;
}
asSNameSpace *asCBuilder::GetNameSpaceFromNode(asCScriptNode *node, asCScriptCode *script, asSNameSpace *implicitNs, asCScriptNode **next, asCObjectType **objType)
{
if (objType)
*objType = 0;
// If no scope has been informed, then return the implicit namespace
if (node->nodeType != snScope)
{
if (next)
*next = node;
return implicitNs ? implicitNs : engine->nameSpaces[0];
}
if (next)
*next = node->next;
asCString scope;
asCScriptNode *sn = node->firstChild;
if (sn && sn->tokenType == ttScope)
{
scope = "::";
sn = sn->next;
}
while (sn)
{
if (sn->next->tokenType == ttScope)
{
asCString tmp;
tmp.Assign(&script->code[sn->tokenPos], sn->tokenLength);
if (scope != "" && scope != "::")
scope += "::";
scope += tmp;
sn = sn->next->next;
}
else
{
// This is a template type
asASSERT(sn->next->nodeType == snDataType);
asSNameSpace *ns = implicitNs;
if (scope != "")
ns = engine->FindNameSpace(scope.AddressOf());
asCString templateName(&script->code[sn->tokenPos], sn->tokenLength);
asCObjectType *templateType = GetObjectType(templateName.AddressOf(), ns);
if (templateType == 0 || (templateType->flags & asOBJ_TEMPLATE) == 0)
{
// TODO: child funcdef: Report error
return ns;
}
if (objType)
*objType = GetTemplateInstanceFromNode(sn, script, templateType, implicitNs, 0);
// Return no namespace, since this is an object type
return 0;
}
}
asCTypeInfo *ti = 0;
asSNameSpace *ns = GetNameSpaceByString(scope, implicitNs ? implicitNs : engine->nameSpaces[0], node, script, &ti);
if (ti && objType)
*objType = ti->CastToObjectType();
return ns;
}
asSNameSpace *asCBuilder::GetNameSpaceByString(const asCString &nsName, asSNameSpace *implicitNs, asCScriptNode *errNode, asCScriptCode *script, asCTypeInfo **scopeType, bool isRequired)
{
if( scopeType )
*scopeType = 0;
asSNameSpace *ns = implicitNs;
if( nsName == "::" )
ns = engine->nameSpaces[0];
else if( nsName != "" )
{
ns = engine->FindNameSpace(nsName.AddressOf());
if (ns == 0 && scopeType)
{
asCString typeName;
asCString searchNs;
// Split the scope with at the inner most ::
int pos = nsName.FindLast("::");
bool recursive = false;
if (pos >= 0)
{
// Fully qualified namespace
typeName = nsName.SubString(pos + 2);
searchNs = nsName.SubString(0, pos);
}
else
{
// Partially qualified, use the implicit namespace and then search recursively for the type
typeName = nsName;
searchNs = implicitNs->name;
recursive = true;
}
asSNameSpace *nsTmp = searchNs == "::" ? engine->nameSpaces[0] : engine->FindNameSpace(searchNs.AddressOf());
asCTypeInfo *ti = 0;
while( !ti && nsTmp )
{
// Check if the typeName is an existing type in the namespace
ti = GetType(typeName.AddressOf(), nsTmp, 0);
if (ti)
{
// The informed scope is not a namespace, but it does match a type
*scopeType = ti;
return 0;
}
nsTmp = recursive ? engine->GetParentNameSpace(nsTmp) : 0;
}
}
if (ns == 0 && isRequired)
{
asCString msg;
msg.Format(TXT_NAMESPACE_s_DOESNT_EXIST, nsName.AddressOf());
WriteError(msg, script, errNode);
}
}
return ns;
}
asCDataType asCBuilder::CreateDataTypeFromNode(asCScriptNode *node, asCScriptCode *file, asSNameSpace *implicitNamespace, bool acceptHandleForScope, asCObjectType *currentType)
{
asASSERT(node->nodeType == snDataType);
asCDataType dt;
asCScriptNode *n = node->firstChild;
bool isConst = false;
bool isImplicitHandle = false;
if( n->tokenType == ttConst )
{
isConst = true;
n = n->next;
}
// Determine namespace (or parent type) to search for the data type in
asCObjectType *parentType = 0;
asSNameSpace *ns = GetNameSpaceFromNode(n, file, implicitNamespace, &n, &parentType);
if( ns == 0 && parentType == 0 )
{
// The namespace and parent type doesn't exist. Return a dummy type instead.
dt = asCDataType::CreatePrimitive(ttInt, false);
return dt;
}
if( n->tokenType == ttIdentifier )
{
bool found = false;
asCString str;
str.Assign(&file->code[n->tokenPos], n->tokenLength);
// Recursively search parent namespaces for matching type
asSNameSpace *origNs = ns;
asCObjectType *origParentType = parentType;
while( (ns || parentType) && !found )
{
asCTypeInfo *ti = 0;
if (currentType)
{
// If this is for a template type, then we must first determine if the
// identifier matches any of the template subtypes
if (currentType->flags & asOBJ_TEMPLATE)
{
for (asUINT subtypeIndex = 0; subtypeIndex < currentType->templateSubTypes.GetLength(); subtypeIndex++)
{
asCTypeInfo *type = currentType->templateSubTypes[subtypeIndex].GetTypeInfo();
if (type && str == type->name)
{
ti = type;
break;
}
}
}
if (ti == 0)
{
// Check if the type is a child type of the current type
ti = GetFuncDef(str.AddressOf(), 0, currentType);
if (ti)
{
dt = asCDataType::CreateType(ti, false);
found = true;
}
}
}
if( ti == 0 )
ti = GetType(str.AddressOf(), ns, parentType);
if( ti == 0 && !module && currentType )
ti = GetTypeFromTypesKnownByObject(str.AddressOf(), currentType);
if( ti && !found )
{
found = true;
if( ti->flags & asOBJ_IMPLICIT_HANDLE )
isImplicitHandle = true;
// Make sure the module has access to the object type
if( !module || (module->accessMask & ti->accessMask) )
{
if( asOBJ_TYPEDEF == (ti->flags & asOBJ_TYPEDEF) )
{
// TODO: typedef: A typedef should be considered different from the original type (though with implicit conversions between the two)
// Create primitive data type based on object flags
dt = ti->CastToTypedefType()->aliasForType;
dt.MakeReadOnly(isConst);
}
else
{
if( ti->flags & asOBJ_TEMPLATE )
{
ti = GetTemplateInstanceFromNode(n, file, ti->CastToObjectType(), implicitNamespace, currentType, &n);
if (ti == 0)
{
// Return a dummy
return asCDataType::CreatePrimitive(ttInt, false);
}
}
else if( n && n->next && n->next->nodeType == snDataType )
{
asCString msg;
msg.Format(TXT_TYPE_s_NOT_TEMPLATE, ti->name.AddressOf());
WriteError(msg, file, n);
}
// Create object data type
if( ti )
dt = asCDataType::CreateType(ti, isConst);
else
dt = asCDataType::CreatePrimitive(ttInt, isConst);
}
}
else
{
asCString msg;
msg.Format(TXT_TYPE_s_NOT_AVAILABLE_FOR_MODULE, (const char *)str.AddressOf());
WriteError(msg, file, n);
dt.SetTokenType(ttInt);
}
}
if( !found )
{
// Try to find it in the parent namespace
if( ns )
ns = engine->GetParentNameSpace(ns);
if (parentType)
parentType = 0;
}
}
if( !found )
{
asCString msg;
if( origNs && origNs->name == "" )
msg.Format(TXT_IDENTIFIER_s_NOT_DATA_TYPE_IN_GLOBAL_NS, str.AddressOf());
else if (origNs)
msg.Format(TXT_IDENTIFIER_s_NOT_DATA_TYPE_IN_NS_s, str.AddressOf(), origNs->name.AddressOf());
else
{
// TODO: child funcdef: Message should explain that the identifier is not a type of the parent type
asCDataType pt = asCDataType::CreateType(origParentType, false);
msg.Format(TXT_IDENTIFIER_s_NOT_DATA_TYPE_IN_NS_s, str.AddressOf(), pt.Format(origParentType->nameSpace, false).AddressOf());
}
WriteError(msg, file, n);
dt = asCDataType::CreatePrimitive(ttInt, isConst);
return dt;
}
}
else if( n->tokenType == ttAuto )
{
dt = asCDataType::CreateAuto(isConst);
}
else
{
// Create primitive data type
dt = asCDataType::CreatePrimitive(n->tokenType, isConst);
}
// Determine array dimensions and object handles
n = n->next;
while( n && (n->tokenType == ttOpenBracket || n->tokenType == ttHandle) )
{
if( n->tokenType == ttOpenBracket )
{
// Make sure the sub type can be instantiated
if( !dt.CanBeInstantiated() )
{
asCString str;
if( dt.IsAbstractClass() )
str.Format(TXT_ABSTRACT_CLASS_s_CANNOT_BE_INSTANTIATED, dt.Format(ns).AddressOf());
else if( dt.IsInterface() )
str.Format(TXT_INTERFACE_s_CANNOT_BE_INSTANTIATED, dt.Format(ns).AddressOf());
else
// TODO: Improve error message to explain why
str.Format(TXT_DATA_TYPE_CANT_BE_s, dt.Format(ns).AddressOf());
WriteError(str, file, n);
}
// Make the type an array (or multidimensional array)
if( dt.MakeArray(engine, module) < 0 )
{
WriteError(TXT_NO_DEFAULT_ARRAY_TYPE, file, n);
break;
}
}
else
{
// Make the type a handle
if( dt.IsObjectHandle() )
{
WriteError(TXT_HANDLE_OF_HANDLE_IS_NOT_ALLOWED, file, n);
break;
}
else if( dt.MakeHandle(true, acceptHandleForScope) < 0 )
{
WriteError(TXT_OBJECT_HANDLE_NOT_SUPPORTED, file, n);
break;
}
}
n = n->next;
}
if( isImplicitHandle )
{
// Make the type a handle
if( dt.MakeHandle(true, acceptHandleForScope) < 0 )
WriteError(TXT_OBJECT_HANDLE_NOT_SUPPORTED, file, n);
}
return dt;
}
asCObjectType *asCBuilder::GetTemplateInstanceFromNode(asCScriptNode *node, asCScriptCode *file, asCObjectType *templateType, asSNameSpace *implicitNamespace, asCObjectType *currentType, asCScriptNode **next)
{
// Check if the subtype is a type or the template's subtype
// if it is the template's subtype then this is the actual template type,
// orderwise it is a template instance.
// Only do this for application registered interface, as the
// scripts cannot implement templates.
asCArray<asCDataType> subTypes;
asUINT subtypeIndex;
asCScriptNode *n = node;
while (n && n->next && n->next->nodeType == snDataType)
{
n = n->next;
// When parsing function definitions for template registrations (currentType != 0) it is necessary
// to pass in the current template type to the recursive call since it is this ones sub-template types
// that should be allowed.
asCDataType subType = CreateDataTypeFromNode(n, file, implicitNamespace, false, module ? 0 : (currentType ? currentType : templateType));
subTypes.PushLast(subType);
if (subType.IsReadOnly())
{
asCString msg;
msg.Format(TXT_TMPL_SUBTYPE_MUST_NOT_BE_READ_ONLY);
WriteError(msg, file, n);
// Return a dummy
return 0;
}
}
if (next)
*next = n;
if (subTypes.GetLength() != templateType->templateSubTypes.GetLength())
{
asCString msg;
msg.Format(TXT_TMPL_s_EXPECTS_d_SUBTYPES, templateType->name.AddressOf(), int(templateType->templateSubTypes.GetLength()));
WriteError(msg, file, node);
// Return a dummy
return 0;
}
// Check if any of the given subtypes are different from the template's declared subtypes
bool isDifferent = false;
for (subtypeIndex = 0; subtypeIndex < subTypes.GetLength(); subtypeIndex++)
{
if (subTypes[subtypeIndex].GetTypeInfo() != templateType->templateSubTypes[subtypeIndex].GetTypeInfo())
{
isDifferent = true;
break;
}
}
if (isDifferent)
{
// This is a template instance
// Need to find the correct object type
asCObjectType *otInstance = engine->GetTemplateInstanceType(templateType, subTypes, module);
if (otInstance && otInstance->scriptSectionIdx < 0)
{
// If this is the first time the template instance is used, store where it was declared from
otInstance->scriptSectionIdx = engine->GetScriptSectionNameIndex(file->name.AddressOf());
int row, column;
file->ConvertPosToRowCol(n->tokenPos, &row, &column);
otInstance->declaredAt = (row & 0xFFFFF) | (column << 20);
}
if (!otInstance)
{
asCString sub = subTypes[0].Format(templateType->nameSpace);
for (asUINT s = 1; s < subTypes.GetLength(); s++)
{
sub += ",";
sub += subTypes[s].Format(templateType->nameSpace);
}
asCString msg;
msg.Format(TXT_INSTANCING_INVLD_TMPL_TYPE_s_s, templateType->name.AddressOf(), sub.AddressOf());
WriteError(msg, file, n);
}
return otInstance;
}
return templateType;
}
asCDataType asCBuilder::ModifyDataTypeFromNode(const asCDataType &type, asCScriptNode *node, asCScriptCode *file, asETypeModifiers *inOutFlags, bool *autoHandle)
{
asCDataType dt = type;
if( inOutFlags ) *inOutFlags = asTM_NONE;
// Is the argument sent by reference?
asCScriptNode *n = node->firstChild;
if( n && n->tokenType == ttAmp )
{
dt.MakeReference(true);
n = n->next;
if( n )
{
if( inOutFlags )
{
if( n->tokenType == ttIn )
*inOutFlags = asTM_INREF;
else if( n->tokenType == ttOut )
*inOutFlags = asTM_OUTREF;
else if( n->tokenType == ttInOut )
*inOutFlags = asTM_INOUTREF;
else
asASSERT(false);
}
n = n->next;
}
else
{
if( inOutFlags )
*inOutFlags = asTM_INOUTREF; // ttInOut
}
if( !engine->ep.allowUnsafeReferences &&
inOutFlags && *inOutFlags == asTM_INOUTREF )
{
// Verify that the base type support &inout parameter types
if( !dt.IsObject() || dt.IsObjectHandle() || !((dt.GetTypeInfo()->flags & asOBJ_NOCOUNT) || (dt.GetTypeInfo()->CastToObjectType()->beh.addref && dt.GetTypeInfo()->CastToObjectType()->beh.release)) )
WriteError(TXT_ONLY_OBJECTS_MAY_USE_REF_INOUT, file, node->firstChild);
}
}
if( autoHandle ) *autoHandle = false;
if( n && n->tokenType == ttPlus )
{
// Autohandles are not supported for types with NOCOUNT
// If the type is not a handle then there was an error with building the type, but
// this error would already have been reported so no need to report another error here
if( dt.IsObjectHandle() && (dt.GetTypeInfo()->flags & asOBJ_NOCOUNT) )
WriteError(TXT_AUTOHANDLE_CANNOT_BE_USED_FOR_NOCOUNT, file, node->firstChild);
if( autoHandle ) *autoHandle = true;
}
return dt;
}
asCTypeInfo *asCBuilder::GetType(const char *type, asSNameSpace *ns, asCObjectType *parentType)
{
asASSERT((ns == 0 && parentType) || (ns && parentType == 0));
if (ns)
{
asCTypeInfo *ti = engine->GetRegisteredType(type, ns);
if (!ti && module)
ti = module->GetType(type, ns);
return ti;
}
else
{
// Recursively check base classes
asCObjectType *currType = parentType;
while (currType)
{
for (asUINT n = 0; n < currType->childFuncDefs.GetLength(); n++)
{
asCFuncdefType *funcDef = currType->childFuncDefs[n];
if (funcDef && funcDef->name == type)
return funcDef;
}
currType = currType->derivedFrom;
}
}
return 0;
}
asCObjectType *asCBuilder::GetObjectType(const char *type, asSNameSpace *ns)
{
- return GetType(type, ns, 0)->CastToObjectType();
+ auto* typeInfo = GetType(type, ns, 0);
+ if(!typeInfo)
+ return nullptr;
+ return typeInfo->CastToObjectType();
}
#ifndef AS_NO_COMPILER
// This function will return true if there are any types in the engine or module
// with the given name. The namespace is ignored in this verification.
bool asCBuilder::DoesTypeExist(const asCString &type)
{
asUINT n;
// This function is only used when parsing expressions for building bytecode
// and this is only done after all types are known. For this reason the types
// can be safely cached in a map for quick lookup. Once the builder is released
// the cache will also be destroyed thus avoiding unnecessary memory consumption.
if( !hasCachedKnownTypes )
{
// Only do this once
hasCachedKnownTypes = true;
// Add registered types
asSMapNode<asSNameSpaceNamePair, asCTypeInfo*> *cursor;
engine->allRegisteredTypes.MoveFirst(&cursor);
while( cursor )
{
if( !knownTypes.MoveTo(0, cursor->key.name) )
knownTypes.Insert(cursor->key.name, true);
engine->allRegisteredTypes.MoveNext(&cursor, cursor);
}
if (module)
{
// Add script classes and interfaces
for (n = 0; n < module->classTypes.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->classTypes[n]->name))
knownTypes.Insert(module->classTypes[n]->name, true);
// Add script enums
for (n = 0; n < module->enumTypes.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->enumTypes[n]->name))
knownTypes.Insert(module->enumTypes[n]->name, true);
// Add script typedefs
for (n = 0; n < module->typeDefs.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->typeDefs[n]->name))
knownTypes.Insert(module->typeDefs[n]->name, true);
// Add script funcdefs
for (n = 0; n < module->funcDefs.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->funcDefs[n]->name))
knownTypes.Insert(module->funcDefs[n]->name, true);
//Add anything that was imported
for (n = 0; n < module->importedClassTypes.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->importedClassTypes[n]->name))
knownTypes.Insert(module->importedClassTypes[n]->name, true);
for (n = 0; n < module->importedEnumTypes.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->importedEnumTypes[n]->name))
knownTypes.Insert(module->importedEnumTypes[n]->name, true);
for (n = 0; n < module->importedTypeDefs.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->importedTypeDefs[n]->name))
knownTypes.Insert(module->importedTypeDefs[n]->name, true);
for (n = 0; n < module->importedFuncDefs.GetLength(); n++)
if (!knownTypes.MoveTo(0, module->importedFuncDefs[n]->name))
knownTypes.Insert(module->importedFuncDefs[n]->name, true);
}
}
// Check if the type is known
return knownTypes.MoveTo(0, type);
}
#endif
asCTypeInfo *asCBuilder::GetTypeFromTypesKnownByObject(const char *type, asCObjectType *currentType)
{
if (currentType->name == type)
return currentType;
asUINT n;
asCTypeInfo *found = 0;
for (n = 0; found == 0 && n < currentType->properties.GetLength(); n++)
if (currentType->properties[n]->type.GetTypeInfo() &&
currentType->properties[n]->type.GetTypeInfo()->name == type)
found = currentType->properties[n]->type.GetTypeInfo();
for (n = 0; found == 0 && n < currentType->methods.GetLength(); n++)
{
asCScriptFunction *func = engine->scriptFunctions[currentType->methods[n]];
if (func->returnType.GetTypeInfo() &&
func->returnType.GetTypeInfo()->name == type)
found = func->returnType.GetTypeInfo();
for (asUINT f = 0; found == 0 && f < func->parameterTypes.GetLength(); f++)
if (func->parameterTypes[f].GetTypeInfo() &&
func->parameterTypes[f].GetTypeInfo()->name == type)
found = func->parameterTypes[f].GetTypeInfo();
}
if (found)
{
// In case we find a template instance it mustn't be returned
// because it is not known if the subtype is really matching
if (found->flags & asOBJ_TEMPLATE)
return 0;
}
return found;
}
asCFuncdefType *asCBuilder::GetFuncDef(const char *type, asSNameSpace *ns, asCObjectType *parentType)
{
asASSERT((ns == 0 && parentType) || (ns && parentType == 0));
if (ns)
{
for (asUINT n = 0; n < engine->registeredFuncDefs.GetLength(); n++)
{
asCFuncdefType *funcDef = engine->registeredFuncDefs[n];
// TODO: access: Only return the definitions that the module has access to
if (funcDef && funcDef->nameSpace == ns && funcDef->name == type)
return funcDef;
}
if (module)
{
for (asUINT n = 0; n < module->funcDefs.GetLength(); n++)
{
asCFuncdefType *funcDef = module->funcDefs[n];
if (funcDef && funcDef->nameSpace == ns && funcDef->name == type)
return funcDef;
}
}
}
else
{
// Recursively check base classes
asCObjectType *currType = parentType;
while (currType)
{
for (asUINT n = 0; n < currType->childFuncDefs.GetLength(); n++)
{
asCFuncdefType *funcDef = currType->childFuncDefs[n];
if (funcDef && funcDef->name == type)
return funcDef;
}
currType = currType->derivedFrom;
}
}
return 0;
}
#ifndef AS_NO_COMPILER
int asCBuilder::GetEnumValueFromType(asCEnumType *type, const char *name, asCDataType &outDt, asDWORD &outValue)
{
if( !type || !(type->flags & asOBJ_ENUM) )
return 0;
for( asUINT n = 0; n < type->enumValues.GetLength(); ++n )
{
if( type->enumValues[n]->name == name )
{
outDt = asCDataType::CreateType(type, true);
outValue = type->enumValues[n]->value;
return 1;
}
}
return 0;
}
int asCBuilder::GetEnumValue(const char *name, asCDataType &outDt, asDWORD &outValue, asSNameSpace *ns)
{
bool found = false;
// Search all available enum types
asUINT t;
for( t = 0; t < engine->registeredEnums.GetLength(); t++ )
{
asCEnumType *et = engine->registeredEnums[t];
if( ns != et->nameSpace ) continue;
// Don't bother with types the module doesn't have access to
if( (et->accessMask & module->accessMask) == 0 )
continue;
if( GetEnumValueFromType(et, name, outDt, outValue) )
{
if( !found )
found = true;
else
{
// Found more than one value in different enum types
return 2;
}
}
}
for( t = 0; t < module->enumTypes.GetLength(); t++ )
{
asCEnumType *et = module->enumTypes[t];
if( ns != et->nameSpace ) continue;
if( GetEnumValueFromType(et, name, outDt, outValue) )
{
if( !found )
found = true;
else
{
// Found more than one value in different enum types
return 2;
}
}
}
for( t = 0; t < module->importedEnumTypes.GetLength(); t++ )
{
asCEnumType *et = module->importedEnumTypes[t];
if( ns != et->nameSpace ) continue;
if( GetEnumValueFromType(et, name, outDt, outValue) )
{
if( !found )
found = true;
else
{
// Found more than one value in different enum types
return 2;
}
}
}
if( found )
return 1;
// Didn't find any value
return 0;
}
#endif // AS_NO_COMPILER
END_AS_NAMESPACE
diff --git a/source/angelscript/source/as_compiler.cpp b/source/angelscript/source/as_compiler.cpp
index 9cf6329..db219dc 100644
--- a/source/angelscript/source/as_compiler.cpp
+++ b/source/angelscript/source/as_compiler.cpp
@@ -1,14811 +1,14813 @@
/*
AngelCode Scripting Library
Copyright (c) 2003-2016 Andreas Jonsson
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
The original version of this library can be located at:
http://www.angelcode.com/angelscript/
Andreas Jonsson
andreas@angelcode.com
*/
//
// as_compiler.cpp
//
// The class that does the actual compilation of the functions
//
#include <math.h> // fmodf() pow()
#include "as_config.h"
#ifndef AS_NO_COMPILER
#include "as_compiler.h"
#include "as_tokendef.h"
#include "as_tokenizer.h"
#include "as_string_util.h"
#include "as_texts.h"
#include "as_parser.h"
#include "as_debug.h"
#include "as_context.h" // as_powi()
BEGIN_AS_NAMESPACE
//
// The calling convention rules for script functions:
// - If a class method returns a reference, the caller must guarantee the object pointer stays alive until the function returns, and the reference is no longer going to be used
// - If a class method doesn't return a reference, it must guarantee by itself that the this pointer stays alive during the function call. If no outside access is made, then the function is guaranteed to stay alive and nothing needs to be done
// - The object pointer is always passed as the first argument, position 0
// - If the function returns a value type the caller must reserve the memory for this and pass the pointer as the first argument after the object pointer
//
// TODO: I must correct the interpretation of a reference to objects in the compiler.
// A reference should mean that a pointer to the object is on the stack.
// No expression should end up as non-references to objects, as the actual object is
// never put on the stack.
// Local variables are declared as non-references, but the expression should be a reference to the variable.
// Function parameters of called functions can also be non-references, but in that case it means the
// object will be passed by value (currently on the heap, which will be moved to the application stack).
//
// The compiler shouldn't use the asCDataType::IsReference. The datatype should always be stored as non-references.
// Instead the compiler should keep track of references in TypeInfo, where it should also state how the reference
// is currently stored, i.e. in variable, in register, on stack, etc.
asCCompiler::asCCompiler(asCScriptEngine *engine) : byteCode(engine)
{
builder = 0;
script = 0;
variables = 0;
isProcessingDeferredParams = false;
isCompilingDefaultArg = false;
noCodeOutput = 0;
}
asCCompiler::~asCCompiler()
{
while( variables )
{
asCVariableScope *var = variables;
variables = variables->parent;
asDELETE(var,asCVariableScope);
}
}
void asCCompiler::Reset(asCBuilder *in_builder, asCScriptCode *in_script, asCScriptFunction *in_outFunc)
{
this->builder = in_builder;
this->engine = in_builder->engine;
this->script = in_script;
this->outFunc = in_outFunc;
hasCompileErrors = false;
m_isConstructor = false;
m_isConstructorCalled = false;
m_classDecl = 0;
m_globalVar = 0;
nextLabel = 0;
breakLabels.SetLength(0);
continueLabels.SetLength(0);
numLambdas = 0;
byteCode.ClearAll();
}
int asCCompiler::CompileDefaultConstructor(asCBuilder *in_builder, asCScriptCode *in_script, asCScriptNode *in_node, asCScriptFunction *in_outFunc, sClassDeclaration *in_classDecl)
{
Reset(in_builder, in_script, in_outFunc);
m_classDecl = in_classDecl;
// Insert a JitEntry at the start of the function for JIT compilers
byteCode.InstrPTR(asBC_JitEntry, 0);
// Add a variable scope that might be needed to declare dummy variables
// in case the member initialization refers to undefined symbols.
AddVariableScope();
// Initialize the class members that have no explicit expression first. This will allow the
// base class' constructor to access these members without worry they will be uninitialized.
// This can happen if the base class' constructor calls a method that is overridden by the derived class
CompileMemberInitialization(&byteCode, true);
// If the class is derived from another, then the base class' default constructor must be called
if( outFunc->objectType->derivedFrom )
{
// Make sure the base class really has a default constructor
if( outFunc->objectType->derivedFrom->beh.construct == 0 )
Error(TEXT_BASE_DOESNT_HAVE_DEF_CONSTR, in_node);
// Call the base class' default constructor
byteCode.InstrSHORT(asBC_PSF, 0);
byteCode.Instr(asBC_RDSPtr);
byteCode.Call(asBC_CALL, outFunc->objectType->derivedFrom->beh.construct, AS_PTR_SIZE);
}
// Initialize the class members that explicit expressions afterwards. This allow the expressions
// to access the base class members without worry they will be uninitialized
CompileMemberInitialization(&byteCode, false);
byteCode.OptimizeLocally(tempVariableOffsets);
// If there are compile errors, there is no reason to build the final code
if( hasCompileErrors )
return -1;
// Pop the object pointer from the stack
byteCode.Ret(AS_PTR_SIZE);
// Count total variable size
int varSize = GetVariableOffset((int)variableAllocations.GetLength()) - 1;
outFunc->scriptData->variableSpace = varSize;
FinalizeFunction();
#ifdef AS_DEBUG
// DEBUG: output byte code
byteCode.DebugOutput(("__" + outFunc->objectType->name + "_" + outFunc->name + "__defconstr.txt").AddressOf(), in_outFunc);
#endif
return 0;
}
int asCCompiler::CompileFactory(asCBuilder *in_builder, asCScriptCode *in_script, asCScriptFunction *in_outFunc)
{
Reset(in_builder, in_script, in_outFunc);
// Insert a JitEntry at the start of the function for JIT compilers
byteCode.InstrPTR(asBC_JitEntry, 0);
// Find the corresponding constructor
asCDataType dt = asCDataType::CreateType(outFunc->returnType.GetTypeInfo(), false);
int constructor = 0;
for( unsigned int n = 0; n < dt.GetBehaviour()->factories.GetLength(); n++ )
{
if( dt.GetBehaviour()->factories[n] == outFunc->id )
{
constructor = dt.GetBehaviour()->constructors[n];
break;
}
}
// Allocate the class and instantiate it with the constructor
int varOffset = AllocateVariable(dt, true);
outFunc->scriptData->variableSpace = AS_PTR_SIZE;
byteCode.InstrSHORT(asBC_PSF, (short)varOffset);
// Copy all arguments to the top of the stack
// TODO: runtime optimize: Might be interesting to have a specific instruction for copying all arguments
int offset = (int)outFunc->GetSpaceNeededForArguments();
for( int a = int(outFunc->parameterTypes.GetLength()) - 1; a >= 0; a-- )
{
if( !outFunc->parameterTypes[a].IsPrimitive() ||
outFunc->parameterTypes[a].IsReference() )
{
offset -= AS_PTR_SIZE;
byteCode.InstrSHORT(asBC_PshVPtr, short(-offset));
}
else
{
if( outFunc->parameterTypes[a].GetSizeOnStackDWords() == 2 )
{
offset -= 2;
byteCode.InstrSHORT(asBC_PshV8, short(-offset));
}
else
{
offset -= 1;
byteCode.InstrSHORT(asBC_PshV4, short(-offset));
}
}
}
int argDwords = (int)outFunc->GetSpaceNeededForArguments();
byteCode.Alloc(asBC_ALLOC, dt.GetTypeInfo(), constructor, argDwords + AS_PTR_SIZE);
// Return a handle to the newly created object
byteCode.InstrSHORT(asBC_LOADOBJ, (short)varOffset);
byteCode.Ret(argDwords);
FinalizeFunction();
// Tell the virtual machine not to clean up parameters on exception
outFunc->dontCleanUpOnException = true;
/*
#ifdef AS_DEBUG
// DEBUG: output byte code
asCString args;
args.Format("%d", outFunc->parameterTypes.GetLength());
byteCode.DebugOutput(("__" + outFunc->name + "__factory" + args + ".txt").AddressOf(), engine);
#endif
*/
return 0;
}
void asCCompiler::FinalizeFunction()
{
TimeIt("asCCompiler::FinalizeFunction");
asASSERT( outFunc->scriptData );
asUINT n;
// Finalize the bytecode
byteCode.Finalize(tempVariableOffsets);
byteCode.ExtractObjectVariableInfo(outFunc);
// Compile the list of object variables for the exception handler
// Start with the variables allocated on the heap, and then the ones allocated on the stack
for( n = 0; n < variableAllocations.GetLength(); n++ )
{
if( (variableAllocations[n].IsObject() || variableAllocations[n].IsFuncdef()) && !variableAllocations[n].IsReference() )
{
if( variableIsOnHeap[n] )
{
outFunc->scriptData->objVariableTypes.PushLast(variableAllocations[n].GetTypeInfo());
outFunc->scriptData->objVariablePos.PushLast(GetVariableOffset(n));
}
}
}
outFunc->scriptData->objVariablesOnHeap = asUINT(outFunc->scriptData->objVariablePos.GetLength());
for( n = 0; n < variableAllocations.GetLength(); n++ )
{
if( (variableAllocations[n].IsObject() || variableAllocations[n].IsFuncdef()) && !variableAllocations[n].IsReference() )
{
if( !variableIsOnHeap[n] )
{
outFunc->scriptData->objVariableTypes.PushLast(variableAllocations[n].GetTypeInfo());
outFunc->scriptData->objVariablePos.PushLast(GetVariableOffset(n));
}
}
}
// Copy byte code to the function
asASSERT( outFunc->scriptData->byteCode.GetLength() == 0 );
outFunc->scriptData->byteCode.SetLength(byteCode.GetSize());
byteCode.Output(outFunc->scriptData->byteCode.AddressOf());
outFunc->AddReferences();
outFunc->scriptData->stackNeeded = byteCode.largestStackUsed + outFunc->scriptData->variableSpace;
outFunc->scriptData->lineNumbers = byteCode.lineNumbers;
// Extract the script section indexes too if there are any entries that are different from the function's script section
int lastIdx = outFunc->scriptData->scriptSectionIdx;
for( n = 0; n < byteCode.sectionIdxs.GetLength(); n++ )
{
if( byteCode.sectionIdxs[n] != lastIdx )
{
lastIdx = byteCode.sectionIdxs[n];
outFunc->scriptData->sectionIdxs.PushLast(byteCode.lineNumbers[n*2]);
outFunc->scriptData->sectionIdxs.PushLast(lastIdx);
}
}
}
// internal
int asCCompiler::SetupParametersAndReturnVariable(asCArray<asCString> &parameterNames, asCScriptNode *func)
{
int stackPos = 0;
if( outFunc->objectType )
stackPos = -AS_PTR_SIZE; // The first parameter is the pointer to the object
// Add the first variable scope, which the parameters and
// variables declared in the outermost statement block is
// part of.
AddVariableScope();
bool isDestructor = false;
asCDataType returnType;
// Examine return type
returnType = outFunc->returnType;
// Check if this is a constructor or destructor
if( returnType.GetTokenType() == ttVoid && outFunc->objectType )
{
if( outFunc->name[0] == '~' )
isDestructor = true;
else if( outFunc->objectType->name == outFunc->name )
m_isConstructor = true;
}
// Is the return type allowed?
if( returnType != asCDataType::CreatePrimitive(ttVoid, false) &&
!returnType.CanBeInstantiated() )
{
// TODO: Hasn't this been validated by the builder already?
asCString str;
str.Format(TXT_RETURN_CANT_BE_s, returnType.Format(outFunc->nameSpace).AddressOf());
Error(str, func);
}
// If the return type is a value type returned by value the address of the
// location where the value will be stored is pushed on the stack before
// the arguments
if( !(isDestructor || m_isConstructor) && outFunc->DoesReturnOnStack() )
stackPos -= AS_PTR_SIZE;
asCVariableScope vs(0);
// Declare parameters
asUINT n;
for( n = 0; n < parameterNames.GetLength(); n++ )
{
// Get the parameter type
asCDataType &type = outFunc->parameterTypes[n];
asETypeModifiers inoutFlag = n < outFunc->inOutFlags.GetLength() ? outFunc->inOutFlags[n] : asTM_NONE;
// Is the data type allowed?
// TODO: Hasn't this been validated by the builder already?
if( (type.IsReference() && inoutFlag != asTM_INOUTREF && !type.CanBeInstantiated()) ||
(!type.IsReference() && !type.CanBeInstantiated()) )
{
asCString parm = type.Format(outFunc->nameSpace);
if( inoutFlag == asTM_INREF )
parm += "in";
else if( inoutFlag == asTM_OUTREF )
parm += "out";
asCString str;
str.Format(TXT_PARAMETER_CANT_BE_s, parm.AddressOf());
Error(str, func);
}
// If the parameter has a name then declare it as variable
if( parameterNames[n] != "" )
{
asCString &name = parameterNames[n];
if( vs.DeclareVariable(name.AddressOf(), type, stackPos, true) < 0 )
{
// TODO: It might be an out-of-memory too
Error(TXT_PARAMETER_ALREADY_DECLARED, func);
}
// Add marker for variable declaration
byteCode.VarDecl((int)outFunc->scriptData->variables.GetLength());
outFunc->AddVariable(name, type, stackPos);
}
else
vs.DeclareVariable("", type, stackPos, true);
// Move to next parameter
stackPos -= type.GetSizeOnStackDWords();
}
for( n = asUINT(vs.variables.GetLength()); n-- > 0; )
variables->DeclareVariable(vs.variables[n]->name.AddressOf(), vs.variables[n]->type, vs.variables[n]->stackOffset, vs.variables[n]->onHeap);
variables->DeclareVariable("return", returnType, stackPos, true);
return stackPos;
}
void asCCompiler::CompileMemberInitialization(asCByteCode *bc, bool onlyDefaults)
{
asASSERT( m_classDecl );
// Initialize each member in the order they were declared
for( asUINT n = 0; n < outFunc->objectType->properties.GetLength(); n++ )
{
asCObjectProperty *prop = outFunc->objectType->properties[n];
// Check if the property has an initialization expression
asCScriptNode *declNode = 0;
asCScriptNode *initNode = 0;
asCScriptCode *initScript = 0;
for( asUINT m = 0; m < m_classDecl->propInits.GetLength(); m++ )
{
if( m_classDecl->propInits[m].name == prop->name )
{
declNode = m_classDecl->propInits[m].declNode;
initNode = m_classDecl->propInits[m].initNode;
initScript = m_classDecl->propInits[m].file;
break;
}
}
// If declNode is null, the property was inherited in which case
// it was already initialized by the base class' constructor
if( declNode )
{
if( initNode )
{
if( onlyDefaults )
continue;
#ifdef AS_NO_MEMBER_INIT
// Give an error as the initialization in the declaration has been disabled
asCScriptCode *origScript = script;
script = initScript;
Error("Initialization of members in declaration is not supported", initNode);
script = origScript;
// Clear the initialization node
initNode = 0;
initScript = script;
#else
// Re-parse the initialization expression as the parser now knows the types, which it didn't earlier
asCParser parser(builder);
int r = parser.ParseVarInit(initScript, initNode);
if( r < 0 )
continue;
initNode = parser.GetScriptNode();
#endif
}
else
{
if( !onlyDefaults )
continue;
}
#ifdef AS_NO_MEMBER_INIT
// The initialization will be done in the asCScriptObject constructor, so
// here we should just validate that the member has a default constructor
if( prop->type.IsObject() &&
!prop->type.IsObjectHandle() &&
(((prop->type.GetTypeInfo()->flags & asOBJ_REF) &&
prop->type.GetBehaviour()->factory == 0) ||
((prop->type.GetTypeInfo()->flags & asOBJ_VALUE) &&
prop->type.GetBehaviour()->construct == 0 &&
!(prop->type.GetTypeInfo()->flags & asOBJ_POD))) )
{
// Class has no default factory/constructor.
asCString str;
// TODO: funcdef: asCDataType should have a GetTypeName()
if( prop->type.GetFuncDef() )
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, prop->type.GetFuncDef()->GetName());
else
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, prop->type.GetTypeInfo()->GetName());
Error(str, declNode);
}
#else
// Temporarily set the script that is being compiled to where the member initialization is declared.
// The script can be different when including mixin classes from a different script section
asCScriptCode *origScript = script;
script = initScript;
// Add a line instruction with the position of the declaration
LineInstr(bc, declNode->tokenPos);
// Compile the initialization
asQWORD constantValue;
asCByteCode bcInit(engine);
CompileInitialization(initNode, &bcInit, prop->type, declNode, prop->byteOffset, &constantValue, 2);
bcInit.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&bcInit);
script = origScript;
#endif
}
}
}
// Entry
int asCCompiler::CompileFunction(asCBuilder *in_builder, asCScriptCode *in_script, asCArray<asCString> &in_parameterNames, asCScriptNode *in_func, asCScriptFunction *in_outFunc, sClassDeclaration *in_classDecl)
{
TimeIt("asCCompiler::CompileFunction");
Reset(in_builder, in_script, in_outFunc);
int buildErrors = builder->numErrors;
int stackPos = SetupParametersAndReturnVariable(in_parameterNames, in_func);
//--------------------------------------------
// Compile the statement block
if( m_isConstructor )
m_classDecl = in_classDecl;
// We need to parse the statement block now
asCScriptNode *blockBegin;
// If the function signature was implicit, e.g. virtual property accessor or
// lambda function, then the received node already is the statement block
if( in_func->nodeType != snStatementBlock )
blockBegin = in_func->lastChild;
else
blockBegin = in_func;
// TODO: memory: We can parse the statement block one statement at a time, thus save even more memory
// TODO: optimize: For large functions, the parsing of the statement block can take a long time. Presumably because a lot of memory needs to be allocated
asCParser parser(builder);
int r = parser.ParseStatementBlock(script, blockBegin);
if( r < 0 ) return -1;
asCScriptNode *block = parser.GetScriptNode();
// Reserve a label for the cleanup code
nextLabel++;
bool hasReturn;
asCByteCode bc(engine);
LineInstr(&bc, blockBegin->tokenPos);
CompileStatementBlock(block, false, &hasReturn, &bc);
LineInstr(&bc, blockBegin->tokenPos + blockBegin->tokenLength);
// Make sure there is a return in all paths (if not return type is void)
// Don't bother with this check if there are compiler errors, e.g. Unreachable code
if( !hasCompileErrors && outFunc->returnType != asCDataType::CreatePrimitive(ttVoid, false) )
{
if( hasReturn == false )
Error(TXT_NOT_ALL_PATHS_RETURN, blockBegin);
}
//------------------------------------------------
// Concatenate the bytecode
// Insert a JitEntry at the start of the function for JIT compilers
byteCode.InstrPTR(asBC_JitEntry, 0);
if( outFunc->objectType )
{
if( m_isConstructor )
{
if( outFunc->objectType->derivedFrom )
{
// Call the base class' default constructor unless called manually in the code
if( !m_isConstructorCalled )
{
if( outFunc->objectType->derivedFrom->beh.construct )
{
// Initialize members without explicit expression first
CompileMemberInitialization(&byteCode, true);
// Call base class' constructor
asCByteCode tmpBC(engine);
tmpBC.InstrSHORT(asBC_PSF, 0);
tmpBC.Instr(asBC_RDSPtr);
tmpBC.Call(asBC_CALL, outFunc->objectType->derivedFrom->beh.construct, AS_PTR_SIZE);
tmpBC.OptimizeLocally(tempVariableOffsets);
byteCode.AddCode(&tmpBC);
// Add the initialization of the members with explicit expressions
CompileMemberInitialization(&byteCode, false);
}
else
Error(TEXT_BASE_DOESNT_HAVE_DEF_CONSTR, blockBegin);
}
else
{
// Only initialize members that don't have an explicit expression
// The members that are explicitly initialized will be initialized after the call to base class' constructor
CompileMemberInitialization(&byteCode, true);
}
}
else
{
// Add the initialization of the members
CompileMemberInitialization(&byteCode, true);
CompileMemberInitialization(&byteCode, false);
}
}
}
// Add the code for the statement block
byteCode.AddCode(&bc);
// Count total variable size
int varSize = GetVariableOffset((int)variableAllocations.GetLength()) - 1;
outFunc->scriptData->variableSpace = varSize;
// Deallocate all local variables
int n;
for( n = (int)variables->variables.GetLength() - 1; n >= 0; n-- )
{
sVariable *v = variables->variables[n];
if( v->stackOffset > 0 )
{
// Call variables destructors
if( v->name != "return" && v->name != "return address" )
CallDestructor(v->type, v->stackOffset, v->onHeap, &byteCode);
DeallocateVariable(v->stackOffset);
}
}
// This is the label that return statements jump to
// in order to exit the function
byteCode.Label(0);
// Call destructors for function parameters
for( n = (int)variables->variables.GetLength() - 1; n >= 0; n-- )
{
sVariable *v = variables->variables[n];
if( v->stackOffset <= 0 )
{
// Call variable destructors here, for variables not yet destroyed
if( v->name != "return" && v->name != "return address" )
CallDestructor(v->type, v->stackOffset, v->onHeap, &byteCode);
}
// Do not deallocate parameters
}
// Check if the number of labels in the functions isn't too many to be handled
if( nextLabel >= (1<<15) )
Error(TXT_TOO_MANY_JUMP_LABELS, in_func);
// If there are compile errors, there is no reason to build the final code
if( hasCompileErrors || builder->numErrors != buildErrors )
return -1;
// At this point there should be no variables allocated
asASSERT(variableAllocations.GetLength() == freeVariables.GetLength());
// Remove the variable scope
RemoveVariableScope();
byteCode.Ret(-stackPos);
FinalizeFunction();
#ifdef AS_DEBUG
// DEBUG: output byte code
if( outFunc->objectType )
byteCode.DebugOutput(("__" + outFunc->objectType->name + "_" + outFunc->name + ".txt").AddressOf(), in_outFunc);
else
byteCode.DebugOutput(("__" + outFunc->name + ".txt").AddressOf(), in_outFunc);
#endif
return 0;
}
int asCCompiler::CallCopyConstructor(asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc, asCExprContext *arg, asCScriptNode *node, bool isGlobalVar, bool derefDest)
{
if( !type.IsObject() )
return 0;
// CallCopyConstructor should not be called for object handles.
asASSERT( !type.IsObjectHandle() );
asCArray<asCExprContext*> args;
args.PushLast(arg);
// The reference parameter must be pushed on the stack
asASSERT( arg->type.dataType.GetTypeInfo() == type.GetTypeInfo() );
// Since we're calling the copy constructor, we have to trust the function to not do
// anything stupid otherwise we will just enter a loop, as we try to make temporary
// copies of the argument in order to guarantee safety.
if( type.GetTypeInfo()->flags & asOBJ_REF )
{
asCExprContext ctx(engine);
int func = 0;
asSTypeBehaviour *beh = type.GetBehaviour();
if( beh ) func = beh->copyfactory;
if( func > 0 )
{
if( !isGlobalVar )
{
// Call factory and store the handle in the given variable
PerformFunctionCall(func, &ctx, false, &args, type.GetTypeInfo()->CastToObjectType(), true, offset);
// Pop the reference left by the function call
ctx.bc.Instr(asBC_PopPtr);
}
else
{
// Call factory
PerformFunctionCall(func, &ctx, false, &args, type.GetTypeInfo()->CastToObjectType());
// Store the returned handle in the global variable
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
ctx.bc.InstrPTR(asBC_REFCPY, type.GetTypeInfo());
ctx.bc.Instr(asBC_PopPtr);
ReleaseTemporaryVariable(ctx.type.stackOffset, &ctx.bc);
}
bc->AddCode(&ctx.bc);
return 0;
}
}
else
{
asSTypeBehaviour *beh = type.GetBehaviour();
int func = beh ? beh->copyconstruct : 0;
if( func > 0 )
{
// Push the address where the object will be stored on the stack, before the argument
// TODO: When the context is serializable this probably has to be changed, since this
// pointer can remain on the stack while the context is suspended. There is no
// risk the pointer becomes invalid though, there is just no easy way to serialize it.
asCByteCode tmp(engine);
if( isGlobalVar )
tmp.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
else if( isObjectOnHeap )
tmp.InstrSHORT(asBC_PSF, (short)offset);
tmp.AddCode(bc);
bc->AddCode(&tmp);
// When the object is allocated on the stack the object pointer
// must be pushed on the stack after the arguments
if( !isObjectOnHeap )
{
asASSERT( !isGlobalVar );
bc->InstrSHORT(asBC_PSF, (short)offset);
if( derefDest )
{
// The variable is a reference to the real location, so we need to dereference it
bc->Instr(asBC_RDSPtr);
}
}
asCExprContext ctx(engine);
PerformFunctionCall(func, &ctx, isObjectOnHeap, &args, type.GetTypeInfo()->CastToObjectType());
bc->AddCode(&ctx.bc);
// TODO: value on stack: This probably needs to be done in PerformFunctionCall
// Mark the object as initialized
if( !isObjectOnHeap )
bc->ObjInfo(offset, asOBJ_INIT);
return 0;
}
}
// Class has no copy constructor/factory.
asCString str;
str.Format(TXT_NO_COPY_CONSTRUCTOR_FOR_s, type.GetTypeInfo()->GetName());
Error(str, node);
return -1;
}
int asCCompiler::CallDefaultConstructor(const asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc, asCScriptNode *node, int isVarGlobOrMem, bool derefDest)
{
if( !type.IsObject() || type.IsObjectHandle() )
return 0;
if( type.GetTypeInfo()->flags & asOBJ_REF )
{
asCExprContext ctx(engine);
ctx.exprNode = node;
int func = 0;
asSTypeBehaviour *beh = type.GetBehaviour();
if( beh )
{
func = beh->factory;
// If no trivial default factory is found, look for a factory where all params have default args
if( func == 0 )
{
for( asUINT n = 0; n < beh->factories.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[beh->factories[n]];
if( f->defaultArgs.GetLength() == f->parameterTypes.GetLength() &&
f->defaultArgs[0] != 0 )
{
func = beh->factories[n];
break;
}
}
}
}
if( func > 0 )
{
asCArray<asCExprContext *> args;
asCScriptFunction *f = engine->scriptFunctions[func];
if( f->parameterTypes.GetLength() )
{
// Add the default values for arguments not explicitly supplied
CompileDefaultAndNamedArgs(node, args, func, type.GetTypeInfo()->CastToObjectType());
PrepareFunctionCall(func, &ctx.bc, args);
MoveArgsToStack(func, &ctx.bc, args, false);
}
if( isVarGlobOrMem == 0 )
{
// Call factory and store the handle in the given variable
PerformFunctionCall(func, &ctx, false, &args, type.GetTypeInfo()->CastToObjectType(), true, offset);
// Pop the reference left by the function call
ctx.bc.Instr(asBC_PopPtr);
}
else
{
// Call factory
PerformFunctionCall(func, &ctx, false, &args, type.GetTypeInfo()->CastToObjectType());
// TODO: runtime optimize: Should have a way of storing the object pointer directly to the destination
// instead of first storing it in a local variable and then copying it to the
// destination.
if( !(type.GetTypeInfo()->flags & asOBJ_SCOPED) )
{
// Only dereference the variable if not a scoped type
ctx.bc.Instr(asBC_RDSPtr);
}
if( isVarGlobOrMem == 1 )
{
// Store the returned handle in the global variable
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
// Store the returned handle in the class member
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
if( type.GetTypeInfo()->flags & asOBJ_SCOPED )
{
// For scoped typed we must move the reference from the local
// variable rather than copy it as there is no AddRef behaviour
ctx.bc.InstrSHORT_DW(asBC_COPY, AS_PTR_SIZE, asTYPEID_OBJHANDLE | engine->GetTypeIdFromDataType(type));
// Clear the local variable so the reference isn't released
ctx.bc.InstrSHORT(asBC_ClrVPtr, ctx.type.stackOffset);
}
else
{
if( type.IsFuncdef() )
ctx.bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx.bc.InstrPTR(asBC_REFCPY, type.GetTypeInfo());
}
ctx.bc.Instr(asBC_PopPtr);
ReleaseTemporaryVariable(ctx.type.stackOffset, &ctx.bc);
}
bc->AddCode(&ctx.bc);
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
return 0;
}
}
else
{
asCExprContext ctx(engine);
ctx.exprNode = node;
asSTypeBehaviour *beh = type.GetBehaviour();
int func = 0;
if( beh )
{
func = beh->construct;
// If no trivial default constructor is found, look for a constructor where all params have default args
if( func == 0 )
{
for( asUINT n = 0; n < beh->constructors.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[beh->constructors[n]];
if( f->defaultArgs.GetLength() == f->parameterTypes.GetLength() &&
f->defaultArgs[0] != 0 )
{
func = beh->constructors[n];
break;
}
}
}
}
// Allocate and initialize with the default constructor
if( func != 0 || (type.GetTypeInfo()->flags & asOBJ_POD) )
{
asCArray<asCExprContext *> args;
asCScriptFunction *f = engine->scriptFunctions[func];
if( f && f->parameterTypes.GetLength() )
{
// Add the default values for arguments not explicitly supplied
CompileDefaultAndNamedArgs(node, args, func, type.GetTypeInfo()->CastToObjectType());
PrepareFunctionCall(func, &ctx.bc, args);
MoveArgsToStack(func, &ctx.bc, args, false);
}
if( !isObjectOnHeap )
{
if( isVarGlobOrMem == 0 )
{
// There is nothing to do if there is no function,
// as the memory is already allocated on the stack
if( func )
{
// Call the constructor as a normal function
bc->InstrSHORT(asBC_PSF, (short)offset);
if( derefDest )
bc->Instr(asBC_RDSPtr);
asCExprContext ctxCall(engine);
PerformFunctionCall(func, &ctxCall, false, 0, type.GetTypeInfo()->CastToObjectType());
bc->AddCode(&ctxCall.bc);
// TODO: value on stack: This probably needs to be done in PerformFunctionCall
// Mark the object as initialized
bc->ObjInfo(offset, asOBJ_INIT);
}
}
else if( isVarGlobOrMem == 2 )
{
// Only POD types can be allocated inline in script classes
asASSERT( type.GetTypeInfo()->flags & asOBJ_POD );
if( func )
{
// Call the constructor as a normal function
bc->InstrSHORT(asBC_PSF, 0);
bc->Instr(asBC_RDSPtr);
bc->InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
asCExprContext ctxCall(engine);
PerformFunctionCall(func, &ctxCall, false, 0, type.GetTypeInfo()->CastToObjectType());
bc->AddCode(&ctxCall.bc);
}
}
else
{
asASSERT( false );
}
}
else
{
if( isVarGlobOrMem == 0 )
bc->InstrSHORT(asBC_PSF, (short)offset);
else if( isVarGlobOrMem == 1 )
bc->InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
else
{
bc->InstrSHORT(asBC_PSF, 0);
bc->Instr(asBC_RDSPtr);
bc->InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
if( (type.GetTypeInfo()->flags & asOBJ_TEMPLATE) )
{
asCScriptFunction *descr = engine->scriptFunctions[func];
asASSERT( descr->funcType == asFUNC_SCRIPT );
// Find the id of the real constructor and not the generated stub
asUINT id = 0;
asDWORD *funcBc = descr->scriptData->byteCode.AddressOf();
while( funcBc )
{
if( (*(asBYTE*)funcBc) == asBC_CALLSYS )
{
id = asBC_INTARG(funcBc);
break;
}
funcBc += asBCTypeSize[asBCInfo[*(asBYTE*)funcBc].type];
}
asASSERT( id );
bc->InstrPTR(asBC_OBJTYPE, type.GetTypeInfo());
bc->Alloc(asBC_ALLOC, type.GetTypeInfo(), id, AS_PTR_SIZE + AS_PTR_SIZE);
}
else
bc->Alloc(asBC_ALLOC, type.GetTypeInfo(), func, AS_PTR_SIZE);
}
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
return 0;
}
}
// Class has no default factory/constructor.
asCString str;
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, type.GetTypeInfo()->GetName());
Error(str, node);
return -1;
}
void asCCompiler::CallDestructor(asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc)
{
if( !type.IsReference() )
{
// Call destructor for the data type
if( type.IsObject() || type.IsFuncdef() )
{
// The null pointer doesn't need to be destroyed
if( type.IsNullHandle() )
return;
// Nothing is done for list pattern types, as this is taken care of by the CompileInitList method
if( type.GetTypeInfo()->flags & asOBJ_LIST_PATTERN )
return;
if( isObjectOnHeap || type.IsObjectHandle() )
{
// Free the memory
if (type.IsFuncdef())
bc->InstrW_PTR(asBC_FREE, (short)offset, &engine->functionBehaviours);
else
bc->InstrW_PTR(asBC_FREE, (short)offset, type.GetTypeInfo());
}
else
{
asASSERT( type.GetTypeInfo()->GetFlags() & asOBJ_VALUE );
if( type.GetBehaviour()->destruct )
{
// Call the destructor as a regular function
asCExprContext ctx(engine);
ctx.bc.InstrSHORT(asBC_PSF, (short)offset);
PerformFunctionCall(type.GetBehaviour()->destruct, &ctx);
ctx.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&ctx.bc);
}
// TODO: Value on stack: This probably needs to be done in PerformFunctionCall
// Mark the object as destroyed
bc->ObjInfo(offset, asOBJ_UNINIT);
}
}
}
}
void asCCompiler::LineInstr(asCByteCode *bc, size_t pos)
{
int r, c;
script->ConvertPosToRowCol(pos, &r, &c);
bc->Line(r, c, script->idx);
}
void asCCompiler::CompileStatementBlock(asCScriptNode *block, bool ownVariableScope, bool *hasReturn, asCByteCode *bc)
{
*hasReturn = false;
bool isFinished = false;
bool hasUnreachableCode = false;
bool hasReturnBefore = false;
if( ownVariableScope )
{
bc->Block(true);
AddVariableScope();
}
asCScriptNode *node = block->firstChild;
while( node )
{
#ifdef AS_DEBUG
// Keep the current line in a variable so it will be easier
// to determine where in a script an assert is occurring.
int currentLine = 0;
script->ConvertPosToRowCol(node->tokenPos, &currentLine, 0);
#endif
if( !hasUnreachableCode && (*hasReturn || isFinished) )
{
// Empty statements don't count
if( node->nodeType != snExpressionStatement || node->firstChild )
{
hasUnreachableCode = true;
Warning(TXT_UNREACHABLE_CODE, node);
}
if( *hasReturn )
hasReturnBefore = true;
}
if( node->nodeType == snBreak || node->nodeType == snContinue )
isFinished = true;
asCByteCode statement(engine);
if( node->nodeType == snDeclaration )
CompileDeclaration(node, &statement);
else
CompileStatement(node, hasReturn, &statement);
// Ignore missing returns in unreachable code paths
if( !(*hasReturn) && hasReturnBefore )
*hasReturn = true;
LineInstr(bc, node->tokenPos);
bc->AddCode(&statement);
if( !hasCompileErrors )
{
asASSERT( tempVariables.GetLength() == 0 );
asASSERT( reservedVariables.GetLength() == 0 );
}
node = node->next;
}
if( ownVariableScope )
{
// Deallocate variables in this block, in reverse order
for( int n = (int)variables->variables.GetLength() - 1; n >= 0; n-- )
{
sVariable *v = variables->variables[n];
// Call variable destructors here, for variables not yet destroyed
// If the block is terminated with a break, continue, or
// return the variables are already destroyed
if( !isFinished && !*hasReturn )
CallDestructor(v->type, v->stackOffset, v->onHeap, bc);
// Don't deallocate function parameters
if( v->stackOffset > 0 )
DeallocateVariable(v->stackOffset);
}
RemoveVariableScope();
bc->Block(false);
}
}
// Entry
int asCCompiler::CompileGlobalVariable(asCBuilder *in_builder, asCScriptCode *in_script, asCScriptNode *in_node, sGlobalVariableDescription *in_gvar, asCScriptFunction *in_outFunc)
{
Reset(in_builder, in_script, in_outFunc);
m_globalVar = in_gvar;
// Add a variable scope (even though variables can't be declared)
AddVariableScope();
in_gvar->isPureConstant = false;
// Parse the initialization nodes
asCParser parser(builder);
if (in_node)
{
int r = parser.ParseVarInit(in_script, in_node);
if (r < 0)
return r;
in_node = parser.GetScriptNode();
}
asCExprContext compiledCtx(engine);
bool preCompiled = false;
if (in_gvar->datatype.IsAuto())
{
preCompiled = CompileAutoType(in_gvar->datatype, compiledCtx, in_node, in_gvar->declaredAtNode);
if (!preCompiled)
{
// If it wasn't possible to determine the type from the expression then there
// is no need to continue with the initialization. The error was already reported
// in CompileAutoType.
return -1;
}
}
if( in_gvar->property == 0 )
{
in_gvar->property = builder->module->AllocateGlobalProperty(in_gvar->name.AddressOf(), in_gvar->datatype, in_gvar->ns);
in_gvar->index = in_gvar->property->id;
}
// Compile the expression
asCExprContext ctx(engine);
asQWORD constantValue = 0;
if( CompileInitialization(in_node, &ctx.bc, in_gvar->datatype, in_gvar->declaredAtNode, in_gvar->index, &constantValue, 1, preCompiled ? &compiledCtx : 0) )
{
// Should the variable be marked as pure constant?
if( in_gvar->datatype.IsPrimitive() && in_gvar->datatype.IsReadOnly() )
{
in_gvar->isPureConstant = true;
in_gvar->constantValue = constantValue;
}
}
// Concatenate the bytecode
int varSize = GetVariableOffset((int)variableAllocations.GetLength()) - 1;
// Add information on the line number for the global variable
size_t pos = 0;
if( in_gvar->declaredAtNode )
pos = in_gvar->declaredAtNode->tokenPos;
else if( in_gvar->initializationNode )
pos = in_gvar->initializationNode->tokenPos;
LineInstr(&byteCode, pos);
// Reserve space for all local variables
outFunc->scriptData->variableSpace = varSize;
ctx.bc.OptimizeLocally(tempVariableOffsets);
byteCode.AddCode(&ctx.bc);
// Deallocate variables in this block, in reverse order
for( int n = (int)variables->variables.GetLength() - 1; n >= 0; --n )
{
sVariable *v = variables->variables[n];
// Call variable destructors here, for variables not yet destroyed
CallDestructor(v->type, v->stackOffset, v->onHeap, &byteCode);
DeallocateVariable(v->stackOffset);
}
if( hasCompileErrors ) return -1;
// At this point there should be no variables allocated
asASSERT(variableAllocations.GetLength() == freeVariables.GetLength());
// Remove the variable scope again
RemoveVariableScope();
byteCode.Ret(0);
FinalizeFunction();
#ifdef AS_DEBUG
// DEBUG: output byte code
byteCode.DebugOutput(("___init_" + in_gvar->name + ".txt").AddressOf(), outFunc);
#endif
return 0;
}
void asCCompiler::DetermineSingleFunc(asCExprContext *ctx, asCScriptNode *node)
{
// Don't do anything if this is not a deferred global function
if( !ctx->IsGlobalFunc() )
return;
// Determine the namespace
asSNameSpace *ns = 0;
asCString name = "";
int pos = ctx->methodName.FindLast("::");
if( pos >= 0 )
{
asCString nsName = ctx->methodName.SubString(0, pos+2);
// Cut off the ::
if( nsName.GetLength() > 2 )
nsName.SetLength(nsName.GetLength()-2);
ns = DetermineNameSpace(nsName);
name = ctx->methodName.SubString(pos+2);
}
else
{
DetermineNameSpace("");
name = ctx->methodName;
}
asCArray<int> funcs;
if( ns )
builder->GetFunctionDescriptions(name.AddressOf(), funcs, ns);
// CompileVariableAccess should guarantee that at least one function is exists
asASSERT( funcs.GetLength() > 0 );
if( funcs.GetLength() > 1 )
{
asCString str;
str.Format(TXT_MULTIPLE_MATCHING_SIGNATURES_TO_s, ctx->methodName.AddressOf());
Error(str, node);
// Fall through so the compiler can continue as if only one function was matching
}
// A shared object may not access global functions unless they too are shared (e.g. registered functions)
if( !builder->GetFunctionDescription(funcs[0])->IsShared() &&
outFunc->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_CALL_NON_SHARED_FUNC_s, builder->GetFunctionDescription(funcs[0])->GetDeclaration());
Error(msg, node);
// Fall through so the compiler can continue anyway
}
// Push the function pointer on the stack
ctx->bc.InstrPTR(asBC_FuncPtr, builder->GetFunctionDescription(funcs[0]));
ctx->type.Set(asCDataType::CreateType(engine->FindMatchingFuncdef(builder->GetFunctionDescription(funcs[0]), builder->module), false));
ctx->type.dataType.MakeHandle(true);
ctx->type.isExplicitHandle = true;
ctx->methodName = "";
}
void asCCompiler::CompileInitAsCopy(asCDataType &dt, int offset, asCByteCode *bc, asCExprContext *arg, asCScriptNode *node, bool derefDestination)
{
bool isObjectOnHeap = derefDestination ? false : IsVariableOnHeap(offset);
// Use copy constructor if available.
if( dt.GetTypeInfo()->CastToObjectType() && dt.GetTypeInfo()->CastToObjectType()->beh.copyconstruct )
{
PrepareForAssignment(&dt, arg, node, true);
int r = CallCopyConstructor(dt, offset, isObjectOnHeap, bc, arg, node, 0, derefDestination);
if( r < 0 && tempVariables.Exists(offset) )
Error(TXT_FAILED_TO_CREATE_TEMP_OBJ, node);
}
else
{
// TODO: Need to reserve variables, as the default constructor may need
// to allocate temporary variables to compute default args
// Allocate and construct the temporary object before whatever is already in the bytecode
asCByteCode tmpBC(engine);
int r = CallDefaultConstructor(dt, offset, isObjectOnHeap, &tmpBC, node, 0, derefDestination);
if( r < 0 )
{
if( tempVariables.Exists(offset) )
Error(TXT_FAILED_TO_CREATE_TEMP_OBJ, node);
return;
}
tmpBC.AddCode(bc);
bc->AddCode(&tmpBC);
// Assign the evaluated expression to the temporary variable
PrepareForAssignment(&dt, arg, node, true);
bc->AddCode(&arg->bc);
// Call the opAssign method to assign the value to the temporary object
dt.MakeReference(isObjectOnHeap);
asCExprValue type;
type.Set(dt);
type.isTemporary = true;
type.stackOffset = (short)offset;
if( dt.IsObjectHandle() )
type.isExplicitHandle = true;
bc->InstrSHORT(asBC_PSF, (short)offset);
if( derefDestination )
bc->Instr(asBC_RDSPtr);
r = PerformAssignment(&type, &arg->type, bc, node);
if( r < 0 )
{
if( tempVariables.Exists(offset) )
Error(TXT_FAILED_TO_CREATE_TEMP_OBJ, node);
return;
}
// Pop the reference that was pushed on the stack if the result is an object
if( type.dataType.IsObject() || type.dataType.IsFuncdef() )
bc->Instr(asBC_PopPtr);
// If the assignment operator returned an object by value it will
// be in a temporary variable which we need to destroy now
if( type.isTemporary && type.stackOffset != (short)offset )
ReleaseTemporaryVariable(type.stackOffset, bc);
// Release the original value too in case it is a temporary
ReleaseTemporaryVariable(arg->type, bc);
}
}
int asCCompiler::PrepareArgument(asCDataType *paramType, asCExprContext *ctx, asCScriptNode *node, bool isFunction, int refType, bool isMakingCopy)
{
asCDataType param = *paramType;
if( paramType->GetTokenType() == ttQuestion )
{
// The function is expecting a var type. If the argument is a function name, we must now decide which function it is
DetermineSingleFunc(ctx, node);
// Since the function is expecting a var type ?, then we don't want to convert the argument to anything else
param = ctx->type.dataType;
param.MakeHandle(ctx->type.isExplicitHandle || ctx->type.IsNullConstant());
// Treat the void expression like a null handle when working with var types
if( ctx->IsVoidExpression() )
param = asCDataType::CreateNullHandle();
// If value assign is disabled for reference types, then make
// sure to always pass the handle to ? parameters
if( builder->engine->ep.disallowValueAssignForRefType &&
ctx->type.dataType.GetTypeInfo() && (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_REF) && !(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCOPED) )
{
param.MakeHandle(true);
}
param.MakeReference(paramType->IsReference());
param.MakeReadOnly(paramType->IsReadOnly());
}
else
param = *paramType;
asCDataType dt = param;
// Need to protect arguments by reference
if( isFunction && dt.IsReference() )
{
// Allocate a temporary variable of the same type as the argument
dt.MakeReference(false);
dt.MakeReadOnly(false);
int offset;
if( refType == asTM_INREF )
{
ProcessPropertyGetAccessor(ctx, node);
// Add the type id as hidden arg if the parameter is a ? type
if( paramType->GetTokenType() == ttQuestion )
{
asCByteCode tmpBC(engine);
// Place the type id on the stack as a hidden parameter
tmpBC.InstrDWORD(asBC_TYPEID, engine->GetTypeIdFromDataType(param));
// Insert the code before the expression code
tmpBC.AddCode(&ctx->bc);
ctx->bc.AddCode(&tmpBC);
}
if( dt.IsPrimitive() )
{
// If the reference is const, then it is not necessary to make a copy if the value already is a variable
// Even if the same variable is passed in another argument as non-const then there is no problem
IsVariableInitialized(&ctx->type, node);
if( ctx->type.dataType.IsReference() ) ConvertToVariable(ctx);
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV, true);
if( !(param.IsReadOnly() && ctx->type.isVariable) )
ConvertToTempVariable(ctx);
PushVariableOnStack(ctx, true);
ctx->type.dataType.MakeReadOnly(param.IsReadOnly());
}
else if( ctx->type.dataType.IsNullHandle() )
{
// Make sure the argument type can support handles (or is itself a handle)
if( !dt.SupportHandles() && !dt.IsObjectHandle() )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), param.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.Set(param);
return -1;
}
// Need to initialize a local temporary variable to
// represent the null handle when passed as reference
asASSERT( ctx->bc.GetLastInstr() == asBC_PshNull );
ctx->bc.Instr(asBC_PopPtr);
dt.MakeHandle(true);
offset = AllocateVariableNotIn(dt, true, false, ctx);
// Push the reference to the variable on the stack
ctx->bc.InstrWORD(asBC_PSF, (short)offset);
ctx->type.SetVariable(dt, offset, true);
}
else
{
IsVariableInitialized(&ctx->type, node);
if( !isMakingCopy )
{
// Even though the parameter expects a reference, it is only meant to be
// used as input value and doesn't have to refer to the actual object, so it
// is OK to do an implicit conversion.
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV, true);
if( !ctx->type.dataType.IsEqualExceptRefAndConst(param) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), param.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.Set(param);
return -1;
}
// The compiler must guarantee that the object stays alive during the execution
// of the function, and it must also guarantee that the value isn't modified by
// the function.
// If the argument is a temporary local variable then it is safe to be passed to
// the function as it is, since the local variable will stay alive, and since it
// is temporary there is no side effect if the function modifies it.
// If the parameter is read-only and therefor guaranteed not to be modified by the
// function, then it is enough that the variable is local to guarantee the lifetime.
if( !ctx->type.isTemporary && !(param.IsReadOnly() && ctx->type.isVariable) )
{
if( (ctx->type.dataType.IsFuncdef() || (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_REF)) && param.IsReadOnly() && !(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCOPED) )
{
// If the object is a reference type (except scoped reference types), and the
// parameter is a const reference, then it is not necessary to make a copy of the
// object. The compiler just needs to hold a handle to guarantee the lifetime.
// Allocate a handle variable
dt.MakeHandle(true);
offset = AllocateVariableNotIn(dt, true, false, ctx);
// Copy the handle
Dereference(ctx, true);
ctx->bc.InstrWORD(asBC_PSF, (asWORD)offset);
if (ctx->type.dataType.IsFuncdef())
ctx->bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx->bc.InstrPTR(asBC_REFCPY, ctx->type.dataType.GetTypeInfo());
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrWORD(asBC_PSF, (asWORD)offset);
// The type should be set to the param type instead of dt to guarantee
// that the expression keeps the correct type for variable ? args. Otherwise
// MoveArgsToStack will use the wrong bytecode to move the arg to the stack
ctx->type.SetVariable(param, offset, true);
}
else
{
asASSERT(!dt.IsFuncdef());
// Allocate and initialize a temporary local object
offset = AllocateVariableNotIn(dt, true, false, ctx);
CompileInitAsCopy(dt, offset, &ctx->bc, ctx, node, false);
// Push the object pointer on the stack
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
if( dt.IsObject() && !dt.IsObjectHandle() )
ctx->bc.Instr(asBC_RDSPtr);
// Set the resulting type
ctx->type.Set(dt);
ctx->type.isTemporary = true;
ctx->type.stackOffset = short(offset);
if( dt.IsObjectHandle() )
ctx->type.isExplicitHandle = true;
ctx->type.dataType.MakeReference(false);
if( paramType->IsReadOnly() )
ctx->type.dataType.MakeReadOnly(true);
}
}
}
else
{
// We must guarantee that the address to the value is on the stack
if( (ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) &&
!ctx->type.dataType.IsObjectHandle() &&
ctx->type.dataType.IsReference() )
Dereference(ctx, true);
}
}
}
else if( refType == asTM_OUTREF )
{
// Add the type id as hidden arg if the parameter is a ? type
if( paramType->GetTokenType() == ttQuestion )
{
asCByteCode tmpBC(engine);
// Place the type id on the stack as a hidden parameter
tmpBC.InstrDWORD(asBC_TYPEID, engine->GetTypeIdFromDataType(param));
// Insert the code before the expression code
tmpBC.AddCode(&ctx->bc);
ctx->bc.AddCode(&tmpBC);
}
// If the expression is marked as clean, then it can be used directly
// without the need to allocate another temporary value as it is known
// that the argument has no other value than the default
if( ctx->isCleanArg )
{
// Must be a local variable
asASSERT( ctx->type.isVariable );
}
else
{
// Make sure the variable is not used in the expression
offset = AllocateVariableNotIn(dt, true, false, ctx);
if( dt.IsPrimitive() )
{
ctx->type.SetVariable(dt, offset, true);
PushVariableOnStack(ctx, true);
}
else
{
// TODO: Need to reserve variables, as the default constructor may need
// to allocate temporary variables to compute default args
// Allocate and construct the temporary object
asCByteCode tmpBC(engine);
CallDefaultConstructor(dt, offset, IsVariableOnHeap(offset), &tmpBC, node);
// Insert the code before the expression code
tmpBC.AddCode(&ctx->bc);
ctx->bc.AddCode(&tmpBC);
dt.MakeReference(!(dt.IsObject() || dt.IsFuncdef()) || dt.IsObjectHandle());
asCExprValue type;
type.Set(dt);
type.isTemporary = true;
type.stackOffset = (short)offset;
ctx->type = type;
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
if( (dt.IsObject() || dt.IsFuncdef()) && !dt.IsObjectHandle() )
ctx->bc.Instr(asBC_RDSPtr);
}
// After the function returns the temporary variable will
// be assigned to the expression, if it is a valid lvalue
}
}
else if( refType == asTM_INOUTREF )
{
ProcessPropertyGetAccessor(ctx, node);
// Add the type id as hidden arg if the parameter is a ? type
if( paramType->GetTokenType() == ttQuestion )
{
asCByteCode tmpBC(engine);
// Place the type id on the stack as a hidden parameter
tmpBC.InstrDWORD(asBC_TYPEID, engine->GetTypeIdFromDataType(param));
// Insert the code before the expression code
tmpBC.AddCode(&ctx->bc);
ctx->bc.AddCode(&tmpBC);
}
// Literal constants cannot be passed to inout ref arguments
if( !ctx->type.isVariable && ctx->type.isConstant )
{
// Unless unsafe references are turned on and the reference is const
if( param.IsReadOnly() && engine->ep.allowUnsafeReferences )
{
// Since the parameter is a const & make a copy.
ConvertToTempVariable(ctx);
ctx->type.dataType.MakeReadOnly(true);
}
else
{
Error(TXT_NOT_VALID_REFERENCE, node);
return -1;
}
}
// Perform implicit ref cast if necessary, but don't allow the implicit conversion to create new objects
if( (ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) && ctx->type.dataType.GetTypeInfo() != dt.GetTypeInfo() )
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV, true, false);
// Only objects that support object handles
// can be guaranteed to be safe. Local variables are
// already safe, so there is no need to add an extra
// references
if( !engine->ep.allowUnsafeReferences &&
!ctx->type.isVariable &&
(ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) &&
!ctx->type.dataType.IsObjectHandle() &&
((ctx->type.dataType.GetBehaviour()->addref &&
ctx->type.dataType.GetBehaviour()->release) ||
(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_NOCOUNT) ||
ctx->type.dataType.IsFuncdef()) )
{
// Store a handle to the object as local variable
asCExprContext tmp(engine);
dt = ctx->type.dataType;
dt.MakeHandle(true);
dt.MakeReference(false);
offset = AllocateVariableNotIn(dt, true, false, ctx);
// Copy the handle
if( !ctx->type.dataType.IsObjectHandle() && ctx->type.dataType.IsReference() )
ctx->bc.Instr(asBC_RDSPtr);
ctx->bc.InstrWORD(asBC_PSF, (asWORD)offset);
if( ctx->type.dataType.IsFuncdef() )
ctx->bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx->bc.InstrPTR(asBC_REFCPY, ctx->type.dataType.GetTypeInfo());
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrWORD(asBC_PSF, (asWORD)offset);
dt.MakeHandle(false);
dt.MakeReference(true);
// Release previous temporary variable stored in the context (if any)
if( ctx->type.isTemporary )
ReleaseTemporaryVariable(ctx->type.stackOffset, &ctx->bc);
ctx->type.SetVariable(dt, offset, true);
}
// Make sure the reference to the value is on the stack
// For objects, the reference needs to be dereferenced so the pointer on the stack is to the actual object
// For handles, the reference shouldn't be changed because the pointer on the stack should be to the handle
if( (ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) && ctx->type.dataType.IsReference() && !param.IsObjectHandle() )
Dereference(ctx, true);
else if( ctx->type.isVariable && !(ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) )
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
else if( ctx->type.dataType.IsPrimitive() )
ctx->bc.Instr(asBC_PshRPtr);
else if( ctx->type.dataType.IsObjectHandle() && !ctx->type.dataType.IsReference() )
ImplicitConversion(ctx, param, node, asIC_IMPLICIT_CONV, true, false);
}
}
else
{
ProcessPropertyGetAccessor(ctx, node);
if( dt.IsPrimitive() )
{
IsVariableInitialized(&ctx->type, node);
if( ctx->type.dataType.IsReference() ) ConvertToVariable(ctx);
// Implicitly convert primitives to the parameter type
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV);
if( ctx->type.isVariable )
{
PushVariableOnStack(ctx, dt.IsReference());
}
else if( ctx->type.isConstant )
{
ConvertToVariable(ctx);
PushVariableOnStack(ctx, dt.IsReference());
}
}
else
{
IsVariableInitialized(&ctx->type, node);
// Implicitly convert primitives to the parameter type
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV);
// Was the conversion successful?
if( !ctx->type.dataType.IsEqualExceptRef(dt) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), dt.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.Set(dt);
return -1;
}
if( dt.IsObjectHandle() )
ctx->type.isExplicitHandle = true;
if( (dt.IsObject() || dt.IsFuncdef()) && !dt.IsNullHandle() && !dt.IsReference() )
{
// Objects passed by value must be placed in temporary variables
// so that they are guaranteed to not be referenced anywhere else.
// The object must also be allocated on the heap, as the memory will
// be deleted by the called function.
// Handles passed by value must also be placed in a temporary variable
// to guarantee that the object referred to isn't freed too early.
// TODO: value on stack: How can we avoid this unnecessary allocation?
// Don't make temporary copies of handles if it is going to be used
// for handle assignment anyway, i.e. REFCPY.
if( !(!isFunction && isMakingCopy && ctx->type.dataType.IsObjectHandle() && ctx->type.isVariable) )
PrepareTemporaryVariable(node, ctx, true);
}
}
}
// Don't put any pointer on the stack yet
if( param.IsReference() || ((param.IsObject() || param.IsFuncdef()) && !param.IsNullHandle()) )
{
// &inout parameter may leave the reference on the stack already
if( refType != asTM_INOUTREF )
{
asASSERT( ctx->type.isVariable || ctx->type.isTemporary || isMakingCopy );
if( ctx->type.isVariable || ctx->type.isTemporary )
{
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrSHORT(asBC_VAR, ctx->type.stackOffset);
ProcessDeferredParams(ctx);
}
}
}
return 0;
}
void asCCompiler::PrepareFunctionCall(int funcId, asCByteCode *bc, asCArray<asCExprContext *> &args)
{
// When a match has been found, compile the final byte code using correct parameter types
asCScriptFunction *descr = builder->GetFunctionDescription(funcId);
asASSERT( descr->parameterTypes.GetLength() == args.GetLength() );
// If the function being called is the opAssign or copy constructor for the same type
// as the argument, then we should avoid making temporary copy of the argument
bool makingCopy = false;
if( descr->parameterTypes.GetLength() == 1 &&
descr->parameterTypes[0].IsEqualExceptRefAndConst(args[0]->type.dataType) &&
(((descr->name == "opAssign" || descr->name == "$beh0") && descr->objectType && descr->objectType == args[0]->type.dataType.GetTypeInfo()) ||
(descr->objectType == 0 && args[0]->type.dataType.GetTypeInfo() && descr->name == args[0]->type.dataType.GetTypeInfo()->name)) )
makingCopy = true;
// Add code for arguments
asCExprContext e(engine);
for( int n = (int)args.GetLength()-1; n >= 0; n-- )
{
// Make sure PrepareArgument doesn't use any variable that is already
// being used by any of the following argument expressions
int l = int(reservedVariables.GetLength());
for( int m = n-1; m >= 0; m-- )
args[m]->bc.GetVarsUsed(reservedVariables);
PrepareArgument2(&e, args[n], &descr->parameterTypes[n], true, descr->inOutFlags[n], makingCopy);
reservedVariables.SetLength(l);
}
bc->AddCode(&e.bc);
}
void asCCompiler::MoveArgsToStack(int funcId, asCByteCode *bc, asCArray<asCExprContext *> &args, bool addOneToOffset)
{
asCScriptFunction *descr = builder->GetFunctionDescription(funcId);
int offset = 0;
if( addOneToOffset )
offset += AS_PTR_SIZE;
// The address of where the return value should be stored is push on top of the arguments
if( descr->DoesReturnOnStack() )
offset += AS_PTR_SIZE;
#ifdef AS_DEBUG
// If the function being called is the opAssign or copy constructor for the same type
// as the argument, then we should avoid making temporary copy of the argument
bool makingCopy = false;
if( descr->parameterTypes.GetLength() == 1 &&
descr->parameterTypes[0].IsEqualExceptRefAndConst(args[0]->type.dataType) &&
(((descr->name == "opAssign" || descr->name == "$beh0") && descr->objectType && descr->objectType == args[0]->type.dataType.GetTypeInfo()) ||
(descr->objectType == 0 && args[0]->type.dataType.GetTypeInfo() && descr->name == args[0]->type.dataType.GetTypeInfo()->name)) )
makingCopy = true;
#endif
// Move the objects that are sent by value to the stack just before the call
for( asUINT n = 0; n < descr->parameterTypes.GetLength(); n++ )
{
if( descr->parameterTypes[n].IsReference() )
{
if( (descr->parameterTypes[n].IsObject() || descr->parameterTypes[n].IsFuncdef()) && !descr->parameterTypes[n].IsObjectHandle() )
{
if( descr->inOutFlags[n] != asTM_INOUTREF )
{
#ifdef AS_DEBUG
// This assert is inside AS_DEBUG because of the variable makingCopy which is only defined in debug mode
asASSERT( args[n]->type.isVariable || args[n]->type.isTemporary || makingCopy );
#endif
if( (args[n]->type.isVariable || args[n]->type.isTemporary) )
{
if( !IsVariableOnHeap(args[n]->type.stackOffset) )
// TODO: runtime optimize: Actually the reference can be pushed on the stack directly
// as the value allocated on the stack is guaranteed to be safe
bc->InstrWORD(asBC_GETREF, (asWORD)offset);
else
bc->InstrWORD(asBC_GETOBJREF, (asWORD)offset);
}
}
if( args[n]->type.dataType.IsObjectHandle() )
bc->InstrWORD(asBC_ChkNullS, (asWORD)offset);
}
else if( descr->inOutFlags[n] != asTM_INOUTREF )
{
if( descr->parameterTypes[n].GetTokenType() == ttQuestion &&
(args[n]->type.dataType.IsObject() || args[n]->type.dataType.IsFuncdef()) &&
!args[n]->type.dataType.IsObjectHandle() )
{
// Send the object as a reference to the object,
// and not to the variable holding the object
if( !IsVariableOnHeap(args[n]->type.stackOffset) )
// TODO: runtime optimize: Actually the reference can be pushed on the stack directly
// as the value allocated on the stack is guaranteed to be safe
bc->InstrWORD(asBC_GETREF, (asWORD)offset);
else
bc->InstrWORD(asBC_GETOBJREF, (asWORD)offset);
}
else
{
bc->InstrWORD(asBC_GETREF, (asWORD)offset);
}
}
}
else if( descr->parameterTypes[n].IsObject() || descr->parameterTypes[n].IsFuncdef() )
{
// TODO: value on stack: What can we do to avoid this unnecessary allocation?
// The object must be allocated on the heap, because this memory will be deleted in as_callfunc_xxx
asASSERT(IsVariableOnHeap(args[n]->type.stackOffset));
// The pointer in the variable will be moved to the stack
bc->InstrWORD(asBC_GETOBJ, (asWORD)offset);
// Deallocate the variable slot so it can be reused, but do not attempt to
// free the content of the variable since it was moved to the stack for the call
DeallocateVariable(args[n]->type.stackOffset);
args[n]->type.isTemporary = false;
}
offset += descr->parameterTypes[n].GetSizeOnStackDWords();
}
}
int asCCompiler::CompileArgumentList(asCScriptNode *node, asCArray<asCExprContext*> &args, asCArray<asSNamedArgument> &namedArgs)
{
asASSERT(node->nodeType == snArgList);
// Count arguments
asCScriptNode *arg = node->firstChild;
int argCount = 0;
while( arg )
{
if( arg->nodeType != snNamedArgument )
argCount++;
arg = arg->next;
}
// Prepare the arrays
args.SetLength(argCount);
int n;
for( n = 0; n < argCount; n++ )
args[n] = 0;
n = argCount-1;
// Compile the arguments in reverse order (as they will be pushed on the stack)
bool anyErrors = false, inPositionalArguments = false;
arg = node->lastChild;
while( arg )
{
asCScriptNode *asgNode = arg, *namedNode = 0;
if( asgNode->nodeType == snNamedArgument )
{
if( inPositionalArguments )
{
Error(TXT_POS_ARG_AFTER_NAMED_ARG, node);
return -1;
}
asgNode = arg->firstChild->next;
namedNode = arg->firstChild;
asASSERT( namedNode->nodeType == snIdentifier );
}
else
inPositionalArguments = true;
asCExprContext expr(engine);
int r = CompileAssignment(asgNode, &expr);
if( r < 0 ) anyErrors = true;
asCExprContext *ctx = asNEW(asCExprContext)(engine);
if( ctx == 0 )
{
// Out of memory
return -1;
}
MergeExprBytecodeAndType(ctx, &expr);
if( inPositionalArguments )
{
args[n] = ctx;
n--;
}
else
{
asSNamedArgument namedArg;
namedArg.name = asCString(&script->code[namedNode->tokenPos], namedNode->tokenLength);
namedArg.ctx = ctx;
// Error out when multiple arguments with the same name are passed
for( asUINT a = 0; a < namedArgs.GetLength(); ++a )
{
if( namedArgs[a].name == namedArg.name )
{
Error(TXT_DUPLICATE_NAMED_ARG, asgNode);
anyErrors = true;
break;
}
}
namedArgs.PushLast(namedArg);
}
arg = arg->prev;
}
return anyErrors ? -1 : 0;
}
int asCCompiler::CompileDefaultAndNamedArgs(asCScriptNode *node, asCArray<asCExprContext*> &args, int funcId, asCObjectType *objectType, asCArray<asSNamedArgument> *namedArgs)
{
asCScriptFunction *func = builder->GetFunctionDescription(funcId);
if( func == 0 || args.GetLength() >= (asUINT)func->GetParamCount() )
return 0;
// Make sure to use the real function for virtual functions
if( func->funcType == asFUNC_VIRTUAL )
{
asASSERT( objectType );
func = objectType->virtualFunctionTable[func->vfTableIdx];
}
// Make sure none of the variables used in the previous arguments are reused in the default arguments
bool anyErrors = false;
int prevReservedVars = reservedVariables.GetLength();
int explicitArgs = (int)args.GetLength();
for( int p = 0; p < explicitArgs; p++ )
args[p]->bc.GetVarsUsed(reservedVariables);
// Make space for all the new arguments
args.SetLength(func->parameterTypes.GetLength());
for( asUINT c = explicitArgs; c < args.GetLength(); c++ )
args[c] = 0;
// Add the named arguments to the argument list in the right position
if( namedArgs )
{
for( asUINT n = 0; n < namedArgs->GetLength(); ++n )
{
asSNamedArgument &named = (*namedArgs)[n];
named.ctx->bc.GetVarsUsed(reservedVariables);
// Find the right spot to put it in
asUINT index = asUINT(-1);
for( asUINT j = 0; j < func->parameterTypes.GetLength(); ++j )
{
if( func->parameterNames[j] == (*namedArgs)[n].name )
{
index = j;
break;
}
}
asASSERT( index < args.GetLength() );
args[index] = named.ctx;
named.ctx = 0;
}
}
// Compile the arguments in reverse order (as they will be pushed on the stack)
for( int n = (int)func->parameterTypes.GetLength() - 1; n >= explicitArgs; n-- )
{
if( args[n] != 0 ) continue;
if( func->defaultArgs[n] == 0 ) { anyErrors = true; continue; }
// Parse the default arg string
asCParser parser(builder);
asCScriptCode code;
code.SetCode("default arg", func->defaultArgs[n]->AddressOf(), false);
int r = parser.ParseExpression(&code);
if( r < 0 )
{
asCString msg;
msg.Format(TXT_FAILED_TO_COMPILE_DEF_ARG_d_IN_FUNC_s, n, func->GetDeclaration());
Error(msg, node);
anyErrors = true;
continue;
}
asCScriptNode *arg = parser.GetScriptNode();
// Temporarily set the script code to the default arg expression
asCScriptCode *origScript = script;
script = &code;
// Don't allow the expression to access local variables
isCompilingDefaultArg = true;
// Temporarily set the namespace in the output function to the namespace of the called
// function so that the default arguments are evaluated in the correct namespace
asSNameSpace *origNameSpace = outFunc->nameSpace;
outFunc->nameSpace = func->nameSpace;
asCExprContext expr(engine);
r = CompileExpression(arg, &expr);
// Restore the namespace
outFunc->nameSpace = origNameSpace;
// Don't allow address of class method
if( expr.methodName != "" )
{
// TODO: Improve error message
Error(TXT_DEF_ARG_TYPE_DOESNT_MATCH, arg);
r = -1;
}
// Make sure the expression can be implicitly converted to the parameter type
if( r >= 0 )
{
asCArray<int> funcs;
funcs.PushLast(func->id);
asCArray<asSOverloadCandidate> matches;
if( MatchArgument(funcs, matches, &expr, n) == 0 )
{
Error(TXT_DEF_ARG_TYPE_DOESNT_MATCH, arg);
r = -1;
}
}
isCompilingDefaultArg = false;
script = origScript;
if( r < 0 )
{
asCString msg;
msg.Format(TXT_FAILED_TO_COMPILE_DEF_ARG_d_IN_FUNC_s, n, func->GetDeclaration());
Error(msg, node);
anyErrors = true;
continue;
}
args[n] = asNEW(asCExprContext)(engine);
if( args[n] == 0 )
{
// Out of memory
reservedVariables.SetLength(prevReservedVars);
return -1;
}
MergeExprBytecodeAndType(args[n], &expr);
}
reservedVariables.SetLength(prevReservedVars);
return anyErrors ? -1 : 0;
}
asUINT asCCompiler::MatchFunctions(asCArray<int> &funcs, asCArray<asCExprContext*> &args, asCScriptNode *node, const char *name, asCArray<asSNamedArgument> *namedArgs, asCObjectType *objectType, bool isConstMethod, bool silent, bool allowObjectConstruct, const asCString &scope)
{
asCArray<int> origFuncs = funcs; // Keep the original list for error message
asUINT cost = 0;
asUINT n;
if( funcs.GetLength() > 0 )
{
// Check the number of parameters in the found functions
asUINT totalArgs = (asUINT)args.GetLength();
if( namedArgs != 0 )
totalArgs += (asUINT)namedArgs->GetLength();
for( n = 0; n < funcs.GetLength(); ++n )
{
asCScriptFunction *desc = builder->GetFunctionDescription(funcs[n]);
if( desc->parameterTypes.GetLength() != totalArgs )
{
bool noMatch = true;
if( totalArgs < desc->parameterTypes.GetLength() )
{
// For virtual functions, the default args are defined in the real function of the object
if( desc->funcType == asFUNC_VIRTUAL )
desc = objectType->virtualFunctionTable[desc->vfTableIdx];
// Count the number of default args
asUINT defaultArgs = 0;
for( asUINT d = 0; d < desc->defaultArgs.GetLength(); d++ )
if( desc->defaultArgs[d] )
defaultArgs++;
if( totalArgs >= desc->parameterTypes.GetLength() - defaultArgs )
noMatch = false;
}
if( noMatch )
{
// remove it from the list
if( n == funcs.GetLength()-1 )
funcs.PopLast();
else
funcs[n] = funcs.PopLast();
n--;
}
}
}
// Match functions with the parameters, and discard those that do not match
asCArray<asSOverloadCandidate> matchingFuncs;
matchingFuncs.SetLengthNoConstruct( funcs.GetLength() );
for ( n = 0; n < funcs.GetLength(); ++n )
{
matchingFuncs[n].funcId = funcs[n];
matchingFuncs[n].cost = 0;
}
// Match positionally passed arguments
for( n = 0; n < args.GetLength(); ++n )
{
asCArray<asSOverloadCandidate> tempFuncs;
MatchArgument(funcs, tempFuncs, args[n], n, allowObjectConstruct);
// Intersect the found functions with the list of matching functions
for( asUINT f = 0; f < matchingFuncs.GetLength(); f++ )
{
asUINT c;
for( c = 0; c < tempFuncs.GetLength(); c++ )
{
if( matchingFuncs[f].funcId == tempFuncs[c].funcId )
{
// Sum argument cost
matchingFuncs[f].cost += tempFuncs[c].cost;
break;
} // End if match
}
// Was the function a match?
if( c == tempFuncs.GetLength() )
{
// No, remove it from the list
if( f == matchingFuncs.GetLength()-1 )
matchingFuncs.PopLast();
else
matchingFuncs[f] = matchingFuncs.PopLast();
f--;
}
}
}
// Match named arguments
if( namedArgs != 0 )
{
for( asUINT i = 0; i < matchingFuncs.GetLength(); ++i )
{
asCScriptFunction *desc = builder->GetFunctionDescription(matchingFuncs[i].funcId);
if( desc->funcType == asFUNC_VIRTUAL )
desc = objectType->virtualFunctionTable[desc->vfTableIdx];
// Match every named argument to an argument in the function
for( n = 0; n < namedArgs->GetLength(); ++n )
(*namedArgs)[n].match = asUINT(-1);
bool matchedAll = true;
for( asUINT j = 0; j < desc->parameterTypes.GetLength(); ++j )
{
asUINT match = asUINT(-1);
for( n = 0; n < namedArgs->GetLength(); ++n )
{
asSNamedArgument &namedArg = (*namedArgs)[n];
if( desc->parameterNames[j] == namedArg.name )
{
namedArg.match = j;
match = n;
break;
}
}
// Check that every position is filled somehow
if( j >= args.GetLength() )
{
if( match == asUINT(-1) && !desc->defaultArgs[j] )
{
// No argument was found for this, and there is no
// default, so it doesn't work.
matchedAll = false;
break;
}
}
else
{
if( match != asUINT(-1) )
{
// Can't name an argument that was already passed
matchedAll = false;
break;
}
}
}
// Check that every named argument was matched
if( matchedAll )
{
for( n = 0; n < namedArgs->GetLength(); ++n )
{
asSNamedArgument &named = (*namedArgs)[n];
if( named.match == asUINT(-1) )
{
matchedAll = false;
break;
}
// Add to the cost
cost = MatchArgument(desc, named.ctx, named.match, allowObjectConstruct);
if( cost == asUINT(-1) )
{
matchedAll = false;
break;
}
matchingFuncs[i].cost += cost;
}
}
if( !matchedAll )
{
// Remove the function, we didn't match all the arguments.
if( i == matchingFuncs.GetLength()-1 )
matchingFuncs.PopLast();
else
matchingFuncs[i] = matchingFuncs.PopLast();
i--;
}
}
}
// Select the overload(s) with the lowest overall cost
funcs.SetLength(0);
asUINT bestCost = asUINT(-1);
for( n = 0; n < matchingFuncs.GetLength(); ++n )
{
cost = matchingFuncs[n].cost;
if( cost < bestCost )
{
funcs.SetLength(0);
bestCost = cost;
}
if( cost == bestCost )
funcs.PushLast( matchingFuncs[n].funcId );
}
// Cost returned is equivalent to the best cost discovered
cost = bestCost;
}
if( !isConstMethod )
FilterConst(funcs);
if( funcs.GetLength() != 1 && !silent )
{
// Build a readable string of the function with parameter types
bool attemptsPassingClassMethod = false;
asCString str;
if( scope != "" && scope != "::" )
str = scope + "::";
str += name;
str += "(";
for( n = 0; n < args.GetLength(); n++ )
{
if( n > 0 )
str += ", ";
if( args[n]->methodName != "" )
{
if( args[n]->IsClassMethod() )
{
attemptsPassingClassMethod = true;
str += args[n]->type.dataType.GetTypeInfo()->GetName();
str += "::";
}
str += args[n]->methodName;
}
else
str += args[n]->type.dataType.Format(outFunc->nameSpace);
}
if( namedArgs != 0 )
{
for( n = 0; n < namedArgs->GetLength(); n++ )
{
if( n > 0 || args.GetLength() )
str += ", ";
asSNamedArgument &named = (*namedArgs)[n];
str += named.name;
str += "=";
if( named.ctx->methodName != "" )
str += named.ctx->methodName;
else
str += named.ctx->type.dataType.Format(outFunc->nameSpace);
}
}
str += ")";
if( isConstMethod )
str += " const";
if( objectType && scope == "" )
str = objectType->name + "::" + str;
if( funcs.GetLength() == 0 )
{
str.Format(TXT_NO_MATCHING_SIGNATURES_TO_s, str.AddressOf());
Error(str, node);
if( attemptsPassingClassMethod )
{
// Class methods must use delegate objects
Error(TXT_CANNOT_PASS_CLASS_METHOD_AS_ARG, node);
}
else
{
// Print the list of candidates
if( origFuncs.GetLength() > 0 )
{
int r = 0, c = 0;
asASSERT( node );
if( node ) script->ConvertPosToRowCol(node->tokenPos, &r, &c);
builder->WriteInfo(script->name.AddressOf(), TXT_CANDIDATES_ARE, r, c, false);
PrintMatchingFuncs(origFuncs, node, objectType);
}
}
}
else
{
asASSERT( attemptsPassingClassMethod == false );
str.Format(TXT_MULTIPLE_MATCHING_SIGNATURES_TO_s, str.AddressOf());
Error(str, node);
PrintMatchingFuncs(funcs, node, objectType);
}
}
return cost;
}
bool asCCompiler::CompileAutoType(asCDataType &type, asCExprContext &compiledCtx, asCScriptNode *node, asCScriptNode *errNode)
{
if( node && node->nodeType == snAssignment )
{
int r = CompileAssignment(node, &compiledCtx);
if( r >= 0 )
{
// Must not have unused ambiguous names
if (compiledCtx.IsClassMethod() || compiledCtx.IsGlobalFunc())
{
// TODO: Should mention that the problem is the ambiguous name
Error(TXT_CANNOT_RESOLVE_AUTO, errNode);
return false;
}
// Must not have unused anonymous functions
if (compiledCtx.IsLambda())
{
// TODO: Should mention that the problem is the anonymous function
Error(TXT_CANNOT_RESOLVE_AUTO, errNode);
return false;
}
asCDataType newType = compiledCtx.type.dataType;
// Handle const qualifier on auto
if (type.IsReadOnly())
newType.MakeReadOnly(true);
else if (newType.IsPrimitive())
newType.MakeReadOnly(false);
// Handle reference/value stuff
newType.MakeReference(false);
if (!newType.IsObjectHandle())
{
// We got a value object or an object reference.
// Turn the variable into a handle if specified
// as auto@, otherwise make it a 'value'.
if (type.IsHandleToAuto())
{
if (newType.MakeHandle(true) < 0)
{
Error(TXT_OBJECT_HANDLE_NOT_SUPPORTED, errNode);
return false;
}
}
}
type = newType;
return true;
}
return false;
}
else
{
Error(TXT_CANNOT_RESOLVE_AUTO, errNode);
type = asCDataType::CreatePrimitive(ttInt, false);
return false;
}
}
void asCCompiler::CompileDeclaration(asCScriptNode *decl, asCByteCode *bc)
{
// Get the data type
asCDataType type = builder->CreateDataTypeFromNode(decl->firstChild, script, outFunc->nameSpace, false, outFunc->objectType);
// Declare all variables in this declaration
asCScriptNode *node = decl->firstChild->next;
while( node )
{
// If this is an auto type, we have to compile the assignment now to figure out the type
asCExprContext compiledCtx(engine);
bool preCompiled = false;
if (type.IsAuto())
{
preCompiled = CompileAutoType(type, compiledCtx, node->next, node);
if (!preCompiled)
{
// If it wasn't possible to determine the type from the expression then there
// is no need to continue with the initialization. The error was already reported
// in CompileAutoType.
return;
}
}
// Is the type allowed?
if( !type.CanBeInstantiated() )
{
asCString str;
if( type.IsAbstractClass() )
str.Format(TXT_ABSTRACT_CLASS_s_CANNOT_BE_INSTANTIATED, type.Format(outFunc->nameSpace).AddressOf());
else if( type.IsInterface() )
str.Format(TXT_INTERFACE_s_CANNOT_BE_INSTANTIATED, type.Format(outFunc->nameSpace).AddressOf());
else
// TODO: Improve error message to explain why
str.Format(TXT_DATA_TYPE_CANT_BE_s, type.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
// Use int instead to avoid further problems
type = asCDataType::CreatePrimitive(ttInt, false);
}
// A shared object may not declare variables of non-shared types
if( outFunc->IsShared() )
{
asCTypeInfo *ot = type.GetTypeInfo();
if( ot && !ot->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, ot->name.AddressOf());
Error(msg, decl);
}
}
// Get the name of the identifier
asCString name(&script->code[node->tokenPos], node->tokenLength);
// Verify that the name isn't used by a dynamic data type
// TODO: Must check against registered funcdefs too
if( engine->GetRegisteredType(name.AddressOf(), outFunc->nameSpace) != 0 )
{
asCString str;
str.Format(TXT_ILLEGAL_VARIABLE_NAME_s, name.AddressOf());
Error(str, node);
}
int offset = AllocateVariable(type, false);
if( variables->DeclareVariable(name.AddressOf(), type, offset, IsVariableOnHeap(offset)) < 0 )
{
// TODO: It might be an out-of-memory too
asCString str;
str.Format(TXT_s_ALREADY_DECLARED, name.AddressOf());
Error(str, node);
// Don't continue after this error, as it will just
// lead to more errors that are likely false
return;
}
else
{
// Warn if this variable hides another variable in a higher scope
//if( variables->parent && variables->parent->GetVariable(name.AddressOf()) )
//{
// asCString str;
// str.Format(TXT_s_HIDES_VAR_IN_OUTER_SCOPE, name.AddressOf());
// Warning(str, node);
//}
}
// Add marker that the variable has been declared
bc->VarDecl((int)outFunc->scriptData->variables.GetLength());
outFunc->AddVariable(name, type, offset);
// Keep the node for the variable decl
asCScriptNode *varNode = node;
node = node->next;
if( node == 0 || node->nodeType == snIdentifier )
{
// Initialize with default constructor
CompileInitialization(0, bc, type, varNode, offset, 0, 0);
}
else
{
// Compile the initialization expression
asQWORD constantValue = 0;
if( CompileInitialization(node, bc, type, varNode, offset, &constantValue, 0, preCompiled ? &compiledCtx : 0) )
{
// Check if the variable should be marked as pure constant
if( type.IsPrimitive() && type.IsReadOnly() )
{
sVariable *v = variables->GetVariable(name.AddressOf());
v->isPureConstant = true;
v->constantValue = constantValue;
}
}
node = node->next;
}
}
bc->OptimizeLocally(tempVariableOffsets);
}
// Returns true if the initialization expression is a constant expression
bool asCCompiler::CompileInitialization(asCScriptNode *node, asCByteCode *bc, asCDataType &type, asCScriptNode *errNode, int offset, asQWORD *constantValue, int isVarGlobOrMem, asCExprContext *preCompiled)
{
bool isConstantExpression = false;
if( node && node->nodeType == snArgList )
{
// Make sure it is an object and not a handle
if( type.GetTypeInfo() == 0 || type.IsObjectHandle() )
{
Error(TXT_MUST_BE_OBJECT, node);
}
else
{
// Compile the arguments
asCArray<asCExprContext *> args;
asCArray<asSNamedArgument> namedArgs;
if( CompileArgumentList(node, args, namedArgs) >= 0 )
{
// Find all constructors
asCArray<int> funcs;
asSTypeBehaviour *beh = type.GetBehaviour();
if( beh )
{
if( type.GetTypeInfo()->flags & asOBJ_REF )
funcs = beh->factories;
else
funcs = beh->constructors;
}
asCString str = type.Format(outFunc->nameSpace);
MatchFunctions(funcs, args, node, str.AddressOf(), &namedArgs);
if( funcs.GetLength() == 1 )
{
// Add the default values for arguments not explicitly supplied
int r = CompileDefaultAndNamedArgs(node, args, funcs[0], type.GetTypeInfo()->CastToObjectType(), &namedArgs);
if( r == asSUCCESS )
{
asCExprContext ctx(engine);
if( type.GetTypeInfo() && (type.GetTypeInfo()->flags & asOBJ_REF) )
{
if( isVarGlobOrMem == 0 )
MakeFunctionCall(&ctx, funcs[0], 0, args, node, true, offset);
else
{
MakeFunctionCall(&ctx, funcs[0], 0, args, node);
ctx.bc.Instr(asBC_RDSPtr);
if( isVarGlobOrMem == 1 )
{
// Store the returned handle in the global variable
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
// Store the returned handle in the member
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
if( type.IsFuncdef())
ctx.bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx.bc.InstrPTR(asBC_REFCPY, type.GetTypeInfo());
ReleaseTemporaryVariable(ctx.type.stackOffset, &ctx.bc);
}
// Pop the reference left by the function call
ctx.bc.Instr(asBC_PopPtr);
}
else
{
bool onHeap = false;
if( isVarGlobOrMem == 0 )
{
// When the object is allocated on the heap, the address where the
// reference will be stored must be pushed on the stack before the
// arguments. This reference on the stack is safe, even if the script
// is suspended during the evaluation of the arguments.
onHeap = IsVariableOnHeap(offset);
if( onHeap )
ctx.bc.InstrSHORT(asBC_PSF, (short)offset);
}
else if( isVarGlobOrMem == 1 )
{
// Push the address of the location where the variable will be stored on the stack.
// This reference is safe, because the addresses of the global variables cannot change.
onHeap = true;
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
// Value types may be allocated inline if they are POD types
onHeap = !(type.IsObject() || type.IsFuncdef()) || type.IsReference() || (type.GetTypeInfo()->flags & asOBJ_REF);
if( onHeap )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
}
PrepareFunctionCall(funcs[0], &ctx.bc, args);
MoveArgsToStack(funcs[0], &ctx.bc, args, false);
// When the object is allocated on the stack, the address to the
// object is pushed on the stack after the arguments as the object pointer
if( !onHeap )
{
if( isVarGlobOrMem == 2 )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
else
{
ctx.bc.InstrSHORT(asBC_PSF, (short)offset);
}
}
PerformFunctionCall(funcs[0], &ctx, onHeap, &args, type.GetTypeInfo()->CastToObjectType());
if( isVarGlobOrMem == 0 )
{
// Mark the object in the local variable as initialized
ctx.bc.ObjInfo(offset, asOBJ_INIT);
}
}
bc->AddCode(&ctx.bc);
}
}
}
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
for( asUINT n = 0; n < namedArgs.GetLength(); n++ )
if( namedArgs[n].ctx )
{
asDELETE(namedArgs[n].ctx, asCExprContext);
}
}
}
else if( node && node->nodeType == snInitList )
{
asCExprValue ti;
ti.Set(type);
ti.isVariable = (isVarGlobOrMem == 0);
ti.isTemporary = false;
ti.stackOffset = (short)offset;
ti.isLValue = true;
CompileInitList(&ti, node, bc, isVarGlobOrMem);
}
else if( node && node->nodeType == snAssignment )
{
// Compile the expression
asCExprContext newExpr(engine);
asCExprContext* expr;
int r = 0;
if( preCompiled )
{
expr = preCompiled;
}
else
{
expr = &newExpr;
r = CompileAssignment(node, expr);
}
// Look for appropriate constructor
asCArray<int> funcs;
asCArray<asCExprContext *> args;
// Handles must use the handle assignment operation.
// Types that are ASHANDLE must not allow the use of the constructor in this case,
// because it is ambiguous whether a value assignment or handle assignment will be done.
// Only do this if the expression is of the same type, as the expression is an assignment
// and an initialization constructor may not have the same meaning.
// TODO: Should allow initialization constructor if it is declared as allowed for implicit conversions.
if( !type.IsObjectHandle() && !expr->type.isExplicitHandle &&
!(type.GetTypeInfo() && (type.GetTypeInfo()->GetFlags() & asOBJ_ASHANDLE)) &&
type.IsEqualExceptRefAndConst(expr->type.dataType) )
{
asSTypeBehaviour *beh = type.GetBehaviour();
if( beh )
{
if( type.GetTypeInfo()->flags & asOBJ_REF )
funcs = beh->factories;
else
funcs = beh->constructors;
}
asCString str = type.Format(outFunc->nameSpace);
args.PushLast(expr);
MatchFunctions(funcs, args, node, str.AddressOf(), 0, 0, 0, true);
}
if( funcs.GetLength() == 1 )
{
// Use the constructor
// TODO: clean-up: A large part of this is identical to the initalization with argList above
// Add the default values for arguments not explicitly supplied
r = CompileDefaultAndNamedArgs(node, args, funcs[0], type.GetTypeInfo()->CastToObjectType());
if( r == asSUCCESS )
{
asCExprContext ctx(engine);
if( type.GetTypeInfo() && (type.GetTypeInfo()->flags & asOBJ_REF) )
{
if( isVarGlobOrMem == 0 )
MakeFunctionCall(&ctx, funcs[0], 0, args, node, true, offset);
else
{
MakeFunctionCall(&ctx, funcs[0], 0, args, node);
ctx.bc.Instr(asBC_RDSPtr);
if( isVarGlobOrMem == 1 )
{
// Store the returned handle in the global variable
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
// Store the returned handle in the member
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
if( type.IsFuncdef() )
ctx.bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx.bc.InstrPTR(asBC_REFCPY, type.GetTypeInfo());
ReleaseTemporaryVariable(ctx.type.stackOffset, &ctx.bc);
}
// Pop the reference left by the function call
ctx.bc.Instr(asBC_PopPtr);
}
else
{
bool onHeap = false;
if( isVarGlobOrMem == 0 )
{
// When the object is allocated on the heap, the address where the
// reference will be stored must be pushed on the stack before the
// arguments. This reference on the stack is safe, even if the script
// is suspended during the evaluation of the arguments.
onHeap = IsVariableOnHeap(offset);
if( onHeap )
ctx.bc.InstrSHORT(asBC_PSF, (short)offset);
}
else if( isVarGlobOrMem == 1 )
{
// Push the address of the location where the variable will be stored on the stack.
// This reference is safe, because the addresses of the global variables cannot change.
onHeap = true;
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
// Value types may be allocated inline if they are POD types
onHeap = !(type.IsObject() || type.IsFuncdef()) || type.IsReference() || (type.GetTypeInfo()->flags & asOBJ_REF);
if( onHeap )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
}
PrepareFunctionCall(funcs[0], &ctx.bc, args);
MoveArgsToStack(funcs[0], &ctx.bc, args, false);
// When the object is allocated on the stack, the address to the
// object is pushed on the stack after the arguments as the object pointer
if( !onHeap )
{
if( isVarGlobOrMem == 2 )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
else
{
ctx.bc.InstrSHORT(asBC_PSF, (short)offset);
}
}
PerformFunctionCall(funcs[0], &ctx, onHeap, &args, type.GetTypeInfo()->CastToObjectType());
if( isVarGlobOrMem == 0 )
{
// Mark the object in the local variable as initialized
ctx.bc.ObjInfo(offset, asOBJ_INIT);
}
}
bc->AddCode(&ctx.bc);
}
}
else
{
// Call the default constructur, then call the assignment operator
asCExprContext ctx(engine);
// Call the default constructor here
if( isVarGlobOrMem == 0 )
CallDefaultConstructor(type, offset, IsVariableOnHeap(offset), &ctx.bc, errNode);
else if( isVarGlobOrMem == 1 )
CallDefaultConstructor(type, offset, true, &ctx.bc, errNode, isVarGlobOrMem);
else if( isVarGlobOrMem == 2 )
CallDefaultConstructor(type, offset, type.IsReference(), &ctx.bc, errNode, isVarGlobOrMem);
if( r >= 0 )
{
if( type.IsPrimitive() )
{
if( type.IsReadOnly() && expr->type.isConstant )
{
ImplicitConversion(expr, type, node, asIC_IMPLICIT_CONV);
// Tell caller that the expression is a constant so it can mark the variable as pure constant
isConstantExpression = true;
*constantValue = expr->type.qwordValue;
}
asCExprContext lctx(engine);
if( isVarGlobOrMem == 0 )
lctx.type.SetVariable(type, offset, false);
else if( isVarGlobOrMem == 1 )
{
lctx.type.Set(type);
lctx.type.dataType.MakeReference(true);
// If it is an enum value, i.e. offset is negative, that is being compiled then
// we skip this as the bytecode won't be used anyway, only the constant value
if( offset >= 0 )
lctx.bc.InstrPTR(asBC_LDG, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
asASSERT( isVarGlobOrMem == 2 );
lctx.type.Set(type);
lctx.type.dataType.MakeReference(true);
// Load the reference of the primitive member into the register
lctx.bc.InstrSHORT(asBC_PSF, 0);
lctx.bc.Instr(asBC_RDSPtr);
lctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
lctx.bc.Instr(asBC_PopRPtr);
}
lctx.type.dataType.MakeReadOnly(false);
lctx.type.isLValue = true;
DoAssignment(&ctx, &lctx, expr, node, node, ttAssignment, node);
ProcessDeferredParams(&ctx);
}
else
{
// TODO: runtime optimize: Here we should look for the best matching constructor, instead of
// just the copy constructor. Only if no appropriate constructor is
// available should the assignment operator be used.
asCExprContext lexpr(engine);
lexpr.type.Set(type);
if( isVarGlobOrMem == 0 )
lexpr.type.dataType.MakeReference(IsVariableOnHeap(offset));
else if( isVarGlobOrMem == 1 )
lexpr.type.dataType.MakeReference(true);
else if( isVarGlobOrMem == 2 )
{
if( !lexpr.type.dataType.IsObject() || lexpr.type.dataType.IsFuncdef() || (lexpr.type.dataType.GetTypeInfo()->flags & asOBJ_REF) )
lexpr.type.dataType.MakeReference(true);
}
// Allow initialization of constant variables
lexpr.type.dataType.MakeReadOnly(false);
if( type.IsObjectHandle() )
lexpr.type.isExplicitHandle = true;
if( isVarGlobOrMem == 0 )
{
lexpr.bc.InstrSHORT(asBC_PSF, (short)offset);
lexpr.type.stackOffset = (short)offset;
lexpr.type.isVariable = true;
}
else if( isVarGlobOrMem == 1 )
{
lexpr.bc.InstrPTR(asBC_PGA, engine->globalProperties[offset]->GetAddressOfValue());
}
else
{
lexpr.bc.InstrSHORT(asBC_PSF, 0);
lexpr.bc.Instr(asBC_RDSPtr);
lexpr.bc.InstrSHORT_DW(asBC_ADDSi, (short)offset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
lexpr.type.stackOffset = -1;
}
lexpr.type.isLValue = true;
// If left expression resolves into a registered type
// check if the assignment operator is overloaded, and check
// the type of the right hand expression. If none is found
// the default action is a direct copy if it is the same type
// and a simple assignment.
bool assigned = false;
// Even though an ASHANDLE can be an explicit handle the overloaded operator needs to be called
if( (lexpr.type.dataType.IsObject() || lexpr.type.dataType.IsFuncdef()) && (!lexpr.type.isExplicitHandle || (lexpr.type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE)) )
{
bool useHndlAssign = lexpr.type.dataType.IsHandleToAsHandleType();
assigned = CompileOverloadedDualOperator(node, &lexpr, expr, &ctx, useHndlAssign);
if( assigned )
{
// Pop the resulting value
if( !ctx.type.dataType.IsPrimitive() )
ctx.bc.Instr(asBC_PopPtr);
// Release the argument
ProcessDeferredParams(&ctx);
// Release temporary variable that may be allocated by the overloaded operator
ReleaseTemporaryVariable(ctx.type, &ctx.bc);
}
}
if( !assigned )
{
PrepareForAssignment(&lexpr.type.dataType, expr, node, false);
// If the expression is constant and the variable also is constant
// then mark the variable as pure constant. This will allow the compiler
// to optimize expressions with this variable.
if( type.IsReadOnly() && expr->type.isConstant )
{
isConstantExpression = true;
*constantValue = expr->type.qwordValue;
}
// Add expression code to bytecode
MergeExprBytecode(&ctx, expr);
// Add byte code for storing value of expression in variable
ctx.bc.AddCode(&lexpr.bc);
PerformAssignment(&lexpr.type, &expr->type, &ctx.bc, errNode);
// Release temporary variables used by expression
ReleaseTemporaryVariable(expr->type, &ctx.bc);
ctx.bc.Instr(asBC_PopPtr);
ProcessDeferredParams(&ctx);
}
}
}
bc->AddCode(&ctx.bc);
}
}
else
{
asASSERT( node == 0 );
// Call the default constructor here, as no explicit initialization is done
if( isVarGlobOrMem == 0 )
CallDefaultConstructor(type, offset, IsVariableOnHeap(offset), bc, errNode);
else if( isVarGlobOrMem == 1 )
CallDefaultConstructor(type, offset, true, bc, errNode, isVarGlobOrMem);
else if( isVarGlobOrMem == 2 )
{
if( !(type.IsObject() || type.IsFuncdef()) || type.IsReference() || (type.GetTypeInfo()->flags & asOBJ_REF) )
CallDefaultConstructor(type, offset, true, bc, errNode, isVarGlobOrMem);
else
CallDefaultConstructor(type, offset, false, bc, errNode, isVarGlobOrMem);
}
}
return isConstantExpression;
}
void asCCompiler::CompileInitList(asCExprValue *var, asCScriptNode *node, asCByteCode *bc, int isVarGlobOrMem)
{
// Check if the type supports initialization lists
if( var->dataType.GetTypeInfo() == 0 ||
var->dataType.GetBehaviour()->listFactory == 0 )
{
asCString str;
str.Format(TXT_INIT_LIST_CANNOT_BE_USED_WITH_s, var->dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
return;
}
// Construct the buffer with the elements
// Find the list factory
int funcId = var->dataType.GetBehaviour()->listFactory;
asASSERT( engine->scriptFunctions[funcId]->listPattern );
// TODO: runtime optimize: A future optimization should be to use the stack space directly
// for small buffers so that the dynamic allocation is skipped
// Create a new special object type for the lists. Both asCRestore and the
// context exception handler will need this to know how to parse the buffer.
asCObjectType *listPatternType = engine->GetListPatternType(funcId);
// Allocate a temporary variable to hold the pointer to the buffer
int bufferVar = AllocateVariable(asCDataType::CreateType(listPatternType, false), true);
asUINT bufferSize = 0;
// Evaluate all elements of the list
asCExprContext valueExpr(engine);
asCScriptNode *el = node;
asSListPatternNode *patternNode = engine->scriptFunctions[listPatternType->templateSubTypes[0].GetBehaviour()->listFactory]->listPattern;
int elementsInSubList = -1;
int r = CompileInitListElement(patternNode, el, engine->GetTypeIdFromDataType(asCDataType::CreateType(listPatternType, false)), short(bufferVar), bufferSize, valueExpr.bc, elementsInSubList);
asASSERT( r || patternNode == 0 );
UNUSED_VAR(r);
// After all values have been evaluated we know the final size of the buffer
asCExprContext allocExpr(engine);
allocExpr.bc.InstrSHORT_DW(asBC_AllocMem, short(bufferVar), bufferSize);
// Merge the bytecode into the final sequence
bc->AddCode(&allocExpr.bc);
bc->AddCode(&valueExpr.bc);
// The object itself is the last to be created and will receive the pointer to the buffer
asCArray<asCExprContext *> args;
asCExprContext arg1(engine);
arg1.type.Set(asCDataType::CreatePrimitive(ttUInt, false));
arg1.type.dataType.MakeReference(true);
arg1.bc.InstrSHORT(asBC_PshVPtr, short(bufferVar));
args.PushLast(&arg1);
asCExprContext ctx(engine);
if( var->isVariable )
{
asASSERT( isVarGlobOrMem == 0 );
if( var->dataType.GetTypeInfo()->GetFlags() & asOBJ_REF )
{
ctx.bc.AddCode(&arg1.bc);
// Call factory and store the handle in the given variable
PerformFunctionCall(funcId, &ctx, false, &args, 0, true, var->stackOffset);
ctx.bc.Instr(asBC_PopPtr);
}
else
{
// Call the constructor
// When the object is allocated on the heap, the address where the
// reference will be stored must be pushed on the stack before the
// arguments. This reference on the stack is safe, even if the script
// is suspended during the evaluation of the arguments.
bool onHeap = IsVariableOnHeap(var->stackOffset);
if( onHeap )
ctx.bc.InstrSHORT(asBC_PSF, var->stackOffset);
ctx.bc.AddCode(&arg1.bc);
// When the object is allocated on the stack, the address to the
// object is pushed on the stack after the arguments as the object pointer
if( !onHeap )
ctx.bc.InstrSHORT(asBC_PSF, var->stackOffset);
PerformFunctionCall(funcId, &ctx, onHeap, &args, var->dataType.GetTypeInfo()->CastToObjectType());
// Mark the object in the local variable as initialized
ctx.bc.ObjInfo(var->stackOffset, asOBJ_INIT);
}
}
else
{
if( var->dataType.GetTypeInfo()->GetFlags() & asOBJ_REF )
{
ctx.bc.AddCode(&arg1.bc);
PerformFunctionCall(funcId, &ctx, false, &args);
ctx.bc.Instr(asBC_RDSPtr);
if( isVarGlobOrMem == 1 )
{
// Store the returned handle in the global variable
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[var->stackOffset]->GetAddressOfValue());
}
else
{
// Store the returned handle in the member
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)var->stackOffset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
if (var->dataType.IsFuncdef())
ctx.bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx.bc.InstrPTR(asBC_REFCPY, var->dataType.GetTypeInfo());
ctx.bc.Instr(asBC_PopPtr);
ReleaseTemporaryVariable(ctx.type.stackOffset, &ctx.bc);
}
else
{
bool onHeap = true;
// Put the address where the object pointer will be placed on the stack
if( isVarGlobOrMem == 1 )
ctx.bc.InstrPTR(asBC_PGA, engine->globalProperties[var->stackOffset]->GetAddressOfValue());
else
{
onHeap = !(var->dataType.IsObject() || var->dataType.IsFuncdef()) || var->dataType.IsReference() || (var->dataType.GetTypeInfo()->flags & asOBJ_REF);
if( onHeap )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)var->stackOffset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
}
// Add the address of the list buffer as the argument
ctx.bc.AddCode(&arg1.bc);
if( !onHeap )
{
ctx.bc.InstrSHORT(asBC_PSF, 0);
ctx.bc.Instr(asBC_RDSPtr);
ctx.bc.InstrSHORT_DW(asBC_ADDSi, (short)var->stackOffset, engine->GetTypeIdFromDataType(asCDataType::CreateType(outFunc->objectType, false)));
}
// Call the ALLOC instruction to allocate memory and invoke constructor
PerformFunctionCall(funcId, &ctx, onHeap, &args, var->dataType.GetTypeInfo()->CastToObjectType());
}
}
bc->AddCode(&ctx.bc);
// Free the temporary buffer. The FREE instruction will make sure to destroy
// each element in the buffer so there is no need to do this manually
bc->InstrW_PTR(asBC_FREE, short(bufferVar), listPatternType);
ReleaseTemporaryVariable(bufferVar, bc);
}
int asCCompiler::CompileInitListElement(asSListPatternNode *&patternNode, asCScriptNode *&valueNode, int bufferTypeId, short bufferVar, asUINT &bufferSize, asCByteCode &bcInit, int &elementsInSubList)
{
if( patternNode->type == asLPT_START )
{
if( valueNode == 0 || valueNode->nodeType != snInitList )
{
Error(TXT_EXPECTED_LIST, valueNode);
return -1;
}
// Compile all values until asLPT_END
patternNode = patternNode->next;
asCScriptNode *node = valueNode->firstChild;
while( patternNode->type != asLPT_END )
{
// Check for missing value here, else the error reporting will not have a source position to report the error for
if( node == 0 && patternNode->type == asLPT_TYPE )
{
Error(TXT_NOT_ENOUGH_VALUES_FOR_LIST, valueNode);
return -1;
}
asCScriptNode *errNode = node;
int r = CompileInitListElement(patternNode, node, bufferTypeId, bufferVar, bufferSize, bcInit, elementsInSubList);
if( r < 0 ) return r;
if( r == 1 )
{
asASSERT( engine->ep.disallowEmptyListElements );
// Empty elements in the middle are not allowed
Error(TXT_EMPTY_LIST_ELEMENT_IS_NOT_ALLOWED, errNode);
}
asASSERT( patternNode );
}
if( node )
{
Error(TXT_TOO_MANY_VALUES_FOR_LIST, valueNode);
return -1;
}
// Move to the next node
valueNode = valueNode->next;
patternNode = patternNode->next;
}
else if( patternNode->type == asLPT_REPEAT || patternNode->type == asLPT_REPEAT_SAME )
{
// TODO: list: repeat_inner should make sure the list has the same size as the inner list, i.e. square area
// TODO: list: repeat_prev should make sure the list is the same size as the previous
asEListPatternNodeType repeatType = patternNode->type;
asCScriptNode *firstValue = valueNode;
// The following values will be repeated N times
patternNode = patternNode->next;
// Keep track of the patternNode so it can be reset
asSListPatternNode *nextNode = patternNode;
// Align the buffer size to 4 bytes in case previous value was smaller than 4 bytes
if( bufferSize & 0x3 )
bufferSize += 4 - (bufferSize & 0x3);
// The first dword will hold the number of elements in the list
asDWORD currSize = bufferSize;
bufferSize += 4;
asUINT countElements = 0;
int elementsInSubSubList = -1;
asCExprContext ctx(engine);
while( valueNode )
{
patternNode = nextNode;
asCScriptNode *errNode = valueNode;
int r = CompileInitListElement(patternNode, valueNode, bufferTypeId, bufferVar, bufferSize, ctx.bc, elementsInSubSubList);
if( r < 0 ) return r;
if( r == 0 )
countElements++;
else
{
asASSERT( r == 1 && engine->ep.disallowEmptyListElements );
if( valueNode )
{
// Empty elements in the middle are not allowed
Error(TXT_EMPTY_LIST_ELEMENT_IS_NOT_ALLOWED, errNode);
}
}
}
if( countElements == 0 )
{
// Skip the sub pattern that was expected to be repeated, otherwise the caller will try to match these when we return
patternNode = nextNode;
if( patternNode->type == asLPT_TYPE )
patternNode = patternNode->next;
else if( patternNode->type == asLPT_START )
{
int subCount = 1;
do
{
patternNode = patternNode->next;
if( patternNode->type == asLPT_START )
subCount++;
else if( patternNode->type == asLPT_END )
subCount--;
} while( subCount > 0 );
patternNode = patternNode->next;
}
}
// For repeat_same each repeated sublist must have the same size to form a rectangular array
if( repeatType == asLPT_REPEAT_SAME && elementsInSubList != -1 && asUINT(elementsInSubList) != countElements )
{
if( countElements < asUINT(elementsInSubList) )
Error(TXT_NOT_ENOUGH_VALUES_FOR_LIST, firstValue);
else
Error(TXT_TOO_MANY_VALUES_FOR_LIST, firstValue);
return -1;
}
else
{
// Return to caller the amount of elments in this sublist
elementsInSubList = countElements;
}
// The first dword in the buffer will hold the number of elements
bcInit.InstrSHORT_DW_DW(asBC_SetListSize, bufferVar, currSize, countElements);
// Add the values
bcInit.AddCode(&ctx.bc);
}
else if( patternNode->type == asLPT_TYPE )
{
bool isEmpty = false;
// Determine the size of the element
asUINT size = 0;
asCDataType dt = reinterpret_cast<asSListPatternDataTypeNode*>(patternNode)->dataType;
if( valueNode->nodeType == snAssignment || valueNode->nodeType == snInitList )
{
asCExprContext lctx(engine);
asCExprContext rctx(engine);
if( valueNode->nodeType == snAssignment )
{
// Compile the assignment expression
CompileAssignment(valueNode, &rctx);
if( dt.GetTokenType() == ttQuestion )
{
// We now know the type
dt = rctx.type.dataType;
dt.MakeReadOnly(false);
dt.MakeReference(false);
// Values on the list must be aligned to 32bit boundaries, except if the type is smaller than 32bit.
if( bufferSize & 0x3 )
bufferSize += 4 - (bufferSize & 0x3);
// Place the type id in the buffer
bcInit.InstrSHORT_DW_DW(asBC_SetListType, bufferVar, bufferSize, engine->GetTypeIdFromDataType(dt));
bufferSize += 4;
}
}
else if( valueNode->nodeType == snInitList )
{
if( dt.GetTokenType() == ttQuestion )
{
// Can't use init lists with var type as it is not possible to determine what type should be allocated
asCString str;
str.Format(TXT_INIT_LIST_CANNOT_BE_USED_WITH_s, "?");
Error(str.AddressOf(), valueNode);
rctx.type.SetDummy();
dt = rctx.type.dataType;
}
else
{
// Allocate a temporary variable that will be initialized with the list
int offset = AllocateVariable(dt, true);
rctx.type.Set(dt);
rctx.type.isVariable = true;
rctx.type.isTemporary = true;
rctx.type.stackOffset = (short)offset;
CompileInitList(&rctx.type, valueNode, &rctx.bc, 0);
// Put the object on the stack
rctx.bc.InstrSHORT(asBC_PSF, rctx.type.stackOffset);
// It is a reference that we place on the stack
rctx.type.dataType.MakeReference(true);
}
}
// Determine size of the element
if( dt.IsPrimitive() || (!dt.IsNullHandle() && (dt.GetTypeInfo()->flags & asOBJ_VALUE)) )
size = dt.GetSizeInMemoryBytes();
else
size = AS_PTR_SIZE*4;
// Values on the list must be aligned to 32bit boundaries, except if the type is smaller than 32bit.
if( size >= 4 && (bufferSize & 0x3) )
bufferSize += 4 - (bufferSize & 0x3);
// Compile the lvalue
lctx.bc.InstrSHORT_DW(asBC_PshListElmnt, bufferVar, bufferSize);
lctx.type.Set(dt);
lctx.type.isLValue = true;
if( dt.IsPrimitive() )
{
lctx.bc.Instr(asBC_PopRPtr);
lctx.type.dataType.MakeReference(true);
}
else if( dt.IsObjectHandle() ||
dt.GetTypeInfo()->flags & asOBJ_REF )
{
lctx.type.isExplicitHandle = true;
lctx.type.dataType.MakeReference(true);
}
else
{
asASSERT( dt.GetTypeInfo()->flags & asOBJ_VALUE );
// Make sure the object has been constructed before the assignment
// TODO: runtime optimize: Use copy constructor instead of assignment to initialize the objects
asSTypeBehaviour *beh = dt.GetBehaviour();
int func = 0;
if( beh ) func = beh->construct;
if( func == 0 && (dt.GetTypeInfo()->flags & asOBJ_POD) == 0 )
{
asCString str;
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, dt.GetTypeInfo()->GetName());
Error(str, valueNode);
}
else if( func )
{
// Call the constructor as a normal function
bcInit.InstrSHORT_DW(asBC_PshListElmnt, bufferVar, bufferSize);
asCExprContext ctx(engine);
PerformFunctionCall(func, &ctx, false, 0, dt.GetTypeInfo()->CastToObjectType());
bcInit.AddCode(&ctx.bc);
}
}
if( lctx.type.dataType.IsNullHandle() )
{
// Don't add any code to assign a null handle. RefCpy doesn't work without a known type.
// The buffer is already initialized to zero in asBC_AllocMem anyway.
asASSERT( rctx.bc.GetLastInstr() == asBC_PshNull );
asASSERT( reinterpret_cast<asSListPatternDataTypeNode*>(patternNode)->dataType.GetTokenType() == ttQuestion );
}
else
{
asCExprContext ctx(engine);
DoAssignment(&ctx, &lctx, &rctx, valueNode, valueNode, ttAssignment, valueNode);
if( !lctx.type.dataType.IsPrimitive() )
ctx.bc.Instr(asBC_PopPtr);
// Release temporary variables used by expression
ReleaseTemporaryVariable(ctx.type, &ctx.bc);
ProcessDeferredParams(&ctx);
bcInit.AddCode(&ctx.bc);
}
}
else
{
if( builder->engine->ep.disallowEmptyListElements )
{
// Empty elements are not allowed, except if it is the last in the list
isEmpty = true;
}
else
{
// There is no specific value so we need to fill it with a default value
if( dt.GetTokenType() == ttQuestion )
{
// Values on the list must be aligned to 32bit boundaries, except if the type is smaller than 32bit.
if( bufferSize & 0x3 )
bufferSize += 4 - (bufferSize & 0x3);
// Place the type id for a null handle in the buffer
bcInit.InstrSHORT_DW_DW(asBC_SetListType, bufferVar, bufferSize, 0);
bufferSize += 4;
dt = asCDataType::CreateNullHandle();
// No need to initialize the handle as the buffer is already initialized with zeroes
}
else if( dt.GetTypeInfo() && dt.GetTypeInfo()->flags & asOBJ_VALUE )
{
// For value types with default constructor we need to call the constructor
asSTypeBehaviour *beh = dt.GetBehaviour();
int func = 0;
if( beh ) func = beh->construct;
if( func == 0 && (dt.GetTypeInfo()->flags & asOBJ_POD) == 0 )
{
asCString str;
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, dt.GetTypeInfo()->GetName());
Error(str, valueNode);
}
else if( func )
{
// Values on the list must be aligned to 32bit boundaries, except if the type is smaller than 32bit.
if( bufferSize & 0x3 )
bufferSize += 4 - (bufferSize & 0x3);
// Call the constructor as a normal function
bcInit.InstrSHORT_DW(asBC_PshListElmnt, bufferVar, bufferSize);
asCExprContext ctx(engine);
PerformFunctionCall(func, &ctx, false, 0, dt.GetTypeInfo()->CastToObjectType());
bcInit.AddCode(&ctx.bc);
}
}
else if( !dt.IsObjectHandle() && dt.GetTypeInfo() && dt.GetTypeInfo()->flags & asOBJ_REF )
{
// For ref types (not handles) we need to call the default factory
asSTypeBehaviour *beh = dt.GetBehaviour();
int func = 0;
if( beh ) func = beh->factory;
if( func == 0 )
{
asCString str;
str.Format(TXT_NO_DEFAULT_CONSTRUCTOR_FOR_s, dt.GetTypeInfo()->GetName());
Error(str, valueNode);
}
else if( func )
{
asCExprContext rctx(engine);
PerformFunctionCall(func, &rctx, false, 0, dt.GetTypeInfo()->CastToObjectType());
// Values on the list must be aligned to 32bit boundaries, except if the type is smaller than 32bit.
if( bufferSize & 0x3 )
bufferSize += 4 - (bufferSize & 0x3);
asCExprContext lctx(engine);
lctx.bc.InstrSHORT_DW(asBC_PshListElmnt, bufferVar, bufferSize);
lctx.type.Set(dt);
lctx.type.isLValue = true;
lctx.type.isExplicitHandle = true;
lctx.type.dataType.MakeReference(true);
asCExprContext ctx(engine);
DoAssignment(&ctx, &lctx, &rctx, valueNode, valueNode, ttAssignment, valueNode);
if( !lctx.type.dataType.IsPrimitive() )
ctx.bc.Instr(asBC_PopPtr);
// Release temporary variables used by expression
ReleaseTemporaryVariable(ctx.type, &ctx.bc);
ProcessDeferredParams(&ctx);
bcInit.AddCode(&ctx.bc);
}
}
}
}
if( !isEmpty )
{
// Determine size of the element
if( dt.IsPrimitive() || (!dt.IsNullHandle() && (dt.GetTypeInfo()->flags & asOBJ_VALUE)) )
size = dt.GetSizeInMemoryBytes();
else
size = AS_PTR_SIZE*4;
asASSERT( size <= 4 || (size & 0x3) == 0 );
bufferSize += size;
}
// Move to the next element
patternNode = patternNode->next;
valueNode = valueNode->next;
if( isEmpty )
{
// The caller will determine if the empty element should be ignored or not
return 1;
}
}
else
asASSERT( false );
return 0;
}
void asCCompiler::CompileStatement(asCScriptNode *statement, bool *hasReturn, asCByteCode *bc)
{
// Don't clear the hasReturn flag if this is an empty statement
// to avoid false errors of 'not all paths return'
if( statement->nodeType != snExpressionStatement || statement->firstChild )
*hasReturn = false;
if( statement->nodeType == snStatementBlock )
CompileStatementBlock(statement, true, hasReturn, bc);
else if( statement->nodeType == snIf )
CompileIfStatement(statement, hasReturn, bc);
else if( statement->nodeType == snFor )
CompileForStatement(statement, bc);
else if( statement->nodeType == snWhile )
CompileWhileStatement(statement, bc);
else if( statement->nodeType == snDoWhile )
CompileDoWhileStatement(statement, bc);
else if( statement->nodeType == snExpressionStatement )
CompileExpressionStatement(statement, bc);
else if( statement->nodeType == snBreak )
CompileBreakStatement(statement, bc);
else if( statement->nodeType == snContinue )
CompileContinueStatement(statement, bc);
else if( statement->nodeType == snSwitch )
CompileSwitchStatement(statement, hasReturn, bc);
else if( statement->nodeType == snReturn )
{
CompileReturnStatement(statement, bc);
*hasReturn = true;
}
}
void asCCompiler::CompileSwitchStatement(asCScriptNode *snode, bool *, asCByteCode *bc)
{
// TODO: inheritance: Must guarantee that all options in the switch case call a constructor, or that none call it.
// Reserve label for break statements
int breakLabel = nextLabel++;
breakLabels.PushLast(breakLabel);
// Add a variable scope that will be used by CompileBreak
// to know where to stop deallocating variables
AddVariableScope(true, false);
//---------------------------
// Compile the switch expression
//-------------------------------
// Compile the switch expression
asCExprContext expr(engine);
CompileAssignment(snode->firstChild, &expr);
// Verify that the expression is a primitive type
if( !expr.type.dataType.IsIntegerType() && !expr.type.dataType.IsUnsignedType() )
{
Error(TXT_SWITCH_MUST_BE_INTEGRAL, snode->firstChild);
return;
}
ProcessPropertyGetAccessor(&expr, snode);
// TODO: Need to support 64bit integers
// Convert the expression to a 32bit variable
asCDataType to;
if( expr.type.dataType.IsIntegerType() )
to.SetTokenType(ttInt);
else if( expr.type.dataType.IsUnsignedType() )
to.SetTokenType(ttUInt);
// Make sure the value is in a variable
if( expr.type.dataType.IsReference() )
ConvertToVariable(&expr);
ImplicitConversion(&expr, to, snode->firstChild, asIC_IMPLICIT_CONV, true);
ConvertToVariable(&expr);
int offset = expr.type.stackOffset;
ProcessDeferredParams(&expr);
//-------------------------------
// Determine case values and labels
//--------------------------------
// Remember the first label so that we can later pass the
// correct label to each CompileCase()
int firstCaseLabel = nextLabel;
int defaultLabel = 0;
asCArray<int> caseValues;
asCArray<int> caseLabels;
// Compile all case comparisons and make them jump to the right label
asCScriptNode *cnode = snode->firstChild->next;
while( cnode )
{
// Each case should have a constant expression
if( cnode->firstChild && cnode->firstChild->nodeType == snExpression )
{
// Compile expression
asCExprContext c(engine);
CompileExpression(cnode->firstChild, &c);
// Verify that the result is a constant
if( !c.type.isConstant )
Error(TXT_SWITCH_CASE_MUST_BE_CONSTANT, cnode->firstChild);
// Verify that the result is an integral number
if( !c.type.dataType.IsIntegerType() && !c.type.dataType.IsUnsignedType() )
Error(TXT_SWITCH_MUST_BE_INTEGRAL, cnode->firstChild);
ImplicitConversion(&c, to, cnode->firstChild, asIC_IMPLICIT_CONV, true);
// Has this case been declared already?
if( caseValues.IndexOf(c.type.intValue) >= 0 )
{
Error(TXT_DUPLICATE_SWITCH_CASE, cnode->firstChild);
}
// TODO: Optimize: We can insert the numbers sorted already
// Store constant for later use
caseValues.PushLast(c.type.intValue);
// Reserve label for this case
caseLabels.PushLast(nextLabel++);
}
else
{
// TODO: It shouldn't be necessary for the default case to be the last one.
// Is default the last case?
if( cnode->next )
{
Error(TXT_DEFAULT_MUST_BE_LAST, cnode);
break;
}
// Reserve label for this case
defaultLabel = nextLabel++;
}
cnode = cnode->next;
}
// check for empty switch
if (caseValues.GetLength() == 0)
{
Error(TXT_EMPTY_SWITCH, snode);
return;
}
if( defaultLabel == 0 )
defaultLabel = breakLabel;
//---------------------------------
// Output the optimized case comparisons
// with jumps to the case code
//------------------------------------
// Sort the case values by increasing value. Do the sort together with the labels
// A simple bubble sort is sufficient since we don't expect a huge number of values
for( asUINT fwd = 1; fwd < caseValues.GetLength(); fwd++ )
{
for( int bck = fwd - 1; bck >= 0; bck-- )
{
int bckp = bck + 1;
if( caseValues[bck] > caseValues[bckp] )
{
// Swap the values in both arrays
int swap = caseValues[bckp];
caseValues[bckp] = caseValues[bck];
caseValues[bck] = swap;
swap = caseLabels[bckp];
caseLabels[bckp] = caseLabels[bck];
caseLabels[bck] = swap;
}
else
break;
}
}
// Find ranges of consecutive numbers
asCArray<int> ranges;
ranges.PushLast(0);
asUINT n;
for( n = 1; n < caseValues.GetLength(); ++n )
{
// We can join numbers that are less than 5 numbers
// apart since the output code will still be smaller
if( caseValues[n] > caseValues[n-1] + 5 )
ranges.PushLast(n);
}
// If the value is larger than the largest case value, jump to default
int tmpOffset = AllocateVariable(asCDataType::CreatePrimitive(ttInt, false), true);
expr.bc.InstrSHORT_DW(asBC_SetV4, (short)tmpOffset, caseValues[caseValues.GetLength()-1]);
expr.bc.InstrW_W(asBC_CMPi, offset, tmpOffset);
expr.bc.InstrDWORD(asBC_JP, defaultLabel);
ReleaseTemporaryVariable(tmpOffset, &expr.bc);
// TODO: runtime optimize: We could possibly optimize this even more by doing a
// binary search instead of a linear search through the ranges
// For each range
int range;
for( range = 0; range < (int)ranges.GetLength(); range++ )
{
// Find the largest value in this range
int maxRange = caseValues[ranges[range]];
int index = ranges[range];
for( ; (index < (int)caseValues.GetLength()) && (caseValues[index] <= maxRange + 5); index++ )
maxRange = caseValues[index];
// If there are only 2 numbers then it is better to compare them directly
if( index - ranges[range] > 2 )
{
// If the value is smaller than the smallest case value in the range, jump to default
tmpOffset = AllocateVariable(asCDataType::CreatePrimitive(ttInt, false), true);
expr.bc.InstrSHORT_DW(asBC_SetV4, (short)tmpOffset, caseValues[ranges[range]]);
expr.bc.InstrW_W(asBC_CMPi, offset, tmpOffset);
expr.bc.InstrDWORD(asBC_JS, defaultLabel);
ReleaseTemporaryVariable(tmpOffset, &expr.bc);
int nextRangeLabel = nextLabel++;
// If this is the last range we don't have to make this test
if( range < (int)ranges.GetLength() - 1 )
{
// If the value is larger than the largest case value in the range, jump to the next range
tmpOffset = AllocateVariable(asCDataType::CreatePrimitive(ttInt, false), true);
expr.bc.InstrSHORT_DW(asBC_SetV4, (short)tmpOffset, maxRange);
expr.bc.InstrW_W(asBC_CMPi, offset, tmpOffset);
expr.bc.InstrDWORD(asBC_JP, nextRangeLabel);
ReleaseTemporaryVariable(tmpOffset, &expr.bc);
}
// Jump forward according to the value
tmpOffset = AllocateVariable(asCDataType::CreatePrimitive(ttInt, false), true);
expr.bc.InstrSHORT_DW(asBC_SetV4, (short)tmpOffset, caseValues[ranges[range]]);
expr.bc.InstrW_W_W(asBC_SUBi, tmpOffset, offset, tmpOffset);
ReleaseTemporaryVariable(tmpOffset, &expr.bc);
expr.bc.JmpP(tmpOffset, maxRange - caseValues[ranges[range]]);
// Add the list of jumps to the correct labels (any holes, jump to default)
index = ranges[range];
for( int i = caseValues[index]; i <= maxRange; i++ )
{
if( caseValues[index] == i )
expr.bc.InstrINT(asBC_JMP, caseLabels[index++]);
else
expr.bc.InstrINT(asBC_JMP, defaultLabel);
}
expr.bc.Label((short)nextRangeLabel);
}
else
{
// Simply make a comparison with each value
for( int i = ranges[range]; i < index; ++i )
{
tmpOffset = AllocateVariable(asCDataType::CreatePrimitive(ttInt, false), true);
expr.bc.InstrSHORT_DW(asBC_SetV4, (short)tmpOffset, caseValues[i]);
expr.bc.InstrW_W(asBC_CMPi, offset, tmpOffset);
expr.bc.InstrDWORD(asBC_JZ, caseLabels[i]);
ReleaseTemporaryVariable(tmpOffset, &expr.bc);
}
}
}
// Catch any value that falls trough
expr.bc.InstrINT(asBC_JMP, defaultLabel);
// Release the temporary variable previously stored
ReleaseTemporaryVariable(expr.type, &expr.bc);
// TODO: optimize: Should optimize each piece individually
expr.bc.OptimizeLocally(tempVariableOffsets);
//----------------------------------
// Output case implementations
//----------------------------------
// Compile case implementations, each one with the label before it
cnode = snode->firstChild->next;
while( cnode )
{
// Each case should have a constant expression
if( cnode->firstChild && cnode->firstChild->nodeType == snExpression )
{
expr.bc.Label((short)firstCaseLabel++);
CompileCase(cnode->firstChild->next, &expr.bc);
}
else
{
expr.bc.Label((short)defaultLabel);
// Is default the last case?
if( cnode->next )
{
// We've already reported this error
break;
}
CompileCase(cnode->firstChild, &expr.bc);
}
cnode = cnode->next;
}
//--------------------------------
bc->AddCode(&expr.bc);
// Add break label
bc->Label((short)breakLabel);
breakLabels.PopLast();
RemoveVariableScope();
}
void asCCompiler::CompileCase(asCScriptNode *node, asCByteCode *bc)
{
bool isFinished = false;
bool hasReturn = false;
bool hasUnreachableCode = false;
while( node )
{
if( !hasUnreachableCode && (hasReturn || isFinished) )
{
hasUnreachableCode = true;
Warning(TXT_UNREACHABLE_CODE, node);
break;
}
if( node->nodeType == snBreak || node->nodeType == snContinue )
isFinished = true;
asCByteCode statement(engine);
if( node->nodeType == snDeclaration )
{
Error(TXT_DECL_IN_SWITCH, node);
// Compile it anyway to avoid further compiler errors
CompileDeclaration(node, &statement);
}
else
CompileStatement(node, &hasReturn, &statement);
LineInstr(bc, node->tokenPos);
bc->AddCode(&statement);
if( !hasCompileErrors )
asASSERT( tempVariables.GetLength() == 0 );
node = node->next;
}
}
void asCCompiler::CompileIfStatement(asCScriptNode *inode, bool *hasReturn, asCByteCode *bc)
{
// We will use one label for the if statement
// and possibly another for the else statement
int afterLabel = nextLabel++;
// Compile the expression
asCExprContext expr(engine);
int r = CompileAssignment(inode->firstChild, &expr);
if( r == 0 )
{
// Allow value types to be converted to bool using 'bool opImplConv()'
if( expr.type.dataType.GetTypeInfo() && (expr.type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(&expr, asCDataType::CreatePrimitive(ttBool, false), inode, asIC_IMPLICIT_CONV);
if( !expr.type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
Error(TXT_EXPR_MUST_BE_BOOL, inode->firstChild);
else
{
if( expr.type.dataType.IsReference() ) ConvertToVariable(&expr);
ProcessDeferredParams(&expr);
if( !expr.type.isConstant )
{
ProcessPropertyGetAccessor(&expr, inode);
ConvertToVariable(&expr);
// Add a test
expr.bc.InstrSHORT(asBC_CpyVtoR4, expr.type.stackOffset);
expr.bc.Instr(asBC_ClrHi);
expr.bc.InstrDWORD(asBC_JZ, afterLabel);
ReleaseTemporaryVariable(expr.type, &expr.bc);
expr.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&expr.bc);
}
else if( expr.type.dwordValue == 0 )
{
// Jump to the else case
bc->InstrINT(asBC_JMP, afterLabel);
// TODO: Should we warn that the expression will always go to the else?
}
}
}
// Compile the if statement
bool origIsConstructorCalled = m_isConstructorCalled;
bool hasReturn1;
asCByteCode ifBC(engine);
CompileStatement(inode->firstChild->next, &hasReturn1, &ifBC);
// Add the byte code
LineInstr(bc, inode->firstChild->next->tokenPos);
bc->AddCode(&ifBC);
if( inode->firstChild->next->nodeType == snExpressionStatement && inode->firstChild->next->firstChild == 0 )
{
// Don't allow if( expr );
Error(TXT_IF_WITH_EMPTY_STATEMENT, inode->firstChild->next);
}
// If one of the statements call the constructor, the other must as well
// otherwise it is possible the constructor is never called
bool constructorCall1 = false;
bool constructorCall2 = false;
if( !origIsConstructorCalled && m_isConstructorCalled )
constructorCall1 = true;
// Do we have an else statement?
if( inode->firstChild->next != inode->lastChild )
{
// Reset the constructor called flag so the else statement can call the constructor too
m_isConstructorCalled = origIsConstructorCalled;
int afterElse = 0;
if( !hasReturn1 )
{
afterElse = nextLabel++;
// Add jump to after the else statement
bc->InstrINT(asBC_JMP, afterElse);
}
// Add label for the else statement
bc->Label((short)afterLabel);
bool hasReturn2;
asCByteCode elseBC(engine);
CompileStatement(inode->lastChild, &hasReturn2, &elseBC);
// Add byte code for the else statement
LineInstr(bc, inode->lastChild->tokenPos);
bc->AddCode(&elseBC);
if( inode->lastChild->nodeType == snExpressionStatement && inode->lastChild->firstChild == 0 )
{
// Don't allow if( expr ) {} else;
Error(TXT_ELSE_WITH_EMPTY_STATEMENT, inode->lastChild);
}
if( !hasReturn1 )
{
// Add label for the end of else statement
bc->Label((short)afterElse);
}
// The if statement only has return if both alternatives have
*hasReturn = hasReturn1 && hasReturn2;
if( !origIsConstructorCalled && m_isConstructorCalled )
constructorCall2 = true;
}
else
{
// Add label for the end of if statement
bc->Label((short)afterLabel);
*hasReturn = false;
}
// Make sure both or neither conditions call a constructor
if( (constructorCall1 && !constructorCall2) ||
(constructorCall2 && !constructorCall1) )
{
Error(TXT_BOTH_CONDITIONS_MUST_CALL_CONSTRUCTOR, inode);
}
m_isConstructorCalled = origIsConstructorCalled || constructorCall1 || constructorCall2;
}
void asCCompiler::CompileForStatement(asCScriptNode *fnode, asCByteCode *bc)
{
// Add a variable scope that will be used by CompileBreak/Continue to know where to stop deallocating variables
AddVariableScope(true, true);
// We will use three labels for the for loop
int conditionLabel = nextLabel++;
int afterLabel = nextLabel++;
int continueLabel = nextLabel++;
int insideLabel = nextLabel++;
continueLabels.PushLast(continueLabel);
breakLabels.PushLast(afterLabel);
//---------------------------------------
// Compile the initialization statement
asCByteCode initBC(engine);
LineInstr(&initBC, fnode->firstChild->tokenPos);
if( fnode->firstChild->nodeType == snDeclaration )
CompileDeclaration(fnode->firstChild, &initBC);
else
CompileExpressionStatement(fnode->firstChild, &initBC);
//-----------------------------------
// Compile the condition statement
asCExprContext expr(engine);
asCScriptNode *second = fnode->firstChild->next;
if( second->firstChild )
{
int r = CompileAssignment(second->firstChild, &expr);
if( r >= 0 )
{
// Allow value types to be converted to bool using 'bool opImplConv()'
if( expr.type.dataType.GetTypeInfo() && (expr.type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(&expr, asCDataType::CreatePrimitive(ttBool, false), second->firstChild, asIC_IMPLICIT_CONV);
if( !expr.type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
Error(TXT_EXPR_MUST_BE_BOOL, second);
else
{
if( expr.type.dataType.IsReference() ) ConvertToVariable(&expr);
ProcessDeferredParams(&expr);
ProcessPropertyGetAccessor(&expr, second);
// If expression is false exit the loop
ConvertToVariable(&expr);
expr.bc.InstrSHORT(asBC_CpyVtoR4, expr.type.stackOffset);
expr.bc.Instr(asBC_ClrHi);
expr.bc.InstrDWORD(asBC_JNZ, insideLabel);
ReleaseTemporaryVariable(expr.type, &expr.bc);
expr.bc.OptimizeLocally(tempVariableOffsets);
// Prepend the line instruction for the condition
asCByteCode tmp(engine);
LineInstr(&tmp, second->firstChild->tokenPos);
tmp.AddCode(&expr.bc);
expr.bc.AddCode(&tmp);
}
}
}
//---------------------------
// Compile the increment statement(s)
asCByteCode nextBC(engine);
asCScriptNode *cnode = second->next;
while( cnode && cnode->nodeType == snExpressionStatement && cnode != fnode->lastChild )
{
LineInstr(&nextBC, cnode->tokenPos);
CompileExpressionStatement(cnode, &nextBC);
cnode = cnode->next;
}
//------------------------------
// Compile loop statement
bool hasReturn;
asCByteCode forBC(engine);
CompileStatement(fnode->lastChild, &hasReturn, &forBC);
//-------------------------------
// Join the code pieces
bc->AddCode(&initBC);
bc->InstrDWORD(asBC_JMP, conditionLabel);
bc->Label((short)insideLabel);
// Add a suspend bytecode inside the loop to guarantee
// that the application can suspend the execution
bc->Instr(asBC_SUSPEND);
bc->InstrPTR(asBC_JitEntry, 0);
LineInstr(bc, fnode->lastChild->tokenPos);
bc->AddCode(&forBC);
bc->Label((short)continueLabel);
bc->AddCode(&nextBC);
bc->Label((short)conditionLabel);
if( expr.bc.GetLastInstr() == -1 )
// There is no condition, so we just always jump
bc->InstrDWORD(asBC_JMP, insideLabel);
else
bc->AddCode(&expr.bc);
bc->Label((short)afterLabel);
continueLabels.PopLast();
breakLabels.PopLast();
// Deallocate variables in this block, in reverse order
for( int n = (int)variables->variables.GetLength() - 1; n >= 0; n-- )
{
sVariable *v = variables->variables[n];
// Call variable destructors here, for variables not yet destroyed
CallDestructor(v->type, v->stackOffset, v->onHeap, bc);
// Don't deallocate function parameters
if( v->stackOffset > 0 )
DeallocateVariable(v->stackOffset);
}
RemoveVariableScope();
}
void asCCompiler::CompileWhileStatement(asCScriptNode *wnode, asCByteCode *bc)
{
// Add a variable scope that will be used by CompileBreak/Continue to know where to stop deallocating variables
AddVariableScope(true, true);
// We will use two labels for the while loop
int beforeLabel = nextLabel++;
int afterLabel = nextLabel++;
continueLabels.PushLast(beforeLabel);
breakLabels.PushLast(afterLabel);
// Add label before the expression
bc->Label((short)beforeLabel);
// Compile expression
asCExprContext expr(engine);
int r = CompileAssignment(wnode->firstChild, &expr);
if( r == 0 )
{
// Allow value types to be converted to bool using 'bool opImplConv()'
if( expr.type.dataType.GetTypeInfo() && (expr.type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(&expr, asCDataType::CreatePrimitive(ttBool, false), wnode->firstChild, asIC_IMPLICIT_CONV);
if( !expr.type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
Error(TXT_EXPR_MUST_BE_BOOL, wnode->firstChild);
else
{
if( expr.type.dataType.IsReference() ) ConvertToVariable(&expr);
ProcessDeferredParams(&expr);
ProcessPropertyGetAccessor(&expr, wnode);
// Add byte code for the expression
ConvertToVariable(&expr);
// Jump to end of statement if expression is false
expr.bc.InstrSHORT(asBC_CpyVtoR4, expr.type.stackOffset);
expr.bc.Instr(asBC_ClrHi);
expr.bc.InstrDWORD(asBC_JZ, afterLabel);
ReleaseTemporaryVariable(expr.type, &expr.bc);
expr.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&expr.bc);
}
}
// Add a suspend bytecode inside the loop to guarantee
// that the application can suspend the execution
bc->Instr(asBC_SUSPEND);
bc->InstrPTR(asBC_JitEntry, 0);
// Compile statement
bool hasReturn;
asCByteCode whileBC(engine);
CompileStatement(wnode->lastChild, &hasReturn, &whileBC);
// Add byte code for the statement
LineInstr(bc, wnode->lastChild->tokenPos);
bc->AddCode(&whileBC);
// Jump to the expression
bc->InstrINT(asBC_JMP, beforeLabel);
// Add label after the statement
bc->Label((short)afterLabel);
continueLabels.PopLast();
breakLabels.PopLast();
RemoveVariableScope();
}
void asCCompiler::CompileDoWhileStatement(asCScriptNode *wnode, asCByteCode *bc)
{
// Add a variable scope that will be used by CompileBreak/Continue to know where to stop deallocating variables
AddVariableScope(true, true);
// We will use two labels for the while loop
int beforeLabel = nextLabel++;
int beforeTest = nextLabel++;
int afterLabel = nextLabel++;
continueLabels.PushLast(beforeTest);
breakLabels.PushLast(afterLabel);
// Add label before the statement
bc->Label((short)beforeLabel);
// Compile statement
bool hasReturn;
asCByteCode whileBC(engine);
CompileStatement(wnode->firstChild, &hasReturn, &whileBC);
// Add byte code for the statement
LineInstr(bc, wnode->firstChild->tokenPos);
bc->AddCode(&whileBC);
// Add label before the expression
bc->Label((short)beforeTest);
// Add a suspend bytecode inside the loop to guarantee
// that the application can suspend the execution
bc->Instr(asBC_SUSPEND);
bc->InstrPTR(asBC_JitEntry, 0);
// Add a line instruction
LineInstr(bc, wnode->lastChild->tokenPos);
// Compile expression
asCExprContext expr(engine);
CompileAssignment(wnode->lastChild, &expr);
// Allow value types to be converted to bool using 'bool opImplConv()'
if( expr.type.dataType.GetTypeInfo() && (expr.type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(&expr, asCDataType::CreatePrimitive(ttBool, false), wnode->lastChild, asIC_IMPLICIT_CONV);
if( !expr.type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
Error(TXT_EXPR_MUST_BE_BOOL, wnode->firstChild);
else
{
if( expr.type.dataType.IsReference() ) ConvertToVariable(&expr);
ProcessDeferredParams(&expr);
ProcessPropertyGetAccessor(&expr, wnode);
// Add byte code for the expression
ConvertToVariable(&expr);
// Jump to next iteration if expression is true
expr.bc.InstrSHORT(asBC_CpyVtoR4, expr.type.stackOffset);
expr.bc.Instr(asBC_ClrHi);
expr.bc.InstrDWORD(asBC_JNZ, beforeLabel);
ReleaseTemporaryVariable(expr.type, &expr.bc);
expr.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&expr.bc);
}
// Add label after the statement
bc->Label((short)afterLabel);
continueLabels.PopLast();
breakLabels.PopLast();
RemoveVariableScope();
}
void asCCompiler::CompileBreakStatement(asCScriptNode *node, asCByteCode *bc)
{
if( breakLabels.GetLength() == 0 )
{
Error(TXT_INVALID_BREAK, node);
return;
}
// Add destructor calls for all variables that will go out of scope
// Put this clean up in a block to allow exception handler to understand them
bc->Block(true);
asCVariableScope *vs = variables;
while( !vs->isBreakScope )
{
for( int n = (int)vs->variables.GetLength() - 1; n >= 0; n-- )
CallDestructor(vs->variables[n]->type, vs->variables[n]->stackOffset, vs->variables[n]->onHeap, bc);
vs = vs->parent;
}
bc->Block(false);
bc->InstrINT(asBC_JMP, breakLabels[breakLabels.GetLength()-1]);
}
void asCCompiler::CompileContinueStatement(asCScriptNode *node, asCByteCode *bc)
{
if( continueLabels.GetLength() == 0 )
{
Error(TXT_INVALID_CONTINUE, node);
return;
}
// Add destructor calls for all variables that will go out of scope
// Put this clean up in a block to allow exception handler to understand them
bc->Block(true);
asCVariableScope *vs = variables;
while( !vs->isContinueScope )
{
for( int n = (int)vs->variables.GetLength() - 1; n >= 0; n-- )
CallDestructor(vs->variables[n]->type, vs->variables[n]->stackOffset, vs->variables[n]->onHeap, bc);
vs = vs->parent;
}
bc->Block(false);
bc->InstrINT(asBC_JMP, continueLabels[continueLabels.GetLength()-1]);
}
void asCCompiler::CompileExpressionStatement(asCScriptNode *enode, asCByteCode *bc)
{
if( enode->firstChild )
{
// Compile the expression
asCExprContext expr(engine);
CompileAssignment(enode->firstChild, &expr);
// Must not have unused ambiguous names
if( expr.IsClassMethod() || expr.IsGlobalFunc() )
Error(TXT_INVALID_EXPRESSION_AMBIGUOUS_NAME, enode);
// Must not have unused anonymous functions
if( expr.IsLambda() )
Error(TXT_INVALID_EXPRESSION_LAMBDA, enode);
// If we get here and there is still an unprocessed property
// accessor, then process it as a get access. Don't call if there is
// already a compile error, or we might report an error that is not valid
if( !hasCompileErrors )
ProcessPropertyGetAccessor(&expr, enode);
// Pop the value from the stack
if( !expr.type.dataType.IsPrimitive() )
expr.bc.Instr(asBC_PopPtr);
// Release temporary variables used by expression
ReleaseTemporaryVariable(expr.type, &expr.bc);
ProcessDeferredParams(&expr);
expr.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&expr.bc);
}
}
void asCCompiler::PrepareTemporaryVariable(asCScriptNode *node, asCExprContext *ctx, bool forceOnHeap)
{
// The input can be either an object or funcdef, either as handle or reference
asASSERT(ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef());
// If the object already is stored in temporary variable then nothing needs to be done
// Note, a type can be temporary without being a variable, in which case it is holding off
// on releasing a previously used object.
if( ctx->type.isTemporary && ctx->type.isVariable &&
!(forceOnHeap && !IsVariableOnHeap(ctx->type.stackOffset)) )
{
// If the temporary object is currently not a reference
// the expression needs to be reevaluated to a reference
if( !ctx->type.dataType.IsReference() )
{
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
ctx->type.dataType.MakeReference(true);
}
return;
}
// Allocate temporary variable
asCDataType dt = ctx->type.dataType;
dt.MakeReference(false);
dt.MakeReadOnly(false);
int offset = AllocateVariable(dt, true, forceOnHeap);
// Objects stored on the stack are not considered references
dt.MakeReference(IsVariableOnHeap(offset));
asCExprValue lvalue;
lvalue.Set(dt);
lvalue.isExplicitHandle = ctx->type.isExplicitHandle;
bool isExplicitHandle = ctx->type.isExplicitHandle;
CompileInitAsCopy(dt, offset, &ctx->bc, ctx, node, false);
// Push the reference to the temporary variable on the stack
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
ctx->type.Set(dt);
ctx->type.isTemporary = true;
ctx->type.stackOffset = (short)offset;
ctx->type.isVariable = true;
ctx->type.isExplicitHandle = isExplicitHandle;
ctx->type.dataType.MakeReference(IsVariableOnHeap(offset));
}
void asCCompiler::CompileReturnStatement(asCScriptNode *rnode, asCByteCode *bc)
{
// Get return type and location
sVariable *v = variables->GetVariable("return");
// Basic validations
if( v->type.GetSizeOnStackDWords() > 0 && !rnode->firstChild )
{
Error(TXT_MUST_RETURN_VALUE, rnode);
return;
}
else if( v->type.GetSizeOnStackDWords() == 0 && rnode->firstChild )
{
Error(TXT_CANT_RETURN_VALUE, rnode);
return;
}
// Compile the expression
if( rnode->firstChild )
{
// Compile the expression
asCExprContext expr(engine);
int r = CompileAssignment(rnode->firstChild, &expr);
if( r < 0 ) return;
if( v->type.IsReference() )
{
// The expression that gives the reference must not use any of the
// variables that must be destroyed upon exit, because then it means
// reference will stay alive while the clean-up is done, which could
// potentially mean that the reference is invalidated by the clean-up.
//
// When the function is returning a reference, the clean-up of the
// variables must be done before the evaluation of the expression.
//
// A reference to a global variable, or a class member for class methods
// should be allowed to be returned.
if( !(expr.type.dataType.IsReference() ||
(expr.type.dataType.IsObject() && !expr.type.dataType.IsObjectHandle())) )
{
// Clean up the potential deferred parameters
ProcessDeferredParams(&expr);
Error(TXT_NOT_VALID_REFERENCE, rnode);
return;
}
// No references to local variables, temporary variables, or parameters
// are allowed to be returned, since they go out of scope when the function
// returns. Even reference parameters are disallowed, since it is not possible
// to know the scope of them. The exception is the 'this' pointer, which
// is treated by the compiler as a local variable, but isn't really so.
if( (expr.type.isVariable && !(expr.type.stackOffset == 0 && outFunc->objectType)) || expr.type.isTemporary )
{
// Clean up the potential deferred parameters
ProcessDeferredParams(&expr);
Error(TXT_CANNOT_RETURN_REF_TO_LOCAL, rnode);
return;
}
// The type must match exactly as we cannot convert
// the reference without loosing the original value
if( !(v->type.IsEqualExceptConst(expr.type.dataType) ||
((expr.type.dataType.IsObject() || expr.type.dataType.IsFuncdef()) &&
!expr.type.dataType.IsObjectHandle() &&
v->type.IsEqualExceptRefAndConst(expr.type.dataType))) ||
(!v->type.IsReadOnly() && expr.type.dataType.IsReadOnly()) )
{
// Clean up the potential deferred parameters
ProcessDeferredParams(&expr);
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, expr.type.dataType.Format(outFunc->nameSpace).AddressOf(), v->type.Format(outFunc->nameSpace).AddressOf());
Error(str, rnode);
return;
}
// The expression must not have any deferred expressions, because the evaluation
// of these cannot be done without keeping the reference which is not safe
if( expr.deferredParams.GetLength() )
{
// Clean up the potential deferred parameters
ProcessDeferredParams(&expr);
Error(TXT_REF_CANT_BE_RETURNED_DEFERRED_PARAM, rnode);
return;
}
// Make sure the expression isn't using any local variables that
// will need to be cleaned up before the function completes
asCArray<int> usedVars;
expr.bc.GetVarsUsed(usedVars);
for( asUINT n = 0; n < usedVars.GetLength(); n++ )
{
int var = GetVariableSlot(usedVars[n]);
if( var != -1 )
{
asCDataType dt = variableAllocations[var];
if( dt.IsObject() )
{
ProcessDeferredParams(&expr);
Error(TXT_REF_CANT_BE_RETURNED_LOCAL_VARS, rnode);
return;
}
}
}
// Can't return the reference if could point to a local variable
if( expr.type.isRefToLocal )
{
ProcessDeferredParams(&expr);
Error(TXT_REF_CANT_BE_TO_LOCAL_VAR, rnode);
return;
}
// All objects in the function must be cleaned up before the expression
// is evaluated, otherwise there is a possibility that the cleanup will
// invalidate the reference.
// Destroy the local variables before loading
// the reference into the register. This will
// be done before the expression is evaluated.
DestroyVariables(bc);
// For primitives the reference is already in the register,
// but for non-primitives the reference is on the stack so we
// need to load it into the register
if( !expr.type.dataType.IsPrimitive() )
{
if( !expr.type.dataType.IsObjectHandle() &&
expr.type.dataType.IsReference() )
expr.bc.Instr(asBC_RDSPtr);
expr.bc.Instr(asBC_PopRPtr);
}
// There are no temporaries to release so we're done
}
else // if( !v->type.IsReference() )
{
ProcessPropertyGetAccessor(&expr, rnode);
// Prepare the value for assignment
IsVariableInitialized(&expr.type, rnode->firstChild);
if( v->type.IsPrimitive() )
{
if( expr.type.dataType.IsReference() ) ConvertToVariable(&expr);
// Implicitly convert the value to the return type
ImplicitConversion(&expr, v->type, rnode->firstChild, asIC_IMPLICIT_CONV);
// Verify that the conversion was successful
if( expr.type.dataType != v->type )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, expr.type.dataType.Format(outFunc->nameSpace).AddressOf(), v->type.Format(outFunc->nameSpace).AddressOf());
Error(str, rnode);
return;
}
else
{
ConvertToVariable(&expr);
// Clean up the local variables and process deferred parameters
DestroyVariables(&expr.bc);
ProcessDeferredParams(&expr);
ReleaseTemporaryVariable(expr.type, &expr.bc);
// Load the variable in the register
if( v->type.GetSizeOnStackDWords() == 1 )
expr.bc.InstrSHORT(asBC_CpyVtoR4, expr.type.stackOffset);
else
expr.bc.InstrSHORT(asBC_CpyVtoR8, expr.type.stackOffset);
}
}
else if( v->type.IsObject() || v->type.IsFuncdef() )
{
// Value types are returned on the stack, in a location
// that has been reserved by the calling function.
if( outFunc->DoesReturnOnStack() )
{
// TODO: runtime optimize: If the return type has a constructor that takes the type of the expression,
// it should be called directly instead of first converting the expression and
// then copy the value.
if( !v->type.IsEqualExceptRefAndConst(expr.type.dataType) )
{
ImplicitConversion(&expr, v->type, rnode->firstChild, asIC_IMPLICIT_CONV);
if( !v->type.IsEqualExceptRefAndConst(expr.type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, expr.type.dataType.Format(outFunc->nameSpace).AddressOf(), v->type.Format(outFunc->nameSpace).AddressOf());
Error(str, rnode->firstChild);
return;
}
}
int offset = outFunc->objectType ? -AS_PTR_SIZE : 0;
CompileInitAsCopy(v->type, offset, &expr.bc, &expr, rnode->firstChild, true);
// Clean up the local variables and process deferred parameters
DestroyVariables(&expr.bc);
ProcessDeferredParams(&expr);
}
else
{
asASSERT( (v->type.GetTypeInfo()->flags & asOBJ_REF) || v->type.IsFuncdef() );
// Prepare the expression to be loaded into the object
// register. This will place the reference in local variable
PrepareArgument(&v->type, &expr, rnode->firstChild, false, 0);
// Pop the reference to the temporary variable
expr.bc.Instr(asBC_PopPtr);
// Clean up the local variables and process deferred parameters
DestroyVariables(&expr.bc);
ProcessDeferredParams(&expr);
// Load the object pointer into the object register
// LOADOBJ also clears the address in the variable
expr.bc.InstrSHORT(asBC_LOADOBJ, expr.type.stackOffset);
// LOADOBJ cleared the address in the variable so the object will not be freed
// here, but the temporary variable must still be freed so the slot can be reused
// By releasing without the bytecode we do just that.
ReleaseTemporaryVariable(expr.type, 0);
}
}
}
expr.bc.OptimizeLocally(tempVariableOffsets);
bc->AddCode(&expr.bc);
}
else
{
// For functions that don't return anything
// we just detroy the local variables
DestroyVariables(bc);
}
// Jump to the end of the function
bc->InstrINT(asBC_JMP, 0);
}
void asCCompiler::DestroyVariables(asCByteCode *bc)
{
// Call destructor on all variables except for the function parameters
// Put the clean-up in a block to allow exception handler to understand this
bc->Block(true);
asCVariableScope *vs = variables;
while( vs )
{
for( int n = (int)vs->variables.GetLength() - 1; n >= 0; n-- )
if( vs->variables[n]->stackOffset > 0 )
CallDestructor(vs->variables[n]->type, vs->variables[n]->stackOffset, vs->variables[n]->onHeap, bc);
vs = vs->parent;
}
bc->Block(false);
}
void asCCompiler::AddVariableScope(bool isBreakScope, bool isContinueScope)
{
variables = asNEW(asCVariableScope)(variables);
if( variables == 0 )
{
// Out of memory
return;
}
variables->isBreakScope = isBreakScope;
variables->isContinueScope = isContinueScope;
}
void asCCompiler::RemoveVariableScope()
{
if( variables )
{
asCVariableScope *var = variables;
variables = variables->parent;
asDELETE(var,asCVariableScope);
}
}
void asCCompiler::Error(const asCString &msg, asCScriptNode *node)
{
asCString str;
int r = 0, c = 0;
asASSERT( node );
if( node ) script->ConvertPosToRowCol(node->tokenPos, &r, &c);
builder->WriteError(script->name, msg, r, c);
hasCompileErrors = true;
}
void asCCompiler::Warning(const asCString &msg, asCScriptNode *node)
{
asCString str;
int r = 0, c = 0;
asASSERT( node );
if( node ) script->ConvertPosToRowCol(node->tokenPos, &r, &c);
builder->WriteWarning(script->name, msg, r, c);
}
void asCCompiler::Information(const asCString &msg, asCScriptNode *node)
{
asCString str;
int r = 0, c = 0;
asASSERT( node );
if( node ) script->ConvertPosToRowCol(node->tokenPos, &r, &c);
builder->WriteInfo(script->name, msg, r, c, false);
}
void asCCompiler::PrintMatchingFuncs(asCArray<int> &funcs, asCScriptNode *node, asCObjectType *inType)
{
int r = 0, c = 0;
asASSERT( node );
if( node ) script->ConvertPosToRowCol(node->tokenPos, &r, &c);
for( unsigned int n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *func = builder->GetFunctionDescription(funcs[n]);
if( inType && func->funcType == asFUNC_VIRTUAL )
func = inType->virtualFunctionTable[func->vfTableIdx];
builder->WriteInfo(script->name, func->GetDeclaration(true, false, true), r, c, false);
}
}
int asCCompiler::AllocateVariableNotIn(const asCDataType &type, bool isTemporary, bool forceOnHeap, asCExprContext *ctx)
{
int l = int(reservedVariables.GetLength());
ctx->bc.GetVarsUsed(reservedVariables);
int var = AllocateVariable(type, isTemporary, forceOnHeap);
reservedVariables.SetLength(l);
return var;
}
int asCCompiler::AllocateVariable(const asCDataType &type, bool isTemporary, bool forceOnHeap)
{
asCDataType t(type);
t.MakeReference(false);
if( t.IsPrimitive() && t.GetSizeOnStackDWords() == 1 )
t.SetTokenType(ttInt);
if( t.IsPrimitive() && t.GetSizeOnStackDWords() == 2 )
t.SetTokenType(ttDouble);
// Only null handles have the token type unrecognized token
asASSERT( t.IsObjectHandle() || t.GetTokenType() != ttUnrecognizedToken );
bool isOnHeap = true;
if( t.IsPrimitive() ||
(t.GetTypeInfo() && (t.GetTypeInfo()->GetFlags() & asOBJ_VALUE) && !forceOnHeap) )
{
// Primitives and value types (unless overridden) are allocated on the stack
isOnHeap = false;
}
// Find a free location with the same type
for( asUINT n = 0; n < freeVariables.GetLength(); n++ )
{
int slot = freeVariables[n];
if( variableAllocations[slot].IsEqualExceptConst(t) &&
variableIsTemporary[slot] == isTemporary &&
variableIsOnHeap[slot] == isOnHeap )
{
// We can't return by slot, must count variable sizes
int offset = GetVariableOffset(slot);
// Verify that it is not in the list of reserved variables
bool isUsed = false;
if( reservedVariables.GetLength() )
isUsed = reservedVariables.Exists(offset);
if( !isUsed )
{
if( n != freeVariables.GetLength() - 1 )
freeVariables[n] = freeVariables.PopLast();
else
freeVariables.PopLast();
if( isTemporary )
tempVariables.PushLast(offset);
return offset;
}
}
}
variableAllocations.PushLast(t);
variableIsTemporary.PushLast(isTemporary);
variableIsOnHeap.PushLast(isOnHeap);
int offset = GetVariableOffset((int)variableAllocations.GetLength()-1);
if( isTemporary )
{
// Add offset to the currently allocated temporary variables
tempVariables.PushLast(offset);
// Add offset to all known offsets to temporary variables, whether allocated or not
tempVariableOffsets.PushLast(offset);
}
return offset;
}
int asCCompiler::GetVariableOffset(int varIndex)
{
// Return offset to the last dword on the stack
// Start at 1 as offset 0 is reserved for the this pointer (or first argument for global functions)
int varOffset = 1;
// Skip lower variables
for( int n = 0; n < varIndex; n++ )
{
if( !variableIsOnHeap[n] && variableAllocations[n].IsObject() )
varOffset += variableAllocations[n].GetSizeInMemoryDWords();
else
varOffset += variableAllocations[n].GetSizeOnStackDWords();
}
if( varIndex < (int)variableAllocations.GetLength() )
{
// For variables larger than 1 dword the returned offset should be to the last dword
int size;
if( !variableIsOnHeap[varIndex] && variableAllocations[varIndex].IsObject() )
size = variableAllocations[varIndex].GetSizeInMemoryDWords();
else
size = variableAllocations[varIndex].GetSizeOnStackDWords();
if( size > 1 )
varOffset += size-1;
}
return varOffset;
}
int asCCompiler::GetVariableSlot(int offset)
{
int varOffset = 1;
for( asUINT n = 0; n < variableAllocations.GetLength(); n++ )
{
if( !variableIsOnHeap[n] && variableAllocations[n].IsObject() )
varOffset += -1 + variableAllocations[n].GetSizeInMemoryDWords();
else
varOffset += -1 + variableAllocations[n].GetSizeOnStackDWords();
if( varOffset == offset )
return n;
varOffset++;
}
return -1;
}
bool asCCompiler::IsVariableOnHeap(int offset)
{
int varSlot = GetVariableSlot(offset);
if( varSlot < 0 )
{
// This happens for function arguments that are considered as on the heap
return true;
}
return variableIsOnHeap[varSlot];
}
void asCCompiler::DeallocateVariable(int offset)
{
// Remove temporary variable
int n;
for( n = 0; n < (int)tempVariables.GetLength(); n++ )
{
if( offset == tempVariables[n] )
{
if( n == (int)tempVariables.GetLength()-1 )
tempVariables.PopLast();
else
tempVariables[n] = tempVariables.PopLast();
break;
}
}
// Mark the variable slot available for new allocations
n = GetVariableSlot(offset);
if( n != -1 )
{
freeVariables.PushLast(n);
return;
}
// We might get here if the variable was implicitly declared
// because it was used before a formal declaration, in this case
// the offset is 0x7FFF
asASSERT(offset == 0x7FFF);
}
void asCCompiler::ReleaseTemporaryVariable(asCExprValue &t, asCByteCode *bc)
{
if( t.isTemporary )
{
ReleaseTemporaryVariable(t.stackOffset, bc);
t.isTemporary = false;
}
}
void asCCompiler::ReleaseTemporaryVariable(int offset, asCByteCode *bc)
{
asASSERT( tempVariables.Exists(offset) );
if( bc )
{
// We need to call the destructor on the true variable type
int n = GetVariableSlot(offset);
asASSERT( n >= 0 );
if( n >= 0 )
{
asCDataType dt = variableAllocations[n];
bool isOnHeap = variableIsOnHeap[n];
// Call destructor
CallDestructor(dt, offset, isOnHeap, bc);
}
}
DeallocateVariable(offset);
}
void asCCompiler::Dereference(asCExprContext *ctx, bool generateCode)
{
if( ctx->type.dataType.IsReference() )
{
if( ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef() )
{
ctx->type.dataType.MakeReference(false);
if( generateCode )
ctx->bc.Instr(asBC_RDSPtr);
}
else
{
// This should never happen as primitives are treated differently
asASSERT(false);
}
}
}
bool asCCompiler::IsVariableInitialized(asCExprValue *type, asCScriptNode *node)
{
// No need to check if there is no variable scope
if( variables == 0 ) return true;
// Temporary variables are assumed to be initialized
if( type->isTemporary ) return true;
// Verify that it is a variable
if( !type->isVariable ) return true;
// Find the variable
sVariable *v = variables->GetVariableByOffset(type->stackOffset);
// The variable isn't found if it is a constant, in which case it is guaranteed to be initialized
if( v == 0 ) return true;
if( v->isInitialized ) return true;
// Complex types don't need this test
if( v->type.IsObject() || v->type.IsFuncdef() ) return true;
// Mark as initialized so that the user will not be bothered again
v->isInitialized = true;
// Write warning
asCString str;
str.Format(TXT_s_NOT_INITIALIZED, (const char *)v->name.AddressOf());
Warning(str, node);
return false;
}
void asCCompiler::PrepareOperand(asCExprContext *ctx, asCScriptNode *node)
{
// Check if the variable is initialized (if it indeed is a variable)
IsVariableInitialized(&ctx->type, node);
asCDataType to = ctx->type.dataType;
to.MakeReference(false);
ImplicitConversion(ctx, to, node, asIC_IMPLICIT_CONV);
ProcessDeferredParams(ctx);
}
void asCCompiler::PrepareForAssignment(asCDataType *lvalue, asCExprContext *rctx, asCScriptNode *node, bool toTemporary, asCExprContext *lvalueExpr)
{
// Reserve the temporary variables used in the lvalue expression so they won't end up being used by the rvalue too
int l = int(reservedVariables.GetLength());
if( lvalueExpr ) lvalueExpr->bc.GetVarsUsed(reservedVariables);
ProcessPropertyGetAccessor(rctx, node);
// Make sure the rvalue is initialized if it is a variable
IsVariableInitialized(&rctx->type, node);
if( lvalue->IsPrimitive() )
{
if( rctx->type.dataType.IsPrimitive() )
{
if( rctx->type.dataType.IsReference() )
{
// Cannot do implicit conversion of references so we first convert the reference to a variable
ConvertToVariableNotIn(rctx, lvalueExpr);
}
}
// Implicitly convert the value to the right type
ImplicitConversion(rctx, *lvalue, node, asIC_IMPLICIT_CONV);
// Check data type
if( !lvalue->IsEqualExceptRefAndConst(rctx->type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lvalue->Format(outFunc->nameSpace).AddressOf());
Error(str, node);
rctx->type.SetDummy();
}
// Make sure the rvalue is a variable
if( !rctx->type.isVariable )
ConvertToVariableNotIn(rctx, lvalueExpr);
}
else
{
asCDataType to = *lvalue;
to.MakeReference(false);
// TODO: ImplicitConversion should know to do this by itself
// First convert to a handle which will do a reference cast
if( !lvalue->IsObjectHandle() &&
(lvalue->GetTypeInfo()->flags & asOBJ_SCRIPT_OBJECT) )
to.MakeHandle(true);
// Don't allow the implicit conversion to create an object
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV, true, !toTemporary);
if( !lvalue->IsObjectHandle() &&
(lvalue->GetTypeInfo()->flags & asOBJ_SCRIPT_OBJECT) )
{
// Then convert to a reference, which will validate the handle
to.MakeHandle(false);
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV, true, !toTemporary);
}
// Check data type
if( !lvalue->IsEqualExceptRefAndConst(rctx->type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lvalue->Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
else
{
// If the assignment will be made with the copy behaviour then the rvalue must not be a reference
asASSERT(!lvalue->IsObject() || !rctx->type.dataType.IsReference());
}
}
// Unreserve variables
reservedVariables.SetLength(l);
}
bool asCCompiler::IsLValue(asCExprValue &type)
{
if( !type.isLValue ) return false;
if( type.dataType.IsReadOnly() ) return false;
if( !type.dataType.IsObject() && !type.isVariable && !type.dataType.IsReference() ) return false;
return true;
}
int asCCompiler::PerformAssignment(asCExprValue *lvalue, asCExprValue *rvalue, asCByteCode *bc, asCScriptNode *node)
{
if( lvalue->dataType.IsReadOnly() )
{
Error(TXT_REF_IS_READ_ONLY, node);
return -1;
}
if( lvalue->dataType.IsPrimitive() )
{
if( lvalue->isVariable )
{
// Copy the value between the variables directly
if( lvalue->dataType.GetSizeInMemoryDWords() == 1 )
bc->InstrW_W(asBC_CpyVtoV4, lvalue->stackOffset, rvalue->stackOffset);
else
bc->InstrW_W(asBC_CpyVtoV8, lvalue->stackOffset, rvalue->stackOffset);
// Mark variable as initialized
sVariable *v = variables->GetVariableByOffset(lvalue->stackOffset);
if( v ) v->isInitialized = true;
}
else if( lvalue->dataType.IsReference() )
{
// Copy the value of the variable to the reference in the register
int s = lvalue->dataType.GetSizeInMemoryBytes();
if( s == 1 )
bc->InstrSHORT(asBC_WRTV1, rvalue->stackOffset);
else if( s == 2 )
bc->InstrSHORT(asBC_WRTV2, rvalue->stackOffset);
else if( s == 4 )
bc->InstrSHORT(asBC_WRTV4, rvalue->stackOffset);
else if( s == 8 )
bc->InstrSHORT(asBC_WRTV8, rvalue->stackOffset);
}
else
{
Error(TXT_NOT_VALID_LVALUE, node);
return -1;
}
}
else if( !lvalue->isExplicitHandle )
{
asCExprContext ctx(engine);
ctx.type = *lvalue;
Dereference(&ctx, true);
*lvalue = ctx.type;
bc->AddCode(&ctx.bc);
asSTypeBehaviour *beh = lvalue->dataType.GetBehaviour();
if( beh->copy && beh->copy != engine->scriptTypeBehaviours.beh.copy )
{
asCExprContext res(engine);
PerformFunctionCall(beh->copy, &res, false, 0, lvalue->dataType.GetTypeInfo()->CastToObjectType());
bc->AddCode(&res.bc);
*lvalue = res.type;
}
else if( beh->copy == engine->scriptTypeBehaviours.beh.copy )
{
// Call the default copy operator for script classes
// This is done differently because the default copy operator
// is registered as returning int&, but in reality it returns
// a reference to the object.
// TODO: Avoid this special case by implementing a copystub for
// script classes that uses the default copy operator
bc->Call(asBC_CALLSYS, beh->copy, 2*AS_PTR_SIZE);
bc->Instr(asBC_PshRPtr);
}
else
{
// Default copy operator
if( lvalue->dataType.GetSizeInMemoryDWords() == 0 ||
!(lvalue->dataType.GetTypeInfo()->flags & asOBJ_POD) )
{
asCString msg;
msg.Format(TXT_NO_DEFAULT_COPY_OP_FOR_s, lvalue->dataType.GetTypeInfo()->name.AddressOf());
Error(msg, node);
return -1;
}
// Copy larger data types from a reference
// TODO: runtime optimize: COPY should pop both arguments and store the reference in the register.
bc->InstrSHORT_DW(asBC_COPY, (short)lvalue->dataType.GetSizeInMemoryDWords(), engine->GetTypeIdFromDataType(lvalue->dataType));
}
}
else
{
// TODO: The object handle can be stored in a variable as well
if( !lvalue->dataType.IsReference() )
{
Error(TXT_NOT_VALID_REFERENCE, node);
return -1;
}
if( lvalue->dataType.IsFuncdef() )
bc->InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
bc->InstrPTR(asBC_REFCPY, lvalue->dataType.GetTypeInfo());
// Mark variable as initialized
if( variables )
{
sVariable *v = variables->GetVariableByOffset(lvalue->stackOffset);
if( v ) v->isInitialized = true;
}
}
return 0;
}
bool asCCompiler::CompileRefCast(asCExprContext *ctx, const asCDataType &to, bool isExplicit, asCScriptNode *node, bool generateCode)
{
bool conversionDone = false;
asCArray<int> ops;
asUINT n;
// A ref cast must not remove the constness
bool isConst = ctx->type.dataType.IsObjectConst();
// Find a suitable opCast or opImplCast method
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( n = 0; ot && n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( (isExplicit && func->name == "opCast") ||
func->name == "opImplCast" )
{
// Is the operator for the output type?
if( func->returnType.GetTypeInfo() != to.GetTypeInfo() )
continue;
// Can't call a non-const function on a const object
if( isConst && !func->IsReadOnly() )
continue;
ops.PushLast(func->id);
}
}
// Filter the list by constness to remove const methods if there are matching non-const methods
FilterConst(ops, !isConst);
// It shouldn't be possible to have more than one
// TODO: Should be allowed to have different behaviours for const and non-const references
asASSERT( ops.GetLength() <= 1 );
// Should only have one behaviour for each output type
if( ops.GetLength() == 1 )
{
conversionDone = true;
if( generateCode )
{
// TODO: runtime optimize: Instead of producing bytecode for checking if the handle is
// null, we can create a special CALLSYS instruction that checks
// if the object pointer is null and if so sets the object register
// to null directly without executing the function.
//
// Alternatively I could force the ref cast behaviours be global
// functions with 1 parameter, even though they should still be
// registered with RegisterObjectBehaviour()
if( ctx->type.dataType.GetTypeInfo()->flags & asOBJ_REF )
{
// Add code to avoid calling the cast behaviour if the handle is already null,
// because that will raise a null pointer exception due to the cast behaviour
// being a class method, and the this pointer cannot be null.
if( !ctx->type.isVariable )
{
Dereference(ctx, true);
ConvertToVariable(ctx);
}
// The reference on the stack will not be used
ctx->bc.Instr(asBC_PopPtr);
// TODO: runtime optimize: should have immediate comparison for null pointer
int offset = AllocateVariable(asCDataType::CreateNullHandle(), true);
// TODO: runtime optimize: ClrVPtr is not necessary, because the VM should initialize the variable to null anyway (it is currently not done for null pointers though)
ctx->bc.InstrSHORT(asBC_ClrVPtr, (asWORD)offset);
ctx->bc.InstrW_W(asBC_CmpPtr, ctx->type.stackOffset, offset);
DeallocateVariable(offset);
int afterLabel = nextLabel++;
ctx->bc.InstrDWORD(asBC_JZ, afterLabel);
// Call the cast operator
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
ctx->bc.Instr(asBC_RDSPtr);
ctx->type.dataType.MakeReference(false);
asCArray<asCExprContext *> args;
MakeFunctionCall(ctx, ops[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
ctx->bc.Instr(asBC_PopPtr);
int endLabel = nextLabel++;
ctx->bc.InstrINT(asBC_JMP, endLabel);
ctx->bc.Label((short)afterLabel);
// Make a NULL pointer
ctx->bc.InstrSHORT(asBC_ClrVPtr, ctx->type.stackOffset);
ctx->bc.Label((short)endLabel);
// Push the reference to the handle on the stack
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
}
else
{
// Value types cannot be null, so there is no need to check for this
// Call the cast operator
asCArray<asCExprContext *> args;
MakeFunctionCall(ctx, ops[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
}
}
else
{
asCScriptFunction *func = engine->scriptFunctions[ops[0]];
ctx->type.Set(func->returnType);
}
}
else if( ops.GetLength() == 0 && !(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCRIPT_OBJECT) )
{
// Check for the generic ref cast method: void opCast(?&out)
for( n = 0; ot && n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( (isExplicit && func->name == "opCast") ||
func->name == "opImplCast" )
{
// Does the operator take the ?&out parameter?
if( func->returnType.GetTokenType() != ttVoid ||
func->parameterTypes.GetLength() != 1 ||
func->parameterTypes[0].GetTokenType() != ttQuestion ||
func->inOutFlags[0] != asTM_OUTREF )
continue;
ops.PushLast(func->id);
}
}
// It shouldn't be possible to have more than one
// TODO: Should be allowed to have different implementations for const and non-const references
asASSERT( ops.GetLength() <= 1 );
if( ops.GetLength() == 1 )
{
conversionDone = true;
if( generateCode )
{
asASSERT(to.IsObjectHandle());
// Allocate a temporary variable of the requested handle type
int stackOffset = AllocateVariableNotIn(to, true, false, ctx);
// Pass the reference of that variable to the function as output parameter
asCDataType toRef(to);
toRef.MakeReference(true);
asCArray<asCExprContext *> args;
asCExprContext arg(engine);
arg.bc.InstrSHORT(asBC_PSF, (short)stackOffset);
// Don't mark the variable as temporary, so it won't be freed too early
arg.type.SetVariable(toRef, stackOffset, false);
arg.type.isLValue = true;
arg.type.isExplicitHandle = true;
args.PushLast(&arg);
// Call the behaviour method
MakeFunctionCall(ctx, ops[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
// Use the reference to the variable as the result of the expression
// Now we can mark the variable as temporary
ctx->type.SetVariable(toRef, stackOffset, true);
ctx->bc.InstrSHORT(asBC_PSF, (short)stackOffset);
}
else
{
// All casts are legal
ctx->type.Set(to);
}
}
}
// If the script object didn't implement a matching opCast or opImplCast
// then check if the desired type is part of the hierarchy
if( !conversionDone && (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCRIPT_OBJECT) )
{
// We need it to be a reference
if( !ctx->type.dataType.IsReference() )
{
asCDataType toRef = ctx->type.dataType;
toRef.MakeReference(true);
ImplicitConversion(ctx, toRef, 0, isExplicit ? asIC_EXPLICIT_REF_CAST : asIC_IMPLICIT_CONV, generateCode);
}
if( isExplicit )
{
// Allow dynamic cast between object handles (only for script objects).
// At run time this may result in a null handle,
// which when used will throw an exception
conversionDone = true;
if( generateCode )
{
ctx->bc.InstrDWORD(asBC_Cast, engine->GetTypeIdFromDataType(to));
// Allocate a temporary variable for the returned object
int returnOffset = AllocateVariable(to, true);
// Move the pointer from the object register to the temporary variable
ctx->bc.InstrSHORT(asBC_STOREOBJ, (short)returnOffset);
ctx->bc.InstrSHORT(asBC_PSF, (short)returnOffset);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
ctx->type.SetVariable(to, returnOffset, true);
ctx->type.dataType.MakeReference(true);
}
else
{
ctx->type.dataType = to;
ctx->type.dataType.MakeReference(true);
}
}
else
{
if( ctx->type.dataType.GetTypeInfo()->CastToObjectType()->DerivesFrom(to.GetTypeInfo()->CastToObjectType()) )
{
conversionDone = true;
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
}
// A ref cast must not remove the constness
if( isConst )
ctx->type.dataType.MakeHandleToConst(true);
}
return conversionDone;
}
asUINT asCCompiler::ImplicitConvPrimitiveToPrimitive(asCExprContext *ctx, const asCDataType &toOrig, asCScriptNode *node, EImplicitConv convType, bool generateCode)
{
asCDataType to = toOrig;
to.MakeReference(false);
asASSERT( !ctx->type.dataType.IsReference() );
// Maybe no conversion is needed
if( to.IsEqualExceptConst(ctx->type.dataType) )
{
// A primitive is const or not
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
return asCC_NO_CONV;
}
// Is the conversion an ambiguous enum value?
if( ctx->enumValue != "" )
{
if( to.IsEnumType() )
{
// Attempt to resolve an ambiguous enum value
asCDataType out;
asDWORD value;
if( builder->GetEnumValueFromType(to.GetTypeInfo()->CastToEnumType(), ctx->enumValue.AddressOf(), out, value) )
{
ctx->type.SetConstantDW(out, value);
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
// Reset the enum value since we no longer need it
ctx->enumValue = "";
// It wasn't really a conversion. The compiler just resolved the ambiguity (or not)
return asCC_NO_CONV;
}
}
// The enum value is ambiguous
if( node && generateCode )
Error(TXT_FOUND_MULTIPLE_ENUM_VALUES, node);
// Set a dummy to allow the compiler to try to continue the conversion
ctx->type.SetDummy();
}
// Determine the cost of this conversion
asUINT cost = asCC_NO_CONV;
if( (to.IsIntegerType() || to.IsUnsignedType()) && (ctx->type.dataType.IsFloatType() || ctx->type.dataType.IsDoubleType()) )
cost = asCC_INT_FLOAT_CONV;
else if( (to.IsFloatType() || to.IsDoubleType()) && (ctx->type.dataType.IsIntegerType() || ctx->type.dataType.IsUnsignedType()) )
cost = asCC_INT_FLOAT_CONV;
else if( to.IsUnsignedType() && ctx->type.dataType.IsIntegerType() )
cost = asCC_SIGNED_CONV;
else if( to.IsIntegerType() && ctx->type.dataType.IsUnsignedType() )
cost = asCC_SIGNED_CONV;
else if( to.GetSizeInMemoryBytes() || ctx->type.dataType.GetSizeInMemoryBytes() )
cost = asCC_PRIMITIVE_SIZE_CONV;
// Start by implicitly converting constant values
if( ctx->type.isConstant )
{
ImplicitConversionConstant(ctx, to, node, convType);
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
return cost;
}
// Allow implicit conversion between numbers
if( generateCode )
{
// When generating the code the decision has already been made, so we don't bother determining the cost
// Convert smaller types to 32bit first
int s = ctx->type.dataType.GetSizeInMemoryBytes();
if( s < 4 )
{
ConvertToTempVariable(ctx);
if( ctx->type.dataType.IsIntegerType() )
{
if( s == 1 )
ctx->bc.InstrSHORT(asBC_sbTOi, ctx->type.stackOffset);
else if( s == 2 )
ctx->bc.InstrSHORT(asBC_swTOi, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(ttInt);
}
else if( ctx->type.dataType.IsUnsignedType() )
{
if( s == 1 )
ctx->bc.InstrSHORT(asBC_ubTOi, ctx->type.stackOffset);
else if( s == 2 )
ctx->bc.InstrSHORT(asBC_uwTOi, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(ttUInt);
}
}
if( (to.IsIntegerType() && to.GetSizeInMemoryDWords() == 1 && !to.IsEnumType()) ||
(to.IsEnumType() && convType == asIC_EXPLICIT_VAL_CAST) )
{
if( ctx->type.dataType.IsIntegerType() ||
ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_i64TOi, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
else if( ctx->type.dataType.IsFloatType() )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_fTOi, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
else if( ctx->type.dataType.IsDoubleType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_dTOi, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
// Convert to smaller integer if necessary
s = to.GetSizeInMemoryBytes();
if( s < 4 )
{
ConvertToTempVariable(ctx);
if( s == 1 )
ctx->bc.InstrSHORT(asBC_iTOb, ctx->type.stackOffset);
else if( s == 2 )
ctx->bc.InstrSHORT(asBC_iTOw, ctx->type.stackOffset);
}
}
else if( to.IsIntegerType() && to.GetSizeInMemoryDWords() == 2 )
{
if( ctx->type.dataType.IsIntegerType() ||
ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
if( ctx->type.dataType.IsUnsignedType() )
ctx->bc.InstrW_W(asBC_uTOi64, offset, ctx->type.stackOffset);
else
ctx->bc.InstrW_W(asBC_iTOi64, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
else if( ctx->type.dataType.IsFloatType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_fTOi64, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
else if( ctx->type.dataType.IsDoubleType() )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_dTOi64, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
}
else if( to.IsUnsignedType() && to.GetSizeInMemoryDWords() == 1 )
{
if( ctx->type.dataType.IsIntegerType() ||
ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_i64TOi, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
else if( ctx->type.dataType.IsFloatType() )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_fTOu, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
else if( ctx->type.dataType.IsDoubleType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_dTOu, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
// Convert to smaller integer if necessary
s = to.GetSizeInMemoryBytes();
if( s < 4 )
{
ConvertToTempVariable(ctx);
if( s == 1 )
ctx->bc.InstrSHORT(asBC_iTOb, ctx->type.stackOffset);
else if( s == 2 )
ctx->bc.InstrSHORT(asBC_iTOw, ctx->type.stackOffset);
}
}
else if( to.IsUnsignedType() && to.GetSizeInMemoryDWords() == 2 )
{
if( ctx->type.dataType.IsIntegerType() ||
ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
if( ctx->type.dataType.IsUnsignedType() )
ctx->bc.InstrW_W(asBC_uTOi64, offset, ctx->type.stackOffset);
else
ctx->bc.InstrW_W(asBC_iTOi64, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
else if( ctx->type.dataType.IsFloatType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_fTOu64, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
else if( ctx->type.dataType.IsDoubleType() )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_dTOu64, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
//if( convType != asIC_EXPLICIT_VAL_CAST )
// Warning(TXT_FLOAT_CONV_TO_INT_CAUSE_TRUNC, node);
}
}
else if( to.IsFloatType() )
{
if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_iTOf, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_i64TOf, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
else if( ctx->type.dataType.IsUnsignedType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_uTOf, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else if( ctx->type.dataType.IsUnsignedType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_u64TOf, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
else if( ctx->type.dataType.IsDoubleType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_dTOf, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
else if( to.IsDoubleType() )
{
if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_iTOd, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
else if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_i64TOd, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else if( ctx->type.dataType.IsUnsignedType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_uTOd, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
else if( ctx->type.dataType.IsUnsignedType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
ConvertToTempVariable(ctx);
ctx->bc.InstrSHORT(asBC_u64TOd, ctx->type.stackOffset);
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
else if( ctx->type.dataType.IsFloatType() )
{
ConvertToTempVariable(ctx);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int offset = AllocateVariable(to, true);
ctx->bc.InstrW_W(asBC_fTOd, offset, ctx->type.stackOffset);
ctx->type.SetVariable(to, offset, true);
}
}
}
else
{
if( ((to.IsIntegerType() && !to.IsEnumType()) || to.IsUnsignedType() ||
to.IsFloatType() || to.IsDoubleType() ||
(to.IsEnumType() && convType == asIC_EXPLICIT_VAL_CAST)) &&
(ctx->type.dataType.IsIntegerType() || ctx->type.dataType.IsUnsignedType() ||
ctx->type.dataType.IsFloatType() || ctx->type.dataType.IsDoubleType()) )
{
ctx->type.dataType.SetTokenType(to.GetTokenType());
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
}
}
// Primitive types on the stack, can be const or non-const
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
return cost;
}
asUINT asCCompiler::ImplicitConvLambdaToFunc(asCExprContext *ctx, const asCDataType &to, asCScriptNode * /*node*/, EImplicitConv /*convType*/, bool generateCode)
{
asASSERT( to.IsFuncdef() && ctx->IsLambda() );
// Check that the lambda has the correct amount of arguments
asUINT count = 0;
asCScriptNode *argNode = ctx->exprNode->firstChild;
while( argNode->nodeType == snIdentifier )
{
count++;
argNode = argNode->next;
}
asASSERT( argNode->nodeType == snStatementBlock );
asCScriptFunction *funcDef = to.GetTypeInfo()->CastToFuncdefType()->funcdef;
if( funcDef->parameterTypes.GetLength() != count )
return asCC_NO_CONV;
// The Lambda can be used as this funcdef
ctx->type.dataType = to;
if( generateCode )
{
// Build a unique name for the anonymous function
asCString name;
if( m_globalVar )
name.Format("$%s$%d", m_globalVar->name.AddressOf(), numLambdas++);
else
name.Format("$%s$%d", outFunc->GetDeclaration(), numLambdas++);
// Register the lambda with the builder for later compilation
asCScriptFunction *func = builder->RegisterLambda(ctx->exprNode, script, funcDef, name, outFunc->nameSpace);
asASSERT( func == 0 || funcDef->IsSignatureExceptNameEqual(func) );
ctx->bc.InstrPTR(asBC_FuncPtr, func);
// Clear the expression node as it is no longer valid
ctx->exprNode = 0;
}
return asCC_CONST_CONV;
}
asUINT asCCompiler::ImplicitConversion(asCExprContext *ctx, const asCDataType &to, asCScriptNode *node, EImplicitConv convType, bool generateCode, bool allowObjectConstruct)
{
asASSERT( ctx->type.dataType.GetTokenType() != ttUnrecognizedToken ||
ctx->type.dataType.IsNullHandle() );
if( to.IsFuncdef() && ctx->IsLambda() )
return ImplicitConvLambdaToFunc(ctx, to, node, convType, generateCode);
// No conversion from void to any other type
if( ctx->type.dataType.GetTokenType() == ttVoid )
return asCC_NO_CONV;
// No conversion from class method to any type (it requires delegate)
if( ctx->IsClassMethod() )
return asCC_NO_CONV;
// Do we want a var type?
if( to.GetTokenType() == ttQuestion )
{
// Any type can be converted to a var type, but only when not generating code
asASSERT( !generateCode );
ctx->type.dataType = to;
return asCC_VARIABLE_CONV;
}
// Do we want a primitive?
else if( to.IsPrimitive() )
{
if( !ctx->type.dataType.IsPrimitive() )
return ImplicitConvObjectToPrimitive(ctx, to, node, convType, generateCode);
else
return ImplicitConvPrimitiveToPrimitive(ctx, to, node, convType, generateCode);
}
else // The target is a complex type
{
if( ctx->type.dataType.IsPrimitive() )
return ImplicitConvPrimitiveToObject(ctx, to, node, convType, generateCode, allowObjectConstruct);
else if( ctx->type.IsNullConstant() || ctx->type.dataType.GetTypeInfo() )
return ImplicitConvObjectToObject(ctx, to, node, convType, generateCode, allowObjectConstruct);
}
return asCC_NO_CONV;
}
asUINT asCCompiler::ImplicitConvObjectToPrimitive(asCExprContext *ctx, const asCDataType &to, asCScriptNode *node, EImplicitConv convType, bool generateCode)
{
if( ctx->type.isExplicitHandle )
{
// An explicit handle cannot be converted to a primitive
if( convType != asIC_IMPLICIT_CONV && node )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
return asCC_NO_CONV;
}
// TODO: Must use the const cast behaviour if the object is read-only
// Find matching value cast behaviours
// Here we're only interested in those that convert the type to a primitive type
asCArray<int> funcs;
- asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
+ auto *typeInfo = ctx->type.dataType.GetTypeInfo();
+ asCObjectType *ot = typeInfo ? typeInfo->CastToObjectType() : 0;
if( ot == 0 )
{
if( convType != asIC_IMPLICIT_CONV && node )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
return asCC_NO_CONV;
}
if( convType == asIC_EXPLICIT_VAL_CAST )
{
for( unsigned int n = 0; n < ot->methods.GetLength(); n++ )
{
// accept both implicit and explicit cast
asCScriptFunction *mthd = engine->scriptFunctions[ot->methods[n]];
if( (mthd->name == "opConv" || mthd->name == "opImplConv") &&
mthd->parameterTypes.GetLength() == 0 &&
mthd->returnType.IsPrimitive() )
funcs.PushLast(ot->methods[n]);
}
}
else
{
for( unsigned int n = 0; n < ot->methods.GetLength(); n++ )
{
// accept only implicit cast
asCScriptFunction *mthd = engine->scriptFunctions[ot->methods[n]];
if( mthd->name == "opImplConv" &&
mthd->parameterTypes.GetLength() == 0 &&
mthd->returnType.IsPrimitive() )
funcs.PushLast(ot->methods[n]);
}
}
int funcId = 0;
if( to.IsMathType() )
{
// This matrix describes the priorities of the types to search for, for each target type
// The first column is the target type, the priorities goes from left to right
eTokenType matchMtx[10][10] =
{
{ttDouble, ttFloat, ttInt64, ttUInt64, ttInt, ttUInt, ttInt16, ttUInt16, ttInt8, ttUInt8},
{ttFloat, ttDouble, ttInt64, ttUInt64, ttInt, ttUInt, ttInt16, ttUInt16, ttInt8, ttUInt8},
{ttInt64, ttUInt64, ttInt, ttUInt, ttInt16, ttUInt16, ttInt8, ttUInt8, ttDouble, ttFloat},
{ttUInt64, ttInt64, ttUInt, ttInt, ttUInt16, ttInt16, ttUInt8, ttInt8, ttDouble, ttFloat},
{ttInt, ttUInt, ttInt64, ttUInt64, ttInt16, ttUInt16, ttInt8, ttUInt8, ttDouble, ttFloat},
{ttUInt, ttInt, ttUInt64, ttInt64, ttUInt16, ttInt16, ttUInt8, ttInt8, ttDouble, ttFloat},
{ttInt16, ttUInt16, ttInt, ttUInt, ttInt64, ttUInt64, ttInt8, ttUInt8, ttDouble, ttFloat},
{ttUInt16, ttInt16, ttUInt, ttInt, ttUInt64, ttInt64, ttUInt8, ttInt8, ttDouble, ttFloat},
{ttInt8, ttUInt8, ttInt16, ttUInt16, ttInt, ttUInt, ttInt64, ttUInt64, ttDouble, ttFloat},
{ttUInt8, ttInt8, ttUInt16, ttInt16, ttUInt, ttInt, ttUInt64, ttInt64, ttDouble, ttFloat},
};
// Which row to use?
eTokenType *row = 0;
for( unsigned int type = 0; type < 10; type++ )
{
if( to.GetTokenType() == matchMtx[type][0] )
{
row = &matchMtx[type][0];
break;
}
}
// Find the best matching cast operator
if( row )
{
asCDataType target(to);
// Priority goes from left to right in the matrix
for( unsigned int attempt = 0; attempt < 10 && funcId == 0; attempt++ )
{
target.SetTokenType(row[attempt]);
for( unsigned int n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *descr = builder->GetFunctionDescription(funcs[n]);
if( descr->returnType.IsEqualExceptRefAndConst(target) )
{
funcId = funcs[n];
break;
}
}
}
}
}
else
{
// Only accept the exact conversion for non-math types
// Find the matching cast operator
for( unsigned int n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *descr = builder->GetFunctionDescription(funcs[n]);
if( descr->returnType.IsEqualExceptRefAndConst(to) )
{
funcId = funcs[n];
break;
}
}
}
// Did we find a suitable function?
if( funcId != 0 )
{
asCScriptFunction *descr = builder->GetFunctionDescription(funcId);
if( generateCode )
{
Dereference(ctx, true);
PerformFunctionCall(funcId, ctx);
}
else
ctx->type.Set(descr->returnType);
// Allow one more implicit conversion to another primitive type
return asCC_OBJ_TO_PRIMITIVE_CONV + ImplicitConversion(ctx, to, node, convType, generateCode, false);
}
// TODO: clean-up: This part is similar to what is in ImplicitConvObjectValue
// If no direct conversion is found we should look for the generic form 'void opConv(?&out)'
funcs.SetLength(0);
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( ((convType == asIC_EXPLICIT_VAL_CAST) && func->name == "opConv") ||
func->name == "opImplConv" )
{
// Does the operator take the ?&out parameter?
if( func->returnType != asCDataType::CreatePrimitive(ttVoid, false) ||
func->parameterTypes.GetLength() != 1 ||
func->parameterTypes[0].GetTokenType() != ttQuestion ||
func->inOutFlags[0] != asTM_OUTREF )
continue;
funcs.PushLast(ot->methods[n]);
}
}
// TODO: If there are multiple valid value casts, then we must choose the most appropriate one
asASSERT( funcs.GetLength() <= 1 );
if( funcs.GetLength() == 1 )
{
if( generateCode )
{
// Allocate a temporary variable of the requested type
int stackOffset = AllocateVariableNotIn(to, true, false, ctx);
CallDefaultConstructor(to, stackOffset, IsVariableOnHeap(stackOffset), &ctx->bc, node);
// Pass the reference of that variable to the function as output parameter
asCDataType toRef(to);
toRef.MakeReference(true);
toRef.MakeReadOnly(false);
asCArray<asCExprContext *> args;
asCExprContext arg(engine);
// Don't mark the variable as temporary, so it won't be freed too early
arg.type.SetVariable(toRef, stackOffset, false);
arg.type.isLValue = true;
arg.exprNode = node;
args.PushLast(&arg);
// Call the behaviour method
MakeFunctionCall(ctx, funcs[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
// Use the reference to the variable as the result of the expression
// Now we can mark the variable as temporary
toRef.MakeReference(false);
ctx->type.SetVariable(toRef, stackOffset, true);
}
else
ctx->type.Set(to);
return asCC_OBJ_TO_PRIMITIVE_CONV;
}
if( convType != asIC_IMPLICIT_CONV && node )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
return asCC_NO_CONV;
}
asUINT asCCompiler::ImplicitConvObjectRef(asCExprContext *ctx, const asCDataType &to, asCScriptNode *node, EImplicitConv convType, bool generateCode)
{
// Convert null to any object type handle, but not to a non-handle type
if( ctx->type.IsNullConstant() && ctx->methodName == "" )
{
if( to.IsObjectHandle() )
{
ctx->type.dataType = to;
return asCC_REF_CONV;
}
return asCC_NO_CONV;
}
asASSERT(ctx->type.dataType.GetTypeInfo() || ctx->methodName != "");
// First attempt to convert the base type without instantiating another instance
if( to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() && ctx->methodName == "" )
{
// If the to type is an interface and the from type implements it, then we can convert it immediately
if( ctx->type.dataType.GetTypeInfo()->Implements(to.GetTypeInfo()) )
{
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
return asCC_REF_CONV;
}
// If the to type is a class and the from type derives from it, then we can convert it immediately
else if( ctx->type.dataType.GetTypeInfo()->DerivesFrom(to.GetTypeInfo()) )
{
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
return asCC_REF_CONV;
}
// If the types are not equal yet, then we may still be able to find a reference cast
else if( ctx->type.dataType.GetTypeInfo() != to.GetTypeInfo() )
{
// We may still be able to find an implicit ref cast behaviour
CompileRefCast(ctx, to, convType == asIC_EXPLICIT_REF_CAST, node, generateCode);
// Was the conversion done?
if( ctx->type.dataType.GetTypeInfo() == to.GetTypeInfo() )
return asCC_REF_CONV;
}
}
// Convert matching function types
if( to.IsFuncdef() )
{
// If the input expression is already a funcdef, check if it can be converted
if( ctx->type.dataType.IsFuncdef() &&
to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() )
{
asCScriptFunction *toFunc = to.GetTypeInfo()->CastToFuncdefType()->funcdef;
asCScriptFunction *fromFunc = ctx->type.dataType.GetTypeInfo()->CastToFuncdefType()->funcdef;
if( toFunc->IsSignatureExceptNameEqual(fromFunc) )
{
ctx->type.dataType.SetTypeInfo(to.GetTypeInfo());
return asCC_REF_CONV;
}
}
// If the input expression is a deferred function ref, check if there is a matching func
if( ctx->methodName != "" )
{
// Determine the namespace
asSNameSpace *ns = 0;
asCString name = "";
int pos = ctx->methodName.FindLast("::");
if( pos >= 0 )
{
asCString nsName = ctx->methodName.SubString(0, pos+2);
// Trim off the last ::
if( nsName.GetLength() > 2 )
nsName.SetLength(nsName.GetLength()-2);
ns = DetermineNameSpace(nsName);
name = ctx->methodName.SubString(pos+2);
}
else
{
DetermineNameSpace("");
name = ctx->methodName;
}
asCArray<int> funcs;
if( ns )
builder->GetFunctionDescriptions(name.AddressOf(), funcs, ns);
// Check if any of the functions have perfect match
asCScriptFunction *toFunc = to.GetTypeInfo()->CastToFuncdefType()->funcdef;
for( asUINT n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *func = builder->GetFunctionDescription(funcs[n]);
if( toFunc->IsSignatureExceptNameEqual(func) )
{
if( generateCode )
{
ctx->bc.InstrPTR(asBC_FuncPtr, func);
// Make sure the identified function is shared if we're compiling a shared function
if( !func->IsShared() && outFunc->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_CALL_NON_SHARED_FUNC_s, func->GetDeclaration());
Error(msg, node);
}
}
ctx->type.dataType = asCDataType::CreateType(to.GetTypeInfo(), false);
return asCC_REF_CONV;
}
}
}
}
return asCC_NO_CONV;
}
asUINT asCCompiler::ImplicitConvObjectValue(asCExprContext *ctx, const asCDataType &to, asCScriptNode *node, EImplicitConv convType, bool generateCode)
{
asUINT cost = asCC_NO_CONV;
// If the base type is still different, and we are allowed to instance
// another object then we can try an implicit value cast
if( to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() )
{
// TODO: Implement support for implicit constructor/factory
- asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
+ auto* typeInfo =ctx->type.dataType.GetTypeInfo();
+ asCObjectType *ot = typeInfo ? typeInfo->CastToObjectType() : 0;
if( ot == 0 )
return cost;
asCArray<int> funcs;
if( convType == asIC_EXPLICIT_VAL_CAST )
{
for( unsigned int n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
// accept both implicit and explicit cast
if( (func->name == "opConv" ||
func->name == "opImplConv") &&
func->returnType.GetTypeInfo() == to.GetTypeInfo() &&
func->parameterTypes.GetLength() == 0 )
funcs.PushLast(ot->methods[n]);
}
}
else
{
for( unsigned int n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
// accept only implicit cast
if( func->name == "opImplConv" &&
func->returnType.GetTypeInfo() == to.GetTypeInfo() &&
func->parameterTypes.GetLength() == 0 )
funcs.PushLast(ot->methods[n]);
}
}
// TODO: If there are multiple valid value casts, then we must choose the most appropriate one
asASSERT( funcs.GetLength() <= 1 );
if( funcs.GetLength() == 1 )
{
asCScriptFunction *f = builder->GetFunctionDescription(funcs[0]);
if( generateCode )
{
Dereference(ctx, true);
bool useVariable = false;
int stackOffset = 0;
if( f->DoesReturnOnStack() )
{
useVariable = true;
stackOffset = AllocateVariable(f->returnType, true);
// Push the pointer to the pre-allocated space for the return value
ctx->bc.InstrSHORT(asBC_PSF, short(stackOffset));
// The object pointer is already on the stack, but should be the top
// one, so we need to swap the pointers in order to get the correct
ctx->bc.Instr(asBC_SwapPtr);
}
PerformFunctionCall(funcs[0], ctx, false, 0, 0, useVariable, stackOffset);
}
else
ctx->type.Set(f->returnType);
cost = asCC_TO_OBJECT_CONV;
}
else
{
// TODO: cleanup: This part is similar to the second half of ImplicitConvObjectToPrimitive
// Look for a value cast with variable type
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( ((convType == asIC_EXPLICIT_VAL_CAST) && func->name == "opConv") ||
func->name == "opImplConv" )
{
// Does the operator take the ?&out parameter?
if( func->returnType != asCDataType::CreatePrimitive(ttVoid, false) ||
func->parameterTypes.GetLength() != 1 ||
func->parameterTypes[0].GetTokenType() != ttQuestion ||
func->inOutFlags[0] != asTM_OUTREF )
continue;
funcs.PushLast(ot->methods[n]);
}
}
// TODO: If there are multiple valid value casts, then we must choose the most appropriate one
asASSERT( funcs.GetLength() <= 1 );
if( funcs.GetLength() == 1 )
{
cost = asCC_TO_OBJECT_CONV;
if( generateCode )
{
// Allocate a temporary variable of the requested type
int stackOffset = AllocateVariableNotIn(to, true, false, ctx);
CallDefaultConstructor(to, stackOffset, IsVariableOnHeap(stackOffset), &ctx->bc, node);
// Pass the reference of that variable to the function as output parameter
asCDataType toRef(to);
toRef.MakeReference(false);
asCExprContext arg(engine);
arg.bc.InstrSHORT(asBC_PSF, (short)stackOffset);
// If this an object on the heap, the pointer must be dereferenced
if( IsVariableOnHeap(stackOffset) )
arg.bc.Instr(asBC_RDSPtr);
// Don't mark the variable as temporary, so it won't be freed too early
arg.type.SetVariable(toRef, stackOffset, false);
arg.type.isLValue = true;
arg.exprNode = node;
// Mark the argument as clean, so that MakeFunctionCall knows it
// doesn't have to make a copy of it in order to protect the value
arg.isCleanArg = true;
// Call the behaviour method
asCArray<asCExprContext *> args;
args.PushLast(&arg);
MakeFunctionCall(ctx, funcs[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
// Use the reference to the variable as the result of the expression
// Now we can mark the variable as temporary
ctx->type.SetVariable(toRef, stackOffset, true);
ctx->bc.InstrSHORT(asBC_PSF, (short)stackOffset);
}
else
{
// All casts are legal
ctx->type.Set(to);
}
}
}
}
return cost;
}
asUINT asCCompiler::ImplicitConvObjectToObject(asCExprContext *ctx, const asCDataType &to, asCScriptNode *node, EImplicitConv convType, bool generateCode, bool allowObjectConstruct)
{
// First try a ref cast
asUINT cost = ImplicitConvObjectRef(ctx, to, node, convType, generateCode);
// If the desired type is an asOBJ_ASHANDLE then we'll assume it is allowed to implicitly
// construct the object through any of the available constructors
if( to.GetTypeInfo() && (to.GetTypeInfo()->flags & asOBJ_ASHANDLE) && to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() && allowObjectConstruct )
{
asCArray<int> funcs;
funcs = to.GetTypeInfo()->CastToObjectType()->beh.constructors;
asCArray<asCExprContext *> args;
args.PushLast(ctx);
cost = asCC_TO_OBJECT_CONV + MatchFunctions(funcs, args, node, 0, 0, 0, false, true, false);
// Did we find a matching constructor?
if( funcs.GetLength() == 1 )
{
if( generateCode )
{
// If the ASHANDLE receives a variable type parameter, then we need to
// make sure the expression is treated as a handle and not as a value
asCScriptFunction *func = engine->scriptFunctions[funcs[0]];
if( func->parameterTypes[0].GetTokenType() == ttQuestion )
{
if( !ctx->type.isExplicitHandle )
{
asCDataType toHandle = ctx->type.dataType;
toHandle.MakeHandle(true);
toHandle.MakeReference(true);
toHandle.MakeHandleToConst(ctx->type.dataType.IsReadOnly());
ImplicitConversion(ctx, toHandle, node, asIC_IMPLICIT_CONV, true, false);
asASSERT( ctx->type.dataType.IsObjectHandle() );
}
ctx->type.isExplicitHandle = true;
}
// TODO: This should really reuse the code from CompileConstructCall
// Allocate the new object
asCExprValue tempObj;
tempObj.dataType = to;
tempObj.dataType.MakeReference(false);
tempObj.stackOffset = (short)AllocateVariable(tempObj.dataType, true);
tempObj.dataType.MakeReference(true);
tempObj.isTemporary = true;
tempObj.isVariable = true;
bool onHeap = IsVariableOnHeap(tempObj.stackOffset);
// Push the address of the object on the stack
asCExprContext e(engine);
if( onHeap )
e.bc.InstrSHORT(asBC_VAR, tempObj.stackOffset);
PrepareFunctionCall(funcs[0], &e.bc, args);
MoveArgsToStack(funcs[0], &e.bc, args, false);
// If the object is allocated on the stack, then call the constructor as a normal function
if( onHeap )
{
int offset = 0;
asCScriptFunction *descr = builder->GetFunctionDescription(funcs[0]);
offset = descr->parameterTypes[0].GetSizeOnStackDWords();
e.bc.InstrWORD(asBC_GETREF, (asWORD)offset);
}
else
e.bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
PerformFunctionCall(funcs[0], &e, onHeap, &args, tempObj.dataType.GetTypeInfo()->CastToObjectType());
// Add tag that the object has been initialized
e.bc.ObjInfo(tempObj.stackOffset, asOBJ_INIT);
// The constructor doesn't return anything,
// so we have to manually inform the type of
// the return value
e.type = tempObj;
if( !onHeap )
e.type.dataType.MakeReference(false);
// Push the address of the object on the stack again
e.bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
MergeExprBytecodeAndType(ctx, &e);
}
else
{
ctx->type.Set(asCDataType::CreateType(to.GetTypeInfo(), false));
}
}
}
// If the base type is still different, and we are allowed to instance
// another object then we can try an implicit value cast
if( to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() && allowObjectConstruct )
{
// Attempt implicit value cast
cost = ImplicitConvObjectValue(ctx, to, node, convType, generateCode);
}
// If we still haven't converted the base type to the correct type, then there is
// no need to continue as it is not possible to do the conversion
if( to.GetTypeInfo() != ctx->type.dataType.GetTypeInfo() )
return asCC_NO_CONV;
if( to.IsObjectHandle() )
{
// There is no extra cost in converting to a handle
// reference to handle -> handle
// reference -> handle
// object -> handle
// handle -> reference to handle
// reference -> reference to handle
// object -> reference to handle
// TODO: If the type is handle, then we can't use IsReadOnly to determine the constness of the basetype
// If the rvalue is a handle to a const object, then
// the lvalue must also be a handle to a const object
if( ctx->type.dataType.IsReadOnly() && !to.IsReadOnly() )
{
if( convType != asIC_IMPLICIT_CONV )
{
asASSERT(node);
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
}
if( !ctx->type.dataType.IsObjectHandle() )
{
// An object type can be directly converted to a handle of the
// same type by doing a ref copy to a new variable
if( ctx->type.dataType.SupportHandles() )
{
asCDataType dt = ctx->type.dataType;
dt.MakeHandle(true);
dt.MakeReference(false);
if( generateCode )
{
// If the expression is already a local variable, then it is not
// necessary to do a ref copy, as the ref objects on the stack are
// really handles, only the handles cannot be modified.
if( ctx->type.isVariable )
{
bool isHandleToConst = ctx->type.dataType.IsReadOnly();
ctx->type.dataType.MakeReadOnly(false);
ctx->type.dataType.MakeHandle(true);
ctx->type.dataType.MakeReadOnly(true);
ctx->type.dataType.MakeHandleToConst(isHandleToConst);
if( to.IsReference() && !ctx->type.dataType.IsReference() )
{
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
ctx->type.dataType.MakeReference(true);
}
else if( ctx->type.dataType.IsReference() )
{
ctx->bc.Instr(asBC_RDSPtr);
ctx->type.dataType.MakeReference(false);
}
}
else
{
int offset = AllocateVariable(dt, true);
if( ctx->type.dataType.IsReference() )
ctx->bc.Instr(asBC_RDSPtr);
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
if (dt.IsFuncdef())
ctx->bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx->bc.InstrPTR(asBC_REFCPY, dt.GetTypeInfo());
ctx->bc.Instr(asBC_PopPtr);
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
if( to.IsReference() )
dt.MakeReference(true);
else
ctx->bc.Instr(asBC_RDSPtr);
ctx->type.SetVariable(dt, offset, true);
}
}
else
ctx->type.dataType = dt;
// When this conversion is done the expression is no longer an lvalue
ctx->type.isLValue = false;
}
}
if( ctx->type.dataType.IsObjectHandle() )
{
// A handle to non-const can be converted to a
// handle to const, but not the other way
if( to.IsHandleToConst() )
ctx->type.dataType.MakeHandleToConst(true);
// A const handle can be converted to a non-const
// handle and vice versa as the handle is just a value
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
}
if( to.IsReference() && !ctx->type.dataType.IsReference() )
{
if( generateCode )
{
asASSERT( ctx->type.dataType.IsObjectHandle() );
// If the input type is a handle, then a simple ref copy is enough
bool isExplicitHandle = ctx->type.isExplicitHandle;
ctx->type.isExplicitHandle = ctx->type.dataType.IsObjectHandle();
// If the input type is read-only we'll need to temporarily
// remove this constness, otherwise the assignment will fail
bool typeIsReadOnly = ctx->type.dataType.IsReadOnly();
ctx->type.dataType.MakeReadOnly(false);
// If the object already is a temporary variable, then the copy
// doesn't have to be made as it is already a unique object
PrepareTemporaryVariable(node, ctx);
ctx->type.dataType.MakeReadOnly(typeIsReadOnly);
ctx->type.isExplicitHandle = isExplicitHandle;
}
// A non-reference can be converted to a reference,
// by putting the value in a temporary variable
ctx->type.dataType.MakeReference(true);
// Since it is a new temporary variable it doesn't have to be const
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
}
else if( !to.IsReference() && ctx->type.dataType.IsReference() )
{
Dereference(ctx, generateCode);
}
}
else // if( !to.IsObjectHandle() )
{
if( !to.IsReference() )
{
// reference to handle -> object
// handle -> object
// reference -> object
// An implicit handle can be converted to an object by adding a check for null pointer
if( ctx->type.dataType.IsObjectHandle() && !ctx->type.isExplicitHandle )
{
if( generateCode )
{
if( ctx->type.dataType.IsReference() )
{
// The pointer on the stack refers to the handle
ctx->bc.Instr(asBC_ChkRefS);
}
else
{
// The pointer on the stack refers to the object
ctx->bc.Instr(asBC_CHKREF);
}
}
ctx->type.dataType.MakeHandle(false);
}
// A const object can be converted to a non-const object through a copy
if( ctx->type.dataType.IsReadOnly() && !to.IsReadOnly() &&
allowObjectConstruct )
{
// Does the object type allow a copy to be made?
if( ctx->type.dataType.CanBeCopied() )
{
if( generateCode )
{
// Make a temporary object with the copy
PrepareTemporaryVariable(node, ctx);
}
// In case the object was already in a temporary variable, then the function
// didn't really do anything so we need to remove the constness here
ctx->type.dataType.MakeReadOnly(false);
// Add the cost for the copy
cost += asCC_TO_OBJECT_CONV;
}
}
if( ctx->type.dataType.IsReference() )
{
// This may look strange, but a value type allocated on the stack is already
// correct, so nothing should be done other than remove the mark as reference.
// For types allocated on the heap, it is necessary to dereference the pointer
// that is currently on the stack
if( IsVariableOnHeap(ctx->type.stackOffset) )
Dereference(ctx, generateCode);
else
ctx->type.dataType.MakeReference(false);
}
// A non-const object can be converted to a const object directly
if( !ctx->type.dataType.IsReadOnly() && to.IsReadOnly() )
{
ctx->type.dataType.MakeReadOnly(true);
}
}
else // if( to.IsReference() )
{
// reference to handle -> reference
// handle -> reference
// object -> reference
if( ctx->type.dataType.IsReference() )
{
if( ctx->type.isExplicitHandle && ctx->type.dataType.GetTypeInfo() && (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE) )
{
// ASHANDLE objects are really value types, so explicit handle can be removed
ctx->type.isExplicitHandle = false;
ctx->type.dataType.MakeHandle(false);
}
// A reference to a handle can be converted to a reference to an object
// by first reading the address, then verifying that it is not null
if( !to.IsObjectHandle() && ctx->type.dataType.IsObjectHandle() && !ctx->type.isExplicitHandle )
{
ctx->type.dataType.MakeHandle(false);
if( generateCode )
ctx->bc.Instr(asBC_ChkRefS);
}
// A reference to a non-const can be converted to a reference to a const
if( to.IsReadOnly() )
ctx->type.dataType.MakeReadOnly(true);
else if( ctx->type.dataType.IsReadOnly() && allowObjectConstruct )
{
// A reference to a const can be converted to a reference to a
// non-const by copying the object to a temporary variable
ctx->type.dataType.MakeReadOnly(false);
if( generateCode )
{
// If the object already is a temporary variable, then the copy
// doesn't have to be made as it is already a unique object
PrepareTemporaryVariable(node, ctx);
}
// Add the cost for the copy
cost += asCC_TO_OBJECT_CONV;
}
}
else // if( !ctx->type.dataType.IsReference() )
{
// A non-reference handle can be converted to a non-handle reference by checking against null handle
if( ctx->type.dataType.IsObjectHandle() )
{
bool readOnly = false;
if( ctx->type.dataType.IsHandleToConst() )
readOnly = true;
if( generateCode )
{
if( ctx->type.isVariable )
ctx->bc.InstrSHORT(asBC_ChkNullV, ctx->type.stackOffset);
else
ctx->bc.Instr(asBC_CHKREF);
}
ctx->type.dataType.MakeHandle(false);
ctx->type.dataType.MakeReference(true);
// Make sure a handle to const isn't converted to non-const reference
if( readOnly )
ctx->type.dataType.MakeReadOnly(true);
}
else
{
// A value type allocated on the stack is differentiated
// by it not being a reference. But it can be handled as
// reference by pushing the pointer on the stack
if( (ctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) &&
(ctx->type.isVariable || ctx->type.isTemporary) &&
!IsVariableOnHeap(ctx->type.stackOffset) )
{
// Actually the pointer is already pushed on the stack in
// CompileVariableAccess, so we don't need to do anything else
}
else if( generateCode )
{
// A non-reference can be converted to a reference,
// by putting the value in a temporary variable
// If the input type is read-only we'll need to temporarily
// remove this constness, otherwise the assignment will fail
bool typeIsReadOnly = ctx->type.dataType.IsReadOnly();
ctx->type.dataType.MakeReadOnly(false);
// If the object already is a temporary variable, then the copy
// doesn't have to be made as it is already a unique object
PrepareTemporaryVariable(node, ctx);
ctx->type.dataType.MakeReadOnly(typeIsReadOnly);
// Add the cost for the copy
cost += asCC_TO_OBJECT_CONV;
}
// This may look strange as the conversion was to make the expression a reference
// but a value type allocated on the stack is a reference even without the type
// being marked as such.
ctx->type.dataType.MakeReference(IsVariableOnHeap(ctx->type.stackOffset));
}
// TODO: If the variable is an object allocated on the stack the following is not true as the copy may not have been made
// Since it is a new temporary variable it doesn't have to be const
ctx->type.dataType.MakeReadOnly(to.IsReadOnly());
}
}
}
return cost;
}
asUINT asCCompiler::ImplicitConvPrimitiveToObject(asCExprContext *ctx, const asCDataType &to, asCScriptNode * /*node*/, EImplicitConv /*isExplicit*/, bool generateCode, bool /*allowObjectConstruct*/)
{
// Reference types currently don't allow implicit conversion from primitive to object
// TODO: Allow implicit conversion to scoped reference types as they are supposed to appear like ordinary value types
asCObjectType *objType = to.GetTypeInfo()->CastToObjectType();
asASSERT( objType );
if( !objType || (objType->flags & asOBJ_REF) )
return asCC_NO_CONV;
// For value types the object must have a constructor that takes a single primitive argument either by value or as input reference
asCArray<int> funcs;
for( asUINT n = 0; n < objType->beh.constructors.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[objType->beh.constructors[n]];
if( func->parameterTypes.GetLength() == 1 &&
func->parameterTypes[0].IsPrimitive() &&
!(func->inOutFlags[0] & asTM_OUTREF) )
{
funcs.PushLast(func->id);
}
}
if( funcs.GetLength() == 0 )
return asCC_NO_CONV;
// Check if it is possible to choose a best match
asCExprContext arg(engine);
arg.type = ctx->type;
arg.exprNode = ctx->exprNode; // Use the same node for compiler messages
asCArray<asCExprContext*> args;
args.PushLast(&arg);
asUINT cost = asCC_TO_OBJECT_CONV + MatchFunctions(funcs, args, 0, 0, 0, objType, false, true, false);
if( funcs.GetLength() != 1 )
return asCC_NO_CONV;
if( !generateCode )
{
ctx->type.Set(to);
return cost;
}
// TODO: clean up: This part is similar to CompileConstructCall(). It should be put in a common function
// Clear the type of ctx, as the type is moved to the arg
ctx->type.SetDummy();
// Value types and script types are allocated through the constructor
asCExprValue tempObj;
tempObj.dataType = to;
tempObj.stackOffset = (short)AllocateVariable(to, true);
tempObj.dataType.MakeReference(true);
tempObj.isTemporary = true;
tempObj.isVariable = true;
bool onHeap = IsVariableOnHeap(tempObj.stackOffset);
// Push the address of the object on the stack
if( onHeap )
ctx->bc.InstrSHORT(asBC_VAR, tempObj.stackOffset);
PrepareFunctionCall(funcs[0], &ctx->bc, args);
MoveArgsToStack(funcs[0], &ctx->bc, args, false);
if( !(objType->flags & asOBJ_REF) )
{
// If the object is allocated on the stack, then call the constructor as a normal function
if( onHeap )
{
int offset = 0;
asCScriptFunction *descr = builder->GetFunctionDescription(funcs[0]);
for( asUINT n = 0; n < args.GetLength(); n++ )
offset += descr->parameterTypes[n].GetSizeOnStackDWords();
ctx->bc.InstrWORD(asBC_GETREF, (asWORD)offset);
}
else
ctx->bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
PerformFunctionCall(funcs[0], ctx, onHeap, &args, tempObj.dataType.GetTypeInfo()->CastToObjectType());
// Add tag that the object has been initialized
ctx->bc.ObjInfo(tempObj.stackOffset, asOBJ_INIT);
// The constructor doesn't return anything,
// so we have to manually inform the type of
// the return value
ctx->type = tempObj;
if( !onHeap )
ctx->type.dataType.MakeReference(false);
// Push the address of the object on the stack again
ctx->bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
}
else
{
asASSERT( objType->flags & asOBJ_SCOPED );
// Call the factory to create the reference type
PerformFunctionCall(funcs[0], ctx, false, &args);
}
return cost;
}
void asCCompiler::ImplicitConversionConstant(asCExprContext *from, const asCDataType &to, asCScriptNode *node, EImplicitConv convType)
{
asASSERT(from->type.isConstant);
// TODO: node should be the node of the value that is
// converted (not the operator that provokes the implicit
// conversion)
// If the base type is correct there is no more to do
if( to.IsEqualExceptRefAndConst(from->type.dataType) ) return;
// References cannot be constants
if( from->type.dataType.IsReference() ) return;
if( (to.IsIntegerType() && to.GetSizeInMemoryDWords() == 1 && !to.IsEnumType()) ||
(to.IsEnumType() && convType == asIC_EXPLICIT_VAL_CAST) )
{
if( from->type.dataType.IsFloatType() ||
from->type.dataType.IsDoubleType() ||
from->type.dataType.IsUnsignedType() ||
from->type.dataType.IsIntegerType() )
{
// Transform the value
// Float constants can be implicitly converted to int
if( from->type.dataType.IsFloatType() )
{
float fc = from->type.floatValue;
int ic = int(fc);
if( float(ic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.intValue = ic;
}
// Double constants can be implicitly converted to int
else if( from->type.dataType.IsDoubleType() )
{
double fc = from->type.doubleValue;
int ic = int(fc);
if( double(ic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.intValue = ic;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Verify that it is possible to convert to signed without getting negative
if( from->type.intValue < 0 )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_CHANGE_SIGN, node);
}
// Convert to 32bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.intValue = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.intValue = from->type.wordValue;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
// Convert to 32bit
from->type.intValue = int(from->type.qwordValue);
}
else if( from->type.dataType.IsIntegerType() &&
from->type.dataType.GetSizeInMemoryBytes() < 4 )
{
// Convert to 32bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.intValue = (signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.intValue = (short)from->type.wordValue;
}
// Set the resulting type
if( to.IsEnumType() )
from->type.dataType = to;
else
from->type.dataType = asCDataType::CreatePrimitive(ttInt, true);
}
// Check if a downsize is necessary
if( to.IsIntegerType() &&
from->type.dataType.IsIntegerType() &&
from->type.dataType.GetSizeInMemoryBytes() > to.GetSizeInMemoryBytes() )
{
// Verify if it is possible
if( to.GetSizeInMemoryBytes() == 1 )
{
if( char(from->type.intValue) != from->type.intValue )
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_VALUE_TOO_LARGE_FOR_TYPE, node);
from->type.byteValue = char(from->type.intValue);
}
else if( to.GetSizeInMemoryBytes() == 2 )
{
if( short(from->type.intValue) != from->type.intValue )
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_VALUE_TOO_LARGE_FOR_TYPE, node);
from->type.wordValue = short(from->type.intValue);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
}
}
else if( to.IsIntegerType() && to.GetSizeInMemoryDWords() == 2 )
{
// Float constants can be implicitly converted to int
if( from->type.dataType.IsFloatType() )
{
float fc = from->type.floatValue;
asINT64 ic = asINT64(fc);
if( float(ic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttInt64, true);
from->type.qwordValue = ic;
}
// Double constants can be implicitly converted to int
else if( from->type.dataType.IsDoubleType() )
{
double fc = from->type.doubleValue;
asINT64 ic = asINT64(fc);
if( double(ic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttInt64, true);
from->type.qwordValue = ic;
}
else if( from->type.dataType.IsUnsignedType() )
{
// Convert to 64bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.qwordValue = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.qwordValue = from->type.wordValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 4 )
from->type.qwordValue = from->type.dwordValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 8 )
{
if( asINT64(from->type.qwordValue) < 0 )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_CHANGE_SIGN, node);
}
}
from->type.dataType = asCDataType::CreatePrimitive(ttInt64, true);
}
else if( from->type.dataType.IsIntegerType() )
{
// Convert to 64bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.qwordValue = (signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.qwordValue = (short)from->type.wordValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 4 )
from->type.qwordValue = from->type.intValue;
from->type.dataType = asCDataType::CreatePrimitive(ttInt64, true);
}
}
else if( to.IsUnsignedType() && to.GetSizeInMemoryDWords() == 1 )
{
if( from->type.dataType.IsFloatType() )
{
float fc = from->type.floatValue;
// Some compilers set the value to 0 when converting a negative float to unsigned int.
// To maintain a consistent behaviour across compilers we convert to int first.
asUINT uic = asUINT(int(fc));
if( float(uic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttUInt, true);
from->type.intValue = uic;
// Try once more, in case of a smaller type
ImplicitConversionConstant(from, to, node, convType);
}
else if( from->type.dataType.IsDoubleType() )
{
double fc = from->type.doubleValue;
// Some compilers set the value to 0 when converting a negative double to unsigned int.
// To maintain a consistent behaviour across compilers we convert to int first.
asUINT uic = asUINT(int(fc));
if( double(uic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttUInt, true);
from->type.intValue = uic;
// Try once more, in case of a smaller type
ImplicitConversionConstant(from, to, node, convType);
}
else if( from->type.dataType.IsIntegerType() )
{
// Verify that it is possible to convert to unsigned without loosing negative
if( (from->type.dataType.GetSizeInMemoryBytes() > 4 && asINT64(from->type.qwordValue) < 0) ||
(from->type.dataType.GetSizeInMemoryBytes() <= 4 && from->type.intValue < 0) )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_CHANGE_SIGN, node);
}
// Check if any data is lost
if( from->type.dataType.GetSizeInMemoryBytes() > 4 && (from->type.qwordValue >> 32) != 0 && (from->type.qwordValue >> 32) != 0xFFFFFFFF )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_VALUE_TOO_LARGE_FOR_TYPE, node);
}
// Convert to 32bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.intValue = (signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.intValue = (short)from->type.wordValue;
from->type.dataType = asCDataType::CreatePrimitive(ttUInt, true);
// Try once more, in case of a smaller type
ImplicitConversionConstant(from, to, node, convType);
}
else if( from->type.dataType.IsUnsignedType() &&
from->type.dataType.GetSizeInMemoryBytes() < 4 )
{
// Convert to 32bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.dwordValue = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.dwordValue = from->type.wordValue;
from->type.dataType = asCDataType::CreatePrimitive(ttUInt, true);
// Try once more, in case of a smaller type
ImplicitConversionConstant(from, to, node, convType);
}
else if( from->type.dataType.IsUnsignedType() &&
from->type.dataType.GetSizeInMemoryBytes() > to.GetSizeInMemoryBytes() )
{
// Verify if it is possible
if( to.GetSizeInMemoryBytes() == 1 )
{
if( asBYTE(from->type.dwordValue) != from->type.dwordValue )
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_VALUE_TOO_LARGE_FOR_TYPE, node);
from->type.byteValue = asBYTE(from->type.dwordValue);
}
else if( to.GetSizeInMemoryBytes() == 2 )
{
if( asWORD(from->type.dwordValue) != from->type.dwordValue )
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_VALUE_TOO_LARGE_FOR_TYPE, node);
from->type.wordValue = asWORD(from->type.dwordValue);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
}
}
else if( to.IsUnsignedType() && to.GetSizeInMemoryDWords() == 2 )
{
if( from->type.dataType.IsFloatType() )
{
float fc = from->type.floatValue;
// Convert first to int64 then to uint64 to avoid negative float becoming 0 on gnuc base compilers
asQWORD uic = asQWORD(asINT64(fc));
#if !defined(_MSC_VER) || _MSC_VER > 1200 // MSVC++ 6
// MSVC6 doesn't support this conversion
if( float(uic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
#endif
from->type.dataType = asCDataType::CreatePrimitive(ttUInt64, true);
from->type.qwordValue = uic;
}
else if( from->type.dataType.IsDoubleType() )
{
double fc = from->type.doubleValue;
// Convert first to int64 then to uint64 to avoid negative float becoming 0 on gnuc base compilers
asQWORD uic = asQWORD(asINT64(fc));
#if !defined(_MSC_VER) || _MSC_VER > 1200 // MSVC++ 6
// MSVC6 doesn't support this conversion
if( double(uic) != fc )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
#endif
from->type.dataType = asCDataType::CreatePrimitive(ttUInt64, true);
from->type.qwordValue = uic;
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Convert to 64bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.qwordValue = (asINT64)(signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.qwordValue = (asINT64)(short)from->type.wordValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 4 )
from->type.qwordValue = (asINT64)from->type.intValue;
// Verify that it is possible to convert to unsigned without loosing negative
if( asINT64(from->type.qwordValue) < 0 )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_CHANGE_SIGN, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttUInt64, true);
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
// Verify that it is possible to convert to unsigned without loosing negative
if( asINT64(from->type.qwordValue) < 0 )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_CHANGE_SIGN, node);
}
from->type.dataType = asCDataType::CreatePrimitive(ttUInt64, true);
}
else if( from->type.dataType.IsUnsignedType() )
{
// Convert to 64bit
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
from->type.qwordValue = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
from->type.qwordValue = from->type.wordValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 4 )
from->type.qwordValue = from->type.dwordValue;
from->type.dataType = asCDataType::CreatePrimitive(ttUInt64, true);
}
}
else if( to.IsFloatType() )
{
if( from->type.dataType.IsDoubleType() )
{
double ic = from->type.doubleValue;
float fc = float(ic);
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.floatValue = fc;
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Must properly convert value in case the from value is smaller
int ic;
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
ic = (signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
ic = (short)from->type.wordValue;
else
ic = from->type.intValue;
float fc = float(ic);
if( int(fc) != ic )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.floatValue = fc;
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
float fc = float(asINT64(from->type.qwordValue));
if( asINT64(fc) != asINT64(from->type.qwordValue) )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.floatValue = fc;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Must properly convert value in case the from value is smaller
unsigned int uic;
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
uic = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
uic = from->type.wordValue;
else
uic = from->type.dwordValue;
float fc = float(uic);
if( (unsigned int)(fc) != uic )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.floatValue = fc;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
float fc = float((asINT64)from->type.qwordValue);
if( asQWORD(fc) != from->type.qwordValue )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.floatValue = fc;
}
}
else if( to.IsDoubleType() )
{
if( from->type.dataType.IsFloatType() )
{
float ic = from->type.floatValue;
double fc = double(ic);
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.doubleValue = fc;
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Must properly convert value in case the from value is smaller
int ic;
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
ic = (signed char)from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
ic = (short)from->type.wordValue;
else
ic = from->type.intValue;
double fc = double(ic);
if( int(fc) != ic )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.doubleValue = fc;
}
else if( from->type.dataType.IsIntegerType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
double fc = double(asINT64(from->type.qwordValue));
if( asINT64(fc) != asINT64(from->type.qwordValue) )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.doubleValue = fc;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 1 )
{
// Must properly convert value in case the from value is smaller
unsigned int uic;
if( from->type.dataType.GetSizeInMemoryBytes() == 1 )
uic = from->type.byteValue;
else if( from->type.dataType.GetSizeInMemoryBytes() == 2 )
uic = from->type.wordValue;
else
uic = from->type.dwordValue;
double fc = double(uic);
if( (unsigned int)(fc) != uic )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.doubleValue = fc;
}
else if( from->type.dataType.IsUnsignedType() && from->type.dataType.GetSizeInMemoryDWords() == 2 )
{
double fc = double((asINT64)from->type.qwordValue);
if( asQWORD(fc) != from->type.qwordValue )
{
if( convType != asIC_EXPLICIT_VAL_CAST && node ) Warning(TXT_NOT_EXACT, node);
}
from->type.dataType = asCDataType::CreatePrimitive(to.GetTokenType(), true);
from->type.doubleValue = fc;
}
}
}
int asCCompiler::DoAssignment(asCExprContext *ctx, asCExprContext *lctx, asCExprContext *rctx, asCScriptNode *lexpr, asCScriptNode *rexpr, eTokenType op, asCScriptNode *opNode)
{
// Don't allow any operators on expressions that take address of class method
// If methodName is set but the type is not an object, then it is a global function
if( lctx->methodName != "" || rctx->IsClassMethod() )
{
Error(TXT_INVALID_OP_ON_METHOD, opNode);
return -1;
}
// Implicit handle types should always be treated as handles in assignments
if (lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_IMPLICIT_HANDLE) )
{
lctx->type.dataType.MakeHandle(true);
lctx->type.isExplicitHandle = true;
}
// If the left hand expression is a property accessor, then that should be used
// to do the assignment instead of the ordinary operator. The exception is when
// the property accessor is for a handle property, and the operation is a value
// assignment.
if( (lctx->property_get || lctx->property_set) &&
!(lctx->type.dataType.IsObjectHandle() && !lctx->type.isExplicitHandle) )
{
if( op != ttAssignment )
{
// Generate the code for the compound assignment, i.e. get the value, apply operator, then set the value
return ProcessPropertyGetSetAccessor(ctx, lctx, rctx, op, opNode);
}
// It is not allowed to do a handle assignment on a property
// accessor that doesn't take a handle in the set accessor.
if( lctx->property_set && lctx->type.isExplicitHandle )
{
// set_opIndex has 2 arguments, where as normal setters have only 1
asCArray<asCDataType>& parameterTypes =
builder->GetFunctionDescription(lctx->property_set)->parameterTypes;
if( !parameterTypes[parameterTypes.GetLength() - 1].IsObjectHandle() )
{
// Process the property to free the memory
ProcessPropertySetAccessor(lctx, rctx, opNode);
Error(TXT_HANDLE_ASSIGN_ON_NON_HANDLE_PROP, opNode);
return -1;
}
}
MergeExprBytecodeAndType(ctx, lctx);
return ProcessPropertySetAccessor(ctx, rctx, opNode);
}
else if( lctx->property_get && lctx->type.dataType.IsObjectHandle() && !lctx->type.isExplicitHandle )
{
// Get the handle to the object that will be used for the value assignment
ProcessPropertyGetAccessor(lctx, opNode);
}
if( lctx->type.dataType.IsPrimitive() )
{
if( !lctx->type.isLValue )
{
Error(TXT_NOT_LVALUE, lexpr);
return -1;
}
if( op != ttAssignment )
{
// Compute the operator before the assignment
asCExprValue lvalue = lctx->type;
if( lctx->type.isTemporary && !lctx->type.isVariable )
{
// The temporary variable must not be freed until the
// assignment has been performed. lvalue still holds
// the information about the temporary variable
lctx->type.isTemporary = false;
}
asCExprContext o(engine);
CompileOperator(opNode, lctx, rctx, &o);
MergeExprBytecode(rctx, &o);
rctx->type = o.type;
// Convert the rvalue to the right type and validate it
PrepareForAssignment(&lvalue.dataType, rctx, rexpr, false);
MergeExprBytecode(ctx, rctx);
lctx->type = lvalue;
// The lvalue continues the same, either it was a variable, or a reference in the register
}
else
{
// Convert the rvalue to the right type and validate it
PrepareForAssignment(&lctx->type.dataType, rctx, rexpr, false, lctx);
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
}
ReleaseTemporaryVariable(rctx->type, &ctx->bc);
PerformAssignment(&lctx->type, &rctx->type, &ctx->bc, opNode);
ctx->type = lctx->type;
}
else if( lctx->type.isExplicitHandle )
{
if( !lctx->type.isLValue )
{
Error(TXT_NOT_LVALUE, lexpr);
return -1;
}
// Object handles don't have any compound assignment operators
if( op != ttAssignment )
{
asCString str;
str.Format(TXT_ILLEGAL_OPERATION_ON_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, lexpr);
return -1;
}
if( lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE) )
{
// The object is a value type but that should be treated as a handle
// Make sure the right hand value is a handle
if( !rctx->type.isExplicitHandle &&
!(rctx->type.dataType.GetTypeInfo() && (rctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE)) )
{
// Function names can be considered handles already
if( rctx->methodName == "" )
{
asCDataType dt = rctx->type.dataType;
dt.MakeHandle(true);
dt.MakeReference(false);
PrepareArgument(&dt, rctx, rexpr, true, asTM_INREF);
if( !dt.IsEqualExceptRefAndConst(rctx->type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, rexpr);
return -1;
}
}
}
if( CompileOverloadedDualOperator(opNode, lctx, rctx, ctx, true) )
{
// An overloaded assignment operator was found (or a compilation error occured)
return 0;
}
// The object must implement the opAssign method
asCString msg;
msg.Format(TXT_NO_APPROPRIATE_OPHNDLASSIGN_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(msg.AddressOf(), opNode);
return -1;
}
else
{
asCDataType dt = lctx->type.dataType;
dt.MakeReference(false);
PrepareArgument(&dt, rctx, rexpr, false, asTM_INREF , true);
if( !dt.IsEqualExceptRefAndConst(rctx->type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, rexpr);
return -1;
}
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
ctx->bc.InstrWORD(asBC_GETOBJREF, AS_PTR_SIZE);
PerformAssignment(&lctx->type, &rctx->type, &ctx->bc, opNode);
ReleaseTemporaryVariable(rctx->type, &ctx->bc);
ctx->type = lctx->type;
// After the handle assignment the original handle is left on the stack
ctx->type.dataType.MakeReference(false);
}
}
else // if( lctx->type.dataType.IsObject() )
{
// The lvalue reference may be marked as a temporary, if for example
// it was originated as a handle returned from a function. In such
// cases it must be possible to assign values to it anyway.
if( lctx->type.dataType.IsObjectHandle() && !lctx->type.isExplicitHandle )
{
// Convert the handle to a object reference
asCDataType to;
to = lctx->type.dataType;
to.MakeHandle(false);
ImplicitConversion(lctx, to, lexpr, asIC_IMPLICIT_CONV);
lctx->type.isLValue = true; // Handle may not have been an lvalue, but the dereferenced object is
}
// Check for overloaded assignment operator
if( CompileOverloadedDualOperator(opNode, lctx, rctx, ctx) )
{
// An overloaded assignment operator was found (or a compilation error occured)
return 0;
}
// No registered operator was found. In case the operation is a direct
// assignment and the rvalue is the same type as the lvalue, then we can
// still use the byte-for-byte copy to do the assignment
if( op != ttAssignment )
{
asCString str;
str.Format(TXT_ILLEGAL_OPERATION_ON_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, lexpr);
return -1;
}
// If the left hand expression is simple, i.e. without any
// function calls or allocations of memory, then we can avoid
// doing a copy of the right hand expression (done by PrepareArgument).
// Instead the reference to the value can be placed directly on the
// stack.
//
// This optimization should only be done for value types, where
// the application developer is responsible for making the
// implementation safe against unwanted destruction of the input
// reference before the time.
bool simpleExpr = (lctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) && lctx->bc.IsSimpleExpression();
// Implicitly convert the rvalue to the type of the lvalue
bool needConversion = false;
if( !lctx->type.dataType.IsEqualExceptRefAndConst(rctx->type.dataType) )
needConversion = true;
if( !simpleExpr || needConversion )
{
asCDataType dt = lctx->type.dataType;
dt.MakeReference(true);
dt.MakeReadOnly(true);
int r = PrepareArgument(&dt, rctx, rexpr, true, 1, !needConversion);
if( r < 0 )
return -1;
if( !dt.IsEqualExceptRefAndConst(rctx->type.dataType) )
{
asCString str;
str.Format(TXT_CANT_IMPLICITLY_CONVERT_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, rexpr);
return -1;
}
}
else
{
// Process any property accessor first, before placing the final reference on the stack
ProcessPropertyGetAccessor(rctx, rexpr);
if( rctx->type.dataType.IsReference() && (!(rctx->type.isVariable || rctx->type.isTemporary) || IsVariableOnHeap(rctx->type.stackOffset)) )
rctx->bc.Instr(asBC_RDSPtr);
}
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
if( !simpleExpr || needConversion )
{
if( (rctx->type.isVariable || rctx->type.isTemporary) )
{
if( !IsVariableOnHeap(rctx->type.stackOffset) )
// TODO: runtime optimize: Actually the reference can be pushed on the stack directly
// as the value allocated on the stack is guaranteed to be safe.
// The bytecode optimizer should be able to determine this and optimize away the VAR + GETREF
ctx->bc.InstrWORD(asBC_GETREF, AS_PTR_SIZE);
else
ctx->bc.InstrWORD(asBC_GETOBJREF, AS_PTR_SIZE);
}
}
PerformAssignment(&lctx->type, &rctx->type, &ctx->bc, opNode);
ReleaseTemporaryVariable(rctx->type, &ctx->bc);
ctx->type = lctx->type;
}
return 0;
}
int asCCompiler::CompileAssignment(asCScriptNode *expr, asCExprContext *ctx)
{
asCScriptNode *lexpr = expr->firstChild;
if( lexpr->next )
{
// Compile the two expression terms
asCExprContext lctx(engine), rctx(engine);
int rr = CompileAssignment(lexpr->next->next, &rctx);
int lr = CompileCondition(lexpr, &lctx);
if( lr >= 0 && rr >= 0 )
return DoAssignment(ctx, &lctx, &rctx, lexpr, lexpr->next->next, lexpr->next->tokenType, lexpr->next);
// Since the operands failed, the assignment was not computed
ctx->type.SetDummy();
return -1;
}
return CompileCondition(lexpr, ctx);
}
int asCCompiler::CompileCondition(asCScriptNode *expr, asCExprContext *ctx)
{
asCExprValue ctype;
// Compile the conditional expression
asCScriptNode *cexpr = expr->firstChild;
if( cexpr->next )
{
//-------------------------------
// Compile the condition
asCExprContext e(engine);
int r = CompileExpression(cexpr, &e);
if( r < 0 )
e.type.SetConstantB(asCDataType::CreatePrimitive(ttBool, true), true);
// Allow value types to be converted to bool using 'bool opImplConv()'
if( e.type.dataType.GetTypeInfo() && (e.type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(&e, asCDataType::CreatePrimitive(ttBool, false), cexpr, asIC_IMPLICIT_CONV);
if( r >= 0 && !e.type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
{
Error(TXT_EXPR_MUST_BE_BOOL, cexpr);
e.type.SetConstantB(asCDataType::CreatePrimitive(ttBool, true), true);
}
ctype = e.type;
ProcessPropertyGetAccessor(&e, cexpr);
if( e.type.dataType.IsReference() ) ConvertToVariable(&e);
ProcessDeferredParams(&e);
//-------------------------------
// Compile the left expression
asCExprContext le(engine);
int lr = CompileAssignment(cexpr->next, &le);
// Resolve any function names already
DetermineSingleFunc(&le, cexpr->next);
//-------------------------------
// Compile the right expression
asCExprContext re(engine);
int rr = CompileAssignment(cexpr->next->next, &re);
DetermineSingleFunc(&re, cexpr->next->next);
if( lr >= 0 && rr >= 0 )
{
// Don't allow any operators on expressions that take address of class method
if( le.IsClassMethod() || re.IsClassMethod() )
{
Error(TXT_INVALID_OP_ON_METHOD, expr);
return -1;
}
ProcessPropertyGetAccessor(&le, cexpr->next);
ProcessPropertyGetAccessor(&re, cexpr->next->next);
bool isExplicitHandle = le.type.isExplicitHandle || re.type.isExplicitHandle;
// Allow a 0 or null in the first case to be implicitly converted to the second type
if( le.type.isConstant && le.type.intValue == 0 && le.type.dataType.IsIntegerType() )
{
asCDataType to = re.type.dataType;
to.MakeReference(false);
to.MakeReadOnly(true);
ImplicitConversionConstant(&le, to, cexpr->next, asIC_IMPLICIT_CONV);
}
else if( le.type.IsNullConstant() )
{
asCDataType to = re.type.dataType;
to.MakeHandle(true);
ImplicitConversion(&le, to, cexpr->next, asIC_IMPLICIT_CONV);
}
// Allow either case to be converted to const @ if the other is const @
if( (le.type.dataType.IsHandleToConst() && !le.type.IsNullConstant()) || (re.type.dataType.IsHandleToConst() && !re.type.dataType.IsNullHandle()) )
{
le.type.dataType.MakeHandleToConst(true);
re.type.dataType.MakeHandleToConst(true);
}
//---------------------------------
// Output the byte code
int afterLabel = nextLabel++;
int elseLabel = nextLabel++;
// If left expression is void, then we don't need to store the result
if( le.type.dataType.IsEqualExceptConst(asCDataType::CreatePrimitive(ttVoid, false)) )
{
// Put the code for the condition expression on the output
MergeExprBytecode(ctx, &e);
// Added the branch decision
ctx->type = e.type;
ConvertToVariable(ctx);
ctx->bc.InstrSHORT(asBC_CpyVtoR4, ctx->type.stackOffset);
ctx->bc.Instr(asBC_ClrHi);
ctx->bc.InstrDWORD(asBC_JZ, elseLabel);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
// Add the left expression
MergeExprBytecode(ctx, &le);
ctx->bc.InstrINT(asBC_JMP, afterLabel);
// Add the right expression
ctx->bc.Label((short)elseLabel);
MergeExprBytecode(ctx, &re);
ctx->bc.Label((short)afterLabel);
// Make sure both expressions have the same type
if( le.type.dataType != re.type.dataType )
Error(TXT_BOTH_MUST_BE_SAME, expr);
// Set the type of the result
ctx->type = le.type;
}
else
{
// Allow "(a ? b : c) = d;" and "return (a ? b : c);" (where the latter returns the reference)
//
// Restrictions for the condition to be used as lvalue:
// 1. both b and c must be of the same type and be lvalue references
// 2. neither of the expressions can have any deferred arguments
// that would have to be cleaned up after the reference
// 3. neither expression can be temporary
//
// If either expression is local, the resulting lvalue is not valid
// for return since it is not allowed to return references to local
// variables.
//
// The reference to the local variable must be loaded into the register,
// the resulting expression must not be considered as a local variable
// with a stack offset (i.e. it will not be allowed to use asBC_VAR)
if( le.type.isLValue && re.type.isLValue &&
le.deferredParams.GetLength() == 0 && re.deferredParams.GetLength() ==0 &&
!le.type.isTemporary && !re.type.isTemporary &&
le.type.dataType == re.type.dataType )
{
// Put the code for the condition expression on the output
MergeExprBytecode(ctx, &e);
// Add the branch decision
ctx->type = e.type;
ConvertToVariable(ctx);
ctx->bc.InstrSHORT(asBC_CpyVtoR4, ctx->type.stackOffset);
ctx->bc.Instr(asBC_ClrHi);
ctx->bc.InstrDWORD(asBC_JZ, elseLabel);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
// Start of the left expression
MergeExprBytecode(ctx, &le);
if( !le.type.dataType.IsReference() && le.type.isVariable )
{
// Load the address of the variable into the register
ctx->bc.InstrSHORT(asBC_LDV, le.type.stackOffset);
}
ctx->bc.InstrINT(asBC_JMP, afterLabel);
// Start of the right expression
ctx->bc.Label((short)elseLabel);
MergeExprBytecode(ctx, &re);
if( !re.type.dataType.IsReference() && re.type.isVariable )
{
// Load the address of the variable into the register
ctx->bc.InstrSHORT(asBC_LDV, re.type.stackOffset);
}
ctx->bc.Label((short)afterLabel);
// In case the options were to objects, it is necessary to dereference the pointer on
// the stack so it will point to the actual object, instead of the variable
if( le.type.dataType.IsReference() && le.type.dataType.IsObject() && !le.type.dataType.IsObjectHandle() )
{
asASSERT( re.type.dataType.IsReference() && re.type.dataType.IsObject() && !re.type.dataType.IsObjectHandle() );
ctx->bc.Instr(asBC_RDSPtr);
}
// The result is an lvalue
ctx->type.isLValue = true;
ctx->type.dataType = le.type.dataType;
if( ctx->type.dataType.IsPrimitive() || ctx->type.dataType.IsObjectHandle() )
ctx->type.dataType.MakeReference(true);
else
ctx->type.dataType.MakeReference(false);
// It can't be a treated as a variable, since we don't know which one was used
ctx->type.isVariable = false;
ctx->type.isTemporary = false;
// Must remember if the reference was to a local variable, since it must not be allowed to be returned
ctx->type.isRefToLocal = le.type.isVariable || le.type.isRefToLocal || re.type.isVariable || re.type.isRefToLocal;
}
else
{
// Allocate temporary variable and copy the result to that one
asCExprValue temp;
temp = le.type;
temp.dataType.MakeReference(false);
temp.dataType.MakeReadOnly(false);
// Make sure the variable isn't used in any of the expressions,
// as it would be overwritten which may cause crashes or less visible bugs
int l = int(reservedVariables.GetLength());
e.bc.GetVarsUsed(reservedVariables);
le.bc.GetVarsUsed(reservedVariables);
re.bc.GetVarsUsed(reservedVariables);
int offset = AllocateVariable(temp.dataType, true, false);
reservedVariables.SetLength(l);
temp.SetVariable(temp.dataType, offset, true);
// TODO: copy: Use copy constructor if available. See PrepareTemporaryVariable()
CallDefaultConstructor(temp.dataType, offset, IsVariableOnHeap(offset), &ctx->bc, expr);
// Put the code for the condition expression on the output
MergeExprBytecode(ctx, &e);
// Add the branch decision
ctx->type = e.type;
ConvertToVariable(ctx);
ctx->bc.InstrSHORT(asBC_CpyVtoR4, ctx->type.stackOffset);
ctx->bc.Instr(asBC_ClrHi);
ctx->bc.InstrDWORD(asBC_JZ, elseLabel);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
// Assign the result of the left expression to the temporary variable
asCExprValue rtemp;
rtemp = temp;
if( rtemp.dataType.IsObjectHandle() )
rtemp.isExplicitHandle = true;
PrepareForAssignment(&rtemp.dataType, &le, cexpr->next, true);
MergeExprBytecode(ctx, &le);
if( !rtemp.dataType.IsPrimitive() )
{
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
rtemp.dataType.MakeReference(IsVariableOnHeap(offset));
}
asCExprValue result;
result = rtemp;
PerformAssignment(&result, &le.type, &ctx->bc, cexpr->next);
if( !result.dataType.IsPrimitive() )
ctx->bc.Instr(asBC_PopPtr); // Pop the original value (always a pointer)
// Release the old temporary variable
ReleaseTemporaryVariable(le.type, &ctx->bc);
ctx->bc.InstrINT(asBC_JMP, afterLabel);
// Start of the right expression
ctx->bc.Label((short)elseLabel);
// Copy the result to the same temporary variable
PrepareForAssignment(&rtemp.dataType, &re, cexpr->next, true);
MergeExprBytecode(ctx, &re);
if( !rtemp.dataType.IsPrimitive() )
{
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
rtemp.dataType.MakeReference(IsVariableOnHeap(offset));
}
result = rtemp;
PerformAssignment(&result, &re.type, &ctx->bc, cexpr->next);
if( !result.dataType.IsPrimitive() )
ctx->bc.Instr(asBC_PopPtr); // Pop the original value (always a pointer)
// Release the old temporary variable
ReleaseTemporaryVariable(re.type, &ctx->bc);
ctx->bc.Label((short)afterLabel);
// Make sure both expressions have the same type
if( !le.type.dataType.IsEqualExceptConst(re.type.dataType) )
Error(TXT_BOTH_MUST_BE_SAME, expr);
// Set the temporary variable as output
ctx->type = rtemp;
ctx->type.isExplicitHandle = isExplicitHandle;
if( !ctx->type.dataType.IsPrimitive() )
{
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
ctx->type.dataType.MakeReference(IsVariableOnHeap(offset));
}
// Make sure the output isn't marked as being a literal constant
ctx->type.isConstant = false;
}
}
}
else
{
ctx->type.SetDummy();
return -1;
}
}
else
return CompileExpression(cexpr, ctx);
return 0;
}
int asCCompiler::CompileExpression(asCScriptNode *expr, asCExprContext *ctx)
{
asASSERT(expr->nodeType == snExpression);
// Check if this is an initialization of a temp object with an initialization list
if( expr->firstChild && expr->firstChild->nodeType == snDataType )
{
// TODO: It should be possible to infer the type of the object from where the
// expression will be used. The compilation of the initialization list
// should be deferred until it is known for what it will be used. It will
// then for example be possible to write expressions like:
//
// @dict = {{'key', 'value'}};
// funcTakingArrayOfInt({1,2,3,4});
// Determine the type of the temporary object
asCDataType dt = builder->CreateDataTypeFromNode(expr->firstChild, script, outFunc->nameSpace);
// Do not allow constructing non-shared types in shared functions
if( outFunc->IsShared() &&
dt.GetTypeInfo() && !dt.GetTypeInfo()->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, dt.GetTypeInfo()->name.AddressOf());
Error(msg, expr);
}
// Allocate and initialize the temporary object
int offset = AllocateVariable(dt, true);
CompileInitialization(expr->lastChild, &ctx->bc, dt, expr, offset, 0, 0);
// Push the reference to the object on the stack
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
ctx->type.SetVariable(dt, offset, true);
ctx->type.isLValue = false;
// If the variable is allocated on the heap we have a reference,
// otherwise the actual object pointer is pushed on the stack.
if( IsVariableOnHeap(offset) )
ctx->type.dataType.MakeReference(true);
return 0;
}
// Convert to polish post fix, i.e: a+b => ab+
asCArray<asCScriptNode *> postfix;
ConvertToPostFix(expr, postfix);
// Compile the postfix formatted expression
return CompilePostFixExpression(&postfix, ctx);
}
void asCCompiler::ConvertToPostFix(asCScriptNode *expr, asCArray<asCScriptNode *> &postfix)
{
// The algorithm that I've implemented here is similar to
// Djikstra's Shunting Yard algorithm, though I didn't know it at the time.
// ref: http://en.wikipedia.org/wiki/Shunting-yard_algorithm
// Count the nodes in order to preallocate the buffers
int count = 0;
asCScriptNode *node = expr->firstChild;
while( node )
{
count++;
node = node->next;
}
asCArray<asCScriptNode *> stackA(count);
asCArray<asCScriptNode *> &stackB = postfix;
stackB.Allocate(count, false);
node = expr->firstChild;
while( node )
{
int precedence = GetPrecedence(node);
while( stackA.GetLength() > 0 &&
precedence <= GetPrecedence(stackA[stackA.GetLength()-1]) )
stackB.PushLast(stackA.PopLast());
stackA.PushLast(node);
node = node->next;
}
while( stackA.GetLength() > 0 )
stackB.PushLast(stackA.PopLast());
}
int asCCompiler::CompilePostFixExpression(asCArray<asCScriptNode *> *postfix, asCExprContext *ctx)
{
// Shouldn't send any byte code
asASSERT(ctx->bc.GetLastInstr() == -1);
// Set the context to a dummy type to avoid further
// errors in case the expression fails to compile
ctx->type.SetDummy();
// Evaluate the operands and operators
asCArray<asCExprContext*> free;
asCArray<asCExprContext*> expr;
int ret = 0;
for( asUINT n = 0; ret == 0 && n < postfix->GetLength(); n++ )
{
asCScriptNode *node = (*postfix)[n];
if( node->nodeType == snExprTerm )
{
asCExprContext *e = free.GetLength() ? free.PopLast() : asNEW(asCExprContext)(engine);
expr.PushLast(e);
e->exprNode = node;
ret = CompileExpressionTerm(node, e);
}
else
{
asCExprContext *r = expr.PopLast();
asCExprContext *l = expr.PopLast();
// Now compile the operator
asCExprContext *e = free.GetLength() ? free.PopLast() : asNEW(asCExprContext)(engine);
ret = CompileOperator(node, l, r, e);
expr.PushLast(e);
// Free the operands
l->Clear();
free.PushLast(l);
r->Clear();
free.PushLast(r);
}
}
if( ret == 0 )
{
asASSERT(expr.GetLength() == 1);
// The final result should be moved to the output context
MergeExprBytecodeAndType(ctx, expr[0]);
}
// Clean up
for( asUINT e = 0; e < expr.GetLength(); e++ )
asDELETE(expr[e], asCExprContext);
for( asUINT f = 0; f < free.GetLength(); f++ )
asDELETE(free[f], asCExprContext);
return ret;
}
int asCCompiler::CompileExpressionTerm(asCScriptNode *node, asCExprContext *ctx)
{
// Shouldn't send any byte code
asASSERT(ctx->bc.GetLastInstr() == -1);
// Set the type as a dummy by default, in case of any compiler errors
ctx->type.SetDummy();
// Compile the value node
asCScriptNode *vnode = node->firstChild;
while( vnode->nodeType != snExprValue )
vnode = vnode->next;
asCExprContext v(engine);
int r = CompileExpressionValue(vnode, &v); if( r < 0 ) return r;
// Compile post fix operators
asCScriptNode *pnode = vnode->next;
while( pnode )
{
r = CompileExpressionPostOp(pnode, &v); if( r < 0 ) return r;
pnode = pnode->next;
}
// Compile pre fix operators
pnode = vnode->prev;
while( pnode )
{
r = CompileExpressionPreOp(pnode, &v); if( r < 0 ) return r;
pnode = pnode->prev;
}
// Return the byte code and final type description
MergeExprBytecodeAndType(ctx, &v);
return 0;
}
int asCCompiler::CompileVariableAccess(const asCString &name, const asCString &scope, asCExprContext *ctx, asCScriptNode *errNode, bool isOptional, bool noFunction, bool noGlobal, asCObjectType *objType)
{
bool found = false;
// It is a local variable or parameter?
// This is not accessible by default arg expressions
sVariable *v = 0;
if( !isCompilingDefaultArg && scope == "" && !objType && variables )
v = variables->GetVariable(name.AddressOf());
if( v )
{
found = true;
if( v->isPureConstant )
ctx->type.SetConstantQW(v->type, v->constantValue);
else if( v->type.IsPrimitive() )
{
if( v->type.IsReference() )
{
// Copy the reference into the register
ctx->bc.InstrSHORT(asBC_PshVPtr, (short)v->stackOffset);
ctx->bc.Instr(asBC_PopRPtr);
ctx->type.Set(v->type);
}
else
ctx->type.SetVariable(v->type, v->stackOffset, false);
// Set as lvalue unless it is a const variable
if( !v->type.IsReadOnly() )
ctx->type.isLValue = true;
}
else
{
ctx->bc.InstrSHORT(asBC_PSF, (short)v->stackOffset);
ctx->type.SetVariable(v->type, v->stackOffset, false);
// If the variable is allocated on the heap we have a reference,
// otherwise the actual object pointer is pushed on the stack.
if( v->onHeap || v->type.IsObjectHandle() ) ctx->type.dataType.MakeReference(true);
// Implicitly dereference handle parameters sent by reference
if( v->type.IsReference() && (!v->type.IsObject() || v->type.IsObjectHandle()) )
ctx->bc.Instr(asBC_RDSPtr);
// Set as lvalue unless it is a const variable
if (!v->type.IsReadOnly())
ctx->type.isLValue = true;
}
}
// Is it a class member?
// This is not accessible by default arg expressions
if( !isCompilingDefaultArg && !found && ((objType) || (outFunc && outFunc->objectType && scope == "")) )
{
if( name == THIS_TOKEN && !objType )
{
asCDataType dt = asCDataType::CreateType(outFunc->objectType, outFunc->isReadOnly);
// 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);
ctx->type.isLValue = true;
found = true;
}
if( !found )
{
// See if there are any matching property accessors
asCExprContext access(engine);
if( objType )
access.type.Set(asCDataType::CreateType(objType, false));
else
access.type.Set(asCDataType::CreateType(outFunc->objectType, outFunc->isReadOnly));
access.type.dataType.MakeReference(true);
int r = 0;
if( errNode->next && errNode->next->tokenType == ttOpenBracket )
{
// This is an index access, check if there is a property accessor that takes an index arg
asCExprContext dummyArg(engine);
r = FindPropertyAccessor(name, &access, &dummyArg, errNode, 0, true);
}
if( r == 0 )
{
// Normal property access
r = FindPropertyAccessor(name, &access, errNode, 0, true);
}
if( r < 0 ) return -1;
if( access.property_get || access.property_set )
{
if( !objType )
{
// Prepare the bytecode for the member access
// This is only done when accessing through the implicit this pointer
ctx->bc.InstrSHORT(asBC_PSF, 0);
}
MergeExprBytecodeAndType(ctx, &access);
found = true;
}
}
if( !found )
{
asCDataType dt;
if( objType )
dt = asCDataType::CreateType(objType, false);
else
dt = asCDataType::CreateType(outFunc->objectType, false);
asCObjectProperty *prop = builder->GetObjectProperty(dt, name.AddressOf());
if( prop )
{
// Is the property access allowed?
if( prop->isPrivate && prop->isInherited )
{
if( engine->ep.privatePropAsProtected )
{
// The application is allowing inherited classes to access private properties of the parent
// class. This option is allowed to provide backwards compatibility with pre-2.30.0 versions
// as it was how the compiler behaved earlier.
asCString msg;
msg.Format(TXT_ACCESSING_PRIVATE_PROP_s, name.AddressOf());
Warning(msg, errNode);
}
else
{
asCString msg;
msg.Format(TXT_INHERITED_PRIVATE_PROP_ACCESS_s, name.AddressOf());
Error(msg, errNode);
}
}
if( !objType )
{
// The object pointer is located at stack position 0
// This is only done when accessing through the implicit this pointer
ctx->bc.InstrSHORT(asBC_PSF, 0);
ctx->type.SetVariable(dt, 0, false);
ctx->type.dataType.MakeReference(true);
Dereference(ctx, true);
}
// TODO: This is the same as what is in CompileExpressionPostOp
// Put the offset on the stack
ctx->bc.InstrSHORT_DW(asBC_ADDSi, (short)prop->byteOffset, engine->GetTypeIdFromDataType(dt));
if( prop->type.IsReference() )
ctx->bc.Instr(asBC_RDSPtr);
// Reference to primitive must be stored in the temp register
if( prop->type.IsPrimitive() )
{
// TODO: runtime optimize: The ADD offset command should store the reference in the register directly
ctx->bc.Instr(asBC_PopRPtr);
}
// Set the new type (keeping info about temp variable)
ctx->type.dataType = prop->type;
ctx->type.dataType.MakeReference(true);
ctx->type.isVariable = false;
ctx->type.isLValue = true;
if( ctx->type.dataType.IsObject() && !ctx->type.dataType.IsObjectHandle() )
{
// Objects that are members are not references
ctx->type.dataType.MakeReference(false);
}
// If the object reference is const, the property will also be const
ctx->type.dataType.MakeReadOnly(outFunc->isReadOnly);
found = true;
}
else if( outFunc->objectType )
{
// If it is not a property, it may still be the name of a method which can be used to create delegates
asCObjectType *ot = outFunc->objectType;
asCScriptFunction *func = 0;
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[ot->methods[n]];
if( f->name == name &&
(builder->module->accessMask & f->accessMask) )
{
func = f;
break;
}
}
if( func )
{
// An object method was found. Keep the name of the method in the expression, but
// don't actually modify the bytecode at this point since it is not yet known what
// the method will be used for, or even what overloaded method should be used.
ctx->methodName = name;
// Place the object pointer on the stack, as if the expression was this.func
if( !objType )
{
// The object pointer is located at stack position 0
// This is only done when accessing through the implicit this pointer
ctx->bc.InstrSHORT(asBC_PSF, 0);
ctx->type.SetVariable(asCDataType::CreateType(outFunc->objectType, false), 0, false);
ctx->type.dataType.MakeReference(true);
Dereference(ctx, true);
}
found = true;
}
}
}
}
// Recursively search parent namespaces for global entities
asCString currScope = scope;
// Get the namespace for this scope. This may return null if the scope is an enum
asSNameSpace *ns = DetermineNameSpace(currScope);
if( ns && currScope != "::" )
currScope = ns->name;
while( !found && !noGlobal && !objType )
{
// Is it a global property?
if( !found && ns )
{
// See if there are any matching global property accessors
asCExprContext access(engine);
int r = 0;
if( errNode->next && errNode->next->tokenType == ttOpenBracket )
{
// This is an index access, check if there is a property accessor that takes an index arg
asCExprContext dummyArg(engine);
r = FindPropertyAccessor(name, &access, &dummyArg, errNode, ns);
}
if( r == 0 )
{
// Normal property access
r = FindPropertyAccessor(name, &access, errNode, ns);
}
if( r < 0 ) return -1;
if( access.property_get || access.property_set )
{
// Prepare the bytecode for the function call
MergeExprBytecodeAndType(ctx, &access);
found = true;
}
// See if there is any matching global property
if( !found )
{
bool isCompiled = true;
bool isPureConstant = false;
bool isAppProp = false;
asQWORD constantValue = 0;
asCGlobalProperty *prop = builder->GetGlobalProperty(name.AddressOf(), ns, &isCompiled, &isPureConstant, &constantValue, &isAppProp);
if( prop )
{
found = true;
// Verify that the global property has been compiled already
if( isCompiled )
{
if( ctx->type.dataType.GetTypeInfo() && (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_IMPLICIT_HANDLE) )
{
ctx->type.dataType.MakeHandle(true);
ctx->type.isExplicitHandle = true;
}
// If the global property is a pure constant
// we can allow the compiler to optimize it. Pure
// constants are global constant variables that were
// initialized by literal constants.
if( isPureConstant )
ctx->type.SetConstantQW(prop->type, constantValue);
else
{
// A shared type must not access global vars, unless they
// too are shared, e.g. application registered vars
if( outFunc->IsShared() )
{
if( !isAppProp )
{
asCString str;
str.Format(TXT_SHARED_CANNOT_ACCESS_NON_SHARED_VAR_s, prop->name.AddressOf());
Error(str, errNode);
// Allow the compilation to continue to catch other problems
}
}
ctx->type.Set(prop->type);
ctx->type.isLValue = true;
if( ctx->type.dataType.IsPrimitive() )
{
// Load the address of the variable into the register
ctx->bc.InstrPTR(asBC_LDG, prop->GetAddressOfValue());
ctx->type.dataType.MakeReference(true);
}
else
{
// Push the address of the variable on the stack
ctx->bc.InstrPTR(asBC_PGA, prop->GetAddressOfValue());
// If the object is a value type or a non-handle variable to a reference type,
// then we must validate the existance as it could potentially be accessed
// before it is initialized.
// This check is not needed for application registered properties, since they
// are guaranteed to be valid by the application itself.
if( !isAppProp &&
((ctx->type.dataType.GetTypeInfo()->flags & asOBJ_VALUE) ||
!ctx->type.dataType.IsObjectHandle()) )
{
ctx->bc.Instr(asBC_ChkRefS);
}
// If the address pushed on the stack is to a value type or an object
// handle, then mark the expression as a reference. Addresses to a reference
// type aren't marked as references to get correct behaviour
if( (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_VALUE) ||
ctx->type.dataType.IsObjectHandle() )
{
ctx->type.dataType.MakeReference(true);
}
else
{
asASSERT( (ctx->type.dataType.GetTypeInfo()->flags & asOBJ_REF) && !ctx->type.dataType.IsObjectHandle() );
// It's necessary to dereference the pointer so the pointer on the stack will point to the actual object
ctx->bc.Instr(asBC_RDSPtr);
}
}
}
}
else
{
asCString str;
str.Format(TXT_UNINITIALIZED_GLOBAL_VAR_s, prop->name.AddressOf());
Error(str, errNode);
return -1;
}
}
}
}
// Is it the name of a global function?
if( !noFunction && !found && ns )
{
asCArray<int> funcs;
builder->GetFunctionDescriptions(name.AddressOf(), funcs, ns);
if( funcs.GetLength() > 0 )
{
found = true;
// Defer the evaluation of which function until it is actually used
// Store the namespace and name of the function for later
ctx->type.SetUndefinedFuncHandle(engine);
ctx->methodName = ns ? ns->name + "::" + name : name;
}
}
// Is it an enum value?
if( !found )
{
// The enum type may be declared in a namespace too
asCTypeInfo *scopeType = 0;
if( currScope != "" && currScope != "::" )
{
builder->GetNameSpaceByString(currScope, outFunc->objectType ? outFunc->objectType->nameSpace : outFunc->nameSpace, errNode, script, &scopeType, false);
- if (scopeType->CastToEnumType() == 0)
+ if (scopeType && scopeType->CastToEnumType() == 0)
scopeType = 0;
}
asDWORD value = 0;
asCDataType dt;
if( scopeType && builder->GetEnumValueFromType(scopeType->CastToEnumType(), name.AddressOf(), dt, value) )
{
// scoped enum value found
found = true;
}
else if( !engine->ep.requireEnumScope )
{
// Look for the enum value without explicitly informing the enum type
asSNameSpace *nsEnum = DetermineNameSpace(currScope);
int e = 0;
if(nsEnum)
e = builder->GetEnumValue(name.AddressOf(), dt, value, nsEnum);
if( e )
{
found = true;
if( e == 2 )
{
// Ambiguous enum value: Save the name for resolution later.
// The ambiguity could be resolved now, but I hesitate
// to store too much information in the context.
ctx->enumValue = name.AddressOf();
// We cannot set a dummy value because it will pass through
// cleanly as an integer.
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttIdentifier, true), 0);
return 0;
}
}
}
if( found )
{
// Even if the enum type is not shared, and we're compiling a shared object,
// the use of the values are still allowed, since they are treated as constants.
// an enum value was resolved
ctx->type.SetConstantDW(dt, value);
}
else
{
// If nothing was found because the scope doesn't match a namespace or an enum
// then this should be reported as an error and the search interrupted
if( !ns && !scopeType )
{
ctx->type.SetDummy();
asCString str;
str.Format(TXT_UNKNOWN_SCOPE_s, scope.AddressOf());
Error(str, errNode);
return -1;
}
}
}
if( !found )
{
if( currScope == "" || currScope == "::" )
break;
// Move up to parent namespace
int pos = currScope.FindLast("::");
if( pos >= 0 )
currScope = currScope.SubString(0, pos);
else
currScope = "::";
if( ns )
ns = engine->GetParentNameSpace(ns);
}
}
// The name doesn't match any variable
if( !found )
{
// Give dummy value
ctx->type.SetDummy();
if( !isOptional )
{
// Prepend the scope to the name for the error message
asCString ename;
if( scope != "" && scope != "::" )
ename = scope + "::";
else
ename = scope;
ename += name;
asCString str;
str.Format(TXT_s_NOT_DECLARED, ename.AddressOf());
Error(str, errNode);
// Declare the variable now so that it will not be reported again
variables->DeclareVariable(name.AddressOf(), asCDataType::CreatePrimitive(ttInt, false), 0x7FFF, true);
// Mark the variable as initialized so that the user will not be bother by it again
v = variables->GetVariable(name.AddressOf());
asASSERT(v);
if( v ) v->isInitialized = true;
}
// Return -1 to signal that the variable wasn't found
return -1;
}
return 0;
}
int asCCompiler::CompileExpressionValue(asCScriptNode *node, asCExprContext *ctx)
{
// Shouldn't receive any byte code
asASSERT(ctx->bc.GetLastInstr() == -1);
asCScriptNode *vnode = node->firstChild;
ctx->exprNode = vnode;
if( vnode->nodeType == snVariableAccess )
{
// Determine the scope resolution of the variable
asCString scope = builder->GetScopeFromNode(vnode->firstChild, script, &vnode);
// Determine the name of the variable
asASSERT(vnode->nodeType == snIdentifier );
asCString name(&script->code[vnode->tokenPos], vnode->tokenLength);
return CompileVariableAccess(name, scope, ctx, node);
}
else if( vnode->nodeType == snConstant )
{
if( vnode->tokenType == ttIntConstant )
{
asCString value(&script->code[vnode->tokenPos], vnode->tokenLength);
asQWORD val = asStringScanUInt64(value.AddressOf(), 10, 0);
// Do we need 64 bits?
// If the 31st bit is set we'll treat the value as a signed 64bit number to avoid
// incorrect warnings about changing signs if the value is assigned to a 64bit variable
if( val>>31 )
{
// Only if the value uses the last bit of a 64bit word do we consider the number unsigned
if( val>>63 )
ctx->type.SetConstantQW(asCDataType::CreatePrimitive(ttUInt64, true), val);
else
ctx->type.SetConstantQW(asCDataType::CreatePrimitive(ttInt64, true), val);
}
else
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttInt, true), asDWORD(val));
}
else if( vnode->tokenType == ttBitsConstant )
{
asCString value(&script->code[vnode->tokenPos], vnode->tokenLength);
// Let the function determine the radix from the prefix 0x = 16, 0d = 10, 0o = 8, or 0b = 2
// TODO: Check for overflow
asQWORD val = asStringScanUInt64(value.AddressOf(), 0, 0);
// Do we need 64 bits?
if( val>>32 )
ctx->type.SetConstantQW(asCDataType::CreatePrimitive(ttUInt64, true), val);
else
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttUInt, true), asDWORD(val));
}
else if( vnode->tokenType == ttFloatConstant )
{
asCString value(&script->code[vnode->tokenPos], vnode->tokenLength);
// TODO: Check for overflow
size_t numScanned;
float v = float(asStringScanDouble(value.AddressOf(), &numScanned));
ctx->type.SetConstantF(asCDataType::CreatePrimitive(ttFloat, true), v);
#ifndef AS_USE_DOUBLE_AS_FLOAT
// Don't check this if we have double as float, because then the whole token would be scanned (i.e. no f suffix)
asASSERT(numScanned == vnode->tokenLength - 1);
#endif
}
else if( vnode->tokenType == ttDoubleConstant )
{
asCString value(&script->code[vnode->tokenPos], vnode->tokenLength);
// TODO: Check for overflow
size_t numScanned;
double v = asStringScanDouble(value.AddressOf(), &numScanned);
ctx->type.SetConstantD(asCDataType::CreatePrimitive(ttDouble, true), v);
asASSERT(numScanned == vnode->tokenLength);
}
else if( vnode->tokenType == ttTrue ||
vnode->tokenType == ttFalse )
{
#if AS_SIZEOF_BOOL == 1
ctx->type.SetConstantB(asCDataType::CreatePrimitive(ttBool, true), vnode->tokenType == ttTrue ? VALUE_OF_BOOLEAN_TRUE : 0);
#else
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), vnode->tokenType == ttTrue ? VALUE_OF_BOOLEAN_TRUE : 0);
#endif
}
else if( vnode->tokenType == ttStringConstant ||
vnode->tokenType == ttMultilineStringConstant ||
vnode->tokenType == ttHeredocStringConstant )
{
asCString str;
asCScriptNode *snode = vnode->firstChild;
if( script->code[snode->tokenPos] == '\'' && engine->ep.useCharacterLiterals )
{
// Treat the single quoted string as a single character literal
str.Assign(&script->code[snode->tokenPos+1], snode->tokenLength-2);
asDWORD val = 0;
if( str.GetLength() && (unsigned char)str[0] > 127 && engine->ep.scanner == 1 )
{
// This is the start of a UTF8 encoded character. We need to decode it
val = asStringDecodeUTF8(str.AddressOf(), 0);
if( val == (asDWORD)-1 )
Error(TXT_INVALID_CHAR_LITERAL, vnode);
}
else
{
val = ProcessStringConstant(str, snode);
if( val == (asDWORD)-1 )
Error(TXT_INVALID_CHAR_LITERAL, vnode);
}
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttUInt, true), val);
}
else
{
// Process the string constants
while( snode )
{
asCString cat;
if( snode->tokenType == ttStringConstant )
{
cat.Assign(&script->code[snode->tokenPos+1], snode->tokenLength-2);
ProcessStringConstant(cat, snode);
}
else if( snode->tokenType == ttMultilineStringConstant )
{
if( !engine->ep.allowMultilineStrings )
Error(TXT_MULTILINE_STRINGS_NOT_ALLOWED, snode);
cat.Assign(&script->code[snode->tokenPos+1], snode->tokenLength-2);
ProcessStringConstant(cat, snode);
}
else if( snode->tokenType == ttHeredocStringConstant )
{
cat.Assign(&script->code[snode->tokenPos+3], snode->tokenLength-6);
ProcessHeredocStringConstant(cat, snode);
}
str += cat;
snode = snode->next;
}
// Call the string factory function to create a string object
asCScriptFunction *descr = engine->stringFactory;
if( descr == 0 )
{
// Error
Error(TXT_STRINGS_NOT_RECOGNIZED, vnode);
// Give dummy value
ctx->type.SetDummy();
return -1;
}
else
{
// Register the constant string with the engine
int id = engine->AddConstantString(str.AddressOf(), str.GetLength());
ctx->bc.InstrWORD(asBC_STR, (asWORD)id);
bool useVariable = false;
int stackOffset = 0;
if( descr->DoesReturnOnStack() )
{
useVariable = true;
stackOffset = AllocateVariable(descr->returnType, true);
ctx->bc.InstrSHORT(asBC_PSF, short(stackOffset));
}
PerformFunctionCall(descr->id, ctx, false, 0, 0, useVariable, stackOffset);
}
}
}
else if( vnode->tokenType == ttNull )
{
ctx->bc.Instr(asBC_PshNull);
ctx->type.SetNullConstant();
}
else
asASSERT(false);
}
else if( vnode->nodeType == snFunctionCall )
{
// Determine the scope resolution
asCString scope = builder->GetScopeFromNode(vnode->firstChild, script);
return CompileFunctionCall(vnode, ctx, 0, false, scope);
}
else if( vnode->nodeType == snConstructCall )
{
return CompileConstructCall(vnode, ctx);
}
else if( vnode->nodeType == snAssignment )
{
asCExprContext e(engine);
int r = CompileAssignment(vnode, &e);
if( r < 0 )
{
ctx->type.SetDummy();
return r;
}
MergeExprBytecodeAndType(ctx, &e);
}
else if( vnode->nodeType == snCast )
{
// Implement the cast operator
return CompileConversion(vnode, ctx);
}
else if( vnode->nodeType == snUndefined && vnode->tokenType == ttVoid )
{
// This is a void expression
ctx->SetVoidExpression();
}
else if( vnode->nodeType == snFunction )
{
// This is an anonymous function
// Defer the evaluation of the function until it known where it
// will be used, which is where the signature will be defined
ctx->SetLambda(vnode);
}
else
asASSERT(false);
return 0;
}
asUINT asCCompiler::ProcessStringConstant(asCString &cstr, asCScriptNode *node, bool processEscapeSequences)
{
int charLiteral = -1;
// Process escape sequences
asCArray<char> str((int)cstr.GetLength());
for( asUINT n = 0; n < cstr.GetLength(); n++ )
{
#ifdef AS_DOUBLEBYTE_CHARSET
// Double-byte charset is only allowed for ASCII and not UTF16 encoded strings
if( (cstr[n] & 0x80) && engine->ep.scanner == 0 && engine->ep.stringEncoding != 1 )
{
// This is the lead character of a double byte character
// include the trail character without checking it's value.
str.PushLast(cstr[n]);
n++;
str.PushLast(cstr[n]);
continue;
}
#endif
asUINT val;
if( processEscapeSequences && cstr[n] == '\\' )
{
++n;
if( n == cstr.GetLength() )
{
if( charLiteral == -1 ) charLiteral = 0;
return charLiteral;
}
// Hexadecimal escape sequences will allow the construction of
// invalid unicode sequences, but the string should also work as
// a bytearray so we must support this. The code for working with
// unicode text must be prepared to handle invalid unicode sequences
if( cstr[n] == 'x' || cstr[n] == 'X' )
{
++n;
if( n == cstr.GetLength() ) break;
val = 0;
int c = engine->ep.stringEncoding == 1 ? 4 : 2;
for( ; c > 0 && n < cstr.GetLength(); c--, n++ )
{
if( cstr[n] >= '0' && cstr[n] <= '9' )
val = val*16 + cstr[n] - '0';
else if( cstr[n] >= 'a' && cstr[n] <= 'f' )
val = val*16 + cstr[n] - 'a' + 10;
else if( cstr[n] >= 'A' && cstr[n] <= 'F' )
val = val*16 + cstr[n] - 'A' + 10;
else
break;
}
// Rewind one, since the loop will increment it again
n--;
// Hexadecimal escape sequences produce exact value, even if it is not proper unicode chars
if( engine->ep.stringEncoding == 0 )
{
str.PushLast((asBYTE)val);
}
else
{
#ifndef AS_BIG_ENDIAN
str.PushLast((asBYTE)val);
str.PushLast((asBYTE)(val>>8));
#else
str.PushLast((asBYTE)(val>>8));
str.PushLast((asBYTE)val);
#endif
}
if( charLiteral == -1 ) charLiteral = val;
continue;
}
else if( cstr[n] == 'u' || cstr[n] == 'U' )
{
// \u expects 4 hex digits
// \U expects 8 hex digits
bool expect2 = cstr[n] == 'u';
int c = expect2 ? 4 : 8;
val = 0;
for( ; c > 0; c-- )
{
++n;
if( n == cstr.GetLength() ) break;
if( cstr[n] >= '0' && cstr[n] <= '9' )
val = val*16 + cstr[n] - '0';
else if( cstr[n] >= 'a' && cstr[n] <= 'f' )
val = val*16 + cstr[n] - 'a' + 10;
else if( cstr[n] >= 'A' && cstr[n] <= 'F' )
val = val*16 + cstr[n] - 'A' + 10;
else
break;
}
if( c != 0 )
{
// Give warning about invalid code point
// TODO: Need code position for warning
asCString msg;
msg.Format(TXT_INVALID_UNICODE_FORMAT_EXPECTED_d, expect2 ? 4 : 8);
Warning(msg, node);
continue;
}
}
else
{
if( cstr[n] == '"' )
val = '"';
else if( cstr[n] == '\'' )
val = '\'';
else if( cstr[n] == 'n' )
val = '\n';
else if( cstr[n] == 'r' )
val = '\r';
else if( cstr[n] == 't' )
val = '\t';
else if( cstr[n] == '0' )
val = '\0';
else if( cstr[n] == '\\' )
val = '\\';
else
{
// Invalid escape sequence
Warning(TXT_INVALID_ESCAPE_SEQUENCE, node);
continue;
}
}
}
else
{
if( engine->ep.scanner == 1 && (cstr[n] & 0x80) )
{
unsigned int len;
val = asStringDecodeUTF8(&cstr[n], &len);
if( val == 0xFFFFFFFF )
{
// Incorrect UTF8 encoding. Use only the first byte
// TODO: Need code position for warning
Warning(TXT_INVALID_UNICODE_SEQUENCE_IN_SRC, node);
val = (unsigned char)cstr[n];
}
else
n += len-1;
}
else
val = (unsigned char)cstr[n];
}
// Add the character to the final string
char encodedValue[5];
int len;
if( engine->ep.scanner == 1 && engine->ep.stringEncoding == 0 )
{
// Convert to UTF8 encoded
len = asStringEncodeUTF8(val, encodedValue);
}
else if( engine->ep.stringEncoding == 1 )
{
// Convert to 16bit wide character string (even if the script is scanned as ASCII)
len = asStringEncodeUTF16(val, encodedValue);
}
else
{
// Do not convert ASCII characters
encodedValue[0] = (asBYTE)val;
len = 1;
}
if( len < 0 )
{
// Give warning about invalid code point
// TODO: Need code position for warning
Warning(TXT_INVALID_UNICODE_VALUE, node);
}
else
{
// Add the encoded value to the final string
str.Concatenate(encodedValue, len);
if( charLiteral == -1 ) charLiteral = val;
}
}
cstr.Assign(str.AddressOf(), str.GetLength());
return charLiteral;
}
void asCCompiler::ProcessHeredocStringConstant(asCString &str, asCScriptNode *node)
{
// Remove first line if it only contains whitespace
int start;
for( start = 0; start < (int)str.GetLength(); start++ )
{
if( str[start] == '\n' )
{
// Remove the linebreak as well
start++;
break;
}
if( str[start] != ' ' &&
str[start] != '\t' &&
str[start] != '\r' )
{
// Don't remove anything
start = 0;
break;
}
}
// Remove the line after the last line break if it only contains whitespaces
int end;
for( end = (int)str.GetLength() - 1; end >= 0; end-- )
{
if( str[end] == '\n' )
{
// Don't remove the last line break
end++;
break;
}
if( str[end] != ' ' &&
str[end] != '\t' &&
str[end] != '\r' )
{
// Don't remove anything
end = (int)str.GetLength();
break;
}
}
if( end < 0 ) end = 0;
asCString tmp;
if( end > start )
tmp.Assign(&str[start], end-start);
ProcessStringConstant(tmp, node, false);
str = tmp;
}
int asCCompiler::CompileConversion(asCScriptNode *node, asCExprContext *ctx)
{
asCExprContext expr(engine);
asCDataType to;
bool anyErrors = false;
EImplicitConv convType;
if( node->nodeType == snConstructCall )
{
convType = asIC_EXPLICIT_VAL_CAST;
// Verify that there is only one argument
if( node->lastChild->firstChild == 0 ||
node->lastChild->firstChild != node->lastChild->lastChild )
{
Error(TXT_ONLY_ONE_ARGUMENT_IN_CAST, node->lastChild);
expr.type.SetDummy();
anyErrors = true;
}
else
{
// Compile the expression
int r = CompileAssignment(node->lastChild->firstChild, &expr);
if( r < 0 )
anyErrors = true;
}
// Determine the requested type
to = builder->CreateDataTypeFromNode(node->firstChild, script, outFunc->nameSpace);
to.MakeReadOnly(true); // Default to const
asASSERT(to.IsPrimitive());
}
else
{
convType = asIC_EXPLICIT_REF_CAST;
// Compile the expression
int r = CompileAssignment(node->lastChild, &expr);
if( r < 0 )
anyErrors = true;
// Determine the requested type
to = builder->CreateDataTypeFromNode(node->firstChild, script, outFunc->nameSpace);
// If the type support object handles, then use it
if( to.SupportHandles() )
{
to.MakeHandle(true);
if( expr.type.dataType.IsObjectConst() )
to.MakeHandleToConst(true);
}
else if( !to.IsObjectHandle() )
{
// The cast<type> operator can only be used for reference casts
Error(TXT_ILLEGAL_TARGET_TYPE_FOR_REF_CAST, node->firstChild);
anyErrors = true;
}
}
// Do not allow casting to non shared type if we're compiling a shared method
if( outFunc->IsShared() &&
to.GetTypeInfo() && !to.GetTypeInfo()->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, to.GetTypeInfo()->name.AddressOf());
Error(msg, node);
anyErrors = true;
}
if( anyErrors )
{
// Assume that the error can be fixed and allow the compilation to continue
ctx->type.SetConstantDW(to, 0);
return -1;
}
ProcessPropertyGetAccessor(&expr, node);
// Don't allow any operators on expressions that take address of class method
if( expr.IsClassMethod() )
{
Error(TXT_INVALID_OP_ON_METHOD, node);
return -1;
}
// We don't want a reference for conversion casts
if( convType == asIC_EXPLICIT_VAL_CAST && expr.type.dataType.IsReference() )
{
if( expr.type.dataType.IsObject() )
Dereference(&expr, true);
else
ConvertToVariable(&expr);
}
ImplicitConversion(&expr, to, node, convType);
IsVariableInitialized(&expr.type, node);
// If no type conversion is really tried ignore it
if( to == expr.type.dataType )
{
// This will keep information about constant type
MergeExprBytecode(ctx, &expr);
ctx->type = expr.type;
return 0;
}
if( to.IsEqualExceptRefAndConst(expr.type.dataType) && to.IsPrimitive() )
{
MergeExprBytecode(ctx, &expr);
ctx->type = expr.type;
ctx->type.dataType.MakeReadOnly(true);
return 0;
}
// The implicit conversion already does most of the conversions permitted,
// here we'll only treat those conversions that require an explicit cast.
bool conversionOK = false;
if( !expr.type.isConstant && expr.type.dataType != asCDataType::CreatePrimitive(ttVoid, false) )
{
if( !expr.type.dataType.IsObject() )
ConvertToTempVariable(&expr);
if( to.IsObjectHandle() &&
expr.type.dataType.IsObjectHandle() &&
!(!to.IsHandleToConst() && expr.type.dataType.IsHandleToConst()) )
{
conversionOK = CompileRefCast(&expr, to, true, node);
MergeExprBytecode(ctx, &expr);
ctx->type = expr.type;
}
}
if( conversionOK )
return 0;
// Conversion not available
ctx->type.SetDummy();
asCString strTo, strFrom;
strTo = to.Format(outFunc->nameSpace);
strFrom = expr.type.dataType.Format(outFunc->nameSpace);
asCString msg;
msg.Format(TXT_NO_CONVERSION_s_TO_s, strFrom.AddressOf(), strTo.AddressOf());
Error(msg, node);
return -1;
}
void asCCompiler::AfterFunctionCall(int funcID, asCArray<asCExprContext*> &args, asCExprContext *ctx, bool deferAll)
{
// deferAll is set to true if for example the function returns a reference, since in
// this case the function might be returning a reference to one of the arguments.
asCScriptFunction *descr = builder->GetFunctionDescription(funcID);
// Parameters that are sent by reference should be assigned
// to the evaluated expression if it is an lvalue
// Evaluate the arguments from last to first
int n = (int)descr->parameterTypes.GetLength() - 1;
for( ; n >= 0; n-- )
{
// All &out arguments must be deferred, except if the argument is clean, in which case the actual reference was passed in to the function
// If deferAll is set all objects passed by reference or handle must be deferred
if( (descr->parameterTypes[n].IsReference() && (descr->inOutFlags[n] & asTM_OUTREF) && !args[n]->isCleanArg) ||
(descr->parameterTypes[n].IsObject() && deferAll && (descr->parameterTypes[n].IsReference() || descr->parameterTypes[n].IsObjectHandle())) )
{
asASSERT( !(descr->parameterTypes[n].IsReference() && (descr->inOutFlags[n] == asTM_OUTREF) && !args[n]->isCleanArg) || args[n]->origExpr );
// For &inout, only store the argument if it is for a temporary variable
if( engine->ep.allowUnsafeReferences ||
descr->inOutFlags[n] != asTM_INOUTREF || args[n]->type.isTemporary )
{
// Store the argument for later processing
asSDeferredParam outParam;
outParam.argNode = args[n]->exprNode;
outParam.argType = args[n]->type;
outParam.argInOutFlags = descr->inOutFlags[n];
outParam.origExpr = args[n]->origExpr;
ctx->deferredParams.PushLast(outParam);
}
}
else
{
// Release the temporary variable now
ReleaseTemporaryVariable(args[n]->type, &ctx->bc);
}
// Move the argument's deferred expressions over to the final expression
for( asUINT m = 0; m < args[n]->deferredParams.GetLength(); m++ )
{
ctx->deferredParams.PushLast(args[n]->deferredParams[m]);
args[n]->deferredParams[m].origExpr = 0;
}
args[n]->deferredParams.SetLength(0);
}
}
void asCCompiler::ProcessDeferredParams(asCExprContext *ctx)
{
if( isProcessingDeferredParams ) return;
isProcessingDeferredParams = true;
for( asUINT n = 0; n < ctx->deferredParams.GetLength(); n++ )
{
asSDeferredParam outParam = ctx->deferredParams[n];
if( outParam.argInOutFlags < asTM_OUTREF ) // &in, or not reference
{
// Just release the variable
ReleaseTemporaryVariable(outParam.argType, &ctx->bc);
}
else if( outParam.argInOutFlags == asTM_OUTREF )
{
asCExprContext *expr = outParam.origExpr;
outParam.origExpr = 0;
if( outParam.argType.dataType.IsObjectHandle() )
{
// Implicitly convert the value to a handle
if( expr->type.dataType.IsObjectHandle() )
expr->type.isExplicitHandle = true;
}
// Verify that the expression result in a lvalue, or a property accessor
if( IsLValue(expr->type) || expr->property_get || expr->property_set )
{
asCExprContext rctx(engine);
rctx.type = outParam.argType;
if( rctx.type.dataType.IsPrimitive() )
rctx.type.dataType.MakeReference(false);
else
{
rctx.bc.InstrSHORT(asBC_PSF, outParam.argType.stackOffset);
rctx.type.dataType.MakeReference(IsVariableOnHeap(outParam.argType.stackOffset));
if( expr->type.isExplicitHandle )
rctx.type.isExplicitHandle = true;
}
asCExprContext o(engine);
DoAssignment(&o, expr, &rctx, outParam.argNode, outParam.argNode, ttAssignment, outParam.argNode);
if( !o.type.dataType.IsPrimitive() ) o.bc.Instr(asBC_PopPtr);
// The assignment may itself have resulted in a new temporary variable, e.g. if
// the opAssign returns a non-reference. We must release this temporary variable
// since it won't be used
ReleaseTemporaryVariable(o.type, &o.bc);
MergeExprBytecode(ctx, &o);
}
else
{
// We must still evaluate the expression
MergeExprBytecode(ctx, expr);
if( !expr->IsVoidExpression() && (!expr->type.isConstant || expr->type.IsNullConstant()) )
ctx->bc.Instr(asBC_PopPtr);
// Give an error, except if the argument is void, null or 0 which indicate the argument is explicitly to be ignored
if( !expr->IsVoidExpression() && !expr->type.IsNullConstant() && !(expr->type.isConstant && expr->type.qwordValue == 0) )
Error(TXT_ARG_NOT_LVALUE, outParam.argNode);
ReleaseTemporaryVariable(outParam.argType, &ctx->bc);
}
ReleaseTemporaryVariable(expr->type, &ctx->bc);
// Delete the original expression context
asDELETE(expr, asCExprContext);
}
else // &inout
{
if( outParam.argType.isTemporary )
ReleaseTemporaryVariable(outParam.argType, &ctx->bc);
else if( !outParam.argType.isVariable )
{
if( outParam.argType.dataType.IsObject() &&
((outParam.argType.dataType.GetBehaviour()->addref &&
outParam.argType.dataType.GetBehaviour()->release) ||
(outParam.argType.dataType.GetTypeInfo()->flags & asOBJ_NOCOUNT)) )
{
// Release the object handle that was taken to guarantee the reference
ReleaseTemporaryVariable(outParam.argType, &ctx->bc);
}
}
}
}
ctx->deferredParams.SetLength(0);
isProcessingDeferredParams = false;
}
int asCCompiler::CompileConstructCall(asCScriptNode *node, asCExprContext *ctx)
{
// The first node is a datatype node
asCString name;
asCExprValue tempObj;
bool onHeap = true;
asCArray<int> funcs;
bool error = false;
// It is possible that the name is really a constructor
asCDataType dt;
dt = builder->CreateDataTypeFromNode(node->firstChild, script, outFunc->nameSpace);
if( dt.IsPrimitive() )
{
// This is a cast to a primitive type
return CompileConversion(node, ctx);
}
if( dt.GetTypeInfo() && (dt.GetTypeInfo()->flags & asOBJ_IMPLICIT_HANDLE) )
{
// Types declared as implicit handle must not attempt to construct a handle
dt.MakeHandle(false);
}
// Don't accept syntax like object@(expr)
if( dt.IsObjectHandle() )
{
asCString str;
str.Format(TXT_CANT_CONSTRUCT_s_USE_REF_CAST, dt.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.SetDummy();
return -1;
}
// Make sure the desired type can actually be instantiated
// Delegates are allowed to be created through construct calls,
// even though they cannot be instantiated as variables
if( !dt.CanBeInstantiated() && !dt.IsFuncdef() )
{
asCString str;
if( dt.IsAbstractClass() )
str.Format(TXT_ABSTRACT_CLASS_s_CANNOT_BE_INSTANTIATED, dt.Format(outFunc->nameSpace).AddressOf());
else if( dt.IsInterface() )
str.Format(TXT_INTERFACE_s_CANNOT_BE_INSTANTIATED, dt.Format(outFunc->nameSpace).AddressOf());
else
// TODO: Improve error message to explain why
str.Format(TXT_DATA_TYPE_CANT_BE_s, dt.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.SetDummy();
return -1;
}
// Do not allow constructing non-shared types in shared functions
if( outFunc->IsShared() &&
dt.GetTypeInfo() && !dt.GetTypeInfo()->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, dt.GetTypeInfo()->name.AddressOf());
Error(msg, node);
return -1;
}
// Compile the arguments
asCArray<asCExprContext *> args;
asCArray<asSNamedArgument> namedArgs;
asCArray<asCExprValue> temporaryVariables;
if( CompileArgumentList(node->lastChild, args, namedArgs) >= 0 )
{
// Check for a value cast behaviour
if( args.GetLength() == 1 && args[0]->type.dataType.GetTypeInfo() )
{
asCExprContext conv(engine);
conv.type = args[0]->type;
asUINT cost = ImplicitConversion(&conv, dt, node->lastChild, asIC_EXPLICIT_VAL_CAST, false);
// Don't use this if the cost is 0 because it would mean that nothing
// is done and the scipt wants a new value to be constructed
if( conv.type.dataType.IsEqualExceptRef(dt) && cost > 0 )
{
ImplicitConversion(args[0], dt, node->lastChild, asIC_EXPLICIT_VAL_CAST);
ctx->bc.AddCode(&args[0]->bc);
ctx->type = args[0]->type;
asDELETE(args[0], asCExprContext);
return 0;
}
}
// Check for possible constructor/factory
name = dt.Format(outFunc->nameSpace);
asSTypeBehaviour *beh = dt.GetBehaviour();
if( !(dt.GetTypeInfo()->flags & asOBJ_REF) && !dt.IsFuncdef() )
{
funcs = beh->constructors;
// Value types and script types are allocated through the constructor
tempObj.dataType = dt;
tempObj.stackOffset = (short)AllocateVariable(dt, true);
tempObj.dataType.MakeReference(true);
tempObj.isTemporary = true;
tempObj.isVariable = true;
onHeap = IsVariableOnHeap(tempObj.stackOffset);
// Push the address of the object on the stack
if( onHeap )
ctx->bc.InstrSHORT(asBC_VAR, tempObj.stackOffset);
}
else if( beh )
funcs = beh->factories;
// Special case: Allow calling func(void) with a void expression.
if( args.GetLength() == 1 && args[0]->type.dataType == asCDataType::CreatePrimitive(ttVoid, false) )
{
// Evaluate the expression before the function call
MergeExprBytecode(ctx, args[0]);
asDELETE(args[0], asCExprContext);
args.SetLength(0);
}
// Special case: If this is an object constructor and there are no arguments use the default constructor.
// If none has been registered, just allocate the variable and push it on the stack.
if( args.GetLength() == 0 )
{
beh = tempObj.dataType.GetBehaviour();
if( beh && beh->construct == 0 && !(dt.GetTypeInfo()->flags & asOBJ_REF) )
{
// Call the default constructor
ctx->type = tempObj;
if( onHeap )
{
asASSERT(ctx->bc.GetLastInstr() == asBC_VAR);
ctx->bc.RemoveLastInstr();
}
CallDefaultConstructor(tempObj.dataType, tempObj.stackOffset, IsVariableOnHeap(tempObj.stackOffset), &ctx->bc, node);
// Push the reference on the stack
ctx->bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
return 0;
}
}
// Special case: If this is a construction of a delegate and the expression names an object method
if( dt.IsFuncdef() && args.GetLength() == 1 && args[0]->methodName != "" )
{
// TODO: delegate: It is possible that the argument returns a function pointer already, in which
// case no object delegate will be created, but instead a delegate for a function pointer
// In theory a simple cast would be good in this case, but this is a construct call so it
// is expected that a new object is created.
dt.MakeHandle(true);
ctx->type.Set(dt);
// The delegate must be able to hold on to a reference to the object
if( !args[0]->type.dataType.SupportHandles() )
{
Error(TXT_CANNOT_CREATE_DELEGATE_FOR_NOREF_TYPES, node);
error = true;
}
else
{
// Filter the available object methods to find the one that matches the func def
asCObjectType *type = args[0]->type.dataType.GetTypeInfo()->CastToObjectType();
asCScriptFunction *bestMethod = 0;
for( asUINT n = 0; n < type->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[type->methods[n]];
if( func->name != args[0]->methodName )
continue;
// If the expression is for a const object, then only const methods should be accepted
if( args[0]->type.dataType.IsReadOnly() && !func->IsReadOnly() )
continue;
if( func->IsSignatureExceptNameAndObjectTypeEqual(dt.GetTypeInfo()->CastToFuncdefType()->funcdef) )
{
bestMethod = func;
// If the expression is non-const the non-const overloaded method has priority
if( args[0]->type.dataType.IsReadOnly() == func->IsReadOnly() )
break;
}
}
if( bestMethod )
{
// The object pointer is already on the stack
MergeExprBytecode(ctx, args[0]);
// Push the function pointer as an additional argument
ctx->bc.InstrPTR(asBC_FuncPtr, bestMethod);
// Call the factory function for the delegate
asCArray<int> delegateFuncs;
builder->GetFunctionDescriptions(DELEGATE_FACTORY, delegateFuncs, engine->nameSpaces[0]);
asASSERT(delegateFuncs.GetLength() == 1 );
ctx->bc.Call(asBC_CALLSYS , delegateFuncs[0], 2*AS_PTR_SIZE);
// Store the returned delegate in a temporary variable
int returnOffset = AllocateVariable(dt, true, false);
dt.MakeReference(true);
ctx->type.SetVariable(dt, returnOffset, true);
ctx->bc.InstrSHORT(asBC_STOREOBJ, (short)returnOffset);
// Push a reference to the temporary variable on the stack
ctx->bc.InstrSHORT(asBC_PSF, (short)returnOffset);
// Clean up arguments
ReleaseTemporaryVariable(args[0]->type, &ctx->bc);
}
else
{
asCString msg;
msg.Format(TXT_NO_MATCHING_SIGNATURES_TO_s, dt.GetTypeInfo()->CastToFuncdefType()->funcdef->GetDeclaration());
Error(msg.AddressOf(), node);
error = true;
}
}
// Clean-up arg
asDELETE(args[0], asCExprContext);
return error ? -1 : 0;
}
MatchFunctions(funcs, args, node, name.AddressOf(), &namedArgs, 0, false);
if( funcs.GetLength() != 1 )
{
// The error was reported by MatchFunctions()
error = true;
// Dummy value
ctx->type.SetDummy();
}
else
{
// TODO: Clean up: Merge this with MakeFunctionCall
// Add the default values for arguments not explicitly supplied
int r = CompileDefaultAndNamedArgs(node, args, funcs[0], dt.GetTypeInfo()->CastToObjectType(), &namedArgs);
if( r == asSUCCESS )
{
asCByteCode objBC(engine);
PrepareFunctionCall(funcs[0], &ctx->bc, args);
MoveArgsToStack(funcs[0], &ctx->bc, args, false);
if( !(dt.GetTypeInfo()->flags & asOBJ_REF) )
{
// If the object is allocated on the stack, then call the constructor as a normal function
if( onHeap )
{
int offset = 0;
asCScriptFunction *descr = builder->GetFunctionDescription(funcs[0]);
for( asUINT n = 0; n < args.GetLength(); n++ )
offset += descr->parameterTypes[n].GetSizeOnStackDWords();
ctx->bc.InstrWORD(asBC_GETREF, (asWORD)offset);
}
else
ctx->bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
PerformFunctionCall(funcs[0], ctx, onHeap, &args, tempObj.dataType.GetTypeInfo()->CastToObjectType());
// Add tag that the object has been initialized
ctx->bc.ObjInfo(tempObj.stackOffset, asOBJ_INIT);
// The constructor doesn't return anything,
// so we have to manually inform the type of
// the return value
ctx->type = tempObj;
if( !onHeap )
ctx->type.dataType.MakeReference(false);
// Push the address of the object on the stack again
ctx->bc.InstrSHORT(asBC_PSF, tempObj.stackOffset);
}
else
{
// Call the factory to create the reference type
PerformFunctionCall(funcs[0], ctx, false, &args);
}
}
else
error = true;
}
}
else
{
// Failed to compile the argument list, set the result to the dummy type
ctx->type.SetDummy();
error = true;
}
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
for( asUINT n = 0; n < namedArgs.GetLength(); n++ )
if( namedArgs[n].ctx )
{
asDELETE(namedArgs[n].ctx, asCExprContext);
}
return error ? -1 : 0;
}
int asCCompiler::CompileFunctionCall(asCScriptNode *node, asCExprContext *ctx, asCObjectType *objectType, bool objIsConst, const asCString &scope)
{
asCExprValue tempObj;
asCArray<int> funcs;
int localVar = -1;
bool initializeMembers = false;
asCExprContext funcExpr(engine);
asCScriptNode *nm = node->lastChild->prev;
asCString name(&script->code[nm->tokenPos], nm->tokenLength);
// First check for a local variable 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
if( objectType == 0 )
{
localVar = CompileVariableAccess(name, scope, &funcExpr, node, true, true, true);
if( localVar >= 0 &&
!(funcExpr.type.dataType.IsFuncdef() || funcExpr.type.dataType.IsObject()) &&
funcExpr.methodName == "" )
{
// The variable is not a function or object with opCall
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( funcExpr.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 can be used to specify the base class
builder->GetObjectMethodDescriptions(name.AddressOf(), objectType ? objectType : outFunc->objectType, funcs, objIsConst, scope, node, script);
}
// It is still possible that there is a class member of a function type or a type with opCall methods
if( funcs.GetLength() == 0 )
{
int r = CompileVariableAccess(name, scope, &funcExpr, node, true, true, true, objectType);
if( r >= 0 &&
!(funcExpr.type.dataType.IsFuncdef() || funcExpr.type.dataType.IsObject()) &&
funcExpr.methodName == "" )
{
// 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 is an access property, make sure the original value isn't
// dereferenced when calling the access property as part a dot post operator
if( objectType && (funcExpr.property_get || funcExpr.property_set) && !ctx->type.dataType.IsReference() )
funcExpr.property_ref = false;
}
// 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::CreateType(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 && (!funcExpr.type.dataType.IsFuncdef() || funcExpr.type.dataType.IsObject()) )
{
// The scope is used to define the namespace
asSNameSpace *ns = DetermineNameSpace(scope);
if( ns )
{
// Search recursively in parent namespaces
while( ns && funcs.GetLength() == 0 && !funcExpr.type.dataType.IsFuncdef() )
{
builder->GetFunctionDescriptions(name.AddressOf(), funcs, ns);
if( funcs.GetLength() == 0 )
{
int r = CompileVariableAccess(name, scope, &funcExpr, node, true, true);
if( r >= 0 &&
!(funcExpr.type.dataType.IsFuncdef() || funcExpr.type.dataType.IsObject()) &&
funcExpr.methodName == "" )
{
// 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 = engine->GetParentNameSpace(ns);
}
}
else
{
asCString msg;
msg.Format(TXT_NAMESPACE_s_DOESNT_EXIST, scope.AddressOf());
Error(msg, node);
return -1;
}
}
}
if( funcs.GetLength() == 0 )
{
if( funcExpr.type.dataType.IsFuncdef() )
{
funcs.PushLast(funcExpr.type.dataType.GetTypeInfo()->CastToFuncdefType()->funcdef->id);
}
else if( funcExpr.type.dataType.IsObject() )
{
// Keep information about temporary variables as deferred expression so it can be properly cleaned up after the call
if( ctx->type.isTemporary )
{
asASSERT( objectType );
asSDeferredParam deferred;
deferred.origExpr = 0;
deferred.argInOutFlags = asTM_INREF;
deferred.argNode = 0;
deferred.argType.SetVariable(ctx->type.dataType, ctx->type.stackOffset, true);
ctx->deferredParams.PushLast(deferred);
}
if( funcExpr.property_get == 0 )
Dereference(ctx, true);
// Add the bytecode for accessing the object on which opCall will be called
MergeExprBytecodeAndType(ctx, &funcExpr);
ProcessPropertyGetAccessor(ctx, node);
Dereference(ctx, true);
objectType = funcExpr.type.dataType.GetTypeInfo()->CastToObjectType();
// Get the opCall methods from the object type
if( funcExpr.type.dataType.IsObjectHandle() )
objIsConst = funcExpr.type.dataType.IsHandleToConst();
else
objIsConst = funcExpr.type.dataType.IsReadOnly();
builder->GetObjectMethodDescriptions("opCall", funcExpr.type.dataType.GetTypeInfo()->CastToObjectType(), funcs, objIsConst);
}
}
// Compile the arguments
asCArray<asCExprContext *> args;
asCArray<asSNamedArgument> namedArgs;
bool isOK = true;
if( CompileArgumentList(node->lastChild, args, namedArgs) >= 0 )
{
// Special case: Allow calling func(void) with an expression that evaluates to no datatype, but isn't exactly 'void'
if( args.GetLength() == 1 && args[0]->type.IsVoid() && !args[0]->IsVoidExpression() )
{
// Evaluate the expression before the function call
MergeExprBytecode(ctx, args[0]);
asDELETE(args[0], asCExprContext);
args.SetLength(0);
}
MatchFunctions(funcs, args, node, name.AddressOf(), &namedArgs, objectType, objIsConst, false, true, scope);
if( funcs.GetLength() != 1 )
{
// The error was reported by MatchFunctions()
// Dummy value
ctx->type.SetDummy();
isOK = false;
}
else
{
// Add the default values for arguments not explicitly supplied
int r = CompileDefaultAndNamedArgs(node, args, funcs[0], objectType, &namedArgs);
// TODO: funcdef: Do we have to make sure the handle is stored in a temporary variable, or
// is it enough to make sure it is in a local variable?
// For function pointer we must guarantee that the function is safe, i.e.
// by first storing the function pointer in a local variable (if it isn't already in one)
if( r == asSUCCESS )
{
asCScriptFunction *func = builder->GetFunctionDescription(funcs[0]);
if( func->funcType == asFUNC_FUNCDEF )
{
if( objectType && funcExpr.property_get <= 0 )
{
// Dereference the object pointer to access the member
Dereference(ctx, true);
}
if( funcExpr.property_get > 0 )
{
ProcessPropertyGetAccessor(&funcExpr, node);
Dereference(&funcExpr, true);
}
else
{
Dereference(&funcExpr, true);
ConvertToVariable(&funcExpr);
}
// The actual function should be called as if a global function
objectType = 0;
// The function call will be made directly from the local variable so the function pointer shouldn't be on the stack
funcExpr.bc.Instr(asBC_PopPtr);
asCExprValue tmp = ctx->type;
MergeExprBytecodeAndType(ctx, &funcExpr);
ReleaseTemporaryVariable(tmp, &ctx->bc);
}
MakeFunctionCall(ctx, funcs[0], objectType, args, node, false, 0, funcExpr.type.stackOffset);
}
else
isOK = false;
}
}
else
{
// Failed to compile the argument list, set the dummy type and continue compilation
ctx->type.SetDummy();
isOK = false;
}
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
for( asUINT n = 0; n < namedArgs.GetLength(); n++ )
if( namedArgs[n].ctx )
{
asDELETE(namedArgs[n].ctx, asCExprContext);
}
if( initializeMembers )
{
asASSERT( m_isConstructor );
// Need to initialize members here, as they may use the properties of the base class
// If there are multiple paths that call super(), then there will also be multiple
// locations with initializations of the members. It is not possible to consolidate
// these in one place, as the expressions for the initialization are evaluated where
// they are compiled, which means that they may access different variables depending
// on the scope where super() is called.
// Members that don't have an explicit initialization expression will be initialized
// beginning of the constructor as they are guaranteed not to use at the any
// members of the base class.
CompileMemberInitialization(&ctx->bc, false);
}
return isOK ? 0 : -1;
}
asSNameSpace *asCCompiler::DetermineNameSpace(const asCString &scope)
{
asSNameSpace *ns;
if( scope == "" )
{
// When compiling default argument expression the correct namespace is stored in the outFunc even for objects
if( outFunc->nameSpace->name != "" || isCompilingDefaultArg )
ns = outFunc->nameSpace;
else if( outFunc->objectType && outFunc->objectType->nameSpace->name != "" )
ns = outFunc->objectType->nameSpace;
else
ns = engine->nameSpaces[0];
}
else if( scope == "::" )
ns = engine->nameSpaces[0];
else
ns = engine->FindNameSpace(scope.AddressOf());
return ns;
}
int asCCompiler::CompileExpressionPreOp(asCScriptNode *node, asCExprContext *ctx)
{
int op = node->tokenType;
// Don't allow any prefix operators except handle on expressions that take address of class method
if( ctx->IsClassMethod() && op != ttHandle )
{
Error(TXT_INVALID_OP_ON_METHOD, node);
return -1;
}
// Don't allow any operators on void expressions
if( ctx->IsVoidExpression() )
{
Error(TXT_VOID_CANT_BE_OPERAND, node);
return -1;
}
IsVariableInitialized(&ctx->type, node);
if( op == ttHandle )
{
if( ctx->methodName != "" )
{
// Don't allow taking the handle of a handle
if( ctx->type.isExplicitHandle )
{
Error(TXT_OBJECT_HANDLE_NOT_SUPPORTED, node);
return -1;
}
}
else
{
// Don't allow taking handle of a handle, i.e. @@
if( ctx->type.isExplicitHandle )
{
Error(TXT_OBJECT_HANDLE_NOT_SUPPORTED, node);
return -1;
}
// @null is allowed even though it is implicit
if( !ctx->type.IsNullConstant() )
{
// Verify that the type allow its handle to be taken
if( !ctx->type.dataType.SupportHandles() && !ctx->type.dataType.IsObjectHandle() )
{
Error(TXT_OBJECT_HANDLE_NOT_SUPPORTED, node);
return -1;
}
// Objects that are not local variables are not references
// Objects allocated on the stack are also not marked as references
if( !ctx->type.dataType.IsReference() &&
!((ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) && !ctx->type.isVariable) &&
!(ctx->type.isVariable && !IsVariableOnHeap(ctx->type.stackOffset)) )
{
Error(TXT_NOT_VALID_REFERENCE, node);
return -1;
}
// Convert the expression to a handle
if( !ctx->type.dataType.IsObjectHandle() && !(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE) )
{
asCDataType to = ctx->type.dataType;
to.MakeHandle(true);
to.MakeReference(true);
to.MakeHandleToConst(ctx->type.dataType.IsReadOnly());
ImplicitConversion(ctx, to, node, asIC_IMPLICIT_CONV, true, false);
asASSERT( ctx->type.dataType.IsObjectHandle() );
}
else if( ctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE )
{
// For the ASHANDLE type we'll simply set the expression as a handle
ctx->type.dataType.MakeHandle(true);
}
}
}
// Mark the expression as an explicit handle to avoid implicit conversions to non-handle expressions
ctx->type.isExplicitHandle = true;
}
else if( (op == ttMinus || op == ttPlus || op == ttBitNot || op == ttInc || op == ttDec) && ctx->type.dataType.IsObject() )
{
// Look for the appropriate method
// There is no overloadable operator for unary plus
const char *opName = 0;
switch( op )
{
case ttMinus: opName = "opNeg"; break;
case ttBitNot: opName = "opCom"; break;
case ttInc: opName = "opPreInc"; break;
case ttDec: opName = "opPreDec"; break;
}
if( opName )
{
// TODO: Should convert this to something similar to CompileOverloadedDualOperator2
ProcessPropertyGetAccessor(ctx, node);
// TODO: If the value isn't const, then first try to find the non const method, and if not found try to find the const method
// Find the correct method
bool isConst = ctx->type.dataType.IsObjectConst();
asCArray<int> funcs;
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( func->name == opName &&
func->parameterTypes.GetLength() == 0 &&
(!isConst || func->isReadOnly) )
{
funcs.PushLast(func->id);
}
}
// Did we find the method?
if( funcs.GetLength() == 1 )
{
asCArray<asCExprContext *> args;
MakeFunctionCall(ctx, funcs[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
return 0;
}
else if( funcs.GetLength() == 0 )
{
asCString str;
str = asCString(opName) + "()";
if( isConst )
str += " const";
str.Format(TXT_FUNCTION_s_NOT_FOUND, str.AddressOf());
Error(str, node);
ctx->type.SetDummy();
return -1;
}
else if( funcs.GetLength() > 1 )
{
Error(TXT_MORE_THAN_ONE_MATCHING_OP, node);
PrintMatchingFuncs(funcs, node);
ctx->type.SetDummy();
return -1;
}
}
else if( op == ttPlus )
{
Error(TXT_ILLEGAL_OPERATION, node);
ctx->type.SetDummy();
return -1;
}
}
else if( op == ttPlus || op == ttMinus )
{
// This is only for primitives. Objects are treated in the above block
// Make sure the type is a math type
if( !(ctx->type.dataType.IsIntegerType() ||
ctx->type.dataType.IsUnsignedType() ||
ctx->type.dataType.IsFloatType() ||
ctx->type.dataType.IsDoubleType() ) )
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
ProcessPropertyGetAccessor(ctx, node);
asCDataType to = ctx->type.dataType;
if( ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.GetSizeInMemoryBytes() == 1 )
to = asCDataType::CreatePrimitive(ttInt8, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 2 )
to = asCDataType::CreatePrimitive(ttInt16, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 4 )
to = asCDataType::CreatePrimitive(ttInt, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 8 )
to = asCDataType::CreatePrimitive(ttInt64, false);
else
{
Error(TXT_INVALID_TYPE, node);
return -1;
}
}
if( ctx->type.dataType.IsReference() ) ConvertToVariable(ctx);
// Use an explicit conversion in case of constants to avoid unnecessary warning about change of sign
ImplicitConversion(ctx, to, node, ctx->type.isConstant ? asIC_EXPLICIT_VAL_CAST : asIC_IMPLICIT_CONV);
if( !ctx->type.isConstant )
{
ConvertToTempVariable(ctx);
asASSERT(!ctx->type.isLValue);
if( op == ttMinus )
{
if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->bc.InstrSHORT(asBC_NEGi, ctx->type.stackOffset);
else if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
ctx->bc.InstrSHORT(asBC_NEGi64, ctx->type.stackOffset);
else if( ctx->type.dataType.IsFloatType() )
ctx->bc.InstrSHORT(asBC_NEGf, ctx->type.stackOffset);
else if( ctx->type.dataType.IsDoubleType() )
ctx->bc.InstrSHORT(asBC_NEGd, ctx->type.stackOffset);
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
return 0;
}
}
else
{
if( op == ttMinus )
{
if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->type.intValue = -ctx->type.intValue;
else if( ctx->type.dataType.IsIntegerType() && ctx->type.dataType.GetSizeInMemoryDWords() == 2 )
ctx->type.qwordValue = -(asINT64)ctx->type.qwordValue;
else if( ctx->type.dataType.IsFloatType() )
ctx->type.floatValue = -ctx->type.floatValue;
else if( ctx->type.dataType.IsDoubleType() )
ctx->type.doubleValue = -ctx->type.doubleValue;
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
return 0;
}
}
}
else if( op == ttNot )
{
// Allow value types to be converted to bool using 'bool opImplConv()'
if( ctx->type.dataType.GetTypeInfo() && (ctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(ctx, asCDataType::CreatePrimitive(ttBool, false), node, asIC_IMPLICIT_CONV);
if( ctx->type.dataType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, true)) )
{
if( ctx->type.isConstant )
{
ctx->type.dwordValue = (ctx->type.dwordValue == 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
return 0;
}
ProcessPropertyGetAccessor(ctx, node);
ConvertToTempVariable(ctx);
asASSERT(!ctx->type.isLValue);
ctx->bc.InstrSHORT(asBC_NOT, ctx->type.stackOffset);
}
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
}
else if( op == ttBitNot )
{
ProcessPropertyGetAccessor(ctx, node);
asCDataType to = ctx->type.dataType;
if( ctx->type.dataType.IsIntegerType() )
{
if( ctx->type.dataType.GetSizeInMemoryBytes() == 1 )
to = asCDataType::CreatePrimitive(ttUInt8, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 2 )
to = asCDataType::CreatePrimitive(ttUInt16, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 4 )
to = asCDataType::CreatePrimitive(ttUInt, false);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 8 )
to = asCDataType::CreatePrimitive(ttUInt64, false);
else
{
Error(TXT_INVALID_TYPE, node);
return -1;
}
}
if( ctx->type.dataType.IsReference() ) ConvertToVariable(ctx);
ImplicitConversion(ctx, to, node, asIC_IMPLICIT_CONV);
if( ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.isConstant )
{
ctx->type.qwordValue = ~ctx->type.qwordValue;
return 0;
}
ConvertToTempVariable(ctx);
asASSERT(!ctx->type.isLValue);
if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->bc.InstrSHORT(asBC_BNOT, ctx->type.stackOffset);
else
ctx->bc.InstrSHORT(asBC_BNOT64, ctx->type.stackOffset);
}
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
}
else if( op == ttInc || op == ttDec )
{
// Need a reference to the primitive that will be updated
// The result of this expression is the same reference as before
// Make sure the reference isn't a temporary variable
if( ctx->type.isTemporary )
{
Error(TXT_REF_IS_TEMP, node);
return -1;
}
if( ctx->type.dataType.IsReadOnly() )
{
Error(TXT_REF_IS_READ_ONLY, node);
return -1;
}
if( ctx->property_get || ctx->property_set )
{
Error(TXT_INVALID_REF_PROP_ACCESS, node);
return -1;
}
if( !ctx->type.isLValue )
{
Error(TXT_NOT_LVALUE, node);
return -1;
}
if( ctx->type.isVariable && !ctx->type.dataType.IsReference() )
ConvertToReference(ctx);
else if( !ctx->type.dataType.IsReference() )
{
Error(TXT_NOT_VALID_REFERENCE, node);
return -1;
}
if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt64, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt64, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCi64);
else
ctx->bc.Instr(asBC_DECi64);
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCi);
else
ctx->bc.Instr(asBC_DECi);
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt16, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt16, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCi16);
else
ctx->bc.Instr(asBC_DECi16);
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt8, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt8, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCi8);
else
ctx->bc.Instr(asBC_DECi8);
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttFloat, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCf);
else
ctx->bc.Instr(asBC_DECf);
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttDouble, false)) )
{
if( op == ttInc )
ctx->bc.Instr(asBC_INCd);
else
ctx->bc.Instr(asBC_DECd);
}
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
}
else
{
// Unknown operator
asASSERT(false);
return -1;
}
return 0;
}
void asCCompiler::ConvertToReference(asCExprContext *ctx)
{
if( ctx->type.isVariable && !ctx->type.dataType.IsReference() )
{
ctx->bc.InstrSHORT(asBC_LDV, ctx->type.stackOffset);
ctx->type.dataType.MakeReference(true);
ctx->type.SetVariable(ctx->type.dataType, ctx->type.stackOffset, ctx->type.isTemporary);
}
}
int asCCompiler::FindPropertyAccessor(const asCString &name, asCExprContext *ctx, asCScriptNode *node, asSNameSpace *ns, bool isThisAccess)
{
return FindPropertyAccessor(name, ctx, 0, node, ns, isThisAccess);
}
int asCCompiler::FindPropertyAccessor(const asCString &name, asCExprContext *ctx, asCExprContext *arg, asCScriptNode *node, asSNameSpace *ns, bool isThisAccess)
{
if( engine->ep.propertyAccessorMode == 0 )
{
// Property accessors have been disabled by the application
return 0;
}
int getId = 0, setId = 0;
asCString getName = "get_" + name;
asCString setName = "set_" + name;
asCArray<int> multipleGetFuncs, multipleSetFuncs;
if( ctx->type.dataType.IsObject() )
{
asASSERT( ns == 0 );
// Don't look for property accessors in script classes if the script
// property accessors have been disabled by the application
if( !(ctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCRIPT_OBJECT) ||
engine->ep.propertyAccessorMode == 2 )
{
// Check if the object has any methods with the corresponding accessor name(s)
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *f = engine->scriptFunctions[ot->methods[n]];
// TODO: The type of the parameter should match the argument (unless the arg is a dummy)
if( f->name == getName && (int)f->parameterTypes.GetLength() == (arg?1:0) )
{
if( getId == 0 )
getId = ot->methods[n];
else
{
if( multipleGetFuncs.GetLength() == 0 )
multipleGetFuncs.PushLast(getId);
multipleGetFuncs.PushLast(ot->methods[n]);
}
}
// TODO: getset: If the parameter is a reference, it must not be an out reference. Should we allow inout ref?
if( f->name == setName && (int)f->parameterTypes.GetLength() == (arg?2:1) )
{
if( setId == 0 )
setId = ot->methods[n];
else
{
if( multipleSetFuncs.GetLength() == 0 )
multipleSetFuncs.PushLast(setId);
multipleSetFuncs.PushLast(ot->methods[n]);
}
}
}
}
}
else
{
asASSERT( ns != 0 );
// Look for appropriate global functions.
asCArray<int> funcs;
asUINT n;
builder->GetFunctionDescriptions(getName.AddressOf(), funcs, ns);
for( n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *f = builder->GetFunctionDescription(funcs[n]);
// TODO: The type of the parameter should match the argument (unless the arg is a dummy)
if( (int)f->parameterTypes.GetLength() == (arg?1:0) )
{
if( getId == 0 )
getId = funcs[n];
else
{
if( multipleGetFuncs.GetLength() == 0 )
multipleGetFuncs.PushLast(getId);
multipleGetFuncs.PushLast(funcs[n]);
}
}
}
funcs.SetLength(0);
builder->GetFunctionDescriptions(setName.AddressOf(), funcs, ns);
for( n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *f = builder->GetFunctionDescription(funcs[n]);
// TODO: getset: If the parameter is a reference, it must not be an out reference. Should we allow inout ref?
if( (int)f->parameterTypes.GetLength() == (arg?2:1) )
{
if( setId == 0 )
setId = funcs[n];
else
{
if( multipleSetFuncs.GetLength() == 0 )
multipleSetFuncs.PushLast(getId);
multipleSetFuncs.PushLast(funcs[n]);
}
}
}
}
bool isConst = ctx->type.dataType.IsObjectConst();
// Check for multiple matches
if( multipleGetFuncs.GetLength() > 0 )
{
// Filter the list by constness
FilterConst(multipleGetFuncs, !isConst);
if( multipleGetFuncs.GetLength() > 1 )
{
asCString str;
str.Format(TXT_MULTIPLE_PROP_GET_ACCESSOR_FOR_s, name.AddressOf());
Error(str, node);
PrintMatchingFuncs(multipleGetFuncs, node);
return -1;
}
else
{
// The id may have changed
getId = multipleGetFuncs[0];
}
}
if( multipleSetFuncs.GetLength() > 0 )
{
// Filter the list by constness
FilterConst(multipleSetFuncs, !isConst);
if( multipleSetFuncs.GetLength() > 1 )
{
asCString str;
str.Format(TXT_MULTIPLE_PROP_SET_ACCESSOR_FOR_s, name.AddressOf());
Error(str, node);
PrintMatchingFuncs(multipleSetFuncs, node);
return -1;
}
else
{
// The id may have changed
setId = multipleSetFuncs[0];
}
}
// Check for type compatibility between get and set accessor
if( getId && setId )
{
asCScriptFunction *getFunc = builder->GetFunctionDescription(getId);
asCScriptFunction *setFunc = builder->GetFunctionDescription(setId);
// It is permitted for a getter to return a handle and the setter to take a reference
int idx = (arg?1:0);
if( !getFunc->returnType.IsEqualExceptRefAndConst(setFunc->parameterTypes[idx]) &&
!((getFunc->returnType.IsObjectHandle() && !setFunc->parameterTypes[idx].IsObjectHandle()) &&
(getFunc->returnType.GetTypeInfo() == setFunc->parameterTypes[idx].GetTypeInfo())) )
{
asCString str;
str.Format(TXT_GET_SET_ACCESSOR_TYPE_MISMATCH_FOR_s, name.AddressOf());
Error(str, node);
asCArray<int> funcs;
funcs.PushLast(getId);
funcs.PushLast(setId);
PrintMatchingFuncs(funcs, node);
return -1;
}
}
// Check if we are within one of the accessors
int realGetId = getId;
int realSetId = setId;
if( outFunc->objectType && isThisAccess )
{
// The property accessors would be virtual functions, so we need to find the real implementation
asCScriptFunction *getFunc = getId ? builder->GetFunctionDescription(getId) : 0;
if( getFunc &&
getFunc->funcType == asFUNC_VIRTUAL &&
outFunc->objectType->DerivesFrom(getFunc->objectType) )
realGetId = outFunc->objectType->virtualFunctionTable[getFunc->vfTableIdx]->id;
asCScriptFunction *setFunc = setId ? builder->GetFunctionDescription(setId) : 0;
if( setFunc &&
setFunc->funcType == asFUNC_VIRTUAL &&
outFunc->objectType->DerivesFrom(setFunc->objectType) )
realSetId = outFunc->objectType->virtualFunctionTable[setFunc->vfTableIdx]->id;
}
// Avoid recursive call, by not treating this as a property accessor call.
// This will also allow having the real property with the same name as the accessors.
if( (isThisAccess || outFunc->objectType == 0) &&
((realGetId && realGetId == outFunc->id) ||
(realSetId && realSetId == outFunc->id)) )
{
getId = 0;
setId = 0;
}
// Check if the application has disabled script written property accessors
if( engine->ep.propertyAccessorMode == 1 )
{
if( getId && builder->GetFunctionDescription(getId)->funcType != asFUNC_SYSTEM )
getId = 0;
if( setId && builder->GetFunctionDescription(setId)->funcType != asFUNC_SYSTEM )
setId = 0;
}
if( getId || setId )
{
// Property accessors were found, but we don't know which is to be used yet, so
// we just prepare the bytecode for the method call, and then store the function ids
// so that the right one can be used when we get there.
ctx->property_get = getId;
ctx->property_set = setId;
if( ctx->type.dataType.IsObject() )
{
// If the object is read-only then we need to remember that
if( (!ctx->type.dataType.IsObjectHandle() && ctx->type.dataType.IsReadOnly()) ||
(ctx->type.dataType.IsObjectHandle() && ctx->type.dataType.IsHandleToConst()) )
ctx->property_const = true;
else
ctx->property_const = false;
// If the object is a handle then we need to remember that
ctx->property_handle = ctx->type.dataType.IsObjectHandle();
ctx->property_ref = ctx->type.dataType.IsReference();
}
// The setter's parameter type is used as the property type,
// unless only the getter is available
asCDataType dt;
if( setId )
dt = builder->GetFunctionDescription(setId)->parameterTypes[(arg?1:0)];
else
dt = builder->GetFunctionDescription(getId)->returnType;
// Just change the type, the context must still maintain information
// about previous variable offset and the indicator of temporary variable.
int offset = ctx->type.stackOffset;
bool isTemp = ctx->type.isTemporary;
ctx->type.Set(dt);
ctx->type.stackOffset = (short)offset;
ctx->type.isTemporary = isTemp;
ctx->exprNode = node;
// Store the argument for later use
if( arg )
{
ctx->property_arg = asNEW(asCExprContext)(engine);
if( ctx->property_arg == 0 )
{
// Out of memory
return -1;
}
MergeExprBytecodeAndType(ctx->property_arg, arg);
}
return 1;
}
// No accessor was found
return 0;
}
int asCCompiler::ProcessPropertySetAccessor(asCExprContext *ctx, asCExprContext *arg, asCScriptNode *node)
{
// TODO: A lot of this code is similar to ProcessPropertyGetAccessor. Can we unify them?
if( !ctx->property_set )
{
Error(TXT_PROPERTY_HAS_NO_SET_ACCESSOR, node);
return -1;
}
asCScriptFunction *func = builder->GetFunctionDescription(ctx->property_set);
// Make sure the arg match the property
asCArray<int> funcs;
funcs.PushLast(ctx->property_set);
asCArray<asCExprContext *> args;
if( ctx->property_arg )
args.PushLast(ctx->property_arg);
args.PushLast(arg);
MatchFunctions(funcs, args, node, func->GetName(), 0, func->objectType, ctx->property_const);
if( funcs.GetLength() == 0 )
{
// MatchFunctions already reported the error
if( ctx->property_arg )
{
asDELETE(ctx->property_arg, asCExprContext);
ctx->property_arg = 0;
}
return -1;
}
if( func->objectType )
{
// Setup the context with the original type so the method call gets built correctly
ctx->type.dataType = asCDataType::CreateType(func->objectType, ctx->property_const);
if( ctx->property_handle ) ctx->type.dataType.MakeHandle(true);
if( ctx->property_ref ) ctx->type.dataType.MakeReference(true);
// Don't allow the call if the object is read-only and the property accessor is not const
if( ctx->property_const && !func->isReadOnly )
{
Error(TXT_NON_CONST_METHOD_ON_CONST_OBJ, node);
asCArray<int> funcCandidates;
funcCandidates.PushLast(ctx->property_set);
PrintMatchingFuncs(funcCandidates, node);
}
}
// Call the accessor
MakeFunctionCall(ctx, ctx->property_set, func->objectType, args, node);
ctx->property_get = 0;
ctx->property_set = 0;
if( ctx->property_arg )
{
asDELETE(ctx->property_arg, asCExprContext);
ctx->property_arg = 0;
}
return 0;
}
int asCCompiler::ProcessPropertyGetSetAccessor(asCExprContext *ctx, asCExprContext *lctx, asCExprContext *rctx, eTokenType op, asCScriptNode *errNode)
{
// TODO: Perhaps it might be interesting to allow the definition of compound setters for better
// performance, e.g. set_add_prop, set_mul_prop, etc. With these it would also be possible
// to support value types, since it would be a single call
// Compound assignment for indexed property accessors is not supported yet
if( lctx->property_arg != 0 )
{
// Process the property to free the memory
ProcessPropertySetAccessor(lctx, rctx, errNode);
Error(TXT_COMPOUND_ASGN_WITH_IDX_PROP, errNode);
return -1;
}
// Compound assignments require both get and set accessors
if( lctx->property_set == 0 || lctx->property_get == 0 )
{
// Process the property to free the memory
ProcessPropertySetAccessor(lctx, rctx, errNode);
Error(TXT_COMPOUND_ASGN_REQUIRE_GET_SET, errNode);
return -1;
}
// Property accessors on value types (or scoped references types) are not supported since
// it is not possible to guarantee that the object will stay alive between the two calls
asCScriptFunction *func = engine->scriptFunctions[lctx->property_set];
if( func->objectType && (func->objectType->flags & (asOBJ_VALUE | asOBJ_SCOPED)) )
{
// Process the property to free the memory
ProcessPropertySetAccessor(lctx, rctx, errNode);
Error(TXT_COMPOUND_ASGN_ON_VALUE_TYPE, errNode);
return -1;
}
// Translate the compound assignment to the corresponding dual operator
switch( op )
{
case ttAddAssign: op = ttPlus; break;
case ttSubAssign: op = ttMinus; break;
case ttMulAssign: op = ttStar; break;
case ttDivAssign: op = ttSlash; break;
case ttModAssign: op = ttPercent; break;
case ttPowAssign: op = ttStarStar; break;
case ttAndAssign: op = ttAmp; break;
case ttOrAssign: op = ttBitOr; break;
case ttXorAssign: op = ttBitXor; break;
case ttShiftLeftAssign: op = ttBitShiftLeft; break;
case ttShiftRightAAssign: op = ttBitShiftRightArith; break;
case ttShiftRightLAssign: op = ttBitShiftRight; break;
default: op = ttUnrecognizedToken; break;
}
if( op == ttUnrecognizedToken )
{
// Shouldn't happen
asASSERT(false);
// Process the property to free the memory
ProcessPropertySetAccessor(lctx, rctx, errNode);
return -1;
}
asCExprContext before(engine);
if( func->objectType && (func->objectType->flags & (asOBJ_REF|asOBJ_SCOPED)) == asOBJ_REF )
{
// Keep a reference to the object in a local variable
before.bc.AddCode(&lctx->bc);
asUINT len = reservedVariables.GetLength();
rctx->bc.GetVarsUsed(reservedVariables);
before.bc.GetVarsUsed(reservedVariables);
asCDataType dt = asCDataType::CreateObjectHandle(func->objectType, false);
int offset = AllocateVariable(dt, true);
reservedVariables.SetLength(len);
before.type.SetVariable(dt, offset, true);
if( lctx->property_ref )
before.bc.Instr(asBC_RDSPtr);
before.bc.InstrSHORT(asBC_PSF, (short)offset);
before.bc.InstrPTR(asBC_REFCPY, func->objectType);
before.bc.Instr(asBC_PopPtr);
if( lctx->type.isTemporary )
{
// Add the release of the temporary variable as a deferred expression
asSDeferredParam deferred;
deferred.origExpr = 0;
deferred.argInOutFlags = asTM_INREF;
deferred.argNode = 0;
deferred.argType.SetVariable(ctx->type.dataType, lctx->type.stackOffset, true);
before.deferredParams.PushLast(deferred);
}
// Update the left expression to use the local variable
lctx->bc.InstrSHORT(asBC_PSF, (short)offset);
lctx->type.stackOffset = (short)offset;
lctx->property_ref = true;
// Don't release the temporary variable too early
lctx->type.isTemporary = false;
ctx->bc.AddCode(&before.bc);
}
// Keep the original information on the property
asCExprContext llctx(engine);
llctx.type = lctx->type;
llctx.property_arg = lctx->property_arg;
llctx.property_const = lctx->property_const;
llctx.property_get = lctx->property_get;
llctx.property_handle = lctx->property_handle;
llctx.property_ref = lctx->property_ref;
llctx.property_set = lctx->property_set;
// Compile the dual operator using the get accessor
CompileOperator(errNode, lctx, rctx, ctx, op);
// If we made a local variable to hold the reference it must be reused
if( before.type.stackOffset )
llctx.bc.InstrSHORT(asBC_PSF, before.type.stackOffset);
// Compile the assignment using the set accessor
ProcessPropertySetAccessor(&llctx, ctx, errNode);
MergeExprBytecodeAndType(ctx, &llctx);
if( before.type.stackOffset )
ReleaseTemporaryVariable(before.type.stackOffset, &ctx->bc);
asASSERT( ctx->deferredParams.GetLength() == 0 );
ctx->deferredParams = before.deferredParams;
ProcessDeferredParams(ctx);
return 0;
}
void asCCompiler::ProcessPropertyGetAccessor(asCExprContext *ctx, asCScriptNode *node)
{
// If no property accessor has been prepared then don't do anything
if( !ctx->property_get && !ctx->property_set )
return;
if( !ctx->property_get )
{
// Raise error on missing accessor
Error(TXT_PROPERTY_HAS_NO_GET_ACCESSOR, node);
ctx->type.SetDummy();
return;
}
asCExprValue objType = ctx->type;
asCScriptFunction *func = builder->GetFunctionDescription(ctx->property_get);
// Make sure the arg match the property
asCArray<int> funcs;
funcs.PushLast(ctx->property_get);
asCArray<asCExprContext *> args;
if( ctx->property_arg )
args.PushLast(ctx->property_arg);
MatchFunctions(funcs, args, node, func->GetName(), 0, func->objectType, ctx->property_const);
if( funcs.GetLength() == 0 )
{
// MatchFunctions already reported the error
if( ctx->property_arg )
{
asDELETE(ctx->property_arg, asCExprContext);
ctx->property_arg = 0;
}
ctx->type.SetDummy();
return;
}
if( func->objectType )
{
// Setup the context with the original type so the method call gets built correctly
ctx->type.dataType = asCDataType::CreateType(func->objectType, ctx->property_const);
if( ctx->property_handle ) ctx->type.dataType.MakeHandle(true);
if( ctx->property_ref ) ctx->type.dataType.MakeReference(true);
// Don't allow the call if the object is read-only and the property accessor is not const
if( ctx->property_const && !func->isReadOnly )
{
Error(TXT_NON_CONST_METHOD_ON_CONST_OBJ, node);
asCArray<int> funcCandidates;
funcCandidates.PushLast(ctx->property_get);
PrintMatchingFuncs(funcCandidates, node);
}
}
// The explicit handle flag must be remembered
bool isExplicitHandle = ctx->type.isExplicitHandle;
// Call the accessor
MakeFunctionCall(ctx, ctx->property_get, func->objectType, args, node);
if( isExplicitHandle )
ctx->type.isExplicitHandle = true;
// Clear the property get/set ids
ctx->property_get = 0;
ctx->property_set = 0;
if( ctx->property_arg )
{
asDELETE(ctx->property_arg, asCExprContext);
ctx->property_arg = 0;
}
}
int asCCompiler::CompileExpressionPostOp(asCScriptNode *node, asCExprContext *ctx)
{
// Don't allow any postfix operators on expressions that take address of class method
if( ctx->IsClassMethod() )
{
Error(TXT_INVALID_OP_ON_METHOD, node);
return -1;
}
// Don't allow any operators on void expressions
if( ctx->IsVoidExpression() )
{
Error(TXT_VOID_CANT_BE_OPERAND, node);
return -1;
}
// Check if the variable is initialized (if it indeed is a variable)
IsVariableInitialized(&ctx->type, node);
int op = node->tokenType;
if( (op == ttInc || op == ttDec) && ctx->type.dataType.IsObject() )
{
const char *opName = 0;
switch( op )
{
case ttInc: opName = "opPostInc"; break;
case ttDec: opName = "opPostDec"; break;
}
if( opName )
{
// TODO: Should convert this to something similar to CompileOverloadedDualOperator2
ProcessPropertyGetAccessor(ctx, node);
// TODO: If the value isn't const, then first try to find the non const method, and if not found try to find the const method
// Find the correct method
bool isConst = ctx->type.dataType.IsObjectConst();
asCArray<int> funcs;
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( func->name == opName &&
func->parameterTypes.GetLength() == 0 &&
(!isConst || func->isReadOnly) )
{
funcs.PushLast(func->id);
}
}
// Did we find the method?
if( funcs.GetLength() == 1 )
{
asCArray<asCExprContext *> args;
MakeFunctionCall(ctx, funcs[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
return 0;
}
else if( funcs.GetLength() == 0 )
{
asCString str;
str = asCString(opName) + "()";
if( isConst )
str += " const";
str.Format(TXT_FUNCTION_s_NOT_FOUND, str.AddressOf());
Error(str, node);
ctx->type.SetDummy();
return -1;
}
else if( funcs.GetLength() > 1 )
{
Error(TXT_MORE_THAN_ONE_MATCHING_OP, node);
PrintMatchingFuncs(funcs, node);
ctx->type.SetDummy();
return -1;
}
}
}
else if( op == ttInc || op == ttDec )
{
// Make sure the reference isn't a temporary variable
if( ctx->type.isTemporary )
{
Error(TXT_REF_IS_TEMP, node);
return -1;
}
if( ctx->type.dataType.IsReadOnly() )
{
Error(TXT_REF_IS_READ_ONLY, node);
return -1;
}
if( ctx->property_get || ctx->property_set )
{
Error(TXT_INVALID_REF_PROP_ACCESS, node);
return -1;
}
if( !ctx->type.isLValue )
{
Error(TXT_NOT_LVALUE, node);
return -1;
}
if( ctx->type.isVariable && !ctx->type.dataType.IsReference() )
ConvertToReference(ctx);
else if( !ctx->type.dataType.IsReference() )
{
Error(TXT_NOT_VALID_REFERENCE, node);
return -1;
}
// Copy the value to a temp before changing it
ConvertToTempVariable(ctx);
asASSERT(!ctx->type.isLValue);
// Increment the value pointed to by the reference still in the register
asEBCInstr iInc = asBC_INCi, iDec = asBC_DECi;
if( ctx->type.dataType.IsDoubleType() )
{
iInc = asBC_INCd;
iDec = asBC_DECd;
}
else if( ctx->type.dataType.IsFloatType() )
{
iInc = asBC_INCf;
iDec = asBC_DECf;
}
else if( ctx->type.dataType.IsIntegerType() || ctx->type.dataType.IsUnsignedType() )
{
if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt16, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt16, false)) )
{
iInc = asBC_INCi16;
iDec = asBC_DECi16;
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt8, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt8, false)) )
{
iInc = asBC_INCi8;
iDec = asBC_DECi8;
}
else if( ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttInt64, false)) ||
ctx->type.dataType.IsEqualExceptRef(asCDataType::CreatePrimitive(ttUInt64, false)) )
{
iInc = asBC_INCi64;
iDec = asBC_DECi64;
}
}
else
{
Error(TXT_ILLEGAL_OPERATION, node);
return -1;
}
if( op == ttInc ) ctx->bc.Instr(iInc); else ctx->bc.Instr(iDec);
}
else if( op == ttDot )
{
if( node->firstChild->nodeType == snIdentifier )
{
ProcessPropertyGetAccessor(ctx, node);
// Get the property name
asCString name(&script->code[node->firstChild->tokenPos], node->firstChild->tokenLength);
if( ctx->type.dataType.IsObject() )
{
// We need to look for get/set property accessors.
// If found, the context stores information on the get/set accessors
// until it is known which is to be used.
int r = 0;
if( node->next && node->next->tokenType == ttOpenBracket )
{
// The property accessor should take an index arg
asCExprContext dummyArg(engine);
r = FindPropertyAccessor(name, ctx, &dummyArg, node, 0);
}
if( r == 0 )
r = FindPropertyAccessor(name, ctx, node, 0);
if( r != 0 )
return r;
if( !ctx->type.dataType.IsPrimitive() )
Dereference(ctx, true);
if( ctx->type.dataType.IsObjectHandle() )
{
// Convert the handle to a normal object
asCDataType dt = ctx->type.dataType;
dt.MakeHandle(false);
ImplicitConversion(ctx, dt, node, asIC_IMPLICIT_CONV);
// The handle may not have been an lvalue, but the dereferenced object is
ctx->type.isLValue = true;
}
bool isConst = ctx->type.dataType.IsObjectConst();
asCObjectProperty *prop = builder->GetObjectProperty(ctx->type.dataType, name.AddressOf());
if( prop )
{
// Is the property access allowed?
if( (prop->isPrivate || prop->isProtected) && (!outFunc || outFunc->objectType != ctx->type.dataType.GetTypeInfo()) )
{
asCString msg;
if( prop->isPrivate )
msg.Format(TXT_PRIVATE_PROP_ACCESS_s, name.AddressOf());
else
msg.Format(TXT_PROTECTED_PROP_ACCESS_s, name.AddressOf());
Error(msg, node);
}
// Put the offset on the stack
ctx->bc.InstrSHORT_DW(asBC_ADDSi, (short)prop->byteOffset, engine->GetTypeIdFromDataType(asCDataType::CreateType(ctx->type.dataType.GetTypeInfo(), false)));
if( prop->type.IsReference() )
ctx->bc.Instr(asBC_RDSPtr);
// Reference to primitive must be stored in the temp register
if( prop->type.IsPrimitive() )
{
ctx->bc.Instr(asBC_PopRPtr);
}
// Keep information about temporary variables as deferred expression
if( ctx->type.isTemporary )
{
// Add the release of this reference, as a deferred expression
asSDeferredParam deferred;
deferred.origExpr = 0;
deferred.argInOutFlags = asTM_INREF;
deferred.argNode = 0;
deferred.argType.SetVariable(ctx->type.dataType, ctx->type.stackOffset, true);
ctx->deferredParams.PushLast(deferred);
}
// Set the new type and make sure it is not treated as a variable anymore
ctx->type.dataType = prop->type;
ctx->type.dataType.MakeReference(true);
ctx->type.isVariable = false;
ctx->type.isTemporary = false;
if( (ctx->type.dataType.IsObject() || ctx->type.dataType.IsFuncdef()) && !ctx->type.dataType.IsObjectHandle() )
{
// Objects that are members are not references
ctx->type.dataType.MakeReference(false);
}
ctx->type.dataType.MakeReadOnly(isConst ? true : prop->type.IsReadOnly());
}
else
{
// If the name is not a property, the compiler must check if the name matches
// a method, which can be used for constructing delegates
asIScriptFunction *func = 0;
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( asUINT n = 0; n < ot->methods.GetLength(); n++ )
{
if( engine->scriptFunctions[ot->methods[n]]->name == name )
{
func = engine->scriptFunctions[ot->methods[n]];
break;
}
}
if( func )
{
// An object method was found. Keep the name of the method in the expression, but
// don't actually modify the bytecode at this point since it is not yet known what
// the method will be used for, or even what overloaded method should be used.
ctx->methodName = name;
}
else
{
asCString str;
str.Format(TXT_s_NOT_MEMBER_OF_s, name.AddressOf(), ctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
return -1;
}
}
}
else
{
asCString str;
str.Format(TXT_s_NOT_MEMBER_OF_s, name.AddressOf(), ctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
return -1;
}
}
else
{
// Make sure it is an object we are accessing
if( !ctx->type.dataType.IsObject() )
{
asCString str;
str.Format(TXT_ILLEGAL_OPERATION_ON_s, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
return -1;
}
// Process the get property accessor
ProcessPropertyGetAccessor(ctx, node);
// Compile function call
int r = CompileFunctionCall(node->firstChild, ctx, ctx->type.dataType.GetTypeInfo()->CastToObjectType(), ctx->type.dataType.IsObjectConst());
if( r < 0 ) return r;
}
}
else if( op == ttOpenBracket )
{
// If the property access takes an index arg and the argument hasn't been evaluated yet,
// then we should use that instead of processing it now. If the argument has already been
// evaluated, then we should process the property accessor as a get access now as the new
// index operator is on the result of that accessor.
asCString propertyName;
asSNameSpace *ns = 0;
if( ((ctx->property_get && builder->GetFunctionDescription(ctx->property_get)->GetParamCount() == 1) ||
(ctx->property_set && builder->GetFunctionDescription(ctx->property_set)->GetParamCount() == 2)) &&
(ctx->property_arg && ctx->property_arg->type.dataType.GetTokenType() == ttUnrecognizedToken) )
{
// Determine the name of the property accessor
asCScriptFunction *func = 0;
if( ctx->property_get )
func = builder->GetFunctionDescription(ctx->property_get);
else
func = builder->GetFunctionDescription(ctx->property_set);
propertyName = func->GetName();
propertyName = propertyName.SubString(4);
// Set the original type of the expression so we can re-evaluate the property accessor
if( func->objectType )
{
ctx->type.dataType = asCDataType::CreateType(func->objectType, ctx->property_const);
if( ctx->property_handle ) ctx->type.dataType.MakeHandle(true);
if( ctx->property_ref ) ctx->type.dataType.MakeReference(true);
}
else
{
// Store the namespace where the function is declared
// so the same function can be found later
ctx->type.SetDummy();
ns = func->nameSpace;
}
ctx->property_get = ctx->property_set = 0;
if( ctx->property_arg )
{
asDELETE(ctx->property_arg, asCExprContext);
ctx->property_arg = 0;
}
}
else
{
if( !ctx->type.dataType.IsObject() )
{
asCString str;
str.Format(TXT_OBJECT_DOESNT_SUPPORT_INDEX_OP, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
return -1;
}
ProcessPropertyGetAccessor(ctx, node);
}
// Compile the expression
bool isOK = true;
asCArray<asCExprContext *> args;
asCArray<asSNamedArgument> namedArgs;
asASSERT( node->firstChild->nodeType == snArgList );
if( CompileArgumentList(node->firstChild, args, namedArgs) >= 0 )
{
// Check for the existence of the opIndex method
bool lookForProperty = true;
if( propertyName == "" )
{
bool isConst = ctx->type.dataType.IsObjectConst();
asCObjectType *objectType = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
asCArray<int> funcs;
builder->GetObjectMethodDescriptions("opIndex", objectType, funcs, isConst);
if( funcs.GetLength() > 0 )
{
// Since there are opIndex methods, the compiler should not look for get/set_opIndex accessors
lookForProperty = false;
// Determine which of opIndex methods that match
MatchFunctions(funcs, args, node, "opIndex", 0, objectType, isConst);
if( funcs.GetLength() != 1 )
{
// The error has already been reported by MatchFunctions
isOK = false;
}
else
{
// Add the default values for arguments not explicitly supplied
int r = CompileDefaultAndNamedArgs(node, args, funcs[0], objectType);
if( r == 0 )
MakeFunctionCall(ctx, funcs[0], objectType, args, node, false, 0, ctx->type.stackOffset);
else
isOK = false;
}
}
}
if( lookForProperty && isOK )
{
if( args.GetLength() != 1 )
{
// TODO: opIndex: Implement this
Error("Property accessor with index only support 1 index argument for now", node);
isOK = false;
}
Dereference(ctx, true);
asCExprContext lctx(engine);
MergeExprBytecodeAndType(&lctx, ctx);
// Check for accessors methods for the opIndex, either as get/set_opIndex or as get/set with the property name
int r = FindPropertyAccessor(propertyName == "" ? "opIndex" : propertyName.AddressOf(), &lctx, args[0], node, ns);
if( r == 0 )
{
asCString str;
str.Format(TXT_OBJECT_DOESNT_SUPPORT_INDEX_OP, ctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
isOK = false;
}
else if( r < 0 )
isOK = false;
if( isOK )
MergeExprBytecodeAndType(ctx, &lctx);
}
}
else
isOK = false;
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
if( !isOK )
return -1;
}
else if( op == ttOpenParanthesis )
{
// TODO: Most of this is already done by CompileFunctionCall(). Can we share the code?
// Make sure the expression is a funcdef or an object that may have opCall methods
if( !ctx->type.dataType.IsFuncdef() && !ctx->type.dataType.IsObject() )
{
Error(TXT_EXPR_DOESNT_EVAL_TO_FUNC, node);
return -1;
}
// Compile arguments
asCArray<asCExprContext *> args;
asCArray<asSNamedArgument> namedArgs;
if( CompileArgumentList(node->lastChild, args, namedArgs) >= 0 )
{
// Match arguments with the funcdef
asCArray<int> funcs;
if( ctx->type.dataType.IsFuncdef() )
{
funcs.PushLast(ctx->type.dataType.GetTypeInfo()->CastToFuncdefType()->funcdef->id);
MatchFunctions(funcs, args, node, ctx->type.dataType.GetTypeInfo()->name.AddressOf(), &namedArgs);
}
else
{
bool isConst = ctx->type.dataType.IsObjectConst();
builder->GetObjectMethodDescriptions("opCall", ctx->type.dataType.GetTypeInfo()->CastToObjectType(), funcs, isConst);
MatchFunctions(funcs, args, node, "opCall", &namedArgs, ctx->type.dataType.GetTypeInfo()->CastToObjectType(), isConst);
}
if( funcs.GetLength() != 1 )
{
// The error was reported by MatchFunctions()
// Dummy value
ctx->type.SetDummy();
}
else
{
// Add the default values for arguments not explicitly supplied
int r = CompileDefaultAndNamedArgs(node, args, funcs[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), &namedArgs);
// TODO: funcdef: Do we have to make sure the handle is stored in a temporary variable, or
// is it enough to make sure it is in a local variable?
// For function pointer we must guarantee that the function is safe, i.e.
// by first storing the function pointer in a local variable (if it isn't already in one)
if( r == asSUCCESS )
{
Dereference(ctx, true);
if( ctx->type.dataType.IsFuncdef() )
{
if( !ctx->type.isVariable )
ConvertToVariable(ctx);
// Remove the reference from the stack as the asBC_CALLPTR instruction takes the variable as argument
ctx->bc.Instr(asBC_PopPtr);
}
MakeFunctionCall(ctx, funcs[0], ctx->type.dataType.IsFuncdef() ? 0 : ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node, false, 0, ctx->type.stackOffset);
}
}
}
else
ctx->type.SetDummy();
// Cleanup
for( asUINT n = 0; n < args.GetLength(); n++ )
if( args[n] )
{
asDELETE(args[n], asCExprContext);
}
for( asUINT n = 0; n < namedArgs.GetLength(); n++ )
if( namedArgs[n].ctx )
{
asDELETE(namedArgs[n].ctx, asCExprContext);
}
}
return 0;
}
int asCCompiler::GetPrecedence(asCScriptNode *op)
{
// x ** y
// x * y, x / y, x % y
// x + y, x - y
// x <= y, x < y, x >= y, x > y
// x = =y, x != y, x xor y, x is y, x !is y
// x and y
// x or y
// The following are not used in this function,
// but should have lower precedence than the above
// x ? y : z
// x = y
// The expression term have the highest precedence
if( op->nodeType == snExprTerm )
return 1;
// Evaluate operators by token
int tokenType = op->tokenType;
if( tokenType == ttStarStar )
return 0;
if( tokenType == ttStar || tokenType == ttSlash || tokenType == ttPercent )
return -1;
if( tokenType == ttPlus || tokenType == ttMinus )
return -2;
if( tokenType == ttBitShiftLeft ||
tokenType == ttBitShiftRight ||
tokenType == ttBitShiftRightArith )
return -3;
if( tokenType == ttAmp )
return -4;
if( tokenType == ttBitXor )
return -5;
if( tokenType == ttBitOr )
return -6;
if( tokenType == ttLessThanOrEqual ||
tokenType == ttLessThan ||
tokenType == ttGreaterThanOrEqual ||
tokenType == ttGreaterThan )
return -7;
if( tokenType == ttEqual || tokenType == ttNotEqual || tokenType == ttXor || tokenType == ttIs || tokenType == ttNotIs )
return -8;
if( tokenType == ttAnd )
return -9;
if( tokenType == ttOr )
return -10;
// Unknown operator
asASSERT(false);
return 0;
}
asUINT asCCompiler::MatchArgument(asCArray<int> &funcs, asCArray<asSOverloadCandidate> &matches, const asCExprContext *argExpr, int paramNum, bool allowObjectConstruct)
{
matches.SetLength(0);
for( asUINT n = 0; n < funcs.GetLength(); n++ )
{
asCScriptFunction *desc = builder->GetFunctionDescription(funcs[n]);
// Does the function have arguments enough?
if( (int)desc->parameterTypes.GetLength() <= paramNum )
continue;
int cost = MatchArgument(desc, argExpr, paramNum, allowObjectConstruct);
if( cost != -1 )
matches.PushLast(asSOverloadCandidate(funcs[n], asUINT(cost)));
}
return (asUINT)matches.GetLength();
}
int asCCompiler::MatchArgument(asCScriptFunction *desc, const asCExprContext *argExpr, int paramNum, bool allowObjectConstruct)
{
// void expressions can match any out parameter, but nothing else
if( argExpr->IsVoidExpression() )
{
if( desc->inOutFlags[paramNum] == asTM_OUTREF )
return 0;
return -1;
}
// Can we make the match by implicit conversion?
asCExprContext ti(engine);
ti.type = argExpr->type;
ti.methodName = argExpr->methodName;
ti.enumValue = argExpr->enumValue;
ti.exprNode = argExpr->exprNode;
if( argExpr->type.dataType.IsPrimitive() )
ti.type.dataType.MakeReference(false);
// Don't allow the implicit conversion to make a copy in case the argument is expecting a reference to the true value
if (desc->parameterTypes[paramNum].IsReference() && desc->inOutFlags[paramNum] == asTM_INOUTREF)
allowObjectConstruct = false;
int cost = ImplicitConversion(&ti, desc->parameterTypes[paramNum], 0, asIC_IMPLICIT_CONV, false, allowObjectConstruct);
// If the function parameter is an inout-reference then it must not be possible to call the
// function with an incorrect argument type, even though the type can normally be converted.
if( desc->parameterTypes[paramNum].IsReference() &&
desc->inOutFlags[paramNum] == asTM_INOUTREF &&
desc->parameterTypes[paramNum].GetTokenType() != ttQuestion )
{
// Observe, that the below checks are only necessary for when unsafe references have been
// enabled by the application. Without this the &inout reference form wouldn't be allowed
// for these value types.
// Don't allow a primitive to be converted to a reference of another primitive type
if( desc->parameterTypes[paramNum].IsPrimitive() &&
desc->parameterTypes[paramNum].GetTokenType() != argExpr->type.dataType.GetTokenType() )
{
asASSERT( engine->ep.allowUnsafeReferences );
return -1;
}
// Don't allow an enum to be converted to a reference of another enum type
if( desc->parameterTypes[paramNum].IsEnumType() &&
desc->parameterTypes[paramNum].GetTypeInfo() != argExpr->type.dataType.GetTypeInfo() )
{
asASSERT( engine->ep.allowUnsafeReferences );
return -1;
}
// Don't allow a non-handle expression to be converted to a reference to a handle
if( desc->parameterTypes[paramNum].IsObjectHandle() &&
!argExpr->type.dataType.IsObjectHandle() )
{
asASSERT( engine->ep.allowUnsafeReferences );
return -1;
}
// Don't allow a value type to be converted
if( (desc->parameterTypes[paramNum].GetTypeInfo() && (desc->parameterTypes[paramNum].GetTypeInfo()->GetFlags() & asOBJ_VALUE)) &&
(desc->parameterTypes[paramNum].GetTypeInfo() != argExpr->type.dataType.GetTypeInfo()) )
{
asASSERT( engine->ep.allowUnsafeReferences );
return -1;
}
}
// How well does the argument match the function parameter?
if( desc->parameterTypes[paramNum].IsEqualExceptRef(ti.type.dataType) )
return cost;
// No match is available
return -1;
}
void asCCompiler::PrepareArgument2(asCExprContext *ctx, asCExprContext *arg, asCDataType *paramType, bool isFunction, int refType, bool isMakingCopy)
{
// Reference parameters whose value won't be used don't evaluate the expression
// Clean arguments (i.e. default value) will be passed in directly as there is nothing to protect
if( paramType->IsReference() && !(refType & asTM_INREF) && !arg->isCleanArg )
{
// Store the original bytecode so that it can be reused when processing the deferred output parameter
asCExprContext *orig = asNEW(asCExprContext)(engine);
if( orig == 0 )
{
// Out of memory
return;
}
MergeExprBytecodeAndType(orig, arg);
arg->origExpr = orig;
}
PrepareArgument(paramType, arg, arg->exprNode, isFunction, refType, isMakingCopy);
// arg still holds the original expression for output parameters
ctx->bc.AddCode(&arg->bc);
}
bool asCCompiler::CompileOverloadedDualOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, bool isHandle, eTokenType token)
{
DetermineSingleFunc(lctx, node);
DetermineSingleFunc(rctx, node);
ctx->exprNode = node;
// What type of operator is it?
if( token == ttUnrecognizedToken )
token = node->tokenType;
if( token == ttUnrecognizedToken )
{
// This happens when the compiler is inferring an assignment
// operation from another action, for example in preparing a value
// as a function argument
token = ttAssignment;
}
// boolean operators are not overloadable
if( token == ttAnd ||
token == ttOr ||
token == ttXor )
return false;
// Dual operators can also be implemented as class methods
if( token == ttEqual ||
token == ttNotEqual )
{
// TODO: Should evaluate which of the two have the best match. If both have equal match, the first version should be used
// Find the matching opEquals method
int r = CompileOverloadedDualOperator2(node, "opEquals", lctx, rctx, ctx, true, asCDataType::CreatePrimitive(ttBool, false));
if( r == 0 )
{
// Try again by switching the order of the operands
r = CompileOverloadedDualOperator2(node, "opEquals", rctx, lctx, ctx, true, asCDataType::CreatePrimitive(ttBool, false));
}
if( r == 1 )
{
if( token == ttNotEqual )
ctx->bc.InstrSHORT(asBC_NOT, ctx->type.stackOffset);
// Success, don't continue
return true;
}
else if( r < 0 )
{
// Compiler error, don't continue
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), true);
return true;
}
}
if( token == ttEqual ||
token == ttNotEqual ||
token == ttLessThan ||
token == ttLessThanOrEqual ||
token == ttGreaterThan ||
token == ttGreaterThanOrEqual )
{
bool swappedOrder = false;
// TODO: Should evaluate which of the two have the best match. If both have equal match, the first version should be used
// Find the matching opCmp method
int r = CompileOverloadedDualOperator2(node, "opCmp", lctx, rctx, ctx, true, asCDataType::CreatePrimitive(ttInt, false));
if( r == 0 )
{
// Try again by switching the order of the operands
swappedOrder = true;
r = CompileOverloadedDualOperator2(node, "opCmp", rctx, lctx, ctx, true, asCDataType::CreatePrimitive(ttInt, false));
}
if( r == 1 )
{
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
int a = AllocateVariable(asCDataType::CreatePrimitive(ttBool, false), true);
ctx->bc.InstrW_DW(asBC_CMPIi, ctx->type.stackOffset, 0);
if( token == ttEqual )
ctx->bc.Instr(asBC_TZ);
else if( token == ttNotEqual )
ctx->bc.Instr(asBC_TNZ);
else if( (token == ttLessThan && !swappedOrder) ||
(token == ttGreaterThan && swappedOrder) )
ctx->bc.Instr(asBC_TS);
else if( (token == ttLessThanOrEqual && !swappedOrder) ||
(token == ttGreaterThanOrEqual && swappedOrder) )
ctx->bc.Instr(asBC_TNP);
else if( (token == ttGreaterThan && !swappedOrder) ||
(token == ttLessThan && swappedOrder) )
ctx->bc.Instr(asBC_TP);
else if( (token == ttGreaterThanOrEqual && !swappedOrder) ||
(token == ttLessThanOrEqual && swappedOrder) )
ctx->bc.Instr(asBC_TNS);
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)a);
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, false), a, true);
// Success, don't continue
return true;
}
else if( r < 0 )
{
// Compiler error, don't continue
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), true);
return true;
}
}
// The rest of the operators are not commutative, and doesn't require specific return type
const char *op = 0, *op_r = 0;
switch( int(token) ) // convert to int to avoid warning in gnuc that not all values are tested
{
case ttPlus: op = "opAdd"; op_r = "opAdd_r"; break;
case ttMinus: op = "opSub"; op_r = "opSub_r"; break;
case ttStar: op = "opMul"; op_r = "opMul_r"; break;
case ttSlash: op = "opDiv"; op_r = "opDiv_r"; break;
case ttPercent: op = "opMod"; op_r = "opMod_r"; break;
case ttStarStar: op = "opPow"; op_r = "opPow_r"; break;
case ttBitOr: op = "opOr"; op_r = "opOr_r"; break;
case ttAmp: op = "opAnd"; op_r = "opAnd_r"; break;
case ttBitXor: op = "opXor"; op_r = "opXor_r"; break;
case ttBitShiftLeft: op = "opShl"; op_r = "opShl_r"; break;
case ttBitShiftRight: op = "opShr"; op_r = "opShr_r"; break;
case ttBitShiftRightArith: op = "opUShr"; op_r = "opUShr_r"; break;
}
// TODO: Might be interesting to support a concatenation operator, e.g. ~
if( op && op_r )
{
// TODO: Should evaluate which of the two have the best match. If both have equal match, the first version should be used
// Find the matching operator method
int r = CompileOverloadedDualOperator2(node, op, lctx, rctx, ctx);
if( r == 0 )
{
// Try again by switching the order of the operands, and using the reversed operator
r = CompileOverloadedDualOperator2(node, op_r, rctx, lctx, ctx);
}
if( r == 1 )
{
// Success, don't continue
return true;
}
else if( r < 0 )
{
// Compiler error, don't continue
ctx->type.SetDummy();
return true;
}
}
// Assignment operators
op = 0;
if( isHandle )
{
// Only asOBJ_ASHANDLE types can get here
asASSERT( lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE) );
asASSERT( token == ttAssignment );
if( token == ttAssignment )
op = "opHndlAssign";
}
else
{
switch( int(token) ) // convert to int to avoid warning in gnuc that not all values are tested
{
case ttAssignment: op = "opAssign"; break;
case ttAddAssign: op = "opAddAssign"; break;
case ttSubAssign: op = "opSubAssign"; break;
case ttMulAssign: op = "opMulAssign"; break;
case ttDivAssign: op = "opDivAssign"; break;
case ttModAssign: op = "opModAssign"; break;
case ttPowAssign: op = "opPowAssign"; break;
case ttOrAssign: op = "opOrAssign"; break;
case ttAndAssign: op = "opAndAssign"; break;
case ttXorAssign: op = "opXorAssign"; break;
case ttShiftLeftAssign: op = "opShlAssign"; break;
case ttShiftRightLAssign: op = "opShrAssign"; break;
case ttShiftRightAAssign: op = "opUShrAssign"; break;
}
}
if( op )
{
if( builder->engine->ep.disallowValueAssignForRefType &&
lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_REF) && !(lctx->type.dataType.GetTypeInfo()->flags & asOBJ_SCOPED) )
{
if( token == ttAssignment )
Error(TXT_DISALLOW_ASSIGN_ON_REF_TYPE, node);
else
Error(TXT_DISALLOW_COMPOUND_ASSIGN_ON_REF_TYPE, node);
// Set a dummy output
ctx->type.Set(lctx->type.dataType);
return true;
}
// TODO: Shouldn't accept const lvalue with the assignment operators
// Find the matching operator method
int r = CompileOverloadedDualOperator2(node, op, lctx, rctx, ctx);
if( r == 1 )
{
// Success, don't continue
return true;
}
else if( r < 0 )
{
// Compiler error, don't continue
ctx->type.SetDummy();
return true;
}
}
// No suitable operator was found
return false;
}
// Returns negative on compile error
// zero on no matching operator
// one on matching operator
int asCCompiler::CompileOverloadedDualOperator2(asCScriptNode *node, const char *methodName, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, bool specificReturn, const asCDataType &returnType)
{
// Find the matching method
if( lctx->type.dataType.IsObject() &&
(!lctx->type.isExplicitHandle ||
lctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE) )
{
asUINT n;
// Is the left value a const?
bool isConst = lctx->type.dataType.IsObjectConst();
asCArray<int> funcs;
asCObjectType *ot = lctx->type.dataType.GetTypeInfo()->CastToObjectType();
for( n = 0; n < ot->methods.GetLength(); n++ )
{
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
asASSERT( func );
if( func && func->name == methodName &&
(!specificReturn || func->returnType == returnType) &&
func->parameterTypes.GetLength() == 1 &&
(!isConst || func->isReadOnly) )
{
// Make sure the method is accessible by the module
if( builder->module->accessMask & func->accessMask )
{
funcs.PushLast(func->id);
}
}
}
// Which is the best matching function?
asCArray<asSOverloadCandidate> tempFuncs;
MatchArgument(funcs, tempFuncs, rctx, 0);
// Find the lowest cost operator(s)
asCArray<int> ops;
asUINT bestCost = asUINT(-1);
for( n = 0; n < tempFuncs.GetLength(); ++n )
{
asUINT cost = tempFuncs[n].cost;
if( cost < bestCost )
{
ops.SetLength(0);
bestCost = cost;
}
if( cost == bestCost )
ops.PushLast(tempFuncs[n].funcId);
}
// If the object is not const, then we need to prioritize non-const methods
if( !isConst )
FilterConst(ops);
// Did we find an operator?
if( ops.GetLength() == 1 )
{
// Process the lctx expression as get accessor
ProcessPropertyGetAccessor(lctx, node);
// Make sure the rvalue doesn't have deferred temporary variables that are also used in the lvalue,
// since that would cause the VM to overwrite the variable while executing the bytecode for the lvalue.
asCArray<int> usedVars;
lctx->bc.GetVarsUsed(usedVars);
asUINT oldReservedVars = reservedVariables.GetLength();
for( n = 0; n < rctx->deferredParams.GetLength(); n++ )
{
if( rctx->deferredParams[n].argType.isTemporary &&
usedVars.Exists(rctx->deferredParams[n].argType.stackOffset) )
{
if( reservedVariables.GetLength() == oldReservedVars )
reservedVariables.Concatenate(usedVars);
// Allocate a new variable for the deferred argument
int offset = AllocateVariableNotIn(rctx->deferredParams[n].argType.dataType, true, false, rctx);
int oldVar = rctx->deferredParams[n].argType.stackOffset;
rctx->deferredParams[n].argType.stackOffset = short(offset);
rctx->bc.ExchangeVar(oldVar, offset);
ReleaseTemporaryVariable(oldVar, 0);
}
}
reservedVariables.SetLength(oldReservedVars);
// Merge the bytecode so that it forms lvalue.methodName(rvalue)
asCArray<asCExprContext *> args;
args.PushLast(rctx);
MergeExprBytecode(ctx, lctx);
ctx->type = lctx->type;
MakeFunctionCall(ctx, ops[0], ctx->type.dataType.GetTypeInfo()->CastToObjectType(), args, node);
// Found matching operator
return 1;
}
else if( ops.GetLength() > 1 )
{
Error(TXT_MORE_THAN_ONE_MATCHING_OP, node);
PrintMatchingFuncs(ops, node);
ctx->type.SetDummy();
// Compiler error
return -1;
}
}
// No matching operator
return 0;
}
void asCCompiler::MakeFunctionCall(asCExprContext *ctx, int funcId, asCObjectType *objectType, asCArray<asCExprContext*> &args, asCScriptNode *node, bool useVariable, int stackOffset, int funcPtrVar)
{
if( objectType )
Dereference(ctx, true);
// Store the expression node for error reporting
if( ctx->exprNode == 0 )
ctx->exprNode = node;
asCByteCode objBC(engine);
objBC.AddCode(&ctx->bc);
PrepareFunctionCall(funcId, &ctx->bc, args);
// Verify if any of the args variable offsets are used in the other code.
// If they are exchange the offset for a new one
asUINT n;
for( n = 0; n < args.GetLength(); n++ )
{
if( args[n]->type.isTemporary && objBC.IsVarUsed(args[n]->type.stackOffset) )
{
// Release the current temporary variable
ReleaseTemporaryVariable(args[n]->type, 0);
asCDataType dt = args[n]->type.dataType;
dt.MakeReference(false);
int l = int(reservedVariables.GetLength());
objBC.GetVarsUsed(reservedVariables);
ctx->bc.GetVarsUsed(reservedVariables);
int newOffset = AllocateVariable(dt, true, IsVariableOnHeap(args[n]->type.stackOffset));
reservedVariables.SetLength(l);
asASSERT( IsVariableOnHeap(args[n]->type.stackOffset) == IsVariableOnHeap(newOffset) );
ctx->bc.ExchangeVar(args[n]->type.stackOffset, newOffset);
args[n]->type.stackOffset = (short)newOffset;
args[n]->type.isTemporary = true;
args[n]->type.isVariable = true;
}
}
// If the function will return a value type on the stack, then we must allocate space
// for that here and push the address on the stack as a hidden argument to the function
asCScriptFunction *func = builder->GetFunctionDescription(funcId);
if( func->DoesReturnOnStack() )
{
asASSERT(!useVariable);
useVariable = true;
stackOffset = AllocateVariable(func->returnType, true);
ctx->bc.InstrSHORT(asBC_PSF, short(stackOffset));
}
ctx->bc.AddCode(&objBC);
MoveArgsToStack(funcId, &ctx->bc, args, objectType ? true : false);
PerformFunctionCall(funcId, ctx, false, &args, 0, useVariable, stackOffset, funcPtrVar);
}
int asCCompiler::CompileOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType op)
{
// Don't allow any operators on expressions that take address of class method, but allow it on global functions
if( (lctx->IsClassMethod()) || (rctx->IsClassMethod()) )
{
Error(TXT_INVALID_OP_ON_METHOD, node);
return -1;
}
// Don't allow any operators on void expressions
if( lctx->IsVoidExpression() || rctx->IsVoidExpression() )
{
Error(TXT_VOID_CANT_BE_OPERAND, node);
return -1;
}
if( op == ttUnrecognizedToken )
op = node->tokenType;
IsVariableInitialized(&lctx->type, node);
IsVariableInitialized(&rctx->type, node);
if( lctx->type.isExplicitHandle || rctx->type.isExplicitHandle ||
lctx->type.IsNullConstant() || rctx->type.IsNullConstant() ||
op == ttIs || op == ttNotIs )
{
CompileOperatorOnHandles(node, lctx, rctx, ctx, op);
return 0;
}
else
{
// Compile an overloaded operator for the two operands
if( CompileOverloadedDualOperator(node, lctx, rctx, ctx, false, op) )
return 0;
// If both operands are objects, then we shouldn't continue
if( lctx->type.dataType.IsObject() && rctx->type.dataType.IsObject() )
{
asCString str;
str.Format(TXT_NO_MATCHING_OP_FOUND_FOR_TYPES_s_AND_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), rctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.SetDummy();
return -1;
}
// Process the property get accessors (if any)
ProcessPropertyGetAccessor(lctx, node);
ProcessPropertyGetAccessor(rctx, node);
// Make sure we have two variables or constants
if( lctx->type.dataType.IsReference() ) ConvertToVariableNotIn(lctx, rctx);
if( rctx->type.dataType.IsReference() ) ConvertToVariableNotIn(rctx, lctx);
// Make sure lctx doesn't end up with a variable used in rctx
if( lctx->type.isTemporary && rctx->bc.IsVarUsed(lctx->type.stackOffset) )
{
int offset = AllocateVariableNotIn(lctx->type.dataType, true, false, rctx);
rctx->bc.ExchangeVar(lctx->type.stackOffset, offset);
ReleaseTemporaryVariable(offset, 0);
}
// Math operators
// + - * / % ** += -= *= /= %= **=
if( op == ttPlus || op == ttAddAssign ||
op == ttMinus || op == ttSubAssign ||
op == ttStar || op == ttMulAssign ||
op == ttSlash || op == ttDivAssign ||
op == ttPercent || op == ttModAssign ||
op == ttStarStar || op == ttPowAssign )
{
CompileMathOperator(node, lctx, rctx, ctx, op);
return 0;
}
// Bitwise operators
// << >> >>> & | ^ <<= >>= >>>= &= |= ^=
if( op == ttAmp || op == ttAndAssign ||
op == ttBitOr || op == ttOrAssign ||
op == ttBitXor || op == ttXorAssign ||
op == ttBitShiftLeft || op == ttShiftLeftAssign ||
op == ttBitShiftRight || op == ttShiftRightLAssign ||
op == ttBitShiftRightArith || op == ttShiftRightAAssign )
{
CompileBitwiseOperator(node, lctx, rctx, ctx, op);
return 0;
}
// Comparison operators
// == != < > <= >=
if( op == ttEqual || op == ttNotEqual ||
op == ttLessThan || op == ttLessThanOrEqual ||
op == ttGreaterThan || op == ttGreaterThanOrEqual )
{
CompileComparisonOperator(node, lctx, rctx, ctx, op);
return 0;
}
// Boolean operators
// && || ^^
if( op == ttAnd || op == ttOr || op == ttXor )
{
CompileBooleanOperator(node, lctx, rctx, ctx, op);
return 0;
}
}
asASSERT(false);
return -1;
}
void asCCompiler::ConvertToTempVariableNotIn(asCExprContext *ctx, asCExprContext *exclude)
{
int l = int(reservedVariables.GetLength());
if( exclude ) exclude->bc.GetVarsUsed(reservedVariables);
ConvertToTempVariable(ctx);
reservedVariables.SetLength(l);
}
void asCCompiler::ConvertToTempVariable(asCExprContext *ctx)
{
// This is only used for primitive types and null handles
asASSERT( ctx->type.dataType.IsPrimitive() || ctx->type.dataType.IsNullHandle() );
ConvertToVariable(ctx);
if( !ctx->type.isTemporary )
{
if( ctx->type.dataType.IsPrimitive() )
{
// Copy the variable to a temporary variable
int offset = AllocateVariable(ctx->type.dataType, true);
if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->bc.InstrW_W(asBC_CpyVtoV4, offset, ctx->type.stackOffset);
else
ctx->bc.InstrW_W(asBC_CpyVtoV8, offset, ctx->type.stackOffset);
ctx->type.SetVariable(ctx->type.dataType, offset, true);
}
else
{
// We should never get here
asASSERT(false);
}
}
}
void asCCompiler::ConvertToVariable(asCExprContext *ctx)
{
// We should never get here while the context is still an unprocessed property accessor
asASSERT(ctx->property_get == 0 && ctx->property_set == 0);
int offset;
if( !ctx->type.isVariable &&
(ctx->type.dataType.IsObjectHandle() ||
(ctx->type.dataType.IsObject() && ctx->type.dataType.SupportHandles())) )
{
offset = AllocateVariable(ctx->type.dataType, true);
if( ctx->type.IsNullConstant() )
{
if( ctx->bc.GetLastInstr() == asBC_PshNull )
ctx->bc.Instr(asBC_PopPtr); // Pop the null constant pushed onto the stack
ctx->bc.InstrSHORT(asBC_ClrVPtr, (short)offset);
}
else
{
Dereference(ctx, true);
// Copy the object handle to a variable
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
if( ctx->type.dataType.IsFuncdef() )
ctx->bc.InstrPTR(asBC_REFCPY, &engine->functionBehaviours);
else
ctx->bc.InstrPTR(asBC_REFCPY, ctx->type.dataType.GetTypeInfo());
ctx->bc.Instr(asBC_PopPtr);
}
// As this is an object the reference must be placed on the stack
ctx->bc.InstrSHORT(asBC_PSF, (short)offset);
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
ctx->type.SetVariable(ctx->type.dataType, offset, true);
ctx->type.dataType.MakeHandle(true);
ctx->type.dataType.MakeReference(true);
}
else if( (!ctx->type.isVariable || ctx->type.dataType.IsReference()) &&
ctx->type.dataType.IsPrimitive() )
{
if( ctx->type.isConstant )
{
offset = AllocateVariable(ctx->type.dataType, true);
if( ctx->type.dataType.GetSizeInMemoryBytes() == 1 )
ctx->bc.InstrSHORT_B(asBC_SetV1, (short)offset, ctx->type.byteValue);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 2 )
ctx->bc.InstrSHORT_W(asBC_SetV2, (short)offset, ctx->type.wordValue);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 4 )
ctx->bc.InstrSHORT_DW(asBC_SetV4, (short)offset, ctx->type.dwordValue);
else
ctx->bc.InstrSHORT_QW(asBC_SetV8, (short)offset, ctx->type.qwordValue);
ctx->type.SetVariable(ctx->type.dataType, offset, true);
return;
}
else
{
asASSERT(ctx->type.dataType.IsPrimitive());
asASSERT(ctx->type.dataType.IsReference());
ctx->type.dataType.MakeReference(false);
offset = AllocateVariable(ctx->type.dataType, true);
// Read the value from the address in the register directly into the variable
if( ctx->type.dataType.GetSizeInMemoryBytes() == 1 )
ctx->bc.InstrSHORT(asBC_RDR1, (short)offset);
else if( ctx->type.dataType.GetSizeInMemoryBytes() == 2 )
ctx->bc.InstrSHORT(asBC_RDR2, (short)offset);
else if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->bc.InstrSHORT(asBC_RDR4, (short)offset);
else
ctx->bc.InstrSHORT(asBC_RDR8, (short)offset);
}
ReleaseTemporaryVariable(ctx->type, &ctx->bc);
ctx->type.SetVariable(ctx->type.dataType, offset, true);
}
}
void asCCompiler::ConvertToVariableNotIn(asCExprContext *ctx, asCExprContext *exclude)
{
int l = int(reservedVariables.GetLength());
if( exclude ) exclude->bc.GetVarsUsed(reservedVariables);
ConvertToVariable(ctx);
reservedVariables.SetLength(l);
}
void asCCompiler::ImplicitConvObjectToBestMathType(asCExprContext *ctx, asCScriptNode *node)
{
asCArray<int> funcs;
asCObjectType *ot = ctx->type.dataType.GetTypeInfo()->CastToObjectType();
if( ot )
{
for( unsigned int n = 0; n < ot->methods.GetLength(); n++ )
{
// Consider only implicit casts
asCScriptFunction *func = engine->scriptFunctions[ot->methods[n]];
if( func->name == "opImplConv" &&
func->returnType.IsPrimitive() &&
func->parameterTypes.GetLength() == 0 )
funcs.PushLast(ot->methods[n]);
}
// Use the one with the highest precision
const eTokenType match[10] = {ttDouble, ttFloat, ttInt64, ttUInt64, ttInt, ttUInt, ttInt16, ttUInt16, ttInt8, ttUInt8};
while( funcs.GetLength() > 1 )
{
eTokenType returnType = builder->GetFunctionDescription(funcs[0])->returnType.GetTokenType();
int value1 = 11, value2 = 11;
for( asUINT i = 0; i < 10; i++ )
{
if( returnType == match[i] )
{
value1 = i;
break;
}
}
for( asUINT n = 1; n < funcs.GetLength(); n++ )
{
returnType = builder->GetFunctionDescription(funcs[n])->returnType.GetTokenType();
for( asUINT i = 0; i < 10; i++ )
{
if( returnType == match[i] )
{
value2 = i;
break;
}
}
if( value2 >= value1 )
{
// Remove this and continue searching
funcs.RemoveIndexUnordered(n--);
}
else
{
// Remove the first, and start over
funcs.RemoveIndexUnordered(0);
break;
}
}
}
// Do the conversion
if( funcs.GetLength() )
ImplicitConvObjectToPrimitive(ctx, builder->GetFunctionDescription(funcs[0])->returnType, node, asIC_IMPLICIT_CONV);
}
}
void asCCompiler::CompileMathOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType op)
{
// TODO: If a constant is only using 32bits, then a 32bit operation is preferred
// TODO: clean up: This initial part is identical to CompileComparisonOperator. Make a common function out of it
// If either operand is a non-primitive then use the primitive type
if( !lctx->type.dataType.IsPrimitive() )
{
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
ImplicitConvObjectToBestMathType(lctx, node);
reservedVariables.SetLength(l);
}
if( !rctx->type.dataType.IsPrimitive() )
{
int l = int(reservedVariables.GetLength());
lctx->bc.GetVarsUsed(reservedVariables);
ImplicitConvObjectToBestMathType(rctx, node);
reservedVariables.SetLength(l);
}
// Both types must now be primitives. Implicitly convert them so they match
asCDataType to;
if( lctx->type.dataType.IsDoubleType() || rctx->type.dataType.IsDoubleType() )
to.SetTokenType(ttDouble);
else if( lctx->type.dataType.IsFloatType() || rctx->type.dataType.IsFloatType() )
to.SetTokenType(ttFloat);
else if( lctx->type.dataType.GetSizeInMemoryDWords() == 2 || rctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
// Convert to int64 if both are signed or if one is non-constant and signed
if( (lctx->type.dataType.IsIntegerType() && !lctx->type.isConstant) ||
(rctx->type.dataType.IsIntegerType() && !rctx->type.isConstant) )
to.SetTokenType(ttInt64);
else if( lctx->type.dataType.IsUnsignedType() || rctx->type.dataType.IsUnsignedType() )
to.SetTokenType(ttUInt64);
else
to.SetTokenType(ttInt64);
}
else
{
// Convert to int32 if both are signed or if one is non-constant and signed
if( (lctx->type.dataType.IsIntegerType() && !lctx->type.isConstant) ||
(rctx->type.dataType.IsIntegerType() && !rctx->type.isConstant) )
to.SetTokenType(ttInt);
else if( lctx->type.dataType.IsUnsignedType() || rctx->type.dataType.IsUnsignedType() )
to.SetTokenType(ttUInt);
else
to.SetTokenType(ttInt);
}
// If doing an operation with double constant and float variable, the constant should be converted to float
if( (lctx->type.isConstant && lctx->type.dataType.IsDoubleType() && !rctx->type.isConstant && rctx->type.dataType.IsFloatType()) ||
(rctx->type.isConstant && rctx->type.dataType.IsDoubleType() && !lctx->type.isConstant && lctx->type.dataType.IsFloatType()) )
to.SetTokenType(ttFloat);
if( op == ttUnrecognizedToken )
op = node->tokenType;
// If integer division is disabled, convert to floating-point
if( engine->ep.disableIntegerDivision &&
(op == ttSlash || op == ttDivAssign) &&
(to.IsIntegerType() || to.IsUnsignedType()) )
{
// Use double to avoid losing precision when dividing with 32bit ints
// For 64bit ints there is unfortunately no greater type so with those
// there is still a risk of loosing precision
to.SetTokenType(ttDouble);
}
// Do the actual conversion
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
lctx->bc.GetVarsUsed(reservedVariables);
if( lctx->type.dataType.IsReference() )
ConvertToVariable(lctx);
if( rctx->type.dataType.IsReference() )
ConvertToVariable(rctx);
if( to.IsPrimitive() )
{
// ttStarStar allows an integer, right-hand operand and a double
// left-hand operand.
if( (op == ttStarStar || op == ttPowAssign) &&
lctx->type.dataType.IsDoubleType() &&
(rctx->type.dataType.IsIntegerType() ||
rctx->type.dataType.IsUnsignedType()) )
{
to.SetTokenType(ttInt);
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV, true);
to.SetTokenType(ttDouble);
}
else
{
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV, true);
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV, true);
}
}
reservedVariables.SetLength(l);
// Verify that the conversion was successful
if( !lctx->type.dataType.IsIntegerType() &&
!lctx->type.dataType.IsUnsignedType() &&
!lctx->type.dataType.IsFloatType() &&
!lctx->type.dataType.IsDoubleType() )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_MATH_TYPE, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.SetDummy();
return;
}
if( !rctx->type.dataType.IsIntegerType() &&
!rctx->type.dataType.IsUnsignedType() &&
!rctx->type.dataType.IsFloatType() &&
!rctx->type.dataType.IsDoubleType() )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_MATH_TYPE, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ctx->type.SetDummy();
return;
}
bool isConstant = lctx->type.isConstant && rctx->type.isConstant;
// Verify if we are dividing with a constant zero
if( rctx->type.isConstant && rctx->type.qwordValue == 0 &&
(op == ttSlash || op == ttDivAssign ||
op == ttPercent || op == ttModAssign) )
{
Error(TXT_DIVIDE_BY_ZERO, node);
}
if( !isConstant )
{
ConvertToVariableNotIn(lctx, rctx);
ConvertToVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
if( op == ttAddAssign || op == ttSubAssign ||
op == ttMulAssign || op == ttDivAssign ||
op == ttModAssign || op == ttPowAssign )
{
// Merge the operands in the different order so that they are evaluated correctly
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
// We must not process the deferred parameters yet, as
// it may overwrite the lvalue kept in the register
}
else
{
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
ProcessDeferredParams(ctx);
}
asEBCInstr instruction = asBC_ADDi;
if( lctx->type.dataType.IsIntegerType() ||
lctx->type.dataType.IsUnsignedType() )
{
if( lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
if( op == ttPlus || op == ttAddAssign )
instruction = asBC_ADDi;
else if( op == ttMinus || op == ttSubAssign )
instruction = asBC_SUBi;
else if( op == ttStar || op == ttMulAssign )
instruction = asBC_MULi;
else if( op == ttSlash || op == ttDivAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_DIVi;
else
instruction = asBC_DIVu;
}
else if( op == ttPercent || op == ttModAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_MODi;
else
instruction = asBC_MODu;
}
else if( op == ttStarStar || op == ttPowAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_POWi;
else
instruction = asBC_POWu;
}
}
else
{
if( op == ttPlus || op == ttAddAssign )
instruction = asBC_ADDi64;
else if( op == ttMinus || op == ttSubAssign )
instruction = asBC_SUBi64;
else if( op == ttStar || op == ttMulAssign )
instruction = asBC_MULi64;
else if( op == ttSlash || op == ttDivAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_DIVi64;
else
instruction = asBC_DIVu64;
}
else if( op == ttPercent || op == ttModAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_MODi64;
else
instruction = asBC_MODu64;
}
else if( op == ttStarStar || op == ttPowAssign )
{
if( lctx->type.dataType.IsIntegerType() )
instruction = asBC_POWi64;
else
instruction = asBC_POWu64;
}
}
}
else if( lctx->type.dataType.IsFloatType() )
{
if( op == ttPlus || op == ttAddAssign )
instruction = asBC_ADDf;
else if( op == ttMinus || op == ttSubAssign )
instruction = asBC_SUBf;
else if( op == ttStar || op == ttMulAssign )
instruction = asBC_MULf;
else if( op == ttSlash || op == ttDivAssign )
instruction = asBC_DIVf;
else if( op == ttPercent || op == ttModAssign )
instruction = asBC_MODf;
else if( op == ttStarStar || op == ttPowAssign )
instruction = asBC_POWf;
}
else if( lctx->type.dataType.IsDoubleType() )
{
if( rctx->type.dataType.IsIntegerType() )
{
asASSERT(rctx->type.dataType.GetSizeInMemoryDWords() == 1);
if( op == ttStarStar || op == ttPowAssign )
instruction = asBC_POWdi;
else
asASSERT(false); // Should not be possible
}
else
{
if( op == ttPlus || op == ttAddAssign )
instruction = asBC_ADDd;
else if( op == ttMinus || op == ttSubAssign )
instruction = asBC_SUBd;
else if( op == ttStar || op == ttMulAssign )
instruction = asBC_MULd;
else if( op == ttSlash || op == ttDivAssign )
instruction = asBC_DIVd;
else if( op == ttPercent || op == ttModAssign )
instruction = asBC_MODd;
else if( op == ttStarStar || op == ttPowAssign )
instruction = asBC_POWd;
}
}
else
{
// Shouldn't be possible
asASSERT(false);
}
// Do the operation
int a = AllocateVariable(lctx->type.dataType, true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W_W(instruction, a, b, c);
ctx->type.SetVariable(lctx->type.dataType, a, true);
}
else
{
// Both values are constants
if( lctx->type.dataType.IsIntegerType() ||
lctx->type.dataType.IsUnsignedType() )
{
if( lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
int v = 0;
if( op == ttPlus )
v = lctx->type.intValue + rctx->type.intValue;
else if( op == ttMinus )
v = lctx->type.intValue - rctx->type.intValue;
else if( op == ttStar )
v = lctx->type.intValue * rctx->type.intValue;
else if( op == ttSlash )
{
// TODO: Should probably report an error, rather than silently convert the value to 0
if( rctx->type.intValue == 0 || (rctx->type.intValue == -1 && lctx->type.dwordValue == 0x80000000) )
v = 0;
else
if( lctx->type.dataType.IsIntegerType() )
v = lctx->type.intValue / rctx->type.intValue;
else
v = lctx->type.dwordValue / rctx->type.dwordValue;
}
else if( op == ttPercent )
{
// TODO: Should probably report an error, rather than silently convert the value to 0
if( rctx->type.intValue == 0 || (rctx->type.intValue == -1 && lctx->type.dwordValue == 0x80000000) )
v = 0;
else
if( lctx->type.dataType.IsIntegerType() )
v = lctx->type.intValue % rctx->type.intValue;
else
v = lctx->type.dwordValue % rctx->type.dwordValue;
}
else if( op == ttStarStar )
{
bool isOverflow;
if( lctx->type.dataType.IsIntegerType() )
v = as_powi(lctx->type.intValue, rctx->type.intValue, isOverflow);
else
v = as_powu(lctx->type.dwordValue, rctx->type.dwordValue, isOverflow);
if( isOverflow )
Error(TXT_POW_OVERFLOW, node);
}
ctx->type.SetConstantDW(lctx->type.dataType, v);
// If the right value is greater than the left value in a minus operation, then we need to convert the type to int
if( lctx->type.dataType.GetTokenType() == ttUInt && op == ttMinus && lctx->type.intValue < rctx->type.intValue )
ctx->type.dataType.SetTokenType(ttInt);
}
else
{
asQWORD v = 0;
if( op == ttPlus )
v = lctx->type.qwordValue + rctx->type.qwordValue;
else if( op == ttMinus )
v = lctx->type.qwordValue - rctx->type.qwordValue;
else if( op == ttStar )
v = lctx->type.qwordValue * rctx->type.qwordValue;
else if( op == ttSlash )
{
// TODO: Should probably report an error, rather than silently convert the value to 0
if( rctx->type.qwordValue == 0 || (rctx->type.qwordValue == asQWORD(-1) && lctx->type.qwordValue == (asQWORD(1)<<63)) )
v = 0;
else
if( lctx->type.dataType.IsIntegerType() )
v = asINT64(lctx->type.qwordValue) / asINT64(rctx->type.qwordValue);
else
v = lctx->type.qwordValue / rctx->type.qwordValue;
}
else if( op == ttPercent )
{
// TODO: Should probably report an error, rather than silently convert the value to 0
if( rctx->type.qwordValue == 0 || (rctx->type.qwordValue == asQWORD(-1) && lctx->type.qwordValue == (asQWORD(1)<<63)) )
v = 0;
else
if( lctx->type.dataType.IsIntegerType() )
v = asINT64(lctx->type.qwordValue) % asINT64(rctx->type.qwordValue);
else
v = lctx->type.qwordValue % rctx->type.qwordValue;
}
else if( op == ttStarStar )
{
bool isOverflow;
if( lctx->type.dataType.IsIntegerType() )
v = as_powi64(asINT64(lctx->type.qwordValue), asINT64(rctx->type.qwordValue), isOverflow);
else
v = as_powu64(lctx->type.qwordValue, rctx->type.qwordValue, isOverflow);
if( isOverflow )
Error(TXT_POW_OVERFLOW, node);
}
ctx->type.SetConstantQW(lctx->type.dataType, v);
// If the right value is greater than the left value in a minus operation, then we need to convert the type to int
if( lctx->type.dataType.GetTokenType() == ttUInt64 && op == ttMinus && lctx->type.qwordValue < rctx->type.qwordValue )
ctx->type.dataType.SetTokenType(ttInt64);
}
}
else if( lctx->type.dataType.IsFloatType() )
{
float v = 0.0f;
if( op == ttPlus )
v = lctx->type.floatValue + rctx->type.floatValue;
else if( op == ttMinus )
v = lctx->type.floatValue - rctx->type.floatValue;
else if( op == ttStar )
v = lctx->type.floatValue * rctx->type.floatValue;
else if( op == ttSlash )
{
if( rctx->type.floatValue == 0 )
v = 0;
else
v = lctx->type.floatValue / rctx->type.floatValue;
}
else if( op == ttPercent )
{
if( rctx->type.floatValue == 0 )
v = 0;
else
v = fmodf(lctx->type.floatValue, rctx->type.floatValue);
}
else if( op == ttStarStar )
{
v = powf(lctx->type.floatValue, rctx->type.floatValue);
if( v == HUGE_VAL )
Error(TXT_POW_OVERFLOW, node);
}
ctx->type.SetConstantF(lctx->type.dataType, v);
}
else if( lctx->type.dataType.IsDoubleType() )
{
double v = 0.0;
if( rctx->type.dataType.IsIntegerType() )
{
asASSERT(rctx->type.dataType.GetSizeInMemoryDWords() == 1);
if( op == ttStarStar || op == ttPowAssign )
{
v = pow(lctx->type.doubleValue, rctx->type.intValue);
if( v == HUGE_VAL )
Error(TXT_POW_OVERFLOW, node);
}
else
asASSERT(false); // Should not be possible
}
else
{
if( op == ttPlus )
v = lctx->type.doubleValue + rctx->type.doubleValue;
else if( op == ttMinus )
v = lctx->type.doubleValue - rctx->type.doubleValue;
else if( op == ttStar )
v = lctx->type.doubleValue * rctx->type.doubleValue;
else if( op == ttSlash )
{
if( rctx->type.doubleValue == 0 )
v = 0;
else
v = lctx->type.doubleValue / rctx->type.doubleValue;
}
else if( op == ttPercent )
{
if( rctx->type.doubleValue == 0 )
v = 0;
else
v = fmod(lctx->type.doubleValue, rctx->type.doubleValue);
}
else if( op == ttStarStar )
{
v = pow(lctx->type.doubleValue, rctx->type.doubleValue);
if( v == HUGE_VAL )
Error(TXT_POW_OVERFLOW, node);
}
}
ctx->type.SetConstantD(lctx->type.dataType, v);
}
else
{
// Shouldn't be possible
asASSERT(false);
}
}
}
void asCCompiler::CompileBitwiseOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType op)
{
// TODO: If a constant is only using 32bits, then a 32bit operation is preferred
if( op == ttUnrecognizedToken )
op = node->tokenType;
if( op == ttAmp || op == ttAndAssign ||
op == ttBitOr || op == ttOrAssign ||
op == ttBitXor || op == ttXorAssign )
{
// Convert left hand operand to integer if it's not already one
asCDataType to;
if( lctx->type.dataType.GetSizeInMemoryDWords() == 2 ||
rctx->type.dataType.GetSizeInMemoryDWords() == 2 )
to.SetTokenType(ttInt64);
else
to.SetTokenType(ttInt);
// Do the actual conversion (keep sign/unsigned if possible)
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
if( lctx->type.dataType.IsUnsignedType() )
to.SetTokenType( to.GetSizeOnStackDWords() == 1 ? ttUInt : ttUInt64 );
else
to.SetTokenType( to.GetSizeOnStackDWords() == 1 ? ttInt : ttInt64 );
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV, true);
reservedVariables.SetLength(l);
// Verify that the conversion was successful
if( lctx->type.dataType != to )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
// Convert right hand operand to same size as left hand
l = int(reservedVariables.GetLength());
lctx->bc.GetVarsUsed(reservedVariables);
if( rctx->type.dataType.IsUnsignedType() )
to.SetTokenType( to.GetSizeOnStackDWords() == 1 ? ttUInt : ttUInt64 );
else
to.SetTokenType( to.GetSizeOnStackDWords() == 1 ? ttInt : ttInt64 );
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV, true);
reservedVariables.SetLength(l);
if( rctx->type.dataType != to )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
bool isConstant = lctx->type.isConstant && rctx->type.isConstant;
if( !isConstant )
{
ConvertToVariableNotIn(lctx, rctx);
ConvertToVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
if( op == ttAndAssign || op == ttOrAssign || op == ttXorAssign )
{
// Compound assignments execute the right hand value first
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
}
else
{
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
}
ProcessDeferredParams(ctx);
asEBCInstr instruction = asBC_BAND;
if( lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
if( op == ttAmp || op == ttAndAssign )
instruction = asBC_BAND;
else if( op == ttBitOr || op == ttOrAssign )
instruction = asBC_BOR;
else if( op == ttBitXor || op == ttXorAssign )
instruction = asBC_BXOR;
}
else
{
if( op == ttAmp || op == ttAndAssign )
instruction = asBC_BAND64;
else if( op == ttBitOr || op == ttOrAssign )
instruction = asBC_BOR64;
else if( op == ttBitXor || op == ttXorAssign )
instruction = asBC_BXOR64;
}
// Do the operation
int a = AllocateVariable(lctx->type.dataType, true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W_W(instruction, a, b, c);
ctx->type.SetVariable(lctx->type.dataType, a, true);
}
else
{
if( lctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
asQWORD v = 0;
if( op == ttAmp )
v = lctx->type.qwordValue & rctx->type.qwordValue;
else if( op == ttBitOr )
v = lctx->type.qwordValue | rctx->type.qwordValue;
else if( op == ttBitXor )
v = lctx->type.qwordValue ^ rctx->type.qwordValue;
// Remember the result
ctx->type.SetConstantQW(lctx->type.dataType, v);
}
else
{
asDWORD v = 0;
if( op == ttAmp )
v = lctx->type.dwordValue & rctx->type.dwordValue;
else if( op == ttBitOr )
v = lctx->type.dwordValue | rctx->type.dwordValue;
else if( op == ttBitXor )
v = lctx->type.dwordValue ^ rctx->type.dwordValue;
// Remember the result
ctx->type.SetConstantDW(lctx->type.dataType, v);
}
}
}
else if( op == ttBitShiftLeft || op == ttShiftLeftAssign ||
op == ttBitShiftRight || op == ttShiftRightLAssign ||
op == ttBitShiftRightArith || op == ttShiftRightAAssign )
{
// Don't permit object to primitive conversion, since we don't know which integer type is the correct one
if( lctx->type.dataType.IsObject() )
{
asCString str;
str.Format(TXT_ILLEGAL_OPERATION_ON_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
// Set an integer value and allow the compiler to continue
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttInt, true), 0);
return;
}
// Convert left hand operand to integer if it's not already one
asCDataType to = lctx->type.dataType;
if( lctx->type.dataType.IsUnsignedType() &&
lctx->type.dataType.GetSizeInMemoryBytes() < 4 )
{
// Upgrade to 32bit
to = asCDataType::CreatePrimitive(ttUInt, false);
}
else if( !lctx->type.dataType.IsUnsignedType() )
{
if (lctx->type.dataType.GetSizeInMemoryDWords() == 2)
to = asCDataType::CreatePrimitive(ttInt64, false);
else
to = asCDataType::CreatePrimitive(ttInt, false);
}
// Do the actual conversion
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV, true);
reservedVariables.SetLength(l);
// Verify that the conversion was successful
if( lctx->type.dataType != to )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
// Right operand must be 32bit uint
l = int(reservedVariables.GetLength());
lctx->bc.GetVarsUsed(reservedVariables);
ImplicitConversion(rctx, asCDataType::CreatePrimitive(ttUInt, true), node, asIC_IMPLICIT_CONV, true);
reservedVariables.SetLength(l);
if( !rctx->type.dataType.IsUnsignedType() )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), "uint");
Error(str, node);
}
bool isConstant = lctx->type.isConstant && rctx->type.isConstant;
if( !isConstant )
{
ConvertToVariableNotIn(lctx, rctx);
ConvertToVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
if( op == ttShiftLeftAssign || op == ttShiftRightLAssign || op == ttShiftRightAAssign )
{
// Compound assignments execute the right hand value first
MergeExprBytecode(ctx, rctx);
MergeExprBytecode(ctx, lctx);
}
else
{
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
}
ProcessDeferredParams(ctx);
asEBCInstr instruction = asBC_BSLL;
if( lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
if( op == ttBitShiftLeft || op == ttShiftLeftAssign )
instruction = asBC_BSLL;
else if( op == ttBitShiftRight || op == ttShiftRightLAssign )
instruction = asBC_BSRL;
else if( op == ttBitShiftRightArith || op == ttShiftRightAAssign )
instruction = asBC_BSRA;
}
else
{
if( op == ttBitShiftLeft || op == ttShiftLeftAssign )
instruction = asBC_BSLL64;
else if( op == ttBitShiftRight || op == ttShiftRightLAssign )
instruction = asBC_BSRL64;
else if( op == ttBitShiftRightArith || op == ttShiftRightAAssign )
instruction = asBC_BSRA64;
}
// Do the operation
int a = AllocateVariable(lctx->type.dataType, true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W_W(instruction, a, b, c);
ctx->type.SetVariable(lctx->type.dataType, a, true);
}
else
{
if( lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
asDWORD v = 0;
if( op == ttBitShiftLeft )
v = lctx->type.dwordValue << rctx->type.dwordValue;
else if( op == ttBitShiftRight )
v = lctx->type.dwordValue >> rctx->type.dwordValue;
else if( op == ttBitShiftRightArith )
v = lctx->type.intValue >> rctx->type.dwordValue;
ctx->type.SetConstantDW(lctx->type.dataType, v);
}
else
{
asQWORD v = 0;
if( op == ttBitShiftLeft )
v = lctx->type.qwordValue << rctx->type.dwordValue;
else if( op == ttBitShiftRight )
v = lctx->type.qwordValue >> rctx->type.dwordValue;
else if( op == ttBitShiftRightArith )
v = asINT64(lctx->type.qwordValue) >> rctx->type.dwordValue;
ctx->type.SetConstantQW(lctx->type.dataType, v);
}
}
}
}
void asCCompiler::CompileComparisonOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType op)
{
// Both operands must be of the same type
// If either operand is a non-primitive then first convert them to the best number type
if( !lctx->type.dataType.IsPrimitive() )
{
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
ImplicitConvObjectToBestMathType(lctx, node);
reservedVariables.SetLength(l);
}
if( !rctx->type.dataType.IsPrimitive() )
{
int l = int(reservedVariables.GetLength());
lctx->bc.GetVarsUsed(reservedVariables);
ImplicitConvObjectToBestMathType(rctx, node);
reservedVariables.SetLength(l);
}
// Implicitly convert the operands to matching types
asCDataType to;
if( lctx->type.dataType.IsDoubleType() || rctx->type.dataType.IsDoubleType() )
to.SetTokenType(ttDouble);
else if( lctx->type.dataType.IsFloatType() || rctx->type.dataType.IsFloatType() )
to.SetTokenType(ttFloat);
else if( lctx->type.dataType.GetSizeInMemoryDWords() == 2 || rctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
// Convert to int64 if both are signed or if one is non-constant and signed
if( (lctx->type.dataType.IsIntegerType() && !lctx->type.isConstant) ||
(rctx->type.dataType.IsIntegerType() && !rctx->type.isConstant) )
to.SetTokenType(ttInt64);
else if( lctx->type.dataType.IsUnsignedType() || rctx->type.dataType.IsUnsignedType() )
to.SetTokenType(ttUInt64);
else
to.SetTokenType(ttInt64);
}
else
{
// Convert to int32 if both are signed or if one is non-constant and signed
if( (lctx->type.dataType.IsIntegerType() && !lctx->type.isConstant) ||
(rctx->type.dataType.IsIntegerType() && !rctx->type.isConstant) )
to.SetTokenType(ttInt);
else if( lctx->type.dataType.IsUnsignedType() || rctx->type.dataType.IsUnsignedType() )
to.SetTokenType(ttUInt);
else if( lctx->type.dataType.IsBooleanType() || rctx->type.dataType.IsBooleanType() )
to.SetTokenType(ttBool);
else
to.SetTokenType(ttInt);
}
// If doing an operation with double constant and float variable, the constant should be converted to float
if( (lctx->type.isConstant && lctx->type.dataType.IsDoubleType() && !rctx->type.isConstant && rctx->type.dataType.IsFloatType()) ||
(rctx->type.isConstant && rctx->type.dataType.IsDoubleType() && !lctx->type.isConstant && lctx->type.dataType.IsFloatType()) )
to.SetTokenType(ttFloat);
asASSERT( to.GetTokenType() != ttUnrecognizedToken );
// Do we have a mismatch between the sign of the operand?
bool signMismatch = false;
for( int n = 0; !signMismatch && n < 2; n++ )
{
asCExprContext *opCtx = n ? rctx : lctx;
if( opCtx->type.dataType.IsUnsignedType() != to.IsUnsignedType() )
{
// We have a mismatch, unless the value is a literal constant and the conversion won't affect its value
signMismatch = true;
if( opCtx->type.isConstant )
{
if( opCtx->type.dataType.GetTokenType() == ttUInt64 || opCtx->type.dataType.GetTokenType() == ttInt64 )
{
if( !(opCtx->type.qwordValue & (asQWORD(1)<<63)) )
signMismatch = false;
}
else
{
if( !(opCtx->type.dwordValue & (1<<31)) )
signMismatch = false;
}
// It's not necessary to check for floats or double, because if
// it was then the types for the conversion will never be unsigned
}
}
}
// Check for signed/unsigned mismatch
if( signMismatch )
Warning(TXT_SIGNED_UNSIGNED_MISMATCH, node);
// Attempt to resolve ambiguous enumerations
if( lctx->type.dataType.IsEnumType() && rctx->enumValue != "" )
ImplicitConversion(rctx, lctx->type.dataType, node, asIC_IMPLICIT_CONV);
else if( rctx->type.dataType.IsEnumType() && lctx->enumValue != "" )
ImplicitConversion(lctx, rctx->type.dataType, node, asIC_IMPLICIT_CONV);
// Do the actual conversion
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
if( lctx->type.dataType.IsReference() )
ConvertToVariable(lctx);
if( rctx->type.dataType.IsReference() )
ConvertToVariable(rctx);
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV);
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV);
reservedVariables.SetLength(l);
// Verify that the conversion was successful
bool ok = true;
if( !lctx->type.dataType.IsEqualExceptConst(to) )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ok = false;
}
if( !rctx->type.dataType.IsEqualExceptConst(to) )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
ok = false;
}
if( !ok )
{
// It wasn't possible to get two valid operands, so we just return
// a boolean result and let the compiler continue.
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), true);
return;
}
bool isConstant = lctx->type.isConstant && rctx->type.isConstant;
if( op == ttUnrecognizedToken )
op = node->tokenType;
if( !isConstant )
{
if( to.IsBooleanType() )
{
if( op == ttEqual || op == ttNotEqual )
{
// Must convert to temporary variable, because we are changing the value before comparison
ConvertToTempVariableNotIn(lctx, rctx);
ConvertToTempVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
// Make sure they are equal if not false
lctx->bc.InstrWORD(asBC_NOT, lctx->type.stackOffset);
rctx->bc.InstrWORD(asBC_NOT, rctx->type.stackOffset);
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
ProcessDeferredParams(ctx);
int a = AllocateVariable(asCDataType::CreatePrimitive(ttBool, true), true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
if( op == ttEqual )
{
ctx->bc.InstrW_W(asBC_CMPi,b,c);
ctx->bc.Instr(asBC_TZ);
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)a);
}
else if( op == ttNotEqual )
{
ctx->bc.InstrW_W(asBC_CMPi,b,c);
ctx->bc.Instr(asBC_TNZ);
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)a);
}
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, true), a, true);
}
else
{
// TODO: Use TXT_ILLEGAL_OPERATION_ON
Error(TXT_ILLEGAL_OPERATION, node);
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), 0);
}
}
else
{
ConvertToVariableNotIn(lctx, rctx);
ConvertToVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
ProcessDeferredParams(ctx);
asEBCInstr iCmp = asBC_CMPi, iT = asBC_TZ;
if( lctx->type.dataType.IsIntegerType() && lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
iCmp = asBC_CMPi;
else if( lctx->type.dataType.IsUnsignedType() && lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
iCmp = asBC_CMPu;
else if( lctx->type.dataType.IsIntegerType() && lctx->type.dataType.GetSizeInMemoryDWords() == 2 )
iCmp = asBC_CMPi64;
else if( lctx->type.dataType.IsUnsignedType() && lctx->type.dataType.GetSizeInMemoryDWords() == 2 )
iCmp = asBC_CMPu64;
else if( lctx->type.dataType.IsFloatType() )
iCmp = asBC_CMPf;
else if( lctx->type.dataType.IsDoubleType() )
iCmp = asBC_CMPd;
else
asASSERT(false);
if( op == ttEqual )
iT = asBC_TZ;
else if( op == ttNotEqual )
iT = asBC_TNZ;
else if( op == ttLessThan )
iT = asBC_TS;
else if( op == ttLessThanOrEqual )
iT = asBC_TNP;
else if( op == ttGreaterThan )
iT = asBC_TP;
else if( op == ttGreaterThanOrEqual )
iT = asBC_TNS;
int a = AllocateVariable(asCDataType::CreatePrimitive(ttBool, true), true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W(iCmp, b, c);
ctx->bc.Instr(iT);
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)a);
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, true), a, true);
}
}
else
{
if( to.IsBooleanType() )
{
if( op == ttEqual || op == ttNotEqual )
{
// Make sure they are equal if not false
if( lctx->type.dwordValue != 0 ) lctx->type.dwordValue = VALUE_OF_BOOLEAN_TRUE;
if( rctx->type.dwordValue != 0 ) rctx->type.dwordValue = VALUE_OF_BOOLEAN_TRUE;
asDWORD v = 0;
if( op == ttEqual )
{
v = lctx->type.intValue - rctx->type.intValue;
if( v == 0 ) v = VALUE_OF_BOOLEAN_TRUE; else v = 0;
}
else if( op == ttNotEqual )
{
v = lctx->type.intValue - rctx->type.intValue;
if( v != 0 ) v = VALUE_OF_BOOLEAN_TRUE; else v = 0;
}
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), v);
}
else
{
// TODO: Use TXT_ILLEGAL_OPERATION_ON
Error(TXT_ILLEGAL_OPERATION, node);
}
}
else
{
int i = 0;
if( lctx->type.dataType.IsIntegerType() && lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
int v = lctx->type.intValue - rctx->type.intValue;
if( v < 0 ) i = -1;
if( v > 0 ) i = 1;
}
else if( lctx->type.dataType.IsUnsignedType() && lctx->type.dataType.GetSizeInMemoryDWords() == 1 )
{
asDWORD v1 = lctx->type.dwordValue;
asDWORD v2 = rctx->type.dwordValue;
if( v1 < v2 ) i = -1;
if( v1 > v2 ) i = 1;
}
else if( lctx->type.dataType.IsIntegerType() && lctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
asINT64 v = asINT64(lctx->type.qwordValue) - asINT64(rctx->type.qwordValue);
if( v < 0 ) i = -1;
if( v > 0 ) i = 1;
}
else if( lctx->type.dataType.IsUnsignedType() && lctx->type.dataType.GetSizeInMemoryDWords() == 2 )
{
asQWORD v1 = lctx->type.qwordValue;
asQWORD v2 = rctx->type.qwordValue;
if( v1 < v2 ) i = -1;
if( v1 > v2 ) i = 1;
}
else if( lctx->type.dataType.IsFloatType() )
{
float v = lctx->type.floatValue - rctx->type.floatValue;
if( v < 0 ) i = -1;
if( v > 0 ) i = 1;
}
else if( lctx->type.dataType.IsDoubleType() )
{
double v = lctx->type.doubleValue - rctx->type.doubleValue;
if( v < 0 ) i = -1;
if( v > 0 ) i = 1;
}
if( op == ttEqual )
i = (i == 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
else if( op == ttNotEqual )
i = (i != 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
else if( op == ttLessThan )
i = (i < 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
else if( op == ttLessThanOrEqual )
i = (i <= 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
else if( op == ttGreaterThan )
i = (i > 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
else if( op == ttGreaterThanOrEqual )
i = (i >= 0 ? VALUE_OF_BOOLEAN_TRUE : 0);
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), i);
}
}
}
void asCCompiler::PushVariableOnStack(asCExprContext *ctx, bool asReference)
{
// Put the result on the stack
if( asReference )
{
ctx->bc.InstrSHORT(asBC_PSF, ctx->type.stackOffset);
ctx->type.dataType.MakeReference(true);
}
else
{
if( ctx->type.dataType.GetSizeInMemoryDWords() == 1 )
ctx->bc.InstrSHORT(asBC_PshV4, ctx->type.stackOffset);
else
ctx->bc.InstrSHORT(asBC_PshV8, ctx->type.stackOffset);
}
}
void asCCompiler::CompileBooleanOperator(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType op)
{
// Both operands must be booleans
asCDataType to;
to.SetTokenType(ttBool);
// Do the actual conversion
int l = int(reservedVariables.GetLength());
rctx->bc.GetVarsUsed(reservedVariables);
lctx->bc.GetVarsUsed(reservedVariables);
// Allow value types to be converted to bool using 'bool opImplConv()'
if( lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV);
if( rctx->type.dataType.GetTypeInfo() && (rctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_VALUE) )
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV);
reservedVariables.SetLength(l);
// Verify that the conversion was successful
if( !lctx->type.dataType.IsBooleanType() )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), "bool");
Error(str, node);
// Force the conversion to allow compilation to proceed
lctx->type.SetConstantB(asCDataType::CreatePrimitive(ttBool, true), true);
}
if( !rctx->type.dataType.IsBooleanType() )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), "bool");
Error(str, node);
// Force the conversion to allow compilation to proceed
rctx->type.SetConstantB(asCDataType::CreatePrimitive(ttBool, true), true);
}
bool isConstant = lctx->type.isConstant && rctx->type.isConstant;
ctx->type.Set(asCDataType::CreatePrimitive(ttBool, true));
// What kind of operator is it?
if( op == ttUnrecognizedToken )
op = node->tokenType;
if( op == ttXor )
{
if( !isConstant )
{
// Must convert to temporary variable, because we are changing the value before comparison
ConvertToTempVariableNotIn(lctx, rctx);
ConvertToTempVariableNotIn(rctx, lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
// Make sure they are equal if not false
lctx->bc.InstrWORD(asBC_NOT, lctx->type.stackOffset);
rctx->bc.InstrWORD(asBC_NOT, rctx->type.stackOffset);
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
ProcessDeferredParams(ctx);
int a = AllocateVariable(ctx->type.dataType, true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W_W(asBC_BXOR,a,b,c);
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, true), a, true);
}
else
{
// Make sure they are equal if not false
#if AS_SIZEOF_BOOL == 1
if( lctx->type.byteValue != 0 ) lctx->type.byteValue = VALUE_OF_BOOLEAN_TRUE;
if( rctx->type.byteValue != 0 ) rctx->type.byteValue = VALUE_OF_BOOLEAN_TRUE;
asBYTE v = 0;
v = lctx->type.byteValue - rctx->type.byteValue;
if( v != 0 ) v = VALUE_OF_BOOLEAN_TRUE; else v = 0;
ctx->type.isConstant = true;
ctx->type.byteValue = v;
#else
if( lctx->type.dwordValue != 0 ) lctx->type.dwordValue = VALUE_OF_BOOLEAN_TRUE;
if( rctx->type.dwordValue != 0 ) rctx->type.dwordValue = VALUE_OF_BOOLEAN_TRUE;
asDWORD v = 0;
v = lctx->type.intValue - rctx->type.intValue;
if( v != 0 ) v = VALUE_OF_BOOLEAN_TRUE; else v = 0;
ctx->type.isConstant = true;
ctx->type.dwordValue = v;
#endif
}
}
else if( op == ttAnd ||
op == ttOr )
{
if( !isConstant )
{
// If or-operator and first value is 1 the second value shouldn't be calculated
// if and-operator and first value is 0 the second value shouldn't be calculated
ConvertToVariable(lctx);
ReleaseTemporaryVariable(lctx->type, &lctx->bc);
MergeExprBytecode(ctx, lctx);
int offset = AllocateVariable(asCDataType::CreatePrimitive(ttBool, false), true);
int label1 = nextLabel++;
int label2 = nextLabel++;
ctx->bc.InstrSHORT(asBC_CpyVtoR4, lctx->type.stackOffset);
ctx->bc.Instr(asBC_ClrHi);
if( op == ttAnd )
{
ctx->bc.InstrDWORD(asBC_JNZ, label1);
ctx->bc.InstrW_DW(asBC_SetV4, (asWORD)offset, 0);
ctx->bc.InstrINT(asBC_JMP, label2);
}
else if( op == ttOr )
{
ctx->bc.InstrDWORD(asBC_JZ, label1);
#if AS_SIZEOF_BOOL == 1
ctx->bc.InstrSHORT_B(asBC_SetV1, (short)offset, VALUE_OF_BOOLEAN_TRUE);
#else
ctx->bc.InstrSHORT_DW(asBC_SetV4, (short)offset, VALUE_OF_BOOLEAN_TRUE);
#endif
ctx->bc.InstrINT(asBC_JMP, label2);
}
ctx->bc.Label((short)label1);
ConvertToVariable(rctx);
ReleaseTemporaryVariable(rctx->type, &rctx->bc);
rctx->bc.InstrW_W(asBC_CpyVtoV4, offset, rctx->type.stackOffset);
MergeExprBytecode(ctx, rctx);
ctx->bc.Label((short)label2);
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, false), offset, true);
}
else
{
#if AS_SIZEOF_BOOL == 1
asBYTE v = 0;
if( op == ttAnd )
v = lctx->type.byteValue && rctx->type.byteValue;
else if( op == ttOr )
v = lctx->type.byteValue || rctx->type.byteValue;
// Remember the result
ctx->type.isConstant = true;
ctx->type.byteValue = v;
#else
asDWORD v = 0;
if( op == ttAnd )
v = lctx->type.dwordValue && rctx->type.dwordValue;
else if( op == ttOr )
v = lctx->type.dwordValue || rctx->type.dwordValue;
// Remember the result
ctx->type.isConstant = true;
ctx->type.dwordValue = v;
#endif
}
}
}
void asCCompiler::CompileOperatorOnHandles(asCScriptNode *node, asCExprContext *lctx, asCExprContext *rctx, asCExprContext *ctx, eTokenType opToken)
{
// Process the property accessor as get
ProcessPropertyGetAccessor(lctx, node);
ProcessPropertyGetAccessor(rctx, node);
DetermineSingleFunc(lctx, node);
DetermineSingleFunc(rctx, node);
// Make sure lctx doesn't end up with a variable used in rctx
if( lctx->type.isTemporary && rctx->bc.IsVarUsed(lctx->type.stackOffset) )
{
asCArray<int> vars;
rctx->bc.GetVarsUsed(vars);
int offset = AllocateVariable(lctx->type.dataType, true);
rctx->bc.ExchangeVar(lctx->type.stackOffset, offset);
ReleaseTemporaryVariable(offset, 0);
}
if( opToken == ttUnrecognizedToken )
opToken = node->tokenType;
// Warn if not both operands are explicit handles or null handles
if( (opToken == ttEqual || opToken == ttNotEqual) &&
((!(lctx->type.isExplicitHandle || lctx->type.IsNullConstant()) && !(lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_IMPLICIT_HANDLE))) ||
(!(rctx->type.isExplicitHandle || rctx->type.IsNullConstant()) && !(rctx->type.dataType.GetTypeInfo() && (rctx->type.dataType.GetTypeInfo()->flags & asOBJ_IMPLICIT_HANDLE)))) )
{
Warning(TXT_HANDLE_COMPARISON, node);
}
// If one of the operands is a value type used as handle, we should look for the opEquals method
if( ((lctx->type.dataType.GetTypeInfo() && (lctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE)) ||
(rctx->type.dataType.GetTypeInfo() && (rctx->type.dataType.GetTypeInfo()->flags & asOBJ_ASHANDLE))) &&
(opToken == ttEqual || opToken == ttIs ||
opToken == ttNotEqual || opToken == ttNotIs) )
{
// TODO: Should evaluate which of the two have the best match. If both have equal match, the first version should be used
// Find the matching opEquals method
int r = CompileOverloadedDualOperator2(node, "opEquals", lctx, rctx, ctx, true, asCDataType::CreatePrimitive(ttBool, false));
if( r == 0 )
{
// Try again by switching the order of the operands
r = CompileOverloadedDualOperator2(node, "opEquals", rctx, lctx, ctx, true, asCDataType::CreatePrimitive(ttBool, false));
}
if( r == 1 )
{
if( opToken == ttNotEqual || opToken == ttNotIs )
ctx->bc.InstrSHORT(asBC_NOT, ctx->type.stackOffset);
// Success, don't continue
return;
}
else if( r == 0 )
{
// Couldn't find opEquals method
Error(TXT_NO_APPROPRIATE_OPEQUALS, node);
}
// Compiler error, don't continue
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), true);
return;
}
// Implicitly convert null to the other type
asCDataType to;
if( lctx->type.IsNullConstant() )
to = rctx->type.dataType;
else if( rctx->type.IsNullConstant() )
to = lctx->type.dataType;
else
{
// Find a common base type
asCExprContext tmp(engine);
tmp.type = rctx->type;
ImplicitConversion(&tmp, lctx->type.dataType, 0, asIC_IMPLICIT_CONV, false);
if( tmp.type.dataType.GetTypeInfo() == lctx->type.dataType.GetTypeInfo() )
to = lctx->type.dataType;
else
to = rctx->type.dataType;
// Assume handle-to-const as it is not possible to convert handle-to-const to handle-to-non-const
to.MakeHandleToConst(true);
}
// Need to pop the value if it is a null constant
if( lctx->type.IsNullConstant() )
lctx->bc.Instr(asBC_PopPtr);
if( rctx->type.IsNullConstant() )
rctx->bc.Instr(asBC_PopPtr);
// Convert both sides to explicit handles
to.MakeHandle(true);
to.MakeReference(false);
if( !to.IsObjectHandle() )
{
// Compiler error, don't continue
Error(TXT_OPERANDS_MUST_BE_HANDLES, node);
ctx->type.SetConstantDW(asCDataType::CreatePrimitive(ttBool, true), true);
return;
}
// Do the conversion
ImplicitConversion(lctx, to, node, asIC_IMPLICIT_CONV);
ImplicitConversion(rctx, to, node, asIC_IMPLICIT_CONV);
// Both operands must be of the same type
// Verify that the conversion was successful
if( !lctx->type.dataType.IsEqualExceptConst(to) )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, lctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
if( !rctx->type.dataType.IsEqualExceptConst(to) )
{
asCString str;
str.Format(TXT_NO_CONVERSION_s_TO_s, rctx->type.dataType.Format(outFunc->nameSpace).AddressOf(), to.Format(outFunc->nameSpace).AddressOf());
Error(str, node);
}
// Make sure it really is handles that are being compared
if( !lctx->type.dataType.IsObjectHandle() )
{
Error(TXT_OPERANDS_MUST_BE_HANDLES, node);
}
ctx->type.Set(asCDataType::CreatePrimitive(ttBool, true));
if( opToken == ttEqual || opToken == ttNotEqual || opToken == ttIs || opToken == ttNotIs )
{
// Make sure handles received as parameters by reference are copied to a local variable before the
// asBC_CmpPtr, so we don't end up comparing the reference to the handle instead of the handle itself
if( lctx->type.isVariable && !lctx->type.isTemporary && lctx->type.stackOffset <= 0 )
lctx->type.isVariable = false;
if( rctx->type.isVariable && !rctx->type.isTemporary && rctx->type.stackOffset <= 0 )
rctx->type.isVariable = false;
// TODO: runtime optimize: don't do REFCPY if not necessary
ConvertToVariableNotIn(lctx, rctx);
ConvertToVariable(rctx);
// Pop the pointers from the stack as they will not be used
lctx->bc.Instr(asBC_PopPtr);
rctx->bc.Instr(asBC_PopPtr);
MergeExprBytecode(ctx, lctx);
MergeExprBytecode(ctx, rctx);
int a = AllocateVariable(ctx->type.dataType, true);
int b = lctx->type.stackOffset;
int c = rctx->type.stackOffset;
ctx->bc.InstrW_W(asBC_CmpPtr, b, c);
if( opToken == ttEqual || opToken == ttIs )
ctx->bc.Instr(asBC_TZ);
else if( opToken == ttNotEqual || opToken == ttNotIs )
ctx->bc.Instr(asBC_TNZ);
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)a);
ctx->type.SetVariable(asCDataType::CreatePrimitive(ttBool, true), a, true);
ReleaseTemporaryVariable(lctx->type, &ctx->bc);
ReleaseTemporaryVariable(rctx->type, &ctx->bc);
ProcessDeferredParams(ctx);
}
else
{
// TODO: Use TXT_ILLEGAL_OPERATION_ON
Error(TXT_ILLEGAL_OPERATION, node);
}
}
void asCCompiler::PerformFunctionCall(int funcId, asCExprContext *ctx, bool isConstructor, asCArray<asCExprContext*> *args, asCObjectType *objType, bool useVariable, int varOffset, int funcPtrVar)
{
asCScriptFunction *descr = builder->GetFunctionDescription(funcId);
// A shared object may not call non-shared functions
if( outFunc->IsShared() && !descr->IsShared() )
{
asCString msg;
msg.Format(TXT_SHARED_CANNOT_CALL_NON_SHARED_FUNC_s, descr->GetDeclarationStr().AddressOf());
Error(msg, ctx->exprNode);
}
// Check if the function is private or protected
if( descr->isPrivate && descr->GetObjectType() != outFunc->GetObjectType() )
{
asCString msg;
msg.Format(TXT_PRIVATE_METHOD_CALL_s, descr->GetDeclarationStr().AddressOf());
Error(msg, ctx->exprNode);
}
else if( descr->isProtected &&
!(descr->GetObjectType() == outFunc->GetObjectType() ||
(outFunc->GetObjectType() && outFunc->GetObjectType()->DerivesFrom(descr->GetObjectType()))) )
{
asCString msg;
msg.Format(TXT_PROTECTED_METHOD_CALL_s, descr->GetDeclarationStr().AddressOf());
Error(msg, ctx->exprNode);
}
int argSize = descr->GetSpaceNeededForArguments();
// If we're calling a class method we must make sure the object is guaranteed to stay
// alive throughout the call by holding on to a reference in a local variable. This must
// be done for any methods that return references, and any calls on script objects.
// Application registered objects are assumed to know to keep themselves alive even
// if the method doesn't return a refernce.
if( descr->objectType &&
(ctx->type.dataType.IsObjectHandle() || ctx->type.dataType.SupportHandles()) &&
(descr->returnType.IsReference() || (ctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_SCRIPT_OBJECT)) &&
!(ctx->type.isVariable || ctx->type.isTemporary) &&
!(ctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_SCOPED) &&
!(ctx->type.dataType.GetTypeInfo()->GetFlags() & asOBJ_ASHANDLE) )
{
// TODO: runtime optimize: Avoid this for global variables, by storing a reference to the global variable once in a
// local variable and then refer to the same for each call. An alias for the global variable
// should be stored in the variable scope so that the compiler can find it. For loops and
// scopes that will always be executed, i.e. non-if scopes the alias should be stored in the
// higher scope to increase the probability of re-use.
// TODO: runtime optimize: This can be avoided for local variables (non-handles) as they have a well defined life time
int tempRef = AllocateVariable(ctx->type.dataType, true);
ctx->bc.InstrSHORT(asBC_PSF, (short)tempRef);
ctx->bc.InstrPTR(asBC_REFCPY, ctx->type.dataType.GetTypeInfo());
// Add the release of this reference as a deferred expression
asSDeferredParam deferred;
deferred.origExpr = 0;
deferred.argInOutFlags = asTM_INREF;
deferred.argNode = 0;
deferred.argType.SetVariable(ctx->type.dataType, tempRef, true);
ctx->deferredParams.PushLast(deferred);
// Forget the current type
ctx->type.SetDummy();
}
// Check if there is a need to add a hidden pointer for when the function returns an object by value
if( descr->DoesReturnOnStack() && !useVariable )
{
useVariable = true;
varOffset = AllocateVariable(descr->returnType, true);
// Push the pointer to the pre-allocated space for the return value
ctx->bc.InstrSHORT(asBC_PSF, short(varOffset));
if( descr->objectType )
{
// The object pointer is already on the stack, but should be the top
// one, so we need to swap the pointers in order to get the correct
ctx->bc.Instr(asBC_SwapPtr);
}
}
if( isConstructor )
{
// Sometimes the value types are allocated on the heap,
// which is when this way of constructing them is used.
asASSERT(useVariable == false);
if( (objType->flags & asOBJ_TEMPLATE) )
{
asASSERT( descr->funcType == asFUNC_SCRIPT );
// Find the id of the real constructor and not the generated stub
asUINT id = 0;
asDWORD *bc = descr->scriptData->byteCode.AddressOf();
while( bc )
{
if( (*(asBYTE*)bc) == asBC_CALLSYS )
{
id = asBC_INTARG(bc);
break;
}
bc += asBCTypeSize[asBCInfo[*(asBYTE*)bc].type];
}
asASSERT( id );
ctx->bc.InstrPTR(asBC_OBJTYPE, objType);
ctx->bc.Alloc(asBC_ALLOC, objType, id, argSize + AS_PTR_SIZE + AS_PTR_SIZE);
}
else
ctx->bc.Alloc(asBC_ALLOC, objType, descr->id, argSize+AS_PTR_SIZE);
// The instruction has already moved the returned object to the variable
ctx->type.Set(asCDataType::CreatePrimitive(ttVoid, false));
ctx->type.isLValue = false;
// Clean up arguments
if( args )
AfterFunctionCall(funcId, *args, ctx, false);
ProcessDeferredParams(ctx);
return;
}
else
{
if( descr->objectType )
argSize += AS_PTR_SIZE;
// If the function returns an object by value the address of the location
// where the value should be stored is passed as an argument too
if( descr->DoesReturnOnStack() )
argSize += AS_PTR_SIZE;
// TODO: runtime optimize: If it is known that a class method cannot be overridden the call
// should be made with asBC_CALL as it is faster. Examples where this
// is known is for example finalled methods where the class doesn't derive
// from any other, or even non-finalled methods but where it is known
// at compile time the true type of the object. The first should be
// quite easy to determine, but the latter will be quite complex and possibly
// not worth it.
if( descr->funcType == asFUNC_IMPORTED )
ctx->bc.Call(asBC_CALLBND , descr->id, argSize);
// TODO: Maybe we need two different byte codes
else if( descr->funcType == asFUNC_INTERFACE || descr->funcType == asFUNC_VIRTUAL )
ctx->bc.Call(asBC_CALLINTF, descr->id, argSize);
else if( descr->funcType == asFUNC_SCRIPT )
ctx->bc.Call(asBC_CALL , descr->id, argSize);
else if( descr->funcType == asFUNC_SYSTEM )
{
// Check if we can use the faster asBC_Thiscall1 instruction, i.e. one of
// type &obj::func(int)
// type &obj::func(uint)
if( descr->GetObjectType() && descr->returnType.IsReference() &&
descr->parameterTypes.GetLength() == 1 &&
(descr->parameterTypes[0].IsIntegerType() || descr->parameterTypes[0].IsUnsignedType()) &&
descr->parameterTypes[0].GetSizeInMemoryBytes() == 4 &&
!descr->parameterTypes[0].IsReference() )
ctx->bc.Call(asBC_Thiscall1, descr->id, argSize);
else
ctx->bc.Call(asBC_CALLSYS , descr->id, argSize);
}
else if( descr->funcType == asFUNC_FUNCDEF )
ctx->bc.CallPtr(asBC_CallPtr, funcPtrVar, argSize);
}
if( (descr->returnType.IsObject() || descr->returnType.IsFuncdef()) && !descr->returnType.IsReference() )
{
int returnOffset = 0;
asCExprValue tmpExpr = ctx->type;
if( descr->DoesReturnOnStack() )
{
asASSERT( useVariable );
// The variable was allocated before the function was called
returnOffset = varOffset;
ctx->type.SetVariable(descr->returnType, returnOffset, true);
// The variable was initialized by the function, so we need to mark it as initialized here
ctx->bc.ObjInfo(varOffset, asOBJ_INIT);
}
else
{
if( useVariable )
{
// Use the given variable
returnOffset = varOffset;
ctx->type.SetVariable(descr->returnType, returnOffset, false);
}
else
{
// Allocate a temporary variable for the returned object
// The returned object will actually be allocated on the heap, so
// we must force the allocation of the variable to do the same
returnOffset = AllocateVariable(descr->returnType, true, !descr->returnType.IsObjectHandle());
ctx->type.SetVariable(descr->returnType, returnOffset, true);
}
// Move the pointer from the object register to the temporary variable
ctx->bc.InstrSHORT(asBC_STOREOBJ, (short)returnOffset);
}
ReleaseTemporaryVariable(tmpExpr, &ctx->bc);
ctx->type.dataType.MakeReference(IsVariableOnHeap(returnOffset));
ctx->type.isLValue = false; // It is a reference, but not an lvalue
// Clean up arguments
if( args )
AfterFunctionCall(funcId, *args, ctx, false);
ProcessDeferredParams(ctx);
ctx->bc.InstrSHORT(asBC_PSF, (short)returnOffset);
}
else if( descr->returnType.IsReference() )
{
asASSERT(useVariable == false);
// We cannot clean up the arguments yet, because the
// reference might be pointing to one of them.
if( args )
AfterFunctionCall(funcId, *args, ctx, true);
// Do not process the output parameters yet, because it
// might invalidate the returned reference
// If the context holds a variable that needs cleanup
// store it as a deferred parameter so it will be cleaned up
// afterwards.
if( ctx->type.isTemporary )
{
asSDeferredParam defer;
defer.argNode = 0;
defer.argType = ctx->type;
defer.argInOutFlags = asTM_INOUTREF;
defer.origExpr = 0;
ctx->deferredParams.PushLast(defer);
}
ctx->type.Set(descr->returnType);
if( !descr->returnType.IsPrimitive() )
{
ctx->bc.Instr(asBC_PshRPtr);
if( descr->returnType.IsObject() &&
!descr->returnType.IsObjectHandle() )
{
// We are getting the pointer to the object
// not a pointer to a object variable
ctx->type.dataType.MakeReference(false);
}
}
// A returned reference can be used as lvalue
ctx->type.isLValue = true;
}
else
{
asASSERT(useVariable == false);
asCExprValue tmpExpr = ctx->type;
if( descr->returnType.GetSizeInMemoryBytes() )
{
// Allocate a temporary variable to hold the value, but make sure
// the temporary variable isn't used in any of the deferred arguments
int l = int(reservedVariables.GetLength());
for( asUINT n = 0; args && n < args->GetLength(); n++ )
{
asCExprContext *expr = (*args)[n]->origExpr;
if( expr )
expr->bc.GetVarsUsed(reservedVariables);
}
int offset = AllocateVariable(descr->returnType, true);
reservedVariables.SetLength(l);
ctx->type.SetVariable(descr->returnType, offset, true);
// Move the value from the return register to the variable
if( descr->returnType.GetSizeOnStackDWords() == 1 )
ctx->bc.InstrSHORT(asBC_CpyRtoV4, (short)offset);
else if( descr->returnType.GetSizeOnStackDWords() == 2 )
ctx->bc.InstrSHORT(asBC_CpyRtoV8, (short)offset);
}
else
ctx->type.Set(descr->returnType);
ReleaseTemporaryVariable(tmpExpr, &ctx->bc);
ctx->type.isLValue = false;
// Clean up arguments
if( args )
AfterFunctionCall(funcId, *args, ctx, false);
ProcessDeferredParams(ctx);
}
}
// This only merges the bytecode, but doesn't modify the type of the final context
void asCCompiler::MergeExprBytecode(asCExprContext *before, asCExprContext *after)
{
before->bc.AddCode(&after->bc);
for( asUINT n = 0; n < after->deferredParams.GetLength(); n++ )
{
before->deferredParams.PushLast(after->deferredParams[n]);
after->deferredParams[n].origExpr = 0;
}
after->deferredParams.SetLength(0);
}
// This merges both bytecode and the type of the final context
void asCCompiler::MergeExprBytecodeAndType(asCExprContext *before, asCExprContext *after)
{
MergeExprBytecode(before, after);
before->Merge(after);
}
void asCCompiler::FilterConst(asCArray<int> &funcs, bool removeConst)
{
if( funcs.GetLength() == 0 ) return;
// This is only done for object methods
asCScriptFunction *desc = builder->GetFunctionDescription(funcs[0]);
if( desc->objectType == 0 ) return;
// Check if there are any non-const matches
asUINT n;
bool foundNonConst = false;
for( n = 0; n < funcs.GetLength(); n++ )
{
desc = builder->GetFunctionDescription(funcs[n]);
if( desc->isReadOnly != removeConst )
{
foundNonConst = true;
break;
}
}
if( foundNonConst )
{
// Remove all const methods
for( n = 0; n < funcs.GetLength(); n++ )
{
desc = builder->GetFunctionDescription(funcs[n]);
if( desc->isReadOnly == removeConst )
{
if( n == funcs.GetLength() - 1 )
funcs.PopLast();
else
funcs[n] = funcs.PopLast();
n--;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
asCExprValue::asCExprValue()
{
isTemporary = false;
stackOffset = 0;
isConstant = false;
isVariable = false;
isExplicitHandle = false;
qwordValue = 0;
isLValue = false;
isRefToLocal = false;
}
void asCExprValue::Set(const asCDataType &dt)
{
dataType = dt;
isTemporary = false;
stackOffset = 0;
isConstant = false;
isVariable = false;
isExplicitHandle = false;
qwordValue = 0;
isLValue = false;
isRefToLocal = false;
}
void asCExprValue::SetVariable(const asCDataType &in_dt, int in_stackOffset, bool in_isTemporary)
{
Set(in_dt);
this->isVariable = true;
this->isTemporary = in_isTemporary;
this->stackOffset = (short)in_stackOffset;
}
void asCExprValue::SetConstantQW(const asCDataType &dt, asQWORD value)
{
Set(dt);
isConstant = true;
qwordValue = value;
}
void asCExprValue::SetConstantDW(const asCDataType &dt, asDWORD value)
{
Set(dt);
isConstant = true;
dwordValue = value;
}
void asCExprValue::SetConstantB(const asCDataType &dt, asBYTE value)
{
Set(dt);
isConstant = true;
byteValue = value;
}
void asCExprValue::SetConstantF(const asCDataType &dt, float value)
{
Set(dt);
isConstant = true;
floatValue = value;
}
void asCExprValue::SetConstantD(const asCDataType &dt, double value)
{
Set(dt);
isConstant = true;
doubleValue = value;
}
void asCExprValue::SetUndefinedFuncHandle(asCScriptEngine *engine)
{
// This is used for when the expression evaluates to a
// function, but it is not yet known exactly which. The
// owner expression will hold the name of the function
// to determine the exact function when the signature is
// known.
Set(asCDataType::CreateObjectHandle(&engine->functionBehaviours, true));
isConstant = true;
isExplicitHandle = false;
qwordValue = 1; // Set to a different value than 0 to differentiate from null constant
isLValue = false;
}
bool asCExprValue::IsUndefinedFuncHandle() const
{
if (isConstant == false) return false;
if (qwordValue == 0) return false;
if (isLValue) return false;
if (dataType.GetTypeInfo() == 0) return false;
if (dataType.GetTypeInfo()->name != "$func") return false;
if (dataType.IsFuncdef()) return false;
return true;
}
void asCExprValue::SetNullConstant()
{
Set(asCDataType::CreateNullHandle());
isConstant = true;
isExplicitHandle = false;
qwordValue = 0;
isLValue = false;
}
bool asCExprValue::IsNullConstant() const
{
// We can't check the actual object type, because the null constant may have been cast to another type
if (isConstant && dataType.IsObjectHandle() && qwordValue == 0)
return true;
return false;
}
void asCExprValue::SetVoid()
{
Set(asCDataType::CreatePrimitive(ttVoid, false));
isLValue = false;
isConstant = true;
}
bool asCExprValue::IsVoid() const
{
if (dataType.GetTokenType() == ttVoid)
return true;
return false;
}
void asCExprValue::SetDummy()
{
SetConstantQW(asCDataType::CreatePrimitive(ttInt, true), 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////
asCExprContext::asCExprContext(asCScriptEngine *engine) : bc(engine)
{
property_arg = 0;
Clear();
}
asCExprContext::~asCExprContext()
{
if (property_arg)
asDELETE(property_arg, asCExprContext);
}
void asCExprContext::Clear()
{
bc.ClearAll();
type.Set(asCDataType());
deferredParams.SetLength(0);
if (property_arg)
asDELETE(property_arg, asCExprContext);
property_arg = 0;
exprNode = 0;
origExpr = 0;
property_get = 0;
property_set = 0;
property_const = false;
property_handle = false;
property_ref = false;
methodName = "";
enumValue = "";
isVoidExpression = false;
isCleanArg = false;
}
bool asCExprContext::IsClassMethod() const
{
if (type.dataType.GetTypeInfo() == 0) return false;
if (methodName == "") return false;
if (type.dataType.GetTypeInfo() == &type.dataType.GetTypeInfo()->engine->functionBehaviours) return false;
return true;
}
bool asCExprContext::IsGlobalFunc() const
{
if (type.dataType.GetTypeInfo() == 0) return false;
if (methodName == "") return false;
if (type.dataType.GetTypeInfo() != &type.dataType.GetTypeInfo()->engine->functionBehaviours) return false;
return true;
}
void asCExprContext::SetLambda(asCScriptNode *funcDecl)
{
asASSERT(funcDecl && funcDecl->nodeType == snFunction);
asASSERT(bc.GetLastInstr() == -1);
Clear();
type.SetUndefinedFuncHandle(bc.GetEngine());
exprNode = funcDecl;
}
bool asCExprContext::IsLambda() const
{
if (type.IsUndefinedFuncHandle() && exprNode && exprNode->nodeType == snFunction)
return true;
return false;
}
void asCExprContext::SetVoidExpression()
{
Clear();
type.SetVoid();
isVoidExpression = true;
}
bool asCExprContext::IsVoidExpression() const
{
if (isVoidExpression && type.IsVoid() && exprNode == 0)
return true;
return false;
}
void asCExprContext::Merge(asCExprContext *after)
{
type = after->type;
property_get = after->property_get;
property_set = after->property_set;
property_const = after->property_const;
property_handle = after->property_handle;
property_ref = after->property_ref;
property_arg = after->property_arg;
exprNode = after->exprNode;
methodName = after->methodName;
enumValue = after->enumValue;
isVoidExpression = after->isVoidExpression;
isCleanArg = after->isCleanArg;
after->property_arg = 0;
// Do not copy the origExpr member
}
END_AS_NAMESPACE
#endif // AS_NO_COMPILER
diff --git a/source/angelscript/source/as_scriptengine.cpp b/source/angelscript/source/as_scriptengine.cpp
index 20af74e..1d65587 100644
--- a/source/angelscript/source/as_scriptengine.cpp
+++ b/source/angelscript/source/as_scriptengine.cpp
@@ -1,6383 +1,6383 @@
/*
AngelCode Scripting Library
Copyright (c) 2003-2016 Andreas Jonsson
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
The original version of this library can be located at:
http://www.angelcode.com/angelscript/
Andreas Jonsson
andreas@angelcode.com
*/
//
// as_scriptengine.cpp
//
// The implementation of the script engine interface
//
#include <stdlib.h>
#include "as_config.h"
#include "as_scriptengine.h"
#include "as_builder.h"
#include "as_context.h"
#include "as_string_util.h"
#include "as_tokenizer.h"
#include "as_texts.h"
#include "as_module.h"
#include "as_callfunc.h"
#include "as_generic.h"
#include "as_scriptobject.h"
#include "as_compiler.h"
#include "as_bytecode.h"
#include "as_debug.h"
BEGIN_AS_NAMESPACE
#ifdef AS_PROFILE
// Instantiate the profiler once
CProfiler g_profiler;
#endif
extern "C"
{
AS_API const char * asGetLibraryVersion()
{
#ifdef _DEBUG
return ANGELSCRIPT_VERSION_STRING " DEBUG";
#else
return ANGELSCRIPT_VERSION_STRING;
#endif
}
AS_API const char * asGetLibraryOptions()
{
const char *string = " "
// Options
#ifdef AS_MAX_PORTABILITY
"AS_MAX_PORTABILITY "
#endif
#ifdef AS_DEBUG
"AS_DEBUG "
#endif
#ifdef AS_NO_CLASS_METHODS
"AS_NO_CLASS_METHODS "
#endif
#ifdef AS_USE_DOUBLE_AS_FLOAT
"AS_USE_DOUBLE_AS_FLOAT "
#endif
#ifdef AS_64BIT_PTR
"AS_64BIT_PTR "
#endif
#ifdef AS_NO_THREADS
"AS_NO_THREADS "
#endif
#ifdef AS_NO_ATOMIC
"AS_NO_ATOMIC "
#endif
#ifdef AS_NO_COMPILER
"AS_NO_COMPILER "
#endif
#ifdef AS_NO_MEMBER_INIT
"AS_NO_MEMBER_INIT "
#endif
#ifdef AS_NO_THISCALL_FUNCTOR_METHOD
"AS_NO_THISCALL_FUNCTOR_METHOD "
#endif
#ifdef AS_NO_EXCEPTIONS
"AS_NO_EXCEPTIONS "
#endif
#ifdef WIP_16BYTE_ALIGN
"WIP_16BYTE_ALIGN "
#endif
#ifdef AS_BIG_ENDIAN
"AS_BIG_ENDIAN "
#endif
// Target system
#ifdef AS_WIN
"AS_WIN "
#endif
#ifdef AS_LINUX
"AS_LINUX "
#endif
#ifdef AS_MAC
"AS_MAC "
#endif
#ifdef AS_SUN
"AS_SUN "
#endif
#ifdef AS_BSD
"AS_BSD "
#endif
#ifdef AS_XBOX
"AS_XBOX "
#endif
#ifdef AS_XBOX360
"AS_XBOX360 "
#endif
#ifdef AS_PSP
"AS_PSP "
#endif
#ifdef AS_PS2
"AS_PS2 "
#endif
#ifdef AS_PS3
"AS_PS3 "
#endif
#ifdef AS_PSVITA
"AS_PSVITA "
#endif
#ifdef AS_DC
"AS_DC "
#endif
#ifdef AS_GC
"AS_GC "
#endif
#ifdef AS_WII
"AS_WII "
#endif
#ifdef AS_WIIU
"AS_WIIU "
#endif
#ifdef AS_IPHONE
"AS_IPHONE "
#endif
#ifdef AS_ANDROID
"AS_ANDROID "
#endif
#ifdef AS_HAIKU
"AS_HAIKU "
#endif
#ifdef AS_ILLUMOS
"AS_ILLUMOS "
#endif
#ifdef AS_MARMALADE
"AS_MARMALADE "
#endif
// CPU family
#ifdef AS_PPC
"AS_PPC "
#endif
#ifdef AS_PPC_64
"AS_PPC_64 "
#endif
#ifdef AS_X86
"AS_X86 "
#endif
#ifdef AS_MIPS
"AS_MIPS "
#endif
#ifdef AS_SH4
"AS_SH4 "
#endif
#ifdef AS_XENON
"AS_XENON "
#endif
#ifdef AS_ARM
"AS_ARM "
#endif
#ifdef AS_SOFTFP
"AS_SOFTFP "
#endif
#ifdef AS_X64_GCC
"AS_X64_GCC "
#endif
#ifdef AS_X64_MSVC
"AS_X64_MSVC "
#endif
#ifdef AS_SPARC
"AS_SPARC "
#endif
;
return string;
}
AS_API asIScriptEngine *asCreateScriptEngine(asDWORD version)
{
// Verify the version that the application expects
if( (version/10000) != (ANGELSCRIPT_VERSION/10000) )
return 0;
if( (version/100)%100 != (ANGELSCRIPT_VERSION/100)%100 )
return 0;
if( (version%100) > (ANGELSCRIPT_VERSION%100) )
return 0;
// Verify the size of the types
asASSERT( sizeof(asBYTE) == 1 );
asASSERT( sizeof(asWORD) == 2 );
asASSERT( sizeof(asDWORD) == 4 );
asASSERT( sizeof(asQWORD) == 8 );
asASSERT( sizeof(asPWORD) == sizeof(void*) );
// Verify the boolean type
asASSERT( sizeof(bool) == AS_SIZEOF_BOOL );
asASSERT( true == VALUE_OF_BOOLEAN_TRUE );
// Verify endianess
#ifdef AS_BIG_ENDIAN
asDWORD dw = 0x00010203;
asQWORD qw = ((asQWORD(0x00010203)<<32)|asQWORD(0x04050607));
#else
asDWORD dw = 0x03020100;
// C++ didn't have a standard way of declaring 64bit literal constants until C++11, so
// I'm forced to do it like this to avoid compilers warnings when compiling with the full
// C++ compliance.
asQWORD qw = ((asQWORD(0x07060504)<<32)|asQWORD(0x03020100));
#endif
asASSERT( memcmp("\x00\x01\x02\x03", &dw, 4) == 0 );
asASSERT( memcmp("\x00\x01\x02\x03\x04\x05\x06\x07", &qw, 8) == 0 );
UNUSED_VAR(dw);
UNUSED_VAR(qw);
return asNEW(asCScriptEngine)();
}
} // extern "C"
// interface
int asCScriptEngine::SetEngineProperty(asEEngineProp property, asPWORD value)
{
switch( property )
{
case asEP_ALLOW_UNSAFE_REFERENCES:
ep.allowUnsafeReferences = value ? true : false;
break;
case asEP_OPTIMIZE_BYTECODE:
ep.optimizeByteCode = value ? true : false;
break;
case asEP_COPY_SCRIPT_SECTIONS:
ep.copyScriptSections = value ? true : false;
break;
case asEP_MAX_STACK_SIZE:
if( value == 0 )
{
// Restore default: no limit and initially size 4KB
ep.maximumContextStackSize = 0;
initialContextStackSize = 1024;
}
else
{
// The size is given in bytes, but we only store dwords
ep.maximumContextStackSize = (asUINT)value/4;
if( initialContextStackSize > ep.maximumContextStackSize )
{
initialContextStackSize = ep.maximumContextStackSize;
if( initialContextStackSize == 0 )
initialContextStackSize = 1;
}
}
break;
case asEP_USE_CHARACTER_LITERALS:
ep.useCharacterLiterals = value ? true : false;
break;
case asEP_ALLOW_MULTILINE_STRINGS:
ep.allowMultilineStrings = value ? true : false;
break;
case asEP_ALLOW_IMPLICIT_HANDLE_TYPES:
ep.allowImplicitHandleTypes = value ? true : false;
break;
case asEP_BUILD_WITHOUT_LINE_CUES:
ep.buildWithoutLineCues = value ? true : false;
break;
case asEP_INIT_GLOBAL_VARS_AFTER_BUILD:
ep.initGlobalVarsAfterBuild = value ? true : false;
break;
case asEP_REQUIRE_ENUM_SCOPE:
ep.requireEnumScope = value ? true : false;
break;
case asEP_SCRIPT_SCANNER:
if( value <= 1 )
ep.scanner = (int)value;
else
return asINVALID_ARG;
break;
case asEP_INCLUDE_JIT_INSTRUCTIONS:
ep.includeJitInstructions = value ? true : false;
break;
case asEP_STRING_ENCODING:
if( value <= 1 )
ep.stringEncoding = (int)value;
else
return asINVALID_ARG;
break;
case asEP_PROPERTY_ACCESSOR_MODE:
if( value <= 2 )
ep.propertyAccessorMode = (int)value;
else
return asINVALID_ARG;
break;
case asEP_EXPAND_DEF_ARRAY_TO_TMPL:
ep.expandDefaultArrayToTemplate = value ? true : false;
break;
case asEP_AUTO_GARBAGE_COLLECT:
ep.autoGarbageCollect = value ? true : false;
break;
case asEP_DISALLOW_GLOBAL_VARS:
ep.disallowGlobalVars = value ? true : false;
break;
case asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT:
ep.alwaysImplDefaultConstruct = value ? true : false;
break;
case asEP_COMPILER_WARNINGS:
if( value <= 2 )
ep.compilerWarnings = (int)value;
else
return asINVALID_ARG;
break;
case asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE:
ep.disallowValueAssignForRefType = value ? true : false;
break;
case asEP_ALTER_SYNTAX_NAMED_ARGS:
if( value <= 2 )
ep.alterSyntaxNamedArgs = (int)value;
else
return asINVALID_ARG;
break;
case asEP_DISABLE_INTEGER_DIVISION:
ep.disableIntegerDivision = value ? true : false;
break;
case asEP_DISALLOW_EMPTY_LIST_ELEMENTS:
ep.disallowEmptyListElements = value ? true : false;
break;
case asEP_PRIVATE_PROP_AS_PROTECTED:
ep.privatePropAsProtected = value ? true : false;
break;
case asEP_ALLOW_UNICODE_IDENTIFIERS:
ep.allowUnicodeIdentifiers = value ? true : false;
break;
default:
return asINVALID_ARG;
}
return asSUCCESS;
}
// interface
asPWORD asCScriptEngine::GetEngineProperty(asEEngineProp property) const
{
switch( property )
{
case asEP_ALLOW_UNSAFE_REFERENCES:
return ep.allowUnsafeReferences;
case asEP_OPTIMIZE_BYTECODE:
return ep.optimizeByteCode;
case asEP_COPY_SCRIPT_SECTIONS:
return ep.copyScriptSections;
case asEP_MAX_STACK_SIZE:
return ep.maximumContextStackSize*4;
case asEP_USE_CHARACTER_LITERALS:
return ep.useCharacterLiterals;
case asEP_ALLOW_MULTILINE_STRINGS:
return ep.allowMultilineStrings;
case asEP_ALLOW_IMPLICIT_HANDLE_TYPES:
return ep.allowImplicitHandleTypes;
case asEP_BUILD_WITHOUT_LINE_CUES:
return ep.buildWithoutLineCues;
case asEP_INIT_GLOBAL_VARS_AFTER_BUILD:
return ep.initGlobalVarsAfterBuild;
case asEP_REQUIRE_ENUM_SCOPE:
return ep.requireEnumScope;
case asEP_SCRIPT_SCANNER:
return ep.scanner;
case asEP_INCLUDE_JIT_INSTRUCTIONS:
return ep.includeJitInstructions;
case asEP_STRING_ENCODING:
return ep.stringEncoding;
case asEP_PROPERTY_ACCESSOR_MODE:
return ep.propertyAccessorMode;
case asEP_EXPAND_DEF_ARRAY_TO_TMPL:
return ep.expandDefaultArrayToTemplate;
case asEP_AUTO_GARBAGE_COLLECT:
return ep.autoGarbageCollect;
case asEP_DISALLOW_GLOBAL_VARS:
return ep.disallowGlobalVars;
case asEP_ALWAYS_IMPL_DEFAULT_CONSTRUCT:
return ep.alwaysImplDefaultConstruct;
case asEP_COMPILER_WARNINGS:
return ep.compilerWarnings;
case asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE:
return ep.disallowValueAssignForRefType;
case asEP_ALTER_SYNTAX_NAMED_ARGS:
return ep.alterSyntaxNamedArgs;
case asEP_DISABLE_INTEGER_DIVISION:
return ep.disableIntegerDivision;
case asEP_DISALLOW_EMPTY_LIST_ELEMENTS:
return ep.disallowEmptyListElements;
case asEP_PRIVATE_PROP_AS_PROTECTED:
return ep.privatePropAsProtected;
case asEP_ALLOW_UNICODE_IDENTIFIERS:
return ep.allowUnicodeIdentifiers;
default:
return 0;
}
UNREACHABLE_RETURN;
}
// interface
asIScriptFunction *asCScriptEngine::CreateDelegate(asIScriptFunction *func, void *obj)
{
if( func == 0 || obj == 0 )
return 0;
// The function must be a class method
asITypeInfo *type = func->GetObjectType();
if( type == 0 )
return 0;
// The object type must allow handles
if( (type->GetFlags() & asOBJ_REF) == 0 || (type->GetFlags() & (asOBJ_SCOPED | asOBJ_NOHANDLE)) )
return 0;
// Create the delegate the same way it would be created by the scripts
return AS_NAMESPACE_QUALIFIER CreateDelegate(reinterpret_cast<asCScriptFunction*>(func), obj);
}
asCScriptEngine::asCScriptEngine()
{
asCThreadManager::Prepare(0);
shuttingDown = false;
inDestructor = false;
// Engine properties
{
ep.allowUnsafeReferences = false;
ep.optimizeByteCode = true;
ep.copyScriptSections = true;
ep.maximumContextStackSize = 0; // no limit
ep.useCharacterLiterals = false;
ep.allowMultilineStrings = false;
ep.allowImplicitHandleTypes = false;
// TODO: optimize: Maybe this should be turned off by default? If a debugger is not used
// then this is just slowing down the execution.
ep.buildWithoutLineCues = false;
ep.initGlobalVarsAfterBuild = true;
ep.requireEnumScope = false;
ep.scanner = 1; // utf8. 0 = ascii
ep.includeJitInstructions = false;
ep.stringEncoding = 0; // utf8. 1 = utf16
ep.propertyAccessorMode = 2; // 0 = disable, 1 = app registered only, 2 = app and script created
ep.expandDefaultArrayToTemplate = false;
ep.autoGarbageCollect = true;
ep.disallowGlobalVars = false;
ep.alwaysImplDefaultConstruct = false;
ep.compilerWarnings = 1; // 0 = no warnings, 1 = warning, 2 = treat as error
// TODO: 3.0.0: disallowValueAssignForRefType should be true by default
ep.disallowValueAssignForRefType = false;
ep.alterSyntaxNamedArgs = 0; // 0 = no alternate syntax, 1 = accept alternate syntax but warn, 2 = accept without warning
ep.disableIntegerDivision = false;
ep.disallowEmptyListElements = false;
ep.privatePropAsProtected = false;
ep.allowUnicodeIdentifiers = false;
}
gc.engine = this;
tok.engine = this;
refCount.set(1);
stringFactory = 0;
configFailed = false;
isPrepared = false;
isBuilding = false;
deferValidationOfTemplateTypes = false;
lastModule = 0;
initialContextStackSize = 1024; // 4 KB (1024 * sizeof(asDWORD)
typeIdSeqNbr = 0;
currentGroup = &defaultGroup;
defaultAccessMask = 0xFFFFFFFF; // All bits set so that built-in functions/types will be available to all modules
msgCallback = 0;
jitCompiler = 0;
// Create the global namespace
defaultNamespace = AddNameSpace("");
requestCtxFunc = 0;
returnCtxFunc = 0;
ctxCallbackParam = 0;
// We must set the namespace in the built-in types explicitly as
// this wasn't done by the default constructor. If we do not do
// this we will get null pointer access in other parts of the code
scriptTypeBehaviours.nameSpace = defaultNamespace;
functionBehaviours.nameSpace = defaultNamespace;
// Reserve function id 0 for no function
scriptFunctions.PushLast(0);
// Reserve the first typeIds for the primitive types
typeIdSeqNbr = asTYPEID_DOUBLE + 1;
// Make sure typeId for the built-in primitives are defined according to asETypeIdFlags
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttVoid, false)) == asTYPEID_VOID );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttBool, false)) == asTYPEID_BOOL );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttInt8, false)) == asTYPEID_INT8 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttInt16, false)) == asTYPEID_INT16 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttInt, false)) == asTYPEID_INT32 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttInt64, false)) == asTYPEID_INT64 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttUInt8, false)) == asTYPEID_UINT8 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttUInt16, false)) == asTYPEID_UINT16 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttUInt, false)) == asTYPEID_UINT32 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttUInt64, false)) == asTYPEID_UINT64 );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttFloat, false)) == asTYPEID_FLOAT );
asASSERT( GetTypeIdFromDataType(asCDataType::CreatePrimitive(ttDouble, false)) == asTYPEID_DOUBLE );
defaultArrayObjectType = 0;
RegisterScriptObject(this);
RegisterScriptFunction(this);
}
void asCScriptEngine::DeleteDiscardedModules()
{
// TODO: redesign: Prevent more than one thread from entering this function at the same time.
// If a thread is already doing the work for the clean-up the other thread should
// simply return, as the first thread will continue.
ACQUIRESHARED(engineRWLock);
asUINT maxCount = discardedModules.GetLength();
RELEASESHARED(engineRWLock);
for( asUINT n = 0; n < maxCount; n++ )
{
ACQUIRESHARED(engineRWLock);
asCModule *mod = discardedModules[n];
RELEASESHARED(engineRWLock);
if( !mod->HasExternalReferences(shuttingDown) )
{
asDELETE(mod, asCModule);
n--;
}
ACQUIRESHARED(engineRWLock);
// Determine the max count again, since another module may have been discarded during the processing
maxCount = discardedModules.GetLength();
RELEASESHARED(engineRWLock);
}
// Go over the list of global properties, to see if it is possible to clean
// up some variables that are no longer referred to by any functions
for( asUINT n = 0; n < globalProperties.GetLength(); n++ )
{
asCGlobalProperty *prop = globalProperties[n];
if( prop && prop->refCount.get() == 1 )
RemoveGlobalProperty(prop);
}
}
asCScriptEngine::~asCScriptEngine()
{
// TODO: clean-up: Clean up redundant code
inDestructor = true;
asASSERT(refCount.get() == 0);
// If ShutDown hasn't been called yet do it now
if( !shuttingDown )
{
AddRef();
ShutDownAndRelease();
}
// Unravel the registered interface
if( defaultArrayObjectType )
{
defaultArrayObjectType->ReleaseInternal();
defaultArrayObjectType = 0;
}
// Delete the functions for generated template types that may references object types
for( asUINT n = 0; n < templateInstanceTypes.GetLength(); n++ )
{
asCObjectType *templateType = templateInstanceTypes[n];
if( templateInstanceTypes[n] )
templateType->DestroyInternal();
}
for( asUINT n = 0; n < listPatternTypes.GetLength(); n++ )
{
asCObjectType *type = listPatternTypes[n];
if( type )
type->ReleaseInternal();
}
listPatternTypes.SetLength(0);
// No script types must have survived
asASSERT( sharedScriptTypes.GetLength() == 0 );
// It is allowed to create new references to the engine temporarily while destroying objects
// but these references must be release immediately or else something is can go wrong later on
if( refCount.get() > 0 )
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ENGINE_REF_COUNT_ERROR_DURING_SHUTDOWN);
mapTypeIdToTypeInfo.EraseAll();
// First remove what is not used, so that other groups can be deleted safely
defaultGroup.RemoveConfiguration(this, true);
while( configGroups.GetLength() )
{
// Delete config groups in the right order
asCConfigGroup *grp = configGroups.PopLast();
if( grp )
{
grp->RemoveConfiguration(this);
asDELETE(grp,asCConfigGroup);
}
}
// Remove what is remaining
defaultGroup.RemoveConfiguration(this);
// Any remaining objects in templateInstanceTypes is from generated template instances
for( asUINT n = 0; n < templateInstanceTypes.GetLength(); n++ )
{
asCObjectType *templateType = templateInstanceTypes[n];
if( templateInstanceTypes[n] )
templateType->ReleaseInternal();
}
templateInstanceTypes.SetLength(0);
asCSymbolTable<asCGlobalProperty>::iterator it = registeredGlobalProps.List();
for( ; it; it++ )
{
RemoveGlobalProperty(*it);
(*it)->Release();
}
registeredGlobalProps.Clear();
for( asUINT n = 0; n < templateSubTypes.GetLength(); n++ )
{
if( templateSubTypes[n] )
{
templateSubTypes[n]->DestroyInternal();
templateSubTypes[n]->ReleaseInternal();
}
}
templateSubTypes.SetLength(0);
registeredTypeDefs.SetLength(0);
registeredEnums.SetLength(0);
registeredObjTypes.SetLength(0);
asCSymbolTable<asCScriptFunction>::iterator funcIt = registeredGlobalFuncs.List();
for( ; funcIt; funcIt++ )
(*funcIt)->ReleaseInternal();
registeredGlobalFuncs.Clear();
scriptTypeBehaviours.ReleaseAllFunctions();
functionBehaviours.ReleaseAllFunctions();
for( asUINT n = 0; n < scriptFunctions.GetLength(); n++ )
if( scriptFunctions[n] )
{
scriptFunctions[n]->DestroyInternal();
// Set the engine pointer to null to signal that the function is no longer part of the engine
scriptFunctions[n]->engine = 0;
}
scriptFunctions.SetLength(0);
// Increase the internal ref count for these builtin object types, so the destructor is not called incorrectly
scriptTypeBehaviours.AddRefInternal();
functionBehaviours.AddRefInternal();
// Destroy the funcdefs
// As funcdefs are shared between modules it shouldn't be a problem to keep the objects until the engine is released
for( asUINT n = 0; n < funcDefs.GetLength(); n++ )
if( funcDefs[n] )
{
funcDefs[n]->DestroyInternal();
funcDefs[n]->ReleaseInternal();
}
funcDefs.SetLength(0);
// Free the global properties
for( asUINT n = 0; n < globalProperties.GetLength(); n++ )
{
asCGlobalProperty *prop = globalProperties[n];
if( prop )
{
asASSERT( prop->refCount.get() == 1 );
RemoveGlobalProperty(prop);
}
}
// Free string constants
for( asUINT n = 0; n < stringConstants.GetLength(); n++ )
asDELETE(stringConstants[n],asCString);
stringConstants.SetLength(0);
stringToIdMap.EraseAll();
// Free the script section names
for( asUINT n = 0; n < scriptSectionNames.GetLength(); n++ )
asDELETE(scriptSectionNames[n],asCString);
scriptSectionNames.SetLength(0);
// Clean the user data
for( asUINT n = 0; n < userData.GetLength(); n += 2 )
{
if( userData[n+1] )
{
for( asUINT c = 0; c < cleanEngineFuncs.GetLength(); c++ )
if( cleanEngineFuncs[c].type == userData[n] )
cleanEngineFuncs[c].cleanFunc(this);
}
}
// Free namespaces
for( asUINT n = 0; n < nameSpaces.GetLength(); n++ )
asDELETE(nameSpaces[n], asSNameSpace);
nameSpaces.SetLength(0);
asCThreadManager::Unprepare();
}
// interface
int asCScriptEngine::SetContextCallbacks(asREQUESTCONTEXTFUNC_t requestCtx, asRETURNCONTEXTFUNC_t returnCtx, void *param)
{
// Both callbacks or neither must be set
if( (requestCtx == 0 && returnCtx != 0) || (requestCtx != 0 && returnCtx == 0) )
return asINVALID_ARG;
requestCtxFunc = requestCtx;
returnCtxFunc = returnCtx;
ctxCallbackParam = param;
return 0;
}
// interface
asIScriptContext *asCScriptEngine::RequestContext()
{
if( requestCtxFunc )
{
// The return callback must also exist
asASSERT( returnCtxFunc );
asIScriptContext *ctx = requestCtxFunc(this, ctxCallbackParam);
return ctx;
}
// As fallback we create a new context
return CreateContext();
}
// internal
asCModule *asCScriptEngine::FindNewOwnerForSharedType(asCTypeInfo *in_type, asCModule *in_mod)
{
asASSERT( in_type->IsShared() );
if( in_type->module != in_mod)
return in_type->module;
for( asUINT n = 0; n < scriptModules.GetLength(); n++ )
{
// TODO: optimize: If the modules already stored the shared types separately, this would be quicker
int foundIdx = -1;
asCModule *mod = scriptModules[n];
if( mod == in_type->module ) continue;
if( in_type->flags & asOBJ_ENUM )
foundIdx = mod->enumTypes.IndexOf(in_type->CastToEnumType());
else if (in_type->flags & asOBJ_TYPEDEF)
foundIdx = mod->typeDefs.IndexOf(in_type->CastToTypedefType());
else if (in_type->flags & asOBJ_FUNCDEF)
foundIdx = mod->funcDefs.IndexOf(in_type->CastToFuncdefType());
else
foundIdx = mod->classTypes.IndexOf(in_type->CastToObjectType());
if( foundIdx >= 0 )
{
in_type->module = mod;
break;
}
}
return in_type->module;
}
// internal
asCModule *asCScriptEngine::FindNewOwnerForSharedFunc(asCScriptFunction *in_func, asCModule *in_mod)
{
asASSERT( in_func->IsShared() );
asASSERT(!(in_func->funcType & asFUNC_FUNCDEF));
if( in_func->module != in_mod)
return in_func->module;
for( asUINT n = 0; n < scriptModules.GetLength(); n++ )
{
// TODO: optimize: If the modules already stored the shared types separately, this would be quicker
int foundIdx = -1;
asCModule *mod = scriptModules[n];
if( mod == in_func->module ) continue;
foundIdx = mod->scriptFunctions.IndexOf(in_func);
if( foundIdx >= 0 )
{
in_func->module = mod;
break;
}
}
return in_func->module;
}
// interface
void asCScriptEngine::ReturnContext(asIScriptContext *ctx)
{
if( returnCtxFunc )
{
returnCtxFunc(this, ctx, ctxCallbackParam);
return;
}
// As fallback we just release the context
if( ctx )
ctx->Release();
}
// interface
int asCScriptEngine::AddRef() const
{
asASSERT( refCount.get() > 0 || inDestructor );
return refCount.atomicInc();
}
// interface
int asCScriptEngine::Release() const
{
int r = refCount.atomicDec();
if( r == 0 )
{
// It is possible that some function will temporarily increment the engine ref count
// during clean-up for example while destroying the objects in the garbage collector.
if( !inDestructor )
asDELETE(const_cast<asCScriptEngine*>(this),asCScriptEngine);
return 0;
}
return r;
}
// interface
int asCScriptEngine::ShutDownAndRelease()
{
// Do a full garbage collection cycle to clean up any object that may still hold on to the engine
GarbageCollect();
// Set the flag that the engine is being shutdown now. This will speed up
// the process, and will also allow the engine to warn about invalid calls
shuttingDown = true;
// Clear the context callbacks. If new context's are needed for the clean-up the engine will take care of this itself.
// Context callbacks are normally used for pooling contexts, and if we allow new contexts to be created without being
// immediately destroyed afterwards it means the engine's refcount will increase. This is turn may cause memory access
// violations later on when the pool releases its contexts.
SetContextCallbacks(0, 0, 0);
// The modules must be deleted first, as they may use
// object types from the config groups
for( asUINT n = (asUINT)scriptModules.GetLength(); n-- > 0; )
if( scriptModules[n] )
scriptModules[n]->Discard();
scriptModules.SetLength(0);
// Do another full garbage collection to destroy the object types/functions
// that may have been placed in the gc when destroying the modules
GarbageCollect();
// Do another sweep to delete discarded modules, that may not have
// been deleted earlier due to still having external references
DeleteDiscardedModules();
// If the application hasn't registered GC behaviours for all types
// that can form circular references with script types, then there
// may still be objects in the GC.
gc.ReportAndReleaseUndestroyedObjects();
// Release the engine reference
return Release();
}
// internal
asSNameSpace *asCScriptEngine::AddNameSpace(const char *name)
{
// First check if it doesn't exist already
asSNameSpace *ns = FindNameSpace(name);
if( ns ) return ns;
ns = asNEW(asSNameSpace);
if( ns == 0 )
{
// Out of memory
return 0;
}
ns->name = name;
nameSpaces.PushLast(ns);
return ns;
}
// internal
asSNameSpace *asCScriptEngine::FindNameSpace(const char *name) const
{
// TODO: optimize: Improve linear search
for( asUINT n = 0; n < nameSpaces.GetLength(); n++ )
if( nameSpaces[n]->name == name )
return nameSpaces[n];
return 0;
}
// interface
const char *asCScriptEngine::GetDefaultNamespace() const
{
return defaultNamespace->name.AddressOf();
}
// interface
int asCScriptEngine::SetDefaultNamespace(const char *nameSpace)
{
if( nameSpace == 0 )
return ConfigError(asINVALID_ARG, "SetDefaultNamespace", nameSpace, 0);
asCString ns = nameSpace;
if( ns != "" )
{
// Make sure the namespace is composed of alternating identifier and ::
size_t pos = 0;
bool expectIdentifier = true;
size_t len;
eTokenType t = ttIdentifier;
for( ; pos < ns.GetLength(); pos += len)
{
t = tok.GetToken(ns.AddressOf() + pos, ns.GetLength() - pos, &len);
if( (expectIdentifier && t != ttIdentifier) || (!expectIdentifier && t != ttScope) )
return ConfigError(asINVALID_DECLARATION, "SetDefaultNamespace", nameSpace, 0);
expectIdentifier = !expectIdentifier;
}
// If the namespace ends with :: then strip it off
if( t == ttScope )
ns.SetLength(ns.GetLength()-2);
}
defaultNamespace = AddNameSpace(ns.AddressOf());
return 0;
}
// interface
void *asCScriptEngine::SetUserData(void *data, asPWORD type)
{
// As a thread might add a new new user data at the same time as another
// it is necessary to protect both read and write access to the userData member
ACQUIREEXCLUSIVE(engineRWLock);
// It is not intended to store a lot of different types of userdata,
// so a more complex structure like a associative map would just have
// more overhead than a simple array.
for( asUINT n = 0; n < userData.GetLength(); n += 2 )
{
if( userData[n] == type )
{
void *oldData = reinterpret_cast<void*>(userData[n+1]);
userData[n+1] = reinterpret_cast<asPWORD>(data);
RELEASEEXCLUSIVE(engineRWLock);
return oldData;
}
}
userData.PushLast(type);
userData.PushLast(reinterpret_cast<asPWORD>(data));
RELEASEEXCLUSIVE(engineRWLock);
return 0;
}
// interface
void *asCScriptEngine::GetUserData(asPWORD type) const
{
// There may be multiple threads reading, but when
// setting the user data nobody must be reading.
ACQUIRESHARED(engineRWLock);
for( asUINT n = 0; n < userData.GetLength(); n += 2 )
{
if( userData[n] == type )
{
RELEASESHARED(engineRWLock);
return reinterpret_cast<void*>(userData[n+1]);
}
}
RELEASESHARED(engineRWLock);
return 0;
}
// interface
int asCScriptEngine::SetMessageCallback(const asSFuncPtr &callback, void *obj, asDWORD callConv)
{
msgCallback = true;
msgCallbackObj = obj;
bool isObj = false;
if( (unsigned)callConv == asCALL_GENERIC || (unsigned)callConv == asCALL_THISCALL_OBJFIRST || (unsigned)callConv == asCALL_THISCALL_OBJLAST )
{
msgCallback = false;
return asNOT_SUPPORTED;
}
if( (unsigned)callConv >= asCALL_THISCALL )
{
isObj = true;
if( obj == 0 )
{
msgCallback = false;
return asINVALID_ARG;
}
}
int r = DetectCallingConvention(isObj, callback, callConv, 0, &msgCallbackFunc);
if( r < 0 ) msgCallback = false;
return r;
}
// interface
int asCScriptEngine::ClearMessageCallback()
{
msgCallback = false;
return 0;
}
// interface
int asCScriptEngine::WriteMessage(const char *section, int row, int col, asEMsgType type, const char *message)
{
// Validate input parameters
if( section == 0 ||
message == 0 )
return asINVALID_ARG;
// If there is no callback then there's nothing to do
if( !msgCallback )
return 0;
// If a pre-message has been set, then write that first
if( preMessage.isSet )
{
asSMessageInfo msg;
msg.section = preMessage.scriptname.AddressOf();
msg.row = preMessage.r;
msg.col = preMessage.c;
msg.type = asMSGTYPE_INFORMATION;
msg.message = preMessage.message.AddressOf();
if( msgCallbackFunc.callConv < ICC_THISCALL )
CallGlobalFunction(&msg, msgCallbackObj, &msgCallbackFunc, 0);
else
CallObjectMethod(msgCallbackObj, &msg, &msgCallbackFunc, 0);
preMessage.isSet = false;
}
// Write the message to the callback
asSMessageInfo msg;
msg.section = section;
msg.row = row;
msg.col = col;
msg.type = type;
msg.message = message;
if( msgCallbackFunc.callConv < ICC_THISCALL )
CallGlobalFunction(&msg, msgCallbackObj, &msgCallbackFunc, 0);
else
CallObjectMethod(msgCallbackObj, &msg, &msgCallbackFunc, 0);
return 0;
}
int asCScriptEngine::SetJITCompiler(asIJITCompiler *compiler)
{
jitCompiler = compiler;
return asSUCCESS;
}
asIJITCompiler *asCScriptEngine::GetJITCompiler() const
{
return jitCompiler;
}
// interface
asETokenClass asCScriptEngine::ParseToken(const char *string, size_t stringLength, asUINT *tokenLength) const
{
if( stringLength == 0 )
stringLength = strlen(string);
size_t len;
asETokenClass tc;
tok.GetToken(string, stringLength, &len, &tc);
if( tokenLength )
*tokenLength = (asUINT)len;
return tc;
}
// interface
asIScriptModule *asCScriptEngine::GetModule(const char *module, asEGMFlags flag)
{
asCModule *mod = GetModule(module, false);
if( flag == asGM_ALWAYS_CREATE )
{
if( mod != 0 )
mod->Discard();
return GetModule(module, true);
}
if( mod == 0 && flag == asGM_CREATE_IF_NOT_EXISTS )
return GetModule(module, true);
return mod;
}
// interface
int asCScriptEngine::DiscardModule(const char *module)
{
asCModule *mod = GetModule(module, false);
if( mod == 0 ) return asNO_MODULE;
mod->Discard();
return 0;
}
// interface
asUINT asCScriptEngine::GetModuleCount() const
{
ACQUIRESHARED(engineRWLock);
asUINT length = asUINT(scriptModules.GetLength());
RELEASESHARED(engineRWLock);
return length;
}
// interface
asIScriptModule *asCScriptEngine::GetModuleByIndex(asUINT index) const
{
asIScriptModule *mod = 0;
ACQUIRESHARED(engineRWLock);
if( index < scriptModules.GetLength() )
mod = scriptModules[index];
RELEASESHARED(engineRWLock);
return mod;
}
// internal
int asCScriptEngine::GetFactoryIdByDecl(const asCObjectType *ot, const char *decl)
{
asCModule *mod = 0;
// Is this a script class?
if( (ot->flags & asOBJ_SCRIPT_OBJECT) && ot->size > 0 )
mod = scriptFunctions[ot->beh.factories[0]]->module;
asCBuilder bld(this, mod);
// Don't write parser errors to the message callback
bld.silent = true;
asCScriptFunction func(this, mod, asFUNC_DUMMY);
int r = bld.ParseFunctionDeclaration(0, decl, &func, false, 0, 0, defaultNamespace);
if( r < 0 )
return asINVALID_DECLARATION;
// Search for matching factory function
int id = -1;
for( asUINT n = 0; n < ot->beh.factories.GetLength(); n++ )
{
asCScriptFunction *f = scriptFunctions[ot->beh.factories[n]];
if( f->IsSignatureEqual(&func) )
{
id = ot->beh.factories[n];
break;
}
}
if( id == -1 ) return asNO_FUNCTION;
return id;
}
// internal
int asCScriptEngine::GetMethodIdByDecl(const asCObjectType *ot, const char *decl, asCModule *mod)
{
asCBuilder bld(this, mod);
// Don't write parser errors to the message callback
bld.silent = true;
asCScriptFunction func(this, mod, asFUNC_DUMMY);
// Set the object type so that the signature can be properly compared
// This cast is OK, it will only be used for comparison
func.objectType = const_cast<asCObjectType*>(ot);
func.objectType->AddRefInternal();
int r = bld.ParseFunctionDeclaration(func.objectType, decl, &func, false);
if( r < 0 )
return asINVALID_DECLARATION;
// Search script functions for matching interface
int id = -1;
for( asUINT n = 0; n < ot->methods.GetLength(); ++n )
{
if( func.IsSignatureEqual(scriptFunctions[ot->methods[n]]) )
{
if( id == -1 )
id = ot->methods[n];
else
return asMULTIPLE_FUNCTIONS;
}
}
if( id == -1 ) return asNO_FUNCTION;
return id;
}
// internal
asCString asCScriptEngine::GetFunctionDeclaration(int funcId)
{
asCString str;
asCScriptFunction *func = GetScriptFunction(funcId);
if( func )
str = func->GetDeclarationStr();
return str;
}
// internal
asCScriptFunction *asCScriptEngine::GetScriptFunction(int funcId) const
{
if( funcId < 0 || funcId >= (int)scriptFunctions.GetLength() )
return 0;
return scriptFunctions[funcId];
}
// interface
asIScriptContext *asCScriptEngine::CreateContext()
{
asIScriptContext *ctx = 0;
CreateContext(&ctx, false);
return ctx;
}
// internal
int asCScriptEngine::CreateContext(asIScriptContext **context, bool isInternal)
{
*context = asNEW(asCContext)(this, !isInternal);
if( *context == 0 )
return asOUT_OF_MEMORY;
// We need to make sure the engine has been
// prepared before any context is executed
PrepareEngine();
return 0;
}
// interface
int asCScriptEngine::RegisterObjectProperty(const char *obj, const char *declaration, int byteOffset)
{
int r;
asCDataType dt;
asCBuilder bld(this, 0);
r = bld.ParseDataType(obj, &dt, defaultNamespace);
if( r < 0 )
return ConfigError(r, "RegisterObjectProperty", obj, declaration);
// Don't allow modifying generated template instances
if( dt.GetTypeInfo() && (dt.GetTypeInfo()->flags & asOBJ_TEMPLATE) && generatedTemplateTypes.Exists(dt.GetTypeInfo()->CastToObjectType()) )
return ConfigError(asINVALID_TYPE, "RegisterObjectProperty", obj, declaration);
// Verify that the correct config group is used
if( currentGroup->FindType(dt.GetTypeInfo()->name.AddressOf()) == 0 )
return ConfigError(asWRONG_CONFIG_GROUP, "RegisterObjectProperty", obj, declaration);
asCDataType type;
asCString name;
if( (r = bld.VerifyProperty(&dt, declaration, name, type, 0)) < 0 )
return ConfigError(r, "RegisterObjectProperty", obj, declaration);
// Store the property info
if( dt.GetTypeInfo() == 0 || dt.IsObjectHandle() )
return ConfigError(asINVALID_OBJECT, "RegisterObjectProperty", obj, declaration);
// The VM currently only supports 16bit offsets
// TODO: The VM needs to have support for 32bit offsets. Probably with a second ADDSi instruction
// However, when implementing this it is necessary for the bytecode serialization to support
// the switch between the instructions upon loading bytecode as the offset may not be the
// same on all platforms
if( byteOffset > 32767 || byteOffset < -32768 )
return ConfigError(asINVALID_ARG, "RegisterObjectProperty", obj, declaration);
asCObjectProperty *prop = asNEW(asCObjectProperty);
if( prop == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectProperty", obj, declaration);
prop->name = name;
prop->type = type;
prop->byteOffset = byteOffset;
prop->isPrivate = false;
prop->isProtected = false;
prop->accessMask = defaultAccessMask;
dt.GetTypeInfo()->CastToObjectType()->properties.PushLast(prop);
// Add references to types so they are not released too early
if( type.GetTypeInfo() )
{
type.GetTypeInfo()->AddRefInternal();
// Add template instances to the config group
if( (type.GetTypeInfo()->flags & asOBJ_TEMPLATE) && !currentGroup->types.Exists(type.GetTypeInfo()) )
currentGroup->types.PushLast(type.GetTypeInfo());
}
currentGroup->AddReferencesForType(this, type.GetTypeInfo());
return asSUCCESS;
}
// interface
int asCScriptEngine::RegisterInterface(const char *name)
{
if( name == 0 ) return ConfigError(asINVALID_NAME, "RegisterInterface", 0, 0);
// Verify if the name has been registered as a type already
if( GetRegisteredType(name, defaultNamespace) )
return asALREADY_REGISTERED;
// Use builder to parse the datatype
asCDataType dt;
asCBuilder bld(this, 0);
bool oldMsgCallback = msgCallback; msgCallback = false;
int r = bld.ParseDataType(name, &dt, defaultNamespace);
msgCallback = oldMsgCallback;
if( r >= 0 )
{
// If it is not in the defaultNamespace then the type was successfully parsed because
// it is declared in a parent namespace which shouldn't be treated as an error
if( dt.GetTypeInfo() && dt.GetTypeInfo()->nameSpace == defaultNamespace )
return ConfigError(asERROR, "RegisterInterface", name, 0);
}
// Make sure the name is not a reserved keyword
size_t tokenLen;
int token = tok.GetToken(name, strlen(name), &tokenLen);
if( token != ttIdentifier || strlen(name) != tokenLen )
return ConfigError(asINVALID_NAME, "RegisterInterface", name, 0);
r = bld.CheckNameConflict(name, 0, 0, defaultNamespace);
if( r < 0 )
return ConfigError(asNAME_TAKEN, "RegisterInterface", name, 0);
// Don't have to check against members of object
// types as they are allowed to use the names
// Register the object type for the interface
asCObjectType *st = asNEW(asCObjectType)(this);
if( st == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterInterface", name, 0);
st->flags = asOBJ_REF | asOBJ_SCRIPT_OBJECT | asOBJ_SHARED;
st->size = 0; // Cannot be instantiated
st->name = name;
st->nameSpace = defaultNamespace;
// Use the default script class behaviours
st->beh.factory = 0;
st->beh.addref = scriptTypeBehaviours.beh.addref;
scriptFunctions[st->beh.addref]->AddRefInternal();
st->beh.release = scriptTypeBehaviours.beh.release;
scriptFunctions[st->beh.release]->AddRefInternal();
st->beh.copy = 0;
allRegisteredTypes.Insert(asSNameSpaceNamePair(st->nameSpace, st->name), st);
registeredObjTypes.PushLast(st);
currentGroup->types.PushLast(st);
return asSUCCESS;
}
// interface
int asCScriptEngine::RegisterInterfaceMethod(const char *intf, const char *declaration)
{
// Verify that the correct config group is set.
if( currentGroup->FindType(intf) == 0 )
return ConfigError(asWRONG_CONFIG_GROUP, "RegisterInterfaceMethod", intf, declaration);
asCDataType dt;
asCBuilder bld(this, 0);
int r = bld.ParseDataType(intf, &dt, defaultNamespace);
if( r < 0 )
return ConfigError(r, "RegisterInterfaceMethod", intf, declaration);
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_INTERFACE);
if( func == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterInterfaceMethod", intf, declaration);
func->objectType = dt.GetTypeInfo()->CastToObjectType();
func->objectType->AddRefInternal();
r = bld.ParseFunctionDeclaration(func->objectType, declaration, func, false);
if( r < 0 )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asINVALID_DECLARATION, "RegisterInterfaceMethod", intf, declaration);
}
// Check name conflicts
r = bld.CheckNameConflictMember(dt.GetTypeInfo(), func->name.AddressOf(), 0, 0, false);
if( r < 0 )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asNAME_TAKEN, "RegisterInterfaceMethod", intf, declaration);
}
func->id = GetNextScriptFunctionId();
AddScriptFunction(func);
// The index into the interface's vftable chunk should be
// its index in the methods array.
func->vfTableIdx = int(func->objectType->methods.GetLength());
func->objectType->methods.PushLast(func->id);
func->ComputeSignatureId();
currentGroup->AddReferencesForFunc(this, func);
// Return function id as success
return func->id;
}
int asCScriptEngine::RegisterObjectType(const char *name, int byteSize, asDWORD flags)
{
int r;
isPrepared = false;
// Verify flags
// Must have either asOBJ_REF or asOBJ_VALUE
if( flags & asOBJ_REF )
{
// Can optionally have the asOBJ_GC, asOBJ_NOHANDLE, asOBJ_SCOPED, or asOBJ_TEMPLATE flag set, but nothing else
if( flags & ~(asOBJ_REF | asOBJ_GC | asOBJ_NOHANDLE | asOBJ_SCOPED | asOBJ_TEMPLATE | asOBJ_NOCOUNT | asOBJ_IMPLICIT_HANDLE) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// flags are exclusive
if( (flags & asOBJ_GC) && (flags & (asOBJ_NOHANDLE|asOBJ_SCOPED|asOBJ_NOCOUNT)) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
if( (flags & asOBJ_NOHANDLE) && (flags & (asOBJ_GC|asOBJ_SCOPED|asOBJ_NOCOUNT|asOBJ_IMPLICIT_HANDLE)) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
if( (flags & asOBJ_SCOPED) && (flags & (asOBJ_GC|asOBJ_NOHANDLE|asOBJ_NOCOUNT|asOBJ_IMPLICIT_HANDLE)) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
if( (flags & asOBJ_NOCOUNT) && (flags & (asOBJ_GC|asOBJ_NOHANDLE|asOBJ_SCOPED)) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// Implicit handle is only allowed if the engine property for this is turned on
if( !ep.allowImplicitHandleTypes && (flags & asOBJ_IMPLICIT_HANDLE) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
else if( flags & asOBJ_VALUE )
{
// Cannot use reference flags
if( flags & (asOBJ_REF | asOBJ_GC | asOBJ_NOHANDLE | asOBJ_SCOPED | asOBJ_NOCOUNT | asOBJ_IMPLICIT_HANDLE) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// Flags are exclusive
if( (flags & asOBJ_POD) && (flags & (asOBJ_ASHANDLE | asOBJ_TEMPLATE)) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// If the app type is given, we must validate the flags
if( flags & asOBJ_APP_CLASS )
{
// Must not set the primitive or float flag
if( flags & (asOBJ_APP_PRIMITIVE | asOBJ_APP_FLOAT | asOBJ_APP_ARRAY) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
else
{
// Must not set the class properties, without the class flag
if( flags & (asOBJ_APP_CLASS_CONSTRUCTOR |
asOBJ_APP_CLASS_DESTRUCTOR |
asOBJ_APP_CLASS_ASSIGNMENT |
asOBJ_APP_CLASS_COPY_CONSTRUCTOR |
asOBJ_APP_CLASS_ALLINTS |
asOBJ_APP_CLASS_ALLFLOATS) )
{
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
}
if( flags & asOBJ_APP_PRIMITIVE )
{
if( flags & (asOBJ_APP_CLASS |
asOBJ_APP_FLOAT |
asOBJ_APP_ARRAY) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
else if( flags & asOBJ_APP_FLOAT )
{
if( flags & (asOBJ_APP_CLASS |
asOBJ_APP_PRIMITIVE |
asOBJ_APP_ARRAY) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
else if( flags & asOBJ_APP_ARRAY )
{
if( flags & (asOBJ_APP_CLASS |
asOBJ_APP_PRIMITIVE |
asOBJ_APP_FLOAT) )
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
}
else
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// Don't allow anything else than the defined flags
#ifndef WIP_16BYTE_ALIGN
if( flags - (flags & asOBJ_MASK_VALID_FLAGS) )
#else
if( flags - (flags & (asOBJ_MASK_VALID_FLAGS | asOBJ_APP_ALIGN16)) )
#endif
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
// Value types must have a defined size
if( (flags & asOBJ_VALUE) && byteSize == 0 )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_VALUE_TYPE_MUST_HAVE_SIZE);
return ConfigError(asINVALID_ARG, "RegisterObjectType", name, 0);
}
// Verify type name
if( name == 0 )
return ConfigError(asINVALID_NAME, "RegisterObjectType", name, 0);
asCString typeName;
asCBuilder bld(this, 0);
if( flags & asOBJ_TEMPLATE )
{
asCArray<asCString> subtypeNames;
r = bld.ParseTemplateDecl(name, &typeName, subtypeNames);
if( r < 0 )
return ConfigError(r, "RegisterObjectType", name, 0);
// Verify that the template name hasn't been registered as a type already
if( GetRegisteredType(typeName, defaultNamespace) )
// This is not an irrepairable error, as it may just be that the same type is registered twice
return asALREADY_REGISTERED;
asCObjectType *type = asNEW(asCObjectType)(this);
if( type == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectType", name, 0);
type->name = typeName;
type->nameSpace = defaultNamespace;
type->size = byteSize;
#ifdef WIP_16BYTE_ALIGN
// TODO: Types smaller than 4 don't need to be aligned to 4 byte boundaries
type->alignment = (flags & asOBJ_APP_ALIGN16) ? 16 : 4;
#endif
type->flags = flags;
type->accessMask = defaultAccessMask;
// Store it in the object types
allRegisteredTypes.Insert(asSNameSpaceNamePair(type->nameSpace, type->name), type);
currentGroup->types.PushLast(type);
registeredObjTypes.PushLast(type);
registeredTemplateTypes.PushLast(type);
// Define the template subtypes
for( asUINT subTypeIdx = 0; subTypeIdx < subtypeNames.GetLength(); subTypeIdx++ )
{
asCTypeInfo *subtype = 0;
for( asUINT n = 0; n < templateSubTypes.GetLength(); n++ )
{
if( templateSubTypes[n]->name == subtypeNames[subTypeIdx] )
{
subtype = templateSubTypes[n];
break;
}
}
if( subtype == 0 )
{
// Create the new subtype if not already existing
subtype = asNEW(asCTypeInfo)(this);
if( subtype == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectType", name, 0);
subtype->name = subtypeNames[subTypeIdx];
subtype->size = 0;
subtype->flags = asOBJ_TEMPLATE_SUBTYPE;
templateSubTypes.PushLast(subtype);
}
type->templateSubTypes.PushLast(asCDataType::CreateType(subtype, false));
subtype->AddRefInternal();
}
}
else
{
typeName = name;
// Verify if the name has been registered as a type already
if( GetRegisteredType(typeName, defaultNamespace) )
// This is not an irrepairable error, as it may just be that the same type is registered twice
return asALREADY_REGISTERED;
// TODO: clean up: Is it really necessary to check here?
for( asUINT n = 0; n < templateInstanceTypes.GetLength(); n++ )
{
if( templateInstanceTypes[n] &&
templateInstanceTypes[n]->name == typeName &&
templateInstanceTypes[n]->nameSpace == defaultNamespace )
// This is not an irrepairable error, as it may just be that the same type is registered twice
return asALREADY_REGISTERED;
}
// Keep the most recent template generated instance type, so we know what it was before parsing the datatype
asCObjectType *mostRecentTemplateInstanceType = 0;
asUINT originalSizeOfGeneratedTemplateTypes = (asUINT)generatedTemplateTypes.GetLength();
if( originalSizeOfGeneratedTemplateTypes )
mostRecentTemplateInstanceType = generatedTemplateTypes[originalSizeOfGeneratedTemplateTypes-1];
// Use builder to parse the datatype
asCDataType dt;
bool oldMsgCallback = msgCallback; msgCallback = false;
r = bld.ParseDataType(name, &dt, defaultNamespace);
msgCallback = oldMsgCallback;
// If the builder fails or the namespace is different than the default
// namespace, then the type name is new and it should be registered
if( r < 0 || dt.GetTypeInfo()->nameSpace != defaultNamespace )
{
// Make sure the name is not a reserved keyword
size_t tokenLen;
int token = tok.GetToken(name, typeName.GetLength(), &tokenLen);
if( token != ttIdentifier || typeName.GetLength() != tokenLen )
return ConfigError(asINVALID_NAME, "RegisterObjectType", name, 0);
r = bld.CheckNameConflict(name, 0, 0, defaultNamespace);
if( r < 0 )
return ConfigError(asNAME_TAKEN, "RegisterObjectType", name, 0);
// Don't have to check against members of object
// types as they are allowed to use the names
// Put the data type in the list
asCObjectType *type = asNEW(asCObjectType)(this);
if( type == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectType", name, 0);
type->name = typeName;
type->nameSpace = defaultNamespace;
type->size = byteSize;
#ifdef WIP_16BYTE_ALIGN
// TODO: Types smaller than 4 don't need to be aligned to 4 byte boundaries
type->alignment = (flags & asOBJ_APP_ALIGN16) ? 16 : 4;
#endif
type->flags = flags;
type->accessMask = defaultAccessMask;
allRegisteredTypes.Insert(asSNameSpaceNamePair(type->nameSpace, type->name), type);
registeredObjTypes.PushLast(type);
currentGroup->types.PushLast(type);
}
else
{
// The application is registering a template specialization so we
// need to replace the template instance type with the new type.
// TODO: Template: We don't require the lower dimensions to be registered first for registered template types
// int[][] must not be allowed to be registered
// if int[] hasn't been registered first
if( dt.GetSubType().IsTemplate() )
return ConfigError(asLOWER_ARRAY_DIMENSION_NOT_REGISTERED, "RegisterObjectType", name, 0);
if( dt.IsReadOnly() ||
dt.IsReference() )
return ConfigError(asINVALID_TYPE, "RegisterObjectType", name, 0);
// Was the template instance type generated before?
if( generatedTemplateTypes.Exists(dt.GetTypeInfo()->CastToObjectType()) &&
generatedTemplateTypes[generatedTemplateTypes.GetLength()-1] == mostRecentTemplateInstanceType )
{
asCString str;
str.Format(TXT_TEMPLATE_s_ALREADY_GENERATED_CANT_REGISTER, typeName.AddressOf());
WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
return ConfigError(asNOT_SUPPORTED, "RegisterObjectType", name, 0);
}
// If this is not a generated template instance type, then it means it is an
// already registered template specialization
if( !generatedTemplateTypes.Exists(dt.GetTypeInfo()->CastToObjectType()) )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectType", name, 0);
// TODO: Add this again. The type is used by the factory stubs so we need to discount that
// Is the template instance type already being used?
// if( dt.GetTypeInfo()->GetRefCount() > 1 )
// return ConfigError(asNOT_SUPPORTED, "RegisterObjectType", name, 0);
// Put the data type in the list
asCObjectType *type = asNEW(asCObjectType)(this);
if( type == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectType", name, 0);
type->name = dt.GetTypeInfo()->name;
// The namespace will be the same as the original template type
type->nameSpace = dt.GetTypeInfo()->nameSpace;
type->templateSubTypes.PushLast(dt.GetSubType());
for( asUINT s = 0; s < type->templateSubTypes.GetLength(); s++ )
if( type->templateSubTypes[s].GetTypeInfo() )
type->templateSubTypes[s].GetTypeInfo()->AddRefInternal();
type->size = byteSize;
#ifdef WIP_16BYTE_ALIGN
// TODO: Types smaller than 4 don't need to be aligned to 4 byte boundaries
type->alignment = (flags & asOBJ_APP_ALIGN16) ? 16 : 4;
#endif
type->flags = flags;
type->accessMask = defaultAccessMask;
templateInstanceTypes.PushLast(type);
currentGroup->types.PushLast(type);
// Remove the template instance type, which will no longer be used.
// It is possible that multiple template instances are generated if
// they have any relationship, so all of them must be removed
while( generatedTemplateTypes.GetLength() > originalSizeOfGeneratedTemplateTypes )
RemoveTemplateInstanceType(generatedTemplateTypes[generatedTemplateTypes.GetLength()-1]);
}
}
// Return the type id as the success (except for template types)
if( flags & asOBJ_TEMPLATE )
return asSUCCESS;
return GetTypeIdByDecl(name);
}
// interface
int asCScriptEngine::RegisterObjectBehaviour(const char *datatype, asEBehaviours behaviour, const char *decl, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
if( datatype == 0 ) return ConfigError(asINVALID_ARG, "RegisterObjectBehaviour", datatype, decl);
// Determine the object type
asCBuilder bld(this, 0);
asCDataType type;
int r = bld.ParseDataType(datatype, &type, defaultNamespace);
if( r < 0 )
return ConfigError(r, "RegisterObjectBehaviour", datatype, decl);
if( type.GetTypeInfo() == 0 || type.IsObjectHandle() )
return ConfigError(asINVALID_TYPE, "RegisterObjectBehaviour", datatype, decl);
// Don't allow application to modify built-in types
if( type.GetTypeInfo() == &functionBehaviours ||
type.GetTypeInfo() == &scriptTypeBehaviours )
return ConfigError(asINVALID_TYPE, "RegisterObjectBehaviour", datatype, decl);
if( type.IsReadOnly() || type.IsReference() )
return ConfigError(asINVALID_TYPE, "RegisterObjectBehaviour", datatype, decl);
// Don't allow modifying generated template instances
if( type.GetTypeInfo() && (type.GetTypeInfo()->flags & asOBJ_TEMPLATE) && generatedTemplateTypes.Exists(type.GetTypeInfo()->CastToObjectType()) )
return ConfigError(asINVALID_TYPE, "RegisterObjectBehaviour", datatype, decl);
return RegisterBehaviourToObjectType(type.GetTypeInfo()->CastToObjectType(), behaviour, decl, funcPointer, callConv, auxiliary);
}
// internal
int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, asEBehaviours behaviour, const char *decl, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
#ifdef AS_MAX_PORTABILITY
if( callConv != asCALL_GENERIC )
return ConfigError(asNOT_SUPPORTED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
#endif
asSSystemFunctionInterface internal;
bool isMethod = !(behaviour == asBEHAVE_FACTORY ||
behaviour == asBEHAVE_LIST_FACTORY ||
behaviour == asBEHAVE_TEMPLATE_CALLBACK);
int r = DetectCallingConvention(isMethod, funcPointer, callConv, auxiliary, &internal);
if( r < 0 )
return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// TODO: cleanup: This is identical to what is in RegisterMethodToObjectType
// If the object type is a template, make sure there are no generated instances already
if( objectType->flags & asOBJ_TEMPLATE )
{
for( asUINT n = 0; n < generatedTemplateTypes.GetLength(); n++ )
{
asCObjectType *tmpl = generatedTemplateTypes[n];
if( tmpl->name == objectType->name &&
tmpl->nameSpace == objectType->nameSpace &&
!(tmpl->templateSubTypes[0].GetTypeInfo() && (tmpl->templateSubTypes[0].GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE)) )
{
asCString msg;
msg.Format(TXT_TEMPLATE_s_ALREADY_GENERATED_CANT_REGISTER, asCDataType::CreateType(tmpl, false).Format(tmpl->nameSpace).AddressOf());
WriteMessage("",0,0, asMSGTYPE_ERROR, msg.AddressOf());
return ConfigError(asERROR, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
}
}
isPrepared = false;
asSTypeBehaviour *beh = &objectType->beh;
// Verify function declaration
asCScriptFunction func(this, 0, asFUNC_DUMMY);
bool expectListPattern = behaviour == asBEHAVE_LIST_FACTORY || behaviour == asBEHAVE_LIST_CONSTRUCT;
asCScriptNode *listPattern = 0;
asCBuilder bld(this, 0);
r = bld.ParseFunctionDeclaration(objectType, decl, &func, true, &internal.paramAutoHandles, &internal.returnAutoHandle, 0, expectListPattern ? &listPattern : 0);
if( r < 0 )
{
if( listPattern )
listPattern->Destroy(this);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
func.name.Format("$beh%d", behaviour);
if( behaviour != asBEHAVE_FACTORY && behaviour != asBEHAVE_LIST_FACTORY )
{
func.objectType = objectType;
func.objectType->AddRefInternal();
}
// Check if the method restricts that use of the template to value types or reference types
if( objectType->flags & asOBJ_TEMPLATE )
{
r = SetTemplateRestrictions(objectType, &func, "RegisterObjectBehaviour", decl);
if (r < 0)
return r;
}
if( behaviour == asBEHAVE_CONSTRUCT )
{
// Verify that the return type is void
if( func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( objectType->flags & asOBJ_SCRIPT_OBJECT )
{
// The script object is a special case
asASSERT(func.parameterTypes.GetLength() == 1);
beh->construct = AddBehaviourFunction(func, internal);
beh->factory = beh->construct;
scriptFunctions[beh->factory]->AddRefInternal();
beh->constructors.PushLast(beh->construct);
beh->factories.PushLast(beh->factory);
func.id = beh->construct;
}
else
{
// Verify that it is a value type
if( !(func.objectType->flags & asOBJ_VALUE) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// The templates take a hidden parameter with the object type
if( (objectType->flags & asOBJ_TEMPLATE) &&
(func.parameterTypes.GetLength() == 0 ||
!func.parameterTypes[0].IsReference()) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_FIRST_PARAM_MUST_BE_REF_FOR_TEMPLATE_FACTORY);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// TODO: Verify that the same constructor hasn't been registered already
// Store all constructors in a list
func.id = AddBehaviourFunction(func, internal);
beh->constructors.PushLast(func.id);
if( func.parameterTypes.GetLength() == 0 ||
(func.parameterTypes.GetLength() == 1 && (objectType->flags & asOBJ_TEMPLATE)) )
{
beh->construct = func.id;
}
else if( func.parameterTypes.GetLength() == 1 )
{
// Is this the copy constructor?
asCDataType paramType = func.parameterTypes[0];
// If the parameter is object, and const reference for input or inout,
// and same type as this class, then this is a copy constructor.
if( paramType.IsObject() && paramType.IsReference() && paramType.IsReadOnly() &&
(func.inOutFlags[0] & asTM_INREF) && paramType.GetTypeInfo() == objectType )
beh->copyconstruct = func.id;
}
}
}
else if( behaviour == asBEHAVE_DESTRUCT )
{
// Must be a value type
if( !(func.objectType->flags & asOBJ_VALUE) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( beh->destruct )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that the return type is void
if( func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that there are no parameters
if( func.parameterTypes.GetLength() > 0 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
func.id = beh->destruct = AddBehaviourFunction(func, internal);
}
else if( behaviour == asBEHAVE_LIST_CONSTRUCT )
{
// Verify that the return type is void
if( func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
{
if( listPattern )
listPattern->Destroy(this);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Verify that it is a value type
if( !(func.objectType->flags & asOBJ_VALUE) )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Verify the parameters
if( func.parameterTypes.GetLength() != 1 || !func.parameterTypes[0].IsReference() )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LIST_FACTORY_EXPECTS_1_REF_PARAM);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Don't accept duplicates
if( beh->listFactory )
{
if( listPattern )
listPattern->Destroy(this);
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Add the function
func.id = AddBehaviourFunction(func, internal);
// Re-use the listFactory member, as it is not possible to have both anyway
beh->listFactory = func.id;
// Store the list pattern for this function
r = scriptFunctions[func.id]->RegisterListPattern(decl, listPattern);
if( listPattern )
listPattern->Destroy(this);
if( r < 0 )
return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
else if( behaviour == asBEHAVE_FACTORY || behaviour == asBEHAVE_LIST_FACTORY )
{
// Must be a ref type and must not have asOBJ_NOHANDLE
if( !(objectType->flags & asOBJ_REF) || (objectType->flags & asOBJ_NOHANDLE) )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Verify that the return type is a handle to the type
if( func.returnType != asCDataType::CreateObjectHandle(objectType, false) )
{
if( listPattern )
listPattern->Destroy(this);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// The templates take a hidden parameter with the object type
if( (objectType->flags & asOBJ_TEMPLATE) &&
(func.parameterTypes.GetLength() == 0 ||
!func.parameterTypes[0].IsReference()) )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_FIRST_PARAM_MUST_BE_REF_FOR_TEMPLATE_FACTORY);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( behaviour == asBEHAVE_LIST_FACTORY )
{
// Make sure the factory takes a reference as its last parameter
if( objectType->flags & asOBJ_TEMPLATE )
{
if( func.parameterTypes.GetLength() != 2 || !func.parameterTypes[1].IsReference() )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_TEMPLATE_LIST_FACTORY_EXPECTS_2_REF_PARAMS);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
}
else
{
if( func.parameterTypes.GetLength() != 1 || !func.parameterTypes[0].IsReference() )
{
if( listPattern )
listPattern->Destroy(this);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LIST_FACTORY_EXPECTS_1_REF_PARAM);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
}
}
// TODO: Verify that the same factory function hasn't been registered already
// Don't accept duplicates
if( behaviour == asBEHAVE_LIST_FACTORY && beh->listFactory )
{
if( listPattern )
listPattern->Destroy(this);
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Store all factory functions in a list
func.id = AddBehaviourFunction(func, internal);
// The list factory is a special factory and isn't stored together with the rest
if( behaviour != asBEHAVE_LIST_FACTORY )
beh->factories.PushLast(func.id);
if( (func.parameterTypes.GetLength() == 0) ||
(func.parameterTypes.GetLength() == 1 && (objectType->flags & asOBJ_TEMPLATE)) )
{
beh->factory = func.id;
}
else if( (func.parameterTypes.GetLength() == 1) ||
(func.parameterTypes.GetLength() == 2 && (objectType->flags & asOBJ_TEMPLATE)) )
{
if( behaviour == asBEHAVE_LIST_FACTORY )
{
beh->listFactory = func.id;
// Store the list pattern for this function
r = scriptFunctions[func.id]->RegisterListPattern(decl, listPattern);
if( listPattern )
listPattern->Destroy(this);
if( r < 0 )
return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
else
{
// Is this the copy factory?
asCDataType paramType = func.parameterTypes[func.parameterTypes.GetLength()-1];
// If the parameter is object, and const reference for input,
// and same type as this class, then this is a copy constructor.
if( paramType.IsObject() && paramType.IsReference() && paramType.IsReadOnly() && func.inOutFlags[func.parameterTypes.GetLength()-1] == asTM_INREF && paramType.GetTypeInfo() == objectType )
beh->copyfactory = func.id;
}
}
}
else if( behaviour == asBEHAVE_ADDREF )
{
// Must be a ref type and must not have asOBJ_NOHANDLE, nor asOBJ_SCOPED
if( !(func.objectType->flags & asOBJ_REF) ||
(func.objectType->flags & asOBJ_NOHANDLE) ||
(func.objectType->flags & asOBJ_SCOPED) ||
(func.objectType->flags & asOBJ_NOCOUNT) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( beh->addref )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that the return type is void
if( func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that there are no parameters
if( func.parameterTypes.GetLength() > 0 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
func.id = beh->addref = AddBehaviourFunction(func, internal);
}
else if( behaviour == asBEHAVE_RELEASE )
{
// Must be a ref type and must not have asOBJ_NOHANDLE
if( !(func.objectType->flags & asOBJ_REF) ||
(func.objectType->flags & asOBJ_NOHANDLE) ||
(func.objectType->flags & asOBJ_NOCOUNT) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( beh->release )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that the return type is void
if( func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that there are no parameters
if( func.parameterTypes.GetLength() > 0 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
func.id = beh->release = AddBehaviourFunction(func, internal);
}
else if( behaviour == asBEHAVE_TEMPLATE_CALLBACK )
{
// Must be a template type
if( !(func.objectType->flags & asOBJ_TEMPLATE) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( beh->templateCallback )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that the return type is bool
if( func.returnType != asCDataType::CreatePrimitive(ttBool, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that there are two parameters
if( func.parameterTypes.GetLength() != 2 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// The first parameter must be an inref (to receive the object type), and
// the second must be a bool out ref (to return if the type should or shouldn't be garbage collected)
if( func.inOutFlags[0] != asTM_INREF || func.inOutFlags[1] != asTM_OUTREF || !func.parameterTypes[1].IsEqualExceptRef(asCDataType::CreatePrimitive(ttBool, false)) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
func.id = beh->templateCallback = AddBehaviourFunction(func, internal);
}
else if( behaviour >= asBEHAVE_FIRST_GC &&
behaviour <= asBEHAVE_LAST_GC )
{
// Only allow GC behaviours for types registered to be garbage collected
if( !(func.objectType->flags & asOBJ_GC) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Verify parameter count
if( (behaviour == asBEHAVE_GETREFCOUNT ||
behaviour == asBEHAVE_SETGCFLAG ||
behaviour == asBEHAVE_GETGCFLAG) &&
func.parameterTypes.GetLength() != 0 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( (behaviour == asBEHAVE_ENUMREFS ||
behaviour == asBEHAVE_RELEASEREFS) &&
func.parameterTypes.GetLength() != 1 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify return type
if( behaviour == asBEHAVE_GETREFCOUNT &&
func.returnType != asCDataType::CreatePrimitive(ttInt, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( behaviour == asBEHAVE_GETGCFLAG &&
func.returnType != asCDataType::CreatePrimitive(ttBool, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( (behaviour == asBEHAVE_SETGCFLAG ||
behaviour == asBEHAVE_ENUMREFS ||
behaviour == asBEHAVE_RELEASEREFS) &&
func.returnType != asCDataType::CreatePrimitive(ttVoid, false) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( behaviour == asBEHAVE_GETREFCOUNT )
func.id = beh->gcGetRefCount = AddBehaviourFunction(func, internal);
else if( behaviour == asBEHAVE_SETGCFLAG )
func.id = beh->gcSetFlag = AddBehaviourFunction(func, internal);
else if( behaviour == asBEHAVE_GETGCFLAG )
func.id = beh->gcGetFlag = AddBehaviourFunction(func, internal);
else if( behaviour == asBEHAVE_ENUMREFS )
func.id = beh->gcEnumReferences = AddBehaviourFunction(func, internal);
else if( behaviour == asBEHAVE_RELEASEREFS )
func.id = beh->gcReleaseAllReferences = AddBehaviourFunction(func, internal);
}
#ifdef AS_DEPRECATED
// Deprecated since 2.30.0. 2014-10-24
else if( behaviour == asBEHAVE_IMPLICIT_VALUE_CAST ||
behaviour == asBEHAVE_VALUE_CAST )
{
// There are two allowed signatures
// 1. type f()
// 2. void f(?&out)
if( !(func.parameterTypes.GetLength() == 1 && func.parameterTypes[0].GetTokenType() == ttQuestion && func.inOutFlags[0] == asTM_OUTREF && func.returnType.GetTokenType() == ttVoid) &&
!(func.parameterTypes.GetLength() == 0 && func.returnType.GetTokenType() != ttVoid) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// It is not allowed to implement a value cast to bool
if( func.returnType.IsEqualExceptRefAndConst(asCDataType::CreatePrimitive(ttBool, false)) )
return ConfigError(asNOT_SUPPORTED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
asCString decl;
decl += func.returnType.Format(defaultNamespace);
decl += behaviour == asBEHAVE_VALUE_CAST ? " opConv(" : " opImplConv(";
if( func.parameterTypes.GetLength() )
decl += "?&out";
decl += ")";
func.id = RegisterMethodToObjectType(objectType, decl.AddressOf(), funcPointer, callConv, auxiliary);
}
// Deprecated since 2.30.0, 2014-12-30
else if( behaviour == asBEHAVE_REF_CAST ||
behaviour == asBEHAVE_IMPLICIT_REF_CAST )
{
// There are two allowed signatures
// 1. obj @f()
// 2. void f(?&out)
if( !(func.parameterTypes.GetLength() == 0 && func.returnType.IsObjectHandle()) &&
!(func.parameterTypes.GetLength() == 1 && func.parameterTypes[0].GetTokenType() == ttQuestion && func.inOutFlags[0] == asTM_OUTREF && func.returnType.GetTokenType() == ttVoid) )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Currently it is not supported to register const overloads for the ref cast behaviour
if( func.IsReadOnly() )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
asCString decl;
decl += func.returnType.Format(defaultNamespace);
if( internal.returnAutoHandle )
decl += "+";
decl += behaviour == asBEHAVE_REF_CAST ? " opCast(" : " opImplCast(";
if( func.parameterTypes.GetLength() )
decl += "?&out";
decl += ")";
func.id = RegisterMethodToObjectType(objectType, decl.AddressOf(), funcPointer, callConv, auxiliary);
}
#endif
else if ( behaviour == asBEHAVE_GET_WEAKREF_FLAG )
{
// This behaviour is only allowed for reference types
if( !(func.objectType->flags & asOBJ_REF) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Don't allow it if the type is registered with nohandle or scoped
if( func.objectType->flags & (asOBJ_NOHANDLE|asOBJ_SCOPED) )
{
WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE);
return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
// Verify that the return type is a reference since it needs to return a pointer to an asISharedBool
if( !func.returnType.IsReference() )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Verify that there are no parameters
if( func.parameterTypes.GetLength() != 0 )
return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
if( beh->getWeakRefFlag )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
func.id = beh->getWeakRefFlag = AddBehaviourFunction(func, internal);
}
else
{
asASSERT(false);
return ConfigError(asINVALID_ARG, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
}
if( func.id < 0 )
return ConfigError(func.id, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl);
// Return function id as success
return func.id;
}
int asCScriptEngine::SetTemplateRestrictions(asCObjectType *templateType, asCScriptFunction *func, const char *caller, const char *decl)
{
asASSERT(templateType->flags && asOBJ_TEMPLATE);
for (asUINT subTypeIdx = 0; subTypeIdx < templateType->templateSubTypes.GetLength(); subTypeIdx++)
{
if (func->returnType.GetTypeInfo() == templateType->templateSubTypes[subTypeIdx].GetTypeInfo())
{
if (func->returnType.IsObjectHandle())
templateType->acceptValueSubType = false;
else if (!func->returnType.IsReference())
templateType->acceptRefSubType = false;
// Can't support template subtypes by value, since each type is treated differently in the ABI
if (!func->returnType.IsObjectHandle() && !func->returnType.IsReference())
return ConfigError(asNOT_SUPPORTED, caller, templateType->name.AddressOf(), decl);
}
for (asUINT n = 0; n < func->parameterTypes.GetLength(); n++)
{
if (func->parameterTypes[n].GetTypeInfo() == templateType->templateSubTypes[subTypeIdx].GetTypeInfo())
{
// TODO: If unsafe references are allowed, then inout references allow value types
if (func->parameterTypes[n].IsObjectHandle() || (func->parameterTypes[n].IsReference() && func->inOutFlags[n] == asTM_INOUTREF))
templateType->acceptValueSubType = false;
else if (!func->parameterTypes[n].IsReference())
templateType->acceptRefSubType = false;
// Can't support template subtypes by value, since each type is treated differently in the ABI
if (!func->parameterTypes[n].IsObjectHandle() && !func->parameterTypes[n].IsReference())
return ConfigError(asNOT_SUPPORTED, caller, templateType->name.AddressOf(), decl);
}
}
}
return asSUCCESS;
}
int asCScriptEngine::VerifyVarTypeNotInFunction(asCScriptFunction *func)
{
// Don't allow var type in this function
if( func->returnType.GetTokenType() == ttQuestion )
return asINVALID_DECLARATION;
for( unsigned int n = 0; n < func->parameterTypes.GetLength(); n++ )
if( func->parameterTypes[n].GetTokenType() == ttQuestion )
return asINVALID_DECLARATION;
return 0;
}
int asCScriptEngine::AddBehaviourFunction(asCScriptFunction &func, asSSystemFunctionInterface &internal)
{
asUINT n;
int id = GetNextScriptFunctionId();
asSSystemFunctionInterface *newInterface = asNEW(asSSystemFunctionInterface)(internal);
if( newInterface == 0 )
return asOUT_OF_MEMORY;
asCScriptFunction *f = asNEW(asCScriptFunction)(this, 0, asFUNC_SYSTEM);
if( f == 0 )
{
asDELETE(newInterface, asSSystemFunctionInterface);
return asOUT_OF_MEMORY;
}
asASSERT(func.name != "" && func.name != "f");
f->name = func.name;
f->sysFuncIntf = newInterface;
f->returnType = func.returnType;
f->objectType = func.objectType;
if( f->objectType )
f->objectType->AddRefInternal();
f->id = id;
f->isReadOnly = func.isReadOnly;
f->accessMask = defaultAccessMask;
f->parameterTypes = func.parameterTypes;
f->parameterNames = func.parameterNames;
f->inOutFlags = func.inOutFlags;
for( n = 0; n < func.defaultArgs.GetLength(); n++ )
if( func.defaultArgs[n] )
f->defaultArgs.PushLast(asNEW(asCString)(*func.defaultArgs[n]));
else
f->defaultArgs.PushLast(0);
AddScriptFunction(f);
// If parameter type from other groups are used, add references
currentGroup->AddReferencesForFunc(this, f);
return id;
}
// interface
int asCScriptEngine::RegisterGlobalProperty(const char *declaration, void *pointer)
{
// Don't accept a null pointer
if( pointer == 0 )
return ConfigError(asINVALID_ARG, "RegisterGlobalProperty", declaration, 0);
asCDataType type;
asCString name;
int r;
asCBuilder bld(this, 0);
if( (r = bld.VerifyProperty(0, declaration, name, type, defaultNamespace)) < 0 )
return ConfigError(r, "RegisterGlobalProperty", declaration, 0);
// Don't allow registering references as global properties
if( type.IsReference() )
return ConfigError(asINVALID_TYPE, "RegisterGlobalProperty", declaration, 0);
// Store the property info
asCGlobalProperty *prop = AllocateGlobalProperty();
prop->name = name;
prop->nameSpace = defaultNamespace;
prop->type = type;
prop->accessMask = defaultAccessMask;
prop->SetRegisteredAddress(pointer);
varAddressMap.Insert(prop->GetAddressOfValue(), prop);
registeredGlobalProps.Put(prop);
prop->AddRef();
currentGroup->globalProps.PushLast(prop);
currentGroup->AddReferencesForType(this, type.GetTypeInfo());
return asSUCCESS;
}
// internal
asCGlobalProperty *asCScriptEngine::AllocateGlobalProperty()
{
asCGlobalProperty *prop = asNEW(asCGlobalProperty);
if( prop == 0 )
{
// Out of memory
return 0;
}
// First check the availability of a free slot
if( freeGlobalPropertyIds.GetLength() )
{
prop->id = freeGlobalPropertyIds.PopLast();
globalProperties[prop->id] = prop;
return prop;
}
prop->id = (asUINT)globalProperties.GetLength();
globalProperties.PushLast(prop);
return prop;
}
// internal
void asCScriptEngine::RemoveGlobalProperty(asCGlobalProperty *prop)
{
int index = globalProperties.IndexOf(prop);
if( index >= 0 )
{
freeGlobalPropertyIds.PushLast(index);
globalProperties[index] = 0;
asSMapNode<void*, asCGlobalProperty*> *node;
varAddressMap.MoveTo(&node, prop->GetAddressOfValue());
//asASSERT(node);
if( node )
varAddressMap.Erase(node);
prop->Release();
}
}
// interface
asUINT asCScriptEngine::GetGlobalPropertyCount() const
{
return asUINT(registeredGlobalProps.GetSize());
}
// interface
// TODO: If the typeId ever encodes the const flag, then the isConst parameter should be removed
int asCScriptEngine::GetGlobalPropertyByIndex(asUINT index, const char **name, const char **nameSpace, int *typeId, bool *isConst, const char **configGroup, void **pointer, asDWORD *accessMask) const
{
const asCGlobalProperty *prop = registeredGlobalProps.Get(index);
if( !prop )
return asINVALID_ARG;
if( name ) *name = prop->name.AddressOf();
if( nameSpace ) *nameSpace = prop->nameSpace->name.AddressOf();
if( typeId ) *typeId = GetTypeIdFromDataType(prop->type);
if( isConst ) *isConst = prop->type.IsReadOnly();
if( pointer ) *pointer = prop->GetRegisteredAddress();
if( accessMask ) *accessMask = prop->accessMask;
if( configGroup )
{
asCConfigGroup *group = FindConfigGroupForGlobalVar(index);
if( group )
*configGroup = group->groupName.AddressOf();
else
*configGroup = 0;
}
return asSUCCESS;
}
// interface
int asCScriptEngine::GetGlobalPropertyIndexByName(const char *name) const
{
asSNameSpace *ns = defaultNamespace;
// Find the global var id
while( ns )
{
int id = registeredGlobalProps.GetFirstIndex(ns, name);
if( id >= 0 )
return id;
// Recursively search parent namespace
ns = GetParentNameSpace(ns);
}
return asNO_GLOBAL_VAR;
}
// interface
int asCScriptEngine::GetGlobalPropertyIndexByDecl(const char *decl) const
{
// This const cast is OK. The builder won't modify the engine
asCBuilder bld(const_cast<asCScriptEngine*>(this), 0);
// Don't write parser errors to the message callback
bld.silent = true;
asCString name;
asSNameSpace *ns;
asCDataType dt;
int r = bld.ParseVariableDeclaration(decl, defaultNamespace, name, ns, dt);
if( r < 0 )
return r;
// Search for a match
while( ns )
{
int id = registeredGlobalProps.GetFirstIndex(ns, name, asCCompGlobPropType(dt));
if( id >= 0 )
return id;
ns = GetParentNameSpace(ns);
}
return asNO_GLOBAL_VAR;
}
// interface
int asCScriptEngine::RegisterObjectMethod(const char *obj, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
if( obj == 0 )
return ConfigError(asINVALID_ARG, "RegisterObjectMethod", obj, declaration);
// Determine the object type
asCDataType dt;
asCBuilder bld(this, 0);
int r = bld.ParseDataType(obj, &dt, defaultNamespace);
if( r < 0 )
return ConfigError(r, "RegisterObjectMethod", obj, declaration);
// Don't allow application to modify primitives or handles
if( dt.GetTypeInfo() == 0 || dt.IsObjectHandle() )
return ConfigError(asINVALID_ARG, "RegisterObjectMethod", obj, declaration);
// Don't allow application to modify built-in types or funcdefs
if( dt.GetTypeInfo() == &functionBehaviours ||
dt.GetTypeInfo() == &scriptTypeBehaviours ||
dt.GetTypeInfo()->CastToFuncdefType() )
return ConfigError(asINVALID_ARG, "RegisterObjectMethod", obj, declaration);
// Don't allow modifying generated template instances
if( dt.GetTypeInfo() && (dt.GetTypeInfo()->flags & asOBJ_TEMPLATE) && generatedTemplateTypes.Exists(dt.GetTypeInfo()->CastToObjectType()) )
return ConfigError(asINVALID_TYPE, "RegisterObjectMethod", obj, declaration);
return RegisterMethodToObjectType(dt.GetTypeInfo()->CastToObjectType(), declaration, funcPointer, callConv, auxiliary);
}
// internal
int asCScriptEngine::RegisterMethodToObjectType(asCObjectType *objectType, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
#ifdef AS_MAX_PORTABILITY
if( callConv != asCALL_GENERIC )
return ConfigError(asNOT_SUPPORTED, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
#endif
asSSystemFunctionInterface internal;
int r = DetectCallingConvention(true, funcPointer, callConv, auxiliary, &internal);
if( r < 0 )
return ConfigError(r, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
// TODO: cleanup: This is identical to what is in RegisterMethodToObjectType
// If the object type is a template, make sure there are no generated instances already
if( objectType->flags & asOBJ_TEMPLATE )
{
for( asUINT n = 0; n < generatedTemplateTypes.GetLength(); n++ )
{
asCObjectType *tmpl = generatedTemplateTypes[n];
if( tmpl->name == objectType->name &&
tmpl->nameSpace == objectType->nameSpace &&
!(tmpl->templateSubTypes[0].GetTypeInfo() && (tmpl->templateSubTypes[0].GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE)) )
{
asCString msg;
msg.Format(TXT_TEMPLATE_s_ALREADY_GENERATED_CANT_REGISTER, asCDataType::CreateType(tmpl, false).Format(tmpl->nameSpace).AddressOf());
WriteMessage("",0,0, asMSGTYPE_ERROR, msg.AddressOf());
return ConfigError(asERROR, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
}
}
isPrepared = false;
// Put the system function in the list of system functions
asSSystemFunctionInterface *newInterface = asNEW(asSSystemFunctionInterface)(internal);
if( newInterface == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_SYSTEM);
if( func == 0 )
{
asDELETE(newInterface, asSSystemFunctionInterface);
return ConfigError(asOUT_OF_MEMORY, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
func->sysFuncIntf = newInterface;
func->objectType = objectType;
func->objectType->AddRefInternal();
asCBuilder bld(this, 0);
r = bld.ParseFunctionDeclaration(func->objectType, declaration, func, true, &newInterface->paramAutoHandles, &newInterface->returnAutoHandle);
if( r < 0 )
{
// Set as dummy function before deleting
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asINVALID_DECLARATION, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
// Check name conflicts
r = bld.CheckNameConflictMember(objectType, func->name.AddressOf(), 0, 0, false);
if( r < 0 )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asNAME_TAKEN, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
// Check against duplicate methods
if( func->name == "opConv" || func->name == "opImplConv" || func->name == "opCast" || func->name == "opImplCast" )
{
// opConv and opCast are special methods that the compiler differentiates between by the return type
for( asUINT n = 0; n < func->objectType->methods.GetLength(); n++ )
{
asCScriptFunction *f = scriptFunctions[func->objectType->methods[n]];
if( f->name == func->name &&
f->IsSignatureExceptNameEqual(func) )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asALREADY_REGISTERED, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
}
}
else
{
for( asUINT n = 0; n < func->objectType->methods.GetLength(); n++ )
{
asCScriptFunction *f = scriptFunctions[func->objectType->methods[n]];
if( f->name == func->name &&
f->IsSignatureExceptNameAndReturnTypeEqual(func) )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asALREADY_REGISTERED, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
}
}
}
func->id = GetNextScriptFunctionId();
func->objectType->methods.PushLast(func->id);
func->accessMask = defaultAccessMask;
AddScriptFunction(func);
// If parameter type from other groups are used, add references
currentGroup->AddReferencesForFunc(this, func);
// Check if the method restricts that use of the template to value types or reference types
if( func->objectType->flags & asOBJ_TEMPLATE )
{
r = SetTemplateRestrictions(func->objectType, func, "RegisterObjectMethod", declaration);
if (r < 0)
return r;
}
// TODO: beh.copy member will be removed, so this is not necessary
// Is this the default copy behaviour?
if( func->name == "opAssign" && func->parameterTypes.GetLength() == 1 && func->isReadOnly == false &&
((objectType->flags & asOBJ_SCRIPT_OBJECT) || func->parameterTypes[0].IsEqualExceptRefAndConst(asCDataType::CreateType(func->objectType, false))) )
{
if( func->objectType->beh.copy != 0 )
return ConfigError(asALREADY_REGISTERED, "RegisterObjectMethod", objectType->name.AddressOf(), declaration);
func->objectType->beh.copy = func->id;
func->AddRefInternal();
}
// Return the function id as success
return func->id;
}
// interface
int asCScriptEngine::RegisterGlobalFunction(const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
#ifdef AS_MAX_PORTABILITY
if( callConv != asCALL_GENERIC )
return ConfigError(asNOT_SUPPORTED, "RegisterGlobalFunction", declaration, 0);
#endif
asSSystemFunctionInterface internal;
int r = DetectCallingConvention(false, funcPointer, callConv, auxiliary, &internal);
if( r < 0 )
return ConfigError(r, "RegisterGlobalFunction", declaration, 0);
isPrepared = false;
// Put the system function in the list of system functions
asSSystemFunctionInterface *newInterface = asNEW(asSSystemFunctionInterface)(internal);
if( newInterface == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterGlobalFunction", declaration, 0);
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_SYSTEM);
if( func == 0 )
{
asDELETE(newInterface, asSSystemFunctionInterface);
return ConfigError(asOUT_OF_MEMORY, "RegisterGlobalFunction", declaration, 0);
}
func->sysFuncIntf = newInterface;
asCBuilder bld(this, 0);
r = bld.ParseFunctionDeclaration(0, declaration, func, true, &newInterface->paramAutoHandles, &newInterface->returnAutoHandle, defaultNamespace);
if( r < 0 )
{
// Set as dummy function before deleting
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asINVALID_DECLARATION, "RegisterGlobalFunction", declaration, 0);
}
// TODO: namespace: What if the declaration defined an explicit namespace?
func->nameSpace = defaultNamespace;
// Check name conflicts
r = bld.CheckNameConflict(func->name.AddressOf(), 0, 0, defaultNamespace);
if( r < 0 )
{
// Set as dummy function before deleting
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asNAME_TAKEN, "RegisterGlobalFunction", declaration, 0);
}
// Make sure the function is not identical to a previously registered function
asUINT n;
const asCArray<unsigned int> &idxs = registeredGlobalFuncs.GetIndexes(func->nameSpace, func->name);
for( n = 0; n < idxs.GetLength(); n++ )
{
asCScriptFunction *f = registeredGlobalFuncs.Get(idxs[n]);
if( f->IsSignatureExceptNameAndReturnTypeEqual(func) )
{
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asALREADY_REGISTERED, "RegisterGlobalFunction", declaration, 0);
}
}
func->id = GetNextScriptFunctionId();
AddScriptFunction(func);
currentGroup->scriptFunctions.PushLast(func);
func->accessMask = defaultAccessMask;
registeredGlobalFuncs.Put(func);
// If parameter type from other groups are used, add references
currentGroup->AddReferencesForFunc(this, func);
// Return the function id as success
return func->id;
}
// interface
asUINT asCScriptEngine::GetGlobalFunctionCount() const
{
// Don't count the builtin delegate factory
return asUINT(registeredGlobalFuncs.GetSize()-1);
}
// interface
asIScriptFunction *asCScriptEngine::GetGlobalFunctionByIndex(asUINT index) const
{
// Don't count the builtin delegate factory
index++;
if( index >= registeredGlobalFuncs.GetSize() )
return 0;
return static_cast<asIScriptFunction*>(const_cast<asCScriptFunction*>(registeredGlobalFuncs.Get(index)));
}
// interface
asIScriptFunction *asCScriptEngine::GetGlobalFunctionByDecl(const char *decl) const
{
asCBuilder bld(const_cast<asCScriptEngine*>(this), 0);
// Don't write parser errors to the message callback
bld.silent = true;
asCScriptFunction func(const_cast<asCScriptEngine*>(this), 0, asFUNC_DUMMY);
int r = bld.ParseFunctionDeclaration(0, decl, &func, false, 0, 0, defaultNamespace);
if( r < 0 )
return 0;
asSNameSpace *ns = defaultNamespace;
// Search script functions for matching interface
while( ns )
{
asIScriptFunction *f = 0;
const asCArray<unsigned int> &idxs = registeredGlobalFuncs.GetIndexes(ns, func.name);
for( unsigned int n = 0; n < idxs.GetLength(); n++ )
{
const asCScriptFunction *funcPtr = registeredGlobalFuncs.Get(idxs[n]);
if( funcPtr->objectType == 0 &&
func.returnType == funcPtr->returnType &&
func.parameterTypes.GetLength() == funcPtr->parameterTypes.GetLength()
)
{
bool match = true;
for( asUINT p = 0; p < func.parameterTypes.GetLength(); ++p )
{
if( func.parameterTypes[p] != funcPtr->parameterTypes[p] )
{
match = false;
break;
}
}
if( match )
{
if( f == 0 )
f = const_cast<asCScriptFunction*>(funcPtr);
else
// Multiple functions
return 0;
}
}
}
if( f )
return f;
// Recursively search parent namespaces
ns = GetParentNameSpace(ns);
}
return 0;
}
asCTypeInfo *asCScriptEngine::GetRegisteredType(const asCString &type, asSNameSpace *ns) const
{
asSMapNode<asSNameSpaceNamePair, asCTypeInfo *> *cursor;
if( allRegisteredTypes.MoveTo(&cursor, asSNameSpaceNamePair(ns, type)) )
return cursor->value;
return 0;
}
void asCScriptEngine::PrepareEngine()
{
if( isPrepared ) return;
if( configFailed ) return;
asUINT n;
for( n = 0; n < scriptFunctions.GetLength(); n++ )
{
// Determine the host application interface
if( scriptFunctions[n] && scriptFunctions[n]->funcType == asFUNC_SYSTEM )
{
if( scriptFunctions[n]->sysFuncIntf->callConv == ICC_GENERIC_FUNC ||
scriptFunctions[n]->sysFuncIntf->callConv == ICC_GENERIC_METHOD )
PrepareSystemFunctionGeneric(scriptFunctions[n], scriptFunctions[n]->sysFuncIntf, this);
else
PrepareSystemFunction(scriptFunctions[n], scriptFunctions[n]->sysFuncIntf, this);
}
}
// Validate object type registrations
for( n = 0; n < registeredObjTypes.GetLength(); n++ )
{
asCObjectType *type = registeredObjTypes[n];
if( type && !(type->flags & asOBJ_SCRIPT_OBJECT) )
{
bool missingBehaviour = false;
const char *infoMsg = 0;
// Verify that GC types have all behaviours
if( type->flags & asOBJ_GC )
{
if( type->beh.addref == 0 ||
type->beh.release == 0 ||
type->beh.gcGetRefCount == 0 ||
type->beh.gcSetFlag == 0 ||
type->beh.gcGetFlag == 0 ||
type->beh.gcEnumReferences == 0 ||
type->beh.gcReleaseAllReferences == 0 )
{
infoMsg = TXT_GC_REQUIRE_ADD_REL_GC_BEHAVIOUR;
missingBehaviour = true;
}
}
// Verify that scoped ref types have the release behaviour
else if( type->flags & asOBJ_SCOPED )
{
if( type->beh.release == 0 )
{
infoMsg = TXT_SCOPE_REQUIRE_REL_BEHAVIOUR;
missingBehaviour = true;
}
}
// Verify that ref types have add ref and release behaviours
else if( (type->flags & asOBJ_REF) &&
!(type->flags & asOBJ_NOHANDLE) &&
!(type->flags & asOBJ_NOCOUNT) )
{
if( type->beh.addref == 0 ||
type->beh.release == 0 )
{
infoMsg = TXT_REF_REQUIRE_ADD_REL_BEHAVIOUR;
missingBehaviour = true;
}
}
// Verify that non-pod value types have the constructor and destructor registered
else if( (type->flags & asOBJ_VALUE) &&
!(type->flags & asOBJ_POD) )
{
if( type->beh.construct == 0 ||
type->beh.destruct == 0 )
{
infoMsg = TXT_NON_POD_REQUIRE_CONSTR_DESTR_BEHAVIOUR;
missingBehaviour = true;
}
}
if( missingBehaviour )
{
asCString str;
str.Format(TXT_TYPE_s_IS_MISSING_BEHAVIOURS, type->name.AddressOf());
WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, infoMsg);
ConfigError(asINVALID_CONFIGURATION, 0, 0, 0);
}
}
}
isPrepared = true;
}
int asCScriptEngine::ConfigError(int err, const char *funcName, const char *arg1, const char *arg2)
{
configFailed = true;
if( funcName )
{
asCString str;
if( arg1 )
{
if( arg2 )
str.Format(TXT_FAILED_IN_FUNC_s_WITH_s_AND_s_d, funcName, arg1, arg2, err);
else
str.Format(TXT_FAILED_IN_FUNC_s_WITH_s_d, funcName, arg1, err);
}
else
str.Format(TXT_FAILED_IN_FUNC_s_d, funcName, err);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
}
return err;
}
// interface
int asCScriptEngine::RegisterDefaultArrayType(const char *type)
{
asCBuilder bld(this, 0);
asCDataType dt;
int r = bld.ParseDataType(type, &dt, defaultNamespace);
if( r < 0 ) return r;
if( dt.GetTypeInfo() == 0 ||
!(dt.GetTypeInfo()->GetFlags() & asOBJ_TEMPLATE) )
return asINVALID_TYPE;
defaultArrayObjectType = dt.GetTypeInfo()->CastToObjectType();
defaultArrayObjectType->AddRefInternal();
return 0;
}
// interface
int asCScriptEngine::GetDefaultArrayTypeId() const
{
if( defaultArrayObjectType )
return GetTypeIdFromDataType(asCDataType::CreateType(defaultArrayObjectType, false));
return asINVALID_TYPE;
}
// interface
int asCScriptEngine::RegisterStringFactory(const char *datatype, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary)
{
asSSystemFunctionInterface internal;
int r = DetectCallingConvention(false, funcPointer, callConv, auxiliary, &internal);
if( r < 0 )
return ConfigError(r, "RegisterStringFactory", datatype, 0);
#ifdef AS_MAX_PORTABILITY
if( callConv != asCALL_GENERIC )
return ConfigError(asNOT_SUPPORTED, "RegisterStringFactory", datatype, 0);
#else
if( callConv != asCALL_CDECL &&
callConv != asCALL_STDCALL &&
callConv != asCALL_THISCALL_ASGLOBAL &&
callConv != asCALL_GENERIC )
return ConfigError(asNOT_SUPPORTED, "RegisterStringFactory", datatype, 0);
#endif
// Put the system function in the list of system functions
asSSystemFunctionInterface *newInterface = asNEW(asSSystemFunctionInterface)(internal);
if( newInterface == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterStringFactory", datatype, 0);
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_SYSTEM);
if( func == 0 )
{
asDELETE(newInterface, asSSystemFunctionInterface);
return ConfigError(asOUT_OF_MEMORY, "RegisterStringFactory", datatype, 0);
}
func->name = "$str";
func->sysFuncIntf = newInterface;
asCBuilder bld(this, 0);
asCDataType dt;
r = bld.ParseDataType(datatype, &dt, defaultNamespace, true);
if( r < 0 )
{
// Set as dummy before deleting
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asINVALID_TYPE, "RegisterStringFactory", datatype, 0);
}
func->returnType = dt;
func->parameterTypes.PushLast(asCDataType::CreatePrimitive(ttInt, true));
func->inOutFlags.PushLast(asTM_NONE);
asCDataType parm1 = asCDataType::CreatePrimitive(ttUInt8, true);
parm1.MakeReference(true);
func->parameterTypes.PushLast(parm1);
func->inOutFlags.PushLast(asTM_INREF);
func->id = GetNextScriptFunctionId();
AddScriptFunction(func);
stringFactory = func;
if( func->returnType.GetTypeInfo() )
{
asCConfigGroup *group = FindConfigGroupForTypeInfo(func->returnType.GetTypeInfo());
if( group == 0 ) group = &defaultGroup;
group->scriptFunctions.PushLast(func);
}
// Register function id as success
return func->id;
}
// interface
int asCScriptEngine::GetStringFactoryReturnTypeId(asDWORD *flags) const
{
if( stringFactory == 0 )
return asNO_FUNCTION;
return stringFactory->GetReturnTypeId(flags);
}
// internal
asCModule *asCScriptEngine::GetModule(const char *name, bool create)
{
// Accept null as well as zero-length string
if( name == 0 ) name = "";
asCModule *retModule = 0;
ACQUIRESHARED(engineRWLock);
if( lastModule && lastModule->name == name )
retModule = lastModule;
else
{
// TODO: optimize: Improve linear search
for( asUINT n = 0; n < scriptModules.GetLength(); ++n )
if( scriptModules[n] && scriptModules[n]->name == name )
{
retModule = scriptModules[n];
break;
}
}
RELEASESHARED(engineRWLock);
if( retModule )
{
ACQUIREEXCLUSIVE(engineRWLock);
lastModule = retModule;
RELEASEEXCLUSIVE(engineRWLock);
return retModule;
}
if( create )
{
retModule = asNEW(asCModule)(name, this);
if( retModule == 0 )
{
// Out of memory
return 0;
}
ACQUIREEXCLUSIVE(engineRWLock);
scriptModules.PushLast(retModule);
lastModule = retModule;
RELEASEEXCLUSIVE(engineRWLock);
}
return retModule;
}
asCModule *asCScriptEngine::GetModuleFromFuncId(int id)
{
if( id < 0 ) return 0;
if( id >= (int)scriptFunctions.GetLength() ) return 0;
asCScriptFunction *func = scriptFunctions[id];
if( func == 0 ) return 0;
return func->module;
}
// internal
int asCScriptEngine::RequestBuild()
{
ACQUIREEXCLUSIVE(engineRWLock);
if( isBuilding )
{
RELEASEEXCLUSIVE(engineRWLock);
return asBUILD_IN_PROGRESS;
}
isBuilding = true;
RELEASEEXCLUSIVE(engineRWLock);
return 0;
}
// internal
void asCScriptEngine::BuildCompleted()
{
// Always free up pooled memory after a completed build
memoryMgr.FreeUnusedMemory();
isBuilding = false;
}
void asCScriptEngine::RemoveTemplateInstanceType(asCObjectType *t)
{
// If there is a module that still owns the generated type, then don't remove it
if( t->module )
return;
// Don't remove it if there are external refernces
if( t->externalRefCount.get() )
return;
// Only remove the template instance type if no config group is using it
if( defaultGroup.generatedTemplateInstances.Exists(t) )
return;
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
if( configGroups[n]->generatedTemplateInstances.Exists(t) )
return;
t->DestroyInternal();
templateInstanceTypes.RemoveValue(t);
generatedTemplateTypes.RemoveValue(t);
t->ReleaseInternal();
}
// internal
asCObjectType *asCScriptEngine::GetTemplateInstanceType(asCObjectType *templateType, asCArray<asCDataType> &subTypes, asCModule *requestingModule)
{
asUINT n;
// Is there any template instance type or template specialization already with this subtype?
for( n = 0; n < templateInstanceTypes.GetLength(); n++ )
{
asCObjectType *type = templateInstanceTypes[n];
if( type &&
type->name == templateType->name &&
type->templateSubTypes == subTypes )
{
// If the template instance is generated, then the module should hold a reference
// to it so the config group can determine see that the template type is in use.
// Template specializations will be treated as normal types
if( requestingModule && generatedTemplateTypes.Exists(type) )
{
if( type->module == 0 )
{
// Set the ownership of this template type
// It may be without ownership if it was previously created from application with for example GetTypeInfoByDecl
type->module = requestingModule;
}
if( !requestingModule->templateInstances.Exists(type) )
{
requestingModule->templateInstances.PushLast(type);
type->AddRefInternal();
}
}
return templateInstanceTypes[n];
}
}
// No previous template instance exists
// Make sure this template supports the subtype
for( n = 0; n < subTypes.GetLength(); n++ )
{
if( !templateType->acceptValueSubType && (subTypes[n].IsPrimitive() || (subTypes[n].GetTypeInfo()->flags & asOBJ_VALUE)) )
return 0;
if( !templateType->acceptRefSubType && (subTypes[n].IsObject() && (subTypes[n].GetTypeInfo()->flags & asOBJ_REF)) )
return 0;
}
// Create a new template instance type based on the templateType
asCObjectType *ot = asNEW(asCObjectType)(this);
if( ot == 0 )
{
// Out of memory
return 0;
}
ot->templateSubTypes = subTypes;
ot->flags = templateType->flags;
ot->size = templateType->size;
ot->name = templateType->name;
ot->nameSpace = templateType->nameSpace;
// If the template is being requested from a module, then the module should hold a reference to the type
if( requestingModule )
{
// Set the ownership of this template type
ot->module = requestingModule;
requestingModule->templateInstances.PushLast(ot);
ot->AddRefInternal();
}
else
{
// If the template type is not requested directly from a module, then set the ownership
// of it to the same module as one of the subtypes. If none of the subtypes are owned by]
// any module, the template instance will be without ownership and can be removed from the
// engine at any time (unless the application holds an external reference).
for( n = 0; n < subTypes.GetLength(); n++ )
{
if( subTypes[n].GetTypeInfo() )
{
ot->module = subTypes[n].GetTypeInfo()->module;
if( ot->module )
{
ot->module->templateInstances.PushLast(ot);
ot->AddRefInternal();
break;
}
}
}
}
// Before filling in the methods, call the template instance callback behaviour to validate the type
if( templateType->beh.templateCallback )
{
// If the validation is deferred then the validation will be done later,
// so it is necessary to continue the preparation of the template instance type
if( !deferValidationOfTemplateTypes )
{
asCScriptFunction *callback = scriptFunctions[templateType->beh.templateCallback];
bool dontGarbageCollect = false;
if( !CallGlobalFunctionRetBool(ot, &dontGarbageCollect, callback->sysFuncIntf, callback) )
{
// The type cannot be instantiated
ot->templateSubTypes.SetLength(0);
if( ot->module )
{
ot->module->templateInstances.RemoveValue(ot);
ot->ReleaseInternal();
}
ot->ReleaseInternal();
return 0;
}
// If the callback said this template instance won't be garbage collected then remove the flag
if( dontGarbageCollect )
ot->flags &= ~asOBJ_GC;
}
ot->beh.templateCallback = templateType->beh.templateCallback;
scriptFunctions[ot->beh.templateCallback]->AddRefInternal();
}
ot->methods = templateType->methods;
for( n = 0; n < ot->methods.GetLength(); n++ )
scriptFunctions[ot->methods[n]]->AddRefInternal();
if( templateType->flags & asOBJ_REF )
{
// Store the real factory in the constructor. This is used by the CreateScriptObject function.
// Otherwise it wouldn't be necessary to store the real factory ids.
ot->beh.construct = templateType->beh.factory;
ot->beh.constructors = templateType->beh.factories;
}
else
{
ot->beh.construct = templateType->beh.construct;
ot->beh.constructors = templateType->beh.constructors;
}
for( n = 0; n < ot->beh.constructors.GetLength(); n++ )
scriptFunctions[ot->beh.constructors[n]]->AddRefInternal();
// Before proceeding with the generation of the template functions for the template instance it is necessary
// to include the new template instance type in the list of known types, otherwise it is possible that we get
// a infinite recursive loop as the template instance type is requested again during the generation of the
// template functions.
templateInstanceTypes.PushLast(ot);
// Store the template instance types that have been created automatically by the engine from a template type
// The object types in templateInstanceTypes that are not also in generatedTemplateTypes are registered template specializations
generatedTemplateTypes.PushLast(ot);
// As the new template type is instantiated the engine should
// generate new functions to substitute the ones with the template subtype.
for( n = 0; n < ot->beh.constructors.GetLength(); n++ )
{
int funcId = ot->beh.constructors[n];
asCScriptFunction *func = scriptFunctions[funcId];
if( GenerateNewTemplateFunction(templateType, ot, func, &func) )
{
// Release the old function, the new one already has its ref count set to 1
scriptFunctions[funcId]->ReleaseInternal();
ot->beh.constructors[n] = func->id;
if( ot->beh.construct == funcId )
ot->beh.construct = func->id;
}
}
ot->beh.factory = 0;
if( templateType->flags & asOBJ_REF )
{
// Generate factory stubs for each of the factories
for( n = 0; n < ot->beh.constructors.GetLength(); n++ )
{
asCScriptFunction *func = GenerateTemplateFactoryStub(templateType, ot, ot->beh.constructors[n]);
ot->beh.factories.PushLast(func->id);
// Set the default factory as well
if( ot->beh.constructors[n] == ot->beh.construct )
ot->beh.factory = func->id;
}
}
else
{
// Generate factory stubs for each of the constructors
for( n = 0; n < ot->beh.constructors.GetLength(); n++ )
{
asCScriptFunction *func = GenerateTemplateFactoryStub(templateType, ot, ot->beh.constructors[n]);
if( ot->beh.constructors[n] == ot->beh.construct )
ot->beh.construct = func->id;
// Release previous constructor
scriptFunctions[ot->beh.constructors[n]]->ReleaseInternal();
ot->beh.constructors[n] = func->id;
}
}
// Generate stub for the list factory as well
if( templateType->beh.listFactory )
{
asCScriptFunction *func = GenerateTemplateFactoryStub(templateType, ot, templateType->beh.listFactory);
// Rename the function to easily identify it in LoadByteCode
func->name = "$list";
ot->beh.listFactory = func->id;
}
ot->beh.addref = templateType->beh.addref;
if( scriptFunctions[ot->beh.addref] ) scriptFunctions[ot->beh.addref]->AddRefInternal();
ot->beh.release = templateType->beh.release;
if( scriptFunctions[ot->beh.release] ) scriptFunctions[ot->beh.release]->AddRefInternal();
ot->beh.destruct = templateType->beh.destruct;
if( scriptFunctions[ot->beh.destruct] ) scriptFunctions[ot->beh.destruct]->AddRefInternal();
ot->beh.copy = templateType->beh.copy;
if( scriptFunctions[ot->beh.copy] ) scriptFunctions[ot->beh.copy]->AddRefInternal();
ot->beh.gcGetRefCount = templateType->beh.gcGetRefCount;
if( scriptFunctions[ot->beh.gcGetRefCount] ) scriptFunctions[ot->beh.gcGetRefCount]->AddRefInternal();
ot->beh.gcSetFlag = templateType->beh.gcSetFlag;
if( scriptFunctions[ot->beh.gcSetFlag] ) scriptFunctions[ot->beh.gcSetFlag]->AddRefInternal();
ot->beh.gcGetFlag = templateType->beh.gcGetFlag;
if( scriptFunctions[ot->beh.gcGetFlag] ) scriptFunctions[ot->beh.gcGetFlag]->AddRefInternal();
ot->beh.gcEnumReferences = templateType->beh.gcEnumReferences;
if( scriptFunctions[ot->beh.gcEnumReferences] ) scriptFunctions[ot->beh.gcEnumReferences]->AddRefInternal();
ot->beh.gcReleaseAllReferences = templateType->beh.gcReleaseAllReferences;
if( scriptFunctions[ot->beh.gcReleaseAllReferences] ) scriptFunctions[ot->beh.gcReleaseAllReferences]->AddRefInternal();
ot->beh.getWeakRefFlag = templateType->beh.getWeakRefFlag;
if( scriptFunctions[ot->beh.getWeakRefFlag] ) scriptFunctions[ot->beh.getWeakRefFlag]->AddRefInternal();
// As the new template type is instantiated, the engine should
// generate new functions to substitute the ones with the template subtype.
for( n = 0; n < ot->methods.GetLength(); n++ )
{
int funcId = ot->methods[n];
asCScriptFunction *func = scriptFunctions[funcId];
if( GenerateNewTemplateFunction(templateType, ot, func, &func) )
{
// Release the old function, the new one already has its ref count set to 1
scriptFunctions[funcId]->ReleaseInternal();
ot->methods[n] = func->id;
}
}
// Increase ref counter for sub type if it is an object type
for( n = 0; n < ot->templateSubTypes.GetLength(); n++ )
if( ot->templateSubTypes[n].GetTypeInfo() )
ot->templateSubTypes[n].GetTypeInfo()->AddRefInternal();
// Copy the properties to the template instance
for( n = 0; n < templateType->properties.GetLength(); n++ )
{
asCObjectProperty *prop = templateType->properties[n];
ot->properties.PushLast(asNEW(asCObjectProperty)(*prop));
if( prop->type.GetTypeInfo() )
prop->type.GetTypeInfo()->AddRefInternal();
}
// Any child funcdefs must also be copied to the template instance (with adjustments in case of template subtypes)
for (n = 0; n < templateType->childFuncDefs.GetLength(); n++)
{
asCFuncdefType *funcdef = GenerateNewTemplateFuncdef(templateType, ot, templateType->childFuncDefs[n]);
funcdef->parentClass = ot;
ot->childFuncDefs.PushLast(funcdef);
}
return ot;
}
// interface
asILockableSharedBool *asCScriptEngine::GetWeakRefFlagOfScriptObject(void *obj, const asITypeInfo *type) const
{
// Make sure it is not a null pointer
if( obj == 0 || type == 0 ) return 0;
const asCObjectType *objType = static_cast<const asCObjectType *>(type);
asILockableSharedBool *dest = 0;
if( objType->beh.getWeakRefFlag )
{
// Call the getweakrefflag behaviour
dest = reinterpret_cast<asILockableSharedBool*>(CallObjectMethodRetPtr(obj, objType->beh.getWeakRefFlag));
}
return dest;
}
// internal
asCDataType asCScriptEngine::DetermineTypeForTemplate(const asCDataType &orig, asCObjectType *tmpl, asCObjectType *ot)
{
asCDataType dt;
if( orig.GetTypeInfo() && (orig.GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE) )
{
bool found = false;
for( asUINT n = 0; n < tmpl->templateSubTypes.GetLength(); n++ )
{
if( orig.GetTypeInfo() == tmpl->templateSubTypes[n].GetTypeInfo() )
{
found = true;
dt = ot->templateSubTypes[n];
if( orig.IsObjectHandle() && !ot->templateSubTypes[n].IsObjectHandle() )
{
dt.MakeHandle(true, true);
asASSERT(dt.IsObjectHandle());
if( orig.IsHandleToConst() )
dt.MakeHandleToConst(true);
dt.MakeReference(orig.IsReference());
dt.MakeReadOnly(orig.IsReadOnly());
}
else
{
dt.MakeReference(orig.IsReference());
dt.MakeReadOnly(ot->templateSubTypes[n].IsReadOnly() || orig.IsReadOnly());
}
break;
}
}
asASSERT( found );
UNUSED_VAR( found );
}
else if( orig.GetTypeInfo() == tmpl )
{
if( orig.IsObjectHandle() )
dt = asCDataType::CreateObjectHandle(ot, false);
else
dt = asCDataType::CreateType(ot, false);
dt.MakeReference(orig.IsReference());
dt.MakeReadOnly(orig.IsReadOnly());
}
else if( orig.GetTypeInfo() && (orig.GetTypeInfo()->flags & asOBJ_TEMPLATE) )
{
// The type is itself a template, so it is necessary to find the correct template instance type
asCArray<asCDataType> tmplSubTypes;
asCObjectType *origType = orig.GetTypeInfo()->CastToObjectType();
bool needInstance = true;
// Find the matching replacements for the subtypes
for( asUINT n = 0; n < origType->templateSubTypes.GetLength(); n++ )
{
if( origType->templateSubTypes[n].GetTypeInfo() == 0 ||
!(origType->templateSubTypes[n].GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE) )
{
// The template is already an instance so we shouldn't attempt to create another instance
needInstance = false;
break;
}
for( asUINT m = 0; m < tmpl->templateSubTypes.GetLength(); m++ )
if( origType->templateSubTypes[n].GetTypeInfo() == tmpl->templateSubTypes[m].GetTypeInfo() )
tmplSubTypes.PushLast(ot->templateSubTypes[m]);
if( tmplSubTypes.GetLength() != n+1 )
{
asASSERT( false );
return orig;
}
}
asCObjectType *ntype = origType;
if( needInstance )
{
// Always find the original template type when creating a new template instance otherwise the
// generation will fail since it will attempt to create factory stubs when they already exists, etc
for( asUINT n = 0; n < registeredTemplateTypes.GetLength(); n++ )
if( registeredTemplateTypes[n]->name == origType->name )
{
origType = registeredTemplateTypes[n];
break;
}
ntype = GetTemplateInstanceType(origType, tmplSubTypes, ot->module);
if( ntype == 0 )
{
// It not possible to instantiate the subtype
asASSERT( false );
ntype = tmpl;
}
}
if( orig.IsObjectHandle() )
dt = asCDataType::CreateObjectHandle(ntype, false);
else
dt = asCDataType::CreateType(ntype, false);
dt.MakeReference(orig.IsReference());
dt.MakeReadOnly(orig.IsReadOnly());
}
else
dt = orig;
return dt;
}
// internal
asCScriptFunction *asCScriptEngine::GenerateTemplateFactoryStub(asCObjectType *templateType, asCObjectType *ot, int factoryId)
{
asCScriptFunction *factory = scriptFunctions[factoryId];
// By first instantiating the function as a dummy and then changing it to be a script function
// I avoid having it added to the garbage collector. As it is known that this object will stay
// alive until the template instance is no longer used there is no need to have the GC check
// this function all the time.
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_DUMMY);
if( func == 0 )
{
// Out of memory
return 0;
}
func->funcType = asFUNC_SCRIPT;
func->AllocateScriptFunctionData();
func->id = GetNextScriptFunctionId();
AddScriptFunction(func);
func->isShared = true;
if( templateType->flags & asOBJ_REF )
{
func->name = "$fact";
func->returnType = asCDataType::CreateObjectHandle(ot, false);
}
else
{
func->name = "$beh0";
func->returnType = factory->returnType; // constructors return nothing
func->objectType = ot;
func->objectType->AddRefInternal();
}
// Skip the first parameter as this is the object type pointer that the stub will add
func->parameterTypes.SetLength(factory->parameterTypes.GetLength()-1);
func->parameterNames.SetLength(factory->parameterNames.GetLength()-1);
func->inOutFlags.SetLength(factory->inOutFlags.GetLength()-1);
func->defaultArgs.SetLength(factory->defaultArgs.GetLength()-1);
for( asUINT p = 1; p < factory->parameterTypes.GetLength(); p++ )
{
func->parameterTypes[p-1] = factory->parameterTypes[p];
func->parameterNames[p-1] = factory->parameterNames[p];
func->inOutFlags[p-1] = factory->inOutFlags[p];
func->defaultArgs[p-1] = factory->defaultArgs[p] ? asNEW(asCString)(*factory->defaultArgs[p]) : 0;
}
func->scriptData->objVariablesOnHeap = 0;
// Generate the bytecode for the factory stub
asUINT bcLength = asBCTypeSize[asBCInfo[asBC_OBJTYPE].type] +
asBCTypeSize[asBCInfo[asBC_CALLSYS].type] +
asBCTypeSize[asBCInfo[asBC_RET].type];
if( ep.includeJitInstructions )
bcLength += asBCTypeSize[asBCInfo[asBC_JitEntry].type];
if( templateType->flags & asOBJ_VALUE )
bcLength += asBCTypeSize[asBCInfo[asBC_SwapPtr].type];
func->scriptData->byteCode.SetLength(bcLength);
asDWORD *bc = func->scriptData->byteCode.AddressOf();
if( ep.includeJitInstructions )
{
*(asBYTE*)bc = asBC_JitEntry;
*(asPWORD*)(bc+1) = 0;
bc += asBCTypeSize[asBCInfo[asBC_JitEntry].type];
}
*(asBYTE*)bc = asBC_OBJTYPE;
*(asPWORD*)(bc+1) = (asPWORD)ot;
bc += asBCTypeSize[asBCInfo[asBC_OBJTYPE].type];
if( templateType->flags & asOBJ_VALUE )
{
// Swap the object pointer with the object type
*(asBYTE*)bc = asBC_SwapPtr;
bc += asBCTypeSize[asBCInfo[asBC_SwapPtr].type];
}
*(asBYTE*)bc = asBC_CALLSYS;
*(asDWORD*)(bc+1) = factoryId;
bc += asBCTypeSize[asBCInfo[asBC_CALLSYS].type];
*(asBYTE*)bc = asBC_RET;
*(((asWORD*)bc)+1) = (asWORD)func->GetSpaceNeededForArguments() + (func->objectType ? AS_PTR_SIZE : 0);
func->AddReferences();
func->scriptData->stackNeeded = AS_PTR_SIZE;
// Tell the virtual machine not to clean up the object on exception
func->dontCleanUpOnException = true;
func->JITCompile();
// Need to translate the list pattern too so the VM and compiler will know the correct type of the members
if( factory->listPattern )
{
asSListPatternNode *n = factory->listPattern;
asSListPatternNode *last = 0;
while( n )
{
asSListPatternNode *newNode = n->Duplicate();
if( newNode->type == asLPT_TYPE )
{
asSListPatternDataTypeNode *typeNode = reinterpret_cast<asSListPatternDataTypeNode*>(newNode);
typeNode->dataType = DetermineTypeForTemplate(typeNode->dataType, templateType, ot);
}
if( last )
last->next = newNode;
else
func->listPattern = newNode;
last = newNode;
n = n->next;
}
}
return func;
}
bool asCScriptEngine::RequireTypeReplacement(asCDataType &type, asCObjectType *templateType)
{
if( type.GetTypeInfo() == templateType ) return true;
if( type.GetTypeInfo() && (type.GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE) ) return true;
if( type.GetTypeInfo() && (type.GetTypeInfo()->flags & asOBJ_TEMPLATE) )
{
asCObjectType *ot = type.GetTypeInfo()->CastToObjectType();
for( asUINT n = 0; n < ot->templateSubTypes.GetLength(); n++ )
if( ot->templateSubTypes[n].GetTypeInfo() &&
ot->templateSubTypes[n].GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE )
return true;
}
return false;
}
bool asCScriptEngine::GenerateNewTemplateFunction(asCObjectType *templateType, asCObjectType *ot, asCScriptFunction *func, asCScriptFunction **newFunc)
{
bool needNewFunc = false;
if( RequireTypeReplacement(func->returnType, templateType) )
needNewFunc = true;
else
{
for( asUINT p = 0; p < func->parameterTypes.GetLength(); p++ )
{
if( RequireTypeReplacement(func->parameterTypes[p], templateType) )
{
needNewFunc = true;
break;
}
}
}
if( !needNewFunc )
return false;
asCScriptFunction *func2 = asNEW(asCScriptFunction)(this, 0, func->funcType);
if( func2 == 0 )
{
// Out of memory
return false;
}
func2->name = func->name;
func2->returnType = DetermineTypeForTemplate(func->returnType, templateType, ot);
func2->parameterTypes.SetLength(func->parameterTypes.GetLength());
for (asUINT p = 0; p < func->parameterTypes.GetLength(); p++)
func2->parameterTypes[p] = DetermineTypeForTemplate(func->parameterTypes[p], templateType, ot);
for (asUINT n = 0; n < func->defaultArgs.GetLength(); n++)
if (func->defaultArgs[n])
func2->defaultArgs.PushLast(asNEW(asCString)(*func->defaultArgs[n]));
else
func2->defaultArgs.PushLast(0);
// TODO: template: Must be careful when instantiating templates for garbage collected types
// If the template hasn't been registered with the behaviours, it shouldn't
// permit instantiation of garbage collected types that in turn may refer to
// this instance.
func2->parameterNames = func->parameterNames;
func2->inOutFlags = func->inOutFlags;
func2->isReadOnly = func->isReadOnly;
func2->objectType = ot;
func2->objectType->AddRefInternal();
func2->sysFuncIntf = asNEW(asSSystemFunctionInterface)(*func->sysFuncIntf);
// Adjust the clean up instructions
if( func2->sysFuncIntf->callConv == ICC_GENERIC_FUNC ||
func2->sysFuncIntf->callConv == ICC_GENERIC_METHOD )
PrepareSystemFunctionGeneric(func2, func2->sysFuncIntf, this);
else
PrepareSystemFunction(func2, func2->sysFuncIntf, this);
func2->id = GetNextScriptFunctionId();
AddScriptFunction(func2);
// Return the new function
*newFunc = func2;
return true;
}
asCFuncdefType *asCScriptEngine::GenerateNewTemplateFuncdef(asCObjectType *templateType, asCObjectType *ot, asCFuncdefType *func)
{
asCScriptFunction *func2 = asNEW(asCScriptFunction)(this, 0, func->funcdef->funcType);
if (func2 == 0)
{
// Out of memory
return 0;
}
func2->name = func->name;
func2->returnType = DetermineTypeForTemplate(func->funcdef->returnType, templateType, ot);
func2->parameterTypes.SetLength(func->funcdef->parameterTypes.GetLength());
for (asUINT p = 0; p < func->funcdef->parameterTypes.GetLength(); p++)
func2->parameterTypes[p] = DetermineTypeForTemplate(func->funcdef->parameterTypes[p], templateType, ot);
// TODO: template: Must be careful when instantiating templates for garbage collected types
// If the template hasn't been registered with the behaviours, it shouldn't
// permit instantiation of garbage collected types that in turn may refer to
// this instance.
func2->inOutFlags = func->funcdef->inOutFlags;
func2->isReadOnly = func->funcdef->isReadOnly;
asASSERT(func->funcdef->objectType == 0);
asASSERT(func->funcdef->sysFuncIntf == 0);
func2->id = GetNextScriptFunctionId();
AddScriptFunction(func2);
asCFuncdefType *fdt2 = asNEW(asCFuncdefType)(this, func2);
funcDefs.PushLast(fdt2); // don't increase refCount as the constructor already set it to 1
// Return the new function
return fdt2;
}
void asCScriptEngine::CallObjectMethod(void *obj, int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
CallObjectMethod(obj, s->sysFuncIntf, s);
}
void asCScriptEngine::CallObjectMethod(void *obj, asSSystemFunctionInterface *i, asCScriptFunction *s) const
{
#if defined(__GNUC__) || defined(AS_PSVITA)
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
}
else if( i->callConv == ICC_THISCALL || i->callConv == ICC_VIRTUAL_THISCALL )
{
// For virtual thiscalls we must call the method as a true class method
// so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset; // Same size as the pointer
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
void (asCSimpleDummy::*f)() = p.mthd;
(((asCSimpleDummy*)obj)->*f)();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
void (*f)(void *) = (void (*)(void *))(i->func);
f(obj);
}
#else
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL )
{
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
void (asCSimpleDummy::*f)() = p.mthd;
obj = (void*)(asPWORD(obj) + i->baseOffset);
(((asCSimpleDummy*)obj)->*f)();
}
else
#endif
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
void (*f)(void *) = (void (*)(void *))(i->func);
f(obj);
}
#endif
}
bool asCScriptEngine::CallObjectMethodRetBool(void *obj, int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
asSSystemFunctionInterface *i = s->sysFuncIntf;
#if defined(__GNUC__) || defined(AS_PSVITA)
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(bool*)gen.GetReturnPointer();
}
else if( i->callConv == ICC_THISCALL || i->callConv == ICC_VIRTUAL_THISCALL )
{
// For virtual thiscalls we must call the method as a true class method so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset;
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
bool (asCSimpleDummy::*f)() = (bool (asCSimpleDummy::*)())(p.mthd);
return (((asCSimpleDummy*)obj)->*f)();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
bool (*f)(void *) = (bool (*)(void *))(i->func);
return f(obj);
}
#else
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL )
{
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
bool (asCSimpleDummy::*f)() = (bool (asCSimpleDummy::*)())p.mthd;
obj = (void*)(asPWORD(obj) + i->baseOffset);
return (((asCSimpleDummy*)obj)->*f)();
}
else
#endif
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(bool*)gen.GetReturnPointer();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
bool (*f)(void *) = (bool (*)(void *))(i->func);
return f(obj);
}
#endif
}
int asCScriptEngine::CallObjectMethodRetInt(void *obj, int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
asSSystemFunctionInterface *i = s->sysFuncIntf;
#if defined(__GNUC__) || defined(AS_PSVITA)
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(int*)gen.GetReturnPointer();
}
else if( i->callConv == ICC_THISCALL || i->callConv == ICC_VIRTUAL_THISCALL )
{
// For virtual thiscalls we must call the method as a true class method so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset;
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
int (asCSimpleDummy::*f)() = (int (asCSimpleDummy::*)())(p.mthd);
return (((asCSimpleDummy*)obj)->*f)();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
int (*f)(void *) = (int (*)(void *))(i->func);
return f(obj);
}
#else
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL )
{
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
int (asCSimpleDummy::*f)() = (int (asCSimpleDummy::*)())p.mthd;
obj = (void*)(asPWORD(obj) + i->baseOffset);
return (((asCSimpleDummy*)obj)->*f)();
}
else
#endif
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(int*)gen.GetReturnPointer();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
int (*f)(void *) = (int (*)(void *))(i->func);
return f(obj);
}
#endif
}
void *asCScriptEngine::CallObjectMethodRetPtr(void *obj, int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
asSSystemFunctionInterface *i = s->sysFuncIntf;
#if defined(__GNUC__) || defined(AS_PSVITA)
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(void**)gen.GetReturnPointer();
}
else if( i->callConv == ICC_THISCALL || i->callConv == ICC_VIRTUAL_THISCALL )
{
// For virtual thiscalls we must call the method as a true class method so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset;
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
void *(asCSimpleDummy::*f)() = (void *(asCSimpleDummy::*)())(p.mthd);
return (((asCSimpleDummy*)obj)->*f)();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
void *(*f)(void *) = (void *(*)(void *))(i->func);
return f(obj);
}
#else
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL )
{
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
void *(asCSimpleDummy::*f)() = (void *(asCSimpleDummy::*)())p.mthd;
obj = (void*)(asPWORD(obj) + i->baseOffset);
return (((asCSimpleDummy*)obj)->*f)();
}
else
#endif
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(void **)gen.GetReturnPointer();
}
else /*if( i->callConv == ICC_CDECL_OBJLAST || i->callConv == ICC_CDECL_OBJFIRST )*/
{
void *(*f)(void *) = (void *(*)(void *))(i->func);
return f(obj);
}
#endif
}
void *asCScriptEngine::CallObjectMethodRetPtr(void *obj, int param1, asCScriptFunction *func) const
{
asASSERT( func != 0 );
asSSystemFunctionInterface *i = func->sysFuncIntf;
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL || i->callConv == ICC_VIRTUAL_THISCALL )
{
#if defined(__GNUC__) || defined(AS_PSVITA)
// For virtual thiscalls we must call the method as a true class method so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset;
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
void *(asCSimpleDummy::*f)(int) = (void *(asCSimpleDummy::*)(int))(p.mthd);
return (((asCSimpleDummy*)obj)->*f)(param1);
#else
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
void *(asCSimpleDummy::*f)(int) = (void *(asCSimpleDummy::*)(int))p.mthd;
obj = (void*)(asPWORD(obj) + i->baseOffset);
return (((asCSimpleDummy*)obj)->*f)(param1);
#endif
}
else
#endif
if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), func, obj, reinterpret_cast<asDWORD*>(&param1));
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(void **)gen.GetReturnPointer();
}
else if( i->callConv == ICC_CDECL_OBJLAST )
{
void *(*f)(int, void *) = (void *(*)(int, void *))(i->func);
return f(param1, obj);
}
else /*if( i->callConv == ICC_CDECL_OBJFIRST )*/
{
void *(*f)(void *, int) = (void *(*)(void *, int))(i->func);
return f(obj, param1);
}
}
void *asCScriptEngine::CallGlobalFunctionRetPtr(int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
return CallGlobalFunctionRetPtr(s->sysFuncIntf, s);
}
void *asCScriptEngine::CallGlobalFunctionRetPtr(int func, void *param1) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
return CallGlobalFunctionRetPtr(s->sysFuncIntf, s, param1);
}
void *asCScriptEngine::CallGlobalFunctionRetPtr(asSSystemFunctionInterface *i, asCScriptFunction *s) const
{
if( i->callConv == ICC_CDECL )
{
void *(*f)() = (void *(*)())(i->func);
return f();
}
else if( i->callConv == ICC_STDCALL )
{
typedef void *(STDCALL *func_t)();
func_t f = (func_t)(i->func);
return f();
}
else
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, 0, 0);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(void**)gen.GetReturnPointer();
}
}
void *asCScriptEngine::CallGlobalFunctionRetPtr(asSSystemFunctionInterface *i, asCScriptFunction *s, void *param1) const
{
if( i->callConv == ICC_CDECL )
{
void *(*f)(void *) = (void *(*)(void *))(i->func);
return f(param1);
}
else if( i->callConv == ICC_STDCALL )
{
typedef void *(STDCALL *func_t)(void *);
func_t f = (func_t)(i->func);
return f(param1);
}
else
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, 0, (asDWORD*)&param1);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(void**)gen.GetReturnPointer();
}
}
void asCScriptEngine::CallObjectMethod(void *obj, void *param, int func) const
{
asCScriptFunction *s = scriptFunctions[func];
asASSERT( s != 0 );
CallObjectMethod(obj, param, s->sysFuncIntf, s);
}
void asCScriptEngine::CallObjectMethod(void *obj, void *param, asSSystemFunctionInterface *i, asCScriptFunction *s) const
{
#if defined(__GNUC__) || defined(AS_PSVITA)
if( i->callConv == ICC_CDECL_OBJLAST )
{
void (*f)(void *, void *) = (void (*)(void *, void *))(i->func);
f(param, obj);
}
else if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, (asDWORD*)&param);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
}
else if( i->callConv == ICC_VIRTUAL_THISCALL || i->callConv == ICC_THISCALL )
{
// For virtual thiscalls we must call the method as a true class method
// so that the compiler will lookup the function address in the vftable
union
{
asSIMPLEMETHOD_t mthd;
struct
{
asFUNCTION_t func;
asPWORD baseOffset; // Same size as the pointer
} f;
} p;
p.f.func = (asFUNCTION_t)(i->func);
p.f.baseOffset = asPWORD(i->baseOffset);
void (asCSimpleDummy::*f)(void*) = (void (asCSimpleDummy::*)(void*))(p.mthd);
(((asCSimpleDummy*)obj)->*f)(param);
}
else /*if( i->callConv == ICC_CDECL_OBJFIRST */
{
void (*f)(void *, void *) = (void (*)(void *, void *))(i->func);
f(obj, param);
}
#else
#ifndef AS_NO_CLASS_METHODS
if( i->callConv == ICC_THISCALL )
{
union
{
asSIMPLEMETHOD_t mthd;
asFUNCTION_t func;
} p;
p.func = (asFUNCTION_t)(i->func);
void (asCSimpleDummy::*f)(void *) = (void (asCSimpleDummy::*)(void *))(p.mthd);
obj = (void*)(asPWORD(obj) + i->baseOffset);
(((asCSimpleDummy*)obj)->*f)(param);
}
else
#endif
if( i->callConv == ICC_CDECL_OBJLAST )
{
void (*f)(void *, void *) = (void (*)(void *, void *))(i->func);
f(param, obj);
}
else if( i->callConv == ICC_GENERIC_METHOD )
{
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, obj, (asDWORD*)&param);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
}
else /*if( i->callConv == ICC_CDECL_OBJFIRST )*/
{
void (*f)(void *, void *) = (void (*)(void *, void *))(i->func);
f(obj, param);
}
#endif
}
void asCScriptEngine::CallGlobalFunction(void *param1, void *param2, asSSystemFunctionInterface *i, asCScriptFunction *s) const
{
if( i->callConv == ICC_CDECL )
{
void (*f)(void *, void *) = (void (*)(void *, void *))(i->func);
f(param1, param2);
}
else if( i->callConv == ICC_STDCALL )
{
typedef void (STDCALL *func_t)(void *, void *);
func_t f = (func_t)(i->func);
f(param1, param2);
}
else
{
// We must guarantee the order of the arguments which is why we copy them to this
// array. Otherwise the compiler may put them anywhere it likes, or even keep them
// in the registers which causes problem.
void *params[2] = {param1, param2};
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, 0, (asDWORD*)&params);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
}
}
bool asCScriptEngine::CallGlobalFunctionRetBool(void *param1, void *param2, asSSystemFunctionInterface *i, asCScriptFunction *s) const
{
if( i->callConv == ICC_CDECL )
{
bool (*f)(void *, void *) = (bool (*)(void *, void *))(i->func);
return f(param1, param2);
}
else if( i->callConv == ICC_STDCALL )
{
typedef bool (STDCALL *func_t)(void *, void *);
func_t f = (func_t)(i->func);
return f(param1, param2);
}
else
{
// TODO: When simulating a 64bit environment by defining AS_64BIT_PTR on a 32bit platform this code
// fails, because the stack given to asCGeneric is not prepared with two 64bit arguments.
// We must guarantee the order of the arguments which is why we copy them to this
// array. Otherwise the compiler may put them anywhere it likes, or even keep them
// in the registers which causes problem.
void *params[2] = {param1, param2};
asCGeneric gen(const_cast<asCScriptEngine*>(this), s, 0, (asDWORD*)params);
void (*f)(asIScriptGeneric *) = (void (*)(asIScriptGeneric *))(i->func);
f(&gen);
return *(bool*)gen.GetReturnPointer();
}
}
void *asCScriptEngine::CallAlloc(const asCObjectType *type) const
{
// Allocate 4 bytes as the smallest size. Otherwise CallSystemFunction may try to
// copy a DWORD onto a smaller memory block, in case the object type is return in registers.
// Pad to the next even 4 bytes to avoid asBC_CPY writing outside of allocated buffer for registered POD types
asUINT size = type->size;
if( size & 0x3 )
size += 4 - (size & 0x3);
#ifndef WIP_16BYTE_ALIGN
#if defined(AS_DEBUG)
return ((asALLOCFUNCDEBUG_t)userAlloc)(size, __FILE__, __LINE__);
#else
return userAlloc(size);
#endif
#else
#if defined(AS_DEBUG)
return ((asALLOCALIGNEDFUNCDEBUG_t)userAllocAligned)(size, type->alignment, __FILE__, __LINE__);
#else
return userAllocAligned(size, type->alignment);
#endif
#endif
}
void asCScriptEngine::CallFree(void *obj) const
{
#ifndef WIP_16BYTE_ALIGN
userFree(obj);
#else
userFreeAligned(obj);
#endif
}
// interface
int asCScriptEngine::NotifyGarbageCollectorOfNewObject(void *obj, asITypeInfo *type)
{
return gc.AddScriptObjectToGC(obj, static_cast<asCObjectType*>(type));
}
// interface
int asCScriptEngine::GetObjectInGC(asUINT idx, asUINT *seqNbr, void **obj, asITypeInfo **type)
{
return gc.GetObjectInGC(idx, seqNbr, obj, type);
}
// interface
int asCScriptEngine::GarbageCollect(asDWORD flags, asUINT iterations)
{
int r = gc.GarbageCollect(flags, iterations);
if( r == 0 )
{
// Delete any modules that have been discarded previously but not
// removed due to being referred to by objects in the garbage collector
DeleteDiscardedModules();
}
return r;
}
// interface
void asCScriptEngine::GetGCStatistics(asUINT *currentSize, asUINT *totalDestroyed, asUINT *totalDetected, asUINT *newObjects, asUINT *totalNewDestroyed) const
{
gc.GetStatistics(currentSize, totalDestroyed, totalDetected, newObjects, totalNewDestroyed);
}
// interface
void asCScriptEngine::GCEnumCallback(void *reference)
{
gc.GCEnumCallback(reference);
}
int asCScriptEngine::GetTypeIdFromDataType(const asCDataType &dtIn) const
{
if( dtIn.IsNullHandle() ) return asTYPEID_VOID;
if( dtIn.GetTypeInfo() == 0 )
{
// Primitives have pre-fixed typeIds
switch( dtIn.GetTokenType() )
{
case ttVoid: return asTYPEID_VOID;
case ttBool: return asTYPEID_BOOL;
case ttInt8: return asTYPEID_INT8;
case ttInt16: return asTYPEID_INT16;
case ttInt: return asTYPEID_INT32;
case ttInt64: return asTYPEID_INT64;
case ttUInt8: return asTYPEID_UINT8;
case ttUInt16: return asTYPEID_UINT16;
case ttUInt: return asTYPEID_UINT32;
case ttUInt64: return asTYPEID_UINT64;
case ttFloat: return asTYPEID_FLOAT;
case ttDouble: return asTYPEID_DOUBLE;
default:
// All types should be covered by the above. The variable type is not really a type
asASSERT(dtIn.GetTokenType() == ttQuestion);
return -1;
}
}
int typeId = -1;
asCTypeInfo *ot = dtIn.GetTypeInfo();
asASSERT(ot != &functionBehaviours);
// Object's hold the typeId themselves
typeId = ot->typeId;
if( typeId == -1 )
{
ACQUIREEXCLUSIVE(engineRWLock);
// Make sure another thread didn't determine the typeId while we were waiting for the lock
if( ot->typeId == -1 )
{
typeId = typeIdSeqNbr++;
if( ot->flags & asOBJ_SCRIPT_OBJECT ) typeId |= asTYPEID_SCRIPTOBJECT;
else if( ot->flags & asOBJ_TEMPLATE ) typeId |= asTYPEID_TEMPLATE;
else if( ot->flags & asOBJ_ENUM ) {} // TODO: Should we have a specific bit for this?
else typeId |= asTYPEID_APPOBJECT;
ot->typeId = typeId;
mapTypeIdToTypeInfo.Insert(typeId, ot);
}
RELEASEEXCLUSIVE(engineRWLock);
}
// Add flags according to the requested type
if( dtIn.GetTypeInfo() && !(dtIn.GetTypeInfo()->flags & asOBJ_ASHANDLE) )
{
// The ASHANDLE types behave like handles, but are really
// value types so the typeId is never returned as a handle
if( dtIn.IsObjectHandle() )
typeId |= asTYPEID_OBJHANDLE;
if( dtIn.IsHandleToConst() )
typeId |= asTYPEID_HANDLETOCONST;
}
return typeId;
}
asCDataType asCScriptEngine::GetDataTypeFromTypeId(int typeId) const
{
int baseId = typeId & (asTYPEID_MASK_OBJECT | asTYPEID_MASK_SEQNBR);
if( typeId <= asTYPEID_DOUBLE )
{
eTokenType type[] = {ttVoid, ttBool, ttInt8, ttInt16, ttInt, ttInt64, ttUInt8, ttUInt16, ttUInt, ttUInt64, ttFloat, ttDouble};
return asCDataType::CreatePrimitive(type[typeId], false);
}
// First check if the typeId is an object type
asCTypeInfo *ot = 0;
ACQUIRESHARED(engineRWLock);
asSMapNode<int,asCTypeInfo*> *cursor = 0;
if( mapTypeIdToTypeInfo.MoveTo(&cursor, baseId) )
ot = mapTypeIdToTypeInfo.GetValue(cursor);
RELEASESHARED(engineRWLock);
if( ot )
{
asCDataType dt = asCDataType::CreateType(ot, false);
if( typeId & asTYPEID_OBJHANDLE )
dt.MakeHandle(true, true);
if( typeId & asTYPEID_HANDLETOCONST )
dt.MakeHandleToConst(true);
return dt;
}
return asCDataType();
}
asCObjectType *asCScriptEngine::GetObjectTypeFromTypeId(int typeId) const
{
asCDataType dt = GetDataTypeFromTypeId(typeId);
return dt.GetTypeInfo()->CastToObjectType();
}
void asCScriptEngine::RemoveFromTypeIdMap(asCTypeInfo *type)
{
ACQUIREEXCLUSIVE(engineRWLock);
asSMapNode<int,asCTypeInfo*> *cursor = 0;
mapTypeIdToTypeInfo.MoveFirst(&cursor);
while( cursor )
{
if(mapTypeIdToTypeInfo.GetValue(cursor) == type )
{
mapTypeIdToTypeInfo.Erase(cursor);
break;
}
mapTypeIdToTypeInfo.MoveNext(&cursor, cursor);
}
RELEASEEXCLUSIVE(engineRWLock);
}
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
asITypeInfo *asCScriptEngine::GetObjectTypeByDecl(const char *decl) const
{
asITypeInfo *ti = GetTypeInfoByDecl(decl);
return reinterpret_cast<asCTypeInfo*>(ti)->CastToObjectType();
}
#endif
// interface
asITypeInfo *asCScriptEngine::GetTypeInfoByDecl(const char *decl) const
{
asCDataType dt;
// This cast is ok, because we are not changing anything in the engine
asCBuilder bld(const_cast<asCScriptEngine*>(this), 0);
// Don't write parser errors to the message callback
bld.silent = true;
int r = bld.ParseDataType(decl, &dt, defaultNamespace);
if (r < 0)
return 0;
return dt.GetTypeInfo();
}
// interface
int asCScriptEngine::GetTypeIdByDecl(const char *decl) const
{
asCDataType dt;
// This cast is ok, because we are not changing anything in the engine
asCBuilder bld(const_cast<asCScriptEngine*>(this), 0);
// Don't write parser errors to the message callback
bld.silent = true;
int r = bld.ParseDataType(decl, &dt, defaultNamespace);
if( r < 0 )
return asINVALID_TYPE;
return GetTypeIdFromDataType(dt);
}
// interface
const char *asCScriptEngine::GetTypeDeclaration(int typeId, bool includeNamespace) const
{
asCDataType dt = GetDataTypeFromTypeId(typeId);
asCString *tempString = &asCThreadManager::GetLocalData()->string;
*tempString = dt.Format(defaultNamespace, includeNamespace);
return tempString->AddressOf();
}
// interface
int asCScriptEngine::GetSizeOfPrimitiveType(int typeId) const
{
asCDataType dt = GetDataTypeFromTypeId(typeId);
if( !dt.IsPrimitive() ) return 0;
return dt.GetSizeInMemoryBytes();
}
// interface
int asCScriptEngine::RefCastObject(void *obj, asITypeInfo *fromType, asITypeInfo *toType, void **newPtr, bool useOnlyImplicitCast)
{
if( newPtr == 0 ) return asINVALID_ARG;
*newPtr = 0;
if( fromType == 0 || toType == 0 ) return asINVALID_ARG;
// A null-pointer can always be cast to another type, so it will always be successful
if( obj == 0 )
return asSUCCESS;
if( fromType == toType )
{
*newPtr = obj;
AddRefScriptObject(*newPtr, toType);
return asSUCCESS;
}
// Check for funcdefs
if ((fromType->GetFlags() & asOBJ_FUNCDEF) && (toType->GetFlags() & asOBJ_FUNCDEF))
{
asCFuncdefType *fromFunc = reinterpret_cast<asCTypeInfo*>(fromType)->CastToFuncdefType();
asCFuncdefType *toFunc = reinterpret_cast<asCTypeInfo*>(toType)->CastToFuncdefType();
if (fromFunc && toFunc && fromFunc->funcdef->IsSignatureExceptNameEqual(toFunc->funcdef))
{
*newPtr = obj;
AddRefScriptObject(*newPtr, toType);
return asSUCCESS;
}
return asSUCCESS;
}
// Look for ref cast behaviours
asCScriptFunction *universalCastFunc = 0;
asCObjectType *from = reinterpret_cast<asCObjectType*>(fromType);
for( asUINT n = 0; n < from->methods.GetLength(); n++ )
{
asCScriptFunction *func = scriptFunctions[from->methods[n]];
if( func->name == "opImplCast" ||
(!useOnlyImplicitCast && func->name == "opCast") )
{
if( func->returnType.GetTypeInfo() == toType )
{
*newPtr = CallObjectMethodRetPtr(obj, func->id);
// The ref cast behaviour returns a handle with incremented
// ref counter, so there is no need to call AddRef explicitly
// unless the function is registered with autohandle
if( func->sysFuncIntf->returnAutoHandle )
AddRefScriptObject(*newPtr, toType);
return asSUCCESS;
}
else
{
asASSERT( func->returnType.GetTokenType() == ttVoid &&
func->parameterTypes.GetLength() == 1 &&
func->parameterTypes[0].GetTokenType() == ttQuestion );
universalCastFunc = func;
}
}
}
// One last chance if the object has a void opCast(?&out) behaviour
if( universalCastFunc )
{
// TODO: Add proper error handling
asIScriptContext *ctx = RequestContext();
ctx->Prepare(universalCastFunc);
ctx->SetObject(obj);
ctx->SetArgVarType(0, newPtr, toType->GetTypeId() | asTYPEID_OBJHANDLE);
ctx->Execute();
ReturnContext(ctx);
// The opCast(?&out) method already incremented the
// refCount so there is no need to do it manually
return asSUCCESS;
}
// For script classes and interfaces there is a quick route
if( (fromType->GetFlags() & asOBJ_SCRIPT_OBJECT) && (toType->GetFlags() & asOBJ_SCRIPT_OBJECT) )
{
if( fromType == toType )
{
*newPtr = obj;
reinterpret_cast<asCScriptObject*>(*newPtr)->AddRef();
return asSUCCESS;
}
// Up casts to base class or interface can be done implicitly
if( fromType->DerivesFrom(toType) ||
fromType->Implements(toType) )
{
*newPtr = obj;
reinterpret_cast<asCScriptObject*>(*newPtr)->AddRef();
return asSUCCESS;
}
// Down casts to derived class or from interface can only be done explicitly
if( !useOnlyImplicitCast )
{
if( toType->Implements(fromType) ||
toType->DerivesFrom(fromType) )
{
*newPtr = obj;
reinterpret_cast<asCScriptObject*>(*newPtr)->AddRef();
return asSUCCESS;
}
// Get the true type of the object so the explicit cast can evaluate all possibilities
asITypeInfo *trueType = reinterpret_cast<asCScriptObject*>(obj)->GetObjectType();
if( trueType->DerivesFrom(toType) ||
trueType->Implements(toType) )
{
*newPtr = obj;
reinterpret_cast<asCScriptObject*>(*newPtr)->AddRef();
return asSUCCESS;
}
}
}
// The cast is not available, but it is still a success
return asSUCCESS;
}
// interface
void *asCScriptEngine::CreateScriptObject(const asITypeInfo *type)
{
if( type == 0 ) return 0;
asCObjectType *objType = const_cast<asCObjectType*>(reinterpret_cast<const asCObjectType *>(type));
void *ptr = 0;
// Check that there is a default factory for ref types
if( objType->beh.factory == 0 && (objType->flags & asOBJ_REF) )
{
asCString str;
str.Format(TXT_FAILED_IN_FUNC_s_d, "CreateScriptObject", asNO_FUNCTION);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
return 0;
}
// Construct the object
if( objType->flags & asOBJ_SCRIPT_OBJECT )
{
// Call the script class' default factory with a context
ptr = ScriptObjectFactory(objType, this);
}
else if( objType->flags & asOBJ_TEMPLATE )
{
// The registered factory that takes the object type is moved
// to the construct behaviour when the type is instantiated
#ifdef AS_NO_EXCEPTIONS
ptr = CallGlobalFunctionRetPtr(objType->beh.construct, objType);
#else
try
{
ptr = CallGlobalFunctionRetPtr(objType->beh.construct, objType);
}
catch(...)
{
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
ctx->SetException(TXT_EXCEPTION_CAUGHT);
}
#endif
}
else if( objType->flags & asOBJ_REF )
{
// Call the default factory directly
#ifdef AS_NO_EXCEPTIONS
ptr = CallGlobalFunctionRetPtr(objType->beh.factory);
#else
try
{
ptr = CallGlobalFunctionRetPtr(objType->beh.factory);
}
catch(...)
{
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
ctx->SetException(TXT_EXCEPTION_CAUGHT);
}
#endif
}
else
{
// Make sure there is a default constructor or that it is a POD type
if( objType->beh.construct == 0 && !(objType->flags & asOBJ_POD) )
{
asCString str;
str.Format(TXT_FAILED_IN_FUNC_s_d, "CreateScriptObject", asNO_FUNCTION);
WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.AddressOf());
return 0;
}
// Manually allocate the memory, then call the default constructor
ptr = CallAlloc(objType);
int funcIndex = objType->beh.construct;
if( funcIndex )
{
#ifdef AS_NO_EXCEPTIONS
CallObjectMethod(ptr, funcIndex);
#else
try
{
CallObjectMethod(ptr, funcIndex);
}
catch(...)
{
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
ctx->SetException(TXT_EXCEPTION_CAUGHT);
// Free the memory
CallFree(ptr);
ptr = 0;
}
#endif
}
}
return ptr;
}
// interface
void *asCScriptEngine::CreateUninitializedScriptObject(const asITypeInfo *type)
{
// This function only works for script classes. Registered types cannot be created this way.
if( type == 0 || !(type->GetFlags() & asOBJ_SCRIPT_OBJECT) )
return 0;
asCObjectType *objType = const_cast<asCObjectType*>(reinterpret_cast<const asCObjectType*>(type));
// Construct the object, but do not call the actual constructor that initializes the members
// The initialization will be done by the application afterwards, e.g. through serialization.
asCScriptObject *obj = reinterpret_cast<asCScriptObject*>(CallAlloc(objType));
// Pre-initialize the memory so there are no invalid pointers
ScriptObject_ConstructUnitialized(objType, obj);
return obj;
}
// interface
void *asCScriptEngine::CreateScriptObjectCopy(void *origObj, const asITypeInfo *type)
{
if( origObj == 0 || type == 0 ) return 0;
void *newObj = 0;
const asCObjectType *ot = reinterpret_cast<const asCObjectType*>(type);
// TODO: runtime optimize: Should call copy factory for ref types too
if( ot->beh.copyconstruct )
{
// Manually allocate the memory, then call the copy constructor
newObj = CallAlloc(ot);
#ifdef AS_NO_EXCEPTIONS
CallObjectMethod(newObj, origObj, ot->beh.copyconstruct);
#else
try
{
CallObjectMethod(newObj, origObj, ot->beh.copyconstruct);
}
catch(...)
{
asIScriptContext *ctx = asGetActiveContext();
if( ctx )
ctx->SetException(TXT_EXCEPTION_CAUGHT);
// Free the memory
CallFree(newObj);
newObj = 0;
}
#endif
}
else
{
// Allocate the object and then do a value assign
newObj = CreateScriptObject(type);
if( newObj == 0 ) return 0;
AssignScriptObject(newObj, origObj, type);
}
return newObj;
}
// internal
void asCScriptEngine::ConstructScriptObjectCopy(void *mem, void *obj, asCObjectType *type)
{
if( type == 0 || mem == 0 || obj == 0 ) return;
// This function is only meant to be used for value types
asASSERT( type->flags & asOBJ_VALUE );
// Call the copy constructor if available, else call the default constructor followed by the opAssign
int funcIndex = type->beh.copyconstruct;
if( funcIndex )
{
CallObjectMethod(mem, obj, funcIndex);
}
else
{
funcIndex = type->beh.construct;
if( funcIndex )
CallObjectMethod(mem, funcIndex);
AssignScriptObject(mem, obj, type);
}
}
// interface
int asCScriptEngine::AssignScriptObject(void *dstObj, void *srcObj, const asITypeInfo *type)
{
// TODO: Warn about invalid call in message stream
// TODO: Should a script exception be set in case a context is active?
if( type == 0 || dstObj == 0 || srcObj == 0 ) return asINVALID_ARG;
const asCObjectType *objType = reinterpret_cast<const asCObjectType*>(type);
// If value assign for ref types has been disabled, then don't do anything if the type is a ref type
if( ep.disallowValueAssignForRefType && (objType->flags & asOBJ_REF) && !(objType->flags & asOBJ_SCOPED) )
return asNOT_SUPPORTED;
// Must not copy if the opAssign is not available and the object is not a POD object
if( objType->beh.copy )
{
asCScriptFunction *func = scriptFunctions[objType->beh.copy];
if( func->funcType == asFUNC_SYSTEM )
CallObjectMethod(dstObj, srcObj, objType->beh.copy);
else
{
// Call the script class' opAssign method
asASSERT( objType->flags & asOBJ_SCRIPT_OBJECT );
reinterpret_cast<asCScriptObject*>(dstObj)->CopyFrom(reinterpret_cast<asCScriptObject*>(srcObj));
}
}
else if( objType->size && (objType->flags & asOBJ_POD) )
{
memcpy(dstObj, srcObj, objType->size);
}
return asSUCCESS;
}
// interface
void asCScriptEngine::AddRefScriptObject(void *obj, const asITypeInfo *type)
{
// Make sure it is not a null pointer
if( obj == 0 || type == 0 ) return;
const asCTypeInfo *ti = static_cast<const asCTypeInfo*>(type);
if (ti->flags & asOBJ_FUNCDEF)
{
CallObjectMethod(obj, functionBehaviours.beh.addref);
}
else
{
asCObjectType *objType = const_cast<asCTypeInfo*>(ti)->CastToObjectType();
if (objType && objType->beh.addref)
{
// Call the addref behaviour
CallObjectMethod(obj, objType->beh.addref);
}
}
}
// interface
void asCScriptEngine::ReleaseScriptObject(void *obj, const asITypeInfo *type)
{
// Make sure it is not a null pointer
if( obj == 0 || type == 0 ) return;
const asCTypeInfo *ti = static_cast<const asCTypeInfo*>(type);
if (ti->flags & asOBJ_FUNCDEF)
{
CallObjectMethod(obj, functionBehaviours.beh.release);
}
else
{
asCObjectType *objType = const_cast<asCTypeInfo*>(ti)->CastToObjectType();
if (objType && objType->flags & asOBJ_REF)
{
asASSERT((objType->flags & asOBJ_NOCOUNT) || objType->beh.release);
if (objType->beh.release)
{
// Call the release behaviour
CallObjectMethod(obj, objType->beh.release);
}
}
else if( objType )
{
// Call the destructor
if (objType->beh.destruct)
CallObjectMethod(obj, objType->beh.destruct);
else if (objType->flags & asOBJ_LIST_PATTERN)
DestroyList((asBYTE*)obj, objType);
// We'll have to trust that the memory for the object was allocated with CallAlloc.
// This is true if the object was created in the context, or with CreateScriptObject.
// Then free the memory
CallFree(obj);
}
}
}
#ifdef AS_DEPRECATED
// Deprecated since 2.30.0, 2014-11-04
// interface
bool asCScriptEngine::IsHandleCompatibleWithObject(void *obj, int objTypeId, int handleTypeId) const
{
// if equal, then it is obvious they are compatible
if( objTypeId == handleTypeId )
return true;
// Get the actual data types from the type ids
asCDataType objDt = GetDataTypeFromTypeId(objTypeId);
asCDataType hdlDt = GetDataTypeFromTypeId(handleTypeId);
// A handle to const cannot be passed to a handle that is not referencing a const object
if( objDt.IsHandleToConst() && !hdlDt.IsHandleToConst() )
return false;
if( objDt.GetTypeInfo() == hdlDt.GetTypeInfo() )
{
// The object type is equal
return true;
}
else if( objDt.IsScriptObject() && obj )
{
// Get the true type from the object instance
asCObjectType *objType = ((asCScriptObject*)obj)->objType;
// Check if the object implements the interface, or derives from the base class
// This will also return true, if the requested handle type is an exact match for the object type
if( objType->Implements(hdlDt.GetTypeInfo()) ||
objType->DerivesFrom(hdlDt.GetTypeInfo()) )
return true;
}
return false;
}
#endif
// interface
int asCScriptEngine::BeginConfigGroup(const char *groupName)
{
// Make sure the group name doesn't already exist
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
if( configGroups[n]->groupName == groupName )
return asNAME_TAKEN;
}
if( currentGroup != &defaultGroup )
return asNOT_SUPPORTED;
asCConfigGroup *group = asNEW(asCConfigGroup)();
if( group == 0 )
return asOUT_OF_MEMORY;
group->groupName = groupName;
configGroups.PushLast(group);
currentGroup = group;
return 0;
}
// interface
int asCScriptEngine::EndConfigGroup()
{
// Raise error if trying to end the default config
if( currentGroup == &defaultGroup )
return asERROR;
currentGroup = &defaultGroup;
return 0;
}
// interface
int asCScriptEngine::RemoveConfigGroup(const char *groupName)
{
// It is not allowed to remove a group that is still in use.
// It would be possible to change the code in such a way that
// the group could be removed even though it was still in use,
// but that would cause severe negative impact on runtime
// performance, since the VM would then have to be able handle
// situations where the types, functions, and global variables
// can be removed at any time.
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
if( configGroups[n]->groupName == groupName )
{
asCConfigGroup *group = configGroups[n];
// Remove any unused generated template instances
// before verifying if the config group is still in use.
// RemoveTemplateInstanceType() checks if the instance is in use
for( asUINT g = generatedTemplateTypes.GetLength(); g-- > 0; )
RemoveTemplateInstanceType(generatedTemplateTypes[g]);
// Make sure the group isn't referenced by anyone
if( group->refCount > 0 )
return asCONFIG_GROUP_IS_IN_USE;
// Verify if any objects registered in this group is still alive
if( group->HasLiveObjects() )
return asCONFIG_GROUP_IS_IN_USE;
// Remove the group from the list
if( n == configGroups.GetLength() - 1 )
configGroups.PopLast();
else
configGroups[n] = configGroups.PopLast();
// Remove the configurations registered with this group
group->RemoveConfiguration(this);
asDELETE(group,asCConfigGroup);
}
}
return 0;
}
asCConfigGroup *asCScriptEngine::FindConfigGroupForFunction(int funcId) const
{
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
// Check global functions
asUINT m;
for( m = 0; m < configGroups[n]->scriptFunctions.GetLength(); m++ )
{
if( configGroups[n]->scriptFunctions[m]->id == funcId )
return configGroups[n];
}
}
return 0;
}
asCConfigGroup *asCScriptEngine::FindConfigGroupForGlobalVar(int gvarId) const
{
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
for( asUINT m = 0; m < configGroups[n]->globalProps.GetLength(); m++ )
{
if( int(configGroups[n]->globalProps[m]->id) == gvarId )
return configGroups[n];
}
}
return 0;
}
asCConfigGroup *asCScriptEngine::FindConfigGroupForTypeInfo(const asCTypeInfo *objType) const
{
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
for( asUINT m = 0; m < configGroups[n]->types.GetLength(); m++ )
{
if( configGroups[n]->types[m] == objType )
return configGroups[n];
}
}
return 0;
}
asCConfigGroup *asCScriptEngine::FindConfigGroupForFuncDef(const asCFuncdefType *funcDef) const
{
for( asUINT n = 0; n < configGroups.GetLength(); n++ )
{
asCFuncdefType *f = const_cast<asCFuncdefType*>(funcDef);
if( configGroups[n]->types.Exists(f) )
return configGroups[n];
}
return 0;
}
// interface
asDWORD asCScriptEngine::SetDefaultAccessMask(asDWORD defaultMask)
{
asDWORD old = defaultAccessMask;
defaultAccessMask = defaultMask;
return old;
}
int asCScriptEngine::GetNextScriptFunctionId()
{
// This function only returns the next function id that
// should be used. It doesn't update the internal arrays.
if( freeScriptFunctionIds.GetLength() )
return freeScriptFunctionIds[freeScriptFunctionIds.GetLength()-1];
return (int)scriptFunctions.GetLength();
}
void asCScriptEngine::AddScriptFunction(asCScriptFunction *func)
{
// Update the internal arrays with the function id that is now used
if( freeScriptFunctionIds.GetLength() && freeScriptFunctionIds[freeScriptFunctionIds.GetLength()-1] == func->id )
freeScriptFunctionIds.PopLast();
if( asUINT(func->id) == scriptFunctions.GetLength() )
scriptFunctions.PushLast(func);
else
{
// The slot should be empty or already set with the function, which happens if an existing shared function is reused
asASSERT( scriptFunctions[func->id] == 0 || scriptFunctions[func->id] == func );
scriptFunctions[func->id] = func;
}
}
void asCScriptEngine::RemoveScriptFunction(asCScriptFunction *func)
{
if( func == 0 || func->id < 0 ) return;
int id = func->id & ~FUNC_IMPORTED;
if( func->funcType == asFUNC_IMPORTED )
{
if( id >= (int)importedFunctions.GetLength() ) return;
if( importedFunctions[id] )
{
// Remove the function from the list of script functions
if( id == (int)importedFunctions.GetLength() - 1 )
{
importedFunctions.PopLast();
}
else
{
importedFunctions[id] = 0;
freeImportedFunctionIdxs.PushLast(id);
}
}
}
else
{
if( id >= (int)scriptFunctions.GetLength() ) return;
asASSERT( func == scriptFunctions[id] );
if( scriptFunctions[id] )
{
// Remove the function from the list of script functions
if( id == (int)scriptFunctions.GetLength() - 1 )
{
scriptFunctions.PopLast();
}
else
{
scriptFunctions[id] = 0;
freeScriptFunctionIds.PushLast(id);
}
// Is the function used as signature id?
if( func->signatureId == id )
{
// Remove the signature id
signatureIds.RemoveValue(func);
// Update all functions using the signature id
int newSigId = 0;
for( asUINT n = 0; n < scriptFunctions.GetLength(); n++ )
{
if( scriptFunctions[n] && scriptFunctions[n]->signatureId == id )
{
if( newSigId == 0 )
{
newSigId = scriptFunctions[n]->id;
signatureIds.PushLast(scriptFunctions[n]);
}
scriptFunctions[n]->signatureId = newSigId;
}
}
}
}
}
}
// internal
void asCScriptEngine::RemoveFuncdef(asCFuncdefType *funcdef)
{
funcDefs.RemoveValue(funcdef);
}
// interface
int asCScriptEngine::RegisterFuncdef(const char *decl)
{
if( decl == 0 ) return ConfigError(asINVALID_ARG, "RegisterFuncdef", decl, 0);
// Parse the function declaration
asCScriptFunction *func = asNEW(asCScriptFunction)(this, 0, asFUNC_FUNCDEF);
if( func == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterFuncdef", decl, 0);
asCBuilder bld(this, 0);
asCObjectType *parentClass = 0;
int r = bld.ParseFunctionDeclaration(0, decl, func, false, 0, 0, defaultNamespace, 0, &parentClass);
if( r < 0 )
{
// Set as dummy function before deleting
func->funcType = asFUNC_DUMMY;
asDELETE(func,asCScriptFunction);
return ConfigError(asINVALID_DECLARATION, "RegisterFuncdef", decl, 0);
}
// Check name conflicts
r = bld.CheckNameConflict(func->name.AddressOf(), 0, 0, defaultNamespace);
if( r < 0 )
{
asDELETE(func,asCScriptFunction);
return ConfigError(asNAME_TAKEN, "RegisterFuncdef", decl, 0);
}
func->id = GetNextScriptFunctionId();
AddScriptFunction(func);
asCFuncdefType *fdt = asNEW(asCFuncdefType)(this, func);
funcDefs.PushLast(fdt); // doesn't increase refcount
registeredFuncDefs.PushLast(fdt); // doesn't increase refcount
allRegisteredTypes.Insert(asSNameSpaceNamePair(fdt->nameSpace, fdt->name), fdt); // constructor already set the ref count to 1
currentGroup->types.PushLast(fdt);
if (parentClass)
{
parentClass->childFuncDefs.PushLast(fdt);
fdt->parentClass = parentClass;
// Check if the method restricts that use of the template to value types or reference types
if (parentClass->flags & asOBJ_TEMPLATE)
{
r = SetTemplateRestrictions(parentClass, func, "RegisterFuncdef", decl);
if (r < 0)
return r;
}
}
// If parameter type from other groups are used, add references
currentGroup->AddReferencesForFunc(this, func);
// Return the function id as success
return func->id;
}
// interface
asUINT asCScriptEngine::GetFuncdefCount() const
{
return asUINT(registeredFuncDefs.GetLength());
}
// interface
asITypeInfo *asCScriptEngine::GetFuncdefByIndex(asUINT index) const
{
if( index >= registeredFuncDefs.GetLength() )
return 0;
return registeredFuncDefs[index];
}
// internal
asCFuncdefType *asCScriptEngine::FindMatchingFuncdef(asCScriptFunction *func, asCModule *module)
{
asCFuncdefType *funcDef = func->funcdefType;
if (funcDef == 0)
{
// Check if there is any matching funcdefs already in the engine that can be reused
for (asUINT n = 0; n < funcDefs.GetLength(); n++)
{
if (funcDefs[n]->funcdef->IsSignatureExceptNameEqual(func))
{
if (func->isShared && !funcDefs[n]->funcdef->isShared)
continue;
funcDef = funcDefs[n];
break;
}
}
}
if (funcDef == 0)
{
// Create a matching funcdef
asCScriptFunction *fd = asNEW(asCScriptFunction)(this, 0, asFUNC_FUNCDEF);
fd->name = func->name;
fd->nameSpace = func->nameSpace;
fd->isShared = func->isShared;
fd->returnType = func->returnType;
fd->parameterTypes = func->parameterTypes;
fd->inOutFlags = func->inOutFlags;
funcDef = asNEW(asCFuncdefType)(this, fd);
funcDefs.PushLast(funcDef); // doesn't increase the refCount
fd->id = GetNextScriptFunctionId();
AddScriptFunction(fd);
if (module)
{
// Add the new funcdef to the module so it will
// be available when saving the bytecode
funcDef->module = module;
module->funcDefs.PushLast(funcDef); // the refCount was already accounted for in the constructor
}
// Observe, if the funcdef is created without informing a module a reference will be stored in the
// engine's funcDefs array, but it will not be owned by any module. This means that it will live on
// until the engine is released.
}
if (funcDef && module && funcDef->module && funcDef->module != module)
{
// Unless this is a registered funcDef the returned funcDef must
// be stored as part of the module for saving/loading bytecode
if (!module->funcDefs.Exists(funcDef))
{
module->funcDefs.PushLast(funcDef);
funcDef->AddRefInternal();
}
else
{
asASSERT(funcDef->IsShared());
}
}
return funcDef;
}
// interface
// TODO: typedef: Accept complex types for the typedefs
int asCScriptEngine::RegisterTypedef(const char *type, const char *decl)
{
if( type == 0 ) return ConfigError(asINVALID_NAME, "RegisterTypedef", type, decl);
// Verify if the name has been registered as a type already
// TODO: Must check against registered funcdefs too
if( GetRegisteredType(type, defaultNamespace) )
// Let the application recover from this error, for example if the same typedef is registered twice
return asALREADY_REGISTERED;
// Grab the data type
size_t tokenLen;
eTokenType token;
asCDataType dataType;
// Create the data type
token = tok.GetToken(decl, strlen(decl), &tokenLen);
switch(token)
{
case ttBool:
case ttInt:
case ttInt8:
case ttInt16:
case ttInt64:
case ttUInt:
case ttUInt8:
case ttUInt16:
case ttUInt64:
case ttFloat:
case ttDouble:
if( strlen(decl) != tokenLen )
{
return ConfigError(asINVALID_TYPE, "RegisterTypedef", type, decl);
}
break;
default:
return ConfigError(asINVALID_TYPE, "RegisterTypedef", type, decl);
}
dataType = asCDataType::CreatePrimitive(token, false);
// Make sure the name is not a reserved keyword
token = tok.GetToken(type, strlen(type), &tokenLen);
if( token != ttIdentifier || strlen(type) != tokenLen )
return ConfigError(asINVALID_NAME, "RegisterTypedef", type, decl);
asCBuilder bld(this, 0);
int r = bld.CheckNameConflict(type, 0, 0, defaultNamespace);
if( r < 0 )
return ConfigError(asNAME_TAKEN, "RegisterTypedef", type, decl);
// Don't have to check against members of object
// types as they are allowed to use the names
// Put the data type in the list
asCTypedefType *td = asNEW(asCTypedefType)(this);
if( td == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterTypedef", type, decl);
td->flags = asOBJ_TYPEDEF;
td->size = dataType.GetSizeInMemoryBytes();
td->name = type;
td->nameSpace = defaultNamespace;
td->aliasForType = dataType;
allRegisteredTypes.Insert(asSNameSpaceNamePair(td->nameSpace, td->name), td);
registeredTypeDefs.PushLast(td);
currentGroup->types.PushLast(td);
return asSUCCESS;
}
// interface
asUINT asCScriptEngine::GetTypedefCount() const
{
return asUINT(registeredTypeDefs.GetLength());
}
// interface
asITypeInfo *asCScriptEngine::GetTypedefByIndex(asUINT index) const
{
if( index >= registeredTypeDefs.GetLength() )
return 0;
return registeredTypeDefs[index];
}
// interface
int asCScriptEngine::RegisterEnum(const char *name)
{
// Check the name
if( NULL == name )
return ConfigError(asINVALID_NAME, "RegisterEnum", name, 0);
// Verify if the name has been registered as a type already
if( GetRegisteredType(name, defaultNamespace) )
return asALREADY_REGISTERED;
// Use builder to parse the datatype
asCDataType dt;
asCBuilder bld(this, 0);
bool oldMsgCallback = msgCallback; msgCallback = false;
int r = bld.ParseDataType(name, &dt, defaultNamespace);
msgCallback = oldMsgCallback;
if( r >= 0 )
{
// If it is not in the defaultNamespace then the type was successfully parsed because
// it is declared in a parent namespace which shouldn't be treated as an error
if( dt.GetTypeInfo() && dt.GetTypeInfo()->nameSpace == defaultNamespace )
return ConfigError(asERROR, "RegisterEnum", name, 0);
}
// Make sure the name is not a reserved keyword
size_t tokenLen;
int token = tok.GetToken(name, strlen(name), &tokenLen);
if( token != ttIdentifier || strlen(name) != tokenLen )
return ConfigError(asINVALID_NAME, "RegisterEnum", name, 0);
r = bld.CheckNameConflict(name, 0, 0, defaultNamespace);
if( r < 0 )
return ConfigError(asNAME_TAKEN, "RegisterEnum", name, 0);
asCEnumType *st = asNEW(asCEnumType)(this);
if( st == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterEnum", name, 0);
asCDataType dataType;
dataType.CreatePrimitive(ttInt, false);
st->flags = asOBJ_ENUM | asOBJ_SHARED;
st->size = 4;
st->name = name;
st->nameSpace = defaultNamespace;
allRegisteredTypes.Insert(asSNameSpaceNamePair(st->nameSpace, st->name), st);
registeredEnums.PushLast(st);
currentGroup->types.PushLast(st);
return asSUCCESS;
}
// interface
int asCScriptEngine::RegisterEnumValue(const char *typeName, const char *valueName, int value)
{
// Verify that the correct config group is used
if( currentGroup->FindType(typeName) == 0 )
return ConfigError(asWRONG_CONFIG_GROUP, "RegisterEnumValue", typeName, valueName);
asCDataType dt;
int r;
asCBuilder bld(this, 0);
r = bld.ParseDataType(typeName, &dt, defaultNamespace);
if( r < 0 )
return ConfigError(r, "RegisterEnumValue", typeName, valueName);
// Store the enum value
asCEnumType *ot = dt.GetTypeInfo()->CastToEnumType();
if( ot == 0 )
return ConfigError(asINVALID_TYPE, "RegisterEnumValue", typeName, valueName);
if( NULL == valueName )
return ConfigError(asINVALID_NAME, "RegisterEnumValue", typeName, valueName);
asUINT tokenLen = 0;
asETokenClass tokenClass = ParseToken(valueName, 0, &tokenLen);
if( tokenClass != asTC_IDENTIFIER || tokenLen != strlen(valueName) )
return ConfigError(asINVALID_NAME, "RegisterEnumValue", typeName, valueName);
for( unsigned int n = 0; n < ot->enumValues.GetLength(); n++ )
{
if( ot->enumValues[n]->name == valueName )
return ConfigError(asALREADY_REGISTERED, "RegisterEnumValue", typeName, valueName);
}
asSEnumValue *e = asNEW(asSEnumValue);
if( e == 0 )
return ConfigError(asOUT_OF_MEMORY, "RegisterEnumValue", typeName, valueName);
e->name = valueName;
e->value = value;
ot->enumValues.PushLast(e);
return asSUCCESS;
}
// interface
asUINT asCScriptEngine::GetEnumCount() const
{
return registeredEnums.GetLength();
}
// interface
asITypeInfo *asCScriptEngine::GetEnumByIndex(asUINT index) const
{
if( index >= registeredEnums.GetLength() )
return 0;
return registeredEnums[index];
}
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
int asCScriptEngine::GetEnumValueCount(int enumTypeId) const
{
asITypeInfo *ti = GetTypeInfoById(enumTypeId);
asCEnumType *e = reinterpret_cast<asCTypeInfo*>(ti)->CastToEnumType();
if (e == 0)
return asINVALID_TYPE;
return e->GetEnumValueCount();
}
#endif
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
const char *asCScriptEngine::GetEnumValueByIndex(int enumTypeId, asUINT index, int *outValue) const
{
asITypeInfo *ti = GetTypeInfoById(enumTypeId);
asCEnumType *e = reinterpret_cast<asCTypeInfo*>(ti)->CastToEnumType();
if (e == 0)
return 0;
return e->GetEnumValueByIndex(index, outValue);
}
#endif
// interface
asUINT asCScriptEngine::GetObjectTypeCount() const
{
return asUINT(registeredObjTypes.GetLength());
}
// interface
asITypeInfo *asCScriptEngine::GetObjectTypeByIndex(asUINT index) const
{
if( index >= registeredObjTypes.GetLength() )
return 0;
return registeredObjTypes[index];
}
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
asITypeInfo *asCScriptEngine::GetObjectTypeByName(const char *name) const
{
asITypeInfo *ti = GetTypeInfoByName(name);
return reinterpret_cast<asCTypeInfo*>(ti)->CastToObjectType();
}
#endif
// interface
asITypeInfo *asCScriptEngine::GetTypeInfoByName(const char *name) const
{
asSNameSpace *ns = defaultNamespace;
while (ns)
{
// Check the object types
for (asUINT n = 0; n < registeredObjTypes.GetLength(); n++)
{
if (registeredObjTypes[n]->name == name &&
registeredObjTypes[n]->nameSpace == ns)
return registeredObjTypes[n];
}
// Perhaps it is a template type? In this case
// the returned type will be the generic type
for (asUINT n = 0; n < registeredTemplateTypes.GetLength(); n++)
{
if (registeredTemplateTypes[n]->name == name &&
registeredTemplateTypes[n]->nameSpace == ns)
return registeredTemplateTypes[n];
}
// Check the enum types
for (asUINT n = 0; n < registeredEnums.GetLength(); n++)
{
if (registeredEnums[n]->name == name &&
registeredEnums[n]->nameSpace == ns)
return registeredEnums[n];
}
// Check the typedefs
for (asUINT n = 0; n < registeredTypeDefs.GetLength();n++)
{
if (registeredTypeDefs[n]->name == name &&
registeredTypeDefs[n]->nameSpace == ns)
return registeredTypeDefs[n];
}
// Recursively search parent namespace
ns = GetParentNameSpace(ns);
}
return 0;
}
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
asITypeInfo *asCScriptEngine::GetObjectTypeById(int typeId) const
{
asITypeInfo *ti = GetTypeInfoById(typeId);
return reinterpret_cast<asCTypeInfo*>(ti)->CastToObjectType();
}
#endif
// interface
asITypeInfo *asCScriptEngine::GetTypeInfoById(int typeId) const
{
asCDataType dt = GetDataTypeFromTypeId(typeId);
// Is the type id valid?
if (!dt.IsValid()) return 0;
return dt.GetTypeInfo();
}
// interface
asIScriptFunction *asCScriptEngine::GetFunctionById(int funcId) const
{
return GetScriptFunction(funcId);
}
#ifdef AS_DEPRECATED
// deprecated since 2.31.0, 2016-01-01
// interface
asIScriptFunction *asCScriptEngine::GetFuncdefFromTypeId(int typeId) const
{
asCFuncdefType *t = GetDataTypeFromTypeId(typeId).GetTypeInfo()->CastToFuncdefType();
if (t)
return t->funcdef;
return 0;
}
#endif
// internal
bool asCScriptEngine::IsTemplateType(const char *name) const
{
// Only look in the list of template types (not instance types)
for( unsigned int n = 0; n < registeredTemplateTypes.GetLength(); n++ )
{
asCObjectType *type = registeredTemplateTypes[n];
if( type && type->name == name )
return true;
}
return false;
}
// internal
int asCScriptEngine::AddConstantString(const char *str, size_t len)
{
// This is only called when build a script module, so it is
// known that only one thread can enter the function at a time.
asASSERT( isBuilding );
// The str may contain null chars, so we cannot use strlen, or strcmp, or strcpy
// Has the string been registered before?
asSMapNode<asCStringPointer, int> *cursor = 0;
if (stringToIdMap.MoveTo(&cursor, asCStringPointer(str, len)))
return cursor->value;
// No match was found, add the string
asCString *cstr = asNEW(asCString)(str, len);
if( cstr )
{
stringConstants.PushLast(cstr);
int index = (int)stringConstants.GetLength() - 1;
stringToIdMap.Insert(asCStringPointer(cstr), index);
// The VM currently doesn't handle string ids larger than 65535
asASSERT(stringConstants.GetLength() <= 65536);
return index;
}
return 0;
}
// internal
const asCString &asCScriptEngine::GetConstantString(int id)
{
return *stringConstants[id];
}
// internal
int asCScriptEngine::GetScriptSectionNameIndex(const char *name)
{
ACQUIREEXCLUSIVE(engineRWLock);
// TODO: These names are only released when the engine is freed. The assumption is that
// the same script section names will be reused instead of there always being new
// names. Is this assumption valid? Do we need to add reference counting?
// Store the script section names for future reference
for( asUINT n = 0; n < scriptSectionNames.GetLength(); n++ )
{
if( scriptSectionNames[n]->Compare(name) == 0 )
{
RELEASEEXCLUSIVE(engineRWLock);
return n;
}
}
asCString *str = asNEW(asCString)(name);
if( str )
scriptSectionNames.PushLast(str);
int r = int(scriptSectionNames.GetLength()-1);
RELEASEEXCLUSIVE(engineRWLock);
return r;
}
// interface
void asCScriptEngine::SetEngineUserDataCleanupCallback(asCLEANENGINEFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanEngineFuncs.GetLength(); n++ )
{
if( cleanEngineFuncs[n].type == type )
{
cleanEngineFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
SEngineClean otc = {type, callback};
cleanEngineFuncs.PushLast(otc);
RELEASEEXCLUSIVE(engineRWLock);
}
// interface
void asCScriptEngine::SetModuleUserDataCleanupCallback(asCLEANMODULEFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanModuleFuncs.GetLength(); n++ )
{
if( cleanModuleFuncs[n].type == type )
{
cleanModuleFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
SModuleClean otc = {type, callback};
cleanModuleFuncs.PushLast(otc);
RELEASEEXCLUSIVE(engineRWLock);
}
// interface
void asCScriptEngine::SetContextUserDataCleanupCallback(asCLEANCONTEXTFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanContextFuncs.GetLength(); n++ )
{
if( cleanContextFuncs[n].type == type )
{
cleanContextFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
SContextClean otc = {type, callback};
cleanContextFuncs.PushLast(otc);
RELEASEEXCLUSIVE(engineRWLock);
}
// interface
void asCScriptEngine::SetFunctionUserDataCleanupCallback(asCLEANFUNCTIONFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanFunctionFuncs.GetLength(); n++ )
{
if( cleanFunctionFuncs[n].type == type )
{
cleanFunctionFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
SFunctionClean otc = {type, callback};
cleanFunctionFuncs.PushLast(otc);
RELEASEEXCLUSIVE(engineRWLock);
}
#ifdef AS_DEPRECATED
// Deprecated since 2.31.0, 2015-12-06
// interface
void asCScriptEngine::SetObjectTypeUserDataCleanupCallback(asCLEANTYPEINFOFUNC_t callback, asPWORD type)
{
SetTypeInfoUserDataCleanupCallback(callback, type);
}
#endif
// interface
void asCScriptEngine::SetTypeInfoUserDataCleanupCallback(asCLEANTYPEINFOFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanTypeInfoFuncs.GetLength(); n++ )
{
if( cleanTypeInfoFuncs[n].type == type )
{
cleanTypeInfoFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
STypeInfoClean otc = {type, callback};
cleanTypeInfoFuncs.PushLast(otc);
RELEASEEXCLUSIVE(engineRWLock);
}
// interface
void asCScriptEngine::SetScriptObjectUserDataCleanupCallback(asCLEANSCRIPTOBJECTFUNC_t callback, asPWORD type)
{
ACQUIREEXCLUSIVE(engineRWLock);
for( asUINT n = 0; n < cleanScriptObjectFuncs.GetLength(); n++ )
{
if( cleanScriptObjectFuncs[n].type == type )
{
cleanScriptObjectFuncs[n].cleanFunc = callback;
RELEASEEXCLUSIVE(engineRWLock);
return;
}
}
SScriptObjClean soc = {type, callback};
cleanScriptObjectFuncs.PushLast(soc);
RELEASEEXCLUSIVE(engineRWLock);
}
// internal
asCObjectType *asCScriptEngine::GetListPatternType(int listPatternFuncId)
{
// Get the object type either from the constructor's object for value types
// or from the factory's return type for reference types
asCObjectType *ot = scriptFunctions[listPatternFuncId]->objectType;
if( ot == 0 )
ot = scriptFunctions[listPatternFuncId]->returnType.GetTypeInfo()->CastToObjectType();
asASSERT( ot );
// Check if this object type already has a list pattern type
for( asUINT n = 0; n < listPatternTypes.GetLength(); n++ )
{
if( listPatternTypes[n]->templateSubTypes[0].GetTypeInfo() == ot )
return listPatternTypes[n];
}
// Create a new list pattern type for the given object type
asCObjectType *lpt = asNEW(asCObjectType)(this);
lpt->templateSubTypes.PushLast(asCDataType::CreateType(ot, false));
lpt->flags = asOBJ_LIST_PATTERN;
listPatternTypes.PushLast(lpt);
return lpt;
}
// internal
void asCScriptEngine::DestroyList(asBYTE *buffer, const asCObjectType *listPatternType)
{
asASSERT( listPatternType && (listPatternType->flags & asOBJ_LIST_PATTERN) );
// Get the list pattern from the listFactory function
// TODO: runtime optimize: Store the used list factory in the listPatternType itself
// TODO: runtime optimize: Keep a flag to indicate if there is really a need to free anything
asCObjectType *ot = listPatternType->templateSubTypes[0].GetTypeInfo()->CastToObjectType();
asCScriptFunction *listFactory = scriptFunctions[ot->beh.listFactory];
asASSERT( listFactory );
asSListPatternNode *node = listFactory->listPattern;
DestroySubList(buffer, node);
asASSERT( node->type == asLPT_END );
}
// internal
void asCScriptEngine::DestroySubList(asBYTE *&buffer, asSListPatternNode *&node)
{
asASSERT( node->type == asLPT_START );
int count = 0;
node = node->next;
while( node )
{
if( node->type == asLPT_REPEAT || node->type == asLPT_REPEAT_SAME )
{
// Align the offset to 4 bytes boundary
if( (asPWORD(buffer) & 0x3) )
buffer += 4 - (asPWORD(buffer) & 0x3);
// Determine how many times the pattern repeat
count = *(asUINT*)buffer;
buffer += 4;
if( count == 0 )
{
// Skip the sub pattern that was expected to be repeated, otherwise
// we'll try to delete things that don't exist in the buffer
node = node->next;
if( node->type == asLPT_START )
{
int subCount = 1;
do
{
node = node->next;
if( node->type == asLPT_START )
subCount++;
else if( node->type == asLPT_END )
subCount--;
} while( subCount > 0 );
return;
}
}
}
else if( node->type == asLPT_TYPE )
{
// If we're not in a repeat iteration, then only 1 value should be destroyed
if( count <= 0 )
count = 1;
asCDataType dt = reinterpret_cast<asSListPatternDataTypeNode*>(node)->dataType;
bool isVarType = dt.GetTokenType() == ttQuestion;
while( count-- )
{
if( isVarType )
{
// Align the offset to 4 bytes boundary
if( (asPWORD(buffer) & 0x3) )
buffer += 4 - (asPWORD(buffer) & 0x3);
int typeId = *(int*)buffer;
buffer += 4;
dt = GetDataTypeFromTypeId(typeId);
}
- asCObjectType *ot = dt.GetTypeInfo()->CastToObjectType();
+ asCObjectType *ot = (asCObjectType*)dt.GetTypeInfo();
if( ot && (ot->flags & asOBJ_ENUM) == 0 )
{
// Free all instances of this type
if( ot->flags & asOBJ_VALUE )
{
asUINT size = ot->GetSize();
// Align the offset to 4 bytes boundary
if( size >= 4 && (asPWORD(buffer) & 0x3) )
buffer += 4 - (asPWORD(buffer) & 0x3);
if( ot->beh.destruct )
{
// Only call the destructor if the object has been created
// We'll assume the object has been created if any byte in
// the memory is different from 0.
// TODO: This is not really correct, as bytes may have been
// modified by the constructor, but then an exception
// thrown aborting the initialization. The engine
// really should be keeping track of which objects has
// been successfully initialized.
for( asUINT n = 0; n < size; n++ )
{
if( buffer[n] != 0 )
{
void *ptr = (void*)buffer;
CallObjectMethod(ptr, ot->beh.destruct);
break;
}
}
}
// Advance the pointer in the buffer
buffer += size;
}
else
{
// Align the offset to 4 bytes boundary
if( asPWORD(buffer) & 0x3 )
buffer += 4 - (asPWORD(buffer) & 0x3);
// Call the release behaviour
void *ptr = *(void**)buffer;
if( ptr )
ReleaseScriptObject(ptr, ot);
buffer += AS_PTR_SIZE*4;
}
}
else
{
asUINT size = dt.GetSizeInMemoryBytes();
// Align the offset to 4 bytes boundary
if( size >= 4 && (asPWORD(buffer) & 0x3) )
buffer += 4 - (asPWORD(buffer) & 0x3);
// Advance the buffer
buffer += size;
}
}
}
else if( node->type == asLPT_START )
{
// If we're not in a repeat iteration, then only 1 value should be destroyed
if( count <= 0 )
count = 1;
while( count-- )
{
asSListPatternNode *subList = node;
DestroySubList(buffer, subList);
asASSERT( subList->type == asLPT_END );
if( count == 0 )
node = subList;
}
}
else if( node->type == asLPT_END )
{
return;
}
else
{
asASSERT( false );
}
node = node->next;
}
}
// internal
asSNameSpace *asCScriptEngine::GetParentNameSpace(asSNameSpace *ns) const
{
if( ns == 0 ) return 0;
if( ns == nameSpaces[0] ) return 0;
asCString scope = ns->name;
int pos = scope.FindLast("::");
if( pos >= 0 )
{
scope = scope.SubString(0, pos);
return FindNameSpace(scope.AddressOf());
}
return nameSpaces[0];
}
END_AS_NAMESPACE

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 17, 10:23 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
72549
Default Alt Text
(840 KB)

Event Timeline