---
# System prepended metadata

title: lex
tags: [Coding]

---

---
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
![截圖 2024-10-27 下午3.36.30](https://hackmd.io/_uploads/B1GiTvoxye.png)

3. 副程式區
這部分主要用來放置 C 語言的副程式，如 main() 函數或其他輔助函數。

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

![image](https://hackmd.io/_uploads/HkzE3Pjg1l.png)
假設你有以下 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
```
![image](https://hackmd.io/_uploads/H1o99Dsxye.png)

## 例子
![image](https://hackmd.io/_uploads/HJaUZ22xye.png)
![image](https://hackmd.io/_uploads/S1qD-33eJl.png)
### 測資
```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++;}
```

**讀取小數點**
![未命名的筆記本](https://hackmd.io/_uploads/ry5jpcneJx.jpg)
上面四種可能性 都有分別對應方法
```=
^[-]?[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++;
}
```