Jump to content

REXX Tips & Tricks:Sample source code

From EDM2

This section contains some sample source code. (see also Using the samples)

Using ANSI sequences

This section contains some routines for the display and for redefining of keys using ANSI sequences. (see also ANSI ESC Sequences)

Check if ANSI is active - 1 -

 
/* sample routine to check if ANSI is activated                       */
/*                                                                    */
/* see also Check if ANSI is active - 2 -                             */
/*                                                                    */

  i = CheckAnsi()
  if i = 1 then
    say "ANSI is activated"
  else
    if i = 0 then
      say "ANSI is not activated."
    else
      say "Error detecting ANSI."

exit 0

/* ------------------------------------------------------------------ */
/* function: Check if ANSI is activated                               */
/*                                                                    */
/* call:     CheckAnsi                                                */
/*                                                                    */
/* where:    -                                                        */
/*                                                                    */
/* returns:  1 - ANSI support detected                                */
/*           0 - no ANSI support available                            */
/*          -1 - error detecting ansi                                 */
/*                                                                    */
/* note:     Tested with the German and the US version of OS/2 3.0    */
/*                                                                    */
/*                                                                    */
CheckAnsi: PROCEDURE
  thisRC = -1

  trace off
                        /* install a local error handler              */
  SIGNAL ON ERROR Name InitAnsiEnd

  "@ANSI 2>NUL | rxqueue 2>NUL"

  thisRC = 0

  do while queued() <> 0
    queueLine = lineIN( "QUEUE:" )
    if pos( " on.", queueLine ) <> 0 | ,                       /* USA */
       pos( " (ON).", queueLine ) <> 0 then                    /* GER */
      thisRC = 1
  end /* do while queued() <> 0 */

InitAnsiEnd:
RETURN thisRC
Check if ANSI is active - 2 -
/* sample routine to check if ANSI is activated (using REXXUTIL)      */
/* based on a idea and code of Erik Schneller                         */
/* (see EMail Addresses)                                              */
/*                                                                    */
/* see also Check if ANSI is active - 1 -                             */
/*                                                                    */
  i = CheckAnsi()
  if i = 1 then
    say "ANSI is activated"
  else
    if i = 0 then
      say "ANSI is not activated."
    else
      say "Error detecting ANSI."

exit 0

/* ------------------------------------------------------------------ */
/* function: Check if ANSI is activated                               */
/*                                                                    */
/* call:     CheckAnsi                                                */
/*                                                                    */
/* where:    -                                                        */
/*                                                                    */
/* returns:  1 - ANSI support detected                                */
/*           0 - no ANSI support available                            */
/*          -1 - error detecting ansi                                 */
/*                                                                    */
/* note:     Tested with the German and the US version of OS/2 3.0    */
/*           based on a idea and code of Erik Schneller               */
/*           (see EMail Addresses)                                    */
/*                                                                    */
/*                                                                    */
CheckAnsi: PROCEDURE
  thisRC = -1

                        /* install a local error handler              */
  SIGNAL ON ERROR Name InitAnsiEnd

                    /* register the function SysCurPos from REXXUTIL  */
  call rxFuncAdd "SysCurPos", "REXXUTIL", "SysCurPos"

                    /* get and save the current cursor position       */
  curPos = SysCurPos()

                    /* write a CR/LF and the ANSI code for CursorUp   */
  call charOut , D2C(13) || "1B"x || "[1A"

                    /* now get the current cursor position            */
  NewPos=SysCurPos()

                    /* compare the new position to the old position   */
  if LEFT( NewPos,2 ) == LEFT( curPos,2 ) THEN
  do
                    /* ANSI support is OFF                            */
     thisRC = 0

                /* goto the begin of the line and delete the garbage  */
     call CharOut, D2C(13) || copies( " ",4 ) || D2C(13)
  end /* if left( ... */
  else
  do
                    /* ANSI support is ON                             */

                    /* restore the old cursor position                */
    call CharOut , "1B"x || "[B"
    thisRC = 1
  end /* else */

InitAnsiEnd:
RETURN thisRC


Get the current cursor position

 
/* sample routine to get the current cursor position with plain REXX  */
/* and ANSI commands                                                  */
/* Original code is from the ANSICD package from Jamie Hoglund        */

  parse value GetCursorPos() with col row
  say "At program start the cursor was at " || ,
      "Column " || col || ", Row " || row || "."
exit 0

/* ------------------------------------------------------------------ */
/* function: Get the current cursor position                          */
/*                                                                    */
/* call:     GetCursorPos                                             */
/*                                                                    */
/* returns:  col row                                                  */
/*                                                                    */
/* note:     This function works only for display sizes up to 200 for */
/*           columns or rows. The upper left corner is 1,1.           */
/*           The REXXUTIL function SysCurPos uses zero based values   */
/*           (the upper left corner is 0,0).                          */
/*           Caution:                                            v2.90*/
/*           The REXX Queue must be empty for this code to work!      */
/*           If the REXX Queue is not empty, you can use code         */
/*           like                                                     */
/*             - create a new                                         */
/*             - make the new queue the default queue                 */
/*             - call GetCursorPos                                    */
/*             - make the old queue the default queue again           */
/*             - delete the new queue                                 */
/*                                                                    */
GetCursorPos: PROCEDURE
  usedChars = ":;<=>?@ABCD"

  Rc = Charout(,D2C(27) || "[6n")
  Pull Q

                                                             /* v2.30 */
  parse var q 3 y1 +1 y2 +1 3 row +2 6 x1 +1 x2 +1 6 col +2 .

  if pos( y1, usedChars ) <> 0 then
    row = 10 || y2

  if pos( x1, usedChars ) <> 0 then
    col = 10 || x2

return col row


Get the current screen size
/* sample routine to get the current screensize for textmode windows  */
/* without using REXXUTIL functions.                                  */

  parse value GetDisplaySize() with columns rows
  say "The current OS/2 window size is " || ,
      rows || " rows and " || columns || " columns."
exit 0

/* ------------------------------------------------------------------ */
/* function: Get the current display size                             */
/*                                                                    */
/* call:     GetDisplaySize                                           */
/*                                                                    */
/* returns:  columns rows                                             */
/*                                                                    */
/* note:     This function works only for display sizes up to 200 for */
/*           columns or rows. The upper left corner is 1,1.           */
/*           The REXXUTIL function SysCurPos uses zero based values   */
/*           (the upper left corner is 0,0).                          */
/*                                                                    */
GetDisplaySize: PROCEDURE expose thisPos

  usedChars = ":;<=>?@ABCD"

                    /* save current cursor position                   */
  rc = CharOut(, D2C(27) || '[' || "6n")
  pull curPos

                    /* try to set the cursor to the position 200,200  */
  rc = CharOut(, D2C(27) || '[' || "200;200H" )

                    /* get cursor position                            */
  rc = CharOut(, D2C(27) || '[' || "6n")
  pull tPos

                    /* restore current cursor position                */
  rc = CharOut(, substr( curPos,1, length( curPos)-1) || "H" )

                                                             /* v2.30 */
  parse var tPos 3 y1 +1 y2 +1 3 rows +2 6 x1 +1 x2 +1 6 cols +2 .

  if pos( y1, usedChars ) <> 0 then
    rows = 10 || y2

  if pos( x1, usedChars ) <> 0 then
    cols = 10 || x2

RETURN cols rows


Redefine some keys
/* sample code to do some key remapping with ANSI sequences           */
/* see also ANSI ESC Sequences ,                                      */
/* Key codes for key redefinitions and                                */
/* Using function keys                                                */
/*                                                                    */
/* Note: Turning extended keys off is NOT necessary for key     v1.60 */
/*       remapping. This information in RXT&T v1.50 was wrong.  v1.60 */


                        /* set F1 key to HELP<RETURN>                 */
  call CharOut , '1B'x || '[0;59;"HELP";13p'

                        /* set ALT-F10 to EXIT (without RETURN)       */
  call CharOut , '1B'x || '[0;113;"EXIT";p'

                        /* set "A" to ABER (without RETURN)           */
                        /* corrected in RXT&T v1.60                   */
  call CharOut , '1B'x || '[65;"ABER";p'

                        /* reset F1 key to F1                         */
  call CharOut , '1B'x || '[0;59;0;59;p'
Using function keys

To use function keys without the REXXUTIL functions, redefine them with a trailing CR (see also ANSI ESC Sequences, Key codes for key redefinitions, and download RxLBox for a working example):

 
/* sample key redefinitons                                            */

                    /* new definitons for the function keys           */
  keys.0 = 0
  i = keys.0

  i=i+1; keys.i.__org = '59'; keys.i.__new = 'F1'
  i=i+1; keys.i.__org = '60'; keys.i.__new = 'F2'
  i=i+1; keys.i.__org = '61'; keys.i.__new = 'F3'
  i=i+1; keys.i.__org = '62'; keys.i.__new = 'F4'
  i=i+1; keys.i.__org = '63'; keys.i.__new = 'F5'
  i=i+1; keys.i.__org = '64'; keys.i.__new = 'F6'
  i=i+1; keys.i.__org = '65'; keys.i.__new = 'F7'
  i=i+1; keys.i.__org = '66'; keys.i.__new = 'F8'
  i=i+1; keys.i.__org = '67'; keys.i.__new = 'F9'
  i=i+1; keys.i.__org = '68'; keys.i.__new = 'F10'

  keys.0 = i

                    /* ANSI esc sequence                              */
  ansi.__ESC = '1B'x

                    /* special character to detect function keys      */
  specialChar = 'FE'x

                    /* install error handler for CTRL-BREAK           */
  signal on halt

                    /* redefine the function keys                     */
  do i = 1 to keys.0
    call CharOut , ansi.__ESC || '[0;' || keys.i.__org || ';' || ,
        '"' || specialChar || keys.i.__New || specialChar || '"' || ,
        ';13p'
  end /* do i = 1 to keys.0 */

                    /* test the new key definitons                    */
  do forever
    call LineOut, 'Test the function key redefinitions'
    call CharOut, 'Enter a string (F10 to end): '
    userInput = lineIn()

                    /* test for function keys                         */
    parse var UserInput part1 (specialChar) fKey (specialChar) part2

    UserInput = part1 || part2

    say 'Userinput was: "' || UserInput || '"'

    if fkey = '' then
      say 'No function key pressed.'
    else
      say 'Function key "' || fkey || '" pressed.'

    if fkey = 'F10' then
      leave

  end /* do forever */

ProgramEnd:
                    /* undo the key redefinitons                      */
  do i = 1 to keys.0
    call CharOut , ansi.__ESC || '[0;' || keys.i.__org || ';' || ,
         '0;' || keys.i.__org || ';p'
  end /* do i = 1 to keys.0 */

exit

/* error handler for CTRL-BREAK                                       */

Halt:
  say
  say 'Program aborted by the user!'
  signal ProgramEnd

Use ANSI for a password input routine
/* sample input routine for passwords using ANSI sequences to hide    */
/* the input (stolen from a message on a public CompuServe forum)     */
/*                                                                    */

  myPassWord = GetPassword( "Please enter the password: " )

  say "You entered the password: " || myPassword

exit

/* ------------------------------------------------------------------ */
/* function: get a password from the user (without showing it on the  */
/*           screen)                                                  */
/*                                                                    */
/* call:     GetPassword( {prompt} )                                  */
/*                                                                    */
/* where:    prompt - prompt string                                   */
/*                    def.: none                                      */
/*                                                                    */
/* returns:  the entered password                                     */
/*                                                                    */
/* note:     This code only works with ANSI enabled                   */
/*                                                                    */
/*                                                                    */
GetPassword: PROCEDURE
  parse arg prompt

                    /* show the prompt (if any) and set the screen    */
                    /* attributes to notvisible                       */
  call CharOut , prompt || "1B"x || "[8m"

                    /* get the user input                             */
  parse pull password
                    /* reset the screen attributes                    */
  call CharOut , "1B"x || "[0m"

RETURN password
Using the lower right corner of the display

To print a character to the lower right corner of the screen without scrolling you must turn off the word wrap function of the ANSI driver (see also ANSI ESC Sequences):

/* code to show how to use the lower right corner of the display      */

                    /* ESC code for ANSI sequences                    */
  ansi.__ESC = "1B"x

                    /* ANSI sequences to position the cursor in the   */
                    /* upper right corner                             */
  ansi.__Pos0 = ansi.__ESC || "[1;1H"

                    /* ANSI code to turn word wrap off                */
  ansi.__WordWrapOff = ansi.__ESC || "[7l"

                    /* ANSI code to turn word wrap on                 */
  ansi.__WordWrapOn  = ansi.__ESC || "[7h"

                    /* empty line for the menu frame                  */
  Menu.__emptyLine   = "º" || copies( " ", 78 ) || "º"

                    /* separator lines for the menu frame             */
  Menu.__FrameLine1  = copies( "Í", 78 )
  Menu.__FrameLine2  = copies( "Ä", 76 )

                    /* menu frame                                     */
  Menu.__MenuMask = ,
     ansi.__Pos0                          || ,  /* position cursor    */
     ansi.__WordWrapOn                    || ,  /* turn word wrap on! */
     "É"  || Menu.__FrameLine1 || "»"     || ,  /* menu frame         */
     Menu.__emptyLine                     || ,
     Menu.__emptyLine                     || ,
     "º " || Menu.__FrameLine2 || " º"    || ,
     Menu.__emptyLine                     || ,
     "Ì"  || Menu.__FrameLine1 || "¹"     || ,
     copies( Menu.__emptyLine, 14 )       || ,
     "Ì"  || Menu.__FrameLine1 || "¹"     || ,
     Menu.__emptyLine                     || ,
     "º " || Menu.__FrameLine2 || " º"    || ,
     Menu.__emptyLine                     || ,
     ansi.__WordWrapOff                   || ,  /* turn word wrap off */
     "È"  || Menu.__FrameLine1 || "¼"     || ,  /* last menu line     */
     ansi.__Pos0                          || ,  /* position cursor    */
     ansi.__WordWrapOn                          /* turn word wrap on  */

                    /* clear the screen                               */
  'cls'
                    /* show the menu frame                            */
  call CharOut , Menu.__MenuMask

                    /* position the cursor in the middle of the       */
                    /* screen                                         */
  call CharOut , ansi.__ESC || "[12;30H"

                    /* wait for a key from the user                   */
  "@pause"

Date converting

Unpack packed data

Unpack routine from Steve Pitts (see EMail Addresses) Captured from a message in a public CompuServe Forum

/* ------------------------------------------------------------------ */
/* function: unpack routine for packed data (from mainframes)         */
/*                                                                    */
/* call:     unpack packed_str {,num_dec}                             */
/*                                                                    */
/* where:    packed_str = the packed data                             */
/*           num_dec = number of decimals (def.: 0)                   */
/*                                                                    */
/*                                                                    */
/* returns:  the unpacked number or "" in case of an error            */
/*                                                                    */
Unpack: PROCEDURE
  parse arg packed_str, num_dec

  if num_dec = "" then
    num_dec=0

                        /* Convert packed data to hex and split into  */
                        /* number and sign portions                   */
  hex_str=c2x( packed_str )
  dec_str=left( hex_str, length( hex_str )-1 )
  packed_sign=right( hex_str, 1 )

                        /* Check that sign and numeric portions have  */
                        /* valid values                               */
  if verify( packed_sign, "ABCDEF" ) > 0 then
    return ""

  if verify( dec_str, "0123456789" ) > 0 then
    return ""

                        /* Are there enough digits for the decimal    */
                        /* point??                                    */
  if num_dec > length( dec_str ) then
    return ""

                        /* If sign portion indicates a negative       */
                        /* number then oblige                         */
  if pos( packed_sign, "BD" ) > 0 then
    dec_str=0-dec_str

                        /* If there is a decimal point then add it at */
                        /* the appropriate place                      */
  if num_dec > 0 then
    dec_str=insert( ".", dec_str, length( dec_str )-num_dec )

RETURN dec_str

EBCDIC to ASCII & ASCII to EBCDIC

captured from a message in a public CompuServe Forum Author: Dick Goran, (see EMail Addresses)

(see also RXGWA1 - ASCII <-> EBCDIC)


The tables below can be used with the REXX TRANSLATE() instruction:

/**********************************************************************/
/*       EBCDIC To ASCII & ASCII To EBCDIC Translate Tables           */
/**********************************************************************/
EBCDIC_OUT = XRANGE(000, 003) ||, /* NUL SOH STX ETX                  */
             XRANGE(055, 055) ||, /* EOT                              */
             XRANGE(045, 047) ||, /* ENQ ACK BEL                      */
             XRANGE(022, 022) ||, /* BS                               */
             XRANGE(005, 005) ||, /* HT                               */
             XRANGE(037, 037) ||, /* LF                               */
             XRANGE(011, 018) ||, /* VT FF CR SO SI DLE DC1 DC2       */
             XRANGE(000, 000) ||, /* DC3                              */
             XRANGE(060, 061) ||, /* DC4 NAK                          */
             XRANGE(050, 050) ||, /* SYN                              */
             XRANGE(038, 038) ||, /* ETB                              */
             XRANGE(024, 025) ||, /* CAN EM                           */
             XRANGE(063, 063) ||, /* SUB                              */
             XRANGE(039, 039) ||, /* ESC                              */
             XRANGE(028, 031) ||, /* FS GS RS US                      */
             XRANGE(090, 090) ||, /* !                                */
             XRANGE(127, 127) ||, /* "                                */
             XRANGE(123, 123) ||, /* #                                */
             XRANGE(091, 091) ||, /* $                                */
             XRANGE(108, 108) ||, /* %                                */
             XRANGE(080, 080) ||, /* &                                */
             XRANGE(125, 125) ||, /* '                                */
             XRANGE(077, 077) ||, /* (                                */
             XRANGE(093, 093) ||, /* )                                */
             XRANGE(092, 092) ||, /* *                                */
             XRANGE(078, 078) ||, /* +                                */
             XRANGE(107, 107) ||, /* ,                                */
             XRANGE(096, 096) ||, /* -                                */
             XRANGE(075, 075) ||, /* .                                */
             XRANGE(097, 097) ||, /* /                                */
             XRANGE(240, 249) ||, /* 0 - 9                            */
             XRANGE(122, 122) ||, /* :                                */
             XRANGE(094, 094) ||, /* ;                                */
             XRANGE(076, 076) ||, /* <                                */
             XRANGE(126, 126) ||, /* =                                */
             XRANGE(110, 110) ||, /* >                                */
             XRANGE(111, 111) ||, /* ?                                */
             XRANGE(124, 124) ||, /* @                                */
             XRANGE(193, 201) ||, /* A - I                            */
             XRANGE(209, 217) ||, /* J - R                            */
             XRANGE(226, 233) ||, /* S - Z                            */
             XRANGE(173, 173) ||, /* [                                */
             XRANGE(224, 224) ||, /* \                                */
             XRANGE(189, 189) ||, /* ]                                */
             XRANGE(095, 095) ||, /* ^                                */
             XRANGE(109, 109) ||, /* _                                */
             XRANGE(121, 121) ||, /* `                                */
             XRANGE(129, 137) ||, /* a - i                            */
             XRANGE(145, 153) ||, /* j - r                            */
             XRANGE(162, 169) ||, /* s - z                            */
             XRANGE(139, 139) ||, /* {                                */
             XRANGE(106, 106) ||, /* |                                */
             XRANGE(155, 155) ||, /* }                                */
             XRANGE(161, 161) ||, /* ~                                */
             XRANGE(007, 007)     /* DEL                              */

 ASCII_OUT = XRANGE(000, 003) ||, /* NUL SOH STX ETX                  */
             XRANGE(000, 000) ||, /* PF                               */
             XRANGE(009, 009) ||, /* HT                               */
             XRANGE(000, 000) ||, /* LC                               */
             XRANGE(127, 127) ||, /* DEL                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* SMM                              */
             XRANGE(011, 018) ||, /* VT CR SO SI DLE DC1 DC2          */
             XRANGE(000, 000) ||, /* TM                               */
             XRANGE(000, 000) ||, /* RES                              */
             XRANGE(000, 000) ||, /* NL                               */
             XRANGE(008, 008) ||, /* BS                               */
             XRANGE(000, 000) ||, /* IL                               */
             XRANGE(024, 025) ||, /* CAN EM                           */
             XRANGE(000, 000) ||, /* CC                               */
             XRANGE(000, 000) ||, /* CU1                              */
             XRANGE(028, 031) ||, /* FS GS RS US                      */
             XRANGE(000, 000) ||, /* DS                               */
             XRANGE(000, 000) ||, /* SOS                              */
             XRANGE(028, 028) ||, /* FS                               */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* BYP                              */
             XRANGE(010, 010) ||, /* LF                               */
             XRANGE(023, 023) ||, /* ETB                              */
             XRANGE(027, 027) ||, /* ESC                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* SM                               */
             XRANGE(000, 000) ||, /* CU2                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(005, 007) ||, /* ENQ ACK BEL                      */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(022, 022) ||, /* SYN                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* PN                               */
             XRANGE(000, 000) ||, /* RS                               */
             XRANGE(000, 000) ||, /* UC                               */
             XRANGE(004, 004) ||, /* EOT                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* CU3                              */
             XRANGE(020, 020) ||, /* DC4                              */
             XRANGE(021, 021) ||, /* NAK                              */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(026, 026) ||, /* SUB                              */
             XRANGE(032, 032) ||, /* space                            */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /* cent sign                        */
             XRANGE(046, 046) ||, /* .                                */
             XRANGE(060, 060) ||, /* <                                */
             XRANGE(040, 040) ||, /* (                                */
             XRANGE(043, 043) ||, /* +                                */
             XRANGE(033, 033) ||, /* |                                */
             XRANGE(038, 038) ||, /* &                                */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(033, 033) ||, /* !                                */
             XRANGE(036, 036) ||, /* $                                */
             XRANGE(042, 042) ||, /* *                                */
             XRANGE(041, 041) ||, /* )                                */
             XRANGE(059, 059) ||, /* ;                                */
             XRANGE(000, 000) ||, /* not symbol                       */
             XRANGE(045, 045) ||, /* -                                */
             XRANGE(047, 047) ||, /* /                                */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(124, 124) ||, /* |                                */
             XRANGE(044, 044) ||, /* ,                                */
             XRANGE(037, 037) ||, /* %                                */
             XRANGE(095, 095) ||, /* _                                */
             XRANGE(062, 062) ||, /* >                                */
             XRANGE(063, 063) ||, /* ?                                */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(096, 096) ||, /* `                                */
             XRANGE(058, 058) ||, /* :                                */
             XRANGE(035, 035) ||, /* #                                */
             XRANGE(064, 064) ||, /* @                                */
             XRANGE(039, 039) ||, /* '                                */
             XRANGE(061, 061) ||, /* =                                */
             XRANGE(034, 034) ||, /* "                                */
             XRANGE(097, 105) ||, /* a - i                            */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(106, 114) ||, /* j - r                            */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(126, 126) ||, /* ~                                */
             XRANGE(115, 122) ||, /* s - z                            */
             COPIES(192 - 170, D2C(0)) ||,
             XRANGE(123, 123) ||, /* {                                */
             XRANGE(065, 073) ||, /* A - I                            */
             COPIES(209 - 202, D2C(0)) ||,
             XRANGE(074, 082) ||, /* J - R                            */
             COPIES(224 - 218, D2C(0)) ||,
             XRANGE(092, 092) ||, /* \                                */
             XRANGE(000, 000) ||, /*                                  */
             XRANGE(083, 090) ||, /* S - Z                            */
             COPIES(240 - 234, D2C(0)) ||,
             XRANGE(048, 057) ||, /* J - R                            */
             COPIES(256 - 250, D2C(0))


Uppercase & Lowercase including German "Umlaute"

/* sample code to translate a string to uppercase or lowercase which  */
/* also handles the German "Umlaute"                                  */
/* Note that there's an country-dependent uppercase translation v1.80 */
/* routine in the new REXXUTIL DLL. Object-Oriented REXX             */
    say "Lower() " lower( "AbcdEF Ö Ä Ü ß 1234567890" )
    say "Upper() " upper( "aBcDef ö ä ü ß 1234567890" )
exit

/* ------------------------------------------------------------------ */
/* function: Convert a char or string to uppercase                    */
/*                                                                    */
/* call:     Upper string                                             */
/*                                                                    */
/* where:    string - string to convert                               */
/*                                                                    */
/* returns:  the converted string                                     */
/*                                                                    */
/* note:     This implementation handles German "Umlaute"             */
/*                                                                    */
Upper: PROCEDURE
  parse arg thisString

RETURN translate( thisString, "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜß" ,,
                              "abcdefghijklmnopqrstuvwxyzäöüß" )

/* ------------------------------------------------------------------ */
/* function: Convert a char or string to lowercase                    */
/*                                                                    */
/* call:     Lower string                                             */
/*                                                                    */
/* where:    string - string to convert                               */
/*                                                                    */
/* returns:  the converted string                                     */
/*                                                                    */
/* note:     This implementation handles German "Umlaute"             */
/*                                                                    */
Lower: PROCEDURE
  parse arg thisString

RETURN translate( thisString,  "abcdefghijklmnopqrstuvwxyzäöüß" ,,
                               "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜß" )

Date converting routine - 1 -

Captured from a message in a public CompuServe Forum

Note from the author from the PL/1 version

Julian (sense 1) date routines, handling Julian (sense 2) and Gregorian calendars. Algorithm is valid from 4713 B.C. to 19,999 A.D. This version is known to be free of errors.

Based on Pascal code copyright 1985 by Michael A. Covington, published in P.C. Tech Journal, December 1985, based on formulae appearing in Astronomical Formulae for Calculators by Jean Meeus. Reconversion to normal Julian epoch, integer arithmetic, 4000-year correction, and translation to PL/I by John W. Kennedy

Historical exceptions _not_ allowed for in this module: Until Julius Caesar established the Julian calendar in 45 B.C., calendars were irregular. This module assumes the Julian calendar back to 4713 B.C. The Julian calendar was altered in 8 B.C. From 45 B.C. to 8 B.C., the months were Jan=31, Feb=29(30), Mar=31, Apr=30, May=31, Jun=30, Jul=31, Aug=30, Sep=31, Oct=30, Nov=31, Dec=30 This module assumes the month lengths as we know them. Leap years from 45 B.C. to 8 A.D. were miscalculated: (45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, then none at all until 8 A.D.) This module assumes leap years every four years, as they were meant to have been. January 1 was not always the first day of the year. The United Kingdom, in particular, started the year on March 25 until 1752. (However, the year ended on December 31, leaving the days between in limbo.) This module assumes January 1 is the first day of the year. Leap-year day was originally done by having February 24 (25 from 45 to 8 B.C.) twice. This module assumes Leap-year day is February 29.

The "Transition" argument is the first Julian date to be considered as belonging to the Gregorian calendar. Usual values are: 2299161 = October 5/15, 1582, as in Rome, or 2361222 = September 3/14, 1752, as in the United Kingdom and the American colonies

/* sample routines to convert dates                                   */

  call Charout , "Enter a date in the format dd.mm.yyyy: "
  curDMYDate = lineIn()

  curJulianDate = DMYToJulian( curDMYDate )
  say "DMYToJulian(" || curDMYDate || ") is " || curJulianDate

  say "JulianToDMY(" || curJulianDate || ") is " || ,
      JulianToDMY( curJulianDate )

exit 0

/* ------------------------------------------------------------------ */
/* function: Convert a date from Julian to DMY                        */
/*                                                                    */
/* call:     JulianToDMY julianDate {trans}                           */
/*                                                                    */
/* where:    julianDate - the date in julian format                   */
/*           trans - see note above                                   */
/*                                                                    */
/* returns:  the date in the format dd.mm.yyyy                        */
/*                                                                    */
/*                                                                    */
JulianToDMY: PROCEDURE
  Arg J Trans

  if Trans = "" then
    Trans = 2299161

  if J < Trans then
    A = J
  Else
  do
    AA = J - 1721120
    AC = Trunc(AA / 1460969)
    AB = 31 * AC
    AA = AA - AC * 1460969
    AC = Trunc(AA / 146097)
    AB = AB + 3 * AC
    AA = AA - AC * 146097
    if AA = 146096 then
      AB = AB + 3
    Else
      AB = AB + Trunc(AA / 36524)
    A = J + (AB - 2)
  end
  B = A + 1524
  C = Trunc((20 * B - 2442) / 7305)
  D = Trunc(1461 * C / 4)
  EE = B - D
  E = Trunc(10000 * EE / 306001)
  YMDD = EE - Trunc(306001 * E / 10000)
  if E >= 14 then
    YMDM = E - 13
  else
    YMDM = E - 1
  if YMDM > 2 then
    Y = C - 4716
  else
    Y = C - 4715
  if Y < 1 then
    YMDY = Y - 1
  else
    YMDY = Y

RETURN YMDD || '.' || YMDM || '.' || YMDY

/* ------------------------------------------------------------------ */
/* function: Convert a date from DMY to Julian                        */
/*                                                                    */
/* call:     DMYToJulian dmyDate {trans}                              */
/*           trans - see note above                                   */
/*                                                                    */
/* where:    dmyDate - the date in the format dd.mm.yyyy              */
/*                                                                    */
/* returns:  the date in Julian format                                */
/*                                                                    */
/*                                                                    */
DMYToJulian: PROCEDURE
  parse arg dmyDate trans

  parse var dmyDate YMDD "." YMDM "." YMDY

  if Trans = "" then
    Trans = 2299161

  AY = YMDY
  if YMDY < 0 then
    Y = YMDY + 4717
  else
    Y = YMDY + 4716
  if YMDM < 3 then
  do
    M = YMDM + 12
    Y = Y - 1
    AY = AY - 1
  end
  else
    M = YMDM
  D = Trunc((1461 * Y) / 4) + Trunc((153 * (M + 1)) / 5) + YMDD - 1524
  G = D + 2 - Trunc(AY / 100) + Trunc(AY / 400) - Trunc(AY / 4000)
  if G >= Trans then
    thisRC = G
  else
    thisRC = D

RETURN thisRC

Date converting routine - 2 -

/* sample routine to convert a date in the format dd/mm/yy into the   */
/* base date format                                                   */
/*                                                                    */
/* Description from the author:                                       */
/*   routine to convert a date passed in YY/MM/DD format (assumes the */
/*   date is 19YY/MM/DD ) to Base date format which is based upon an  */
/*   imaginary calendar date of 1/1/0001 it then assumes there is a   */
/*   leap year every 4 years and every 400 years but not if the year  */
/*   is divisble by 100                                               */
/*                                                                    */
/* Note: I do NOT know the author of this code.                       */
/*       I found this code on an IBM BBS.                             */
/*                                                                    */

  do forever
    say ""
    say "Test the routine CalcBaseDate against the REXX function date"
    say "  Note that the REXX function date only handles dates AFTER"
    say "  the 01.01.1980!"
    say "Enter a date to convert (dd.mm.yy, RETURN to end):"
    testDate = strip( lineIn() )

    if testDate = "" then
      leave

    say "  result of CalcBaseDate( """ || testDate || """) is: " || ,
         CalcBaseDate( testDate )

                        /* save the current date                      */
    oldDate = date( "E" )

                        /* set the current date to the testdate to    */
                        /* test the routine CalcBaseDate against the  */
                        /* REXX function date( B )                    */
    "@date " testDate
    say "  result of the REXX function date( ""B"" ) is: " || ,
         date( "B" )

                        /* restore the current date                   */
    "@date " oldDate
  end /* do forever */
exit 0

/* ------------------------------------------------------------------ */
/* function: Convert a date in the format dd.mm.yy into the base date */
/*           format                                                   */
/*                                                                    */
/* usage:    CalcBaseDate dateToConvert                               */
/*                                                                    */
/* where:    dateToConvert - date to convert in the format dd.mm.yy   */
/*                                                                    */
/* returns:  the date in base date format                             */
/*                                                                    */
CalcBaseDate: PROCEDURE

                        /* initialize routine                         */
  NonLeap.   = 31
  NonLeap.0  = 12
  NonLeap.2  = 28
  NonLeap.4  = 30
  NonLeap.6  = 30
  NonLeap.9  = 30
  NonLeap.11 = 30

                /* grab parameter and store it in cyear cmonth cdate  */
  parse arg cdate "." cmonth "." cyear .

                /* grab year and convert it to YYYY                   */
                /* simulate the behaviour of the REXX function date() */
  if length( cyear ) <= 2 then
    if cyear < 80 then
      fullyear = "20" || cyear
    else
      fullyear = "19" || cyear
  else
    fullyear = cyear

  numyears = fullyear -1
  basedays = numyears * 365
  QuadCentury = numyears % 400
  Century = numyears % 100
  LeapYears = numyears % 4
  basedays = basedays + (((LeapYears - Century) + QuadCentury) - 1)

  do i = 1 to (cmonth -1)
    if i <> "2" then
      basedays = basedays + NonLeap.i
    else /* find if it's a leap year or not */
      if (fullyear // 4) > 0 then
        basedays=basedays + 28
      else
        if ((fullyear // 100) = 0) & ((fullyear // 400) > 0) then
        do
                        /* century not divisble by 400                */
          basedays = basedays + 28
        end /* if */
        else
        do
                        /* quad century or regular leap year          */
          basedays = basedays + 29
        end /* else */
  end /* do */

  basedays = basedays + cdate
return basedays

Convert values from/to INTEL format

On Intel processors words and doublewords are saved in the so-called INTEL format (LSB - last signifcant byte first). To use them, you must convert them into the MSB format (MSB - most significant byte first). Before using the following routine, you must convert the value into a hex string (see Get the display resolution for an example).

 
/* ------------------------------------------------------------------ */
/* function: Convert an WORD or DWORD from LSB format to MSB format   */
/*           and vice versa                                           */
/*                                                                    */
/* call:     LSB2MSB inputHexString                                   */
/*                                                                    */
/* where:    inputHexstring - input value as hexstring                */
/*                            (e.g. "3412", "78563412")               */
/*                                                                    */
/* output:   value in MSB format as hexstring                         */
/*           (e.g. "1234", "12345678")                                */
/*                                                                    */
LSB2MSB: PROCEDURE
  HexZiffer = arg(1)                                         /* v3.00 */
  Len = Length(HexZiffer)                                    /* v3.00 */
  If (Len // 2) then                                         /* v3.00 */
     HexZiffer = Right(HexZiffer, Len + 1, '0')              /* v3.00 */
  RETURN strip( translate( "12345678",,                      /* v3.00 */
                           HexZiffer, "78563412" ) )         /* v3.00 */

Formatting numbers

/* sample routine to format a number into the format                  */
/*  nnn.nnn.nnn                                                       */
/* and vice versa                                                     */
/*                                                                    */
/* Source: I found routines of this type in various messages on the   */
/*         IBM REXX forums                                            */
/*         (see also EdmREXX - misc. functions for REXX)              */
/*                                                                    */
  do forever

    call CharOut , "Enter a number (RETURN to end): "
    thisNumber = lineIn()
    if thisNumber = "" then
      leave
    else
    do
      thatNumber = FormatNumber( thisNumber )
      call LineOut , "Result of FormatNumber( " || thisNumber || " ) is " ,
                     thatNumber

      call LineOut , "Result of UnFormatNumber( " || thatNumber || " ) is " ,
                     UnFormatNumber( thatNumber )
    end /* else */
  end /* do forever */

exit

/* ------------------------------------------------------------------ */
/* function: Format a number like 123456789.44 into the format        */
/*           123,456,789.44                                           */
/*                                                                    */
/* call:     FormatNumber number_to_format                            */
/*                                                                    */
/* where:    number_to_format                                         */
/*                                                                    */
/* returns:  the formatted number                                     */
/*                                                                    */
/* note:     works for all numbers up to (but not including)          */
/*           1.000,000,000,000,000.0                                  */
/*                                                                    */
/* Author:   Graham Ewart                                             */
/*                                                                    */
FormatNumber: PROCEDURE expose (exposeList)
  parse value arg(1) with whole "." decs

  formattedNumber = strip( reverse( translate( "abc,def,ghi,jkl,mno",,
                                   reverse(whole),,
                                   "abcdefghijklmno",",")),"L",",")

  if decs <> "" then
    formattedNumber = formattedNumber || "." || decs
RETURN formattedNumber

/* ------------------------------------------------------------------ */
/* function: Unformat a number like 123,456,789.44 into the format    */
/*           123456789.44                                             */
/*                                                                    */
/* call:     UnFormatNumber number_to_unformat                        */
/*                                                                    */
/* where:    number_to_unformat                                       */
/*                                                                    */
/* returns:  the unformatted number                                   */
/*                                                                    */
/* note:     works for all numbers                                    */
/*                                                                    */
UnFormatNumber: PROCEDURE

RETURN space( translate( arg(1), " ", "," ) , 0, )

Convert ASCII 850 string into/from ISO 8859-1 (1004) string

 
/* ------------------------------------------------------------------ */
/* function: Convert an ASCII 850 string into an ISO 8859-1           */
/*           (1004) string and vice versa                             */
/*                                                                    */
/* call:     PC_ISOB thisString, outP                                 */
/*                                                                    */
/* where:    thisString - the string to convert                       */
/*           outP - function code, either                             */
/*                    1004 to convert to ISO                          */
/*                  or                                                */
/*                     850 to convert to ASCII                        */
/*                                                                    */
/* output:   the converted string                                     */
/*                                                                    */
/* Notes:    Note that this not a complete codepage conversion.       */
/*           We only care about the letters.                          */
/*                                                                    */
/* Author:   Oliver Heidelbach (see EMail Addresses)                  */
/*                                                                    */
PC_ISO: PROCEDURE

  PARSE ARG thisString, outp

  aISO = 'a1 a2 a3 bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0',
         'd1 d2 d3 d4 d5 d6 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6',
         'e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f8 f9 fa fb fc',
         'fd fe ff'

  aPC =  'ad bd 9c a8 b7 b5 b6 c7 8e 8f 92 80 d4 90 d2 d3 de d6 d7 d8 d1',
         'a5 d2 d3 d4 e5 99 d8 eb e9 ea 9a ed e8 e1 85 a0 83 c6 84 86 91',
         '87 8a 82 88 89 8d a1 8c 8b d0 a4 95 a2 93 e4 94 9b 97 a3 96 81',
         'ec e7 98'

  if outp = 1004 then
    convbuf = Translate(thisString, X2C(aISO), X2C(aPC))
  else
    if outp = 850 then
      convbuf = Translate(thisString, X2C(aPC), X2C(aISO))

RETURN convbuf

Convert Microsoft/IEEE Float binary into a string in Classic REXX

(see also Convert Microsoft/IEEE Float binary into a string in Object REXX)

/**********************************************************************/
/* These routines are the original work of Thos Davis                 */
/* (see EMail Addresses)                                              */
/* and to the best of his knowledge do not include any copyrighted    */
/* materials.                                                         */
/*                                                                    */
/* These routines are hereby released into the Public Domain          */
/**********************************************************************/
/* Microsoft/IEEE Float binary:                                       *
  +--------------------------------------------------------------------+
  |bit |0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F|
  +====+=============================================+=+===============+
  |MKS |              mantissa                       |s|   exponent    |
  +----+---------------------------------------------+-+-------------+-|
  |IEEE|              mantissa                       |    exponent   |s|
  +--------------------------------------------------+---------------+-+
                                                                      */
/* In both cases, the mantissa is the lower (least significant)       */
/* 23 bits (plus an implied value of 1 for bit 24, the most           */
/* significant bit of the mantissa), the sign is one bit, and         */
/* the exponent is 8 bits.                                            */
/*                                                                    */
/* Because the mantissa has a 'virtual bit' whose value is always 1,  */
/* the exponent is used to determine if the value is 0.               */
/*                                                                    */
/* IEEE Double Float binary is the same format as the single Float    */
/* but the mantissa is 52 bits long (for 53 bits of significant       */
/* binary digits [is that bigits?] after including the 'virtual 1'    */
/* most significant bit) and the exponent is 11 bits long.            */
/*                                                                    */
/* !!! I M P O R T A N T !!!                                          */
/*                                                                    */
/* NUMERIC DIGITS should be set to about 16 to get the full value of  */
/* Doubles. If these procedures are made into ROUTINES, it will be    */
/* necessary to add the NUMERIC DIGITS setting to DoubleToString and  */
/* GeneralFloat.                                                      */
/*                                                                    */
/* !!! A L S O   I M P O R T A N T !!!                                */
/*                                                                    */
/* These functions do not recognize the special values                */
/*    +INF    plus infinity                                           */
/*    -INF    minus infinity                                          */
/*    +NAN    not a number                                            */
/*    -NAN    not a number                                            */
/*                                                                    */

mksToString: procedure
  TheFloat = arg(1)

                    /* mks is the format used in older versions of    */
                    /* MicroSoft BASIC and is, for some bizarre       */
                    /* reason, used as the index value in the QWK     */
                    /* BBS message packing scheme                     */

                    /* Intel uses different BYTE ordering and BIT     */
                    /* ordering so byte strings must be REVERSED to   */
                    /* make all ordering the same                     */
    bFloat = Reverse( TheFloat )

                    /* There is no c2b function                       */
    bFloat = x2b( c2x( bFloat ) )

                    /* make sure its 32 bits long                     */
    bFloat = Right( bFloat, 32, '0' )
    fMantissa = '1' || Right( bFloat, 23 )
    fExponent = Left( bFloat, 8 )
    fSign = SubStr( bFloat, 9, 1 )

                    /* I found the magicNumber values by trial and    */
                    /* error                                          */
    magicNumber = 152

return GeneralFloat( fSign, fMantissa, fExponent, magicNumber )


FloatToString: procedure
  TheFloat = arg(1)

    bFloat = Reverse( TheFloat )
    bFloat = x2b( c2x( bFloat ) )
    bFloat = Right( bFloat, 32, '0' )
    fMantissa = '1' || Right( bFloat, 23 )
    fExponent = SubStr( bFloat, 2, 8 )
    fSign = Left( bFloat, 1 )
    magicNumber = 150
return GeneralFloat( fSign, fMantissa, fExponent, magicNumber )


DoubleToString: procedure
  TheDouble = arg(1)

    bDouble = Reverse( TheDouble )
    bDouble = x2b( c2x( bDouble ) )
    bDouble = Right( bDouble, 64, '0' )
    dMantissa = '1' || Right( bDouble, 52 )
    dExponent = SubStr( bDouble, 2, 11 )
    dSign = Left( bDouble, 1 )
    magicNumber = 1075
return GeneralFloat( dSign, dMantissa, dExponent, magicNumber )


GeneralFloat: procedure
  theSign = arg(1)
  theMantissa = arg(2)
  theExponent = arg(3)
  magicNumber = arg(4)

    if theExponent = 0 then
        ascFloat = 0
    else
      do
        decMantissa = x2d( b2x( theMantissa ) )
        decExponent = x2d( b2x( theExponent ) )
        ascFloat = decMantissa * ( 2 ** ( decExponent - magicNumber ))
      end

    if theSign then
        ascFloat = '-'ascFloat

return ascFloat
AN ADDENDUM

While converting from binary fractions to decimal fractions has some inherent inaccuracies, the REXX procedures I gave have some additional ones built in. REXX does not use binary arithmetic (at least it is not supposed to). Instead it uses decimal methods modeled on human arithmetic systems. This gives better results for decimal numbers, but tends to make arithmetic with binary fractions somewhat less accurate.

Because of this, and because I use arithmetic on the numbers when converting, _sometimes_ 'conversion artifacts' will be introduced which result in a representation slightly greater or less than the value which is returned by standard C library functions (e.g. printf() ). For example 0.5 decimal (1/2), which is 0.1 binary, should translate perfectly from one system to the other, will be translated from the double to 0.5000000000000002 using DoubleToString. Likewise 0.0625 decimal (1/16), which is 0.0001 binary, is translated as 0.06250000000000001.

Additionally, the results are dependent on the value of NUMERIC DIGITS. For example: 1.0 is translated by FloatToString() as 0.99999999999 when NUMERIC DIGITS is set to 11 (decimal), and 0.5 (decimal) is translated by DoubleToString as 0.49999999999999999999 when NUMERIC DIGITS is 20.

It is important to note that the actual value stored in the file is not changed.

If it is important to see very precise translations, these procedures may not be for you. However, with the information on the format of the numbers, you may be able to devise your own conversion procedures.

Additionally, I did not include the IEEE +INFINITY, -INFINITY, +NOT-A-NUMBER, and -NOT-A-NUMBER, because I do not have documentation on these values. However, based on actual conversions by Borland's C++ for OS/2 (version 1.5), I am led to believe that an exponent whose bits are all set to 1 indicates a SPECIAL VALUE. If the exponent is all ones, and the mantissa (with the virtual bit) has only its most significant bit set to 1, then that is INFINITY (+/- depending on the sign), and if the two most significant bits (including the virtual bit) are both set to 1 and no other bits in the mantissa are set to 1, then that is NOT-A-NUMBER. If the other bits are set to 1, I don't know what that means.

I use a test for the special exponent in my own routines (I use ObjectREXX) and then call SpecialFloat if it matches. If you use this type of procedure, then it may be necessary for your program to test for these values before performing additional math on them! I do not know if the MicroSoft Format used by early versions of BASIC has any special values.

::ROUTINE FloatToString PUBLIC
    ...

    if fExponent = '11111111' then
      return SpecialFloat( fSign, fMantissa, 'S' )
    else
      return GeneralFloat( ... )


::ROUTINE SpecialFloat
  use arg theSign, theMantissa, theType

    SELECT
      WHEN theType = 'S' then lenMantissa = 24
      WHEN theType = 'D' then lenMantissa = 53
    END

    SELECT
      WHEN theMantissa = '1'~Left( lenMantissa, '0' ) THEN
        ieeeSpecial = 'INFINITY'
      WHEN theMantissa = '11'~Left( lenMantissa, '0' ) THEN
        ieeeSpecial = 'NOT-A-NUMBER'
      OTHERWISE
        ieeeSpecial = 'UNKNOWN-MEANING'
    END /* SELECT */

    if theSign then
      ieeeSpecial = '-'ieeeSpecial
    else
      ieeeSpecial = '+'ieeeSpecial

    return 'IEEE:' ieeeSpecial

Convert Microsoft/IEEE Float binary into a string in Object REXX

(see also Convert Microsoft/IEEE Float binary into a string in Classic REXX; especially the addendum)

/**********************************************************************/
/* These routines are the original work of Thos Davis                 */
/* (see EMail Addresses)                                              */
/* and to the best of his knowledge do not include any copyrighted    */
/* materials.                                                         */
/*                                                                    */
/* These routines are hereby released into the Public Domain          */
/**********************************************************************/
/* Microsoft/IEEE Float binary:                                       *
 +--------------------------------------------------------------------+
 |bit |0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F|
 +====+=============================================+=+===============+
 |MKS |              mantissa                       |s|   exponent    |
 +----+---------------------------------------------+-+-------------+-|
 |IEEE|              mantissa                       |    exponent   |s|
 +--------------------------------------------------+---------------+-+
                                                                      */
/* In both cases, the mantissa is the lower (least significant)       */
/* 23 bits (plus an implied value of 1 for bit 24, the most           */
/* significant bit of the mantissa), the sign is one bit, and         */
/* the exponent is 8 bits.                                            */
/*                                                                    */
/* Because the mantissa has a 'virtual bit' whose value is always 1,  */
/* the exponent is used to determine if the value is 0.               */
/*                                                                    */
/* IEEE Double Float binary is the same format as the single Float    */
/* but the mantissa is 52 bits long (for 53 bits of significant       */
/* binary digits [is that bigits?] after including the 'virtual 1'    */
/* most significant bit) and the exponent is 11 bits long.            */
/*                                                                    */
/* !!! I M P O R T A N T !!!                                          */
/*                                                                    */
/* NUMERIC DIGITS should be set to about 16 to get the full value of  */
/* Doubles                                                            */
/*                                                                    */
/* !!! A L S O   I M P O R T A N T !!!                                */
/*                                                                    */
/* These functions may not correctly recognize the special values     */
/*    +INF    plus infinity                                           */
/*    -INF    minus infinity                                          */
/*    +NAN    not a number                                            */
/*    -NAN    not a number                                            */
/*                                                                    */

::ROUTINE mksToString PUBLIC
  use arg TheFloat

                    /* mks is the format used in older versions of    */
                    /* MicroSoft BASIC and is, for some bizarre       */
                    /* reason, used as the index value in the QWK     */
                    /* BBS message packing scheme                     */

  if TheFloat~Length \= 4 then
    return 'NOT-A-FLOAT'

    bFloat = TheFloat~Reverse~c2x~x2b~Right(32,'0')
    fMantissa = '1' || bFloat~Right(23)
    fExponent = bFloat~Left( 8 )
    fSign = bFloat~SubStr( 9, 1 )
    magicNumber = 152
    return GeneralFloat( fSign, fMantissa, fExponent, magicNumber, Digits() )


::ROUTINE FloatToString PUBLIC
  use arg TheFloat

  if TheFloat~Length \= 4 then
    return 'NOT-A-FLOAT'

    bFloat = TheFloat~Reverse~c2x~x2b~Right(32,'0')
    fMantissa = '1' || bFloat~Right(23)
    fExponent = bFloat~SubStr( 2, 8 )
    fSign = bFloat~Left(1)
    magicNumber = 150

                    /* IS SPECIAL VALUE                               */
    if fExponent = '11111111' then
      return SpecialFloat( fSign, fMantissa, 'S' )
    else
      return GeneralFloat( fSign, fMantissa, fExponent, magicNumber, Digits() )


::ROUTINE DoubleToString PUBLIC
  use arg TheDouble
  NUMERIC DIGITS 16

  if TheFloat~Length \= 8 then
    return 'NOT-A-FLOAT'

    bDouble = TheDouble~Reverse~c2x~x2b~Right(64,'0')
    dMantissa = '1' || bDouble~Right(52)
    dExponent = bDouble~SubStr( 2, 11 )
    dSign = bDouble~Left(1)
    magicNumber = 1075

                    /* IS SPECIAL VALUE                               */
    if dExponent = '11111111111' then
      return SpecialFloat( dSign, dMantissa, 'D' )
    else
      return GeneralFloat( dSign, dMantissa, dExponent, magicNumber, Digits() )


::ROUTINE GeneralFloat
  use arg theSign, theMantissa, theExponent, magicNumber, numdigits
  NUMERIC DIGITS numdigits

    if theExponent = 0 then
        ascFloat = 0
    else
        ascFloat = (theMantissa~b2x~x2d) * ( 2 ** ( (theExponent~b2x~x2d) - magicNumber ))

    if theSign then
        ascFloat = '-'ascFloat

    return ascFloat


::ROUTINE SpecialFloat
  use arg theSign, theMantissa, theType

    SELECT
      WHEN theType = 'S' then lenMantissa = 24
      WHEN theType = 'D' then lenMantissa = 53
    END

    SELECT
      WHEN theMantissa = '1'~Left( lenMantissa, '0' ) THEN
        ieeeSpecial = 'INFINITY'
      WHEN theMantissa = '11'~Left( lenMantissa, '0' ) THEN
        ieeeSpecial = 'NOT-A-NUMBER'
      OTHERWISE
        ieeeSpecial = 'UNKNOWN-MEANING'
    END /* SELECT */

    if theSign then
      ieeeSpecial = '-'ieeeSpecial
    else
      ieeeSpecial = '+'ieeeSpecial

    return 'IEEE:' ieeeSpecial

Determine what day of the week a date falls on


 
/* sample code to determine what day of the week a date falls on      */
/*                                                                    */
/* Source: Found in a message in a public news group                  */
/*                                                                    */

  do forever
    say 'Enter the date in the format dd.mm.yyyy (<return> to exit):'
    thisDate = strip( translate( lineIn() ) )
    if thisDate = '' then
      leave
    say 'The day of the week for the ' || thisDate || ,
        ' is: ' || dayOfTheWeek( thisDate )
  end /* do forever */
exit

/* ------------------------------------------------------------------ */
/* function: Determine what day of the week a date falls on           */
/*                                                                    */
/* call:     dayOfTheWeek = DayOfTheWeek( thisDate )                  */
/*                                                                    */
/* where:    thisDate - date in the format dd.mm.yyyy                 */
/*                                                                    */
/* returns:  the name of the day or ERROR in case of an error         */
/*                                                                    */
/*                                                                    */
DayOfTheWeek: PROCEDURE

                    /* install a local error handler                  */
  signal on syntax name DayOfTheWeekError

  sep = '.'
  parse arg dd (sep) mm (sep) year

  days = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
  magic = 6 2 3 6 1 4 6 2 5 0 3 5
  parse var year cent +2 yr
  leap = year // 4 = 0 & (yr\=0 | cent//4=0)
  dow=yr + yr%4 + (6-2*(cent//4)) + word(magic,mm) + dd
  if mm < 3 & \leap then
    dow=dow+1
  dow = dow // 7
return word(days,dow+1)

DayOfTheWeekError:
  return 'ERROR'

Input & Output

Simulate the BASIC INPUT command

/* example for simulating the input command used in BASIC             */

                        /* example call ...                           */
  thisString = input( "Enter a string: " )
  say "You entered '" || thisString || "'."

exit

/* ------------------------------------------------------------------ */
/* function: Simulate the BASIC command INPUT                         */
/*                                                                    */
/* call:     input( prompt )                                          */
/*                                                                    */
/* where:    prompt - prompt for the input                            */
/*                                                                    */
/* returns:  entered string                                           */
/*                                                                    */
Input: PROCEDURE
  parse arg prompt

  if prompt = "" then
    prompt = "Your Input:"

                         /* set word wrap off                         */
  call CharOut , "1B"x || "[7l"

                         /* show the prompt string                    */
  call charOut , prompt || " "

                         /* get the user input                        */
  thisRC = lineIn()

                         /* set word wrap on again                    */
  call CharOut , "1B"x || "[7h"

RETURN thisRC

Simple Yes/No question

/* example for a simple yes/no question without loading the dll       */
/* REXXUTIL                                                           */

                        /* example call ...                           */
  thisKey = AskUser( "YN", "Enter Y or N: " )
  say "You entered '" || thisKey || "'."

exit

/* ------------------------------------------------------------------ */
/* AskUser - get input from the user                                  */
/*                                                                    */
/* Usage:    AskUser akeys, prompt                                    */
/*                                                                    */
/* where:                                                             */
/*           akeys - allowed keys (all keys are translated to         */
/*                   uppercase)                                       */
/*           prompt - prompt for the ask                              */
/*                                                                    */
/* Returns:  the pressed key in uppercase                             */
/*                                                                    */
/* note:     This routine uses ESC sequences to position the cursor.  */
/*           This routine only works if you do not use the            */
/*           last line of the screen for the prompt!                  */
/*                                                                    */
AskUser: PROCEDURE
  parse arg aKeys, prompt

  aKeys = translate( akeys )

                         /* set word wrap off                         */
  call CharOut , "1B"x || "[7l"

  if prompt <> "" then
    call charout ,  prompt

  thisKey = " "
  do UNTIL pos( thisKey ,  aKeys ) <> 0
    call charOut ,"1B"x || "[s" || "1B"x || "[K"
    thisKey = translate( charIn() )
    call CharOut , "1B"x || "[u"
                        /* delete the CR/LF sequence from             */
                        /* the keyboard buffer!                       */
    dummy = lineIn()

  end /* do until ... */

                         /* set word wrap on again                    */
  call CharOut , "1B"x || "[7h"

                        /* do a linefeed                              */
  say ""

RETURN thisKey

Choice routine for REXX

/* sample choice routine with timeout                                 */

                        /* load REXXUTIL                              */
 call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
 call SysLoadFuncs

                        /* default key if no key is pressed           */
 thisDefault= "Y"
                        /* timeout value in seconds                   */
 thisTimeOut = 10

 call CharOut , "Waiting for a key "
 parse value GetKey( thisDefault, thisTimeOut, "." ) with ,
             thisRC "," pressedKey "," KeyPressed "," isFunctionKey

 if thisRC = 1 then
   if keyPressed = 1 then
     if isFunctionKey = 1 then
       say "Pressed the function key with the code " || C2D( pressedKey )
     else
       say "Pressed key is <" || pressedKey || ">"
   else
     say "No key pressed. Using the default."
 else
   say "Error while calling GetKey()!"
exit 0

/* ------------------------------------------------------------------ */
/* function: Get a key with timeout                                   */
/*                                                                    */
/* usage:    GetKey default , timeOut, inProgressInd                  */
/*                                                                    */
/* where:    default       - default key                              */
/*           timeOut       - timeOut in seconds                       */
/*           inProgressInd - if <> "" char for the in progress        */
/*                           indicator                                */
/*                                                                    */
/* returns:  thisRc, key, keypressed, functionKey                     */
/*                                                                    */
/* where:    thisRC      - 1 if okay, else error                      */
/*           key         - pressed key (or default)                   */
/*           keypressed  - 1 if a key was pressed else 0              */
/*           functionKey - 1 if a function key was pressed else 0     */
/*                                                                    */
/* note:     This function does not work as desired in OO REXX! v2.30 */
/*           The reason is the different behaviour of the       v2.30 */
/*           function CHARS() in OO REXX!                       v2.30 */
/*                                                                    */
/*           [Tested with OBJREXX 6.00 12 Jul 1996]                   */
/*           [Fixed in OBJREXX 6.00 26 Feb 1997 and newer versions]   */
/*                                                                    */
GetKey: PROCEDURE
  parse arg default, timeOut, inProgressInd

                            /* init the return code(s)                */
  thisRC = 0
  thisKey = default
  keyPressed = 0
  functionKey = 0

                            /* install a local error handler          */
  SIGNAL ON SYNTAX Name GetKeyEnd

  do timeCount = 0 to timeOut
    if InProgressInd <> "" then
      call CharOut , inProgressInd

    if chars() <> 0 then
    do
                            /* there is a key available               */
      thisKey = SysGetKey( "NOECHO" )
      if thisKey = "00"x | thisKey = "E0"x then
      do
                            /* function key pressed                   */
        thisKey = SysGetKey( "NOECHO" )
        functionKey = 1
      end /* if thisKey = "00"x | thisKey = "E0"x then */
      keypressed = 1
      leave
    end /* if chars() <> 0 then */
    else
    do
                            /* wait a second                          */
      call SysSleep 1
    end /* else */
  end /* do timeCount = 0 to timeOut */

                            /* set rc to execution is okay            */
  thisRC = 1

  if inProgressInd <> "" then
    say ""

GetKeyEnd:
RETURN thisRC || "," || thisKey || "," || keyPressed || "," || FunctionKey

sprintf routine for REXX

This is an implementation of the C function sprintf in REXX from Bernie Schneider Captured from a message in a public Internet news group (see Internet - Newsgroups)

/* ------------------------------------------------------------------ */
/* Program: SPRINTF                                                   */
/* Purpose: Returns a string formatted according to the format string */
/*          specified in the first argument and the values specified  */
/*          in the remaining arguments. It is modeled after the       */
/*          SPRINTF function in the C/C++, AWK, PERL, etc.            */
/*          programming languages.                                    */
/*                                                                    */
/* Usage:   say sprintf(fmt_string, value1, value2, ..., valuen)      */
/*                                                                    */
/*  where fmt_string specifies the formatting to be done on the       */
/*  remaining arguments. The string is composed of literals and       */
/*  format specifiers. There should be one format specifier for       */
/*  each value to be formatted.                                       */
/*  Literals are considered to be anything that isn't a format        */
/*  specifier. Format specifiers are coded as follows:                */
/*                                                                    */
/*    %[-][w][.d]z                                                    */
/*                                                                    */
/*  where                                                             */
/*    "%" indicates the start of a format specifier. To use a %       */
/*        in a literal, specify two concecutive %s (%%, no argument   */
/*        value will be consumed).                                    */
/*    "-" is optional, and indicates that the argument value is to be */
/*        left justified in its field. If it isn't present, the value */
/*        will be right justified.                                    */
/*    "w" is optional, and it specifies the width of the field in     */
/*        characters. If it isn't specified, then as many characters  */
/*        as necessary will be used. For decimal numbers, this will   */
/*        depend on the current value of the numeric digits setting.  */
/*        The value will be truncated or padded with blanks as        */
/*        necessary to fit the width specified. Numbers to the right  */
/*        of the decimal point are padded with "0"s, and will be      */
/*        rounded if truncation is necessary. If a numeric value      */
/*        is -1 < valuen < +1, it will be padded with leading "0"s.   */
/*   ".d" is optional, and it specifies the maximum string width, or  */
/*        the number of digits to the right of the decimal point.     */
/*    "z" is a single character that indicates the type of conversion */
/*        to be performed on the corresponding argument value. It may */
/*        be one of the following:                                    */
/*          "d", formats a signed decimal integer                     */
/*          "f", formats a signed decimal real number                 */
/*          "s", formats a character string                           */
/*          "x", formats an unsigned hexadecimal number               */
/*                                                                    */
/*        Examples:                                                   */
/*          say sprintf("Number = %d", 25)                            */
/*            -> "Number = 25"                                        */
/*          say sprintf("Number = %5d", 25)                           */
/*            -> "Number =    25"                                     */
/*          say sprintf("String = %s", "March")                       */
/*            -> "String = March"                                     */
/*          say sprintf("String = %5.3s", "March")                    */
/*            -> "String =   Mar"                                     */
/*          say sprintf("Num = %8.2f", -123.456)                      */
/*            ->  "Num =  -123.46"                                    */
/*          say sprintf("Num = '%-4x'  String = '%-10s'", ,           */
/*                255, ,                                              */
/*                "AbCde")                                            */
/*            -> "Num = 'FF  '  String = 'AbCde     '"                */
/*          say sprintf("%5.1f%%", .1757 * 100)                       */
/*            -> " 17.6%"                                             */
/*          say sprintf("Num = %7.3f", 0.25)                          */
/*            -> "Num = 000.250"                                      */
/*                                                                    */
/* Written: 27Nov95                                                   */
/* Language:REXX                                                      */
/* Author:  Bernie Schneider                                          */
/* Notes:                                                             */
/* Revised:                                                           */
/*====================================================================*/
/*                                                                    */
sprintf: procedure
  argno = 1                     /* Initialize argument counter        */
  string = ""
  start = 1                     /* Initialize pointer                 */
  len = length(arg(1))

  do until(p >= len)
    s = ""
    argno = argno + 1
    p = pos("%", arg(1), start)
    if p = 0 then
    do
      p = len + 1
    end
    if substr(arg(1), p, 1) == "%" then
    do
      s = "%"
    end
    string = string || substr(arg(1), start, p - start)
    start = p + 1
    p = verify(arg(1), "%cdfsx", "M", start)
    if p = 0 then
      leave
    spec = substr(arg(1), start, p - start + 1)
    start = p + 1
    r = right(spec, 1)
    spec = delstr(spec, length(spec), 1)
    if left(spec,1) == "-" then
    do                          /* Get any additional specs           */
      left = 1
      spec = substr(spec, 2)
    end
    else
    do
      left = 0
      spec = substr(spec, 1)
    end
    if spec \== "" then                 /* Get width and precision    */
      parse var spec width "." prec
    else
    do
      width = 0
      prec = 0
    end
    if \datatype(width, "W") then
      width = 0
    if \datatype(prec, "W") then
      prec = 0
    pad = " "

    select

      when r == "s" then
      do
        if width = 0 then
          width = length(arg(argno))
        if prec \= 0 then
          s = left(arg(argno), prec)     /* Truncate or pad           */
        else
          s = arg(argno)
      end

      when r == "d" then
      do
        if width = 0 then
          width = length(arg(argno))
        s = format(arg(argno), length(arg(argno)), 0)
      end

      when r == "f" then
      do
        if arg(argno) > -1 & arg(argno) < 1 then
          pad = "0"
        parse value arg(argno) with int "." frac
        if width = 0 & prec = 0 then
        do
          d = 1
          if arg(argno) < 0 then d = 2
          width = digits() + d
          prec = digits() - (length(int)) + d - 1
        end
        if width = 0 then
          width = len - prec
        s = format(arg(argno), width, prec)
      end

      when r == "x" then
      do
        if width = 0 then
          width = length(arg(argno))
        s = d2x(arg(argno))
        if prec \= 0 then
          s = left(s, prec)     /* Truncate or pad                    */
      end

      when r == "%" then
      do
        argno = argno - 1
      end

      otherwise
        nop

    end /* select */

    if r \== "%" then
    do
      if left then
        s = left(strip(s), width, pad)      /* Justify                */
      else
        s = right(strip(s), width, pad)
    end
    string = string || s
  end /* do until(p >= len) */
return string

Disk-, Directory- and File-Handling

Get the current boot drive

/* ------------------------------------------------------------------ */
/* function: get the OS/2 boot drive                                  */
/*                                                                    */
/* call:     bootDrive = GetOS2BootDrive()                            */
/*                                                                    */
/* where:    -                                                        */
/*                                                                    */
/* returns:  the OS/2 boot drive                                      */
/*                                                                    */
/* note:                                                              */
/*                                                                    */
/* Note that there are functions to get the boot drive in many        */
/* REXX DLLs available, for example in the RXLANUTIL DLL or in the    */
/* new REXXUTIL DLL. introduced with Object-Oriented REXX             */
/*                                                                    */
GetOS2BootDrive: PROCEDURE expose (exposeList)

                    /* load the REXXUTIL functions                    */
  call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
  call SysLoadFuncs

                    /* install a local error handler                  */
  signal on Syntax name GetOS2BootDrive1

  boot_drive = ''
  boot_drive = SysBootDrive()

GetOS2BootDrive1:
                    /* if SysBootDrive() failed, boot_drive is still  */
                    /* empty                                          */
                    /* SysBootDrive() is only in the newer versions   */
                    /* of REXXUTIL!                                   */
  if boot_drive = '' then
  do
                    /* You should do further tests to ensure that     */
                    /* the result of this method is correct!          */
    parse upper value VALUE( "PATH",, prog.__env ) with "\OS2\SYSTEM" -2,
                        boot_drive +2
  end /* if boot_drive = '' then */

return boot_drive

Check if a drive is ready

/* sample code to test if a drive is ready                            */
/* note:     This routine does not work under Object-Oriented REXX    */
/*                                                                    */
/*           [Tested with OBJREXX 6.00 12 Jul 1996]                   */
/*           [Fixed in OBJREXX 6.00 26 Feb 1997 and newer versions]   */
/*                                                                    */

  do forever
    call lineOut , "Enter the name of the drive to test " ,
                   "(RETURN to end): "
    thisDrive = strip( lineIn() )
    if thisDrive = "" then
      leave

    if DriveReady( thisDrive ) = 1 then
      say "The drive <" || thisDrive || "> is ready."
    else
      say "The drive <" || thisDrive || "> is not ready."
  end /* do forever */

exit 0

/* ------------------------------------------------------------------ */
/* function: Check if a drive is ready                                */
/*                                                                    */
/* call:     DriveReady( testDrive )                                  */
/*                                                                    */
/* where:    testdrive - Name of the drive to test (e.g. "A:")        */
/*                                                                    */
/* returns:  1 - drive is ready                                       */
/*           0 - drive is not ready                                   */
/*                                                                    */
/* note:     This routine does not work under Object-Oriented REXX    */
/*           if the line AUTOFAIL=NO is missing in the CONFIG.SYS     */
/*                                                                    */
/*           [Tested with OBJREXX 6.00 12 Jul 1996]                   */
/*           [Fixed in OBJREXX 6.00 26 Feb 1997 and newer versions]   */
/*                                                                    */
DriveReady: PROCEDURE
  parse arg driveToTest ":" .

  thisRC = 0

                        /* install a temporary error handler to check */
                        /* if the drive is ready                      */
  SIGNAL ON NOTREADY Name DriveReadyEnd

  call stream driveToTest || ":\*", "D"
  thisRC = 1

DriveReadyEnd:
  RETURN thisRC

Check if a directory exists

/* sample code to test if a directory exists with restoring all       */
/* directories                                                        */

  do forever
    call lineOut , "Enter the name of the directory to test " ,
                   "(RETURN to end): "
    thisDir = strip( lineIn() )
    if thisDir = "" then
      leave

    if DirExist( thisDir ) <> "" then
      say "The directory <" || thisDir || "> exist."
    else
      say "The directory <" || thisDir || "> does not exist."
  end /* do forever */

exit 0

/* ------------------------------------------------------------------ */
/* function: Check if a directory exists                              */
/*                                                                    */
/* call:     DirExist( testDir )                                      */
/*                                                                    */
/* where:    testDir - name of the directory to test                  */
/*                                                                    */
/* returns:  full name of the directory or "" if the directory        */
/*           don't exist                                              */
/*                                                                    */
DirExist: PROCEDURE
  parse arg testDir .

                        /* init the return code                       */
  thisRC = ""

                        /* test for missing or invalid parameter      */
  testDir = strip( testDir )                                 /* v3.20 */
  if testDir = "" then                                       /* v3.20 */
    signal DirDoesNotExist                                   /* v3.20 */

  if right( testDir, 1 ) = '\' then                          /* v3.20 */
    testDir = dbrright( testDir,1 )                          /* v3.20 */
  testDir = testDir || '\.'                                  /* v3.20 */

                        /* install a temporary error handler to check */
                        /* if the drive with the directory to test is */
                        /* ready                                      */
  SIGNAL ON NOTREADY NAME DirDoesNotExist

                        /* check if the drive is ready                */
  call stream testDir || "\*", "D"


                        /* save the current directory of the current  */
                        /* drive                                      */
  curDir = directory()

                        /* save the current directory of the drive    */
                        /* with the directory to test                 */
  curDir1 = directory( fileSpec( "drive", testDir ) )


                        /* test if the directory exists               */
  thisRC = directory( testDir )

                        /* restore the current directory of the drive */
                        /* with the directory to test                 */
  call directory curDir1

                        /* restore the current directory of the       */
                        /* current drive                              */
  call directory curDir
DirDoesNotExist:

return thisRC

Work on directory trees

/* example for working on a directory tree without loading the dll    */
/* REXXUTIL                                                           */

                        /* flush the REXX queue                 v3.20 */
  do while queued() <> 0; parse pull; end;

                        /* put a list of all directories in the queue */
                        /* (use /FIFO to get the directories in the   */
                        /*  right order)                              */
  "@dir /s/f /Ad C:\ 2>NUL | RXQUEUE /FIFO"

  foundDirs.0 = 0
                    /* put the names of all found directories         */
                    /* in a compound variable for further processing  */
  do while queued() <> 0
    curDir = strip( lineIn( "QUEUE:" ) )
    if curDir <> "" & ,
       right( CurDir, 2 ) <> "\."  & ,
       right( CurDir, 3 ) <> "\.." then
    do
      j = foundDirs.0 + 1
      foundDirs.j = curDir
      foundDirs.0 = j
    end /* if curDir <> "" then */
  end /* do while queued <> 0 */

Create a directory(-tree)

/* ------------------------------------------------------------------ */
/* function: Create a directory(tree)                                 */
/*                                                                    */
/* call:     CreateDirectory dirToCreate                              */
/*                                                                    */
/* where:    dirToCreate - directory to create                        */
/*                                                                    */
/* example:  call dirToCreate C:\TEST1\TEST2\TEST3\TEST4              */
/*           will create the directories                              */
/*             C:\TEST1                                               */
/*             C:\TEST1\TEST2                                         */
/*             C:\TEST1\TEST2\TEST3                                   */
/*           and                                                      */
/*             C:\TEST1\TEST2\TEST3\TEST4                             */
/*                                                                    */
/* returns:  0 - okay                                                 */
/*           else OS Error                                            */
/*                                                                    */
CreateDirectory: PROCEDURE
  parse arg dirToCreate

                        /* file or device for messages                */
  prog.__LogAll = "2>NUL 1>NUL"

                        /* init the return code                       */
  thisRC = -1
                        /* check if the drive is ready                */
  SIGNAL ON NOTREADY Name CreateDirectoryError
  call stream fileSpec( "drive", dirToCreate ) || "\*"

  thisRC = 0
                        /* save the current directories               */
  curDir = directory()
  curDir1 = directory( fileSpec( "drive", dirToCreate ) )

  newDir = translate( dirToCreate, "\", "/" )

  i = pos( ":", dirToCreate )
  if i <> 0 then
  do
    parse var dirToCreate lwForTheDir ":" dirToCreate
    if directory( lwForTheDir || ":\" ) = "" then
      thisRC = 1
  end /* if i <> 0 then */

  if thisRC = 0 then
  do
    if right( dirToCreate, 1 ) <> "\" then
      dirToCreate = dirToCreate || "\"

    do until dirToCreate = "" | thisRC <> 0
      parse var dirToCreate newSubDir "\" dirToCreate
      dirToCreate = strip( dirToCreate )

      if newSubDir = '' then                                 /* v3.20 */
        iterate                                              /* v3.20 */

      if directory( newSubDir ) = "" then
      do
        '@md "' || newSubDir || '"' prog.__LogAll            /* v3.00 */
        if rc = 2 | rc = 1 then
        do
          if stream( newSubDir , "c", "QUERY EXISTS" ) <> "" then
            thisRC = rc
        end /* if rc = 2 | rc = 1 */
        else
          thisRC = rc

        if thisRC = 0 then
          call directory newSubDir
      end /* if directory( newSubDir ) = "" then */
    end /* do until dirToCreate = "" | thisRC <> 0 */
  end /* if thisRC = 0 then */

                        /* restore the current directories            */
  call directory curDir1
  call directory curDir

CreateDirectoryError:

RETURN thisRC

Delete a directory(-tree)

 
/* ------------------------------------------------------------------ */
/* function: Delete all files in a directory and in all its           */
/*           sub directories!                                         */
/*                                                                    */
/* call:     DeleteDirectory dirToDelete                              */
/*                                                                    */
/* where:    dirToDelete - directory to delete                        */
/*                                                                    */
/* returns:  0 - okay                                                 */
/*          -1 - drive not ready                                      */
/*          -2 - missing or invalid parameter                   v3.10 */
/*                                                                    */
/* note:     see also The function SysDestroyObject                   */
/*                                                                    */
DeleteDirectory: PROCEDURE
  parse arg dirToDelete

  signal off error                                           /* v3.20 */
  signal off notready                                        /* v3.20 */
  signal off failure                                         /* v3.20 */

  if dirToDelete = '' then                                   /* v3.10 */
  do                                                         /* v3.10 */
                        /* check for missing parameter          v3.10 */
    thisRC = -2                                              /* v3.10 */
    signal DeleteDirectoryError                              /* v3.10 */
  end /* if */

                        /* file or device for messages                */
  prog.__LogAll = "2>NUL 1>NUL"

                        /* init the return code                       */
  thisRC = -1
                        /* check if the drive is ready                */
  SIGNAL ON NOTREADY Name DeleteDirectoryError
  call stream fileSpec( "drive", dirToDelete ) || "\*"

                        /* flush the REXX queue                 v3.20 */
  do while queued() <> 0; parse pull; end;

                        /* put a list of all subdirectories in the    */
                        /* queue                                      */
  "@dir /s/f /Ad " dirToDelete "2>NUL | RXQUEUE /lifo "

  do while queued() <> 0
    dirToDel = lineIn( "QUEUE:" )
    if dirTodel <> "" & right( dirToDel,2 ) <> "\." & ,
       right( dirToDel,3 )  <> "\.." then
    do
                    /* also delete hidden, system and read-only files */
                                                             /* v3.20 */
      '@attrib -r -s -h "' || dirToDel || '\*.*"' "2>NUL 1>NUL"

                                                             /* v3.20 */
      if stream( dirToDel || '\*.*', 'c', 'QUERY EXISTS' ) <> '' then
        '@del /n "' || dirToDel || '\*.*"' prog.__LogAll     /* v3.00 */

      if dirToDel <> dirToDelete then
        '@rd  "' || dirToDel || '"' prog.__LogAll            /* v3.00 */
    end /* if dirToDel <> "" then */
  end /* do while queued <> 0 */

                    /* also delete hidden, system and read-only files */
                                                             /* v3.20 */
  '@attrib -r -s -h "' || dirToDelete || '\*.*"' "2>NUL 1>NUL"

                                                             /* v3.20 */
  if stream( dirToDelete || '\*.*', 'c', 'QUERY EXISTS' ) <> '' then
    '@del /n "' || dirToDelete || '\*.*"' prog.__LogAll

  '@rd "' || dirToDelete || '"' prog.__logAll                /* v3.00 */

  thisRC = 0

DeleteDirectoryError:

RETURN thisRC

Check if a name describes a device or a file

/* check if a name is the name of a file or the name of a device      */
/*                                                                    */
/* note:                                                        v2.40 */
/*                                                                    */
/*  Another method to test if a name is a device name or filename is  */
/*                                                                    */
/*   testName = stream( testFileName, "c", "QUERY EXISTS" )           */
/*   if fileSpec( "P", testName ) = "\DEV\" then                      */
/*      say testFileName || " is a device."                           */
/*   else                                                             */
/*      say testFileName || " can be a file or directory."            */
/*                                                                    */

  if stream( testFileName, "c", "QUERY EXISTS" ) <> "" then  /* v2.00 */
    if stream( testFileName, "c", "QUERY DATETIME" ) = "" then
      say TestFileName || " is a device!"
    else
      say TestFileName || " is a file!"
  else                                                       /* v2.00 */
    say TestFileName || " does not exist!"                   /* v2.00 */

Check if a file exist

 
/* example code to check if a file exists. This function also checks, */
/* if the name is already used by a directory or a device (e.g. CON)  */
/* Note that this routine won't find hidden or system files.          */

  do until fileName = ""
    call charOut, "Enter a filename to check: "
    fileName = lineIN()
    say "The result of FileExist(" || fileName || ") is: " || ,
        FileExist( fileName )                                /* v2.60 */
  end /* do until iLIne = "" */

exit 0

/* ------------------------------------------------------------------ */
/* function: Check if a file exists                                   */
/*                                                                    */
/* call:     FileExist fileToTest                                     */
/*                                                                    */
/* where:    fileToTest - name of the file to test                    */
/*                                                                    */
/* returns:  -2 - invalid parameter                                   */
/*           -1 - cannot detect (e.g. the drive is not ready)         */
/*            0 - neither a file, a device nor a directory with this  */
/*                name exist                                          */
/*            1 - the file exist                                      */
/*            2 - a device driver with the name exists                */
/*            3 - a directory with the name exists                    */
/*                                                                    */
FileExist: PROCEDURE
  parse arg fileName                                         /* v2.90 */

                        /* install temporary error handler            */
  SIGNAL ON NOTREADY NAME FileExistError
  SIGNAL ON FAILURE  NAME FileExistError
  SIGNAL ON ERROR    NAME FileExistError

  thisRC = -2           /* rc = -2 ->> invalid parameter              */

                        /* check the parameter                        */
  if strip( fileName ) <> "" then
  do
    thisRC = -1         /* rc = -1 ->> cannot detect the result       */

                        /* check if the drive with the file is ready  */
    call stream fileName
                        /* turn of some error handling so we can      */
                        /* determine if the given name is the name of */
                        /* a device (for example "LPT1")              */
    SIGNAL OFF NOTREADY

    if stream( fileName, "c", "QUERY EXISTS" ) <> "" then
    do
                        /* seems that the file exists -- check if     */
                        /* it is a device                             */
      if stream( fileName, "c", "QUERY DATETIME" ) == "" then
        thisRC = 2      /* rc = 2 ->> this is a device name           */
      else
        thisRC = 1      /* rc = 1 ->> this is a file name             */
    end /* if stream( ... */
    else
    do
                        /* seems that the file does not exist --      */
                        /* check if a directory with the name for the */
                        /* file exist                                 */

                        /* save the current directory of the current  */
                        /* drive                                      */
      thisDir = directory()
                        /* save the current directory of the drive    */
                        /* with the file to check                     */
      tempDir = directory( fileSpec( "Drive", fileName ) )

      if directory( fileName ) <> "" then
        thisRC = 3      /* rc = 3 ->> a dir with this name exists     */
      else
        thisRC = 0      /* rc = 0 ->> neither a file, a device nor a  */
                        /*            dir with this name exists       */

                        /* restore the current directory of the drive */
                        /* with the directory to check                */
      call directory tempDir
                        /* restore the current directory of the       */
                        /* current drive                              */
      call directory thisDir
    end /* else */
  end /* if strip( fileName ) <> "" then */

FileExistError:

RETURN thisRC

Get a name for a temporary file

/* routine to get an unique name for a temporary file                 */

                        /* example call                               */
  tempFile = GetTempFile()
  if tempFile <> "" then
    say "The name of the temporary file is " || tempFile
  else
    say "Error: Cannot find a name for a temporary file!"

                        /* close & delete the temporary file          */
  if tempFile <> "" then
  do
    call stream tempFile, "C", "CLOSE"
    '@del "' || tempFile || '"2>NUL 1>NUL'
  end /* if tempFile <> "" then */

exit 0

/* ------------------------------------------------------------------ */
/* function: Get an unique name for a temporary file                  */
/*                                                                    */
/* call:     GetTempFile {noOfTrys} {,targetDir}                      */
/*                                                                    */
/* where:    noOfTrys - no. of trys                                   */
/*                      (optional, def.: 999)                         */
/*           targetDir - target dir for the file                      */
/*                       The directory must exist                     */
/*                       (optional, def.: use the environment         */
/*                       variable TEMP, the environment variable      */
/*                       TMP or the current directory [in this order])*/
/*                                                                    */
/* returns:  name of the file                                         */
/*           or "" if no name was found                               */
/*                                                                    */
/* note:     If GetTempFile finds a name for a new temporary file,    */
/*           it opens this file to prevent it from being used by      */
/*           another process!                                         */
/*           The name of the file is in the format $$nnn$$.TMP where  */
/*           nnn is a number between 000 and 999.                     */
/*                                                                    */
/*           RXTT v2.90: Added parameter noOfTrys and tPath           */
/*                                                                    */
GetTempFile: PROCEDURE expose (exposeList)
  parse arg noOfTrys, tPath                                  /* v2.90 */

  if noOfTrys = '' | datatype( noOfTrys ) <> 'NUM' then      /* v2.90 */
    noOfTrys = 999                                           /* v2.90 */

  if tPath = '' then                                         /* v2.90 */
  do
                        /* get the path for the temporary file        */
    tPath = value( "TEMP", , prog.__Env )
    if tPath = "" then
      tPath = value( "TMP", , prog.__Env )
    if tPath = "" then
      tPath = directory()

    tPath = translate( tPath )                               /* v2.20 */
  end /* if */

  tName = ""
                        /* save the current drive and directory       */
  CurDir = directory()

                        /* get the drive with the directory for the   */
                        /* temporary files                            */
  CurTDrive = filespec( "Drive", tPath )

                        /* save the current directory of the drive    */
                        /* with the directory for temporary files!    */
  CurTDir = directory( curTDrive )

  if directory( tPath ) = tPath then
  do

                        /* restore the current directory of the drive */
                        /* with the directory for temporary files!    */
    call directory CurTDir
                        /* restore the current drive and directory    */
    call directory CurDir

    tPath = strip( tPath, "B", '"' )
    if right( tPath, 1 ) <> "\" then
      tPath = tPath || "\"

    do i=0 to noOfTrys
      tName = tPath || "$$" || right( "000" || i, 3 ) || "$$" || ".TMP"
      if stream( tName, "C", "QUERY EXISTS" ) = "" then
        if stream( tName, "C",,
                   "OPEN WRITE" ) = "READY:" then            /* v2.20 */
          leave i

      tName = ""
    end /* do i=1 to noOfTrys */
  end /* if directory() = ... */

RETURN tName

Search a file

 
/* routine(s) to search a file in the directories saved in an         */
/* environment variable (e.g. "PATH")                                 */
/* Note that this routine won't find hidden or system files.          */

                        /* example call                               */
  do forever
    say "Enter a filename ("""" to abort):"
    searchFile = strip( lineIn() )

    if searchFile = '' then
      leave

    say "Result of SearchDataFile(" || searchFile || ") is: "
    say "  """ || SearchDataFile( searchFile ) || """"
    say "Result of SearchProgram(" || searchFile || ") is: "
    say "  """ || SearchProgram( searchFile ) || """"
  end /* do forever */

exit 0

/* ------------------------------------------------------------------ */
/* function: Search a file in the current directory and in the        */
/*           directories saved in an environment variable             */
/*           (e.g. PATH, DPATH, ... )                                 */
/*                                                                    */
/* call:     SearchFile( fileName, varName {,environment})            */
/*                                                                    */
/* where:    fileName - name of the file to search                    */
/*           varName - name of the environment variable containing    */
/*                     the directories to search                      */
/*           environment - environment with the environment Variable  */
/*                         (Def.: OS2ENVIRONMENT)                     */
/*                                                                    */
/* returns:  full name of the file or "" if not found                 */
/*                                                                    */
SearchFile: PROCEDURE
  parse arg fileName , envVar, environment
  resultStr = ""

  if fileName <> "" & envVar <> "" then
  do
    if environment = '' then
      environment = "OS2ENVIRONMENT"

    searchDirs = ".;" || value( envVar, , environment )

    do forever
      parse var searchDirs curSearchDir ";" searchDirs

      curSearchDir = strip( curSearchDir )

      if curSearchDir = "" then
        iterate

      if right( curSearchDir, 1 ) <> "\" & ,
         right( curSearchDir, 1 ) <> ":" then
        curSearchDir = curSearchDir || "\"

      resultStr = stream( curSearchDir || fileName, "c", "QUERY EXISTS" )
      if resultStr <> "" then
        leave

      if SearchDirs = "" then
        leave

    end /* do forverver */
  end /* if fileName <> "" & ... */

RETURN resultStr

/* ------------------------------------------------------------------ */
/* function: Search a file in the current directory and in the        */
/*           directories saved in the environment variable PATH       */
/*                                                                    */
/* call:     SearchProgram( progName {,environment})                  */
/*                                                                    */
/* where:    progName - name of the program to search                 */
/*           environment - environment with the environment Variable  */
/*                         (Def.: OS2ENVIRONMENT)                     */
/*                                                                    */
/* returns:  full name of the program or "" if not found              */
/*                                                                    */
SearchProgram: PROCEDURE
  parse arg progName , environment
  resultStr = ""
  if progName <> "" then
    resultStr = SearchFile( progName, "PATH", environment )
RETURN resultStr

/* ------------------------------------------------------------------ */
/* function: Search a datafile in the current directory and in the    */
/*           directories saved in the environment variable DPATH      */
/*                                                                    */
/* call:     SearchProgram( datafileName {,environment})              */
/*                                                                    */
/* where:    datafileName - name of the datafile to search            */
/*           environment - environment with the environment Variable  */
/*                         (Def.: OS2ENVIRONMENT)                     */
/*                                                                    */
/* returns:  full name of the datafile or "" if not found             */
/*                                                                    */
SearchDataFile: PROCEDURE
  parse arg dataFileName , environment
  resultStr = ""
  if dataFileName <> "" then
    resultStr = SearchFile( datafileName, "DPATH", environment )
RETURN resultStr

Count lines in a file quickly

/* sample code to count the lines in a file quickly                   */
/*                                                                    */
/* Source: I found this code in a message on the internet             */
/*                                                                    */
/* see also Count lines in a file quickly - 2 -                       */
/*                                                                    */

                        /* name of the file                           */
  testFile = "C:\OS2\INISYS.RC"

  call time "r"

  noOfLines = CountLines( testFile )

  if noOfLines >= 0 then
  do
    say "The file " || testFile || " contains " || ,
        noOfLines || " lines."
    say "It took " || time( "e" ) || " seconds to get the number."

  end /* if */
  else
    say "Error reading the file " || testFile

exit 0

/* ------------------------------------------------------------------ */
/* function: Get the number of lines in a file                        */
/*                                                                    */
/* call:     CountLines( fileName )                                   */
/*                                                                    */
/* where:    fileName - name of the file                              */
/*                                                                    */
/* returns:  n                                                        */
/*             if n is >= 0 then it is the number of lines            */
/*             if n is < 0 an error occured                           */
/*                                                                    */
CountLines: PROCEDURE
  parse arg fileName

  if stream( fileName, "c", "QUERY EXISTS" ) <> "" then
  do
    if stream( fileName, "c", "OPEN READ" )  = "READY:" then
    do

      thisRC = 0
      do while chars( fileName ) > 0

        thisRC = thisRC + ,
         words( translate( charin( fileName, , 24020 ), "          1" ) )

      end /* do while chars( fileName ) > 0 */
      call stream fileName, "c", "close"

    end /* if stream( fileName, "c", "open read" )  = "READY:" then */
    else
      thisRC = -1

  end /* if stream( fileName, "c", "QUERY EXISTS" ) <> "" then */
  else
    thisRC = -2

RETURN thisRC

Count lines in a file quickly - 2 -

 
/* another sample code to count the lines in a file quickly           */
/*                                                                    */
/* Source: Oliver Heidelbach (see EMail Addresses)                    */
/*                                                                    */
/* see also Count lines in a file quickly                             */
/*                                                                    */

                        /* name of the file                           */
  testFile = "C:\OS2\INISYS.RC"

  call time "r"

  noOfLines = CountLines( testFile )

  if noOfLines >= 0 then
  do
    say "The file " || testFile || " contains " || ,
        noOfLines || " lines."
    say "It took " || time( "e" ) || " seconds to get the number."

  end /* if */
  else
    say "Error reading the file " || testFile

exit 0

/* ------------------------------------------------------------------ */
/* function: Get the number of lines in a file                        */
/*                                                                    */
/* call:     CountLines( fileName )                                   */
/*                                                                    */
/* where:    fileName - name of the file                              */
/*                                                                    */
/* returns:  n                                                        */
/*             if n is >= 0 then it is the number of lines            */
/*             if n is < 0 an error occured                           */
/*                                                                    */
CountLines: PROCEDURE
  parse arg fileName

  if stream( fileName, "c", "QUERY EXISTS" ) <> "" then
  do
    if stream( fileName, "c", "OPEN READ" )  = "READY:" then
    do

      thisRC = 0

      buf = charin( File,1,stream( filename,'c','query size' ) )
      call stream filename, 'c', 'close'

      buf = translate( buf,copies( ' ',10 ) || '1' || copies( ' ',245 ) )
      buf = space( buf,0 )
      thisRC = length( buf )

    end /* if stream( fileName, "c", "open read" )  = "READY:" then */
    else
      thisRC = -1

  end /* if stream( fileName, "c", "QUERY EXISTS" ) <> "" then */
  else
    thisRC = -2

RETURN thisRC

Read a file into a compound variable

/* sample code to read a text file into a compound variable           */

                        /* name of the file to read                   */
  testFile = "C:\OS2\INISYS.RC"

  if ReadTextFile( testFile  , "testStem" ) = 0 then
  do
    say "The file " || testFile || " contains " || ,
        testStem.0 || " lines."
  end /* if */
  else
    say "Error reading the file " || testFile

exit 0

/* ------------------------------------------------------------------ */
/* function: Read a text file into a compound variable                */
/*                                                                    */
/* call:     ReadTextFile fileName, stem                              */
/*                                                                    */
/* where:    fileName - name of the file to read                      */
/*           stem - stem for the lines of the file                    */
/*                                                                    */
/* returns:  0 - okay, file read, the compound variable "stem"        */
/*                     contains the lines of the file,                */
/*                     "stem".0 = no. of lines                        */
/*           else error reading the file                              */
/*                                                                    */
ReadTextFile:
  parse arg rtf.__fileName , rtf.__stemName

                        /* set the return code                        */
  rc = -1

  rtf.__i = i

  if rtf.__fileName <> "" & rtf.__stemName <> "" then
  do
    if right( rtf.__stemName,1 ) <> "." then
      rtf.__StemName = rtf.__StemName || "."

                        /* init the number of stem elements with 0    */
    rtf.__iLine = rtf.__StemName || "0 = 0 "
    interpret rtf.__iLine

                        /* set the return code 2 - file not found     */
    rc = 2

    if stream( rtf.__fileName, "c", "QUERY EXISTS" ) <> "" then
    do

                        /* set return code to 0                       */
      rc = 0

                        /* create the code to read the file           */
      rtf.__iLine = ,
       "do i = 1 until lines( """ || rtf.__fileName || """ ) = 0; "  ,
          rtf.__StemName || "i = lineIn( '" || rtf.__fileName || "');" ,
       "end;"
                        /* and now execute the code to read the file  */
      interpret rtf.__iLine

                        /* close the file                             */
      call stream rtf.__fileName, "c", "CLOSE"

                        /* correct the no. of lines in stem.0         */
      rtf.__iLine = rtf.__StemName || "0 = i "
      interpret rtf.__iLine

    end /* if stream( ... */

  end /* if rtf.__fileName <> "" & ... */

  i = rtf.__i
  drop rtf.

RETURN rc

Write a stem using CharOut()

 
/* ------------------------------------------------------------------ */
/* function: Write a stem into a file                                 */
/*                                                                    */
/* call:     rxWriteStem = stem_with_the_lines                        */
/*           call RxWriteTextFile fileName  {, lineSep } {,noEmpty}   */
/*                                                                    */
/* where:    rxWriteStem - name of the stem with the file contents    */
/*                        The name MUST end with a dot!               */
/*           fileName - name of the file to write                     */
/*           LineSep - line separator chars                           */
/*                     (def.: "0D0A"x)                                */
/*           noEmpty - if 1 empty lines are not written               */
/*                     (def.: 0)                                      */
/*                                                                    */
/* returns:                                                           */
/*           0 -> ok                                                  */
/*           1 -> parameter missing                                   */
/*           2 -> could not write the whole file                      */
/*           3 -> variable referenced in RxWriteStem is invalid       */
/*           4 -> NOTREADY condition occured                          */
/*           5 -> ERROR condition occured                             */
/*           6 -> FAILURE condition occured                           */
/*           7 -> unexpected condition occured                        */
/*                                                                    */
RxWriteTextFile: PROCEDURE expose (RxWriteStem) (exposeList)

                    /* install local error handlers                   */
  SIGNAL ON  NOTREADY Name RxWriteTextFileError
  SIGNAL ON  ERROR    Name RxWriteTextFileError
  SIGNAL ON  FAILURE  Name RxWriteTextFileError

                    /* init the return code                           */
  thisRC = 3
                    /* check the name of the variable for the         */
                    /* result                                         */
  if  symbol( rxWriteStem || 0 ) = 'VAR' & right( rxWriteStem,1 ) = '.' then
  do
                    /* get the parameter                              */
    parse arg fileName , lineSep, noEmpty

                    /* remove leading and trailing blanks from the    */
                    /* parameter                                      */
    fileName = strip( fileName )
    lineSep = strip( lineSep )

                    /* use default line separator if necessary        */
    if arg( 2, 'o' ) = 1 "" then
      lineSep = d2c(13) || d2c(10)

    if noEmpty = "" then
      noEmpty = 0

                    /* set the return code                            */
    thisRC = 1

    if fileName <> "" then
    do
                    /* copy the stem into a variable                  */
      fileContents = ''
      do i = 1 to value( RxWriteStem || 0 )
        curLine = value( RxWriteStem || i )
        if noEmpty = 1 & curLine = '' then
          iterate
        fileContents = fileContents || curLine || lineSep
      end /* do lineCount = 1 to value( RxWriteStem || 0 ) */

                    /* open the file                                  */
      call stream fileName, "c", "OPEN WRITE"
                    /* write the complete file using CharOut()        */
      tRC = CharOut( fileName, fileContents, 1 )
                    /* close the file                                 */
      call stream fileName, "c", "CLOSE"
      if tRC <> 0 | result <> 'READY:' then
        thisRC = 2
      else
        thisRC = 0

    end /* if filename <> "" then */
  end /* if */

RETURN thisRC

/* error exit for RxWriteTextFile                                     */

RxWriteTextFileError:
                    /* turn off the condition that caused the error   */
  INTERPRET 'SIGNAL OFF ' condition( 'C' )

  curCondition = condition('C')
  select
    when curCondition = 'NOTREADY' then
      thisRC = 4
    when curCondition = 'ERROR' then
      thisRC = 5
    when curCondition = 'FAILURE' then
      thisRC = 6
    otherwise
      thisRC = 7
  end /* select */

                    /* close the file                                 */
  call stream fileName, 'c', 'CLOSE'

return thisRC

Expand the function FILESPEC

 
/* sample code to extend the FILESPEC function with code to extract   */
/* the extension of a fileName                                        */

  do until myInput = ""
    say "New options for FILESPEC are:"                      /* v2.30 */
    say "-----------------------------"                      /* v2.30 */
    say "E - return the extension of the file"               /* v2.30 */
    say "B - return the name without the extension of the file"  /* " */
    say "H - return the drive & path of the file"            /* v2.30 */
    say "-----------------------------"                      /* v2.30 */
    say "Enter the parameter for FILESPEC(option, fileName): "
    myInput = strip( lineIn() )
    if myInput <> "" then
    do
      parse var myInput myOption "," myfileName
      say "The result of FILESPEC( " myOption "," myfileName ") is: "
      say "<" || fileSpec( myOption, myfileName ) || ">"
    end /* if myInput <> "" then */
  end /* do until myInput = "" */

exit 0

/* ------------------------------------------------------------------ */
/* function: Extended FILESPEC function                               */
/*                                                                    */
/* call:     FileSpec option,fileName                                 */
/*                                                                    */
/* where:    option                                                   */
/*                                                                    */
/*             - E{xtension}                                          */
/*               return the extension of the file                     */
/*                                                                    */
/*             - B{asename}                                           */
/*               returns the name of the file without extension       */
/*                                                                    */
/*             - H{ome]                                               */
/*               returns the fully qualified path of the file         */
/*               (including the drive specifier; without the trailing */
/*               backslash)                                           */
/*                                                                    */
/*             All other values for "option" are processed by the     */
/*             original FILESPEC function.                            */
/*                                                                    */
/*           fileName                                                 */
/*             - name of the file                                     */
/*                                                                    */
/* returns:  if option = E{xtension}:                                 */
/*             the extension of the fileName or "" if none            */
/*           else                                                     */
/*             if option = B{asename}:                                */
/*               the name of the file without the path and extension  */
/*             else                                                   */
/*               the return code of the original FILESPEC function    */
/*               or "SYNTAX ERROR" if called with invalid parameter   */
/*                                                                    */
/* note:     To call the original FILESPEC function directly, use     */
/*             myResult = "FILESPEC"( option, fileName )              */
/*                                                                    */
/* history:                                                           */
/*           RXT&T v1.90 /bs                                          */
/*            - added the option B{asename}                           */
/*           RXT&T v2.30 /bs                                          */
/*            - added the option H{ome}                               */
/*                                                                    */
FileSpec: PROCEDURE
  parse arg option, fileName

                        /* init the return code                       */
  rc = "SYNTAX ERROR"
                        /* install a local error handler              */

  SIGNAL ON SYNTAX NAME FileSpecError

  fileName = strip( fileName )                               /* v2.30 */
  option = translate( strip( option ) )

                        /* check the option code                      */
  select

    when abbrev( "EXTENSION", option ) = 1 then
    do
                        /* process the new added option code          */
      i = lastPos( ".", fileName )
      if i > lastPos( "\", fileName ) then
        rc = substr( fileName, i+1 )
      else
        rc = ""
    end /* when */

    when abbrev( "BASENAME", option ) = 1 then               /* v1.90 */
    do                                                       /* v1.90 */
                        /* call the original FILESPEC function  v1.90 */
                        /* to get the filename                  v1.90 */
      rc = "FILESPEC"( "N", fileName )                       /* v1.90 */
      i = lastpos( ".", rc )                                 /* v1.90 */
      if i <> 0 then                                         /* v1.90 */
        rc = substr( rc,1, i-1 )                             /* v1.90 */
    end /* when */                                           /* v1.90 */

    when abbrev( "HOME", option ) = 1 then                   /* v2.30 */
    do                                                       /* v2.30 */
      rc = "FILESPEC"( "D", fileName ) ||,                   /* v2.30 */
           "FILESPEC"( "P", fileName )                       /* v2.30 */
      if right( rc,1 ) = "\" then                            /* v2.30 */
        rc = dbrright( rc,1 )                                /* v2.30 */
    end /* when */                                           /* v2.30 */

    otherwise
    do
                        /* call the original FILESPEC function        */
      rc = "FILESPEC"( option, fileName )
    end /* otherwise */

  end /* select */

FileSpecError:

RETURN rc

Maintain Multi-Value EAs in REXX

/*                                                                    */
/* sample routine to maintain multi-Value EAs in REXX                 */
/* The Demo program uses the EA ".HISTORY"                            */
/*                                                                    */
/* (see also Extended Attribute Data Types and EAs used by the WPS)   */
/*                                                                    */
  say ""
  say "Sample program to show the use of the routine FileHistory"
  say ""

/* -------------------------- */

                    /* get the name of this file                      */
  parse source . . thisFile

                    /* load the REXXUTIL functions for the demo       */
  call rxFuncAdd "SysLoadFuncs", "REXXUTIL", "SysLoadFuncs"
  call SysLoadFuncs

/* -------------------------- */

  say "Now detecting the history of this file ..."
  call ReadEA1

/* -------------------------- */

  say "Now initializing the history of this file with 1 value ..."

  call FileHistory "ADD", thisFile,,
                   "Jim Bacon    Created    01.01.1995", testStem
  say "  Result of FileHistory is " || result

  call ReadEA

/* -------------------------- */

  say "Now initializing the history of this file with 3 values ..."

  MyStem1.0 = 3
  MyStem1.0.codepage = 0
  Mystem1.1 = "Jon Doe I     Created      20.01.1995"
  MyStem1.2 = "Jon Doe II    Changed      22.01.1995"
  MyStem1.3 = "Jon Doe III   Changed      23.01.1995"

  call FileHistory "SET", thisFile, "MyStem1"
  say "  Result of FileHistory is " || result

  call ReadEA
/* -------------------------- */

  say "Now adding another entry to the history of this file ..."
  call FileHistory "ADD", thisFile, "Jim Bean   Changed   24.01.1995"
  say "  Result of FileHistory is " || result

  call ReadEA

/* -------------------------- */

  say 'Now deleting the history of this file ...'
  call FileHistory 'CLEAR', thisFile
  say '  Result of FileHistory is ' || result

  call ReadEA

/* -------------------------- */

exit

/*                                                                    */
/* demo subroutine to read the EA                                     */
/*                                                                    */
ReadEA:
  say "Now reading the new history of this file ..."

ReadEA1:
  call FileHistory "GET", thisFile, "MyStem"
  say "  Result of FileHistory is " || result

  say "  The history list for this file contains " || ,
      MyStem.0 || " entries."
  say "  The codepage of the history list is " || MyStem.0.CodePage || "."

  do i = 1 to MyStem.0
    say "  History list entry no " || i  || " is  "
    say "     <" || myStem.i || ">"
  end /* do i = 1 to MyStem.0 */

  say "Press O to open the Settings Notebook of this file " || ,
      "or any other key to continue"
  UserInput = translate( SysGetKey( "NOECHO" ) )
  if userInput = "O" then
  do
    call SysOpenObject thisFile, 2 , 1
    say "Close the Settings Notebook and press any key to continue"
    UserInput = translate( SysGetKey( "NOECHO" ) )
  end /* if userInput = "O" then */
RETURN

/* ------------------------------------------------------------------ */
/* function: Get, Set or Clear the .HISTORY EA of a file              */
/*                                                                    */
/* call:     FileHistory GET, filename, NewHistoryStem                */
/*           FileHistory ADD, filename, newHistoryEntry {,newStem}    */
/*           FileHistory SET, filename, CurHistoryStem                */
/*           FileHistory CLEAR, filename                              */
/*                                                                    */
/* where:    GET, ADD, SET, CLEAR                                     */
/*             - action:                                              */
/*                GET - get a list of the current entries             */
/*                ADD - add an entry to the list                      */
/*                SET - replace the EA with a new list                */
/*              CLEAR - clear the whole list                          */
/*           filename                                                 */
/*             - name of the file                                     */
/*           NewHistoryStem                                           */
/*             - stem for the history list entries                    */
/*           newStem                                                  */
/*             - stem for the history list entries                    */
/*           CurHistoryStem                                           */
/*             - stem _with_ the history list entries                 */
/*           newHistoryEntry                                          */
/*             - new entry for the history list                       */
/*               (ASCII string)                                       */
/*                                                                    */
/* returns:  0 - okay                                                 */
/*           1 - file not found                                       */
/*           2 - EA is invalid                                        */
/*           3 - CurHistoryStem.0 is invalid                          */
/*           4 - CurHistoryStem.0.codepage is invalid                 */
/*          -1 - invalid parameter                                    */
/*        else - unexpected error                                     */
/*                                                                    */
/* notes:                                                             */
/*  Do not add the trailing dot to the stem name!                     */
/*  Format of the stems:                                              */
/*    history_stem.0 = number of entries                              */
/*    history_stem.0.codepage = codepage of the EA                    */
/*                              (def.: 0, use default codepage)       */
/*    history_stem.n = entry n                                        */
/*                                                                    */
/*  The format of the .HISTORY EA is:                                 */
/*                                                                    */
/*     EA Type  Code                                                  */
/*              page Count                                            */
/*   +--------------------------------------------------+             */
/*   | EAT_MVMT 0000 0002                               |             */
/*   |          EAT_ASCII 0017  Joe    Created  2/10/88 |             */
/*   |          EAT_ASCII 0017  Harry  Changed  2/11/88 |             */
/*   +--------------------------------------------------+             */
/*              EA Type  length contents (ASCII string)               */
/*                                                                    */
/*  All numeric values are WORDs in INTEL format.                     */
/*                                                                    */
/* (see also Extended Attribute Data Types and EAs used by the WPS)   */
/*                                                                    */
/*  FileHistory uses the prefix 'FH.' for all local variables. The    */
/*  local variables are dropped at the end of the routine!            */
/*                                                                    */
/* (c) 1996 Bernd Schemmer, Germany, EMail: Bernd.Schemmer@gmx.de     */
/*                                                                    */
FileHistory:

                    /* name of the EA to use                          */
                    /* note: change this variable to use the routine  */
                    /*       for the EAs .COMMENTS or .KEYPHRASES.    */
                    /*       In this case you must also delete the    */
                    /*       Codepage related code in this routine.   */
  FH.__EAName = '.HISTORY'

                    /* init the return code                           */
  rc = 0

/* -------------------------- */
                    /* install local error handlers                   */
  SIGNAL ON SYNTAX  NAME FileHistoryEnd
  SIGNAL ON ERROR   NAME FileHistoryEnd
  SIGNAL ON FAILURE NAME FileHistoryEnd
/* -------------------------- */
                    /* get the parameter                              */
  parse upper arg FH.__action , FH.__file , FH.__variable , .

                    /* get the parameter for the ADD action           */
  parse arg  , , FH.__newValue , FH.__tempStem

                    /* check the parameter                            */
  select

                    /* check the action parameter                     */
    when wordPos( FH.__action, 'GET ADD SET CLEAR' ) = 0 then
      rc = -1

                    /* check the parameter for the stem variable      */
    when wordPos( FH.__action, 'GET ADD SET' ) <> 0 & ,
         FH.__variable = '' then
      rc = -1

                    /* check the parameter for the filename           */
    when FH.__file = '' then
      rc = -1

                    /* test, if the file exists                       */
    when stream( FH.__file, 'c', 'QUERY EXISTS' ) = '' then
      rc = 1

                    /* check the number fields in the stem            */
    when FH.__action = 'SET' then
    do
      select

                    /* stem.0 must contain the number of entries      */
        when datatype( value( FH.__variable || '.0' ) ) <> 'NUM' then
          rc = 3
                    /* use the default codepage if the entry          */
                    /* stem.0.codepage is missing                     */
        when symbol( FH.__variable || '.0.CodePage' ) <> 'VAR' then
          call value FH.__variable || '.0.CodePage', 0

                    /* stem.0.codepage must be a numeric value if     */
                    /* it exist                                       */
        when datatype( value( FH.__variable || '.0' ) ) <> 'NUM' then
          rc = 4

        otherwise
          nop
      end /* select */

    end /* when */

    when FH.__action = 'ADD' then
    do
                    /* use the fourth parameter as name of the stem   */
                    /* if entered                                     */
      if FH.__tempStem <> '' then
        FH.__variable = FH.__tempStem
      else
        FH.__variable = 'FH.__tempStem'
    end /* when */

    otherwise
      nop

  end /* select */

/* -------------------------- */

  if rc = 0 then
  do
                    /* load the necessary REXXUTIL functions          */

                    /* use special REXX names to avoid errors if      */
                    /* another program drops the REXXUTIL functions   */
    call rxFuncAdd 'FH_SysGetEA', 'REXXUTIL', 'SysGetEA'
    call rxFuncAdd 'FH_SysPutEA', 'REXXUTIL', 'SysPutEA'

/* -------------------------- */
                    /* constants for the EA type specifier            */
    FH.__EAT_BINARY       = SwapWord( 'FFFE'x )
    FH.__EAT_ASCII        = SwapWord( 'FFFD'x )
    FH.__EAT_BITMAP       = SwapWord( 'FFFB'x )
    FH.__EAT_METAFILE     = SwapWord( 'FFFA'x )
    FH.__EAT_ICON         = SwapWord( 'FFF9'x )
    FH.__EAT_EA           = SwapWord( 'FFEE'x )
    FH.__EAT_MVMT         = SwapWord( 'FFDF'x )
    FH.__EAT_MVST         = SwapWord( 'FFDE'x )
    FH.__EAT_ANS1         = SwapWord( 'FFDD'x )

/* -------------------------- */

    if FH.__action = 'CLEAR' then
    do
                    /* clear the history list                         */
                                                             /* v2.80 */
      call FH_SysPutEA FH.__file, FH.__EAName, ''

    end /* if FH.__action = 'CLEAR' then */

/* -------------------------- */

    if wordPos( FH.__action, 'GET ADD' ) <> 0 then
    do
                    /* read the EA                                    */

                    /* init the stem for the EA values                */
      call value FH.__variable || '.', ''
      call value FH.__variable || '.0' , 0
      call value FH.__variable || '.0.codepage', 0

                    /* read the EA                                    */
      rc = FH_SysGetEA( FH.__file, FH.__EAName, FH.__historyEA )
      if rc = 0 & FH.__historyEA <> '' then
      do
                    /* split the EA into the header fields and the    */
                    /* values                                         */
        parse var FH.__historyEA FH.__historyEAType +2 ,
                                 FH.__historyEACodePage +2,
                                 FH.__historyEACount +2 ,
                                 FH.__historyEAValues

                    /* convert the count value to decimal             */
        FH.__historyEACount = c2d( SwapWord( FH.__HistoryEACount ) )

                    /* check the EA type                              */
        if FH.__historyEAType = FH.__EAT_MVMT then
        do
                    /* save the codepage                              */
          call value FH.__variable || '.0.codepage' ,,
                     c2d( SwapWord( FH.__historyEACodePage ) )

                    /* split the value into separate fields           */
          do FH.__i = 1 to FH.__HistoryEACount while rc = 0

            FH.__HistoryEACurType = substr( FH.__HistoryEAValues, 1, 2 )
            if FH.__HistoryEACurType <> FH.__EAT_ASCII then
              rc = 2    /* invalid EA type                            */
            else
            do
                    /* get the length of this value                   */
              FH.__HistoryEACurLen  = c2d( SwapWord( substr( FH.__HistoryEAValues, 3, 2 ) ) )

              parse var FH.__historyEAValues 5 FH.__HistoryEACurVal,
                                             +( FH.__HistoryEACurLen) ,
                                             FH.__historyEAValues

                    /* save the value into the stem                   */
              call value FH.__variable || '.' || FH.__i ,,
                         FH.__HistoryEACurVal

            end /* else */
          end /* do FH.__i = 1 to c2d( FH.__HistoryEACount ) while rc = 0 */

                    /* save the number of entries in stem.0           */
          if rc = 0 then
            call value FH.__variable || '.0' , FH.__i-1

        end /* if FH.__historyEAType = FH.__EAT_MVST then */
        else
          rc = 2    /* invalid EA type                                */

      end /* if rc = 0 then */

    end /* if wordPos( FH.__action, 'GET ADD' ) <> 0 then */

/* -------------------------- */

    if FH.__action = 'ADD' & rc = 0 then
    do
                    /* add an entry                                   */

      FH.__i = value( FH.__variable || '.0' ) +1
      call value FH.__variable || '.' || FH.__i , FH.__newValue
      call value FH.__variable || '.0' , FH.__i

    end /* if FH.__action = 'ADD' & rc = 0 then */

/* -------------------------- */

    if wordPos( FH.__action, 'SET ADD' ) <> 0 & rc = 0 then
    do
                    /* write the EA                                   */

      FH.__newEA = FH.__EAT_MVMT || ,
         SwapWord( right( '00'x || d2c( value( FH.__variable || '.0.codepage' ) ), 2 ) ) || ,
         SwapWord( right( '00'x || d2c( value( FH.__variable || '.0' ) ), 2 ) )

      do FH.__i = 1 to value( FH.__variable || '.0' )
        FH.__curEntry = value( FH.__variable || '.' || FH.__i )

        FH.__newEA = FH.__newEA || ,
                     FH.__EAT_ASCII || ,
                     SwapWord( right( '00'x || d2c( length( FH.__curEntry ) ), 2 ) ) || ,
                     FH.__curEntry
      end /* do FH.__i = 1 to value( FH.__variable || '.0' ) */

                                                             /* v2.80 */
      call FH_SysPutEA FH.__file, FH.__EAName, FH.__newEA
      rc = result

    end /* if wordPos( FH.__action, 'SET ADD' ) <> 0 then */

  end /* if rc = 0 then */

                    /* label for the local error handler              */
FileHistoryEnd:

                    /* drop the REXXUTIL functions                    */
                    /* (possible and necessary because we use unique  */
                    /*  REXX names!)                                  */
    call rxFuncDrop 'FH_SysGetEA'
    call rxFuncDrop 'FH_SysPutEA'

                    /* drop local variables                           */
  drop FH.

RETURN rc

/* ------------------------------------------------------------------ */
/* function: Convert a hexadecimal WORD from LSB format to MSB format */
/*           and vice versa                                           */
/*                                                                    */
/* call:     SwapWord hexadecimal_word                                */
/*                                                                    */
/* where:    hexadecimal_word - input as hexadecimal word             */
/*                                                                    */
/* output:   value in MSB format as hexadecimal word                  */
/*                                                                    */
SwapWord: PROCEDURE
  RETURN strip( translate( "12", arg(1), "21" ) )