#include "scriptbuilder.h" #include #include using namespace std; #include #if defined(_MSC_VER) && !defined(_WIN32_WCE) && !defined(__S3E__) #include #endif #ifdef _WIN32_WCE #include // For GetModuleFileName() #endif #if defined(__S3E__) || defined(__APPLE__) || defined(__GNUC__) #include // 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; callbackParam = 0; } void CScriptBuilder::SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam) { includeCallback = callback; callbackParam = 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; } 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::const_iterator it = includedScripts.begin(); #else set::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 = static_cast (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 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 metadata, declaration; metadata.reserve(500); 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 { 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; } // 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 = ExtractMetadataString(pos, metadata); // Determine what this metadata is for int type; ExtractDeclaration(pos, declaration, type); // Store away the declaration in a map for lookup after the build has completed if( type > 0 ) { SMetadataDecl decl(metadata, 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); } } } } // 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, callbackParam); 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 == 1 ) { // Find the type id int typeId = module->GetTypeIdByDecl(decl->declaration.c_str()); assert( typeId >= 0 ); if( typeId >= 0 ) typeMetadataMap.insert(map::value_type(typeId, decl->metadata)); } else if( decl->type == 2 ) { if( decl->parentClass == "" ) { // Find the function id asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str()); assert( func ); if( func ) funcMetadataMap.insert(map::value_type(func->GetId(), decl->metadata)); } else { // Find the method id int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); assert( typeId > 0 ); map::iterator it = classMetadataMap.find(typeId); if( it == classMetadataMap.end() ) { classMetadataMap.insert(map::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::value_type(func->GetId(), decl->metadata)); } } else if( decl->type == 4 ) { if( decl->parentClass == "" ) { // Find the global virtual property accessors asIScriptFunction *func = module->GetFunctionByName(("get_" + decl->declaration).c_str()); if( func ) funcMetadataMap.insert(map::value_type(func->GetId(), decl->metadata)); func = module->GetFunctionByName(("set_" + decl->declaration).c_str()); if( func ) funcMetadataMap.insert(map::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::iterator it = classMetadataMap.find(typeId); if( it == classMetadataMap.end() ) { classMetadataMap.insert(map::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::value_type(func->GetId(), decl->metadata)); func = type->GetMethodByName(("set_" + decl->declaration).c_str()); if( func ) it->second.funcMetadataMap.insert(map::value_type(func->GetId(), decl->metadata)); } } else if( decl->type == 3 ) { 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::value_type(varIdx, decl->metadata)); } else { int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); assert( typeId > 0 ); // Add the classes if needed map::iterator it = classMetadataMap.find(typeId); if( it == classMetadataMap.end() ) { classMetadataMap.insert(map::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::value_type(idx, 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::ExtractMetadataString(int pos, string &metadata) { metadata = ""; // 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 ) metadata.append(&modifiedScript[pos], len); // Overwrite the metadata with space characters to allow compilation if( t != asTC_WHITESPACE ) OverwriteCode(pos, len); pos += len; } return pos; } int CScriptBuilder::ExtractDeclaration(int pos, string &declaration, int &type) { declaration = ""; type = 0; int start = pos; std::string token; asUINT len = 0; asETokenClass t = asTC_WHITESPACE; // Skip white spaces and comments do { pos += len; t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); } while ( t == asTC_WHITESPACE || t == asTC_COMMENT ); // 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" ) { // 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 = 1; declaration.assign(&modifiedScript[pos], len); pos += len; return pos; } } else { // For function declarations, store everything up to the start of the statement block // 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; declaration.append(&modifiedScript[pos], len); pos += len; string name; for(; pos < (int)modifiedScript.size();) { t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); if( t == asTC_KEYWORD ) { token.assign(&modifiedScript[pos], len); if( token == "{" ) { if( hasParenthesis ) { // We've found the end of a function signature type = 2; } else { // We've found a virtual property. Just keep the name declaration = name; type = 4; } return pos; } if( token == "=" || token == ";" ) { // Substitute the declaration with just the name declaration = name; type = 3; return pos; } else if( token == "(" ) { // 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( t == asTC_IDENTIFIER ) { name.assign(&modifiedScript[pos], len); } declaration.append(&modifiedScript[pos], len); pos += len; } } } return start; } const char *CScriptBuilder::GetMetadataStringForType(int typeId) { map::iterator it = typeMetadataMap.find(typeId); if( it != typeMetadataMap.end() ) return it->second.c_str(); return ""; } const char *CScriptBuilder::GetMetadataStringForFunc(asIScriptFunction *func) { if( func ) { map::iterator it = funcMetadataMap.find(func->GetId()); if( it != funcMetadataMap.end() ) return it->second.c_str(); } return ""; } const char *CScriptBuilder::GetMetadataStringForVar(int varIdx) { map::iterator it = varMetadataMap.find(varIdx); if( it != varMetadataMap.end() ) return it->second.c_str(); return ""; } const char *CScriptBuilder::GetMetadataStringForTypeProperty(int typeId, int varIdx) { map::iterator typeIt = classMetadataMap.find(typeId); if(typeIt == classMetadataMap.end()) return ""; map::iterator propIt = typeIt->second.varMetadataMap.find(varIdx); if(propIt == typeIt->second.varMetadataMap.end()) return ""; return propIt->second.c_str(); } const char *CScriptBuilder::GetMetadataStringForTypeMethod(int typeId, asIScriptFunction *method) { if( method ) { map::iterator typeIt = classMetadataMap.find(typeId); if(typeIt == classMetadataMap.end()) return ""; map::iterator methodIt = typeIt->second.funcMetadataMap.find(method->GetId()); if(methodIt == typeIt->second.funcMetadataMap.end()) return ""; return methodIt->second.c_str(); } return ""; } #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