Python之父新發文,將替換現有解析器

来源:https://www.cnblogs.com/pythonista/archive/2019/07/27/11256098.html
-Advertisement-
Play Games

花下貓語: Guido van Rossum 是 Python 的創造者,雖然他現在放棄了“終身仁慈獨裁者”的職位,但卻成為了指導委員會的五位成員之一,其一舉一動依然備受矚目。近日,他開通了 Medium 賬號,併發表了第一篇文章,透露出要替換 Python 的核心部件(解析器)的想法。這篇文章分析 ...


花下貓語: Guido van Rossum 是 Python 的創造者,雖然他現在放棄了“終身仁慈獨裁者”的職位,但卻成為了指導委員會的五位成員之一,其一舉一動依然備受矚目。近日,他開通了 Medium 賬號,併發表了第一篇文章,透露出要替換 Python 的核心部件(解析器)的想法。這篇文章分析了當前的 pgen 解析器的諸多缺陷,並介紹了 PEG 解析器的優點,令人振奮。這項改造工作仍在進行中,Guido 說他還會寫更多相關的文章,我們就拭目以待吧。

本文原創並首發於公眾號【Python貓】,未經授權,請勿轉載。

原文地址: https://mp.weixin.qq.com/s/yqVVaZVn8RRanllaXMFD9A


原題 | PEG Parsers

作者 | Guido van Rossum(Python之父)

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

原文 | https://medium.com/@gvanrossum_83706/peg-parsers-7ed72462f97c

聲明 | 翻譯是出於交流學習的目的,歡迎轉載,但請保留本文出處,請勿用於商業或非法用途。

幾年前,有人問 Python 是否會轉換用 PEG 解析器(或者是 PEG 語法,我不記得確切內容、誰說的、什麼時候說的)。我稍微看過這個主題,但沒有頭緒,就放棄了。

最近,我學了很多關於 PEG(Parsing Expression Grammars)的知識,如今我認為它是個有趣的替代品,正好替換掉我在 30 年前剛開始創造 Python 時自製的(home-grown)語法分析生成器(parser generator)(那個語法分析生成器,被稱為“pgen”,是我為 Python 寫下的第一段代碼)。

我現在感興趣於 PEG,原因是對 pgen 的局限性感到有些惱火了。

它使用了我自己寫的 LL(1) 解析的變種——我不喜歡可以產生空字元串的語法規則,所以我禁用了它,進而稍微地簡化了生成解析表的演算法。

同時,我還發明瞭一套類似 EBNF 的語法符號(譯註:Extended Backus-Naur Form,BNF 的擴展,是一種形式化符號,用於描述給定語言中的語法),至今仍非常喜歡。

以下是 pgen 令我感到煩惱的一些問題。

LL(1) 名字中的 “1” 表明它只使用單一的前向標記符(a single token lookahead),而這限制了我們編寫漂亮的語法規則的能力。例如,一個 Python 語句(statement)既可以是表達式(expression),又可以是賦值(assignment)(或者是其它東西,但那些都以 if 或 def 這類專用的關鍵字開頭)。

我們希望使用 pgen 表示法來編寫如下的語法。(請註意,這個示例描述了一種玩具語言(toy language),它是 Python 的一個微小的子集,就像傳統中的語言設計一樣。)

statement: assignment | expr | if_statement
expr: expr '+' term | expr '-' term | term
term: term '*' atom | term '/' atom | atom
atom: NAME | NUMBER | '(' expr ')'
assignment: target '=' expr
target: NAME
if_statement: 'if' expr ':' statement

關於這些符號,解釋幾句:NAMENUMBER 是標記符(token),預定義在語法之外。引號中的字元串如 '+' 或 'if' 也是標記符。(我以後會講講標記符。)語法規則以其名稱開頭,跟在後面的是 : 號,再後面則是一個或多個以 | 符號分隔的可選內容(alternatives)。

但問題是,如果你這樣寫語法,解析器不會起作用,pgen 將會罷工。

其中一個原因是某些規則(如 exprterm)是左遞歸的,而 pgen 還不足以聰明地解析。這通常需要通過重寫規則來解決,例如(在保持其它規則不變的情況下):

expr: term ('+' term | '-' term)*
term: atom ('*' atom | '/' atom)*

這就揭示了 pgen 的一部分 EBNF 能力:你可以在括弧內嵌套可選內容,並且可以在括弧後放 * 來創建重覆,所以這裡的 expr 規則就意味著:它是一個術語(term),跟著零個或多個語句塊,語句塊內是加號跟術語,或者是減號跟術語。

這個語法相容了第一個版本的語言,但它並沒有反映出語言設計者的本意——尤其是它並沒有表明運算符是左綁定的,而這在你嘗試生成代碼時非常重要。

但是在這種玩具語言(以及在 Python)中,還有另一個煩人的問題。

由於前向的單一標記符,解析器無法確定它查看的是一個表達式的開頭,還是一個賦值。在一個語句的開頭,解析器需要根據它看到的第一個標記符,來決定它要查看的 statement 的可選內容。(為什麼呢?pgen 的自動解析器就是這樣工作的。)

假設我們的程式是這樣的:

answer = 42

這句程式會被解析成三個標記符:NAME (值是 answer),‘=’ 和 NUMBER (值為 42)。在程式開始時,我們擁有的唯一的前向標記符是 NAME 。此時,我們試圖滿足的規則是 statement (這個語法的起始標誌)。此規則有三個可選內容:exprassignment 以及 if_statement 。我們可以排除if_statement ,因為前向標記符不是 “if”。

但是 exprassignment 都能以 NAME 標記符開頭,因此就會引起歧義(ambiguous),pgen 會拒絕我們的語法。

(這也不完全正確,因為語法在技術上並不會導致歧義;但我們先不管它,因為我想不到更好的詞來表達。那麼 pgen 是如何做決定的呢?它會為每條語法規則計算出一個叫做 FIRST 組的東西,如果在給定的點上,FIRST 組出現了重疊選項,它就會抱怨)(譯註:抱怨?應該指的是解析不下去,前文譯作了罷工)。

那麼,我們能否為解析器提供一個更大的前向緩衝區,來解決這個煩惱呢?

對於我們的玩具語言,第二個前向標記符就足夠了,因為在這個語法中,assignment 的第二個標記符必須是 “=”。

但是在 Python 這種更現實的語言中,你可能需要一個無限的前向緩衝,因為在 “=” 標記符左側的東西可能極其複雜,例如:

table[index + 1].name.first = 'Steven'

在 “=” 標記符之前,它已經用了 10 個標記符,如果想挑戰的話,我還可以舉出任意長的例子。為了在 pgen 中解決它,我們的方法是修改語法,並增加一個額外的檢查,令它能接收一些非法的程式,但如果檢查到對左側的賦值是無效的,則會拋出一個 SyntaxError

對於我們的玩具語言,這可歸結成如下寫法:

statement: assignment_or_expr | if_statement
assignment_or_expr: expr ['=' expr]

(方括弧表示了一個可選部分。)然後在隨後的編譯過程中(比如,在生成位元組碼時),我們會檢查是否存在 “=”,如果存在,我們再檢查左側是否有 target 語法。

在調用函數時,關鍵字參數也有類似的麻煩。我們想要寫成這樣(同樣,這是 Python 的調用語法的簡化版本):

call: atom '(' arguments ')'
arguments: arg (',' arg)*
arg: posarg | kwarg
posarg: expr
kwarg: NAME '=' expr

但是前向的單一標記符無法告訴解析器,一個參數的開頭中的 NAME 到底是 posarg 的開頭(因為 expr 可能以 NAME 開頭)還是 kwarg 的開頭。

同樣地,Python 當前的解析器在解決這個問題時,是通過特別聲明:

arg: expr ['=' expr]

然後在後續的編譯過程中再解決問題。(我們甚至出了點小錯,允許了像 foo((a)=1) 這樣的東西,給了它跟 foo(a=1) 相同的含義,直到 Python 3.8 時才修複掉。)

那麼,PEG 解析器是如何解決這些煩惱的呢?

通過使用無限的前向緩衝!PEG 解析器的經典實現中使用了一個叫作“packrat parsing”(譯註:PackRat,口袋老鼠)的東西,它不僅會在解析之前將整個程式載入到記憶體中,而且還能允許解析器任意地回溯。

雖然 PEG 這個術語主要指的是語法符號,但是以 PEG 語法生成的解析器是可以無限回溯的遞歸下降(recursive-descent)解析器,“packrat parsing”通過記憶每個位置所匹配的規則,來使之生效。

這使一切變得簡單,然而當然也有成本:記憶體。

三十年前,我有充分的理由來使用單一前向標記符的解析技術:記憶體很昂貴。LL(1) 解析(以及其它技術像 LALR(1),因 YACC 而著名)使用狀態機和堆棧(一種“下推自動機”)來有效地構造解析樹。

幸運的是,運行 CPython 的電腦比 30 年前有了更多的記憶體,將整個文件存在記憶體中確實已不再是一個負擔。例如,我能在標準庫中找到的最大的非測試文件是 _pydecimal.py ,它大約有 223 千位元組(譯註:kilobytes,即 KB)。在一個 GB 級的世界里,這基本不算什麼。

這就是令我再次研究解析技術的原因。

但是,當前 CPython 中的解析器還有另一個 bug 我的東西。

編譯器都是複雜的,CPython 也不例外:雖然 pgen-驅動的解析器輸出的是一個解析樹,但是這個解析樹並不直接用作代碼生成器的輸入:它首先會被轉換成抽象語法樹(AST),然後再被編譯成位元組碼。(還有更多細節,但在這我不關註。)

為什麼不直接從解析樹編譯呢?這其實正是它最早的工作方式,但是大約在 15 年前,我們發現編譯器因為解析樹的結構而變得複雜了,所以我們引入了一個單獨的 AST,還引入了一個將解析樹翻譯成 AST 的環節。隨著 Python 的發展,AST 比解析樹更穩定,這減少了編譯器出錯的可能。

AST 對於那些想要檢查(inspect)Python 代碼的第三方代碼,也更加容易,它還通過被大眾歡迎的 ast 模塊而公開。這個模塊還允許你從頭構建 AST 節點,或是修改現有的 AST 節點,然後你可以將新的節點編譯成位元組碼。

後一項能力支撐起了一整個為 Python 語言添加擴展的家庭手工業(譯註:ast 模塊為 Python 的三方擴展提供了便利)。(藉助 parser 模塊,解析樹同樣能面向 Python 的用戶開放,但它使用起來太麻煩了,因此相比於 ast 模塊,它就過時了。)

綜上所述,我現在的想法是看看能否為 CPython 創造一個新的解析器,在解析時,使用 PEG 與 packrat parsing 來直接構建 AST,從而跳過中間解析樹結構,並儘可能地節省記憶體,儘管它會使用無限的前向緩衝。

我還沒進展到這個地步,但已經有了一個原型,可以將一個 Python 的子集編譯成一個 AST,其速度與當前 CPython 的解析器大致相當。只不過,它占用的記憶體更多,所以我預計在將它擴展到整個語言時,將會降低 PEG 解析器的速度。

但是,我還沒去優化它,所以還是挺有希望的。

轉換成 PEG 的最後一個好處是它為語言的未來演化提供了更大的靈活性。

過去有人曾說,pgen 的 LL(1) 缺陷幫助了 Python 保持語法的簡單。這很有道理,但我們還有很多適當的流程,可以防止語言不受控制地膨脹(主要是 PEP 流程,在非常嚴格的向後相容性要求以及新的治理結構的幫助下)。所以我並不擔心。

我還有很多內容要寫,關於 PEG 解析以及我的具體實現,但是要等我整理好代碼後,在後續的文章中再去寫了。

公眾號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關註哦。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Swing的輸入框仍然分成兩類:單行輸入框和多行輸入框,但與AWT的同類控制項相比,它們在若幹細節上有所調整。首先說單行輸入框,AWT的單行輸入框名叫TextField,平時輸入什麼字元它便顯示什麼字元,可一旦調用了setEchoChar方法設置回顯字元,TextField馬上變成只顯示密文字元了。然 ...
  • 在使用Visual C++ 6.0打開文件時可能會出現下麵的情況 這可能是Vc6.0和win7相容性問題。 方法: 下載filetool即可 鏈接:https://pan.baidu.com/s/1Xmx0XI0Dy9uZGJEQW4cHQg 提取碼:drgz 下載之後,解壓到一個目錄,我這個是解壓 ...
  • 關於python單例模式是什麼,以及一些實現方法的整理與講解。 ...
  • 今天在刷題的時候用到了正則,用的過程中就感覺有點不太熟練了,很久沒有用正則都有點忘了。所以現在呢,我們就一起來review一下python中正則模塊re的用法吧。 今天是review,所以一些基礎的概念就不做介紹了,先來看正則中的修飾符以及它的功能: 修飾符 re.I 使匹配對大小寫不敏感 re.L ...
  • 題目 "The XOR Largest Pair" 解析 一年前聽學長講這道題,什麼01trie,好高級啊,所以沒學,現在一看。。。。 看到xor就應該想到二進位,一看數據$A_i using namespace std; const int N = 4e6 + 10; int n, a, num, ...
  • 一個大的系統,在代碼的復用肯定是必不可少的,它能解決: 1. 統一的響應處理(可以對外提供統一的響應對象包裝) 2. 統一的異常處理(可以將業務異常統一收集處理) 3. 通用代碼定義、配置定義(通用的配置信息放在統一的代碼管理中,便於維護和更新) 創建項目 POM文件 項目結構 vo (統一響應對象 ...
  • go中已經實現了int->bin的轉化函數,我這裡只是化過程邏輯的實現,至於原理我就假設大家都知道了 本案例只考慮 int->bin 的轉化 包含了正整數,負整數,0 的轉化 結果 : 比如-11111的轉化: 比如-1的轉化: ...
  • 一、Numpy簡介 NumPy(Numerical Python) 是 Python 語言的一個擴展程式庫,支持大量的維度數組與矩陣運算,此外也針對數組運算提供大量的數學函數庫。NumPy 是一個運行速度非常快的數學庫,主要用於數組計算,包含: NumPy 通常與 SciPy(Scientific ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...