---
tags: Coding
---
# lex
Lex 是一種工具,用來生成詞法分析器(lexer),將輸入的字符流分割成 "tokens",這在構建編譯器和解釋器中非常重要。Lex 使用基於規則的語法來描述如何將輸入轉換為 tokens。
## Lex 語法結構
Lex 的語法分為三個主要部分:
1. **宣告區(Declarations)**
2. **規則區(Rules)**
3. **副程式區(User Subroutines)**
在 Lex 文件中,這三個部分以 `%%` 作為分隔。
### 基本格式
```c
%{
// C 語言代碼部分,用來包含必要的標頭文件或變數聲明
%}
%%
// 規則部分,每個規則定義一個正則表達式和相應的動作
正則表達式 { 動作 }
%%
// C 語言的副程式部分,用來實現特定功能或支持程式的運行
```
### Lex 的用法
1. 宣告區
這部分用 %{ ... %} 包裹,包含 C 語言代碼,如標頭文件、全局變數和宏定義。
例子
```c=
%{
#include <stdio.h>
int num_lines = 0;
%}
```
2. 規則區
這是 Lex 文件的核心部分,用來定義正則表達式及其對應的動作。
正則表達式:用來匹配輸入的模式。
動作:當正則表達式被匹配時執行的 C 語言代碼。
例子
```c=
%%
[0-9]+ { printf("找到一個數字:%s\n", yytext); }
\n { num_lines++; }
. { /* 忽略其他字符 */ }
%%
[0-9]+:匹配一個或多個數字,並執行 printf 動作。
\n:匹配換行符並計算行數。
.:匹配任何單個字符並忽略它。
```
注意:
\^放在\[ \]裡面是否定的意思 ex: \[\^A-Z\] => A-Z以外
\^放在\[\]前面是作為每一行開頭讀取的意思 ex: \^\[A-Z\] => 第一個是A-Z

3. 副程式區
這部分主要用來放置 C 語言的副程式,如 main() 函數或其他輔助函數。
例子
```lex=
int main() {
yylex(); // 呼叫詞法分析器開始分析
printf("總共行數:%d\n", num_lines);
return 0;
}
```
### yywrap
在 Lex 生成的詞法分析器中,yywrap() 是一個內建函數,用來在輸入結束時決定是否需要進行進一步的處理。
預設情況下,Lex 期望這個函數在詞法分析器完成所有輸入處理後被調用:
如果 yywrap() 返回 0,則表示還有更多輸入需要處理;
如果返回 1,則表示輸入結束。

假設你有以下 Lex 文件,但未加入 %option noyywrap:
```c=
%{
#include <stdio.h>
%}
%%
[0-9]+ { printf("找到數字:%s\n", yytext); }
%%
int main() {
yylex();
return 0;
}
```
在這個範例中,若沒有 yywrap() 函數的定義,編譯時會出現錯誤。
為了解決這個問題,你需要加上 yywrap() 定義:
```c=
%{
#include <stdio.h>
int yywrap() {
return 1;
}
%}
```
### 生成與運行詞法分析器
編寫 Lex 文件(如 example.l)。
使用 lex 命令生成詞法分析代碼:
```bash=
flex example.l
```
使用 C 編譯器(如 gcc)編譯生成的詞法分析代碼:
```bash=
gcc lex.yy.c -ll -o example
```
執行生成的可執行文件:
```bash=
./example < input.txt
```

## 例子


### 測資
```md=
123;
12jj12
-54564
12
9-
0.00002
-222.1111;
A_XX2;
ABD_UU_X
ABD_UU_X~
.12345
1.0.1.0;
CACHE_ ;
A A A~
1 1 3 4
A
A
~ab ~df aa
_ABX
123ABC;
~
.
23;
```
### 程式
```c=
%{
#include <stdio.h>
#include <string.h>
int int_count = 0;
int float_count = 0;
int id_count = 0;
int error_count = 0;
%}
%option noyywrap
%%
[a-zA-Z][a-zA-Z0-9_]* { printf("Identifier(ID) : %s\n", yytext); id_count++; }
[a-zA-Z][a-zA-Z0-9_]*[^\x00-\x1Fa-zA-Z0-9_\n]+.* {printf("Error : %s\n", yytext);error_count++;/*只要不符合上述就會報錯*/}
[-]?[0-9]+ { printf("Integer : %s\n", yytext); int_count++; }
[-]?[0-9]+[^0-9.\x00-\x1F\n]+.* {
printf("Error : %s\n", yytext);
error_count++;
}
^[-]?[0-9]+\.[0-9]+ { printf("Float : %s\n", yytext); float_count++; }
^[-]?[0-9]+\. { printf("Float : %s\n", yytext); float_count++; }
^[-]?[0-9]+\.[^.\x00-\x1F0-9\n]+.* {printf("Error : %s\n", yytext);error_count++;}
^[-]?[0-9]+\.[0-9]+[^.\x00-\x1F0-9\n]+.* {printf("Error : %s\n", yytext);error_count++;}
[0-9]+\.[0-9]+\..* {printf("Error : %s\n", yytext);error_count++;}
^[^ \t\n]+[ \t]+[^ \t\n]+.* { printf("Error : %s\n", yytext); error_count++; }
\n { /* 忽略换行符 */ }
[ \t] {}
^[^ \-a-zA-Z0-9].* {
if(yytext[0]>=' ')printf("Error : %s\n", yytext);//因為會把控制符號也一起掃進去
error_count++;
}
%%
int main() {
yylex();
printf("Total_Integer : %d\n", int_count);
printf("Total_Float : %d\n", float_count);
printf("Total_Identifier(ID) : %d\n", id_count);
printf("Total_Error : %d\n", error_count);
return 0;
}
```
### 解釋
**讀取ID**
\\x00-\\x1F排除掉空白前面的控制字串
\[\^\\x00-\\x1Fa-zA-Z0-9_\\n\]排除非英文、數字、底線、控制字符。也就是特殊字元
\^放在\[ \]裡面是否定的意思
\^放在\[\]前面是作為第一行讀取的意思
```c=
[a-zA-Z][a-zA-Z0-9_]* { printf("Identifier(ID) : %s\n", yytext); id_count++; }
[a-zA-Z][a-zA-Z0-9_]*[^\x00-\x1Fa-zA-Z0-9_\n]+.* {printf("Error : %s\n", yytext);error_count++;/*只要不符合上述就會報錯*/}
```
**讀取整數**
\?表示0個或1個
.*表示遇到特殊字元後 後面的東西不管是什麼都要讀掉
```c=
[-]?[0-9]+ { printf("Integer : %s\n", yytext); int_count++; }
[-]?[0-9]+[^0-9.\x00-\x1F\n]+.* {printf("Error : %s\n", yytext);error_count++;}
```
**讀取小數點**

上面四種可能性 都有分別對應方法
```=
^[-]?[0-9]+\.[0-9]+ 正確:1.1
^[-]?[0-9]+\. 錯誤:9.
^[-]?[0-9]+\.[^.\x00-\x1F0-9\n]+.* 錯誤:9.~
^[-]?[0-9]+\.[0-9]+[^.\x00-\x1F0-9\n]+.* 錯誤:9.9~
[0-9]+\.[0-9]+\..* 錯誤:9.9.0
```
```c=
^[-]?[0-9]+\.[0-9]+ {printf("Float : %s\n", yytext); float_count++; }
^[-]?[0-9]+\.{printf("Float : %s\n", yytext); float_count++; }
^[-]?[0-9]+\.[^.\x00-\x1F0-9\n]+.* {printf("Error : %s\n", yytext);error_count++;}
^[-]?[0-9]+\.[0-9]+[^.\x00-\x1F0-9\n]+.* {printf("Error : %s\n", yytext);error_count++;}
[0-9]+\.[0-9]+\..* {printf("Error : %s\n", yytext);error_count++;}
```
**讀取空白以及其他**
```c=
^[^ \t\n]+[ \t]+[^ \t\n]+.* { printf("Error : %s\n", yytext); error_count++; }
\n { /* 忽略换行符 */ }
[ \t] { /* 忽略空白與tab */ }
^[^ \-a-zA-Z0-9].* { //只要開頭是特殊字元就跳過直到換行
if(yytext[0]>=' ')printf("Error : %s\n", yytext);//因為會把控制符號也一起掃進去
error_count++;
}
```