官术网_书友最值得收藏!

2.4.2 結合GCC源代碼講解詞法分析過程

1.分析空白分隔符

空白分隔符不屬于C語言中符號的實際內容,遇到它后,會直接將其當作空白字符跳過,即跳轉到skipped_white這個點,回到初始狀態,情景如圖2-42所示。

圖2-42 空白分隔符的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
    switch (c)                                                        // c中保存著當前正要分析的字符,準備分析
    {
      case ' ': case '\t': case '\f': case '\v': case '\0':        // 這幾個字符就是空白分隔符
      result->flags |= PREV_WHITE;                                // 設置空白分隔符標志
      skip_whitespace (pfile, c);                                // 發現空白字符后一直往后遍歷,直到找到
                                                                        // 非空白分隔符為止
      goto skipped_white;                                                // 回到初始狀態,準備繼續分析下一個字符
      ……
}
2.分析換行符

換行符也不屬于實際的符號內容,從代碼中可以看到,沒有對這個字符做什么符號屬性標記,遇到它后跳轉到fresh_line這個點,回到初始狀態,情景如圖2-43所示。

圖2-43 換行符的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
    switch (c)                                                        // c中保存著當前正要分析的字符
    {
      ……
      case '\n':                                                        // 遇到換行符
      ……
      goto fresh_line;                                                // 跳轉到這個點,回到初始狀態,準備繼續
                                                                        // 分析下一個字符
      ……
}
3.確定符號為數字

根據C語言規則,如果符號的第一個字符是0到9中的任意一個,該符號就是數字,如果第一個字符是“.”,就要看緊跟著的字符,如果仍是0到9中的任意一個,也表示該符號是數字,是浮點型數字。至于具體的情景,我們在介紹完_cpp_lex_direct函數后會詳細介紹,代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                        // c中保存著當前正要分析的字符“{
     ……
     case '0': case '1': case '2': case '3': case '4':                // 遇到這幾個字符就確定當前符號是數字
     case '5': case '6': case '7': case '8': case '9':
       {
         ……
         result->type = CPP_NUMBER;                                // 將當前符號屬性設置為數字
         lex_number (pfile, &result->val.str, &nst);                // 此函數將數字的全部內容先以字符串的形式
                                                                        // 存儲起來,以待分析
         ……
         break;
       }
       ……
    case '.':                                                        // 識別到“.
      ……
      if (ISDIGIT (*buffer->cur))                                // 檢測后面跟隨的是不是09中的字符
      {
       ……
       result->type = CPP_NUMBER;                                // 將符號屬性設置為數字
       lex_number (pfile, &result->val.str, &nst);                // 此函數將數字的全部內容先以字符串的形式
                                                                        // 存儲起來,以待分析
       ……
      }
      else if (*buffer->cur == '.' && buffer->cur[1] == '.')        // 再前瞻兩個字符,如果都是“.
         buffer->cur += 2, result->type = CPP_ELLIPSIS;        // 將符號屬性設置為缺省符
    ……
}
4.確定符號為字符或字符串

具體的情景我們在介紹完_cpp_lex_direct函數后會詳細介紹,這里先總體看一下。如果符號的第一個字符如果是“L”、“u”、“U”或“R”,可能是標識符,也可能是字符串,程序中先分析它是不是字符串,代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                        // c中保存著當前正要分析的字符
    {
       ……
       case 'L':                                                        // 識別到“L”、“u”、“U”或“R”,先分析
                                                                        // 是不是字符或字符串
       case 'u':
       case 'U':
       case 'R':
         /* 'L', 'u', 'U', 'u8' or 'R' may introduce wide characters,
      wide strings or raw strings.  */
        if (c == 'L' || CPP_OPTION (pfile, rliterals)
       || (c != 'R' && CPP_OPTION (pfile, uliterals)))
      {
       if ((*buffer->cur == '\'' && c != 'R')                        // cur指向的是符號第二個字符,cur[1]cur[2]
          || *buffer->cur == '"'                                        // 分別是第三個和第四個字符,這里以前瞻的
          || (*buffer->cur == 'R'                                // 方式來判斷符號是不是字符或字符串
            && c != 'R'
            && buffer->cur[1] == '"'
            && CPP_OPTION (pfile, rliterals))
          || (*buffer->cur == '8'
            && c == 'u'
            && (buffer->cur[1] == '"'
               || (buffer->cur[1] == 'R' && buffer->cur[2] == '"'
                 && CPP_OPTION (pfile, rliterals)))))
         {
          lex_string (pfile, result, buffer->cur - 1);                // 如果是字符或字符串,進入這個函數,具體分析
                                                                        // 它們的內容
          break;
         }
      }
         /* Fall through.  */
    case '_':                                                // 執行到這里,說明不是字符串,而是標識符,則處理標識符
    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
    case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
    case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':……
}

符號的第一個字符如果是“\'”或“"”,就可以確定符號是字符或字符串,進入lex_string函數來分析字符串內容。代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case '\'':                                                // 識別到“\'”或“"”,確定當前符號是字符串
       case '"':
         lex_string (pfile, result, buffer->cur - 1);        // 確定是字符串后,進入此函數,具體分析字符串的內容
         break;
       ……
}
5.確定符號為標識符

下面我們來看標識符的識別,至于具體的情景,我們在介紹完_cpp_lex_direct函數后會詳細介紹,先總體看一下。對于前面介紹的識別字符串,在識別到“L”、“u”、“U”或“R”時,如果當前符號不是字符串,那就是標識符,標識符的第一個字符是字母或下劃線,這幾個字符屬于字母。我們來看完整的標識符識別條件,代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case 'L':                                                // 識別到“L”、“u”、“U”或“R”,先分析是不是字符串
       case 'u':
       case 'U':
       case 'R':
     /* 'L', 'u', 'U', 'u8' or 'R' may introduce wide characters,
      wide strings or raw strings.  */
     if (c == 'L' || CPP_OPTION (pfile, rliterals)        // 如果不是字符或字符串,則是標識符
       || (c != 'R' && CPP_OPTION (pfile, uliterals)))
     {
       if ((*buffer->cur == '\'' && c != 'R')
          || *buffer->cur == '"'
          || (*buffer->cur == 'R'
            && c != 'R'
            && buffer->cur[1] == '"'
            && CPP_OPTION (pfile, rliterals))
          || (*buffer->cur == '8'
            && c == 'u'
            && (buffer->cur[1] == '"'
               || (buffer->cur[1] == 'R' && buffer->cur[2] == '"'
                 && CPP_OPTION (pfile, rliterals)))))
         {
          lex_string (pfile, result, buffer->cur - 1);        // 如果是字符或字符串,進入這個函數,具體分析它們的內容
          break;
         }
      }
         /* Fall through.  */
    case '_':                                                // 字母或下劃線開頭的符號都是標識符
    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
    case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
    case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
    case 's': case 't':           case 'v': case 'w': case 'x':
    case 'y': case 'z':
    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
    case 'G': case 'H': case 'I': case 'J': case 'K':
    case 'M': case 'N': case 'O': case 'P': case 'Q':
    case 'S': case 'T':           case 'V': case 'W': case 'X':
    case 'Y': case 'Z':
       result->type = CPP_NAME;                                // 設置符號屬性為標識符
       {
          ……
          result->val.node.node = lex_identifier (pfile, buffer->cur - 1, false,&nst);
                                                                // 確定符號為標識符后,進入此函數,繼續識別標識符的內容
          ……
       }
        /* Convert named operators to their proper types.  */
         ……
       break;
    ……
}
6.分析運算符和分隔符

下面我們來看各種運算符和分隔符。

先看符號“/”,它后面緊跟不同的字符來構成不同的運算符。“/*”和“//”都是注釋的意思,“/*”和“*/”組合時可以注釋掉它們之間的內容;“//”用來注釋掉當前行的內容;“/=”是指對運算符左邊和右邊的操作數做除法,然后將數值賦給左邊操作數;不是以上情況才能確定“/”就是除號,情景如圖2-44所示。

圖2-44 “注釋”、“/=”和“/”的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case '/':                                                // 識別到“/
         ……
        c = *buffer->cur;                                        // 獲取“/”后面的字符,準備繼續往后識別,看是不是除號
         if (c == '*')                                        // 識別到后面是“*”,確定是注釋一些內容
        {
           if (_cpp_skip_block_comment (pfile))                // 進入該函數,一直遍歷后續的ASCII碼,看能不能找到
                                                                // */”,確定注釋掉哪部分內容,這些內容就不參與詞法分析了
             ……
        }
        else if (c == '/' && (CPP_OPTION (pfile, cplusplus_comments)
                   || cpp_in_system_header (pfile)))        // 識別到“/”,確定是要注釋掉當前行中的內容
        {
          ……
          if (skip_line_comment (pfile) && CPP_OPTION (pfile, warn_comments))
                                                                // 一直遍歷ASCII碼,找到“\n”,確定一行結束,
                                                                // 此行的內容不再參與詞法分析
            ……
        }
        else if (c == '=')                                // 識別到“=”,確定是“/=”運算符
        {
          buffer->cur++;                                        // cur指向“=”后面的字符,準備識別后面的符號
          result->type = CPP_DIV_EQ;                        // 設置當前符號屬性為“/=
          break;
        }
        else
        {
          result->type = CPP_DIV;                        // 后面跟的字符不是以上三種情況,說明當前符號就是
                                                                // 除號,設置屬性
          break;
        }
         ……
}

下面來看“<”。如果此時正在進行預處理,“<”可以用來引用某個頭文件,它就不是小于號,否則,還要看它后面跟著什么字符,如果后續是“=”,則構成“<=”運算符,即小于等于;如果后續仍然是“<”,就還要往后看,如果后續仍然是“=”,就是“<<=”,即左移并賦值,如果后續沒有“=”,就是“<<”,即左移;這些情況都排除掉,才可以確定“<”就是小于號,情景如圖2-45所示。

圖2-45 “<=”、“<<=”、“<<”、“<”的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case '&lt;':                                                // 識別到“&lt;
         if (pfile-&gt;state.angled_headers)                // 此時正處于預處理階段,要確定頭文件了
        {
           lex_string (pfile, result, buffer-&gt;cur - 1);        // 分析頭文件的字符串信息
           if (result-&gt;type != CPP_LESS)
           break;
        }
       result-&gt;type = CPP_LESS;                                // 默認運算符是小于號,后面如果沒有改變,就是它了
       if (*buffer-&gt;cur == '=')                                // 識別到下一個字符號是“=
         buffer-&gt;cur++, result-&gt;type = CPP_LESS_EQ;        // 設置當前符號是小于等于號
      else if (*buffer-&gt;cur == '&lt;')                        // 識別到下一個字符是“&lt;
      {
        buffer-&gt;cur++;                                        // 繼續往后看
        IF_NEXT_IS ('=', CPP_LSHIFT_EQ, CPP_LSHIFT);        // 分析出究竟是“&lt;&lt;=”還是“&lt;&lt;
      }
      ……
      break;
    ……
}

大于號的分析識別過程與小于號類似,如果后續是“=”,則構成“>=”運算符,即大于等于;如果仍然跟著“>”,就還要往后看,若后續是“=”,就是“>>=”,即右移并賦值,如果后續沒有“=”,就是“>>”,即右移;這些情況都排除掉,才可以確定“>”就是大于號,情景如圖2-46所示。

圖2-46 “>=”、“>>=”、“>>”、“>”的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case '&gt;':                                                // 識別到“&gt;
         result-&gt;type = CPP_GREATER;                        // 將屬性默認設置為大于號,后面沒有變動,就是它
         if (*buffer-&gt;cur == '=')                        // 識別到后面跟著的字符是“=
           buffer-&gt;cur++, result-&gt;type = CPP_GREATER_EQ;        // 設置符號屬性為大于等于
        else if (*buffer-&gt;cur == '&gt;')                        // 識別到后面跟著的字符是“&gt;
        {
          buffer-&gt;cur++;                                        // 還得往后看
          IF_NEXT_IS ('=', CPP_RSHIFT_EQ, CPP_RSHIFT);        // 分析出究竟是“&gt;&gt;=”還是“&gt;&gt;
        }
        break;
      ……
}

下面來看“%”,情況比較簡單,識別到“%”后,往后再看一個字符,如果是“=”,就是“%=”,即求余并賦值,否則就是求余,情景如圖2-47所示。

圖2-47 “%=”、“%”的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                // c中保存著當前正要分析的ASCII碼,準備分析
    { 
       ……
       case '%':                                                // 識別到“%
        result-&gt;type = CPP_MOD;                                // 默認就是求余運算符,后面沒有改變,就是它
         if (*buffer-&gt;cur == '=')                        // 繼續往后識別到“=
           buffer-&gt;cur++, result-&gt;type = CPP_MOD_EQ;        // 確定此運算符是“%=
        ……
       break;
        ……
}

下面來看“.”。如果后面跟著的第二個和第三個字符都是“.”,說明當前符號是“...”,即缺省符,表示變參,情景如圖2-48所示。

圖2-48 “…”、“.*”的狀態轉換圖

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                        // c中保存著當前正要分析的ASCII碼,準備分析
    {
       ……
       case '.':                                                        // 識別到“.
        ……
          if (ISDIGIT (*buffer-&gt;cur))                                // 檢測后面跟隨的是不是09中的字符
          {
           ……
           result-&gt;type = CPP_NUMBER;                                // 將符號屬性設置為數字
           lex_number (pfile, &amp;result-&gt;val.str, &amp;nst);                // 此函數將數字的全部內容先以字符串的形式
                                                                        // 存儲起來,以待分析
           ……
          }
          else if (*buffer-&gt;cur == '.' &amp;&amp; buffer-&gt;cur[1] == '.')        // 再往后識別兩個字符,如果都是“.
             buffer-&gt;cur += 2, result-&gt;type = CPP_ELLIPSIS;        // 將符號屬性設置為缺省符
       ……
}

其他運算符的識別過程大體類似,情景如圖2-49和圖2-50所示。

圖2-49 各種運算符和分隔符的狀態轉換圖一

圖2-50 各種運算符和分隔符的狀態轉換圖二

代碼如下所示:

//代碼路徑:libcpp/lex.c:
cpp_token *
_cpp_lex_direct (cpp_reader *pfile)
{
  ……
  switch (c)                                                        // c中保存著當前正要分析的ASCII碼,準備分析
    {
     ……
     case '+':                                                        // 識別出字符“+
       result-&gt;type = CPP_PLUS;                                        // 默認符號屬性是加法運算符
       if (*buffer-&gt;cur == '+')                                        // 繼續往后識別,又識別到了字符“+
          buffer-&gt;cur++, result-&gt;type = CPP_PLUS_PLUS;                // 將符號屬性設置為“++
       else if (*buffer-&gt;cur == '=')                                // 繼續往后識別,又識別到了字符“=
          buffer-&gt;cur++, result-&gt;type = CPP_PLUS_EQ;                // 將符號屬性設置為“+=
       break;
     case '-':                                                        // 識別出字符“-
       result-&gt;type = CPP_MINUS;                                        // 默認符號屬性是減法運算符
       if (*buffer-&gt;cur == '&gt;')                                        // 繼續往后識別,識別到了字符“&gt;
       {
         ……
         result-&gt;type = CPP_DEREF;                                // 確認是“-&gt;”運算符,即指向運算符
         ……
       }
       else if (*buffer-&gt;cur == '-')                                // 繼續往后識別,識別到了字符“-
         buffer-&gt;cur++, result-&gt;type = CPP_MINUS_MINUS;        // 確認是“--”運算符
       else if (*buffer-&gt;cur == '=')                                // 繼續往后識別,識別到了字符“=
         buffer-&gt;cur++, result-&gt;type = CPP_MINUS_EQ;                // 確認是“-=”運算符
      break;
    case '&amp;':                                                        // 識別出字符“&amp;
       result-&gt;type = CPP_AND;                                        // 確定運算符屬性是“&amp;”,即按位與
        if (*buffer-&gt;cur == '&amp;')                                        // 繼續識別到字符“&amp;”,
           buffer-&gt;cur++, result-&gt;type = CPP_AND_AND;                // 確定運算符為“&amp;&amp;
        else if (*buffer-&gt;cur == '=')                                // 繼續識別到字符“=
           buffer-&gt;cur++, result-&gt;type = CPP_AND_EQ;                // 確定運算符為“&amp;=
        break;
     case '|':                                                        // 識別出字符“|
        result-&gt;type = CPP_OR;                                        // 確定運算符屬性是“|”,即按位或
          if (*buffer-&gt;cur == '|')                                // 繼續識別到字符“|
            buffer-&gt;cur++, result-&gt;type = CPP_OR_OR;                // 確定運算符為“||
          else if (*buffer-&gt;cur == '=')                                // 繼續識別到字符“=
            buffer-&gt;cur++, result-&gt;type = CPP_OR_EQ;                // 確定運算符為“|=
         break;
      case ':':                                                        // 識別出字符“:
        result-&gt;type = CPP_COLON;                                // 確定運算符為“:
        ……
        break;
      case '*': IF_NEXT_IS ('=', CPP_MULT_EQ, CPP_MULT); break;        // 確定運算符為“*=”或“*
      case '=': IF_NEXT_IS ('=', CPP_EQ_EQ, CPP_EQ); break;        // 確定運算符為“==”或“=
      case '!': IF_NEXT_IS ('=', CPP_NOT_EQ, CPP_NOT); break;        // 確定運算符為“!=”或“!
      case '^': IF_NEXT_IS ('=', CPP_XOR_EQ, CPP_XOR); break;        // 確定運算符為“^=”或“^
      case '#': IF_NEXT_IS ('#', CPP_PASTE, CPP_HASH); result-&gt;val.token_no = 0; break;
                                                                        // 確定運算符為“##”或“#
      case '?': result-&gt;type = CPP_QUERY; break;                // 確定運算符為“?”
      case '~': result-&gt;type = CPP_COMPL; break;                // 確定運算符為“~
      case ',': result-&gt;type = CPP_COMMA; break;                // 確定運算符為“,
      case '(': result-&gt;type = CPP_OPEN_PAREN; break;                // 確定運算符為“(
      case ')': result-&gt;type = CPP_CLOSE_PAREN; break;                // 確定運算符為“)
      case '[': result-&gt;type = CPP_OPEN_SQUARE; break;                // 確定運算符為“[
      case ']': result-&gt;type = CPP_CLOSE_SQUARE; break;        // 確定運算符為“]
      case '{': result-&gt;type = CPP_OPEN_BRACE; break;                // 確定運算符為“{
      case '}': result-&gt;type = CPP_CLOSE_BRACE; break;                // 確定運算符為“}
      case ';': result-&gt;type = CPP_SEMICOLON; break;                // 確定運算符為“;
      ……
}

在_cpp_lex_direct函數中,每一次switch...case...都對應著一次狀態轉換圖上的“起始狀態”至“終態”,即識別一個符號。不難發現,各個運算符的識別過程比較簡單,一兩步或兩三步就到終態了,而標識符、數字以及字符串的識別過程相對煩瑣一些,我們在代碼分析中只做了大體介紹。在確定了符號屬性后,后續的內容分別在lex_identifier、lex_number和lex_string函數中實現,最終達到終態。接下來,我們分別詳細講解這3個函數的實現過程。

主站蜘蛛池模板: 砀山县| 龙胜| 宣恩县| 惠安县| 嵩明县| 乐都县| 平和县| 招远市| 德昌县| 郸城县| 南平市| 泸西县| 合作市| 安岳县| 屯门区| 永川市| 正定县| 阳朔县| 金川县| 呈贡县| 团风县| 阜阳市| 安远县| 祁东县| 威海市| 临颍县| 河曲县| 尉氏县| 额敏县| 高清| 兴和县| 云浮市| 广安市| 揭阳市| 襄垣县| 南部县| 常德市| 淅川县| 绩溪县| 茂名市| 郎溪县|