51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 5764|回复: 4
打印 上一主题 下一主题

[资料] tcl简介

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2007-7-9 23:33:00 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
看到不少朋友还不了解Tcl/Tk脚本 我找了点资料上来  大家可以看看 一起学习sdlkfj5

Tcl的诞生  by-John K. Ousterhout

Tcl脚本诞生于80年代初,我在加州大学伯克利分校的项目“集成电路设计工具”的研究。那时,我和我的学生已经写出了许多用于IC设计的交互工具,如Magic和Crystal。每个工具都需要一个命令语言(在那个年代,人们需要键入命令才能使用这些工具;图形用户界面还没有被广泛的使用)。不过我们的研究重点是工具而不是它们的命令语言,所以我们没有在命令语言上投入太多的精力,结果语言方面变得很薄弱。更糟的是,每个工具的语言之间是互不相通的。这样一来,经过一段时间后,就变得相当棘手。

1987年秋,在DEC的西部研究所的假期中,我有了设计一种嵌入式命令语言的想法。这个想法就是花费一些精力去创建一种优秀的解释型语言,然后进一步把它做成一个能够在许多不同的应用程序中重复利用的库包(library package),语言的解释器要提供一系列相对通用的工具,如变量,控制结构,过程。每个使用这个语言的程序都可以以扩展的形式把自己的功能加到语言中,达到用语言来控制程序的目的。Tcl的全称“工具命令语言”正说明了这一点。

Tk的诞生

那时,我对图形用户界面也很感兴趣。80年代图形用户界面逐渐流行的时候,我注意到交互式软件的复杂性正在迅速的增长,最引人注目的发展趋势看起来好像是对巨额投资的庞大项目的需求在不断增加。作为一名教授,实验条件不是很充足,这使我开始担心像我们这样类似小团队的研究所是不是就不可能再做出交互式的系统了。

我得出的结论是,我们的唯一希望就是减少创建大型系统时除可重复利用的部件以外的部分对资源的需求,如果一个系统最复杂的地方是用部件来完成的,而且可以把部件从一个系统移植到另一个系统,也许我们就可以以小团队的规模来创建强大的系统,这要求花费一些精力来开发这些部件。不过如果有几个小组协同工作的话,完成它只是一个时间的问题。

我也在考虑这样一个问题,要想使基于组建的设计能够实现,需要有一个强大的有弹性的控件整合工具。这些想法在我一开始考虑做Tcl的时候就产生了,我觉得像Tcl这样的嵌入式命令语言作为脚本语言对整合部件会很有用。我决定创建一些作为Tcl扩展的GUI部件并且使用Tcl来把这些部件整合到图形用户界面中,借此实践一下这个理论。这个扩展就成为了Tk。

我1988年末开始做Tk。这只是一个兼职的项目,所以用了两年的时间才使Tk具有了一些实用的功能。

早期的集成套件

我已经记不清是什么时候开始让人们使用Tcl了,我记得我在伯克利“产学研大会”上作了一个关于Tcl的演讲,来自工业界的几百名与会者听了我们系的这个研究成果。我还被一些对此感兴趣的公司邀请去做演讲。在这几次演讲期间,一些人向我要Tcl的拷贝,于是在1989年,这一部分人最早接触到了Tcl。

1990年1月,我在USENIX会议上宣读了一篇关于Tcl的论文,听众有几百人。他们对Tcl的兴趣越来越浓厚,许多人开始向我要Tcl。大约在那个时候,我决定在伯克利的FTP站点上免费发放Tcl的源代码。从此,通过因特网,Tcl的用户开始与日俱增。

我在USENIX会议中作演讲的听众中有一个人叫Don Libes。他来自国家标准和技术研究院。Don一直想编写一个程序,这个程序能够自动的操作交互的UNIX应用程序,但这需要一种优秀的命令语言,而Don没有时间自己编写这样的一个语言。于是这个项目只能搁置在一边。听了我的演讲后,Don立即跑回家,下载了Tcl,编写了一个叫做Expect的非常优秀的程序,并且赶在下一次USENIX会议提交论文的最后期限之前完成了一篇关于它的论文。整个过程只用了三个星期。Don在1990年夏发表了他的论文之后,把Expect免费的提供给人们使用,于是在系统管理员界立即引起了一场不小的震动。Expect不仅是第一个广泛发布的Tcl应用程序,而且多年来一直也是最流行的。许多早期的Tcl用户都是通过使用Expect来了解到Tcl的。

在1990年末,Tk的基本功能变得很实用了,1991年1月,我同时在USENIX会议和X应用程序开发者大会上发表了一篇关于Tk的论文,这两次会议期间的几个星期中,我在因特网上发布了第一个Tk版本。Tk的第一版缺少许多重要的构件(比如,没有多行文本框构件),不过到了1992年夏天的时候,我就把文本框构件和一个强大的画布构件加了进去。这时Tk的用户开始迅速增长。

开始变得受欢迎

Tcl/Tk的用户从1990年初开始每年都在迅速增加。从1989年的几个使用者到1993年的成百上千的使用者,导致这种迅速增长的原因主要有两个方面:

Tcl/Tk使用者增加的第一个原因是:它提供了最简单的在UNIX(那时Tcl只能在UNIX系统下运行)下开发图形用户界面的方法。Tk的GUI工具既简单又强大;所编写出的出色的应用程序可以完全由Tcl编写而不用写一句C代码,换成基于C的工具来开发比如Motif工具则要变得复杂得多,而且功能也比较少。人们很快就发现用Tcl来开发GUI比起用Motif来可以少投入5到10倍的精力,这也证明了在工业界有许多人和我一样很关心开发交互式应用程序的高开支问题。

Tcl流行的第二个原因是它的可嵌入特性。在我开始做Tcl的时候,我猜想除了我没有人会想要可嵌入式的命令语言,但是我错了。包括Don Libes在内的许多人都想要编写脚本式的可扩展的应用程序,但却没有时间自己来创建这么一种语言。正因为可嵌入式的特性,Tcl为他们提供了一个完美的解决方案。结果Tcl一推出就被广泛的用于股票交易,科学观测,生产自动化等许多领域。

在Tcl用户之间每一两年都会有对Tcl和Tk的使用的讨论,许多Tcl的使用者认为Tk是他们使用Tcl的唯一原因,他们中的一些甚至不喜欢Tcl语言,而希望Tk能够和其他的脚本语言结合。(实际上,已经有人这么做了,如Tkperl和Python),而另一部分人认为Tcl正是他们的需求所在,他们把它作为可嵌入式的脚本语言来使用,而常常不去使用Tk。由于Tcl能够满足他们的特殊目的,所以他们很喜欢Tcl,从某种意义上说,这两部分人都没错。

Tcl社区

90年代初随着Tcl使用者的增加,人们在网上建立了一个关于这门语言的社区,目的是让大家发布功能强大的扩展和为新的用户提供帮助。Mark Diekhans和Karl Lehenbauer是早期开创者中的两位,。他们编写了Tclx,最早可用的Tcl扩展之一,Tclx提供了对文件的访问,时间和日期控制,以及许多其它有用的工具。随着时间的推移,Tclx的功能被证明是非常重要的,以至于其中的许多功能已经被整合到了Tcl的基本功能中。

许多早期的Tcl开创者编写了大量高质量的扩展并使这些扩展能够免费的使用,比如Michael Mclennan编写的[incr Tcl],为Tcl提供了面向对象的功能。George Howlett编写的BLT为Tk加进了许多重要的构件。Tom Poindexter编写的Sybtcl和Oratcl,提供了对主流数据库的访问。Brian Smith和Lary Rowe编写的TclDP提供了Socket接口及一些有用的编程功能。还有许多贡献者我已经记不太清了,在此我只能表示歉意。

社区中其他的论坛在这个时期也在发展。我起初为社区中的用户建立了一个邮件列表,但是短短几年里,这个列表变得相当冗长,以至于后来无法控制。在社区的帮助下,我们建立了comp.lang.tcl新闻组来代替原来的邮件列表,从此,它就成为了交换信息和讨论Tcl新功能的绝佳地点。

1993年Larry Rowe意识到社区中的人们需要面对面的交流。于是他在伯克利组织了第一届Tcl专题讨论会,大约有60人参加。由于这次大会开得非常成功,所以我们决定把它定为一年一届。1995年,USENIX协会开始对这个讨论会进行资助。从此,它就变为了一个有几百人参加的,发表大量论文、教程,开展众多活动的全方位的大会。Tcl大会也是世界上最大的参加者上身着装统一的大会之一,每年要分发5-10种不同设计图案的短袖衫来区别不同的扩展和活动。

在这段时间里,我大约每6到10个月发布一次Tcl/Tk的新版本。大部分新的功能都是采用的Tcl社区的建议。有时,使用者甚至直接给我提供新功能的源代码,然后,我再把它加到软件的核心代码中。通常,每次新版本发布前都有一个讨论的过程,我提供一份新功能的候选列表,然后在社区中由大家投票选出他们认为最重要的功能。我尽可能优先考虑得票最多的功能。

在sun公司的日子

1994年我决定离开伯克利投身工业界。过去我就一只想在工业界做兼职工作,在学术界干了14年,现在我看时机已经成熟了。在考虑了多方面的因素后,我决定接受sun公司的邀请。Eric Schmidt,sun公司的首席技术官和Bert Sutherland,sun实验室的领导者,给我提供了一个机会,在sun实验室创立一个小组,将Tcl开发成因特网上通用的脚本语言。

我一直觉得Tcl最终需要进行商业赢利才能在长远的开发中生存下来,sun公司为Tcl向更高性能的发展提供了很好的机会。我在1994年5月加入sun公司,然后组建了一个Tcl开发小组,在以后的3年里,sun的Tcl小组成员发展到了12人,以前基本上Tcl/tk的每一行代码都有我亲自来编写,但是从那以后,编写Tcl代码的工作就由Tcl小组来完成了。

Sun公司提供的许多帮助使我们对Tcl/Tk的性能进行了大规模的提升。Scott Stanton 和Ray Johnson将Tcl/Tk输出到了Windows和Macintosh上,于是Tcl就成了一个跨平台的开发环境;当前,超过2/3下载Tcl的用户是用于Windows开发。Jacob Levy和Scott Stanton仔细测试了输入输出系统并加进了对Socket的支持,于是Tcl可以被广泛的用于网络应用程序。Brian Lewis为Tcl脚本创建了一个二进制编译器,这使它的运行速度提升了10倍。Jacob Levy开发了Safe-Tcl,一个强大的安全模块,使非信任的脚本能更安全的执行。Jacob Levy和Laurent Demailly创建了一个Tcl插件,使Tcl脚本可以在web浏览器中运行,我们创建了Jacl和TclBlend,使Tcl和Java可以紧密的工作在一起。我们还进行了一些小的改进,如动态加载,名称空间,时间和日期支持,二进制I/O,额外的文件操作命令,和一个改进的字体机制。

当我加盟sun后,许多人担心Tcl是否会变成一个收费的语言,当时在comp.lang.tcl上出现了许多热烈的讨论。幸运的是,sun公司同意在源代码论坛中继续发放Tcl/Tk的核心代码库,并且sun一直信守诺言。随着性能不断提升的新版本的出现,起初那些担心逐渐被开发新功能的热情所代替。

在那些年,Tcl的使用者继续不断增长。到1997年已经有了成千上万的Tcl开发者;sun公司的FTP站点的Tcl下载率由1995年底的2000人每星期上升到1998年初的超过10000人每星期。很明显,Tcl对我是一个巨大的商业机会。开放源代码的发展满足不了Tcl社区中的许多要求。比如,几乎没有开发Tcl的工具,像培训和技术支持这样的专业服务也仅仅是零散进行的。许多重要的扩展,如对ActiveX和CORBA的支持还没有编写出来。

Ajuba

1997年底,为了更好的发展Tcl,我决定离开sun创建一个完全关注于Tcl的公司。1998年1月,我和Sarah Daniels创建了Scriptics(即今天的 Tcl),由Sarah负责市场营销。一个月内,sun公司Tcl小组的约一半成员都跳槽加入了我们公司,我们开始开发TclPro,一套包括我们最初产品的开发工具。TclPro 1.0 于1998年9月完成。它在市场上的成功为Ajuba的出现提供了强有力的支持。1999年底我们准备再雇佣50名员工。

Tcl公司已经代替了sun公司在开发Tcl核心工具上的角色,并且它将继续像sun公司那样免费的发布它。1998年,Tcl公司为Tcl8.0做了一些改进,修复了一些错误并加进了一些小的功能,比如可以更好的支持[Incr Tcl]扩展。1999年4月,Tcl发布了第一个最重要的开放源码的版本,Tcl/Tk8.1,这个版本加进了对Unicode的支持(国际化),线性安全机制(用于多线程服务程序)和一个由Henry Spencer开发的全新的规则表达式包,还有一些类似对Unicode提供支持这样优秀的新功能。Tcl成为第一个满足企业中关键程序各方面要求的脚本语言。

今后,Tcl将会继续开发开放源代码的和进行商业赢利的两种产品,在这种理念下,我们会不断的提升开放源代码的核心部件的性能,改进产品的不足,为免费的和商品化的两种开发工具提供资金支持,我们要做就要做到最好。Tcl社区将一如既往的在Tcl的发展中扮演重要的角色。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

5#
发表于 2007-8-23 14:13:26 | 只看该作者
是好长  现在有些困 下次慢慢看
回复 支持 反对

使用道具 举报

该用户从未签到

4#
 楼主| 发表于 2007-7-9 23:41:44 | 只看该作者
为跟踪我们当前处于哪一个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=%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++代码,它能自动产生文档,类图,编程参考,当然还有可编译的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

    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的话,一个好的程序员的最重要的潜质就是懒惰.也就是说,有创造性的懒惰.本文提到了两个建议,他们能够归于一件事:懒惰.
    当你需要一个解析器时,使用一个现成的解析器,修改你的文件格式去造就分析器的要求(当然,需要你已经达到了能够自由选择文件格式的境界)
    当你需要使用堆栈时,你可以使用现成的函数调用堆栈,忘掉压入,弹出和其他的操作.
    "重用"并不仅表示封装和信息的隐藏.有些时候它只不过表示懒惰罢了.
    (完)

    有点长  希望大家多点耐心看完  一定有所收获
回复 支持 反对

使用道具 举报

该用户从未签到

3#
 楼主| 发表于 2007-7-9 23:38:28 | 只看该作者
(go onsdlkfj5 )

▲更多复杂的数据
至今为止,我们已经对一个非常简单的包含矩形与文本的例子进行了研究.这种数据格式用主动文件设计样本非常容易读取并加以分析.
现在我们来看一个更为复杂的数据格式,来解释一下使用主动文件的更加"高级"的技巧.这将使你在使用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 "roxy" {
# This pattern fits in two categories:
Categories {"Access control" "Structural decomposition:bject"}
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:bject"}
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.
}
}

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

#我们把数据保存在以下三个列表内:
set l_patterns

    set l_sources

      set l_categories


        #我们还需要一个变量跟踪我们当前所在的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
        }
        }
回复 支持 反对

使用道具 举报

该用户从未签到

2#
 楼主| 发表于 2007-7-9 23:36:47 | 只看该作者
TCL脚本数据文件格式 -by Koen Van Damme
简介
一个典型的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)(这句话是作者在调侃自己,但我不知如何把它译出来,请哪位大侠帮忙指点一下,译者注)
--------------------------------------------------------------------------------
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-11 07:37 , Processed in 0.075915 second(s), 30 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表