1168 lines
30 KiB
C++
1168 lines
30 KiB
C++
|
#include "scriptbuilder.h"
|
||
|
#include <vector>
|
||
|
#include <assert.h>
|
||
|
using namespace std;
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#if defined(_MSC_VER) && !defined(_WIN32_WCE) && !defined(__S3E__)
|
||
|
#include <direct.h>
|
||
|
#endif
|
||
|
#ifdef _WIN32_WCE
|
||
|
#include <windows.h> // For GetModuleFileName()
|
||
|
#endif
|
||
|
|
||
|
#if defined(__S3E__) || defined(__APPLE__) || defined(__GNUC__)
|
||
|
#include <unistd.h> // For getcwd()
|
||
|
#endif
|
||
|
|
||
|
BEGIN_AS_NAMESPACE
|
||
|
|
||
|
// Helper functions
|
||
|
static string GetCurrentDir();
|
||
|
static string GetAbsolutePath(const string &path);
|
||
|
|
||
|
|
||
|
CScriptBuilder::CScriptBuilder()
|
||
|
{
|
||
|
engine = 0;
|
||
|
module = 0;
|
||
|
|
||
|
includeCallback = 0;
|
||
|
includeParam = 0;
|
||
|
|
||
|
pragmaCallback = 0;
|
||
|
pragmaParam = 0;
|
||
|
}
|
||
|
|
||
|
void CScriptBuilder::SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam)
|
||
|
{
|
||
|
includeCallback = callback;
|
||
|
includeParam = userParam;
|
||
|
}
|
||
|
|
||
|
void CScriptBuilder::SetPragmaCallback(PRAGMACALLBACK_t callback, void *userParam)
|
||
|
{
|
||
|
pragmaCallback = callback;
|
||
|
pragmaParam = userParam;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::StartNewModule(asIScriptEngine *inEngine, const char *moduleName)
|
||
|
{
|
||
|
if(inEngine == 0 ) return -1;
|
||
|
|
||
|
engine = inEngine;
|
||
|
module = inEngine->GetModule(moduleName, asGM_ALWAYS_CREATE);
|
||
|
if( module == 0 )
|
||
|
return -1;
|
||
|
|
||
|
ClearAll();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
asIScriptEngine *CScriptBuilder::GetEngine()
|
||
|
{
|
||
|
return engine;
|
||
|
}
|
||
|
|
||
|
asIScriptModule *CScriptBuilder::GetModule()
|
||
|
{
|
||
|
return module;
|
||
|
}
|
||
|
|
||
|
unsigned int CScriptBuilder::GetSectionCount() const
|
||
|
{
|
||
|
return (unsigned int)(includedScripts.size());
|
||
|
}
|
||
|
|
||
|
string CScriptBuilder::GetSectionName(unsigned int idx) const
|
||
|
{
|
||
|
if( idx >= includedScripts.size() ) return "";
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
set<string, ci_less>::const_iterator it = includedScripts.begin();
|
||
|
#else
|
||
|
set<string>::const_iterator it = includedScripts.begin();
|
||
|
#endif
|
||
|
while( idx-- > 0 ) it++;
|
||
|
return *it;
|
||
|
}
|
||
|
|
||
|
// Returns 1 if the section was included
|
||
|
// Returns 0 if the section was not included because it had already been included before
|
||
|
// Returns <0 if there was an error
|
||
|
int CScriptBuilder::AddSectionFromFile(const char *filename)
|
||
|
{
|
||
|
// The file name stored in the set should be the fully resolved name because
|
||
|
// it is possible to name the same file in multiple ways using relative paths.
|
||
|
string fullpath = GetAbsolutePath(filename);
|
||
|
|
||
|
if( IncludeIfNotAlreadyIncluded(fullpath.c_str()) )
|
||
|
{
|
||
|
int r = LoadScriptSection(fullpath.c_str());
|
||
|
if( r < 0 )
|
||
|
return r;
|
||
|
else
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Returns 1 if the section was included
|
||
|
// Returns 0 if the section was not included because it had already been included before
|
||
|
// Returns <0 if there was an error
|
||
|
int CScriptBuilder::AddSectionFromMemory(const char *sectionName, const char *scriptCode, unsigned int scriptLength, int lineOffset)
|
||
|
{
|
||
|
if( IncludeIfNotAlreadyIncluded(sectionName) )
|
||
|
{
|
||
|
int r = ProcessScriptSection(scriptCode, scriptLength, sectionName, lineOffset);
|
||
|
if( r < 0 )
|
||
|
return r;
|
||
|
else
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::BuildModule()
|
||
|
{
|
||
|
return Build();
|
||
|
}
|
||
|
|
||
|
void CScriptBuilder::DefineWord(const char *word)
|
||
|
{
|
||
|
string sword = word;
|
||
|
if( definedWords.find(sword) == definedWords.end() )
|
||
|
{
|
||
|
definedWords.insert(sword);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CScriptBuilder::ClearAll()
|
||
|
{
|
||
|
includedScripts.clear();
|
||
|
|
||
|
#if AS_PROCESS_METADATA == 1
|
||
|
currentClass = "";
|
||
|
currentNamespace = "";
|
||
|
|
||
|
foundDeclarations.clear();
|
||
|
typeMetadataMap.clear();
|
||
|
funcMetadataMap.clear();
|
||
|
varMetadataMap.clear();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
bool CScriptBuilder::IncludeIfNotAlreadyIncluded(const char *filename)
|
||
|
{
|
||
|
string scriptFile = filename;
|
||
|
if( includedScripts.find(scriptFile) != includedScripts.end() )
|
||
|
{
|
||
|
// Already included
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Add the file to the set of included sections
|
||
|
includedScripts.insert(scriptFile);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::LoadScriptSection(const char *filename)
|
||
|
{
|
||
|
// Open the script file
|
||
|
string scriptFile = filename;
|
||
|
#if _MSC_VER >= 1500 && !defined(__S3E__)
|
||
|
FILE *f = 0;
|
||
|
fopen_s(&f, scriptFile.c_str(), "rb");
|
||
|
#else
|
||
|
FILE *f = fopen(scriptFile.c_str(), "rb");
|
||
|
#endif
|
||
|
if( f == 0 )
|
||
|
{
|
||
|
// Write a message to the engine's message callback
|
||
|
string msg = "Failed to open script file '" + GetAbsolutePath(scriptFile) + "'";
|
||
|
engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str());
|
||
|
|
||
|
// TODO: Write the file where this one was included from
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// Determine size of the file
|
||
|
fseek(f, 0, SEEK_END);
|
||
|
int len = ftell(f);
|
||
|
fseek(f, 0, SEEK_SET);
|
||
|
|
||
|
// On Win32 it is possible to do the following instead
|
||
|
// int len = _filelength(_fileno(f));
|
||
|
|
||
|
// Read the entire file
|
||
|
string code;
|
||
|
size_t c = 0;
|
||
|
if( len > 0 )
|
||
|
{
|
||
|
code.resize(len);
|
||
|
c = fread(&code[0], len, 1, f);
|
||
|
}
|
||
|
|
||
|
fclose(f);
|
||
|
|
||
|
if( c == 0 && len > 0 )
|
||
|
{
|
||
|
// Write a message to the engine's message callback
|
||
|
string msg = "Failed to load script file '" + GetAbsolutePath(scriptFile) + "'";
|
||
|
engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str());
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// Process the script section even if it is zero length so that the name is registered
|
||
|
return ProcessScriptSection(code.c_str(), (unsigned int)(code.length()), filename, 0);
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::ProcessScriptSection(const char *script, unsigned int length, const char *sectionname, int lineOffset)
|
||
|
{
|
||
|
vector<string> includes;
|
||
|
|
||
|
// Perform a superficial parsing of the script first to store the metadata
|
||
|
if( length )
|
||
|
modifiedScript.assign(script, length);
|
||
|
else
|
||
|
modifiedScript = script;
|
||
|
|
||
|
// First perform the checks for #if directives to exclude code that shouldn't be compiled
|
||
|
unsigned int pos = 0;
|
||
|
int nested = 0;
|
||
|
while( pos < modifiedScript.size() )
|
||
|
{
|
||
|
asUINT len = 0;
|
||
|
asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_UNKNOWN && modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) )
|
||
|
{
|
||
|
int start = pos++;
|
||
|
|
||
|
// Is this an #if directive?
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
|
||
|
string token;
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
|
||
|
pos += len;
|
||
|
|
||
|
if( token == "if" )
|
||
|
{
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_WHITESPACE )
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
}
|
||
|
|
||
|
if( t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
string word;
|
||
|
word.assign(&modifiedScript[pos], len);
|
||
|
|
||
|
// Overwrite the #if directive with space characters to avoid compiler error
|
||
|
pos += len;
|
||
|
OverwriteCode(start, pos-start);
|
||
|
|
||
|
// Has this identifier been defined by the application or not?
|
||
|
if( definedWords.find(word) == definedWords.end() )
|
||
|
{
|
||
|
// Exclude all the code until and including the #endif
|
||
|
pos = ExcludeCode(pos);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nested++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( token == "endif" )
|
||
|
{
|
||
|
// Only remove the #endif if there was a matching #if
|
||
|
if( nested > 0 )
|
||
|
{
|
||
|
OverwriteCode(start, pos-start);
|
||
|
nested--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
#if AS_PROCESS_METADATA == 1
|
||
|
// Preallocate memory
|
||
|
string name, declaration;
|
||
|
vector<string> metadata;
|
||
|
declaration.reserve(100);
|
||
|
#endif
|
||
|
|
||
|
// Then check for meta data and #include directives
|
||
|
pos = 0;
|
||
|
while( pos < modifiedScript.size() )
|
||
|
{
|
||
|
asUINT len = 0;
|
||
|
asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_COMMENT || t == asTC_WHITESPACE )
|
||
|
{
|
||
|
pos += len;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
#if AS_PROCESS_METADATA == 1
|
||
|
// Check if class
|
||
|
if( currentClass == "" && modifiedScript.substr(pos,len) == "class" )
|
||
|
{
|
||
|
// Get the identifier after "class"
|
||
|
do
|
||
|
{
|
||
|
pos += len;
|
||
|
if( pos >= modifiedScript.size() )
|
||
|
{
|
||
|
t = asTC_UNKNOWN;
|
||
|
break;
|
||
|
}
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
} while(t == asTC_COMMENT || t == asTC_WHITESPACE);
|
||
|
|
||
|
if( t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
currentClass = modifiedScript.substr(pos,len);
|
||
|
|
||
|
// Search until first { or ; is encountered
|
||
|
while( pos < modifiedScript.length() )
|
||
|
{
|
||
|
engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
|
||
|
// If start of class section encountered stop
|
||
|
if( modifiedScript[pos] == '{' )
|
||
|
{
|
||
|
pos += len;
|
||
|
break;
|
||
|
}
|
||
|
else if (modifiedScript[pos] == ';')
|
||
|
{
|
||
|
// The class declaration has ended and there are no children
|
||
|
currentClass = "";
|
||
|
pos += len;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Check next symbol
|
||
|
pos += len;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Check if end of class
|
||
|
if( currentClass != "" && modifiedScript[pos] == '}' )
|
||
|
{
|
||
|
currentClass = "";
|
||
|
pos += len;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Check if namespace
|
||
|
if( modifiedScript.substr(pos,len) == "namespace" )
|
||
|
{
|
||
|
// Get the identifier after "namespace"
|
||
|
do
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
} while(t == asTC_COMMENT || t == asTC_WHITESPACE);
|
||
|
|
||
|
if( currentNamespace != "" )
|
||
|
currentNamespace += "::";
|
||
|
currentNamespace += modifiedScript.substr(pos,len);
|
||
|
|
||
|
// Search until first { is encountered
|
||
|
while( pos < modifiedScript.length() )
|
||
|
{
|
||
|
engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
|
||
|
// If start of namespace section encountered stop
|
||
|
if( modifiedScript[pos] == '{' )
|
||
|
{
|
||
|
pos += len;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Check next symbol
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Check if end of namespace
|
||
|
if( currentNamespace != "" && modifiedScript[pos] == '}' )
|
||
|
{
|
||
|
size_t found = currentNamespace.rfind( "::" );
|
||
|
if( found != string::npos )
|
||
|
{
|
||
|
currentNamespace.erase( found );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
currentNamespace = "";
|
||
|
}
|
||
|
pos += len;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Is this the start of metadata?
|
||
|
if( modifiedScript[pos] == '[' )
|
||
|
{
|
||
|
// Get the metadata string
|
||
|
pos = ExtractMetadata(pos, metadata);
|
||
|
|
||
|
// Determine what this metadata is for
|
||
|
int type;
|
||
|
ExtractDeclaration(pos, name, declaration, type);
|
||
|
|
||
|
// Store away the declaration in a map for lookup after the build has completed
|
||
|
if( type > 0 )
|
||
|
{
|
||
|
SMetadataDecl decl(metadata, name, declaration, type, currentClass, currentNamespace);
|
||
|
foundDeclarations.push_back(decl);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
// Is this a preprocessor directive?
|
||
|
if( modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) )
|
||
|
{
|
||
|
int start = pos++;
|
||
|
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
string token;
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
if( token == "include" )
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_WHITESPACE )
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
}
|
||
|
|
||
|
if( t == asTC_VALUE && len > 2 && (modifiedScript[pos] == '"' || modifiedScript[pos] == '\'') )
|
||
|
{
|
||
|
// Get the include file
|
||
|
string includefile;
|
||
|
includefile.assign(&modifiedScript[pos+1], len-2);
|
||
|
pos += len;
|
||
|
|
||
|
// Store it for later processing
|
||
|
includes.push_back(includefile);
|
||
|
|
||
|
// Overwrite the include directive with space characters to avoid compiler error
|
||
|
OverwriteCode(start, pos-start);
|
||
|
}
|
||
|
}
|
||
|
else if (token == "pragma")
|
||
|
{
|
||
|
// Read until the end of the line
|
||
|
pos += len;
|
||
|
for (; pos < modifiedScript.size() && modifiedScript[pos] != '\n'; pos++);
|
||
|
|
||
|
// Call the pragma callback
|
||
|
string pragmaText(&modifiedScript[start + 7], pos - start - 7);
|
||
|
int r = pragmaCallback ? pragmaCallback(pragmaText, *this, pragmaParam) : -1;
|
||
|
if (r < 0)
|
||
|
{
|
||
|
// TODO: Report the correct line number
|
||
|
engine->WriteMessage(sectionname, 0, 0, asMSGTYPE_ERROR, "Invalid #pragma directive");
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
// Overwrite the pragma directive with space characters to avoid compiler error
|
||
|
OverwriteCode(start, pos - start);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Don't search for metadata/includes within statement blocks or between tokens in statements
|
||
|
else
|
||
|
{
|
||
|
pos = SkipStatement(pos);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Build the actual script
|
||
|
engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true);
|
||
|
module->AddScriptSection(sectionname, modifiedScript.c_str(), modifiedScript.size(), lineOffset);
|
||
|
|
||
|
if( includes.size() > 0 )
|
||
|
{
|
||
|
// If the callback has been set, then call it for each included file
|
||
|
if( includeCallback )
|
||
|
{
|
||
|
for( int n = 0; n < (int)includes.size(); n++ )
|
||
|
{
|
||
|
int r = includeCallback(includes[n].c_str(), sectionname, this, includeParam);
|
||
|
if( r < 0 )
|
||
|
return r;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// By default we try to load the included file from the relative directory of the current file
|
||
|
|
||
|
// Determine the path of the current script so that we can resolve relative paths for includes
|
||
|
string path = sectionname;
|
||
|
size_t posOfSlash = path.find_last_of("/\\");
|
||
|
if( posOfSlash != string::npos )
|
||
|
path.resize(posOfSlash+1);
|
||
|
else
|
||
|
path = "";
|
||
|
|
||
|
// Load the included scripts
|
||
|
for( int n = 0; n < (int)includes.size(); n++ )
|
||
|
{
|
||
|
// If the include is a relative path, then prepend the path of the originating script
|
||
|
if( includes[n].find_first_of("/\\") != 0 &&
|
||
|
includes[n].find_first_of(":") == string::npos )
|
||
|
{
|
||
|
includes[n] = path + includes[n];
|
||
|
}
|
||
|
|
||
|
// Include the script section
|
||
|
int r = AddSectionFromFile(includes[n].c_str());
|
||
|
if( r < 0 )
|
||
|
return r;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::Build()
|
||
|
{
|
||
|
int r = module->Build();
|
||
|
if( r < 0 )
|
||
|
return r;
|
||
|
|
||
|
#if AS_PROCESS_METADATA == 1
|
||
|
// After the script has been built, the metadata strings should be
|
||
|
// stored for later lookup by function id, type id, and variable index
|
||
|
for( int n = 0; n < (int)foundDeclarations.size(); n++ )
|
||
|
{
|
||
|
SMetadataDecl *decl = &foundDeclarations[n];
|
||
|
module->SetDefaultNamespace(decl->nameSpace.c_str());
|
||
|
if( decl->type == MDT_TYPE )
|
||
|
{
|
||
|
// Find the type id
|
||
|
int typeId = module->GetTypeIdByDecl(decl->declaration.c_str());
|
||
|
assert( typeId >= 0 );
|
||
|
if( typeId >= 0 )
|
||
|
typeMetadataMap.insert(map<int, vector<string> >::value_type(typeId, decl->metadata));
|
||
|
}
|
||
|
else if( decl->type == MDT_FUNC )
|
||
|
{
|
||
|
if( decl->parentClass == "" )
|
||
|
{
|
||
|
// Find the function id
|
||
|
asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str());
|
||
|
assert( func );
|
||
|
if( func )
|
||
|
funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Find the method id
|
||
|
int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
|
||
|
assert( typeId > 0 );
|
||
|
map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
|
||
|
if( it == classMetadataMap.end() )
|
||
|
{
|
||
|
classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
|
||
|
it = classMetadataMap.find(typeId);
|
||
|
}
|
||
|
|
||
|
asITypeInfo *type = engine->GetTypeInfoById(typeId);
|
||
|
asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str());
|
||
|
assert( func );
|
||
|
if( func )
|
||
|
it->second.funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
}
|
||
|
else if( decl->type == MDT_VIRTPROP )
|
||
|
{
|
||
|
if( decl->parentClass == "" )
|
||
|
{
|
||
|
// Find the global virtual property accessors
|
||
|
asIScriptFunction *func = module->GetFunctionByName(("get_" + decl->declaration).c_str());
|
||
|
if( func )
|
||
|
funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
func = module->GetFunctionByName(("set_" + decl->declaration).c_str());
|
||
|
if( func )
|
||
|
funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Find the method virtual property accessors
|
||
|
int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
|
||
|
assert( typeId > 0 );
|
||
|
map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
|
||
|
if( it == classMetadataMap.end() )
|
||
|
{
|
||
|
classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
|
||
|
it = classMetadataMap.find(typeId);
|
||
|
}
|
||
|
|
||
|
asITypeInfo *type = engine->GetTypeInfoById(typeId);
|
||
|
asIScriptFunction *func = type->GetMethodByName(("get_" + decl->declaration).c_str());
|
||
|
if( func )
|
||
|
it->second.funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
func = type->GetMethodByName(("set_" + decl->declaration).c_str());
|
||
|
if( func )
|
||
|
it->second.funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
}
|
||
|
else if( decl->type == MDT_VAR )
|
||
|
{
|
||
|
if( decl->parentClass == "" )
|
||
|
{
|
||
|
// Find the global variable index
|
||
|
int varIdx = module->GetGlobalVarIndexByName(decl->declaration.c_str());
|
||
|
assert( varIdx >= 0 );
|
||
|
if( varIdx >= 0 )
|
||
|
varMetadataMap.insert(map<int, vector<string> >::value_type(varIdx, decl->metadata));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
|
||
|
assert( typeId > 0 );
|
||
|
|
||
|
// Add the classes if needed
|
||
|
map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
|
||
|
if( it == classMetadataMap.end() )
|
||
|
{
|
||
|
classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
|
||
|
it = classMetadataMap.find(typeId);
|
||
|
}
|
||
|
|
||
|
// Add the variable to class
|
||
|
asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
|
||
|
int idx = -1;
|
||
|
|
||
|
// Search through all properties to get proper declaration
|
||
|
for( asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i )
|
||
|
{
|
||
|
const char *name;
|
||
|
objectType->GetProperty(i, &name);
|
||
|
if( decl->declaration == name )
|
||
|
{
|
||
|
idx = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If found, add it
|
||
|
assert( idx >= 0 );
|
||
|
if( idx >= 0 ) it->second.varMetadataMap.insert(map<int, vector<string> >::value_type(idx, decl->metadata));
|
||
|
}
|
||
|
}
|
||
|
else if (decl->type == MDT_FUNC_OR_VAR)
|
||
|
{
|
||
|
if (decl->parentClass == "")
|
||
|
{
|
||
|
// Find the global variable index
|
||
|
int varIdx = module->GetGlobalVarIndexByName(decl->name.c_str());
|
||
|
if (varIdx >= 0)
|
||
|
varMetadataMap.insert(map<int, vector<string> >::value_type(varIdx, decl->metadata));
|
||
|
else
|
||
|
{
|
||
|
asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str());
|
||
|
assert(func);
|
||
|
if (func)
|
||
|
funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
|
||
|
assert(typeId > 0);
|
||
|
|
||
|
// Add the classes if needed
|
||
|
map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
|
||
|
if (it == classMetadataMap.end())
|
||
|
{
|
||
|
classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
|
||
|
it = classMetadataMap.find(typeId);
|
||
|
}
|
||
|
|
||
|
// Add the variable to class
|
||
|
asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
|
||
|
int idx = -1;
|
||
|
|
||
|
// Search through all properties to get proper declaration
|
||
|
for (asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i)
|
||
|
{
|
||
|
const char *name;
|
||
|
objectType->GetProperty(i, &name);
|
||
|
if (decl->name == name)
|
||
|
{
|
||
|
idx = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If found, add it
|
||
|
if (idx >= 0)
|
||
|
it->second.varMetadataMap.insert(map<int, vector<string> >::value_type(idx, decl->metadata));
|
||
|
else
|
||
|
{
|
||
|
// Look for the matching method instead
|
||
|
asITypeInfo *type = engine->GetTypeInfoById(typeId);
|
||
|
asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str());
|
||
|
assert(func);
|
||
|
if (func)
|
||
|
it->second.funcMetadataMap.insert(map<int, vector<string> >::value_type(func->GetId(), decl->metadata));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
module->SetDefaultNamespace("");
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::SkipStatement(int pos)
|
||
|
{
|
||
|
asUINT len = 0;
|
||
|
|
||
|
// Skip until ; or { whichever comes first
|
||
|
while( pos < (int)modifiedScript.length() && modifiedScript[pos] != ';' && modifiedScript[pos] != '{' )
|
||
|
{
|
||
|
engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
// Skip entire statement block
|
||
|
if( pos < (int)modifiedScript.length() && modifiedScript[pos] == '{' )
|
||
|
{
|
||
|
pos += 1;
|
||
|
|
||
|
// Find the end of the statement block
|
||
|
int level = 1;
|
||
|
while( level > 0 && pos < (int)modifiedScript.size() )
|
||
|
{
|
||
|
asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( t == asTC_KEYWORD )
|
||
|
{
|
||
|
if( modifiedScript[pos] == '{' )
|
||
|
level++;
|
||
|
else if( modifiedScript[pos] == '}' )
|
||
|
level--;
|
||
|
}
|
||
|
|
||
|
pos += len;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
pos += 1;
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
// Overwrite all code with blanks until the matching #endif
|
||
|
int CScriptBuilder::ExcludeCode(int pos)
|
||
|
{
|
||
|
asUINT len = 0;
|
||
|
int nested = 0;
|
||
|
while( pos < (int)modifiedScript.size() )
|
||
|
{
|
||
|
engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if( modifiedScript[pos] == '#' )
|
||
|
{
|
||
|
modifiedScript[pos] = ' ';
|
||
|
pos++;
|
||
|
|
||
|
// Is it an #if or #endif directive?
|
||
|
engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
string token;
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
OverwriteCode(pos, len);
|
||
|
|
||
|
if( token == "if" )
|
||
|
{
|
||
|
nested++;
|
||
|
}
|
||
|
else if( token == "endif" )
|
||
|
{
|
||
|
if( nested-- == 0 )
|
||
|
{
|
||
|
pos += len;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( modifiedScript[pos] != '\n' )
|
||
|
{
|
||
|
OverwriteCode(pos, len);
|
||
|
}
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
// Overwrite all characters except line breaks with blanks
|
||
|
void CScriptBuilder::OverwriteCode(int start, int len)
|
||
|
{
|
||
|
char *code = &modifiedScript[start];
|
||
|
for( int n = 0; n < len; n++ )
|
||
|
{
|
||
|
if( *code != '\n' )
|
||
|
*code = ' ';
|
||
|
code++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if AS_PROCESS_METADATA == 1
|
||
|
int CScriptBuilder::ExtractMetadata(int pos, vector<string> &metadata)
|
||
|
{
|
||
|
metadata.clear();
|
||
|
|
||
|
// Extract all metadata. They can be separated by whitespace and comments
|
||
|
for (;;)
|
||
|
{
|
||
|
string metadataString = "";
|
||
|
|
||
|
// Overwrite the metadata with space characters to allow compilation
|
||
|
modifiedScript[pos] = ' ';
|
||
|
|
||
|
// Skip opening brackets
|
||
|
pos += 1;
|
||
|
|
||
|
int level = 1;
|
||
|
asUINT len = 0;
|
||
|
while (level > 0 && pos < (int)modifiedScript.size())
|
||
|
{
|
||
|
asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
if (t == asTC_KEYWORD)
|
||
|
{
|
||
|
if (modifiedScript[pos] == '[')
|
||
|
level++;
|
||
|
else if (modifiedScript[pos] == ']')
|
||
|
level--;
|
||
|
}
|
||
|
|
||
|
// Copy the metadata to our buffer
|
||
|
if (level > 0)
|
||
|
metadataString.append(&modifiedScript[pos], len);
|
||
|
|
||
|
// Overwrite the metadata with space characters to allow compilation
|
||
|
if (t != asTC_WHITESPACE)
|
||
|
OverwriteCode(pos, len);
|
||
|
|
||
|
pos += len;
|
||
|
}
|
||
|
|
||
|
metadata.push_back(metadataString);
|
||
|
|
||
|
// Check for more metadata. Possibly separated by comments
|
||
|
asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
while (t == asTC_COMMENT || t == asTC_WHITESPACE)
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
}
|
||
|
|
||
|
if (modifiedScript[pos] != '[')
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
int CScriptBuilder::ExtractDeclaration(int pos, string &name, string &declaration, int &type)
|
||
|
{
|
||
|
declaration = "";
|
||
|
type = 0;
|
||
|
|
||
|
int start = pos;
|
||
|
|
||
|
std::string token;
|
||
|
asUINT len = 0;
|
||
|
asETokenClass t = asTC_WHITESPACE;
|
||
|
|
||
|
// Skip white spaces, comments, and leading decorators
|
||
|
do
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
} while ( t == asTC_WHITESPACE || t == asTC_COMMENT ||
|
||
|
token == "private" || token == "protected" ||
|
||
|
token == "shared" || token == "external" ||
|
||
|
token == "final" || token == "abstract" );
|
||
|
|
||
|
// We're expecting, either a class, interface, function, or variable declaration
|
||
|
if( t == asTC_KEYWORD || t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
if( token == "interface" || token == "class" || token == "enum" )
|
||
|
{
|
||
|
// Skip white spaces and comments
|
||
|
do
|
||
|
{
|
||
|
pos += len;
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
} while ( t == asTC_WHITESPACE || t == asTC_COMMENT );
|
||
|
|
||
|
if( t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
type = MDT_TYPE;
|
||
|
declaration.assign(&modifiedScript[pos], len);
|
||
|
pos += len;
|
||
|
return pos;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// For function declarations, store everything up to the start of the
|
||
|
// statement block, except for succeeding decorators (final, override, etc)
|
||
|
|
||
|
// For variable declaration store just the name as there can only be one
|
||
|
|
||
|
// We'll only know if the declaration is a variable or function declaration
|
||
|
// when we see the statement block, or absense of a statement block.
|
||
|
bool hasParenthesis = false;
|
||
|
int nestedParenthesis = 0;
|
||
|
declaration.append(&modifiedScript[pos], len);
|
||
|
pos += len;
|
||
|
for(; pos < (int)modifiedScript.size();)
|
||
|
{
|
||
|
t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
|
||
|
token.assign(&modifiedScript[pos], len);
|
||
|
if (t == asTC_KEYWORD)
|
||
|
{
|
||
|
if (token == "{" && nestedParenthesis == 0)
|
||
|
{
|
||
|
if (hasParenthesis)
|
||
|
{
|
||
|
// We've found the end of a function signature
|
||
|
type = MDT_FUNC;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We've found a virtual property. Just keep the name
|
||
|
declaration = name;
|
||
|
type = MDT_VIRTPROP;
|
||
|
}
|
||
|
return pos;
|
||
|
}
|
||
|
if ((token == "=" && !hasParenthesis) || token == ";")
|
||
|
{
|
||
|
if (hasParenthesis)
|
||
|
{
|
||
|
// The declaration is ambigous. It can be a variable with initialization, or a function prototype
|
||
|
type = MDT_FUNC_OR_VAR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Substitute the declaration with just the name
|
||
|
declaration = name;
|
||
|
type = MDT_VAR;
|
||
|
}
|
||
|
return pos;
|
||
|
}
|
||
|
else if (token == "(")
|
||
|
{
|
||
|
nestedParenthesis++;
|
||
|
|
||
|
// This is the first parenthesis we encounter. If the parenthesis isn't followed
|
||
|
// by a statement block, then this is a variable declaration, in which case we
|
||
|
// should only store the type and name of the variable, not the initialization parameters.
|
||
|
hasParenthesis = true;
|
||
|
}
|
||
|
else if (token == ")")
|
||
|
{
|
||
|
nestedParenthesis--;
|
||
|
}
|
||
|
}
|
||
|
else if( t == asTC_IDENTIFIER )
|
||
|
{
|
||
|
name = token;
|
||
|
}
|
||
|
|
||
|
// Skip trailing decorators
|
||
|
if( !hasParenthesis || nestedParenthesis > 0 || t != asTC_IDENTIFIER || (token != "final" && token != "override") )
|
||
|
declaration += token;
|
||
|
|
||
|
pos += len;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return start;
|
||
|
}
|
||
|
|
||
|
vector<string> CScriptBuilder::GetMetadataForType(int typeId)
|
||
|
{
|
||
|
map<int,vector<string> >::iterator it = typeMetadataMap.find(typeId);
|
||
|
if( it != typeMetadataMap.end() )
|
||
|
return it->second;
|
||
|
|
||
|
return vector<string>();
|
||
|
}
|
||
|
|
||
|
vector<string> CScriptBuilder::GetMetadataForFunc(asIScriptFunction *func)
|
||
|
{
|
||
|
if( func )
|
||
|
{
|
||
|
map<int,vector<string> >::iterator it = funcMetadataMap.find(func->GetId());
|
||
|
if( it != funcMetadataMap.end() )
|
||
|
return it->second;
|
||
|
}
|
||
|
|
||
|
return vector<string>();
|
||
|
}
|
||
|
|
||
|
vector<string> CScriptBuilder::GetMetadataForVar(int varIdx)
|
||
|
{
|
||
|
map<int,vector<string> >::iterator it = varMetadataMap.find(varIdx);
|
||
|
if( it != varMetadataMap.end() )
|
||
|
return it->second;
|
||
|
|
||
|
return vector<string>();
|
||
|
}
|
||
|
|
||
|
vector<string> CScriptBuilder::GetMetadataForTypeProperty(int typeId, int varIdx)
|
||
|
{
|
||
|
map<int, SClassMetadata>::iterator typeIt = classMetadataMap.find(typeId);
|
||
|
if(typeIt == classMetadataMap.end()) return vector<string>();
|
||
|
|
||
|
map<int, vector<string> >::iterator propIt = typeIt->second.varMetadataMap.find(varIdx);
|
||
|
if(propIt == typeIt->second.varMetadataMap.end()) return vector<string>();
|
||
|
|
||
|
return propIt->second;
|
||
|
}
|
||
|
|
||
|
vector<string> CScriptBuilder::GetMetadataForTypeMethod(int typeId, asIScriptFunction *method)
|
||
|
{
|
||
|
if( method )
|
||
|
{
|
||
|
map<int, SClassMetadata>::iterator typeIt = classMetadataMap.find(typeId);
|
||
|
if (typeIt == classMetadataMap.end()) return vector<string>();
|
||
|
|
||
|
map<int, vector<string> >::iterator methodIt = typeIt->second.funcMetadataMap.find(method->GetId());
|
||
|
if(methodIt == typeIt->second.funcMetadataMap.end()) return vector<string>();
|
||
|
|
||
|
return methodIt->second;
|
||
|
}
|
||
|
|
||
|
return vector<string>();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
string GetAbsolutePath(const string &file)
|
||
|
{
|
||
|
string str = file;
|
||
|
|
||
|
// If this is a relative path, complement it with the current path
|
||
|
if( !((str.length() > 0 && (str[0] == '/' || str[0] == '\\')) ||
|
||
|
str.find(":") != string::npos) )
|
||
|
{
|
||
|
str = GetCurrentDir() + "/" + str;
|
||
|
}
|
||
|
|
||
|
// Replace backslashes for forward slashes
|
||
|
size_t pos = 0;
|
||
|
while( (pos = str.find("\\", pos)) != string::npos )
|
||
|
str[pos] = '/';
|
||
|
|
||
|
// Replace /./ with /
|
||
|
pos = 0;
|
||
|
while( (pos = str.find("/./", pos)) != string::npos )
|
||
|
str.erase(pos+1, 2);
|
||
|
|
||
|
// For each /../ remove the parent dir and the /../
|
||
|
pos = 0;
|
||
|
while( (pos = str.find("/../")) != string::npos )
|
||
|
{
|
||
|
size_t pos2 = str.rfind("/", pos-1);
|
||
|
if( pos2 != string::npos )
|
||
|
str.erase(pos2, pos+3-pos2);
|
||
|
else
|
||
|
{
|
||
|
// The path is invalid
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
string GetCurrentDir()
|
||
|
{
|
||
|
char buffer[1024];
|
||
|
#if defined(_MSC_VER) || defined(_WIN32)
|
||
|
#ifdef _WIN32_WCE
|
||
|
static TCHAR apppath[MAX_PATH] = TEXT("");
|
||
|
if (!apppath[0])
|
||
|
{
|
||
|
GetModuleFileName(NULL, apppath, MAX_PATH);
|
||
|
|
||
|
int appLen = _tcslen(apppath);
|
||
|
|
||
|
// Look for the last backslash in the path, which would be the end
|
||
|
// of the path itself and the start of the filename. We only want
|
||
|
// the path part of the exe's full-path filename
|
||
|
// Safety is that we make sure not to walk off the front of the
|
||
|
// array (in case the path is nothing more than a filename)
|
||
|
while (appLen > 1)
|
||
|
{
|
||
|
if (apppath[appLen-1] == TEXT('\\'))
|
||
|
break;
|
||
|
appLen--;
|
||
|
}
|
||
|
|
||
|
// Terminate the string after the trailing backslash
|
||
|
apppath[appLen] = TEXT('\0');
|
||
|
}
|
||
|
#ifdef _UNICODE
|
||
|
wcstombs(buffer, apppath, min(1024, wcslen(apppath)*sizeof(wchar_t)));
|
||
|
#else
|
||
|
memcpy(buffer, apppath, min(1024, strlen(apppath)));
|
||
|
#endif
|
||
|
|
||
|
return buffer;
|
||
|
#elif defined(__S3E__)
|
||
|
// Marmalade uses its own portable C library
|
||
|
return getcwd(buffer, (int)1024);
|
||
|
#elif _XBOX_VER >= 200
|
||
|
// XBox 360 doesn't support the getcwd function, just use the root folder
|
||
|
return "game:/";
|
||
|
#elif defined(_M_ARM)
|
||
|
// TODO: How to determine current working dir on Windows Phone?
|
||
|
return "";
|
||
|
#else
|
||
|
return _getcwd(buffer, (int)1024);
|
||
|
#endif // _MSC_VER
|
||
|
#elif defined(__APPLE__) || defined(__linux__)
|
||
|
return getcwd(buffer, 1024);
|
||
|
#else
|
||
|
return "";
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
END_AS_NAMESPACE
|
||
|
|
||
|
|