zoukankan      html  css  js  c++  java
  • Ogre源代码浅析——脚本及其解析(二)

          对文本文件的解析,有两种方式比较常见。第一种,文件信息有固定的格式但没有太过复杂的结构,比如Ogre中的.cfg格式文件。对这种文件,一般可以逐行读取并直接按行解析。整个过程相对比较简单。第二种,文件本身有比较复杂的结构,而且文件信息的组织要符合一定的语法规范。比如各种计算机语言的源文件以及各种脚本语言(JavaScript、Python、Lua等等)的源文件。对这种文件的解析一般要经历几个较复杂的阶段,并根据最终的解析结果得到相应的指令顺列。Ogre的脚本文件的组织和解析,其方法和复杂呈度实际上介于这两者之间。

          以.material、.program、.particle和.compositor为例,其脚本包含各种待处理的输入信息且有相同的格式规范,这与第一种文件类似;但同时这些输入信息,一般是由较简单的数据单元按照某种方式嵌套构造而成,各单元之间可以形成一种树形结构,这又明显比第一种文件要复杂。另外,以上Ogre脚本与各种脚本语言的源文件(乃至各种计算机语言源文件)的最大不同是,Ogre的脚本没有控制结构,也没有对各种数据类型的定义。实际上,Ogre的各项脚本文件更象是HML或XML等格式化文本文件。研究Ogre对其脚本文件的解析机制,对了解这类文件的解析机制,或定义自已的外部文本格式并编写相应的文件解析程序是有帮助的。

         Ogre的各种脚本都遵循共同的格式或者说共同的数据组织规则。Ogre对脚本的解析方法分为:词法分析,解析或称语义分析和编译三个过程。这与计算机语言源文件的编译过程很象,但研究其源代码后可知道,其中的“词法分析”和“语义分析过程”要比实际的计算机语言源文件编译过程简单很多(但作用有类似之处),而其第三阶段的所谓的compile(位于ScriptCompile::compile()函数中,以下代码的第6行)的作用则与脚本程序语言源文件的最后阶段的完全不同。来看Script::compiler()函数:

    1     bool ScriptCompiler::compile(const String &str, const String &source, const String &group)
    2     {
    3         ScriptLexer lexer;
    4         ScriptParser parser;
    5         ConcreteNodeListPtr nodes = parser.parse(lexer.tokenize(str, source));
    6         return compile(nodes, group);
    7     }

           ScriptLexer是Ogre自定义的词法分析器,ScriptParser则是语法解析器,compile()函数的第一个参数,str指向的是从外部读入的整个脚本文件的文本。待解析的脚本文件首先由词法分析器的tokenize()函数进行相关处理,看一下相应源代码:

      1     ScriptTokenListPtr ScriptLexer::tokenize(const String &str, const String &source)
      2     {
      3         // State enums
      4         enum{ READY = 0, COMMENT, MULTICOMMENT, WORD, QUOTE, VAR, POSSIBLECOMMENT };
      5 
      6         // Set up some constant characters of interest
      7 #if OGRE_WCHAR_T_STRINGS
      8         const wchar_t varopener = L'$', quote = L'\"', slash = L'/', backslash = L'\\', openbrace = L'{', closebrace = L'}', colon = L':', star = L'*', cr = L'\r', lf = L'\n';
      9         wchar_t c = 0, lastc = 0;
     10 #else
     11         const wchar_t varopener = '$', quote = '\"', slash = '/', backslash = '\\', openbrace = '{', closebrace = '}', colon = ':', star = '*', cr = '\r', lf = '\n';
     12         char c = 0, lastc = 0;
     13 #endif
     14 
     15         String lexeme;
     16         uint32 line = 1, state = READY, lastQuote = 0;
     17         ScriptTokenListPtr tokens(OGRE_NEW_T(ScriptTokenList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);
     18 
     19         // Iterate over the input
     20         String::const_iterator i = str.begin(), end = str.end();
     21         while(i != end)
     22         {
     23             lastc = c;
     24             c = *i;
     25 
     26             if(c == quote)
     27                 lastQuote = line;
     28 
     29             switch(state)
     30             {
     31             case READY:
     32                 if(c == slash && lastc == slash)
     33                 {
     34                     // Comment start, clear out the lexeme
     35                     lexeme = "";
     36                     state = COMMENT;
     37                 }
     38                 else if(c == star && lastc == slash)
     39                 {
     40                     lexeme = "";
     41                     state = MULTICOMMENT;
     42                 }
     43                 else if(c == quote)
     44                 {
     45                     // Clear out the lexeme ready to be filled with quotes!
     46                     lexeme = c;
     47                     state = QUOTE;
     48                 }
     49                 else if(c == varopener)
     50                 {
     51                     // Set up to read in a variable
     52                     lexeme = c;
     53                     state = VAR;
     54                 }
     55                 else if(isNewline(c))
     56                 {
     57                     lexeme = c;
     58                     setToken(lexeme, line, source, tokens.get());
     59                 }
     60                 else if(!isWhitespace(c))
     61                 {
     62                     lexeme = c;
     63                     if(c == slash)
     64                         state = POSSIBLECOMMENT;
     65                     else
     66                         state = WORD;
     67                 }
     68                 break;
     69             case COMMENT:
     70                 // This newline happens to be ignored automatically
     71                 if(isNewline(c))
     72                     state = READY;
     73                 break;
     74             case MULTICOMMENT:
     75                 if(c == slash && lastc == star)
     76                     state = READY;
     77                 break;
     78             case POSSIBLECOMMENT:
     79                 if(c == slash && lastc == slash)
     80                 {
     81                     lexeme = "";
     82                     state = COMMENT;
     83                     break;    
     84                 }
     85                 else if(c == star && lastc == slash)
     86                 {
     87                     lexeme = "";
     88                     state = MULTICOMMENT;
     89                     break;
     90                 }
     91                 else
     92                 {
     93                     state = WORD;
     94                 }
     95             case WORD:
     96                 if(isNewline(c))
     97                 {
     98                     setToken(lexeme, line, source, tokens.get());
     99                     lexeme = c;
    100                     setToken(lexeme, line, source, tokens.get());
    101                     state = READY;
    102                 }
    103                 else if(isWhitespace(c))
    104                 {
    105                     setToken(lexeme, line, source, tokens.get());
    106                     state = READY;
    107                 }
    108                 else if(c == openbrace || c == closebrace || c == colon)
    109                 {
    110                     setToken(lexeme, line, source, tokens.get());
    111                     lexeme = c;
    112                     setToken(lexeme, line, source, tokens.get());
    113                     state = READY;
    114                 }
    115                 else
    116                 {
    117                     lexeme += c;
    118                 }
    119                 break;
    120             case QUOTE:
    121                 if(c != backslash)
    122                 {
    123                     // Allow embedded quotes with escaping
    124                     if(c == quote && lastc == backslash)
    125                     {
    126                         lexeme += c;
    127                     }
    128                     else if(c == quote)
    129                     {
    130                         lexeme += c;
    131                         setToken(lexeme, line, source, tokens.get());
    132                         state = READY;
    133                     }
    134                     else
    135                     {
    136                         // Backtrack here and allow a backslash normally within the quote
    137                         if(lastc == backslash)
    138                             lexeme = lexeme + "\\" + c;
    139                         else
    140                             lexeme += c;
    141                     }
    142                 }
    143                 break;
    144             case VAR:
    145                 if(isNewline(c))
    146                 {
    147                     setToken(lexeme, line, source, tokens.get());
    148                     lexeme = c;
    149                     setToken(lexeme, line, source, tokens.get());
    150                     state = READY;
    151                 }
    152                 else if(isWhitespace(c))
    153                 {
    154                     setToken(lexeme, line, source, tokens.get());
    155                     state = READY;
    156                 }
    157                 else if(c == openbrace || c == closebrace || c == colon)
    158                 {
    159                     setToken(lexeme, line, source, tokens.get());
    160                     lexeme = c;
    161                     setToken(lexeme, line, source, tokens.get());
    162                     state = READY;
    163                 }
    164                 else
    165                 {
    166                     lexeme += c;
    167                 }
    168                 break;
    169             }
    170 
    171             // Separate check for newlines just to track line numbers
    172             if(c == cr || (c == lf && lastc != cr))
    173                 line++;
    174             
    175             i++;
    176         }
    177 
    178         // Check for valid exit states
    179         if(state == WORD || state == VAR)
    180         {
    181             if(!lexeme.empty())
    182                 setToken(lexeme, line, source, tokens.get());
    183         }
    184         else
    185         {
    186             if(state == QUOTE)
    187             {
    188                 OGRE_EXCEPT(Exception::ERR_INVALID_STATE, 
    189                     Ogre::String("no matching \" found for \" at line ") + 
    190                         Ogre::StringConverter::toString(lastQuote),
    191                     "ScriptLexer::tokenize");
    192             }
    193         }
    194 
    195         return tokens;
    196     }

          由于返回值是ScriptTokenListPtr,可以先看一下相关定义:

     1     /** This struct represents a token, which is an ID'd lexeme from the
     2         parsing input stream.
     3     */
     4     struct ScriptToken
     5     {
     6         /// This is the lexeme for this token
     7         String lexeme, file;
     8         /// This is the id associated with the lexeme, which comes from a lexeme-token id mapping
     9         uint32 type;
    10         /// This holds the line number of the input stream where the token was found.
    11         uint32 line;
    12     };
    13     typedef SharedPtr<ScriptToken> ScriptTokenPtr;
    14     typedef vector<ScriptTokenPtr>::type ScriptTokenList;
    15     typedef SharedPtr<ScriptTokenList> ScriptTokenListPtr;

          ScriptLexer::tokenize()函数,先定义了一个保存ScriptToken的vector(ScriptTokenList)并得到了它的指针——tokens(17行),然后将词法分析结果以token为单位,逐一保存到tokens中并返回tokens值。整个函数的处理机制是:逐字符分析+状态机。

          变量“i”和“end”分别标识了读入的待解析的脚本文件的开头和结尾(20行),随着解析的进行,变量“i”将逐字符后移(175行)。整个解析过程由状态机的几种状态来表达,它们分别是:准备状态(READY 31-68行)、对注释信息的解析状态(COMMENT, MULTICOMMENT  69-77行)、对单词的解析状态(WORD  95-119)、对双引号中引用信息的解析状态(QUOTE  120-143行), 对变量信息的解析状态(VAR  144-168行)、对可能是注释信息的数据进行解析的状态(POSSIBLECOMMENT 78-94行)。词法分析的主要目的,是将脚本文件中的各个词素(lexeme 比如,一个单词、脚本中大括号的左半边、脚本中大括号的右半边等都被看一个词素)解读出来,并针对每个词素生成一个token对象,将此词素的相关信息保存在token对象中。在每一次循环开始时都要初始化两个变量:“c”和“lastc” (23,24行)。c表示当前正要被处理字符,lastc表示当前字符的前一个字符。之所以要申请这两个变量是因为,Ogre脚本中的“词素(lexeme)”是以空格为分格符的,用这两个变量就可以方便的识别出:当前读取的字符是一个新词素的第一个字符,还是正在解析的词素的最后一个字符,又或者是当前正在解析的词素的多个字符(如果存在的话)中间位置的某个字符。

          生成token对象并保存相应词素信息的过程由ScriptLexer::setToken()函数来实现,来看一下相关代码:

     1     void ScriptLexer::setToken(const Ogre::String &lexeme, Ogre::uint32 line, const String &source, Ogre::ScriptTokenList *tokens)
     2     {
     3 #if OGRE_WCHAR_T_STRINGS
     4         const wchar_t openBracket = L'{', closeBracket = L'}', colon = L':', 
     5             quote = L'\"', var = L'$';
     6 #else
     7         const char openBracket = '{', closeBracket = '}', colon = ':', 
     8             quote = '\"', var = '$';
     9 #endif
    10 
    11         ScriptTokenPtr token(OGRE_NEW_T(ScriptToken, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);
    12         token->lexeme = lexeme;
    13         token->line = line;
    14         token->file = source;
    15         bool ignore = false;
    16 
    17         // Check the user token map first
    18         if(lexeme.size() == 1 && isNewline(lexeme[0]))
    19         {
    20             token->type = TID_NEWLINE;
    21             if(!tokens->empty() && tokens->back()->type == TID_NEWLINE)
    22                 ignore = true;
    23         }
    24         else if(lexeme.size() == 1 && lexeme[0] == openBracket)
    25             token->type = TID_LBRACKET;
    26         else if(lexeme.size() == 1 && lexeme[0] == closeBracket)
    27             token->type = TID_RBRACKET;
    28         else if(lexeme.size() == 1 && lexeme[0] == colon)
    29             token->type = TID_COLON;
    30         else if(lexeme[0] == var)
    31             token->type = TID_VARIABLE;
    32         else
    33         {
    34             // This is either a non-zero length phrase or quoted phrase
    35             if(lexeme.size() >= 2 && lexeme[0] == quote && lexeme[lexeme.size() - 1] == quote)
    36             {
    37                 token->type = TID_QUOTE;
    38             }
    39             else
    40             {
    41                 token->type = TID_WORD;
    42             }
    43         }
    44 
    45         if(!ignore)
    46             tokens->push_back(token);
    47     }

           可以看到,本函数生成ScriptToken对象token(11行),赋给token的lexeme成员变量的值(12行),就是ScriptLexer::tokenize()函数分析后得到的词素,它是一个字符串(在tokenize()函数15行定义,并在后续的while()循环中解析得到实际值);赋给token的line成员变量的值(13行),表示此词素所在脚本的行的序数;赋给token的file成员变量的值(14行),表示此词素所在的脚本文件的文件名。token的type表示此词素的属性,其属性定义为:

        enum{
            TID_LBRACKET = 0, // {
            TID_RBRACKET, // }
            TID_COLON, // :
            TID_VARIABLE, // $...
            TID_WORD, // *
            TID_QUOTE, // "*"
            TID_NEWLINE, // \n
            TID_UNKNOWN,
            TID_END
        };

         它是一个定义在“OgreScriptLexer.h”头文件中的枚举类型。定义中各枚举项的含义已在其相应的注释中标明。

    作者:yzwalkman
    转载请注明出处。
  • 相关阅读:
    多个表单如何同时验证
    vue+element 动态表单验证
    ‘Maximum call stack size exceeded’错误的解决方法
    select下拉框option的样式修改
    vue项目打包之后样式错乱问题,如何处理
    11_我拥有了属于自己的公众号了
    10_更改自己的ID
    001_Spring之xml的class的补全(eclipse)
    01_Navicat的快捷键学习
    web开发资源网站汇总
  • 原文地址:https://www.cnblogs.com/yzwalkman/p/2841607.html
Copyright © 2011-2022 走看看