第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支持多文件组织的方式:
将词法分析和语法分析分离:
lexer.l:词法分析器parser.y:语法分析器ast.h/ast.c:抽象语法树定义和操作symbol.h/symbol.c:符号表定义和操作
使用头文件共享声明:
- 创建
parser.h文件,包含token定义和共享函数声明 - 在
lexer.l和其他文件中包含这个头文件
- 创建
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.cast.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);
#endifast.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);
#endifsymbol.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代码优化建议
内存管理优化:
- 使用内存池管理AST节点和符号表项,减少内存分配开销
- 实现引用计数或垃圾收集机制,避免内存泄漏
- 对于字符串常量,使用字符串池避免重复分配
错误处理优化:
- 实现更详细的错误信息,包括错误位置、预期的token和实际的token
- 添加错误恢复规则,使解析器在遇到错误后能够继续解析
- 使用
yyerror函数的扩展版本,提供更丰富的错误上下文
性能优化:
- 对于大型文法,使用Yacc的
-v选项生成分析报告,识别和解决冲突 - 合理设计文法规则,避免左递归和二义性
- 考虑使用Bison的一些高级特性,如GLR解析器,处理更复杂的文法
- 对于大型文法,使用Yacc的
可维护性优化:
- 将复杂的语义动作分离到单独的函数中
- 使用注释清晰地说明文法规则的含义
- 模块化设计,将不同功能的语法规则分组
- 考虑使用Bison的
%define指令,自定义生成的解析器的行为
扩展性优化:
- 设计灵活的AST结构,支持未来的语言特性扩展
- 实现符号表的作用域管理,支持嵌套作用域
- 考虑添加类型检查和语义分析的框架
总结
本集我们深入学习了Yacc的高级特性,包括:
- 语义值的高级用法,包括
%union声明和类型指定 - 错误产生式的使用,实现更健壮的错误处理
- 多文件组织的编译器前端设计
- 实际案例:多类型表达式解析器和多文件编译器前端
通过这些知识,你已经可以构建更复杂、更健壮的语法分析器,处理包括多种数据类型、嵌套作用域在内的各种复杂语法结构。在后续的课程中,我们将学习如何使用这些技术构建完整的编译器前端,并与后端代码生成相结合。