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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM