SML PROGRAMMING

Loops and Branches

If there is one keyword in the programming world, more than any other, which is politically incorrect, it most certainly is GOTO. Nonetheless, the &goto directive (and its variant &goback) forms the essential analytical component of SML[1]. The chief function of &goto is to jump to a location in the program that is indicated by a &label statement. Optionally, however, a logical statement may be analyzed and the jump will only take place if the statement is true.

   &label do
      &ask 1 "Enter a number: "
      &goto end &if &nn %1
      &goto false0 &if &ne %1 0
         &type "The number is zero."
         &goback do
      &label false0
      &cv 2 %1 abs
      &goto false1 &if &ne %1 %2
         &type "The number is positive."
         &goback do
      &label false1
         &type "The number is negative."
         &goback do
   &label end
The above example illustrates the fundamental difference between &goto and &goback: &goto searches forward in the program file for the label whereas &goback searches backward. As a result, &goto tends to be used more for branching (i.e. jumping to another section of code) and &goback is used for looping. A WHILE loop is a section of code which is executed continuously as long as a certain condition is true[2]:

   &cv 1 0
   &cv 2 0
   &label do
      &goto end &if &eq %2 9
      &cv 1 %1 + 1
      &type "This is loop #%1"
      &key 2 "Hit any key (9 to end):"
      &goback do
   &label end
The above loop executes if the statement "the contents of variable 2 are not equal to '9'" is true. This is an important point which hopefully will be made more clear when structured programming directives are discussed. Note that in the above example the loop will always execute once. If the statement &cv 2 0 were replaced with &key 2 "Hit any key (9 to end):" then the user could circumvent the loop altogether by pressing 9 the first time.

Another common type of loop repeats a certain number of times using a numeric counter (also known as a FOR loop):

   &cv 1 1
   &label do
      &goto end &if &nr %1 1 10
      &type "This is loop #%1"
      &cv 1 %1 + 1
      &goback do
   &label end
   &cv 1 %1 - 1
   &type "The loop executed %1 times"

Logical Statements

The logical conditions that may be tested by &goto and &goback are listed fully in the Online Help. A few are discussed below:

   &goto label &if &rn %1 1 10
The program will jump to "label" if variable 1 is numeric and within the range of 1 to 10, inclusive. If &rn were replaced with &nr the program would jump to "label" when variable 1 is numeric and outside the range of 1 to 10.

   &goto label &if &cn %1 %2
The program will jump to "label" if variable 2 is a substring of variable 1. The test is case-sensitive. As always, if either string may contain spaces, it should be enclosed in double quotes.

[TIP: Problems may arise when a string contains a list of tokens, one of which may be a substring of another token. This situation may be averted as in the following example:

   &rem **** assume that variable 1 was generated by
   &rem **** a loop containing the following line:
   &rem ****    &sv 1 "%1 %5,"
   &rem
   &sv 1 " COVER_ID, NAME, SYMBOL,"
   &sv 2 COVER_
   &label do
      &goto end &if &cn "%1" " %2,"
      &type "'%1' does not contain ' %2,'"
   &label end
Note that variable 1 is a valid item list for ARCEDIT's FORMS command, despite the terminating comma.]

   &goto label &if &eq %1 %2
The program will jump to "label" if variable 1 equals variable 2. In the case of non-numeric strings, a case-sensitive comparison is made. In the case of numeric strings, values are compared (e.g. 2.1 equals 2.100000). Strings containing commas are not considered numeric (e.g. 4,123 does not equal 4123). Null strings may be tested in one of the following ways:

   &goto label &if &eq %1
   &goto label &if &eq "x%1" "x"
The second method is more universal: if the first method were used and variable 1 contained "A A", the program would jump to "label"[3].

[TIP: to check if %1 is less than or equal to %2:

   &goto label &if &eq %<%1 min %2> %1
To check if %1 is greater than or equal to %2:

   &goto label &if &eq %<%1 max %2> %1
To test for the opposite condition, substitute &ne for &eq.]

   &goto label &if &eqnc %1 %2
&eqnc is equivalent to &eq save that the string comparison is not case-sensitive. Thus "aBCDe" would be equal to "abcde".

   &goto label &if &nm %1
The program will jump to "label" if variable 1 only contains numeric characters (0-9, hyphen, comma, period, and/or space). Since positioning is not checked, "4,-3 ,5.-2" is considered a numeric string by this test.

   &goto label &if &fn %1
The program will jump to "label" if the directory or filename in variable 1 is found. Note that ARC/INFO's table naming convention is not supported; thus "COVER.PAT" will not be found whereas "COVER\PAT.DBF" will be found. This may be circumvented by using &info:

   &goto label &if &info %1

Structured Programming

The reason that GOTO is "politically incorrect" is that casual use of it tends to violate widely accepted principles of structured programming. The philosophy of structured programming calls for developing programs in a top-down or "layered" fashion, in which blocks of code act as single logical units. Lower levels of code are nested within higher levels, and any frequently used code is allocated to a function or subroutine. Each logical unit has a single entry point (e.g. DO or CALL) and a single exit point (e.g. END or RETURN). This allows debugging to occur on a modular basis.

Structured programming, therefore, has chiefly a cosmetic function: it does not make the final code more efficient so much as it makes the original script easier for someone else to understand. A routine that is organized into nested logical sections is generally simpler to follow (and debug) than one that bounces all over the place. For this reason I will emphasize the use of structured programming directives in future examples. End of sermon.

The SML compiler, which will be discussed in the next article, supports two directives for structured programming: &if...&end and &while...&end. These directives are read by the SML compiler and translated into the appropriate &goto and &goback statements. Using these directives, the example at the beginning of this article could be rewritten as follows:

   &ask 1 "Enter a number: "
   &while &nm %1 &do
      &cv 2 %1 abs
      &if &eq %1 0 &do
         &type "The number is zero."
      &elseif &eq %1 %2 &do
         &type "The number is positive."
      &else
         &type "The number is negative."
      &end
      &ask 1 "Enter a number: "
   &end
&elseif and &else are optional directives used when the programmer wishes to address alternative conditions within the same structure; note that &else is not followed by &do. The SML compiler would translate the above code as follows:

   &ask 1 "Enter a number: "
    &LABEL *WHILE0000*
    &goto *END__0000* &if &NN %1
      &cv 2 %1 abs
       &goto *FALSE0001* &if &NE %1 0
         &type "The number is zero."
       &goto *END__0001*
       &LABEL *FALSE0001*
       &goto *FALSE0002* &if &NE %1 %2
         &type "The number is positive."
       &goto *END__0001*
       &LABEL *FALSE0002*
         &type "The number is negative."
       &LABEL *END__0001*
      &ask 1 "Enter a number: "
    &goback *WHILE0000*
    &LABEL *END__0000*
If you compare the third line of the translated code with the third line of the original example at the beginning of the article, you should see that in translating a &while (or &if) to &goto, the logic of the translated statement is the inverse of the original. That is why I made my earlier point about the statement which is being tested by the loop. I'm probably just being confusing right now, but understanding this concept will help in debugging routine files, which which will be discussed under Debugging.

Two other useful compiler directives are &inc and &dec. &inc [var] {n} increments the specified variable (default by 1, optionally by n), and &dec [var] {n} decrements the specified variable. These directives are generally used in FOR loops. For example, the 10-cycle loop shown earlier could be rewritten as follows:

   &cv 1 1
   &while &rn %1 1 10 &do
      &type "This is loop #%1"
      &inc 1
   &end
   &dec 1
   &type "The loop executed %1 times"
The &while compiler directive does not require a logical statement to function; in that case, it acts as an infinite loop until a condition arises which directs the loop to terminate:

   &open temp1.txt error
   &while &do
      &read 1 [break]
      &type "%1"
   &end
   &close
   &type "<EOF>"
The "[break]" is a pseudo-label: the SML compiler automatically replaces it with the label which exits the loop.

Finally, &and and &or may be used to create compound &if and &while statements:

   &if &ne %1 0 &and &ne %2 0 &and &eq %3 1 &do
      &while &rn %10 1 4 &or &eq %11 T &do
         ...
      &end
   &end
Note that parentheses may not be used to create more complex expressions.

Next: "What the heck, at least it compiles!"


[1]The &jump directive may be substituted for &goto.

[2]A loop that executes until a condition is true is known as an UNTIL loop. Because the distinction between WHILE and UNTIL is so fine, many languages do not implement a form of UNTIL (Host ARC/INFO's AML is a notable exception).

[3]The use of "x" is also important to force character string comparisons. One limitation of version 3.5 is that if the SML processor initially encounters a numeric token when comparing strings, it automatically attempts a numeric comparison (from what I have been told, the FORTRAN compiler used to generate the code is at fault.) For example, the following code:

&sv -1 "123 xxx"
&if &eq "%-1" "/?" &do
   &type "This comparison is bogus."
&end
will return:

This comparison is bogus.

Return to ArcTips page