Ogre脚本解析的前两个阶段——“语义分析”的准备工作和“词法分析”是在ScriptCompiler::compile(const String &str, const String &source, const String &group)函数中进行的(参见“http://www.cnblogs.com/yzwalkman/archive/2013/01/02/2841607.html 脚本及其解析(二)”的3-5行);而生成AbstractNodeList并进行相关后期处理的过程,则会在ScriptCompiler::compile(const ConcreteNodeListPtr &nodes, const String &group)函数中完成。函数展开如下:
1 bool ScriptCompiler::compile(const ConcreteNodeListPtr &nodes, const String &group) 2 { 3 // Set up the compilation context 4 mGroup = group; 5 6 // Clear the past errors 7 mErrors.clear(); 8 9 // Clear the environment 10 mEnv.clear(); 11 12 if(mListener) 13 mListener->preConversion(this, nodes); 14 15 // Convert our nodes to an AST 16 AbstractNodeListPtr ast = convertToAST(nodes); 17 // Processes the imports for this script 18 processImports(ast); 19 // Process object inheritance 20 processObjects(ast.get(), ast); 21 // Process variable expansion 22 processVariables(ast.get()); 23 24 // Allows early bail-out through the listener 25 if(mListener && !mListener->postConversion(this, ast)) 26 return mErrors.empty(); 27 28 // Translate the nodes 29 for(AbstractNodeList::iterator i = ast->begin(); i != ast->end(); ++i) 30 { 31 //logAST(0, *i); 32 if((*i)->type == ANT_OBJECT && reinterpret_cast<ObjectAbstractNode*>((*i).get())->abstract) 33 continue; 34 //LogManager::getSingleton().logMessage(reinterpret_cast<ObjectAbstractNode*>((*i).get())->name); 35 ScriptTranslator *translator = ScriptCompilerManager::getSingleton().getTranslator(*i); 36 if(translator) 37 translator->translate(this, *i); 38 } 39 40 mImports.clear(); 41 mImportRequests.clear(); 42 mImportTable.clear(); 43 44 return mErrors.empty(); 45 }
16-22行代码的作用已在前面的“脚本及其解析”系列中进行了阐述。对脚本对象数据的解读工作则是在35-37行中完成的。compile()函数首先根据AbstractNode的类型来获取相应的ScriptTranslator(35行),获取ScriptTranslator对象的函数调用展开如下:
1 ScriptTranslator *BuiltinScriptTranslatorManager::getTranslator(const AbstractNodePtr &node) 2 { 3 ScriptTranslator *translator = 0; 4 5 if(node->type == ANT_OBJECT) 6 { 7 ObjectAbstractNode *obj = reinterpret_cast<ObjectAbstractNode*>(node.get()); 8 ObjectAbstractNode *parent = obj->parent ? reinterpret_cast<ObjectAbstractNode*>(obj->parent) : 0; 9 if(obj->id == ID_MATERIAL) 10 translator = &mMaterialTranslator; 11 else if(obj->id == ID_TECHNIQUE && parent && parent->id == ID_MATERIAL) 12 translator = &mTechniqueTranslator; 13 else if(obj->id == ID_PASS && parent && parent->id == ID_TECHNIQUE) 14 translator = &mPassTranslator; 15 else if(obj->id == ID_TEXTURE_UNIT && parent && parent->id == ID_PASS) 16 translator = &mTextureUnitTranslator; 17 else if(obj->id == ID_TEXTURE_SOURCE && parent && parent->id == ID_TEXTURE_UNIT) 18 translator = &mTextureSourceTranslator; 19 else if(obj->id == ID_FRAGMENT_PROGRAM || obj->id == ID_VERTEX_PROGRAM || obj->id == ID_GEOMETRY_PROGRAM) 20 translator = &mGpuProgramTranslator; 21 else if(obj->id == ID_SHARED_PARAMS) 22 translator = &mSharedParamsTranslator; 23 else if(obj->id == ID_PARTICLE_SYSTEM) 24 translator = &mParticleSystemTranslator; 25 else if(obj->id == ID_EMITTER) 26 translator = &mParticleEmitterTranslator; 27 else if(obj->id == ID_AFFECTOR) 28 translator = &mParticleAffectorTranslator; 29 else if(obj->id == ID_COMPOSITOR) 30 translator = &mCompositorTranslator; 31 else if(obj->id == ID_TECHNIQUE && parent && parent->id == ID_COMPOSITOR) 32 translator = &mCompositionTechniqueTranslator; 33 else if((obj->id == ID_TARGET || obj->id == ID_TARGET_OUTPUT) && parent && parent->id == ID_TECHNIQUE) 34 translator = &mCompositionTargetPassTranslator; 35 else if(obj->id == ID_PASS && parent && (parent->id == ID_TARGET || parent->id == ID_TARGET_OUTPUT)) 36 translator = &mCompositionPassTranslator; 37 } 38 39 return translator; 40 }
可以看到,函数只对ANT_OBJECT类型的AbstractNode(对应着脚本对象)进行下一步的数据处理(5行)。每一个ObjectAbstractNode有一个自己的id值,此id与之前定义的枚举变量(参见“脚本及其解析(七)”)所对应的脚本对象相关联。BuiltinScriptTranslatorManager类对象将根据传入结点的此id值来查找相应的ScriptTranslator类型对象,并返回对象指针。
然后,ScriptCompiler::compile()函数正式开始对脚本对象数据的解析(第一段代码36、37行)。以材质脚本的解析为例,translate()函数将展开如下:
1 void MaterialTranslator::translate(ScriptCompiler *compiler, const AbstractNodePtr &node) 2 { 3 ObjectAbstractNode *obj = reinterpret_cast<ObjectAbstractNode*>(node.get()); 4 if(obj->name.empty()) 5 compiler->addError(ScriptCompiler::CE_OBJECTNAMEEXPECTED, obj->file, obj->line); 6 7 // Create a material with the given name 8 CreateMaterialScriptCompilerEvent evt(node->file, obj->name, compiler->getResourceGroup()); 9 bool processed = compiler->_fireEvent(&evt, (void*)&mMaterial); 10 11 if(!processed) 12 { 13 mMaterial = reinterpret_cast<Ogre::Material*>(MaterialManager::getSingleton().create(obj->name, compiler->getResourceGroup()).get()); 14 } 15 else 16 { 17 if(!mMaterial) 18 compiler->addError(ScriptCompiler::CE_OBJECTALLOCATIONERROR, obj->file, obj->line, 19 "failed to find or create material \"" + obj->name + "\""); 20 } 21 22 mMaterial->removeAllTechniques(); 23 obj->context = Any(mMaterial); 24 mMaterial->_notifyOrigin(obj->file); 25 26 for(AbstractNodeList::iterator i = obj->children.begin(); i != obj->children.end(); ++i) 27 { 28 if((*i)->type == ANT_PROPERTY) 29 { 30 PropertyAbstractNode *prop = reinterpret_cast<PropertyAbstractNode*>((*i).get()); 31 switch(prop->id) 32 { 33 case ID_LOD_VALUES: 34 { 35 Material::LodValueList lods; 36 for(AbstractNodeList::iterator j = prop->values.begin(); j != prop->values.end(); ++j) 37 { 38 Real v = 0; 39 if(getReal(*j, &v)) 40 lods.push_back(v); 41 else 42 compiler->addError(ScriptCompiler::CE_NUMBEREXPECTED, prop->file, prop->line, 43 "lod_values expects only numbers as arguments"); 44 } 45 mMaterial->setLodLevels(lods); 46 } 47 break; 48 case ID_LOD_DISTANCES: 49 { 50 // Set strategy to distance strategy 51 LodStrategy *strategy = DistanceLodStrategy::getSingletonPtr(); 52 mMaterial->setLodStrategy(strategy); 53 54 // Read in lod distances 55 Material::LodValueList lods; 56 for(AbstractNodeList::iterator j = prop->values.begin(); j != prop->values.end(); ++j) 57 { 58 Real v = 0; 59 if(getReal(*j, &v)) 60 lods.push_back(v); 61 else 62 compiler->addError(ScriptCompiler::CE_NUMBEREXPECTED, prop->file, prop->line, 63 "lod_values expects only numbers as arguments"); 64 } 65 mMaterial->setLodLevels(lods); 66 } 67 break; 68 case ID_LOD_STRATEGY: 69 if (prop->values.empty()) 70 { 71 compiler->addError(ScriptCompiler::CE_STRINGEXPECTED, prop->file, prop->line); 72 } 73 else if (prop->values.size() > 1) 74 { 75 compiler->addError(ScriptCompiler::CE_FEWERPARAMETERSEXPECTED, prop->file, prop->line, 76 "lod_strategy only supports 1 argument"); 77 } 78 else 79 { 80 String strategyName; 81 bool result = getString(prop->values.front(), &strategyName); 82 if (result) 83 { 84 LodStrategy *strategy = LodStrategyManager::getSingleton().getStrategy(strategyName); 85 86 result = (strategy != 0); 87 88 if (result) 89 mMaterial->setLodStrategy(strategy); 90 } 91 92 if (!result) 93 { 94 compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line, 95 "lod_strategy argument must be a valid lod strategy"); 96 } 97 } 98 break; 99 case ID_RECEIVE_SHADOWS: 100 if(prop->values.empty()) 101 { 102 compiler->addError(ScriptCompiler::CE_STRINGEXPECTED, prop->file, prop->line); 103 } 104 else if(prop->values.size() > 1) 105 { 106 compiler->addError(ScriptCompiler::CE_FEWERPARAMETERSEXPECTED, prop->file, prop->line, 107 "receive_shadows only supports 1 argument"); 108 } 109 else 110 { 111 bool val = true; 112 if(getBoolean(prop->values.front(), &val)) 113 mMaterial->setReceiveShadows(val); 114 else 115 compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line, 116 "receive_shadows argument must be \"true\", \"false\", \"yes\", \"no\", \"on\", or \"off\""); 117 } 118 break; 119 case ID_TRANSPARENCY_CASTS_SHADOWS: 120 if(prop->values.empty()) 121 { 122 compiler->addError(ScriptCompiler::CE_STRINGEXPECTED, prop->file, prop->line); 123 } 124 else if(prop->values.size() > 1) 125 { 126 compiler->addError(ScriptCompiler::CE_FEWERPARAMETERSEXPECTED, prop->file, prop->line, 127 "transparency_casts_shadows only supports 1 argument"); 128 } 129 else 130 { 131 bool val = true; 132 if(getBoolean(prop->values.front(), &val)) 133 mMaterial->setTransparencyCastsShadows(val); 134 else 135 compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line, 136 "transparency_casts_shadows argument must be \"true\", \"false\", \"yes\", \"no\", \"on\", or \"off\""); 137 } 138 break; 139 case ID_SET_TEXTURE_ALIAS: 140 if(prop->values.empty()) 141 { 142 compiler->addError(ScriptCompiler::CE_STRINGEXPECTED, prop->file, prop->line); 143 } 144 else if(prop->values.size() > 3) 145 { 146 compiler->addError(ScriptCompiler::CE_FEWERPARAMETERSEXPECTED, prop->file, prop->line, 147 "set_texture_alias only supports 2 arguments"); 148 } 149 else 150 { 151 AbstractNodeList::const_iterator i0 = getNodeAt(prop->values, 0), i1 = getNodeAt(prop->values, 1); 152 String name, value; 153 if(getString(*i0, &name) && getString(*i1, &value)) 154 mTextureAliases.insert(std::make_pair(name, value)); 155 else 156 compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line, 157 "set_texture_alias must have 2 string argument"); 158 } 159 break; 160 default: 161 compiler->addError(ScriptCompiler::CE_UNEXPECTEDTOKEN, prop->file, prop->line, 162 "token \"" + prop->name + "\" is not recognized"); 163 } 164 } 165 else if((*i)->type == ANT_OBJECT) 166 { 167 processNode(compiler, *i); 168 } 169 } 170 171 // Apply the texture aliases 172 if(compiler->getListener()) 173 { 174 PreApplyTextureAliasesScriptCompilerEvent locEvt(mMaterial, &mTextureAliases); 175 compiler->_fireEvent(&locEvt, 0); 176 } 177 mMaterial->applyTextureAliases(mTextureAliases); 178 mTextureAliases.clear(); 179 }
在translate函数中,会对传入的AbstractNode的子链表中的结点进行逐一解析,如果子链表中的当前AbstractNode是一个属性类型的结点的话,函数就会根据它的id值,进行相应的数据处理(28-164行);如果此AbstractNode自身也是一个脚本对象的话就会调用processNode()函数进行处理(165-168行)。processNode函数的核心部分也是一个translate调用,也就是说,167行实际上实现了一个translate函数的递归调用。processNode函数展开如下:
1 void ScriptTranslator::processNode(ScriptCompiler *compiler, const AbstractNodePtr &node) 2 { 3 if(node->type != ANT_OBJECT) 4 return; 5 6 // Abstract objects are completely skipped 7 if((reinterpret_cast<ObjectAbstractNode*>(node.get()))->abstract) 8 return; 9 10 // Retrieve the translator to use 11 ScriptTranslator *translator = 12 ScriptCompilerManager::getSingleton().getTranslator(node); 13 14 if(translator) 15 translator->translate(compiler, node); 16 else 17 compiler->addError(ScriptCompiler::CE_UNEXPECTEDTOKEN, node->file, node->line, 18 "token \"" + reinterpret_cast<ObjectAbstractNode*>(node.get())->cls + "\" is not recognized"); 19 }
至此,Ogre脚本的解析工作才算基本完成。
分析概念清晰、设计巧妙的代码是一种乐趣;隐藏在代码背后的思想有时会引发读者不由自主的感慨。好的代码是通过它的正确性、逻辑的严谨和设计的巧妙来展示其中的美感的。分析优质的代码,还会带来许多“福利”——为我们处理类似或相关问题提供最直观的参考。但最好仅限于参考,因为,依照自已的想法来创造,这个过程同样甚至会有更多的乐趣;另外,解决同一问题的优秀设计永远不会是唯一的。