Skip to content

Add HexStringToUDINT() and UDINTToHexString() - #12

Merged
sclaiborne merged 1 commit into
mainfrom
feature/hex-udint-conversions
Aug 20, 2026
Merged

Add HexStringToUDINT() and UDINTToHexString()#12
sclaiborne merged 1 commit into
mainfrom
feature/hex-udint-conversions

Conversation

@sclaiborne

Copy link
Copy Markdown
Member

Groundwork for BYTE/WORD/DWORD support in CSVFileLib (CSVFileLib#9 discussion). The parse/format primitives belong here rather than in CSVFileLib, since StringExt is already the home of atoui/uitoa/HexStringToDINT/ByteToHexString and is already a dependency of both CSVFileLib and VarTools.

Why not the existing functions

  • HexStringToDINT is strtol(s, 0, 16) returning signed long. 16#FFFFFFFF saturates to 16#7FFFFFFF — the top half of the range is unrepresentable.
  • atoui is a decimal-only loop that stops at the first non-digit. atoui("16#FF") returns 16, reported as success.
  • Both return the value directly, so a parse failure is indistinguishable from a valid 0. That's disqualifying for reading values out of a file, where bad input must be rejected rather than silently read as zero.

What's added

FUNCTION HexStringToUDINT : DINT   (* 0 on success, else STREXT_ERR_enum *)
    pHexStr : UDINT;   (* string to parse *)
    pValue  : UDINT;   (* receives the value *)

FUNCTION UDINTToHexString : DINT   (* string length, else STREXT_ERR_enum *)
    Value     : UDINT;
    pString   : UDINT;
    Size      : UDINT;   (* buffer size including terminator *)
    NumDigits : USINT;   (* zero pad to 1..8 digits, 0 = shortest *)
    Prefix    : USINT;   (* STREXT_HEXPREFIX_enum *)

Multiple prefixes accepted on parse, since projects and tools differ on notation:

Input Style
0xFF, 0XFF C
16#FF IEC 61131-3
$FF Pascal
FF bare

STREXT_HEXPREFIX_enum (NONE / 0X / IEC / DOLLAR) selects the notation on output, so the caller owns the format decision.

Details worth flagging:

  • A sign is rejected explicitly. strtoul would silently negate -0x10 into 0xFFFFFFF0.
  • Trailing junk is an error. "0xFF junk" is rejected rather than parsed up to the space — a truncated or mistyped value should not read as a plausible number. Trailing whitespace is fine.
  • Leading zeros don't overflow. 16#000000FF is 8 significant digits after the zeros are skipped, not 10.
  • The digit run is measured rather than left to strtoul, which would otherwise accept a second prefix in 0x0xFF.
  • The formatter writes nothing at all unless the whole string fits — no truncated half-value in the destination.

Error codes

STREXT_ERR_enum gains STREXT_ERR_INVALID_FORMAT (-2), STREXT_ERR_RANGE (-3), STREXT_ERR_BUFFER_TOO_SMALL (-4). Existing STREXT_ERR_INVALID_INPUT (-1) is unchanged.

Verification

  • Cross-compiles clean for Ia32 and ARM through the example project — no warnings from either new file.
  • Self test added to the example (hexTesthexTestPass / hexTestFail / hexTestFirstFail) covering every prefix, case handling, padding, whitespace, the full 32-bit range, out-of-range, malformed input, null pointers, buffer-too-small, and round trip at each prefix.
  • The algorithm was additionally checked against that same case table plus 800k randomized format→parse round trips.

Note: I could not run the self test on hardware here — this machine's AR install has no firmware module for the example's 5PC900, so no RUC package is produced locally. CI builds against the pinned AR 6.6.2.

Not addressed here: whether VarTools should adopt hex output. It currently formats DWORD as decimal deliberately, for interop with systems that don't take hex — that stays as it is.

🤖 Generated with Claude Code

HexStringToDINT() cannot represent the top half of the 32-bit range -
strtol() saturates 16#FFFFFFFF to 16#7FFFFFFF - and neither it nor
atoui() can report a parse failure, since the return value is the parsed
value and a failure is indistinguishable from a valid 0. That makes both
unusable for reading bit string types (BYTE/WORD/DWORD) out of a file,
where an invalid literal has to be rejected rather than quietly read as
zero.

HexStringToUDINT() returns a status and writes the value through a
pointer. It accepts several notations, since projects and tools differ:

    0xFF  0XFF     C style
    16#FF          IEC 61131-3 style
    $FF            Pascal style
    FF             bare

Leading and trailing white space is allowed; any other trailing
character is an error, so a truncated or mistyped literal is rejected
instead of being parsed up to the bad character. A sign is rejected
explicitly - strtoul() would otherwise silently negate it. Leading zeros
are ignored when checking width, so 16#000000FF is not an overflow.

UDINTToHexString() formats with a selectable prefix
(STREXT_HEXPREFIX_enum) and optional zero padding, and writes nothing at
all unless the whole string fits in the destination.

The example project gains a self test covering every prefix, the full
range, malformed input, null pointers, buffer overflow, and round trip
at each prefix. Set hexTest to run it; hexTestFail must come back 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sclaiborne
sclaiborne merged commit c0312d1 into main Aug 20, 2026
1 check passed
@sclaiborne
sclaiborne deleted the feature/hex-udint-conversions branch August 20, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant