生成了与脚本相应的ConcreteNodeList列表,并不意味着“语义分析”的结束,恰恰相反,“语义分析”其实刚刚开了个头。分析后面的代码,我们会知道,ConcreteNodeList列表的生成不过是为“语义分析”做一个准备罢了。“语义分析”的核心功能是在ScriptCompiler::convertToAST()函数中完成的。为了正确理解它,需要先来明确以下几个问题:
1. 脚本文件的格式
a. 脚本中的对象(object)和对象的命名格式。
Ogre自定义了一些关键字用来对外部数据进行定义,比如材质脚本中的“material”、“technique”、“pass”等。脚本中要定义一个对象一般遵从以下格式:
关键字+对象名+ “{” + 对象数据 + “}”
以上格式中,对象名可以有也可以没有,但定义在脚本中的“根对象”(如material对象)一般都需要有相应的名称。Ogre提供的SDK中,脚本对象的名称有两种表示方式,一种是“相对路径”+对象名,其路径中包含反斜杠表示层级关系,“根目录”一般用对象所在脚本的脚本文件名来表示(Ogre将这种带反斜杠的名称只作为字符串来看待而并无其他特殊之处,之所以要这样来命名对象,主要是为了保证对象名称的全局惟一性。以材质对象material为例,某材质对象不论原先被定义在哪个材质文件里,一旦其被加载到内存,则只能通过材质名称来相互区分。由于外部文件在保存时,文件名都是惟一的,因此在某个脚本文件中命名一个材质对象时,加上其所在的脚本文件名为前缀,就可以无需担心此材质对象与其他脚本文件中的材质对象重名);另一种,如果想要在对象名中包含空格符,则必须在对象名两端加双引号。
b. 派生关系符(:)——冒号(colon)用来表示脚本中两个对象之间的派生和继承关系。如下例:
1 material RTSS/NormalMapping_MultiPass 2 { 3 ... 4 } 5 6 material RTSS/Athene/NormalMapping_SinglePass : RTSS/NormalMapping_SinglePass 7 { 8 ... 9 }
其中的材质“RTSS/Athene/NormalMapping_SinglePass”派生自材质“RTSS/NormalMapping_SinglePass”,因此前者自动拥有后者的所有属性,且在此基础上还可以定义自已的成员。
c. 变量定义符($)和变量赋值符(set)——前者表示引入一个变量对象,后者用以表示对此变量对象赋以指定值。如下例:
1 abstract material PSSM/base_receiver 2 { 3 technique 4 { 5 pass 6 { 7 ambient 1 1 1 1 8 diffuse 1 1 1 1 9 ... 10 texture_unit diffuse_tex 11 { 12 texture $diffuse 13 } 14 } 15 } 16 } 17 material PSSM/Plane : PSSM/base_receiver 18 { 19 set $diffuse BumpyMetal.jpg 20 }
在父对象PSSM/base_receiver中,用“$”符定义了一个变量diffuse(12行),在其派生对象中用set对此变量赋以实际值BumpyMetal.jpg(19行)。(注意“变量赋值”与“别名设置”是有区别的,仍以材质为例,在很多情况下我们会用set_texture_alias关键字来对别名所指向的纹理对象的相关数据进行重新设定,比如:set_texture_alias DiffuseMap r2skin.jpg。但这类关键字的解析并不在“语义分析”阶段进行,所以暂不在此处讨论)
d. 脚本引用标识符(import)——在一个脚本中要引用另一个脚本中定义的对象时,需要用import关键字导入相关脚本文件。如下例:
1 (表本文件的文件名myMaterial.material) 2 3 import PSSM/base_receiver from "pssm.material" 4 5 material myMt : PSSM/base_receiver 6 { 7 ... 8 }
以上所说的脚本格式,不仅仅只针对材质脚本。之前提到的“.program”、“.particle”、“.compositor”脚本在编辑时都应遵循。
2. 解析过程各个阶段的对应关系
“语义分析”的最终结果是生成AbstractNodeList,可以看一下相关的定义:
1 enum AbstractNodeType 2 { 3 ANT_UNKNOWN, 4 ANT_ATOM, 5 ANT_OBJECT, 6 ANT_PROPERTY, 7 ANT_IMPORT, 8 ANT_VARIABLE_SET, 9 ANT_VARIABLE_ACCESS 10 }; 11 class AbstractNode; 12 typedef SharedPtr<AbstractNode> AbstractNodePtr; 13 typedef list<AbstractNodePtr>::type AbstractNodeList; 14 typedef SharedPtr<AbstractNodeList> AbstractNodeListPtr;
之前列举了ScriptToken与ConcreteNode间的类型,现在将ScriptToken、ConcreteNode与AbstractNode三者的类型定义列出,比较三者的相关项:
1 enum{ 2 TID_LBRACKET = 0, // { 3 TID_RBRACKET, // } 4 TID_COLON, // : 5 TID_VARIABLE, // $... 6 TID_WORD, // * 7 TID_QUOTE, // "*" 8 TID_NEWLINE, // \n 9 TID_UNKNOWN, 10 TID_END 11 }; 12 13 enum ConcreteNodeType 14 { 15 CNT_VARIABLE, 16 CNT_VARIABLE_ASSIGN, 17 CNT_WORD, 18 CNT_IMPORT, 19 CNT_QUOTE, 20 CNT_LBRACE, 21 CNT_RBRACE, 22 CNT_COLON 23 }; 24 25 enum AbstractNodeType 26 { 27 ANT_UNKNOWN, 28 ANT_ATOM, 29 ANT_OBJECT, 30 ANT_PROPERTY, 31 ANT_IMPORT, 32 ANT_VARIABLE_SET, 33 ANT_VARIABLE_ACCESS 34 };
可以看到,在ScriptNode阶段,左大括号(TID_LBRACE)与右大括号(TID_RBRACE)到了ConcreteNode阶段变为了CNT_LBRACE和CNT_RBRACE所标识的对象,而在AbstractNode阶段中则不再有相应的对象出现,这是因此为左右大括号,是为了定义数据单元的数据(见http://www.cnblogs.com/yzwalkman/archive/2013/01/04/2842904.html的相关讨论),在第三个阶段,相关的数据单元已由ANT_OBJECT所标识的对象来表示,所以大括号就无需单独表示出来。同样的道理,TID_COLON与CNT_COLON所标识的两个不同阶段的对象,相互对应。但到了第三阶段,代表继承含义的冒号(colon)已经随着派生对象(仍由ANT_OBJECT)的生成,而无需再存在,故而在AbstractNode中不再有相应的结点类型与之对应。另外,在第二阶段和第三阶段出现的由CNT_IMPORT和ANT_IMPORT标识出的对象,有对应关系,但在第一阶段中却并无相关对象与之对应。这是因为,import作为脚本的一个词素,在第一阶段中是被解释成了一个词(word),由TID_WORD所对应的结点与对应。关于import的处理过程,是很有意思的,后面会加以讨论。
AbstractNode是一个抽象类,以此为基类,Ogre定义了AtomAbstractNode、ObjectAbstractNode、PropertyAbstractNode、ImportAbstractNode、VariableAccessAbstractNode五个派生类,其type值分别为:ANT_ATOM、ANT_OBJ、ANT_PROPERTY、ANT_IMPORT、ANT_VARIABLE_ACCESS。
Ogre根据第二阶段生成的ConcreteNodeList中的ConcreteNode对象,创建相应的AbstractNode的派生类对象,并将其保存在AbstractNodeList中。这一过程是由ScriptCompiler::convertToAST()函数启动的。其代码如下:
1 AbstractNodeListPtr ScriptCompiler::convertToAST(const Ogre::ConcreteNodeListPtr &nodes) 2 { 3 AbstractTreeBuilder builder(this); 4 AbstractTreeBuilder::visit(&builder, *nodes.get()); 5 return builder.getResult(); 6 }
其中的AbstractTreeBuilder类是ScriptCompiler的内嵌类,以下是它的定义:
1 class AbstractTreeBuilder 2 { 3 private: 4 AbstractNodeListPtr mNodes; 5 AbstractNode *mCurrent; 6 ScriptCompiler *mCompiler; 7 public: 8 AbstractTreeBuilder(ScriptCompiler *compiler); 9 const AbstractNodeListPtr &getResult() const; 10 void visit(ConcreteNode *node); 11 static void visit(AbstractTreeBuilder *visitor, const ConcreteNodeList &nodes); 12 };
它的主要作用是生成相应的AbstractNode实体类对象,并将相应指针保存在内部的AbstractNodeList容器中。AbstractTreeBuilder又被定义为其宿主类ScriptCompiler的firend以便于对相关数据的访问。AbstractTreeBuilder有两个visit()函数,带两个参数的静态函数static void visit(AbstractTreeBuilder *visitor, const ConcreteNodeList &nodes)先被调用,此调用会引发对void visit(ConcreteNode *node)的调用,其调用关系如下:
1 void ScriptCompiler::AbstractTreeBuilder::visit(AbstractTreeBuilder *visitor, const ConcreteNodeList &nodes) 2 { 3 for(ConcreteNodeList::const_iterator i = nodes.begin(); i != nodes.end(); ++i) 4 visitor->visit((*i).get()); 5 }
展开ScriptCompiler::AbstractTreeBuilder::visit(ConcreteNode *node)可以看到根据ConcreteNode对象来创建相应AbstractNode派生类对象的实际过程:
1 void ScriptCompiler::AbstractTreeBuilder::visit(ConcreteNode *node) 2 { 3 AbstractNodePtr asn; 4 5 // Import = "import" >> 2 children, mCurrent == null 6 if(node->type == CNT_IMPORT && mCurrent == 0) 7 { 8 if(node->children.size() > 2) 9 { 10 mCompiler->addError(CE_FEWERPARAMETERSEXPECTED, node->file, node->line); 11 return; 12 } 13 if(node->children.size() < 2) 14 { 15 mCompiler->addError(CE_STRINGEXPECTED, node->file, node->line); 16 return; 17 } 18 19 ImportAbstractNode *impl = OGRE_NEW ImportAbstractNode(); 20 impl->line = node->line; 21 impl->file = node->file; 22 23 ConcreteNodeList::iterator iter = node->children.begin(); 24 impl->target = (*iter)->token; 25 26 iter++; 27 impl->source = (*iter)->token; 28 29 asn = AbstractNodePtr(impl); 30 } 31 // variable set = "set" >> 2 children, children[0] == variable 32 else if(node->type == CNT_VARIABLE_ASSIGN) 33 { 34 if(node->children.size() > 2) 35 { 36 mCompiler->addError(CE_FEWERPARAMETERSEXPECTED, node->file, node->line); 37 return; 38 } 39 if(node->children.size() < 2) 40 { 41 mCompiler->addError(CE_STRINGEXPECTED, node->file, node->line); 42 return; 43 } 44 if(node->children.front()->type != CNT_VARIABLE) 45 { 46 mCompiler->addError(CE_VARIABLEEXPECTED, node->children.front()->file, node->children.front()->line); 47 return; 48 } 49 50 ConcreteNodeList::iterator i = node->children.begin(); 51 String name = (*i)->token; 52 53 ++i; 54 String value = (*i)->token; 55 56 if(mCurrent && mCurrent->type == ANT_OBJECT) 57 { 58 ObjectAbstractNode *ptr = (ObjectAbstractNode*)mCurrent; 59 ptr->setVariable(name, value); 60 } 61 else 62 { 63 mCompiler->mEnv.insert(std::make_pair(name, value)); 64 } 65 } 66 // variable = $*, no children 67 else if(node->type == CNT_VARIABLE) 68 { 69 if(!node->children.empty()) 70 { 71 mCompiler->addError(CE_FEWERPARAMETERSEXPECTED, node->file, node->line); 72 return; 73 } 74 75 VariableAccessAbstractNode *impl = OGRE_NEW VariableAccessAbstractNode(mCurrent); 76 impl->line = node->line; 77 impl->file = node->file; 78 impl->name = node->token; 79 80 asn = AbstractNodePtr(impl); 81 } 82 // Handle properties and objects here 83 else if(!node->children.empty()) 84 { 85 // Grab the last two nodes 86 ConcreteNodePtr temp1, temp2; 87 ConcreteNodeList::reverse_iterator riter = node->children.rbegin(); 88 if(riter != node->children.rend()) 89 { 90 temp1 = *riter; 91 riter++; 92 } 93 if(riter != node->children.rend()) 94 temp2 = *riter; 95 96 // object = last 2 children == { and } 97 if(!temp1.isNull() && !temp2.isNull() && 98 temp1->type == CNT_RBRACE && temp2->type == CNT_LBRACE) 99 { 100 if(node->children.size() < 2) 101 { 102 mCompiler->addError(CE_STRINGEXPECTED, node->file, node->line); 103 return; 104 } 105 106 ObjectAbstractNode *impl = OGRE_NEW ObjectAbstractNode(mCurrent); 107 impl->line = node->line; 108 impl->file = node->file; 109 impl->abstract = false; 110 111 // Create a temporary detail list 112 list<ConcreteNode*>::type temp; 113 if(node->token == "abstract") 114 { 115 impl->abstract = true; 116 for(ConcreteNodeList::const_iterator i = node->children.begin(); i != node->children.end(); ++i) 117 temp.push_back((*i).get()); 118 } 119 else 120 { 121 temp.push_back(node); 122 for(ConcreteNodeList::const_iterator i = node->children.begin(); i != node->children.end(); ++i) 123 temp.push_back((*i).get()); 124 } 125 126 // Get the type of object 127 list<ConcreteNode*>::type::const_iterator iter = temp.begin(); 128 impl->cls = (*iter)->token; 129 ++iter; 130 131 // Get the name 132 // Unless the type is in the exclusion list 133 if(iter != temp.end() && ((*iter)->type == CNT_WORD || (*iter)->type == CNT_QUOTE) && 134 !mCompiler->isNameExcluded(impl->cls, mCurrent)) 135 { 136 impl->name = (*iter)->token; 137 ++iter; 138 } 139 140 // Everything up until the colon is a "value" of this object 141 while(iter != temp.end() && (*iter)->type != CNT_COLON && (*iter)->type != CNT_LBRACE) 142 { 143 if((*iter)->type == CNT_VARIABLE) 144 { 145 VariableAccessAbstractNode *var = OGRE_NEW VariableAccessAbstractNode(impl); 146 var->file = (*iter)->file; 147 var->line = (*iter)->line; 148 var->type = ANT_VARIABLE_ACCESS; 149 var->name = (*iter)->token; 150 impl->values.push_back(AbstractNodePtr(var)); 151 } 152 else 153 { 154 AtomAbstractNode *atom = OGRE_NEW AtomAbstractNode(impl); 155 atom->file = (*iter)->file; 156 atom->line = (*iter)->line; 157 atom->type = ANT_ATOM; 158 atom->value = (*iter)->token; 159 impl->values.push_back(AbstractNodePtr(atom)); 160 } 161 ++iter; 162 } 163 164 // Find the bases 165 if(iter != temp.end() && (*iter)->type == CNT_COLON) 166 { 167 // Children of the ':' are bases 168 for(ConcreteNodeList::iterator j = (*iter)->children.begin(); j != (*iter)->children.end(); ++j) 169 impl->bases.push_back((*j)->token); 170 ++iter; 171 } 172 173 // Finally try to map the cls to an id 174 ScriptCompiler::IdMap::const_iterator iter2 = mCompiler->mIds.find(impl->cls); 175 if(iter2 != mCompiler->mIds.end()) 176 { 177 impl->id = iter2->second; 178 } 179 else 180 { 181 mCompiler->addError(CE_UNEXPECTEDTOKEN, impl->file, impl->line, "token class, " + impl->cls + ", unrecognized."); 182 } 183 184 asn = AbstractNodePtr(impl); 185 mCurrent = impl; 186 187 // Visit the children of the { 188 AbstractTreeBuilder::visit(this, temp2->children); 189 190 // Go back up the stack 191 mCurrent = impl->parent; 192 } 193 // Otherwise, it is a property 194 else 195 { 196 PropertyAbstractNode *impl = OGRE_NEW PropertyAbstractNode(mCurrent); 197 impl->line = node->line; 198 impl->file = node->file; 199 impl->name = node->token; 200 201 ScriptCompiler::IdMap::const_iterator iter2 = mCompiler->mIds.find(impl->name); 202 if(iter2 != mCompiler->mIds.end()) 203 impl->id = iter2->second; 204 205 asn = AbstractNodePtr(impl); 206 mCurrent = impl; 207 208 // Visit the children of the { 209 AbstractTreeBuilder::visit(this, node->children); 210 211 // Go back up the stack 212 mCurrent = impl->parent; 213 } 214 } 215 // Otherwise, it is a standard atom 216 else 217 { 218 AtomAbstractNode *impl = OGRE_NEW AtomAbstractNode(mCurrent); 219 impl->line = node->line; 220 impl->file = node->file; 221 impl->value = node->token; 222 223 ScriptCompiler::IdMap::const_iterator iter2 = mCompiler->mIds.find(impl->value); 224 if(iter2 != mCompiler->mIds.end()) 225 impl->id = iter2->second; 226 227 asn = AbstractNodePtr(impl); 228 } 229 230 // Here, we must insert the node into the tree 231 if(!asn.isNull()) 232 { 233 if(mCurrent) 234 { 235 if(mCurrent->type == ANT_PROPERTY) 236 { 237 PropertyAbstractNode *impl = reinterpret_cast<PropertyAbstractNode*>(mCurrent); 238 impl->values.push_back(asn); 239 } 240 else 241 { 242 ObjectAbstractNode *impl = reinterpret_cast<ObjectAbstractNode*>(mCurrent); 243 impl->children.push_back(asn); 244 } 245 } 246 else 247 { 248 mNodes->push_back(asn); 249 } 250 } 251 }
代码中6-30行主要任务是根据ConcreteNodeList中的CNT_IMPORT类型的ConcreteNode来创建相应的ImportAbstractNode。如果数据正确的话,CNT_IMPORT类型的ConcreteNode会有且只有两个子结点,不满足此条件的结点将被视为数据错误,并将中止解析工作(8-17行)。之所以在正确情况下,CNT_IMPORT类型结点有且只有两个子结点,是由脚本格式和第二阶段解析方式决定的。以本文第三段代码为例,第3行经第一阶段解析后,将会被分解为,“import”、“PSSM/base_receiver”、“from”、“pssm.material”四个词素。这四个词素作为输入将在ScriptParser::parse()函数中被解析(参见:http://www.cnblogs.com/yzwalkman/archive/2013/01/04/2842904.html 的“脚本解析(三)”)。在“脚本解析(三)”中所列的ScriptParse::parse()函数里,当i所指的token为import时(20行),parse()函数会根据第一个token(此token所对应的词素应该是脚本中的“import”)来创建ConcreteNode对象(24-28行);接下来i后移一个位置,指向下一个token(31行),在本例中此token应该对应“PSSM/base_receiver”词素,并据此创建第二个ConcreteNode对象(37-45行),但此对象将被作为第一个创建的ConcreteNode对象的第一个子结点,放入其children链表中(46行);然后 i 又后移一个位置(49行),此时 i 指向的token所对应的词素是“from”,但此时不作任何处理;紧接着i再后移一个位置(50行),此时i所指向的token对应的词素是“pssm.material”,函数会根据此token生成新的ConcreteNode对象,并将此对象作为第一个创建的ConcreteNode对象的第二个子结点,放入其children链表中(56-65行)。根据以上分析可知,在脚本中的import行,经过第二阶段ScriptParse::parse()函数的解析后,将会生成三个ConcreteNode类型对象,其中“import”是根结点,而材质名(如本例中的PSSM/base_receiver)所对应的ConcreteNode对象将作为其第一个子结点(此子结点又被称为target),材质所在脚本文件名(如本例中的pssm.material)所对应的ConcreteNode对象则会被作为其第二个子结点(此子结点又被称为source)。原“from”词素所对应的token,将会被跳过而不作处理,因此在本函数ScriptCompiler::AbstractTreeBuilder::visit()中,作为输入的CNT_IMPORT结点如果所含数据正确,将有且只有两个子结点。
接下来,visit()函数会根据“import”的ConcreteNode创建一个ImportAbstractNode类型对象(19-21行),然后将“import”的ConcreteNode所对应的第一个子结点的值(在本例中对应着词素“PSSM/base_receiver”)赋给ImportAbstractNode对象的target成员变量(23、24行);并将其第二个子结点的值(在本例中对应着词素“pssm.material”)赋给ImportAbstractNode对象的source成员变量(26、27行)。也就是说,在第二阶段生成的三个ConcreteNode对象中的数据,至此就汇集到同一个ImportAbstractNode对象中了。
Ogre脚本中作为变量出现的字符串,都以"$"为首字符的“变量字符串”。“变量字符串”在两种情况下出现,一是对此字符串所表示的变量进行引用时(如本文第二段代码12行);另一种是对其进行赋值时(如本文第二段代码19行)。第一阶段的解析,由于其目的是为了进行“词素分析”,产生独立的字符串单元。所以对以上所说的两种情况下的“变量字符串”的处理方式和处理结果是相同的,在之前列出的ScriptLexer::setToken()函数(参见“脚本及其解析(二)”)中,如果函数发现待解析的字符串的首字符为'$',那么两种情况下都会将创建的ConcreteNode对象类型设为TID_VARIABLE(30-31行)。到了第二阶段,对这两种情况的处理就有所不同了。在之前列出的ScriptParser::parse()函数(参见“脚本及其解析(三)”)中,对于以引用方式出现的“变量字符串”,程序将直接创建ConcreteNode对象,并将其类型设为CNT_VARIABLE(337-357行);如果“变量字符串”出现在“赋值语句”中,那么它将被作为此“赋值语句”的一部分进行解析(83-139行)。以本文第二段代码中的"set $diffuse BumpyMetal.jpg" (19行)为例,经parse()函数的解析,此语句将被分解成三个ConcreteNode对象。首先,将根据词素“set”(此词素为脚本预留的关键字)生成第一个ConcreteNode对象作为根结点,并将其变量类型设为CNT_VARIABLE_ASSIGN(85-89行);然后,变量指针i后移一个位置(92行)指向下一个词素——“$diffuse”——并创建第二个ConcreteNode对象,其类型将被设为CNT_VARIABLE,对象指针将被作为第一个子结点保存在set所对应的根结点的children链表中(98-104行);接下来,变量指针i再后移一个位置(107行)指向下一个词素——“BumpyMetal.jpg”——并创建第三个ConcreteNode对象(113-122行),对象指针将被作为第二个子结点保存在set所对应的根结点的children链表中。
到了第三阶段的visit()函数(见上),以被引用方式出现的ConcreteNode对象(CNT_VARIABLE类型),会在67-81行中被处理,visit()函数将根据此ConcreteNode对象生成VariableAccessAbstractNode对象。而对于CN_VARIABLE_ASSIGN所表示的ConcreteNode对象,visit()函数首先要保证其有且只有两个子结点(34-43行),然后取出这两个子结点中的值 ——其中一个作为变量名(50、51行),另一个作为要赋给此变量的值(53、54行)——保存在两个临时变量里;如果当前正在处理的是一个脚本对象(57-60行),那么说明这个set语句,位于这个脚本对象内部,所以就要把这个两个提取出来的值传给当前处理的脚本对象(59行),保存在它的mEnv容器中,如果当前处理的不是一个脚本对象(62-64行),那么说明这个set语句,不从属于此对象,所提取出的两个值需要暂时保存在ScriptCompiler的mEnv容器中,为后续的处理作准备。
对于脚本中的对象(object)的处理,是在visit()函数的84-192行中进行的。通过前面的讨论可以知道一个脚本中的对象经第二阶段的处理后,其数据信息,一般情况下(带有派生关系的情况不包含在内)会由一个根结点(保存相应关键字,如material、technical、pass等)和此根结点的三个子结点(第一个是脚本对象名称,第二个是左大括号,第三个是右大括号)来构建(参见“脚本及其解析(三)”)。
根据这一结构,visit()函数首先会定义一个反向迭代器,用以指向传入的Concrete类型的根结点的子结点链表(87行),由于是反向迭代,所以取到的第一个子结点对应右大括号,将其保存到变量temp1中(90行),接下来取到的第二个子结点对应左大括号,将其保存到变量temp2中(94行)。112-124行的作用是先把根结点(不是抽象结点时)作为第一个结点保存到临时变量列表temp中(121),然后把根结点的子结点(多数情况下是三个)逐一取出,按原来的顺序也保存到temp中(122、123行),以便于随后的处理。由于当前处理的结点(mCurrent)为脚本对象(经过前面各条件判断的筛选),所以visit()会先根据mCurrent直接创建一个ObjectAbstractNode类对象(106-109行),然后从temp列表中,按正向顺序(声明的是正向的迭代器——127行)逐一读取其中的ConcreteNode对象并进行处理。从上面的讨论可以知道,其所读取的第一个结点(127行)一定对应着某个关键字,它标识出此脚本对象的类型,所以要把此结点的信息保存到ObjectAbstractNode类对象的cls成员变量中(128行);其所读取的第二个结点,一般是脚本对象的名称,所以要把其中的信息保存到ObjectAbstractNode类对象的name成员变量中;如果读取到的第三个结点不是预期的左大括号,而是冒号的话,说明此脚本对象一定是派生自另一脚本对象,那么函数会逐一访问冒号所对应结点的所有子结点(冒号结点的子结点保存的其实是当前脚本对象的基类脚本对象的名称,参见“脚本及其解析(三)”ScriptParser::parse()函数的239-249行),并将子结点中的数据全部保存到ObjectAbstractNode类对象base容器中(165-171行)。base容器是ObjectAbstractNode的一个以String为成员类型vector,它用来保存当前脚本对象的基类对象名。以上就是脚本对象的主要数据的解析过程。
从“脚本及其解析(三)”可以知道,每个脚本对象的内部数据,都保存在一个以ConcreteNode对象为根结点的树形结构中,这个ConcreteNode对象对应着此脚本对象的左大括号。同时,每个脚本对象的内部数据,其格式或者说组织方式都与脚本对象本身是一致的。因此,可以用递归的方式对脚本对象的内部数据进行解析。本例中,temp2中保存着脚本对象的左大括号结点,所以第188行就用来驱动对脚本对象内部信息的解析工作(第三阶段)。另外,需要注意的是,visit()函数是ScriptCompiler::AbstractTreeBuilder的成员函数,所以作为第一个参数传入的this指针,指向的是ScriptCompiler的AbstractTreeBuilder对象,而不是ScriptCompiler对象。