%==============================================================================% % Start of Ch2.tex % %==============================================================================% % % Copyright % --------- % Copyright (C) 1992 Ross N. Williams. % This file contains a chapter of the FunnelWeb User's Manual. % See the main TeX file for this manual for further information. % %==============================================================================% \chapter{FunnelWeb Hints} \label{chapadvanced}\xx{FunnelWeb}{hints} Whereas Chapter~\ref{chapintroduction} provides an introduction to FunnelWeb and Chapter~\ref{chapdefinition} a definition, \i{this} chapter contains hints about how FunnelWeb can be used. This chapter probably should not be read until the reader has already commenced using FunnelWeb, or at the very least, tried out some of the examples in Chapter~\ref{chapintroduction}. Those who find themselves using FunnelWeb frequently should read this chapter at some stage so as to ensure that they are getting the most out of it. Most of the examples in this chapter have been placed in the FunnelWeb regression test suite which should be available in a directory called \p{/fwdir/tests/}. The files to examine are \p{hi01.fw} through \p{hi10.fw}. \section{Macro Names} \xx{macro}{names}\xx{macro}{identifiers} When using FunnelWeb, the choice of macro names can be as important to the readability of a program as the choice of program identifiers, and it is important that the user know the range of options available. \narrowthing{Names are case sensitive and exact matching:}{Macro names are case sensitive and are matched exactly. The strings used as a macro name at the point of definition and call must be \i{identical} for the connection to be made.} \narrowthing{Names can contain any printable character:}{FunnelWeb is less restrictive about its macro names than most programming languages are about their identifiers. A FunnelWeb macro name can contain any sequence of printable characters, including blanks and punctuation. Names can start and end with any character. Names cannot cross line boundaries. The following are all legal macro names:} \begin{verbatim} @ @<@> @<453 #$ %&# --===~~1">>>@> @<<@> @<<>@> @ @< ! @> @ @<"Who's been hacking MY program" said Father Bear.@> @ \end{verbatim} \narrowthing{Names must be no more than a maximum limit in length:}{Names can be no longer than a predefined maximum length. Currently this length cannot be modified.} Typically, macro names will consist of a short English phrase or sentence that describes the contents of the macro. \section{Quick Names} \xx{quick}{names} Sometimes a particular macro must be used extremely often. When this happens it is desirable to make the macro's name as short as possible. The shortest ordinary FunnelWeb macro name is the empty name\xx{empty}{name} \dqp{@<@>}, which is four characters long. Single-character names are five characters long. To cater for the cases where really short names are needed, FunnelWeb provides a \newterm{quick name} syntax that allows one-character macro names to be specified in two less characters. Quick names take the form of the special character, followed by a hash (\p{\#}) followed by a single character. Examples: \begin{verbatim} @#A @#| @#& @#m \end{verbatim} This form of macro name has the same syntactic functionality as an ordinary name and can be substituted wherever an ordinary name can be. In fact quick names live in the same namespace as ordinary macro names. For example the quickname \p{@\#A} is the \i{same name} (refers to the same macro) as the ordinary name \p{@}. Because quick names look syntactically \dq{open} (\ie{}they do not have a closing\p{@>} as ordinary names do), it is best to avoid them except where a macro must be called very often. \section{FunnelWeb the Martinet} \xx{FunnelWeb}{rules}\xx{FunnelWeb}{martinet} There are many ways in which a macro preprocessor can cause unexpected difficulties. FunnelWeb seeks to avoid many of these problems by performing a number of checks. This section describes some of the checks that FunnelWeb performs. \narrowthing{Trailing blanks in the input file:}{Trailing blanks\xx{trailing}{blanks} are usually not dangerous, but FunnelWeb disallows them anyway. All trailing blanks in the \i{input} (\p{.fw} file) are flagged as errors by FunnelWeb. FunnelWeb does not flag trailing blanks in any of its output files.} \narrowthing{Input line length:}{FunnelWeb has a maximum input line length.\xx{input line}{length} If FunnelWeb reads an input line longer than this length, it flags the line with an error message. The maximum length can be changed using a pragma (see Chapter~\ref{chapdefinition}).} \narrowthing{Product file line length:}{FunnelWeb watches the length of output lines\xx{output line}{length} and all output lines longer than the limit are flagged with error messages. The maximum length can be changed using a pragma (see Chapter~\ref{chapdefinition}). That FunnelWeb polices output lines is very important. Some programs can behave very strangely if they get an input line that is too long (\eg{}Fortran compilers\xx{Fortran}{compilers} can simply ignore text past a certain column!) and once FunnelWeb starts expanding macros using indentation, it is sometimes not obvious how wide the product file will be.} \narrowthing{Control characters:}{The presence of control characters\xx{control}{characters} in a text file can result in some confusing behaviour downstream when the file is presented to various programs. Unfortunately, some text editors\xx{text}{editors} allow control characters to be inserted into the text rather too easily, and it is all too easy to be tripped up. FunnelWeb prevents these problems by flagging with diagnostics all non-end-of-line control characters detected in the input (\p{.fw}) file (even TABs\x{tabs}). The result is that the user is guaranteed that product files generated from FunnelWeb contain no unintentional control characters. This said, FunnelWeb does allow the insertion of control characters in the output file by explicitly specifying them in the text using a \p{@\circumflex{}} control sequence.} \narrowthing{Number of invocations:}{FunnelWeb checks the number of times that\xx{invocations}{number} each macro is called and issues an error if the total is not one. The \p{@Z} (for zero) and \p{@M} (for many) macro attributes can be used to bypass these checks.} \narrowthing{Recursion:}{Because\xx{recursion}{macro} FunnelWeb does not provide any conditional constructs, all recursively defined macros must, by definition, expand infinitely,\footnote{A special case exists where there is recursion but no content. In this case, the expansion is finite (the empty string) even though the operation of expanding is infinite. FunnelWeb does not treat this case specially.} and are therefore unacceptable. FunnelWeb performs \i{static} checks to detect recursion, detecting it before macro expansion commences. The user need not fear that FunnelWeb will lock up or spew forth if a recursive macro is accidentally specified.} \section{Fiddling With End of Lines} \xx{EOL}{fiddling with}\xx{end-of-line}{fiddling with}\x{spacing} One of the fiddly aspects of programming with FunnelWeb is coping with end of lines. If you want your product file to be well indented without multiple blank lines or code run-ons, you have to spend a little time working out how the end of line markers get moved around. The rule to remember is that, disregarding the effects of special sequences within a macro body, \i{the body of a macro consists of exactly the text between the opening \p{@\{} and the closing \p{@\}}}. This text includes end of line markers. If for example you call a macro in a sequence of code$\ldots$ \begin{verbatim} while the_walrus_is_sleepy do begin writeln('zzzzzzz'); @ writeln('Umpharumpha...'); end; \end{verbatim} where \p{} is defined as follows \begin{verbatim} @$@==@{ wake_up_the_walrus(the_walrus); @} \end{verbatim} then when \p{} is expanded you will get \begin{verbatim} while the_walrus_is_sleepy do begin writeln("zzzzzzz"); wake_up_the_walrus(the_walrus); writeln("Umpharumpha..."); end; \end{verbatim} The blank lines were introduced by the end on line markers included in the definition of \p{}. A good solution to this problem is to suppress the end of line markers by defining the macro as follows \begin{verbatim} @$@==@{@- wake_up_the_walrus(the_walrus);@} \end{verbatim} This is the usual form of macro definitions in FunnelWeb files. In additive macros, this format does not work properly because the end of line that is suppressed by the trailing \p{@\}} does not get replaced by the end of line at the end of the macro invocation. For example the definition \begin{verbatim} @$@+=@{@- wake_up_the_walrus_once(the_walrus);@} \end{verbatim} later followed by \begin{verbatim} @$@+=@{@- wake_up_the_walrus_again(the_walrus);@} \end{verbatim} is equivalent to the single definition \begin{verbatim} @$@==@{@- wake_up_the_walrus_once(the_walrus);wake_up_the_walrus_again(the_walrus);@} \end{verbatim} Putting the trailing \p{@\}} on a new line at the end of the macro (except for the last definition part) solves the problem. \begin{verbatim} @$@+=@{@- wake_up_the_walrus_once(the_walrus); @} \end{verbatim} later followed by \begin{verbatim} @$@+=@{@- wake_up_the_walrus_again(the_walrus);@} \end{verbatim} is equivalent to the single definition \begin{verbatim} @$@==@{@- wake_up_the_walrus_once(the_walrus); wake_up_the_walrus_again(the_walrus);@} \end{verbatim} Managing end of line markers is tricky, but once you establish a convention for coping with them, the problem disappears into the background. \section{Fudging Conditionals} \xx{fudging}{conditionals} As a macro preprocessor, the facility that FunnelWeb most obviously lacks is a conditional facility (such as C's \p{\#ifdef}). It might, therefore, come as a surprise to know that the first version of FunnelWeb actually had a built in conditional facility. The facility allowed the programmer to specify a construct that would select from one of a number of macro expressions depending on the value of a controlling macro expression. In three years the construct was never used. The reason was that conditional constructs could be fudged nearly as easily as they could be used. Because of this, the inbuilt conditional feature was removed in the current version of FunnelWeb. Not only did this simplify the program, but is also allowed recursive macros to be detected through static analysis rather than during macro expansion. There are two basic ways to fudge a conditional. First, the comment facility of the target programming language may be employed. For example, in Ada,\x{Ada} comments commence with \dqp{--} and terminate at the end of the line. Using this fact, it is easy to construct macros that can be called at the start of each target line and which turn on and off the lines so marked by defining the macro to be the empty string (ON) or the comment symbol (\p{--}) (OFF). For example: \begin{verbatim} @A@ The following macro determines whether debug code will be included in the program. All lines of debug code commence with a call to this macro and so we can turn all that code on or off here by defining this macro to be either empty or the single-line comment symbol (\p{--}). Note the use of a quick macro name. @$@#D@M==@{@} @! Turns the debug code ON. @! Use this definition to turn the debug code OFF: @$@#D==@{--@} ... then later in the file... @$@==@{@- while sloth=walrus!!!!!!!"); @#D assert(timer=timermax!!!"); inc(sloth); end loop@} \end{verbatim} The other way to fudge a conditional is to define a macro with a single parameter. A call to the macro is then wrapped around all the conditional code in the program. The macro can then be defined to present or ignore the code of its argument. For example: \begin{verbatim} @A@ The following macro determines whether debug code will be included in the program. All debug code is wrapped by a call to this macro and so we can turn all the debug code on or off here by defining this macro to be either empty or its parameter. @$@#D@(@1@)@M==@{@1@} @! Turns the debug code ON. @! Use this definition to turn the debug code OFF: @$@#D@(@1@)==@{@} ... then later in the file... @$@==@{@- while sloth=walrus!!!!!!!"); assert(timer=timermax!!!");@) inc(sloth); end loop@} \end{verbatim} In languages that allow multi-line comments (\eg{}C with \p{/*} and \p{*/}), comments can be used to eliminate the conditioned code rather than absence. For example: \begin{verbatim} @$@#D@(@1@)@M==@{/* @1 */@} @! Comments out the debug code \end{verbatim} (Note: If this example were ever actually used, the programmer would have to be careful not to place comments in the argument code. Nested comments in~C are non-portable.) The parameterized macro idea can be generalized to support the choice of more than one mutually exclusive alternative. For example: \begin{verbatim} @A This module contains non-portable code that must execute on Hewlett Packard, Sun, and DEC workstations. The following FunnelWeb macro is defined to choose between these three. The first parameter is the HP code, the second is the Sun code, and the third is the DEC code. Whichever parameter constitutes the body of this macro determines which machine the code is being targeted\note{Dictionary says only one t in targeted.} for. @$@@(@3@)@M==@{@1@} @! Configure for HP. ...then later in the file... @@( @"get_command_line(comline)@" @, @! HP. @"scan_command_line(128,comline);@" @, @! Sun. @"dcl_get_command_line(comline,256);@" @) @! DEC. \end{verbatim} Of course, this could also be performed using three separate macros. The main advantage of using a single macro is that the mutual exclusivity is enforced. Also, because FunnelWeb ensures that the number of formal and actual parameters are the same, this method lessens the chance that a machine will be forgotten in some places. \section{Changing the Strength of Headings} \xx{headings}{strength}\xx{typesetting}{strength}% \xx{section}{strength}\xx{font}{size} FunnelWeb provides five heading levels: \p{@A}, \p{@B}, \p{@C}, \p{@D}, and \p{@E} to which it binds five different typographical strengths. These bindings are static; a level \p{@A} heading will always be typeset in a particular font size regardless of the size of the document. The font sizes have been preset to be \dq{reasonable} for a range of document sizes, but may be inappropriate for very small or large documents. FunnelWeb does not currently provide an \dq{official} way (\eg{}a pragma) to change the typesetting strength of headings. This feature might be added in later versions. Meanwhile, a hack is available that will do the job, providing that you do not mind the hack being \TeX{}-specific and probably FunnelWeb-version specific. Inside the set of \TeX{} macro definitions that FunnelWeb writes at the top of every documentation file are five \dq{library} definitions \p{fwliba}$\ldots$\p{fwlibe} which provide five different typesetting strengths for headings. Near the end of the set of definitions, FunnelWeb binds these macros to five other macros \p{fwseca}$\ldots$\p{fwsece} which are invoked directly in the generated \TeX{} code to typeset the headings. \begin{verbatim} \def\fwseca#1#2{\fwliba{#1}{#2}} \def\fwsecb#1#2{\fwlibb{#1}{#2}} \def\fwsecc#1#2{\fwlibc{#1}{#2}} \def\fwsecd#1#2{\fwlibd{#1}{#2}} \def\fwsece#1#2{\fwlibe{#1}{#2}} \end{verbatim} This means that the typesetting strength of the headings in a FunnelWeb document can be changed by redefining these macros at the top of a FunnelWeb document. For example: \begin{verbatim} @p typesetter = tex \def\fwseca#1#2{\fwlibc{#1}{#2}} \end{verbatim} would set \p{@A} headings at the same strength as the default strength of \p{@C} headings. The \p{typesetter} directive is necessary to ensure that the \TeX{} control sequences get through to the documentation file unfiltered. The following will tone down all headings by two levels (with the \p{@D} and \p{@E} levels being allocated the default \p{@E} typesetting strength because there is nothing weaker). \begin{verbatim} @p typesetter = tex \def\fwseca#1#2{\fwlibc{#1}{#2}} \def\fwsecb#1#2{\fwlibd{#1}{#2}} \def\fwsecc#1#2{\fwlibe{#1}{#2}} \def\fwsecd#1#2{\fwlibe{#1}{#2}} \def\fwsece#1#2{\fwlibe{#1}{#2}} \end{verbatim} These definitions affect only the headings that follow them, and so they should be placed at the top of the FunnelWeb input file. \section{Efficiency Notes} \xx{FunnelWeb}{efficiency}\xx{efficiency}{notes} The following notes are worth keeping in mind when using FunnelWeb. \narrowthing{Memory:}{When\x{memory}\xx{input}{files} FunnelWeb processes an input file, it reads the entire input file, and all the included files into memory.\footnote{If a file is included $n$ times, FunnelWeb keeps $n$ copies in memory.} This organization does not pose a constraint on machines with large memories, but could present a problem on the smaller machines such as the PC.} \narrowthing{Speed:}{FunnelWeb\x{speed} is not a slow program. However, it is not particularly fast either. If the speed at which FunnelWeb runs is important to you, then the thing to keep in mind is that FunnelWeb has been optimized to deal efficiently with large slabs of text. FunnelWeb treats input files as a sequence of text slabs and special sequences (\eg{}\p{@+}) and whenever it hits a special sequence, it has to stop and think. Thus, while a ten megabyte text slab would be manipulated as a single token, in a few milliseconds, a similar ten megabyte chunk filled with special sequences would take a lot longer. If FunnelWeb is running slowly, look to see if the input contains a high density of special sequences. This can sometimes happen if FunnelWeb is being used as a backend macro processor and its input is being generated automatically by some other program.} \narrowthing{Macro expansion:}{When\xx{macro}{expansion} tangling (expanding macros), FunnelWeb never expands a macro expression into memory; it always writes it to the product file as it goes. This is a powerful fact, because it means that you can write macros containing an unlimited amount of text, and pass such macros as parameters to other macros without becoming concerned about overflowing some kind of buffer memory. In short, FunnelWeb does not impose any limits on the size of macro bodies or their expansions.} \section{Interactive Mode} \x{interactive mode}\x{keyboard mode} As well as having a command line interface with lots of options, FunnelWeb also provides a command language and a mode (\dq{interactive mode}) in which commands in the language can be typed interactively. The FunnelWeb command interpreter was created primarily to support regression testing,\xx{regression}{testing} but can also be useful to FunnelWeb users. FunnelWeb's command interpreter\xx{command}{interpreter} reads one command per line and can read a stream of commands either from a text file, or from the console. The interpreter can understand over twenty commands. See Chapter~\ref{chapdefinition} for a full list. However, most of them were designed to support regression testing and will not be of use to the casual user. The commands that are of greatest use to the casual user are:\xx{useful}{commands} \begin{verbatim} ! - Comment. Ignores the whole line. EXECUTE fn - Execute the specified file. FW options - Invoke FunnelWeb-proper once. SET options - Sets options. SHOW - Displays currently active options. TRACE ON - Turns command tracing ON. QUIT - Quits FunnelWeb. \end{verbatim} To distinguish here between invocations of the FunnelWeb program and FunnelWeb runs inside the shell, we call the latter \newterm{FunnelWeb proper}. The \dqp{FW} command invokes FunnelWeb proper with the specified options which take the same syntax as they do on the command line. The only restriction is that none of the action options can be turned on except \dqp{+F} which must be turned on. The \dqp{SET} command\xx{set}{command} has the same syntax as the \dqp{FW} command except that it does not allow \i{any} action options to be specified. It's sole effect is to set default option values for the rest of the run. The \dqp{SHOW}\xx{show}{command} command displays the current default options. By default, FunnelWeb does not echo the commands that it processes in a script. The \dqp{TRACE ON}\xx{trace on}{command} command turns on such tracing. These commands can be combined to streamline the use of FunnelWeb. For example, you might wish to create a script called \p{typeset.fws} to process a whole group of files. \begin{verbatim} trace on !This script typesets the whole program. ! Set no listing file, no product files, but specify a documentation file ! and specify the directory into which it should be placed. set -L -O +T/usr/ross/typeset/ fw prog1 fw prog2 fw prog3 fw prog4 \end{verbatim} There are a few ways in which this script can be run. The simplest is simply to specify it in the \dqp{+X} option of a FunnelWeb invocation. FunnelWeb shellscripts default to \dqp{} and \dqp{.fws}. \begin{verbatim} fw +xtypeset \end{verbatim} The second alternative is to enter interactive mode. \begin{verbatim} fw +k \end{verbatim} From there, you can execute the script using: \begin{verbatim} execute typeset \end{verbatim} Interactive mode could be very useful to those with multiple-window workstations.\x{workstations} The user could create a window containing an interactive session of FunnelWeb, and then switch between windows, editing, and executing FunnelWeb proper and other programs. If you find yourself using the command interpreter a lot, be sure to read about the other commands that are available in Chapter~\ref{chapdefinition}. \section{Setting Up Default Options} \xx{options}{setting defaults}\xx{default}{options} If you do not like FunnelWeb's default settings for its command line options, there are a number of ways in which you can change them. \narrowthing{Define an \dq{alias}:}{Use your operating system \dqp{alias}\x{alias} facility to create an alias for FunnelWeb containing the desired options. FunnelWeb processes options from left to right, so you can override these defaults later if you wish.} \narrowthing{Create a script called \dqp{fwinit.fws}:}{When FunnelWeb starts up, it executes a script called \dqp{fwinit.fws}\x{fwinit.fws} if\xx{startup}{script}\xx{initialization}{script} such a script exists in the current directory. You can use this fact to set options before the run of FunnelWeb proper by creating such a script and placing a single \dqp{set} command in it containing the desired options. The main trouble with this approach is that the options in the \p{set} command will be processed \i{after} the command line options, which means that you won't be able to override them on the command line.} For example, you might be involved more with presenting programs than with running them, and want FunnelWeb to generate a documentation file by default, but not to produce listing or product files by default. In Unix you could do this with: \begin{verbatim} alias fw fw -L -O +T \end{verbatim} \section{FunnelWeb and Make} \x{make utility}\xx{file}{dependencies} The Unix \p{Make} program allows a set of dependencies between a set of files to be described, and then uses these dependencies to control the way in which the files are created and updated. Typically, \p{Make} is used to control the process of transforming a collection of source code files to one or more executable files. As the use of FunnelWeb implies an extra stage to this process, it is natural to include the transformation of \p{.fw} files to source code files as part of the \p{Make} process. This is easy to do, but the user should be aware of one aspect of FunnelWeb which can cause problems. It is often useful, when using FunnelWeb, to create a FunnelWeb \p{.fw} file that generates more than one product file. That is, a single \p{.fw} file may have many macro definitions connected to product files so that when the FunnelWeb \p{.fw} file is processed by FunnelWeb, several files are created. For example, this facility has been used to great effect to place the description of an Ada\x{Ada} package's package specification file and package body file in the same FunnelWeb \p{.fw} file. The use of multiple product files, however, provokes a problem with dependencies. Suppose for example that a FunnelWeb \p{prog.fw} produces two product files \p{proc.spec} (a package specification) and \p{prog.body} (a package body). If the package is accessed in the way that packages normally are, it will be quite common for the programmer to want to modify the package body without modifying the program specification. So the programmer will edit the \p{prog.fw} file to change the package body. The result of running this through FunnelWeb will be the desired new package body file. However, FunnelWeb will also produce a new package specification product file \i{even though it may be identical to the previous version!} The result is that the newly created (with a recent file date) specification package file could provoke a huge remake of much of the program in which it resides. To solve the problem, FunnelWeb includes a command line option (\p{D} for Delete),\xx{D}{option}\xx{delete}{output files} which when turned on (using \dqp{+D}) causes FunnelWeb to suppress\xx{suppression}{file} product and documentation files that are identical to the previously existing versions of the same files. For example, if, during a FunnelWeb run, a macro was connected to a product file called \p{x.dat}, and the macro expanded to \i{exactly} the same text as is contained in \p{x.dat} then FunnelWeb would simply \i{never write the product file}, the file \p{x.dat} would be untouched and, as a result, no further \p{Make} propagations would take place. FunnelWeb implements this feature by writing each product file to a temporary file with a temporary file name. It then compares the temporary file with the target file. If the two are identical, it deletes the temporary file. If the two are different it deletes the target file and renames the temporary file to the target file. Use of the \p{D} facility means that the programmer need not be punished (by extra \p{Make} propagations) for describing more than one product file in the same FunnelWeb file. \section{The Dangers of FunnelWeb} \xx{FunnelWeb}{dangers}\xx{FunnelWeb}{pitfalls} Like many tools that are general and flexible, FunnelWeb can be used in a variety of ways, both good and bad. One of the original appeals of the literate approach to programming for Knuth,\xn{Donald}{Knuth} the inventor of literate programming,\xx{literate}{programming} was that it allows the programmer to describe the target program bottom up, top down, size to side, or chaotically if desired. The flexibility that this style of programming leaves much room for bad documentation as well as good documentation. Years of experience with FunnelWeb has revealed the following stylistic pitfalls which the experienced FunnelWeb user should take care to avoid.\footnote{The fact that these faults are listed here does not mean that the author has eliminated them in his own work. Rather, it is mainly the author's own mistakes that have resulted in this list being compiled. The author immediately confesses to several of the faults listed here, most notably that of Pavlov documentation.} \narrowthing{Spaghetti organization:}{By\xx{spaghetti}{organization} far the worst problem that arises in connection with the literate style occurs where the programmer has used the literate tool to completely scramble the program so that the program is described and layed out in an unordered, undisciplined \dq{stream of consciousness}.\x{stream of consciousness} In such cases the programmer may be using the literate style as a crutch to avoid having to think about structuring the presentation.} \narrowthing{Boring organization:}{At\xx{boring}{organization} the other extreme, a program may be organized in such a strict way that it is essentially laid out in the order most \dq{desired} by the target programming language. For example, each macro might contain a single procedure, with all the macros being called by a macro connected to a file at the top. In many cases a boring structure may be entirely appropriate, but the programmer should be warned that it is easy to slip into such a normative style, largely forgetting the descriptive structural power that FunnelWeb provides.} \narrowthing{Poor random access:}{Using\xx{random}{access} FunnelWeb, it is quite possible to write programs like novels\x{novels} --- to be read from cover to cover. Sometimes the story is very exciting, with data structures making dashing triumphs and optimized code bringing the story to a satisfying conclusion. These programs can be works of art. Unfortunately, without careful construction, such \dq{novel-programs} can become very hard to access randomly by (say) a maintenance programmer\xx{maintenance}{programmer} who wishes only to dive in and fix a specific problem. If the entire program is scrambled for sequential exposition, it can be hard to find the parts relating to a single function. Somehow a balance must be struck in the document between the needs of the sequential and of the random-access reader. This balance will depend on the intended use of the program.} \narrowthing{Too-interdependent documentation:}{Sometimes, when\xx{documentation}{interdependent} editing a program written using FunnelWeb, one knows how to modify the program, but one is unsure of how to update the surrounding documentation! The documentation may be woven into such a network of facts that it seems that changing a small piece of code could invalidate many pieces of documentation scattered throughout the document. The documentation becomes a big tar pit in which movement is impossible. For example, if you have talked about a particular data structure invariant throughout a document, changing that invariant in a small way could mean having to update all the documentation without touching much code. In such cases, the documentation is too interdependent. This could be symptomatic of an excessibly interconnected program, or of an excessively verbose or redundant documenting style. In any case, a balance must be struck between the conversational style that encourages redundancy (by mentioning things many times) and the normalized database approach where each fact is given at only one point, and the reader is left to figure out the implications throughout the document.} \narrowthing{Pavlov documentation:}{By\xx{pavlov}{documentation} placing so much emphasis on the documentation, FunnelWeb naturally provides slots where documentation \dq{should} go. For example, a FunnelWeb user may feel that there may be a rather unpleasant gap between a \p{@C} marker and the following macro. In many cases \i{no} commentary is needed and the zone is better left blank rather than being filled with the kind of uninformative waffle one often finds filling the slots of structured documentation written according to a military standards (\eg{}MIL-STD-2167A).\x{MIL-STD-2167A}\x{2167A}\footnote{Note: This is not a criticism of 2167A, only of the way it is sometimes used.} The lesson is to add documentation only when it adds something. The lesson in Strunk and White\paper{Strunk79} (p.~23) holds for program documentation as it does for other writing: \dq{Vigorous writing is concise. A sentence should contain no unnecessary words, a paragraph no unnecessary sentences, for the same reason that a drawing should have no unnecessary lines and a machine no unnecessary parts. This requires not that the writer make all his sentences short, or that he avoid all detail and treat his subjects only in outline, but that every word tell.}.\checked{}} \narrowthing{Duplicate documentation:}{Where\xx{duplicate}{documentation} the programmer is generating product files that must exist on their own within the entire programming environment (\eg{}the case of a programmer in a team who is using FunnelWeb for his own benefit but must generate (say) commented Ada\x{Ada} specification package files) there is a tendency for the comments in the target code to duplicate the commentary in the FunnelWeb text. This may or may not be a problem, depending on the exact situation. However, if this is happening, it is certainly worth the programmer spending some time deciding if one or other of the FunnelWeb or inline-comment documentation should be discarded. In many cases, a mixture can be used, with the FunnelWeb documentation referring the reader to the inline comments where they are present. For example:} \begin{verbatim} @A Here is the header comment for the list package specification. The reader should read these comments carefully as they define a list. There is no need to duplicate the comments in this text. @$@==@{@- -- LIST PACKAGE -- ============ -- * A LIST consists of zero or more ITEMS. -- * The items are numbered 1 to N where N is the number of items in the list. -- * If the list is non-empty, item 1 is called the HEAD of the list. -- * If the list is non-empty, item N is called the TAIL of the list. -- ... @} \end{verbatim} \narrowthing{Overdocumenting:}{Another\xx{over}{documentation} evil that can arise when using FunnelWeb is to over-document the target program. In some of Knuth's earlier (\eg{}1984) examples of literate programming, each variable is given its own description and each piece of code has a detailed explanation. This level of analysis, while justified for tricky tracts of code, is probably not warranted for most of the code that constitutes most programs. Such over-commenting can even have the detrimental affect of obscuring the code, making it hard to understand because it is so scattered (see \dq{spaghetti organization} earlier). It is up to the user to decide when a stretch of just a few lines of code should be pulled to bits and analysed and when it is clearer to leave it alone.} \narrowtext{In the case where there are a few rather tricky lines of code, a detailed explanation may be appropriate. The following example contains a solution to a problem outlined in section 16.3 of the book \dq{The Science of Programming} by David Gries\paper{Gries81}.} \begin{verbatim} @C@ This section contains a solution to a problem outlined in section 16.3 of the book @/The Science of Programming@/ by David Gries[Gries81]. @D Given a sorted array @{b[1..N]@} of integers, we wish to determine the @/length@/ of the longest run of identically valued elements in the array. This problem is defined by the following precondition and postcondition. @$@==@{/* Pre: sorted(b). */@} @$@==@{@- /* Post: sorted(b) and p is the length of the longest run in b[1..N]. */@} @D We approach a solution to the problem by deciding to try the approach of scanning through the array one element at a time maintaining a useful invariant through each iteration. A loop variable array index @{i@} is created for this purpose. The bound function is @{N-i@}. Here is the invariant. @$@==@{@- /* Invariant: sorted(b) and 1<=i<=N and */ /* p is len of longest run in b[1..i]. */@} @D Establishing the invariant above in the initial, degenerate case is easy. @$@==@{i=1; p=1;@} @D At this stage, we have the following loop structure. Note that when both the invariant and @{i != N@} are true, the postcondition holds and the loop can terminate. @$@==@{@- @ @ while (i != N) { @ @ } @ @} @D Now there remains only the loop body whose sole task is to increase @{i@} (and so decrease the value of the bound function) while maintaining the invariant. If @{p@} is the length of the longest run seen so far (i.e. in b[1..i]), then, because the array is sorted, the extension of our array range to @{b[1..i+1]@} can only result in an increase in @{p@} if the new element terminates a run of length @{p+1@}. The increase can be at most 1. Because the array is sorted, we need only compare the endpoints of this possible run to see if it exists. This is performed as shown below. @$@==@{i++; if (b[i] != b[i-p]) p++;@} \end{verbatim} \narrowtext{Where the code is more obvious, it is often better to let the code speak for itself.} \begin{verbatim} @C The following function compares two C~strings and returns TRUE iff they are identical. @$@==@{@- bool comp(p,q) char *p,*q; { while (TRUE) { if (*p != *q ) return FALSE; if (*p == '\0') return TRUE; p++; q++; } } @} \end{verbatim} \section{Wholistic Debugging} \xx{wholistic}{debugging} Surprising though it may be, FunnelWeb has a key role to play in the \i{debugging} of programs. Long experience in programming has led me to the concept of \newterm{wholistic debugging}. When most programmers detect a bug, their first reaction seems to be to jump into the debugger\x{debugger} where they often spend many hours stepping through endless stretches of code and generally wasting a lot of time. In contrast, my first reaction when I detect a bug is to realize that \i{the code must not be in good enough shape if such a bug can arise.} The presence of the bug is taken as symptomatic of the lack of general health of the code. If that bug occurred, why not another? In response to this realization, my reaction is not to enter the debugger, but rather to return to the original code and tend it like a garden,\xx{code}{gardening} adding more comments, reworking the grotty bits, adding assertions, and looking for faults. In many cases, the search for faults does not even centre on the specific bug that arose, but does tend to focus on the area of code where the bug is likely to be. The result is often that the original bug is located more quickly than it would have been had the debugger been involved. But even if it isn't, there are other benefits. A programmer who enters the debugger may stay there for hours and still not find the bug. The result is frustration and no positive gain at all. In contrast, by tending to the code, the programmer is making forward progress at all times (the code is constantly improving) even if the bug is not immediately found. At the end of ten hours, the programmer can at least feel that the code is \dq{ten hours better}, whereas the debugger freak will likely feel defeated. All this makes code tending better psychologically as well as a more efficient approach to debugging. I call this technique wholistic debugging, for it is like the difference between conventional and wholistic medicine.\xx{wholistic}{medicine} Go to a conventional doctor with a headache and he might send off for head X-rays, perform allergy tests and perform many other debugging activities. Go to a wholistic doctor with the same problem and he might look to see if you are fit, assess your mental health, and ask you if your marriage is working. Both approaches are appropriate at different times. In programming, the wholistic approach is not used enough. \section{Examples of FunnelWeb Applications} \xx{FunnelWeb}{example applications}\xx{FunnelWeb}{applications} Despite (or perhaps because of) its flexibility and simplicity, FunnelWeb can be applied to quite a number of different text processing and documenting problems. This section describes some of the more interesting real problems that FunnelWeb has solved. \subsection{Analyzing the Monster Postscript Header File} \xx{monster file}{postscript} During my Ph.D. candidature, I determined at one point that it would be very desirable to automatically insert diagrams from the \i{MacDraw}\x{MacDraw} program on my Macintosh into \TeX{}\x{TeX} \p{insert}ions in my thesis.\xx{PhD}{thesis} This would allow diagrams to float around with the text and be printed automatically rather than having to be printed separately and stuck in with real glue. On the face of it, the problem seemed inherently solvable as the Macintosh\x{Macintosh} could generate PostScript\x{PostScript} for each diagram and this PostScript could presumably be inserted into the PostScript generated using \TeX{}. The only trouble was that the Macintosh PostScript code for the diagrams relied on an Apple PostScript header file.\xx{postscript}{header file} This meant that the header file had to be included at the start of the \TeX{} PostScript if the inserted PostScript for the diagrams was to work. Unfortunately, merely including the header file at the top didn't work, and it turned out that a rather detailed analysis of some parts of the Apple header file was required in order to perform the necessary surgery on the header file to make it work. This analysis was severely aggravated by the fact that the PostScript header file was virtually unreadable. Basically it was about 50K of interwoven definitions, that looked as if it had been run through a word processor. There was no way that the code could be understood clearly without some kind of reformatting. Two other aspects of the problem further complicated the analysis: \begin{itemize} \item The definitions of interest (\ie{}the ones causing the problems) were scattered throughout the file. \item Many definitions could not be moved. For one or more reasons (\eg{}to keep a definition within the activation of a particular dictionary \p{begin} and \p{end}) it would have been unwise to move the definitions of interest to the same point in the file. \end{itemize} In fact the file was so messy and complicated that, as a rule, it had to be handled with kid gloves. It would have been unwise to re-arrange the definitions or to insert comments. To my surprise, FunnelWeb provided an unexpected solution to the problem. First I replaced all occurrences of the \p{@} in the header file with \p{@@}. Second, I placed the entire header file in a FunnelWeb macro definition connected to a product file. I then processed the file and checked to make sure that the product file was identical to the original file. By doing all this I had placed the header file under FunnelWeb control. I then left the macro definition largely untouched, but replaced the PostScript definitions of interest with FunnelWeb macro calls, moving the actual PostScript definitions into FunnelWeb macro definitions at the end of the FunnelWeb file. \begin{verbatim} @O@==@{@- Unreadable Postscript code @ Unreadable Postscript code @ Unreadable Postscript code @} @A This routine looks as if it does this, but really is does that, blah, blah blah. @$@==@{@- /print { push pop pop push turn around and jump up and down and print it} def @} @A This routine zaps the... @$@==@{@- /zap { push pop pop push turn around and jump up and down and print it} def @} \end{verbatim} Use of FunnelWeb meant that I was able to pluck out the definitions of interest (a very small part of the whole file) and collect them as a group at the end of the file where they could be studied. Because each definition was safely contained in a macro, it was possible to write a detailed commentary of each routine without fear of affecting the final PostScript code in any way at all. Once this analysis was completed, it was possible to perform surgery on the offending PostScript definitions in an extremely controlled way. In particular, the FunnelWeb input file served as a repository for all the different versions of particular routines that were tried in order to get the definitions to work. A new (\b{Z}ero) macro was created for each version of each definition, and a commentary of how it performed added above it. This case demonstrates that FunnelWeb is an extremely powerful tool for dissecting and documenting cryptic text files.\xx{cryptic}{text files} Through the use of macros, particular parts of the file can be isolated and discussed without affecting the final product file in any way. In the example above, only a small part of the file was analysed, the rest being left as a blob, but in the general case, a cryptic text file could be inserted into FunnelWeb and then incrementally dissected (and possibly modified) until the result is a fully documented literate program. That this can be done without affecting the actual product file demonstrates the high degree of descriptive control that FunnelWeb provides. \subsection{Making Ada ADTs more A} \x{Ada}\x{abstract data type}\x{ADT} Like many modern programming languages, Ada provides mechanisms for hiding information and structure. In particular, Ada provides a \newterm{package} facility that allows the programmer to declare objects in a package definition and define them in a corresponding package body. This works well for functions and procedures. However, in the case of types, implementation issues (in particular, the need to know the size of exported types) have led the designers of Ada to force the placement of private type definitions in the definition package rather than the implementation package. This means that some implementation details are present in the package definition for all to see. While not actually dangerous (the user of the package cannot make use of the information without recourse to \dq{Chapter~13} of the Ada Language Reference Manual\paper{DOD83}), this aspect of Ada is certainly unpleasant. During the development of some Ada programs, FunnelWeb was used to solve this problem. Instead of creating a separate file for the package specification and package body, a single FunnelWeb file was created containing two sections, one for the each package part. The \dq{private} part of the package specification was then moved (using a FunnelWeb macro definition) to the section describing the package body. Readers who wished only to read the package specification could read only the first part, which contained a fully documented description not containing the private definition. \subsection{Multiple Language Systems} \xx{multiple}{languages} With the prevalence of open systems\x{open systems} and multi-vendor computing, it is often necessary to construct systems consisting of programs written in a number of different programming languages for a number of different systems. For example, a particular functionality might be implemented by a shellscript (invoked by the user) that calls a C~program that makes a network connection to a Pascal program that queries a database. Quite often all these programs must conspire closely to execute their function. In the normal case, they must be written separately. FunnelWeb allows them to be written as a whole. By creating a single FunnelWeb file that creates many product files in different languages, the programmer can describe the interaction between the different programs in any manner desired. Furthermore, because the different product files are all created in the same \dq{text space} (\ie{}in a single FunnelWeb file), it is easy for them to share information. For example, in one real application FunnelWeb was used to create a system for printing files\xx{printing}{system} on a laser printer\xx{laser}{printer} connected to a remote Vax Unix machine from a local Vax VMS machine. The system consisted of two files: a VMS DCL command procedure to run on the local node, and a Unix shellscript to run on the remote node. The user, by giving the print command, invoked the local VMS command procedure, which in turn fired up the remote Unix shellscript. The two scripts then cooperated to transfer the files to be printed and print them. In addition to its usual documentation powers, FunnelWeb assisted in the creation of this system in two special ways. First, it allowed pieces of code from the two different command procedures to be partially interwoven in a description of their interaction. This is just not possible with comments. Second, it facilitated the use of shared information. For example, under some conditions, each file to be printed would be renamed and copied to the remote system using a particular constant filename (\eg{}\dqp{printfile.tmp}). FunnelWeb allowed this constant filename to be included in a single macro definition which was invoked in the definition of each of the scripts. This ensured that the two scripts used the same name. \begin{verbatim} @A The following macro contains the temporary file name used to allow the two shellscripts to transfer each file to be printed. @$@@M==@{printme.txt@} @A Here are the scripts for the local VMS node and the remote UNIX node. @O@==@{@- DCL commands copy @ unixnode:: DCL commands @} @O@==@{@- unix commands print @ unix commands @} \end{verbatim} In the case of the printing system, the entire system was described and defined in a single FunnelWeb \p{.fw} file. In larger systems containing many FunnelWeb \p{.fw} files for many different modules in many different languages, the same trick can be pulled by placing FunnelWeb macro definitions for shared values into FunnelWeb include files. For example, a suite of implementations of network nodes, with each implementation being in a different programming language for a different target machine, could all share a table of configuration constants defined in macros in a FunnelWeb include file. In summary, FunnelWeb's macro and include file mechanisms provide a simple way for programs written in different languages to share information. This reduces redundancy between the systems and hence the chance of inconsistencies arising.\x{sharing information} \subsection{The Case of the Small Function} \xx{small}{functions} Often, when programming, there is a need for a code abstraction\xx{code}{abstraction} facility that operates at the text level. If the statement \dqp{a:=3;} occurs often, it may be best simply to repeat it verbatim. If a sequence of one hundred statements is repeated often, it is normal to remove the code to a function and replace the occurrences by a function call. However, in between these two extremes are cases where a particular sequence of code is long enough and appears often enough to be troublesome, but which is bound so messily to its environment as to make a function call cumbersome. For example, the following line of statements (referring to five variables declared \i{local} to a function) might appear ten times in a function: \begin{verbatim} a=b*3.14159; c=d % 256; e=e+1; \end{verbatim} Now the \dq{normal} rule of programming says that these statements should be placed in a procedure (also called a \dq{function} in the C programming language used in this example), but here five local variables are used. Use of a procedure (function) would result in a procedure definition looking something like: \begin{verbatim} void frobit(a,b,c,d,e) float *a,b; int *c,d; unsigned *e; {*a=b << 8; *c=d % 256; *e=*e+1;} \end{verbatim} and a procedure call something like \begin{verbatim} frobit(&a,b,&c,d,&e); \end{verbatim} This might be workable in a language that allowed formal parameters to be specified to be bound only to particular variables. Similarly, it might be possible to avoid the parameter list in languages that support local procedures that can access non-local variables (such as Pascal\x{Pascal}). However, in our example here, in the C programming language, these options are not available, and so we must either create a function with five parameters, or use the C macro preprocessor\xx{preprocessor}{C} (the best solution). FunnelWeb provides the same macro facility for languages that do not have a built-in preprocessor. In particularly speed-stressed applications, the programmer may be reluctant to remove code to a procedure because of the procedure-call overhead.\xx{procedure call}{overhead} FunnelWeb macros can help there too. In summary, there sometimes arises in programming situations where the cost of defining a procedure is higher than the benefits it will bestow. Common reasons for this are the run-time procedure overhead and the messy binding problems\xx{binding}{problems} caused by removing target code from its target context. FunnelWeb can help in these situations by allowing the programmer to define a text macro. This avoids all the problems and provides an additional incentive for the programmer to describe the piece of code so isolated. \subsection{When Comments are Bad} \xx{comments}{abuse}\xx{eliminating}{comments} In the \dq{good old days}\x{good old days} of small machine memories and interpreted BASIC,\x{BASIC} programmers would eliminate the \dqp{REM} statements\xx{REM}{statement} (comments) from their BASIC programs so as to save space and increase execution speed. Whilst this was obviously an appalling programming practice, the small memories and slow microprocessors often made this tempting, if not necessary. Thankfully, times have changed since then, and most code is now compiled rather than interpreted. However, from time to time one still runs into an environment or situation, or special-purpose language, where comments are either unavailable (no comment feature) or undesirable. Here FunnelWeb can be used to fully document the code without resulting in any comments in the final code at all. For example:\xx{header}{files} \begin{itemize} \item Comments in frequently used \p{.h} header files in C programs\xx{C}{header} can have a significant impact on compilation speed. Often such header files are fairly cryptic and really ought to be well commented, but their authors are reluctant to. \item Comments are undesirable in PostScript\x{postscript} header files that must be transferred repeatedly along communications channels (\eg{}the Apple Macintosh LaserWriter header file). \item Interpreted programs in embedded systems. \item Hand written machine code in hex dump form could be commented. \item A programmer may wish to annotate a text data file containing lists of numbers that is to be fed into a statistical program that does not provide any comment facility for its input file. \end{itemize} In all these situations, FunnelWeb allows full integrated documentation without any impact on the final code. \subsection{Documents That Share Text} \xx{sharing}{text} FunnelWeb is very useful when preparing multiple documents that must share large slabs of identical text that are being constantly modified. For example someone preparing two slightly different user manuals for two slightly different audiences might want the manuals to share large slabs of text, while still allowing differences between them. The following example shows how this can be done. The code is cluttered, but this clutter would not be a problem if the lumps of text were moderately large. \begin{verbatim} @O@==@{@@+@} @O@==@{@@+@} @$@+=@{@@} @$@+=@{@@} @$@@M==@{First lump of text shared by both documents.@+@} @$@+=@{Text for first document@+@} @$@+=@{Text for second document@+@} @$@+=@{@@} @$@+=@{@@} @$@@M==@{Second lump of text shared by both documents.@+@} \end{verbatim} An alternative approach, which might work better in situations where there are many small differences between the two documents rather than a few large ones, is to define a macro with two arguments, one for each product file document. Write the document from top to bottom, but place all stretches that differ between the two documents in a macro call.\xx{annual}{report} \begin{verbatim} @! Set the definition of @#D to @! @1 to create the shareholders report. @! @2 to create the customers report. @$@#D@(@2@)@M==@{@1@} @O@==@{@- 1992 ANNUAL REPORT TO @#D@(Shareholders@,Customers@) ======================@#D@(============@,=========@) This has been a very good year for The Very Big Corporation of America. With your help, we have been able to successfully @#D@(@"screw the customers for every cent they have@"@, @"knock the shareholders into submission to bring you lower prices@"@). With gross earnings approaching six trillion dollars, we have been able to @#D@(@"increase dividends@"@, @"lower prices@"@). We expect to have an even better year next year. @} \end{verbatim} One application where text sharing can be particularly useful is in the preparation of computer documentation\xx{examples}{documentation} containing examples. For example, a book describing a new programming language might be full of examples of small programs written in the language which the user might want to try without having to type them all in. The \dq{default} approach of keeping a copy of the examples in the text of the book and another copy in separate files is cumbersome and error prone, because both files have to be updated whenever an example is changed. A more sophisticated approach is to store each example in a separate file, and then use the \dq{include file} facility of the word processor to include each example in the text. This is a better solution, but suffers from a few drawbacks. First, when editing the book in a word processor, the examples in the book will not be directly accessible or visible. To see an example, the writer would have to open the file containing the example in a separate window. This could become tedious if the text contained many examples, as many texts do. Furthermore, there is a risk that some example files will be included in the wrong place. Second, because the book is dependent on the included files, the book will end up consisting of a directory of a hundred or more files instead of just a few. An alternative solution is to construct a single FunnelWeb \p{.fw} file that, when processed, produces both the book file and the example files. This solution assumes that the book consists of a text file containing commands for a typesetter such as \TeX{}. \begin{verbatim} @O@==@{@#B@} @$@#B+=@{@- The first step to learning the object oriented AdaCgol++ language is to examine a hello world program. \start{verbatim} @ \finish{verbatim} @} @$@==@{read iopack@+Enter !World~! !Hello~! ex pr flu X[1]@} @O@==@{@@} @$@#B+=@{@- To understand the program, think of the execution state as a plate of cheese... @} \end{verbatim} Most of the file will consist of part definitions of the additive macro \p{@\#B}. The definition is \dq{broken} to allow a macro definition, wherever an example appears. The example above is a little messy because FunnelWeb does not allow macros connected to product files to be called, and it does not have text expressions that write to an product file as well as evaluating to text. Nevertheless, it presents a fairly clean solution to the problem of keeping the example programs in a computing text up to date. \subsection{Generics} \xx{generics}{fudging} It is well known that generics in programming languages are closely aligned with textual substitution. In fact, a good way to understand the generic facility of a new programming language is to ask oneself the question \dq{In what way does this generic facility differ from simple text substitution?} The differences, if any, typically have to do with the difference in scoping between textual and intelligent substitution and whether the generic code is shared or copied by the implementation. In most cases the differences are quite minor. Because generic facilities are so closely aligned with text substitution, it is possible to use FunnelWeb's parameterized macros to provide generics in programming languages that do not support generics. Simply write a FunnelWeb macro whose parameters are the parameters of the generic and whose body is the generic object. The following FunnelWeb file gives an example of a fully worked Vax Pascal\x{Pascal} generic set package implemented using FunnelWeb parameterized macros. The package was written by Barry Dwyer\xn{Barry}{Dwyer} of the Computer Science Department of the University of Adelaide\xx{University}{Adelaide} in 1987 and was emailed to me on 11~November 1987. The generic package provides a set abstraction\xx{set}{abstraction} implemented using linked lists. Note the clever use of the instantiation parameters in type, function, and procedure names. \begin{verbatim} @$@@(@2@)==@{@- @! @1 is the base type, @2 is the set type. [inherit ('@1'), environment ('@2')] module @2; type @2 = ^@2Record; @2Record = record Member: @1; Next: @2; end; procedure Null@2 (var Result: @2); begin new (Result); Result^.Member := (- MaxInt)::@1; Result^.Next := nil end; function IsNull@2 (S: @2): boolean; begin IsNull@2 := S^.Member::integer = - MaxInt end; procedure ForEach@1 (S: @2; procedure DoIt (i: @1)); var ThisS, NextS: @2; begin ThisS := S; while ThisS^.Member::integer <> - MaxInt do begin NextS := ThisS^.Next; DoIt (ThisS^.Member); ThisS := NextS end; end; function First@1 (S: @2): @1; begin First@1 := S^.Member end; function Is@1InSet (i: @1; S: @2): boolean; procedure TestEquals (j: @1); begin if Equal@1 (i, j) then Is@1InSet := true; end; begin Is@1InSet := false; ForEach@1 (S, TestEquals); end; function Includes@2 (S1, S2: @2): boolean; var Result: boolean; procedure TestIfInS1 (i: @1); begin if Result then if not Is@1InSet (i, S1) then Result := false; end; begin Result := true; ForEach@1 (S2, TestIfInS1); Includes@2 := Result end; function Disjoint@2s (S1, S2: @2): boolean; var Result: boolean; procedure TestIfInS1 (i: @1); begin if Result then if Is@1InSet (i, S1) then Result := false; end; begin Result := true; ForEach@1 (S2, TestIfInS1); Disjoint@2s := Result end; function Equal@2 (S1, S2: @2): boolean; begin Equal@2 := Includes@2 (S1, S2) and Includes@2 (S2, S1); end; procedure Insert@1 (i: @1; var S: @2); var This, Pred, Succ: @2; begin if not Is@1InSet (i, S) then begin Pred := nil; Succ := S; while Succ^.Member::integer > i::integer do begin Pred := Succ; Succ := Succ^.Next end; if Succ^.Member::integer < i::integer then begin new (This); This^.Next := Succ; This^.Member := i; if Pred <> nil then Pred^.Next := This else S := This; end; end; end; procedure Insert@1s (S1: @2; var S2: @2); var This, Pred, Succ: @2; procedure Add@1 (i: @1); begin Insert@1 (i, S2) end; begin ForEach@1 (S1, Add@1); end; procedure Remove@1 (i: @1; var S: @2); var Pred, This: @2; begin Pred := nil; This := S; while not Equal@1 (This^.Member, i) do begin Pred := This; This := This^.Next end; if Pred <> nil then Pred^.Next := This^.Next else S := This^.Next; Dispose (This); end; procedure Dispose@2 (var S: @2); var Old: @2; begin while S <> nil do begin Old := S; S := S^.Next; Dispose (Old) end; end; end. @} @O@==@{@- @@(@"NaryTree@"@,@"NaryTreeSet@"@)@} @O@==@{@- @@(@"NaryTreeSet@"@,@"NaryTreeSetSet@"@)@} \end{verbatim} A great advantage of the approach reflected in the above example is that it allows the programmer to construct a generic object in a language that does not supply generics, \i{with complete typesafety.}\xx{typesafe}{generics} This contrasts to the approach that might be used in a language such as C where the programmer might choose to construct a \dq{generic} package by parameterizing a package with pointers to \p{void}. The resulting package is powerful but extremely untypesafe. Such a generic list package is used in the code of FunnelWeb itself and caused no end of problems, as the compiler had no way of telling if pointers to the correctly typed object were being handed to the correct list-object/function combination. The major disadvantage of the text generic approach is that it causes the code of the generic object to be duplicated once for each instantiation. Depending on the number and size of the instantiations, this may or may not be acceptable. Where the duplication of code is unacceptable, a hybrid approach may be taken. As in the C~example, the programmer could write a single generic package using pointers to \p{void} or some other untypesafe mechanism. Then the programmer creates a FunnelWeb generic package whose functions do nothing more than call the functions of the untypesafe package, and whose types do nothing more than contain the types of the untypesafe package. This solution involves the use of untypesafe programming, but this is a one-off and if done carefully and correctly, the result can be a typesafe generic package involving minimal code duplication. \section{Summary} This chapter has described some of the finer aspects of the use of FunnelWeb. Throughout, the power and danger of FunnelWeb as a general text-rearranging preprocessor has been emphasised. FunnelWeb can be used both to make programs more readable or more obscure. It is up to the programmer to ensure that FunnelWeb is used properly. %==============================================================================% % End of Ch2.tex % %==============================================================================%