C语言 一个简单的声明语义分析器
前面我们已经学会了如何理解声明:https://www.cnblogs.com/surplusvalue/p/12123398.html
事实上,在我们读源码的时候,或许也会遇到错综复杂的声明语句,为什么不写一个程序帮助我们理解呢?接下来我们将编写一个能够分析C语言的声明并把它们翻译成通俗语言的程序。为了简单起见,暂且忽略错误处理,而且在处理结构、枚举和联合时只简单地用“struct”、“enum”和“union”来代表它们的具体内容。最后,这个程序假定函数的括号内没有参数列表(实际上我们在分析的时候,参数列表也被忽略了)。
主要的数据结构是一个堆栈,我们从左向右读取,把各个标记依次压入堆栈,直到读到标识符为止。然后我们向右读入一个标记,也就是标识符右边的那个标记。接着观察标识符左边的那个标记(需要从堆栈中弹出)。数据结构大致如下:
struct token { char type; char string[MAXTOKENLEN]; }; /* 保存第一个标识之前的所有标记 */ struct token stack[MAXTOKENS]; /* 保存刚读入的那个标记 */ struct token t;
伪码如下:
实用程序---------- classify_string(字符串分类) 查看当前标记, 通过t.type返回一个值,内容为“type(类型)”,“qualifier(限定符)”或“identifier(标识符)” gettoken(取标记) 把下一个标记读入t.string 如果是字母数字组合,调用classify_string 否则,它必是一个单字符标记,t.type=该标记;用一个null结束t.string read_to_first_identifier(读至第一个标识符) 调用gettoken,并把标记压入到堆栈中,直到遇见第一个标识符。 Print“identifier is (标识符是)”,t.string 继续调用gettoken
解析程序----------
deal_with_function_args(处理函数参数)
当读取越过右括号‘)’后,打印“函数返回”
deal_with_arrays(处理函数数组)
当你读取“[size]”后,将其打印并继续向右读取。
deal_with_any_pointers(处理任何指针)
当你从堆栈中读取“*”时,打印“指向...的指针”并将其弹出堆栈。
deal_with_declarator(处理声明器)
if t.type is '[' deal_with_arrays
if t.type is '(' deal_with_function_args
deal_with_any_pointers
while 堆栈里还有东西
if 它是一个左括号'('
将其弹出堆栈,并调用gettoken;应该获得右括号')'
deal_with_declarator
else 将其弹出堆栈并打印它
主程序----------
main
read_to_first_identifier
deal_with_declarator
代码如下:
#pragma warning( disable : 4996) #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 //标识符identifier,修饰词qualifier,类型type(具体的例子见classify_string函数) enum type_tag { IDENTIFIER, QUALIFIER, TYPE }; struct token { char type; //词素的类型 char string[MAXTOKENLEN]; //保存词素的内容 }; int top = -1; //栈顶指针 //从左到右依次读取,把各个标记依次压入堆栈;保存第一个标识之前的所有标记的堆栈 struct token stack[MAXTOKENS]; struct token t; //保存刚读入的那个标记; //出栈和入栈操作 #define pop stack[top--] #define push(s) stack[++top]=s enum type_tag classify_string(void) //推断标识符的类型 { char *s = t.string; if (!strcmp(s, "const")) {//复习strcmp,两字符串相同,返回0 strcpy(s, "read-only"); return QUALIFIER; } if (!strcmp(s, "volatile")) return QUALIFIER; if (!strcmp(s, "void")) return TYPE; if (!strcmp(s, "char")) return TYPE; if (!strcmp(s, "signed")) return TYPE; if (!strcmp(s, "unsigned")) return TYPE; if (!strcmp(s, "short")) return TYPE; if (!strcmp(s, "int")) return TYPE; if (!strcmp(s, "long")) return TYPE; if (!strcmp(s, "float")) return TYPE; if (!strcmp(s, "double")) return TYPE; if (!strcmp(s, "struct")) return TYPE; if (!strcmp(s, "union")) return TYPE; if (!strcmp(s, "enum")) return TYPE; return IDENTIFIER; //上述结构都不是的情况,就是标识符 } void gettoken(void) // 读取下一个标记到 "t" { char *p = t.string; // 略过空白字符 while ((*p = getchar()) == ' '); if (isalnum(*p)) { // 读入的标识符以A-Z, 0-9开头 while (isalnum(*++p = getchar())); ungetc(*p, stdin); *p = '\0'; t.type = classify_string(); return; } if (*p == '*') { strcpy(t.string, "pointer to"); t.type = '*'; return; } t.string[1] = '\0'; t.type = *p; return; } // 理解所有分析过程的代码段 void read_to_first_identifier() { gettoken(); //把标记压入到堆栈,直到遇见第一个标识符 while (t.type != IDENTIFIER) { //只要没有遇见标识符,就将t压栈,继续取标记,直到遇见第一个标识符 push(t); gettoken(); } printf("%s is ", t.string); //这条分析语句的主语是while循环后得到的标识符 gettoken(); //继续向右读入一个标记,也就是标识符右边的标记 } void deal_with_arrays() { while (t.type == '[') { printf("array "); gettoken(); // 数字或 ']' if (isdigit(t.string[0])) { printf("0..%d ", atoi(t.string) - 1); gettoken(); // 读取 ']' } gettoken(); // 读取 ']' 之后的再一个标记 printf("of ");//这个数组是: } } void deal_with_function_args() { while (t.type != ')') { gettoken();//忽略函数的参数列表 } gettoken();//读到和左括号匹配的右括号,再读括号后的一个标记 printf("function returning "); } void deal_with_pointers() { while (stack[top].type == '*') { printf("%s ", pop.string);//* 号出栈,输出pointer to } } void deal_with_declarator() { // 处理标识符之后可能存在的数组或函数 switch (t.type) { case '[': deal_with_arrays(); break; case '(': deal_with_function_args(); } //观察标识符左边的标记 deal_with_pointers(); // 处理在读入到标识符之前压入到堆栈中的符号 while (top >= 0) { //直到栈空 if (stack[top].type == '(') { pop; gettoken(); // 读取 ')'之后的符号 deal_with_declarator();//这个可以对着例子学习,如果这对括号后面还有标记就继续处理 } else { printf("%s ", pop.string); } } } int main() { // 将标记压入堆栈中,直到遇见标识符 read_to_first_identifier(); // 处理标识符右边的那个标记 deal_with_declarator(); printf("\n"); return 0; }
运行结果:
错误解决
触发断点的解决办法 https://blog.csdn.net/leowinbow/article/details/82380252