#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX_BUF 512
#define MAX_ID 128

/* token 种类枚举,词法扫描输出的记号类型 */
enum {
    SYN_INT,
    SYN_FLOAT,
    SYN_IF,
    SYN_WHILE,
    SYN_PRINT,
    SYN_ID,
    SYN_NUM,
    SYN_EQ,
    SYN_PLUS,
    SYN_MINUS,
    SYN_STAR,
    SYN_SLASH,
    SYN_LPAREN,
    SYN_RPAREN,
    SYN_LBRACE,
    SYN_RBRACE,
    SYN_SEMI,
    SYN_END,
    SYN_ERR
};

/* AST 结点类型枚举,区分不同语法结构 */
typedef enum {
    NODE_PROG,
    NODE_STMTLIST,
    NODE_DECL,
    NODE_ASSIGN,
    NODE_IF,
    NODE_WHILE,
    NODE_PRINT,
    NODE_BLOCK,
    NODE_BINOP,
    NODE_ID,
    NODE_NUM,
    NODE_TYPE
} NodeType;

/* 抽象语法树结点结构体 */
typedef struct AstNode {
    NodeType type;
    char val[MAX_ID];
    struct AstNode *lchild;
    struct AstNode *rchild;
    struct AstNode *next;
} AstNode;

/* 记号名称数组,报错时用于打印记号名字 */
const char *syn_name[] = {
    "int", "float", "if", "while", "print", "ID", "NUM",
    "=", "+", "-", "*", "/", "(", ")", "{", "}", ";", "$", "ERR"
};

char input[MAX_BUF];    /* 存放输入的源代码 */
char token[MAX_ID];     /* 当前识别出的单词字符串 */
int p_input;            /* 源码缓冲区字符指针 */
int p_token;            /* token 字符串填充指针 */
char ch;                /* 当前正在处理的字符 */
int syn;                /* 当前记号类型编号 */
int line_no;            /* 当前行号,用于报错定位 */
int error_cnt;          /* 语法错误总数 */

/* 函数声明:语法分析入口与各个非终结符分析函数 */
AstNode *program(void);
AstNode *stmt_list(void);
AstNode *stmt(void);
AstNode *decl_stmt(void);
AstNode *type_node(void);
AstNode *assign_stmt(void);
AstNode *if_stmt(void);
AstNode *while_stmt(void);
AstNode *print_stmt(void);
AstNode *block_stmt(void);
AstNode *expr(void);
AstNode *expr_prime(AstNode *left);
AstNode *term(void);
AstNode *term_prime(AstNode *left);
AstNode *factor(void);

void scaner(void);
void match(int expect);
void panic_recover(void);
AstNode *new_node(NodeType t, const char *s);
void print_tree(AstNode *root, int depth);

/**
 * @brief 创建一个抽象语法树结点
 * @param t 结点类型
 * @param s 结点携带文本
 * @return 新结点指针
 */
AstNode *new_node(NodeType t, const char *s)
{
    AstNode *p = (AstNode *)malloc(sizeof(AstNode));
    p->type = t;
    strncpy(p->val, s, MAX_ID - 1);
    p->val[MAX_ID - 1] = '\0';
    p->lchild = NULL;
    p->rchild = NULL;
    p->next = NULL;
    return p;
}

/**
 * @brief 词法扫描器:读取字符,生成下一个 token
 */
void scaner(void)
{
    p_token = 0;
    ch = input[p_input++];

    /* 跳过空白字符,换行则行号 +1 */
    while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
    {
        if (ch == '\n')
            line_no++;
        ch = input[p_input++];
    }

    /* 识别标识符 / 关键字 */
    if (isalpha((unsigned char)ch) || ch == '_')
    {
        while (isalnum((unsigned char)ch) || ch == '_')
        {
            token[p_token++] = ch;
            ch = input[p_input++];
        }
        p_input--;
        token[p_token] = '\0';

        if (strcmp(token, "int") == 0)
            syn = SYN_INT;
        else if (strcmp(token, "float") == 0)
            syn = SYN_FLOAT;
        else if (strcmp(token, "if") == 0)
            syn = SYN_IF;
        else if (strcmp(token, "while") == 0)
            syn = SYN_WHILE;
        else if (strcmp(token, "print") == 0)
            syn = SYN_PRINT;
        else
            syn = SYN_ID;
        return;
    }

    /* 识别无符号数字 */
    if (isdigit((unsigned char)ch))
    {
        while (isdigit((unsigned char)ch))
        {
            token[p_token++] = ch;
            ch = input[p_input++];
        }
        p_input--;
        token[p_token] = '\0';
        syn = SYN_NUM;
        return;
    }

    /* 识别运算符、分隔符 */
    switch (ch)
    {
        case '=': syn = SYN_EQ; break;
        case '+': syn = SYN_PLUS; break;
        case '-': syn = SYN_MINUS; break;
        case '*': syn = SYN_STAR; break;
        case '/': syn = SYN_SLASH; break;
        case '(': syn = SYN_LPAREN; break;
        case ')': syn = SYN_RPAREN; break;
        case '{': syn = SYN_LBRACE; break;
        case '}': syn = SYN_RBRACE; break;
        case ';': syn = SYN_SEMI; break;
        case '\0': syn = SYN_END; break;
        default: syn = SYN_ERR; break;
    }
}

/**
 * @brief 记号匹配函数
 * @param expect 期望读到的记号类型
 */
void match(int expect)
{
    if (syn == expect)
    {
        scaner();
    }
    else
    {
        printf("【第 %d 行 语法错误】期望:%s,当前读到:%s\n",
               line_no, syn_name[expect], syn_name[syn]);
        error_cnt++;
        panic_recover();
    }
}

/**
 * @brief 恐慌模式错误恢复
 * 不断吃掉 token,直到碰到同步记号:; { } if while
 */
void panic_recover(void)
{
    while (syn != SYN_END)
    {
        if (syn == SYN_SEMI || syn == SYN_LBRACE || syn == SYN_RBRACE
            || syn == SYN_IF || syn == SYN_WHILE)
        {
            return;
        }
        scaner();
    }
}

/**
 * @brief 打印抽象语法树,depth 控制缩进
 * @param root 当前结点
 * @param depth 当前深度
 */
void print_tree(AstNode *root, int depth)
{
    if (root == NULL)
        return;
    for (int i = 0; i < depth; i++)
        printf("    ");

    switch (root->type)
    {
        case NODE_PROG:
            printf("program\n");
            break;
        case NODE_STMTLIST:
            printf("stmt_list\n");
            break;
        case NODE_DECL:
            printf("decl_stmt\n");
            break;
        case NODE_ASSIGN:
            printf("assign_stmt\n");
            break;
        case NODE_IF:
            printf("if_stmt\n");
            break;
        case NODE_WHILE:
            printf("while_stmt\n");
            break;
        case NODE_PRINT:
            printf("print_stmt\n");
            break;
        case NODE_BLOCK:
            printf("block\n");
            break;
        case NODE_BINOP:
            printf("binary %s\n", root->val);
            break;
        case NODE_ID:
            printf("id: %s\n", root->val);
            break;
        case NODE_NUM:
            printf("NUM: %s\n", root->val);
            break;
        case NODE_TYPE:
            printf("type: %s\n", root->val);
            break;
        default:
            printf("unknown\n");
    }

    print_tree(root->lchild, depth + 1);
    print_tree(root->rchild, depth + 1);
    print_tree(root->next, depth);
}

/**
 * @brief 入口:分析整个程序 program → stmt_list
 */
AstNode *program(void)
{
    AstNode *root = new_node(NODE_PROG, "program");
    root->lchild = stmt_list();
    return root;
}

/**
 * @brief 语句序列 stmt_list → stmt stmt_list | ε
 */
AstNode *stmt_list(void)
{
    AstNode *head = NULL, *tail = NULL;
    while (syn == SYN_INT || syn == SYN_FLOAT || syn == SYN_ID
           || syn == SYN_IF || syn == SYN_WHILE || syn == SYN_PRINT
           || syn == SYN_LBRACE)
    {
        AstNode *s = stmt();
        if (head == NULL)
        {
            head = new_node(NODE_STMTLIST, "");
            head->lchild = s;
            tail = head;
        }
        else
        {
            tail->next = new_node(NODE_STMTLIST, "");
            tail->next->lchild = s;
            tail = tail->next;
        }
    }
    return head;
}

/**
 * @brief 语句分发 stmt,根据首记号选择语句种类
 */
AstNode *stmt(void)
{
    if (syn == SYN_INT || syn == SYN_FLOAT)
        return decl_stmt();
    else if (syn == SYN_ID)
        return assign_stmt();
    else if (syn == SYN_IF)
        return if_stmt();
    else if (syn == SYN_WHILE)
        return while_stmt();
    else if (syn == SYN_PRINT)
        return print_stmt();
    else if (syn == SYN_LBRACE)
        return block_stmt();
    else
    {
        printf("【第 %d 行 语法错误】不能作为语句开头:%s\n", line_no, syn_name[syn]);
        error_cnt++;
        panic_recover();
        return NULL;
    }
}

/**
 * @brief 变量声明 decl_stmt → type ID ;
 */
AstNode *decl_stmt(void)
{
    AstNode *n = new_node(NODE_DECL, "");
    n->lchild = type_node();
    char buf[MAX_ID];
    strcpy(buf, token);
    match(SYN_ID);
    n->rchild = new_node(NODE_ID, buf);
    match(SYN_SEMI);
    return n;
}

/**
 * @brief 类型结点 type → int | float
 */
AstNode *type_node(void)
{
    char buf[MAX_ID];
    strcpy(buf, token);
    if (syn == SYN_INT)
        match(SYN_INT);
    else
        match(SYN_FLOAT);
    return new_node(NODE_TYPE, buf);
}

/**
 * @brief 赋值语句 assign_stmt → ID = expr ;
 */
AstNode *assign_stmt(void)
{
    AstNode *n = new_node(NODE_ASSIGN, "");
    char buf[MAX_ID];
    strcpy(buf, token);
    match(SYN_ID);
    n->lchild = new_node(NODE_ID, buf);
    match(SYN_EQ);
    n->rchild = expr();
    match(SYN_SEMI);
    return n;
}

/**
 * @brief if 语句 if_stmt → if ( expr ) stmt
 */
AstNode *if_stmt(void)
{
    AstNode *n = new_node(NODE_IF, "");
    match(SYN_IF);
    match(SYN_LPAREN);
    n->lchild = expr();
    match(SYN_RPAREN);
    n->rchild = stmt();
    return n;
}

/**
 * @brief while 语句 while_stmt → while ( expr ) stmt
 */
AstNode *while_stmt(void)
{
    AstNode *n = new_node(NODE_WHILE, "");
    match(SYN_WHILE);
    match(SYN_LPAREN);
    n->lchild = expr();
    match(SYN_RPAREN);
    n->rchild = stmt();
    return n;
}

/**
 * @brief print 输出语句 print_stmt → print ( expr ) ;
 */
AstNode *print_stmt(void)
{
    AstNode *n = new_node(NODE_PRINT, "");
    match(SYN_PRINT);
    match(SYN_LPAREN);
    n->lchild = expr();
    match(SYN_RPAREN);
    match(SYN_SEMI);
    return n;
}

/**
 * @brief 复合语句块 block_stmt → { stmt_list }
 */
AstNode *block_stmt(void)
{
    AstNode *n = new_node(NODE_BLOCK, "");
    match(SYN_LBRACE);
    n->lchild = stmt_list();
    match(SYN_RBRACE);
    return n;
}

/**
 * @brief 表达式入口 expr → term expr_prime
 */
AstNode *expr(void)
{
    AstNode *left = term();
    return expr_prime(left);
}

/**
 * @brief 处理加减,消除左递归
 */
AstNode *expr_prime(AstNode *left)
{
    if (syn == SYN_PLUS || syn == SYN_MINUS)
    {
        char op[MAX_ID];
        strcpy(op, syn_name[syn]);
        match(syn);
        AstNode *right = term();
        AstNode *bin = new_node(NODE_BINOP, op);
        bin->lchild = left;
        bin->rchild = right;
        return expr_prime(bin);
    }
    return left;
}

/**
 * @brief term → factor term_prime
 */
AstNode *term(void)
{
    AstNode *left = factor();
    return term_prime(left);
}

/**
 * @brief 处理乘除,消除左递归
 */
AstNode *term_prime(AstNode *left)
{
    if (syn == SYN_STAR || syn == SYN_SLASH)
    {
        char op[MAX_ID];
        strcpy(op, syn_name[syn]);
        match(syn);
        AstNode *right = factor();
        AstNode *bin = new_node(NODE_BINOP, op);
        bin->lchild = left;
        bin->rchild = right;
        return term_prime(bin);
    }
    return left;
}

/**
 * @brief factor → ( expr ) | ID | NUM
 */
AstNode *factor(void)
{
    if (syn == SYN_LPAREN)
    {
        match(SYN_LPAREN);
        AstNode *res = expr();
        match(SYN_RPAREN);
        return res;
    }
    else if (syn == SYN_ID)
    {
        char buf[MAX_ID];
        strcpy(buf, token);
        match(SYN_ID);
        return new_node(NODE_ID, buf);
    }
    else if (syn == SYN_NUM)
    {
        char buf[MAX_ID];
        strcpy(buf, token);
        match(SYN_NUM);
        return new_node(NODE_NUM, buf);
    }
    else
    {
        printf("【第 %d 行 语法错误】因子位置错误,需要 ID / NUM / (expr)\n", line_no, syn_name[syn]);
        error_cnt++;
        panic_recover();
        return NULL;
    }
}

/**
 * @brief 主函数:读取输入、初始化、启动分析、输出结果
 */
int main(void)
{
    printf("===== C子集递归下降语法分析器(AST + 错误恢复)=====\n");
    printf("输入源码,以 # 结束输入:\n");

    p_input = 0;
    line_no = 1;
    error_cnt = 0;
    scanf("%[^#]s", input);

    printf("\n【你输入的源代码】\n%s\n", input);

    p_input = 0;
    scaner();
    AstNode *tree_root = program();

    if (error_cnt == 0)
    {
        printf("\n>>> 语法分析成功,无语法错误!\n");
    }
    else
    {
        printf("\n>>> 语法分析结束,一共发现 %d 处语法错误!\n", error_cnt);
    }

    printf("\n===== 抽象语法树 =====\n");
    print_tree(tree_root, 0);

    return 0;
}

Intégrer sur un site web

Pour intégrer ce projet à votre site web, copiez le code suivant et collez-le dans le HTML de votre site web :