Introduction
This session will be a mixture of some different and interesting REXX programming techniques. Melanie will discuss Stem Variables and the Parse Command, two unique and powerful features of REXX. She will also show how to use VREXX2, which provides a way to add simple window, dialog and menu facilities to your REXX programs.
Compound (Stem) Variables
Think of compound variables as you would an array in other programming languages. But these arrays have some special features. Not only do you not have to worry about how big your arrays are, but the array index/subscript doesn't have to be a number.
Here is a simple example of using a REXX compound variable as an array. It will set values for "Array.1" as "1", "Array.2" as "2", ... and "Array.5" as "5".
Do i=1 to 5
Array.i =i
End
You can also create 2 dimensional arrays in a similar manner. This example will create a multiplication table, where "Array.1.1" is "1" and "Array.5.5" is "25".
Do i=1 to 5
Do j=1 to 5
Array.i.j = i*j
End
End
There is a common convention to use a tail of "0" to store the count of values in an array:
Array.0 = 5
Do i=1 to Array.0
Array.i =i
End
There are some commands/functions in Rexx (like the EXECIO command in the VM/CMS version and the SysFileTree RexxUtil function in the OS/2 version) that will automatically put the count of returned values into the ".0" tail when storing values into a stem variable. Other times, you must keep track of the number of array elements yourself.
Even used as a simple array, stem variables have a very neat feature. You can initialize the entire array with one command. Just specify the stem name without a tail in your assignment command. For example, to set the default value of "Array" to "0", use "Array. = 0".
Stem variables can also be associative arrays by using non-numbers as the tail. Here is a simple program to show a phone number for a specified name.
/* Phone.Cmd */
Phone. = 'Unlisted'
Phone.Fred = '213-555-1212'
Phone.Wilma = '714-555-1212'
Phone.Pebbles = '310-555-1212'
Say 'Enter a name:'
Pull Name .
Say Name||"'s phone number is:" Phone.Name
Return
Here is another example that will determine if a letter is a vowel.
/* IsVowel.Cmd */
IsVowel. = 0
IsVowel.A = 1
IsVowel.E = 1
IsVowel.I = 1
IsVowel.O = 1
IsVowel.U = 1
IsVowel.Y = 1
Say 'Enter a Letter'
Pull Letter
If IsVowel.Letter
Then Say 'Yes,' Letter 'is a vowel.'
Else Say 'No,' Letter 'is not a vowel.'
Return
Parse Command
Generic form: Parse [upper] {what/where-from} {how}
The "what/where-from" can be:
- Arg
- Passed as an argument
- LineIn()
- From an input file
- Pull
- From the keyboard/buffer
- Value ... with
- From a specified expression
- Var
- From a variable
- and the "how" can be:
- ... almost anything!
- Standard blank-delimited "tokens" (words)
- Matching specified characters
- Separated by specific column or position numbers (like a substring)
- By relative column positioning (+1, -3, etc.)
- ... or any combination of the above methods
By Tokens
Example:Parse value "ab cde fghijk lmnop qrstuvw xyz" with v1 v2 v3 v4 v5
Will result in:
- v1='ab'
- v2='cde'
- v3='fghijk'
- v4='lmnop'
- v5='qrstuvw xyz'
The special things to note here, are that the tokenizing uses the blanks that separate each word or token and the last variable is handled differently than the others. The first blank after the preceding token is used and EVERYTHING else inthe string, including any leading, imbedded, and trailing blanks will go with the last variable. For example:
This: Parse value 'ab cd ef ' with v1 v2 v3
Will result in:
- v1='ab'
- v2='cd'
- v3=' ef '
Notice that both the extra leading and trailing blanks will be added to the last variable. It is easy to eliminate this inconsistency. Just add the magic "." to the end of your variable list. This will now become the "leftover variable" and eliminate any unexpected results from this parsing "feature".
By Matching Characters
This: Parse value "ab; cde; fghijk lmnop; qrs;tuv; wxyz" with v1 ';' v2 ';' v3 ';' v4 ';' v5
Will result in:
- v1='ab'
- v2=' cde'
- v3=' fghijk lmnop'
- v4=' qrs'
- v5='tuv; wxyz
Notice that the matching character is "used up" by the parse command and does not appear in any of the variables. (The ';' is in v5 because it is the 5th one in the input string and wasn't used as a pattern matching character.)
By Columns/Positions
This: Parse value 'ab cd ef gh ij' with 1 v1 3 v2 5 v3 7 v4 9 v5
Will result in:
- v1='ab'
- v2=' c'
- v3='d '
- v4='ef'
- v5=' gh ij'
By Relative Columns/Positions
This: Parse value 'ab cd ef gh ij' with 1 v1 +2 v2 +2 v3 +2 v4 +2 v5
Will result in:
- v1='ab'
- v2=' c'
- v3='d '
- v4='ef'
- v5=' gh ij'
One of the more powerful features of the parse command is the ability to parse the same input string multiple ways in the same command.
This: Parse value 'ab cd ef gh ij' with 1 v1 3 v2 5 v3 1 v4 9 v5
Will result in:
- v1='ab'
- v2=' c'
- v3='d ef gh ij'
- v4='ab cd ef'
- v5=' gh ij'
Wow! How did that happen? By specifying column 1 again, in the middle of the variable list, REXX will "back up" and re-parse the input string starting over again at column 1. Notice that doing this makes v3 act kind of like the "last" variable, and it gets the value of the rest of the input string. So, if there were any trailing blanks at the end of the input string, there would be trailing blanks at the end of variable v3.
Using negative relative columns will produce a similar result.
This: Parse value 'ab cd ef gh ij' with 1 v1 +2 v2 -2 v3 +2 v4 -2 v5
Will result in:
v1='ab'
- v2=' cd ef gh ij'
- v3= 'ab'
- v4=' cd ef gh ij'
- v5='ab cd ef gh ij'
But what if you didn't want v3 to get the rest of the input string? Just remember to use that magic '.':
This: Parse value 'ab cd ef gh ij' with 1 v1 3 v2 5 v3 7 . 1 v4 9 v5
Will result in:
- v1='ab'
- v2=' c'
- v3='d '
- v4='ab cd ef'
- v5=' gh ij'
VREXX
VREXX is Visual REXX for OS/2 Presentation Manager! VREXX allows you to write standard OS/2 REXX command files which can create and control PM windows. VREXX also dynamically constructs dialog boxes which allow filename selection, font and color selection, string input, and message display. Other functions allow selection of items through listbox, radiobutton and checkbox dialogs.
VREXX also supports graphical text output in 14 different fonts, as well as pixel, marker, polyline, polygon, spline and arc drawing functions.
You DO NOT need to be a PM programmer to use VREXX! Here is a complete VREXX hello world program, with an additional message box at the end:
/* HELLO.CMD */
/* initialize VREXX external functions */
'@echo off'
call RXFUNCADD 'VInit', 'VREXX', 'VINIT'
initcode = VInit()
if initcode = 'ERROR' then signal CLEANUP
signal on failure name CLEANUP
signal on halt name CLEANUP
signal on syntax name CLEANUP
/* do hello, world window */
position.left = 25 /* put window in the center of the screen */
position.bottom = 25 /* coordinates are % of screen */
position.right = 75
position.top = 75
/* open the window and draw the text string */
id = VOpenWindow('My VREXX Window', 'WHITE', position)
call VSay id, 300, 500, 'Hello, world'
/* put up a 1 line message box -
when user presses ok, end the program */
msg.0 = 1
msg.1 = 'Press OK to end the HELLO.CMD program'
call VMsgBox 'HELLO.CMD', msg, 1
call VCloseWindow id
CLEANUP:
call VExit /* terminate VREXX */
exit
Run the procedure just like any other REXX procedure, by typing
hello
or
start hello.cmd
at an OS/2 command prompt. On-line help is available by typing:
view vrexx.inf
VREXX was written by Richard B. Lam at the IBM T.J. Watson Research Center.
VREXX Samples
|
VMsgBox with button style 2 |
Buttons have different styles. 1=OK, 2=CANCEL, 3=OK and CANCEL, 4=YES, 5=NO, 6=YES and No
|
VInputBox with button style 3 |
|
VMsgBox with button style 2 |
|
VMultBox with button style 3 |
|
VFontBox with button style 3 |
|
VCheckBox with button style 1 |
Sample Program: AREACODE.CMD
The original REXX version:
/* REXX Exec to display the state and time zone for a given areacode */
Arg areacode .
If areacode = '?' Then Signal Explain
If areacode = ''
Then Do
Say 'Enter Areacode:'
Pull areacode .
End
/* Set up area code stem variables */
Call INIT
If state.areacode = ''
Then Say 'Areacode' areacode 'not found.'
Else Do
Say 'Areacode ==>' areacode
Say 'State ==>' state.areacode
Say 'Time Zone ==>' zone.areacode
End
Return
/* ------------------------------------------------------------------ */
Explain:
Say 'The AREACODE exec is used to get the location (state)'
Say 'and time zone of a given area code.'
Say ''
Say 'The time zones indicated are:'
Say '======================================='
Say 'Hawaii (Pacific time -3 hours)'
Say 'Alaska (Pacific time -1 hour)'
Say 'Pacific'
Say 'Mountain (Pacific time +1 hour)'
Say 'Central (Pacific time +2 hours)'
Say 'Eastern (Pacific time +3 hours)'
Say 'Atlantic (Pacific time +4 hours)'
Return
/* ------------------------------------------------------------------ */
Init:
State.=''; Zone. = ''
State.201='New Jersey'; Zone.201='Eastern'
State.202='Washington DC'; Zone.202='Eastern'
State.203='Connecticut'; Zone.203='Eastern'
State.204='Manitoba, Canada'; Zone.204='Central'
State.205='Alabama'; Zone.205='Central'
/* Remainder of areacode data removed to conserve paper */
Return
The new VREXX version:
/* REXX Exec to display the state and time zone for a given areacode */
call RxFuncAdd 'VInit', 'VREXX', 'VINIT'
initcode = VInit()
if initcode = 'ERROR' then signal CLEANUP
signal on error name CLEANUP
signal on failure name CLEANUP
signal on halt name CLEANUP
signal on syntax name CLEANUP
/* End of VREXX setup */
Call Explain
/* Set up area code stem variables */
Call INIT
prompt.0 = 1
prompt.1 = 'Enter Areacode:'
button = VInputBox('AreaCode', prompt, 5, 3)
Areacode = prompt.vstring
If button <> 'OK' Then Signal CLEANUP
If state.areacode = ''
Then Do
msg.0 = 1
msg.1 = 'Areacode' areacode 'not found.'
Call VMsgBox 'Areacode: Error', msg, 1
End
Else Do
msg.0 = 3
msg.1 = 'Areacode ==>' areacode
msg.2 = 'State ==>' state.areacode
msg.3 = 'Time Zone ==>' zone.areacode
Call VMsgBox 'Areacode: Results', msg, 1
End
/* Close up and Exit */
CLEANUP:
Call VExit
Return
/* ------------------------------------------------------------------ */
Explain:
msg.1 = 'AREACODE is used to get the location (state)'
msg.2 = 'and time zone of a given area code.'
msg.3 = '======================================='
msg.4 = 'Hawaii (Pacific time -3 hours)'
msg.5 = 'Alaska (Pacific time -1 hour)'
msg.6 = 'Pacific'
msg.7 = 'Mountain (Pacific time +1 hour)'
msg.8 = 'Central (Pacific time +2 hours)'
msg.9 = 'Eastern (Pacific time +3 hours)'
msg.10 = 'Atlantic (Pacific time +4 hours)'
msg.0 = 10
Ans = VMsgBox('Areacode: Help', msg, 3)
If Ans = 'OK'
Then Return
Call VExit
Exit
/* ------------------------------------------------------------------ */
Init:
State.=''; Zone. = ''
State.201='New Jersey'; Zone.201='Eastern'
State.202='Washington DC'; Zone.202='Eastern'
State.203='Connecticut'; Zone.203='Eastern'
State.204='Manitoba, Canada'; Zone.204='Central'
State.205='Alabama'; Zone.205='Central'
/* Remainder of areacode data removed to conserve paper */
Return
|