Data file formats for TCL scripts

Introduction
A typical TCL script stores its internal data in lists and arrays (the two primary data structures in TCL). Suppose you want to write a TCL application that can save its data on disk and read it back again. For example, this allows your users to save a project and load it back later. You need a way to write the data from the place where it is stored internally (lists and arrays) to a file. You also need a way to read the data back into a running script.

You can choose to store the data in a binary form, or in a text file. This paper is limited to textual data file formats. We will look at a number of possible formats and how to parse them in TCL. In particular, we will show some simple techniques that make text file parsing a lot easier.

This paper assumes that you are familiar with TCL, and that you have written at least a few simple scripts in TCL.

A simple example
Suppose you have a simple drawing tool that places text and rectangle items on a canvas. To save the resulting pictures, you want a textual file format that must be easy to read. The first and simplest file format that comes to mind, looks something like this:

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.

Saving your data in a text file makes it easier to debug the application, because you can inspect the output to see if everything is correct. It also allows users to manually tinker with the saved data (which may be good or bad depending on your purposes).

When reading a data file in this format, you somehow need to parse the file and create data structures from it. To parse the file, you may be tempted to step through the file line by line, and use something like regexp to analyse the different pieces of the text. This is one possible implementation:

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

We read one line at a time, and use regular expressions to find out what kind of data the line represents. By looking at the first word, we can distinguish between data for rectangles and data for text. The first word therefor serves as a keyword: it tells us exactly what kind of data we are dealing with. We also parse the coordinates, color and other attributes of each item. Grouping parts of the regular expression between parentheses allows us to retrieve the parsed results in the variables 'x1', 'x2', etc.
This looks like a simple enough implementation, assuming that you understand how regular expressions work. But I find it pretty hard to maintain. The regular expressions also make it hard to understand.

There is a more elegant solution, known as an 'active file'. It is captured in a design pattern, originally written by Nat Pryce. It is based on a very simple suggestion: Instead of writing your own parser in TCL (using regexp or other means), why not let the TCL parser do all the work for you?

The Active File design pattern
To explain this design pattern, we continue the example of the simple drawing tool from the previous section. First, we write two procedures in TCL, one that draws a rectangle, the other writes text.

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
}

To make a picture on the canvas, we can now call these two procs several times, once for each item we want to draw. To make the same picture as above, we need the following three calls:
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

Does this look familiar? The code for calling our two procs looks almost exactly like the data file we parsed earlier. The only difference is that the keywords have changed from 'rectangle' and 'text' to 'd_rect' and 'd_text'.

Now we come to the insight that makes this design pattern tick: to parse the data file, we treat it like a TCL script. We just put the calls to our two procedures in a file, and we use that as the data file. The fact that the data file actually contains calls to TCL procedures, is the heart of this design pattern.

Parsing the data file is now extremely easy:

source "datafile.dat"

The built-in TCL command source reads the file, parses it, and executes the commands in the file. Since we have implemented the procedures d_rect and d_text, the source command will automatically invoke the two procedures with the correct parameters. We will call d_rect and d_text the parsing procedures.
We do not need to do any further parsing. No regular expressions, no line-by-line loop, no opening and closing of files. Just one call to source does the trick.

The data file has become a TCL script that can be executed. This is called an Active File because it contains executable commands, not just passive data. The Active File design pattern works in most scripting languages, and is excellently described by >> Nat Pryce on his website.

Advantages of using the Active File pattern:

No more need to write a parser. source invokes the TCL parser which does the job.
Easy to read data file format.
Disadvantages of using the Active File pattern:

If the data file contains dangerous commands a la exec rm *, they get executed and can cause serious damage. You can solve this by executing the active file in a 'safe interpreter' that blocks the dangerous commands. See 'safe interpreter' in your TCL manual.
Limitations of the Active File pattern:

This pattern does not work for all possible data formats. The format must be line-based, and every line must begin with a keyword. You write a TCL procedure with the same name as the keyword, turning the passive keyword into an active command. This also implies that you cannot use keywords such as if or while, because TCL does not allow you to write procedures with those names. In fact, the reason why I changed the keyword text into the command d_text in our example, is because Tk already has a reserved word text for creating text widgets.
The English proc
So far we have been able to come up with a very simple file format:

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

And we have a very simple parser for it, using only two parsing procedures and the source command. Now, let's see how we can improve things.

When you look at large volumes of this kind of data, it is easy to get confused by all the numbers. The first line contains the numbers 10 10 110 50 3, and it takes some training to quickly see the first two as a pair of coordinates, the next two as another pair, and the last one as the line thickness. We can make this easier to read for a programmer by introducing some additional text in the data:

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

Prepositions like to and from, and argument names like thick and color make the data look more like an English sentence. To accomodate these new prepositions, our parsing procedure needs to get some additional arguments:
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
}

As you can see, the implementation does not change. The new arguments are not used in the procedure's body; their only purpose is to make the data more readable. I make it a habit to make the names of the proc's arguments the same as the corresponding preposition in the data file (e.g. from appears at the same place in the data file and in the argument list). That way, I can quickly see how one maps to the other.
Introducing dummy arguments for readability is called the 'English proc' idiom. We will see other ways of making data more readable.

Option/value pairs
The Tk toolkit offers a set of widgets to create graphical interfaces. These widgets are configured with options and their values. The syntax for the configuration is simple (a dash, followed by the option name, followed by the value) and standardized (many other Tcl extensions use the same syntax for configuring their components).

With option/value pairs, our data file looks like this:

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

To parse this data, we need to introduce the parsing of option/value pairs in the parsing procedures d_rect and d_text. Our first attempt is to use dummy arguments similar to the English proc idiom:
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
}

Again, the implementation does not change. It is clear though, that this solution will only work for the simplest of data formats. It has two major disadvantages:
The position of the options in the argument list is fixed. For example, you cannot specify the color before the thickness. This is not so bad for a pure data file format (because the values are typically always saved in the same order anyway), but it becomes a hindrance when you also want to use the format for manually inputting data into the script.
The options do not have default values: you must supply all the options, you cannot leave any of them out.
Here is an implementation that solves both these problems:

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

Instead of a long list of arguments, the parsing procedure now only has one argument called args, which captures all the actual parameters of the call. The arguments x1, y1 etc have disappeared. They are now handled by a local array, as we will shortly explain.
The first part of the code sets the default values for some options. The second part parses option/value pairs from args. This is done very elegantly with the TCL built-in array set mechanism. It creates new entries in the array a, using the option names (including the leading dash) as keys into the array, and the option values as the array values.

If the user does not specify -color in the call, the default value of the a(-color) entry still stands. The final line in the proc body is the same as in the previous implementations, except that it now uses array entries rather than procedure arguments.

If the user forgets to specify option -x1 in the call, the array entry for -x1 is not set (there is no default for it) and the call to create rectangle results in an error. This example shows that you can give default values to some options, making them optional, while leaving others mandatory by not specifying defaults for them.

The best format is usually a combination
Now that we have seen some commonly known tricks for TCL data files (Active File, English proc, option/value pairs), we can combine the advantages of each into a single data format. For the mandatory options, we should use fixed-position arguments, perhaps combined with dummy prepositions for readability (as in the English proc). All the optional options on the other hand, should be handled with the option/value pair mechanism, so that users can leave the options out or change their positions in the call. The final format could then look something like this:

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

assuming that 'blue' is the default color for all items.
As a personal convention, I usually write such commands as follows:

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 :-).

--------------------------------------------------------------------------------


More complicated data
So far, we have worked on a very simple example involving only rectangles and text on a canvas. The data format was easy to read and easy to parse using the Active File design pattern.

We will now move to a more complex data format, to explain more 'advanced' techniques for using the Active File pattern in TCL. This will make you an expert in TCL data file formats.

The repository tool
I used to collect design patterns. I made a repository of patterns, each with a brief description and some properties. I also kept the names, authors and ISBN numbers of the books in which I found the patterns, as a reference to be able to look them up later. To keep track of all this information, I implemented a repository tool in TCL. It had features to organize patterns into categories and levels, and to point from each pattern to the book and page number where it was described.

The input to the tool was a file that looked like this:

# First, I describe some books in which you can find good design patterns
# and programming idioms. Each book, website or other source of patterns
# is specified with the 'Source' keyword, followed by a unique tag and some
# additional information.

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
}

# Next, I describe some categories. I want to group patterns
# in categories so I can find them back more easily. Each category
# has a name (such as "Access control") and a short description.

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

# Finally, I describe the patterns themselves. Each of them has a name,
# belongs to one or more categories, and occurs in one or more of the
# pattern sources listed above. Each pattern has a level, which can
# be 'arch' (for architectural patterns), 'design' for smaller-scale
# design patterns, or 'idiom' for language-specific patterns.

Pattern "Broker" {
Categories {"Distributed systems"}
Level arch
Sources {SYST:99} ; # This means that this pattern is described in
# the book with tag 'SYST' on page 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.
}
}

This is only a part of the input file I originally worked with, but it already contains enough data to serve as a good example. The descriptions of the patterns are short and pretty stupid, but that's ok for this example.

As you can see, this data file has a number of interesting new features:

The data is contained in some kind of structures, grouped between curly braces {}. Each structure starts with a keyword.
These structures can be nested: for example, the Pattern structure can contain an Info structure.
The elements inside the structures can take many forms. Some of them are identifiers or strings (such as the Level element), others seem like special codes (such as SYST:353), and some of them are even freeform text (as in the Category or Info structures).
The order of the elements in each structure is free. Look at the final two patterns to see that the order of the Level and Sources elements can be swapped. The elements can indeed be placed in any order you want.
The data file contains TCL comments, not only between the structures but even inside the structures. Comments allow you to make the data more understandable.
You may think that this format is a lot more complicated than the one in our previous example, and that it is near to impossible to write a parser for this in TCL. It may not seem straightforward, we can use the Active File pattern again, making the task a lot simpler. The parsing procedures are a bit more elaborate than before, but they are definitely not "complicated".

Here's the part of my tool that parses a data file such as the one above:

# We will internally store the data in these three lists:
set l_patterns [list]
set l_sources [list]
set l_categories [list]

# We also need a variable to keep track of the Pattern structure we are
# currently in:
set curPattern ""

# This is the parsing proc for the 'Source' keyword.
# As you can see, the keyword is followed by an id (the unique tag for the
# source), and some textual description of the 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
}

At first sight, this may seem to take much more work than what we did for the simple canvas example. But think of the power of this technique. With only a few parsing procedures and by making clever use of the uplevel command, we can parse data files with intricate structure, containing comments, nested sub-structures and freeform textual data. Imagine writing a parser for this from scratch.

The data is parsed by the procedures such as Source, Pattern or Info. The parsed data is stored internally in three lists and three arrays. The nestedness of the data is handled by calls to uplevel, and by remembering in which 'scope' we currently are using the variable curPattern.

Note that this technique requires that your data follows TCL syntax. This implies, among other things, that opening curly braces should be placed at the end of a line, not on the beginning of the next line.

Recursive structures
In the pattern repository example, the structures of type Pattern contain sub-structures of other types such as Info and Sources. What happens when a structure contains sub-structures of the same type? In other words, how do we handle recursive structures?

Suppose, for example, that you want to describe the design of an object-oriented system, which is divided recursively into subsystems:

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

To keep track of which System structure we are currently in, it seems that we need more than just a single global variable like currPattern. At any point during parsing, we can be inside many nested System structures, so we need more than one variable. We probably need some kind of stack, on which we push a value when we enter the System parsing proc, and from which we pop again at the end of the parsing proc. We can make such a stack using a TCL list.
But if you do not want to maintain a stack, there is a simple way of avoiding that. It is again based on a very simple suggestion: When you need a stack, see if you can use the function call stack. When dealing with such recursive data, I usually implement my parsing procedures like this:

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"

Instead of storing the nested system names on a stack (simulated by a TCL list or array I suppose), we just store the names in local variables called tmpSystem. Since the parsnig procedures are automatically called in a stack-based order by TCL, we do not need to explicitly push/pop anything.

Other examples
The CGI library by Don Libes uses the Active File pattern to represent HTML documents. The idea is that you write a TCL script that acts as an HTML document and generates pure HTML for you. The documents contain nested structures for bulleted lists, preformatted text and other HTML elements. The parsing procedures call uplevel to handle recursive sub-structures.

Here is a part of Don's code, showing you how he uses some of the tricks described in this paper:

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

I am not going to explain the fine details of this great library, but you can find out for yourself by downloading it from >> Don's homepage.

--------------------------------------------------------------------------------


As another example, my TODL tool parses object-oriented schemas using parsing procedures such as class and method. Here is an example of an input file to the tool:

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

By looking at this data file, can you find out what the complete list of all parsing procedures is?

--------------------------------------------------------------------------------


I once wrote a (very!) simple parser for C++ class implementations. Lazy as I am, I wrote the parser in TCL. It actually turned out to be too complicated to be of any use, but it shows how far you can go with the Active File pattern. Just look at this data file containing twisted 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

This example may be far-fetched, but it gives you an idea of the power of the Active File pattern. What you see is TCL code, but it looks a lot like C++ code, and it can automatically generate documentation, class diagrams, programming references and of course compilable C++ code.

The parsing procedures such as method and class store the C++ implementation in internal TCL data structures. Finally, the call to cpp_report generates the resulting C++ code.

The following fragment from the parser gives you an idea of how you can bend the TCL parser to read a file with C++-like syntax:

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

--------------------------------------------------------------------------------


A word about being lazy
According to Perl's Larry Wall, one of the most important talents of a good programmer is lazyness. Creative lazyness, that is. This paper makes two suggestions that both come down to the same thing: be lazy.

When you need a parser, use an existing parser and adapt your file format to please that parser (assuming that you have some degree of freedom in choosing the file format, of course).
When you need a stack, use the existing function call stack and forget about pushing, popping and other bookkeeping.
"Reuse" is not all about encapsulation and information hiding. Sometimes it's just about being lazy.

--------------------------------------------------------------------------------

转载于:https://www.cnblogs.com/jiemupig/archive/2007/10/26/938084.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值