cc-preprocessor

Is there an equivalent to AWK sprintf for use in C compiler #define directives?


For context, I am trying to do some simplistic localization, as a first step into that "ocean", with limited language options. I want to be more "inclusive" than just limited to itemizing all instances of the type (three options for values of Bash environment variable LANGUAGE).

#define SPANISH 'es_MX:es'
#define FRENCH  'fr_CA:fr'
#define ENGLISH 'en_CA:en'

In AWK, I can do

MYLANG = sprintf("%2s", LANG)

I want to do something that simple, so I can do

// AWK syntax for sprintf
#define   MYLANG   {simple}

to obtain only the first two characters of the string LANG.

Is there something that I can use for that?

The full working script, all behaving as expected except for not having access to the environment variable LANGUAGE is the following:

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define SPANISH 'es'
#define FRENCH  'fr'
#define ENGLISH 'en'


#ifndef LANGUAGE
#include "CIMIfrecN_MessagesTable_fr.h"
#else

#define MYLANG LANGUAGE[0],LANGUAGE[1]        // Not valid

#if MYLANG == SPANISH
//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#elif MYLANG == FRENCH
//#include "_cimifrecn_messagestable_fr_h"
#include "CIMIfrecN_MessagesTable_fr.h"
#else
//#include "_cimifrecn_messagestable_en_h"
#include "CIMIfrecN_MessagesTable_en.h"
#endif
#endif
//==================================================================================================
//    START OF MAIN PROGRAM
//==================================================================================================

int main() {

    char yellowON[12] = "\e[93;1m";
    char yellowOFF[12] = "\e[0m";

    char redON[12] = "\e[91;1m";
    char redOFF[12] = "\e[0m";

    char blueON[12] = "\e[94;1m";
    char blueOFF[12] = "\e[0m";

    char ThisLOCALE[30];

    sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
    printf("\n\t %s\n\n", ThisLOCALE);


    printf("\n    == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);

    return 0;
}

The outputs are displayed as follows:

enter image description here

That shows that the proposed method using two single-position references to LANGUAGE does not work. :-(

<Addendum 1>

So, I tried a revised version of that script, where I do compare to the full value of the LANGUAGE variable, thinking that it demonstrated that it was accessible during the pre-processing. Unfortunately, I did not realize I had English as the default fallback case, so my conclusion was incorrect. I had received a false-positive. :-(

...(deleted)...

<Addendum 2>

I revised my program again, setting English for a specific value match, and added specific strings to identify which of the invalid conditions are encountered:

#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#define SPANISH 'es_MX:es'        // Format reported by bash environment variable LANGUAGE
#define FRENCH  'fr_CA:fr'
#define ENGLISH 'en_CA:en'
//#define SPANISH 'es'
//#define FRENCH  'fr'
//#define ENGLISH 'en'

//#define TESTOR LANGUAGE[0],LANGUAGE[1]    // Not valid
#define TESTOR LANGUAGE

//#ifndef LANG
#ifndef TESTOR
    char ScenarioSetUp[30] = "TESTOR was not defined";
//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#else

#if TESTOR == NULL
    char ScenarioSetUp[50] = "LANGUAGE was not obtained from the environment";
#elif TESTOR == SPANISH

//#include "_cimifrecn_messagestable_es_h"
#include "CIMIfrecN_MessagesTable_es.h"
#elif TESTOR == ENGLISH
//#include "_cimifrecn_messagestable_en_h"
#include "CIMIfrecN_MessagesTable_en.h"
#else
//#include "_cimifrecn_messagestable_fr_h"
#include "CIMIfrecN_MessagesTable_fr.h"
#endif

#endif

//==================================================================================================
//    START OF MAIN PROGRAM
//==================================================================================================

//int setProgramLOCALE() {
int main() {

    char yellowON[12] = "\e[93;1m";
    char yellowOFF[12] = "\e[0m";

    char redON[12] = "\e[91;1m";
    char redOFF[12] = "\e[0m";

    char blueON[12] = "\e[94;1m";
    char blueOFF[12] = "\e[0m";

    char ThisLOCALE[30];

    sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
    printf("\n\t %s\n\n", ThisLOCALE);


    printf("\n    == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);

    return 0;
}

This time, it definitely shows specifically failure to detect LANGUAGE for the assignment to TESTOR for compiler directives, as can be seen by the message reported:

Enter image description here

So I am still looking for a way to get at LANGUAGE during compiler directives. :-(

Please help.

Sample include file:

//==================================================================================================
//    FILE:    CIMIfrecN_MessagesTable_fr.h
//    Header initializing context-based messages for the program
//==================================================================================================

#ifndef _cimifrecn_messagestable_fr_h
#define _cimifrecn_messagestable_fr_h

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

    char ScenarioSetUp[91]                = "configuration du scénario";
    char EnterDesiredFrequency[91]        = "Veuillez entrer la fréquence désirée";
    char DesiredFrequency[91]             = "Fréquence désirée";
    char NumberLevelsImplied[91]          = "Nombre de niveaux impliqués";
    char NumberStrutsRequired[91]         = "Nombre de poutres nécessaires";
    char EvaluationPointRelevance[91]     = "Évaluation des point du grillage pour identifier leur pertinence";
    char Considering[91]                  = "Examinons";
    char Horizontals[91]                  = "Horizontales";
    char NoneMetCriteria[91]              = "(aucunes comblait les critères)";
    char Diagnonals[91]                   = "Diagonales";
    char VerticesMinimalSet[91]           = "Intersections identifiées pour un ensemble minimal contraignant entièrement la grille";
    char ThereAre[91]                     = "Il y a";
    char Vertices[91]                     = "Intersections";
    char VerteIndexMatrix[91]             = "Matrice des indices pour Intersections";
    char VerticesProjectedSphere[91]      = "Liste des intersections du polyhèdre projetés sur la sphère";
    char EvalStrutelevance[91]            = "Évaluation des poutres pour déterminer leur pertinence";
    char HorizontalStruts[91]             = "Poutres Horizontales";
    char DiagonalStruts[91]               = "Poutres Diagonales";
    char PreOptimizationStruts[91]        = "Groupe maximal de poutres requis à être optimisés pour l'ensemble minimum";
    char StrutsAndTheirLengthsAre[91]     = "poutres et leur longeurs sont";

#endif /* _cimifrecn_messagestable_fr_h */
//==================================================================================================

Script to generate language specific header file from comprehensive master message file:

#!/bin/sh

lang="${1}"

MESSAGE_TABLE="CIMIfrecN_MessagesTable_ALL.h"
MESSAGE_HEADER="CIMIfrecN_MessagesTable_${lang}.h"
Module="_"`echo "${MESSAGE_HEADER}" | tr '[A-Z]' '[a-z]' `
Module=`basename "${Module}" ".h" `"_h"

maxVar=`grep '^V|' "${MESSAGE_TABLE}" | cut -f2- -d\| |
    while read msg
    do
        echo "${msg}" | wc -c
    done | sort -r | head -1 `

maxStr=`grep '^'${lang}'|' "${MESSAGE_TABLE}" | cut -f2- -d\| |
    while read msg
    do
        echo "${msg}" | wc -c
    done | sort -r | head -1 `

#echo ${maxStr}

awk -v maxV="${maxVar}" -v maxS="${maxStr}" -v local="${lang}" -v file="${MESSAGE_HEADER}" -v module="${Module}" 'BEGIN{
    var="" ;
    msg=""
    printf("\t local = %s\n", local ) | "cat 1>&2" ;

    printf("//==================================================================================================\n") ;
    printf("//    FILE:    %s\n", file ) ;
    printf("//    Header initializing context-based messages for the program\n") ;
    printf("//==================================================================================================\n") ;
    printf("\n") ;

    printf("#ifndef %s\n", module ) ;    ###    Insert "header guard" to avoid double referencing by compiler
    printf("#define %s\n", module ) ;
    printf("\n") ;

    printf("//#include <stdio.h>\n") ;
    printf("//#include <stdlib.h>\n") ;
    printf("//#include <string.h>\n") ;
    printf("\n") ;
}{
    switch( $0 ){
        case /\|/ :
            #printf("\n %s\n", $1 ) ;
            gsub( "\015", "", $0 );

            split( $0, dat, "|" ) ;

            if( dat[1] == "V" ){
                var=dat[2] ;
                #printf("\n\t %s\n", var ) ;
            }else{
                #printf("\n %s\n", $1 ) ;
                if( dat[1] ~ local ){
                    msg=dat[2] ;
                    #printf("\n\t %s\n", msg ) ;
                } ;
            } ;

            if( var != "" && msg != "" ){
                #printf("\n\t\t var = %s\n", var ) ;
                #printf("\t\t msg = %s\n", msg ) ;
                offset=maxV-length(var)+3 ;
                printf("\tchar %s\133%d\135%"offset"s= %s ;\n", var, maxS, "", msg ) ;
                var=""
                msg=""
            } ;
            break ;
        default :
            break ;
    } ;
}END{
    printf("\n#endif /* %s */\n", module ) ;
    printf("//==================================================================================================\n") ;
}' "${MESSAGE_TABLE}" > "${MESSAGE_HEADER}"

gvim "${MESSAGE_HEADER}"

echo "\n\t Done.\n"
#//3456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+

Solution

  • Updated answer...

    Try to use this code...

    #include <langinfo.h>
    #include <locale.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    // Define preprocessor macros for each language
    #ifdef SPANISH
    #define MYLANG "es_MX:es"
    #include "CIMIfrecN_MessagesTable_es.h"
    #endif
    
    #ifdef FRENCH
    #define MYLANG "fr_CA:fr"
    #include "CIMIfrecN_MessagesTable_fr.h"
    #endif
    
    #ifdef ENGLISH
    #define MYLANG "en_CA:en"
    #include "CIMIfrecN_MessagesTable_en.h"
    #endif
    
    //==================================================================================================
    //  START OF MAIN PROGRAM
    //==================================================================================================
    
    int main() {
        char yellowON[12] = "\e[93;1m";
        char yellowOFF[12] = "\e[0m";
    
        char redON[12] = "\e[91;1m";
        char redOFF[12] = "\e[0m";
    
        char blueON[12] = "\e[94;1m";
        char blueOFF[12] = "\e[0m";
    
        char ThisLOCALE[30];
    
        sprintf(ThisLOCALE, "%s", setlocale(LC_ALL, ""));
        printf("\n\t %s\n\n", ThisLOCALE);
    
        printf("Selected language: %s\n", MYLANG);
    
        printf("\n  == %s%s%s ==\n", blueON, ScenarioSetUp, blueOFF);
    
        return 0;
    }
    

    Now, you should be able to compile your program with the right language preprocessor macro.

    Here are examples of how you’d do this.

    Spanish:

    gcc -DSPANISH -o my_program my_program.c
    

    French:

    gcc -DFRENCH -o my_program my_program.c
    

    English:

    gcc -DENGLISH -o my_program my_program.c
    

    The individual compilation should now include the language-specific header file and set MYLANG macro accordingly. Then you should be able to switch between languages at compile.

    I don't believe you need to make any changes to your "Script to generate language specific header file from comprehensive master message file"

    Your Include file seems to be fine... Just remember that each language-specific file should be structured the same but translated in the respective language so your program can use the correct translation.

    Assuming you've made similar changes to each of the other language-specific message files (e.g., CIMIfrecN_MessagesTable_es.h, CIMIfrecN_MessagesTable_en.h), I'd think the structure should work correctly.