电脑技术学习

TCL脚本数据文件格式

dn001


写在前面的话
在论坛上看到这篇文章时,一时冲动,发出一个贴子:Let me try.等真正大致看了一下原文后,才有些后悔,倒不是怕工作量太大,只是担心以自己的英文水平能否把这个文章按照作者的意思表达清楚.不怕各位笑话,在此之前,我对TCL几乎是没有听说过(只知道TCL----王牌),更不用说掌握了.没有办法,只能现学现卖,在网上找到相关的介绍TCL的文章,认真地对其进行了了解,也只能是了解.
说点题外话,我现在对计算机这一行,真的有些倦了,因为作为一个系统管理员,每天都有更新的东西在等待你去学习,必须不停地学,而不能有片刻的停顿,否则你就将面临被淘汰的危险,有时真的感觉很累,但没有办法,这就是生活,头天晚上你对着厚厚的书本说,我看到你就恶心,再也不想碰你了.但睡一觉醒来,你还是得把它当作一位良师,一位益友.
我的英文水平一般,况且TCL语言对我是个新事物,尽管现在有了一定的了解,但其中的一些术语,我还是理解的不够透彻.所有译文中的有些地方可能让大家觉得有些迷惑,甚至可能有些可笑,对此还请各位谅解.也希望有高手能指出译文中的错误,别让我的劣作影响了各位网友的学习.
同时也真诚希望大家给我来信,交朋友,共同提高.
我的email:zkzxl@etang.com
不知我的译文出来前,是否已经有网友为各位译出来,希望能够互相交流一下.
********************************************************************************
TCL脚本数据文件格式
简介
一个典型的tcl脚本把它的内部数据保存在列表和数组(tcl中两种主要的数据结构)中.比如,假定你想写一个能将数据先保存在磁盘上,然后再读取的tcl应用程序, 这将使你的用户可以先把一个项目保存下来,以后再重新装入.你需要一个办法,把数据从其内部存储处(列表与数组)写入到一个文件中,同样,也要有一个办法把数据从文件中读出装入到正在运行的脚本中去.
你可以选择把数据保存为二进制格式或文本格式.本文讨论的仅限文本格式,我们将考虑几种可能的数据格式及如何用tcl来进行分析.我们会特别介绍一些简单的技巧,使文本文件分析更容易.
本文假定你对tcl语言很熟悉,至少已经用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"来指定)----在两个长方形的中间.
用文本文件保存你的数据使程序的调试更容易,因为你可以检查程序输出来保证一切都正常。同时也允许用户手工修改保存的数据(这样做可能好,也可能不好,取决于你的意图).

当你读取这种格式的文件时,或许得先对文件进行分析然后据此创建数据结构.分析文件时,你要一行一行地尝试,使用象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

我们一次读取一行数据,使用正则表达式查找该行代表的是某种数据类型.通过检查第一个词,我们可以区分代表长方形的数据和代表文本的数据,所以第一个词是一个关键字,它明确地告诉我们正在处理的是什么类型的数据.同样我们分析每个项目的坐标,颜色和其他属性.括号中正则表达式的分组部分使我们找到变量'x1','x2'等的分析后的结果.
假如你知道正则表达式如何工作,这看上去是一个很简单的实现.但我觉得它有点难以维护,正则表达式也使其难以理解.
还有一个更简捷的解决方法,叫做“active file(主动文件)”.原本由Nat Pryce在设计样本时想到的。这种方法基于一个非常简单的提议:与其用TCL自己来写语法分析器(用regexp或其他途径),干嘛不让TCL的语法分析器为你做这些工作呢?
▲主动文件设计样本
为解释这种设计样本,我们继续使用上节中那个简单的绘图工具。首先我们用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
}

现在要在画布上绘图,我们调用这两个过程就行了,每次调用其中的一项。比如要画如前所述的图形,需要下面三个调用。
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

看上去眼熟吗?调用过程的代码看上去与先前我们分析的代码几乎完全一样。唯一的不同之处是关键词由"rectangle"和"text"变成了"d_rect"和"d_text".
现在我们看到了写样本的技巧:为分析数据文件,我们要把它当作一个TCL脚本来对待。我们只把对我们写好的过程的调用放到一个文件中,并用此文件作为数据文件.设计样本的核心是数据文件实际上包含着对TCL过程的调用.
分析数据文件现在太容易了:
source "datafile.dat"

内建的TCL命令source读取文件,分析并执行文件中的命令.因为我们已经完成了d_rect和d_text过程,source命令将自动以正确的参数调用这两个过程.我们将d_rect和d_text称为分析过程.
我们无需再做任何分析,不用正则表达式,不用一行一行地循环,不用打开/关闭文件.只需调用source命令就完成了所有的工作。
数据文件已经成了可以执行的TCL脚本.因为它包含的是可执行命令,而不仅仅是被动的数据,所以称之为主动文件.主动文件在大多数脚本语言环境中均可正常运行,在Nat Pryce的主页上对其有详细的描述.
▲使用主动文件样本的优点:
无需再写一个分析程序,source调用TCL分析程序即可完成.
容易读取数据文件格式.
使用主动文件样本的缺点:
如果数据文件包含有危险命令,象l -a exec rm *,它们执行后会带来严重的后果.解决这个问题的办法是在安全模式下执行主动文件,防止危险命令。具体信息可参看TCL手册中"安全解释器"部分.
▲主动文件样本的局限
此样本不是对所有可能的数据格式都有效.数据格式必须是以行为基础的,每一行必须以一个关键字开头.用关键字开头写TCL过程,就把被动的关键字变成了主动的命令。这也意味着你不能使用象if或while之类的关键字,因为TCL不允许你用这样的名字来写过程.事实上,上面的例子中我把关键字改为d_text,就是因为开发工具包已经有了保留字text,该命令用来创建文本工具.
▲英语言过程
至此我们已经可以写一个简单的文件格式了:
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命令.现在,我们看一下如何来进一步改进.
当你观察大量此类数据时,极易被数据搞糊涂.第一行包含10 10 110 50 3,你得有些这方面的经验才能很快明白前两个代表一个坐标,后两个是另一个坐标,最后一个是线宽.我们能用在数据中引入附加文本的方法来使一个程序员在阅读时较为容易.
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,参数名thick和color使数据看上去更象英语句子了,为适应这些介词,我们的分析过程需要其他的附加参数:
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
}

正如你所看到的,执行过程并未改变.新参数在过程体中并未使用;其目的仅仅是为了使用数据可读性更强.
▲选项/数值对
Tk工具包提供了一个创建图形界面部件的集合.这些部件以选项和他们的值来加以配置,配置的语法很简单(一个横线,后跟选项名,再后面是其值)而且标准化(许多其他的TCL扩展集使用相同的语法来配置其部件).
使用选项/数值对后,数据文件看上去象这样:
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

为分析数据,我们需要在分析过程d_rect和d_text中引入选项/数值对,我们首先试一下使用与英语过程相似的哑变量.
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
}

我们再一次看到,实现的过程并未改变.尽管这个解决方案只对最简单的数据格式有效,但它很清晰明了.它的优点有两个:选项在参数列表中的位置是固定的.比如,你不能把color(颜色属性)放在thickness(线宽属性)前面.对一个纯数据文件格式来说这个方法还不错(因为数值往往按相同的顺序存储),但当你想将其用于脚本中的手工输入数据时,这个方法则成了一个障碍.
选项没有默认值:你必须提供所有选项的值,而不能遗漏其中任何一个.
下面是一个可解决所有问题的实现过程.
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)
}

与使用一个长长的参数表不同,分析过程现在仅有一个名为args的参数,由它来收集调用过程时所有的实际参数.参数x1,y1等消失了.他们现在由一个局部的数组来处理,稍后我们将圆心解释.
代码的第一部分为选项设定默认值,第二部分分析args中的选项/数值对.TCL内建的数组处理模块对此做得非常得心映手.它先在数组a中创建新的入口,使用选项名(包括前导横线"-")作为索引,选项值作为数组值.
如果用户在调用中不指定-color选项,a(-color)的入口默认值保持不变. 除用数组入口代替过程参数外,过程体中的最后一行与前面的实现一样.
如果用户调用时忘记指定选项-x1,则-x1的数组入口不会被设置(没有其默认值),创建矩形的调用就会引发一个错误.此例说明你可以给其中一些选项指定默认值,使其可随意选择,而另一些则不指定默认值,强制其必须由用户指定.
▲最好的格式通常是各种方法的结合
现在我们已经明白了TCL数据文件的常见方法(主动文件,英语言过程,选项/数值对),我们可以将其各自的优点组合进一个单独的数据格式中去.对强制性选项,我们使用固定位置参数时,多半与哑介词相结合增强可读性(见英语言过程).而所有的可随意选择的选项,宜用选项/数值对机制来进行处理,好让用户可以空着选项或在调用时改变其位置.最后,数据文件可能会是这样的:
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)(这句话是作者在调侃自己,但我不知如何把它译出来,请哪位大侠帮忙指点一下,译者注)
--------------------------------------------------------------------------------


▲更多复杂的数据
至今为止,我们已经对一个非常简单的包含矩形与文本的例子进行了研究.这种数据格式用主动文件设计样本非常容易读取并加以分析.
现在我们来看一个更为复杂的数据格式,来解释一下使用主动文件的更加"高级"的技巧.这将使你在使用TCL数据文件格式方面成为一个专家.
▲数据仓库工具
我过去经常收集设计样本,组成了一个样本库,每个都有一个简短的说明和一些属性.我还把在其中找到样本的书的名字,作者和ISBN号记下来,作为以后查找时的参考.为了记录所有这些信息,我用TCL写了一个数据仓库工具.其主要功能是把样本按照类别和级别进行分类,指出全书中每一个样本和讲述它的页码.
此工具的输入是与此相似的一个文件:

#首先,我介绍一些你从中可以找到好的设计样本的书和设计程序时的习惯写法.每一本书,
#每一个网址,或是其他的样本资源都用关键字"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.
}

#最后,我介绍了样本本身,每一个都有一个名字,属于一个或多个类别,出现在上述样
#本资源列表的一处或多处.每个样本都有级别,可能是"arch"(对于结构型样本),
#"design"代表较小规模的设计样本,"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.
}
}

这仅是我最初编写的输入文件的一部分,但它还是包含了足够的数据来作为一个较好的例子.样本的说明很短,还有些笨拙,但对这个例子来说已经够了.
正如你看到的,这个数据文件几个新的特点:
▲数据被包含在一些结构中,用大括号{}加以分组.每个结构都由一个关键字开头.
这些结构可以嵌套,如:结构"Pattern"可以包含一个"Info"结构.
▲结构中的元素可以采用很多形式。它们中的一些是标志符或字符串(比如元素"Level"),其他的看上去象是特殊的代码(如SYST:353),还有一些甚至是自由格式的文本(如在结构Category和Info中的那样).
▲每个结构中的元素的排列顺序是任意的.观察一下最后两个样本就会发现Level和Sources两个元素的顺序可以互换.所有元素实际上都可以按你想要的顺序排列.
▲数据文件包含有TCL注释语句,他们不仅可以在结构之间出现,甚至可以出现在结构内部.注释语句能让你的数据更易理解.
你可能会想这种格式比前面的例子复杂太多了,用TCL语言为其写一个分析器几乎是不可能的.可能看上去不太明了,我们还可以用主动文件样本来使此工作更加简单.分析(解析)过程比前面的更细而已,但肯定不是"复杂".
下面是我的分析如上数据文件的工具:

#我们把数据保存在以下三个列表内:
set l_patterns [list]
set l_sources [list]
set l_categories [list]

#我们还需要一个变量跟踪我们当前所在的Pattern结构
set curPattern ""

# 下面是关键字"Source"的分析过程.
# 正如你所看到的,关键字后面跟有一个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",我们同样可以分析包含有复杂结构,注释,嵌套子结构和自由格式文本数据的数据文件.设想一下如果我们从头写这样一个分析器会有多难.
数据由Source,Pattern或Info等过程进行解析.解析后的数据在内部存储在三个列表和三个数组中.数据的嵌套由调用uplevel来进行处理,用变量curPattern来记住我们当前所在的位置.
要注意的是这种方法需要你的数据能够理解TCL语法.这意味着大括号应该放在一行的最后,而不是下一行的开头.
▲递归结构
在仓库的样例中,Pattern类型的结构包含有其他类型的子结构如Info和Sources.那么当一个结构包含有相同类型的子结构时会如何呢?换句话说,我们如何处理递归结构?
例如,你要描述一个面向对象系统的设计,该设计由递归子系统实现.
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系统结构中,看上去我们需要不只一个全局变量currPattern.在分析的任何时刻,我们都可能处在很多嵌套的System结构中,因此我们需要两个以上的变量.我们可能需要某种堆栈,在遇到System过程时压入一个值,在过程的结束时再弹出来.我们用一个TCL列表可以构造这样一个栈.
但若你不想维护一个栈的话,也可以不用它.这种方法也是基于一个非常简单的建议:当你需要使用一个栈时,看一下能否使用函数调用栈.处理递归数据时,我通常就用这个方法来实现我的分析过程的.
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"

与把嵌套的系统名存储在一个栈中(该栈由TCL的列表或数组来模拟)不同,我们只把对象名存储在一个名为tmpSystem的局部变量中.由于解析过程会由TCL依据栈中的顺序自动调用,我们无需再去显式地压入/弹出任何数据了.
▲其他例子
由Don Libes 写的CGI库使用主动文件样本来表达HTML文档.这个想法是写一个TCL脚本作为HTML文档并为你生成纯正的HTML文件.该文档包含有核心列表,格式化文本和其他的HTML元素.分析过程调用uplevel处理递归子结构.
下面是Don的代码的一部分,告诉你他是如何应用本文所讲述的技巧的.

# 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
}

我不想对这个庞大的库的细节进行详细的解释,你可以自己从Don的主页上下载后看一下.
--------------------------------------------------------------------------------
作为另一个例子,我的TODL工具使用类和方法等解析过程对面向对象的设计加以分析.下面是我的工具中一个输入文件的例子:
# 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
}

查看本文件后,你能指出全部分析过程的列表吗?
--------------------------------------------------------------------------------
我曾经为C++的类实现写过一个(非常)简单的解析器.因为太懒,所以我用TCL语言来写.事实证明它过于复杂以致没有一点用处,但它说明了主动文件样本的强大功能.下面看一下这个包含有复杂的C++的数据文件:

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=%dn", 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++代码,它能自动产生文档,类图,编程参考,当然还有可编译的C++代码.
解析过程如方法和类把C++实现存储在内部的TCL数据结构中,最后,调用cpp_report产生最终的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) ""
}
--------------------------------------------------------------------------------
关于懒惰
按Larry Wall的话,一个好的程序员的最重要的潜质就是懒惰.也就是说,有创造性的懒惰.本文提到了两个建议,他们能够归于一件事:懒惰.
当你需要一个解析器时,使用一个现成的解析器,修改你的文件格式去造就分析器的要求(当然,需要你已经达到了能够自由选择文件格式的境界)
当你需要使用堆栈时,你可以使用现成的函数调用堆栈,忘掉压入,弹出和其他的操作.
"重用"并不仅表示封装和信息的隐藏.有些时候它只不过表示懒惰罢了.
--------------------------------------------------------------------------------

Koen Van Damme (koen.vandamme1 at pandora.be)
John Silver译

标签: