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 = '