一篇办理编译原理大作业,基于Flex、Bison设计编译器(含语法分析树和符号
1.工具简朴介绍Flex 和 Bison 是编译器开发中常用的两个工具,分别用于天生词法分析器和语法分析器。它们通常一起使用,共同完成源代码的词法分析和语法分析工作。
Flex:
Flex通过读取一个规则文件(通常是.l文件),这个文件中定义了一系列的模式和对应的动作。模式用于匹配输入文本中的特定字符序列,动作则是当匹配成功时要实行的操作。Flex会根据这些规则天生一个词法分析器的C代码,这个天生的词法分析器可以或许辨认输入文本中的词法单元,并实行相应的动作,如返回一个token给语法分析器。
输入文件:以 .l 为扩展名,包罗三个主要部分:
头部定义部分:定义正则表达式和宏。
规则部分:定义词法规则和相应的动作。
用户代码部分:定义用户自定义的函数和全局变量。
格式如下:
定义部分
%%
辨认规则
%%
用户代码部分
输出文件:通过对源文件的扫描自动天生相应的词法分析函数yylex(), 天生一个 C 源文件,后缀为.yy.c,其中包罗词法分析器的实现代码 。
定义部分:
位于 Flex 文件的顶部,通常用于定义全局变量、包罗头文件等,以 %{ 开始,以 %} 结束。
如
%{
#include <stdio.h>
#include <stdlib.h>
%}辨认规则:
定义了词法规则和相应的动作。每个规则由一个模式(正则表达式)和一个动作(C 语言代码)构成。
其中此次可以会用到flex提供的2个全局变量:
yytext:刚刚匹配到的字符串
yyleng:刚刚匹配到的字符串的长度
{IDENT} { printf("IDENTIFIER: %s\n", yytext); }
{DIGIT}+ { printf("INTEGER: %s\n", yytext); }
"+" { printf("PLUS\n"); }
"-" { printf("MINUS\n"); }
"*" { printf("MULTIPLY\n"); }
"/" { printf("DIVIDE\n"); }
\n { return 0; }用户代码部分:
用户代码部分位于规则部分之后,包罗用户自定义的c语言函数,以 %% 开始,会直接复制到 lex.yy.c的C语言文件中。如可以定义一些辅助函数。比方yywrap()函数,这个函数在词法分析器读取完输入后被调用。返回1表示没有更多的输入了,这在处理单个输入文件时很常见。
int yywrap()
{
return 1;
}Bison:
Bison是一个语法分析器天生器。它用于天生语法分析器程序,语法分析器的任务是根据语法规则对词法分析器返回的词法单元序列举行分析,构建语法树等结构,从而实现对输入文本(如程序代码)的语义明白。
Bison读取一个语法规则文件(通常是.y文件),这个文件中定义了语法规则以及对应的语义动作。语法规则描述了输入文本的结构,比方怎样由词法单元构成语句等。语义动作则是在语法规则匹配成功时实行的操作,如构建抽象语法树(AST)节点等。Bison根据这些规则天生一个语法分析器的C代码,这个语法分析器可以或许对词法分析器返回的词法单元序列举行分析,并实行语义动作。
输入文件:以 .y 为扩展名,包罗三个主要部分:
头部定义部分:定义标记、类型、先决条件等。
规则部分:定义语法规则和相应的动作。
用户代码部分:定义用户自定义的函数和全局变量。
输出文件:天生一个后缀为 .tab.c 的 C 文件,其中包罗了根据语法规则天生的代码,以及一个头文件后缀为 .tab.h,包罗标记的定义。
2.代码逻辑解释
.l文件:
[*]
%{
#include "parser.tab.h"
#include <string.h>
#include <stdlib.h>
%}这里parser.tab.h是Bison天生的头文件,它包罗了词法单元的定义等信息,string.h和stdlib.h提供了字符串操作和动态内存分配等功能。
[*]
定义了一系列的模式和动作。模式可以是简朴的字符、字符类、正则表达式等。比方+用于匹配一个或多个数字字符,*用于匹配以字母开头,后跟任意个字母或数字字符的字符串。动作是花括号困绕的C代码块。
%%
+ { yylval.intVal = atoi(yytext); return NUMBER; }
* { yylval.strVal = strdup(yytext); return IDENTIFIER; }// 变量
"\n" { return EOL; }
[ \t] { /* 忽略空白字符 */ }
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return MULTIPLY; }
"/" { return DIVIDE; }
"(" { return LPAREN; }
")" { return RPAREN; }
"=" { return ASSIGN; }
. { fprintf(stderr, "lexical error at line %d: unexpected character '%s'\n", yylineno, yytext); return yytext; }
%%就拿+解释。这里yylval是一个全局变量,用于转达词法单元的值给语法分析器。intVal是yylval的成员,用于存储整数值。atoi(yytext)将匹配到的数字字符串转换为整数。return NUMBER;表示返回一个名为NUMBER的词法单元给语法分析器。
[*]
用户代码部分就写了一个yywrap就不多赘述了。
.y文件:
[*]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 符号表:存储变量名和值
#define MAX_VARS 100
typedef struct {
char *name;
int value;
} Variable;
Variable symbol_table;
int symbol_count = 0;引用了C语言的一些库,定义了结构体、数组和一个变量;分别存储变量名和值、存储结构体和已存储的变量数目
[*]
void set_variable_value(const char *name, int value) {
// 检查符号表中是否已有该变量
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbol_table.name, name) == 0) {
symbol_table.value = value;
return;
}
}
// 如果没有,插入新变量
symbol_table.name = strdup(name);
symbol_table.value = value;
symbol_count++;
}设置变量的值。如果变量已存在,则更新其值;如果不存在,则插入新变量。
[*]
int get_variable_value(const char *name) {
// 查找变量值
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbol_table.name, name) == 0) {
return symbol_table.value;
}
}
// 如果未找到变量,报错并退出
printf("Error: Undefined variable %s\n", name);
exit(1);
}获取变量的值。如果变量不存在,则报错并退出。
[*]
int yylex(void);声明 Flex 天生的词法分析器函数。
// 定义抽象语法树节点
typedef enum {
NODE_NUMBER,
NODE_IDENTIFIER,
NODE_PLUS,
NODE_MINUS,
NODE_MULTIPLY,
NODE_DIVIDE,
NODE_ASSIGN,
NODE_PAREN
} NodeType;
typedef struct ASTNode {
NodeType type;
int value; // 用于存储数字
char *name; // 用于存储变量名
struct ASTNode *left;
struct ASTNode *right;
} ASTNode;NodeType:定义一个枚举类型,表示 AST 节点的类型。
ASTNode:定义一个结构体,表示 AST 节点。
[*]
ASTNode* createNode(NodeType type, int value, char *name, ASTNode *left, ASTNode *right) {
ASTNode *node = (ASTNode*)malloc(sizeof(ASTNode));
node->type = type;
node->value = value;
node->name = name;
node->left = left;
node->right = right;
return node;
}createNode:创建一个新的 AST 节点。
[*]
int evaluateNode(ASTNode *node) {
if (node == NULL) return 0;
switch (node->type) {
case NODE_NUMBER:
return node->value;
case NODE_IDENTIFIER:
return get_variable_value(node->name);
case NODE_PLUS:
return evaluateNode(node->left) + evaluateNode(node->right);
case NODE_MINUS:
return evaluateNode(node->left) - evaluateNode(node->right);
case NODE_MULTIPLY:
return evaluateNode(node->left) * evaluateNode(node->right);
case NODE_DIVIDE:
return evaluateNode(node->left) / evaluateNode(node->right);
case NODE_ASSIGN:
return node->right->value;
case NODE_PAREN:
return evaluateNode(node->left);
default:
return 0;
}
}evaluateNode:计算 AST 节点的值。
[*]
void printAST(ASTNode *node, int level) {
if (node == NULL) return;
for (int i = 0; i < level; i++) printf("");
switch (node->type) {
case NODE_NUMBER:
printf("Number: %d\n", node->value);
break;
case NODE_IDENTIFIER:
printf("Identifier: %s\n", node->name);
break;
case NODE_PLUS:
printf("Plus\n");
break;
case NODE_MINUS:
printf("Minus\n");
break;
case NODE_MULTIPLY:
printf("Multiply\n");
break;
case NODE_DIVIDE:
printf("Divide\n");
break;
case NODE_ASSIGN:
printf("Assign\n");
break;
case NODE_PAREN:
printf("Paren\n");
break;
}
printAST(node->left, level + 1);
printAST(node->right, level + 1);
}
void printSymbolTable() {
printf("Symbol Table:\n");
for (int i = 0; i < symbol_count; i++) {
printf("%s = %d\n", symbol_table.name, symbol_table.value);
}
}printAST:打印 AST 节点及其子节点。
printSymbolTable:打印符号表的内容。
[*]
%union {
int intVal;
char *strVal;
struct ASTNode *astNode;
}
%token <intVal> NUMBER
%token <strVal> IDENTIFIER
%token PLUS MINUS MULTIPLY DIVIDE
%token LPAREN RPAREN EOL ASSIGN
%type <astNode> exp term factor program
%right ASSIGN
%left PLUS MINUS
%left MULTIPLY DIVIDE
%nonassoc EOL%union:定义 yylval 的联合体类型,可以存储不同类型的数据。
intVal,用于存储整数值;
strVal,用于存储字符串值;
astNode,用于存储 AST 节点指针。
%token,声明词法单元及其类型。
NUMBER,整数,类型为 intVal;
IDENTIFIER,标识符,类型为 strVal;
PLUS、MINUS、MULTIPLY、DIVIDE、LPAREN、RPAREN、EOL、ASSIGN分别为操作符和特殊字符。
%type,声明语法规则的结果类型。
exp、term、factor、program:这些语法规则的结果类型为 astNode。
当然还得说明优先级和联合性。
%right ASSIGN指赋值运算符右联合;
%left PLUS MINUS指加减运算符左联合;
%left MULTIPLY DIVIDE指乘除运算符左联合;
%nonassoc EOL指换行符不联合。
[*]
program:
exp EOL {
$$ = $1;
$$->value = evaluateNode($1);
printf("Result: %d\n", $$->value);
printAST($1, 0);
}
| program exp EOL {
$$ = $2;
$$->value = evaluateNode($2);
printf("Result: %d\n", $$->value);
printAST($2, 0);
}
;
exp : term { $$ = $1; $$->value = evaluateNode($1); }
| exp PLUS term {
$$ = createNode(NODE_PLUS, 0, NULL, $1, $3);
$$->value = evaluateNode($1) + evaluateNode($3);
}
| exp MINUS term {
$$ = createNode(NODE_MINUS, 0, NULL, $1, $3);
$$->value = evaluateNode($1) - evaluateNode($3);
}
| IDENTIFIER ASSIGN exp {
set_variable_value($1, evaluateNode($3));
$$ = createNode(NODE_ASSIGN, 0, strdup($1), NULL, $3);
$$->value = $3->value;
}
;
term : factor { $$ = $1; $$->value = evaluateNode($1); }
| term MULTIPLY factor {
$$ = createNode(NODE_MULTIPLY, 0, NULL, $1, $3);
$$->value = evaluateNode($1) * evaluateNode($3);
}
| term DIVIDE factor {
$$ = createNode(NODE_DIVIDE, 0, NULL, $1, $3);
$$->value = evaluateNode($1) / evaluateNode($3);
}
;
factor: NUMBER { $$ = createNode(NODE_NUMBER, $1, NULL, NULL, NULL); }
| IDENTIFIER {
$$ = createNode(NODE_IDENTIFIER, get_variable_value($1), strdup($1), NULL, NULL);
}
| LPAREN exp RPAREN {
$$ = createNode(NODE_PAREN, 0, NULL, $2, NULL);
$$->value = evaluateNode($2);
}
;这部分代码定义了输入文本的结构和对应的语义动作。
就拿program 举例说明一下
这部分定义了程序的结构,一个程序可以是一个表达式后跟一个换行符,大概是一个程序后跟一个表达式和换行符。
exp EOL:
匹配:一个表达式后跟一个换行符。
语义动作:
$$ = $1;:将表达式的结果赋值给当前规则的结果。
$$->value = evaluateNode($1);:计算表达式的值并赋值给当前规则的结果。
printf("Result: %d\n", $$->value);:打印表达式的计算结果。
printAST($1, 0);:打印表达式的抽象语法树(AST)。
program exp EOL:
匹配:一个程序后跟一个表达式和换行符。
语义动作:
$$ = $2;:将表达式的结果赋值给当前规则的结果。
$$->value = evaluateNode($2);:计算表达式的值并赋值给当前规则的结果。
printf("Result: %d\n", $$->value);:打印表达式的计算结果。
printAST($2, 0);:打印表达式的抽象语法树(AST)。
[*]
int main() {
printf("Enter expression: \n");
yyparse();
printSymbolTable();
return 0;
}主函数好像也没啥说的
3.终极代码及结果
.l文件
%{
#include "parser.tab.h"
#include <string.h>
#include <stdlib.h>
%}%%
+ { yylval.intVal = atoi(yytext); return NUMBER; }
* { yylval.strVal = strdup(yytext); return IDENTIFIER; }// 变量
"\n" { return EOL; }
[ \t] { /* 忽略空白字符 */ }
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return MULTIPLY; }
"/" { return DIVIDE; }
"(" { return LPAREN; }
")" { return RPAREN; }
"=" { return ASSIGN; }
. { fprintf(stderr, "lexical error at line %d: unexpected character '%s'\n", yylineno, yytext); return yytext; }
%%int yywrap()
{
return 1;
}.y文件
%{#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 符号表:存储变量名和值
#define MAX_VARS 100
typedef struct {
char *name;
int value;
} Variable;
Variable symbol_table;
int symbol_count = 0;void set_variable_value(const char *name, int value) {
// 检查符号表中是否已有该变量
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbol_table.name, name) == 0) {
symbol_table.value = value;
return;
}
}
// 如果没有,插入新变量
symbol_table.name = strdup(name);
symbol_table.value = value;
symbol_count++;
}int get_variable_value(const char *name) {
// 查找变量值
for (int i = 0; i < symbol_count; i++) {
if (strcmp(symbol_table.name, name) == 0) {
return symbol_table.value;
}
}
// 如果未找到变量,报错并退出
printf("Error: Undefined variable %s\n", name);
exit(1);
}void yyerror(const char *s) { fprintf(stderr, "error: %s\n", s);}int yylex(void);// 定义抽象语法树节点
typedef enum {
NODE_NUMBER,
NODE_IDENTIFIER,
NODE_PLUS,
NODE_MINUS,
NODE_MULTIPLY,
NODE_DIVIDE,
NODE_ASSIGN,
NODE_PAREN
} NodeType;
typedef struct ASTNode {
NodeType type;
int value; // 用于存储数字
char *name; // 用于存储变量名
struct ASTNode *left;
struct ASTNode *right;
} ASTNode;ASTNode* createNode(NodeType type, int value, char *name, ASTNode *left, ASTNode *right) {
ASTNode *node = (ASTNode*)malloc(sizeof(ASTNode));
node->type = type;
node->value = value;
node->name = name;
node->left = left;
node->right = right;
return node;
}int evaluateNode(ASTNode *node) {
if (node == NULL) return 0;
switch (node->type) {
case NODE_NUMBER:
return node->value;
case NODE_IDENTIFIER:
return get_variable_value(node->name);
case NODE_PLUS:
return evaluateNode(node->left) + evaluateNode(node->right);
case NODE_MINUS:
return evaluateNode(node->left) - evaluateNode(node->right);
case NODE_MULTIPLY:
return evaluateNode(node->left) * evaluateNode(node->right);
case NODE_DIVIDE:
return evaluateNode(node->left) / evaluateNode(node->right);
case NODE_ASSIGN:
return node->right->value;
case NODE_PAREN:
return evaluateNode(node->left);
default:
return 0;
}
}void printAST(ASTNode *node, int level) {
if (node == NULL) return;
for (int i = 0; i < level; i++) printf("");
switch (node->type) {
case NODE_NUMBER:
printf("Number: %d\n", node->value);
break;
case NODE_IDENTIFIER:
printf("Identifier: %s\n", node->name);
break;
case NODE_PLUS:
printf("Plus\n");
break;
case NODE_MINUS:
printf("Minus\n");
break;
case NODE_MULTIPLY:
printf("Multiply\n");
break;
case NODE_DIVIDE:
printf("Divide\n");
break;
case NODE_ASSIGN:
printf("Assign\n");
break;
case NODE_PAREN:
printf("Paren\n");
break;
}
printAST(node->left, level + 1);
printAST(node->right, level + 1);
}
void printSymbolTable() {
printf("Symbol Table:\n");
for (int i = 0; i < symbol_count; i++) {
printf("%s = %d\n", symbol_table.name, symbol_table.value);
}
}%}%union {
int intVal;
char *strVal;
struct ASTNode *astNode;
}
%token <intVal> NUMBER
%token <strVal> IDENTIFIER
%token PLUS MINUS MULTIPLY DIVIDE
%token LPAREN RPAREN EOL ASSIGN
%type <astNode> exp term factor program
%right ASSIGN
%left PLUS MINUS
%left MULTIPLY DIVIDE
%nonassoc EOL%%program:
exp EOL {
$$ = $1;
$$->value = evaluateNode($1);
printf("Result: %d\n", $$->value);
printAST($1, 0);
}
| program exp EOL {
$$ = $2;
$$->value = evaluateNode($2);
printf("Result: %d\n", $$->value);
printAST($2, 0);
}
;
exp : term { $$ = $1; $$->value = evaluateNode($1); }
| exp PLUS term {
$$ = createNode(NODE_PLUS, 0, NULL, $1, $3);
$$->value = evaluateNode($1) + evaluateNode($3);
}
| exp MINUS term {
$$ = createNode(NODE_MINUS, 0, NULL, $1, $3);
$$->value = evaluateNode($1) - evaluateNode($3);
}
| IDENTIFIER ASSIGN exp {
set_variable_value($1, evaluateNode($3));
$$ = createNode(NODE_ASSIGN, 0, strdup($1), NULL, $3);
$$->value = $3->value;
}
;
term : factor { $$ = $1; $$->value = evaluateNode($1); }
| term MULTIPLY factor {
$$ = createNode(NODE_MULTIPLY, 0, NULL, $1, $3);
$$->value = evaluateNode($1) * evaluateNode($3);
}
| term DIVIDE factor {
$$ = createNode(NODE_DIVIDE, 0, NULL, $1, $3);
$$->value = evaluateNode($1) / evaluateNode($3);
}
;
factor: NUMBER { $$ = createNode(NODE_NUMBER, $1, NULL, NULL, NULL); }
| IDENTIFIER {
$$ = createNode(NODE_IDENTIFIER, get_variable_value($1), strdup($1), NULL, NULL);
}
| LPAREN exp RPAREN {
$$ = createNode(NODE_PAREN, 0, NULL, $2, NULL);
$$->value = evaluateNode($2);
}
;%%int main() {
printf("Enter expression: \n");
yyparse();
printSymbolTable();
return 0;
}使用方法:
1.天生词法分析器:
使用 Flex 天生词法分析器的 C 代码。在终端中运行以下下令:
flex -o lex.yy.c name1.l
这里 name1.l 是你的 Flex 文件名,lex.yy.c 是天生的 C 代码文件。
2.天生语法分析器:
使用 Bison 天生语法分析器的 C 代码。在终端中运行以下下令
bison -d -o parser.tab.c name2.y
这里 name2.y 是你的 Bison 文件名,parser.tab.c 是天生的 C 代码文件,-d 选项会天生一个头文件 parser.tab.h,这个头文件包罗了词法单元的定义等信息。
3.编译天生的 C 代码
将天生的 C 代码文件编译成可实行文件。在终端中运行以下下令:
gcc lex.yy.c parser.tab.c -o name3
这里 name3 是你想要天生的可实行文件的名称。
步骤 4: 运行编译器
编译完成后,你可以运行天生的编译器来解析输入的表达式。在终端中运行以下下令:
./name3
然后输入你的表达式,按回车键结束输入。编译器会解析表达式并输出结果。
运行截图:
https://img2024.cnblogs.com/blog/3388666/202501/3388666-20250113212531919-654892431.png
信赖看到这里的你,也是来自广州某大学的吧!
https://img2024.cnblogs.com/blog/3388666/202501/3388666-20250112205830388-1151824060.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]