您好,登錄后才能下訂單哦!
傳統的源代碼簡單文本表示對于編程人員來說很方便,但需要進行語法分析來揭示程序的深層結構。盡管某些復雜的軟件工具通過分析源代碼可以訪問程序的結構,但許多像 grep 這樣的輕量級編程輔助工具卻僅僅依賴于源代碼的詞法結構。我說明的是一種新的 XML 應用程序,該應用程序提供了另一種 Java 源代碼表示法。這種基于 XML 的表示法叫做 JavaML,對工具軟件來說顯得更加自然,它利用豐富的 XML 工具和技術,可以方便地對大量軟件工程分析進行規范。使用 Jikes Java 編譯器框架構建的強健的轉換器,可將傳統的源代碼表示轉換為 JavaML;而使用 XSLT 樣式表,又可將 JavaML 轉回到傳統的文本格式。
簡介
從第一種計算機編程語言開始,編程人員就已經開始將文本表示用作軟件結構和計算過程的編碼媒介。這些年來,技術已得到充分發展,使編譯器的前端大大自動化了;編譯器的前端就是執行詞法分析和語法分析的那部分,是揭示以簡單文本表示的編程語言的結構所必不可少的。借助于具有牢固基礎的正規表達式的概念和語法,Lex/Flex 和 Yacc/Bison [42] 之類的工具使這些單調乏味的分析工作走向了自動化。正規表達式說明單個字符如何組合成表單記號,語法則描述更高級別的結構是如何由其他結構和原始表單記號遞歸構成的。這些過程一起將字符系列轉換成一種叫做 抽象語法樹 (AST) 的數據結構,這種數據結構可以更直接地反映程序的結構。
源代碼的文本表示有幾種良好的特性。文本表示相當簡明,并且與自然語言相似,往往比較容易閱讀。文本還是一種通用的數據格式,因而可以方便地使用大量軟件工具轉換和處理源代碼,這些軟件工具包括文本編輯器、版本控制系統以及 grep, awk 和 wc 之類的命令流水線實用程序。
然而,傳統的源代碼表示有許多問題。諸如 C++ 和 Perl 之類的當前十分流行的語言的語法結構,更加重了對語法分析能力的限制。盡管有許多工具的支持,但是為這些語言構造一個編譯器的前端仍很困難。更為困難的也許是,要發展一種語言的語法,就常常需要處理脆弱的文法。這種局限性使得對一種正在發展的語言的處理復雜化了。
文本表示和軟件工具
傳統的源代碼表示的最大局限性是,只有在語法分析后,才能搞清楚程序的結構。這一缺陷使得,某種語言專用的分析功能,在每一個工具中都要重復配置,只要這些工具在進行程序的詞法分析之外,還要對程序進行語法推理。編譯器必然需要與抽象結構樹(AST)一起使用,而許多其他軟件工程工具將從訪問源代碼的結構化表示中獲益。遺憾的是,許多軟件工程工具沒有嵌入語法分析器,因而僅限于執行一些詞法分析任務。
有幾種原因使得開發人員常常避免在工具軟件中嵌入語法分析器。正如前面所提到的,對于語法結構復雜的語言,構建一個完整的編譯器前端是具有挑戰性的。雖然重復使用(例如重復使用語法定義)簡化了實現的過程,但產生的抽象結構樹(AST)并不總是直觀的。抽象結構樹(AST)通常反映的是奇特的人為技巧,而不是直接表示編程層次上的結構。此外,如果您的目標是使用詞法信息就能完成得“相當好”的簡單語法分析,嵌入編譯器的前端無異于“殺雞用牛刀”。
如果希望轉換源代碼的格式,則會出現其他的復雜問題:抽象結構樹(AST)的變化最終必定反映到傳統的源代碼表示中,因為后者是主要的長期存儲格式。要從一個抽象結構樹(AST)重新創建一種文本表示,最直接了當的方法就是不進行語法分析,但這樣會產生不受歡迎的副作用,如縮排或空白的更改。開發人員所依賴的其他詞法工具會分不清這些更改,例如,版本控制系統就無法區分一個有意義的修改和一個意外產生的無理修改。
最后,在某種工具中使用語法分析器必定會把該工具定位于那種特定的語言,因而會減弱它的適用性和通用性。更糟的是,由于源程序沒有標準的結構化的外部表示法,甚至對于以同一種編程語言為目標的不同工具來說,要對它們之間的互操作性進行支持,都是很困難的。
這些復雜因素的最終結果是,開發人員往往不得不使用簡單的、面向詞法分析的工具,比如編輯器中的 grep 或者“搜索并替換”。這種方法犧牲了準確性:試想將一個局部變量從 result 重命名為 answer。借助簡單的“搜索并替換”,每一處出現這個單詞的地方都會替換,即使是注釋、文本串或者毫不相干的實例字段中的字符,也不例外。
一些開發人員采用的一種替代方法是,依靠集成開發環境 (IDE) 中提供的一套固定的工具,該開發環境能夠通過一個集成的語言專用分析器訪問其源程序的結構,但這種方法犧牲了靈活性。集成開發環境(IDE)一般只提供一套有限的功能,而且這些功能難于擴展。此外,使用現有的交互式環境,難以自動或批量分析和轉換源代碼。一些更高級的集成開發環境 (IDE) 如 IBM VisualAge for C++ [ 48],將應用程序編程接口提供給程序的表示法,雖然這也算是一種改進,但這項技術仍然有弊端,因為它不能將簡單工具從一種復雜的環境中分離出來,此外還產生了對專有技術的依賴,這是我們所不希望看到的。
解決方案
隱藏在上述問題背后的一個基本問題是,源代碼缺乏規范的結構化表示方法。我們需要有一種通用的格式來直接表示程序結構,以便于軟件工具分析和處理。我們觀察到的一個關鍵之處是,XML 這種可擴展標記語言(eXtensible Markup Language) [ 9],正好提供了這種功能,而且它是一種功能無比強大的源代碼補充表示。
在本文中,我要介紹 Java 標記語言,也即 JavaML ? 一種用于描述 Java 源程序的 XML 應用程序。JavaML 文檔類型定義 (DTD) 規定了有效的 JavaML 文檔的元素,以及這些元素組合的方式。在這些元素、元素的屬性以及用這些元素編寫的編程語言結構之間,有一種自然的對應關系。在 JavaML 文檔中,源程序的結構反映在元素的嵌套中。借助這種表示,我們就可以利用處理和查詢 XML 和 SGML 文檔的大量工具,提供一種功能豐富的、開放式的基本結構,進行 Java 源代碼的軟件工程轉換和分析。
JavaML 很適合用作工具軟件的規范的 Java 源代碼表示。它保留了傳統表示的大部分優點,而且克服了這些表示法的許多弱點。下一節講述 Java 語言和 XML 的有關特性, “Java 標記語言 (JavaML)” 一節則詳細描述標記語言,以及傳統表示與 JavaML 之間的轉換器的實現。 “使用 XML” 一節舉了許多例子來說明,如何利用現有的 XML 和 SGML 工具,在 JavaML 提供的功能更為豐富的表示法的基礎上,分析源代碼和進行格式轉換。 “相關工作”和 “下一步工作” 講述相關的工作,并推薦完成更令人興奮的下一步工作的途徑。 附錄 A 中有 JavaML 的完整的文檔類型定義 (DTD),而已轉換的源代碼的更多實例可從作者 的 JavaML Web 頁 [ 4 ] 上獲得。
背景
Java 標記語言在 Java 和 XML 這兩種技術之間架起了一座橋梁,它受到這兩種技術的大量特性的影響,獲益匪淺。
Java 技術
雖然基于 XML 的編程語言結構的表示法與語言無關,但是 Java 語言是試驗這些理念和技術的最佳選擇。
Java 語言是一種流行的面向對象的編程語言,由 Sun Microsystems 在 90 年代中期開發 [ 3][ 25 ]。它是一個基于 Java 虛擬機 (JVM) 的、與操作平臺無關的執行模型,由于用作萬維網應用程序的編程語言而很快被廣泛的接受。Java 語言將一種令人聯想到 Smalltalk [ 26 ] 的簡單對象模型,與 Algol 語言塊結構、一種類似 C++ [ 49] 的語法,一種靜態類型系統和一種由 Modula-2 [ 10] 產生的軟件包系統結合在一起。
在 Java 語言中,與在大多數其他面向對象 (OO) 的語言中一樣,對程序語言進行解析所得到的基本單元是 類(class),類規定了一組對象的行為。每一個類可定義幾種 方法,或者稱之為行為,類似于函數或過程。一個類還可以定義 域,或稱之為狀態變量,這些域與類的 實例相關聯,而這些實例叫做 對象。類可以從 超類繼承行為和狀態,從而形成互相關的類的層次結構,該層次結構允許在其頂部將相關的代碼分解為類,從而有利于重復使用。行為是通過向目標接受者對象發送一個 消息來調用的,此消息就是執行一個為該類定義的方法的請求。選擇執行什么方法來響應一個消息叫做 動態調用 ,依據的是接收消息的對象的運行時類。例如 ColoredBall 類的一個實例,可能用一種與 Ball 類的實例不同的方法響應 draw 消息。這種收到同一消息而做出不同響應的能力,主要得益于 Java 語言的可擴展性優勢,而這種優勢正是面向對象(OO)的團體所大力稱道的。
Java 語言在工業和教育領域都得到了廣泛應用,并且仍然是一種廣受歡迎的 Web 編程語言。與 C++ 不同,Java 類定義放在一個單獨的自含式文件中,既沒有單獨的頭文件也沒有執行文件,并且 Java 語言基本上沒有定義的次序相關性。在出現方法體時,它總是緊隨方法特征聲明之后定義。此外,Java 語言缺少集成處理器。這些特性合在一起,使 Java 源程序在語法上很簡潔,從而使 Java 語言成為使用 XML 表示的最理想的語言。(這種方法對其他語言的適應能力將在 “下一步工作” 中進一步討論。)
XML:可擴展標記語言
XML 是一種標準的可擴展標記語言 [ 9 ],是 SGML(標準通用標記語言)的子集 [ 37 ]。萬維網聯盟 (W3C) 將 XML 設計得既小巧又簡單,同時仍保持與 SGML 兼容。盡管 HTML(超文本標記語言)當前是標準的 Web 文檔語言,W3C 正在將 XML 定位為 HTML 替代語言。標記文檔時,HTML 僅允許編程人員使用預先定義的一套固定標記,而 XML 卻可以方便地使用用戶定義的標記,來適應手頭文檔和數據的標記要求 [ 27][ 28]。
XML 文檔只是由使用標記符進行標記的文本簡單地組成,這些標記符含在尖括號內。下面是一個簡單例子:
XML 文檔的一個更嚴格的特點是 有效性 。當且僅當一個 XML 文檔既是合式的,又符合其指定的 文檔類型定義 (或稱 DTD )時,該 XML 文檔才是有效的。文檔類型定義是一種形式描述,用于一類 XML 文檔使用的特定語言的語法,它定義所有已允許的元素名稱,并描述每一類元素可以擁有的屬性,同時,它還限制一個有效的 XML 文檔內的嵌套結構。就下面的文檔定義類型 (DTD) 而言,前面的 XML 示例是有效的:
<!-- email DTD -->
按照此文檔類型定義 (DTD),有六種元素類型。email 元素必須正好包含一個 head,后面正好跟一個 body 元素。Head 也同樣必須包含一個或多個 to 元素,然后是一個 from 元素,后面跟一個可選的 subject 元素。元素的次序必須是指定的次序。這些元素中的每一個都可以包含文本(又稱 已分析的字符數據 或 PCDATA)。文檔定義類型 (DTD) 中的單一 ATTLIST 聲明規定,body 元素 可以 為 encrypted 屬性指定一個值,并且 必須為 encoding 屬性指定 ascii 或 mime。encoding-attribute 的 ENTITY 聲明(在 DTD 頂部)是一種分解出冗余文本的簡單方法;即引號之間給出的文本,可按原樣替換到下面的 ATTLIST 聲明中(并且,重要的是,它可以用在多個 ATTLIST 中)。
如果上述準則有任何一個未得到滿足,則聲明遵循此文檔定義類型(DTD)的 XML 文檔無效。例如,如果某個 email 文檔中缺少 from 元素,則該文檔是無效的,雖然它可能仍然是合式的。
當您在 XML 中制作數據模型時,主要的設計決策是在嵌套元素或使用屬性之間作出選擇。在上例中,如果我們這樣選擇的話,本來也可以把 head 中包含的全部元素都合并到 email 元素的各個屬性中去。在使用屬性和嵌套元素之間有一些重要區別:
雖然上面的區別有時可以決定使用這種技術還是那種技術,但最初的決策往往是一個愛好問題。但是,以后使用由此產生的文檔的經驗,可能會啟發人們重新去考察這個決策,以便使文檔中所需的某些操作變得容易或簡單。
XML 的另一種有用的數據模型編制特性是,通過 id 屬性給元素附上一個唯一標識符,然后其他元素的 idref 就可以引用這些元素。一個合式的 XML 文檔,必須讓每一個 idref 值與文檔中的一個給定 id 匹配。id/idref 鏈接描述一些優勢,這些優勢能夠使 XML 表示通用的定向圖,而不只是表示樹目錄。
一些工具(如 Emacs 編輯模式、基于結構的編輯器、DTD 語法分析器、驗證實用程序、查詢系統、格式轉換和樣式語言,以及許多其他工具)能夠很好地支持 XML,這在一定程度上是由于 XML 是從 SGML 繼承而來的。許多其他 W3C comments都與 XML 有關,包括級聯樣式表 [ 8]、XSL(可擴展樣式表語言)[ 19]、XSLT (用于轉換的 XSL)[ 14]、XPath [ 16 ] 和 DOM(文檔對象模型)[ 2]。
Java 標記語言 (JavaML)
Java 標記語言提供了一套完整的 Java 源代碼自描述表示。與傳統的基于字符的程序表示不同,JavaML 以基于 XML 語法的元素嵌套方式,直接反映了軟件產品的結構。此外,通過使用 XML 的 id 和 idref 鏈接,JavaML 還在程序圖中表現出了更多的側面。
由于 XML 是一種基于文本的表示,所以它保留了傳統源代碼表示的許多優點。而 JavaML 是一種 XML 的應用程序,所以它易于進行語法分析;并且,現有的所有用于 XML 的工具,都可用于以 JavaML 表示的 Java 源代碼。JavaML 的各種工具可以利用現有的基礎結構和規范表示法來增強其互操作性。
可能的方法
雖然使用 XML 應用程序來編制源代碼的基本方法是相當直接的,但到底使用哪種可能的標記語言,仍然有一個很大的設計空間。最明顯的可能性是直接將 XML 用作一個典型的抽象語法樹文本轉儲格式,該語法樹源于對源代碼的語法分析。現考慮下面這個簡單的 Java 程序:
import java.applet.*;import java.awt.*;public class FirstAppletextends Applet {public void paint(Graphics g) {g.drawString("FirstApplet", 25, 50);}}
執行上例中抽象語法樹明顯的(但極不令人滿意的)轉換,可能會產生 僅第一行代碼 的如下 XML 形式:
毫無疑問,這種轉換與理想相去甚遠:它冗長得令人無法接受,并且它給出了底層語法的許多令人乏味的細節,這些語法原本是用來分析傳統源代碼表示的。
另一種可能性是直接標記 Java 源程序而不更改程序的文本(即僅添加標記)。這種方法可能把 FirstApplet.java 實現轉換為:
這一格式朝著一種更有用的標記語言的方向邁了一大步。我們已經肯定地向源代碼添加了值,而轉回傳統的表示法也很容易:只要刪除所有 tag,并留下元素的內容(這種刪除標記的方法,與 stripsgml [ 31 ] 實用程序的作法,如出一轍)。雖然這種表示似乎對許多任務有用,但它仍然存在一些問題。首先,編碼的許多詳細信息包含在元素的文本內容中,如果我們想確定要導入什么軟件包, XML 查詢將需要對聲明導入的元素進行詞法分析。這種分析用起來不方便,而且沒有利用 XML 提供的能力。也許更為重要的一點是,上述的 XML 表示法保留了傳統源代碼的人為因素,而另一種表示法則可能允許我們不考慮語法細節進行抽象,使我們自己從這些語法重負中完全解脫出來。
選擇的表示
我選擇原型 JavaML 表示,旨在制定 Java 語言(實際上包括類似的面向對象)的編程語言結構模型,而不依賴特定語言的語法。人們可以很容易想到有一個 Smalltalk 標記語言(SmalltalkML),與此想法非常類似,甚至可以想到應該有一個面向對象標記語言(OOML),它既可以轉換成傳統的 Java 源代碼,又可以轉換成 Smalltalk 的外部定義格式。心中有了這個目標,于是首先按這些結構原則設計出 JavaML,然后對它不斷地進行提煉,使它在功能和可讀性方面都堪稱優秀的標記語言。
JavaML 由 附錄 A 中的文檔類型定義 (DTD) 來確定,但最好還是用示例來說明。對于上面列出的 FirstApplet.java 源代碼,我們給出如圖 1 所示的 JavaML 程序。
圖 1.轉換成 JavaML 的 FirstApplet.java
1 2 34
在 JavaML 中,諸如方法、超類、消息發送和字面值數字之類的概念,都是直接在文檔內容的元素和屬性中表示的。JavaML 的表示法用元素的嵌套來反映編程語言的結構。例如,文本串 “FirstApplet” 是消息發送的一部分,這樣 literal-string 元素就嵌套在 send 元素內。如果像圖 2a 和圖 2b 那樣,以可視形式給出源代碼時,這種嵌套就更明顯了。有關的更多例子,請參見 JavaML 網頁 [ 4]。
細心的讀者會看到, JavaML 表示的源代碼的長度約比傳統的源代碼長三倍。源代碼變長是轉化成自描述數據格式(如 XML)的基本代價。有一點很重要,即編程人員在某些任務(包括一般開發和程序編輯)中能夠使用簡明扼要的傳統表示法,盡管 JavaML 可能是底層表示法。JavaML 是對傳統源代碼表示的補充,并且特別適合在工具軟件中使用,同時仍然可由開發人員訪問并直接讀取。
圖 2a. 由 XML Notepad 實用程序 [ 44 ] 顯示的 FirstApplet 示例 JavaML 表示法樹形圖
圖 2b. XML Spy [ 36 ]顯示 的 FirstApplet 示例的 JavaML 表示法樹形圖
設計決策
JavaML 所提供的不只是源程序的結構。請注意,將圖 1 第 17 行的形參 g 用作了消息發送的目標,該 var-ref 標記的 idref 屬性向后指向所引用的 formal-argument 元素(通過其 id 屬性)。(在一個文檔內,為要引用的元素選擇的 id 值必須是唯一的,以便每一個標識符都用一個整數標記,從而使其值各不相同。)這種鏈接是標準的 XML,這樣 XML 工具就能夠從一個變量的使用追溯到變量的定義,例如,可獲得變量的類型信息。局部變量(即代碼段內聲明的變量)也有類似的鏈接,程序結構圖的其他方面還可以有更多鏈接。盡管單一的 var-use 標記已足以指示在任何地方出現的一個變量,但 JavaML 能夠在變量值的引用和用作左值的變量之間進行區分:var-ref 元素用于前者,var-set 用于后者。
在整個 JavaML 中,除非元素值的結構比簡單文本字符串更加復雜,否則程序員隨時可以使用元素的屬性。元素的屬性可用于諸如 synchronized 和 final 之類的修飾符,也可用于諸如 public 或 private 之類的可視性設置,但屬性不用于類型之類的特性,因為類型具有某種結構形式:類型可由一個基本名和一個維數組成,并且它還可以引用實現該類型的類的定義,如果您想這樣做的話。如果,比方說,一個返回類型只是方法元素的一個屬性值,那么最終用戶將不得不對屬性的值 "int[][]" 執行字符串處理(這是令人不能接受的),以確定該二維數組的基本類型是原始類型 int。實際上,類型編寫為顯式子元素,如
JavaML 推廣了一些相關的概念,使某些分析過程得以簡化,但保留了實現其他任務可能需要的一些特征。例如,45 和 1.9 分別表示為
JavaML 對程序語言結構的另一處推廣見之于循環。for 循環 和 while 循環均可視為廣義的循環結構:以 0 或其他值作的初始化值、每次循環之前進行一次檢測、對 0 或其他值進行更新操作,以及一組包括循環結束指令的語句。因此,JavaML 不分別使用 for-loop 和 while-loop 兩個元素類,而是只使用單一的 loop 元素,這個循環元素具有 kind 屬性,它的值可以是 for 或是 while。當轉換一個 while 循環時,它既沒有 initializer,也沒有 update 子元素,而一個 for 循環可能包含許多 initializer 和 update 子元素。另外, do 循環所具有的一些獨特的 do-loop 元素仍用于 do 循環,因為 do 循環把檢測放在循環的末尾而不是循環的開始。
作為另外一個例子,我們把實例字段和類(即靜態的)字段都表示成具有 static 屬性的 field 元素,以便對它們進行區分。雖然實例字段和類字段這兩個概念之間的差異遠遠超過 do 循環和 while 循環之間的差異,但是用單一元素表示這兩種字段仍然是大有好處的。
局部變量聲明是一種語法速記,它給我們提出了有關聲明底層表示的有趣的問題。代碼段 int dx, dy; 定義了兩個 int 類型的變量,但是這個代碼段可能隱隱約約帶有這樣的意思:這兩個變量具有相同的類型。相反,請看一看代碼段 int weight, i;。這段代碼可能就沒有這種暗示的意味,它使用語法速記僅僅是為了語言的簡潔。因為很難自動識別這兩種情況,故對于使用這種速記法的變量聲明語句,JavaML 運用了屬性 continued="true",從而直接保留了這一語法特性。
在 JavaML 中,處理源代碼中的注釋語句尤其麻煩。目前,DTD 允許某些“重要”元素(包括 class、anonymous-class、interface、method、 field、block、loop)指定一個 comment 屬性。確定將哪些注釋附加到哪些元素上,是一項具有挑戰性的工作;當前的實施方案只是簡單地將注釋排隊,并且將上一個“重要”元素指定屬性以來出現的所有注釋,都包含到當前這個元素的 comment 屬性中。
另外一個解決注釋問題的可能方案是,簡單地把注釋語句插入 JavaML 表示法中,在進行語法分析時,注釋語句僅僅作為散布在正常程序結構中的字符數據,這樣就把語義分析的問題留給了其他軟件工具。遺憾的是,這種方法將使各種元素具有“混合內容”,在檢查 DTD 一致性時,驗證能力將會降低。使用 XML 模式 [ 51 ] 來取代 DTD,可以使這種方法更為有效。
轉換器實現
為了試驗 JavaML 的設計效果并獲得使用這種表示法的經驗,必須實現從 Java 傳統源代碼表示到 JavaML 的轉換器。在 IBM Jikes Java 編譯器框架 [ 35 ] 中,我為每個抽象結構樹(AST)節點都添加了一個 XMLUnparse 方法。這種更改,再加上使用一些小的代碼段來管理用于請求 XML 輸出的選項,形成了一個強健而快速的 JavaML 轉換器。我總共向 Jikes 框架添加了大約 1650 行既非注釋也非空行的 C++ 代碼,以支持 JavaML。
該轉換器已經測試過,共轉換了 15,000 行多種樣本程序,其中包括 4,300 行的 Cassowary Constraint Solving Toolkit [ 5 ] 和各式各樣的 applet [ 50 ] 20 多個。然后,使用 James Clark 的 Jade 軟件包的 nsgmls 工具 [ 12 ],對每個轉換的文件做有關 JavaML 的 DTD 驗證。在基于 RedHat6 的雙 Pentium III-450 機器上,整個回歸測試的處理只需要 12 秒鐘。
此外還實現了 XSLT 樣式表轉換,該樣式表輸出了用 JavaML 表示法表示的傳統源代碼。此樣式表由 65 個模板規則和不到 600 行代碼構成。對許多程序是這樣測試(既用 Saxon [ 39] 又用 XT [ 15 ])的:將一個文件轉換為 JavaML 文件,再把它轉回原文件,然后重新轉換為 JavaML 文件 ? 在最后結果和第一次轉換成的 JavaML 文件之間不存在差別。所有的源代碼均可從 JavaML 主頁 [ 4] 獲得。
利用 XML
JavaML 將 XML 用作 Java 源程序可供替換的另一種結構化表示法。僅從語法抽象不必考慮 Java 語言的語法細節這一點來說,已經非常方便了;但 JavaML 還有一個更重要的好處:那就是能夠利用為支持 SGML 和 XML 而開發的豐富的基礎結構。我們可以使用、組合及擴展現有的 SGML 和 XML 工具,而不必從暫存區構建分析和轉換工具,來處理程序的專用二進制結構化格式。XML 工具含有豐富的功能部件,這些功能部件包括查詢和轉換工具、文檔識別和合并工具 [ 32 ] 以及一些簡單的 API,可直接處理文檔。限于篇幅,本文僅討論三種組合工具的使用:
愛丁堡大學(Edinburgh University [ 52 ]) 的 XML 工具箱,包含 sgcount、sgrpg、sggrep 等等XSLT [ 14] 處理器(例如 XT [ 15] 和 Saxon [ 39 ])和 XML 語法分析器 XP [ 13 ]
Perl XML:: DOM 軟件包 [ 20 ],向 XML 樹展示了一個 DOM 一 級 [ 2 ] 接口
這只是在使用 JavaML 時,非常有用的工具的很少一部分。在下面的示例中,我們將查詢 Hangman.java.xml,它是 Hangman applet 的 JavaML 表示,該 applet 可從 Sun Microsystems 的 applet 頁 [ 50 ] 獲得,也可從 JavaML 主頁 [ 4 ] 獲得。雖然從現實世界的標準來看,這些示例不大,但是 XML 工具 和 SGML 工具的目標是處理像大部頭的書那么長的程序,所以實現時設計了很強的可伸縮性。
軟件工程的一項通用任務(不論好壞)是積累關于原文件的標準。借助 JavaML,SGML 實用程序 sgcount 就出色地總結了 Java 程序的結構(輸出的命令已經過刪節并略加編輯,以便于在此展示):
% sgcount Hangman.java.xml outputs: arguments 103array-initializer 4assignment-expr 60catch 3class 1if 27true-case 27false-case 7field 28field-access 18import 5java-source-program 1literal-char 5literal-boolean 5literal-null 5literal-number 127literal-string 61local-variable 23loop 13method 18new 4new-array 5return 5send 99type 96var-ref 262var-set 52...
在上面的輸出中,每一行列出一個元素類和該元素在文檔中出現的次數。這樣,我們很容易看出共有 18 個方法元素,因而就有 18 種 method 定義。類似地,我們可以看出有 1 個類定義、262 個變量引用、99 個消息發送和 61 個字符串文字。此摘要與其說是一個典型的詞法分析(例如代碼行數字),還不如說是對程序內容的說明。
如果我們想看一看一個程序包含的字符串文字,我們可以在該程序的 JavaML 表示中使用 sggrep 來完成這項瑣碎的工作:
% sggrep '.*/literal-string' < Hangman.java.xml outputs:
請注意 sggrep 的輸出仍然是一個(不一定有效,甚至不一定是合式的) XML 文檔。因此,我們可以在 UNIX 的管道中排列 SGML 和 XML 工具,使這些工具以一種新穎的,有用的方式結合在一起。例如,在某些情況下,把結果轉回普通的 Java 源程序表示是非常必要的,這樣可以幫助我們的軟件工程師。我們可以使用 results-to-plain-source 實現這個功能,results-to-plain-source 是一個圍繞 XSLT 樣式表(該樣式表可以把 JavaML 轉回簡單源代碼)的外殼:
% sggrep '.*/literal-string' < Hangman.java.xml | results-to-plain-source outputs:"audio/dance.au""img/dancing-duke/T"".gif""img/hanging-duke/h"".gif""Courier""Courier"...
也可以根據元素的屬性在 JavaML 源程序中查詢一個元素。例如,如果想找到所有的消息發送setFont,可以很容易地得到精確的結果:
% sggrep '.*/send[message=setFont]' < Hangman.java.xml outputs:
在這種結構性標記中,七個字符 "setFont" 出現的地方有注釋,文本串,或者變量名,但是這些信息都不會在查詢結果中得到體現。一個類似的操作就是利用詞法工具去檢索信息,其結果將包含同樣的錯誤的正數。請試想如果你僅僅使用詞法工具,去查找各種強制類型轉換的表達式,將是什么情形 -- Java 表達式中大量使用的括號將使這個工作非常困難,但是,對 JavaML 來說那只是小菜一碟,因為可以使用 cast-expr 元素。
另外一種常見的分析是,在實現轉換之前由編譯器進行語法檢查。例如,在 Java 代碼中,只有抽象類才具有抽象方法。在編譯過程中,如果程序違反了這條規則,那么編譯器會標記一個語法錯誤。我們可以在一個 JavaML 文檔中查詢出包含抽象方法的具體類(也就是非抽象類):
% sggrep -q '.*/class[abstract!=true]/method[abstract=true]' < Hangman.java.xml
當然,輸出的結果肯定是空的,因為我們的目標文檔(也就是被分析的程序)沒有違反這條語法規則。
Java 編程新手經常犯的錯誤之一就是,在應該使用等值檢測符 == 的地方不小心使用了賦值運算符 =。 雖然在編譯階段中,Java 的類型檢查器可以捕獲絕大部分此類錯誤,但是如果被賦值的變量是 boolean 型變量,那么這種錯誤將會逃過類型檢查器的檢測。如果想發現這種結構性錯誤,那么 JavaML 中的 sggrep 可以使我們很輕松地完成工作:
% sggrep -q '.*/if/test/assignment-expr' < Hangman.java.xml
程序 sgrpg (SGML RePort Generator) 可以把高水平的查詢,對子元素的約束和查詢結果的輸出格式結合起來(一個查詢工具的通用語句變化表 [ 24 ])。例如:
% sgrpg '.*/method' '.*/send[message=drawLine]' '' '%s %s' visibility name < Hangman.java.xml outputs:public paint
可搜索包含消息 drawLine 的消息發送的方法定義。如上所示,程序立即輸出匹配元素的的 visibility 屬性和 name 屬性,這正好同我們的直覺相符,因為 paint() 是唯一調用 drawLine 的方法。
只需利用標準的 XML 工具提供的查詢能力,就可以進行大量的分析。我們能做的其他事情包括從循環體內部的返回,所有整型變量的定義,所有不符合項目命名慣例的字符串變量,等等。
前面的查詢反映了當前 XML 查詢工具的一個缺點: 大多數反應只是對匹配的元素作出的 -- 它們不能提供元素在文檔中出現處的上下文信息。雖然在把 XML 文檔嚴格地看作一個數據庫時,這種操作是合適的,但是軟件工程師可能想知道查詢結果發生在 JavaML 文件中什么地方,這樣可以把結果映射回原始文檔,以便對原始文檔進行手工編輯和閱讀。為了闡明 JavaML 中存在的這種困境,我把有關原始程序代碼結構位置的信息附加在各種元素的屬性中。位置信息包括起始行,終止行以及結構的列數(文件名放在祖先類的 java-class-file 元素中)。
除了查詢之外,當對軟件產品進行修改和擴展時,轉換源代碼是非常有用的。一般來說,查詢工具僅僅是從源文檔中撿出符合條件的元素,或者從多個文檔中撿出幾個元素的組合。功能更為強大的轉換工具包括 XSLT [ 14], DSSSL [ 38 ], 或者使用一個能訪問多種語言(例如 Perl, Python, Java, 和 C++ )的 DOM (文件對象模型 Document Object Model) 接口直接處理文檔 [ 2 ]。例如,我們可以直接使用 XSLT 樣式表把所有名為 isBall 的方法重命名為 FIsBall :
執行過程如下:
xt source.java.xml method-rename.xsl oldname=isBall newname=FIsBall
盡管使用文本編輯器或者 Sed 可以獲得類似的文本轉換的結果,但是這類工具會改過了頭,它們會修改所有順序出現 isBall 這六個字符的地方。以文本為基礎的轉換可能會錯誤地影響變量名,文本串,注釋和包。而 JavaML 表示的主要優點之一是: 我們可以對影響到的結構進行更為精細的,以語義為基礎的控制。
其他可能的轉化包括使用類型表,輸出一個程序的可瀏覽的 HTML 格式的頁面(見圖 3 )或者強調語法的 PostScript。在函數的入口和出口增加調試或者測試代碼也是一件非常容易的事情(見圖 4)。
圖 3. Hangman.java.xml,經過 XSLT HTML 極妙的打印和索引程序處理
方法索引同每個方法定義的開頭相鏈接,使用彩色編碼和斜體字,可以起到突出語法的顯示效果。
圖 4. 調用 Tracer.StartMethod(方法名)和 Tracer.Exitmethod(方法名) 檢測某個 Java 類的所有方法的 Perl 程序
#!/usr/bin/perl -wuse XML::DOM;use IO::Handle;my $filename = shift @ARGV;my $parser = new XML::DOM::Parser;my $doc = $parser->parsefile ($filename);my $nodes = $doc->getElementsByTagName("method");for (my $i = 0; $i < $nodes->getLength(); $i++){my $method = $nodes->item($i);my $block = $method->getElementsByTagName("block")->item(0);my $name = $method->getAttribute("name");my $start_code= SendMessageBlock($doc,"Tracer","StartMethod", $name);my $exit_code= SendMessageBlock($doc,"Tracer","ExitMethod", $name);$block->insertBefore($start_code, $block->getFirstChild());$block->appendChild($exit_code);}print $doc->toString;sub SendMessageBlock {my ($doc,$target_var,$method_name,$data) = (@_);# insert, e.g: Tracer.StartMethod("paint");return parseXMLFragment($doc,<<"__END_FRAGMENT__"
相關工作
JavaML 的重要優點之一,就是它可以利用不斷擴展的同 SGML 和 XML 相關的軟件工具,作為自己的基礎,這些軟件在前面已經介紹過。許多研究人員幾乎都采用類似的方法,來解決軟件工程升級和開發工具升級問題,他們已經取得了不同程度的成功。
通過匹配 C 程序中的抽象結構樹 (AST)模式,TAWK [ 29] 對 AWK [ 21 ] 的語句變化表進行了擴展。許多 XML 查詢工具為 JavaML 提供了相同的功能,而且其中的事件操作框架同 SAX (XML 的簡單 API) [ 43] 使用的框架非常類似。
ASTLog [ 18] 擴展了 Prolog [ 17 ] 邏輯編程語言,這種語言可以對模擬抽象結構樹(AST)的外部數據庫進行推理。與 Prolog 不同,ASTLog 根據一個當前的對象求值。把 Crew 所使用的方法用于 XML 可能非常有趣,但是大量的 XML 工具軟件已經按照一種更為傳統(可能不太方便)的框架,提供了相同的功能。
GRAS [ 40 ] 是一個面向圖像的,用于軟件工程環境的數據庫系統。編譯器的前端可以把普通的 C, Modula-3, 和 Modula-2 程序表示的源代碼整合到數據庫中去。這種數據庫方法在存儲 XML 方面已經證明是非常有用的,尤其是用 JavaML 設計軟件工程應用程序時,數據庫方法更是顯示出它的優越性。
軟件開發基礎 (Software Development Foundation [ 46 ]) 基于 XML 的開放式體系結構, 它用來設計編程環境下的開發工具。有一種稱為 CSF (代碼結構格式)的 XML 數據庫格式,可以用來存儲關系,但是它不包括任何已經執行的計算過程的細節。 Chava [ 41 ] 也采用類似的方法,不過它是以 C 程序數據庫為基礎的[ 11 ]。 Chava 也允許通過對字節代碼執行逆向工程來查詢 Java 代碼。
CCEL [ 22 ] 為 C++ 編寫的軟件產品提供了一種表達非語言意義(也就是說不能用語言表達)的超語言。JavaML 通過以下方法來提供相同的功能:編寫一些簡單的查詢以搜索指定常量的違規,同時在整個開發周期的各個階段,如編輯,編譯和回歸測試過程中,報告查詢結果。
微軟公司的意念編程小組 (Intentional Programming group [ 47 ])一直在努力開發一種更為抽象的,與具體語法無關的計算過程表示。他們的目標看來是允許軟件開發人員描述新的抽象方法,并且找到一種技術使得這些抽象精簡為已知的原語。從本質上講,他們感興趣的是讓軟件開發人員在編寫軟件的過程中創造出域專用的語言。作為這種方法的一個代表,JavaML 特別令人激動。我們可以把新的抽象方法作為文檔類型定義(DTD)的漸次擴展。為了使新類型的文檔(可以稱為 Java++ML)仍能在已有的 Java 編譯器中編譯,開發人員只需要簡單地編寫一個從 Java++ML 到 JavaML 的轉換。由于 DTD非常容易擴展,因此這種方法應該是站得住腳的,而且它很可能為進一步的工作開創富有成果的途徑。運用這種技術時,有幾種實用程序會有用處,例如 perlSGML 包的 dtd2html 和 dtddiff [ 31],它們用于文檔定義類型(DTD)的文檔編制和比較。
下一步工作
雖然本文只是為 Java 語言提供了一種標記語言,但是本文提出的基本方法也可以用于其他語言,甚至可以在不同的語言之間進行轉換。根據表示法忽略具體語法細節進行抽象的程度,JavaML 也可能允許導入可視化表示(例如統一模型語言圖 [ 1][ 35 ])。由于 XSL 和 DSSSL 的強大功能,生產關于軟件產品重要特性的可視化表示已經是指日可待的事情了。
把本文提出的方法應用于 C++ 語言,即另外一個流行的面向對象的編程語言時,一個非常棘手的問題是 C 語言的預處理程序。C 語言的預處理程序首先通過一個文本處理,允許使用那些不能用核心的 C++ 語言表達的抽象。這些抽象對代碼的可理解性和可維護性都非常重要,但是他們同語法分析技術 [ 23][ 6] 不能很好地交互。
對當前轉換系統的有益的擴展之一,就是對更多的元素進行橫向鏈接。類型元素可以在其他 JavaML 文件中引用他們定義的類。導入聲明可以為導入的包引用頂層的文檔。還有許多可能性都是行得通的。
當前能把 JavaML 轉回傳統源代碼的轉換器是基于 XSLT 的。增加一個 Jikes 前端就可以使編譯器直接閱讀 JavaML。為實現這種功能,將使用一個 XML 語法分析器(例如 XML4C++ [ 33 ]),從 JavaML 源代碼構建 XML DOM, 然后利用 DOM API 可以很方便地循環建立 Jikes 內部抽象結構樹 (AST)。運用 Jikes 以前就擁有的、傳統的逆向解析器,可以把 JavaML 轉回簡單源代碼。
使用 JavaML 作為主要的源代碼表示將具有簡化編譯器的潛在功能,這不僅僅限于對傳統的前端編譯器進行簡化。一旦編譯器知道輸入的是一份有效的 JavaML 文檔,編譯器就可以省略某些語法分析。如果能說明在給定的條件下不可能出現哪些語法錯誤,那將是非常有用的。由于 XML Schema [ 51][ 7 ] 給出了一套更為精致的 XML 文檔有效性規范,因此在工作文檔最后確定之后,把 JavaML 而不是 DTD 移植進來將可能非常有益。另外,編輯環境中可以很方便地以直接查詢的形式(例如前面 "利用 XML" 中給出的一些例子)移入更多的語法分析功能。
用簡潔的文本表示法書寫源程序是程序員最樂于接受的方法,因此他們不可能在很短的時間拋棄他們心愛的文本編輯器。我們必須研究更好的方法,以實現傳統的源程序表示法和 JavaML 之間的交互式的和漸進式的轉換。然后,這種功能就能直接用來支持使用簡單文本格式的 XML 表示的交互式編輯,而簡單文本格式是工程師們情有獨鐘的。關于結構性文本編輯器,已有大量的工作 [ 30][ 45 ],它們之間有緊密的聯系,并且最終可望獲得承認,同時提供無限的資源。由于 XML 技術在商業上的重要性日益增長,這些資源目前就會用來解決問題。
結論
JavaML 是一個可選的、基于 XML 的 Java 源程序表示。與傳統的文本源程序表示不同,JavaML 可以使軟件工具對 Java 程序中的結構,很方便地進行編程水平的分析推理。這是因為 JavaML 可以更為直接地表示程序的結構。
有了 JavaML,大量既有的 XML 和 SGML 工具就能對 Java 源程序執行各種有趣的和有用的分析與轉換。XML 工具軟件正在不斷地改進,以支持基于 XML 的文檔的不斷更新的基礎結構。最終,JavaML 將代替 Java 程序的傳統源代碼表示,成為 Java 程序的存儲格式,而在整個程序開發過程中,當開發人員同軟件產品的結構化表示進行交互時,文本語法分析將僅僅成為幾種可能的途徑之一。
致謝
我衷心地感謝 Zack Ives,感謝他的批評,他同我的探討,以及他為我進行的錄入工作。我還要感謝 Corin Anderson 和 Alan Borning,感謝他們對這篇論文初稿提出的寶貴的批評comments。我還要感謝 Miguel Figueroa, Karl-Trygve Kalleberg, Craig Kaplan, Todd Millstein, Stig E. Sand 和 Stefan Bjarni Sigurdsson,感謝他們使我受益匪淺的討論。非常感謝 IBM 構建了 Jikes 編譯器框架并把它公之于眾,同時非常感謝 Mike Ernst 指導我如何使用 Jikes 編譯器。以上工作受到了華盛頓大學計算機科學與工程系 Wilma Bradley 獎學金和國家科學基金第 IIS-9975990 號基金 (NSF Grant No. IIS-9975990)的資助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。