xyzzy

REXXTRAP.cmd

Contents A .. M N .. T T .. W X .. Z external
Introduction  AKEY  OKAY  TRAP  XDEL  Home
Procedures BOOT  PROC  TREE  XDIR  Contact
Usage KWIK  RENO  UTIL  XEAT  Xedit
Observations  LINE  SHOW  WAIT  XENV  Rexx
History MAKE  TEST  WARN  XRMD  Sources

top   Introduction

REXXTRAP.cmd is a collection of REXX procedures used in almost all my scripts. Some procedures like XDIR or TREE are designed to run in PC DOS 7 REXXSAA, OS/2 REXXSAA, Quercus REXX/Personal for DOS, and Quercus REXX/Personal for OS/2. Other environments like BREXX, Regina, or VM REXXSAA are not yet supported. I'm less interested in PC DOS portability today, but a common interface for procedures like XENV depending on OS and / or REXX interpreter is still handy.

A second set of REXXTRAP procedures depends on OS/2 REXXUTIL.dll entries, but could also work with the now obsolete RxUtils.dll, e.g. BOOT uses RxUtils RxBootDrive or REXXUTIL SysBootDrive to determine the boot drive. If a script requires OS/2, I'll simply use something like call UTIL 'SysFileSystemType' as one of the first statements, because REXXTRAP UTIL uses RxFuncQuery, and this would cause a syntax error in PC DOS REXX interpreters.

A third set of procedures supports the creation of simple scripts running under all OS/2 session types as determined by REXXTRAP procedure PROC, the essential point here is to use RxMessageBox in a PM session (PMREXX.exe or CMD.exe started as PROGTYPE=PM), charout + SysGetKey in any VIO session, charout + RxGetKey in a PC DOS 7 session, charout + INKEY in a DOS REXX/Personal session, or to abort interactive input in a DETACHed session.

Finally TRAP is my common exception handler, quite big but working in all environments (normal, pipes or DETACHed, or PM). Two (optional) KEDIT macros are used to create and later update REXX scripts:

REXX.kex 

REXX.kex copies a selected REXXTRAP procedure to an edited REXX script, or starts the creation of a new script by copying the common exception handler TRAP and inserting four signal on something name TRAP statements.

REXXTRAP.kex 

REXXTRAP.kex simply updates new REXXTRAP procedures found in any script using the latest version in REXXTRAP.cmd. Unfortunately this will "touch" all scripts containing TRAP in the configured directories, because the common TRAP handler is always updated. In other words, timestamps shown in my src directory don't correspond to the last real modification, some scripts are several years old.

All REXXTRAP procedures and the two optional KEDIT macros REXX.kex and REXXTRAP.kex expect to find the TRAP handler at the end of a script. Of course you can use REXXTRAP.cmd procedures with other editors like EPM or THE (maybe try to rename REXX.kex to REXX.the) and even without my common TRAP handler. In the latter case simply replace any exit TRAP('message') statement by your variant of a common exit with message or use exit WAIT('message').

In very simple cases I use something like exit abs() to force REXX error 40 (missing argument for abs) or omit the otherwise part of select to cause error 7 (WHEN or OTHERWISE expected ), because classic REXX has no explicit "raise", "throw", or "abort" function.

top   Procedures

AKEY

AKEY waits for and returns the next input key using RxGetKey or SysGetKey('NoEcho') or DOS REXX/Personal INKEY(). In a DETACHed background session AKEY() returns x2c(1B), i.e. fakes a pressed ESC-key. AKEY does not read STDIN, so it can be used in pipes. Maybe add charin('/dev/con') or similar for REXX interpreters not yet recognized by AKEY like BREXX or Regina. As far as I know REXXUTIL.dll has been ported to some systems using Regina, so you might also be able to use SysGetKey('NoEcho').

BOOT

BOOT determines the OS/2 boot drive with RxUtils.dll RxBootDrive or REXXUTIL.dll SysBootDrive. Not supported in a DOS REXX session, compare UTIL.

LINE

Obsolete, don't use it. LINE(text) writes text to device or file LINE.. and notes text as LINE.n using n = LINE.0 + 1 and LINE.0 = n for a later SHOW.

If you still want to use procedure SHOW together with LINE initialize LINE.. = log-file (e.g. '/dev/nul' or 'stderr:'), LINE.0 = 1 (counter), and LINE.1 = header for RxMessageBox.

KWIK

KWIK('stem.') sorts stem.1 up to stem.n with n = stem.0 alphabetically returning stem.0. This quick sort is essentially a copy from REXXSORT.cmd to support other tools using REXXTRAP.cmd as database. KWIK is not included in the self test, consult the rexxsort info including test results.

MAKE

MAKE updates or creates a OS/2 WPS program object for the actual source script. The first MAKE argument is the object title, default name of source. The second optional argument is an additional parameter string added to the source invocation. Use e.g. %* for the complete path in drag and drop, or [msg] for interactive input. The third and last optional argument is the start directory, default is the value of TMP in the environment or "." (CWD).

MAKE() uses the source name as object id., e.g. <Test.Cmd> for Test.Cmd (WPS object id.s are case sensitive), and as /C parameter for CMD.exe, so you can use MAKE() only in unambiguous cases for scripts in your PATH (not checked). It's no problem to call MAKE more than once, the update option of SysCreateObject won't change the current location of an existing program object.

Generally there are two ways to create a PROGTYPE=PM program object for a REXX script, either use PMREXX.exe with option /Q to get a simple PM console for I/O, or use * with option /C to start the script without classic I/O.

MAKE uses the second * /C method. Don't use MAKE in a script with classic REXX say, pull, trace, etc. console I/O. Any PM object created by MAKE has to use TRAP, OKAY, or WAIT based on RxMessageBox instead of console I/O. Most default REXX exception handlers use normal trace output, i.e. the same standard output device as REXX say, therefore you have to replace exception handlers based on RxMessageBox. The second variant of TRAP can handle exceptions in PM programs.

MAKE() returns the object id. ready for further actions like creation of a shadow in <WP_START> (autostart folder). MAKE() specifies <WP_DESKTOP> (desktop folder) as location for new objects. An existing .ico file with the path and name of the source script is used as ICONFILE. Otherwise an existing EA .ICON is extracted and used as temporary ICONFILE.

OKAY

OKAY handles normal Yes-No-Cancel dialogues. In text mode, i.e. DOS, VIO, or full screen sessions as determined by PROC(), OKAY displays its message on STDERR (or \dev\con if used with DOS REXX/Personal). Therefore you can use OKAY or WAIT interactively even if STDOUT and / or STDIN are redirected.

In text mode OKAY uses AKEY to get a response from the user. The result is 1 for any key in "Yy+1" or simply ENTER. For OS/2 national Yes-keys determined by SysGetMessage(0)  like German "J", French "O", Spanish "S", etc. are also treated as "okay" returning 1.

The result is 0 for any key in "Nn-0", DEL (^?, hex. 7F), or corresponding national No-keys determined by the second word of SysGetMessage(0). No-keys are processed after the hardwired Yes-keys, but as far as I know this should be no problem, because no language handles Y as "No".

OKAY terminates the script if any key recognized as Cancel-key is pressed. Hardwired Cancel-keys are "X=k" and ESC (hex. 1B). This is a hack, because AKEY returns F3 and Alt-F4 as hex. 00 followed by hex. 3D "=" or hex. 6B "k", and OKAY ignores any unrecognized keys incl. hex. 00. For OS/2 the third word of SysGetMessage(0)  determines further Cancel-keys.

Finally if OKAY is used in a PM session it simply translates the result codes 6 and 7 of a YesNoCancel RxMessageBox to 1 (yes) and 0 (no), or it terminates the script with exit code 2 (RxMessageBox result for Cancel). RxMessageBox is a very simple PM popup handler, see also VIEW REXX RxMessageBox or better test it, the number of lines and the length of the message are restricted. See also the observations below.

PROC

PROC simply returns the result of RxUtils.dll RxProcessType or of REXXUTIL.dll SysProcessType. OS/2 1.x had a now obsolete process type 1 (real mode), PROC returns 1 if it is called in an unknown session (not OS/2), on my system this is either PC DOS REXXSAA or Quercus REXX/Personal for DOS. Actual OS/2 session types are 0 (full screen, text mode), 2 (VIO, text window), 3 (PM), or 4 (DETACHed).

RENO

RENO('<old_id>', '<new_id>') tries to replace <old_id> by <new_id> using a complex sequence of SysSetObjectData and SysIni calls. RENO returns an error message string or an an empty string (no error). Of course the WPS object handle is still the same as before, only the object id. is modified.

Unfortunately it's easy to create objects without object id., e.g. SysCopyObject or SysCreateShadow don't allow to set an object id., and even in SysCreateObject a final OBJECTID=<whatever> in the setup string is only optional.

Instead of SysCreateShadow('<original>', '<WP_START>') to create a shadow of <original> you could use SysCreateObject('WPShadow', ' ', '<WP_START>', setup) with setup = 'SHADOWID=<original>;OBJECTID=<whatever>'. The blank title ' ' (second argument) has no effect for shadows, it's only needed to bypass the syntax check of SysCreateObject.

As soon as you start to use and even depend on object id.s in your REXX scripts, you may want to change id.s of existing objects. Don't use RENO for id.s set by an unknown creator, or in other words, use RENO only if you're pretty sure that no other program expects the old object id.

SHOW

Obsolete, don't use it. SHOW displays LINE.2 upto LINE.n with n = LINE.0 (probably collected by LINE, see above) using either one RxMessageBox or a series of say LINE.n statements followed by a pull.

This idea didn't work as expected: RxMessageBox uses a proportional font and restricts the number of displayed lines. To get around these limitations in a PM session I'd have to use PMREXX, and then I won't need LINE and SHOW, because PMREXX supports classic I/O (REXX say and pull ).

TEST

REXXTRAP.cmd can be used as standalone script, and then procedure TEST simulates a call by value for a single test case. Classic REXX (unlike standard REXX) doesn't support call by value (i.e. function or procedure pointers), but can of course simulate a call by value with interpret. TEST is a part of the REXXTRAP test suite, see also usage.

TRAP

TRAP is my common exception handler used in (almost) all REXX scripts incl. REXXTRAP.cmd itself. TRAP can even handle weird cases, where all file handles are already in use, so that calls of sourceline() cause a double fault.

Generally copying the TRAP handler from REXXTRAP.cmd to a REXX script (i.e. all lines from label TRAP: upto EOF) and adding signal on novalue name TRAP; signal on halt name TRAP; etc. near the top of the script is all you need. This enables the default TRAP handler showing an error message depending on the exception and starting interactive label trace. Press enter to exit and then edit the script, or use say to check variable values etc.

You can also use exit TRAP('text') directly to handle very unusual situations like failed assertions, but this only works as expected if no other error condition()  is pending. TRAP can also handle call on exceptions returning to the (next) statement after an interrupted statement.

TRAP uses only two variables internally, TRAP and rc. Other variables incl. result and sigl are visible but not modified. TRAP sets a return code rc corresponding to the condition() :

  1. -1 is (ab)used for a NOTREADY condition (no REXX error).
  2. -2 is (ab)used for a NOVALUE condition (no REXX error).
  3. -3 errortext( 3 ) is shown for a FAILURE condition.
  4. -4 errortext( 4 ) is shown for a HALT condition (^C or ^Break).
  5. -5 errortext( 5 ) is shown for a REXX syntax error with rc = 5,
    etc. upto errortext(99), but REXX interpreters generally use only error numbers 5 .. 49 for REXX syntax errors as defined in The REXX Language   by Mike Cowlishaw. The convention to convert REXX error numbers to negative return codes is at least 20 years old and no special REXXTRAP trick.

The way how TRAP displays its error message can be modified by replacing one of two when 0 then by when 1 then selections:

The first when 1 then uses STDERR or \dev\con for output instead of STDOUT. It also does not start interactive trace - because trace uses STDIN - and simply returns for a call on or terminates the script for a signal on condition(). This variant can be used in pipes or DETACHed sessions.

The second when 1 then uses RxMessageBox for output, this variant can be used in PM sessions. Both variants also work in normal VIO sessions, therefore it's always possible to use one of the when 1 then selections. KEDIT macro REXXTRAP.kex preserves any when 1 then selection in TRAP.

TREE

TREE is an attempt to emulate SysFileTree() for PC DOS 7 REXX using RxFInfo()  or for DOS REXX/Personal using DosDir() . In both cases XDIR() is also used. Only the first three arguments of SysFileTree() are supported, not the weird attribute stuff. For the 3rd argument of SysFileTree all combinations of the options F (files), D (directories), B (both), S (subdirectories), and O (only paths, no info) are supported, or in other words option T (time) to get a simplified timestamp is not supported.

The "new" (1999) SysFileTree() option L (long) for ISO timestamps is also not supported. Unlike T this option is really useful, but not supported by old REXXUTIL.dll versions. Of course it's easy to emulate L if necessary, and today you should use either O or L.

TREE plus XDIR is so huge that I rarely use it - but if an interesting OS/2 REXX script uses SysFileTree and no other OS/2 tricks, you might be able to replace SysFileTree by TREE. With the help of REXX value() and a global variable THIS... (3 dots) KWIK, TREE, and XEAT simulate REXX stem pointers.

UTIL

UTIL checks and if necessary reloads any required REXXUTIL.dll entry. Some REXXTRAP procedures use UTIL to (re)load procedures.

Note that "check" doesn't mean much. OS/2 REXX RxFuncAdd() or REXXUTIL.dll SysLoadFuncs() simply note all loaded entries. If you use something like RxFuncAdd('Func', 'Any', 'This') and if Any.dll exists, then 'Func' is noted as defined even if no entry 'This' exists in Any.dll. RxFuncAdd returns 1 (error) only if 'Func' is already defined or if Any.dll is not found.

It's a simple but bad idea to reload required functions by force, i.e. to use RxFuncDrop() and RxFuncAdd() unconditionally, because this would break all running scripts using the same functions. The worst variant is of course SysDropFuncs() , this is only a shorthand for RxFuncDrop() on all defined entries found in REXXUTIL.dll.

One way to load dubious functions is shown by BOOT: If RxFuncQuery('SysBootDrive') returns 0 (okay) use it. Else try RxFuncAdd('SysBootDrive', 'RxUtils', 'RxBootDrive') and use it. If this causes a syntax error, because RxUtils.dll contains no entry RxBootDrive, then "undefine" (= drop) this function with RxFuncDrop('SysBootDrive'), and try to define it again with UTIL, i.e. use REXXUTIL instead of RxUtils. You cannot use UTIL or RxFuncAdd as long as the old definition based on RxUtils.dll is not "undefined" (dropped).

WAIT

WAIT() displays a message and then waits for any pressed key, see also OKAY. Result 1 indicates any pressed key except from ESC (hex. 1B), F3 (hex. 003D), '=' (hex. 3D), 'k' (hex. 6B), or Alt-F4 (hex. 006B), in these cases WAIT returns 0.

If PROC() is 3, i.e. a PM sesion, then WAIT uses an OkCancel RxMessageBox to display the message, see also OKAY. Think of WAIT as some kind of @ECHO followed by @PAUSE > /dev/nul, but also working in PM sessions.

WARN

OKAY and WAIT are already simple, but need AKEY, PROC, and UTIL to handle keyboard input in environments without RxMessageBox. WARN() is an alternative, it tries to display its argument in a plain Okay-RxMessageBox.

RxMessageBox causes a syntax error for scripts running in text mode or detached sessions. WAIT then writes on STDERR (or \dev\con for REXX/Personal). Please note that WARN unlike WAIT does not wait for keyboard input in these cases. WARN() returns 0 (okay) or 1 (error).

XDEL

XDEL() uses SysFileDelete(), RxDelete(), or DosDel() to delete one file depending on the used REXX interpreter. The different result codes are returned as 0 (okay) or 1 (error).

If you want to delete many files bypassing the very slow OS/2 "undelete" feature, use address CMD 'erase /N /F' whatever intead of XDEL() or SysFileDelete().   XDEL() doesn't handle wild char.s ( * or ? ), because the used functions also don't support wild char.s.

XDIR

XDIR() is a clumsy wrapper for directory() and PC DOS 7 REXXSAA. This REXX interpreter has no directory() and you can't substitute it by a homebrewn DIRECTORY.rx (9+2 isn't 8+3). If necessary XDIR emulates directory with RxChDrv, RxChDir, RxGetDrv, and RxGetDir.

Quercus REXX/Personal supports directory() for DOS and OS/2. You need XDIR only for TREE and PC DOS 7 compatibility. See RXFILES.ZIP for other external *.RX procedures (24 KB, incl. *.ASM assembler sources).

XEAT

Use XEAT(file, stem, 0) to get attribute .TYPE into a REXX stem variable. The result is 0 (okay), 1 (dito), 13 (invalid), or any SysGetEA error code, 32 (locked) is common. Result 1  indicates an almost valid .TYPE, one trailing d2c(0) was ignored. Result 13 is used for serious problems with the EA .TYPE, e.g. neither EAT_MVMT nor EAT_ASCII, or an invalid length. See also the RXOS2EAS manual.

XEAT(file,stem,0) also returns error 13 if it does not find all announced items. This behaviour is compatible with EAS.exe in the OS/2 toolkit, but the WPS is very tolerant and ignores missing items in an EAT_MVMT .TYPE EA. Maybe a future version of XEAT should return 1 in this special case.

For result 0 (or 1 ) stem.0 is the last type N stored in stem.N. A codepage may be set for EAT_MVMT (multi value multi type) EAs, this value is stored in stem.., where 0 means undefined. An empty string '' in stem.. indicates a simple EAT_ASCII  type, semantically this is the same as one type stored in EAT_MVMT with undefined codepage 0.

Use XEAT(file, stem, 1) to put EA .TYPE from a REXX stem variable. Result 0  etc. is set by SysPutEA, and result 13 is used for an invalid stem.0 or an undefined stem.N value (0 <= N <= stem.0). To add a new type simply get the old types into a stem, check if the type is already set, otherwise increment stem.0, define a new last stem.N, and finally put the modified stem.

XEAT(file,stem,1) automatically uses a simple EAT_ASCII  instead of EAT_MVMT  if there is only one type with an undefined codepage. XEAT deletes the .TYPE attribute for stem.0 = 0 only if the codepage is undefined (stem.. = 0, empty, or no value), otherwise it would put a valid EAT_MVMT with no types at all.

BTW, if you ever get an empty type string (length 0) or type Reserved, you could remove it. The WPS sets .TYPE Reserved if the last type in a settings notebook was deleted, e.g. if you removed Plain Text without keeping or replacing any other type.

XEAT uses argument .TYPE, a global variable THIS..., and internal procedure XEAT.GETEA or XEAT.PUTEA. You could use the same internal procedures to manage EAs like .COMMENTS or .HISTORY - just copy the XEAT stub code, rename it, and replace argument .TYPE.

XENV

Quercus REXX/Personal for DOS and OS/2 supports the selector ENVIRONMENT for environmental variables in the REXX value() procedure. OS/2 REXXSAA insists on OS2ENVIRONMENT, PC DOS 7 REXXSAA insists on DOSENVIRONMENT and upper case. XENV is a simple wrapper for access on environmental variables using value() adding the required selector. Use XENV with one argument to get the value of an environmental variable, or use XENV with two arguments to get the old and set a new value of an environmental variable.

XRMD

XRMD() uses SysRmDir(), RxRmDir(), or DosRmDir() to remove a directory, translating the result to 0 (okay) or 1 (error) like XDEL. Please note that SysDestroyObject isn't the same as "XDEL or XRMD" - SysDestroyObject can destroy non-empty folders (complete subdirectory trees) with all contained objects (files, subdirectories, abstract objects, and transient objects).

top   Usage

REXXTRAP.cmd can be used as a test suite for most REXXTRAP procedures. KWIK, MAKE, RENO, XEAT, and XRMD are not contained in the test suite, because they are either not portable requiring OS/2 or tested elswhere (e.g. KWIK). Maybe try xdeltree.cmd as test for XRMD, you get what you pay for... ;-)

Use REXXTRAP without arguments to get a short usage message, or use REXTRAP -0 to select a single test case interactively. This is a bit clumsy resulting in upto 24 OKAY() calls.

Use REXXTRAP -1 for the first test case 1 (etc. upto 16). Test cases 17 .. 23 are not yet implemented. Test case 24 tries to use all file handles causing a double fault in TRAP, because sourceline() doesn't work if all handles are in use.

Use REXXTRAP .. to run the complete test suite. Like REXXTRAP -0 or REXXTRAP -1 etc. you can use REXXTRAP .. in any relevant environment, i.e. the REXX interpreters PC DOS 7 REXXSAA, OS/2 REXXSAA, Quercus REXX/Personal for DOS, and REXX/Personal for OS/2. With OS/2 try also to run the test suite in a PM session, e.g. use PMREXX REXXTRAP .. for the complete test suite.

Use REXXTRAP . to run the complete test suite three times, first in a normal VIO (command line) session, then in a DETACHed session, and finally in a PM session. Normally you wouldn't want to run a single test case unless there was some problem in the complete test suite.

top   Observations

Here's the old updated TRAPs and pitfalls section of REXXTRAP.cmd, now removed from the script, RTFM... ;-)

top   History

07-2006
Fixed KWIK setup to look for a missing stem dot at the right end. I've updated TREE and XEAT everywhere to use the same style of missing stem dot-statement.
03-2004
Added WARN for cases, where WAIT is not simple enough.
08-2003
External xdel.cmd script renamed to xdeltree.cmd, because it has almost nothing to do with the internal XDEL, not counting its (ab)use as missing test case for XRMD.
07-2003
Added KWIK and updated all scripts again. With the help of XREFTRAP.cmd I found some unused imported procedures - a minor bug caused by REXX.kex and REXXTRAP.kex. As a temporary fix both KEDIT macros now handle only labels with four letters.
XARG removed, it didn't work with PMREXX and REXX/Personal. If you need it anyway use the fixed XARG in RXLYNX.cmd or RXTRACE.cmd.
01-2003
Test case 11 failed (TRAP kept rc = 0): fixed, if necessary TRAP forces an integer rc <> 0.
Procedure MAKE now attaches a source program icon to the created or updated program object.
Added XEAT to manipulate the extended attribute .TYPE.
12-2002
HTML manual (this text) for REXXTRAP.cmd, REXX.kex, and REXXTRAP.kex created.
Added signal on syntax name TRAP after labels BOOT.TRAP: and PROC.TRAP:.
Unfortunately REXXTRAP.kex updates all scripts using REXXTRAP procedures, not only those containing BOOT or PROC, therefore many src directory timestamps now show 12-2002.
10-2002
Created REXX.kex in the style of KEX.kex to simplify editing of REXX scripts incl. copies of selected REXXTRAP procedures. All procedures not explicitly mentioned in the history are in fact several years old and stable.

W3 validator Last update: 27 Jul 2006 18:00 by F.Ellermann