5695 lines
184 KiB
C++
5695 lines
184 KiB
C++
/*
|
|
AngelCode Scripting Library
|
|
Copyright (c) 2003-2021 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
|
|
CleanupEnumValues();
|
|
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;
|
|
}
|
|
|
|
asCScriptCode *asCBuilder::FindOrAddCode(const char *name, const char *code, size_t length) {
|
|
for (asUINT n = 0; n < scripts.GetLength(); n++)
|
|
if (scripts[n]->name == name && scripts[n]->codeLength == length && memcmp(scripts[n]->code, code, length) == 0)
|
|
return scripts[n];
|
|
|
|
asCScriptCode *script = asNEW(asCScriptCode);
|
|
if (script == 0)
|
|
return 0;
|
|
|
|
int r = script->SetCode(name, code, length, true);
|
|
if (r < 0) {
|
|
asDELETE(script, asCScriptCode);
|
|
return 0;
|
|
}
|
|
|
|
script->idx = engine->GetScriptSectionNameIndex(name);
|
|
scripts.PushLast(script);
|
|
return script;
|
|
}
|
|
|
|
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(asFUNCTION(asCOutputBuffer::CDeclCallback), &outBuffer, asCALL_CDECL_OBJLAST);
|
|
|
|
// 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();
|
|
if (numErrors > 0)
|
|
return asERROR;
|
|
|
|
// 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;
|
|
if (numErrors > 0)
|
|
return asERROR;
|
|
|
|
// 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->m_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->m_globalFunctions.GetIndex(func) >= 0) {
|
|
module->m_globalFunctions.Erase(module->m_globalFunctions.GetIndex(func));
|
|
module->m_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
|
|
asSFunctionTraits funcTraits;
|
|
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, funcTraits, module->m_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->m_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->m_defaultNamespace, false, false);
|
|
if (r < 0) {
|
|
func->ReleaseInternal();
|
|
return asERROR;
|
|
}
|
|
|
|
module->m_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->m_globalFunctions.GetIndex(f) >= 0) {
|
|
module->m_globalFunctions.Erase(module->m_globalFunctions.GetIndex(f));
|
|
module->m_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]);
|
|
|
|
// 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]);
|
|
}
|
|
|
|
// 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, CastToObjectType(decl->typeInfo), true, false, 0, decl->isExistingShared);
|
|
} else if (node->nodeType == snVirtualProperty) {
|
|
node->DisconnectParent();
|
|
RegisterVirtualProperty(node, decl->script, CastToObjectType(decl->typeInfo), 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, CastToObjectType(decl->typeInfo), false, false, 0, decl->isExistingShared);
|
|
} else if (node->nodeType == snVirtualProperty) {
|
|
node->DisconnectParent();
|
|
RegisterVirtualProperty(node, decl->script, CastToObjectType(decl->typeInfo), false, false, 0, decl->isExistingShared);
|
|
}
|
|
|
|
node = next;
|
|
}
|
|
|
|
// Make sure the default factory & constructor exists for classes
|
|
asCObjectType *ot = CastToObjectType(decl->typeInfo);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Don't compile if there is no statement block
|
|
if (current->node && !(current->node->nodeType == snStatementBlock || current->node->lastChild->nodeType == snStatementBlock))
|
|
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 (CastToObjectType(dt->GetTypeInfo()) == 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, false) < 0)
|
|
return asNAME_TAKEN;
|
|
} else {
|
|
if (CheckNameConflict(name.AddressOf(), nameNode, &source, ns, true, false) < 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(CastToObjectType(obj.GetTypeInfo()) != 0);
|
|
|
|
// TODO: optimize: Improve linear search
|
|
asCArray<asCObjectProperty *> &props = CastToObjectType(obj.GetTypeInfo())->properties;
|
|
for (asUINT n = 0; n < props.GetLength(); n++) {
|
|
if (props[n]->name == prop) {
|
|
if (module->m_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->m_scriptGlobals.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->m_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->SetReadOnly(true);
|
|
|
|
n = n->next;
|
|
} else
|
|
func->SetReadOnly(false);
|
|
|
|
// Check for additional function traits
|
|
while (n && n->nodeType == snIdentifier) {
|
|
if (source.TokenEquals(n->tokenPos, n->tokenLength, EXPLICIT_TOKEN))
|
|
func->SetExplicit(true);
|
|
else if (source.TokenEquals(n->tokenPos, n->tokenLength, PROPERTY_TOKEN))
|
|
func->SetProperty(true);
|
|
else
|
|
return asINVALID_DECLARATION;
|
|
|
|
n = n->next;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// TODO: This should use SymbolLookupMember, which should be available in the TypeInfo class
|
|
int asCBuilder::CheckNameConflictMember(asCTypeInfo *t, const char *name, asCScriptNode *node, asCScriptCode *code, bool isProperty, bool isVirtualProperty) {
|
|
// It's not necessary to check against object types
|
|
|
|
asCObjectType *ot = CastToObjectType(t);
|
|
if (!ot)
|
|
return 0;
|
|
|
|
// Check against properties
|
|
// TODO: optimize: Improve linear search
|
|
// Properties are allowed to have the same name as virtual properties
|
|
if (!isVirtualProperty) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check against virtual properties
|
|
// Don't do this when the check is for a virtual property, as it is allowed to have multiple overloads for virtual properties
|
|
// Properties are allowed to have the same name as virtual properties
|
|
if (!isProperty && !isVirtualProperty) {
|
|
asCArray<int> methods = ot->methods;
|
|
for (asUINT n = 0; n < methods.GetLength(); n++) {
|
|
asCScriptFunction *func = engine->scriptFunctions[methods[n]];
|
|
if (func->IsProperty() && func->name.SubString(4) == name) {
|
|
if (code) {
|
|
asCString str;
|
|
str.Format(TXT_NAME_CONFLICT_s_OBJ_PROPERTY, name);
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check against child types
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there is a namespace at the same level with the same name as the class, then need to check for conflicts with symbols in that namespace too
|
|
// TODO: When classes can have static members, the code should change so that class name cannot be the same as a namespace
|
|
asCString scope;
|
|
if (ot->nameSpace->name != "")
|
|
scope = ot->nameSpace->name + "::" + ot->name;
|
|
else
|
|
scope = ot->name;
|
|
asSNameSpace *ns = engine->FindNameSpace(scope.AddressOf());
|
|
if (ns) {
|
|
// Check as if not a function as it doesn't matter the function signature
|
|
return CheckNameConflict(name, node, code, ns, true, isVirtualProperty);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// TODO: This should use SymbolLookup
|
|
int asCBuilder::CheckNameConflict(const char *name, asCScriptNode *node, asCScriptCode *code, asSNameSpace *ns, bool isProperty, bool isVirtualProperty) {
|
|
// Check against registered object types
|
|
if (engine->GetRegisteredType(name, ns) != 0) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_EXTENDED_TYPE, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Check against global properties
|
|
// Virtual properties are allowed to have the same name as a real property
|
|
if (!isVirtualProperty && DoesGlobalPropertyExist(name, ns)) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_GLOBAL_PROPERTY, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Check against registered global virtual properties
|
|
// Don't do this when the check is for a virtual property, as it is allowed to have multiple overloads for virtual properties
|
|
if (!isProperty || !isVirtualProperty) {
|
|
for (asUINT n = 0; n < engine->registeredGlobalFuncs.GetSize(); n++) {
|
|
asCScriptFunction *func = engine->registeredGlobalFuncs.Get(n);
|
|
if (func->IsProperty() &&
|
|
func->nameSpace == ns &&
|
|
func->name.SubString(4) == name) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_VIRTPROP, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Property names must be checked against function names
|
|
if (isProperty) {
|
|
for (asUINT n = 0; n < engine->registeredGlobalFuncs.GetSize(); n++) {
|
|
if (engine->registeredGlobalFuncs.Get(n)->name == name &&
|
|
engine->registeredGlobalFuncs.Get(n)->nameSpace == ns) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_FUNCTION, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef AS_NO_COMPILER
|
|
// Check against interface types
|
|
asUINT n;
|
|
for (n = 0; n < interfaceDeclarations.GetLength(); n++) {
|
|
if (interfaceDeclarations[n]->name == name &&
|
|
interfaceDeclarations[n]->typeInfo->nameSpace == ns) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_INTF, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Check against class types
|
|
for (n = 0; n < classDeclarations.GetLength(); n++) {
|
|
if (classDeclarations[n]->name == name &&
|
|
classDeclarations[n]->typeInfo->nameSpace == ns) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_STRUCT, str.AddressOf());
|
|
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;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_NAMED_TYPE, str.AddressOf());
|
|
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->m_funcDefs[funcDefs[n]->idx]->nameSpace == ns) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_FUNCDEF, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Check against mixin classes
|
|
if (GetMixinClass(name, ns)) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_MIXIN, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Check against virtual properties
|
|
// Don't do this when the check is for a virtual property, as it is allowed to have multiple overloads for virtual properties
|
|
if (!isProperty && !isVirtualProperty) {
|
|
for (n = 0; n < functions.GetLength(); n++) {
|
|
asCScriptFunction *func = engine->scriptFunctions[functions[n] ? functions[n]->funcId : 0];
|
|
if (func &&
|
|
func->IsProperty() &&
|
|
func->objectType == 0 &&
|
|
func->nameSpace == ns &&
|
|
func->name.SubString(4) == name) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_VIRTPROP, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Property names must be checked against function names
|
|
if (isProperty) {
|
|
for (n = 0; n < functions.GetLength(); n++) {
|
|
if (functions[n] &&
|
|
functions[n]->objType == 0 &&
|
|
functions[n]->name == name &&
|
|
engine->scriptFunctions[functions[n]->funcId]->nameSpace == ns) {
|
|
if (code) {
|
|
asCString str;
|
|
if (ns->name != "")
|
|
str = ns->name + "::" + name;
|
|
else
|
|
str = name;
|
|
str.Format(TXT_NAME_CONFLICT_s_IS_FUNCTION, str.AddressOf());
|
|
WriteError(str, code, node);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Returns a negative value on invalid property
|
|
// -2 incorrect prefix
|
|
// -3 invalid signature
|
|
// -4 mismatching type for get/set
|
|
// -5 name conflict
|
|
int asCBuilder::ValidateVirtualProperty(asCScriptFunction *func) {
|
|
asASSERT(func->IsProperty());
|
|
|
|
// A virtual property must have the prefix "get_" or "set_"
|
|
asCString prefix = func->name.SubString(0, 4);
|
|
if (prefix != "get_" && prefix != "set_")
|
|
return -2;
|
|
|
|
// A getter must return a non-void type and have at most 1 argument (indexed property)
|
|
if (prefix == "get_" && (func->returnType == asCDataType::CreatePrimitive(ttVoid, false) || func->parameterTypes.GetLength() > 1))
|
|
return -3;
|
|
|
|
// A setter must return a void and have 1 or 2 arguments (indexed property)
|
|
if (prefix == "set_" && (func->returnType != asCDataType::CreatePrimitive(ttVoid, false) || func->parameterTypes.GetLength() < 1 || func->parameterTypes.GetLength() > 2))
|
|
return -3;
|
|
|
|
// Check matching getter/setter
|
|
asCDataType getType, setType;
|
|
bool found = false;
|
|
if (prefix == "get_") {
|
|
getType = func->returnType;
|
|
|
|
// Find if there is a set accessor in the same scope, and then validate the type of it
|
|
// TODO: optimize search
|
|
asCString setName = "set_" + func->name.SubString(4);
|
|
for (asUINT n = 0; n < engine->scriptFunctions.GetLength(); n++) {
|
|
asCScriptFunction *setFunc = engine->scriptFunctions[n];
|
|
if (setFunc == 0 || setFunc->name != setName || !setFunc->IsProperty())
|
|
continue;
|
|
|
|
// Is it the same scope?
|
|
if (func->module != setFunc->module || func->nameSpace != setFunc->nameSpace || func->objectType != setFunc->objectType)
|
|
continue;
|
|
|
|
setType = setFunc->parameterTypes[setFunc->parameterTypes.GetLength() - 1];
|
|
found = true;
|
|
break;
|
|
}
|
|
} else {
|
|
setType = func->parameterTypes[func->parameterTypes.GetLength() - 1];
|
|
|
|
// Find if there is a get accessor in the same scope and then validate the type of it
|
|
// TODO: optimize search
|
|
asCString getName = "get_" + func->name.SubString(4);
|
|
for (asUINT n = 0; n < engine->scriptFunctions.GetLength(); n++) {
|
|
asCScriptFunction *getFunc = engine->scriptFunctions[n];
|
|
if (getFunc == 0 || getFunc->name != getName || !getFunc->IsProperty())
|
|
continue;
|
|
|
|
// Is it the same scope?
|
|
if (func->module != getFunc->module || func->nameSpace != getFunc->nameSpace || func->objectType != getFunc->objectType)
|
|
continue;
|
|
|
|
getType = getFunc->returnType;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
// Check that the type matches
|
|
// It is permitted for a getter to return a handle and the setter to take a reference
|
|
if (!getType.IsEqualExceptRefAndConst(setType) &&
|
|
!((getType.IsObjectHandle() && !setType.IsObjectHandle()) &&
|
|
(getType.GetTypeInfo() == setType.GetTypeInfo()))) {
|
|
return -4;
|
|
}
|
|
}
|
|
|
|
// Check name conflict with other entities in the same scope
|
|
// It is allowed to have a real property of the same name, in which case the virtual property hides the real one.
|
|
int r;
|
|
if (func->objectType)
|
|
r = CheckNameConflictMember(func->objectType, func->name.SubString(4).AddressOf(), 0, 0, true, true);
|
|
else
|
|
r = CheckNameConflict(func->name.SubString(4).AddressOf(), 0, 0, func->nameSpace, true, true);
|
|
if (r < 0)
|
|
return -5;
|
|
|
|
// Everything is OK
|
|
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));
|
|
|
|
// Skip leading 'shared' and 'external' keywords
|
|
asCScriptNode *n = node->firstChild;
|
|
while (n->nodeType == snIdentifier)
|
|
n = n->next;
|
|
|
|
// Find the name
|
|
asASSERT(n->nodeType == snDataType);
|
|
n = n->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, true, false);
|
|
if (asSUCCESS != r) {
|
|
node->Destroy(engine);
|
|
return r;
|
|
}
|
|
} else {
|
|
int r = CheckNameConflictMember(parent, name.AddressOf(), node, file, false, 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;
|
|
asSFunctionTraits funcTraits;
|
|
|
|
asCFuncdefType *fdt = module->m_funcDefs[funcDef->idx];
|
|
asASSERT(fdt);
|
|
asCScriptFunction *func = fdt->funcdef;
|
|
|
|
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, funcTraits, 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
|
|
bool declaredShared = funcTraits.GetTrait(asTRAIT_SHARED);
|
|
funcTraits.SetTrait(asTRAIT_SHARED, true);
|
|
if (func->returnType.GetTypeInfo() && !func->returnType.GetTypeInfo()->IsShared()) {
|
|
if (declaredShared) {
|
|
asCString s;
|
|
s.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, func->returnType.GetTypeInfo()->name.AddressOf());
|
|
WriteError(s.AddressOf(), funcDef->script, funcDef->node);
|
|
}
|
|
funcTraits.SetTrait(asTRAIT_SHARED, false);
|
|
}
|
|
for (asUINT n = 0; funcTraits.GetTrait(asTRAIT_SHARED) && n < func->parameterTypes.GetLength(); n++)
|
|
if (func->parameterTypes[n].GetTypeInfo() && !func->parameterTypes[n].GetTypeInfo()->IsShared()) {
|
|
if (declaredShared) {
|
|
asCString s;
|
|
s.Format(TXT_SHARED_CANNOT_USE_NON_SHARED_TYPE_s, func->parameterTypes[n].GetTypeInfo()->name.AddressOf());
|
|
WriteError(s.AddressOf(), funcDef->script, funcDef->node);
|
|
}
|
|
funcTraits.SetTrait(asTRAIT_SHARED, false);
|
|
}
|
|
func->SetShared(funcTraits.GetTrait(asTRAIT_SHARED));
|
|
|
|
// Check if there is another identical funcdef from another module and if so reuse that instead
|
|
bool found = false;
|
|
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->ReplaceFuncDef(fdt, fdt2);
|
|
fdt2->AddRefInternal();
|
|
|
|
engine->funcDefs.RemoveValue(fdt);
|
|
|
|
fdt->ReleaseInternal();
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the funcdef was declared as external then the existing shared declaration must have been found
|
|
if (funcTraits.GetTrait(asTRAIT_EXTERNAL) && !found) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_NOT_FOUND, funcDef->name.AddressOf());
|
|
WriteError(str, funcDef->script, funcDef->node);
|
|
}
|
|
|
|
// Remember if the type was declared as external so the saved bytecode can be flagged accordingly
|
|
if (funcTraits.GetTrait(asTRAIT_EXTERNAL) && found)
|
|
module->m_externalTypes.PushLast(engine->scriptFunctions[funcDef->idx]->funcdefType);
|
|
}
|
|
|
|
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, true, false);
|
|
|
|
// 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 decorator tokens
|
|
while (n->tokenType == ttIdentifier &&
|
|
(file->TokenEquals(n->tokenPos, n->tokenLength, FINAL_TOKEN) ||
|
|
file->TokenEquals(n->tokenPos, n->tokenLength, SHARED_TOKEN) ||
|
|
file->TokenEquals(n->tokenPos, n->tokenLength, ABSTRACT_TOKEN) ||
|
|
file->TokenEquals(n->tokenPos, n->tokenLength, EXTERNAL_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, true, false);
|
|
|
|
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 isShared = false;
|
|
bool isAbstract = false;
|
|
bool isExternal = 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, EXTERNAL_TOKEN)) {
|
|
if (isExternal) {
|
|
asCString msg;
|
|
msg.Format(TXT_ATTR_s_INFORMED_MULTIPLE_TIMES, asCString(&file->code[n->tokenPos], n->tokenLength).AddressOf());
|
|
WriteWarning(msg, file, n);
|
|
}
|
|
isExternal = 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 {
|
|
// 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, true, false);
|
|
|
|
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;
|
|
|
|
// External shared interfaces must not try to redefine the interface
|
|
if (isExternal && (n->next == 0 || n->next->tokenType != ttEndStatement)) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_CANNOT_REDEF, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
} else if (!isExternal && n->next && n->next->tokenType == ttEndStatement) {
|
|
asCString str;
|
|
str.Format(TXT_MISSING_DEFINITION_OF_s, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
}
|
|
|
|
// 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.
|
|
asCObjectType *st = 0;
|
|
if (isShared) {
|
|
for (asUINT i = 0; i < engine->sharedScriptTypes.GetLength(); i++) {
|
|
st = CastToObjectType(engine->sharedScriptTypes[i]);
|
|
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->AddClassType(st);
|
|
st->AddRefInternal();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the class was declared as external then it must have been compiled in a different module first
|
|
if (isExternal && decl->typeInfo == 0) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_NOT_FOUND, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
}
|
|
|
|
// Remember if the class was declared as external so the saved bytecode can be flagged accordingly
|
|
if (isExternal)
|
|
module->m_externalTypes.PushLast(st);
|
|
|
|
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->AddClassType(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;
|
|
|
|
bool isShared = false;
|
|
bool isExternal = false;
|
|
while (n->nodeType == snIdentifier) {
|
|
if (file->TokenEquals(n->tokenPos, n->tokenLength, SHARED_TOKEN))
|
|
isShared = true;
|
|
else if (file->TokenEquals(n->tokenPos, n->tokenLength, EXTERNAL_TOKEN))
|
|
isExternal = true;
|
|
else
|
|
break;
|
|
n = n->next;
|
|
}
|
|
|
|
int r, c;
|
|
file->ConvertPosToRowCol(n->tokenPos, &r, &c);
|
|
|
|
asCString name;
|
|
name.Assign(&file->code[n->tokenPos], n->tokenLength);
|
|
CheckNameConflict(name.AddressOf(), n, file, ns, true, false);
|
|
|
|
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;
|
|
|
|
// External shared interfaces must not try to redefine the interface
|
|
if (isExternal && (n->next == 0 || n->next->tokenType != ttEndStatement)) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_CANNOT_REDEF, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
} else if (!isExternal && n->next && n->next->tokenType == ttEndStatement) {
|
|
asCString str;
|
|
str.Format(TXT_MISSING_DEFINITION_OF_s, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
}
|
|
|
|
// 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 = CastToObjectType(engine->sharedScriptTypes[i]);
|
|
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->AddClassType(st);
|
|
st->AddRefInternal();
|
|
|
|
// Remember if the interface was declared as external so the saved bytecode can be flagged accordingly
|
|
if (isExternal)
|
|
module->m_externalTypes.PushLast(st);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the interface was declared as external then it must have been compiled in a different module first
|
|
if (isExternal) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_NOT_FOUND, name.AddressOf());
|
|
WriteError(str, file, n);
|
|
}
|
|
|
|
// 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->AddClassType(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(asFUNCTION(asCOutputBuffer::CDeclCallback), &outBuffer, asCALL_CDECL_OBJLAST);
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = CastToEnumType(gvar->datatype.GetTypeInfo());
|
|
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->m_scriptGlobals.GetSize() == initOrder.GetSize())
|
|
module->m_scriptGlobals.SwapWith(initOrder);
|
|
}
|
|
|
|
CleanupEnumValues();
|
|
}
|
|
|
|
void asCBuilder::CleanupEnumValues() {
|
|
// 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
|
|
CastToObjectType(decl->typeInfo)->interfaces.PushLast(intfType);
|
|
|
|
// Add the inherited interfaces too
|
|
// For interfaces this will be done outside to handle out-of-order declarations
|
|
if (!CastToObjectType(decl->typeInfo)->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 = CastToObjectType(intfDecl->typeInfo);
|
|
|
|
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 != interfaceDeclarations[m]->typeInfo &&
|
|
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];
|
|
if (intfDecl->isExistingShared) {
|
|
// Set the declaration as validated already, so that other
|
|
// types that contain this will accept this type
|
|
intfDecl->validState = 1;
|
|
continue;
|
|
}
|
|
|
|
asCObjectType *intfType = CastToObjectType(intfDecl->typeInfo);
|
|
|
|
// 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 = CastToObjectType(intfDecl->typeInfo);
|
|
|
|
asCScriptNode *node = intfDecl->node;
|
|
asASSERT(node && node->nodeType == snInterface);
|
|
node = node->firstChild;
|
|
|
|
// Skip the 'shared' & 'external' keywords
|
|
while (node->nodeType == snIdentifier &&
|
|
(intfDecl->script->TokenEquals(node->tokenPos, node->tokenLength, SHARED_TOKEN) ||
|
|
intfDecl->script->TokenEquals(node->tokenPos, node->tokenLength, EXTERNAL_TOKEN)))
|
|
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, ABSTRACT_TOKEN) ||
|
|
file->TokenEquals(node->tokenPos, node->tokenLength, EXTERNAL_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 && CastToObjectType(decl->typeInfo)->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 (CastToObjectType(decl->typeInfo)->derivedFrom != objType) {
|
|
asCString str;
|
|
str.Format(TXT_SHARED_s_DOESNT_MATCH_ORIGINAL, decl->typeInfo->GetName());
|
|
WriteError(str, file, node);
|
|
}
|
|
} else {
|
|
// Set the base class
|
|
CastToObjectType(decl->typeInfo)->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 = CastToObjectType(decl->typeInfo);
|
|
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 = CastToObjectType(decl->typeInfo);
|
|
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 && 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, false);
|
|
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;
|
|
}
|
|
|
|
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 = CastToObjectType(decl->typeInfo);
|
|
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 = CastToObjectType(decl->typeInfo);
|
|
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 = CastToObjectType(decl->typeInfo);
|
|
if (ot->IsInterface()) 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(CastToObjectType(dt.GetTypeInfo())))
|
|
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 = CastToObjectType(dt.GetTypeInfo());
|
|
|
|
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) {
|
|
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)) {
|
|
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, CastToObjectType(decl->typeInfo), 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 = CastToObjectType(decl->typeInfo);
|
|
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, false);
|
|
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->nameSpace = func->nameSpace;
|
|
vf->returnType = func->returnType;
|
|
vf->parameterTypes = func->parameterTypes;
|
|
vf->inOutFlags = func->inOutFlags;
|
|
vf->id = engine->GetNextScriptFunctionId();
|
|
vf->objectType = func->objectType;
|
|
vf->objectType->AddRefInternal();
|
|
vf->signatureId = func->signatureId;
|
|
vf->vfTableIdx = idx;
|
|
vf->traits = func->traits;
|
|
|
|
// Clear the shared trait since the virtual function should not have that
|
|
vf->SetShared(false);
|
|
|
|
// 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 CastToObjectType(decl->typeInfo)->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, asSFunctionTraits(), 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]->SetShared(true);
|
|
}
|
|
|
|
int asCBuilder::RegisterEnum(asCScriptNode *node, asCScriptCode *file, asSNameSpace *ns) {
|
|
// Is it a shared enum?
|
|
bool isShared = false;
|
|
bool isExternal = false;
|
|
asCEnumType *existingSharedType = 0;
|
|
asCScriptNode *tmp = node->firstChild;
|
|
while (tmp->nodeType == snIdentifier) {
|
|
if (file->TokenEquals(tmp->tokenPos, tmp->tokenLength, SHARED_TOKEN))
|
|
isShared = true;
|
|
else if (file->TokenEquals(tmp->tokenPos, tmp->tokenLength, EXTERNAL_TOKEN))
|
|
isExternal = true;
|
|
else
|
|
break;
|
|
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 = CastToEnumType(o);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the enum was declared as external then it must have been compiled in a different module first
|
|
if (isExternal && existingSharedType == 0) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_NOT_FOUND, name.AddressOf());
|
|
WriteError(str, file, tmp);
|
|
}
|
|
|
|
// Remember if the type was declared as external so the saved bytecode can be flagged accordingly
|
|
if (isExternal && existingSharedType)
|
|
module->m_externalTypes.PushLast(existingSharedType);
|
|
|
|
// Check the name and add the enum
|
|
int r = CheckNameConflict(name.AddressOf(), tmp->firstChild, file, ns, true, false);
|
|
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->AddEnumType(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());
|
|
|
|
// External shared enums must not redeclare the enum values
|
|
if (isExternal && (tmp->next == 0 || tmp->next->tokenType != ttEndStatement)) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_CANNOT_REDEF, name.AddressOf());
|
|
WriteError(str, file, tmp);
|
|
} else if (!isExternal && tmp->next && tmp->next->tokenType == ttEndStatement) {
|
|
asCString str;
|
|
str.Format(TXT_MISSING_DEFINITION_OF_s, name.AddressOf());
|
|
WriteError(str, file, tmp);
|
|
}
|
|
|
|
// Register the enum values
|
|
tmp = tmp->next;
|
|
while (tmp && tmp->nodeType == snIdentifier) {
|
|
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, true, false);
|
|
|
|
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->AddTypeDef(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> ¶meterNames, asCArray<asCDataType> ¶meterTypes, asCArray<asETypeModifiers> &inOutFlags, asCArray<asCString *> &defaultArgs, asSFunctionTraits &funcTraits, asSNameSpace *implicitNamespace) {
|
|
node = node->firstChild;
|
|
|
|
// Is the function shared?
|
|
funcTraits.SetTrait(asTRAIT_SHARED, false);
|
|
funcTraits.SetTrait(asTRAIT_EXTERNAL, false);
|
|
while (node->tokenType == ttIdentifier) {
|
|
if (file->TokenEquals(node->tokenPos, node->tokenLength, SHARED_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_SHARED, true);
|
|
else if (file->TokenEquals(node->tokenPos, node->tokenLength, EXTERNAL_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_EXTERNAL, true);
|
|
else
|
|
break;
|
|
node = node->next;
|
|
}
|
|
|
|
// Is the function a private or protected class method?
|
|
funcTraits.SetTrait(asTRAIT_PRIVATE, false);
|
|
funcTraits.SetTrait(asTRAIT_PROTECTED, false);
|
|
if (node->tokenType == ttPrivate) {
|
|
funcTraits.SetTrait(asTRAIT_PRIVATE, true);
|
|
node = node->next;
|
|
} else if (node->tokenType == ttProtected) {
|
|
funcTraits.SetTrait(asTRAIT_PROTECTED, true);
|
|
node = node->next;
|
|
}
|
|
|
|
// Find the name
|
|
funcTraits.SetTrait(asTRAIT_CONSTRUCTOR, false);
|
|
funcTraits.SetTrait(asTRAIT_DESTRUCTOR, 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;
|
|
funcTraits.SetTrait(asTRAIT_DESTRUCTOR, true);
|
|
} else {
|
|
n = node;
|
|
funcTraits.SetTrait(asTRAIT_CONSTRUCTOR, true);
|
|
}
|
|
}
|
|
name.Assign(&file->code[n->tokenPos], n->tokenLength);
|
|
|
|
if (!funcTraits.GetTrait(asTRAIT_CONSTRUCTOR) && !funcTraits.GetTrait(asTRAIT_DESTRUCTOR)) {
|
|
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);
|
|
|
|
funcTraits.SetTrait(asTRAIT_CONST, false);
|
|
funcTraits.SetTrait(asTRAIT_FINAL, false);
|
|
funcTraits.SetTrait(asTRAIT_OVERRIDE, false);
|
|
funcTraits.SetTrait(asTRAIT_EXPLICIT, false);
|
|
funcTraits.SetTrait(asTRAIT_PROPERTY, false);
|
|
|
|
if (n->next->next) {
|
|
asCScriptNode *decorator = n->next->next;
|
|
|
|
// Is this a const method?
|
|
if (objType && decorator->tokenType == ttConst) {
|
|
funcTraits.SetTrait(asTRAIT_CONST, true);
|
|
decorator = decorator->next;
|
|
}
|
|
|
|
while (decorator && decorator->tokenType == ttIdentifier) {
|
|
if (objType && file->TokenEquals(decorator->tokenPos, decorator->tokenLength, FINAL_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_FINAL, true);
|
|
else if (objType && file->TokenEquals(decorator->tokenPos, decorator->tokenLength, OVERRIDE_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_OVERRIDE, true);
|
|
else if (objType && file->TokenEquals(decorator->tokenPos, decorator->tokenLength, EXPLICIT_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_EXPLICIT, true);
|
|
else if (file->TokenEquals(decorator->tokenPos, decorator->tokenLength, PROPERTY_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_PROPERTY, true);
|
|
else {
|
|
asCString msg(&file->code[decorator->tokenPos], decorator->tokenLength);
|
|
msg.Format(TXT_UNEXPECTED_TOKEN_s, msg.AddressOf());
|
|
WriteError(msg.AddressOf(), file, decorator);
|
|
}
|
|
|
|
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;
|
|
asSFunctionTraits funcTraits;
|
|
|
|
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, funcTraits, ns);
|
|
|
|
return RegisterScriptFunction(node, file, objType, isInterface, isGlobalFunction, ns, isExistingShared, isMixin, name, returnType, parameterNames, parameterTypes, inOutFlags, defaultArgs, funcTraits);
|
|
}
|
|
|
|
asCScriptFunction *asCBuilder::RegisterLambda(asCScriptNode *node, asCScriptCode *file, asCScriptFunction *funcDef, const asCString &name, asSNameSpace *ns, bool isShared) {
|
|
// Get the parameter names from the node
|
|
asCArray<asCString> parameterNames;
|
|
asCArray<asCString *> defaultArgs;
|
|
asCScriptNode *args = node->firstChild;
|
|
while (args && args->nodeType != snStatementBlock) {
|
|
if (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;
|
|
asSFunctionTraits traits;
|
|
traits.SetTrait(asTRAIT_SHARED, isShared);
|
|
int r = RegisterScriptFunction(args, file, 0, 0, true, ns, false, false, funcName, funcDef->returnType, parameterNames, funcDef->parameterTypes, funcDef->inOutFlags, defaultArgs, traits);
|
|
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> ¶meterNames, asCArray<asCDataType> ¶meterTypes, asCArray<asETypeModifiers> &inOutFlags, asCArray<asCString *> &defaultArgs, asSFunctionTraits funcTraits) {
|
|
// 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 (funcTraits.GetTrait(asTRAIT_CONSTRUCTOR) || funcTraits.GetTrait(asTRAIT_DESTRUCTOR)) {
|
|
// 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, funcTraits.GetTrait(asTRAIT_CONST))) {
|
|
// 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 (!funcTraits.GetTrait(asTRAIT_CONSTRUCTOR) && !funcTraits.GetTrait(asTRAIT_DESTRUCTOR)) {
|
|
if (objType) {
|
|
CheckNameConflictMember(objType, name.AddressOf(), node, file, false, false);
|
|
|
|
if (name == objType->name)
|
|
WriteError(TXT_METHOD_CANT_HAVE_NAME_OF_CLASS, file, node);
|
|
} else
|
|
CheckNameConflict(name.AddressOf(), node, file, ns, false, false);
|
|
} 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 (funcTraits.GetTrait(asTRAIT_DESTRUCTOR))
|
|
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 (funcTraits.GetTrait(asTRAIT_DESTRUCTOR))
|
|
name = "~" + name;
|
|
}
|
|
|
|
// Validate virtual properties signature
|
|
if (funcTraits.GetTrait(asTRAIT_PROPERTY)) {
|
|
asCScriptFunction func(engine, module, asFUNC_SCRIPT);
|
|
func.name = name;
|
|
func.nameSpace = ns;
|
|
func.objectType = objType;
|
|
if (objType)
|
|
objType->AddRefInternal();
|
|
func.traits = funcTraits;
|
|
func.returnType = returnType;
|
|
func.parameterTypes = parameterTypes;
|
|
|
|
int r = ValidateVirtualProperty(&func);
|
|
if (r < 0) {
|
|
asCString str;
|
|
if (r == -2 || r == -3)
|
|
str.Format(TXT_INVALID_SIG_FOR_VIRTPROP);
|
|
else if (r == -4)
|
|
str.Format(TXT_GET_SET_ACCESSOR_TYPE_MISMATCH_FOR_s, name.SubString(4).AddressOf());
|
|
else if (r == -5)
|
|
str.Format(TXT_NAME_CONFLICT_s_ALREADY_USED, name.SubString(4).AddressOf());
|
|
WriteError(str, file, node);
|
|
}
|
|
|
|
func.funcType = asFUNC_DUMMY;
|
|
}
|
|
|
|
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 (funcTraits.GetTrait(asTRAIT_SHARED)) {
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remember if the function was declared as external so the saved bytecode can be flagged accordingly
|
|
if (funcTraits.GetTrait(asTRAIT_EXTERNAL) && func->isExistingShared)
|
|
module->m_externalFunctions.PushLast(engine->scriptFunctions[func->funcId]);
|
|
|
|
if (funcTraits.GetTrait(asTRAIT_EXTERNAL) && !func->isExistingShared) {
|
|
// Mark it as existing shared to avoid compiling it
|
|
func->isExistingShared = true;
|
|
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_NOT_FOUND, name.AddressOf());
|
|
WriteError(str, file, node);
|
|
}
|
|
|
|
// External shared function must not try to redefine the interface
|
|
if (funcTraits.GetTrait(asTRAIT_EXTERNAL) && !(node->tokenType == ttEndStatement || node->lastChild->tokenType == ttEndStatement)) {
|
|
asCString str;
|
|
str.Format(TXT_EXTERNAL_SHARED_s_CANNOT_REDEF, name.AddressOf());
|
|
WriteError(str, file, node);
|
|
} else if (!funcTraits.GetTrait(asTRAIT_EXTERNAL) && !(node->nodeType == snStatementBlock || node->lastChild->nodeType == snStatementBlock)) {
|
|
asCString str;
|
|
str.Format(TXT_MISSING_DEFINITION_OF_s, name.AddressOf());
|
|
WriteError(str, file, node);
|
|
}
|
|
}
|
|
|
|
// Destructors may not have any parameters
|
|
if (funcTraits.GetTrait(asTRAIT_DESTRUCTOR) && 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()) || funcTraits.GetTrait(asTRAIT_SHARED)) {
|
|
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, funcTraits.GetTrait(asTRAIT_CONST))) {
|
|
// 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, funcTraits.GetTrait(asTRAIT_CONST))) {
|
|
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->m_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, isGlobalFunction, funcTraits, 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 (funcTraits.GetTrait(asTRAIT_CONSTRUCTOR)) {
|
|
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 {
|
|
// The copy constructor needs to be marked for easy finding
|
|
if (parameterTypes.GetLength() == 1 &&
|
|
parameterTypes[0].GetTypeInfo() == objType &&
|
|
(parameterTypes[0].IsReference() || parameterTypes[0].IsObjectHandle())) {
|
|
// Verify that there are not multiple options matching the copy constructor
|
|
// TODO: Need a better message, since the parameters can be slightly different, e.g. & vs @
|
|
if (objType->beh.copyconstruct)
|
|
WriteError(TXT_FUNCTION_ALREADY_EXIST, file, node);
|
|
|
|
objType->beh.copyconstruct = funcId;
|
|
objType->beh.copyfactory = factoryId;
|
|
}
|
|
|
|
// Register as a normal constructor
|
|
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, 0, false, funcTraits);
|
|
|
|
// If the object is shared, then the factory must also be marked as shared
|
|
if (objType->flags & asOBJ_SHARED)
|
|
engine->scriptFunctions[factoryId]->SetShared(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 (funcTraits.GetTrait(asTRAIT_DESTRUCTOR))
|
|
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;
|
|
asSFunctionTraits funcTraits;
|
|
asCDataType returnType;
|
|
asCArray<asCString> paramNames;
|
|
asCArray<asCDataType> paramTypes;
|
|
asCArray<asETypeModifiers> paramModifiers;
|
|
asCArray<asCString *> defaultArgs;
|
|
asCString name;
|
|
|
|
funcTraits.SetTrait(asTRAIT_PRIVATE, isPrivate);
|
|
funcTraits.SetTrait(asTRAIT_PROTECTED, isProtected);
|
|
funcTraits.SetTrait(asTRAIT_PROPERTY, true);
|
|
|
|
if (node->firstChild->nodeType == snIdentifier && file->TokenEquals(node->firstChild->tokenPos, node->firstChild->tokenLength, GET_TOKEN))
|
|
name = "get_";
|
|
else if (node->firstChild->nodeType == snIdentifier && file->TokenEquals(node->firstChild->tokenPos, node->firstChild->tokenLength, SET_TOKEN))
|
|
name = "set_";
|
|
else
|
|
WriteError(TXT_UNRECOGNIZED_VIRTUAL_PROPERTY_NODE, file, node);
|
|
|
|
if (name != "") {
|
|
success = true;
|
|
funcNode = node->firstChild->next;
|
|
|
|
if (funcNode && funcNode->tokenType == ttConst) {
|
|
funcTraits.SetTrait(asTRAIT_CONST, true);
|
|
funcNode = funcNode->next;
|
|
}
|
|
|
|
while (funcNode && funcNode->nodeType != snStatementBlock) {
|
|
if (funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, FINAL_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_FINAL, true);
|
|
else if (funcNode->tokenType == ttIdentifier && file->TokenEquals(funcNode->tokenPos, funcNode->tokenLength, OVERRIDE_TOKEN))
|
|
funcTraits.SetTrait(asTRAIT_OVERRIDE, true);
|
|
else {
|
|
asCString msg(&file->code[funcNode->tokenPos], funcNode->tokenLength);
|
|
msg.Format(TXT_UNEXPECTED_TOKEN_s, msg.AddressOf());
|
|
WriteError(msg.AddressOf(), file, node);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (name == "get_") {
|
|
// Setup the signature for the get accessor method
|
|
returnType = emulatedType;
|
|
name = "get_" + emulatedName;
|
|
} else if (name == "set_") {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
if (success) {
|
|
if (!isExistingShared)
|
|
RegisterScriptFunction(funcNode, file, objType, isInterface, isGlobalFunction, ns, false, false, name, returnType, paramNames, paramTypes, paramModifiers, defaultArgs, funcTraits);
|
|
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, funcTraits.GetTrait(asTRAIT_CONST))) {
|
|
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;
|
|
asSFunctionTraits funcTraits;
|
|
|
|
if (ns == 0)
|
|
ns = engine->nameSpaces[0];
|
|
|
|
GetParsedFunctionDetails(node->firstChild, file, 0, name, returnType, parameterNames, parameterTypes, inOutFlags, defaultArgs, funcTraits, ns);
|
|
CheckNameConflict(name.AddressOf(), node, file, ns, false, false);
|
|
|
|
// 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, funcTraits, 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->m_globalFunctions.GetIndexes(ns, name);
|
|
for (n = 0; n < idxs.GetLength(); n++) {
|
|
const asCScriptFunction *f = module->m_globalFunctions.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->m_bindInformations.GetLength(); n++) {
|
|
if (module->m_bindInformations[n]->importedFunctionSignature->name == name &&
|
|
module->m_bindInformations[n]->importedFunctionSignature->nameSpace == ns)
|
|
funcs.PushLast(module->m_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->m_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) {
|
|
asASSERT(objectType);
|
|
|
|
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) : asCString("");
|
|
|
|
// 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->m_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 *f = engine->scriptFunctions[objectType->methods[n]];
|
|
if (f && f->funcType == asFUNC_VIRTUAL)
|
|
f = objectType->virtualFunctionTable[f->vfTableIdx];
|
|
methods.PushLast(f->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 = CastToObjectType(ti);
|
|
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, bool reportError, bool *isValid) {
|
|
asASSERT(node->nodeType == snDataType || node->nodeType == snIdentifier || node->nodeType == snScope);
|
|
|
|
asCDataType dt;
|
|
|
|
asCScriptNode *n = node->firstChild;
|
|
|
|
if (isValid)
|
|
*isValid = true;
|
|
|
|
// If the informed node is an identifier or scope, then the
|
|
// datatype should be identified directly from that
|
|
if (node->nodeType != snDataType)
|
|
n = node;
|
|
|
|
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);
|
|
if (isValid)
|
|
*isValid = 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->m_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 = CastToTypedefType(ti)->aliasForType;
|
|
dt.MakeReadOnly(isConst);
|
|
} else {
|
|
if (ti->flags & asOBJ_TEMPLATE) {
|
|
ti = GetTemplateInstanceFromNode(n, file, CastToObjectType(ti), implicitNamespace, currentType, &n);
|
|
if (ti == 0) {
|
|
if (isValid)
|
|
*isValid = false;
|
|
|
|
// Return a dummy
|
|
return asCDataType::CreatePrimitive(ttInt, false);
|
|
}
|
|
} else if (n && n->next && n->next->nodeType == snDataType) {
|
|
if (reportError) {
|
|
asCString msg;
|
|
msg.Format(TXT_TYPE_s_NOT_TEMPLATE, ti->name.AddressOf());
|
|
WriteError(msg, file, n);
|
|
}
|
|
if (isValid)
|
|
*isValid = false;
|
|
}
|
|
|
|
// Create object data type
|
|
if (ti)
|
|
dt = asCDataType::CreateType(ti, isConst);
|
|
else
|
|
dt = asCDataType::CreatePrimitive(ttInt, isConst);
|
|
}
|
|
} else {
|
|
if (reportError) {
|
|
asCString msg;
|
|
msg.Format(TXT_TYPE_s_NOT_AVAILABLE_FOR_MODULE, (const char *)str.AddressOf());
|
|
WriteError(msg, file, n);
|
|
}
|
|
|
|
dt.SetTokenType(ttInt);
|
|
if (isValid)
|
|
*isValid = false;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
// Try to find it in the parent namespace
|
|
if (ns)
|
|
ns = engine->GetParentNameSpace(ns);
|
|
if (parentType)
|
|
parentType = 0;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
if (reportError) {
|
|
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);
|
|
if (isValid)
|
|
*isValid = false;
|
|
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) {
|
|
if (isImplicitHandle) {
|
|
// Make the type a handle
|
|
if (dt.MakeHandle(true, acceptHandleForScope) < 0) {
|
|
if (reportError)
|
|
WriteError(TXT_OBJECT_HANDLE_NOT_SUPPORTED, file, n);
|
|
if (isValid)
|
|
*isValid = false;
|
|
}
|
|
isImplicitHandle = false;
|
|
}
|
|
|
|
// Make sure the sub type can be instantiated
|
|
if (!dt.CanBeInstantiated()) {
|
|
if (reportError) {
|
|
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);
|
|
}
|
|
if (isValid)
|
|
*isValid = false;
|
|
}
|
|
|
|
// Make the type an array (or multidimensional array)
|
|
if (dt.MakeArray(engine, module) < 0) {
|
|
if (reportError)
|
|
WriteError(TXT_NO_DEFAULT_ARRAY_TYPE, file, n);
|
|
if (isValid)
|
|
*isValid = false;
|
|
break;
|
|
}
|
|
} else {
|
|
// Make the type a handle
|
|
if (dt.IsObjectHandle()) {
|
|
if (reportError)
|
|
WriteError(TXT_HANDLE_OF_HANDLE_IS_NOT_ALLOWED, file, n);
|
|
if (isValid)
|
|
*isValid = false;
|
|
break;
|
|
} else {
|
|
if (dt.MakeHandle(true, acceptHandleForScope) < 0) {
|
|
if (reportError)
|
|
WriteError(TXT_OBJECT_HANDLE_NOT_SUPPORTED, file, n);
|
|
if (isValid)
|
|
*isValid = false;
|
|
break;
|
|
}
|
|
|
|
// Check if the handle should be read-only
|
|
if (n && n->next && n->next->tokenType == ttConst)
|
|
dt.MakeReadOnly(true);
|
|
}
|
|
}
|
|
n = n->next;
|
|
}
|
|
|
|
if (isImplicitHandle) {
|
|
// Make the type a handle
|
|
if (dt.MakeHandle(true, acceptHandleForScope) < 0) {
|
|
if (reportError)
|
|
WriteError(TXT_OBJECT_HANDLE_NOT_SUPPORTED, file, n);
|
|
if (isValid)
|
|
*isValid = false;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (dt.GetTokenType() == ttVoid) {
|
|
asCString msg;
|
|
msg.Format(TXT_TYPE_s_CANNOT_BE_REFERENCE, type.Format(0).AddressOf());
|
|
WriteError(msg, file, node->firstChild);
|
|
return dt;
|
|
}
|
|
|
|
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 &&
|
|
!(dt.GetTypeInfo() && (dt.GetTypeInfo()->flags & asOBJ_TEMPLATE_SUBTYPE))) {
|
|
// Verify that the base type support &inout parameter types
|
|
if (!dt.IsObject() || dt.IsObjectHandle() ||
|
|
!((dt.GetTypeInfo()->flags & asOBJ_NOCOUNT) || (CastToObjectType(dt.GetTypeInfo())->beh.addref && CastToObjectType(dt.GetTypeInfo())->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;
|
|
}
|
|
|
|
if (n && n->tokenType == ttIdentifier) {
|
|
asCString str;
|
|
str.Assign(&file->code[n->tokenPos], n->tokenLength);
|
|
if (str == IF_HANDLE_TOKEN)
|
|
dt.SetIfHandleThenConst(true);
|
|
else {
|
|
// TODO: Should give error if not currently parsing template registration
|
|
asCString msg;
|
|
msg.Format(TXT_UNEXPECTED_TOKEN_s, str.AddressOf());
|
|
WriteError(msg, file, node->firstChild);
|
|
}
|
|
}
|
|
|
|
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 CastToObjectType(GetType(type, ns, 0));
|
|
}
|
|
|
|
#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->m_classTypes.GetLength(); n++)
|
|
if (!knownTypes.MoveTo(0, module->m_classTypes[n]->name))
|
|
knownTypes.Insert(module->m_classTypes[n]->name, true);
|
|
|
|
// Add script enums
|
|
for (n = 0; n < module->m_enumTypes.GetLength(); n++)
|
|
if (!knownTypes.MoveTo(0, module->m_enumTypes[n]->name))
|
|
knownTypes.Insert(module->m_enumTypes[n]->name, true);
|
|
|
|
// Add script typedefs
|
|
for (n = 0; n < module->m_typeDefs.GetLength(); n++)
|
|
if (!knownTypes.MoveTo(0, module->m_typeDefs[n]->name))
|
|
knownTypes.Insert(module->m_typeDefs[n]->name, true);
|
|
|
|
// Add script funcdefs
|
|
for (n = 0; n < module->m_funcDefs.GetLength(); n++)
|
|
if (!knownTypes.MoveTo(0, module->m_funcDefs[n]->name))
|
|
knownTypes.Insert(module->m_funcDefs[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->m_funcDefs.GetLength(); n++) {
|
|
asCFuncdefType *funcDef = module->m_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->m_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->m_enumTypes.GetLength(); t++) {
|
|
asCEnumType *et = module->m_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
return 1;
|
|
|
|
// Didn't find any value
|
|
return 0;
|
|
}
|
|
|
|
#endif // AS_NO_COMPILER
|
|
|
|
END_AS_NAMESPACE
|