1#
2# QAPI helper library
3#
4# Copyright IBM, Corp. 2011
5# Copyright (c) 2013 Red Hat Inc.
6#
7# Authors:
8#  Anthony Liguori <aliguori@us.ibm.com>
9#  Markus Armbruster <armbru@redhat.com>
10#
11# This work is licensed under the terms of the GNU GPLv2.
12# See the COPYING.LIB file in the top-level directory.
13
14from ordereddict import OrderedDict
15import sys
16
17builtin_types = [
18    'str', 'int', 'number', 'bool',
19    'int8', 'int16', 'int32', 'int64',
20    'uint8', 'uint16', 'uint32', 'uint64'
21]
22
23builtin_type_qtypes = {
24    'str':      'QTYPE_QSTRING',
25    'int':      'QTYPE_QINT',
26    'number':   'QTYPE_QFLOAT',
27    'bool':     'QTYPE_QBOOL',
28    'int8':     'QTYPE_QINT',
29    'int16':    'QTYPE_QINT',
30    'int32':    'QTYPE_QINT',
31    'int64':    'QTYPE_QINT',
32    'uint8':    'QTYPE_QINT',
33    'uint16':   'QTYPE_QINT',
34    'uint32':   'QTYPE_QINT',
35    'uint64':   'QTYPE_QINT',
36}
37
38class QAPISchemaError(Exception):
39    def __init__(self, schema, msg):
40        self.fp = schema.fp
41        self.msg = msg
42        self.line = self.col = 1
43        for ch in schema.src[0:schema.pos]:
44            if ch == '\n':
45                self.line += 1
46                self.col = 1
47            elif ch == '\t':
48                self.col = (self.col + 7) % 8 + 1
49            else:
50                self.col += 1
51
52    def __str__(self):
53        return "%s:%s:%s: %s" % (self.fp.name, self.line, self.col, self.msg)
54
55class QAPISchema:
56
57    def __init__(self, fp):
58        self.fp = fp
59        self.src = fp.read()
60        if self.src == '' or self.src[-1] != '\n':
61            self.src += '\n'
62        self.cursor = 0
63        self.exprs = []
64        self.accept()
65
66        while self.tok != None:
67            self.exprs.append(self.get_expr(False))
68
69    def accept(self):
70        while True:
71            self.tok = self.src[self.cursor]
72            self.pos = self.cursor
73            self.cursor += 1
74            self.val = None
75
76            if self.tok == '#':
77                self.cursor = self.src.find('\n', self.cursor)
78            elif self.tok in ['{', '}', ':', ',', '[', ']']:
79                return
80            elif self.tok == "'":
81                string = ''
82                esc = False
83                while True:
84                    ch = self.src[self.cursor]
85                    self.cursor += 1
86                    if ch == '\n':
87                        raise QAPISchemaError(self,
88                                              'Missing terminating "\'"')
89                    if esc:
90                        string += ch
91                        esc = False
92                    elif ch == "\\":
93                        esc = True
94                    elif ch == "'":
95                        self.val = string
96                        return
97                    else:
98                        string += ch
99            elif self.tok == '\n':
100                if self.cursor == len(self.src):
101                    self.tok = None
102                    return
103            elif not self.tok.isspace():
104                raise QAPISchemaError(self, 'Stray "%s"' % self.tok)
105
106    def get_members(self):
107        expr = OrderedDict()
108        if self.tok == '}':
109            self.accept()
110            return expr
111        if self.tok != "'":
112            raise QAPISchemaError(self, 'Expected string or "}"')
113        while True:
114            key = self.val
115            self.accept()
116            if self.tok != ':':
117                raise QAPISchemaError(self, 'Expected ":"')
118            self.accept()
119            expr[key] = self.get_expr(True)
120            if self.tok == '}':
121                self.accept()
122                return expr
123            if self.tok != ',':
124                raise QAPISchemaError(self, 'Expected "," or "}"')
125            self.accept()
126            if self.tok != "'":
127                raise QAPISchemaError(self, 'Expected string')
128
129    def get_values(self):
130        expr = []
131        if self.tok == ']':
132            self.accept()
133            return expr
134        if not self.tok in [ '{', '[', "'" ]:
135            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')
136        while True:
137            expr.append(self.get_expr(True))
138            if self.tok == ']':
139                self.accept()
140                return expr
141            if self.tok != ',':
142                raise QAPISchemaError(self, 'Expected "," or "]"')
143            self.accept()
144
145    def get_expr(self, nested):
146        if self.tok != '{' and not nested:
147            raise QAPISchemaError(self, 'Expected "{"')
148        if self.tok == '{':
149            self.accept()
150            expr = self.get_members()
151        elif self.tok == '[':
152            self.accept()
153            expr = self.get_values()
154        elif self.tok == "'":
155            expr = self.val
156            self.accept()
157        else:
158            raise QAPISchemaError(self, 'Expected "{", "[" or string')
159        return expr
160
161def parse_schema(fp):
162    try:
163        schema = QAPISchema(fp)
164    except QAPISchemaError, e:
165        print >>sys.stderr, e
166        exit(1)
167
168    exprs = []
169
170    for expr in schema.exprs:
171        if expr.has_key('enum'):
172            add_enum(expr['enum'])
173        elif expr.has_key('union'):
174            add_union(expr)
175            add_enum('%sKind' % expr['union'])
176        elif expr.has_key('type'):
177            add_struct(expr)
178        exprs.append(expr)
179
180    return exprs
181
182def parse_args(typeinfo):
183    if isinstance(typeinfo, basestring):
184        struct = find_struct(typeinfo)
185        assert struct != None
186        typeinfo = struct['data']
187
188    for member in typeinfo:
189        argname = member
190        argentry = typeinfo[member]
191        optional = False
192        structured = False
193        if member.startswith('*'):
194            argname = member[1:]
195            optional = True
196        if isinstance(argentry, OrderedDict):
197            structured = True
198        yield (argname, argentry, optional, structured)
199
200def de_camel_case(name):
201    new_name = ''
202    for ch in name:
203        if ch.isupper() and new_name:
204            new_name += '_'
205        if ch == '-':
206            new_name += '_'
207        else:
208            new_name += ch.lower()
209    return new_name
210
211def camel_case(name):
212    new_name = ''
213    first = True
214    for ch in name:
215        if ch in ['_', '-']:
216            first = True
217        elif first:
218            new_name += ch.upper()
219            first = False
220        else:
221            new_name += ch.lower()
222    return new_name
223
224def c_var(name, protect=True):
225    # ANSI X3J11/88-090, 3.1.1
226    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
227                     'default', 'do', 'double', 'else', 'enum', 'extern', 'float',
228                     'for', 'goto', 'if', 'int', 'long', 'register', 'return',
229                     'short', 'signed', 'sizeof', 'static', 'struct', 'switch',
230                     'typedef', 'union', 'unsigned', 'void', 'volatile', 'while'])
231    # ISO/IEC 9899:1999, 6.4.1
232    c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
233    # ISO/IEC 9899:2011, 6.4.1
234    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn',
235                     '_Static_assert', '_Thread_local'])
236    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
237    # excluding _.*
238    gcc_words = set(['asm', 'typeof'])
239    # C++ ISO/IEC 14882:2003 2.11
240    cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
241                     'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
242                     'namespace', 'new', 'operator', 'private', 'protected',
243                     'public', 'reinterpret_cast', 'static_cast', 'template',
244                     'this', 'throw', 'true', 'try', 'typeid', 'typename',
245                     'using', 'virtual', 'wchar_t',
246                     # alternative representations
247                     'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
248                     'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
249    # namespace pollution:
250    polluted_words = set(['unix'])
251    if protect and (name in c89_words | c99_words | c11_words | gcc_words | cpp_words | polluted_words):
252        return "q_" + name
253    return name.replace('-', '_').lstrip("*")
254
255def c_fun(name, protect=True):
256    return c_var(name, protect).replace('.', '_')
257
258def c_list_type(name):
259    return '%sList' % name
260
261def type_name(name):
262    if type(name) == list:
263        return c_list_type(name[0])
264    return name
265
266enum_types = []
267struct_types = []
268union_types = []
269
270def add_struct(definition):
271    global struct_types
272    struct_types.append(definition)
273
274def find_struct(name):
275    global struct_types
276    for struct in struct_types:
277        if struct['type'] == name:
278            return struct
279    return None
280
281def add_union(definition):
282    global union_types
283    union_types.append(definition)
284
285def find_union(name):
286    global union_types
287    for union in union_types:
288        if union['union'] == name:
289            return union
290    return None
291
292def add_enum(name):
293    global enum_types
294    enum_types.append(name)
295
296def is_enum(name):
297    global enum_types
298    return (name in enum_types)
299
300def c_type(name):
301    if name == 'str':
302        return 'char *'
303    elif name == 'int':
304        return 'int64_t'
305    elif (name == 'int8' or name == 'int16' or name == 'int32' or
306          name == 'int64' or name == 'uint8' or name == 'uint16' or
307          name == 'uint32' or name == 'uint64'):
308        return name + '_t'
309    elif name == 'size':
310        return 'uint64_t'
311    elif name == 'bool':
312        return 'bool'
313    elif name == 'number':
314        return 'double'
315    elif type(name) == list:
316        return '%s *' % c_list_type(name[0])
317    elif is_enum(name):
318        return name
319    elif name == None or len(name) == 0:
320        return 'void'
321    elif name == name.upper():
322        return '%sEvent *' % camel_case(name)
323    else:
324        return '%s *' % name
325
326def genindent(count):
327    ret = ""
328    for i in range(count):
329        ret += " "
330    return ret
331
332indent_level = 0
333
334def push_indent(indent_amount=4):
335    global indent_level
336    indent_level += indent_amount
337
338def pop_indent(indent_amount=4):
339    global indent_level
340    indent_level -= indent_amount
341
342def cgen(code, **kwds):
343    indent = genindent(indent_level)
344    lines = code.split('\n')
345    lines = map(lambda x: indent + x, lines)
346    return '\n'.join(lines) % kwds + '\n'
347
348def mcgen(code, **kwds):
349    return cgen('\n'.join(code.split('\n')[1:-1]), **kwds)
350
351def basename(filename):
352    return filename.split("/")[-1]
353
354def guardname(filename):
355    guard = basename(filename).rsplit(".", 1)[0]
356    for substr in [".", " ", "-"]:
357        guard = guard.replace(substr, "_")
358    return guard.upper() + '_H'
359
360def guardstart(name):
361    return mcgen('''
362
363#ifndef %(name)s
364#define %(name)s
365
366''',
367                 name=guardname(name))
368
369def guardend(name):
370    return mcgen('''
371
372#endif /* %(name)s */
373
374''',
375                 name=guardname(name))
376