It depends on how general your solution must be. As other answers have identified, the two example structures are extremely similar, and therefore can be managed relatively easily (though deciding how to determine the end of the character string presents some problems).
If you need a more general system, you'll probably need to look at some sort of 'structure descriptor' string, which you pass to the converter, or possibly a 'structure descriptor array'.
For example, the strings might be:
"i s16 i i i i" // typeA
"i s32 i i i i" // typeB
"u32 i64 z d d" // struct { uint32_t a; int64_t b; size_t c; double d; double e; };
int parse_any_type(void *output, const char *desc);
You then have to deal with some alignment and padding issues, but (as long as you get the descriptor strings correct) you can write a routine to handle that lot (packed or unpacked).
Using 'descriptors', you'd probably be dealing with one of the less well known macros in C, the offsetof
macro defined in <stddef.h>
. You'd create a descriptor type such as:
enum Type { CHAR, UCHAR, SCHAR, STR, USTR, SSTR, SHORT, USHORT, INT, UINT, LONG, ULONG, ... };
struct descriptor
{
enum Type m_type; // Code for the variable type
size_t m_size; // Size of type
size_t m_offset; // Offset of variable in structure
};
struct descriptor d_TypeA[] =
{
{ INT, sizeof(int), offsetof(TypeA, id) },
{ STR, 16, offsetof(TypeA, name) },
{ INT, sizeof(int), offsetof(TypeA, value1) },
{ INT, sizeof(int), offsetof(TypeA, value2) },
{ INT, sizeof(int), offsetof(TypeA, value3) },
{ INT, sizeof(int), offsetof(TypeA, value4) },
};
You can then pass the appropriate type descriptor array (and the size of that array) to the function, along with the pointer to where the data is to be stored.
Instead of using an enumeration, you might use a function pointer type which points to the correct converter.
int parse_structure(void *output, const struct descriptor *desc, size_t n_desc);
Another alternative is that you simply deal with each type with an appropriate function which calls other simpler functions to handle each piece of the structure.
int parse_TypeA(TypeA *output)
{
if (parse_int(&output->id) == 0 &&
parse_str(output->name, 16) == 0 &&
parse_int(&output->value1) == 0 &&
parse_int(&output->value2) == 0 &&
parse_int(&output->value3) == 0 &&
parse_int(&output->value4) == 0)
return 0;
...diagnose error...
return -1;
}
Your examples have not clearly identified where the data comes from, as opposed to where it is to be stored. This may not matter, but will affect the solution. Given no arguments, it might be reasonable to expect the data to be read from standard input. Alternatively, you might have a string containing the data to be parsed, possibly with a length, too; these would be arguments to the function.
Your examples have not illustrated error handling; how will the calling code know whether the conversion was successful or not.
If done correctly, the same description mechanism can be used for both the parsing and the printing mechanisms - your parse_any_type()
function looks more like a printing function.
See Also