spec

Software for Diffraction

3.2. - Some Tips

The syntax rules for defining macros are given in the Reference Manual 2.4.4.1 The suggestions that follow offer some additional guidance for writing macros that will fit in well with the standard library.

When a macro requires arguments, it is a good idea to check that the right number of arguments have been given, and if not, print a usage message and exit to command level. The symbol $# will be set to the number of arguments when the macro is run. For example,
def ascan '
      if ($# != 5) {
              print "Usage:  ascan  motor start finish  intervals time"
              exit
      }
      ...
'


If an argument is supposed to be a motor number or mnemonic, use the _check0 macro before operating on the motor. The _check0 macro exits to command level if the argument is not a valid mnemonic. For instance, to check the first argument of a macro, use
      _check0 "$1"

A mistyped mnemonic might otherwise become a variable with an arbitrary value (zero for a new variable) resulting in an operation on the wrong motor (usually motor zero).

It is good practice to refer to arguments just once when writing macros to avoid side effects that occur, for example, if the macro is invoked as mymac i++. Here the variable i would be incremented each time $1 is used in the macro. In the scan macros, the arguments are assigned to global variables just after the usage check:
def ascan '
      ...
      { _m1 = $1; _s1 = $2; _f1 = $3; _n1 = int($4); _ctime = $5 }
      ...
'


When a macro changes a parameter or mode that affects later data, it is a good idea to note that change in the data file and on the printer. Macros such as comment, qcomment and gpset are available for that purpose.

If possible, declare local variables local to avoid conflicts with other variables, especially when macros are nested or parsed together.

Watch out for name conflicts when naming new macros and using variables. You can prevent most conflicts by using the local keyword to explicitly declare local names within a statement block. Names declared that way can be used as symbols within the statement block even if they are already in use as macros. Otherwise, if you construct commands using a variable name that is really a macro name, when that intended variable is encountered, it will be replaced by the macro, making a mess of things.

Note that several one-letter names such as d, h, p and l are already in use as macro names. Don't use these names as variables, unless they are declared local inside a statement block. Typing lsdef ? will list all one letter macro names. Typing lsdef _? will list all two letter macro names that begin with an underscore.

Command files that define macros often assign default values to related global variables. You should always check if these global variables have already had a value assigned before assigning default values. If the user had assigned a new value to a variable, you do not want that assignment undone if the macro file is reread. The built-in whatis() function can be used to see if a variable has been assigned a value (see 2.4.1.2 for an explanation of the whatis() return values),
if ((whatis("DATAFILE")>>16)&0x0800) {
      print "Warning:  No open data file.  Using \"/dev/null\".\n"
      open(DATAFILE = "/dev/null")
}


When writing macros that move motors, be careful with the move_all command. When moving motors, always do a waitmove and getangles first. Then assign new values to A[], and finally call move_all (or move_em).


When obtaining input from the user, the functions getval() and yesno() are useful. For example,
      _update = yesno("Show updated moving and counting", _update)
      g_mode = getval("Geometry mode", g_mode)
results in the following screen output:
Show updated moving and counting (NO)?
Geometry mode (3)?
You can also use the input() built-in function to obtain user input. Remember, though, that input() returns a string. If the string contains a valid number, the automatic string-to-number conversion will take place, if context requires it. However, no expression simplification is done on the string, so a response of 2+2 will not have a number value of 4 when returned by input().

When using on() and off() to control output, do the operations on "tty" last. Since "tty" is always turned back on if everything else is turned off, the commands
off("tty");on(PRINTER);print "hello world";on("tty");off(PRINTER)
will not have the desired effect. The first off() turns off everything, so "tty" is automatically turned back on, and the message goes to both PRINTER and "tty".

Use existing UNIX utilities if they can be of help. For example, if you manipulate UNIX file names in your macros you can use the return value of the test utility to check for existence of a file. For example, the function unix("test -r $1") will return zero if the file specified by the argument exists and is readable.