Whiling Away in 4DOS by Marshall E. Giguere November 17, 1991 If you're familiar with the constructions supported by structured programming languages you'll be used to having available things like "while", "repeat" etc. in the language. This is not, however, the case where DOS COMMAND.COM and 4DOS are concerned. DOS has no support for any structured language constructions and 4DOS supports only "if/then/else" constructs. Although neither language supports the "while" construct a "while" can be simulated, and in the case of 4DOS formalized into a convention supported by a couple of simple aliases. That's what this little article is about. What to do When You don't have a WHILE! If you're working with a language like 4DOS that doesn't have a "while" you can simulate the behavior of a "while" by remembering the following things about a while: * logical testing happens at the top of the loop. * the loop only executes if the test is TRUE. * at the end of the code block you always branch back to the top. With these three simple rules you can write a "while" loop using nothing but "if" and "goto" (yes, the dreaded & dangerous GOTO). The result is less than appealing, and is certainly confusing to the eye of the reader. If, God forbid, you're working in DOS COMMAND.COM and you need to repeat a section of code over which some condition holds true the result is truly horrific. Let's say you need to iterate over a block of code while the variable "i" is less than ten, here's what the DOS COMMAND.COM equivalent would look like: :loop if %i GT 10 GOTO endloop . . do something interesting set i=%@eval[%i + 1] . . goto loop :endloop Notice that it requires the creation of two labels to make an equivalent "while" in DOS COMMAND.COM. You can achieve the same result in 4DOS at the expense of only one label with an "if/then" (see below). :loop iff %i LT 10 then . . do something interesting set i=%@eval[%i + 1] . . goto loop endiff *NOTE: it is assumed that "i" is initialized to some reasonable value before entering the loop. You'll notice immediately that the resulting 4DOS code is a bit easier on the eye and saved one label. The above code still, to my mind, does not convey the directly fact that an iteration is in progress until one has scanned the entire block of code up to the "goto loop" statement, it's still just an "if" statement. As you can see we can achieve in effect the semantics of a "while" using nothing but "if" and "goto". It is also possible to do the same for the "repeat/until" or "do/while" constructs by moving the logical tests to the bottom of the loop, that is left as an exercise for the reader. The next logical question is "how can we have the syntax and the semantics of iteration in 4DOS", and that is the subject of the next section. WHILE You Wait Before we begin constructing of a "while" language element for 4DOS I should probably discuss, briefly, why go through this exercise anyway, and discuss some design goals to be achieved. Then we can proceed directly to the implementation of the WHILE construct. Why invent a WHILE language element at all is probably the question on your mind. After all in the preceding section I proved conclusively that a WHILE can be simulated with the basic language elements "testing" and "branching". Isn't that sufficient? The answer is yes it's sufficient, but no, it doesn't convey the direct semantics of the operation "while". We must examine the "if/goto" code block in some detail to determine that an iteration on some condition is in effect. With a "while" construction the semantics of the conditional iteration are readily apparent, i.e. the syntax and the semantics are in harmony. By this I simply mean that the conveyed and computed meaning of a "while" construction are identical. Whenever possible programming language designers have an obligation to assure the user that the conveyed and computed semantics are in harmony. Having established that linguistic harmony is a reasonable and useful goal let's look at the implementation goals next. Implementation of a "while" construction for 4DOS should encompass a few simple goals and ideas: 1. the "while" should be clean as possible. 2. permit the full complement of all possible 4DOS conditionals. 3. be reasonably simple to use. 4. be nestable. The above goals all come into conflict when we consider that in order to implement a "while" it is necessary at the end of the loop to branch back to the beginning. A branch requires that we know in advance where it is intended that the end of the loop branch back to, this can only be resolved through the use of a label statement. The obvious question here is how will such a label be generated, and unfortunately the answer is that you the programmer must generate the label manually. This is the one ugliness I've had to endure for the clarity I desire. The problem then is how to incorporate the labeling into the methodology without creating something really ugly. My goal is still to create something reasonably pleasing to the eye. Since we must declare the branch back label I decided to make a virtue of the necessity and incorporate the requirement into the language elements. Item 2 above is easily accommodated with the 4DOS "iff" which covers a host of possible conditionals. The final result, I hope you'll agree, meets goal 3, simplicity of use. Obviously we need to know two things for the "while" loop to work properly: where does it start and where does it end. These functions are accomplished by introducing two new 4DOS language elements (in the form of aliases) "while" and "endwhile". The "while" alias starts things off be declaring the conditional and bracketing the beginning of the code block to be executed if the condition is valid. The "endwhile" must serve two purposes: force an unconditional branch to the beginning of the loop and act as the closure for when (eventually) the "while" conditional evaluates to false, i.e. this is where you go when the loop is complete. Here are the two aliases for implementing the "while" loop in 4DOS: WHILE=`iff %& then` ENDW*HILE=`goto %1^endiff` As you can see the "WHILE" alias is a disguised "iff" which will swallow any string and treat it as a conditional expression. The "then" clause of the "iff" opens the code block, and this is where you supply any code plus code to modify the conditional variable(s) so the loop reaches closure. The "ENDWHILE" alias takes one argument the name of the label which bounds the top of the loop and supplies the needed "endiff" to allow 4DOS to find the closure point when the "while" conditional finally fails. Obviously just using these aliases as is isn't quite enough, remember I said you had to supply the label to bound the loop. This simply means at the top of the loop you must declare the label that will be used to control the unconditional "goto" in the "endwhile". The label you declare at the top of the "while" and the one in the "endwhile" must be the same. Here's where necessity becomes virtue. By declaring the label at the top and bottom of the "while" some documentation is built into the language, making it obvious which "endwhile" belongs to which "while", this is especially useful in a nested "while" situation. Let's write a simple "while" that prints the numbers from 0 to 10 using the new "WHILE" aliases. @echo off set i=0 :while1 while %i LE 10 echo i = %i set i=%@eval[%i+1] endwhile while1 As you see in the above code the "while" begins with the declaration of the top bounding label ":while1". Next we see the "while" with its conditional expression which states that the loop executes "while" "%i" is less than or equal to ten. The "endwhile" declares the end of the "while" and tells us (and 4DOS) where to go at the end of each pass through the loop, in this case the label ":while1". Sandwiched in between is the code block which is executed on every pass through the loop. The result, I hope you'll agree, isn't too ugly and does what I intended, i.e. conveys the semantics and the syntax of a "while" loop. Restrictions Since the "while" aliases are in fact a 4DOS "iff/then^endiff" all the same restrictions apply to nesting. You cannot nest more that eight levels of "iff" or "while" in any combination. Further to avoid dangling "iff" closures you should close each "iff" with an "endiff". This will prevent 4DOS from becoming clinically confused and leaping as it were to the wrong "conclusion" (endiff). Portability The problem with building language constructs out of aliases/macros is the same faced by code library builders. That simply is that you must distribute your "library" with your code. The same is true here, you must hand-out the "while" aliases with any 4DOS batch file you create that uses them. This can be a painful problem since one tends to forget something that in effect becomes part of the language. This is especially true if a macro fits into the native language so well that you forget it is just that an macro, you take it for granted. All this just goes to say that any batch files you create using the "while/endwhile" aliases will require that the aliases accompany the batch file. Concluding remarks Although the "while/endwhile" isn't perfect meaning labels must be declared the result isn't too ugly and the obligatory labels provide some use as documentation. I tried all kinds of ideas out to rid myself of the necessity of physically declaring the "while" labels, but 4DOS' mechanism for resolving labels is too simple-minded, i.e. 4DOS does a simple textual search with no expansion for labels. This simply means you can't created a computed label, or hide one in an alias. But, in the mean time the affect is achievable with aliases. Maybe someday 4DOS will actually implement a "while" and other structured programming language constructs? The problem that must be addressed by the implementers of 4DOS is the same one I encountered, i.e. how to manufacture and track labels. One is tempted to suggest the use of byte offset signatures to obviate the need to declare labels. Simply put when a "while" is encountered its byte-offset is pushed onto a context stack. When and "endwhile" is encountered it is likewise pushed onto the context stack and the last pushed "while" offset is used to get back to the most resent "while". If the while continues its "endwhile" is popped and execution continues through the loop, if the "while" fails the "while" and "endwhile" offsets are popped and a jump to the statement following the "endwhile" offset is executed. This is a fully reenterant process and may be implemented to any reasonable number of levels. Hey, Rex, it's only a suggestion.