gen_savestruct.py 10.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#!/usr/bin/env python
#-*- encoding: utf-8 -*-
#
# gen_savestruct.py
# Extracts structures from a C header file and generates save/load code.
#
# Copyright (c) 2008 Pierre "delroth" Bourdon <root@delroth.is-a-geek.org>
# Copyright (c) 2009 Arthur Huillet
# Copyright (c) 2011 Samuel Degrande
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys, re

#==============================================================================
# User modifiable lists.
#=====

# List of the 'root' structures for which a read/write function is to be created

dump_structs = [
                 "gps", "point", "moderately_finepoint", "finepoint",
                 "tux_t", "item", "enemy", "bullet", "blast",
35
                 "obstacle", "volatile_obstacle",
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
                 "spell_active", "melee_shot", "mission", "npc",
                 "upgrade_socket", "keybind_t", "configuration_for_freedroid"
               ]

# A list of forbidden types in the structures.
# If such a type is encountered, the script will output an error message, and exit with failure.

forbidden_types = [ "char" ]

# Some types need specific read/write functions (defined in savestruct_internal.c).
# This list prevents the script to auto-generate the read/write functions
# of those types.

hardcoded_types = [ "list_head_t", "keybind_t_array" ]

# Some types are not found inside the struct.h file, but need however read/write
# functions (main use-case: arrays of structures)
# This list contains types for which read/write functions will be added to the
# generated files.

additional_types = [ "bullet_array", "blast_array", "spell_active_array", "melee_shot_array" ]

#=====
# End of user modifiable part
#==============================================================================

# The saved structures can contain array or dynarray of a given type.
# The functions reading/writing arrays/dynarrays are defined through some macros
# (define_write_xxxx_array() for example).
# While the content of a structure is scanned, we will fill a dictionary, to
# keep track of the different types used in the structures (unless the type is
# in the forbidden_list).
# Based on that dict, declarations and definitions of functions reading/writing
# arrays or dynarrays will be added to the generated savestruct.[ch] files
#
# Notes:
#   - additional_types are first added to the dict.

tracked_types = {}
for t in additional_types:
    tracked_types[t] = 1

# Parts of regexps
c_id = r'[\w\d]+' # Standard C identifier
c_type = r'((?:(?:unsigned|signed|short|long)\s+)*' + c_id + r'(?:(?:\s+)|(?:\s*\*\s*)))' # C type

# Regexp which search for a structure
find_structure_typedef_rxp = re.compile(r'typedef struct .+?'
                                r'\{'
                                r'([^\}]+)'
                                r'\}'
                                r'\s*(' + c_id + ').*?;', re.M | re.S)
find_structure_notypedef_rxp = re.compile(
                                r'^struct (' + c_id + ').'
                                r'\{'
                                r'([^\}]+)'
                                r'\};', re.M | re.S)

# Regexp which search for a field
find_members_rxp = re.compile(r'\s*' + c_type + r'\s*(' + c_id + r')(?:\s*\[(.+)\])?\s*?;.*')

# Special types replacements
special_types = {
    #32 bit integers
    'unsigned_int': 'uint32_t',
    'unsigned_long' : 'uint32_t',
    'long' : 'int32_t',
    'int' : 'int32_t',
    #16 bit
    'short_int': 'int16_t',
    'unsigned_short_int' : 'uint16_t',
    'short': 'int16_t',
    #8 bit
    'signed_char' : 'char',
    'unsigned_char' : 'uint8_t'
}

def write_files_header(output_c, output_h, output_fn):

    output_c.write('#include "' + output_fn + '.h"\n\n')

    output_h.write('''
/**
 * \\file savestruct.h
 * \\brief Lua based save/load subsystem's definitions
 */
#include "savestruct_internal.h"

/// \defgroup genrw Auto-generated read/write of C struct
/// \ingroup luasaveload
///
/// Functions used to read and write the C structures defined in struct.h.
/// These functions are auto-generated by the gen_savestruct.py python script.

''')

def main():

    if len(sys.argv) < 3:
        print("Usage: %s <input.h> <output>") % sys.argv[0]
        sys.exit(1)

    # Open files

    input_fn, output_fn = sys.argv[1:]
    input_f = open(input_fn, 'r')
    output_c = open(output_fn+'.c', 'w')
    output_h = open(output_fn+'.h', 'w')

    # Files prelude

    write_files_header(output_c, output_h, output_fn)

    # Read the whole input file and extract (content, name) pairs of data structures

    input_content = input_f.read()

    structures = find_structure_typedef_rxp.findall(input_content)
    for s in find_structure_notypedef_rxp.findall(input_content):
        structures.append((s[1], 'struct ' + s[0]))

    # Scan the structures' content and for each structure, fill a list of (type, field, size) tuples
    # defining the structure fields.
    # Also keep track of all types used by the structures, by filling the 3 track_simple, track_array
    # and track_dynarray dicts.

    data = {}

    for s in structures:
        # s is a tuple which contains (content, name) with content = the content inside the structure
        content, name = s
        if name not in dump_structs:
            continue

        data[name] = []

        # Extract each line of the structure content, avoiding comments
        lines = []
        in_comment_block = False
        for l in content.split('\n'):
            l = l.split('//')[0]
            if "/*" in l:
                l = l.split('/*')[0]
                lines.append(l)
                in_comment_block = True
            elif "*/" in l:
                in_comment_block = False
                l = l.split('*/')[1]
            if not in_comment_block: lines.append(l)

        # This list will contain all the (type, field, size) tuples
        a = []
        for l in lines:
            m = find_members_rxp.findall(l)
            if len(m) == 0: continue
            else: a.append(m[0])

        # Transform the raw tuple
        for f in a:
            type, field, size = f
            type = type.strip()

            # Replace spaces with '_', replace special type names
            type = type.replace(' ', '_')
            if type in special_types.keys(): type = special_types[type]
            if type in forbidden_types:
                print('Found a "%s" forbidden type while scanning "%s" data structure' % (type, name))
                sys.exit(1)

            # Postfix type name on arrays
            if not size: size = 0
            else: type += '_array'

            #print("got type " + str(type) + " field is " + str(field) + " size is " + str(size))
            data[name].append((type, size, field))

            # Fill the tracking dicts
            if '*' in type:
                continue
            else:
                if type in hardcoded_types: continue
                elif type in tracked_types: continue
                else: tracked_types[type] = 1

    # Writing loop

222
    for s_name in sorted(data.keys()):
223 224 225 226 227 228 229 230 231 232 233 234

        func_name = s_name.replace('struct ', '')

        header_str  = '/*! \ingroup genrw */\n'
        header_str += 'void write_%s(struct auto_string *, %s *);\n'  % (func_name, s_name)
        header_str += '/*! \ingroup genrw */\n'
        header_str += 'void read_%s(lua_State *, int, %s *);\n' % (func_name, s_name)

        output_h.write(header_str)

        write_str  = 'void write_%s(struct auto_string *strout, %s *data)\n'  % (func_name, s_name)
        write_str += '{\n'
235
        write_str += '    autostr_append(strout, "{\\n" '
236 237 238
        for (type, size, field) in data[s_name]:
            if not '*' in type:
                # Pointers are not saved (it does not make sense).
239
                write_str += '"%s = ");\n' % field
240
                write_str += '    write_%s(strout, %sdata->%s%s);\n' % (type, '' if size else '&', field, (', %s' % size) if size else '')
241 242
                write_str += '    autostr_append(strout, ",\\n" '
        write_str += '"}");\n'
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
        write_str += '}\n\n'

        output_c.write(write_str)

        read_str  = 'void read_%s(lua_State* L, int index, %s *data)\n' % (func_name, s_name)
        read_str += '{\n'
        for (type, size, field) in data[s_name]:
            if '*' in type:
                # Pointers are not saved (it does not make sense). They are initialized to NULL during loading
                if size == 0: read_str += '    data->%s = NULL;\n' % field
                else:         read_str += '    memcpy(data->%s, 0, %s * sizeof(%s));\n' % (field, size, type)
            else:
                read_str += '    if (lua_getfield_or_warn(L, index, "%s")) {\n' % field
                read_str += '        read_%s(L, -1, %sdata->%s%s);\n' % (type, '' if size else '&', field, (', %s' % size) if size else '')
                read_str += '        lua_pop(L, 1);\n'
                read_str += '    }\n'
        read_str += '}\n\n'

        output_c.write(read_str)

    # Declare and define the functions needed to read/write arrays and dynarrays (based on
    # the content of the type usage tracking dicts.

    header_str = ""
    impl_str = ""

269
    for s_name in sorted(tracked_types.keys()):
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

        if '_array' in s_name:

            s_name = s_name.replace('_array', '')
            func_name = s_name.replace('struct ', '')

            header_str += '/*! \ingroup genrw */\n'
            header_str += 'void write_%s_array(struct auto_string *, %s *, int);\n' % (func_name, s_name)
            header_str += '/*! \ingroup genrw */\n'
            header_str += 'void read_%s_array(lua_State *, int, %s *, int);\n' % (func_name, s_name)

            impl_str += 'define_write_xxx_array(%s);\n' % func_name
            impl_str += 'define_read_xxx_array(%s);\n' % func_name

        elif '_dynarray' in s_name:

            s_name = s_name.replace('_dynarray', '')
            func_name = s_name.replace('struct ', '')

            header_str += '/*! \ingroup genrw */\n'
            header_str += 'void write_%s_dynarray(struct auto_string *, %s_dynarray *);\n' % (func_name, s_name)
            header_str += '/*! \ingroup genrw */\n'
            header_str += 'void read_%s_dynarray(lua_State *, int, %s_dynarray *);\n' % (func_name, s_name)

            impl_str += 'define_write_xxx_dynarray(%s);\n' % s_name
            impl_str += 'define_read_xxx_dynarray(%s);\n' % s_name

    output_h.write(header_str)
    output_c.write(impl_str) 

if __name__ == '__main__': main()