Ogre源代码浅析——脚本及其解析(八)

    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脚本的解析工作才算基本完成。

    分析概念清晰、设计巧妙的代码是一种乐趣;隐藏在代码背后的思想有时会引发读者不由自主的感慨。好的代码是通过它的正确性、逻辑的严谨和设计的巧妙来展示其中的美感的。分析优质的代码,还会带来许多“福利”——为我们处理类似或相关问题提供最直观的参考。但最好仅限于参考,因为,依照自已的想法来创造,这个过程同样甚至会有更多的乐趣;另外,解决同一问题的优秀设计永远不会是唯一的。

作者:yzwalkman
转载请注明出处。
原文地址:https://www.cnblogs.com/yzwalkman/p/2874832.html