第88集:Yacc 高级特性

核心知识点讲解

语义值

在Yacc中,语义值是语法分析过程中传递信息的关键机制。默认情况下,Yacc使用一个整型变量yylval来传递词法单元的值,但对于复杂的语法分析,我们需要更灵活的类型支持。

%union 声明

%union声明允许我们定义一个包含多种类型的联合,从而使yylval能够存储不同类型的值:

%union {
    int int_val;
    double double_val;
    char* string_val;
    struct ast_node* ast_val;
    struct symbol* sym_val;
}

%token 和 %type 声明

在定义了%union之后,我们可以使用尖括号语法为token和非终结符指定具体的类型:

%token <int_val> NUMBER
%token <string_val> IDENTIFIER
%token <string_val> STRING

%type <double_val> expr term factor
%type <ast_val> stmt program
%type <sym_val> declaration

错误产生式

错误产生式是Yacc中处理语法错误的高级机制,它允许我们在文法中显式定义错误恢复规则:

stmt: expr SEMI
    | error SEMI { yyerrok; } // 错误产生式,跳过到下一个分号
    ;

expr: expr '+' term
    | expr '-' term
    | term
    | error '+' term { $$ = $3; } // 错误产生式,从错误中恢复
    ;

其中:

  • error是Yacc的特殊标记,表示预期的语法错误位置
  • yyerrok是Yacc的内置函数,用于重置错误状态

多文件组织

对于复杂的编译器项目,我们通常需要将代码分散到多个文件中,以提高可维护性。Yacc支持多文件组织的方式:

  1. 将词法分析和语法分析分离

    • lexer.l:词法分析器
    • parser.y:语法分析器
    • ast.h/ast.c:抽象语法树定义和操作
    • symbol.h/symbol.c:符号表定义和操作
  2. 使用头文件共享声明

    • 创建parser.h文件,包含token定义和共享函数声明
    • lexer.l和其他文件中包含这个头文件
  3. Makefile管理编译

    CC = gcc
    CFLAGS = -Wall -g
    
    all: compiler
    
    lex.yy.c: lexer.l parser.h
        lex lexer.l
    
    y.tab.c y.tab.h: parser.y
        yacc -d parser.y
    
    parser.h: y.tab.h
        cp y.tab.h parser.h
    
    compiler: lex.yy.c y.tab.c ast.c symbol.c
        $(CC) $(CFLAGS) -o compiler lex.yy.c y.tab.c ast.c symbol.c
    
    clean:
        rm -f lex.yy.c y.tab.c y.tab.h parser.h compiler *.o

其他高级特性

行号和位置信息

在语法分析过程中,跟踪源代码的行号和位置信息对于生成有意义的错误消息非常重要:

%{#include <stdio.h>
#include "location.h"

int yylineno = 1;
void yyerror(const char* s);
%}

%union {
    int int_val;
    char* string_val;
    struct location loc;
}

%token <int_val> NUMBER
%token <string_val> IDENTIFIER
%token <loc> ERROR

%%

program: /* 空规则 */
       | program stmt
       ;

stmt: expr ';' {
    printf("表达式值: %d\n", $1);
    printf("位置: 行 %d\n", @1.first_line);
}

%%

void yyerror(const char* s) {
    fprintf(stderr, "错误: %s 在第 %d 行\n", s, yylineno);
}

条件编译

Yacc支持条件编译,可以根据不同的条件生成不同的解析器代码:

%ifdef DEBUG
    #define YYDEBUG 1
%endif

%{#include <stdio.h>
#ifdef DEBUG
    extern int yydebug;
#endif
%}

%%

/* 文法规则 */

%%

int main() {
#ifdef DEBUG
    yydebug = 1; // 启用调试模式
#endif
    return yyparse();
}

自定义解析器名称

默认情况下,Yacc生成的解析器函数名为yyparse,但我们可以通过%name-prefix指令自定义:

%name-prefix "calc_"

%{#include <stdio.h>
void calc_error(const char* s);
int calc_lex();
%}

%%

/* 文法规则 */

%%

void calc_error(const char* s) {
    fprintf(stderr, "错误: %s\n", s);
}

int main() {
    return calc_parse(); // 使用自定义名称
}

实用案例分析

案例:支持多种数据类型的表达式解析器

让我们创建一个支持多种数据类型的表达式解析器,包括整数、浮点数和字符串:

/* 多类型表达式解析器 */
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_VARS 100

struct var {
    char name[32];
    int type; // 0: 整数, 1: 浮点数, 2: 字符串
    union {
        int int_val;
        double double_val;
        char* string_val;
    } value;
} variables[MAX_VARS];

int var_count = 0;

int find_var(const char* name) {
    for (int i = 0; i < var_count; i++) {
        if (strcmp(variables[i].name, name) == 0) {
            return i;
        }
    }
    return -1;
}

struct var* get_var(const char* name) {
    int idx = find_var(name);
    if (idx == -1) {
        fprintf(stderr, "变量未定义: %s\n", name);
        return NULL;
    }
    return &variables[idx];
}

void set_var(const char* name, int type, void* value) {
    int idx = find_var(name);
    if (idx == -1) {
        if (var_count >= MAX_VARS) {
            fprintf(stderr, "变量数量超过限制\n");
            return;
        }
        strcpy(variables[var_count].name, name);
        variables[var_count].type = type;
        idx = var_count++;
    } else {
        variables[idx].type = type;
    }
    
    switch (type) {
        case 0: // 整数
            variables[idx].value.int_val = *(int*)value;
            break;
        case 1: // 浮点数
            variables[idx].value.double_val = *(double*)value;
            break;
        case 2: // 字符串
            if (variables[idx].value.string_val) {
                free(variables[idx].value.string_val);
            }
            variables[idx].value.string_val = strdup((char*)value);
            break;
    }
}

void print_value(int type, void* value) {
    switch (type) {
        case 0: // 整数
            printf("%d", *(int*)value);
            break;
        case 1: // 浮点数
            printf("%g", *(double*)value);
            break;
        case 2: // 字符串
            printf("%s", (char*)value);
            break;
    }
}

int yylex();
void yyerror(const char* s);
%}

%union {
    int int_val;
    double double_val;
    char* string_val;
    struct var* var_val;
    struct {
        int type;
        union {
            int int_val;
            double double_val;
            char* string_val;
        } value;
    } expr_val;
}

%token <int_val> INT_NUMBER
%token <double_val> DOUBLE_NUMBER
%token <string_val> STRING
%token <string_val> IDENTIFIER
%token ASSIGN
%token SEMI
%token EOL
%token INT DOUBLE STRING_TYPE

%type <expr_val> expr
%type <var_val> declaration

%%

program: /* 空规则 */
       | program stmt EOL
       | program EOL
       ;

stmt: declaration
    | IDENTIFIER ASSIGN expr SEMI {
        struct var* var = get_var($1);
        if (var) {
            var->type = $3.type;
            switch ($3.type) {
                case 0: var->value.int_val = $3.value.int_val; break;
                case 1: var->value.double_val = $3.value.double_val; break;
                case 2: 
                    if (var->value.string_val) free(var->value.string_val);
                    var->value.string_val = $3.value.string_val; 
                    break;
            }
            printf("%s = ", $1);
            print_value($3.type, &$3.value);
            printf("\n");
        }
    }
    | expr SEMI {
        printf("结果: ");
        print_value($1.type, &$1.value);
        printf("\n");
    }
    | error SEMI { yyerrok; printf("跳过错误语句\n"); }
    ;

declaration: INT IDENTIFIER SEMI {
    int value = 0;
    set_var($2, 0, &value);
    printf("声明整数变量: %s\n", $2);
}
| DOUBLE IDENTIFIER SEMI {
    double value = 0.0;
    set_var($2, 1, &value);
    printf("声明浮点数变量: %s\n", $2);
}
| STRING_TYPE IDENTIFIER SEMI {
    char* value = strdup("");
    set_var($2, 2, value);
    free(value);
    printf("声明字符串变量: %s\n", $2);
}
;

expr: INT_NUMBER {
    $$.type = 0;
    $$.value.int_val = $1;
}
| DOUBLE_NUMBER {
    $$.type = 1;
    $$.value.double_val = $1;
}
| STRING {
    $$.type = 2;
    $$.value.string_val = $1;
}
| IDENTIFIER {
    struct var* var = get_var($1);
    if (var) {
        $$.type = var->type;
        switch (var->type) {
            case 0: $$.value.int_val = var->value.int_val; break;
            case 1: $$.value.double_val = var->value.double_val; break;
            case 2: $$.value.string_val = var->value.string_val; break;
        }
    } else {
        $$.type = 0;
        $$.value.int_val = 0;
    }
}
| expr '+' expr {
    if ($1.type == 2 || $3.type == 2) {
        // 字符串连接
        $$.type = 2;
        char* result = malloc(strlen($1.value.string_val) + strlen($3.value.string_val) + 1);
        strcpy(result, $1.value.string_val);
        strcat(result, $3.value.string_val);
        $$.value.string_val = result;
    } else if ($1.type == 1 || $3.type == 1) {
        // 浮点数加法
        $$.type = 1;
        double val1 = ($1.type == 1) ? $1.value.double_val : $1.value.int_val;
        double val3 = ($3.type == 1) ? $3.value.double_val : $3.value.int_val;
        $$.value.double_val = val1 + val3;
    } else {
        // 整数加法
        $$.type = 0;
        $$.value.int_val = $1.value.int_val + $3.value.int_val;
    }
}
| expr '-' expr {
    if ($1.type == 1 || $3.type == 1) {
        // 浮点数减法
        $$.type = 1;
        double val1 = ($1.type == 1) ? $1.value.double_val : $1.value.int_val;
        double val3 = ($3.type == 1) ? $3.value.double_val : $3.value.int_val;
        $$.value.double_val = val1 - val3;
    } else {
        // 整数减法
        $$.type = 0;
        $$.value.int_val = $1.value.int_val - $3.value.int_val;
    }
}
| expr '*' expr {
    if ($1.type == 1 || $3.type == 1) {
        // 浮点数乘法
        $$.type = 1;
        double val1 = ($1.type == 1) ? $1.value.double_val : $1.value.int_val;
        double val3 = ($3.type == 1) ? $3.value.double_val : $3.value.int_val;
        $$.value.double_val = val1 * val3;
    } else {
        // 整数乘法
        $$.type = 0;
        $$.value.int_val = $1.value.int_val * $3.value.int_val;
    }
}
| expr '/' expr {
    // 除法总是返回浮点数
    $$.type = 1;
    double val1 = ($1.type == 1) ? $1.value.double_val : $1.value.int_val;
    double val3 = ($3.type == 1) ? $3.value.double_val : $3.value.int_val;
    $$.value.double_val = val1 / val3;
}
| '(' expr ')' {
    $$.type = $2.type;
    $$.value = $2.value;
}
;

%%

void yyerror(const char* s) {
    fprintf(stderr, "错误: %s\n", s);
}

int main() {
    printf("多类型表达式解析器\n");
    printf("支持整数、浮点数和字符串类型\n");
    printf("示例: int x; x = 5 + 3.5; string s; s = \"Hello\" + \" World\";\n");
    return yyparse();
}

对应的Lex文件:

/* 词法分析器 */
%{
#include "y.tab.h"
#include <string.h>
%}

%%

[0-9]+      { yylval.int_val = atoi(yytext); return INT_NUMBER; }
[0-9]+\.[0-9]+ { yylval.double_val = atof(yytext); return DOUBLE_NUMBER; }
"\"([^\"\\]|\\.)*\"" { 
    // 处理字符串,去掉引号
    char* str = strdup(yytext + 1);
    str[strlen(str) - 1] = '\0';
    yylval.string_val = str;
    return STRING;
}
"int"       { return INT; }
"double"    { return DOUBLE; }
"string"    { return STRING_TYPE; }
[a-zA-Z_][a-zA-Z0-9_]* { yylval.string_val = strdup(yytext); return IDENTIFIER; }
"="         { return ASSIGN; }
";"         { return SEMI; }
"+"         { return '+'; }
"-"         { return '-'; }
"*"         { return '*'; }
"/"         { return '/'; }
"("         { return '('; }
")"         { return ')'; }
"\n"        { return EOL; }
[ \t]       { /* 忽略空白字符 */ }
.

%%

int yywrap() {
    return 1;
}

案例:多文件组织的编译器前端

让我们创建一个多文件组织的编译器前端示例,包括词法分析、语法分析、抽象语法树和符号表:

文件结构

compiler/
├── Makefile
├── lexer.l
├── parser.y
├── ast.h
├── ast.c
├── symbol.h
├── symbol.c
└── main.c

ast.h

/* 抽象语法树定义 */
#ifndef AST_H
#define AST_H

typedef enum {
    AST_ADD,
    AST_SUB,
    AST_MUL,
    AST_DIV,
    AST_NUMBER,
    AST_IDENTIFIER,
    AST_ASSIGN,
    AST_DECLARATION,
    AST_PROGRAM,
    AST_STMT_LIST
} AstType;

typedef struct ast_node {
    AstType type;
    union {
        int number_val;
        char* identifier_val;
        struct {
            struct ast_node* left;
            struct ast_node* right;
        } binary_op;
        struct {
            char* identifier;
            struct ast_node* expression;
        } assign;
        struct {
            char* type;
            char* identifier;
        } declaration;
        struct {
            struct ast_node* head;
            struct ast_node* tail;
        } list;
    } data;
} AstNode;

AstNode* new_ast_node(AstType type);
AstNode* new_binary_op(AstType type, AstNode* left, AstNode* right);
AstNode* new_number(int value);
AstNode* new_identifier(const char* name);
AstNode* new_assign(const char* identifier, AstNode* expression);
AstNode* new_declaration(const char* type, const char* identifier);
AstNode* new_program();
AstNode* new_stmt_list(AstNode* head, AstNode* tail);
void add_stmt_to_list(AstNode* list, AstNode* stmt);
void free_ast(AstNode* node);
void print_ast(AstNode* node, int indent);

#endif

ast.c

/* 抽象语法树实现 */
#include "ast.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

AstNode* new_ast_node(AstType type) {
    AstNode* node = (AstNode*)malloc(sizeof(AstNode));
    node->type = type;
    return node;
}

AstNode* new_binary_op(AstType type, AstNode* left, AstNode* right) {
    AstNode* node = new_ast_node(type);
    node->data.binary_op.left = left;
    node->data.binary_op.right = right;
    return node;
}

AstNode* new_number(int value) {
    AstNode* node = new_ast_node(AST_NUMBER);
    node->data.number_val = value;
    return node;
}

AstNode* new_identifier(const char* name) {
    AstNode* node = new_ast_node(AST_IDENTIFIER);
    node->data.identifier_val = strdup(name);
    return node;
}

AstNode* new_assign(const char* identifier, AstNode* expression) {
    AstNode* node = new_ast_node(AST_ASSIGN);
    node->data.assign.identifier = strdup(identifier);
    node->data.assign.expression = expression;
    return node;
}

AstNode* new_declaration(const char* type, const char* identifier) {
    AstNode* node = new_ast_node(AST_DECLARATION);
    node->data.declaration.type = strdup(type);
    node->data.declaration.identifier = strdup(identifier);
    return node;
}

AstNode* new_program() {
    return new_ast_node(AST_PROGRAM);
}

AstNode* new_stmt_list(AstNode* head, AstNode* tail) {
    AstNode* node = new_ast_node(AST_STMT_LIST);
    node->data.list.head = head;
    node->data.list.tail = tail;
    return node;
}

void add_stmt_to_list(AstNode* list, AstNode* stmt) {
    if (!list->data.list.head) {
        list->data.list.head = stmt;
        list->data.list.tail = stmt;
    } else {
        // 简单实现,实际应该使用链表
        list->data.list.tail = stmt;
    }
}

void free_ast(AstNode* node) {
    if (!node) return;
    
    switch (node->type) {
        case AST_ADD:
        case AST_SUB:
        case AST_MUL:
        case AST_DIV:
            free_ast(node->data.binary_op.left);
            free_ast(node->data.binary_op.right);
            break;
        case AST_IDENTIFIER:
            free(node->data.identifier_val);
            break;
        case AST_ASSIGN:
            free(node->data.assign.identifier);
            free_ast(node->data.assign.expression);
            break;
        case AST_DECLARATION:
            free(node->data.declaration.type);
            free(node->data.declaration.identifier);
            break;
        case AST_STMT_LIST:
            free_ast(node->data.list.head);
            free_ast(node->data.list.tail);
            break;
        default:
            break;
    }
    
    free(node);
}

void print_ast(AstNode* node, int indent) {
    if (!node) return;
    
    for (int i = 0; i < indent; i++) {
        printf("  ");
    }
    
    switch (node->type) {
        case AST_ADD:
            printf("ADD\n");
            print_ast(node->data.binary_op.left, indent + 1);
            print_ast(node->data.binary_op.right, indent + 1);
            break;
        case AST_SUB:
            printf("SUB\n");
            print_ast(node->data.binary_op.left, indent + 1);
            print_ast(node->data.binary_op.right, indent + 1);
            break;
        case AST_MUL:
            printf("MUL\n");
            print_ast(node->data.binary_op.left, indent + 1);
            print_ast(node->data.binary_op.right, indent + 1);
            break;
        case AST_DIV:
            printf("DIV\n");
            print_ast(node->data.binary_op.left, indent + 1);
            print_ast(node->data.binary_op.right, indent + 1);
            break;
        case AST_NUMBER:
            printf("NUMBER: %d\n", node->data.number_val);
            break;
        case AST_IDENTIFIER:
            printf("IDENTIFIER: %s\n", node->data.identifier_val);
            break;
        case AST_ASSIGN:
            printf("ASSIGN: %s\n", node->data.assign.identifier);
            print_ast(node->data.assign.expression, indent + 1);
            break;
        case AST_DECLARATION:
            printf("DECLARATION: %s %s\n", node->data.declaration.type, node->data.declaration.identifier);
            break;
        case AST_PROGRAM:
            printf("PROGRAM\n");
            break;
        case AST_STMT_LIST:
            printf("STMT_LIST\n");
            print_ast(node->data.list.head, indent + 1);
            print_ast(node->data.list.tail, indent + 1);
            break;
        default:
            printf("UNKNOWN\n");
            break;
    }
}

symbol.h

/* 符号表定义 */
#ifndef SYMBOL_H
#define SYMBOL_H

typedef struct symbol {
    char name[32];
    char type[16];
    int initialized;
    struct symbol* next;
} Symbol;

typedef struct symbol_table {
    Symbol* symbols;
    struct symbol_table* parent;
} SymbolTable;

SymbolTable* new_symbol_table(SymbolTable* parent);
void add_symbol(SymbolTable* table, const char* name, const char* type);
Symbol* find_symbol(SymbolTable* table, const char* name);
void free_symbol_table(SymbolTable* table);
void print_symbol_table(SymbolTable* table, int indent);

#endif

symbol.c

/* 符号表实现 */
#include "symbol.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

SymbolTable* new_symbol_table(SymbolTable* parent) {
    SymbolTable* table = (SymbolTable*)malloc(sizeof(SymbolTable));
    table->symbols = NULL;
    table->parent = parent;
    return table;
}

void add_symbol(SymbolTable* table, const char* name, const char* type) {
    Symbol* symbol = (Symbol*)malloc(sizeof(Symbol));
    strcpy(symbol->name, name);
    strcpy(symbol->type, type);
    symbol->initialized = 0;
    symbol->next = table->symbols;
    table->symbols = symbol;
}

Symbol* find_symbol(SymbolTable* table, const char* name) {
    while (table) {
        Symbol* symbol = table->symbols;
        while (symbol) {
            if (strcmp(symbol->name, name) == 0) {
                return symbol;
            }
            symbol = symbol->next;
        }
        table = table->parent;
    }
    return NULL;
}

void free_symbol_table(SymbolTable* table) {
    if (!table) return;
    
    Symbol* symbol = table->symbols;
    while (symbol) {
        Symbol* next = symbol->next;
        free(symbol);
        symbol = next;
    }
    
    free(table);
}

void print_symbol_table(SymbolTable* table, int indent) {
    if (!table) return;
    
    for (int i = 0; i < indent; i++) {
        printf("  ");
    }
    printf("SYMBOL_TABLE\n");
    
    Symbol* symbol = table->symbols;
    while (symbol) {
        for (int i = 0; i < indent + 1; i++) {
            printf("  ");
        }
        printf("%s: %s (initialized: %d)\n", symbol->name, symbol->type, symbol->initialized);
        symbol = symbol->next;
    }
    
    if (table->parent) {
        print_symbol_table(table->parent, indent + 1);
    }
}

parser.y

/* 语法分析器 */
%{
#include <stdio.h>
#include "ast.h"
#include "symbol.h"

SymbolTable* current_symbol_table;

int yylex();
void yyerror(const char* s);
%}

%union {
    int int_val;
    char* string_val;
    AstNode* ast_val;
}

%token <int_val> NUMBER
%token <string_val> IDENTIFIER
%token <string_val> TYPE
%token ASSIGN
%token SEMI
%token EOL

%type <ast_val> program stmt expr declaration assignment

%%

program: /* 空规则 */ { $$ = new_program(); }
       | program stmt { /* 添加语句到程序 */ }
       ;

stmt: declaration
    | assignment
    | expr SEMI { $$ = $1; }
    | error SEMI { yyerrok; $$ = NULL; }
    ;

declaration: TYPE IDENTIFIER SEMI {
    add_symbol(current_symbol_table, $2, $1);
    $$ = new_declaration($1, $2);
    printf("声明变量: %s %s\n", $1, $2);
}
;

assignment: IDENTIFIER ASSIGN expr SEMI {
    Symbol* symbol = find_symbol(current_symbol_table, $1);
    if (symbol) {
        symbol->initialized = 1;
        $$ = new_assign($1, $3);
        printf("赋值: %s = <expression>\n", $1);
    } else {
        fprintf(stderr, "错误: 变量未声明: %s\n", $1);
        $$ = NULL;
    }
}
;

expr: NUMBER { $$ = new_number($1); }
    | IDENTIFIER {
        Symbol* symbol = find_symbol(current_symbol_table, $1);
        if (symbol) {
            $$ = new_identifier($1);
        } else {
            fprintf(stderr, "错误: 变量未声明: %s\n", $1);
            $$ = NULL;
        }
    }
    | expr '+' expr { $$ = new_binary_op(AST_ADD, $1, $3); }
    | expr '-' expr { $$ = new_binary_op(AST_SUB, $1, $3); }
    | expr '*' expr { $$ = new_binary_op(AST_MUL, $1, $3); }
    | expr '/' expr { $$ = new_binary_op(AST_DIV, $1, $3); }
    | '(' expr ')' { $$ = $2; }
    ;

%%

void yyerror(const char* s) {
    fprintf(stderr, "错误: %s\n", s);
}

lexer.l

/* 词法分析器 */
%{
#include "y.tab.h"
#include <string.h>
%}

%%

[0-9]+      { yylval.int_val = atoi(yytext); return NUMBER; }
"int"       { yylval.string_val = strdup(yytext); return TYPE; }
"double"    { yylval.string_val = strdup(yytext); return TYPE; }
"float"     { yylval.string_val = strdup(yytext); return TYPE; }
"char"      { yylval.string_val = strdup(yytext); return TYPE; }
[a-zA-Z_][a-zA-Z0-9_]* { yylval.string_val = strdup(yytext); return IDENTIFIER; }
"="         { return ASSIGN; }
";"         { return SEMI; }
"+"         { return '+'; }
"-"         { return '-'; }
"*"         { return '*'; }
"/"         { return '/'; }
"("         { return '('; }
")"         { return ')'; }
"\n"        { return EOL; }
[ \t]       { /* 忽略空白字符 */ }
.

%%

int yywrap() {
    return 1;
}

main.c

/* 主程序 */
#include <stdio.h>
#include "ast.h"
#include "symbol.h"

extern int yyparse();
extern int yydebug;

int main() {
    // 初始化符号表
    current_symbol_table = new_symbol_table(NULL);
    
    // 启用调试模式
    // yydebug = 1;
    
    printf("多文件组织的编译器前端\n");
    printf("支持变量声明和赋值\n");
    printf("示例: int x; x = 5 + 3;\n");
    
    // 解析输入
    yyparse();
    
    // 打印符号表
    printf("\n符号表:\n");
    print_symbol_table(current_symbol_table, 0);
    
    // 释放资源
    free_symbol_table(current_symbol_table);
    
    return 0;
}

Makefile

CC = gcc
CFLAGS = -Wall -g

all: compiler

lex.yy.c: lexer.l parser.h
    lex lexer.l

y.tab.c y.tab.h: parser.y
    yacc -d parser.y

parser.h: y.tab.h
    cp y.tab.h parser.h

compiler: lex.yy.c y.tab.c ast.c symbol.c main.c
    $(CC) $(CFLAGS) -o compiler lex.yy.c y.tab.c ast.c symbol.c main.c

clean:
    rm -f lex.yy.c y.tab.c y.tab.h parser.h compiler *.o

代码优化建议

  1. 内存管理优化

    • 使用内存池管理AST节点和符号表项,减少内存分配开销
    • 实现引用计数或垃圾收集机制,避免内存泄漏
    • 对于字符串常量,使用字符串池避免重复分配
  2. 错误处理优化

    • 实现更详细的错误信息,包括错误位置、预期的token和实际的token
    • 添加错误恢复规则,使解析器在遇到错误后能够继续解析
    • 使用yyerror函数的扩展版本,提供更丰富的错误上下文
  3. 性能优化

    • 对于大型文法,使用Yacc的-v选项生成分析报告,识别和解决冲突
    • 合理设计文法规则,避免左递归和二义性
    • 考虑使用Bison的一些高级特性,如GLR解析器,处理更复杂的文法
  4. 可维护性优化

    • 将复杂的语义动作分离到单独的函数中
    • 使用注释清晰地说明文法规则的含义
    • 模块化设计,将不同功能的语法规则分组
    • 考虑使用Bison的%define指令,自定义生成的解析器的行为
  5. 扩展性优化

    • 设计灵活的AST结构,支持未来的语言特性扩展
    • 实现符号表的作用域管理,支持嵌套作用域
    • 考虑添加类型检查和语义分析的框架

总结

本集我们深入学习了Yacc的高级特性,包括:

  1. 语义值的高级用法,包括%union声明和类型指定
  2. 错误产生式的使用,实现更健壮的错误处理
  3. 多文件组织的编译器前端设计
  4. 实际案例:多类型表达式解析器和多文件编译器前端

通过这些知识,你已经可以构建更复杂、更健壮的语法分析器,处理包括多种数据类型、嵌套作用域在内的各种复杂语法结构。在后续的课程中,我们将学习如何使用这些技术构建完整的编译器前端,并与后端代码生成相结合。

« 上一篇 Yacc 语法规则 下一篇 » 构造抽象语法树(AST)