COMPSML and LINKSML, however, aid in spotting errors before the program is ever run for the first time. COMPSML can identify a number of problems during the compilation process, and will indicate the line in the routine file in which they occur. Some of the more common ones I encounter are as follows:
1) Syntax errors
Warning: This is not an SML commandThis message generally results from mistyping an SML command or by inadvertently placing an ampersand (&) before an ARC/INFO command.
Warning: Too many parametersThis message usually indicates improper construction of an SML statement.
2) Undefined aliases
Warning: Alias not defined TEMPThese errors commonly result from forgetting to add an &include directive at the beginning of a routine or by mistyping the alias. Another common error is to use delimiters when defining a named variable:
&define [temp] 10 &varwhich causes the following message:
Alias name contains alias delimiters or blanksIn addition, any reference to [temp] in the routine will generate an undefined alias error.
3) Alias collision
Warning: redefining existing alias.This will happen when you define in a routine a temporary variable name which is already used by the include file. A far more dangerous error, not spotted by COMPSML, is variable collision (see below for further discussion), which can be caused by assigning more than one name to the same variable.
4) Improper closure
&IF or &WHILE has no &ENDThis occurs most frequently in programs with complex nesting, i.e. &if within &if within &while, etc. Use of indenting in programs to indicate nested structures greatly aids in finding where that missing &end should be. (Another common error to watch out for is forgetting to increment the counter variable in a &while loop.)
A comprehensive discussion of COMPSML error messages is presented in Appendix A of the "SML User's Guide"; reading it gives a really good impression of what you can and can't do in a routine file.
If there are enough warnings to run off the screen, you can redirect the output to a file:
COMPSML pspot r pspot >error.txtLINKSML can also spot possible bugs by listing external routines, i.e. routine names that were not found by the linker. This would arise, for example, by mistyping the name in a &run statement.
COMPSML test1 NThe "N" option causes each routine in a routine file to be processed into a separate SML file[1], revealing exactly how the SML processor will look at your program (also preserving &rem statements). Named variables will be translated to their numeric equivalents, and &if...&end and &while...&end directives will be translated into equivalent &goto and &goback statements. The SML files are fully executable, and on a fast computer are difficult to distinguish in run time from the final CML.
Note that because compiling to SML using COMPSML's "N" option also preserves blank lines, you will want to delete them in the resulting SML files anywhere in your application that the command processor will react to them, e.g. to terminate an action.
Let's imagine, for example, that I want to write an ARCEDIT routine to break a selected arc into segments. The first idea that comes to me is to use the SPLIT command, and after some thought I come up with the following:
&routine breakup &define coo 11 &var &define arcno 12 &var &define numv 13 &var &define x 14 &var &define y 15 &var SHOW COORDINATE [coo] SHOW SELECT 1 [arcno] SHOW ARC [arcno] NPNTS [numv] COO KEY &while &ne [numv] 2 &do SHOW ARC [arcno] POINT 2 [x] [y] SPLIT 1 [x] [y] SHOW SELECT 2 [arcno] RES $RECNO = [arcno] SHOW ARC [arcno] NPNTS [numv] &end COO [coo] &returnThe underlying hypothesis, which I briefly tested in ARCEDIT before writing the code, is that if you split an arc at its second vertex, the remainder of the arc will be the second feature of the resulting selection set. Because I'm uncertain how the named variables will come out, I do a "COMPSML breakup N" to look at the resulting code:
SHOW COORDINATE 11 SHOW SELECT 1 12 SHOW ARC 12 NPNTS 13 COO KEY &LABEL *WHILE_000* &goto *END___000* &if &EQ %13 2 SHOW ARC 12 POINT 2 14 15 SPLIT 1 %14 %15 SHOW SELECT 2 12 RES $RECNO = %12 SHOW ARC 12 NPNTS 13 &goback *WHILE_000* &LABEL *END___000* COO %11 &returnRight away I see that variable 12 lacks a percent sign in the "SHOW ARC" commands, so I fix the source code by placing "%" in front of "[arcno]" in those statements.
To see, therefore, what's happening in the breakup routine, I add &echo &debug to the beginning of the SML, restart the ARCEDIT session (I had to use <Break> to get out of the loop I was stuck in), select the arc that was giving me trouble, and run the program. I step through the program until the first error message appears:
SPLIT DEBUG: Point to where the arc should be split(From keyboard) %14, %15 DEBUG: Cannot split arc on endpoints SHOW SELECT 2 12 DEBUG:Aha! Here's a situation I hadn't anticipated. Vertices 1 and 2 must be identical, a problem I could easily solve with some extra variables and lines of code to compare coordinates, skipping to the next vertex if they are identical. Let's just double check the variables for kicks:
DEBUG:&lv 11 15 VAR. VALUE %0011 CURSOR %0012 9 %0013 34 %0014 262314.90600 %0015 117457.80500 SHOW SELECT 2 12 DEBUG:Looks ok. I issue a &stop command and use SHOW to verify that the two vertices are in fact identical:
DEBUG:&stop : SHOW ARC 9 VERTEX 1 262315.12500 117457.80500 : SHOW ARC 9 VERTEX 2 262314.90600 117457.80500 :Oh boy. Things are more complex than I thought. Looks like I'll need to compare distances between vertices and skip those under the WEED value. Perhaps the SPLIT approach isn't the best one to take. An alternative might be to read the vertex coordinates, build a GENERATE file, create a temporary coverage, turn SNAPT off, DELETE the original arc, GET the new arcs, and restore the SNAPT setting. I could actually break up multiple arcs that way. But then I'll need to worry about restoring any attributes....
It's not a jobit's a way of life.
&routine pspot &rem *Argument variables* &define coverage 1 &var &define radius 2 &var &define outline 3 &var &rem *Housekeeping variables* &define arc 51 &var &define wksp 52 &var &define curdir 54 &var &value [arc] ARC &value [wksp] WKSP &run curdir [curdir]What just happened? Answer: the program nuked its own [coverage] argument, replacing it with the value "54". Always save global arguments to other variables. In any case, the best approach is to take advantage of local variables and structured routine calls:
&routine pspot &rem *Argument variables* &define coverage -1 &var &define radius -2 &var &define outline -3 &var &rem *Housekeeping variables* &define arc 51 &var &define wksp 52 &var &define curdir 54 &var &value [arc] ARC &value [wksp] WKSP &r curdir &rv [curdir]Use the following rules of thumb for variable management:
Variables | Type | Use |
-20 to -1 | Local | &r arguments and scratch variables |
0 | Dummy | Assign to unwanted SHOW values |
1 | Memory | WIN return value[3] |
2-50 | Memory | Data maintained across routines |
51 to 9999 | Extended | WIN dialogs, arrays, and data maintained across modules |
&eq %1 101 &and &eq %11 1is FALSE. If SML supported Boolean expressions I could just take the statement, enclose it in parentheses, and place a big ¬ in front. Lacking that option, I try what seems in my haste to be the correct logical inverse:
&if &ne %1 101 &and &ne %11 1 &do &type "Invalid selection" &endIt doesn't work. Those of you sitting safely at home may recognize the error right away, but I don't. So I do a "COMPSML N" and look at the resulting code:
&goto *FALSE_000* &if &EQ %1 101 &goto *FALSE_000* &if &EQ %11 1 &type "Invalid selection" &LABEL *FALSE_000*Tracing the flow, I realize that it's just not right. If, for example, variable 1 contained "101" but variable 11 contained "2", the &type statement would still be bypassed. I'm puzzled. I'm inclined to blame the compiler. Then I look back at the original routine, recognize my error, and replace &and with &or, which yields the following result upon compilation:
&goto *TRUE__000* &if &NE %1 101 &goto *FALSE_000* &if &EQ %11 1 &LABEL *TRUE__000* &type "Invalid selection" &LABEL *FALSE_000*That's still not right! I don't want "Invalid selection" to appear if neither variable 1 contains "101" nor variable 11 contains "1". Since the logical inverse is correct, my original logic must be bad. What I'm really after is this:
&if &eq %1 101 &xor &eq %11 1 &do &type "Invalid selection" &endSince, however, SML lacks an &xor directive, I have to express the statement as follows:
&if &eq %1 101 &or &eq %11 1 &do &if &ne %1 101 &or &ne %11 1 &do &type "Invalid selection" &end &endwhich compiles into the following:
&goto *TRUE__000* &if &EQ %1 101 &goto *FALSE_000* &if &NE %11 1 &LABEL *TRUE__000* &goto *TRUE__001* &if &NE %1 101 &goto *FALSE_001* &if &EQ %11 1 &LABEL *TRUE__001* &type "Invalid selection" &LABEL *FALSE_001* &LABEL *FALSE_000*
Sometimes it's best to keep error prevention to a minimum and assume that the user pretty much knows what's going on. Otherwise, you can really go overboard with the extra padding and straps. An extreme case of error prevention in the breakup routine might be as follows:
&run sysprogr [resp] &if &ne [resp] ARCEDIT &and &ne [resp] ARCEDITW &do &type "Must be run in ARCEDIT." &return &end SHOW NUMBER SELECT [numsel] &if &ne [numsel] 1 &do &type "Only one arc may be selected." &return &end SHOW SELECT 1 [arcno] SHOW ARC %[arcno] NPNTS [numv] &if &eq [numv] 2 &do &type "Arc must have more than two vertices." &return &endNote that the check for number of vertices is unnecessary, for if [numv] equals 2 the &while loop will not execute anyway.
Error trapping prevents error propagation. In many cases, a routine will simply continue to the end without generating anything more serious than an error message or two, but if an SML command is involved the application could bomb. Or if another routine acts on erroneous results from a routine it called, a mess could result. Unfortunately, unlike AML, SML has no intrinsic commands for dealing with errors. Nonetheless, it can be desirable to monitor an action for failure, prevent further action, and inform the user of the situation.
SML routines can readily signal error conditions to each other using global variables:
&routine pspot ... &r curdir [curdir] &if &ne "[error]" "OK" &do &r bailout &return &end ... &return &routine curdir &sv [error] OK ... &open [wksp]t$curdir ioerror ... &label ioerror &sv [error] "CURDIR: Could not open temporary file." &returnor via return values:
&routine pspot ... &r curdir [curdir] &rv [error] &if &ne "[error]" "OK" &do &r bailout &return &end ... &return &routine curdir &sv [error] OK ... &open [wksp]t$curdir ioerror ... &return [error] &label ioerror &sv [error] "CURDIR: Could not open temporary file." &return [error]The test of really good nested error trapping is the ability to pass error notices (or cancels, for that matter) back on up to the highest program level (or to the main menu).
You can't always communicate errors through variables. If you were to launch another application, you would have to rely either upon a file or clipboard contents to inform the host program of success or failure:
WIN RUNW arcx.bat [wksp]p$run.sml WIN CB R &if &ne "%1" "OK" &do &value [error] 1 &run bailout &return &endFinally, any routine that aborts a complex process should clean up after itself:
1) Destroy any menus and/or dialog boxes.
2) Put up a message box explaining the error.
3) Find and delete as many temp files as possible without destroying essential system files (do NOT use "& DEL [wksp]t$*.*"!!).
4) Do not QUIT in case something can be salvaged from the session: issue &stop to bring the user to the command prompt.
[2]You cannot use &goto, &run, etc. to jump to another location within a CML filesee the &echo entry of the Online Help for more information.
[3]The WIN command will be discussed under Windows Extensions.