Search code examples
cmacrosdecimalc-preprocessor

C Macro Preprocessor to include a number in string


I would like to insert arbitrary control codes x into a string but use decimal numbers rather than hex (\ux) or oct (\0x). To use a pointless but immediately understandable example, I would be happy to have a macro, say CC, so that I could form a string as:

// Decimal 10 and 13 are ASCII LF and CR resp.
char mystring[] = "This is line 1" CC(10) CC(13) "This is line 2";

Ultimately, this code would be used with other non-standard functions/behaviours on custom hardware.


Solution

  • There is no generic way to do this for decimal numbers, but if there are a limited number of codes, you can use token pasting:

    #include <stdio.h>
    
    #define CC_13  "\x0D"
    #define CC_10  "\x0A"
    
    #define CONCAT(a, b)  a ## b
    #define CC(n) CONCAT(CC_, n)
    
    int main(void) {
        // Decimal 13 and 10 are ASCII CR and LF resp.
        char mystring[] = "This is line 1" CC(13) CC(10) "This is line 2";
    
        // simple macros are supported too
        #define CR 13
        #define LF 10
    
        printf("%s" CC(CR) CC(LF), mystring);
        return 0;
    }
    

    The macro CC can be used with numbers and simple macros, as long as the corresponding CC_xxx macro is defined as a string constant. If this definition is missing, you will get a (somewhat cryptic) error message.

    If your goal is to embed control bytes, the above should be fine albeit a bit overkill as you could use the CC_xxx macros directly.

    From extra information provided in comments, you seem to want to embed binary data inside the string constants as arguments for inline commands, themselves specified as control codes: it would be cumbersome to use the above trick for this and specifying numbers outside the range 1..255 may cause problems as you will embed null bytes in these strings that might be misinterpreted.

    I suggest you pass these arguments as a string of digits to be parsed at runtime, inside the destination device. Here is an example:

    #define STR(n)  #n
    #define WHITESPACE(n)  "\x1B" STR(n) "F"
    
    char sentence[] = "A big space between here" WHITESPACE(30) "and here."
    

    You can send this string to the device and the device handler will parse an optional numeric argument between the escape byte and the F (for forward) to move forward by a number of pixels. Using escape sequences is just an example, you can use a whatever convention suits you best, but it allows for easy string construction on the server side using printf:

    #include <stdio.h>
    
    int main(void) {
        // Slide in a welcome message
        for (int i = 0; i < 100; i++) {
            char buf[80];
            // CR moves the cursor the beginning of the line
            // ESC K erases the rest of the line
            // ESC n F moves the cursor right by n pixels
            snprintf(buf, sizeof buf, "\r\033K\033%dFHello world!", 100 - i);
            send_device(buf);
            usleep(1000);
        }
        return 0;
    }