1#!/usr/bin/env python
2
3import os
4import sys
5from optparse import OptionParser
6import traceback
7from python_modules import spice_parser
8from python_modules import ptypes
9from python_modules import codegen
10from python_modules import demarshal
11from python_modules import marshal
12import six
13
14def write_channel_enums(writer, channel, client, describe):
15    messages = list(filter(lambda m : m.channel == channel, \
16                               channel.client_messages if client else channel.server_messages))
17    if len(messages) == 0:
18        return
19    if client:
20        prefix = [ "MSGC" ]
21    else:
22        prefix = [ "MSG" ]
23    if channel.member_name:
24        prefix.append(channel.member_name.upper())
25    if not describe:
26        writer.begin_block("enum")
27    else:
28        writer.begin_block("static const value_string %s_vs[] = " % (codegen.prefix_underscore_lower(*[x.lower() for x in prefix])))
29    i = 0
30    prefix.append(None) # To be replaced with name
31    for m in messages:
32        prefix[-1] = m.name.upper()
33        enum = codegen.prefix_underscore_upper(*prefix)
34        if describe:
35            writer.writeln("{ %s, \"%s %s\" }," % (enum, "Client" if client else "Server", m.name.upper()))
36        else:
37            if m.value == i:
38                writer.writeln("%s," % enum)
39                i = i + 1
40            else:
41                writer.writeln("%s = %s," % (enum, m.value))
42                i = m.value + 1
43    if describe:
44        writer.writeln("{ 0, NULL }");
45    else:
46        if channel.member_name:
47            prefix[-1] = prefix[-2]
48            prefix[-2] = "END"
49            writer.newline()
50            writer.writeln("%s" % (codegen.prefix_underscore_upper(*prefix)))
51    writer.end_block(semicolon=True)
52    writer.newline()
53
54def write_channel_type_enum(writer, describe=False):
55    i = 0
56    if describe:
57        writer.begin_block("static const value_string channel_types_vs[] =")
58    else:
59        writer.begin_block("enum")
60    for c in proto.channels:
61        enum = codegen.prefix_underscore_upper("CHANNEL", c.name.upper())
62        if describe:
63            writer.writeln("{ %s, \"%s\" }," % (enum, c.name.upper()))
64        else:
65            if c.value == i:
66                writer.writeln("%s," % enum)
67                i = i + 1
68            else:
69                writer.writeln("%s = %s," % (enum, c.value))
70                i = c.value + 1
71    writer.newline()
72    if describe:
73        writer.writeln("{ 0, NULL }")
74    else:
75        writer.writeln("SPICE_END_CHANNEL")
76    writer.end_block(semicolon=True)
77    writer.newline()
78
79
80def write_enums(writer, describe=False):
81    writer.writeln("#ifndef _H_SPICE_ENUMS")
82    writer.writeln("#define _H_SPICE_ENUMS")
83    writer.newline()
84
85    # Define enums
86    for t in ptypes.get_named_types():
87        if isinstance(t, ptypes.EnumBaseType):
88            t.c_define(writer)
89            if describe:
90                t.c_describe(writer)
91
92    write_channel_type_enum(writer)
93    if (describe):
94        write_channel_type_enum(writer, True)
95
96    for c in ptypes.get_named_types():
97        if not isinstance(c, ptypes.ChannelType):
98            continue
99        write_channel_enums(writer, c, False, False)
100        if describe:
101            write_channel_enums(writer, c, False, describe)
102        write_channel_enums(writer, c, True, False)
103        if describe:
104            write_channel_enums(writer, c, True, describe)
105
106    writer.writeln("#endif /* _H_SPICE_ENUMS */")
107
108def write_content(dest_file, content, keep_identical_file):
109    if keep_identical_file:
110        try:
111            f = open(dest_file, 'rb')
112            old_content = f.read()
113            f.close()
114
115            if content == old_content:
116                six.print_("No changes to %s" % dest_file)
117                return
118
119        except IOError:
120            pass
121
122    f = open(dest_file, 'wb')
123    if six.PY3:
124        f.write(bytes(content, 'UTF-8'))
125    else:
126        f.write(content)
127    f.close()
128
129    six.print_("Wrote %s" % dest_file)
130
131
132parser = OptionParser(usage="usage: %prog [options] <protocol_file> <destination file>")
133parser.add_option("-e", "--generate-enums",
134                  action="store_true", dest="generate_enums", default=False,
135                  help="Generate enums")
136parser.add_option("-w", "--generate-wireshark-dissector",
137                  action="store_true", dest="generate_dissector", default=False,
138                  help="Generate Wireshark dissector definitions")
139parser.add_option("-d", "--generate-demarshallers",
140                  action="store_true", dest="generate_demarshallers", default=False,
141                  help="Generate demarshallers")
142parser.add_option("-m", "--generate-marshallers",
143                  action="store_true", dest="generate_marshallers", default=False,
144                  help="Generate message marshallers")
145parser.add_option("-P", "--private-marshallers",
146                  action="store_true", dest="private_marshallers", default=False,
147                  help="Generate private message marshallers")
148parser.add_option("-M", "--generate-struct-marshaller",
149                  action="append", dest="struct_marshallers",
150                  help="Generate struct marshallers")
151parser.add_option("-a", "--assert-on-error",
152                  action="store_true", dest="assert_on_error", default=False,
153                  help="Assert on error")
154parser.add_option("-H", "--header",
155                  action="store_true", dest="header", default=False,
156                  help="Generate header")
157parser.add_option("-p", "--print-error",
158                  action="store_true", dest="print_error", default=False,
159                  help="Print errors")
160parser.add_option("-s", "--server",
161                  action="store_true", dest="server", default=False,
162                  help="Print errors")
163parser.add_option("-c", "--client",
164                  action="store_true", dest="client", default=False,
165                  help="Print errors")
166parser.add_option("-k", "--keep-identical-file",
167                  action="store_true", dest="keep_identical_file", default=False,
168                  help="Print errors")
169parser.add_option("-i", "--include",
170                  action="append", dest="includes", metavar="FILE",
171                  help="Include FILE in generated code")
172parser.add_option("--suffix", dest="suffix",
173                  help="set public symbol suffix", default="")
174parser.add_option("--license", dest="license",
175                  help="license to use for generated file(s) (LGPL/BSD)", default="LGPL")
176parser.add_option("--generate-header",
177                  action="store_true", dest="generate_header", default=False,
178                  help="Generate also the header")
179parser.add_option("--generated-declaration-file", dest="generated_declaration_file", metavar="FILE",
180                  help="Name of the file to generate declarations")
181
182(options, args) = parser.parse_args()
183
184if len(args) == 0:
185    parser.error("No protocol file specified")
186
187if len(args) == 1:
188    parser.error("No destination file specified")
189
190proto_file = args[0]
191dest_file = args[1]
192proto = spice_parser.parse(proto_file)
193
194if proto == None:
195    exit(1)
196
197codegen.set_prefix(proto.name)
198writer = codegen.CodeWriter()
199writer.header = codegen.CodeWriter()
200if options.generate_header:
201    filename = os.path.splitext(dest_file)[0] + '.h'
202    writer.header.set_option("dest_file", filename)
203else:
204    writer.header.set_option("dest_file", dest_file)
205writer.set_option("source", os.path.basename(proto_file))
206
207if options.license == "LGPL":
208    license = """/*
209  Copyright (C) 2013 Red Hat, Inc.
210
211  This library is free software; you can redistribute it and/or
212  modify it under the terms of the GNU Lesser General Public
213  License as published by the Free Software Foundation; either
214  version 2.1 of the License, or (at your option) any later version.
215
216  This library is distributed in the hope that it will be useful,
217  but WITHOUT ANY WARRANTY; without even the implied warranty of
218  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
219  Lesser General Public License for more details.
220
221  You should have received a copy of the GNU Lesser General Public
222  License along with this library; if not, see <http://www.gnu.org/licenses/>.
223*/
224
225"""
226elif options.license == "BSD":
227    license = """/*
228   Copyright (C) 2013 Red Hat, Inc.
229
230   Redistribution and use in source and binary forms, with or without
231   modification, are permitted provided that the following conditions are
232   met:
233
234       * Redistributions of source code must retain the above copyright
235         notice, this list of conditions and the following disclaimer.
236       * Redistributions in binary form must reproduce the above copyright
237         notice, this list of conditions and the following disclaimer in
238         the documentation and/or other materials provided with the
239         distribution.
240       * Neither the name of the copyright holder nor the names of its
241         contributors may be used to endorse or promote products derived
242         from this software without specific prior written permission.
243
244   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
245   IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
246   TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
247   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
248   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
249   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
250   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
251   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
252   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
253   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
254   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
255*/
256
257"""
258else:
259    print >> sys.stderr, "Invalid license specified: %s" % options.license
260    sys.exit(1)
261
262all_structures = {}
263def generate_declaration(t, writer_top):
264    writer = codegen.CodeWriter()
265    try:
266        c_type = t.c_type()
267        t.generate_c_declaration(writer)
268        value = writer.getvalue().strip()
269        if not value:
270            return
271        if c_type in all_structures:
272            assert all_structures[c_type] == value, """Structure %s redefinition
273previous:
274%s
275---
276current:
277%s
278---""" % (c_type, all_structures[c_type], value)
279        else:
280            all_structures[c_type] = value
281            t.generate_c_declaration(writer_top)
282    except:
283        print >> sys.stderr, 'type %s' % t
284        print >> sys.stderr, writer.getvalue()
285        traceback.print_exc(sys.stderr)
286
287def generate_declarations():
288    writer = codegen.CodeWriter()
289    writer.public_suffix = options.suffix
290    writer.write(license)
291
292    # all types
293    for t in ptypes.get_named_types():
294        if isinstance(t, ptypes.StructType):
295            generate_declaration(t, writer)
296        if isinstance(t, ptypes.ChannelType):
297            for m in t.client_messages + t.server_messages:
298                generate_declaration(m.message_type, writer)
299
300    content = writer.getvalue()
301    write_content(options.generated_declaration_file, content,
302                  options.keep_identical_file)
303
304if options.generated_declaration_file:
305    generate_declarations()
306
307writer.public_suffix = options.suffix
308
309writer.writeln("/* this is a file autogenerated by spice_codegen.py */")
310writer.write(license)
311writer.header.writeln("/* this is a file autogenerated by spice_codegen.py */")
312writer.header.write(license)
313if not options.generate_enums:
314    writer.writeln("#ifdef HAVE_CONFIG_H")
315    writer.writeln("#include <config.h>")
316    writer.writeln("#endif")
317
318if options.assert_on_error:
319    writer.set_option("assert_on_error")
320
321if options.print_error:
322    writer.set_option("print_error")
323
324if options.includes:
325    for i in options.includes:
326        writer.header.writeln('#include "%s"' % i)
327        writer.writeln('#include "%s"' % i)
328
329if options.generate_enums or options.generate_dissector:
330    write_enums(writer, options.generate_dissector)
331
332if options.generate_demarshallers:
333    if not options.server and not options.client:
334        print >> sys.stderr, "Must specify client and/or server"
335        sys.exit(1)
336    demarshal.write_includes(writer)
337
338    if options.server:
339        demarshal.write_protocol_parser(writer, proto, False)
340    if options.client:
341        demarshal.write_protocol_parser(writer, proto, True)
342
343if options.generate_marshallers or (options.struct_marshallers and len(options.struct_marshallers) > 0):
344    marshal.write_includes(writer)
345
346if options.generate_marshallers:
347    if not options.server and not options.client:
348        print >> sys.stderr, "Must specify client and/or server"
349        sys.exit(1)
350    if options.server:
351        marshal.write_protocol_marshaller(writer, proto, False, options.private_marshallers)
352    if options.client:
353        marshal.write_protocol_marshaller(writer, proto, True, options.private_marshallers)
354
355if options.struct_marshallers:
356    for structname in options.struct_marshallers:
357        t = ptypes.lookup_type(structname)
358        marshal.write_marshal_ptr_function(writer, t, False)
359
360if options.generate_marshallers or (options.struct_marshallers and len(options.struct_marshallers) > 0):
361    marshal.write_trailer(writer)
362
363if options.header:
364    content = writer.header.getvalue()
365else:
366    content = writer.getvalue()
367write_content(dest_file, content, options.keep_identical_file)
368if options.generate_header:
369    content = writer.header.getvalue()
370    filename = writer.header.options["dest_file"]
371    write_content(filename, content, options.keep_identical_file)
372sys.exit(0)
373