在上一次对第二章的学习中(http://blog.csdn.net/u011602557/article/details/68943237),利用制导翻译的内容做了一个简单的翻译程序。其结构比较混乱,语法分析和词法分析基本混在了一起。在第三章中着重讲了词法分析的内容,其中说到了将分析部分分为语法分析和词法分析的技术原因(龙书第二版69页3.1.1),同时也有利于理解编译过程。因此这一次的内容主要围绕其一的词法分析展开。
词法分析是编译第一阶段,词法分析器的主要任务是读入源程序的字符,将其组成词素,生成一个词法单元序列。
词法单元表示一类具有相同属性的单词,由词法单元名和可选的一个属性值组成。词素是源程序中的一个字符序列,它符合某个词法单元的模式,成为词法单元的一个实例。比如1.2 23 都是数(number),1.2和23是词素,number是词法单元名,其模式以正则表达式书写就是:[0-9]+.?[0-9]+。
词法分析器还需要和符号表交互,在识别到一个标识符的词素时,需要将其添加到符号表中,语法分析器需要从符号表中得到标识符的信息。
这里依然以NC程序的翻译作为例子,其结构简单,没有变量标识符,所以不需要符号表。但为了提高NC代码的灵活性和通用性,这个词法分析器可以通过配置文件来生成一个词法单元表,存储词法单元。
token.txt文件内容如下:
O O
G G
N N
M M
T T
F F
S S
X X
Y Y
Z Z
NUM [+-]?[0-9]+[.]?[0-9]+
END ;
这个表和上次的模式表有一定相似性,但不同的是这是用来进行词法分析的而非语句分析。前者表示词法单元名,后者表示模式,其属性值将由其散列值决定(散列方法为其名字第一个非重复的字符),表的大小固定256,词法单元数不能超过这个数。
词法单元结构如下:
struct lex_token{//词法单元
string token;
int id;
regex reg;//模式
smatch result;
lex_token(string tk, string r, int i) :token(tk), reg(r), id(i){
;
}
inline bool pattern(string&str){
return regex_search(str, result, reg) && result.position()==0;//必须是紧接着的匹配
}
bool compare(string&str){
for (int i = 0; i < token.length(); i++){
if (str[i] != token[i])return false;
}
return true;
}
};
生成词法单元表后,要使用它有些麻烦。因为读入一串字符,要得到其token的话,需要在这张表中找一个与其匹配的词法单元,每次都要扫一遍表,很麻烦。在通常的词法分析程序设计中,这里会被设计为跳转表的形式,根据读入的字符跳转到对应的程序进行处理,而由于这个词法单元表是读入配置文件生成的,不能以通常的方法来做。
这里使用了一种“记忆表”的方法,在每次读入token后,首先根据其第一个字符查询记忆表,如果有且符合模式,则直接返回指针,如果没有,则遍历表来获得指针,同时存入记忆表。表结构如下:
struct mem_table{//记忆链表
lex_token*p;
mem_table*next;
mem_table(lex_token*pc) :p(pc), next(NULL){ ; }
~mem_table(){
if (next){
next->~mem_table();
free(next);
}
}
lex_token*find(string&str){
if (p->pattern(str))return p;
else if (next)return next->find(str);
else return nullptr;
}
void add(lex_token*pc){
if (next)next->add(pc);
else next = new mem_table(pc);
}
};
词法分析模块最终结构如下:
//词法分析
class lex_parser{//词法分析模块:生成用于语法分析的token链 使用散列法存储 最多256种词法单元
private:
vector<lex_token*> tokens = vector<lex_token*>(256, NULL);//词法单元表
vector<mem_table*> mtable = vector<mem_table*>(256, NULL);//记忆表
int line = 0;
public:
lex_parser(string filename)
{
ifstream fin(filename);
string buf1, buf2;
while (fin >> buf1){
//设计词素时要尽力避免二次探查 如有二次 则词素长度要大于1 否则映射失败
fin >> buf2;
int cur = 0;
while (buf1.length() > cur && tokens[buf1[cur]])cur++;
if (buf1.length() <= cur)report("词素token设计有误!");
else{
tokens[buf1[cur]] = new lex_token(buf1, buf2, buf1[cur]);
}
}
}
void init(){
}
~lex_parser(){
for (auto&p : tokens){
if (p) delete p;
}
for (auto&p : mtable){
if (p) delete p;
}
}
lex_token*find_token(string&str){//寻找词法单元
int cur = 0;
while (cur<str.length() && !tokens[str[cur]]->compare(str))cur++;
if (cur >= str.length())report("词法错误,行数:" + line);
else{
return tokens[str[cur]];
}
return NULL;
}
lex_token*find_pattern(string&str){//寻找符合的模式
lex_token*ptr = nullptr;
if (mtable[str[0]])
ptr = mtable[str[0]]->find(str);
if (ptr)
return ptr;
else{
for (auto&p : tokens){
if (p && p->pattern(str)){
if (mtable[str[0]])mtable[str[0]]->add(p);//记忆查询结果
else mtable[str[0]] = new mem_table(p);
return p;
}
}
}
return nullptr;
}
lex_token* getNextToken(string&str, int&pos){
while (str[pos] == ' ' || str[pos] == '\n' || str[pos] == '\t')pos++;//跳过空白字符
if (pos >= str.length()){
line++;
return nullptr;
}
string temp(str.begin()+pos, str.end());
auto ptk = find_pattern(temp);//寻找符合匹配的词法单元
if (!ptk)report("词法错误,行数:" + line);
else{
regex_search(temp, ptk->result, ptk->reg);
pos += ptk->result.length();//光标前移
return ptk;
}
return nullptr;
}
};
在main中添加测试:
int main(){
lex_parser*LX = new lex_parser("NC\\token.txt");
string code = "N001 G01 X-100 Y20 Z+35 M03 S40 T50 F40;";
int pos = 0;
while (pos < code.length()){
auto ptr = LX->getNextToken(code,pos);
if (ptr)
cout << ptr->token << endl;
}
system("pause");
return 0;
}
运行结果如下:
总结:
词法分析是编译第一步,是后续分析的基础。本文在词法分析基础上,增加了配置文件设置词法单元表的方式,可以灵活地建立词法规则,增强了其通用性,但没有考虑符号表的设置,因此目前只适合NC类的程序。
遇到的问题:
1. 调试过程中,构造函数中this指针变成NULL,构造函数后退出后指向一个未知领域,其中的变量无法使用。
猜想可能的原因:内存越界覆写了this指针
最终解决方法:将编译器模式从Realese改为Debug模式再调试
参考:http://bbs.csdn.net/topics/390624297
http://jingyan.baidu.com/article/8ebacdf026993249f65cd58d.html?st=2&os=0&bd_page_type=1&net_type=2
2 .regex_match()无法匹配字符串中的一部分
原因:regex_match()匹配全字符串,含有其他部分则不会匹配。
解决方法:使用regex_search()