簡體   English   中英

用於自動分號插入的 Bison 錯誤恢復

[英]Bison error recovery for automatic semicolon insertion

我正在嘗試編寫一個 Bison C++ 解析器來解析 JavaScript 文件,但我不知道如何使分號成為可選的。

至於 ECMAScript 2018 規范( https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf ,第 11.9 章),分號實際上不是可選的,而是在解析。 在規范中,它指出:

當從左到右解析源文本時,遇到任何語法產生式都不允許的記號(稱為違規記號)時,如果出現下列情況中的一個或多個,則會在違規記號前自動插入分號以下條件為真:

  • 違規令牌與前一個令牌之間至少有一個 LineTerminator[...]

據此,我試圖以這種天真的方式解決這個問題:

  • 檢測錯誤,使用error特殊標記;
  • 告訴詞法分析器在操作期間發生了語法錯誤; 如果在當前標記之前遇到換行符,詞法分析器將在下一次yylex調用時返回一個新的分號標記; 在隨后的調用中,它將返回先前發生語法錯誤時有問題的標記。

我的解析器的一個非常簡化的結構如下所示:

program:
   stmt_list END
;

stmt_list:
    %empty
 |  stmt_list stmt
 |  stmt_list error  { /* error detected; tell the lexer about the syntax error */ }
;

stmt:
    value SEMICOLON
|   [other types of statements...]
;

value:
    NUMBER
|   STRING
;

但是這樣做,如果文件包含一個沒有終止分號但有一個換行符的有效 JavaScript 語句,當遇到有問題的標記時,解析器會將語句的其余部分減少為error特殊標記。 當我告訴詞法分析器語法錯誤時,解析器已經將error標記減少到stmt_list一個,並且之前的有效指令丟失,使得分號插入無用。

顯然我不想讓我的解析器丟棄有效的語句並轉到下一個。

我怎樣才能做到這一點? 這是正確的方法還是我錯過了什么?

我不相信這種方法是可行的。

請注意,您必須在發生任何減少之前檢測到錯誤。 因此,對於語句末尾的分號插入,您需要將錯誤產生式添加到stmt ,而不是stmt_list 所以你最終會得到這樣的結果:

stmt_list
     :  %empty
     |  stmt_list stmt

stmt: value ';'   { handle_value_stmt(); }
    | value error { handle_value_stmt(); }
    | [other types of statements...]

這不會插入分號; 它只是假裝插入了分號。 (如果無法插入分號,則會觸發另一個錯誤。)

但是因為不涉及詞法分析器,所以不管是不是行尾漏分號都會發生,太熱情了。 所以理想的解決方案是以某種方式告訴詞法分析器生成一個分號標記作為下一個標記。 但是在檢測到錯誤的時候,詞法分析器已經產生了先行標記,解析器知道先行標記是什么。 它將使用其記錄的前瞻標記來繼續解析。

還有一個問題是此時如何與詞法分析器進行通信,因為中間規則操作與錯誤恢復算法並不能很好地配合。 理論上,您可以使用yyerror將被調用來報告錯誤的事實,但這意味着yyerror需要能夠推斷出這不是“真正的”錯誤,這意味着它必須進入yyparse '膽量。 (我確信這是可能的,但我不知道如何做到這一點,而且在我看來它並不值得推薦。)

現在,理論上可以告訴解析器丟棄前瞻標記,並告訴詞法分析器生成一個分號,后跟它剛剛發送的標記的重復。 因此,如果您足夠頑固,通過將 hack 堆積在 hack 上,您幾乎不可能完成這項工作。 但是你最終會得到一些非常難以維護、驗證和測試的東西。 (並確保它在所有極端情況下都有效也將是一個挑戰。)

這還沒有考慮可以插入分號的其他情況。

我對 ASI 的方法是通過找出哪些連續標記對是可能的來簡單地分析語法。 (這很容易做到;您只需要構造 FIRST 和 LAST 集,然后閱讀所有查看連續符號的產生式。)然后如果輸入由標記 A 后跟一個或多個換行符和標記 B 組成,並且它在語法中 A 后面不可能跟 B ,那么這是分號插入的候選者。 分號插入可能會失敗,但這會產生語法錯誤,因此您不會得到誤報。 (您可能需要修復語法錯誤消息,但此時您至少知道您插入了一個分號。)

證明該算法有效更棘手,因為理論上可能會出現這樣的情況,即A可以在某些上下文中跟在B之后,但在當前上下文中是不可能的,而A ; B A ; B在當前情況下是可能的。 在這種情況下,您可能會錯過可能的分號插入。 我沒有詳細查看最近的 JS 版本,但是很久以前當我寫一個 JS 詞法分析器時,我設法證明了我自己滿意的情況,沒有這樣的情況。


注意:由於問題是在評論中提出的,我會稍微揮手,盡管我真的不建議遵循這種方法。

如果不深入了解野牛的內臟,真的不可能“取消移動”一個標記,包括error標記(或多或少是一個真正的標記)。 error標記被轉移時,解析實際上被提交到錯誤產生。 所以如果你想取消錯誤,你必須接受這個事實並解決它。

error標記被移動后,解析器將跳過標記直到遇到可移動標記。 因此,如果您設法將自動分號插入令牌流,則可以使用該令牌作為保護:

    stmt: value ';'       { handle_value_stmt(); }
        | value error ';' { handle_value_stmt(); }

但是,您可能無法插入自動分號,在這種情況下,您確實需要報告語法錯誤(並可能嘗試重新同步)。 上面的規則只會默默地將標記放到下一個分號,這肯定是錯誤的。 所以第一個近似值是你的 ASI 插入器總是插入一些東西,它可以在錯誤產生中用作保護:

    stmt: value ';'       { handle_value_stmt(); }
        | value error ';' { handle_value_stmt(); }
        | value error NO_ASI { handle_real_error(); }

這對於“錯誤中止”處理來說已經足夠了,但是如果你想進行錯誤恢復,你需要做更多的黑客。

正如我所說,我真的不建議走這條路。 最終結果不會很漂亮,即使它有效(並且您仍然可能會發現您認為有效的代碼在實際用戶輸入時失敗,如果您沒有考慮過。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM