摘要:首位圖靈獎得主Alan Perlis曾說:“如果一門編程語言不能影響你的思維,就沒學習的必要?!蹦芡ㄟ^這個嚴苛測試的語言寥寥無幾,但PostScript在測試中至少得A。它徹底地改變了桌面出版行業,許多特性至今仍值得借鑒。
PostScript的領域對象和操作
作為針對桌面出版的文檔描述語言,PostScript的設計者力圖要解決的核心問題,是如何設計一個靈活高效的語言,以操控桌面出版中各種各樣的圖形對象,并保證設備無關性。我們不妨戴上語言設計者的眼鏡,來模擬一下這個過程。
我們面臨的首要問題是如何描述桌面出版中的種種復雜對象和操作。盡管任何平面出版物終都是二維像素點的集合,但我們并不希望這個語言局限于描述像素點的顏色。這個語言好還能直接描述文字、線條、形狀等設計師熟悉的對象。因為從根本上講,如果我們要設計的描述語言沒有足夠的表達能力,不能精簡高效地表達圖片、字體、形狀、顏色等桌面出版領域的業務對象,這個語言將不可避免地“難用”。一般來說,把領域特定語言設計得“好用”,需要深厚的領域知識(domain knowledge)。所幸的是,PostScript的設計者們,原先在施樂PARC從事激光打印機控制語言設計,對于桌面出版可算駕輕就熟。因此,他們毫不費力地選取了Bézier曲線、矢量字體、繪圖路徑(Path)等作為整個繪圖系統的基本結構。在對這些對象的操作上,PostScript選取了平移、旋轉、放縮等仿射變換,加上路徑操作和字體控制,構成了一個強大但規整的繪圖系統。
PostScript繪圖系統的設計深刻影響了后來的許多矢量圖形系統。舉例說,如今計算機使用的矢量字體均采用Bézier曲線描述,即起源于PostScript;如今幾乎所有的矢量繪圖語言都支持的“路徑”,也起源于PostScript。我們不在此詳細展開這些領域對象選取背后的原因,對PostScript感興趣的讀者可以閱讀《PostScript Language Tutorial & Cookbook》(也稱“Bluebook”)了解PostScript的一些基本概念。
PostScript的語言設計
基本領域對象確定后,接下來就是力求設計出一個“靈活高效”和“設備無關”的語言來控制這些領域對象。設計目標落實為具體需求,包含以下三點。
,語言本身要能表達曲線、字體、圖片、形狀等領域對象;顏色、分頁及這些對象的平移旋轉等操作,在語言里好也都是一等公民,能直接表達。
第二,語言的表達能力要足夠強大,好是圖靈完全,以支持靈活的需求。
第三,語言要與設備無關,也就是說,語言將運行在一個虛擬機或解釋器上,而非直接編譯為二進制代碼。
考慮到我們要設計的語言是針對桌面出版的,終還要加上一條:這個語言的語法和結構要足夠簡單,使得非編程專業人士也能使用。
有了需求的指導,我們不難理解PostScript所采取的設計:以一個易用的、圖靈完全的語言作為藍本,加入眾多針對桌面出版的對象操作,并實現一個輕量的、與設備無關的解釋器。事實上,PostScript是以FORTH語言作為藍本設計的。選取FORTH的主要原因,是因為它是一個輕量級的、基于棧虛擬機的語言。FORTH的表達能力和易用性當時已被實踐所證明,因此借用它的基本控制語法就是一個很自然的選擇。
逆波蘭表示法和度量單位
逆波蘭表示法是FORTH和PostScript等基于棧的語言的一個鮮明特點。在ALGOL家族語言中,3乘以4的一般寫法是3 * 4,即運算符中綴。PostScript將運算符后綴,寫作“3 4 mul”。意思是將3、4分別推入棧中,然后將乘法(multiply)操作運用于兩個棧頂元素(彈出),并將乘積結果入棧。FORTH仍然采用+、*等數學符號。PostScript規范化了所有的操作符,一致采用add、mul等單詞操作符來代替+、* 等傳統的中綴操作符。我們稍后將闡明規整化的優點。這里只需要了解一點:PostScript程序本質上是一個后綴表達式。PostScript沒有所謂的語法,只有棧操作。如果非要說有語法,那就是逆波蘭表示法。這一點非常類似于LISP——所謂的語法,就是S表達式。
PostScript允許以閉包定義新操作符,其中,閉包是放在{}中的后綴表達式。例如,“乘以3”這個操作可定義為:/mul3 { 3 mul } def。這里,/mul3表示取“mul3”的符號值。{ 3 mul }是一個閉包,而def將mul3這個符號,映射到{ 3 mul }閉包。據此,4 mul3即為4 3 mul。
其實,從語法上看,/mul3 { 3 mul } def和3 4 mul并沒有明顯的不同:都是前兩個操作元入棧,后一個操作符進行運算。也就是說,PostScript的棧是異構的,符號、數字和閉包都可以放入棧中。許多操作符如if,也依賴于棧上有一個布爾值和一個閉包。這種不在棧中區分代碼和數據的設計,允許我們重寫棧上的閉包。實際上我們可以證明這個特性等價于LISP里的宏(Macro)的表達能力,限于篇幅,我們不在這里展開。
現在,我們從mul3這個平淡無奇的例子出發,定義一個英寸(inch)的操作符:/inch {72 mul} def。一眼看去,{72 mul}是閉包,而inch是長度單位,兩者毫不相干,為何強拉在一起?原來,PostScript的基本長度單位是1/72英寸,因此5 inch即展開為5 72 mul,或者說360個基本單位。Inch的定義使得我們可以書寫1.2 inch 2.3 inch moveto這樣直觀的程序。
用閉包定義常用度量單位在PostScript中并不少見。對于從未接觸過這種定義方法的讀者來說,相信inch這個例子讓人印象深刻,因為它昭示了度量單位的實質:度量單位是后綴閉包。比如我們說10美元時,已在自覺或不自覺地將“美元”單位替換成 {匯率 mul}閉包,換算成60元人民幣等。實際上,任何度量單位之所以能被我們感知,都是因為我們腦中的一個潛在后綴閉包的作用。在攝氏度體系下的人對華式溫度沒有感覺,或者僅接觸一定數量級范圍內的人對大數字不敏感,都是由于一個原因:我們尚未建立一個將不熟悉的單位或數量級轉化為可感知的單位或數量級的閉包。
PostScript的運行時字典棧
除基本控制語法外,PostScript引入了對于圖形處理很重要的兩個基本數據結構:字典和數組??梢韵胂螅嬗幸幌盗悬c的數組可以表達一個字符的輪廓,而字典可以很好地表達一套字體。不僅如此,通過字典棧這個概念,PostScript具有了FORTH和其他棧語言所完全不具有的動態特性。我們仍然以一個例子說明。
我們定義一個求直角三角形斜邊長度的操作hyp,即/hyp { dup mul exch dup mul add sqrt } def(這里dup表示重復棧頂元素,exch表示交換棧頂兩元素,sqrt為平方根,讀者可以自行驗證這個函數的正確性)。 這里,3 4 hyp得到5。
對解釋器來說,我們新定義的hyp與mul并沒有本質的不同(后綴表達式和規則化帶來的便利)。解釋器處理這些操作符時,無論是語言預先定義還是用戶定義的,不可避免地需要進行符號表查找??赡艿膮^別僅是到不同的符號表里查找。進一步說,一個叫inch的符號在沒有進行符號表查找之前,我們根本不能確定這究是一個變量,還是一個閉包。
為了一致地處理符號表的查找操作,PostScript引入了字典棧(dictionary stack)的概念。字典棧是一個由解釋器維護的棧,而棧中的元素則是作為符號表的字典。解釋器啟動后,系統字典systemdict中含有所有預定義操作符和變量,如add、mul等。用戶字典userdict將涵蓋自定義的操作符和變量。用戶也可以隨時建立新的字典插入字典棧中。
以字典方式存儲符號表是容易理解的,可是為什么需要把這些字典加入“棧”中呢?原來,PostScript是按棧的順序在字典中尋找操作符的。假如定義“/mul {add round} def”,則當前字典中的mul會被優先使用,而系統定義的mul不再可見。乍看之下,這和面向對象語言里提到的運算符重載概念類似。實質上,PostScript的設計要靈活許多。
首先,因為字典棧的存在,每個運算符都自動有了作用域(預定義的運算符因為存在于systemdict中,從而有全局作用域)。通過字典棧,我們可以實現其他語言中的lambda表達式或者Java中的匿名內部類。PostScript的運算符本質上是動態作用域的,但因為字典棧的存在,我們可以輕松實現詞法作用域,方法即是在作用域中臨時定義一個字典,在字典中定義新的操作符,并將字典推入字典棧。這樣,只要在作用域結束時彈出臨時字典,操作符定義也隨之撤銷。許多PostScript程序都采用這種方法構建。
其次,字典棧巧妙地支持了局部變量。和閉包一樣,局部變量的本質是有作用域的值?;跅5恼Z言對函數局部變量是不友好的,因為局部變量本身是對處理器寄存器的抽象,訪問局部變量也是采取隨機存取而非按棧順序存取的方式。而棧機器本身不直接支持寄存器抽象。熟悉JVM的讀者都知道,JVM的{a,i,l,f,d}{load,store}系列指令,非常繁冗地支持局部變量數組和棧之間的轉存。在字典棧中,局部變量有了優雅的解決方法:通過建立臨時字典,我們可在不引入復雜的轉存操作下,隨機存取隨機變量,而且局部變量的作用域得到了保障。比如,以下程序定義了一個叫做local_variable的局部變量,作用域僅限于/sample_proc。而將something換成{something}閉包,即是一個局部的操作符定義。
/sample_proc
{ 1 dict begin % 定義一個大小為1的臨時字典
/local_variable something def
end % begin end 之間為字典元素
… % 具體的函數定義
} def
PostScript和語言的Annotation
因為.ps文件本質是一個程序而非文檔,打印PostScript文件的過程實質上是調用PostScript解釋器執行程序的過程。因為PostScript的圖靈完全性,在PostScript程序執行完之前,我們對文檔的結構信息,例如一共多少頁,文檔有沒有彩色元素等結構化的信息一無所知。PostScript設計于桌面出版業尚未起步之時,因此僅關心繪制控制,并未考慮到如何表示這些結構信息,這樣的缺憾是可以理解的。HTML語言也經過了這樣的道路:早期引入FONT BIG這種純展示標簽,而如今佳實踐是將結構信息放入HTML,而將格式信息交給CSS。
因為PostScript的成功,越來越多的人希望作為桌面出版標準格式的PostScript能包含文檔結構信息。比方說,如果打印管理系統能在將PostScript任務交給打印機之前知道文檔的頁數,就可以更好地調度打印任務,或按頁面收取費用等。這些關于文檔的結構信息并不影響頁面的展示,卻是文檔不可或缺的一部分。
為解決這個問題,PostScript用戶自發地定義了一種通過注釋表示文檔結構信息的方法。例如,在一個10頁的文檔開頭加入%%Pages: 10,每一頁的開始加入%% Page N等。因為是注釋,PostScript解釋器可以選擇忽略它,而其他程序則可以據此管理文檔。許多桌面出版軟件也采取這樣的方法寫入作者、創建日期等信息。在強大的需求和既定行業標準的驅動下,Adobe終于決定標準化這些用來表征文檔結構的注釋,發布了一系列的“文檔結構約定(Document Structuring Conventions)”。之所以叫約定,是因為木已成舟,無法強行要求每個PS文檔管理器或打印機都遵守標準。
DSC使得靜態結構檢查變得可能。前文提到,PostScript語法就一種后綴表達式,靜態語法檢查并沒有意義,而正確性檢查卻又非常難。引入文檔結構約定后,我們就有條件檢查一些約束,比如在宣稱的描述一頁的區塊之內沒有非法的分頁操作等。DSC不影響現有語言邏輯,卻引入了新的語義正確性約束。
DSC這種引入新的元信息以靜態檢查程序的語義正確性的思想非常有前瞻性??上У氖牵驗榱私釶ostScript的人較少,這樣的思想沒能在其他語言中實現。Java 5.0才正式引入了annotation的概念,用@override這樣的標記幫助編譯器檢查方法多態。Python 2.2引入classmethod、instancemethod等decorator以檢查方法的定義,而C++近才正式支持annotation。這些比程序本身更抽象的元信息,越來越多地成為了自動分析工具的幫手。在Google,我們采用一套線程安全的標記以幫助編譯器靜態檢查代碼的線程安全性。所有這些都成提升開發效率的好幫手。而PostScript,是我所知的個以元信息約束程序語義的編程語言。
其他一些有趣的歷史
PostScript語言的歷史很有趣也很能給人啟發,限于篇幅我僅錄幾則。首先,PostScript其實和Smalltalk很相似。因為同樣出自于施樂PARC的研究,PostScript語言風格受Smalltalk影響很大。比如閉包的設計,if和repeat語法的設計,幾乎就是Smalltalk的翻版,僅在運算符順序上有區別。
Adobe的幾位創始人從PARC獨立出來后,初力圖開發一套打印機控制語言。熟悉這幾位創始人的Steve Jobs認為,這個語言重要的任務不是控制打印機,而是制作高品質文檔。在Jobs的推動下,Adobe才開發了這套可以支持蘋果當時正在開發的LaserWriter激光打印機產出高品質文檔的語言——PostScript。從此,Adobe這家毫不起眼的小公司一舉成為桌面出版革命大的受益者。
因為PostScript語言靈活復雜,解釋PostScript語言需要強大的微處理器。為此,Apple LaserWriter攜帶了一顆12MHz Motorola 68000處理器。而同時期與之相連的Machintosh計算機攜帶的卻是一顆8MHz Motorola 68000處理器。
打印機處理器比主機更強大,用現在的眼光看真是不可思議的。桌面出版的革命來得如此之快,需要的計算能力如此之大,是整個個人計算機行業所沒有預見的。或許,未來的3D打印技術或量子傳輸技術(Star Trek Transporter),會讓這種情況重新出現。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。