/* -*- REXX -*- */ /* @@PLEAC@@_NAME */ /* @@SKIP@@ REXX @@SKIP@@ */ /* @@PLEAC@@_WEB */ /* @@SKIP@@ http://www.rexxla.org/ @@SKIP@@ */ /* @@PLEAC@@_INTRO */ /* @@SKIP@@ REXX is an interpreted, general purpose programming language that @@SKIP@@ */ /* @@SKIP@@ is used for both system and applications programming, as well as @@SKIP@@ */ /* @@SKIP@@ scripting tasks on a number of platforms ranging from mainframes @@SKIP@@ */ /* @@SKIP@@ to hand-held devices. @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ ANSI Standard of the REXX language released in 1996. The language has @@SKIP@@ */ /* @@SKIP@@ undergone extensive development over time, and is now available in @@SKIP@@ */ /* @@SKIP@@ two flavours: @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ * REXX or 'classic' REXX @@SKIP@@ */ /* @@SKIP@@ * Object Oriented REXX or ooREXX @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ The difference between these two flavours can be likened to that @@SKIP@@ */ /* @@SKIP@@ between the C, and C++ languages: syntactically similar, but the @@SKIP@@ */ /* @@SKIP@@ latter extended to support object oriented programming. @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ More information is available at: http://en.wikipedia.org/wiki/REXX @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ Comments about the examples: @@SKIP@@ */ /* @@SKIP@@ @@SKIP@@ */ /* @@SKIP@@ * Incomplete examples are marked with the relevant PLEAC tags @@SKIP@@ */ /* @@SKIP@@ * Complete, but untranslateable, examples are so-marked @@SKIP@@ */ /* @@SKIP@@ * Extensive use of third-party libraries made [see Appendix] @@SKIP@@ */ /* @@SKIP@@ * Regina 3.3 interpreter used for testing [*NIX and Win32] @@SKIP@@ */ /* @@PLEAC@@_APPENDIX */ /* Some of the libraries used: * http://home.interlog.com/~ptjm/software.html REXXUtil General Purpose Utilities - System Information - Basic Console Control - File / Directory Manipulation REXXMath Common Mathematical Functions RxHash Associative Array Support REXXRe Regular Expressions * http://rxsock.sourceforge.net/index.html RxSock TCP/IP Sockets * http://rexxsql.sourceforge.net/index.html REXX/SQL SQL Library * http://rexxtk.sourceforge.net/index.html REXX/Tk TK Toolkit * http://rexxcurses.sourceforge.net/index.html REXX/Curses Curses Toolkit /* ----------------------------- */ A REXX script is assumed to commence with the following lines: options 'STRICT_ANSI' ; trace 'OFF' ; signal on NOVALUE FALSE = 0 ; TRUE = 1 ; NULL = "" ; SPACE = ' ' ; NEWLINE = "0A"X NaN = "NaN" globals = "sys. env. args. $. FALSE TRUE NULL NEWLINE SPACE NaN" /* ----------------------------- */ REXXToolkit routines [when documented] will appear here /* ----------------------------- */ */ /* @@PLEAC@@_1.0 */ string = '\n' /* two characters, \ and an n, though not a newline */ string = "\n" /* two characters, \ and an n, though not a newline */ string = "0A"X /* newline character code [hex] */ string = "1010"B /* newline character code [binary] */ string = "Newline" "0A"X "here" /* embedded newline in string */ string = 'Jon ''Maddog'' Orwant' /* literal single quotes */ string = "Jon ""Maddog"" Orwant" /* literal double quotes */ string = "Jon 'Maddog' Orwant" /* embedded literal single quotes */ string = 'Jon "Maddog" Orwant' /* embedded literal double quotes */ /* ----------------------------- */ /* HERE documents not supported, but multi-line string allowed */ a = "This is a multiline string that is not a HERE document" , "but consists of a series of concatenated strings" , "each on its own line courtesy of the 'comma' which, when" , "it appears as the last, space-separated character on a" , "line, acts as a line continuation character" /* ----------------------------- */ /* Pseudo implementation of a HERE document */ signal HEREDOC /* Line 1 ... Line 2 ... Line 3 */ HEREDOC: a = NULL do i = SIGL + 1 line = SOURCELINE(i) if line = "*/" then leave a = a||NEWLINE||line end /* @@PLEAC@@_1.1 */ /* ------------------------------------------------------------------ */ /* * REXX offers string manipulation built-in functions [BIF's] many */ /* being equivalent to Perl offerings. However, all REXX BIF's */ /* return copies of the transformed string; original is unaltered. */ /* Therefore this type of usage is illegal: */ /* */ /* SUBSTR(string, offset, count) = newstring */ /* */ /* Instead, variable storing original must be reassigned with the */ /* altered copy */ /* */ /* * REXX implements PARSE instruction which provides a faster means */ /* of: */ /* - tokenising strings [from several sources: string, file, stack] */ /* - assigning tokens to variables */ /* - initialisng and swapping variables, multi-line assignments */ /* */ /* Examples of both approaches shown wherever applicable */ /* ------------------------------------------------------------------ */ string = "a value" /* ----------------------------- */ offset = 3 ; count = 9 ; padchar = 'X' parse var string =(offset) v v = SUBSTR(string, offset) /* "value " */ parse var string =(offset) v +(count) v = SUBSTR(string, offset, count) /* "value " */ v = SUBSTR(string, offset, count, padchar) /* "valueXXXX" */ /* ----------------------------- */ offset = 2 ; count = 2 ; padchar = '*' ; newstr = "Z" v = INSERT(newstr, string, offset, count, padchar) /* "a Z*value" */ v = OVERLAY(newstr, string, offset, count, padchar) /* "aZ*alue" */ /* ----------------------------- */ /* *** Unfinished *** - UNPACK */ /* ----------------------------- */ /* PARSE VAR instruction equivalent, but more efficient, than SUBSTR */ string = "This is what you have" slen = LENGTH(string) parse var string =1 first +1 first = SUBSTR(string, 1, 1) /* "T" */ parse var string =6 start +2 start = SUBSTR(string, 6, 2) /* "is" */ parse var string =14 rest rest = SUBSTR(string, 14) /* "you have" */ parse var string =(slen) last +1 last = SUBSTR(string, slen, 1) /* "e" */ parse var string =(slen) -3 end end = SUBSTR(string, slen - 3) /* "have" */ parse var string =(slen) -7 piece +3 piece = SUBSTR(string, slen - 7, 3) /* "you" */ /* Display contents of string */ say string /* Change "is" to "wasn't" : This wasn't what you have */ string = CHANGEWORD("is", string, "wasn't") /* Replace last 12 characters : This wasn't wondrous */ newstr = "ondrous" ; slen = LENGTH(string) ; nlen = LENGTH(newstr) /* 1 - slow */ string = OVERLAY(newstr, string, slen - 11) string = DELSTR(string, LASTPOS(newstr, string) + nlen) /* 2 - faster */ string = LEFT(string, slen - 12) || newstr /* 3 - fastest */ sparse = slen - 12 parse var string string +(sparse) string = string || newstr /* delete first character : his wasn't wondrous */ parse var string =2 string string = DELSTR(string, 1, 1) string = RIGHT(string, slen - 1) /* Return last 15 characters : wasn't wondrous */ slen = LENGTH(string) parse var string =(slen) -14 string +15 string = SUBSTR(string, slen - 14, 15) string = RIGHT(string, 15) /* Delete last 10 characters : wasn' */ slen = LENGTH(string) ; sparse = slen - 10 parse var string string +(sparse) string = DELSTR(string, slen - 9, 10) string = LEFT(string, slen - 10) /* *** Unfinished *** */ /* @@PLEAC@@_1.2 */ /* ------------------------------------------------------------------ */ /* REXX Boolean values are strictly: */ /* */ /* 1 - TRUE */ /* 0 - FALSE */ /* */ /* All other values force an syntax error if used in a Boolean */ /* context; Boolean expression can be forced via a comparision */ /* operation [see example below] */ /* */ /* REXX does not support conditional structures other than the 'IF' */ /* and 'SELECT' instructions; there is no ternary operator, nor a */ /* conditional assignment expression. This can, however, be mimiced */ /* via function; examples below use an 'iif' function implementation */ /* that, rather crudely, supports this type of operation */ /* */ /* iif(CONDITION, TRUE_VALUE, FALSE_VALUE) */ /* */ /* It is also worth mentioning that the WORD BIF can also be used for */ /* performing conditional assignment. It can be used where alternate */ /* values can be placed in the same string, and relies on: */ /* */ /* * The fact that in REXX all data are strings */ /* * The values of FALSE and TRUE being exactly 0, and 1, respectively*/ /* */ /* See example at end of this section */ /* ------------------------------------------------------------------ */ condition = TRUE ; b = 'B' ; c = 'C' ; x = TRUE ; y = 'Y' /* Use 'b' if 'condition' is TRUE, else return 'c' */ a = iif(condition, b, c) /* Use 'b' if 'b' is TRUE, else 'c' */ a = iif(, b, c) /* Set 'x' to 'y' unless 'x' is already TRUE */ x = iif(, \x, y) /* As above; Boolean expression forced in case 'x' non-Boolean */ x = iif(, \(x == TRUE), y) /* ----------- */ /* Use 'b' if 'b' is defined, else 'c' */ a = iif(SYMBOL('b') == "VAR", b, c) bar = "ANOTHER VALUE" foo = iif(SYMBOL('bar') \= "VAR", bar, "DEFAULT VALUE") exit 0 /* ----------- */ iif : procedure expose (globals) if ARG(1, 'E') then cond = ARG(1) ; else cond = ARG(2) if cond == TRUE then return ARG(2) ; else return ARG(3) /* ----------------------------- */ condition = TRUE ; alternatives = "B C" /* condition: FALSE -> 'B' returned condition: TRUE -> 'C' returned */ WORD(alternatives, condition + 1) /* @@PLEAC@@_1.3 */ /* ------------------------------------------------------------------ */ /* No multiple-assignment support, but PARSE VALUE instruction may be */ /* used to perform: */ /* */ /* * Multiple variable initialisation */ /* * Multiple variable assignment [even swap values without temps] */ /* ------------------------------------------------------------------ */ parse value 1 2 with VAR1 VAR2 parse value VAR1 VAR2 with VAR2 VAR1 /* ----------------------------- */ a = 1 ; b = 2 temp = a ; a = b ; b = temp /* ----------------------------- */ parse value 57 72 103 with alpha beta production parse value beta production alpha with alpha beta production /* @@PLEAC@@_1.4 */ /* ------------------------------------------------------------------ */ /* REXX is a typeless language: all data are strings. This means: */ /* */ /* * REXX has no notion of objects, or aggregate types like arrays */ /* * It does not support 'primitive' types, those usually mapped to */ /* hardware registers */ /* */ /* In order to support mathematical operations, however, strings in */ /* Base 10 format [containing 0-9, leading + or -, a decimal point, */ /* exponent indicator 'E' and exponent] are recognised as 'numeric' */ /* strings in such contexts [whilst hex and binary strings are not]. */ /* */ /* The benefit of this approach: */ /* */ /* * Simplifies interpreter implementation on new platforms */ /* * Implicit support for arbitrary precision arithmetic */ /* * Language kept simple - no declarations, casting or conversions */ /* */ /* A set of conversion BIF's is supplied to facilitate the conversion */ /* of strings to / from various numeric representations, though it is */ /* understood that this is not a type conversion, but a 'form' */ /* conversion, one that may facilitate data printing or storage: */ /* */ /* * C2D / D2C [Character to Decimal / vice versa] */ /* * C2X / X2C [Character to Hex / vice versa] */ /* * X2B / B2X [Hex to Binary / vice versa] */ /* ------------------------------------------------------------------ */ char = 'A' /* or: char = '41'X [ASCII] */ num = C2D(char) char = D2C(num) /* ----------------------------- */ char = 'e' say "Number" C2D(char) "is" char /* Number 101 is e */ /* ----------------------------- */ string = "ABCDE" ascii = C2X(string) /* ascii [hex]: 4142434445 */ string = X2C(ascii) /* string: ABCDE */ /* ----------------------------- */ /* Contents: 73616D706C65 */ ascii_character_numbers = C2X("sample") /* Output will now be: 73 61 6D 70 6C 65 */ out = "" ; acn = ascii_character_numbers do while acn <> NULL parse var acn token +2 acn out = out token end say STRIP(out) /* Output will now be: sample */ out = X2C(ascii_character_numbers) say out /* ----------------------------- */ hal = "HAL" ; ibm = "" do while hal <> NULL parse var hal token +1 hal ibm = ibm||D2C(C2D(token) + 1) end /* Output will now be: IBM */ say ibm /* @@PLEAC@@_1.6 */ /* ------------------------------------------------------------------ */ /* The task of reversing strings is easily and efficiently performed */ /* via the REVERSE BIF. Implementation of a palindome-checking routine*/ /* is probably best accomplished via its use since it involves a */ /* single function call, thus incurs minimal calling overhead. Since */ /* REXX is typically used as an interpreted language, it often becomes*/ /* a significant issue. Performance comparision of the following two */ /* 'isPalindrome' functions should clearly reveal it's impact. */ /* */ /* isPalindrome : procedure */ /* i = 1 ; j = LENGTH(ARG(1)) */ /* do until i >= j */ /* if SUBSTR(ARG(1),i,1) \= SUBSTR(ARG(1),j,1) ; then return FALSE*/ /* i = i + 1 ; j = j - 1 */ /* end */ /* return TRUE */ /* */ /* isPalindrome : procedure */ /* return REVERSE(ARG(1)) == ARG(1) */ /* */ /* The task of reversing words within a string can quite easily be */ /* accomplished in several ways: */ /* */ /* * PARSE instruction together with the stack operations PUSH and */ /* PARSE PULL [stack and queue structures are native to REXX, and */ /* are used for many diverse tasks including interprocess comms] */ /* */ /* * Word-oriented BIF's ['reverseWords' is a recursive function that */ /* uses two of these: DELWORD and WORD. Anyone familiar with LISP or*/ /* Scheme will note how they are being used like 'car' and cdr'] */ /* */ /* reverseWords : procedure */ /* if ARG(1) == "" then ; return "" */ /* return STRIP(reverseWords(DELWORD(ARG(1), 1, 1)) WORD(ARG(1), 1))*/ /* ------------------------------------------------------------------ */ string = "A horse is a horse, of course, of course !" /* Reverse string using REXX BIF */ revbytes = REVERSE(string) /* ----------------------------- */ /* Tokenise 'string', and place each token on stack */ do while string <> NULL parse var string token string push token end /* Build 'revwords' by extracting tokens from stack */ revwords = "" do while QUEUED() > 0 parse pull token revwords = revwords token end /* ----------------------------- */ string = 'Yoda said, "can you see this?"' /* Reverse the word order in a string [custom function - see header] */ revwords = reverseWords(string) say revwords /* ----------------------------- */ word = "reviver" /* Check whether string is palindrome [custom function - see header] */ is_palindrome = isPalindrome(word) /* @@PLEAC@@_2.0 */ /* ------------------------------------------------------------------ */ /* REXX uses floating point-based, arbitrary precision arithmetic */ /* which, unlike most computer languages, operates not on hardware- */ /* mapped bit collections, but on strings which represent numbers. */ /* */ /* The immediately-obvious disadvantage of this approach is slower, */ /* less memory-efficient number crunching capabilities when compared */ /* to scripting languages like Perl or Python. On the other hand, this*/ /* design approach simplifies REXX interpreter implementation across */ /* platforms, as well as eliminating most of the loss-of-precision and*/ /* related problems so often encountered when 'number crunching'. So, */ /* from an end-user perspective, arithmetic operations should nearly */ /* always generate 'unsurprising' results, and not cause the naive or */ /* unwary user any confusion. */ /* ------------------------------------------------------------------ */ /* @@PLEAC@@_2.1 */ /* ------------------------------------------------------------------ */ /* The REXX-idiomatic numeric validation approach is to use the */ /* 'DATATYPE' BIF. For more complex validation needs the 'VERIFY' BIF */ /* may also be used but since it only checks for the presence or the */ /* absence of characters it needs to be augmented with other checks. */ /* */ /* Regex-based validation [once implemented] requires the least work. */ /* The examples make use of a REXXToolkit routine, 'match', which */ /* uses the 'RxRe' external library. See Appendix for details. */ /* ------------------------------------------------------------------ */ /* REXX BIF-based Validation */ /* Accepts: +9 -9 9.0 9.0e+2 9.0E-3 */ if \DATATYPE(string, 'N') then ; say "not a decimal number" /* Accepts: +9 -9 Rejects: 9.0 9.0e+2 9.0E-3 */ if \(DATATYPE(string, 'W') & POS(".", string) == 0) then say "not an integer" /* ----------- */ /* Checks for presence / absence of characters, but does not check position of characters, or presence of patterns. Useful for quick, but not thorough, validation */ if VERIFY(string, "0123456789") \= 0 then ; say "has nondigits" if VERIFY(string, "+-.Ee0123456789") \= 0 then ; say "not a decimal" /* ----------- */ /* Custom function, 'isDecimal', which uses a combination of the PARSE instruction, and DATATYPE BIF to thoroughly validate a decimal value */ tbl = "+934.521e-2 -934.521 934 ", "+934.521e-a +934.521f-2 +934.!e-2 ", "e934.521e-2" entries = WORDS(tbl) do i = 1 for entries entry = WORD(tbl, i) if isDecimal(entry) then ; say entry "is decimal" else ; say entry "is NOT decimal" end exit 0 /* ----------- */ isDecimal : procedure expose (globals) parse upper value ARG(1) with whole "." frac "E" exp if exp \= NULL then ; if \DATATYPE(exp, 'W') then ; return FALSE if frac \= NULL then ; if \DATATYPE(frac, 'W') then ; return FALSE if whole \= NULL then ; if \DATATYPE(whole, 'W') then ; return FALSE return TRUE /* ----------------------------- */ /* Regex-based Validation */ if match(string, "PATTERN") then /* Is a number */ else /* Is not */ /* ----------- */ /* Also rejects: +9 -9 9.0 */ if match(string, "[^[:digit:]]") then ; say "has nondigits" /* Also rejects: +9 -9 9.0 */ if \match(string, "^[[:digit:]]+$") then ; say "not a natural number" /* Rejects: +9 9.0 Accepts: -9 */ if \match(string, "^-?[[:digit:]]+$") then ; say "not an integer" /* Rejects: 9.0 Accepts: +9 -9 */ if \match(string, "^[+-]?[[:digit:]]+$") then ; say "not an integer" /* Accepts: +9 -9 9.0 9.0e+2 9.0E-3 */ decimalRE = "^[+-]?[[:digit:]]+\.?[[:digit:]]+[e|E][+-]?[[:digit:]]+$" if \match(string, decimalRE) then say "not a decimal number" /* @@PLEAC@@_2.2 */ /* ------------------------------------------------------------------ */ /* The NUMERIC instruction allows adjustment of: */ /* */ /* * Significant digits used in arithmetic operations [DIGITS] */ /* * Digits to be ignored during arithmetic comparisons [FUZZ] */ /* */ /* Default values are usually adequate. Increasing DIGITS increases */ /* precision, but slows down arithmetic operations. FUZZ is by default*/ /* 0, so all digits are significant in comparison operations. */ /* */ /* The FORMAT BIF may be used like the C-derived, 'sprintf', function */ /* to compare floating point values as strings. */ /* ------------------------------------------------------------------ */ numeric digits 11 a = 1234567.8234 ; b = 1234567.8237 /* Compare 'DIGITS - FUZZ' [11] number of digits */ numeric fuzz 0 if a = b then ; say "a = b" /* FALSE */ /* Compare 'DIGITS - FUZZ' [10] number of digits */ numeric fuzz 1 if a = b then ; say "a = b" /* FALSE */ /* Compare 'DIGITS - FUZZ' [9] number of digits */ numeric fuzz 2 if a = b then ; say "a = b" /* TRUE */ /* ----------------------------- */ /* Returns TRUE if 'num1' and 'num2' are equal to 'accuracy' number of decimal places */ isEqual(num1, num2, accuracy) /* ----------------------------- */ a = 1234567.8234 ; b = 1234567.8237 /* isEqual(a, b, 1) ==> TRUE isEqual(a, b, 2) ==> TRUE isEqual(a, b, 3) ==> TRUE isEqual(a, b, 4) ==> FALSE */ exit 0 /* ----------- */ isEqual : procedure expose (globals) places = ARG(3) ; numeric fuzz 0 return FORMAT(ARG(1),, places) == FORMAT(ARG(2),, places) /* ----------------------------- */ wage = 536 /* $ 5.36 / hr */ week = 40 * wage /* $ 214.40 */ say "One week's wage is: $" FORMAT(week / 100,, 2) /* @@PLEAC@@_2.3 */ /* ------------------------------------------------------------------ */ /* The 'FORMAT' BIF is REXX's equivalent to the much-used, C-derived */ /* 'sprintf' function. */ /* ------------------------------------------------------------------ */ /* Truncate to integer value */ truncated = TRUNC(value, length) /* Round value [and possibly justify] */ rounded = FORMAT(value, n_before_decimal, n_after_decimal) /* ----------------------------- */ a = 0.255 ; b = FORMAT(a, 1, 2) say "Unrounded:" a "Rounded:" b say "Unrounded:" a "Rounded:" FORMAT(a, 1, 2) /* Unrounded: 0.255 Rounded: 0.26 Unrounded: 0.255 Rounded: 0.26 */ /* ----------------------------- */ /* Example illustrating external library routine use. Not, however, that the FORMAT BIF can be used to perform the same tasks as 'NInt', 'Floor' and 'Ceil', making library routine use unnecessary */ /* Load [rexxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rexxMath', 'mathLoadFuncs' call mathLoadFuncs tbl = "3.3 3.5 3.7 -3.3" say cstr2rxstr("number\tint\tfloor\tceil") do while tbl <> NULL parse var tbl n tbl line = FORMAT(n, 2, 1) || "\t" ||, FORMAT(NInt(n), 2, 1) || "\t" ||, FORMAT(Floor(n), 2, 1) || "\t" ||, FORMAT(Ceil(n), 2, 1) say cstr2rxstr(line) end /* number int floor ceil 3.3 3.0 3.0 4.0 3.5 4.0 3.0 4.0 3.7 4.0 3.0 4.0 -3.3 -3.0 -4.0 -3.0 */ /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ cstr2rxstr : procedure expose (globals) s = ARG(1) ; tbl = "\n 0A \r 0D \t 09" do while tbl \= NULL parse var tbl esc replc tbl s = CHANGESTR(esc, s, X2C(replc)) end return s /* @@PLEAC@@_2.4 */ /* ------------------------------------------------------------------ */ /* Binary, hexadecimal, decimal interconversion is well-supported via */ /* the following BIF's: */ /* */ /* * X2D, D2X [hex->dec, dec->hex, respectively] */ /* * X2B, B2X [hex->bin, bin->hex, respectively] */ /* */ /* Easily combined to create functions that interconvert binary and */ /* decimal. */ /* ------------------------------------------------------------------ */ /* Convert binary string to decimal */ decimal = B2D('0110110') /* Convert decimal value to binary string */ binary = D2B(54) exit 0 /* ----------- */ B2D : procedure expose (globals) return X2D(B2X(ARG(1))) D2B : procedure expose (globals) return X2B(D2X(ARG(1))) /* @@PLEAC@@_2.5 */ /* ------------------------------------------------------------------ */ /* The 'do' loop is the REXX-idiomatic control structure for */ /* repetitive tasks such as list traversal. Recursive solutions are */ /* possible but less efficient due to argument passing overhead, and */ /* lack of tail-call optimisation. */ /* ------------------------------------------------------------------ */ x = 1 ; y = 5 ; step = 1 /* Number sequence is traversed using 'do' loop */ /* 'i' set from value of 1 through to 5 in 'step' increments */ do i = x to y by step /* do something with 'i' */ end /* ----------- */ /* 'i' set from value of 1 through to 5; default increment of 1 */ do i = x to y /* do something with 'i' */ end /* ----------------------------- */ call CHAROUT , "Infancy is: " do i = 0 to 2 ; call CHAROUT , i || SPACE ; end say NULL call CHAROUT , "Toddling is: " do i = 3 to 4 ; call CHAROUT , i || SPACE ; end say NULL call CHAROUT , "Childhood is: " do i = 5 to 12 ; call CHAROUT , i || SPACE ; end say NULL /* ----------------------------- */ /* REXX does not sport a native 'foreach' control structure, but it is possible to implement similar behaviour provided certain conventions are followed such as generating lists of SPACE or COMMA-separated sequences */ /* ----------------------------- */ sequence = makeIntegerSequence(1, 5, 1) do while sequence <> NULL parse var sequence value sequence call CHAROUT , value || SPACE end /* ----------- */ /* Partial reimplementation of earlier example */ infancy = makeIntegerSequence(0, 2, 1) call CHAROUT , "Infancy is: " do while infancy <> NULL parse var infancy value infancy call CHAROUT , value || SPACE end /* ... */ exit 0 /* ----------- */ /* Iterative ['do' loop-based] */ makeIntegerSequence : procedure expose (globals) x = ARG(1) ; y = ARG(2) ; step = ARG(3) seq = x ; x = x + 1 ; do i = x to y by step ; seq = seq i ; end return seq /* Recursive */ makeIntegerSequenceR : procedure expose (globals) x = ARG(1) ; y = ARG(2) ; step = ARG(3) if x > y then ; return NULL return x makeIntegerSequenceR(x + step, y, step) /* Iterative [Tail Recursive] */ makeIntegerSequenceI : procedure expose (globals) x = ARG(1) ; y = ARG(2) ; step = ARG(3) ; seq = ARG(4) if x > y then ; return STRIP(seq) return makeIntegerSequenceI(x + step, y, step, (seq x)) /* @@PLEAC@@_2.6 */ /* ------------------------------------------------------------------ */ /* REXX sports no inbuilt Roman numeral-handling routines. A custom */ /* implementation appears below. */ /* ------------------------------------------------------------------ */ roman = arabic2roman(arabic) arabic = roman2arabic(roaman) /* ----------------------------- */ roman_fifteen = arabic2roman(15) say "Roman for fifteen is" roman_fifteen arabic_fifteen = roman2arabic(roman_fifteen) say "Converted back" roman_fifteen "is" arabic_fifteen exit 0 /* ----------- */ roman2arabic : procedure tbl.I = 1 ; tbl.V = 5 ; tbl.X = 10 ; tbl.L = 50 tbl.C = 100 ; tbl.D = 500 ; tbl.M = 1000 tbl.IV = 4 ; tbl.IX = 9 ; tbl.XL = 40 ; tbl.XC = 90 tbl.CD = 400 ; tbl.CM = 900 roman = " " || TRANSLATE(STRIP(ARG(1))) ; arabic = 0 do i = LENGTH(roman) - 1 to 1 by -1 r = SUBSTR(roman, i, 2) if SYMBOL('tbl.r') == 'VAR' then ; i = i - 1 else ; r = RIGHT(r, 1) arabic = arabic + tbl.r end return arabic /* ----------- */ arabic2roman : procedure arabic = REVERSE(ARG(1)) ; len = LENGTH(arabic) ; roman = "" tbl.1 = "I II III IV V VI VII VIII IX" tbl.2 = "X XX XXX XL L LX LXX LXXX XC" tbl.3 = "C CC CCC CD D DC DCC DCCC CM" if len < 4 then do i = 1 to len j = SUBSTR(arabic, i, 1) ; if j == 0 then ; iterate roman = WORD(tbl.i, j) || roman end else ; do do i = 1 to 3 j = SUBSTR(arabic, i, 1) ; if j == 0 then ; iterate roman = WORD(tbl.i, j) || roman end roman = COPIES("M", REVERSE(SUBSTR(arabic, 4))) || roman end return roman /* @@PLEAC@@_2.7 */ /* ------------------------------------------------------------------ */ /* Random number [well, pseudo-random :)] generation is typically */ /* performed using the 'RANDOM' BIF. */ /* ------------------------------------------------------------------ */ random = RANDOM(maxval) /* 0 - maxval [maxval <= 100000] */ random = RANDOM(minval, maxval) /* minval - maxval [as above] */ /* ----------------------------- */ tbl = "abcdefghijklmnop" elt = randomChoice(tbl) /* One of 'a', 'b', ... */ tbl = "12 67 asde cvs +++ &fgt klmnop" elt = randomChoice(tbl) /* One of 12, 67, ... */ /* ----------------------------- */ /* Generate 8 character-length password with randomly chosen chars */ chars = XRANGE("A", "Z") || XRANGE("a", "z") ||, XRANGE("0", "9") || "!$%#@*&" password = NULL do 8 password = password || randomChoice(chars) end exit 0 /* ----------- */ randomChoice : procedure expose (globals) tbl = ARG(1) ; items = WORDS(tbl) if items == 1 then do items = LENGTH(tbl) ; item = SUBSTR(tbl, RANDOM(1, items), 1) end ; else do item = WORD(tbl, RANDOM(1, items)) end return item /* @@PLEAC@@_2.8 */ /* ------------------------------------------------------------------ */ /* See comments in previous section */ /* ------------------------------------------------------------------ */ random = RANDOM(,, seed) /* Each such call reseeds the RNG */ /* @@PLEAC@@_2.9 */ /* ------------------------------------------------------------------ */ /* Custom functions for this type of task are easily written in REXX. */ /* Examples include: */ /* */ /* * 'lcg', simple linear-congruential RNG */ /* * 'randomSlice' - see example below */ /* ------------------------------------------------------------------ */ random = 47523 ; reps = 10 do reps random = lcg(random) /* do something with 'random' ... */ end /* ----------------------------- */ reps = 10 do reps /* Random length digit sequence; sliced from random position of a default-length 'RANDU'-generated digit sequence */ random = randomSlice() /* 3 digit sequence; as previous */ random = randomSlice(3) /* 4 digit sequence; sliced from random position of a 13 digit length 'RANDU'-generated digit sequence */ random = randomSlice(4, 13) end /* ----------- */ lgc : procedure expose (globals) numeric digits 17 return 16807 * ARG(1) // 2147483647 randomSlice : procedure expose (globals) sizeSlice = ARG(1) ; sizePool = ARG(2) if sizePool == NULL | sizePool > 17 then ; sizePool = 17 if sizeSlice == NULL then ; sizeSlice = RANDOM(1, sizePool - 1) if sizeSlice >= sizePool then ; sizeSlice = sizePool - 1; posSlice = RANDOM(1, sizePool - sizeSlice) numeric digits sizePool parse value RANDU() with "." frac return SUBSTR(frac, posSlice, sizeSlice) /* @@PLEAC@@_2.10 */ /* ------------------------------------------------------------------ */ /* Gaussian RNG */ /* ------------------------------------------------------------------ */ /* Need this for access to non-standard, 'RANDU', function */ options 'AREXX_BIFS' /* Using 'rexxMath' Library Routines */ /* Load [rexxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rexxMath', 'mathLoadFuncs' call mathLoadFuncs /* ----------- */ mean = 25.0 ; sdev = 2.0 ; salary = gaussian_rand() * mean + sdev say "You have been hired at:" FORMAT(salary,, 2) /* ----------- */ /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ gaussian_rand : procedure w = 2.0 do while w > 1.0 u1 = 2.0 * RANDU() - 1.0 ; u2 = 2.0 * RANDU() - 1.0 w = u1 * u1 + u2 * u2 end w = Sqrt((-2.0 * Log10(w)) / w) ; g2 = u1 * w ; g1 = u2 * w return g1 /* @@PLEAC@@_2.11 */ /* ------------------------------------------------------------------ */ /* Aside from supporting the usual arithmetic operations, including */ /* exponentiation [via the '**' operator], and a few BIF's including */ /* 'MIN', 'MAX', 'SIGN' and 'ABS', REXX offers no built-in support for*/ /* mathematical operations. Instead the programmer can implement the */ /* required functionality themselves, or make use of external library */ /* routines. */ /* */ /* REXX-native mathematical functions are easily implementable, but */ /* the string-expressable, arbitrary precision arithmetic model used */ /* ensures they will not be as 'high performance' as hardware-based */ /* implementations, precluding their use for 'serious' number crunch- */ /* ing. On the other hand, external library routines are [like the one*/ /* illustrated here] to be hardware-based, hence offer performance */ /* comparable to that of other languages after both function call and */ /* data conversion overhead is taken into account. */ /* ------------------------------------------------------------------ */ /* Using 'rxMath' Library Routines */ /* Load [rxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rxMath', 'mathLoadFuncs' call mathLoadFuncs /* Accepts argumets in either degree, radian, or gradian form */ say rxCalcSin(30, 'D') say FORMAT(rxCalcSin(60, 'D'),,3) /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ /* Using native REXX Routines [need 'Sin' from external library] */ radians = DEG2RAD(degrees) degrees = RAD2DEG(radians) /* ----------------------------- */ /* Load [rexxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rexxMath', 'mathLoadFuncs' call mathLoadFuncs say degree_sin(30) say FORMAT(degree_sin(60),,3) /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ degree_sin : procedure expose (globals) /* ARG(1) - Degrees --- 'Sin' [a 'rexxMath' library routine] expects its argument in radians so 'DEG2RAD' used to perform the conversion */ return Sin(DEG2RAD(ARG(1))) DEG2RAD : procedure expose (globals) return ARG(1) / 180 * PI() RAD2DEG : procedure expose (globals) return ARG(1) / PI() * 180 PI : procedure expose (globals) return 3.14159265358979323846264338327 /* @@PLEAC@@_2.12 */ /* ------------------------------------------------------------------ */ /* See comments in previous section header */ /* ------------------------------------------------------------------ */ /* Using 'rxMath' Library Routines */ /* Load [rxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rxMath', 'mathLoadFuncs' call mathLoadFuncs /* Accepts argumets in either degree, radian, or gradian form */ theta = 1.7 ; tan = rxCalcSin(theta, 'R') / rxCalcCos(theta, 'R') say "tan of theta" theta "[radians]:" tan say "tan of theta" FORMAT(theta,, 3) "[radians]:" FORMAT(tan,, 3) /* ----------- */ say "tan of theta" theta "[radians]:" rxCalcTan(theta, 'R') say "tan of theta" FORMAT(theta,, 3) "[radians]:", /* ----------- */ theta = 0.37 ; say "acos of" theta "[radians]:" rxCalcArcCos(theta, 'R') /* ----------- */ /* Unload math functions */ call mathDropFuncs exit 0 /* ----------------------------- */ /* Using 'rexxMath' Library Routines */ /* Load [rexxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rexxMath', 'mathLoadFuncs' call mathLoadFuncs theta = 1.7 ; tan = Sin(theta) / Cos(theta) say "tan of theta" theta "[radians]:" tan say "tan of theta" FORMAT(theta,, 3) "[radians]:" FORMAT(tan,, 3) /* ----------- */ say "tan of theta" theta "[radians]:" Tan(theta) say "tan of theta" FORMAT(theta,, 3) "[radians]:" FORMAT(Tan(theta),, 3) /* ----------- */ theta = 0.37 ; say "acos of" theta "[radians]:" ACos(theta) /* ----------- */ /* Unload math functions */ call mathDropFuncs exit 0 /* @@PLEAC@@_2.13 */ /* ------------------------------------------------------------------ */ /* See comments in previous section header */ /* ------------------------------------------------------------------ */ /* Using 'rxMath' Library Routines */ /* Load [rxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rxMath', 'mathLoadFuncs' call mathLoadFuncs log_e = rxCalcLog(value) /* ----------- */ log_10 = rxCalcLog10(value) /* ----------- */ answer = rxlog_base(10, 10000) say "log_base(10, 10000) ==>" FORMAT(answer,, 2) say "log10(10000) ==>" FORMAT(rxCalcLog10(10000),, 2) /* ----------- */ /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ rxlog_base : procedure expose (globals) base = ARG(1) ; value = ARG(2) return rxCalcLog(value) / rxCalcLog(base) /* ----------------------------- */ /* Using 'rexxMath' Library Routines */ /* Load [rexxMath] math functions from external library */ call rxFuncAdd 'mathLoadFuncs', 'rexxMath', 'mathLoadFuncs' call mathLoadFuncs log_e = Log(value) /* ----------- */ log_10 = Log10(value) /* ----------- */ answer = log_base(10, 10000) say "log_base(10, 10000) ==>" FORMAT(answer,, 2) say "log10(10000) ==>" FORMAT(Log10(10000),, 2) /* ----------- */ /* Unload math functions */ call mathDropFuncs exit 0 /* ----------- */ log_base : procedure expose (globals) base = ARG(1) ; value = ARG(2) return Log(value) / Log(base) /* @@PLEAC@@_2.14 */ /* ------------------------------------------------------------------ */ /* REXX offers no matrix-handling BIF's. Below can be found a custom */ /* implementation that, perhaps unusually, represents matrices as str-*/ /* ings. Notes: */ /* */ /* * Since strings are immutable, matrix manipulations result in new */ /* strings being created; high performance, therefore, should not be*/ /* expected */ /* */ /* * Only a smattering of operations are offered, and some of them use*/ /* rather naive algorithms [e.g. multiplication - Winograd's algori-*/ /* thm could instead be used] */ /* */ /* * There is much code redundancy [e.g. 'madd' and 'msub' are identi-*/ /* save for the arithmetic operation performed]. This could have be-*/ /* en avoided via use of both the VALUE BIF and INTERPRET instructi-*/ /* on [an approach much used in Chapter 4], but it was felt that co-*/ /* de would be more readable, and perhaps more easily adapted if ke-*/ /* pt simple, despite the repetition. */ /* */ /* * Decision to model matrices as strings was based on two factors: */ /* */ /* - Avoiding global array use */ /* - Illustrate how ADT's may be modelled using strings, and showca-*/ /* se the REXX PARSE instruction and string manipulation BIF's */ /* */ /* Performance can be significantly improved without resorting to the */ /* use of global arrays by using an external library like T. J. McPhe-*/ /* e's, 'rxHash', that implements arrays as special strings that may */ /* be freely passed around. Chapter 4 makes extensive use of this very*/ /* versatile library. I hope to provide an expanded version of the */ /* present library using this technique as part of the REXXToolkit [to*/ /* be found in the Appendix] sometime in 2007. */ /* ------------------------------------------------------------------ */ /* Global Constants */ FALSE = 0 ; TRUE = 1 ; NULL = "" ; NEWLINE = "0A"X ; SPACE = ' ' NaN = "NaN" /* Matrix-specific global constants */ MTAG = "<M>" ; MHSEP = "|" ; MRAWSEP = "; " ; MRSEP = ";" MTYPE_REGULAR = "R" ; MTYPE_SINGULAR = "S" ; MTYPE_ZERO = "Z" MTYPE_IDENTITY = "I" ; MTYPE_VECTOR = "V" /* -- */ /* Global Roots and 'expose' list */ globals = "sys. env. args. $. FALSE TRUE NULL NEWLINE SPACE NaN" /* Matrix-specific 'expose' list */ matdefs = "MTAG MHSEP MTYPE_REGULAR MTYPE_SINGULAR" , "MTYPE_ZERO MTYPE_IDENTITY MTYPE_VECTOR" , "MRSEP" , "MRAWSEP" /* ----------------------------- */ x = makeMatrix("3 2 3;5 9 8;") ; y = makeMatrix("4 7;9 3;8 1;") z = mmul(x, y) say "z =" ; call mdump z /* ----------------------------- */ say "z determinant:" mdet(z) say "z inverse =" ; call mdump minverse(z), 8 say "trace: " mtrace(z) say "z transpose =" ; call mdump mtranspose(z) exit 0 /* ----------------------------- */ /* * *** IMPORTANT *** Matrix rows and columns numbered from 1, and *not* 0 like so many zero-index-based languages * Variable size, delimited strings represent the matrix type. Each such string has a header section followed by a data section; typically, the string is split, metadata extracted from the header, and the data section returned for subsequent processing * Easy to view matrix contents: just SAY the string. The 'mdump' routine is available for pretty printing * Simple error-handling approach used: a value of, 'NaN', is returned where any error is detected [applies only to routines that do error checking - 'stupid' usage merely sees the script crash] * Matrix Format [EBNF]: <matrix> ::= <header> <data> <header> ::= <type-tag> <rows> <columns> <matrix-type> <EOH> <data> ::= <row>+ <type-tag> ::= '<M>' <rows> ::= <integer> <colums> ::= <integer> <matrix-type> ::= 'S' | 'R' | 'V' | 'Z' | 'I' <EOH> ::= '|' <row> ::= <decimal>+ <EOR> <EOR> ::= ';' <integer> ::= digit+ <decimal> ::= <integer> | digit+ '.' digit+ <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' * Matrix Format Examples: - 1-row matrices considered vectors 1x1 -> "<M>1 1 V|7;" 1x3 -> "<M>1 3 V|7 8 9;" - Square Matrices; regular, zero, or identity 2x2 Regular -> "<M>2 2 R|1 2;4 5;" 3x3 Regular -> "<M>3 3 R|1 2 3;4 5 6;7 8 9;" 3x3 Zero -> "<M>3 3 Z|0 0 0;0 0 0;0 0 0;" 3x3 Identity -> "<M>3 3 I|1 0 0;0 1 0;0 0 1;" - Singular Matrices 2x3 -> "<M>2 3 S|1 2 3;4 5 6;" 3x2 -> "<M>3 2 S|1 2;3 4;5 6;" * Matrix string contains both metadata and uses a delimiter to mark out rows. Using only one of these items would have allowed determination of the other [i.e. compute metadata by counting delimiters, or tokenise into rows using metadata], but using both allowed for easy type-checking and simplified tokenisation via the PARSE instruction * Routines classed as follows: - Constructors [makeVector, makeMatrix, makeDiagonal] - Type Checkers [isVector, isMatrix, is1x1, is2x2] - Metadata [mrows, mcols] - Comparators [meql] - Selectors [extractMatrix, mrow, mcol, msubset, mminor] - Matrix Arithmetic [madd, msub, mmul, mdiv] - Matrix OPerations [mtranspose, mcofactor, mdet1x1, mdet2x2, mdet, minv1x1, minverse, mtrace] - Elementary Row / Column Operations [mswapc, mswapr, maddc, maddr, mmulc, mmulr]. These are needed for solving linear equations via Echelon method - Pretty Print [mdump] * Routine documentation has the following structure: Parameter List --- Description --- Routine Example(s) Parameter list conventions include: - x | y | z -> One of x or y or z - [optional arguments ...] - Types: + s, s1, s2 -> string(s) + n, n1, n2 -> numeric + v, v1, v2 -> vector(s) + m, m1, m2 -> matrices */ /* ----------- */ makeVector : procedure expose (globals) (matdefs) /* s | n1 [, n2, ...] --- Returns a vector created by parsing, 's', or assembling, 'n1', 'n2' ... --- v = makeVector("1 2 3;") v = makeVector(1, 2, 3) */ argc = ARG() ; if argc == 0 then ; return NaN if argc == 1 then do v = ARG(1) ; argc = WORDS(v) end ; else do v = NULL ; do i = 1 for argc ; v = v ARG(i) ; end ; v = STRIP(v) end return MTAG || 1 argc MTYPE_VECTOR || MHSEP || v || MRSEP makeMatrix : procedure expose (globals) (matdefs) /* s | v1 [, v2, ...] --- Returns a matrix created by parsing, 's', or assembling, 'v1', 'v2' ... --- m = makeMatrix("1 2 3;4 5 6;7 8 9;") m = makeMatrix(makeVector(1, 2, 3)) m = makeMatrix(makeVector(1, 2, 3), makeVector(4, 5, 6),, makeVector(7, 8, 9)) */ argc = ARG() ; if argc == 0 then ; return NaN if argc == 1 then do m = ARG(1) ; if isVector(m) then ; return m rows = COUNTSTR(MRSEP, m) cols = WORDS(SUBSTR(m, 1, POS(MRSEP, m) - 1)) rv = NULL ; do i = 1 for rows parse var m row (MRSEP) m ; rv = rv || row || MRSEP end end ; else do rows = argc ; rv = NULL ; do i = 1 for rows parse value ARG(i) with (MTAG) . cols . (MHSEP) data rv = rv || data end end select when cols == rows then ; type = MTYPE_REGULAR otherwise ; type = MTYPE_SINGULAR end return MTAG || rows cols type || MHSEP || STRIP(rv) makeDiagonal : procedure expose (globals) (matdefs) /* s | v | n1 [, n2, ...] --- Returns a square matrix with a leading diagonal having the values obtained by parsing, 's', assembling, 'v1', 'v2', or from, 'v' --- m = makeDiagonal("1 2 3;") m = makeDiagonal(1, 2, 3) m = makeDiagonal(makeVector(1, 2, 3)) */ argc = ARG() ; if argc == 0 then ; return NaN ONE_ONLY = TRUE ; chksum = 0 if argc == 1 then do v = ARG(1) ; if isVector(v) then do parse var v (MTAG) . cols . (MHSEP) data end ; else do cols = WORDS(SUBSTR(v, 1, POS(MRSEP, v) - 1)) ; data = v end parse var data row (MRSEP) . rows = cols ; rv = NULL ; do i = 1 for rows do j = 1 for cols if i == j then do parse var row item row chksum = chksum + item ; rv = rv item if item > 1 then ; ONE_ONLY = FALSE end ; else ; rv = rv 0 end rv = rv || MRSEP end end ; else do cols = argc ; rows = cols ; rv = NULL do i = 1 for rows do j = 1 for cols if i == j then do value = ARG(i) ; chksum = chksum + value ; rv = rv value if value > 1 then ; ONE_ONLY = FALSE end ; else ; rv = rv 0 end rv = rv || MRSEP end end select when chksum == 0 then ; type = MTYPE_ZERO when chksum == rows & ONE_ONLY then ; type = MTYPE_IDENTITY otherwise ; type = MTYPE_REGULAR end return MTAG || rows cols type || MHSEP ||, STRIP(CHANGESTR(MRAWSEP, rv, MRSEP)) /* -- */ isMatrix : procedure expose (globals) (matdefs) /* m --- TRUE if 'm' determined to be a matrix --- if \isMatrix(m) then ; return NaN */ parse value WORD