Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions example/As6Project/Logical/Programs/Default/Main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,157 @@
#include "string.h"
#include <stdio.h>


/* Self test for HexStringToUDINT() and UDINTToHexString().
Set hexTest to run it; hexTestFail must come back 0. */

typedef struct hexCase_typ {
char* Input;
signed long ExpectStatus; /* 0, or the STREXT_ERR_enum value expected */
unsigned long ExpectValue; /* only checked when ExpectStatus is 0 */
} hexCase_typ;

static const hexCase_typ hexCases[] = {

/* Every accepted prefix */
{ "0xFF", 0, 0xFF },
{ "0XFF", 0, 0xFF },
{ "16#FF", 0, 0xFF },
{ "$FF", 0, 0xFF },
{ "FF", 0, 0xFF },

/* Case, padding and white space */
{ "0xdeadbeef", 0, 0xDEADBEEF },
{ "0xDEADBEEF", 0, 0xDEADBEEF },
{ "16#000000FF", 0, 0xFF },
{ " 0x10 ", 0, 0x10 },
{ "0x00000000", 0, 0 },
{ "0", 0, 0 },

/* Full range - the case HexStringToDINT() saturates on */
{ "0xFFFFFFFF", 0, 0xFFFFFFFF },
{ "0x80000000", 0, 0x80000000 },

/* Out of range */
{ "0x100000000", STREXT_ERR_RANGE, 0 },
{ "0xFFFFFFFFF", STREXT_ERR_RANGE, 0 },

/* Malformed */
{ "", STREXT_ERR_INVALID_FORMAT, 0 },
{ "0x", STREXT_ERR_INVALID_FORMAT, 0 },
{ "16#", STREXT_ERR_INVALID_FORMAT, 0 },
{ "0xGG", STREXT_ERR_INVALID_FORMAT, 0 },
{ "0xFF junk", STREXT_ERR_INVALID_FORMAT, 0 },
{ "-0x10", STREXT_ERR_INVALID_FORMAT, 0 },
{ "+0x10", STREXT_ERR_INVALID_FORMAT, 0 },
{ "0x0xFF", STREXT_ERR_INVALID_FORMAT, 0 },

};


static void runHexTest(void)
{
unsigned long i;
unsigned long Value;
signed long Status;
char Formatted[16];

hexTestPass= 0;
hexTestFail= 0;
strcpy((char*)hexTestFirstFail, "");


/* Parsing */

for(i=0; i<(sizeof(hexCases)/sizeof(hexCases[0])); i++){

Value= 0xA5A5A5A5;

Status= HexStringToUDINT( (unsigned long)hexCases[i].Input, (unsigned long)&Value );

if( (Status == hexCases[i].ExpectStatus)
&& ((Status != 0) || (Value == hexCases[i].ExpectValue))
&& ((Status == 0) || (Value == 0xA5A5A5A5)) /* value untouched on failure */
){

hexTestPass++;

}
else{

hexTestFail++;

if(strlen((char*)hexTestFirstFail) == 0){
strncpy((char*)hexTestFirstFail, hexCases[i].Input, 79);
}

}

}


/* Null pointers */

if( HexStringToUDINT( 0, (unsigned long)&Value ) == STREXT_ERR_INVALID_INPUT ) hexTestPass++; else hexTestFail++;
if( HexStringToUDINT( (unsigned long)"0xFF", 0 ) == STREXT_ERR_INVALID_INPUT ) hexTestPass++; else hexTestFail++;


/* Formatting - each prefix, minimal and padded */

if( (UDINTToHexString( 0xFF, (unsigned long)Formatted, sizeof(Formatted), 0, STREXT_HEXPREFIX_NONE ) == 2)
&& (strcmp(Formatted, "FF") == 0) ) hexTestPass++; else hexTestFail++;

if( (UDINTToHexString( 0xFF, (unsigned long)Formatted, sizeof(Formatted), 0, STREXT_HEXPREFIX_0X ) == 4)
&& (strcmp(Formatted, "0xFF") == 0) ) hexTestPass++; else hexTestFail++;

if( (UDINTToHexString( 0xFF, (unsigned long)Formatted, sizeof(Formatted), 8, STREXT_HEXPREFIX_IEC ) == 11)
&& (strcmp(Formatted, "16#000000FF") == 0) ) hexTestPass++; else hexTestFail++;

if( (UDINTToHexString( 0xFF, (unsigned long)Formatted, sizeof(Formatted), 4, STREXT_HEXPREFIX_DOLLAR ) == 5)
&& (strcmp(Formatted, "$00FF") == 0) ) hexTestPass++; else hexTestFail++;

if( (UDINTToHexString( 0, (unsigned long)Formatted, sizeof(Formatted), 0, STREXT_HEXPREFIX_NONE ) == 1)
&& (strcmp(Formatted, "0") == 0) ) hexTestPass++; else hexTestFail++;

if( (UDINTToHexString( 0xFFFFFFFF, (unsigned long)Formatted, sizeof(Formatted), 8, STREXT_HEXPREFIX_0X ) == 10)
&& (strcmp(Formatted, "0xFFFFFFFF") == 0) ) hexTestPass++; else hexTestFail++;


/* Buffer too small, and nothing written when it does not fit */

strcpy(Formatted, "untouched");

if( (UDINTToHexString( 0xFFFFFFFF, (unsigned long)Formatted, 5, 8, STREXT_HEXPREFIX_0X ) == STREXT_ERR_BUFFER_TOO_SMALL)
&& (strcmp(Formatted, "untouched") == 0) ) hexTestPass++; else hexTestFail++;

if( UDINTToHexString( 0xFF, 0, 16, 0, STREXT_HEXPREFIX_0X ) == STREXT_ERR_INVALID_INPUT ) hexTestPass++; else hexTestFail++;

if( UDINTToHexString( 0xFF, (unsigned long)Formatted, sizeof(Formatted), 0, 99 ) == STREXT_ERR_INVALID_INPUT ) hexTestPass++; else hexTestFail++;


/* Round trip every value the formatter can produce at each prefix */

for(i=0; i<4; i++){

if( (UDINTToHexString( 0x12AB34CD, (unsigned long)Formatted, sizeof(Formatted), 8, (unsigned char)i ) > 0)
&& (HexStringToUDINT( (unsigned long)Formatted, (unsigned long)&Value ) == 0)
&& (Value == 0x12AB34CD)
){

hexTestPass++;

}
else{

hexTestFail++;

}

}

}


void _INIT ProgramInit(void)
{
}
Expand All @@ -26,6 +177,11 @@ void _CYCLIC ProgramCyclic(void)

}

if(hexTest) {
hexTest = 0;
runHexTest();
}



}
Expand Down
4 changes: 4 additions & 0 deletions example/As6Project/Logical/Programs/Default/Variables.var
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ VAR
string : ARRAY[0..3] OF STRING[80];
buffer : ARRAY[0..3] OF STRING[80];
pString : UDINT;
hexTest : BOOL; (*Rising edge runs the HexStringToUDINT/UDINTToHexString self test*)
hexTestPass : UDINT; (*Cases that behaved as expected*)
hexTestFail : UDINT; (*Cases that did not*)
hexTestFirstFail : STRING[80]; (*Input of the first case that failed*)
END_VAR
4 changes: 3 additions & 1 deletion src/Ar/StringExt/ANSIC.lby
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?AutomationStudio FileVersion="4.9"?>
<Library Version="1.0.0" SubType="ANSIC" xmlns="http://br-automation.co.at/AS/Library">
<Library Version="1.1.0" SubType="ANSIC" xmlns="http://br-automation.co.at/AS/Library">
<Objects>
<Object Type="File" Description="Exported data types">StringExt.typ</Object>
<Object Type="File" Description="Exported constants">StringExt.var</Object>
Expand All @@ -18,6 +18,8 @@
<Object Type="File" Description="Converts output of GenerateTimestamp to DATE_AND_TIME">Timestamp_TO_DT.c</Object>
<Object Type="File" Description="Appends an Array Index to a string">appendArrayIndex.c</Object>
<Object Type="File">HexStringToDINT.c</Object>
<Object Type="File" Description="Parses a hex string into a UDINT">HexStringToUDINT.c</Object>
<Object Type="File" Description="Formats a UDINT as a hex string">UDINTToHexString.c</Object>
<Object Type="File">SplitFileName.c</Object>
<Object Type="File" Description="String copy with max length">stringlcpy.c</Object>
<Object Type="File" Description="String cat with max length">stringlcat.c</Object>
Expand Down
4 changes: 4 additions & 0 deletions src/Ar/StringExt/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
1.1.0 - Add HexStringToUDINT() and UDINTToHexString()
Parse and format hex over the full 32-bit range, with 0x, 16# and $ prefixes
supported and parse failures reported separately from the value

0.15.0 - Add dtoa, strtod and float variants
0.14.4 - Fix bug in SplitFileName()
0.14.3 - Add support for strptime in GCC6
Expand Down
158 changes: 158 additions & 0 deletions src/Ar/StringExt/HexStringToUDINT.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* File: HexStringToUDINT.c
* Copyright (c) 2026 Loupe
* https://loupe.team
*
* This file is part of StringExt, licensed under the MIT License.
*
*/

#include <bur/plctypes.h>
#ifdef __cplusplus
extern "C"
{
#endif

#include "StringExt.h"

#ifdef __cplusplus
};
#endif


#include <string.h>
#include <stdlib.h>


/********************************************************************/
/* Parse a hexadecimal string into a UDINT */
/********************************************************************/

/* Unlike HexStringToDINT(), the full 32-bit range is representable and
a parse failure is reported separately from the value, so a valid 0
cannot be confused with bad input.

Accepted prefixes (all optional, leading white space is ignored):

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

Trailing white space is allowed; any other trailing character is an
error, so a truncated or mistyped value is rejected rather than
silently parsed up to the bad character. */


signed long HexStringToUDINT(unsigned long pHexStr, unsigned long pValue)
{


/************************************************/
/* Check for invalid inputs */
/************************************************/

if( (pHexStr == 0)
|| (pValue == 0)
){

return STREXT_ERR_INVALID_INPUT;

} // Check for null pointer //


/************************************************/
/* Parse the string */
/************************************************/

const char* p;
const char* pEnd;

UDINT Digits;


p= (const char*)pHexStr;


/* Skip leading white space */

p+= strspn( p, " \t\r\n\v\f" );


/* A sign is not valid for a bit string. strtoul() would silently negate
a negative value, so reject it here. */

if( (*p == '-')
|| (*p == '+')
){

return STREXT_ERR_INVALID_FORMAT;

}


/* Strip the prefix, if there is one */

if( (p[0] == '0')
&& ((p[1] == 'x') || (p[1] == 'X'))
){

p+= 2;

}
else if( (p[0] == '1')
&& (p[1] == '6')
&& (p[2] == '#')
){

p+= 3;

}
else if( p[0] == '$' ){

p+= 1;

}


/* Count the hex digits. Note that strtoul() would accept a second "0x"
here, so the run of digits is measured rather than left to strtoul. */

Digits= strspn( p, "0123456789abcdefABCDEF" );

if( Digits == 0 ) return STREXT_ERR_INVALID_FORMAT;


/* Anything after the digits other than white space is an error */

pEnd= p + Digits;

pEnd+= strspn( pEnd, " \t\r\n\v\f" );

if( *pEnd != '\0' ) return STREXT_ERR_INVALID_FORMAT;


/* Ignore leading zeros so that a zero padded literal such as 16#000000FF
is not mistaken for an overflow */

while( (*p == '0')
&& (Digits > 1)
){

p++;
Digits--;

}


/* More than 8 significant hex digits cannot fit in a UDINT */

if( Digits > 8 ) return STREXT_ERR_RANGE;


*(UDINT*)pValue= (UDINT)strtoul( p, 0, 16 );

return 0;


} // End Fn //
17 changes: 17 additions & 0 deletions src/Ar/StringExt/StringExt.fun
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ FUNCTION HexStringToDINT : DINT (*Converts a Hex String to a DINT*) (*$GROUP=Use
END_VAR
END_FUNCTION

FUNCTION HexStringToUDINT : DINT (*Parses a hex string with an optional 0x, 16# or $ prefix. Returns 0 on success or a STREXT_ERR_enum value; the parsed value is written through pValue*) (*$GROUP=User,$CAT=User,$GROUPICON=User.png,$CATICON=User.png*)
VAR_INPUT
pHexStr : UDINT; (*Pointer to the string to parse*)
pValue : UDINT; (*Pointer to the UDINT that receives the value*)
END_VAR
END_FUNCTION

FUNCTION UDINTToHexString : DINT (*Formats a UDINT as a hex string. Returns the string length or a STREXT_ERR_enum value*) (*$GROUP=User,$CAT=User,$GROUPICON=User.png,$CATICON=User.png*)
VAR_INPUT
Value : UDINT; (*Value to format*)
pString : UDINT; (*Pointer to the destination buffer*)
Size : UDINT; (*Size of the destination buffer, including the terminator*)
NumDigits : USINT; (*Zero pad to this many digits, 1..8. 0 for the shortest representation*)
Prefix : USINT; (*Notation to use, see STREXT_HEXPREFIX_enum*)
END_VAR
END_FUNCTION

FUNCTION GenerateTimestampMS_1 : UINT (*Generate a time stamp string from a DTStructure variable*)
VAR_INPUT
pDTStructure : UDINT;
Expand Down
Loading
Loading