iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
自我挑戰組

Lex & Yacc 學習筆記系列 第 7

[Day7] Lex - Error Handling

  • 分享至 

  • xImage
  •  

本篇內容

  • 介紹yyerror
  • 介紹yyterminate
  • 範例 - DNA字母統計2
  • 附錄 - Lex預定義變數統整

介紹

在前面的範例中,我們的測試檔案都符合我們在lex中所定義的規則。然而,要是檔案中有出現預料之外的字串時,lex在無法判斷的狀態下,可能就會導致程式直接crash的狀況。那麼,有甚麼辦法防止這種事情發生呢?

最簡單的方法就是使用.這個語法。在Day2所做的範例”DNA字母統計”,我們便是利用這個語法來處理除了ATCG四個字母以外的其他字母。然而,當DNA序列出現其他字母時,程式依然繼續進行,最後統計出每種鹼基的數量。

那麼,有沒有辦法在讀到異常字母時,直接跳出讀取的環節,並印出錯誤的訊息?

在Lex & Yacc中,有個現成的error function可用,叫做yyerror()

範例 - DNA字母統計2

說明

給定一段DNA核酸序列(由A, T, C, G組成),試著計算各個字母(含氮鹼基)的數量。

若是有出現其他字母,則印出錯誤訊息,並終止parser。

程式實作

Definitions

除了統計變數外,我們額外紀錄行數,作為錯誤回報使用。

另外,yyerror需要在這裡先宣告,在Rule區塊才能使用。

%{
    int numA, numT, numC, numG;
    int lineno = 1;
    void yyerror(const char *s);
%}

Rules

當遇到換行符號時,行數+1。

若是檔案中出現其他字母時,我們改為呼叫yyerror()

另外,我們使用預定義函式yyterminate()直接中止yylex()。

%%

A    { numA++; }
T    { numT++; }
C    { numC++; }
G    { numG++; }
\n   { lineno++; }
.    { yyerror("invalid character"); yyterminate(); } 

Subroutines

除了主程式與yywrap(),我們將yyerror()寫在這裡,將遇到的錯誤種類、內容與行數印出。

%%

void yyerror(const char *s) {
    printf("line %d: %s at %s\n", lineno, s, yytext);
}

int yywrap(void) {
    printf("Counter :\nA: %d\nT: %d\nC: %d\nG: %d\n", numA, numT, numC, numG);
    return 1;
}

int main(void) {
    const char* sFile = "file.txt";
    FILE* fp = fopen(sFile, "r");
    if (fp == NULL) {
        printf("cannot open %s\n", sFile);
        return -1;
    }
    yyin = fp;
    yylex();
    return 0;
}

跟之前不同的是,我們把最終統計的結果放在yywrap裡。當遇到錯誤提前中止時,便不會呼叫yywrap,因此符合我們的需求。

完整程式碼

以下的內容在lex檔案中,檔案命名為lex.l。

%{
    int numA, numT, numC, numG;
    int lineno = 1;
    void yyerror(const char *s);
%}

%%

A    { numA++; }
T    { numT++; }
C    { numC++; }
G    { numG++; }
\n   { lineno++; }
.    { yyerror("invalid character"); yyterminate(); } 

%%

void yyerror(const char *s) {
    printf("line %d: %s at %s\n", lineno, s, yytext);
}

int yywrap(void) {
    printf("Counter :\nA: %d\nT: %d\nC: %d\nG: %d\n", numA, numT, numC, numG);
    return 1;
}

int main(void) {
    const char* sFile = "file.txt";
    FILE* fp = fopen(sFile, "r");
    if (fp == NULL) {
        printf("cannot open %s\n", sFile);
        return -1;
    }
    yyin = fp;
    yylex();
    return 0;
}

執行結果

輸入內容

AAAAACCCCCAAAAACCCCCCAAAAAGGGUUUGGGGTTTTTTT

輸出結果

line 1: invalid character at U

結語

有了Error Handling,就可以避免程式跑到一半就crash,或是造成讀取錯誤內容。

至此,Lex的基本介紹就告一段落了。我們接下來就要進入Yacc的部分。

附錄 - 預定義變數

關於lex and yacc中的預定義變數整理於下表:

變數名稱 敘述
int yylex(void) 呼叫lexer
char *yytext 指向匹配字串的指標
yyleng 匹配字串的長度
yylval 與標記相對應的值
int yywrap(void) 總結函數,完成掃描回傳1,反之回傳0
FILE *yyout 輸出檔案
FILE *yyin 輸入檔案
INITIAL 起始狀態
BEGIN 切換至特定條件
ECHO 輸出匹配的字串

參考資料

  • Levine, John R., Tony Mason and Doug Brown [1992]. Lex & Yacc. O’Reilly & Associates, Inc. Sebastopol, California.

上一篇
[Day6] Lex - yywrap
下一篇
[Day8] Yacc - 基本介紹與原理
系列文
Lex & Yacc 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言