macros

description of macro facility

DESCRIPTION

The following commands and functions are associated with the macro facility.

def m s

Define or redefine m to have the definition s, where s is a string. Each time m occurs on input, it is replaced with s. The definition is made immediately, so the macro can be used later in the same statement block in in which it is defined and can be redefined within the same statement block.

Note that the macro definition is made regardless of any surrounding flow control statements, since the enclosing mini-program is not yet completely parsed and is not executing.

rdef m s
Define or redefine m to have the definition s, where s is a string. Each time m occurs on input, the value s is substituted. Unlike def, described above, the macro definition is not made and will not be available until all the encompassing statement blocks are parsed and executed and the resulting mini-program is executed.
def m([a1, ...]) '{ ... }' or rdef m([a1, ...]) '{ ... }'
Defines a macro function named m, possibly with arguments a1, ..., that can optionally return a value using the return keyword. The string delimiter characters in the macro definition are optional. The curly brackets are required. The same distinctions explained above regarding def and rdef still apply.
lsdef [m1 ...]
List the names and sizes of all (or selected) macros. Arguments may contain the *, ? and [] metacharacters. A * matches any string, while a ? matches any single character. With the [...] pattern, any string that contains the characters within the square brackets is matched. Two characters separated by a hyphen specify a range of characters that will match. An initial ^ negates the enclosed character set. (Support for [] added in spec release 6.07.)
prdef [m1 ... ]
Print all (or selected) macro definitions. The arguments can contain wildcard characters as described above for lsdef. The definitions are prepended with def name ' and terminated with ' so if saved to a file can be read back. Note, though, for cdef() macros the definition is displayed with def, and only the currently enabled segments are shown.
undef m1 [m2 ... ]
Undefine and remove the macro names given as arguments. Arguments may contain wildcard characters as described above for lsdef. To avoid inadvertently deleting all macros or some substantial fraction, the command will require confirmation if the number of matching macro names exceeds a threshold. The default threshold is 10, but can be changed using the "undef_threshold" spec_par() option. In addition, if pattern_name only contains * or ? characters, spec requires confirmation, to avoid, for example, an accidental undef ? action. However, if the undef command is from a client to a spec server, no confirmation is sought.
cdef(name, s, [key, [flags]])

The function cdef() is used to define chained macros. The function can be used to maintain a macro definition in pieces that can be selectively included into a complete macro definition. The argument name is the name of the macro. The argument s contains a piece to add to the macro.

The chained macro can have three parts: a front, a middle and a back. Pieces included in each of the parts of the macros are sorted lexicographically by the keys. Pieces without a key are placed in the middle, in the order in which they were added, but after any middle pieces that include a key.

With the optional key argument, the pieces can be selectively replaced or deleted. The flags argument controls whether the pieces are added to the front or to the back of the macro or whether the pieces should be selectively included in the definition based on whether key is a currently configured motor or counter mnemonic.

The bit meanings for flags are as follows:

0x01
only include if key is a motor mnemonic and the motor is not disabled.
0x02
only include if key is a counter mnemonic and the counter is not disabled.
0x10
place in the front part of the macro.
0x20
place in the back part of the macro.

If flag is the string "delete", the piece associated with key is deleted from the named macro, or if the name is the null string, from all the chained macros.

If flag is the string "enable", the parts of the named macro associated with key are enabled, and if flag is the string "disable", the associated parts are disabled. If name is the null string "", then all chained macros that have parts associated with key will have those parts enabled or disabled.

If key is the null string, the flags have no effect.

For "delete", "enable" and "disable", the key can include the standard metacharacters *, ? and []. (As of spec release 6.10.01.)

The cdef() function can be used to define macro functions (as of spec release 6.12.01). If any of the cdef() calls include parenthesis after the macro name, the macro will become a macro function. Note, though, cdef() macro functions don't allow arguments in the definition. However, a cdef()-defined macro function can be called with arguments that can be accessed in the definitions using the argc, argv[] or arg1, arg2, ... features. (See MACRO FUNCTIONS below.) spec will automatically add an initial and final curly bracket to the pieces of the chained macro.

The cdef() function will remove any existing macro defined using def or rdef. Likewise, def and rdef will remove an existing cdef() macro with the same name.

The commands lsdef, prdef and undef work with chained macros, with lsdef indicating "cdef" macros with an asterisk and prdef using the # cdef() comment before the macro definition. The clone() commands creates an ordinary macro as the cloned copy of the currently enabled parts.

When spec starts, when the reconfig command is run (or the config macro is invoked) or when individual motors or counters are enabled or disabled, all the chained macros are adjusted for the currently configured and enabled motors and counters.

cdef("?")
Lists all the pieces of all the chained macros.
cdef(name, "?")
Lists the pieces of the macro named name, as will a "?" as the third or fourth argument.
clone(dest, src)
Duplicates the macro src as a new macro named dest. A clone of a cdef chained macro becomes an ordinary macro.
strdef(s [, arr])
Returns a string containing the macro definition of s. If s is not a defined macro, returns the string s itself. If an associative array arr is included as an argument, elements will be created and assigned as follows. If the macro s is a macro function, elements of arr indexed starting at 0 will be assigned the string names of the arguments to the macro function. The element arr["argc"] will be assigned the number of arguments to the macro function (as of spec release 6.11.02). For all macros, the element arr["file"] will be assigned the name of file where the macro was defined, "tty" if the macro was defined at the keyboard, "cdef()" if the macro is a chained macro or "clone" if the macro is a clone of another macro (as of spec release 6.06.01).
strdef(s, key [, *arr])
If s is a chained macro, returns a string containing only the definition segment associated with key. If s is not a defined macro, returns the string s itself. If s is a macro, but not a chained macro, returns the definition. If s is a chained macro, but doesn't contain a segment associated with key, returns the null string. (Available as of spec release 6.03.04.) The key can include the standard metacharacters *, ? and [] (as of spec release 6.10.01). If an associative array arr is included as an argument, values are assigned as described above.

MACRO ARGUMENTS

Within an ordinary macro (not a macro function), the symbols $1, $2, ... are replaced with the arguments with which the macro is invoked. Also,

$0 is replaced with the macro name,
$* is replaced with all the arguments,
$@ is replaced with arguments delimited by \a,
$# is replaced with the number of arguments,
$$ is a literal $.

A macro argument is a string of characters delimited by spaces. Use quotes to include spaces within a single argument. Use \" or \' to pass literal quotes to the macro. You can continue supplying arguments on subsequent lines by putting a backslash at the end of the previous line.

When a macro is defined with arguments and is encountered by the program, all characters on that line up to a ;, a { or the end of the line are eaten up, whether or not the macro uses them as arguments. When numbered arguments are referred to in the macro definition, but are missing when the macro is invoked, they are replaced with zeros. If $* is used in the definition and there are no arguments, no characters are substituted.

When a macro is defined without arguments, only the macro name is replaced with the definition. No additional characters are consumed.

It is often useful when parsing macro arguments, particularly when the macro is called with a variable number of arguments, to use the split() function to place the arguments into an associative array. Typical syntax is:

{
  local ac, av[]
  ac = split("$*", av)
}

Note, that usage does not respect quoted arguments, since $* removes quotation marks when concatenating the macro arguments.

Introduced in spec release 6.03.04, the sequence $@ is replaced with the concatenated arguments delimited by the special character \a (the audible bell, ^G, ASCII 7). The string can then be split as follows:

{
  local ac, av[]
  ac = split("$@", av, "\a")
}

The elements of av[] will respect the quoted arguments in the macro invocation. There is no syntax to escape the \a.

The length of a macro definition has been unlimited since spec release 3.03.10. The number of macro arguments has been unlimited since spec release 6.00.10.

CAVEATS

Beware of unwanted side affects when referencing the same argument more than once. For example,

def test 'a = $1; b = 2 * $1'

invoked as test i++, would be replaced with a = i++; b = 2 * i++, with the result that i is incremented twice, even though that action is not apparent to the user. The previous definition also would cause problems if invoked as test 2+3, as that would be replaced with a = 2+3; b = 2 * 2+3. The latter expression evaluates to 7, not 10, as might have been intended by the user. Use of parenthesis to surround arguments used in arithmetic expressions in macro definitions will avoid such problems, as in b = 2 * ($1).

NOTES

The spec parser allows the macro definition string for def and rdef to be delimited by curly brackets instead of the normal string delimiters of ' or ". This extension allows syntax-aware editors to highlight the structure of the macro definition appropriately. When quotes are used with such editors, the content of such strings is not highlighted to show syntax. The following are all equivalent:

def test 'print "hello world"'
def test '{ print "hello world" }'
def test { print "hello world" }
def test "print 'hello world'"
def test "print \"hello world\""

Note that if curly brackets are used to delimit the macro definition, nonfunctional curly brackets need to be escaped if used in strings, for example:

def test { print "this curly right bracket \}" }

(The curly bracket feature appeared in spec release 6.00.04.)

MACRO FUNCTIONS

Macro functions are a type of macro that can return values and can be used as an operand in expressions. The macro definition can include function arguments, which then become available to the body of the macro function. For example:

def factorial(n) '{
       if (n <= 1)
               return(1);
       return(n * factorial(n-1))
}'

The syntax of macro functions is a macro name followed by a set of parenthesis which can contain a comma-separated list of argument names. The argument names become local variables within the macro definition. The definition must be a statement block, that is, the statements must be enclosed in curly brackets.

Local variables named argc and argv[] along with arg1, arg2, etc., are brought into existence within the scope of a macro function. The value of the variable argc is the number of arguments used to invoke the function. The additional variables arg1, arg2, etc., represent the arguments, whether or not the arguments were explicitly declared in the macro definition. The argument number corresponds to the position of the argument, with the first argument being arg1.

Just as with declared macro function arguments, the type of the created variable will be the same type as the variable used in the macro call, whether number, string, associative array or data array.

The arg1, argv[1] symbols and (if it exists) an explicitly named argument in the corresponding position all refer to the same storage. Changing the value of one will change the values of the others.

The argv[] symbol is similar to an associative array with the special property that in addition to number and string values, elements can be associative or data arrays to match the arguments by which the macro function was invoked. There are limitations, though. There is no syntax to refer to individual array elements of argv[] elements that are associative or data arrays. To refer to subelements, assign to a local variable or use the arg1, arg2, ... symbols. For associative arrays, one can use:

def test() '{
    local x
    x = argv[1]
    print x[3]

    print arg1[3]
 }'

The first three lines and the last line are two ways of doing the same thing.

For data arrays, the local variable must be explicitly declared as a data array, as in:

def test() '{
    local array d[10]
    d = argv[1]
    print d[3:6]

    print arg1[3:6]
}'

Again, the first three lines and the last line are ways of doing the same thing, for the most part. In the case of data arrays, if the local data array declaration doesn't match the type or dimension of the macro argument, the usual rules for assigning mismatched arrays will apply. The first three lines and the last line will give differing results if the above macro is called with a two-dimensional array.

In the above, d[] is a one-dimensional column array. If argv[1] is a two-dimensional array, its first row will be assigned to d[], so only elements from that row will be printed. In the last line, arg1 maintains the dimensions of the argument array, so all columns in rows 3 to 6 will be printed.

The automatic local variables argc, arg1, arg2, etc. were introduced in spec release 6.06.11, although arg1, arg2, etc. were not created if there was a macro argument at that position included in the macro definition. The local variable argv[] was introduced in spec release 6.12.01 along with creating arg1, arg2, ... for all macro arguments, not just for arguments included in the macro invocation that weren't part of the macro definition.

The argv[] implementation code makes the elements of the array point to the corresponding arg1, arg2, etc. Thus, when printing or accessing elements of argv[1] for an argument that is an associative array itself, the array name to use or that will display when printed is arg1.

If a macro uses these names for macro arguments or as explicitly declared local variables, that use will take precedence. The behavior described above will not be available for such variables. Global variables with any of the arg* names are not visible in macro functions.

EXAMPLES

def u 'unix("$*")'

Notice the double quotes convert the arguments into a single string, as required by the syntax of the unix() built-in command. When invoked with no arguments, the macro evaluates to unix("").

def ct '{
  local   c
  if ((c = $1) == 0)
     tcount(1)
  else if (c < 0)
     mcount(-c)
  else
     tcount(c)
  waitcount
  getcounts
  print S[0], S[1], S[2]
}'

Notice that the argument $1 is referred to only once, by using the local variable c. When invoked with no arguments, the first line evaluates to if ((c = 0) == 0).

def hscan '
  hklscan $1 $2 K K L L $3 $4
'

BUILT-IN MACRO NAMES

The following macro names are built into spec. They are run at the specified times only if they have been given a definition.

begin_mac
Runs on start up after reading the hardware configuration file and all the start-up command files, but before reading commands from the keyboard.
end_mac
Runs when spec exits from either a ^D or a quit command.
config_mac
Runs after reading the configuration file at start up and after the reconfig command is executed.
prompt_mac
Runs just before spec issues the main, level-zero prompt. If an error or a ^C occurs while running prompt_mac, the macro will be deleted.
cleanup_once
A clean-up macro that is always deleted before a new spec main prompt is issued. If defined, its definition will be pushed on to the input stream whenever an error is encountered, the exit command is encountered, or a user types ^C.
cleanup_always
Like cleanup_once, but its definition is not removed except by an explicit undef command.
cleanup or cleanup1
If either or both exists, they will be run whenever an error is encountered, the exit command is encountered, or a user types ^C. The cleanup macro is run first. However, the cleanup and cleanup1 macros are deprecated and no longer used in the standard macros. Use cleanup_once and cleanup_always instead.

After running the clean-up macros, spec gives the standard prompt and waits for the next command from the keyboard.

Macro definitions for all the above built-in macros should be maintained using cdef() so that independent macro packages can make use of the macros without interference. The standard macros use cdef() when using cleanup_once.

mainloop_mac
If this macro exists, it runs instead of spec prompting for input. The intention of this macro is to provide site administrators a tool to limit what commands are available to users. The macro should prompt the user for input and act on that input. Unless the macro includes an option to undefine itself, the macro will run forever. The macro is undefined automatically when spec exits, either by the quit command or by ^\ (the quit signal). Added in spec release 6.09.05.

Here is an example of a mainloop_mac:

def mainloop_mac '{
        if (mainloop_once == 0) {
                global mainloop_once
                mainloop_once = 1
                print "First time through"
        }

        local s, ac, av[]

        s = input("\nCommand: " )
        ac = split(s, av)
        if (av[0] == "exit") {
                unglobal mainloop_once
                undef mainloop_mac
        } else if (av[0] == "quit") {
                unglobal mainloop_once
                quit
        } else if (av[0] == "dscan") {
                dscan tth -1 1 10 .1
        } else if (av[0] == "do" || av[0] == "qdo") {
                eval(getline(av[1], 0, "end"))
        } else
                eval(s)
}'

Since the macro runs repeatedly, one time initialization can be protected using a global variable flag as illustrated.

In the example, the macro prompts for a command, splits the response into words that are placed in the associated array av[] and then acts based on the first word. The example recognizes an exit command which allows return to the standard spec prompt. The macro also recognizes a quit command to terminate spec. The dscan command runs a preconfigured scan.

The macro also shows an alternative way of executing commands from a file. Unlike the do/qdo macros which use dofile()/qdofile() to put the named files on the input queue, the getline() and eval() method causes the commands from the file to be executed immediately.

When the commands in mainloop_mac are exhausted, if a reconfig command has been entered, spec will then act on that request, as hardware reconfiguration can only happen when no commands are executing. Next, spec will take input from any files that have been put on the input queue with dofile/qdofile(). After exhausting that input, spec will run prompt_mac, if it exists, then run mainloop_mac again.

If there is an error while running mainloop_mac or if the user types a ^C, the usual cleanup actions will occur, but then mainloop_mac will be run again.

The only ways out of mainloop_mac are to include an option to undefine the macro, include an option to quit spec, or for a user to type the ^\ quit signal.

The example macro also runs eval() on any command that isn't recognized, something unlikely to be included in a macro used to limit what commands are available to the user.

START-UP COMMAND FILES

The first time a user starts spec, up to seven command files are automatically read. The path names of these files are:

SPECD/standard.mac
SPECD/geom.mac
SPECD/name/geom.mac
SPECD/site_f.mac
SPECD/site.mac
SPECD/name/conf.mac
./spec.mac

where SPECD is the auxiliary file directory, geom is the first four letters of the name by which spec was invoked and name is the complete name by which spec was invoked. The files are only read if they exist.

The files SPECD/standard.mac, SPECD/geom.mac, SPECD/name/geom.mac and SPECD/site_f.mac are only read if the user is starting spec for the first time or has invoked spec with the -f (fresh start) flag.

The files SPECD/standard.mac and SPECD/geom.mac are part of the spec distribution and will be created when spec is installed and overwritten when spec is updated. (The geom.mac file, of course, is only created if geom is a supported spec geometry.) The file SPECD/name/surf.mac is also part of the spec distribution and is associated with the various flavors of the surf geometry. These files contain spec's standard macro definitions and global variable declarations.

The other files can be created and maintained locally. They will not be disturbed when spec is updated.

SEE ALSO

def undef lsdef