├── .gitignore ├── README.md ├── cpp_serde_gen ├── __init__.py ├── record.py ├── serde_registry.py ├── serdes │ ├── __init__.py │ ├── generic.py │ ├── mpack.py │ └── printf.py └── tests │ ├── __init__.py │ └── test_find_serializable_types.py ├── examples ├── Makefile ├── example-01.cpp.cog └── example-02.cpp.cog ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # Prerequisites 97 | *.d 98 | 99 | # Object files 100 | *.o 101 | *.ko 102 | *.obj 103 | *.elf 104 | 105 | # Linker output 106 | *.ilk 107 | *.map 108 | *.exp 109 | 110 | # Precompiled Headers 111 | *.gch 112 | *.pch 113 | 114 | # Libraries 115 | *.lib 116 | *.a 117 | *.la 118 | *.lo 119 | 120 | # Shared objects (inc. Windows DLLs) 121 | *.dll 122 | *.so 123 | *.so.* 124 | *.dylib 125 | 126 | # Executables 127 | *.exe 128 | *.out 129 | *.app 130 | *.i*86 131 | *.x86_64 132 | *.hex 133 | 134 | # Debug files 135 | *.dSYM/ 136 | *.su 137 | *.idb 138 | *.pdb 139 | 140 | # Kernel Module Compile Results 141 | *.mod* 142 | *.cmd 143 | modules.order 144 | Module.symvers 145 | Mkfile.old 146 | dkms.conf 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-serde-gen: 2 | > Use Python To Parse The C++ AST and Generate Serializing Functions 3 | 4 | **Table of Contents**: 5 | 6 | 7 | 8 | - [Installation](#installation) 9 | - [Usage](#usage) 10 | - [Running The Examples](#running-the-examples) 11 | - [Running The Tests](#running-the-tests) 12 | - [How To Mark A Struct For `serde`](#how-to-mark-a-struct-for-serde) 13 | - [Writing a Serializer](#writing-a-serializer) 14 | - [Resources](#resources) 15 | 16 | 17 | 18 | ## Installation 19 | 20 | This library requires: 21 | 22 | - Python 2.7 and is not tested with Python 3.0. 23 | - pip (for installing dependencies) 24 | - libclang 3.8 25 | 26 | To install: 27 | 28 | ```sh 29 | $ git clone https://github.com/cwoodall/cpp-serde-gen.git 30 | $ cd cpp-serde-gen 31 | $ sudo pip install . 32 | ``` 33 | 34 | ## Usage 35 | ### Running The Examples 36 | 37 | ```sh 38 | $ cd examples 39 | $ make 40 | $ ./example-01.o 41 | 42 | Foo: 43 | bar1: 222 44 | bar2: 3.141590 45 | bar3: [1.000000, 2.000000, 3.000000, 5.000000] 46 | Baz::MyStruct: 47 | a: 1 48 | b: [2.000000, 3.000000, 4.000000, 5.000000] 49 | ``` 50 | 51 | After running make you can look at the `.cpp` files generated by the `.cpp.cog` 52 | files. `cog` is a way of writing python inline in `C`/`C++` and then embedding 53 | the results. You can also run the examples. 54 | 55 | Description of examples: 56 | 57 | - [`example-01`][example-01]: Run the example `printf` serializer, on two structs, one of 58 | which is in a namespace. 59 | - [`example-02`][example-02]: Create and register a custom serializer called `my_new`. 60 | 61 | [example-01]: examples/example-01.cpp.cog 62 | [example-02]: examples/example-02.cpp.cog 63 | 64 | ### Running The Tests 65 | 66 | ```sh 67 | $ nosetests 68 | ``` 69 | 70 | ### How To Mark A Struct For `serde` 71 | 72 | To run the generators you mark the structs with a comment of the form: 73 | `//+serde()`, where `` is a comma seperated 74 | list of registered serializers you want to run the generation code of. 75 | 76 | So for example you can make a `struct Foo` and mark it for the `printf` 77 | serializer, which just prints the struct (from 78 | `examples/example-01.cpp.cog`): 79 | 80 | ```c++ 81 | //+serde(printf) 82 | struct Foo { 83 | uint8_t bar1; ///< 84 | float bar2; ///< 85 | std::array bar3; ///< 86 | }; 87 | ``` 88 | 89 | If we then run the following code inline using cog 90 | (see `examples/example-01.cpp.cog`): 91 | 92 | ```py 93 | from cpp_serde_gen import * 94 | 95 | tu = get_clang_TranslationUnit(cog.inFile) 96 | serializables = find_serializable_types(tu) 97 | registery = SerdeRegistry([PrintfSerdeGenerator()]) 98 | 99 | for serializable in serializables: 100 | for key in serializable.serdes: 101 | try: 102 | cog.outl(registery.generate_serialize(key, serializable)) 103 | cog.outl() 104 | except Exception as e: 105 | cog.msg("Could not serialize {}".format(serializable.name)) 106 | 107 | try: 108 | cog.outl(registery.generate_deserialize(key, serializable)) 109 | cog.outl() 110 | except: 111 | cog.msg("Could not deserialize {}".format(serializable.name)) 112 | ``` 113 | 114 | This will generate the following code. Note that the `printf` serializser 115 | requires that all of the `struct`'s field types have a `printf_serialize` 116 | function written for them, this is a flexible method since you can embed 117 | complicated types inside of one another: 118 | 119 | ```c++ 120 | bool printf_serialize(Foo const & data) { 121 | printf("Foo:"); 122 | printf("\n\tbar1: "); 123 | printf_serialize(data.bar1); 124 | printf("\n\tbar2: "); 125 | printf_serialize(data.bar2); 126 | printf("\n\tbar3: "); 127 | printf_serialize(data.bar3); 128 | printf("\n"); 129 | return true; 130 | } 131 | ``` 132 | 133 | A shorthand method of doing this is also provided, the following code will 134 | achieve the same goal: 135 | 136 | ```python 137 | from cpp_serde_gen import serdes, generate_serde_code 138 | 139 | cog.outl(generate_serde_code(cog.inFile, [PrintfSerdeGenerator()])) 140 | ``` 141 | 142 | ### Writing a Serializer 143 | 144 | Writing a new serializer is rather easy, we need to inherit from 145 | `GenericSerdeGenerator` and implement `generate_serialize(record)` and 146 | `generate_deserialize(record)`, which each take a `Record` type. 147 | 148 | So for example we can implement `MyNewSerializer` which uses the serde 149 | key `my_new`: 150 | 151 | ```py 152 | from .generic import GenericSerdeGenerator 153 | from textwrap import dedent 154 | 155 | 156 | class MyNewSerializer(GenericSerdeGenerator): 157 | 158 | def __init__(self, key="my_new"): 159 | GenericSerdeGenerator.__init__(self, key) 160 | 161 | def generate_serialize(self, record): 162 | return dedent("""\ 163 | void my_new_serializer({0} const &data) {{ return; }} 164 | """.format(record.name)) 165 | 166 | def generate_deserialize(self, record): 167 | return dedent("""\ 168 | void my_new_deserializer({0} *data) {{ return; }} 169 | """.format(record.name)) 170 | ``` 171 | 172 | Which will generate (see `examples/example-02`): 173 | 174 | ```c++ 175 | void my_new_serializer(Foo const &data) { return; } 176 | void my_new_deserializer(Foo *data) { return; } 177 | ``` 178 | 179 | ## Resources 180 | 181 | - http://eli.thegreenplace.net/2011/07/03/parsing-c-in-python-with-clang 182 | - https://nedbatchelder.com/code/cog/ 183 | - https://clang.llvm.org/doxygen/group__CINDEX.html 184 | - https://github.com/llvm-mirror/clang/blob/master/bindings/python/clang/cindex.py 185 | - http://stackoverflow.com/questions/19079070/retrieving-comments-using-python-libclang 186 | - http://blog.glehmann.net/2014/12/29/Playing-with-libclang/ 187 | - https://www.fun4jimmy.com/2013/05/01/finding-all-includes-in-a-file-using-libclang-and-python.html 188 | - http://stackoverflow.com/questions/37098725/parsing-with-libclang-unable-to-parse-certain-tokens-python-in-windows/37100397 189 | -------------------------------------------------------------------------------- /cpp_serde_gen/__init__.py: -------------------------------------------------------------------------------- 1 | from .record import * 2 | from .serde_registry import * 3 | from .serdes import * 4 | import clang.cindex as cl 5 | from ctypes.util import find_library 6 | import ccsyspath 7 | import re 8 | import cog 9 | 10 | 11 | def get_clang_TranslationUnit(path="t.cpp", in_args=[], in_str="", options=0): 12 | """ 13 | Get the TranslationalUnit for the input fule listed: 14 | 15 | Parameters :: 16 | - path: The path of the file to parse (not read if in_str is set) 17 | - in_args: additional arguments to pass to clang 18 | - in_str: input string to parse instead of the file path. 19 | - options: clang.cindex.Options 20 | 21 | Returns :: 22 | - A TranslationalUnits 23 | """ 24 | 25 | # Make sure we are parsing as std c++11 26 | args = '-x c++ --std=c++11'.split() 27 | # Add the include files for the standard library. 28 | syspath = ccsyspath.system_include_paths('clang++') 29 | incargs = [b'-I' + inc for inc in syspath] 30 | # turn args into a list of args (in_args may contain more includes) 31 | args = args + incargs + in_args 32 | 33 | # Create a clang index to parse into 34 | index = cl.Index.create() 35 | 36 | unsaved_files = None 37 | # If we are parsing from a string instead 38 | if in_str: 39 | unsaved_files = [(path, in_str)] 40 | return index.parse(path, args=args, options=options, 41 | unsaved_files=unsaved_files) 42 | 43 | 44 | def get_current_scope(cursor): 45 | """ 46 | Get the current scope of the current cursor. 47 | 48 | For example: 49 | ``` 50 | namespace A { 51 | namespace B { 52 | 53 | class C { 54 | 55 | }; 56 | 57 | } 58 | } 59 | ``` 60 | 61 | will return: ["A", "B", "C"] and can be joined to be "A::B::C" 62 | 63 | Parameters :: 64 | - cursor: A clang.cindex.Cursor to loop for declaration parents of. 65 | 66 | Returns :: 67 | - A list of names of the scopes. 68 | """ 69 | # Get the parent of the current cursor 70 | parent = cursor.lexical_parent 71 | # If the parent is a declartaion type then add it to the end of our scope 72 | # list otherwise return an empty list 73 | if (parent.kind.is_declaration()): 74 | return get_current_scope(parent) + [parent.spelling] 75 | else: 76 | return [] 77 | 78 | 79 | def find_serializable_types(tu, match_str="//\+serde\(([A-Za-z\s,_]*)\)"): 80 | """ 81 | Iterate through all tokens in the current TranslationalUnit looking for comments 82 | which match the match_str. If the comment match look for the next struct or 83 | class declaration, start extracting the scope by looking at the lexical parents 84 | of the declaration. This will let you extract the name, then extract all of the 85 | fields. 86 | 87 | Parameters :: 88 | - tu: The TranslationalUnit to search over 89 | - match_str: The comment string to match. 90 | 91 | Returns :: 92 | - A List of Records. 93 | """ 94 | match_types = [cl.CursorKind.STRUCT_DECL, cl.CursorKind.CLASS_DECL] 95 | 96 | tokens = tu.cursor.get_tokens() 97 | 98 | found = False 99 | serializables = [] 100 | serdes = [] 101 | # iterate through all tokens, looking for the match_str in a comment. If 102 | # found the name, and fields are extracted from the next struct or class 103 | # definition. After extracting these declaration the parser continues to 104 | # look for more Comment otkens matching match_str. 105 | for token in tokens: 106 | match = re.match(match_str, token.spelling) 107 | if found: 108 | cursor = cl.Cursor().from_location(tu, token.location) 109 | if cursor.kind in match_types: 110 | # Extract the name, and the scope of the cursor and join them 111 | # to for the full C++ name. 112 | name = "::".join(get_current_scope(cursor) + [cursor.spelling]) 113 | # Extract all of the fields (including access_specifiers) 114 | fields = [RecordField(field.spelling, field.type.spelling, 115 | field.access_specifier.name) for field in cursor.type.get_fields()] 116 | serializables.append(Record(name, fields, serdes)) 117 | # Start searching for more comments. 118 | found = False 119 | # Clear the list of registered serdes for this Record. 120 | serdes = [] 121 | elif (token.kind == cl.TokenKind.COMMENT) and match: 122 | serdes = [x.strip() for x in match.groups()[0].split(",")] 123 | found = True # Start looking for the struct/class declaration 124 | 125 | return serializables 126 | 127 | 128 | def generate_serde_code(in_file, serdes=[]): 129 | """ 130 | Take an input file, parse it's AST for serializable objects and then 131 | use the SerdeGenerators to create strings of C++ code containing the 132 | generators. 133 | 134 | Parameters :: 135 | - in_file: A path to a file for clang to parse. 136 | - serdes: A list of SerdeGenerators to use to generate the outputs. 137 | 138 | Returns :: 139 | - A string of all of the generated code with new line seperation. 140 | """ 141 | tu = get_clang_TranslationUnit(in_file) 142 | records = find_serializable_types(tu) 143 | registry = SerdeRegistry(serdes) 144 | 145 | out_str = "" 146 | for record in records: 147 | for serde in record.serdes: 148 | try: 149 | # print the generated code to the output file using cog. 150 | out_str += registery.generate_serialize(serde, record) + "\n" 151 | # add a newline after 152 | except Exception as e: 153 | # cog.msg is similar to a logger and gets printed to stderr. 154 | print("Could not serialize {}".format(record.name)) 155 | 156 | try: 157 | out_str += registery.generate_deserialize(serde, record) + "\n" 158 | except: 159 | print("Could not deserialize {}".format(record.name)) 160 | return out_str 161 | -------------------------------------------------------------------------------- /cpp_serde_gen/record.py: -------------------------------------------------------------------------------- 1 | import clang.cindex as cl 2 | 3 | 4 | class RecordField(object): 5 | """ 6 | """ 7 | 8 | def __init__(self, name, t="void", access="PUBLIC"): 9 | self.name = name 10 | self.type = t 11 | self.access = access 12 | 13 | def __str__(self): 14 | return "{0}, {1}, {2}".format(self.name, self.type, self.access) 15 | 16 | def __eq__(self, other): 17 | return self.name == other.name and \ 18 | self.type == other.type and \ 19 | self.access == other.access 20 | 21 | 22 | class Record(object): 23 | 24 | def __init__(self, name, fields=[], serdes=[]): 25 | self.name = name 26 | self.fields = fields 27 | self.serdes = serdes 28 | 29 | def append_field(self, field): 30 | self.fields.append(field) 31 | 32 | # A serde is the string key of a serializer/deserializer class. 33 | def append_serde(self, serde): 34 | self.serdes.append(serde) 35 | 36 | def __str__(self): 37 | return "name: {0}\nfields: {1}".format(self.name, 38 | [str(field) for field in 39 | self.fields]) 40 | -------------------------------------------------------------------------------- /cpp_serde_gen/serde_registry.py: -------------------------------------------------------------------------------- 1 | class SerdeRegistry(object): 2 | """ 3 | A class which wraps a dictionary keys of keys to SerDe generators. 4 | """ 5 | 6 | def __init__(self, serdes=[]): 7 | """ 8 | Initialize with some serdes, extract the serdes key and insert it into 9 | the dictionary. 10 | 11 | Parameters :: 12 | - serdes: A List of SerdeGenerator's (see serdes/generic.py) 13 | """ 14 | self.registry = {serde.key: serde for serde in serdes} 15 | 16 | def register(self, serde): 17 | """ 18 | Add a serde to the registry. 19 | 20 | Parameters :: 21 | - serde: A SerdeGenerator to add. 22 | """ 23 | self.registry[serde.key] = serde 24 | 25 | def get(self, key): 26 | """ 27 | Lookup the SerdeGenerator by key. 28 | 29 | Parameters :: 30 | - key: The key as a string to lookup 31 | 32 | Returns :: 33 | - The SerdeGenerator associated with the key or will throw an 34 | exception 35 | """ 36 | return self.registry[key] 37 | 38 | def generate_serialize(self, key, record): 39 | """ 40 | A convenience function for calling the SerdeGenerator's 41 | generate_serialize function directly from the registry. 42 | 43 | Parameters :: 44 | - key: The key of the SerdeGenerator to look up. 45 | - record: The record to generate the serialize code for. 46 | 47 | Returns :: 48 | - Returns the serialiation code as a string 49 | """ 50 | return self.registry[key].generate_serialize(record) 51 | 52 | def generate_deserialize(self, key, record): 53 | """ 54 | A convenience function for calling the SerdeGenerator's 55 | generate_deserialize function directly from the registry. 56 | 57 | Parameters :: 58 | - key: The key of the SerdeGenerator to look up. 59 | - record: The record to generate the deserialize code for. 60 | 61 | Returns :: 62 | - Returns the deserialize code as a string 63 | """ 64 | return self.registry[key].generate_deserialize(record) 65 | -------------------------------------------------------------------------------- /cpp_serde_gen/serdes/__init__.py: -------------------------------------------------------------------------------- 1 | from .printf import * 2 | from .mpack import * 3 | -------------------------------------------------------------------------------- /cpp_serde_gen/serdes/generic.py: -------------------------------------------------------------------------------- 1 | class GenericSerdeGenerator(object): 2 | 3 | def __init__(self, key): 4 | self.key = key 5 | 6 | def generate_serialize(self, record): 7 | pass 8 | 9 | def generate_deserialize(self, record): 10 | pass 11 | 12 | def __str__(self): 13 | return "SerdeGenerator <{0}>".format(self.key) 14 | -------------------------------------------------------------------------------- /cpp_serde_gen/serdes/mpack.py: -------------------------------------------------------------------------------- 1 | from .generic import GenericSerdeGenerator 2 | from textwrap import dedent 3 | 4 | 5 | class MpackSerdeGenerator(GenericSerdeGenerator): 6 | 7 | def __init__(self, key="mpack"): 8 | GenericSerdeGenerator.__init__(self, key) 9 | 10 | def field_comment_str(self, field): 11 | return "\"{0}\": <{1}>,".format(field.name, field.type) 12 | 13 | def generate_serialize_for_field(self, field): 14 | return """\ 15 | mpack_write_cstr(writer, "{0}"); 16 | mpack_serialize(writer, *(data.{0}));""".format(field.name) 17 | 18 | def generate_serialize(self, record): 19 | entries_str = "\n\t".join([self.generate_serialize_for_field(field) 20 | for field in record.fields]) 21 | entries_comment = "\n * ".join([self.field_comment_str(field) 22 | for field in record.fields]) 23 | 24 | return """\ 25 | /** 26 | * Serialize the {0} type into the format: 27 | * 28 | * {{ 29 | * {3} 30 | * }} 31 | * 32 | * @param writer mpack_writer_t to serialize the message into 33 | * @param data A {0} to serialize. 34 | */ 35 | static inline void mpack_serialize(mpack_writer_t *writer, 36 | {0} const &data) {{ 37 | mpack_start_map(writer, {1}); 38 | {2} 39 | mpack_finish_map(writer); 40 | }} 41 | """.format(record.name, len(record.fields), entries_str, entries_comment) 42 | 43 | def generate_deserialize_for_field(self, field): 44 | return """ 45 | mpack_node_t inode = mpack_node_map_cstr(node, "{0}"); 46 | mpack_error_t err = mpack_node_error(inode); 47 | if (err != mpack_ok) {{ 48 | return err; 49 | }} 50 | 51 | err = mpack_deserialize(inode, &(item->{0})); 52 | if (err != mpack_ok) {{ 53 | return err; 54 | }}""".format(field.name) 55 | 56 | def generate_deserialize(self, record): 57 | entries_str = "\n\t".join([self.generate_deserialize_for_field(field) 58 | for field in record.fields]) 59 | entries_comment = "\n * ".join([self.field_comment_str(field) 60 | for field in record.fields]) 61 | 62 | return """ 63 | /** 64 | * Deserializes the raw msgpack format of a {0} type into a struct: 65 | * 66 | * {{ 67 | * {3} 68 | * }} 69 | * 70 | * @param node mpack_node_t to deserialize with, points to a node on the 71 | * msgpack tree. 72 | * @param item A {0} to deserialize into. 73 | */ 74 | mpack_error_t mpack_deserialize(mpack_node_t node, 75 | {0} *item) {{ 76 | assert(item != nullptr); 77 | if (mpack_node_map_count(node) != {1}) {{ 78 | return mpack_error_invalid; 79 | }} 80 | 81 | {2} 82 | 83 | return mpack_ok; 84 | }} 85 | """.format(record.name, len(record.fields), entries_str, entries_comment) 86 | -------------------------------------------------------------------------------- /cpp_serde_gen/serdes/printf.py: -------------------------------------------------------------------------------- 1 | from .generic import GenericSerdeGenerator 2 | from textwrap import dedent 3 | 4 | 5 | class PrintfSerdeGenerator(GenericSerdeGenerator): 6 | 7 | def __init__(self, key="printf"): 8 | GenericSerdeGenerator.__init__(self, key) 9 | 10 | def generate_serialize_for_fields(self, record): 11 | fmt_str = """\tprintf("\\n\\t{0}: "); 12 | \tprintf_serialize(data.{1});""" 13 | field_strs = [fmt_str.format(field.name, 14 | field.name) for field in record.fields] 15 | return "\n".join(field_strs) 16 | 17 | def generate_serialize(self, record): 18 | return dedent("""\ 19 | bool printf_serialize({0} const & data) {{ 20 | \tprintf("{0}:"); 21 | {1} 22 | \tprintf("\\n"); 23 | \treturn true; 24 | }}""".format(record.name, self.generate_serialize_for_fields(record))) 25 | 26 | def generate_deserialize(self, record): 27 | pass 28 | -------------------------------------------------------------------------------- /cpp_serde_gen/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwoodall/cpp-serde-gen/fbcbe5bb51a99a87ee05a879daa3111576393eb0/cpp_serde_gen/tests/__init__.py -------------------------------------------------------------------------------- /cpp_serde_gen/tests/test_find_serializable_types.py: -------------------------------------------------------------------------------- 1 | from .. import * 2 | import clang.cindex as cl 3 | import unittest 4 | from ..serdes.printf import * 5 | from ..serdes.mpack import * 6 | from ..serde_registry import * 7 | # Get the matching clang library file (.so) 8 | cl.Config.set_library_file(find_library('clang-3.8')) 9 | 10 | UUT_FILE_ONE_H = """ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | typedef float float32_t; 17 | 18 | //+serde(printf, mpack) 19 | struct Foo { 20 | uint8_t bar1; ///< 21 | uint8_t bar2; ///< 22 | uint8_t bar3; ///< 23 | uint8_t bar4; ///< 24 | }; 25 | 26 | namespace outer_test { 27 | 28 | //+serde(printf) 29 | struct InNS { 30 | float32_t param; 31 | }; 32 | 33 | namespace test { 34 | 35 | //+serde(printf) 36 | struct DeepNS { 37 | float32_t param; 38 | }; 39 | 40 | //+serde(printf) 41 | class MyClass { 42 | public: 43 | //+serde(printf, mpack) 44 | struct InMyClass { 45 | int param; 46 | }; 47 | int public_param; 48 | protected: 49 | int protected_param; 50 | private: 51 | int private_param; 52 | }; 53 | 54 | } 55 | } 56 | 57 | //+serde(A, B) 58 | struct MoreComplicatedTypes { 59 | std::array my_std_array; 60 | const char * my_cstr; 61 | int my_c_array[10]; 62 | std::vector my_vec; 63 | } 64 | 65 | int main() { 66 | int a; 67 | 68 | return 0; 69 | } 70 | """ 71 | 72 | 73 | class TestFindSerializableTypes(unittest.TestCase): 74 | 75 | def setUp(self): 76 | self.tu = get_clang_TranslationUnit("temp.h", in_str=UUT_FILE_ONE_H) 77 | self.serializables = find_serializable_types(self.tu) 78 | self.registery = SerdeRegistry( 79 | [PrintfSerdeGenerator(), PrintfSerdeGenerator("A"), PrintfSerdeGenerator("B"), 80 | MpackSerdeGenerator()]) 81 | 82 | def test_find_struct_Foo(self): 83 | assert(self.serializables[0].name == "Foo") 84 | assert(self.serializables[0].fields[0] 85 | == RecordField("bar1", "uint8_t")) 86 | assert(self.serializables[0].fields[1] 87 | == RecordField("bar2", "uint8_t")) 88 | assert(self.serializables[0].fields[2] 89 | == RecordField("bar3", "uint8_t")) 90 | assert(self.serializables[0].fields[3] 91 | == RecordField("bar4", "uint8_t")) 92 | 93 | def test_find_struct_InNS(self): 94 | assert(self.serializables[1].name == "outer_test::InNS") 95 | assert(self.serializables[1].fields[0] == 96 | RecordField("param", "float32_t")) 97 | 98 | def test_find_struct_DeepNS(self): 99 | assert(self.serializables[2].name == "outer_test::test::DeepNS") 100 | assert(self.serializables[2].fields[0] == 101 | RecordField("param", "float32_t")) 102 | 103 | def test_find_struct_MyClass(self): 104 | assert(self.serializables[3].name == "outer_test::test::MyClass") 105 | assert(self.serializables[3].fields[0] == 106 | RecordField("public_param", "int", "PUBLIC")) 107 | assert(self.serializables[3].fields[1] == RecordField( 108 | "protected_param", "int", "PROTECTED")) 109 | assert(self.serializables[3].fields[2] == RecordField( 110 | "private_param", "int", "PRIVATE")) 111 | 112 | def test_find_struct_InMyClass(self): 113 | assert(self.serializables[4].name == 114 | "outer_test::test::MyClass::InMyClass") 115 | assert(self.serializables[4].serdes[0] == "printf") 116 | assert(self.serializables[4].serdes[1] == "mpack") 117 | assert(self.serializables[4].fields[0] == 118 | RecordField("param", "int", "PUBLIC")) 119 | 120 | def test_find_struct_MoreComplicatedTypes(self): 121 | assert(self.serializables[5].name == "MoreComplicatedTypes") 122 | assert(self.serializables[5].serdes[0] == "A") 123 | assert(self.serializables[5].serdes[1] == "B") 124 | assert(self.serializables[5].fields[0] == RecordField( 125 | "my_std_array", "std::array", "PUBLIC")) 126 | assert(self.serializables[5].fields[1] == RecordField( 127 | "my_cstr", "const char *", "PUBLIC")) 128 | assert(self.serializables[5].fields[2] == RecordField( 129 | "my_c_array", "int [10]", "PUBLIC")) 130 | assert(self.serializables[5].fields[3] == RecordField( 131 | "my_vec", "std::vector", "PUBLIC")) 132 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | CPP="g++" 2 | CPP_FLAGS="-std=c++11" 3 | COG="cog.py" 4 | COG_FLAGS="-d" 5 | 6 | all: example-01.o example-02.o 7 | 8 | .PRECIOUS: %.cpp 9 | 10 | %.o: %.cpp 11 | $(CPP) $(CPPFLAGS) -o $@ $< 12 | 13 | %.cpp: %.cpp.cog 14 | $(COG) $(COG_FLAGS) -o $@ $< 15 | 16 | clean: 17 | rm *.o *.cpp 18 | -------------------------------------------------------------------------------- /examples/example-01.cpp.cog: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | bool printf_serialize(float data) { 7 | printf("%f", data); 8 | return true; 9 | } 10 | 11 | bool printf_serialize(uint8_t data) { 12 | printf("%u", data); 13 | return true; 14 | } 15 | 16 | template 17 | bool printf_serialize(std::array const &data) { 18 | printf("["); 19 | 20 | const char *kSeperator = ", "; 21 | const char *sep = ""; 22 | for (const auto &elem : data) { 23 | printf(sep); 24 | printf_serialize(elem); 25 | sep = kSeperator; 26 | } 27 | 28 | printf("]"); 29 | return true; 30 | } 31 | 32 | //+serde(printf) 33 | struct Foo { 34 | uint8_t bar1; ///< 35 | float bar2; ///< 36 | std::array bar3; ///< 37 | }; 38 | 39 | namespace Baz { 40 | //+serde(printf) 41 | struct MyStruct { 42 | uint8_t a; ///< 43 | std::array b; ///< 44 | }; 45 | } 46 | 47 | /* [[[cog 48 | from cpp_serde_gen import * 49 | 50 | # Read the current file being processed by cog and have clang parse it's 51 | # AST and tokens. 52 | tu = get_clang_TranslationUnit(cog.inFile) 53 | # Search the TranslationalUnit for structs marked //+serde() and generate 54 | # the record structures for them. 55 | serializables = find_serializable_types(tu) 56 | # Register the serializers we want to use. In this case just 57 | # PrintfSerdeGenerator which has the default key of `printf` 58 | registery = SerdeRegistry([PrintfSerdeGenerator()]) 59 | 60 | # Iterate through all of the structs we want to serialize, and generate 61 | # the output and print it to the output file using cog. 62 | for serializable in serializables: 63 | for key in serializable.serdes: 64 | try: 65 | # print the generated code to the output file using cog. 66 | cog.outl(registery.generate_serialize(key, serializable)) 67 | # add a newline after 68 | cog.outl() 69 | except Exception as e: 70 | # cog.msg is similar to a logger and gets printed to stderr. 71 | cog.msg("Could not serialize {}".format(serializable.name)) 72 | 73 | try: 74 | cog.outl(registery.generate_deserialize(key, serializable)) 75 | cog.outl() 76 | except: 77 | cog.msg("Could not deserialize {}".format(serializable.name)) 78 | 79 | # The above code can be replace with: 80 | # cog.outl(generate_serde_code(cog.inFile, [PrintfSerdeGenerator()])) 81 | ]]] */ 82 | // [[[end]]] 83 | 84 | int main() { 85 | Foo foo = { 86 | 0xde, 87 | 3.14159, 88 | {1.0, 2.0, 3.0, 5.0} 89 | }; 90 | 91 | Baz::MyStruct a = {1, {2,3,4,5}}; 92 | 93 | printf_serialize(foo); 94 | printf_serialize(a); 95 | 96 | // Output: 97 | // 98 | // Foo: 99 | // bar1: 222 100 | // bar2: 3.141590 101 | // bar3: [1.000000, 2.000000, 3.000000, 5.000000] 102 | // Baz::MyStruct: 103 | // a: 1 104 | // b: [2.000000, 3.000000, 4.000000, 5.000000] 105 | 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /examples/example-02.cpp.cog: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //+serde(my_new) 4 | struct Foo { 5 | int bar; ///< 6 | float baz; ///< 7 | }; 8 | 9 | /* [[[cog 10 | from cpp_serde_gen import * 11 | from textwrap import dedent 12 | 13 | 14 | class MyNewSerializer(serdes.GenericSerdeGenerator): 15 | def __init__(self, key="my_new"): 16 | GenericSerdeGenerator.__init__(self, key) 17 | 18 | def generate_serialize(self, record): 19 | return dedent("""\ 20 | void my_new_serializer({0} const &data) {{ return; }} 21 | """.format(record.name)) 22 | 23 | def generate_deserialize(self, record): 24 | return dedent("""\ 25 | void my_new_deserializer({0} *data) {{ return; }} 26 | """.format(record.name)) 27 | 28 | cog.outl(generate_serde_code(cog.inFile, [MyNewSerializer()])) 29 | ]]] */ 30 | // [[[end]]] 31 | 32 | int main() { 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | ccsyspath==1.1.0 3 | clang==3.8 4 | cogapp==2.5.1 5 | packaging==16.8 6 | pyparsing==2.2.0 7 | six==1.10.0 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | 8 | config = { 9 | 'description': '', 10 | 'author': 'Christopher Woodall', 11 | 'url': '', 12 | 'download_url': '', 13 | 'author_email': 'chris@cwoodall.com', 14 | 'version': '0.2', 15 | 'install_requires': ['nose', 'clang', 'ccsyspath', 'cogapp'], 16 | 'packages': ['cpp_serde_gen', 'cpp_serde_gen.serdes'], 17 | 'name': 'cpp_serde_gen', 18 | 'test_suite': 'nose.collector' 19 | } 20 | 21 | setup(**config) 22 | --------------------------------------------------------------------------------