Practical guide to figurative constants in RPG

What exactly are BLANKS, ON, *OFF, etc? I see these used in RPG all the time and can usually tell what they mean, but I needed to know more details so here they are.

First, it is helpful to remember that string comparisons in RPG have a quirk compared to most languages on other platforms. When comparing strings of different lengths, the shorter string is right-padded with blanks (spaces) up to the length of the longer. Thus the two strings being compared are always equal-length. This means the two variable length strings varcharA = ''; and varcharB = ' '; will compare as equal. Likely this quirk is due to its origins with only fixed-length (unterminated) strings available in the language.

Since this is a Python blog after all, let's see what this comparison would look like in Python:

import functools

@functools.total_ordering  # fill in unimplemented comparison functions
class RpgStr:
    def __init__(self, val: str = None):
        """Decorator for a normal python str, implementing RPG-like comparison function"""
        self.val = val or ''

    def __len__(self):
        return len(self.val)

    def __getattr__(self, item):
        """Pass-through to support other normal str functionality"""
        return getattr(self.val, item)

    def __eq__(self, other):
        return self.ljust(len(other)) == other.ljust(len(self))

    def __lt__(self, other):
        return self.ljust(len(other)) < (other.ljust(len(self)))


a = RpgStr('abc')
b = RpgStr('abc' + '     ')

print(a == b)  # True

Knowing the above regarding string comparisons makes it easy to understand how these "figurative constants" work in RPG. Each represents a number or character set that will be repeated to build a string for use in comparisons and assignments. The length of the constant will be equal to the length of the other "side" of the comparison or assignment.

Figurative Constants

Here are the available figurative constants:

  • *ALL'x...': repeats the characters in the single-quotes up to the specified length. Variations *ALLG, *ALLU, and *ALLX exist for repeated graphic, unicode, and hexadecimal literals.

  • *BLANK / *BLANKS: repeats a blank (' ') character up to the specified length, usually equivalent to *ALL' '. Valid only for string types.

  • *ZERO / *ZEROS: repeats a zero ('0') character up to the specified length, usually equivalent to *ALL'0'.

  • *HIVAL, *LOVAL: represents the maximum or minimum value representable by a numeric or date/time type. examples 99 and -99 for type packed(2,0). Interestingly, the lo/hi value for date/times may vary based on the settings for the DATFMT or TIMFMT.

  • *NULL represents a null value for pointers (not the same as NULL in SQL)

  • *ON and *OFF map to '1' and '0' respectivally and are valid for character types and indicators (indicators are essentially char(1) values which must be '1' or '0')

Note: there is no difference between the singular and plural forms (e.g. *BLANK and *BLANKS); they are synonyms for convenience

Examples for fun

The following code is ready to compile and call:

**free

dcl-s vStr varchar(10) inz('');
dcl-s vInitBlankStr varchar(10) inz(*blanks);  // will have %len() = 10
dcl-s vLongStr varchar(24) inz('');
dcl-s vTenDigit packed(10) inz(0);
dcl-s vStrArray varchar(10) dim(10);

dcl-s vPrompt varchar(25) inz('Press Enter');
dcl-s vResult varchar(1) inz('');                 

// compare empty string to *blanks, *blanks will be ''
if vStr = *blanks;
  dsply 'vStr = *blanks evals TRUE, with length 0';
endif;

// compare long blank string to *blanks, *blanks will be '          '
if vInitBlankStr = *blanks;
  dsply 'vInitBlankStr = *blanks evals TRUE, with length 10';
endif;

// compare different-length strings. shortest will be padded
if vStr = vInitBlankStr;
  dsply 'vStr = vInitBlankStr evals TRUE, with diff lengths';
endif;

// assign blanks to a variable string type, will be padded to the max size
vStr = *blanks;
dsply ('length of vStr: ' + %len(vStr));  // this will display 10

// assign a repeating sequence to a str
vLongStr = *ALL'xyzzy';
dsply ('vLongStr = ' + vLongStr);  // 'xyzzyxyzzyxyzzyxyzzyxyzz'

// assign repeating digits to a number
vTenDigit = *ALL'72';
dsply ('vTenDigit = ' + vTenDigit);  // '7272727272'

// can I turn a string to all ones?
vStr = *ON;
dsply vStr;  // yep; '1111111111'

// fill an entire array with a figurative constant?
vStrArray = *ALL'123';
dsply ('vStrArray(2) = ' + vStrArray(2));  // each array value is now '1231231231'

*inlr = *on;
dsply vPrompt '' vResult;   // pause at the end of execution     

Running the example Code

RPG code runs on IBM i systems. Paste the above code in a source member called CHKCONST in <yourlib>/QRPGLESRC and use the following to compile/run:

Compile: CRTBNDRPG PGM(<yourlib>/CHKCONST) REPLACE(*YES)

Run: CALL <yourlib>/CHKCONST

Recommendations

After looking at many examples of these constants in use, and playing with them, I have these recommendations:

  • Take advantage of checking whether a string is "empty" with vStr = *blanks, whether the string length is variable or fixed! In other languages, we often must write some variation of %rtrim(vStr) = '' and the "RPG way" is cleaner.

  • Remember that assigning *blanks to a variable length string will waste memory. Use vStr = '' for a zero-length string. You can use this for fixed-length strings too, and it will still blank the entire string.

  • If you need to compare BOTH length and content of two strings, you must explicitly do so: if %len(string1) = %len(string2) and string1 = string2. Putting the length comparison first is likely best for both readability and short-circuit efficiency.

  • In free-form (modern) RPG, I see no reason to use *ZERO for numeric types, as opposed to just a literal 0.

  • When setting indicator variables, always use *ON or *OFF as the value (never the literals 1 or 0). This will help future-proof your code.

  • If you are using comparisons to *ON or *OFF you can stop: if *in45 = *ON; is the same as if *in45;. In fact the expression *in45 = *ON internally evaluates one of the literals *ON or *OFF. You can use this in assignments: after the statement isAwesome = ('RPG' = 'Awesome') the indicator/char isAwesome will contain *OFF.

Find out more: