编译系统透视:图解编译原理
上QQ阅读APP看书,第一时间看更新

2.4.3 标识符、数字、字符和字符串的详细分析过程

1.标识符的详细分析过程

先来看lex_identifier函数,即标识符的识别过程,情景如图2-51所示。

图2-51 标识符的状态转换图

代码如下所示:

//代码路径:libcpp/lex.c:
 static cpp_hashnode *
 lex_identifier (cpp_reader *pfile, const uchar *base, bool starts_ucn, struct normalize_state *nst)
 {
   ……
   cur = pfile->buffer->cur;                        // cur指向的是下一个字符
   if (! starts_ucn)
     while (ISIDNUM (*cur))                        // 一直检测,若下一个字符不是字母、数字或下划线,则跳出循环
       {
          hash = HT_HASHSTEP (hash, *cur);
          cur++;                                        // 符合字母、数字或下划线条件,cur就继续指向下一个字符
       }
   pfile->buffer->cur = cur;                        // 跳出循环后,进入终态
   ……
}
// 代码路径:include/safe-ctype.h:
  ……
 _sch_isidnum  = _sch_isidst|_sch_isdigit, /* A-Za-z0-9_ */
 ……
 #define ISIDNUM(c) _sch_test(c, _sch_isidnum)        // 字母、数字或下划线的判断条件展开形式
 ……
}
2.数字的详细分析过程

下面我们来看数字的识别过程。后续的数字识别过程在状态转换图的基础上做了调整,大体分为两步:第一步是,只要识别出是数字,就把后续内容当作字符串存起来,不管是什么;第二步是,根据字符串中的内容,分析它的具体属性,状态转换图的思想主要体现在第二步。

我们先来看第一步,通过调用lex_number函数来实现。代码如下所示:

//代码路径:libcpp/lex.c:
 static void
 lex_number (cpp_reader *pfile, cpp_string *number, struct normalize_state *nst)
 {
     ……
     cur = pfile->buffer->cur;
       /* N.B. ISIDNUM does not include $.  */
       while (ISIDNUM (*cur) || *cur == '.' || VALID_SIGN (*cur, cur[-1]))
                                                                // 只要符合数字规则,就继续循环
       {
         cur++;                                                // cur不断地指向后面的ASCII码,直到遇到空格,跳出循环
         NORMALIZE_STATE_UPDATE_IDNUM (nst);
       }
       pfile->buffer->cur = cur;                                // 跳出循环后,buffer->cur指向空格
     }
   ……
   number->len = cur - base;                                // 计算出数字有多长
   dest = _cpp_unaligned_alloc (pfile, number->len + 1);  
                                                                // 为存储数字开辟空间
   memcpy (dest, base, number->len);                        // 把数字的内容记录下来
   dest[number->len] = '\0';                                // 最后加上“\0”,表明是以字符串的形式记录下
   number->text = dest;
}
// 代码路径:include/safe-ctype.h:
 ……
 _sch_isidnum  = _sch_isidst|_sch_isdigit, /* A-Za-z0-9_ */
 ……
 #define ISIDNUM(c) _sch_test(c, _sch_isidnum)                // 字母、数字或下划线的判断条件展开形式
 ……
}
// 代码路径:libcpp/internal.h:
 ……
 /* Test if a sign is valid within a preprocessing number.  */
 #define VALID_SIGN(c, prevc) \
   (((c) == '+' || (c) == '-') && \
    ((prevc) == 'e' || (prevc) == 'E' \
     || (((prevc) == 'p' || (prevc) == 'P') \
        && CPP_OPTION (pfile, extended_numbers))))
 #define CPP_OPTION(PFILE, OPTION) ((PFILE)->opts.OPTION)        // 科学计数法的识别条件
 ……

我们再来看第二步。前面介绍了词法分析的函数调用顺序。

第二步等cpp_get_token_with_location函数返回后,在c_lex_with_flags函数中进行。代码如下所示:

//代码路径:gcc/c-family/c-lex.c:
 enum cpp_ttype
 c_lex_with_flags (tree *value, location_t *loc, unsigned char *cpp_flags, int lex_flags)
 {
     ……
    tok = cpp_get_token_with_location (parse_in, loc);
    type = tok->type;
 retry_after_at:
   switch (type)
     {
         ……
         unsigned int flags = cpp_classify_number (parse_in, tok, &suffix, *loc);
                                                                // 此函数将确定数据的具体属性
         ……
     }
    ……
}

数字的属性包括三种:类型属性(整型、浮点型等)、进制属性(十进制、八进制、十六进制等)和后缀信息(如短类型、宽类型等辅助类型信息),下面我们来介绍cpp_classify_number函数。情景如图2-52至图2-56所示。

图2-52 十进制数的状态转换图

图2-53 八进制数的状态转换图

图2-54 十六进制数的状态转换图

图2-55 十进制数浮点数的状态转换图

图2-56 十六进制数浮点数的状态转换图

cpp_classify_number函数的代码如下所示:

//代码路径:libcpp/expr.c:
 unsigned int
 cpp_classify_number (cpp_reader *pfile, const cpp_token *token,
              const char **ud_suffix, source_location virtual_location)
 {
     const uchar *str = token->val.str.text;        // 获取字符串的内容(前面lex_number函数中已经保存了内容)
     const uchar *limit;
     unsigned int max_digit, result, radix;
     enum {NOT_FLOAT = 0, AFTER_POINT, AFTER_EXPON} float_flag;
                                                        // 关于浮点数的三种标志:NOT_FLOAT为非浮点标志,
                                                        // AFTER_POINT为已经识别到浮点的标志,
                                                        // AFTER_EXPON为科学计数法标志(exponent:指数)
     bool seen_digit;
    ……
    if (token->val.str.len == 1)                        // 如果字符串的长度为1
       return CPP_N_INTEGER | CPP_N_SMALL | CPP_N_DECIMAL;
                                                        // 那就不用再分析了,这个数据就是个十进制的短整型
    limit = str + token->val.str.len;                // 通过起始字符和字符串长度值,获取字符串末尾字符
    float_flag = NOT_FLOAT;                        // 先默认将要分析的数字是非浮点数
    max_digit = 0;                                // 默认识别到的最大数字是0
    radix = 10;                                        // 先默认将要分析的数字是十进制数
    seen_digit = false;
    // 先确定数字是个几进制的数
    if (*str == '0')                                // 识别到第一个字符是“0
    {
       radix = 8;                                        // 就肯定不是十进制数了,先默认是八进制数,往后看看再说
       str++;                                        // 准备往后遍历
       /* Require at least one hex digit to classify it as hex.  */
       if ((*str == 'x' || *str == 'X')                // 如果第二个字符是“x”或“X”,同时第三个字符
                                                        // 是“.”或0~9A~Fa~f
       && (str[1] == '.' || ISXDIGIT (str[1])))
       {
         radix = 16;                                // 说明是十六进制数
         str++;
       }
        else if ((*str == 'b' || *str == 'B') && (str[1] == '0' || str[1] == '1'))
      {                                                // 如果第二个字符是“b”或“B”,同时第三个字符
                                                        // 是“0”或“1
         radix = 2;                                // 说明是二进制数
         str++;
      }
     }
     // 到这里就确定了数据是几进制数
    // 循环中判断数字是非浮点数还是浮点数,以及有没有使用科学计数法
    for (;;)
    {
       unsigned int c = *str++;
      if (ISDIGIT (c) || (ISXDIGIT (c) && radix == 16))
                                                        // 识别到0~9或者十六进制下的0~9A~Fa~f
     {
        seen_digit = true;                        // 确定当前识别到的字符是数字
        c = hex_value (c);
        if (c > max_digit)                        // 不断更改识别到的最大数字
          max_digit = c;
     }
    else if (c == '.')                                // 识别到浮点了
    {
      if (float_flag == NOT_FLOAT)                // 如果此时还默认数字是非浮点数
        float_flag = AFTER_POINT;                // 就要改设为浮点数了,而且确定此时已经遍历过了浮点
        ……
    }
      else if ((radix <= 10 && (c == 'e' || c == 'E'))
           || (radix == 16 && (c == 'p' || c == 'P')))
                                                        // 识别到了科学计数法标志
     {
       float_flag = AFTER_EXPON;                        // 设置科学计数法标志
       break;                                        // 终止对科学计数法部分数据的进一步识别,跳出循环
     }
     else
     {
        /* Start of suffix.  */
        str--;
        break;                                        // 如果执行到这里,整个数字的数值内容就算识别完了,
                                                        // 要么是非浮点数,要么是浮点数,但不会有科学计数法,
                                                        // 识别完后,准备识别后缀信息
     }
    }
    if (radix != 16 && float_flag == NOT_FLOAT)        // 确定数字不是十六进制数,而且是非浮点数
     {
      result = interpret_float_suffix (pfile, str, limit - str);
                                                        // 设置后缀信息
      ……
      else
      result = 0;                                        // 后缀信息设置为0,这个result最终将用来存储
                                                        // 数字的全部属性信息
     }
     if (float_flag != NOT_FLOAT && radix == 8)        // 如果数字是浮点型或包含科学计数法,同时还是个八进制数
       radix = 10;                                // 设置为十进制数
    ……
    if (float_flag != NOT_FLOAT)                        // 数字是浮点型或包含科学计数法
    {
        ……
      if (float_flag == AFTER_EXPON)                // 如果用了科学计数法,前面跳出了循环,这里继续遍历
     {
        if (*str == '+' || *str == '-')                // 判断指数是“+”还是“-
          str++;
        ……
        do
          str++;
        while (ISDIGIT (*str));                        // 只要是0~9,就说明符合指数规则,继续遍历
     }
     result = interpret_float_suffix (pfile, str, limit - str);
                                                        // 设置后缀信息
     ……
     result |= CPP_N_FLOATING;                        // 确定此数字为浮点数,设置标志位
 }
 else                                                // 数字是非浮点数
 {
   result = interpret_int_suffix (pfile, str, limit - str);
                                                        // 设置后缀信息
   ……
   result |= CPP_N_INTEGER;                        // 确定此数字为非浮点数,设置标志位
 }
 ……
 // 到这里为止,后缀类型信息都已经确定了,下面最后再把几进制这一信息加上,数字就算分析完了
 if (radix == 10)
     result |= CPP_N_DECIMAL;                        // 确定数字为十进制数,设置信息
   else if (radix == 16)
     result |= CPP_N_HEX;                                // 确定数字为十六进制数,设置信息
   else if (radix == 2)
     result |= CPP_N_BINARY;                        // 确定数字为二进制数,设置信息
   else
     result |= CPP_N_OCTAL;                        // 确定数字为八进制数,设置信息
   return result;                                        // 数字分析完毕,返回结果
 ……
}
3.字符或字符串的详细分析过程

先来看lex_string函数,即字符或字符串的识别过程,情景如图2-57和图2-58所示。

图2-57 字符常量的状态转换图一

图2-58 字符常量的状态转换图二

代码如下所示:

//代码路径:libcpp/lex.c:
static void
lex_string (cpp_reader *pfile, cpp_token *token, const uchar *base)
{
  ……
  cur = base;
  terminator = *cur++;                                // 获取到字符或字符串的第一个字符,cur指向第二个字符
  if (terminator == 'L' || terminator == 'U')        // 先看看字符或字符串前面有没有修饰符,“L”、“U”、“u”、“8
                                                        // 都是修饰符
     terminator = *cur++;
  else if (terminator == 'u')
  {
     terminator = *cur++;
     if (terminator == '8')
       terminator = *cur++;
  }
  ……
  // 开始识别字符或字符串的实质内容了
  if (terminator == '"')                                // 识别到“"”,说明是字符串
    type = (*base == 'L' ? CPP_WSTRING :        // 根据修饰符有无或种类来设置字符串的类型
       *base == 'U' ? CPP_STRING32 :
       *base == 'u' ? (base[1] == '8' ? CPP_UTF8STRING : CPP_STRING16) : CPP_STRING);
  else if (terminator == '\'')                        // 识别到“\'”,说明是字符
    type = (*base == 'L' ? CPP_WCHAR :                // 根据修饰符有无或种类来设置字符的类型
       *base == 'U' ? CPP_CHAR32 :
       *base == 'u' ? CPP_CHAR16 : CPP_CHAR);
  else
    terminator = '>', type = CPP_HEADER_NAME;        // 还有一种情况,此时正在识别某个头文件的名字,设置类型
for (;;)                                                // 继续向后遍历字符或字符串的内容
   {
       cppchar_t c = *cur++;                        // 根据字符串或字符的词法规定, "”或“\”成对出现
       ……
       else if (c == terminator)                        // 找到另一个“"”或“\”,说明内容识别完毕了
         break;
       else if (c == '\n')                        // 遇到“\n”,就要准备处理头文件的名字了
     {
        cur--;                                        // cur指针往前退一个字符
        if (terminator == '>')                        // 退了一个正好是“>”,说明头文件的名字识别完毕
        {
          token->type = CPP_LESS;
          return;
        }
        ……
     }
       ……
    }……
}