Definining a C macro that expands to a variable number of elements -
i'm writing usb report descriptors, sequence of bytes: tag byte (in lower bits tell how many data bytes follow) followed 0, 1, 2 or 4 data bytes. e.g. define logical ranges of input:
uint8_t report_descriptor[] = { ... 0x15, 0x00, // logical minimum (0) 0x26, 0xff, 0x03, // logical maximum (1023) ... }; since 0 fits 1 byte, use tag type 0x15 (logical minimum 1 data byte). 1023 requires 2 bytes, tag type 0x26 (logical maximum 2 data bytes).
i had hoped define macros make more readable (and avoid having comment every line):
uint8_t report_descriptor[] = { ... logical_minimum(0), logical_maximum(1023), ... }; however, i've hit snag: macro needs expand different number of elements depending on value. don't see easy way achieve this. i've tried tricks value > 255 ? (value & 0xff, value >> 8) : value, gets expanded 1 byte.
i think spec allows use 4-byte tags, wasteful, i'd rather not that.
is i'm after possible preprocessor?
i don't think c preprocessor powerful enough in clean way. if willing resort m4 macro processor, can done easily. m4 should readily available on vast majority of gnu/linux systems , portable implementations should available platforms.
let's define m4 macros in separate file , name macros.m4.
define(`extract_byte', `(($1 >> (8 * $2)) & 0xff)') dnl don't want define these m4 macros c preprocessor dnl macros in header files. define(`tag_1_bytes', `0x15') define(`tag_2_bytes', `0x26') define(`tag_3_bytes', `0x37') define(`tag_4_bytes', `0x48') define(`expand_1_bytes', `tag_1_bytes, extract_byte($1, 0)') define(`expand_2_bytes', `tag_2_bytes, extract_byte($1, 1), extract_byte($1, 0)') define(`expand_3_bytes', `tag_3_bytes, extract_byte($1, 2), extract_byte($1, 1), extract_byte($1, 0)') define(`expand_4_bytes', `tag_4_bytes, extract_byte($1, 3), extract_byte($1, 2), extract_byte($1, 1), extract_byte($1, 0)') define(`encode', `ifelse(eval($1 < 256), `1', `expand_1_bytes($1)', `ifelse(eval($1 < 65536), `1', `expand_2_bytes($1)', `ifelse(eval($1 < 16777216), `1', `expand_3_bytes($1)', `expand_4_bytes($1)')')')') now, writing c files straight forward. put following code in file test.c.m4:
include(`macros.m4') `static unint8_t report_descriptor[] = {' encode(50), encode(5000), encode(500000), encode(50000000), `};' in makefile, add following rule
test.c: test.c.m4 macros.m4 ${m4} $< > $@ where m4 set m4 processor (usually m4).
if m4 run on test.c.m4, – omitting excess white space – produce following test.c file:
static unint8_t report_descriptor[] = { 0x15, ((50 >> (8 * 0)) & 0xff), 0x26, ((5000 >> (8 * 1)) & 0xff), ((5000 >> (8 * 0)) & 0xff), 0x37, ((500000 >> (8 * 2)) & 0xff), ((500000 >> (8 * 1)) & 0xff), ((500000 >> (8 * 0)) & 0xff), 0x48, ((50000000 >> (8 * 3)) & 0xff), ((50000000 >> (8 * 2)) & 0xff), ((50000000 >> (8 * 1)) & 0xff), ((50000000 >> (8 * 0)) & 0xff), }; you'll find more convenient keep test.c.m4 file minimal possible , #include in ordinary c file.
if don't know m4, can learn basics rather quickly. if using gnu autoconf, might find convenient use m4sugar m4 macro library instead of plain m4 i've used above.
Comments
Post a Comment