0

Normally I try to avoid the use of macros, so I actually don't know how to use them beyond the very most basic ones, but I'm trying to do some meta-manipulation so I assume macros are needed.

I have an enum listing various log entries and their respective id, e.g.

enum LogID
{
  LOG_ID_ITEM1=0,
  LOG_ID_ITEM2,
  LOG_ID_ITEM3=10,
  ...
}

which is used within my program when writing data to the log file. Note that they will not, in general, be in any order.

I do most of my log file post-processing in Matlab so I'd like to write the same variable names and values to a file for Matlab to load in. e.g., a file looking like

LOG_ID_ITEM1=0;
LOG_ID_ITEM2=1;
LOG_ID_ITEM3=10;
...

I have no idea how to go about doing this, but it seems like it shouldn't be too complicated. If it helps, I am using c++11.

edit: For clarification, I'm not looking for the macro itself to write the file. I want a way to store the enum element names and values as strings and ints somehow so I can then use a regular c++ function to write everything to file. I'm thinking the macro might then be used to build up the strings and values into vectors? Does that work? If so, how?

ryan0270
  • 1,135
  • 11
  • 33
  • 1
    As stated it sounds like you could just cut-and-paste. Why do you need to automate this? – Adam Burry Sep 14 '13 at 01:38
  • Rarely does anyone NEED a macro, but it just makes things easier. That enum changes fairly frequently as I change what data is logged so it would be rather convenient to have a ready-made Matlab file generated every time. Also, copy-paste only works if I explicitly define each enum value. – ryan0270 Sep 14 '13 at 02:01
  • 1
    thanks for the clarification. I don't think you can write a pre-processor macro that will write a file for you. The best you might be able to do is a makefile dependency that generates your .m from your .cpp using a custom processor such as a sed script or similar. – Adam Burry Sep 14 '13 at 02:17
  • 1
    Possibly related: [String to enum in C++](http://stackoverflow.com/questions/726664/string-to-enum-in-c) and [How to convert an enum type variable to a string?](http://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string). But you are likely _not_ going to find the simplicity of expression you are looking for. – DavidRR Sep 14 '13 at 02:27
  • The second link is similar but that solution requires that for each new log item I need to update two separate places: once in the enum and once in the array of strings (which I also can't index by the enum since the enum values are arbitrary). @AdamBurry - Sorry, I didn't meant to have the macro write the file. I just need it to make a structure that I can use write a file using normal c++ functions. I've edited my post to clarify. – ryan0270 Sep 14 '13 at 03:41
  • @ryan0270, read all the solutions proposed above. Some of them only require one list. I did not see one that handled setting the int values though. – Adam Burry Sep 14 '13 at 03:53

2 Answers2

1

I agree with Adam Burry that a separate script is likely best for this. Not sure which languages you're familiar with, but here's a quick Python script that'll do the job:

#!/usr/bin/python

'''Makes a .m file from an enum in a C++ source file.'''


from __future__ import print_function
import sys
import re


def parse_cmd_line():

    '''Gets a filename from the first command line argument.'''

    if len(sys.argv) != 2:
        sys.stderr.write('Usage: enummaker [cppfilename]\n')
        sys.exit(1)
    return sys.argv[1]


def make_m_file(cpp_file, m_file):

    '''Makes an .m file from enumerations in a .cpp file.'''

    in_enum = False
    enum_val = 0
    lines = cpp_file.readlines()

    for line in lines:
        if in_enum:

            # Currently processing an enumeration

            if '}' in line:

                # Encountered a closing brace, so stop
                # processing and reset value counter

                in_enum = False
                enum_val = 0
            else:

                # No closing brace, so process line

                if '=' in line:

                    # If a value is supplied, use it

                    ev_string = re.match(r'[^=]*=(\d+)', line)
                    enum_val = int(ev_string.group(1))

                # Write output line to file

                e_out = re.match(r'[^=\n,]+', line)
                m_file.write(e_out.group(0).strip() + '=' +
                        str(enum_val) + ';\n')
                enum_val += 1
        else:

            # Not currently processing an enum,
            # so check for an enum definition

            enumstart = re.match(r'enum \w+ {', line)
            if enumstart:
                in_enum = True


def main():

    '''Main function.'''

    # Get file names

    cpp_name = parse_cmd_line()
    m_name = cpp_name.replace('cpp', 'm')
    print('Converting ' + cpp_name + ' to ' + m_name + '...')

    # Open the files

    try:
        cpp_file = open(cpp_name, 'r')
    except IOError:
        print("Couldn't open " + cpp_name + ' for reading.')
        sys.exit(1)

    try:
        m_file = open(m_name, 'w')
    except IOError:
        print("Couldn't open " + m_name + ' for writing.')
        sys.exit(1)

    # Translate the cpp file

    make_m_file(cpp_file, m_file)

    # Finish

    print("Done.")

    cpp_file.close()
    m_file.close()


if __name__ == '__main__':
    main()

Running ./enummaker.py testenum.cpp on the following file of that name:

/* Random code here */

enum LogID {
    LOG_ID_ITEM1=0,
    LOG_ID_ITEM2,
    LOG_ID_ITEM3=10,
    LOG_ID_ITEM4
};

/* More random code here */

enum Stuff {
    STUFF_ONE,
    STUFF_TWO,
    STUFF_THREE=99,
    STUFF_FOUR,
    STUFF_FIVE
};

/*  Yet more random code here */

produces a file testenum.m containing the following:

LOG_ID_ITEM1=0;
LOG_ID_ITEM2=1;
LOG_ID_ITEM3=10;
LOG_ID_ITEM4=11;
STUFF_ONE=0;
STUFF_TWO=1;
STUFF_THREE=99;
STUFF_FOUR=100;
STUFF_FIVE=101;

This script assumes that the closing brace of an enum block is always on a separate line, that the first identifier is defined on the line following the opening brace, that there are no blank lines between the braces, that enum appears at the start of a line, and that there is no space following the = and the number. Easy enough to modify the script to overcome these limitations. You could have your makefile run this automatically.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • Up voted for the effort, but the question has been clarified to make this an incorrect answer. – Adam Burry Sep 14 '13 at 03:46
  • @AdamBurry: Well, he clarified the question to say that he doesn't want the macro to actually write the file, and that he "can then use a regular c++ function to write everything to file", but doesn't necessarily mean that having a separate mechanism entirely to write the file won't do equally well, but the OP can clarify if he wants. – Crowman Sep 14 '13 at 03:48
  • Adam is correct, the intention is that all this is contained within one program. On exit it would automatically generate the Matlab file. – ryan0270 Sep 14 '13 at 03:57
  • @ryan0270: I suspect you will not find a good solution, but others may answer. Using a separate script will also avoid kludging and obfuscating your code in order to achieve an entirely ancillary end, which would normally be considered a better design. – Crowman Sep 14 '13 at 04:09
0

Have you considered "going the other way"? It usually makes more sense to maintain your data definitions in a (text) file, then as part of your build process you can generate a C++ header and include it. Python and mako is a good tool for doing this.

ajmontag
  • 11
  • 1
  • 1
    Putting the enum in it's own header would simplify things a bit if you go the external processor route. – Adam Burry Sep 14 '13 at 03:48
  • Something along those lines is probably the more "correct" way of doing it, but unfortunately it would require major refactoring of what I have already. – ryan0270 Sep 14 '13 at 03:59