1-3 S语言词法分析器设计
一、实验目的
了解词法分析程序的两种设计方法:
- 根据状态转换图直接编程的方式;
- 利用DFA编写通用的词法分析程序。(选做)
二、实验内容
1. 根据状态转换图直接编程
编写一个词法分析程序,它从左到右逐个字符的对源程序进行扫描,产生一个个的单词的二元式,形成二元式(记号)流文件输出。在此,词法分析程序作为单独的一遍,如下图所示。

具体任务有:
- 组织源程序的输入
- 识别单词的类别并记录类别编号和值,形成二元式输出,得到单词流文件
- 删除注释、空格和无用符号
- 发现并定位词法错误,需要输出错误的位置在源程序中的第几行。将错误信息输出到屏幕上。
- 对于普通标识符和常量,分别建立标识符表和常量表(使用线性表存储),当遇到一个标识符或常量时,查找标识符表或常量表,若存在,则返回位置,否则填写符号表或常量表并且返回位置。
- 标识符表结构:变量名,类型(整型、实型、字符型),分配的数据区地址
注:词法分析阶段只填写变量名,其它部分在语法分析、语义分析、代码生成等阶段逐步填入。
常量表结构:常量名,常量值
2.编写DFA模拟程序(选做)
算法如下:
DFA(S=S0,MOVE[ ][ ],F[ ],ALPHABET[ ])
/*S为状态,初值为DFA的初态,MOVE[ ][ ]为状态转换矩阵,F[ ] 为终态集,ALPHABET[] 为字母表,其中的字母顺序与MOVE[ ][ ] 中列标题的字母顺序一致。*/
{
Char Wordbuffer[10]=“”//单词缓冲区置空
Nextchar=getchar();//读
i=0;
while(nextchar!=NULL)//NULL代表此类单词
{ if (nextchar!∈ALPHABET[]) {ERROR(“非法字符”),return(“非法字符”);}
S=MOVE[S][nextchar] //下一状态
if(S=NULL)return(“不接受”);//下一状态为空,不能识别,单词错误
wordbuffer[i]=nextchar ; //保存单词符号
i++;
nextchar=getchar();
}
Wordbuffer[i]=‘\0’;
If(S∈F)return(wordbuffer); //接受
Else return(“不接受”);
}
该算法要求:实现DFA算法,给定一个DFA(初态、状态转换矩阵、终态集、字母表),调用DFA(),识别给定源程序中的单词,查看结果是否正确。
三、实验要求
1. 能对任何S语言源程序进行分析(S语言定义见下面)
在运行词法分析程序时,应该用问答形式输入要被分析的S源语言程序的文件名,然后对该程序完成词法分析任务。
2.能检查并处理某些词法分析错误
词法分析程序能给出的错误信息包括:总的出错个数,每个错误所在的行号,错误的编号及错误信息。
本实验要求处理以下两种错误(编号分别为1,2):
- 非法字符:单词表中不存在的字符处理为非法字符,处理方式是删除该字符,给出错误信息,“某某字符非法”。
- 源程序文件结束而注释未结束。注释格式为:
/* …… */
四、S语言定义
保留字和特殊符号表
| 种别代码type | 单词 | 类别class | 内码值 |
|---|---|---|---|
| 1 | int | 关键字 | |
| 2 | char | 关键字 | |
| 3 | float | 关键字 | |
| 4 | void | 关键字 | |
| 5 | const | 关键字 | |
| 6 | if | 关键字 | |
| 7 | else | 关键字 | |
| 8 | for | 关键字 | |
| 9 | while | 关键字 | |
| 10 | switch | 关键字 | |
| 11 | break | 关键字 | |
| 12 | begin | 关键字 | |
| 13 | end | 关键字 | |
| 14 | - | 运算符 | |
| 15 | / | 运算符 | |
| 16 | <= | 运算符 | |
| 17 | + | 运算符 | |
| 18 | * | 运算符 | |
| 19 | % | 运算符 | |
| 20 | < | 运算符 | |
| 21 | > | 运算符 | |
| 22 | >= | 运算符 | |
| 23 | == | 运算符 | |
| 24 | != | 运算符 | |
| 25 | /= | 运算符 | |
| 26 | += | 运算符 | |
| 27 | -= | 运算符 | |
| 28 | %= | 运算符 | |
| 29 | *= | 运算符 | |
| 30 | || | 运算符 | |
| 31 | && | 运算符 | |
| 32 | ! | 运算符 | |
| 33+1 | [ | 界符 | |
| 34+1 | ] | 界符 | |
| 35 | , | 界符 | |
| 36 | ) | 界符 | |
| 37 | ( | 界符 | |
| 38 | ; | 界符 | |
| 39+1 | 整形 | 整形 | 在常数表中的位置 |
| 40+1 | 标识符 | 标识符 | 在符号表中的位置 |
| 33 | = | 运算符 | |
单词的构词规则:
- 字母=
[A-Z a-z] - 数字=
[0-9] - 标识符=`(字母|)(字母|数字|)*
- 数字=
数字(数字)*( .数字+|ε)
S语言表达式和语句说明
1.算术表达式:+、-、*、/、% 2.关系运算符:>、>=、<、<=、==、!=
3.赋值运算符:=,+=、-=、*=、/=、%= 4.变量说明:类型标识符 变量名表; 5.类型标识符:int char float 6.If语句:if 表达式then 语句 [else 语句] 7.For语句:for(表达式1;表达式2;表达式3) 语句 8.While语句:while 表达式 do 语句`
9.S语言程序:由函数构成,函数不能嵌套定义。
函数格式为:
返回值 函数名(参数)
begin
数据说明
语句
end
复合语句构成
begin
语句序列
end
五、程序结构参考说明

1.Initscanner函数:程序初始化:输入并打开源程序文件和目标程序文件,初始化保留字表
2.Scanner函数:若文件未结束,反复调用lexscan函数识别单词。
3.Lexscan函数:根据读入的单词的第一个字符确定调用不同的单词识别函数
4.Isalpha函数:识别保留字和标识符
5.Isnumber函数:识别整数,如有精力,可加入识别实数部分工功能
6.Isanotation函数:处理除号/和注释
7.Isother函数识别其他特殊字符
8.Output函数:输出单词的二元式到目标文件,输出格式(单词助记符,单词内码值),如(int,-)(rlop,>)……
9.Error函数:输出错误信息到屏幕
10.除此之外,还可以设置查符号表,填写符号表等函数,学生可自行设计。
六、朴素实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 记录已识别的单词
int node_num = 0;
int line = 1;
char buf[1024];
// 四元组
typedef struct Node{
int type; // 种别代码 最小的分类单位
char name[10]; // 变量名
int* addr; // 变量地址
int line; // 第几行
} Node;
Node nodes[1024] = {};
/*
* 这一块是表的等价,如果需要添加或修改关键字,只需要改动一下代码,解耦
*/
// 保留字和特殊符号
// 关键字表(共13个)
char keyword[][10] = {
"int", // 1
"char", // 2
"float", // 3
"void", // 4
"const", // 5
"if", // 6
"else", // 7
"for", // 8
"while", // 9
"switch", //10
"break", //11
"begin", //12
"end" //13
};
// 运算符表(共19个)
char calculation[][10] = {
"-", //14
"/", //15
"<=", //16
"+", //17
"*", //18
"%", //19
"<", //20
">", //21
">=", //22
"==", //23
"!=", //24
"/=", //25
"+=", //26
"-=", //27
"%=", //28
"*=", //29
"||", //30
"&&", //31
"!" //32
"=" // 41
};
// 界符表(共6个)
char delimiter[][10] = {
"[", //33
"]", //34
",", //35
")", //36
"(", //37
";" //38
};
// 分别表示关键字、界符、运算符的个数
int nk = sizeof(keyword) / sizeof(keyword[0]);
int nd = sizeof(delimiter) / sizeof(delimiter[0]);
int nc = sizeof(calculation) / sizeof(calculation[0]);
// 分别是关键字、界符、运算符、整形、标识符在表中的偏移量 offset偏移o make标识符m 整形i
int ok = 1, oc = 14, od = 34, oi = 40, om = 41;
// -----函数声明-------
// 判断是否是整数
bool is_integer(char ch){
return ch >= '0' && ch <= '9';
};
// 判断是否字母
bool is_character(char ch){
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
}
// 判断字符串是否是关键字,是则返回下标,否则返回-1;不是关键字 则为标识符
int keyword_index_of(const char s[]){
for (int i = 0; i < nk; i++){
if (strcmp(s, keyword[i]) == 0){
return i;
};
}
return -1;
};
// 判断字符串是否纯字母
bool is_pureCharacter(const char s[], int len){
bool flag = true;
for (int i = 0; i < len; i++){
if (!is_character(s[i]))
flag = false;
}
return flag;
}
// 判断是否界符,是则返回下标,否则返回-1
int delimiter_index_of(char ch){
for (int i = 0; i < nd; i++)
if (ch == delimiter[i][0])
return i;
return -1;
};
// 根据种别代码范围判断类别
char* getClassStr(int n){
static char name[20];
if (n >= ok && n <= ok + nk - 1) strcpy(name, "关键字");
else if (n >= oc && n <= oc + nc - 1) strcpy(name, "运算符");
else if (n >= od && n <= od + nd - 1) strcpy(name, "界符");
else if (n == oi) strcpy(name, "实数");
else if (n == om) strcpy(name, "标识符");
else strcpy(name, "未识别");
return name;
}
bool annotation = false; // 是否处于注释中
// offset nodes数组元素偏移量,识别一行的单词,返回单词数量
int Lexscan(const char* s, Node* srcNode){
Node* node = srcNode;
char tokens[20];
int i = 0; //记录token起止 i为开始 j为偏移量
// 注意 fgets的末尾,\0结尾
while (s[i] != '\0'){
int j = 0;
// "自旋"
while (s[i] == '\n' || s[i] == '\r' || s[i] == '\t' || s[i] == ' '){
i++;
}
// 如果注释中,匹配到终止符
if (annotation){
while ((s[i] != '*' && s[i + 1] != '/') && s[i] != '\0'){
i++;
}
if (s[i] == '\0')break;
else annotation = false;
}
if (s[i] == '\0') break; // !!!! 可能空格后没有单词
// 字母 下划线始
if (is_character(s[i]) || s[i + j] == '_'){
j++;
while (is_character(s[i + j]) || is_integer(s[i + j]) || s[i + j] == '_'){
j++;
}
// 非字母下划线数字
j--;
if (is_pureCharacter(s + i, j + 1)){
strncpy(tokens, s + i, j + 1);
tokens[j + 1] = '\0';
// 如果纯字母,判断是否关键字
int index = keyword_index_of(s + i);
if (index != -1){
// 是关键字
strcpy(node->name, tokens);
node->type = index + ok;
node++;
i = j + 1;
// printf("(%s, 关键字, %d)\n", tokens, node->type);
continue;
}
}
// 是标识符
strncpy(tokens, s + i, j + 1);
tokens[j + 1] = '\0';
node->type = om;
strncpy(node->name, tokens, j + 1);
node->name[j + 1] = '\0';
node->line = line;
node++;
// printf("(%s, 标识符, %d)\n", tokens, om);
i += j + 1;
}
// 数字
else if (is_integer(s[i])){
j++;
while (is_integer(s[i + j])){
j++;
}
if(s[i+j] == '.'){
j++;
while (is_integer(s[i + j])){
j++;
}
}
// 非数字
j--;
strncpy(node->name, s + i, j + 1);
node->type = oi;
node->line = line;
node++;
i += j + 1;
}
// 运算符
else if (s[i] == '-'){
if (s[i + 1] == '='){
j++;
strcpy(tokens, "-=");
tokens[j + 1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 27;
}
else{
strcpy(tokens, "-");
tokens[j + 1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 14;
}
node->line = line;
i += j + 1;
node++;
}
else if (s[i] == '/'){
if (s[i + 1] == '='){
j++;
strcpy(tokens, "/=");
tokens[j + 1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 25;
}
else if (s[i + 1] == '*'){
i += 2;
annotation = true;
continue;
}
else if (s[i + 1] == '/') break; // 读取下一行
else{
strcpy(tokens, "/");
tokens[j + 1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 15;
}
node->line = line;
i += j + 1;
node++;
}
else if (s[i] == '<'){
if (s[i + 1] == '='){
strcpy(tokens, "<=");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 16;
i += 2;
}
else{
strcpy(tokens, "<");
tokens[1] = '\0';
strncpy(node->name, s + i, 1);
node->type = 20;
}
node->line = line;
node++;
}
else if (s[i] == '+'){
if (s[i + 1] == '='){
j++;
strcpy(tokens, "+=");
tokens[2] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 26;
}
else{
strcpy(tokens, "+");
tokens[1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 17;
}
node->line = line;
i += j + 1;
node++;
}
else if (s[i] == '*'){
if (s[i + 1] == '='){
j++;
strcpy(tokens, "*=");
tokens[2] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 29;
i += j + 1;
}
else{
strcpy(tokens, "*");
tokens[1] = '\0';
strncpy(node->name, s + i, j + 1);
node->type = 18;
i += j + 1;
}
node->line = line;
node++;
}
else if (s[i] == '%'){
if (s[i + 1] == '='){
strcpy(tokens, "%=");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 28;
i += 2;
}
else{
strcpy(tokens, "%");
tokens[1] = '\0';
strncpy(node->name, s + i, 1);
node->type = 19;
i += 1;
}
node->line = line;
node++;
}
else if (s[i] == '>'){
if (s[i + 1] == '='){
strcpy(tokens, ">=");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 22;
i += 2;
}
else{
strcpy(tokens, ">");
tokens[1] = '\0';
strncpy(node->name, s + i, 1);
node->type = 21;
i += 1;
}
node->line = line;
node++;
}
else if (s[i] == '='){
if (s[i + 1] == '='){
strcpy(tokens, "==");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 23;
i += 2;
}
else{
strcpy(tokens, "=");
tokens[1] = '\0';
strncpy(node->name, s + i, 1);
node->type = 33; /* 适当的类型编号 */
i += 1;
}
node->line = line;
node++;
}
else if (s[i] == '!'){
if (s[i + 1] == '='){
strcpy(tokens, "!=");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 24;
i += 2;
}
else{
strcpy(tokens, "!");
tokens[1] = '\0';
strncpy(node->name, s + i, 1);
node->type = 32;
i += 1;
}
node->line = line;
node++;
}
else if (s[i] == '&' && s[i + 1] == '&'){
strcpy(tokens, "&&");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 31;
node->line = line;
node++;
i += 2;
}
else if (s[i] == '|' && s[i + 1] == '|'){
strcpy(tokens, "||");
tokens[2] = '\0';
strncpy(node->name, s + i, 2);
node->type = 30;
node->line = line;
node++;
i += 2;
}
// 界符
else if (delimiter_index_of(s[i]) != -1){
int index = delimiter_index_of(s[i]);
strncpy(tokens, s + i, 1);
tokens[1] = '\0';
strncpy(node->name, tokens, 1);
node->name[1] = '\0';
node->type = od + index;
i += strlen(delimiter[index]);
// printf("(%s, 界符, %d)\n", delimiter[index], node->type);
node->line = line;
node++;
}
else{
printf("未知符号: %d\n", (int)s[i]);
perror("Lexscan Error");
exit(0);
}
}
return node - srcNode;
}
// 扫描函数
int Scanner(FILE* fp, Node* nodes){
char text[1024] = {};
int Maxline = sizeof(text) / sizeof(text[0]) - 1;
int num = 0;
while (fgets(text, Maxline, fp) != NULL){
int len = strlen(text);
// printf(">>>识别第 %d 代码: %s", line++, text);
num += Lexscan(text, nodes + num);
line++;
}
printf("识别出:%d 单词\n", num);
for (int i = 0; i < num; i++){
printf("(%s, %s, %d, %d)\n", nodes[i].name, getClassStr(nodes[i].type), nodes[i].type, nodes[i].line);
}
return num;
}
int main(){
char in_file[100] = "./text.txt";
char out_file[100] = "out.txt";
FILE *fpin, *fpout;
printf("<<<<<<<<<<<<WELCOME>>>>>>>>>>>>\n");
fpin = fopen(in_file, "r");
if (fpin == NULL){
perror("Error opening input file");
return 1;
}
fpout = fopen(out_file, "w");
if (fpout == NULL){
perror("Error opening output file");
fclose(fpin);
return 1;
}
node_num = Scanner(fpin, nodes);
fclose(fpin);
fclose(fpout);
return 0;
}
测试文本
int main()[
/*
注释
*/
abc123 += 123
1 + 2
begin end
_abc123
abc_123
131.2321
]
输出

七、基于状态转换图的DFA 识别长注释(选做)

#include <stdio.h>
// 长注释的状态转移表
int MOVE[][1024] = {{1, 0, 0}, {0, 2, 0}, {2, 3, 2}, {4, 3, 2}};
// 枚举输入符号
enum IN_CHAR {
FORWARD_SLASH = 0,
STAR,
OTHER
};
int curStatus = 0; // 当前状态
char tokens[1024];
char *tp;
int main() {
FILE *fp;
fp = fopen("text.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
char ch;
// 进入状态1时,开始记录字符串,到达终态时,输出
while ((ch = fgetc(fp)) != EOF && curStatus != 4) { // 未达终态即继续
switch (ch) {
case '/':
curStatus = MOVE[curStatus][FORWARD_SLASH];
break;
case '*':
curStatus = MOVE[curStatus][STAR];
break;
default:
curStatus = MOVE[curStatus][OTHER];
}
printf("当前状态: %d, 输入字符: %c ...\n", curStatus, ch);
if (curStatus == 1) { // 在状态1,注释头,重置注释记录数组指针
tp = tokens;
}
if (curStatus != 0)
*tp++ = ch; // 不在状态0,进入长注释识别,开始记录注释
}
if (curStatus == 4) {
*tp = '\0';
printf("\n以下是读取的注释>>\n\n%s\n", tokens);
} else {
perror("未成功匹配,未达终态");
}
// 关闭文件释放资源
fclose(fp);
return 0; // 正常退出
}
text.txt
fwehoeeiho/**********
这里是乱七***八糟的注释
abc123 += 123
1 + 2
begin end
_abc123
******/
结果

完整。。。畏难

八、使用C++编写优化朴素lexer
增加缓冲区,对IO操作进行合并
同时peek预读在编译器中也较为常见
使用map对关键字和符号匹配进行优化
对一些识别逻辑封装为函数,简化结构,但是一些边界问题识别变得异常复杂
#include <cstddef>
#include <cstdlib>
#include <fcntl.h>
#include <iostream>
#include <iterator>
#include <queue>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <vector>
#include <map>
#include <deque>
#define BUFSIZE 1024
using namespace std;
class Token {
public:
Token() {}
Token(const string type_, const string value_, int line, int column)
: type_(type_), value_(value_), line_(line), column_(column) {}
~Token() {}
string token_type() const { return type_; }
string token_value() const { return value_; }
int line() const { return line_; }
int column() const { return column_; }
private:
string type_;
string value_;
int line_;
int column_;
};
class Lexer {
public:
Lexer(const char* file) : curLine(1) {
fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open error");
exit(-1);
}
// 初始化关键词
initializeKeywords();
// 初始化运算符
initializeOperators();
}
~Lexer() {
close(fd);
for (auto token : tokens) {
delete token;
}
}
void initializeKeywords() {
keyword_["auto"] = "auto";
keyword_["break"] = "break";
keyword_["case"] = "case";
keyword_["char"] = "char";
keyword_["const"] = "const";
keyword_["continue"] = "continue";
keyword_["default"] = "default";
keyword_["do"] = "do";
keyword_["double"] = "double";
keyword_["else"] = "else";
keyword_["enum"] = "enum";
keyword_["extern"] = "extern";
keyword_["float"] = "float";
keyword_["for"] = "for";
keyword_["goto"] = "goto";
keyword_["if"] = "if";
keyword_["int"] = "int";
keyword_["long"] = "long";
keyword_["register"] = "register";
keyword_["return"] = "return";
keyword_["short"] = "short";
keyword_["signed"] = "signed";
keyword_["sizeof"] = "sizeof";
keyword_["static"] = "static";
keyword_["struct"] = "struct";
keyword_["switch"] = "switch";
keyword_["typedef"] = "typedef";
keyword_["union"] = "union";
keyword_["unsigned"] = "unsigned";
keyword_["void"] = "void";
keyword_["volatile"] = "volatile";
keyword_["while"] = "while";
}
void initializeOperators() {
operator_["+"] = "add";
operator_["-"] = "sub";
operator_["*"] = "mul";
operator_["/"] = "div";
operator_["%"] = "mod";
operator_["++"] = "inc";
operator_["--"] = "dec";
operator_["="] = "assign";
operator_["+="] = "addeq";
operator_["-="] = "subeq";
operator_["=="] = "eq";
operator_["!="] = "ne";
operator_["<"] = "lt";
operator_["<="] = "le";
operator_[">"] = "gt";
operator_[">="] = "ge";
operator_["."] = "dot";
operator_["->"] = "arrow";
operator_["!"] = "not";
operator_["~"] = "bit_not";
operator_["&"] = "and";
operator_["|"] = "or";
operator_["^"] = "xor";
operator_["&&"] = "and_and";
operator_["||"] = "or_or";
operator_["<<"] = "shift_left";
operator_[">>"] = "shift_right";
}
// 读取下一个字符,使用buf缓冲区合并多次IO,单向移动,不可预取。临界peek下一字符会导致数据覆盖
char nextChar() {
static int cur_char_index = -1;
cur_char_index = (cur_char_index + 1) % BUFSIZE;
if (cur_char_index == 0) {
int ret = read(fd, buf, BUFSIZE);
if (ret == 0) return EOF;
if (ret < BUFSIZE) buf[ret] = EOF;
}
return buf[cur_char_index];
}
// 读取下一个字符,使用环形队列支持预取和缓冲路径
char readChar() {
if (dq.empty()) { // cur 小于缓冲区
curChar = nextChar();
dq.push_back(curChar);
cur = 0;
} else {
cur++;
if (cur < dq.size()) {
curChar = dq[cur];
} else {
curChar = nextChar();
dq.push_back(curChar);
}
}
return curChar;
}
// 窥视下一字符,不改变状态,使用缓冲区
char peekChar() {
if (cur + 1 < dq.size()) {
return dq[cur + 1];
} else {
char ch = nextChar();
dq.push_back(ch);
return ch;
}
}
// 辅助函数
bool isLetter(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
}
bool isDigit(char ch) {
return ch >= '0' && ch <= '9';
}
bool isSpace(char ch) {
return ch == ' ' || ch == '\t' || ch == '\n';
}
// 获取当前路径形成的字符串
string getStr() {
string s;
for (int i = 0; i <= cur; i++) {
s.push_back(dq[i]);
}
cur = -1;
dq.erase(dq.begin(), dq.begin() + (s.length() + 1));
return s;
}
// 请掉缓冲区当前路径,保存到tokens
void skipSpace() {
while (readChar() != EOF && isSpace(curChar)) {
if (curChar == '\n') curLine++;
}
if (cur > 0) cur--;
}
// 识别逻辑分块封装
bool findOperator() {
static const size_t maxOpLength = 2;
string op(1, curChar);
if (cur + 1 < dq.size() && operator_.find(string(1, dq[cur + 1])) != operator_.end()) {
op.push_back(dq[++cur]);
}
if (operator_.find(op) != operator_.end()) {
Token* token = new Token(operator_[op], op, curLine, getColumn());
tokens.push_back(token);
return true;
}
return false;
}
bool findKeywordOrIdentifier() {
if (!isLetter(curChar)) return false;
while (isLetter(peekChar()) || isDigit(peekChar())) {
readChar();
}
string s = getStr();
if (keyword_.find(s) != keyword_.end()) {
tokens.push_back(new Token("keyword", s, curLine, getColumn()));
} else {
tokens.push_back(new Token("identifier", s, curLine, getColumn()));
}
return true;
}
bool findNumber() {
if (!isDigit(curChar)) return false;
while (isDigit(peekChar())) {
readChar();
}
string s = getStr();
tokens.push_back(new Token("number", s, curLine, getColumn()));
return true;
}
int getColumn() {
int column = 0;
for (auto it = dq.begin(); it != dq.begin() + cur + 1; ++it) {
if (*it == '\n') column = 0;
else column++;
}
return column;
}
void getToken() {
while (readChar() != EOF) {
skipSpace();
if (findOperator()) continue;
if (isLetter(curChar)) {
findKeywordOrIdentifier();
} else if (isDigit(curChar)) {
findNumber();
} else {
// 处理其他字符
string s(1, curChar);
tokens.push_back(new Token("char", s, curLine, getColumn()));
readChar(); // 跳过当前字符
}
}
showTokens();
}
void showTokens() {
for (auto token : tokens) {
cout << "Type: " << token->token_type() << ", Value: " << token->token_value()
<< ", Line: " << token->line() << ", Column: " << token->column() << endl;
}
}
private:
int fd;
char buf[BUFSIZE]; // 读取缓冲
std::deque<char> dq; // 缓冲字符路径,同时实现预取
int cur = -1; // 当前状态索引
int curChar;
int curLine; // 当前行
map<string, string> operator_; // 运算符
map<string, string> keyword_; // 保留字
vector<Token *> tokens; // 保留识别到的token
};
int main() {
const char* file = "./text.txt";
Lexer lex(file); // 词法解释器
lex.getToken();
return 0;
}
九、参考Clang Lexer源码的实现
- 对运算符单独处理,代码可读性好、可修改性高
- 结构简单清晰
在 Lexer 的初始化过程中,文件内容通常会被一次性读取到一个内存缓冲区中。这个缓冲区由 MemoryBuffer 或 MemoryBufferRef 对象管理。
好处是:
高效性:一次性读取文件内容可以减少频繁的 I/O 操作,提高词法分析的效率。
灵活性:词法分析器可以在整个文件内容中自由移动指针,方便处理跨行的语法结构(如多行注释、字符串字面量等),避免了如上面代码的复杂的临界问题
#include <ctype.h>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
// 定义支持的词法单元类型
typedef enum {
TOK_EOF, // 文件结束
TOK_IDENTIFIER, // 标识符
TOK_KEYWORD, // 关键字
TOK_NUMBER, // 整数
TOK_FLOAT, // 小数
TOK_STRING_LITERAL, // 字符串字面量
TOK_CHAR_LITERAL, // 字符字面量
TOK_PLUS,
TOK_MINUS, // +, -
TOK_STAR,
TOK_SLASH, // *, /
TOK_SEMICOLON, // ;
TOK_LPAREN,
TOK_RPAREN, // (, )
TOK_LBRACE,
TOK_RBRACE, // {, }
TOK_LBRACKET,
TOK_RBRACKET, // [, ]
TOK_PLUS_EQ, // +=
TOK_MINUS_EQ, // -=
TOK_STAR_EQ, // *=
TOK_SLASH_EQ, // /=
TOK_PLUS_PLUS, // ++
TOK_MINUS_MINUS, // --
TOK_LESS, // <
TOK_GREATER, // >
TOK_LESS_EQ, // <=
TOK_GREATER_EQ, // >=
TOK_EQUAL, // == (用于 `==`)
TOK_ASSIGN, // = (用于 `=`)
TOK_NOT, // !
TOK_NOT_EQUAL, // !=
TOK_PIPE, // |
TOK_LOGICAL_OR, // ||
TOK_AMP, // &
TOK_LOGICAL_AND, // &&
TOK_UNKNOWN // 未知类型
} TokenType;
// 定义词法分析器结构
typedef struct {
const char *buffer; // 输入代码的缓冲区
const char *current; // 当前分析的位置
const char *end; // 缓冲区结束位置
int line; // 当前行号
int column; // 当前列号
} Lexer;
// 定义词法单元结构
typedef struct {
TokenType type; // 词法单元类型
const char *start; // 词法单元开始位置
const char *end; // 词法单元结束位置
int line; // 词法单元所在行号
int column; // 词法单元所在列号
} Token;
// 初始化词法分析器
void lexer_init(Lexer *lexer, const char *buffer, size_t length) {
lexer->buffer = buffer; // 设置缓冲区
lexer->current = buffer; // 设置当前指针
lexer->end = buffer + length; // 设置缓冲区结束位置
lexer->line = 1; // 初始化行号为1
lexer->column = 1; // 初始化列号为1
}
// 检查是否到达文件末尾
static inline int is_eof(const Lexer *lexer) {
return lexer->current >= lexer->end || *lexer->current == '\0';
}
// 获取当前字符
static inline char peek_char(const Lexer *lexer) {
return is_eof(lexer) ? '\0' : *lexer->current;
}
// 向前移动指针
static inline void advance(Lexer *lexer) {
if (lexer->current < lexer->end) {
if (*lexer->current == '\n') { // 遇到换行符
lexer->line++; // 行号加 1
lexer->column = 1; // 列号重置为 1
} else {
lexer->column++; // 其他字符,列号加 1
}
lexer->current++; // 移动当前指针
}
}
// 判断是否为空白字符
static int is_whitespace(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
// 判断是否为字母
static int is_alpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
// 判断是否为数字
static int is_digit(char c) {
return c >= '0' && c <= '9';
}
// 跳过空白字符
static void skip_whitespace(Lexer *lexer) {
while (!is_eof(lexer) && is_whitespace(peek_char(lexer))) {
advance(lexer);
}
}
// 跳过注释
static void skip_comments(Lexer *lexer) {
if (peek_char(lexer) != '/')
return;
advance(lexer); // 跳过第一个 '/'
if (peek_char(lexer) == '/') { // 单行注释
advance(lexer);
while (!is_eof(lexer) && peek_char(lexer) != '\n') {
advance(lexer);
}
} else if (peek_char(lexer) == '*') { // 多行注释
advance(lexer);
while (!is_eof(lexer)) {
if (peek_char(lexer) == '*' && lexer->current + 1 < lexer->end && *(lexer->current + 1) == '/') {
advance(lexer);
advance(lexer);
break;
}
advance(lexer);
}
}
}
// 跳过空白和注释
static void skip_whitespace_and_comments(Lexer *lexer) {
while (!is_eof(lexer)) {
const char *prev = lexer->current; // 记录当前指针位置
skip_whitespace(lexer); // 跳过空白字符
skip_comments(lexer); // 跳过注释
if (lexer->current == prev) { // 如果指针没有移动,说明没有更多的空白或注释
break;
}
}
}
// 判断是否为关键字
// static TokenType is_keyword(const char *str, size_t len) {
// if (len == 3 && strncmp(str, "int", 3) == 0) return TOK_KEYWORD;
// if (len == 4 && strncmp(str, "void", 4) == 0) return TOK_KEYWORD;
// if (len == 4 && strncmp(str, "char", 4) == 0) return TOK_KEYWORD;
// if (len == 2 && strncmp(str, "if", 2) == 0) return TOK_KEYWORD;
// if (len == 4 && strncmp(str, "else", 4) == 0) return TOK_KEYWORD;
// if (len == 5 && strncmp(str, "while", 5) == 0) return TOK_KEYWORD;
// if (len == 6 && strncmp(str, "return", 6) == 0) return TOK_KEYWORD;
// return TOK_IDENTIFIER; // 默认为标识符
// }
// 判断是否为关键字 map优化
// 判断是否为关键字
static TokenType is_keyword(const char *str, size_t len) {
static std::map<std::string, TokenType> keyword_map = {
{"int", TOK_KEYWORD}, {"void", TOK_KEYWORD}, {"char", TOK_KEYWORD}, {"if", TOK_KEYWORD},
{"else", TOK_KEYWORD}, {"while", TOK_KEYWORD}, {"return", TOK_KEYWORD}, {"begin", TOK_KEYWORD},
{"end", TOK_KEYWORD}, {"for", TOK_KEYWORD}, {"do", TOK_KEYWORD}, {"break", TOK_KEYWORD},
{"continue", TOK_KEYWORD}, {"switch", TOK_KEYWORD}, {"case", TOK_KEYWORD}, {"default", TOK_KEYWORD},
{"struct", TOK_KEYWORD}, {"typedef", TOK_KEYWORD}, {"enum", TOK_KEYWORD}, {"union", TOK_KEYWORD},
{"sizeof", TOK_KEYWORD}, {"static", TOK_KEYWORD}, {"const", TOK_KEYWORD}, {"extern", TOK_KEYWORD},
{"register", TOK_KEYWORD}, {"auto", TOK_KEYWORD}, {"volatile", TOK_KEYWORD}, {"restrict", TOK_KEYWORD},
{"inline", TOK_KEYWORD}, {"signed", TOK_KEYWORD}, {"unsigned", TOK_KEYWORD}, {"short", TOK_KEYWORD},
{"long", TOK_KEYWORD}, {"float", TOK_KEYWORD}, {"double", TOK_KEYWORD}, {"bool", TOK_KEYWORD},
{"true", TOK_KEYWORD}, {"false", TOK_KEYWORD}, {"NULL", TOK_KEYWORD}, {"include", TOK_KEYWORD},
{"define", TOK_KEYWORD}, {"ifdef", TOK_KEYWORD}, {"ifndef", TOK_KEYWORD}, {"endif", TOK_KEYWORD},
{"elif", TOK_KEYWORD}, {"pragma", TOK_KEYWORD}, {"error", TOK_KEYWORD}, {"warning", TOK_KEYWORD},
{"line", TOK_KEYWORD}, {"undef", TOK_KEYWORD}, {"import", TOK_KEYWORD}, {"module", TOK_KEYWORD},
{"export", TOK_KEYWORD}, {"public", TOK_KEYWORD}, {"private", TOK_KEYWORD}, {"protected", TOK_KEYWORD},
{"friend", TOK_KEYWORD}, {"class", TOK_KEYWORD}, {"namespace", TOK_KEYWORD}, {"using", TOK_KEYWORD},
{"template", TOK_KEYWORD}, {"typename", TOK_KEYWORD}, {"this", TOK_KEYWORD}, {"new", TOK_KEYWORD},
{"delete", TOK_KEYWORD}, {"operator", TOK_KEYWORD}, {"virtual", TOK_KEYWORD}, {"override", TOK_KEYWORD},
{"final", TOK_KEYWORD}};
// 将输入字符串转换为 std::string
std::string key(str, len);
if (keyword_map.find(key) != keyword_map.end()) {
return keyword_map[key];
}
return TOK_IDENTIFIER; // 默认为标识符
}
// 读取标识符或关键字
static Token read_identifier_or_keyword(Lexer *lexer) {
const char *start = lexer->current; // 记录起始位置
while (!is_eof(lexer) && (is_alpha(peek_char(lexer)) || is_digit(peek_char(lexer)) || peek_char(lexer) == '_')) {
advance(lexer); // 跳过标识符字符
}
size_t len = lexer->current - start; // 计算长度
Token tok = {is_keyword(start, len), start, lexer->current, lexer->line, lexer->column}; // 创建词法单元
return tok;
}
// 读取数字
static Token read_number(Lexer *lexer) {
const char *start = lexer->current; // 记录起始位置
int has_decimal = 0; // 标记是否包含小数点
while (!is_eof(lexer) && (is_digit(peek_char(lexer)) || (peek_char(lexer) == '.' && !has_decimal))) {
if (peek_char(lexer) == '.') {
has_decimal = 1; // 标记已经读取了小数点
}
advance(lexer); // 跳过数字或小数点
}
if (has_decimal) {
return (Token){TOK_FLOAT, start, lexer->current, lexer->line, lexer->column}; // 返回小数词法单元
} else {
return (Token){TOK_NUMBER, start, lexer->current, lexer->line, lexer->column}; // 返回整数词法单元
}
}
// 读取字符串字面量
static Token read_string_literal(Lexer *lexer) {
const char *start = lexer->current; // 记录起始位置
advance(lexer); // 跳过开头的 '"'
while (!is_eof(lexer) && peek_char(lexer) != '"') {
if (peek_char(lexer) == '\\') {
advance(lexer); // 跳过转义字符
if (!is_eof(lexer)) {
advance(lexer); // 跳过被转义的字符
}
} else {
advance(lexer); // 跳过普通字符
}
}
advance(lexer); // 跳过结尾的 '"'
return (Token){TOK_STRING_LITERAL, start, lexer->current, lexer->line, lexer->column}; // 返回字符串词法单元
}
// 读取字符字面量
static Token read_char_literal(Lexer *lexer) {
const char *start = lexer->current; // 记录起始位置
advance(lexer); // 跳过开头的 '\''
if (!is_eof(lexer)) {
if (peek_char(lexer) == '\\') {
advance(lexer); // 跳过转义字符
if (!is_eof(lexer)) {
advance(lexer); // 跳过被转义的字符
}
} else {
advance(lexer); // 跳过普通字符
}
}
if (!is_eof(lexer) && peek_char(lexer) == '\'') {
advance(lexer); // 跳过结尾的 '\''
}
return (Token){TOK_CHAR_LITERAL, start, lexer->current, lexer->line, lexer->column}; // 返回字符词法单元
}
// 获取下一个词法单元
Token get_next_token(Lexer *lexer) {
skip_whitespace_and_comments(lexer); // 跳过空白和注释
if (is_eof(lexer)) {
return (Token){TOK_EOF, 0, 0, lexer->line, lexer->column}; // 返回文件结束标记
}
const char *start = lexer->current; // 记录起始位置
int line = lexer->line; // 记录当前行号
int column = lexer->column; // 记录当前列号
char c = peek_char(lexer); // 获取当前字符
Token tok;
switch (c) {
case 'a' ... 'z':
case 'A' ... 'Z':
case '_':
return read_identifier_or_keyword(lexer); // 读取标识符或关键字
case '0' ... '9':
return read_number(lexer); // 读取数字
case '"':
return read_string_literal(lexer); // 读取字符串字面量
case '\'':
return read_char_literal(lexer); // 读取字符字面量
case '+':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '+') {
advance(lexer); // 检查并跳过第二个 '+'
return (Token){TOK_PLUS_PLUS, start, lexer->current, line, column}; // 返回 '++' 词法单元
} else if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_PLUS_EQ, start, lexer->current, line, column}; // 返回 '+=' 词法单元
}
return (Token){TOK_PLUS, start, lexer->current, line, column}; // 返回 '+' 词法单元
case '-':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '-') {
advance(lexer); // 检查并跳过第二个 '-'
return (Token){TOK_MINUS_MINUS, start, lexer->current, line, column}; // 返回 '--' 词法单元
} else if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_MINUS_EQ, start, lexer->current, line, column}; // 返回 '-=' 词法单元
}
return (Token){TOK_MINUS, start, lexer->current, line, column}; // 返回 '-' 词法单元
case '*':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_STAR_EQ, start, lexer->current, line, column}; // 返回 '*=' 词法单元
}
return (Token){TOK_STAR, start, lexer->current, line, column}; // 返回 '*' 词法单元
case '/':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_SLASH_EQ, start, lexer->current, line, column}; // 返回 '/=' 词法单元
}
return (Token){TOK_SLASH, start, lexer->current, line, column}; // 返回 '/' 词法单元
case '<':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_LESS_EQ, start, lexer->current, line, column}; // 返回 '<=' 词法单元
}
return (Token){TOK_LESS, start, lexer->current, line, column}; // 返回 '<' 词法单元
case '>':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_GREATER_EQ, start, lexer->current, line, column}; // 返回 '>=' 词法单元
}
return (Token){TOK_GREATER, start, lexer->current, line, column}; // 返回 '>' 词法单元
case ';':
advance(lexer);
return (Token){TOK_SEMICOLON, start, lexer->current, line, column}; // 返回 ';' 词法单元
case '(':
advance(lexer);
return (Token){TOK_LPAREN, start, lexer->current, line, column}; // 返回 '(' 词法单元
case ')':
advance(lexer);
return (Token){TOK_RPAREN, start, lexer->current, line, column}; // 返回 ')' 词法单元
case '{':
advance(lexer);
return (Token){TOK_LBRACE, start, lexer->current, line, column}; // 返回 '{' 词法单元
case '}':
advance(lexer);
return (Token){TOK_RBRACE, start, lexer->current, line, column}; // 返回 '}' 词法单元
case '[':
advance(lexer);
return (Token){TOK_LBRACKET, start, lexer->current, line, column}; // 返回 '[' 词法单元
case ']':
advance(lexer);
return (Token){TOK_RBRACKET, start, lexer->current, line, column}; // 返回 ']' 词法单元
case '=':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过第二个 '='
return (Token){TOK_EQUAL, start, lexer->current, line, column}; // 返回 '==' 词法单元
}
return (Token){TOK_ASSIGN, start, lexer->current, line, column}; // 返回 '=' 词法单元
case '!':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '=') {
advance(lexer); // 检查并跳过 '='
return (Token){TOK_NOT_EQUAL, start, lexer->current, line, column}; // 返回 '!=' 词法单元
}
return (Token){TOK_NOT, start, lexer->current, line, column}; // 返回 '!' 词法单元
case '|':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '|') {
advance(lexer); // 检查并跳过第二个 '|'
return (Token){TOK_LOGICAL_OR, start, lexer->current, line, column}; // 返回 '||' 词法单元
}
return (Token){TOK_PIPE, start, lexer->current, line, column}; // 返回 '|' 词法单元
case '&':
advance(lexer);
if (!is_eof(lexer) && peek_char(lexer) == '&') {
advance(lexer); // 检查并跳过第二个 '&'
return (Token){TOK_LOGICAL_AND, start, lexer->current, line, column}; // 返回 '&&' 词法单元
}
return (Token){TOK_AMP, start, lexer->current, line, column}; // 返回 '&' 词法单元
default:
advance(lexer);
return (Token){TOK_UNKNOWN, start, lexer->current, line, column}; // 返回未知词法单元
}
}
// 打印识别到的 Token
void print_token(const Token *tok) {
const char *type_str;
switch (tok->type) {
case TOK_EOF: type_str = "TOK_EOF"; break;
case TOK_IDENTIFIER: type_str = "TOK_IDENTIFIER"; break;
case TOK_KEYWORD: type_str = "TOK_KEYWORD"; break;
case TOK_NUMBER: type_str = "TOK_NUMBER"; break;
case TOK_FLOAT: type_str = "TOK_FLOAT"; break;
case TOK_STRING_LITERAL: type_str = "TOK_STRING_LITERAL"; break;
case TOK_CHAR_LITERAL: type_str = "TOK_CHAR_LITERAL"; break;
case TOK_PLUS: type_str = "TOK_PLUS"; break;
case TOK_MINUS: type_str = "TOK_MINUS"; break;
case TOK_STAR: type_str = "TOK_STAR"; break;
case TOK_SLASH: type_str = "TOK_SLASH"; break;
case TOK_SEMICOLON: type_str = "TOK_SEMICOLON"; break;
case TOK_LPAREN: type_str = "TOK_LPAREN"; break;
case TOK_RPAREN: type_str = "TOK_RPAREN"; break;
case TOK_LBRACE: type_str = "TOK_LBRACE"; break;
case TOK_RBRACE: type_str = "TOK_RBRACE"; break;
case TOK_LBRACKET: type_str = "TOK_LBRACKET"; break;
case TOK_RBRACKET: type_str = "TOK_RBRACKET"; break;
case TOK_PLUS_EQ: type_str = "TOK_PLUS_EQ"; break;
case TOK_MINUS_EQ: type_str = "TOK_MINUS_EQ"; break;
case TOK_STAR_EQ: type_str = "TOK_STAR_EQ"; break;
case TOK_SLASH_EQ: type_str = "TOK_SLASH_EQ"; break;
case TOK_PLUS_PLUS: type_str = "TOK_PLUS_PLUS"; break;
case TOK_MINUS_MINUS: type_str = "TOK_MINUS_MINUS"; break;
case TOK_LESS: type_str = "TOK_LESS"; break;
case TOK_GREATER: type_str = "TOK_GREATER"; break;
case TOK_LESS_EQ: type_str = "TOK_LESS_EQ"; break;
case TOK_GREATER_EQ: type_str = "TOK_GREATER_EQ"; break;
case TOK_EQUAL: type_str = "TOK_EQUAL"; break;
case TOK_ASSIGN: type_str = "TOK_ASSIGN"; break;
case TOK_NOT: type_str = "TOK_NOT"; break;
case TOK_NOT_EQUAL: type_str = "TOK_NOT_EQUAL"; break;
case TOK_PIPE: type_str = "TOK_PIPE"; break;
case TOK_LOGICAL_OR: type_str = "TOK_LOGICAL_OR"; break;
case TOK_AMP: type_str = "TOK_AMP"; break;
case TOK_LOGICAL_AND: type_str = "TOK_LOGICAL_AND"; break;
default: type_str = "TOK_UNKNOWN"; break;
}
printf("Type: %s, Value: %.*s, Line: %d, Column: %d\n",
type_str, (int)(tok->end - tok->start), tok->start, tok->line, tok->column);
}
// 从文件中读取内容并初始化词法分析器
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
return EXIT_FAILURE;
}
FILE *file = fopen(argv[1], "r");
if (!file) {
perror("Failed to open file");
return EXIT_FAILURE;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
// 分配内存并读取文件内容
char *buffer = (char *)malloc(file_size + 1);
if (!buffer) {
perror("Failed to allocate memory");
fclose(file);
return EXIT_FAILURE;
}
size_t bytes_read = fread(buffer, 1, file_size, file);
if (bytes_read != (size_t)file_size) {
perror("Failed to read file");
free(buffer);
fclose(file);
return EXIT_FAILURE;
}
buffer[file_size] = '\0'; // 确保字符串以 '\0' 结尾
fclose(file);
// 初始化词法分析器
Lexer lexer;
lexer_init(&lexer, buffer, file_size);
// 词法分析并打印结果
Token tok;
do {
tok = get_next_token(&lexer);
print_token(&tok);
} while (tok.type != TOK_EOF);
// 释放内存
free(buffer);
return EXIT_SUCCESS;
}
程序流程图

浙公网安备 33010602011771号