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