Python 之父的解析器系列之六:給 PEG 語法添加動作

Guido 的解析器系列更新了 7 篇,他的生產力真旺盛啊。這對於新的解析器來說是件好事,但對於我來說卻是個不小的挑戰:需要一定的時間和精力,而我對解析器的知識極爲欠缺,也造成了翻譯過程的不順暢。

現在,我把譯文提到 Github 上了( guido_blog_translation ),希望有熱心腸的同學能來幫忙審閱指正,或者認領翻譯任務吧。最後,提前祝大家中秋節快樂,闔家團圓,諸事和美。

原題 | Adding Actions to a PEG Grammar

作者 | Guido van Rossum(Python之父)

譯者 | 豌豆花下貓(“Python貓”公衆號作者)

聲明 | 本翻譯是出於交流學習的目的,基於 CC BY-NC-SA 4.0 授權協議。 爲便於閱讀,內容略有改動。

如果你在語法規則中還可以添加(某些)語義,那麼語法就會更好。特別是對於我正在構建的 Python 解析器,我需要控制每個備選項返回的 AST 節點,因爲 AST 的格式已經規定好。

【這是我的 PEG 系列的第 6 部分。其餘部分請參閱系列概述】 (譯註: 本系列的譯文已在 Github 開源,項目地址: https://github.com/chinesehuazhou/guido_blog_translation )

許多語法都有支持給規則添加動作的約定,通常是 { 花括號 } 內的一段代碼塊。更確切地說,行動與備選項相關聯。動作塊中的代碼通常與編寫編譯器的語言相同,如 C 語言,增加一些工具,用於引用備選項中的條目。在 Python 原始的 pgen 中,我沒有添加此功能,但對於這個新項目,我希望使用它。

對於在這一系列博客文章中開發的簡化版解析器生成器,下面是我們採用的做法。

一般而言,動作的語法如下:

rule: item item item { action 1 } | item item { action 2 }

因爲它會使語法變得冗長,所以解析器生成器通常支持跨行分割規則,例如:

rule: item item item { action 1 }
    | item item { action 2}

它會使語法分析器變得複雜,但可讀性更重要,所以我會使用這種方式。

一個永恆的問題是何時執行動作塊。在 Yacc / Bison 中,因爲沒有回溯,一旦規則被解析器識別到,就會執行動作塊。每個動作會立即執行,這意味着即使操作具有全局副作用,還是會順利執行(例如更新符號表或其它編譯器數據結構)。

在 PEG 解析器中,因爲有無限回溯,我們有其它的選擇:

  • 延遲所有動作,直到解析完所有內容。這對我的目的沒有用,因爲我想在解析期間構造一個 AST。

  • 只要識別出動作所對應的備選項就執行之,但要求操作代碼是冪等的(即無論執行多少次,都具有相同的效果)。這意味着可以執行某個動作,但其結果最終會被丟棄。

  • 緩存動作的結果,因此只有第一次在給定位置識別到備選項時,對應的動作才執行。

我要採用第三個選項——正好我們用 packrat 算法緩存東西,所以我們也可以緩存動作的結果。

關於 {花括號} 裏面的內容,傳統上是使用 C 語言,它約定用 $ 符號來引用已識別的備選項(例如, $1 引用第一個條目),並賦值給 $$ 以指示動作的結果。

在我看來這太老古董了(我記得曾在 Algol-60 中使用對函數名的賦值,來指定返回值),所以我會用一些更 Pythonic 的方式:在括號內,你需要放置一個單一的表達式,它的值是動作的值,而條目的引用則是一些簡單的名稱,給出着條目的文本。

舉個例子,這是一個簡單計算器,可作加減法:

start: expr NEWLINE { expr }
expr: expr '+' term { expr + term }
    | expr '-' term { expr - term }
    | term { term }
term: NUMBER { float(number.string) }

當我們運行時,給定輸入 100+50-38-70 ,它會識別出各部分並計算答案,計算成 ((100+50)-38)-70 ,當然得出結果爲 42。

一個小細節:在 term 的動作中,變量 number 保存了一個 TokenInfo 對象,因此該動作必須使用其 .string 屬性來獲取字符串形式的標識符。

當一個備選項中多次出現相同的規則名稱時,我們該怎麼辦?對同一備選項中出現的規則,解析器生成器會給出唯一的名稱,即在隨後出現的規則上添加 1、2 等等。例如:

factor: atom '**' atom { atom ** atom1 }
      | atom { atom }

它的實現很無聊,所以我請你們 check out 代碼 ,自己看看。試試這個:

python3.8 -m story5.driver story5/calc.txt -g story5.calc.CalcParser

可視化功能現在支持使用左右箭頭鍵來回移動!

本文內容與示例代碼的授權協議:CC BY-NC-SA 4.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章