上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~9、A~F、a~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~9、A~F、a~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; } …… } …… }…… }