$include (..\lib\compStch.ext) /* *============================================================================ * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE * * Permission to use for any purpose, modify, copy, and make enhancements * and derivative works of the software is granted if attribution is given to * R.M. Gillmore, dba the ACME Software Deli, as the author * * While the ACME Software Deli does not work for money, there is nonetheless * a significant amount of work involved. The ACME Software Deli maintains the * rights to all code written, though it may be used and distributed as long as * the following conditions are maintained. * * 1. The copyright statement at the top of each code block is maintained in * your distribution. * 2. You do not identify yourself as the ACME Software Deli * 3. Any changes made to the software are sent to the ACME Software Deli *============================================================================ */ sprintfEngineModule: do; $if not noID declare IDString (*) byte data ( '@(#)prtfEng.p86 $Author: rmgillmore $ $Date:: 2025-05-04 19:35:39#$:', 0 ); $endif $include (..\lib\comnDefs.ext) $set (sprintfEngineSource) $include (..\lib\prtfEng.ext) $include (..\lib\string.ext) $include (..\lib\ctype.ext) $include (..\lib\numStrs.ext) $include (..\lib\parmData.ext) charToCase: procedure ( charIn, charArrayPtr ) word reentrant; declare charIn char, charArrayPtr pointer; declare charArrayLength word, charPosition word; /* * the charIn may or may not be in the char array. If it is not, it will * receive the value which is associated with the length of the string. that * will be the equivalent of "default" in the C-based languages */ charArrayLength = strlen( charArrayPtr ); charPosition = findb( charArrayPtr, charIn, charArrayLength ); if ( charPosition > charArrayLength ) then charPosition = charArrayLength; return ( charPosition ); end charToCase; charToByte: procedure ( charIn ) byte reentrant; declare charIn char; declare valueToReturn byte, xlatChars (*) char data ( '0123456789abcdef', 0 ); valueToReturn = findb( @xlatChars, charIn, strlen( @xlatChars ) ); return ( valueToReturn ); end charToByte; sprintfEngine: procedure ( userStringPtr, parameterInformationPtr ) reentrant public; declare userStringPtr pointer, userString based userStringPtr (*) char, parameterInformationPtr pointer, paramData based parameterInformationPtr paramDataStruct, formatStringPtr pointer, formatString based formatStringPtr (*) char; declare BACKSLASH char data ( '\' ), PERCENT char data ( '%' ); declare formatStringIndex word, outputStringIndex word, formatStringLength word, charToCompare char; call getPointer( @paramData, @formatStringPtr ); formatStringIndex, outputStringIndex = 0; formatStringLength = strlen( @formatString ); userString( 0 ) = NUL; do while ( formatStringIndex < formatStringLength ); charToCompare = formatString( formatStringIndex ); if ( BACKSLASH = charToCompare ) then do; declare switchChars (*) char data ( 'a', 'b', 'f', 'n', 'r', 't', '0', 'h', 0 ); declare switchCase word; /* * BACKSLASH means that we interpret the next character OR we * take it literally (in the case of the backslash character) */ formatStringIndex = formatStringIndex + 1; switchCase = charToCase( formatString( formatStringIndex ), @switchChars ); do case ( switchCase ); do; /* 'a' (bell specifier) */ userString( outputStringIndex ) = BELL; outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; do; /* 'b' (backspace specifier) */ outputStringIndex = outputStringIndex - 1; formatStringIndex = formatStringIndex + 1; end; do; /* 'f' (form feed specifier) */ userString( outputStringIndex ) = FF; outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; do; /* 'n' (new-line specifier) */ userString( outputStringIndex ) = CR; userString( outputStringIndex + 1 ) = LF; outputStringIndex = outputStringIndex + 2; formatStringIndex = formatStringIndex + 1; end; do; /* 'r' (carriage return specifier) */ userString( outputStringIndex ) = CR; outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; do; /* 't' (horizontal tab specifier) */ userString( outputStringIndex ) = TAB; outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; do; /* '0' (octal notation) */ /* * We assume that the next four characters are octal * digits, and (attempt to) interpret them as such. the * string will end either when we have 4 digits or we * come to non-octal digit */ declare octalValue byte, numberDigits byte, MAX_NUMBER_DIGITS_PER_BYTE literally '3'; formatStringIndex = formatStringIndex + 1; octalValue, numberDigits = 0; do while ( isdigitOctal( formatString( formatStringIndex ) ) ) and ( numberDigits < MAX_NUMBER_DIGITS_PER_BYTE ); /* * rather than multiply, we shift 3 bits (x 8) */ octalValue = shl( octalValue, 3 ) + charToByte( formatString( formatStringIndex ) ); formatStringIndex = formatStringIndex + 1; numberDigits = numberDigits + 1; end; userString( outputStringIndex ) = octalValue; outputStringIndex = outputStringIndex + 1; end; do; /* 'h' (hexadecimal notation) */ /* * We assume that the next four characters are hexadecimal * digits, and (attempt to) interpret them as such. the * string will end either when we have 4 digits or we * come to non-hexadecimal digit */ declare hexValue byte, numberDigits byte, MAX_NUMBER_HEX_DIGITS literally '2'; formatStringIndex = formatStringIndex + 1; hexValue, numberDigits = 0; do while ( isdigitHex( formatString( formatStringIndex ) ) ) and ( numberDigits < MAX_NUMBER_HEX_DIGITS ); /* * rather than multiply, we shift 4 bits (x 16) */ hexValue = shl( hexValue, 4 ) + charToByte( tolower( formatString( formatStringIndex ) ) ); formatStringIndex = formatStringIndex + 1; numberDigits = numberDigits + 1; end; userString( outputStringIndex ) = hexValue; outputStringIndex = outputStringIndex + 1; end; do; /* default (accept the char literally) */ userString( outputStringIndex ) = formatString( formatStringIndex ); outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; end; /* switch case backslash modifier */ end; else if ( PERCENT = charToCompare ) then do; /* * PERCENT means that the next part of the string is a format * specifier. However, when the next character is a percent * character, then we do not interpret the format string, we * pass it along into the output string */ declare switchChars (*) char data ( 'x', 'X', 'd', 'c', 's', 'p', 'l', 'b', 'u', 't', 'T', 0 ), falseChar (*) char data ( 'F', 0 ), trueChar (*) char data ( 'T', 0 ), booleanChar (*) pointer data ( @falseChar, @trueChar ), falseString (*) char data ( 'False', 0 ), trueString (*) char data ( 'True', 0 ), booleanString (*) pointer data ( @falseString, @trueString ); declare switchCase word; declare charVariable char, byteVariable byte, booleanVariable boolean, wordVariable word, integerVariable integer, dwordVariable dword, pointerVariable pointer, stringVariable pointer; /* * PERCENT means that we interpret the next character OR we * take it literally (in the case of the PERCENT character) */ formatStringIndex = formatStringIndex + 1; switchCase = charToCase( formatString( formatStringIndex ), @switchChars ); do case ( switchCase ); /* * %x can be %X also (but reported in upper case) * * %x - using as many hex digits as necessary, insert the 16-bit value * %lx - using as many hex digits as necessary, insert the 32-bit value * %bx - using as many hex digits as necessary, insert the 8-bit value * %d - using as many decimal digits as necessary, insert the SIGNED 16-bit value * %ld - using as many decimal digits as necessary, insert the SIGNED 32-bit value * %bd - using as many decimal digits as necessary, insert the SIGNED 8-bit value * %u - using as many decimal digits as necessary, insert the UNSIGNED 16-bit value * %ul - using as many decimal digits as necessary, insert the UNSIGNED 32-bit value * %ub - using as many decimal digits as necessary, insert the UNSIGNED 8-bit value * %c - insert the character * %s - insert the NUL terminated string * %p - insert the pointer value * %t - insert the "T"/"F" boolean indicator * %T - insert the "True"/"False" boolean indicator */ do; /* 'x' - 16-bit hex (always 5 characters) */ call getWord( @paramData, @wordVariable ); call wordToHexString( wordVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'x' - 16-bit hex (always 5 characters) */ do; /* 'X' - 16-bit HEX (always 5 characters) */ declare tempHexString (6) byte; call getWord( @paramData, @wordVariable ); call wordToHexString( wordVariable, @tempHexString ); call strupr( @tempHexString ); call strcat( @userString, @tempHexString ); outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'x' - 16-bit hex (always 5 characters) */ do; /* 'd' - signed 16-bit decimal */ call getInteger( @paramData, @integerVariable ); call integerToString( integerVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'd' - signed 16-bit decimal */ do; /* 'c' - character */ call getChar( @paramData, @charVariable ); userString( outputStringIndex ) = charVariable; outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; /* 'c' - character */ do; /* 's' - string */ userString( outputStringIndex ) = NUL; call getPointer( @paramData, @stringVariable ); call strcat( @userString, stringVariable ); outputStringIndex = outputStringIndex + strlen( stringVariable ); formatStringIndex = formatStringIndex + 1; end; /* 's' - string */ do; /* 'p' - pointer */ call getPointer( @paramData, @pointerVariable ); call pointerToHexString( pointerVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'p' - pointer */ do; /* 'l' - long (32-bit), look at next character for format specifier */ call getDword( @paramData, @dwordVariable ); /* * %l can be followed by x or d */ formatStringIndex = formatStringIndex + 1; if ( formatString( formatStringIndex ) = 'x' ) then do; call dwordToHexString( dwordVariable, @userString( outputStringIndex ) ); end; else do; call dwordToDecimalString( dwordVariable, @userString( outputStringIndex ) ); end; outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'l' - long (32-bit), look at next character for format specifier */ do; /* 'b' - byte (8-bit), look at next character for format specifier */ call getByte( @paramData, @byteVariable ); /* * %b can be followed by x or d */ formatStringIndex = formatStringIndex + 1; if ( formatString( formatStringIndex ) = 'x' ) then do; call byteToHexString( byteVariable, @userString( outputStringIndex ) ); end; else do; call byteToDecimalString( byteVariable, @userString( outputStringIndex ) ); end; outputStringIndex = strlen( @userString ); formatStringIndex = formatStringIndex + 1; end; /* 'b' - byte (8-bit), look at next character for format specifier */ do; /* 'u' - unsigned, look at next character for size specification */ /* * the 'u' could be followed by a space (%ud), 'd', 'l' or 'b' */ formatStringIndex = formatStringIndex + 1; if ( ( formatString( formatStringIndex ) = ' ' ) or ( formatString( formatStringIndex ) = 'd' ) ) then do; call getWord( @paramData, @wordVariable ); call wordToDecimalString( wordVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); end; else if ( formatString( formatStringIndex ) = 'b' ) then do; call getByte( @paramData, @byteVariable ); call byteToDecimalString( byteVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); end; else if ( formatString( formatStringIndex ) = 'l' ) then do; call getDword( @paramData, @dwordVariable ); call dwordToDecimalString( dwordVariable, @userString( outputStringIndex ) ); outputStringIndex = strlen( @userString ); end; end; /* 'u' - unsigned, look at next character for size specification */ do; /* 't' - the T/F boolean indicator */ formatStringIndex = formatStringIndex + 1; call getBoolean( @paramData, @booleanVariable ); if ( booleanVariable = FALSE ) then do; call strcat( @userString, booleanChar( 0 ) ); end; else do; call strcat( @userString, booleanChar( 1 ) ); end; outputStringIndex = strlen( @userString ); end; /* 't' - the T/F boolean indicator */ do; /* 'T' - the True/False boolean indicator */ formatStringIndex = formatStringIndex + 1; call getBoolean( @paramData, @booleanVariable ); if ( booleanVariable = FALSE ) then do; call strcat( @userString, booleanString( 0 ) ); end; else do; call strcat( @userString, booleanString( 1 ) ); end; outputStringIndex = strlen( @userString ); end; /* 'T' - the True/False boolean indicator */ do; /* - default */ /* * if we get here, the only thing that is still valid is a % * character. if not that, do nothing, just pass it along into * the output string */ /* *** WORK TO BE DONE HERE *** */ userString( outputStringIndex ) = formatString( formatStringIndex - 1 ); userString( outputStringIndex + 1 ) = formatString( formatStringIndex ); outputStringIndex = outputStringIndex + 2; formatStringIndex = formatStringIndex + 1; end; /* - default */ end; /* end switch case for percent */ end; else do; /* * just append the character to the output string */ userString( outputStringIndex ) = formatString( formatStringIndex ); outputStringIndex = outputStringIndex + 1; formatStringIndex = formatStringIndex + 1; end; /* * the output string must remain NUL terminated */ userString( outputStringIndex ) = NUL; end; /* while not at the end of the format string */ end sprintfEngine; end sprintfEngineModule;