1. Strings

Introduction

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

Accessing Substrings

/* ------------------------------------------------------------------ */
/* * 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 *** */

Establishing a Default Value

/* ------------------------------------------------------------------ */
/* 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)

Exchanging Values Without Using Temporary Variables

/* ------------------------------------------------------------------ */
/* 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

Converting Between ASCII Characters and Values

/* ------------------------------------------------------------------ */
/* 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

Processing a String One Character at a Time

Reversing a String by Word or Character

/* ------------------------------------------------------------------ */
/* 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)

Expanding and Compressing Tabs

Expanding Variables in User Input

Controlling Case

Interpolating Functions and Expressions Within Strings

Indenting Here Documents

Reformatting Paragraphs

Escaping Characters

Trimming Blanks from the Ends of a String

Parsing Comma-Separated Data

Soundex Matching

Program: fixstyle

Program: psgrep