| 寫在前面的話 在論壇上看到這篇文章時,一時沖動,發(fā)出一個貼子:Let me try.等真正大致看了一下原文后,才有些后悔,倒不是怕工作量太大,只是擔心以自己的英文水平能否把這個文章按照作者的意思表達清楚.不怕各位笑話,在此之前,我對TCL幾乎是沒有聽說過(只知道TCL----王牌),更不用說掌握了.沒有辦法,只能現(xiàn)學現(xiàn)賣,在網(wǎng)上找到相關(guān)的介紹TCL的文章,認真地對其進行了了解,也只能是了解. 說點題外話,我現(xiàn)在對計算機這一行,真的有些倦了,因為作為一個系統(tǒng)管理員,每天都有更新的東西在等待你去學習,必須不停地學,而不能有片刻的停頓,否則你就將面臨被淘汰的危險,有時真的感覺很累,但沒有辦法,這就是生活,頭天晚上你對著厚厚的書本說,我看到你就惡心,再也不想碰你了.但睡一覺醒來,你還是得把它當作一位良師,一位益友. 我的英文水平一般,況且TCL語言對我是個新事物,盡管現(xiàn)在有了一定的了解,但其中的一些術(shù)語,我還是理解的不夠透徹.所有譯文中的有些地方可能讓大家覺得有些迷惑,甚至可能有些可笑,對此還請各位諒解.也希望有高手能指出譯文中的錯誤,別讓我的劣作影響了各位網(wǎng)友的學習. 同時也真誠希望大家給我來信,交朋友,共同提高. 我的email:zkzxl@etang.com 不知我的譯文出來前,是否已經(jīng)有網(wǎng)友為各位譯出來,希望能夠互相交流一下. ********************************************************************************
TCL腳本數(shù)據(jù)文件格式 簡介 一個典型的tcl腳本把它的內(nèi)部數(shù)據(jù)保存在列表和數(shù)組(tcl中兩種主要的數(shù)據(jù)結(jié)構(gòu))中.比如,假定你想寫一個能將數(shù)據(jù)先保存在磁盤上,然后再讀取的tcl應(yīng)用程序, 這將使你的用戶可以先把一個項目保存下來,以后再重新裝入.你需要一個辦法,把數(shù)據(jù)從其內(nèi)部存儲處(列表與數(shù)組)寫入到一個文件中,同樣,也要有一個辦法把數(shù)據(jù)從文件中讀出裝入到正在運行的腳本中去. 你可以選擇把數(shù)據(jù)保存為二進制格式或文本格式.本文討論的僅限文本格式,我們將考慮幾種可能的數(shù)據(jù)格式及如何用tcl來進行分析.我們會特別介紹一些簡單的技巧,使文本文件分析更容易. 本文假定你對tcl語言很熟悉,至少已經(jīng)用tcl語言寫過幾個腳本. ▲一個簡單的例子 假定你有一個簡單的繪圖工具,能把文本和長方形放到畫布上.為了保存畫好的圖,你需要一個必須容易讀取的文本格式的文件,最先想到而且最容易的文件是這樣的: example1/datafile.dat rectangle 10 10 150 50 2 blue rectangle 7 7 153 53 2 blue text 80 30 "Simple Drawing Tool" c red The first two lines of this file represent the data for two blue, horizontally stretched rectangles with a line thickness of 3. The final line places a piece of red text, anchored at the center (hence the "c"), in the middle of the two rectangles. 文件的前兩行代表兩個藍色的水平展開的長方形,線條寬度是2(原文此處為3,可能是筆誤,譯者注).最后一行放了一段紅色的文字,定位在中心(由"c"來指定)----在兩個長方形的中間. 用文本文件保存你的數(shù)據(jù)使程序的調(diào)試更容易,因為你可以檢查程序輸出來保證一切都正常。同時也允許用戶手工修改保存的數(shù)據(jù)(這樣做可能好,也可能不好,取決于你的意圖). 當你讀取這種格式的文件時,或許得先對文件進行分析然后據(jù)此創(chuàng)建數(shù)據(jù)結(jié)構(gòu).分析文件時,你要一行一行地嘗試,使用象regexp這類的工具來分析文本不同的部分.下面是一個可能的過程: example1/parser.tcl canvas .c pack .c set fid [open "datafile.dat" r] while { ![eof $fid] } { # Read a line from the file and analyse it. gets $fid line if { [regexp / {^rectangle +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +(.*)$} / $line dummy x1 y1 x2 y2 thickness color] } { .c create rectangle $x1 $y1 $x2 $y2 -width $thickness -outline $color } elseif { [regexp / {^text +([0-9]+) +([0-9]+) +("[^"]*") +([^ ]+) +(.*)$} / $line dummy x y txt anchor color] } { .c create text $x $y -text $txt -anchor $anchor -fill $color } elseif { [regexp {^ *$} $line] } { # Ignore blank lines } else { puts "error: unknown keyword." } } close $fid 我們一次讀取一行數(shù)據(jù),使用正則表達式查找該行代表的是某種數(shù)據(jù)類型.通過檢查第一個詞,我們可以區(qū)分代表長方形的數(shù)據(jù)和代表文本的數(shù)據(jù),所以第一個詞是一個關(guān)鍵字,它明確地告訴我們正在處理的是什么類型的數(shù)據(jù).同樣我們分析每個項目的坐標,顏色和其他屬性.括號中正則表達式的分組部分使我們找到變量 'x1','x2'等的分析后的結(jié)果. 假如你知道正則表達式如何工作,這看上去是一個很簡單的實現(xiàn).但我覺得它有點難以維護,正則表達式也使其難以理解. 還有一個更簡捷的解決方法,叫做“active file(主動文件)”.原本由Nat Pryce在設(shè)計樣本時想到的。這種方法基于一個非常簡單的提議:與其用TCL自己來寫語法分析器(用regexp或其他途徑),干嘛不讓TCL的語法分析器為你做這些工作呢? ▲主動文件設(shè)計樣本 為解釋這種設(shè)計樣本,我們繼續(xù)使用上節(jié)中那個簡單的繪圖工具。首先我們用TCL語言寫兩個過程,一個畫矩形,一個寫文本。 example2/parser.tcl canvas .c pack .c proc d_rect {x1 y1 x2 y2 thickness color} { .c create rectangle $x1 $y1 $x2 $y2 -width $thickness -outline $color } proc d_text {x y text anchor color} { .c create text $x $y -text $text -anchor $anchor -fill $color } 現(xiàn)在要在畫布上繪圖,我們調(diào)用這兩個過程就行了,每次調(diào)用其中的一項。比如要畫如前所述的圖形,需要下面三個調(diào)用。 example2/datafile.dat d_rect 10 10 150 50 2 blue d_rect 7 7 153 53 2 blue d_text 80 30 "Simple Drawing Tool" c red 看上去眼熟嗎?調(diào)用過程的代碼看上去與先前我們分析的代碼幾乎完全一樣。唯一的不同之處是關(guān)鍵詞由"rectangle"和"text"變成了"d_rect"和"d_text". 現(xiàn)在我們看到了寫樣本的技巧:為分析數(shù)據(jù)文件,我們要把它當作一個TCL腳本來對待。我們只把對我們寫好的過程的調(diào)用放到一個文件中,并用此文件作為數(shù)據(jù)文件.設(shè)計樣本的核心是數(shù)據(jù)文件實際上包含著對TCL過程的調(diào)用. 分析數(shù)據(jù)文件現(xiàn)在太容易了: source "datafile.dat" 內(nèi)建的TCL命令source讀取文件,分析并執(zhí)行文件中的命令.因為我們已經(jīng)完成了d_rect和d_text過程,source命令將自動以正確的參數(shù)調(diào)用這兩個過程.我們將d_rect和d_text稱為分析過程. 我們無需再做任何分析,不用正則表達式,不用一行一行地循環(huán),不用打開/關(guān)閉文件.只需調(diào)用source命令就完成了所有的工作。 數(shù)據(jù)文件已經(jīng)成了可以執(zhí)行的TCL腳本.因為它包含的是可執(zhí)行命令,而不僅僅是被動的數(shù)據(jù),所以稱之為主動文件.主動文件在大多數(shù)腳本語言環(huán)境中均可正常運行,在Nat Pryce的主頁上對其有詳細的描述. ▲使用主動文件樣本的優(yōu)點: 無需再寫一個分析程序,source調(diào)用TCL分析程序即可完成. 容易讀取數(shù)據(jù)文件格式. 使用主動文件樣本的缺點: 如果數(shù)據(jù)文件包含有危險命令,象l -a exec rm *,它們執(zhí)行后會帶來嚴重的后果.解決這個問題的辦法是在安全模式下執(zhí)行主動文件,防止危險命令。具體信息可參看TCL手冊中"安全解釋器"部分. ▲主動文件樣本的局限 此樣本不是對所有可能的數(shù)據(jù)格式都有效.數(shù)據(jù)格式必須是以行為基礎(chǔ)的,每一行必須以一個關(guān)鍵字開頭.用關(guān)鍵字開頭寫TCL過程,就把被動的關(guān)鍵字變成了主動的命令。這也意味著你不能使用象if或while之類的關(guān)鍵字,因為TCL不允許你用這樣的名字來寫過程.事實上,上面的例子中我把關(guān)鍵字改為 d_text,就是因為開發(fā)工具包已經(jīng)有了保留字text,該命令用來創(chuàng)建文本工具. ▲英語言過程 至此我們已經(jīng)可以寫一個簡單的文件格式了: d_rect 10 10 150 50 2 blue d_rect 7 7 153 53 2 blue d_text 80 30 "Simple Drawing Tool" c red 我們還有一個很簡單的分析程序,就是兩個分析過程和source命令.現(xiàn)在,我們看一下如何來進一步改進. 當你觀察大量此類數(shù)據(jù)時,極易被數(shù)據(jù)搞糊涂.第一行包含10 10 110 50 3,你得有些這方面的經(jīng)驗才能很快明白前兩個代表一個坐標,后兩個是另一個坐標,最后一個是線寬.我們能用在數(shù)據(jù)中引入附加文本的方法來使一個程序員在閱讀時較為容易. example3/datafile.dat d_rect from 10 10 to 150 50 thick 2 clr blue d_rect from 7 7 to 153 53 thick 2 clr blue d_text at 80 30 "Simple Drawing Tool" anchor c clr red 介詞to和from,參數(shù)名thick和color使數(shù)據(jù)看上去更象英語句子了,為適應(yīng)這些介詞,我們的分析過程需要其他的附加參數(shù): example3/parser.tcl proc d_rect {from x1 y1 to x2 y2 thick thickness clr color} { .c create rectangle $x1 $y1 $x2 $y2 -width $thickness -outline $color } 正如你所看到的,執(zhí)行過程并未改變.新參數(shù)在過程體中并未使用;其目的僅僅是為了使用數(shù)據(jù)可讀性更強. ▲選項/數(shù)值對 Tk工具包提供了一個創(chuàng)建圖形界面部件的集合.這些部件以選項和他們的值來加以配置,配置的語法很簡單(一個橫線,后跟選項名,再后面是其值)而且標準化(許多其他的TCL擴展集使用相同的語法來配置其部件). 使用選項/數(shù)值對后,數(shù)據(jù)文件看上去象這樣: example4/datafile.dat d_rect -x1 10 -y1 10 -x2 150 -y2 50 -thickness 2 d_rect -thickness 2 -x1 7 -y1 7 -x2 153 -y2 53 d_text -x 80 -y 30 -text "Simple Drawing Tool" -anchor c -color red 為分析數(shù)據(jù),我們需要在分析過程d_rect和d_text中引入選項/數(shù)值對,我們首先試一下使用與英語過程相似的啞變量. proc d_rect {opt1 x1 opt2 y1 opt3 x2 opt4 y2 opt5 thickness opt6 color} { .c create rectangle $x1 $y1 $x2 $y2 -width $thickness -outline $color } 我們再一次看到,實現(xiàn)的過程并未改變.盡管這個解決方案只對最簡單的數(shù)據(jù)格式有效,但它很清晰明了.它的優(yōu)點有兩個:選項在參數(shù)列表中的位置是固定的.比如,你不能把color(顏色屬性)放在thickness(線寬屬性)前面.對一個純數(shù)據(jù)文件格式來說這個方法還不錯(因為數(shù)值往往按相同的順序存儲),但當你想將其用于腳本中的手工輸入數(shù)據(jù)時,這個方法則成了一個障礙. 選項沒有默認值:你必須提供所有選項的值,而不能遺漏其中任何一個. 下面是一個可解決所有問題的實現(xiàn)過程. example4/parser.tcl proc d_rect {args} { # First, specify some defaults set a(-thickness) 1 set a(-color) blue # Then, 'parse' the user-supplied options and values array set a $args # Create the rectangle .c create rectangle $a(-x1) $a(-y1) $a(-x2) $a(-y2) / -width $a(-thickness) -outline $a(-color) } 與使用一個長長的參數(shù)表不同,分析過程現(xiàn)在僅有一個名為args的參數(shù),由它來收集調(diào)用過程時所有的實際參數(shù).參數(shù)x1,y1等消失了.他們現(xiàn)在由一個局部的數(shù)組來處理,稍后我們將圓心解釋. 代碼的第一部分為選項設(shè)定默認值,第二部分分析args中的選項/數(shù)值對.TCL內(nèi)建的數(shù)組處理模塊對此做得非常得心映手.它先在數(shù)組a中創(chuàng)建新的入口,使用選項名(包括前導(dǎo)橫線"-")作為索引,選項值作為數(shù)組值. 如果用戶在調(diào)用中不指定-color選項,a(-color)的入口默認值保持不變. 除用數(shù)組入口代替過程參數(shù)外,過程體中的最后一行與前面的實現(xiàn)一樣. 如果用戶調(diào)用時忘記指定選項-x1,則-x1的數(shù)組入口不會被設(shè)置(沒有其默認值),創(chuàng)建矩形的調(diào)用就會引發(fā)一個錯誤.此例說明你可以給其中一些選項指定默認值,使其可隨意選擇,而另一些則不指定默認值,強制其必須由用戶指定. ▲最好的格式通常是各種方法的結(jié)合 現(xiàn)在我們已經(jīng)明白了TCL數(shù)據(jù)文件的常見方法(主動文件,英語言過程,選項/數(shù)值對),我們可以將其各自的優(yōu)點組合進一個單獨的數(shù)據(jù)格式中去.對強制性選項,我們使用固定位置參數(shù)時,多半與啞介詞相結(jié)合增強可讀性(見英語言過程).而所有的可隨意選擇的選項,宜用選項/數(shù)值對機制來進行處理,好讓用戶可以空著選項或在調(diào)用時改變其位置.最后,數(shù)據(jù)文件可能會是這樣的: d_rect from 10 10 to 150 50 -thickness 2 d_rect from 7 7 to 153 53 -thickness 2 d_text at 60 30 "Simple Drawing Tool" -anchor c -color red 假定所有項目的color屬性的默認值都是"blue". 作為一個個人習慣,我通常會寫這樣的命令: d_rect / from 10 10 / to 150 50 / -thickness 2 d_rect / from 7 7 / to 153 53 / -thickness 2 d_text / at 80 30 "Simple Drawing Tool" / -anchor c / -color red I find it slightly more readable, but that's all a matter of personal taste (or in my case lack of taste :-). 我覺得可讀性要好一些,但這僅是一個個人偏好的問題.(or in my case lack of taste)(這句話是作者在調(diào)侃自己,但我不知如何把它譯出來,請哪位大俠幫忙指點一下,譯者注) -------------------------------------------------------------------------------- ▲更多復(fù)雜的數(shù)據(jù) 至今為止,我們已經(jīng)對一個非常簡單的包含矩形與文本的例子進行了研究.這種數(shù)據(jù)格式用主動文件設(shè)計樣本非常容易讀取并加以分析. 現(xiàn)在我們來看一個更為復(fù)雜的數(shù)據(jù)格式,來解釋一下使用主動文件的更加"高級"的技巧.這將使你在使用TCL數(shù)據(jù)文件格式方面成為一個專家. ▲數(shù)據(jù)倉庫工具 我過去經(jīng)常收集設(shè)計樣本,組成了一個樣本庫,每個都有一個簡短的說明和一些屬性.我還把在其中找到樣本的書的名字,作者和ISBN號記下來,作為以后查找時的參考.為了記錄所有這些信息,我用TCL寫了一個數(shù)據(jù)倉庫工具.其主要功能是把樣本按照類別和級別進行分類,指出全書中每一個樣本和講述它的頁碼. 此工具的輸入是與此相似的一個文件: #首先,我介紹一些你從中可以找到好的設(shè)計樣本的書和設(shè)計程序時的習慣寫法.每一本書, #每一個網(wǎng)址,或是其他的樣本資源都用關(guān)鍵字"source"指定,后跟一個唯一的標簽及其他附 #加信息 Source GOF { Design patterns Elements of reusable object-oriented software Gamm, Helm, Johnson, Vlissides Addison-Wesley, 1995 0 201 63361 2 } Source SYST { A system of patterns Pattern-oriented software architecture Buschmann, Meunier, Rohnert, Sommerlad, Stal Wiley, 1996 0 471 95869 7 } #下一步,我介紹一些類別,為了更容易找到樣本,我想把樣本進行分組.每個類別都 #有一個名稱(如"存取控制")和一個簡短的說明. Category "Access control" { How to let one object control the access to one or more other objects. } Category "Distributed systems" { Distributing computation over multiple processes, managing communication between them. } Category "Resource handling" { Preventing memory leaks, managing resources. } Category "Structural decomposition" { To break monoliths down into indpendent components. } #最后,我介紹了樣本本身,每一個都有一個名字,屬于一個或多個類別,出現(xiàn)在上述樣 #本資源列表的一處或多處.每個樣本都有級別,可能是"arch"(對于結(jié)構(gòu)型樣本), #"design"代表較小規(guī)模的設(shè)計樣本,"idiom"代表語言指定型樣本. Pattern "Broker" { Categories {"Distributed systems"} Level arch Sources {SYST:99} ; # 這表示此樣本在標記為"SYST"的書中 # 第99頁加以講述. Info { Remote service invocations. } } Pattern "Proxy" { # This pattern fits in two categories: Categories {"Access control" "Structural decomposition::object"} Level design # Both these books talk about the Proxy pattern: Sources {SYST:263 GOF:207} Info { Communicate with a representative rather than with the actual object. } } Pattern "Facade" { Categories {"Access control" "Structural decomposition::object"} Sources {GOF:185} Level design Info { Group sub-interfaces into a single interface. } } Pattern "Counted Pointer" { Categories {"Resource handling"} Level idiom Sources {SYST:353} Info { Reference counting prevents memory leaks. } } 這僅是我最初編寫的輸入文件的一部分,但它還是包含了足夠的數(shù)據(jù)來作為一個較好的例子.樣本的說明很短,還有些笨拙,但對這個例子來說已經(jīng)夠了. 正如你看到的,這個數(shù)據(jù)文件幾個新的特點: ▲數(shù)據(jù)被包含在一些結(jié)構(gòu)中,用大括號{}加以分組.每個結(jié)構(gòu)都由一個關(guān)鍵字開頭. 這些結(jié)構(gòu)可以嵌套,如:結(jié)構(gòu)"Pattern"可以包含一個"Info"結(jié)構(gòu). ▲結(jié)構(gòu)中的元素可以采用很多形式。它們中的一些是標志符或字符串(比如元素"Level"),其他的看上去象是特殊的代碼(如SYST:353),還有一些甚至是自由格式的文本(如在結(jié)構(gòu)Category和Info中的那樣). ▲每個結(jié)構(gòu)中的元素的排列順序是任意的.觀察一下最后兩個樣本就會發(fā)現(xiàn)Level和Sources兩個元素的順序可以互換.所有元素實際上都可以按你想要的順序排列. ▲數(shù)據(jù)文件包含有TCL注釋語句,他們不僅可以在結(jié)構(gòu)之間出現(xiàn),甚至可以出現(xiàn)在結(jié)構(gòu)內(nèi)部.注釋語句能讓你的數(shù)據(jù)更易理解. 你可能會想這種格式比前面的例子復(fù)雜太多了,用TCL語言為其寫一個分析器幾乎是不可能的.可能看上去不太明了,我們還可以用主動文件樣本來使此工作更加簡單.分析(解析)過程比前面的更細而已,但肯定不是"復(fù)雜". 下面是我的分析如上數(shù)據(jù)文件的工具: #我們把數(shù)據(jù)保存在以下三個列表內(nèi): set l_patterns [list] set l_sources [list] set l_categories [list] #我們還需要一個變量跟蹤我們當前所在的Pattern結(jié)構(gòu) set curPattern "" # 下面是關(guān)鍵字"Source"的分析過程. # 正如你所看到的,關(guān)鍵字后面跟有一個id號(是source的唯一標志符), #還有source的說明文本. proc Source {id info} { # Remember that we saw this source. global l_sources lappend l_sources $curSource # Remember the info of this source in a global array. global a_sources set a_sources($curSource,info) $info } # The parsing proc for the 'Category' keyword is similar. proc Category {id info} { global l_categories lappend l_categories $curCategory global a_categories set a_categories($curCategory,info) $info } # This is the parsing proc for the 'Pattern' keyword. # Since a 'Pattern' structure can contain sub-structures, # we use 'uplevel' to recursively handle those. proc Pattern {name args} { global curPattern set curPattern $name ; # This will be used in the sub-structures # which are parsed next global l_patterns lappend l_patterns $curPattern # We treat the final argument as a piece of TCL code. # We execute that code in the caller's scope, to parse the elements # of the structure. # 'uplevel' will call 'Categories', 'Level' and other commands that # handle the sub-structures. # This is similar to how we use the 'source' command to parse the entire # data file. uplevel 1 [lindex $args end] set curPattern "" } # The parsing proc for one of the sub-structures. It is called # by 'uplevel' when the 'Pattern' keyword is handled. proc Categories {categoryList} { global curPattern ; # We access the global variable 'curPattern' # to find out inside which structure we are. global a_patterns set a_patterns($curPattern,categories) $categoryList } # The following parsing procs are for the other sub-structures # of the Pattern structure. proc Level {level} { global curPattern global a_patterns set a_patterns($curPattern,level) $level } proc Sources {sourceList} { global curPattern global a_patterns # We store the codes such as 'SYST:99' in a global array. # My implementation uses regular expressions to extract the source tag # and the page number from such a code (not shown here). set a_patterns($curPattern,sources) $sourceList } proc Info {info} { global curPattern global a_patterns set a_patterns($curPattern,info) $info } 猛一看,這個程序比我們在相對簡單的繪圖例子所做的要多很多.但考慮到這個方法的功能,只用幾個分析過程并靈活運用命令"uplevel",我們同樣可以分析包含有復(fù)雜結(jié)構(gòu),注釋,嵌套子結(jié)構(gòu)和自由格式文本數(shù)據(jù)的數(shù)據(jù)文件.設(shè)想一下如果我們從頭寫這樣一個分析器會有多難. 數(shù)據(jù)由Source,Pattern或Info等過程進行解析.解析后的數(shù)據(jù)在內(nèi)部存儲在三個列表和三個數(shù)組中.數(shù)據(jù)的嵌套由調(diào)用uplevel來進行處理,用變量curPattern來記住我們當前所在的位置. 要注意的是這種方法需要你的數(shù)據(jù)能夠理解TCL語法.這意味著大括號應(yīng)該放在一行的最后,而不是下一行的開頭. ▲遞歸結(jié)構(gòu) 在倉庫的樣例中,Pattern類型的結(jié)構(gòu)包含有其他類型的子結(jié)構(gòu)如Info和Sources.那么當一個結(jié)構(gòu)包含有相同類型的子結(jié)構(gòu)時會如何呢?換句話說,我們?nèi)绾翁幚磉f歸結(jié)構(gòu)? 例如,你要描述一個面向?qū)ο笙到y(tǒng)的設(shè)計,該設(shè)計由遞歸子系統(tǒng)實現(xiàn). example6/datafile.dat # Description of an object-oriented video game System VideoGame { System Maze { System Walls { Object WallGenerator Object TextureMapper } System Monsters { Object FightingEngine Object MonsterManipulator } } System Scores { Object ScoreKeeper } } 為跟蹤我們當前處于哪一個System系統(tǒng)結(jié)構(gòu)中,看上去我們需要不只一個全局變量currPattern.在分析的任何時刻,我們都可能處在很多嵌套的 System結(jié)構(gòu)中,因此我們需要兩個以上的變量.我們可能需要某種堆棧,在遇到System過程時壓入一個值,在過程的結(jié)束時再彈出來.我們用一個 TCL列表可以構(gòu)造這樣一個棧. 但若你不想維護一個棧的話,也可以不用它.這種方法也是基于一個非常簡單的建議:當你需要使用一個棧時,看一下能否使用函數(shù)調(diào)用棧.處理遞歸數(shù)據(jù)時,我通常就用這個方法來實現(xiàn)我的分析過程的. example6/parser.tcl set currSystem "" proc System {name args} { # Instead of pushing the new system on the 'stack' of current systems, # we remember it in a local variable, which ends up on TCL's # function call stack. global currSystem set tmpSystem $currSystem set currSystem $name ; # Thanks to this, all sub-structures called by # 'uplevel' will know what the name of their # immediate parent System is # Store the system in an internal data structure # (details not shown here) puts "Storing system $currSystem" # Execute the parsing procedures for the sub-systems uplevel 1 [lindex $args end] # Pop the system off the 'stack' again. set currSystem $tmpSystem } proc Object {name} { global currSystem # Store the object in the internal data structure of the current # system (details not shown here) puts "System $currSystem contains object $name" } source "datafile.dat" 與把嵌套的系統(tǒng)名存儲在一個棧中(該棧由TCL的列表或數(shù)組來模擬)不同,我們只把對象名存儲在一個名為tmpSystem的局部變量中.由于解析過程會由TCL依據(jù)棧中的順序自動調(diào)用,我們無需再去顯式地壓入/彈出任何數(shù)據(jù)了. ▲其他例子 由Don Libes 寫的CGI庫使用主動文件樣本來表達HTML文檔.這個想法是寫一個TCL腳本作為HTML文檔并為你生成純正的HTML文件.該文檔包含有核心列表,格式化文本和其他的HTML元素.分析過程調(diào)用uplevel處理遞歸子結(jié)構(gòu). 下面是Don的代碼的一部分,告訴你他是如何應(yīng)用本文所講述的技巧的. # Output preformatted text. This text must be surrounded by '<pre>' tags. # Since it can recursively contain other tags such as '<em>' or hyperlinks, # the procedure uses 'uplevel' on its final argument. proc cgi_preformatted {args} { cgi_put "<pre" cgi_close_proc_push "cgi_puts </pre>" if {[llength $args]} { cgi_put "[cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_puts ">" uplevel 1 [lindex $args end] cgi_close_proc } # Output a single list bullet. proc cgi_li {args} { cgi_put <li if {[llength $args] > 1} { cgi_put "[cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_puts ">[lindex $args end]" } # Output a bullet list. It contains list bullets, represented # by calls to 'cgi_li' above. Those calls are executed thanks # to 'uplevel'. proc cgi_bullet_list {args} { cgi_put "<ul" cgi_close_proc_push "cgi_puts </ul>" if {[llength $args] > 1} { cgi_put "[cgi_lrange $args 0 [expr [llength $args]-2]]" } cgi_puts ">" uplevel 1 [lindex $args end] cgi_close_proc } 我不想對這個龐大的庫的細節(jié)進行詳細的解釋,你可以自己從Don的主頁上下載后看一下. -------------------------------------------------------------------------------- 作為另一個例子,我的TODL工具使用類和方法等解析過程對面向?qū)ο蟮脑O(shè)計加以分析.下面是我的工具中一個輸入文件的例子: # Todl schema for module 'shapes'. It describes classes for some # geometrical shapes such as rectangles and squares. odl_module shapes { ####### # Classes # Base class for all shapes. class shape {} { attr id 0 ; # Attribute 'id' is inherited by all shapes # and has default value 0. } # Rectangle with a width and height. # Inherits from 'shape'. class rect {shape} { attr w 10 attr h 10 # Some methods to calculate properties for the shape, # and to draw it on the screen. method "" perimeter {} method "" area {} method "" draw { x {y 0} } } class square {shape} { ... (details similar to 'rect') } ####### # Module parameters # All classes automatically get a 'print' method. param all { print } # Name of the 'delete' proc. param delete_name delete # We want debugging output: param debug 1 } 查看本文件后,你能指出全部分析過程的列表嗎? -------------------------------------------------------------------------------- 我曾經(jīng)為C++的類實現(xiàn)寫過一個(非常)簡單的解析器.因為太懶,所以我用TCL語言來寫.事實證明它過于復(fù)雜以致沒有一點用處,但它說明了主動文件樣本的強大功能.下面看一下這個包含有復(fù)雜的C++的數(shù)據(jù)文件: class myListElt: public CListElt, private FString { This is a documentation string for the class 'myListElt'. You can see multiple inheritance here. } { public: method int GetLength(void) { This is a documentation string Returns the total length of the FString. } { // This is the final argument of the 'method' parsing proc. // It contains freeform text, so this is where I can write // pure C++ code, including the comment you are now reading. return myLength; } method char* GetString(void) { Returns the complete FString. } { append(0); return (char*)data; } private: method virtual void privateMethod(short int p1, short int p2) { Note that just saying "short" is not enough: you have to say "short int". } { printf("Boo! p1=%d, p2=%d/n", p1, p2); } } data short int b {This is just a counter} data void* somePointer {to store the end-of-list or something like that} method void error(short int errNo, char* message) { This is a global library procedure, which reports an error message. } { cout << "Hey, there was an error (" << errNo << ") " << message << endl; } cpp_report 這個例子可能有些牽強,但它顯示了主動文件樣本的強大功能.你看到的是TCL代碼,但它看上去象是C++代碼,它能自動產(chǎn)生文檔,類圖,編程參考,當然還有可編譯的C++代碼. 解析過程如方法和類把C++實現(xiàn)存儲在內(nèi)部的TCL數(shù)據(jù)結(jié)構(gòu)中,最后,調(diào)用cpp_report產(chǎn)生最終的C++代碼. 下面的來自分析器的程序片段說明你可以使TCL分析器去讀取與C++語法類似的文件. # Parsing proc for 'class' keyword. # Arguments: # - class name # - list of inheritance specifications, optional # - comment block # - body block proc class {args} { global _cpp # split names from signs like : , * set cargs [expand [lrange $args 0 [expr [llength $args] - 3]]] # -3 to avoid the comment block and the class body. # First process the name set className [lindex $cargs 0] if { $_cpp(CL) == "" } { set _cpp(CL) $className ; # This is like 'currPattern' in the # pattern repository example } else { error "Class definition for $className: we are already inside class $_cpp(CL)" } # Then process the inheritance arguments. # Obvisouly, this is already a lot more complicated than in the # previous examples. set inhr [list] set mode beforeColon set restArgs [lrange $cargs 1 end] foreach arg $restArgs { if { $arg == ":" } { if { $mode != "beforeColon" } { error "Misplaced /":/" in declaration /"class $className $restArgs/"" } set mode afterColon } elseif { $arg == "public" || $arg == "private" } { if { $mode != "afterColon" } { error "Misplaced /"$arg/" in declaration /"class $className $restArgs/"" } set mode $arg } elseif { $arg == "," } { if { $mode != "afterInherit" } { error "Misplaced /",/" in declaration /"class $className $restArgs/"" } set mode afterColon } else { if { $mode != "public" && $mode != "private" } { error "Misplaced /"$arg/" in declaration /"class $className $restArgs/"" } if { ![IsID $arg] } { warning "$arg is not a valid C++ identifier..." } lappend inhr [list $mode $arg] set mode afterInherit } } if { $mode != "afterInherit" && $mode != "beforeColon" } { error "Missing something at end of declaration /"class $className $restArgs/"" } set _cpp(CLih) $inhr set _cpp(CLac) "private" # First execute the comment block uplevel 1 [list syn_cpp_docClass [lindex $args [expr [llength $args] - 2]]] # Then execute the body uplevel 1 [list syn_cpp_bodyClass [lindex $args end]] set _cpp(CL) "" set _cpp(CLac) "" set _cpp(CLih) "" } -------------------------------------------------------------------------------- 關(guān)于懶惰 按Larry Wall的話,一個好的程序員的最重要的潛質(zhì)就是懶惰.也就是說,有創(chuàng)造性的懶惰.本文提到了兩個建議,他們能夠歸于一件事:懶惰. 當你需要一個解析器時,使用一個現(xiàn)成的解析器,修改你的文件格式去造就分析器的要求(當然,需要你已經(jīng)達到了能夠自由選擇文件格式的境界) 當你需要使用堆棧時,你可以使用現(xiàn)成的函數(shù)調(diào)用堆棧,忘掉壓入,彈出和其他的操作. "重用"并不僅表示封裝和信息的隱藏.有些時候它只不過表示懶惰罷了. --------------------------------------------------------------------------------
Data file formats for TCL scripts
|