Ogre对脚本中import语句的内容将生成对应的ImportAbstractNode对象(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/08/2848831.html“脚本及其解析四”)。在创建数据对象前,对ImportAbstractNode对象的处理则将在ScriptCompiler::processImports()函数中进行,函数展开如下:
1 void ScriptCompiler::processImports(Ogre::AbstractNodeListPtr &nodes) 2 { 3 // We only need to iterate over the top-level of nodes 4 AbstractNodeList::iterator i = nodes->begin(); 5 while(i != nodes->end()) 6 { 7 // We move to the next node here and save the current one. 8 // If any replacement happens, then we are still assured that 9 // i points to the node *after* the replaced nodes, no matter 10 // how many insertions and deletions may happen 11 AbstractNodeList::iterator cur = i++; 12 if((*cur)->type == ANT_IMPORT) 13 { 14 ImportAbstractNode *import = (ImportAbstractNode*)(*cur).get(); 15 // Only process if the file's contents haven't been loaded 16 if(mImports.find(import->source) == mImports.end()) 17 { 18 // Load the script 19 AbstractNodeListPtr importedNodes = loadImportPath(import->source); 20 if(!importedNodes.isNull() && !importedNodes->empty()) 21 { 22 processImports(importedNodes); 23 processObjects(importedNodes.get(), importedNodes); 24 } 25 if(!importedNodes.isNull() && !importedNodes->empty()) 26 mImports.insert(std::make_pair(import->source, importedNodes)); 27 } 28 29 // Handle the target request now 30 // If it is a '*' import we remove all previous requests and just use the '*' 31 // Otherwise, ensure '*' isn't already registered and register our request 32 if(import->target == "*") 33 { 34 mImportRequests.erase(mImportRequests.lower_bound(import->source), 35 mImportRequests.upper_bound(import->source)); 36 mImportRequests.insert(std::make_pair(import->source, "*")); 37 } 38 else 39 { 40 ImportRequestMap::iterator iter = mImportRequests.lower_bound(import->source), 41 end = mImportRequests.upper_bound(import->source); 42 if(iter == end || iter->second != "*") 43 { 44 mImportRequests.insert(std::make_pair(import->source, import->target)); 45 } 46 } 47 48 nodes->erase(cur); 49 } 50 } 51 52 // All import nodes are removed 53 // We have cached the code blocks from all the imported scripts 54 // We can process all import requests now 55 for(ImportCacheMap::iterator it = mImports.begin(); it != mImports.end(); ++it) 56 { 57 ImportRequestMap::iterator j = mImportRequests.lower_bound(it->first), 58 end = mImportRequests.upper_bound(it->first); 59 if(j != end) 60 { 61 if(j->second == "*") 62 { 63 // Insert the entire AST into the import table 64 mImportTable.insert(mImportTable.begin(), it->second->begin(), it->second->end()); 65 continue; // Skip ahead to the next file 66 } 67 else 68 { 69 for(; j != end; ++j) 70 { 71 // Locate this target and insert it into the import table 72 AbstractNodeListPtr newNodes = locateTarget(it->second.get(), j->second); 73 if(!newNodes.isNull() && !newNodes->empty()) 74 mImportTable.insert(mImportTable.begin(), newNodes->begin(), newNodes->end()); 75 } 76 } 77 } 78 } 79 }
其中的loadImportPath()函数(19行)主要负责对脚本中由import语句所导入的材质对象进行解析,其中的import->source用来指向需导入的材质对象所在的脚本文件名,函数展开如下:
1 AbstractNodeListPtr ScriptCompiler::loadImportPath(const Ogre::String &name) 2 { 3 AbstractNodeListPtr retval; 4 ConcreteNodeListPtr nodes; 5 6 if(mListener) 7 nodes = mListener->importFile(this, name); 8 9 if(nodes.isNull() && ResourceGroupManager::getSingletonPtr()) 10 { 11 DataStreamPtr stream = ResourceGroupManager::getSingleton().openResource(name, mGroup); 12 if(!stream.isNull()) 13 { 14 ScriptLexer lexer; 15 ScriptTokenListPtr tokens = lexer.tokenize(stream->getAsString(), name); 16 ScriptParser parser; 17 nodes = parser.parse(tokens); 18 } 19 } 20 21 if(!nodes.isNull()) 22 retval = convertToAST(nodes); 23 24 return retval; 25 }
loadImportPath()函数首先打开材质所在的脚本文件(11行);然后对它进行“词法分析”(14、15行);接下来进行“语义分析”(16、17行);再在此基础上创建与脚本文件对应的AbstractNodeList以描述整个导入脚本文件的结构(21、22行)。整个过程与ScriptCompiler::compile(const String &str, const String &source, const String &group)的前半部分(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/02/2841607.html “脚本及其解析二”的ScriptCompiler::compile()函数3-5行)和ScriptCompiler::compile(const ConcreteNodeListPtr &nodes, const String &group)函数的前半部分是一致的。
回到ScriptCompiler::processImports()函数,在创建了导入脚本文件的AbstractNodeList链表结构后,接下来需要对导入脚本文件中的import和object进行预处理,这是一个递归的过程(22、23行)。当整个导入过程完成后,还要根据导入的情况填写ScriptCompiler的mImportTable链表,以备父调用中的processObject()函数(如果在父调用中存在的话)使用(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/13/2856398.html“脚本及其解析五”中的processObjects()函数第16行)。对导入脚本中,'$'字符所定义和引用的脚本变量,并不立即进行替换。它们将在ScriptCompiler::compile(const ConcreteNodeListPtr &nodes, const String &group)函数中统一处理。
对变量进行替换的过程将在ScriptCompiler::processVariables()函数中进行,函数展开如下:
1 void ScriptCompiler::processVariables(Ogre::AbstractNodeList *nodes) 2 { 3 AbstractNodeList::iterator i = nodes->begin(); 4 while(i != nodes->end()) 5 { 6 AbstractNodeList::iterator cur = i; 7 ++i; 8 9 if((*cur)->type == ANT_OBJECT) 10 { 11 // Only process if this object is not abstract 12 ObjectAbstractNode *obj = (ObjectAbstractNode*)(*cur).get(); 13 if(!obj->abstract) 14 { 15 processVariables(&obj->children); 16 processVariables(&obj->values); 17 } 18 } 19 else if((*cur)->type == ANT_PROPERTY) 20 { 21 PropertyAbstractNode *prop = (PropertyAbstractNode*)(*cur).get(); 22 processVariables(&prop->values); 23 } 24 else if((*cur)->type == ANT_VARIABLE_ACCESS) 25 { 26 VariableAccessAbstractNode *var = (VariableAccessAbstractNode*)(*cur).get(); 27 28 // Look up the enclosing scope 29 ObjectAbstractNode *scope = 0; 30 AbstractNode *temp = var->parent; 31 while(temp) 32 { 33 if(temp->type == ANT_OBJECT) 34 { 35 scope = (ObjectAbstractNode*)temp; 36 break; 37 } 38 temp = temp->parent; 39 } 40 41 // Look up the variable in the environment 42 std::pair<bool,String> varAccess; 43 if(scope) 44 varAccess = scope->getVariable(var->name); 45 if(!scope || !varAccess.first) 46 { 47 map<String,String>::type::iterator k = mEnv.find(var->name); 48 varAccess.first = k != mEnv.end(); 49 if(varAccess.first) 50 varAccess.second = k->second; 51 } 52 53 if(varAccess.first) 54 { 55 // Found the variable, so process it and insert it into the tree 56 ScriptLexer lexer; 57 ScriptTokenListPtr tokens = lexer.tokenize(varAccess.second, var->file); 58 ScriptParser parser; 59 ConcreteNodeListPtr cst = parser.parseChunk(tokens); 60 AbstractNodeListPtr ast = convertToAST(cst); 61 62 // Set up ownership for these nodes 63 for(AbstractNodeList::iterator j = ast->begin(); j != ast->end(); ++j) 64 (*j)->parent = var->parent; 65 66 // Recursively handle variable accesses within the variable expansion 67 processVariables(ast.get()); 68 69 // Insert the nodes in place of the variable 70 nodes->insert(cur, ast->begin(), ast->end()); 71 } 72 else 73 { 74 // Error 75 addError(CE_UNDEFINEDVARIABLE, var->file, var->line); 76 } 77 78 // Remove the variable node 79 nodes->erase(cur); 80 } 81 } 82 }
函数会对AbstractNodeList链表中的各AbstractNode逐一进行访问,如果结点是VariableAccessAbstractNode类型对象(24-26行),那么说明结点表示的是对脚本中定义的变量的引用(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/08/2848831.html “脚本及其解析四”),函数就会根据结点的变量名称找到它对应的实际值(43-50行,44行的getVariable()函数会到相应的ObjectAbstractNode的mEnv容器中去寻找,如果找不到就会到ScriptCompiler的mEnv中去寻找,47-50行。这两者的mEnv中都保存了所有的变量和它们对应的实际值。(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/08/2848831.html “脚本及其解析四”))。变量的实际值一般也对应一个脚本对象,因此函数会解析这个脚本对象所在的文件,经过同样的三个阶段后,创建其所对应的AbstractNodeList(56-60行),并将实际值的解析结果插入到函数传入的nodes链表的当前访问的结点之前(70行),然后将当前结点从nodes中删除(79行)。这样一来,所有对变量对象的引用就被实际的变量值对象替换了。