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

posted @ 2020-01-06 09:41  Guloo  阅读(1301)  评论(0编辑  收藏  举报