Perl 6 終於即將面世。在本文中,Ted 將向您介紹 Perl 6 語言的語法和正則表達式,並將它們與當前可用的 Perl 5 Parse::RecDescent 模塊進行對比。認識 Perl 正則表達式的新特性,並學會如何具體使用新奇而又功能強大的 Perl 腳本語言。 對所有 Perl 編程人員而言,Perl 6 項目是一個熱門話題。Perl 一直是一門不斷發展的語言,幾乎從任何可以想像得到的角度都可以確定,Perl 6 確實是由 Perl 5 進化而來(不過,您也可以說它們的起源相同)。Perl 6 將運行於 Parrot 之上,Parrot 是一種通用虛擬機,不但可以加載和解釋 Perl 6 字節碼,還可以加載和解釋其他許多語言。 不要讓將來的問題困擾著您。如果您曾經用了幾個月的時間來觀察某個建築物的建築過程,您就會知道,選好地基後要進行挖掘,金屬骨架似乎總是矗立著。工人們來來往往,工作一直在進行,但是表面看到的卻總是陳舊的、丑陋的、生銹的金屬。然後,若干天以後,突然間,建築物就完成了。Perl 6 項目當前正是處於那個長期的中間階段,表面看到的只是生銹的金屬,而工人們正在深入地下進行幕後工作。如果您想洞悉項目的進展,那麼可以去查看最新的 Parrot 發行版本以及 Perl 6 每周的更新(請參閱參考資料中的鏈接)。 本文將向您介紹 Perl 6 語言的語法和正則表達式,並將這些與當前可用的 Perl 5 Parse::RecDescent 模塊進行對比。如果以前對 Perl 5 有所了解,熟悉 Parse::RecDescent,並且有詞法分析(lexing)和句法分析(parsing)方面的經驗,那麼這些會對您掌握本文有很大幫助,此外,本文是為那些對 Perl 6 語法和正則表達式感興趣的所有 Perl 編程人員撰寫的。
Perl 6 正則表達式和語法概述 首先需要聲明一件事:Perl 將通過使用 :p5 修飾符來支持 Perl 5 正則表達式。對於那些對 Perl 6 正則表達式不感興趣或者不想轉到這方面來的人而言,這是一個福音。此外,Perl 6 正則表達式可能(但不是必須)與 Perl 5 中對應的正則表達式有本質上的區別。 在需要時,Perl 6 正則表達式可以被復用。在匹配單一的詞時,復用正則表達式是很荒謬的;但在解析配置文件時,幾乎必須要復用正則表達式(這取決於配置文法的復雜度、發生修改的頻率等)。 在 Perl 5 中,RegeXP::Common 模塊(請參閱參考資料)已經在嘗試復用正則表達式,但是,因為 Perl 5 不允許復用正則表達式,所以不得不將它們封裝在一個模塊接口中。 Perl 6 完全支持這種復用。 盡管您可以編寫類似 Perl 5 正則表達式的模糊而晦澀的 Perl 6 正則表達式,但在默認情況下,允許啟用空格注解;所以,雖然在 Perl 5 中您可以用“hello there”本身來匹配“hello there”,但在 Perl 6 中,您必須將其改為 /hello <sp> there/。這樣就可以在正則表達中將條件清晰地分離開來。 更重要的是,在語法(grammars)內部使用正則表達式時,Perl 6 正則表達式必須不那麼晦澀。編程人員會發現(我希望如此, Larry Wall 也是),對清單 2 的理解與維護要比對清單 1 的容易得多: 清單 1. 沒有語法的正則表達式 # note this is just a language example, not an accurate name matcher # Perl 6 <[A-Z]> is equivalent to the Perl 5 [A-Z] # Perl 6 :w modifier surrounds all tokens with "automagic" whitespace, # which basically means it will match what most people would call # "Words" $name = m:w/ <[A-Z]><[a-z]>+ <[A-Z]><[a-z]>+ /; 清單 2. 在語法中作為規則的正則表達式 # note this is just a language example, not an accurate name matcher grammar English { rule name :w { <singlename> <singlename> }; rule singlename { <[A-Z]><[a-z]>+ }; }; 清單 2 不僅更容易讀懂,而且維護起來也更容易。例如,Perl 6 本身已經定義了 <upper> 和 <lower> 規則,這使事情變得更為簡單: 清單 3. 在語法中作為規則的經過改進的正則表達式 # note this is just a language example, not an accurate name matcher grammar Names { rule name :w { <singlename> <singlename> }; rule singlename { <upper><lower>+ }; }; 瞧!使用 <upper> 和 <lower> 之後,我們就復用了代碼。此外,我們現在還可以處理 Unicode 名稱,而這之前,我們只能局限於處理從 A 到 Z 開頭的名稱。代碼復用是一項出色的技術。 在進行更進一步的維護時,幾乎總是需要對名稱中的破折號或其他名稱(比如 Don Quixote de la Mancha)進行修正(舉例來說)。同樣,在將對個別規則的更改隔離出來,或者在需要時創建一個新規則的時候,您會注意到這是多麼簡單。 語法(Grammars) 是相當簡單的概念。它們是具有專用名稱空間(namespace)和專用子例程的程序包;每一個子例程被稱為一個規則。語法可以繼承其他語法。這樣就使得編程人員既可以復用其他人的代碼,也可以編寫能夠復用的代碼。從 Perl 模塊的 CPAN 存檔文件(archive)獲得的成功中可以明顯地看出這種復用的價值。Perl 6 語法在規則中使用正則表達式,然後可以將這些規則用於其他規則之中。
對比 Parse::RecDescent 與 Perl 6 的語法 熟悉 Parse::RecDescent 的人都知道,它是一個功能強大的工具。Parse::RecDescent 是一個 Perl 5 模塊,只使用很少代碼就可以生成非常強大的語法。這些語法與 Perl 6 的語法是否非常相似呢?是這樣的,Parse::RecDescent 的作者 Damian Conway 深入參與了 Perl 6 的許多工作。因此,很多在 Parse::RecDescent 中證明好用的思想都被應用到 Perl 6 中也就不足為奇了。它們的一些語法有很多類似之處。 Parse::RecDescent(此後稱之為 P::RD)使用 new() 模塊文法來創建新的語法。每個 P::RD 語法成為 P::RD 類中的一個對象,語法中的每一個規則都可以作為用來執行動作的方法。P::RD 語法可以將動作(action)與每一個規則關聯起來,將其作為解析過程中的一個完整部分。在 Perl 5 本身中,解析是一個事件,而使用了擴展語法的動作則是達成目標途徑中的不幸犧牲品(roadkill),那些擴展的語法被證明是造成迷惑的罪魁禍首。這一區別使得 P::RD 比 Perl 5 正則表達式更為有效,原因在於當檢測到匹配對象時,它會使某些事情發生。 Perl 6 語法吸取了 P::RD 的經驗,它意識到了這些動作的實用性,現在,這些動作已經成為其首要的組成部分。每發現一個匹配對象,就會執行一個動作(代碼塊)。即使匹配對象的內容可能已經被修改也是如此!此外,這些動作的語法與 P::RD 中的語法同樣簡單。
清單 4. 包含動作的 Parse::RecDescent 語法 # small extract from my cfperl.pl program's global parser my $parse_global = new Parse::RecDescent (q{ input: blank comment class section comment: /^\s*/ '#' { 1; } blank: /^\s*$/ { 1; } section: /\w+/ ':' { $::current_section = $item[1]; $::current_classes = 'any'; 1; } class: compound_class '::' { $::current_classes = $item{compound_class}; 1; } compound_class: /[-!.\w]+/ }); $parse_global->input("TEXT GOES HERE"); 上面的語法只有一個規則,即 input,它將匹配 blank、comment、class 或者 section 規則。這些規則中的每一個規則都有一個定義,它們可以是獨立的或是基於另一個規則的,也可以同時具備這兩種特性。 注意像普通的代碼塊那樣封裝在大括號 { } 內的動作。對於一個片斷(section),動作將全局變量 $current_section 設置為正在進行匹配的片斷,並重新設置 $current_classes 全局變量。對於類,動作將全局的 $current_classes 變量設置為匹配的條目。 這個語法在 Perl 6 中會是什麼樣的呢?
清單 5. 清單 4 語法的 Perl 6 譯本 # this may be buggy - it's certainly untested # every input is known to be one line, without newline characters grammar Global { rule input { <blank> <comment> <class> <section> } rule blank { ^^ \s* $$ } rule comment { ^^ \s* \# } rule section { (\w+) \s* \: { $::current_section = $1; $::current_classes = 'any'; } } rule class { (<compound_class>) \s* \:\: { $::current_classes = $1; } } rule compound_class { <[-!.\w]>+ } }
Perl 5 的正則表達式 如果您對 Perl 5 正則表達式非常熟悉,那麼可以跳過這一節。 所有 Perl 5 編程人員都熟悉 Perl 5 正則表達式。在進行匹配時,要用 m// 操作符來標識這些正則表達式(有時是可選的),而當匹配並替換時,則要用 s/// 操作符來標識它們。在特定的情況下,/ 字符可以由其他字符取代,並且有一些特定的操作符,它們與正則表達式有幾分類似,不過這樣的操作符不多(例如 tr///)。Perl 5 正則表達式要指明的或者是“尋找此內容”,或者是“尋找此內容,並