• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            woaidongmao

            文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
            隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
            數(shù)據(jù)加載中……

            使用 Flex 和 Bison 更好地進行錯誤處理

              盡管使用 Flex Bison 生成程序非常簡單,但是要讓這些程序產(chǎn)生用戶友好的語法和語義錯誤消息卻很困難。本文將介紹 Flex Bison 的錯誤處理特性,并展示如何使用它們,然后詳細介紹它們的一些缺陷。

             

              簡介

             

              正如 UNIX? 開發(fā)人員所了解的那樣,Flex Bison 的功能非常強大,非常適合開發(fā)詞法和語法解析器,尤其是語言編譯器和解釋器。如果我們不熟悉它們所實現(xiàn)的工具 —— 分別是 Lex Yacc —— 可以參考一下本文 參考資料一節(jié)中有關 Flex Bison 文檔的鏈接,以及其他介紹這兩個程序的文章。

             

              本文介紹了更高級的一些主題:用來在編譯器和解釋器中更好地實現(xiàn)錯誤處理能力的特性和技術。為了展示這些技術,我使用了一個示例程序 ccalc,它基于 Bison 手冊中的計算機實現(xiàn)了一個增強的計算器。我們可以從本文后面下載 一節(jié)下載 ccalc 和相關文件。

             

              增強包括使用了很多變量。在 ccalc 中,變量是通過在初始化中首次使用時定義的,例如 a = 3。如果變量是在初始化之前使用的,那就會產(chǎn)生語義錯誤,使用值為 0 來創(chuàng)建這個變量,并打印一條消息。

             

              示例源文件

             

              示例源代碼中包括 7 個文件:

             

            ccalc.c:主程序,以及一些進行輸入、輸出和錯誤處理的函數(shù)

            ccalc.h:包括了對所有模塊的定義

            cmath.c:數(shù)學函數(shù)

            parse.yBison 使用的輸入文法

            lex.lFlex 的輸入

            makefile:簡單的 makefile

            defs.txt:示例輸入文件

              這個程序接收兩個參數(shù):

             

            -debug:產(chǎn)生調試輸出

            filename:輸入文件名;默認值為 defs.txt

              Bison 使用的設置

             

              為了處理變量名和實際值,Bison 的語義類型必須進行增強:

             

              清單 1. 更好的 Bison 語義類型

             

            /* generate include-file with symbols and types */

            %defines

            /* a more advanced semantic type */

            %union {

             double   value;

             char    *string;

            }

             

              有些文法規(guī)則可以產(chǎn)生特定的語義類型,這需要像清單 2 中一樣對 Bison 進行聲明。要獲得一個可移植性更好的 Bison 文法版本,我們需要重新定義 +-*/() 符號。下面這個例子沒有使用左括號 (,而是使用了結束符符號 LBRACE,這是由詞法分析提供的。另外,操作符的優(yōu)先順序也必須進行聲明。

             

              對于 Flex 來說,所生成的代碼通常都依賴于平臺所使用的代碼頁(codepage)。盡管我們可以使用其他代碼頁,但是必須要對輸入進行轉換。因此與 Bison 代碼不同,Flex 代碼尚不能進行移植。

             

              清單 2. Bison 聲明

             

            /* terminal symbols */

            %token <string>  IDENTIFIER

            %token <value>  VALUE

            %type <value>   expression

            /* operator-precedence

            * top-0: -

            *   1: * /

            *   2: + -

            */

            %left ADD SUB

            %left MULT DIV

            %left NEG

            %start program

             

              這段文法與 Bison 手冊非常類似,不同之處在于它使用了名字作為終端符號和標識符的簡寫形式。標識符是在賦值語句中進行定義和初始化的,并且可以在任何允許使用的地方使用。清單 3 給出了一個示例文法:

             

              清單 3. 示例 Bison 文法

             

            program

              : statement SEMICOLON program

              | statement SEMICOLON

              | statement error SEMICOLON program

              ;

            statement

              : IDENTIFIER ASSIGN expression

              | expression

              ;

            expression

              : LBRACE expression RBRACE

              | SUB expression %prec NEG

              | expression ADD expression

              | expression SUB expression

              | expression MULT expression

              | expression DIV expression

              | VALUE

              | IDENTIFIER

              ;

             

              program 的第三個輸出讓這個分析程序可以獲得錯誤,從中搜索分號,然后繼續(xù)執(zhí)行(通常錯誤對于解析器來說都是非常嚴重的)。

             

              為了讓這個例子更加有趣,規(guī)則體中的真正數(shù)學函數(shù)都是以單獨函數(shù)的形式實現(xiàn)的。在進行高級文法分析時,我們要盡量保證規(guī)則簡短,并使用函數(shù)來實現(xiàn)一些不會直接處理解析的過程:

             

              清單 4. 使用單獨的函數(shù)來實現(xiàn)數(shù)學規(guī)則

             

            | expression DIV expression

             {

              $$ = ReduceDiv($1, $3);

             }

             

              最后,函數(shù) yyerror() 必須要進行定義。這個函數(shù)是在所生成的解析器檢測到語法錯誤時調用的,它又會調用一個小函數(shù) PrintError(),后者會打印增強的錯誤消息。詳細內(nèi)容請參看源代碼。

             

              Flex 的設置

             

              Flex 所生成的詞法分析器必須要根據(jù)語義類型提供終止符號。清單 5 定義了空格、實際值、標識符和符號所使用的語法。

             

              清單 5. 示例 Flex 規(guī)則

             

            [   

            ]+ {

              /* eat up whitespace */

              }

            {DIGIT}+ {

              yylval.value = atof(yytext);

              return VALUE;

              }

            {DIGIT}+"."{DIGIT}*    {

              yylval.value = atof(yytext);

              return VALUE;

              }

            {DIGIT}+[eE]["+""-"]?{DIGIT}*    {

              yylval.value = atof(yytext);

              return VALUE;

              }

            {DIGIT}+"."{DIGIT}*[eE]["+""-"]?{DIGIT}*    {

              yylval.value = atof(yytext);

              return VALUE;

              }

            {ID}    {

              yylval.string = malloc(strlen(yytext)+1);

              strcpy(yylval.string, yytext);

              return IDENTIFIER;

              }

            "+"    { return ADD; }

            "-"    { return SUB; }

            "*"    { return MULT; }

            "/"    { return DIV; }

            "("    { return LBRACE; }

            ")"    { return RBRACE; }

            ";"    { return SEMICOLON; }

            "="    { return ASSIGN; }

             

              為了幫助調試,我們在程序運行的末尾把所有已知的變量及其當前內(nèi)容都打印了出來。

             

              使用普通錯誤消息的例子

             

              使用下面的輸入(其中稍微進行了排版)來編譯并運行這個示例解析器程序 ccalc

             

              清單 6. 數(shù)學解析器的示例輸入

             

            a = 3;

            3 aa = a * 4;

            b = aa / ( a - 3 );

             

              輸出結果如下所示:

             

              清單 7. 數(shù)學解析器的示例輸出

             

            Error 'syntax error'

            Error: reference to unknown variable 'aa'

            division by zero!

            final content of variables

              Name------------------ Value----------

              'a          ' 3

              'b          ' 3

              'aa         ' 0

             

              這個輸出結果并非非常有用,因為它并沒有顯示問題到底在什么地方。這在下一節(jié)中會進行介紹。

             

              擴展 Bison 可以更好地處理錯誤消息

             

              Bison 的最主要的特性在 Bison 手冊中隱藏的很深,就是它可以通過使用 YYERROR_VERBOSE 宏在產(chǎn)生語法錯誤的情況下生成更有意義的錯誤消息。

             

              普通的 'syntax error' 消息如下:

             

              Error 'syntax error, unexpected IDENTIFIER, expecting SEMICOLON'

             

              這條消息對于調試更為合適。

             

              更好的輸入函數(shù)

             

              使用原來的錯誤消息,很難判斷語義的錯誤。當然,這個例子非常容易修復,因為我們立即就可以找出有錯誤的那一行。在更加復雜的語法和對應輸入中,這可能并不簡單。讓我們編寫一個輸入函數(shù)來從文件中讀取相應的行。

             

              Flex 具有一個非常有用的宏 YY_INPUT,它負責為符號解釋讀入數(shù)據(jù)。我們可以在 YY_INPUT 宏中添加一個對 GetNextChar() 函數(shù)的調用,后者從文件中讀取數(shù)據(jù),并保留了下一個要讀取的字符的位置信息。GetNextChar() 使用了一個緩沖區(qū)來存放一行輸入。這兩個變量保存了當前行號和該行中下一個字符的位置:

             

              清單 8. 更好的 Flex YY_INPUT

             

            #define YY_INPUT(buf,result,max_size) {

              result = GetNextChar(buf, max_size);

              if ( result <= 0 )

               result = YY_NULL;

              }

             

              使用這個增強的錯誤打印函數(shù) PrintError()(在前面討論過,它可以很好地顯示有問題的輸入行,完整的 PrintError() 源代碼請參看 示例源代碼),我們就具有了一個用戶友好的消息,它顯示了下一個字符的位置:

             

              清單 9. 更好的 Flex 錯誤:字符位置

             

                |....+....:....+....:....+....:....+....:....+....:....+

               1 |a = 3;

               2 |3 aa = a * 4;

            ...... !.....^

            Error: syntax error, unexpected IDENTIFIER, expecting SEMICOLON

               3 |b = aa / ( a - 3 );

            ...... !.......^

            Error: reference to unknown variable 'aa'

            ...... !.................^

            Error: division by zero!

             

              這個示例函數(shù)可以從其他函數(shù)(例如 ReduceDiv())中進行調用,從而打印語義錯誤,例如 division by zero unknown identifiers

             

              如果我們希望標記一下最后使用的符號,就可以對 Flex 規(guī)則進行擴展,并修改錯誤的打印。函數(shù) BeginToken() PrintError()(二者都可以在示例源代碼中找到)是關鍵:BeginToken() 是由每條規(guī)則進行調用的,這樣它就可以記住每個符號的開始和結束,每次打印錯誤時都會調用 PrintError()。這樣,我們就可以生成一條有用的消息了,例如:

             

              清單 10. 更好的 Flex 錯誤:表示確切的符號位置

             

               2 |3 aa = a * 4;

            ...... !..^^............

            Error: syntax error, unexpected IDENTIFIER, expecting SEMICOLON

             

              缺點

             

              所生成的詞法解析器可能會在檢測到某個符號之前讀入多個字符。因此,這個過程不可能精確地顯示確切的位置。它最終取決于為 Flex 所提供的規(guī)則。規(guī)則越復雜,位置的精確程度就越低。這個例子中的規(guī)則可以由 Flex 通過提前查找一個字符來進行處理,這會讓位置的預測更加精確。

             

              Bison 的定位機制

             

              下面讓我們來看一下 division by zero 這個錯誤。最后一次符號讀取(結束括號)并不是這個錯誤的根源。表達式 (a-3) 的值就是 0。對于更好的錯誤消息來說,我們需要知道表達式的位置。要實現(xiàn)這種功能,我們可以在 YYLTYPE 類型的全局變量 yylloc 中提供這個符號的確切位置。使用宏 YYLLOC_DEFAULT(請參看 Bison 文檔 中默認的定義),Bison 可以計算出某個表達式的位置。

             

              記住,只有當您在文法中使用位置時才會定義類型。這是一個常見的錯誤。

             

              默認的位置類型 YYLTYPE 如清單 11 所示。我們可以對這個類型重新進行定義,使其包括更多信息,例如 Flex 所讀取的文件名。

             

              清單 11. 默認位置類型 YYLTYPE

             

            typedef struct YYLTYPE

            {

             int first_line;

             int first_column;

             int last_line;

             int last_column;

            } YYLTYPE;

             

              在上一節(jié)中,我們看到了 BeginToken() 函數(shù),它是在新符號開始時調用的。此時就應該存儲這個位置了。在我們的例子中,一個符號不能跨越多行,因此 first_line last_line 是相同的,它們都保存了當前的行號。其他屬性有符號的起點(first_column)和終點(last_column),這是通過符號的起點和長度計算出來的。

             

              要使用這個位置,我們必須對規(guī)則處理函數(shù)進行處理,如清單 12 所示。符號 $3 的位置是通過 @3 進行引用的。為了防止拷貝這個規(guī)則中的整個結構,我們生成了一個指針 &@3。這看起來可能有點奇怪,但卻是正確的。

             

              清單 12. 記住規(guī)則中的位置

             

            | expression DIV expression

             {

              $$ = ReduceDiv($1, $3, &@3);

             }

             

              在處理函數(shù)中,我們獲得了一個指向保存了位置信息的 YYLTYPE 結構的指針,這樣可以生成一條很好的錯誤消息。

             

              清單 13. ReduceDiv 中使用保存的位置

             

            extern

            double ReduceDiv(double a, double b, YYLTYPE *bloc) {

             if ( b == 0 ) {

              PrintError("division by zero! Line %d:c%d to %d:c%d",

                        bloc->first_line, bloc->first_column,

                        bloc->last_line, bloc->last_column);

              return MAXFLOAT;

             }

             return a / b;

            }

             

              現(xiàn)在錯誤消息可以幫助我們來定位問題了。除零操作錯誤在第 3 行的第 10 列到 18 列之間。

             

              清單 14. 更好的 ReduceDiv() 錯誤消息

             

                |....+....:....+....:....+....:....+....:....+....:....+

               1 |a = 3;

               2 |3 aa = a * 4;

            ...... !..^^...........

            Error: syntax error, unexpected IDENTIFIER, expecting SEMICOLON

               3 |b = aa / ( a - 3 );

            ...... !....^^...............

            Error: reference to unknown variable 'aa'

            ...... !.................^..

            Error: division by zero! Line 3:10 to 3:18

            final content of variables

              Name------------------ Value----------

              'a          ' 3

              'b          ' 3.40282e+38

              'aa         ' 0

             

             

              結束語

             

              Flex Bison 是用來解析文法的一對功能強大的組合。通過使用本文中介紹的技巧,我們可以構建更好的解釋器,它們可以生成像您自己喜歡的編譯器中一樣的有用的、容易理解的錯誤消息。

             

             

            posted on 2008-11-22 22:29 肥仔 閱讀(1232) 評論(0)  編輯 收藏 引用 所屬分類: LEX & YACC

            亚洲精品无码久久千人斩| 亚洲国产精品无码久久SM| 久久精品这里只有精99品| 久久这里都是精品| 久久国产精品一区二区| 久久综合久久综合亚洲| 国产精品99久久精品| 伊人热热久久原色播放www| 国产精品久久久久久福利69堂| 国产综合免费精品久久久| 精品久久久久久中文字幕大豆网| 国产欧美久久一区二区| 精品久久人人爽天天玩人人妻| 色综合久久综精品| 久久精品无码一区二区无码| 久久久国产视频| 欧美日韩精品久久久久| 久久夜色tv网站| 久久丫精品国产亚洲av不卡| 久久综合九色综合久99| 亚洲乱亚洲乱淫久久| 亚洲色欲久久久综合网东京热| 久久精品国产福利国产琪琪| 亚洲国产精品久久66| 久久精品亚洲精品国产色婷| 综合网日日天干夜夜久久| 国产69精品久久久久APP下载| 精品久久久久中文字| 伊人久久大香线蕉影院95| 久久人人爽人人爽人人AV| 99精品久久久久久久婷婷| 亚洲精品无码久久久久久| 久久亚洲精品成人无码网站| 精品视频久久久久| 欧美精品福利视频一区二区三区久久久精品 | 久久精品国产只有精品66| 久久久久免费精品国产| 久久91综合国产91久久精品| 久久最近最新中文字幕大全 | 久久久久久久亚洲精品 | 国产成人精品免费久久久久|