; CFG2 SCRIPT FOR GENERATING THE CFG2 HTML FILES ; TSECFGEXPR '{\<\%HEAD}|{^\#send}' ; TSECFGOPTS 'ix' ;=========================================================================== ; 20020410 MSD 004 Reflec the changes made in order to "LINUXise" CFG2. ; 20010519 MSD 003 Correct a block copy in the FAQ. (One "QOPEN" to "QSEMI" ; 003 change was missed.) ; 20010428 MSD 002 Correct a couple of typos. ; 20010312 MSD 001 Document changes whereby the #DEFINE now does *not* cause ; 001 CFG2 to abort. Instead, you merely receive a *warning*. ; 20010213 MSD 000 Initial development. ;=========================================================================== #define DOMAIN glod.net #define RAWSITE www.%DOMAIN% #define FULLSITE http://%RAWSITE% #define RAWCHAOS ChAoS@%DOMAIN% #define CFG2EMAIL cfg2@%DOMAIN% #define HEADA H1 #define HEADB H2 #define HEADC H3 #define < <; #define > >; #define & &; #define CODE_START
#define CODE_END        
;=========================================================================== #switch !MAKECLEAN ;=========================================================================== #file INDEXhtml cfg2.htm #file CFG2TUTORhtml cfg2tut.htm #file CFG2VERSIONShtml cfg2ver.htm #file CFG2FAQhtml cfg2faq.htm #file CFG2REFhtml cfg2ref.htm #file OptItemFil OptItem.ini #source OptItemSrc OptItem.ini #file ResItemFil ResItem.ini #source ResItemSrc ResItem.ini #file WrdItemFil WrdItem.ini #source WrdItemSrc WrdItem.ini #file ExpItemFil ExpItem.ini #source ExpItemSrc ExpItem.ini #source VersionSrc Version.ini #includ VersionSrc #define TITLE CFG2 %PVERSION% ;=========================================================================== ; *Fully* disable switch expressions after "subroutines" are generated. #margin 1 ;=========================================================================== #define WrdName #define WrdGrammar #send WrdItemFil ;
<%%HEADC%%> #%%WrdName%% %%WrdGrammar%%
; #close * ;=========================================================================== #define ResName #send ResItemFil ;
<%%HEADC%%> %%ResName%%
; #close * ;=========================================================================== #define OptName #define OptGrammar #send OptItemFil ;
<%%HEADC%%> -%%OptName%%%%OptGrammar%%
; #close * ;=========================================================================== #define ExpName #define ExpGrammar #send ExpItemFil ;
<%%HEADC%%> %%ExpGrammar%%
; #close * ;=========================================================================== #margin 0 ; Don't require use of switch "expressions". #empty 1 ; Want white space passed through so the HTML is readable. ;=========================================================================== #send *html ; #close * ;=========================================================================== #say Generating index HTML file. #send INDEXhtml %TITLE% - Read Me <%HEADA%> %TITLE% - Read Me <%HEADB%> Introduction

Welcome to CFG2. This program aids the configuration of a program or system by allowing relatively easy generation of different versions of text configuration files. An obvious application for CFG2 is for the generation of AUTOEXEC.BAT and CONFIG.SYS files. However, it can be used for a much broader range of tasks. For example, it is has been used to generate a web site's HTML files, the "make" files for a large project and, indeed, "C" and Forth source files themselves.

The main benefits of using CFG2 are that the need for repetitive editing of configuration files is removed and the fact that different configurations can be easily selected.

<%HEADB%> Requirements

In order to work, CFG2 merely requires that you have an executable for the operating system upon which you wish to run it. At present, we only issue a version suitable for MSdos and its derivatives. (For example, the MSdos version will run happily under Windows 95, Windows 98 and so on.) CFG2 has also had superficial testing under LINUX and appears to function correctly.

<%HEADB%> Package Contents

You should have the following:

cfg2.exe The CFG2 executable file.
*.ini Some example CFG2 script files.
%CFG2VERSIONSHTML% Change history of the CFG2 program.
%CFG2TUTORHTML% CFG2's Tutorial.
%INDEXHTML% This introduction to CFG2.
%CFG2FAQHTML% A file of Frequently Asked Questions.
%CFG2REFHTML% A CFG2 Reference Manual.

To actually run CFG2, only the executable is required. Of course, a script file is required if you want it to do anything useful.

If you are a first-time user of CFG2, I suggest that you read the tutorial.

<%HEADB%> Licence

CFG2 is a public domain program. You are free to re-distribute CFG2 to third parties if:

<%HEADB%> Disclaimer
"The authors disclaim all warranties as to this software, whether express or implied, including without limitation any implied warranties of merchantability, fitness for a particular purpose, functionality or data integrity or protection."
<%HEADB%> Copyright

CFG2 was written by Mark ("Captain ChAoS") Davis. By virtue of the fact that ChAoS had a hand in writing this program:

This program is a ChAoS Utility

I'm not sure that the terms "public domain" and "copyright" aren't mutually exclusive. Even so, this software is:

Copyright ©; Mark Davis, 1998-2002.
<%HEADB%> Contacting The Author

If you have any bug reports or suggestions, you can contact the author at the CFG2 email address.

If you like CFG2, perhaps you'll like the rest of the ChAoS Utilities - please visit the web site. #close * ;=========================================================================== #define QSemi The End Of A Line Of Output Is Missing. Why? #say Generating FAQ HTML file. #send CFG2FAQhtml %TITLE% - Frequently Asked Questions <%HEADA%> %TITLE% - FAQ <%HEADB%> Questions

<%HEADB%> Answers <%HEADB%> %QSEMI%

The semi-colon character (';;') introduces a comment in CFG2's script language. The text from the semi-colon to the end of the line is ignored. If you want a semi-colon to appear in the output text, use two semi-colons next to each other. If you want two semi-colons in your output file, then use four semi-colons and so on. #close * ;=========================================================================== #say Generating Change Log HTML file. #send CFG2VERSIONShtml %TITLE% - Change Log <%HEADA%> %TITLE% - Change Log
1.2.041

The source has been changed in order to make it GCC (the standard LINUX C compiler) friendly. As a result, it now clean compiles under LINUX and appears to function correctly. Only one (intentional) change has been made - when CFG2 constructs the default script file name from the executable file name, it now appends the default script file extension onto the end. (LINUX executables usually have no extension and, as a result, under LINUX, CFG2 would attempt to use "CFG2" as the default script file name rather than "CFG2.INI" as it does now.)

1.2.002

Redefining a macro by using #DEFINE twice will no longer make CFG2 abort with an error message. You will only receive a warning message, instead. (#REDEF still retained as the "approved" way of redefining a macro.)

1.1.222

Adopt the new "standard" format for the files that keep track of the program's version number. This will allow easy generation of the current version of the program and, in particular, the web site can automatically generate a list of current program version numbers.

1.1.204

Cured a bug whereby an over-long tag name or a long, unbroken string of non-space characters at the start of a script line causes CFG2 to crash. (The latter failure mode only occurred when the margin width was set to zero - something which has only been possible with the latest CFG2.)

1.1.199

Added code to provide for destruction of the internal lists rather than rely on the operating system's automatic cleanup procedure at program termination.

1.1.188

Added the "justone" operator to the evaluator. This operator will empty the entire expression stack, counting the number of "active" (that is, TRUE) entries. When it has finished, it returns TRUE only if there was exactly one active entry. (Use this to sanity check mutually exclusive switches.)


Added under-line escapement in the form of a double underline. Hence, a trailing "__" would place a trailing under-line as the last character in the line but retain the normal end-of-line termination sequence.


Added the "xor" operator to the evaluator.

1.0.000

Extensive changes relating to data structures. Instead of the static structures that we had before, they are all now dynamic and, hence, there are very few hard-coded limits remaining.


A zero-length margin in now allowable. This allows maximal use of the line length when margin expressions aren't required.


Altered the handling of comment lines when #EMPTY is turned on. In the past, if, after comment removal, the resulting line was entirely empty, a blank line would be sent to the output files. After this change, this action is inhibited if the entire line is a comment. That is, if there is a comment character in column one of a line, no further processing is carried out.


Added the '-T' source tracing option. Adding a '-T' to the command line will make CFG2 dump lines of text as they are processed. If the line has macro expansion applied, it'll be dumped after the expansion has been carried out, as well.


One can now use a switch name between macro characters and have CFG2 expand the switch's value at run time. For example:

%CODE_START% #switch S #say Switch S is now %%S%%. %CODE_END%

Due to the fact that CFG2 is now a single pass program, it's much more difficult to provide a "reminder" ('-?' and '?') facility. Hence, this feature has been removed.


The way that IF-ELSE-ENDIF constructs has been altered (again as a result of the adoption of a single-pass strategy). Hence, the IF-ELSE-ENDIF construct now takes priority over all other directives. Hence, this is now allowable:

%CODE_START% #switch s #if s #file out sname.txt #else #file out NOTsname.txt #endif %CODE_END%

Before this change, the two declarations of file OUT would've generate a "file already defined" error message.


Added the wild-carding feature such that '*' and '?' can be used in the tags presented to file directives. For example:

%CODE_START% #file AHTML FIRST.HTM #file BHTML SECOND.HTM #file ATXT THIRD.TXT #file BTXT FOURTH.TXT #send ?HTML #send ?TXT #send A* %CODE_END%

And so on.

#close * ;=========================================================================== #say Generating Tutorial HTML file. #send CFG2TUTORhtml %TITLE% - Tutorial <%HEADA%> %TITLE% - Tutorial ;----------------------------------------------------------------------------- ;


<%HEADB%> Contents ;----------------------------------------------------------------------------- ;
<%HEADB%> Introduction

CFG2 is a program that will generate files following a simple script language. Specifically, it was designed to facilitate the generation of configuration files. The following are all examples of files that have been generated via CFG2: MSdos AUTOEXEC.BAT and CONFIG.SYS files, compiler MAKEFILEs, linker "response" files, program source files, web site and documentation HTML files and AWK script files. The uses of CFG2 are only limited by your imagination.

There is very little required to run CFG2 - all you need is an executable suitable for your operating system. Since we develop under an MSdos-compatible operating system, for the purposes of this tutorial, we'll assume that's what you have; if you've compiled CFg2 to run under an "alien" operating system, we assume that you're au fait with any other requirements for your particular system.

Furthermore, we assume that you've had the sense to make a temporary directory in which to follow this tutorial and have a multitasking operating system so that you can copy and paste stuff directly from this tutorial into one or more CFG2 script files.

Please note that all source examples are indented by a few spaces. You cannot do this in a "real" source - the directives must start in column one.

;----------------------------------------------------------------------------- ;
<%HEADB%> File Handling <%HEADC%> Concepts

As we've stated, CFG2's main purpose is to create files and, therefore, we introduce the file-handling script concepts first. The program distinguishes between three sorts of files: output, input and source. The most common file type is the "output file". This is the type of file "declared" when you wish to generate a file. The other two file types, input and source, are read by CFG2 and are different only in that the former is read as "raw data" whereas latter is scanned for "directives".

For each file declared, you must supply a file name (acceptable to your operating system, of course) and a "tag". Once the file has been declared, only the tag is used to refer to a file within the rest of the CFG2 script. This keeps files citations concise and, more importantly, one can refer to these files via a wild-card naming system independent of their "true" file names.

<%HEADC%> Output Files

It's time for our first example. Let's assume that you want to generate a file called "uptime.txt" and you want the text "success" to appear in it. This code will suffice:

%CODE_START% #file UpTime uptime.txt #send UpTime success #close UpTime %CODE_END%

This code fragment should be pretty simple to understand. The first line uses the #FILE directive to declare a file tag called UPTIME with a "real" file name of "uptime.txt". The following line uses a #SEND directive to start sending output to that file. In fact, one can send output to several files at once. (The number of files allowed to be open at a time is limited only by free memory and your operating system.) The last line, of course, closes the file. So, what about the line that just contains "success"?

Note that this line is indented by eight spaces. When processing a line of text, CFG2 first checks to make sure that the line doesn't start by a directive. In this example, all but one of the lines start with a directive. When CFG2 finds a directive, it obeys it. However, when the "margin" (that is, the first few columns of text) contains no text at all, the rest of the line of text is sent to all currently open files. In the above example, by the way, the margin is the default of eight spaces wide

Now, paste this example text it into a file called X.INI, making sure you remove the superfluous indentation. Then, try running CFG2 using something like this:

%CODE_START% CFG2 -FX.INI %CODE_END%

When I did this on my machine, I got:

%CODE_START% CFG2 Version 1.1.184 %CODE_END%

CFG2 has merely displayed its version number. If I look in my temporary directory, I now have a file called UPTIME.TXT with the word "success" in it.

Well, this is all very well but what if we'd wanted to generate two files that contained this text? Well, we could do this:

%CODE_START% #file UpTime uptime.txt #file UpTime2 uptime2.txt #send UpTime UpTime2 success #close UpTime UpTime2 %CODE_END%

If you run this as before, you now have two files that contain "success".

This would seem to be a good point to introduce the wild-carding facilities of CFG2. In the above example, citing two files on the #SEND line wasn't any trouble. However, what if we wanted to send the stuff to, say, ten? What if we wanted to open and close all ten files many times? Well, CFG2 allows you to specify a wild-card tag in all of the file directives. Hence, the above example could have been re-written:

%CODE_START% #file UpTime uptime.txt #file UpTime2 uptime2.txt #send Up* success #close Up* %CODE_END%

Indeed, a single asterisk on its own could have been used because that would match all qualifying file tags.

Please note that all output files can be opened and closed willy-nilly subject, ultimately, only to operating system constraints. In this following example, the file OUT is generated in two separate lumps. (The first time that an output file opened, if it already exists, it will be truncated. Thereafter, output will be appended onto the end of the file.)

%CODE_START% #file out x.txt #send out Line One. Line Two. #close out ;; Some other processing here... #send out Line Three. Line Four. #close out %CODE_END%

(You can place comments in a CFG2 script by writing them after a semi-colon character.)

<%HEADC%> Input Files

Assuming that it resides in a file called X.INI, consider this next example and see if you can work out what it will do before you run it:

%CODE_START% #data in x.ini #file out outfile.txt #send out #read in #close out %CODE_END%

If you guessed that you'd end up with a "clone" of X.INI, you'd be right. The #FILE, #SEND and #CLOSE directives, we've seen before. The new items here are the #DATA and #READ directives.

You use the #DATA directive to declare an input file. Input files are treated as raw text - no interpretation is done on their data at all. In this instance, we actually opened a CFG2 source file as an input file but, of course, the contents of an input are irrelevant to CFG2 - when it finds a corresponding #READ, it just opens the file, reads the data sending each line to all open files and closes the file at the end of the data stream.

Note that we've already noted, the above example could've been written as:

%CODE_START% #data in x.ini #file out outfile.txt #send * #read * #close * %CODE_END%

Beware of one thing, here: when matching wild-card file tags, CFG2 matches only tags of the correct type. So, in the above example, tag IN does not match the "#SEND *" because it's not an output file. Similarly, file tag OUT does not match the "#READ *" line because it's not an input file. In general, we've tried to make the wild-card matching do as you'd expect to make the script source simpler.

<%HEADC%> Deleting Files

By using the #DELETE directive, one can delete files. There are two main uses for this facility: it can be used to remove temporary files that have been generated in a script and are no longer required. It can also be used when a "clean up" operation is wanted - the point is that a large CFG2 script will quite likely to rise to many smaller files. Since they can all be generated at will, it may be cleaner to provide a "make clean" switch in the script so that, when one backup up the system, you only backup what is required rather than the whole directory, or whatever.

Once again, wild-cards can be used with #DELETE but, of course, one must be careful when using this. (Note that you can only delete output files with #DELETE.)

A very simple example:

%CODE_START% #file out y.ini #delete * %CODE_END%

You will, of course, have to have a file called Y.INI hanging around to actually see the effect of this directive.

<%HEADC%> Source Files

From within CFG2, you can handle CFG2 source files as well as plain old output and input files. The main difference between an input file and a source file is that the latter is treated as if one had pasted the text straight into the script that included it. In this respect, the #INCLUD directive behaves much as a "C" language #INCLUDE pre-processor command except that one can't place the file name after the #INCLUD - you have to use a tag. (Or, in fact, a wild-card tag.)

An example follows (paste the text into the correctly named files):

File X.INI:

%CODE_START% #source src y.ini #file out outfile.txt #send * #includ src #close * %CODE_END%

File Y.INI:

%CODE_START% Mary had a little lamb, #say We're half way through the source file... It's fleece was white as snow. %CODE_END%

File OUT was opened and the source of Y.INI was read just as if it had been pasted into the middle of X.INI using a text editor. Note the user message that is generated by the #SAY directive. If a #DATA-#READ pair had been used instead of the #SOURCE-#INCLUD pair, the #SAY message line would have ended up in the output data stream.

Once again, we could have used wild-card patterns with the #INCLUD. However, unlike with the #SEND and #READ directives, there is a little trap waiting for the unwary with a wild-carded #INCLUD. Since, when using an #INCLUD, we are dealing with source files, they have to be interpreted. This means that the program can not treat their operations as "atomic". In practice, when CFG2 comes across a wild-carded #INCLUD, it opens all of the matching source files at the same time. As each source stream is exhausted, then it is closed. However, the other streams will still be open until it is their turn to be read. Hence, it can be a bit "expensive" on files handles to do wild-card #INCLUDing.

<%HEADC%> Generating Source

One trick that can be very useful in a CFG2 script is the ability to generate a CFG2 script file and read it back later on. This is usually done to provide a crude "subroutine" facility. In order to make real use of this facility, we need to use CFG2's macro capability, so we'll leave an example to a later point in the tutorial. However, for now, remember that, within the same script, a file name can be associated with an input file tag and to a source file tag...

;----------------------------------------------------------------------------- ;
<%HEADB%> Switches And Configs <%HEADC%> Concepts

Sending output to files is all very well but it'd be nice to be able to configure what one sends. This is precisely what CFG2's "switches" and "configs" are. We'll cover switches first because configs are merely a convenient way of settings many switches in one go.

If you've been working linearly through this tutorial, you've already seen an example with a switch in it; or rather, you've seen an example of how to use switches. Now, we'll see how to declare them.

Switches come in two basic varieties: "derived" and "simple". We'll cover simple switches first.

<%HEADC%> Simple Switches

Anyone who has done any high level programming will be familiar with "Boolean" or "logical" variables. These are variables that can hold either a TRUE or FALSE value and no others are allowed. In essence, a simple switch is a Boolean variable.

For our first switch example, let's define a simple switch:

%CODE_START% #switch Toggle %CODE_END%

This would declare a switch called TOGGLE that was on by default. In order to make it be off by default, one would have to alter the example thus:

%CODE_START% #switch !Toggle %CODE_END%

When we come to use switches, you'll find that the exclamation mark is CFG's Boolean "not" operator. In this instance, it's just used as an obvious extension to indicate "turn this switch off by default". This same notation is used when setting switch (and configs) from the command-line and in the #SET directive.

Let's say that we had used to above example where the simple switch TOGGLE was off by default and we wanted it turned on. How could we do this? Well, we could do it on the command-line:

%CODE_START% cfg2 -fx Toggle %CODE_END%

This would "run" CFG2 script file X.INI and, at the correct juncture, apply all the settings on the command-line. In this instance, there's only one;; viz, the citing of TOGGLE. If, by the way, we'd wanted TOGGLE turned off on the command-line, then we'd use this instead:

%CODE_START% cfg2 -fx !Toggle %CODE_END%

We can also affect the value of TOGGLE from within a running CFG2 script by using the #SET directive:

%CODE_START% #set Toggle %CODE_END%

By now, I expect you can guess how you turn a switch off by using the #SET word:

%CODE_START% #set !Toggle %CODE_END%

Hence, the real power of CFG2 doesn't become apparent until you realise that switches and their close relative, configs, can be set and reset on the command-line, from within a CFG2 script or merely by using the default values when they are declared. Having declared a switch it can be used in an expression and, hence, to control the file generation process.

Since an understanding of expressions is a pretty basic requirement before one can use CFG2 and since one can't really do much with switches until they are understood, it's time to introduce an example of an expression along with an example of a derived switch.

<%HEADC%> Derived Switches

Derived switches, too, take only a TRUE or FALSE value. However, the user can not set them explicitly - they are called "derived" switches because they each have a derivation expression associated with them. When the value of a derived switch is required, the expression is evaluated and that is used as the switch's value.

Let's have an example. Let's say we want a switch called BREWING that is TRUE if two other switches, HEATING and PRIMED, are both TRUE:

%CODE_START% #switch BREWING HEATING PRIMED %&% %CODE_END%

Here, the switch's name, BREWING, is followed by a Reverse Polish Notation (RPN) expression. Note that unlike ordinary high level language expression syntax, with RPN the operands come first and the operator last. (This notation was adopted for use in CFG2 simply because it is so much easier to program and that parentheses and operator precedence rules are not needed.)

Other than the fact that you cannot explicitly change the value of a derived switch, it behaves much as a simple switch does.

Before covering expressions in detail, we'd better get configs out of the way:

<%HEADC%> Configs

Although, at first sight, a config declaration may look similar to a derived switch declaration, it isn't. Here is an example of a config declaration:

%CODE_START% #config ALLBUTTHREE one two !three four five %CODE_END%

Configs are a very convenient way to affect the settings of many switches by merely referencing one config tag name. In the above example, the config being declared is ALLBUTTHREE. What looks like an expression is, in fact, a list of switch or config names. (That's right - you can reference one config from a different config.)

When the config is cited - and this is done in exactly the same way as you'd alter the value of a simple switch - each item in the tag list is, in turn, set or reset depending on whether the name in the list has a leading "not" sign.

Assuming that X.INI contained the code in the above example, I could use this command-line:

%CODE_START% cfg2 -fname.ini ALLBUTTHREE %CODE_END%

When CFG2 wanted to "execute" the setting of ALLBUTTHREE, it would travel down the list of switch names setting them all except THREE which would be turned off. Note that it is legal to use the following in your CFG2 script:

%CODE_START% #set ALLBUTTHREE %CODE_END%

This would have the same effect as the above command-line example. You can also use a not sign before the config name, like this:

%CODE_START% cfg2 -fname.ini !ALLBUTTHREE %CODE_END%

Or, indeed, this:

%CODE_START% #set !ALLBUTTHREE %CODE_END%

In these two cases, switch THREE would be turned on and all the other switches would be turned off - the leading not sign inverts the actions of all the items in the list.

It is important to realise that this "toggling" action will ripple through all items affected by a config being altered. For example, consider this:

%CODE_START% #switch !SWITCH1 #switch !SWITCH2 #switch !SWITCH3 #config CONFIG1 SWITCH1 SWITCH2 #config CONFIG2 !CONFIG1 SWITCH3 %CODE_END%

Here, we have three switches that default off. Let us say that this command-line is used:

%CODE_START% cfg2 -fname.ini !CONFIG2 %CODE_END%

Well, it seems clear that, after the citation of CONFIG2, that SWITCH3 will be off. (There is a leading not sign before the "CONFIG2" so the action of the switch is reversed.) But what about that citation of CONFIG1 in the declaration of CONFIG2? What effect will that have?

Well, remember we've said that the inversion request on the command-line (or in the use of a #SET word) ripples down to the lower "levels". In this instance, the inversion of CONFIG2 will cause CONFIG1 to be turned on - remember, there was a not sign before the use of CONFIG2 and a not sign before the use of CONFIG1 in the declaration of CONFIG2. Two inversions, of course, will cancel each other out.

Having got switches and configs out of the way, it's time to look at expressions in more detail.

;----------------------------------------------------------------------------- ;
<%HEADB%> Expressions <%HEADC%> Concepts

We have touched lightly on the subject of expressions in the previous couple of sections. However, now it's time to investigate them in detail.

As we have already said, CFG2 ignores the more normal "infix" notation in favour of Reverse Polish Notation. It is much easier to build an expression parser for RPN because it doesn't need complicated operator precedence rules and, as a result, doesn't need precedence-busting parentheses either. RPN can, for the user new to them, seem a little strange but the expressions that you're likely to use from within a CFG2 script will probably be pretty simple so it shouldn't present too much of a problem.

In CFG2 script, expressions can appear in several different places. If a switch declaration doesn't have one, then the switch is merely a simple switch and, hence, all derived switch declarations will contain an expression. Each #IF directive must have an expression following the keyword. Also, on a "normal" line of text, that is, one that does not start with a CFG2 directive, an expression can appear in the first few columns of the line - that is, within the "margin". With one minor exception, the expression syntax is identical in all of these places.

CFG2 maintains an expression "stack" onto which it pushes the results of the expression as it is deduced. Operands are evaluated and pushed straight onto the stack. Operators cause CFG2 to retrieve one or more items from the stack and, after working out the result of the calculations, it places the result back onto the stack. Hence, at the end of the expression evaluation, there should be precisely one item on the stack and that item will, indeed, be the "value" of the expression. (If there is not exactly one item on the stack at the end of the expression evaluation, CFG2 will produce an error message and abort the run.)

So, what do expressions look, like? Well, first, we have to introduce the operators to you. The operators are much what you'd expect from a high-level language compiler. There are symbols for all of the normal logical operators: "and", "or", "xor" and "not". ("C" programmers should not that there is no short-circuiting of the expression evaluations - it always carries on to the bitter end.)

The operators take a varying number of operands and it's convenient to group them together as such:

<%HEADC%> Monadic Operators

The monadic operators take only one operand and, at present, there is only one monadic operator (how apt) - the "not" sign. This, very simply, takes the previous expression (this is RPN, after all) and negates it. Thus, if the previous expression evaluated to TRUE, the result is FALSE and vice versa.

Here's an example - if switch RUNNING is TRUE, then the expression will be FALSE and so on:

%CODE_START% RUNNING ! %CODE_END%

In fact, there is a little departure from true RPN, here. Because CFG2 expressions tend to be very simple and since the not sign is used, elsewhere in CFG2's syntax, as a pre-fix operator, we relax the rules and allow a switch to have a not sign placed in front of the tag name if there is no space between the not sign and the switch tag name. Thus, the above example could be written as:

%CODE_START% !RUNNING %CODE_END%

Remember, though, the "space rule" - if you were to leave a space after the not sign and before the tag name, CFG2 would try to "apply" the not operation to that part of the expression before the not sign. This is wrong :

%CODE_START% ! RUNNING ;; Wrong! This will invoke an error message. %CODE_END% <%HEADC%> Diadic Operators

The majority of CFG2 operators take two operands. These include all the normal logical operators - "and", "or" and "xor". The following code fragment shows an example of each:

%CODE_START% SWA SWB %&% ;; TRUE if switch A *and* switch B are TRUE. SWA SWB | ;; TRUE if switch A *or* switch B are TRUE. SWA SWB ^ ;; TRUE if switch A *xor* switch B are TRUE. %CODE_END%

Note that no relaxation of the rules are allowed - the operator must be after the operands.

<%HEADC%> N-adic Operators

CFG2 has a special operator that grabs all of the existing items off of the expression stack. It is called "JUSTONE" and will return TRUE if one of the items it "pops" is TRUE. You will find this operator useful when you have a set of mutually-exclusive switches and you want an easy way to see if the user has only selected one of them. (It will also return FALSE if none of the switches is selected.) Here is an example:

%CODE_START% #switch !BORLAND #switch !WATCOM #switch !MICROSOFT #if BORLAND WATCOM MICROSOFT JUSTONE ! #error You *must* select *one* compiler. #endif %CODE_END% <%HEADC%> Last Value Operator

This operator isn't, in fact, part of the expression syntax but, since it can only be used where an expression can be used and it doesn't really fit anywhere else, we document it here.

As you will shortly find out, expressions can be used to "filter" whether lines of normal text should be sent to the current set of open files. If you have several lines that all depend on the same expression, you can enter that expression on the first line of text and employ the "use last expression value" operator on the rest. In fact, you've already seen an example of this during the discussion of output files. Here's another example:

%CODE_START% A B %&% First line of text. A B %&% Second line of text. A B %&% Third line of text. %CODE_END%

This can be re-written as:

%CODE_START% A B %&% First line of text. = Second line of text. = Third line of text. %CODE_END%

The latter is, of course, more lucid and easier to alter if the expression has to be changed.

;----------------------------------------------------------------------------- ;
<%HEADB%> Miscellaneous <%HEADC%> Text Macros

Many programming languages allow the programmer to define "text macros". That is, they allow the programmer to associate a given piece of text with an identifier and to be able to retrieve and use that text in a simple fashion.

CFG2 has such a facility via the #DEFINE word. Once defined by supplying the text and tag name to a #DEFINE directive, the associated text can be retrieved by employing the macro substitution characters, as detailed in a separate section of the tutorial. This is an example of a macro being defined:

%CODE_START% #define A_MACRO_TAG some macro text %CODE_END%

This would associate the tag A_MACRO_TAG with the text string "some macro text".

Macro may be "nested";; that is, you may nest a reference to a macro tag in the definition of another macro tag. You can either arrange for the macro expansion to be done as the referrer is defined or have macro expansion done only when the referrer is expanded.

If you try to re-define a macro using the #DEFINE word, CFG2 will give you a warning about duplicate definitions. If you had intended to re-define a macro tag name, a better way to do it is to use the #REDEF word instead:

%CODE_START% ;; This will result in a warning message! #define MACRO1 Definition number one %<%more_code%>% #define MACRO1 Definition number two ;; This is how it *should* be done. #define MACRO1 Definition number one %<%more_code%>% #redef MACRO1 Definition number two %CODE_END%

Please note that you cannot re-define a macro tag until it has been defined. Hence, each re-definition of a tag must be preceded by a "normal" define of that tag.

<%HEADC%> Conditionals

One very useful construct in the CFG2 armoury is the IF-ELSE-ENDIF construct. This allows conditional execution of script text. This facility operates in a very similar manner to the items of the same name in the "C" pre-processor language.

Conditionals can be nested but, of course, the items must be nested correctly. This is an example of a simple IF-ELSE-ENDIF construct:

%CODE_START% #switch RAINING #if RAINING #say Take a brolly with you. #else #say Leave the brolly at home. #endif %CODE_END%

This, if executed, would do the expected - given that you leave the switch in its default state, then you'll get:

%CODE_START% CFG2 Version 1.1.194 Take a brolly with you. %CODE_END%

If you put a "!RAINING" on the end of the command-line, you'll get this instead:

%CODE_START% CFG2 Version 1.1.194 Leave the brolly at home. %CODE_END%

You should note that, unlike versions of CFG2 prior to 1.0.000, the IF-ELSE-ENDIF directives now take priority over all other directives. Thus, it is perfectly legal to do something like this:

%CODE_START% #switch GENC #if GENC #define EXTN .C #define LANGUAGE C #file SOURCE_OUT ROOTNAME.C #else #define EXTN .PAS #define LANGUAGE Pascal #file SOURCE_OUT ROOTNAME.PAS #endif #say Language is '%%LANGUAGE%%' #say Extension is '%%EXTN%%' #say File name is '%%SOURCE_OUT%%' %CODE_END%

This would fail, by the way, on older CFG2s. If you run this in the default state, it'll produce:

%CODE_START% CFG2 Version 1.1.194 Language is 'C' Extension is '.C' File name is 'ROOTNAME.C' %CODE_END%

Whereas, if you add a "!GENC" on the end of your command-line, you'll get this instead:

%CODE_START% CFG2 Version 1.1.194 Language is 'Pascal' Extension is '.PAS' File name is 'ROOTNAME.PAS' %CODE_END%

Please note that, unlike in "C", you cannot indent nested conditionals (or, indeed, any directives). Therefore, this is not legal:

%CODE_START% #switch A #switch B #if A # if B # say A and B # else # say A and *not* B # endif #else # if B # say *not* A and B # else # say *not* A and *not* B # endif #endif %CODE_END%

I'm afraid, it would have to be written something like this, instead:

%CODE_START% #switch A #switch B #if A #if B #say A and B #else #say A and *not* B #endif #else #if B #say *not* A and B #else #say *not* A and *not* B #endif #endif %CODE_END% <%HEADC%> Margin Width

As you may have seen, CFG2 scans the first few columns of a line of "normal" text for a "margin expression". (Lines containing directives are, of course, treated differently.) You may wish to vary the width of this margin and that is the purpose of the #MARGIN directive.

The default width is eight characters (that same as the default tab width) but sometimes I change it to a zero width or to a slightly wider width.

Why would you want to do this? Well, one example of where this has been done is the text that you are reading! On this occasion, the generated text does not have any configurable items. That is, I'm only using the macro facilities of CFG2 to provide a "tidy" way to maintain the sources of these HTML files. (I could, of course, maintain all the separate HTML sources individually. This is a good example, though, of where CFG2 can be used to change, say, the "style" or layout of the HTML without having to do a lot of search-and-replace text edits. In this case, an edit of a couple of lines of CFG2 script, a re-generation and it's done!)

The following example shows how to change the margin to a zero-width margin. In other words, to the width used in the CFG2 sources that you are reading:

%CODE_START% #margin 0 %CODE_END%

We've covered making the margin smaller (or even removing it) but why would you want to make it bigger? If you are generating files that are heavily configurable, then your margin expressions may become so complicated that you run out of room when using the default margin width. Of course, altering the margin width isn't the only alternative in this situation - you could instead, for those few occasions where the margin isn't large enough, define a derived switch, and use that as the margin expression. It's your choice...

As an example, let's assume that we've defined three switches and want to send the text "OUTPUT TEXT" to the output files if all three switches are on. (We also assume the we are currently using a margin width of eight characters.) Our first attempt might be this:

%CODE_START% #switch A #switch B #switch C A B & C &OUTPUT TEXT %CODE_END%

However, in this case the margin is too small for the expression to fit into it. In this case, the expression "seen" by CFG2 is, in fact, "A B & C " - because the margin width is eight characters, the final ampersand will be considered as part of the output text. Therefore, we have to do one of two things. Our first choice is to use a bigger margin, either temporarily or throughout the document:

%CODE_START% #switch A #switch B #switch C #margin 16 A B & C & OUTPUT TEXT %CODE_END%

Or, if the expression is fairly common, we might want to define a derived switch and use it as necessary. (This is, in fact, a pretty good idea anyway - it may help make the script more lucid for the benefit of others.)

%CODE_START% #switch A #switch B #switch C #switch ALLON A B & C & ALLON OUTPUT TEXT %CODE_END% <%HEADC%> Empty Lines

By default, CFG2 will ignore source lines that, after comment stripping and so on, result in an empty line. That is, if there isn't a single "useful" character of output, then no output will be generated. Adoption of this convention means that one can use liberal amounts of "white space" in the layout of the CFG2 source with no worry about it affecting the resulting output files.

Sometimes, however, one might want the generated text to be (human) readable. This is the case, for example, with the HTML sources for the ChAoS web site - even if a user doesn't have an HTML browser to hand, I still want the pages to be formatted such that they are at least amenable to scanning via a plain text editor, for example. Hence, the source of my web pages contains a line of source something akin to this:

%CODE_START% #empty 1 %CODE_END%

This forces CFG2 to make blank lines in the source generate a corresponding blank line in the output. There is, though, one caveat when running with #EMPTY set to a non-zero ("on") state. This is detailed in the section on comments.

<%HEADC%> Diagnostics

Various directives come under the blanket header of "diagnostic aids". Some are intended for diagnostic of CFG2 internal problems but most are intended for users to debug their scripts. Even so, the "heavy" CFG2 diagnostics mode can be useful if you really don't know what's going on. The #SHOW and #DUMP words can be used to get a "snapshot" of the state of the script.

The #SHOW word list the currently selected (that is, TRUE) switches:

%CODE_START% #switch X #switch !Y #switch Z #config G !X Y Z #define A 123 #define B 456 #file P P.TXT #file Q Q.TXT #file R R.TXT #show %CODE_END%

The above gives, as output:

%CODE_START% CFG2 Version 1.1.194 Selected switches: X Z %CODE_END%

Note that switch Y is not represented because I defaulted it to a FALSE state.

The #DUMP word list everything in the CFG2 data structures. In the above example, if we replace the #SHOW word with a #DUMP word, the output changes to:

%CODE_START% CFG2 Version 1.1.194 Configurations: G -> !X Y Z Macros: A -> 123 B -> 456 Defined switches: X Y Z Open Used Type Trun CRLF Tag->Name no no OUT YES YES P 'P.TXT' no no OUT YES YES Q 'Q.TXT' no no OUT YES YES R 'R.TXT' %CODE_END%

One can use the #SAY word to make CFG2 display a message on the uses console. This can be useful when finding out what the current state of, say, selected switches are or it can be useful just to provide the user of your script with a little "comfort" text. Either way, you just place the message text after the #SAY word and CFG2'll stick it on the console at the appropriate juncture. Note that you can use text expansion in the message text.

To quote an example from the very file that generates the text that you are reading:

%CODE_START% #say Generating index HTML file. %CODE_END%

This merely tells me when CFG2 is actually working on the main HTML file of my web site.

Another, related, word is the #ERROR word. This is similar to #SAY except that it will signal an error condition (and so will cause CFG2 to terminate) and that it'll print out the current source file name and line number:

%CODE_START% #error We've found an error! %CODE_END%

The above line, if run from a file called X.INI, produces:

%CODE_START% CFG2 Version 1.1.194 Error at x.ini(1): We've found an error! %CODE_END% <%HEADC%> Executing Command Lines

It's sometimes useful to be able to execute commands from within a CFG2 script file. The #RUN and #RDRUN words fulfil this role. For example, if we want a listing of script files in the current directory, under MSdos, we could use this:

%CODE_START% #run dir *.ini %CODE_END%

Resulting, on my system, in the following output:

%CODE_START% CFG2 Version 1.1.194 Volume in drive C is HEX Volume Serial Number is 2477-A6E6 Directory of C:\Work\C\TC\Cfg2\z X INI 19 21/02/01 19:55 X.INI 1 file(s) 19 bytes 0 dir(s) 27,951,104 bytes free %CODE_END%

The #RDRUN word is similar except that it captures the system's output and sends it to the current set of open output files:

%CODE_START% #file x x.txt #send * #rdrun dir *.ini %CODE_END% ;----------------------------------------------------------------------------- ;
<%HEADB%> Special Characters <%HEADC%> Comments

If you've read all of the preceding part of the tutorial, you've already seen examples of how to put a comment in CFG2 script source. The way to do it, of course, is to use a semi-colon to introduce a comment;; anything up to the following end-of-line is ignored by CFG2. Here is a selection of lines, some of which are comments:

%CODE_START% Line one: text that will be sent to output files. ;; Line two: a line that will be ignored. Line three: another line of output text. Line four: a line ;;;; that *includes* a ';;;;'. Line five: a final line of output text. ;; Line six: a line that *may* produce output. %CODE_END%

The first, third and fifth lines are, of course, plain text that will be sent to all open output files. But what about the second line? Well, that's a comment line and will be ignored by CFG2. Line four is a line of output that includes an instance of the comment character. Therefore, the semi-colon must be "escaped" by using two where, in fact, we only actually want one semi-colon in the output text. So, that leaves line six. Surely, you might think, this line is just another comment line?

Well, it is except that there is a complication: if #EMPTY is on (that is, blank lines are passed through to the output files unchanged), there is a problem with handling lines only contain a comment. A line that is fully commented will always be ignored. Hence, even with #EMPTY enabled, a comment that starts in the first column will produce no output at all. However, consider this:

%CODE_START% ;; A completely ignored line. ;; A line that'll produce a blank line. %CODE_END%

Since this doesn't start in the very first column, CFG2 will strip the comment off and pass the resulting blank line to the code that sends output to files. Therefore, if #EMPTY is on, this comment line will generate a blank line.

<%HEADC%> Text Expansion

After reading a source line, but before the line is processed in any other way, CFG2 searches the line for the "macro" character. In this instance, this character is a per-cent sign. If it finds one, it looks for following macro character and then extracts the text between them. The program then finds suitable replacement text and slaps it in the gap vacated by the macro name. It then re-starts the search for a macro character, start at the first character of the newly-inserted text.

For example, if we want to print out the date and time when CFG2 was used to scan its source file, we could use this:

%CODE_START% #say Program was run on '%%CFG2DATIM%%'. %CODE_END%

If you run this, it'll produce output similar to this:

%CODE_START% CFG2 Version 1.1.194 Program was run on 'Wed Feb 21 16:57:43 2001'. %CODE_END%

In common with the other special characters, you can "escape" the character (that is, prevent it having its special meaning) by using two together. For example, you can add a couple more macro characters like this:

%CODE_START% #say Program was run on '%%%%CFG2DATIM%%%%'. %CODE_END%

Instead of the earlier output, this'll produce:

%CODE_START% CFG2 Version 1.1.194 Program was run on '%%CFG2DATIM%%'. %CODE_END%

Because the program re-scans newly inserted text, nested macro work as well as could be expected. For example:

%CODE_START% #define AAA a%%%%BBB%%%%a #define BBB b%%%%CCC%%%%b #define CCC c #say The first macro expands to '%%AAA%%'. %CODE_END%

This, if run, will produce the following:

%CODE_START% CFG2 Version 1.1.194 The first macro expands to 'abcba'. %CODE_END%

One thing to note, by the way, is use of the escaping mechanism. The above example would not work if the macro characters had not been escaped because the macro expansion would've taken place when the macros were declared. Consider such a version:

%CODE_START% #define AAA a%%BBB%%a #define BBB b%%CCC%%b #define CCC c #say The first macro expands to '%%AAA%%'. %CODE_END%

When AAA is declared, BBB has not been declared so CFG2 will expand the line such that AAA will contain only "aa" as its text. (CFG2 ignores undeclared macros when you try to expand them - it does not produce a error message when this happens.) A similar argument would apply to the following line.

For a full list of the items that can be "expanded", see the special macro names in the relevant section in the reference manual.

<%HEADC%> Line Termination

Lines sent to the list of open output files are terminated in the way that the operating system considers "normal" unless the special line termination character is used. The processing of this character, the under-line ('_'), is unusual in that it is only significant when it occurs as the last character of the script source line. (That is, after comments have been stripped.)

The function of the character is to inhibit the emission of the normal end-of-line sequence. Hence, the following code would generate one line of output text:

%CODE_START% #file Example XXX #send * AAA__ BBB__ CCC %CODE_END%

The results, in file EXAMPLE, would be:

%CODE_START% AAABBBCCC %CODE_END%

As usual, you can escape the special character:

%CODE_START% #file Example XXX #send * AAA___ BBB___ CCC %CODE_END%

This gives, as output:

%CODE_START% AAA__ BBB__ CCC %CODE_END%

There is, however, a complication. Using this scheme, one can generate lines that end with a normal termination sequence. One can generate lines that are normal and end with an under-line. Finally, one can generate lines that have the normal termination sequence inhibited and do not end with an under-line. One cannot, though, (easily) generate lines that have the termination sequence inhibited and end with an under-line.

So far, thankfully, I've never wanted to generate such a line whereas I quite often wanted to generate a normal line ending with an under-line. (It has to be said that I usually want to generate a line ending with an under-line while writing the documentation for CFG2 - I use CFG2 to generates its own documentation...)

You will find a good example of the inhibition of line termination in a later code example.

<%HEADC%> Tab Expansion

In common with many text handling programs, CFG2 will expand all "tab" (ASCII 0x09) characters it finds into a number of spaces. The number of spaces that it'll use depends upon the current "tab-stop" settings.

By default, it will use a tab-stop of eight characters. This can be changed by using the #TAB word. For example, to set the tab-stop back to the default, you would use this:

%CODE_START% #tab 8 %CODE_END% ;----------------------------------------------------------------------------- ;
<%HEADB%> Examples <%HEADC%> Our Web Site

Please note that most of the documentation for the ChAoS utilities and HTML files on the %RAWSITE% web site are generated using CFG2. As a result, for examples of really large CFG2 script files, you could do worse than have a rummage about on that site.

<%HEADC%> Margin Expressions

This is an example of a simple margin expression. The following lines have been extracted from the CFG2 script that I use to configure the files on my main development machine:

%CODE_START% #switch !2940scsi ;; Adaptec 2940 SCSI driver. #switch !lean ;; REALLY stripped down mode. #switch !ice ;; Soft Ice. %CODE_END%

Note that I usually prefer to have all switches off by default. The file continues:

%CODE_START% #switch lice lean ice | %CODE_END%

This little bit of code merely creates a short-cut for the expression "lean ice |". (That is, the expression "switch LEAN is TRUE or switch ICE is TRUE".) Note that in this last code fragment, the margin has been increased from eight to sixteen characters:

%CODE_START% !lice 2940scsi |c:\bin\ecs\mscdex __ !lice /d:ecscd003 __ 2940scsi /d:mscd001 __ !lice 2940scsi |/e /L:r %CODE_END%

This code uses separate expressions for each line of the file. Hence, any of these lines could be discarded depending upon the settings of the three switches, LEAN, ICE and 2940SCSI.

Note that the "or" symbols are in the very last column of the margin - the characters after the "or" symbols are, in fact, part of the data that may (or may not) be sent to the set of currently open files.

<%HEADC%> Boot Disk Config

The following is the CFG2 script that I use on my MSdos boot floppy. It provides only two switches that the user can use: CD and DELETE. By default, it'll make CFG2 generate an AUTOEXEC.BAT and CONFIG.SYS for my floppy with no CD-ROM support. However, if one adds the "CD" switch onto the CFG2 command-line, then the relevant lines magically appear!

If one cites the DELETE switch on the command-line, it'll remove the two files leaving zero-length "stub" files instead - it leaves stubs so that, on boot, MSdos doesn't start asking for the date and time and suchlike. (Please note that I've removed all formatting and comment text and, instead, provided replacement comments for tutorial purposes.)

First, we actually declare the two switches that we need, Note that both default to an off state:

%CODE_START% #switch !delete #switch !cd %CODE_END%

In this case, we only have two output files - the AUTOEXEC.BAT batch file and the corresponding CONFIG.SYS file (I've changed their names so that the script won't overwrite your start-up files if you accidentally run it in the root of your boot drive):

%CODE_START% #file autoexec autoexec.bax #file config config.syx %CODE_END%

Quite often, when using CFG2 to generate these MSdos system files, I find that the margin expressions can become long enough to require a wider margin. In this script file, it's not really necessary, but I do it anyway just to be consistent:

%CODE_START% #margin 16 %CODE_END%

Now, we want to generate the CONFIG.SYS file. So, we #SEND to it:

%CODE_START% #send CONFIG %CODE_END%

Next, we send the required output to the file. Note that only one line has a margin expression and that's the line that should only go to the CONFIG.SYS file if we are generating a CD-ROM enabled system:

%CODE_START% device=himem.sys /testmem:off dos=umb CD device=fdatacd.sys /d:mscd000 lastdrive=z %CODE_END%

That's the CONFIG.SYS file wrapped up. Now, we close the old file and open the AUTOEXEC.BAT instead:

%CODE_START% #close * #send AUTOEXEC %CODE_END%

Here, we send the output to the AUTOEXEC.BAT file, Once again, there's only a single line of text that is dependant upon the switch settings:

%CODE_START% @echo off mode con: rate=30 delay=1 CD lh mscdex /d:mscd000 /e /L:r lh doskey /insert %CODE_END%

Of course, when we are finished with the AUOTEXEC.BAT file, we close it:

%CODE_START% #close * %CODE_END%

The rest of the script is dedicated to providing the delete facility. To recap: if the DELETE switch is present, then we want to leave dummy files for MSdos to find when it boots. Now, we could have had a great big IF-ELSE-ENDIF construct around the previous code so that no output was generated if the delete option was selected. However, in large script files, it's usually easier to let the rest of the process run as normal and, if the delete switch is set, merely delete everything and re-generate the files but this time with zero content. So, is the delete switch set?:

%CODE_START% #if delete %CODE_END%

Now, we need to delete all the files generated so far:

%CODE_START% #delete * %CODE_END%

That done, we need to open all the output files. Closing them straight away will leave them zero-length:

%CODE_START% #send * #close * %CODE_END%

It doesn't hurt to tell the user what's happened:

%CODE_START% #say #say Deleted the configuration files having left dummy #say %%AUTOEXEC%% and %%CONFIG%% files. %CODE_END%

Finally, we have to provide a matching #ENDIF for the above #IF:

%CODE_START% #endif %CODE_END%

That completes the script file. For completeness, we present the full text of the script file and the the results you'd get.

The full script of the file source looks like this:

%CODE_START% #switch !delete #switch !cd #file autoexec autoexec.bax #file config config.syx #margin 16 #send CONFIG device=himem.sys /testmem:off dos=umb CD device=fdatacd.sys /d:mscd000 lastdrive=z #close * #send AUTOEXEC @echo off mode con: rate=30 delay=1 CD lh mscdex /d:mscd000 /e /L:r lh doskey /insert #close * #if delete #delete * #send * #close * #say #say Deleted the configuration files having left dummy #say %AUTOEXEC% and %CONFIG% files. #endif %CODE_END%

On my machine, when I use:

%CODE_START% CFG2 -FX %CODE_END%

The AUTOEXEC.BAT and CONFIG look like:

%CODE_START% @echo off mode con: rate=30 delay=1 lh doskey /insert %CODE_END%

And:

%CODE_START% device=himem.sys /testmem:off dos=umb lastdrive=z %CODE_END%

If I use a command-line citing the CD switch instead:

%CODE_START% CFG2 -FX CD %CODE_END%

I get:

%CODE_START% @echo off mode con: rate=30 delay=1 lh mscdex /d:mscd000 /e /L:r lh doskey /insert %CODE_END%

And:

%CODE_START% device=himem.sys /testmem:off dos=umb device=fdatacd.sys /d:mscd000 lastdrive=z %CODE_END%

Of course, using this:

%CODE_START% CFG2 -FX DELETE %CODE_END%

Results in two empty files being generated.

<%HEADC%> Path Variables

One area where CFG2 really helps is in the creation of the MSdos PATH environment variable. This usually has to change depending upon the system setup. You can avoid having to go through MSdos' "menu" system at start up by using CFG2 to generate your AUTOEXEC.BAT file.

Consider the following example - this is one instance, by the way, where the comment escaping mechanism is required since the items in an MSdos PATH environment variable are separated by semi-colons which is CFG2's comment character:

%CODE_START% #switch !perl #switch !win #switch tse #switch tc #file x x.txt #send * path %%%%path%%%%;;;;__ c:\bin\usr;;;;__ tse c:\bin\tse;;;;__ perl c:\lang\perl;;;;__ win c:\windows;;;;__ tc c:\lang\tc\bin;;;;__ c:\bin\dbase %CODE_END%

Here, we have set our PATH up with various items being optional, depending upon the current set of selected switches. In addition, note that all except the last line terminate in an under-line because, when fully "constructed", the command to change the PATH must be all on one line.

If you run this script file, you'll find that, by default, it'll generate a file containing this single line of text:

%CODE_START% path %%path%%;;c:\bin\usr;;c:\bin\tse;;c:\lang\tc\bin;;c:\bin\dbase %CODE_END% ;----------------------------------------------------------------------------- ; #close * ;=========================================================================== #say Generating Reference HTML file. #send CFG2REFhtml ;----------------------------------------------------------------------------- ; %TITLE% - Reference Manual <%HEADA%> %TITLE% - Reference ;----------------------------------------------------------------------------- ;
<%HEADB%> Contents ;----------------------------------------------------------------------------- ;
<%HEADB%> Command-Line Options

These are the command-line options that you can use. Please note that while these options are "applied" at program start-up, the user-specified switches and configs are applied at a later time. Note that options and settings can be freely mixed - the program can distinguish between them and makes sure that the correct item is applied at the right time.

In fact, a couple of things can force the command-line settings to be applied. Usually, they will be applied when the program has to do its first expression evaluation. However, use of a #SET word will also cause the settings to be applied.

Thus, if there is an error in the set of items cited on the command-line, it may not be reported as an error by the system until is has reached a good way into the script's source files.

#redef OptName D #redef OptGrammar #includ OptItemSrc This will enable full debugging diagnostic output. You will get lots of drivel from CFG2 if you use this option. Although mainly used for debugging of CFG2 itself, the output can be useful in debugging complicated script files. Note that this option will automatically enable the verbose and source trace options. #redef OptName F #redef OptGrammar #includ OptItemSrc This allows you to specify the name of the "root" script file. By default, this name is made up of the root program name plus the default source file extension. #redef OptName H #redef OptGrammar #includ OptItemSrc Use this to get a large help "screen" that will give you the main limits and defaults of the program. That help page, rather than this document, should be taken as the "gospel" as far as default and limits are concerned since the page is generated by the program's internal data and constants - it is not just a hard-coded help page. #redef OptName P #redef OptGrammar %<%path_name%>% #includ OptItemSrc With some CFG2 scripts, especially if they are being used to generate, for example, your system's AUTOEXEC.BAT and CONFIG.SYS files, it can be pretty inconvenient if the program over-writes your current configuration files before you've perfected the script. (Overwriting the previously-mentioned files can, for example, stop your system from re-booting properly.) Therefore, it'd be nice if you could test your scripts in a temporary directory before unleashing it on the real world. That is the purpose of this option - the given path name is added to the start of every file opened or created by the program. #redef OptName T #redef OptGrammar #includ OptItemSrc If enabled, this option will make CFG2 display each non-empty line of source as it is processed. If the use of macros causes the expanded line of text to be different from the original, then the expanded version will also be displayed. #redef OptName V #redef OptGrammar #includ OptItemSrc The "verbose" option causes the program to emit rather more "comfort" messages than it usually does.
;----------------------------------------------------------------------------- ;
<%HEADB%> Pre-Defined Items

The following are the items, of whatever sort, that have "special" names. You should consider these names as "reserved words". Therefore, for example, you should not declare a config called "ALWAYS" unless you really want it to be automatically executed. In fact, these pre-defined names will take priority over any item declared with the same name.

#redef ResName ALWAYS #includ ResItemSrc This is the name of the config that is run before any user-specified configurations are applied. Please note that the "always" config is run before the "default" config. (The latter is only run if no command-line items have been specified, of course.) #redef ResName DEFAULT #includ ResItemSrc This configuration is run if the user has specified no command-line switches or configs at all. Note that this config runs after the "always" config. #redef ResName CFG2PROGNAME #includ ResItemSrc When cited, this macro will expand as the name and version number of the currently running CFG2 program. #redef ResName CFG2PARAMS #includ ResItemSrc This macro will expand to contain the switches and configs that the user specified on the command-line. Note that this is not necessarily the same set as those that are currently on. #redef ResName CFG2SWITCHES #includ ResItemSrc This macro will produce a list of the switches that are currently enabled. #redef ResName CFG2SOURCE #includ ResItemSrc This macro holds the name of the current source file. #redef ResName CFG2DATE #includ ResItemSrc Use this when you want to get hold of the current date. The format used is a numeric YYYYMMDD format. #redef ResName CFG2TIME #includ ResItemSrc Use this when you want to get hold of the current time. The format used is a numeric HHMMSS format. #redef ResName CFG2DATIM #includ ResItemSrc This macro produces the current date and time. The format is dependant upon the compiler used to compile CFG2. In general, though, it will be in a "plain text" format. (That is, it'll be in a non-numeric format convenient for people to read.)

;----------------------------------------------------------------------------- ;
<%HEADB%> Directives

CFG2 classes incoming lines of source text as one of two types - either it's "special" or it's "normal". After macro expansion has taken place, the program checks whether the word, starting in the first column of text, is a directive. If it is, then this line of text is special otherwise it is classed as "normal".

Assuming that the current line is special, the word is checked to see if it is a word that affects the IF Stack - viz, one of #IF, #ELSE or #ENDIF. The IF Stack holds the current state of the answer to the "do we want this line?" question. If the word is one of these words, then the state of the IF Stack is altered to reflect the new status.

If the word does not affect the IF Stack, then the state of the IF Stack is interrogated to see whether the line should be discarded. Assuming that we do want this line, then the directive will be de-coded and any necessary actions carried out.

If the current line just contains normal text, then different processing is applied. The first thing that is done is that the state of the IF Stack is, once again, interrogated. If the current state is FALSE, then the line of text is discarded.

Assuming that we do want to process the line, then the first few characters of the line of text are stripped off. (The size of this is set by the #MARGIN word.) If there is an expression in this margin and it evaluates to FALSE then, once again, the line would be discarded. Otherwise, the line of text (minus the text in the margin) is sent to all currently opened output files.

#redef WrdName CLOSE #redef WrdGrammar %<%wildcard_tag_name%>% ... #includ WrdItemSrc

Having used the #SEND word to create some file output, one must, of course, have a way to close those files. This is what this word does. Note that, once again, this word accepts a wild-carded tag name and, thus, one can close all files at the same time by using a single asterisk.

Also, please bear in mind that a file can be opened and closed more than once throughout the time that the script runs. This can be used to limit the number of concurrently opened files if this is likely to be a problem.

All files are automatically closed at program termination.

#redef WrdName CONFIG #redef WrdGrammar [!]%<%switch_name%>% ... #includ WrdItemSrc

Configs are merely a convenient way of setting a large number of switches without having to use a correspondingly large number of #SET words. When the config's name is cited by use of a #SET word or on the command-line, the corresponding setting (either on or off) is "rippled down" to all the cited switch or config names. Note that it is legal for a config to cite another config name. Hence, one can create a scheme whereby the setting of a single config can have wide-reaching effects upon the setting of all of the system's switches.

There are two special configs. The first is always run at system startup before any user-specified command-line settings are applied. The second is run only if there are no command-line user settings. (Thus, in the latter case, the default setting is applied after the "always run" settings.)

#redef WrdName DATA #redef WrdGrammar %<%tag_name%>% %<%input_file_name%>% #includ WrdItemSrc

A file declared using the #DATA word operates in a similar manner to a file declared using the word #INCLUD except that, when the file is read, it is not scanned for special directive words - it is treated as a "raw data" file and the file's contents sent to the output stream in its entirety. Note that a file declared using this word must be read using the #READ word.

#redef WrdName DEFINE #redef WrdGrammar %<%macro_name%>% [%<%macro_text%>%] #includ WrdItemSrc

CFG2's macro facility operate in a similar, though more limited, manner to those of the "C" language. One nominates a tag name and, optionally, a piece of text. When the tag is referenced in the script source, then the macro's text will be substituted instead. Note that after the substitution has been done, the new text is scanned for further macro substitutions.

In order to cite a macro, by the way, you have to enclose the macro tag name in two per-cent signs. If you want a per-cent sign in your output text, then use two together to represent each per-cent sign that you want.

One might wonder why the macro text is an optional parameter. The reason is that macros can be #REDEFined at a later time. Thus, one could #INCLUDe a script that takes parameters in the form of the currently defined set of macros.

#redef WrdName DELETE #redef WrdGrammar %<%wildcard_tag_name%>% ... #includ WrdItemSrc

This word is used to delete files - it really will! Note that this word also takes a wild-card tag name and thus can delete multiple files in one command. However, you can only #DELETE files which have been declared using the #FILE word. In other words, you can only #DELETE output files.

If this is a problem, remember that one can declare the same file using different file tags.

#redef WrdName DUMP #redef WrdGrammar #includ WrdItemSrc

This word will dump out all the currently defined macros, switches and configs.

#redef WrdName ELSE #redef WrdGrammar #includ WrdItemSrc

The #ELSE word must follow a corresponding #IF word and be followed by an associated #ENDIF. This word "toggles" the state of the flag that indicates whether the script stream should be "seen" by CFG2. Hence, if the expression of it's #IF evaluates to TRUE, then the text following the #ELSE will be ignored and vice versa.

#redef WrdName EMPTY #redef WrdGrammar %<%integer_number%>% #includ WrdItemSrc

By default, CFG2 ignores blank lines in the incoming data stream. In other words, blank lines in the script source are ignored unless a non-zero value is specified with this word. If you do this, then all blank lines encountered will be sent on though to the current set of open output files.

#redef WrdName ENDIF #redef WrdGrammar #includ WrdItemSrc

This word terminates a corresponding #IF or #ELSE.

#redef WrdName ERROR #redef WrdGrammar %<%error_message%>% #includ WrdItemSrc

If the program "executes" this word, it will displayed the specified error message text and then behave as if it had detected an error in the script source. It will tell the user which line and source file it is currently reading and then exit returning a "bad" status code to the operating system.

One would use this word to, for example, tell the user that they have selected an illegal combination of "set" switches.

All normal macro processing is done before the message is displayed so one can place macro names in the message text and have it expanded before being displayed on the screen.

#redef WrdName FILE #redef WrdGrammar %<%tag_name%>% %<%output_file_name%>% #includ WrdItemSrc

The #FILE word associates a file tag with the "real" file name as required by the operating system. The real file name is only ever cited in a #FILE "definition" - elsewhere, the file is only ever referenced by its tag.

The same physical file can be referred to by different tags. For example, files come in three varieties: "output", "input" and "source". One uses the #FILE word to declare output files, the #DATA word to declare input files and the #SOURCE word to declare source files. The point is that CFG2 can generate a source file and read it in, usually with some sort of defined or redefined parameter, at a later stage in the script.

However, in order to to this, one must open it using the #SEND word when generating the file, but use the #INCLUD word when actually executing the file.

#redef WrdName IF #redef WrdGrammar %<%expression%>% #includ WrdItemSrc

This word operates in a way akin to the similarly-named "C" construct. The #IF word takes an expression and, if it is TRUE, it will allow the following lines to executed by CFG2. If the expression evaluates to FALSE, then the following lines are ignored. The construct is terminated by a corresponding #ELSE or #ENDIF.

Note that #IF-#ELSE-#ENDIF groups can be nested several deep.

#redef WrdName INCLUD #redef WrdGrammar %<%wildcard_tag_name%>% ... #includ WrdItemSrc

Use this word in exactly the same way as the #READ word except that the file read in is treated as if the user had physically included the file using a text editor. Unlike the #READ word, any file that is #INCLUDed will have directives obeyed.

As one might expect, the order of file inclusion depends upon the order in which the files were declared. In general, #INCLUD the files one at a time if the inclusion order is important.

#redef WrdName MARGIN #redef WrdGrammar %<%integer_number%>% #includ WrdItemSrc

Use this word to set the width of the CFG2 "margin". Text within this margin is considered not to be part of the text to send to the current set of open files, but as a refuge for the line-by-line switch expression.

Note that a margin value of "one" is valid and will result in all non-special word text being sent to all currently open files.

#redef WrdName RDRUN #redef WrdGrammar %<%system_command_line%>% #includ WrdItemSrc

This directive operates in a similar manner to the #RUN word except that it captures the output from the command and #SENDs it to all the currently open output files.

#redef WrdName READ #redef WrdGrammar %<%wildcard_tag_name%>% ... #includ WrdItemSrc

Use this word when you want to include raw data into the output stream with no interpretation at all. Thus, any directives in a file that is #READ will be ignored. The file #READ must be an input file.

Just as with the #SEND word, one can #READ more than one file with a single command. Be careful, though as the order in which the files are #READ depends upon the order in which they are declared.

#redef WrdName REDEF #redef WrdGrammar %<%macro_name%>% [%<%macro_text%>%] #includ WrdItemSrc

This word has the same effect as the #DEFINE keyword except that the cited macro tag name must have already been created. This word allows you to change the value of a macro's text whereas the #DEFINE word allows you to create a macro but will issue a warning if you use it to change the value.

#redef WrdName RUN #redef WrdGrammar %<%system_command_line%>% #includ WrdItemSrc

This directive executes an operating system command. For example, one could create a set of files using the CFG2 script language and, at the end of the script, execute a system command that copies them all to some other directory.

A separate directive, #RDRUN, allows you to #RUN a command and capture the resulting output to all currently opened output files.

#redef WrdName SAY #redef WrdGrammar %<%user_message%>% #includ WrdItemSrc

This word merely displays the user-supplied text on the screen. Note that as with the #ERROR word, macro names will be expanded before the text is displayed. (This latter fact can be used to your advantage since it allows #SAY to be useful in debugging CFG2 scripts.

#redef WrdName SEND #redef WrdGrammar %<%wildcard_tag_name%>% ... #includ WrdItemSrc

This is how you get CFG2 to send data to a file. Following the #SEND directive must be one or more file tag names. Note, though, that they can be wild-card tag names. That is, they can contain question-marks ('?') and asterisks ('*') denoting, respectively, any single character and any sequence of zero or more characters.

Any number of files can have data sent to them subject, of course, to operating system constraints. Files can be opened and closed any number of times. The first time a file is opened, however, it is truncated if it already exists.

#redef WrdName SET #redef WrdGrammar [!]%<%switch_or_config_name%>% ... #includ WrdItemSrc

The #SET word is one of three ways that you have of changing the value of a switch. (The other two are by creating a derived switch and by citing a switch or config name on the program command-line.)

If the switch name is cited with no preceding "not" sign, then the cited switch will be altered to the "on" state. If the name is preceded by a "not" sign, then the switch will be turned "off".

Note that one can cite a config name as one of the set of parameters to a #SET command. If you do this, then each item in the config's associated list of names is #SET in turn. Note that if you try to "turn off" a config name, then the "turn off" command will ripple down to all the names cited in the config's list.

#redef WrdName SHOW #redef WrdGrammar #includ WrdItemSrc

Invoking this word will make the program display all the currently set switches. (If you want to see all currently defined switches, use the #DUMP word, instead.)

#redef WrdName SOURCE #redef WrdGrammar %<%tag_name%>% %<%source_file_name%>% #includ WrdItemSrc

A file declared using this word operates in a similar manner to a file declared using the word #DATA except that, when the file is read, it is scanned for special directive words - in other words it is the CFG2 equivalent to the "C" #INCLUDE construct. Note that a file declared using this word must be read using the #INCLUD word.

#redef WrdName SWITCH #redef WrdGrammar [!]%<%switch_name%>% [%<%expression%>%] #includ WrdItemSrc

This word is used to declare switches. Switches come in two "flavours": simple and derived.

In order to declare a simple switch, you use the #SWITCH keyword and follow it by a switch name. This will declare a simple switch that is on by default. If the switch name is immediately preceded by a "not" sign (an exclamation mark), then the switch will be off by default. Simple switches can only have their values altered by command-line parameters or by use of the #SET keyword.

Unlike simple switches, "derived" switches can be set neither by use of the #SET word nor by command-line parameters. As its description implies, the switch's value is derived from the supplied switch expression. Derived switch values are re-calculated each time that they are required.

#redef WrdName TAB #redef WrdGrammar %<%integer_number%>% #includ WrdItemSrc

This word is used to set the width of the "tab stops" in the source script. CFG2 will expand tabs in the incoming text by replacing them with the correct number of spaces.

;----------------------------------------------------------------------------- ;
;----------------------------------------------------------------------------- ;
<%HEADB%> Expressions <%HEADC%> Reverse Polish Notation (RPN)

There are three parts of the CFG2 syntax that require an "expression" to be entered: the #IF word, #SWITCH word and the margin area of "normal" text lines. In each case, the syntax of these expressions is the same.

The expressions syntax (almost) follows the RPN scheme. In this syntax, the operands come first and the operator comes last. In the case of CFG2, the operands are always non-derived switches. The operators are all Boolean ("logical") operators allowing the normal set of "and", "or" and "not" operations to be carried out upon the operands.

The RPN convention was adopted because it avoids the need for parentheses and operator precedence rules. Also, it's very easy to implement. As the expression is read, operands are placed upon a local stack and operators are applied to the top items of the stack. If at the end of the expression evaluation, you do not end up with a single item upon the stack, then the expression's syntax is at fault. (Similarly, an empty stack and full stack situation signals, respectively, a syntax error and an expression that is too complex to handle.)

There is one slight departure from true RPN convention. If you wish to invert the value of a switch, then one can preceded the tag name by a NOT operator and it will still be applied. The handling of this syntax has to be done by other parts of the program and employing this RPN extension makes simple expressions a little easier to follow.

The operators and associated syntaxes are as follows:

#redef ExpName AND #redef ExpGrammar %<%expression%>% %<%expression%>% %&% #includ ExpItemSrc The AND operator takes the top two items off of the expression stack and does a logical "and" operation upon them. Note that, unlike "C", there is no "short circuiting" of the evaluation process if the first operand taken off of the stack is FALSE. #redef ExpName OR #redef ExpGrammar %<%expression%>% %<%expression%>% | #includ ExpItemSrc The OR operator takes the top two items off of the expression stack and does a logical "or" operation upon them. Note that, unlike "C", there is no "short circuiting" of the evaluation process if the first operand taken off of the stack is TRUE. #redef ExpName XOR #redef ExpGrammar %<%expression%>% %<%expression%>% ^ #includ ExpItemSrc The XOR operator takes the top two items off of the expression stack and does a logical "exclusive or" operation upon them. #redef ExpName NOT #redef ExpGrammar %<%expression%>% ! #includ ExpItemSrc The NOT operator takes the top item off of the expression stack and does a logical "not" operation upon it. #redef ExpName JUSTONE #redef ExpGrammar %<%expression%>%... JUSTONE #includ ExpItemSrc The JUSTONE operator takes all of the items off of the expression stack. It keeps track of how many of the removed items were TRUE. When the last item has been removed, it returns TRUE if the count of TRUE items is exactly one. (This operator is used to valid a set of switches which should all be mutually-exclusive.) #redef ExpName LAST #redef ExpGrammar = #includ ExpItemSrc Strictly speaking, this is not part of the expression syntax. However, since the only legal place that it can appear is where an expression could, this seams as good a place as any to document it. The same-as-last-expression "operator" is only allowed a the normal text line margin. If it appears, it means "use the results of the last margin expression evaluated". This allows a single margin expression to control the insertion of several lines of output without the need to repeat the same expression.
<%HEADC%> Example Expression One

The following code will only insert the Sound Blaster stuff if the SB switch is enabled. In addition, if the Sound Blaster code is required and we are configuring the system for games playing (that is, switch GAMES is on), then, in addition, we want the system to set an extra environment variable (viz, DMXOPTIONS) and run the DIAGNOSE and SB16SET programs. Note the use of the "last" operator to remove the need for repeating the two expressions:

%CODE_START% sb set sound=c:\bin\sb16 = set blaster=A220 I5 D1 H5 P330 T6 = set midi=synth:1 map:e sb games & set dmxoptions=-phase = c:\bin\sb16\diagnose /s = c:\bin\sb16\sb16set /p /q %CODE_END%

The above piece of code could equally well have been written using a couple of IF-ENDIF constructs:

%CODE_START% #if sb set sound=c:\bin\sb16 set blaster=A220 I5 D1 H5 P330 T6 set midi=synth:1 map:e #if games set dmxoptions=-phase c:\bin\sb16\diagnose /s c:\bin\sb16\sb16set /p /q #endif ;; GAMES #endif ;; SB %CODE_END%

Please note the comments placed at the end of the #ENDIF words. When nesting IF-ELSE-ENDIF lines, it's sometimes useful to comment where each sub-construct ends. (Also, notice that, unlike in "C", you cannot indent the keywords.)

<%HEADC%> Example Expression Two

Let's say that we want to check to make sure that exactly one of two switches is set. We should disallow the situation whereby lines. In each case, the syntax of these expressions is the same.

neither or both are set. This should do the trick:

%CODE_START% #switch !SWA ;; The user *has* to select *one* of the switches #switch !SWB ;; to avoid provoking the following error message. #if SWA SWB ^ ! #error Either SWA *or* SWB must be set, but not both. #endif %CODE_END%

Note that the XOR of two operands is TRUE if exactly one of the operands is TRUE.

As usual, an alternative coding is possible. The #IF expression could equally well (although less lucidly) be coded as:

%CODE_START% #if !SWA SWB & SWA !SWB & | ! %CODE_END%

Note that when using the syntax of placing the not sign before switch tag names, no intervening space is allowed;; if you do put a space after the not sign, it's interpreted as a "stand alone" not sign and will be applied to any preceding expression.

;----------------------------------------------------------------------------- ;
<%HEADB%> Special Characters <%HEADC%> Comments

If you want to embed comments in CFG2 scripts, use the semi-colon character (';;'). The text from the semi-colon until the end of the current line is ignored by the program. For example:

%CODE_START% #file FRED fred.txt ;; This is a comment. %CODE_END%

If you want a semi-colon character to be treated as any other character, you can "escape" it by using two semi-colons next to each other. CFG2 will treated this pair of characters as a single semi-colon character:

%CODE_START% #file FRED fred.txt ;; This is a comment. #send * ;; This is a comment and will be ignored. ;;;; These lines are *not* comments and ;;;; will be sent to FRED complete with ;;;; *single* leading semi-colon. %CODE_END% <%HEADC%> Text Expansion

CFG2 has macro capabilities. Any text between a pair of macro characters - a per-cent sign ('%%') - will be "expanded". After this expansion has been done, the process of checking for a macro character is resumed, starting at the first character of the expanded text.

In a similar manner to the comment character, the macro expansion character can be "escaped" by using two per-cent signs next to each other. For example:

%CODE_START% #define AMACRO Some macro text #say The *expanded* macro text is "%%AMACRO%%". #say The *escaped* macro name is "%%%%AMACRO%%%%". %CODE_END%

This will produce output similar to:

%CODE_START% CFG2 Version 1.1.194 The *expanded* macro text is "Some macro text". The *escaped* macro name is "%%AMACRO%%". %CODE_END%

The text between the macro characters is checked for a matching name to one of several items. These items are checked in this order:

If a match is found, then the text substitution is done as detailed above.

<%HEADC%> Line Termination

Usually, the programmer requires that each line of output text be terminated by the standard operating system line termination characters. (For example, under MSdos-compatible systems, this sequence is ASCII CR-LF, 0x0D-0x0A.) Sometimes, one wants to inhibit this behaviour. The special character used for this purpose is the under-line character ('_').

CFG2 only applies special processing to under-lines characters if, after comment stripping has taken place, they are at the end of the CFG2 source line. For example, some of the following lines will provoke CFG2 to inhibit the normal end-of-line termination sequence - some will not:

%CODE_START% #file BILL bill.txt #send * Some text with normal end-of-line characters. Some text with the end-of-line inhibited.__ ;; In the following line, the under-line isn't ;; at the *end* of the source line and so the ;; line will terminate normally: Some _ more _ *normal* _ text. %CODE_END%

As with the other "special" characters, using two together will inhibit this behaviour. Alas, there is a small limitation of which you must be wary. You can generate normal lines. You can generate lines that have termination inhibited. You can generate lines that have normal termination that end with an under-line (by using two together). However, you can't (easily) generate lines that end with a physical under-line and that have termination inhibited.

Thankfully, so far at any rate, I've never wanted to do this... (I suppose that one could always arrange for the trailing under-line to be generated by the following piece of script instead.)

;----------------------------------------------------------------------------- ; #close * ;=========================================================================== #delete *ItemFil ; We've now finished with the temporary source files. ;=========================================================================== #if MAKECLEAN #say #say Making clean - all generated HTML files will be deleted. #delete * #say Making clean - complete. #endif