1 Loops

This article is about looping constructs like FOR and WHILE that are known from real programming languages, but not explicitely implemented in HSC.

If you have programmed in LISP (or SCHEME or Amiga InstallerScript—if that's how you call the language) before, it may not come as a big surprise that a macro language like HSC can actually do loops. At first it's a weird thought for those who haven't though: there's not even a GOTO, only seemingly linear expansion of, possibly nesting, macros. It's the nesting of macros that is the key, because when you call a macro from within itself, you get a recursive procedure (I don't call it a function because it can not return a value to its caller in HSC). HSC will happily compile something like

<$macro ENDLESS><ENDLESS></$macro>

...and eventually abort, crash or dump core as soon as you use this, because it gets into an infinite loop and eats all memory it can get before giving up. That's not how recursion is supposed to be. We need a termination condition, upon which the macro stops calling itself. This means we have to pass a parameter from one "level" to the other that tells the macro when to stop. The easiest variant is a counter that terminates the recursion when it is zero:

<$macro RECURSE COUNT:num/R>
  <$if COND=(COUNT > "0")>
    <RECURSE COUNT=(COUNT - "1")>
  </$if>
</$macro>

If you call it as <RECURSE COUNT=5>, it will count down to zero and then exit, producing nothing but a few blank lines, unless you compiled with COMPACT enabled. There should be another parameter to feed the macro some content:

<$macro RECURSE COUNT:num/R CONTENT:string/R>
  <$if COND=(COUNT > "0")>
    <RECURSE COUNT=(COUNT - "1") CONTENT=(CONTENT)>
    <(CONTENT)>
  </$if>
</$macro>

Call it as: <RECURSE COUNT=5 CONTENT="Hello, world!<BR>">:

Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!

There's our loop! Five instances of the content you only typed once. Next, we'll look at counting in intervals, with steps and contents from containers. Maybe you can figure out some of that already.

1.1 User-friendliness: FOR

Certainly, this iterator is not the best we can do. It lacks the ability to count in different directions, different steps, and it can only count to zero. All of this is fairly easy to fix, so let's start with the first two shortcomings as they can be treated with just one extra parameter. Obviously, the line that reads

<RECURSE COUNT=(COUNT - "1") CONTENT=(CONTENT)>

has to be changed not to subtract one all the time but to subtract—or rather, as count-up loops are the more frequent use case, add—a constant we pass in as a parameter. That makes the new RECURSE macro look like this:

<$macro RECURSE COUNT:num/R STEP:num/R CONTENT:string/R>
  <$if COND=(COUNT > "0")>
    <RECURSE COUNT=(COUNT & STEP) STEP=(STEP) CONTENT=(CONTENT)>
    <(CONTENT)>
  </$if>
</$macro>

Now that the count-up case is the normal one, this macro dearly needs an upper limit, or it will count forever unless a negative STEP is specified. Adding this is just as easy:

<$macro RECURSE COUNT:num/R TO:num/R STEP:num/R CONTENT:string/R>
  <$if COND=(COUNT < TO)>
    <RECURSE COUNT=(COUNT & STEP) TO=(TO) STEP=(STEP) CONTENT=(CONTENT)>
    <(CONTENT)>
  </$if>
</$macro>

That's everything you could ask from an iterator macro already! It can count in different directions from a certain start value, you can specify the counting step and the termination value. But it's not very nice to have to specify all this, and to pass the content as a string, which can become quite long.
The issue of container macros has been treated already in the article on HSC and scripting. Read it if you want to avoid confusion while writing your own iterator wrappers, though the following should be obvious. In addition to the features described for the scripting macros, it makes use uf HSC's default parameters for macros, so you don't have to specify START or STEP if the defaults are OK:

<$macro FOR /CLOSE START:num=1 TO:num/R STEP:num=1>
  <RECURSE COUNT=(START) TO=(TO) STEP=(STEP) CONTENT=(HSC.Content)>
</$macro>

Try it:

<FOR START=4 TO=8>
Howdy!<BR>
</FOR>
Howdy!
Howdy!
Howdy!
Howdy!
Howdy!

1.2 Beyond repetition

Now you can wrap arbitrary pieces of text, with HTML formatting, HSC macros or anything you like, in a container macro and have them repeated a certain number of times. Nice as it is, all this counting in steps and forwards and backwards doesn't help much if you can't access the counter in the "body" of your loop. But you can already! Look:

<FOR START=4 TO=12 STEP=2>
Counting: <(COUNT)><BR>
</FOR>
Counting: 10
Counting: 8
Counting: 6
Counting: 4

Oops! Well, nice counting, steps and all, but it's the wrong direction. Have a look at the RECURSE macro again:

<$macro RECURSE COUNT:num/R TO:num/R STEP:num/R CONTENT:string/R>
  <$if COND=(COUNT < TO)>
    <RECURSE COUNT=(COUNT & STEP) TO=(TO) STEP=(STEP) CONTENT=(CONTENT)>
    <(CONTENT)>
  </$if>
</$macro>

Imagine how the expansion takes place when you call it like above. You'll get a result like below if you expand it on all levels and substitute the arguments:

<$if COND=(4 < 12)>
  <$if COND=(6 < 12)>
    <$if COND=(8 < 12)>
      <$if COND=(10 < 12)>
        <$if COND=(12 < 12)>
        </$if>
        Counting: <(COUNT)><BR>
      </$if>
      Counting: <(COUNT)><BR>
    </$if>
    Counting: <(COUNT)><BR>
  </$if>
  Counting: <(COUNT)><BR>
</$if>

That is, the arguments expanded first come from the inner levels of recursion where COUNT is highest. To fix this, it's sufficient to swap the expansion of <(CONTENT)> and the recursion part:

<$macro RECURSE COUNT:num/R TO:num/R STEP:num/R CONTENT:string/R>
  <$if COND=(COUNT < TO)>
    <(CONTENT)>
    <RECURSE COUNT=(COUNT & STEP) TO=(TO) STEP=(STEP) CONTENT=(CONTENT)>
  </$if>
</$macro>

That's it! Now the counting goes the right direction as specified in the FOR. The expansion of <(CONTENT)> before the next <$if>-construction gives you another advantage: you can fiddle with COUNT or other values used in RECURSE from your loop's "body", thus manipulating the loop much like you could do1 in languages like C, e.g. set TO to -1 in a count-up loop to leave it prematurely.

1.3 Putting it to work

In the scripting article I mentioned that the multiplication table I used as an example for PERL scripting can just as well be done in HSC alone, albeit with a couple of macros that are much harder to fully understand. It's now time to rewrite the table using the constructs presented so far. We'll start with the static stuff and two nested loops:


<$macro multtab SIZE:num=5>
  <TABLE border="2">
  <TR>
  <FOR START=1 TO=(SIZE)>
    <FOR START=1 TO=(SIZE)>
    </FOR>
  </FOR>
  </TABLE>
</$macro>

But wait—the counter is called "COUNT" in both loops, so how can the inner loop access the outer's counter? It has to be renamed!

<$macro multtab SIZE:num=5>
  <$define y:num>
  <TABLE border="2">
  <TR>
  <FOR START=1 TO=(SIZE)>
    <$let y=(COUNT)>
    <FOR START=1 TO=(SIZE)>
    </FOR>
  </FOR>
  </TABLE>
</$macro>

This defines the temporary variable "y" that is set to the outer loops's counter value just before entering the inner loop. OK, now for the table headings. The script creates a colored border around the actual table, the first line of which is done in a loop of its own:

<$macro multtab SIZE:num=5>
  <$define y:num>
  <TABLE border="2">
  <TR><TH></TH>
  <FOR START=1 TO=(SIZE)>
    <TH bgcolor="#9090f0"><(COUNT)></TH>
  </FOR></TR>
  <FOR START=1 TO=(SIZE)>
    <$let y=(COUNT)>
    <FOR START=1 TO=(SIZE)>
    </FOR>
  </FOR>
  </TABLE>
</$macro>

The first line is done, now for the actual multiplications. Every line gets a header cell with colored background and the value of y first, then a row of cells with results. And that's it for the table macro! See the result on the right...

<multtab SIZE=10>2
<$macro multtab SIZE:num=5>
  <$define y:num>
  <TABLE border="2">
  <TR><TH></TH>
  <FOR START=1 TO=(SIZE)>
    <TH bgcolor="#9090f0"><(COUNT)></TH>
  </FOR></TR>
  <FOR START=1 TO=(SIZE)>
    <$let y=(COUNT)>
    <TR><TH bgcolor="#9090f0"><(y)></TH>
    <FOR START=1 TO=(SIZE)>
      <TD><(y * COUNT)></TD>
    </FOR>
    </TR>
  </FOR>
  </TABLE>
</$macro>
1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9
2 2 4 6 8 10 12 14 16 18
3 3 6 9 12 15 18 21 24 27
4 4 8 12 16 20 24 28 32 36
5 5 10 15 20 25 30 35 40 45
6 6 12 18 24 30 36 42 48 54
7 7 14 21 28 35 42 49 56 63
8 8 16 24 32 40 48 56 64 72
9 9 18 27 36 45 54 63 72 81

Easy enough if you see it developing, and if you don't have to think about what's happening "under the hood" at the same time. You see, the concept of stepwise abstraction you use in "real" programming can be useful even for a fairly simple macro language. The recursive expansion facility is too weird to use directly, unless you're a die-hard LITHP fan, but properly wrapped you can use it to write something that at least resembles an interpreted programming language and that is no more complicated or hard to write than Perl, Python or—shudder—BASIC.

1.4 Nested loops

Being able to nest loops is definitely a Good Thing—but the method of renaming the counter we had had to use in the previous example is a bit awkward. It should be possible to automate this as well.

What has to be done is to pass in some extra HSC code to ITERATE that does this, and then let FOR generate the necessary instructions from a simple variable name. To do the last step first: let's extend FOR with an extra parameter called VAR. How to define a variable whose name isn't known in advance? Dynamic code generation does the trick:


 1 <$macro FOR /CLOSE START:num=1 TO:num/R STEP:num=1 VAR:string>
 2   <$if COND=(set VAR)>
 3     <* create loop-local variable VAR *>
 4     <("<$define " + VAR + ":num>")>
 5     <* call ITERATE with COUNT-renaming code for current VAR *>
 6     <ITERATE COUNT=(START) TO=(TO) STEP=(STEP)
 7              CONTENT=("<$let " + VAR + "=(COUNT)>" + HSC.Content)>
 8   <$else>
 9     <ITERATE COUNT=(START) TO=(TO) STEP=(STEP) CONTENT=(HSC.Content)>
10   </$if>
11 </$macro>

Line 4 shows the simplest way of using dynamically generated code there is: it contains HSC's "insert expression" construct <(...)> with a string expression that builds a $define-tag using the variable name passed in as VAR. When this is expanded, a local variable with the requested name is defined for the rest of the macro. Because it is local, it doesn't hurt to have the same name in the macro's scope already, as the local version will temporally supersede this one.

The only thing we have to ensure now is to have the extra code expanded before the actual loop's contents. It could be passed in to ITERATE via an extra parameter, which is how I did it in the first versions of this macro, but there's an even easier way as you can see in lines 6 and 7: as HSC.Content is just a string, it can be concatenated with other code. In this case, all we have to do is to slap an extra $let-tag in front of the content-string, so it gets expanded each time the content is used.
There you are! Now you can nest loops as easily as this:


<FOR VAR=i TO=10>
  <FOR VAR=j TO=5>
    Nested loops: <(i + ',' + j)><BR>
  </FOR>
</FOR>

If you don't want to use the VAR attribute, the innermost counter ist still available as COUNT of course.

1.5 The other loop: while

Perhaps this one should have come before the FOR loop, because the latter can easily be derived from it, but then again it's a little more complicated because of its dynamic HSC code.
Anyway—parameter-wise, while is very simple. As the only attribute, it takes a condition that must evaluate to TRUE (i.e. non-zero) for the loop to continue running, and of course it should be a container macro that repeats its contents:


<$macro WHILE /CLOSE COND:string/R>
</$macro>

The attribute COND is called the same as $if's attribute, but with an important and unfortunately unavoidable difference: it is a string, not an expression, although it will be evaluated as one internally. So while you write <$if COND=(a < 10)> for a simple conditional, the corresponding loop syntax is <while COND="a < 10">3.

Having read the development of the FOR tag, you can guess the next step—creating an iterator "function". Only that in this case it has to be able to accept an expression instead of a range of numbers, and iterate until this evaluates to FALSE:


<$macro WHILE-ITER COND:string/R CONTENT:string/R>
  <("<$if COND=(" + COND + ")>")>
    <(CONTENT)>
    <WHILE-ITER COND=(COND) CONTENT=(CONTENT)>
  </$if>
<TAG>/$macro</TAG>

How to evaluate the condition we got as a string attribute? Same old problem, same old solution :) As you can see above, all it takes is to construct an $if-tag on the fly and expand it at the same time. As the condition doesn't have to be modified before the recursion, that's all there is to the iterator already.

And yes, of course FOR could be based on this. The code to do the counting would have to be added to the CONTENT attribute of course, either by string concatenation before the first iteration, or with an extra attribute for WHILE-ITER similar to FOR's CODE attribute.


Footnotes
  1. Although, for later code maintainers' sake, every book on structured programming will tell you not to do this.
  2. The condition is "COUNT < TO", so SIZE=10 only makes a 9x9 table, just like the usual C version for(COUNT=1; COUNT<TO; ++COUNT) would.
  3. If you want syntactic consistency, you can define a macro wrapper for <$if> like this:
    <$macro IF /CLOSE COND:string/R><$if COND=(COND)><(HSC.Content)></$if></$macro>.

Last change: 21-Feb-2006, 06:43

You are not supposed to see this—arachnoids only: nude metal goth porn tits video i phone thai hentai gangbang girls i touch lolita emo slutsparis ghettoschlampen arschgeficktgay geigh salope enculée pédé