编译原理学习笔记(三)词法分析

在上一次对第二章的学习中(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()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章