在上一次對第二章的學習中(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()