- 編譯系統(tǒng)透視:圖解編譯原理
- 新設(shè)計(jì)團(tuán)隊(duì)
- 2780字
- 2019-01-04 03:30:37
2.4.3 標(biāo)識符、數(shù)字、字符和字符串的詳細(xì)分析過程
1.標(biāo)識符的詳細(xì)分析過程
先來看lex_identifier函數(shù),即標(biāo)識符的識別過程,情景如圖2-51所示。

圖2-51 標(biāo)識符的狀態(tài)轉(zhuǎn)換圖
代碼如下所示:
//代碼路徑: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指向的是下一個(gè)字符 if (! starts_ucn) while (ISIDNUM (*cur)) // 一直檢測,若下一個(gè)字符不是字母、數(shù)字或下劃線,則跳出循環(huán) { hash = HT_HASHSTEP (hash, *cur); cur++; // 符合字母、數(shù)字或下劃線條件,cur就繼續(xù)指向下一個(gè)字符 } pfile->buffer->cur = cur; // 跳出循環(huán)后,進(jìn)入終態(tài) …… } // 代碼路徑:include/safe-ctype.h: …… _sch_isidnum = _sch_isidst|_sch_isdigit, /* A-Za-z0-9_ */ …… #define ISIDNUM(c) _sch_test(c, _sch_isidnum) // 字母、數(shù)字或下劃線的判斷條件展開形式 …… }
2.數(shù)字的詳細(xì)分析過程
下面我們來看數(shù)字的識別過程。后續(xù)的數(shù)字識別過程在狀態(tài)轉(zhuǎn)換圖的基礎(chǔ)上做了調(diào)整,大體分為兩步:第一步是,只要識別出是數(shù)字,就把后續(xù)內(nèi)容當(dāng)作字符串存起來,不管是什么;第二步是,根據(jù)字符串中的內(nèi)容,分析它的具體屬性,狀態(tài)轉(zhuǎn)換圖的思想主要體現(xiàn)在第二步。
我們先來看第一步,通過調(diào)用lex_number函數(shù)來實(shí)現(xiàn)。代碼如下所示:
//代碼路徑: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])) // 只要符合數(shù)字規(guī)則,就繼續(xù)循環(huán) { cur++; // cur不斷地指向后面的ASCII碼,直到遇到空格,跳出循環(huán) NORMALIZE_STATE_UPDATE_IDNUM (nst); } pfile->buffer->cur = cur; // 跳出循環(huán)后,buffer->cur指向空格 } …… number->len = cur - base; // 計(jì)算出數(shù)字有多長 dest = _cpp_unaligned_alloc (pfile, number->len + 1); // 為存儲數(shù)字開辟空間 memcpy (dest, base, number->len); // 把數(shù)字的內(nèi)容記錄下來 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) // 字母、數(shù)字或下劃線的判斷條件展開形式 …… } // 代碼路徑: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) // 科學(xué)計(jì)數(shù)法的識別條件 ……
我們再來看第二步。前面介紹了詞法分析的函數(shù)調(diào)用順序。
第二步等cpp_get_token_with_location函數(shù)返回后,在c_lex_with_flags函數(shù)中進(jìn)行。代碼如下所示:
//代碼路徑: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); // 此函數(shù)將確定數(shù)據(jù)的具體屬性 …… } …… }
數(shù)字的屬性包括三種:類型屬性(整型、浮點(diǎn)型等)、進(jìn)制屬性(十進(jìn)制、八進(jìn)制、十六進(jìn)制等)和后綴信息(如短類型、寬類型等輔助類型信息),下面我們來介紹cpp_classify_number函數(shù)。情景如圖2-52至圖2-56所示。

圖2-52 十進(jìn)制數(shù)的狀態(tài)轉(zhuǎn)換圖

圖2-53 八進(jìn)制數(shù)的狀態(tài)轉(zhuǎn)換圖

圖2-54 十六進(jìn)制數(shù)的狀態(tài)轉(zhuǎn)換圖


圖2-55 十進(jìn)制數(shù)浮點(diǎn)數(shù)的狀態(tài)轉(zhuǎn)換圖

圖2-56 十六進(jìn)制數(shù)浮點(diǎn)數(shù)的狀態(tài)轉(zhuǎn)換圖
cpp_classify_number函數(shù)的代碼如下所示:
//代碼路徑: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; // 獲取字符串的內(nèi)容(前面lex_number函數(shù)中已經(jīng)保存了內(nèi)容) const uchar *limit; unsigned int max_digit, result, radix; enum {NOT_FLOAT = 0, AFTER_POINT, AFTER_EXPON} float_flag; // 關(guān)于浮點(diǎn)數(shù)的三種標(biāo)志:NOT_FLOAT為非浮點(diǎn)標(biāo)志, // AFTER_POINT為已經(jīng)識別到浮點(diǎn)的標(biāo)志, // AFTER_EXPON為科學(xué)計(jì)數(shù)法標(biāo)志(exponent:指數(shù)) bool seen_digit; …… if (token->val.str.len == 1) // 如果字符串的長度為1 return CPP_N_INTEGER | CPP_N_SMALL | CPP_N_DECIMAL; // 那就不用再分析了,這個(gè)數(shù)據(jù)就是個(gè)十進(jìn)制的短整型 limit = str + token->val.str.len; // 通過起始字符和字符串長度值,獲取字符串末尾字符 float_flag = NOT_FLOAT; // 先默認(rèn)將要分析的數(shù)字是非浮點(diǎn)數(shù) max_digit = 0; // 默認(rèn)識別到的最大數(shù)字是0 radix = 10; // 先默認(rèn)將要分析的數(shù)字是十進(jìn)制數(shù) seen_digit = false; // 先確定數(shù)字是個(gè)幾進(jìn)制的數(shù) if (*str == '0') // 識別到第一個(gè)字符是“0” { radix = 8; // 就肯定不是十進(jìn)制數(shù)了,先默認(rèn)是八進(jìn)制數(shù),往后看看再說 str++; // 準(zhǔn)備往后遍歷 /* Require at least one hex digit to classify it as hex. */ if ((*str == 'x' || *str == 'X') // 如果第二個(gè)字符是“x”或“X”,同時(shí)第三個(gè)字符 // 是“.”或0~9、A~F、a~f && (str[1] == '.' || ISXDIGIT (str[1]))) { radix = 16; // 說明是十六進(jìn)制數(shù) str++; } else if ((*str == 'b' || *str == 'B') && (str[1] == '0' || str[1] == '1')) { // 如果第二個(gè)字符是“b”或“B”,同時(shí)第三個(gè)字符 // 是“0”或“1” radix = 2; // 說明是二進(jìn)制數(shù) str++; } } // 到這里就確定了數(shù)據(jù)是幾進(jìn)制數(shù) // 循環(huán)中判斷數(shù)字是非浮點(diǎn)數(shù)還是浮點(diǎn)數(shù),以及有沒有使用科學(xué)計(jì)數(shù)法 for (;;) { unsigned int c = *str++; if (ISDIGIT (c) || (ISXDIGIT (c) && radix == 16)) // 識別到0~9或者十六進(jìn)制下的0~9、A~F、a~f { seen_digit = true; // 確定當(dāng)前識別到的字符是數(shù)字 c = hex_value (c); if (c > max_digit) // 不斷更改識別到的最大數(shù)字 max_digit = c; } else if (c == '.') // 識別到浮點(diǎn)了 { if (float_flag == NOT_FLOAT) // 如果此時(shí)還默認(rèn)數(shù)字是非浮點(diǎn)數(shù) float_flag = AFTER_POINT; // 就要改設(shè)為浮點(diǎn)數(shù)了,而且確定此時(shí)已經(jīng)遍歷過了浮點(diǎn) …… } else if ((radix <= 10 && (c == 'e' || c == 'E')) || (radix == 16 && (c == 'p' || c == 'P'))) // 識別到了科學(xué)計(jì)數(shù)法標(biāo)志 { float_flag = AFTER_EXPON; // 設(shè)置科學(xué)計(jì)數(shù)法標(biāo)志 break; // 終止對科學(xué)計(jì)數(shù)法部分?jǐn)?shù)據(jù)的進(jìn)一步識別,跳出循環(huán) } else { /* Start of suffix. */ str--; break; // 如果執(zhí)行到這里,整個(gè)數(shù)字的數(shù)值內(nèi)容就算識別完了, // 要么是非浮點(diǎn)數(shù),要么是浮點(diǎn)數(shù),但不會(huì)有科學(xué)計(jì)數(shù)法, // 識別完后,準(zhǔn)備識別后綴信息 } } if (radix != 16 && float_flag == NOT_FLOAT) // 確定數(shù)字不是十六進(jìn)制數(shù),而且是非浮點(diǎn)數(shù) { result = interpret_float_suffix (pfile, str, limit - str); // 設(shè)置后綴信息 …… else result = 0; // 后綴信息設(shè)置為0,這個(gè)result最終將用來存儲 // 數(shù)字的全部屬性信息 } if (float_flag != NOT_FLOAT && radix == 8) // 如果數(shù)字是浮點(diǎn)型或包含科學(xué)計(jì)數(shù)法,同時(shí)還是個(gè)八進(jìn)制數(shù) radix = 10; // 設(shè)置為十進(jìn)制數(shù) …… if (float_flag != NOT_FLOAT) // 數(shù)字是浮點(diǎn)型或包含科學(xué)計(jì)數(shù)法 { …… if (float_flag == AFTER_EXPON) // 如果用了科學(xué)計(jì)數(shù)法,前面跳出了循環(huán),這里繼續(xù)遍歷 { if (*str == '+' || *str == '-') // 判斷指數(shù)是“+”還是“-” str++; …… do str++; while (ISDIGIT (*str)); // 只要是0~9,就說明符合指數(shù)規(guī)則,繼續(xù)遍歷 } result = interpret_float_suffix (pfile, str, limit - str); // 設(shè)置后綴信息 …… result |= CPP_N_FLOATING; // 確定此數(shù)字為浮點(diǎn)數(shù),設(shè)置標(biāo)志位 } else // 數(shù)字是非浮點(diǎn)數(shù) { result = interpret_int_suffix (pfile, str, limit - str); // 設(shè)置后綴信息 …… result |= CPP_N_INTEGER; // 確定此數(shù)字為非浮點(diǎn)數(shù),設(shè)置標(biāo)志位 } …… // 到這里為止,后綴類型信息都已經(jīng)確定了,下面最后再把幾進(jìn)制這一信息加上,數(shù)字就算分析完了 if (radix == 10) result |= CPP_N_DECIMAL; // 確定數(shù)字為十進(jìn)制數(shù),設(shè)置信息 else if (radix == 16) result |= CPP_N_HEX; // 確定數(shù)字為十六進(jìn)制數(shù),設(shè)置信息 else if (radix == 2) result |= CPP_N_BINARY; // 確定數(shù)字為二進(jìn)制數(shù),設(shè)置信息 else result |= CPP_N_OCTAL; // 確定數(shù)字為八進(jìn)制數(shù),設(shè)置信息 return result; // 數(shù)字分析完畢,返回結(jié)果 …… }
3.字符或字符串的詳細(xì)分析過程
先來看lex_string函數(shù),即字符或字符串的識別過程,情景如圖2-57和圖2-58所示。

圖2-57 字符常量的狀態(tài)轉(zhuǎn)換圖一

圖2-58 字符常量的狀態(tài)轉(zhuǎn)換圖二
代碼如下所示:
//代碼路徑:libcpp/lex.c: static void lex_string (cpp_reader *pfile, cpp_token *token, const uchar *base) { …… cur = base; terminator = *cur++; // 獲取到字符或字符串的第一個(gè)字符,cur指向第二個(gè)字符 if (terminator == 'L' || terminator == 'U') // 先看看字符或字符串前面有沒有修飾符,“L”、“U”、“u”、“8” // 都是修飾符 terminator = *cur++; else if (terminator == 'u') { terminator = *cur++; if (terminator == '8') terminator = *cur++; } …… // 開始識別字符或字符串的實(shí)質(zhì)內(nèi)容了 if (terminator == '"') // 識別到“"”,說明是字符串 type = (*base == 'L' ? CPP_WSTRING : // 根據(jù)修飾符有無或種類來設(shè)置字符串的類型 *base == 'U' ? CPP_STRING32 : *base == 'u' ? (base[1] == '8' ? CPP_UTF8STRING : CPP_STRING16) : CPP_STRING); else if (terminator == '\'') // 識別到“\'”,說明是字符 type = (*base == 'L' ? CPP_WCHAR : // 根據(jù)修飾符有無或種類來設(shè)置字符的類型 *base == 'U' ? CPP_CHAR32 : *base == 'u' ? CPP_CHAR16 : CPP_CHAR); else terminator = '>', type = CPP_HEADER_NAME; // 還有一種情況,此時(shí)正在識別某個(gè)頭文件的名字,設(shè)置類型 for (;;) // 繼續(xù)向后遍歷字符或字符串的內(nèi)容 { cppchar_t c = *cur++; // 根據(jù)字符串或字符的詞法規(guī)定, “"”或“\”成對出現(xiàn) …… else if (c == terminator) // 找到另一個(gè)“"”或“\”,說明內(nèi)容識別完畢了 break; else if (c == '\n') // 遇到“\n”,就要準(zhǔn)備處理頭文件的名字了 { cur--; // cur指針往前退一個(gè)字符 if (terminator == '>') // 退了一個(gè)正好是“>”,說明頭文件的名字識別完畢 { token->type = CPP_LESS; return; } …… } …… }…… }
- C語言程序設(shè)計(jì)教程
- TypeScript Essentials
- 數(shù)據(jù)庫程序員面試筆試真題與解析
- C# Programming Cookbook
- Vue.js 3.x從入門到精通(視頻教學(xué)版)
- 精通搜索分析
- Visual Basic程序設(shè)計(jì)與應(yīng)用實(shí)踐教程
- CKA/CKAD應(yīng)試教程:從Docker到Kubernetes完全攻略
- SAS數(shù)據(jù)統(tǒng)計(jì)分析與編程實(shí)踐
- Hands-On Functional Programming with TypeScript
- 大數(shù)據(jù)分析與應(yīng)用實(shí)戰(zhàn):統(tǒng)計(jì)機(jī)器學(xué)習(xí)之?dāng)?shù)據(jù)導(dǎo)向編程
- Python機(jī)器學(xué)習(xí):預(yù)測分析核心算法
- 計(jì)算機(jī)應(yīng)用基礎(chǔ)教程(Windows 7+Office 2010)
- 響應(yīng)式Web設(shè)計(jì):HTML5和CSS3實(shí)戰(zhàn)(第2版)
- 計(jì)算機(jī)應(yīng)用技能實(shí)訓(xùn)教程