]> git.ipfire.org Git - thirdparty/qemu.git/blame - scripts/qapi/common.py
qapi: Simplify check_keys()
[thirdparty/qemu.git] / scripts / qapi / common.py
CommitLineData
0f923be2
MR
1#
2# QAPI helper library
3#
4# Copyright IBM, Corp. 2011
47a6ea9a 5# Copyright (c) 2013-2018 Red Hat Inc.
0f923be2
MR
6#
7# Authors:
8# Anthony Liguori <aliguori@us.ibm.com>
c7a3f252 9# Markus Armbruster <armbru@redhat.com>
0f923be2 10#
678e48a2
MA
11# This work is licensed under the terms of the GNU GPL, version 2.
12# See the COPYING file in the top-level directory.
0f923be2 13
ef9d9108 14from __future__ import print_function
ded9fc28 15from contextlib import contextmanager
12f8e1b9 16import errno
33aaad52 17import os
c2613949 18import re
47299262 19import string
de685ae5 20import sys
c7883412 21from collections import OrderedDict
0f923be2 22
b52c4b9c 23builtin_types = {
4d2d5c41 24 'null': 'QTYPE_QNULL',
69dd62df 25 'str': 'QTYPE_QSTRING',
01b2ffce
MAL
26 'int': 'QTYPE_QNUM',
27 'number': 'QTYPE_QNUM',
69dd62df 28 'bool': 'QTYPE_QBOOL',
01b2ffce
MAL
29 'int8': 'QTYPE_QNUM',
30 'int16': 'QTYPE_QNUM',
31 'int32': 'QTYPE_QNUM',
32 'int64': 'QTYPE_QNUM',
33 'uint8': 'QTYPE_QNUM',
34 'uint16': 'QTYPE_QNUM',
35 'uint32': 'QTYPE_QNUM',
36 'uint64': 'QTYPE_QNUM',
37 'size': 'QTYPE_QNUM',
1310a3d3 38 'any': None, # any QType possible, actually
7264f5c5 39 'QType': 'QTYPE_QSTRING',
69dd62df
KW
40}
41
bc52d03f
MA
42# Are documentation comments required?
43doc_required = False
44
10d4d997 45# Whitelist of commands allowed to return a non-dictionary
1554a8fa 46returns_whitelist = []
10d4d997 47
893e1f2c 48# Whitelist of entities allowed to violate case conventions
2cfbae3c 49name_case_whitelist = []
893e1f2c 50
5f018446 51enum_types = {}
ed285bf8 52struct_types = {}
768562de 53union_types = {}
4dc2e690
EB
54all_names = {}
55
00e4b285
MA
56#
57# Parsing the schema into expressions
58#
59
437db254 60
a719a27c 61def error_path(parent):
ef801a9b 62 res = ''
a719a27c 63 while parent:
ef801a9b 64 res = ('In file included from %s:%d:\n' % (parent['file'],
a719a27c
LV
65 parent['line'])) + res
66 parent = parent['parent']
67 return res
68
437db254 69
4148c298
MAL
70class QAPIError(Exception):
71 def __init__(self, fname, line, col, incl_info, msg):
59b00542 72 Exception.__init__(self)
4148c298
MAL
73 self.fname = fname
74 self.line = line
75 self.col = col
76 self.info = incl_info
2caba36c 77 self.msg = msg
2caba36c
MA
78
79 def __str__(self):
ef801a9b 80 loc = '%s:%d' % (self.fname, self.line)
4148c298 81 if self.col is not None:
ef801a9b
MA
82 loc += ':%s' % self.col
83 return error_path(self.info) + '%s: %s' % (loc, self.msg)
2caba36c 84
437db254 85
4148c298
MAL
86class QAPIParseError(QAPIError):
87 def __init__(self, parser, msg):
88 col = 1
89 for ch in parser.src[parser.line_pos:parser.pos]:
90 if ch == '\t':
91 col = (col + 7) % 8 + 1
92 else:
93 col += 1
94 QAPIError.__init__(self, parser.fname, parser.line, col,
95 parser.incl_info, msg)
b86b05ed 96
4148c298
MAL
97
98class QAPISemError(QAPIError):
99 def __init__(self, info, msg):
100 QAPIError.__init__(self, info['file'], info['line'], None,
101 info['parent'], msg)
b86b05ed 102
437db254 103
3313b612 104class QAPIDoc(object):
157dd363 105 """
8d40738d 106 A documentation comment block, either definition or free-form
157dd363 107
8d40738d 108 Definition documentation blocks consist of
157dd363 109
8d40738d 110 * a body section: one line naming the definition, followed by an
157dd363
MA
111 overview (any number of lines)
112
113 * argument sections: a description of each argument (for commands
114 and events) or member (for structs, unions and alternates)
115
116 * features sections: a description of each feature flag
117
118 * additional (non-argument) sections, possibly tagged
119
120 Free-form documentation blocks consist only of a body section.
121 """
122
3313b612
MAL
123 class Section(object):
124 def __init__(self, name=None):
125 # optional section name (argument/member or section name)
126 self.name = name
127 # the list of lines for this section
09331fce 128 self.text = ''
3313b612
MAL
129
130 def append(self, line):
09331fce 131 self.text += line.rstrip() + '\n'
3313b612
MAL
132
133 class ArgSection(Section):
069fb5b2
MA
134 def __init__(self, name):
135 QAPIDoc.Section.__init__(self, name)
136 self.member = None
137
138 def connect(self, member):
139 self.member = member
3313b612
MAL
140
141 def __init__(self, parser, info):
8cbf1a53 142 # self._parser is used to report errors with QAPIParseError. The
3313b612
MAL
143 # resulting error position depends on the state of the parser.
144 # It happens to be the beginning of the comment. More or less
145 # servicable, but action at a distance.
8cbf1a53 146 self._parser = parser
3313b612
MAL
147 self.info = info
148 self.symbol = None
149 self.body = QAPIDoc.Section()
150 # dict mapping parameter name to ArgSection
151 self.args = OrderedDict()
f3ed93d5 152 self.features = OrderedDict()
3313b612
MAL
153 # a list of Section
154 self.sections = []
155 # the current section
8cbf1a53 156 self._section = self.body
157dd363 157 self._append_line = self._append_body_line
3313b612
MAL
158
159 def has_section(self, name):
160 """Return True if we have a section with this name."""
161 for i in self.sections:
162 if i.name == name:
163 return True
164 return False
165
166 def append(self, line):
03bf06bd
KW
167 """
168 Parse a comment line and add it to the documentation.
169
170 The way that the line is dealt with depends on which part of
171 the documentation we're parsing right now:
157dd363
MA
172 * The body section: ._append_line is ._append_body_line
173 * An argument section: ._append_line is ._append_args_line
174 * A features section: ._append_line is ._append_features_line
175 * An additional section: ._append_line is ._append_various_line
03bf06bd 176 """
3313b612
MAL
177 line = line[1:]
178 if not line:
179 self._append_freeform(line)
180 return
181
182 if line[0] != ' ':
8cbf1a53 183 raise QAPIParseError(self._parser, "Missing space after #")
3313b612 184 line = line[1:]
157dd363 185 self._append_line(line)
03bf06bd
KW
186
187 def end_comment(self):
188 self._end_section()
189
190 @staticmethod
191 def _is_section_tag(name):
192 return name in ('Returns:', 'Since:',
193 # those are often singular or plural
194 'Note:', 'Notes:',
195 'Example:', 'Examples:',
196 'TODO:')
197
198 def _append_body_line(self, line):
157dd363
MA
199 """
200 Process a line of documentation text in the body section.
201
202 If this a symbol line and it is the section's first line, this
8d40738d 203 is a definition documentation block for that symbol.
157dd363 204
8d40738d 205 If it's a definition documentation block, another symbol line
157dd363
MA
206 begins the argument section for the argument named by it, and
207 a section tag begins an additional section. Start that
208 section and append the line to it.
209
210 Else, append the line to the current section.
211 """
03bf06bd 212 name = line.split(' ', 1)[0]
3313b612
MAL
213 # FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
214 # recognized, and get silently treated as ordinary text
03bf06bd 215 if not self.symbol and not self.body.text and line.startswith('@'):
ef801a9b 216 if not line.endswith(':'):
9f5e6b08 217 raise QAPIParseError(self._parser, "Line should end with ':'")
3313b612
MAL
218 self.symbol = line[1:-1]
219 # FIXME invalid names other than the empty string aren't flagged
220 if not self.symbol:
8cbf1a53 221 raise QAPIParseError(self._parser, "Invalid name")
03bf06bd 222 elif self.symbol:
8d40738d 223 # This is a definition documentation block
03bf06bd 224 if name.startswith('@') and name.endswith(':'):
157dd363 225 self._append_line = self._append_args_line
03bf06bd 226 self._append_args_line(line)
f3ed93d5 227 elif line == 'Features:':
157dd363 228 self._append_line = self._append_features_line
03bf06bd 229 elif self._is_section_tag(name):
157dd363 230 self._append_line = self._append_various_line
03bf06bd
KW
231 self._append_various_line(line)
232 else:
233 self._append_freeform(line.strip())
3313b612 234 else:
157dd363 235 # This is a free-form documentation block
03bf06bd 236 self._append_freeform(line.strip())
3313b612 237
03bf06bd 238 def _append_args_line(self, line):
157dd363
MA
239 """
240 Process a line of documentation text in an argument section.
241
242 A symbol line begins the next argument section, a section tag
243 section or a non-indented line after a blank line begins an
244 additional section. Start that section and append the line to
245 it.
246
247 Else, append the line to the current section.
248
249 """
3313b612
MAL
250 name = line.split(' ', 1)[0]
251
ef801a9b 252 if name.startswith('@') and name.endswith(':'):
3313b612
MAL
253 line = line[len(name)+1:]
254 self._start_args_section(name[1:-1])
03bf06bd 255 elif self._is_section_tag(name):
157dd363 256 self._append_line = self._append_various_line
03bf06bd
KW
257 self._append_various_line(line)
258 return
f3ed93d5
KW
259 elif (self._section.text.endswith('\n\n')
260 and line and not line[0].isspace()):
261 if line == 'Features:':
157dd363 262 self._append_line = self._append_features_line
f3ed93d5
KW
263 else:
264 self._start_section()
157dd363 265 self._append_line = self._append_various_line
f3ed93d5
KW
266 self._append_various_line(line)
267 return
268
269 self._append_freeform(line.strip())
270
271 def _append_features_line(self, line):
272 name = line.split(' ', 1)[0]
273
274 if name.startswith('@') and name.endswith(':'):
275 line = line[len(name)+1:]
276 self._start_features_section(name[1:-1])
277 elif self._is_section_tag(name):
157dd363 278 self._append_line = self._append_various_line
f3ed93d5
KW
279 self._append_various_line(line)
280 return
03bf06bd
KW
281 elif (self._section.text.endswith('\n\n')
282 and line and not line[0].isspace()):
283 self._start_section()
157dd363 284 self._append_line = self._append_various_line
03bf06bd
KW
285 self._append_various_line(line)
286 return
287
288 self._append_freeform(line.strip())
289
290 def _append_various_line(self, line):
157dd363
MA
291 """
292 Process a line of documentation text in an additional section.
293
294 A symbol line is an error.
295
296 A section tag begins an additional section. Start that
297 section and append the line to it.
298
299 Else, append the line to the current section.
300 """
03bf06bd
KW
301 name = line.split(' ', 1)[0]
302
303 if name.startswith('@') and name.endswith(':'):
304 raise QAPIParseError(self._parser,
305 "'%s' can't follow '%s' section"
306 % (name, self.sections[0].name))
307 elif self._is_section_tag(name):
3313b612
MAL
308 line = line[len(name)+1:]
309 self._start_section(name[:-1])
310
03bf06bd
KW
311 if (not self._section.name or
312 not self._section.name.startswith('Example')):
313 line = line.strip()
314
3313b612
MAL
315 self._append_freeform(line)
316
f3ed93d5 317 def _start_symbol_section(self, symbols_dict, name):
3313b612
MAL
318 # FIXME invalid names other than the empty string aren't flagged
319 if not name:
8cbf1a53 320 raise QAPIParseError(self._parser, "Invalid parameter name")
f3ed93d5 321 if name in symbols_dict:
8cbf1a53 322 raise QAPIParseError(self._parser,
3313b612 323 "'%s' parameter name duplicated" % name)
03bf06bd 324 assert not self.sections
4ea7148e 325 self._end_section()
8cbf1a53 326 self._section = QAPIDoc.ArgSection(name)
f3ed93d5
KW
327 symbols_dict[name] = self._section
328
329 def _start_args_section(self, name):
330 self._start_symbol_section(self.args, name)
331
332 def _start_features_section(self, name):
333 self._start_symbol_section(self.features, name)
3313b612 334
fc3f0df1 335 def _start_section(self, name=None):
ef801a9b 336 if name in ('Returns', 'Since') and self.has_section(name):
8cbf1a53 337 raise QAPIParseError(self._parser,
3313b612 338 "Duplicated '%s' section" % name)
4ea7148e 339 self._end_section()
8cbf1a53
MA
340 self._section = QAPIDoc.Section(name)
341 self.sections.append(self._section)
3313b612 342
4ea7148e 343 def _end_section(self):
8cbf1a53
MA
344 if self._section:
345 text = self._section.text = self._section.text.strip()
346 if self._section.name and (not text or text.isspace()):
347 raise QAPIParseError(self._parser, "Empty doc section '%s'"
348 % self._section.name)
349 self._section = None
4ea7148e 350
3313b612 351 def _append_freeform(self, line):
2d433236
MA
352 match = re.match(r'(@\S+:)', line)
353 if match:
8cbf1a53 354 raise QAPIParseError(self._parser,
2d433236
MA
355 "'%s' not allowed in free-form documentation"
356 % match.group(1))
8cbf1a53 357 self._section.append(line)
3313b612 358
069fb5b2
MA
359 def connect_member(self, member):
360 if member.name not in self.args:
361 # Undocumented TODO outlaw
860e8778
MA
362 self.args[member.name] = QAPIDoc.ArgSection(member.name)
363 self.args[member.name].connect(member)
069fb5b2 364
a9f396b0
MA
365 def check_expr(self, expr):
366 if self.has_section('Returns') and 'command' not in expr:
367 raise QAPISemError(self.info,
368 "'Returns:' is only valid for commands")
369
816a57cd 370 def check(self):
2f848044 371 bogus = [name for name, section in self.args.items()
816a57cd
MA
372 if not section.member]
373 if bogus:
374 raise QAPISemError(
375 self.info,
376 "The following documented members are not in "
377 "the declaration: %s" % ", ".join(bogus))
378
3313b612 379
a4bcb208 380class QAPISchemaParser(object):
c7a3f252 381
437db254 382 def __init__(self, fp, previously_included=[], incl_info=None):
2281d00c 383 self.fname = fp.name
af97502c 384 previously_included.append(os.path.abspath(fp.name))
54414047 385 self.incl_info = incl_info
c7a3f252
MA
386 self.src = fp.read()
387 if self.src == '' or self.src[-1] != '\n':
388 self.src += '\n'
389 self.cursor = 0
515b943a
WX
390 self.line = 1
391 self.line_pos = 0
c7a3f252 392 self.exprs = []
3313b612 393 self.docs = []
c7a3f252 394 self.accept()
64d6033b 395 cur_doc = None
c7a3f252 396
437db254 397 while self.tok is not None:
2281d00c 398 info = {'file': self.fname, 'line': self.line,
4148c298 399 'parent': self.incl_info}
3313b612 400 if self.tok == '#':
64d6033b
MA
401 self.reject_expr_doc(cur_doc)
402 cur_doc = self.get_doc(info)
403 self.docs.append(cur_doc)
3313b612
MAL
404 continue
405
a719a27c 406 expr = self.get_expr(False)
e04dea88 407 if 'include' in expr:
64d6033b 408 self.reject_expr_doc(cur_doc)
a719a27c 409 if len(expr) != 1:
4148c298 410 raise QAPISemError(info, "Invalid 'include' directive")
ef801a9b 411 include = expr['include']
a719a27c 412 if not isinstance(include, str):
4148c298
MAL
413 raise QAPISemError(info,
414 "Value of 'include' must be a string")
97f02494
MA
415 incl_fname = os.path.join(os.path.dirname(self.fname),
416 include)
417 self.exprs.append({'expr': {'include': incl_fname},
418 'info': info})
419 exprs_include = self._include(include, info, incl_fname,
42570530
MA
420 previously_included)
421 if exprs_include:
422 self.exprs.extend(exprs_include.exprs)
423 self.docs.extend(exprs_include.docs)
bc52d03f 424 elif "pragma" in expr:
64d6033b 425 self.reject_expr_doc(cur_doc)
bc52d03f
MA
426 if len(expr) != 1:
427 raise QAPISemError(info, "Invalid 'pragma' directive")
428 pragma = expr['pragma']
429 if not isinstance(pragma, dict):
430 raise QAPISemError(
8d40738d 431 info, "Value of 'pragma' must be an object")
2f848044 432 for name, value in pragma.items():
bc52d03f 433 self._pragma(name, value, info)
a719a27c
LV
434 else:
435 expr_elem = {'expr': expr,
4148c298 436 'info': info}
64d6033b
MA
437 if cur_doc:
438 if not cur_doc.symbol:
e7823a2a 439 raise QAPISemError(
8d40738d 440 cur_doc.info, "Definition documentation required")
64d6033b 441 expr_elem['doc'] = cur_doc
a719a27c 442 self.exprs.append(expr_elem)
64d6033b
MA
443 cur_doc = None
444 self.reject_expr_doc(cur_doc)
e7823a2a 445
64d6033b
MA
446 @staticmethod
447 def reject_expr_doc(doc):
448 if doc and doc.symbol:
e7823a2a 449 raise QAPISemError(
64d6033b 450 doc.info,
e7823a2a 451 "Documentation for '%s' is not followed by the definition"
64d6033b 452 % doc.symbol)
c7a3f252 453
97f02494 454 def _include(self, include, info, incl_fname, previously_included):
af97502c 455 incl_abs_fname = os.path.abspath(incl_fname)
e04dea88
MA
456 # catch inclusion cycle
457 inf = info
458 while inf:
459 if incl_abs_fname == os.path.abspath(inf['file']):
460 raise QAPISemError(info, "Inclusion loop for %s" % include)
461 inf = inf['parent']
462
463 # skip multiple include of the same file
464 if incl_abs_fname in previously_included:
42570530
MA
465 return None
466
e04dea88 467 try:
de685ae5
MA
468 if sys.version_info[0] >= 3:
469 fobj = open(incl_fname, 'r', encoding='utf-8')
470 else:
471 fobj = open(incl_fname, 'r')
e04dea88 472 except IOError as e:
9f5e6b08 473 raise QAPISemError(info, "%s: %s" % (e.strerror, incl_fname))
42570530 474 return QAPISchemaParser(fobj, previously_included, info)
e04dea88 475
bc52d03f 476 def _pragma(self, name, value, info):
2cfbae3c 477 global doc_required, returns_whitelist, name_case_whitelist
bc52d03f
MA
478 if name == 'doc-required':
479 if not isinstance(value, bool):
480 raise QAPISemError(info,
481 "Pragma 'doc-required' must be boolean")
482 doc_required = value
1554a8fa
MA
483 elif name == 'returns-whitelist':
484 if (not isinstance(value, list)
485 or any([not isinstance(elt, str) for elt in value])):
486 raise QAPISemError(info,
487 "Pragma returns-whitelist must be"
488 " a list of strings")
489 returns_whitelist = value
2cfbae3c
MA
490 elif name == 'name-case-whitelist':
491 if (not isinstance(value, list)
492 or any([not isinstance(elt, str) for elt in value])):
493 raise QAPISemError(info,
494 "Pragma name-case-whitelist must be"
495 " a list of strings")
496 name_case_whitelist = value
bc52d03f
MA
497 else:
498 raise QAPISemError(info, "Unknown pragma '%s'" % name)
499
3313b612 500 def accept(self, skip_comment=True):
c7a3f252 501 while True:
c7a3f252 502 self.tok = self.src[self.cursor]
2caba36c 503 self.pos = self.cursor
c7a3f252
MA
504 self.cursor += 1
505 self.val = None
506
f1a145e1 507 if self.tok == '#':
3313b612
MAL
508 if self.src[self.cursor] == '#':
509 # Start of doc comment
510 skip_comment = False
c7a3f252 511 self.cursor = self.src.find('\n', self.cursor)
3313b612
MAL
512 if not skip_comment:
513 self.val = self.src[self.pos:self.cursor]
514 return
ef801a9b 515 elif self.tok in '{}:,[]':
c7a3f252
MA
516 return
517 elif self.tok == "'":
56a8caff 518 # Note: we accept only printable ASCII
c7a3f252
MA
519 string = ''
520 esc = False
521 while True:
522 ch = self.src[self.cursor]
523 self.cursor += 1
524 if ch == '\n':
9f5e6b08 525 raise QAPIParseError(self, "Missing terminating \"'\"")
c7a3f252 526 if esc:
9b4416bf
MA
527 # Note: we recognize only \\ because we have
528 # no use for funny characters in strings
529 if ch != '\\':
4148c298
MAL
530 raise QAPIParseError(self,
531 "Unknown escape \\%s" % ch)
c7a3f252 532 esc = False
ef801a9b 533 elif ch == '\\':
c7a3f252 534 esc = True
56a8caff 535 continue
c7a3f252
MA
536 elif ch == "'":
537 self.val = string
538 return
56a8caff
MA
539 if ord(ch) < 32 or ord(ch) >= 127:
540 raise QAPIParseError(
541 self, "Funny character in string")
542 string += ch
ef801a9b 543 elif self.src.startswith('true', self.pos):
e565d934
MA
544 self.val = True
545 self.cursor += 3
546 return
ef801a9b 547 elif self.src.startswith('false', self.pos):
e565d934
MA
548 self.val = False
549 self.cursor += 4
550 return
c7a3f252
MA
551 elif self.tok == '\n':
552 if self.cursor == len(self.src):
553 self.tok = None
554 return
515b943a
WX
555 self.line += 1
556 self.line_pos = self.cursor
9213aa53 557 elif not self.tok.isspace():
14c32795
MA
558 # Show up to next structural, whitespace or quote
559 # character
560 match = re.match('[^[\\]{}:,\\s\'"]+',
561 self.src[self.cursor-1:])
562 raise QAPIParseError(self, "Stray '%s'" % match.group(0))
c7a3f252
MA
563
564 def get_members(self):
565 expr = OrderedDict()
6974ccd5
MA
566 if self.tok == '}':
567 self.accept()
568 return expr
569 if self.tok != "'":
9f5e6b08 570 raise QAPIParseError(self, "Expected string or '}'")
6974ccd5 571 while True:
c7a3f252
MA
572 key = self.val
573 self.accept()
6974ccd5 574 if self.tok != ':':
9f5e6b08 575 raise QAPIParseError(self, "Expected ':'")
6974ccd5 576 self.accept()
4b35991a 577 if key in expr:
9f5e6b08 578 raise QAPIParseError(self, "Duplicate key '%s'" % key)
5f3cd2b7 579 expr[key] = self.get_expr(True)
6974ccd5 580 if self.tok == '}':
c7a3f252 581 self.accept()
6974ccd5
MA
582 return expr
583 if self.tok != ',':
9f5e6b08 584 raise QAPIParseError(self, "Expected ',' or '}'")
6974ccd5
MA
585 self.accept()
586 if self.tok != "'":
9f5e6b08 587 raise QAPIParseError(self, "Expected string")
c7a3f252
MA
588
589 def get_values(self):
590 expr = []
6974ccd5
MA
591 if self.tok == ']':
592 self.accept()
593 return expr
437db254 594 if self.tok not in "{['tfn":
9f5e6b08
MA
595 raise QAPIParseError(
596 self, "Expected '{', '[', ']', string, boolean or 'null'")
6974ccd5 597 while True:
5f3cd2b7 598 expr.append(self.get_expr(True))
6974ccd5 599 if self.tok == ']':
c7a3f252 600 self.accept()
6974ccd5
MA
601 return expr
602 if self.tok != ',':
9f5e6b08 603 raise QAPIParseError(self, "Expected ',' or ']'")
6974ccd5 604 self.accept()
c7a3f252 605
5f3cd2b7
MA
606 def get_expr(self, nested):
607 if self.tok != '{' and not nested:
9f5e6b08 608 raise QAPIParseError(self, "Expected '{'")
c7a3f252
MA
609 if self.tok == '{':
610 self.accept()
611 expr = self.get_members()
612 elif self.tok == '[':
613 self.accept()
614 expr = self.get_values()
e53188ad 615 elif self.tok in "'tfn":
c7a3f252
MA
616 expr = self.val
617 self.accept()
6974ccd5 618 else:
9f5e6b08
MA
619 raise QAPIParseError(
620 self, "Expected '{', '[', string, boolean or 'null'")
c7a3f252 621 return expr
bd9927fe 622
3313b612
MAL
623 def get_doc(self, info):
624 if self.val != '##':
625 raise QAPIParseError(self, "Junk after '##' at start of "
626 "documentation comment")
627
628 doc = QAPIDoc(self, info)
629 self.accept(False)
630 while self.tok == '#':
631 if self.val.startswith('##'):
632 # End of doc comment
633 if self.val != '##':
634 raise QAPIParseError(self, "Junk after '##' at end of "
635 "documentation comment")
4ea7148e 636 doc.end_comment()
3313b612
MAL
637 self.accept()
638 return doc
639 else:
640 doc.append(self.val)
641 self.accept(False)
642
643 raise QAPIParseError(self, "Documentation comment must end with '##'")
644
645
00e4b285
MA
646#
647# Semantic analysis of schema expressions
ac88219a
MA
648# TODO fold into QAPISchema
649# TODO catching name collisions in generated code would be nice
00e4b285
MA
650#
651
437db254 652
14f00c6c 653def find_base_members(base):
ac4338f8
EB
654 if isinstance(base, dict):
655 return base
ed285bf8 656 base_struct_define = struct_types.get(base)
b86b05ed
WX
657 if not base_struct_define:
658 return None
659 return base_struct_define['data']
660
437db254 661
811d04fd
EB
662# Return the qtype of an alternate branch, or None on error.
663def find_alternate_member_qtype(qapi_type):
437db254 664 if qapi_type in builtin_types:
44bd1276 665 return builtin_types[qapi_type]
ed285bf8 666 elif qapi_type in struct_types:
ef801a9b 667 return 'QTYPE_QDICT'
5f018446 668 elif qapi_type in enum_types:
ef801a9b 669 return 'QTYPE_QSTRING'
768562de 670 elif qapi_type in union_types:
ef801a9b 671 return 'QTYPE_QDICT'
44bd1276
EB
672 return None
673
437db254 674
bceae769
WX
675# Return the discriminator enum define if discriminator is specified as an
676# enum type, otherwise return None.
677def discriminator_find_enum_define(expr):
678 base = expr.get('base')
679 discriminator = expr.get('discriminator')
680
681 if not (discriminator and base):
682 return None
683
14f00c6c
EB
684 base_members = find_base_members(base)
685 if not base_members:
bceae769
WX
686 return None
687
87adbbff
MAL
688 discriminator_value = base_members.get(discriminator)
689 if not discriminator_value:
bceae769
WX
690 return None
691
87adbbff 692 return enum_types.get(discriminator_value['type'])
bceae769 693
437db254 694
59a92fee
EB
695# Names must be letters, numbers, -, and _. They must start with letter,
696# except for downstream extensions which must start with __RFQDN_.
697# Dots are only valid in the downstream extension prefix.
0fe675af 698valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
59a92fee 699 '[a-zA-Z][a-zA-Z0-9_-]*$')
437db254
EB
700
701
4148c298 702def check_name(info, source, name, allow_optional=False,
437db254 703 enum_member=False):
c9e0a798
EB
704 global valid_name
705 membername = name
706
707 if not isinstance(name, str):
4148c298 708 raise QAPISemError(info, "%s requires a string name" % source)
c9e0a798
EB
709 if name.startswith('*'):
710 membername = name[1:]
711 if not allow_optional:
4148c298
MAL
712 raise QAPISemError(info, "%s does not allow optional name '%s'"
713 % (source, name))
c9e0a798
EB
714 # Enum members can start with a digit, because the generated C
715 # code always prefixes it with the enum name
59a92fee
EB
716 if enum_member and membername[0].isdigit():
717 membername = 'D' + membername
7599697c
EB
718 # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
719 # and 'q_obj_*' implicit type names.
9fb081e0
EB
720 if not valid_name.match(membername) or \
721 c_name(membername, False).startswith('q_'):
4148c298 722 raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
c9e0a798 723
437db254
EB
724
725def add_name(name, info, meta, implicit=False):
00e4b285
MA
726 global all_names
727 check_name(info, "'%s'" % meta, name)
d90675fa
MA
728 # FIXME should reject names that differ only in '_' vs. '.'
729 # vs. '-', because they're liable to clash in generated C.
00e4b285 730 if name in all_names:
4148c298
MAL
731 raise QAPISemError(info, "%s '%s' is already defined"
732 % (all_names[name], name))
255960dd 733 if not implicit and (name.endswith('Kind') or name.endswith('List')):
4148c298
MAL
734 raise QAPISemError(info, "%s '%s' should not end in '%s'"
735 % (meta, name, name[-4:]))
00e4b285
MA
736 all_names[name] = meta
737
437db254 738
967c8851
MAL
739def check_if(expr, info):
740
741 def check_if_str(ifcond, info):
742 if not isinstance(ifcond, str):
743 raise QAPISemError(
744 info, "'if' condition must be a string or a list of strings")
c2c7065e
MA
745 if ifcond.strip() == '':
746 raise QAPISemError(info, "'if' condition '%s' makes no sense"
747 % ifcond)
967c8851
MAL
748
749 ifcond = expr.get('if')
750 if ifcond is None:
751 return
752 if isinstance(ifcond, list):
753 if ifcond == []:
754 raise QAPISemError(info, "'if' condition [] is useless")
755 for elt in ifcond:
756 check_if_str(elt, info)
757 else:
758 check_if_str(ifcond, info)
759
760
dcca907b
MA
761def check_type(info, source, value,
762 allow_array=False, allow_dict=False, allow_metas=[]):
dd883c6f 763 global all_names
dd883c6f
EB
764
765 if value is None:
766 return
767
dd883c6f
EB
768 # Check if array type for value is okay
769 if isinstance(value, list):
770 if not allow_array:
4148c298 771 raise QAPISemError(info, "%s cannot be an array" % source)
dd883c6f 772 if len(value) != 1 or not isinstance(value[0], str):
4148c298
MAL
773 raise QAPISemError(info,
774 "%s: array type must contain single type name" %
775 source)
dd883c6f 776 value = value[0]
dd883c6f
EB
777
778 # Check if type name for value is okay
779 if isinstance(value, str):
437db254 780 if value not in all_names:
4148c298
MAL
781 raise QAPISemError(info, "%s uses unknown type '%s'"
782 % (source, value))
dd883c6f 783 if not all_names[value] in allow_metas:
4148c298
MAL
784 raise QAPISemError(info, "%s cannot use %s type '%s'" %
785 (source, all_names[value], value))
dd883c6f
EB
786 return
787
dd883c6f 788 if not allow_dict:
4148c298 789 raise QAPISemError(info, "%s should be a type name" % source)
c6b71e5a
MA
790
791 if not isinstance(value, OrderedDict):
4148c298 792 raise QAPISemError(info,
8d40738d 793 "%s should be an object or type name" % source)
c6b71e5a
MA
794
795 # value is a dictionary, check that each member is okay
dd883c6f 796 for (key, arg) in value.items():
4148c298 797 check_name(info, "Member of %s" % source, key,
dcca907b 798 allow_optional=True)
5e59baf9 799 if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
4148c298
MAL
800 raise QAPISemError(info, "Member of %s uses reserved name '%s'"
801 % (source, key))
6b5abc7d
EB
802 # Todo: allow dictionaries to represent default values of
803 # an optional argument.
87adbbff 804 check_known_keys(info, "member '%s' of %s" % (key, source),
ccadd6bc 805 arg, ['type'], ['if'])
dec0012e 806 check_if(arg, info)
fe9c4dcf 807 normalize_if(arg)
87adbbff
MAL
808 check_type(info, "Member '%s' of %s" % (key, source),
809 arg['type'], allow_array=True,
dd883c6f 810 allow_metas=['built-in', 'union', 'alternate', 'struct',
6b5abc7d 811 'enum'])
dd883c6f 812
437db254 813
4148c298 814def check_command(expr, info):
dd883c6f 815 name = expr['command']
c818408e 816 boxed = expr.get('boxed', False)
2cbf0992 817
c818408e
EB
818 args_meta = ['struct']
819 if boxed:
b22e8658 820 args_meta += ['union']
4148c298 821 check_type(info, "'data' for command '%s'" % name,
dcca907b 822 expr.get('data'), allow_dict=not boxed,
c818408e 823 allow_metas=args_meta)
10d4d997
EB
824 returns_meta = ['union', 'struct']
825 if name in returns_whitelist:
826 returns_meta += ['built-in', 'alternate', 'enum']
4148c298 827 check_type(info, "'returns' for command '%s'" % name,
9b090d42 828 expr.get('returns'), allow_array=True,
dcca907b 829 allow_metas=returns_meta)
dd883c6f 830
437db254 831
4148c298 832def check_event(expr, info):
4dc2e690 833 name = expr['event']
c818408e 834 boxed = expr.get('boxed', False)
4dc2e690 835
c818408e
EB
836 meta = ['struct']
837 if boxed:
b22e8658 838 meta += ['union']
4148c298 839 check_type(info, "'data' for event '%s'" % name,
dcca907b 840 expr.get('data'), allow_dict=not boxed,
c818408e 841 allow_metas=meta)
21cd70df 842
437db254 843
ea738b21
MAL
844def enum_get_names(expr):
845 return [e['name'] for e in expr['data']]
846
847
4148c298 848def check_union(expr, info):
b86b05ed
WX
849 name = expr['union']
850 base = expr.get('base')
851 discriminator = expr.get('discriminator')
852 members = expr['data']
853
811d04fd 854 # Two types of unions, determined by discriminator.
811d04fd
EB
855
856 # With no discriminator it is a simple union.
857 if discriminator is None:
0ced9531 858 enum_values = members.keys()
437db254 859 allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
44bd1276 860 if base is not None:
4148c298
MAL
861 raise QAPISemError(info, "Simple union '%s' must not have a base" %
862 name)
b86b05ed
WX
863
864 # Else, it's a flat union.
865 else:
ac4338f8 866 # The object must have a string or dictionary 'base'.
4148c298 867 check_type(info, "'base' for union '%s'" % name,
dcca907b 868 base, allow_dict=True,
ac4338f8 869 allow_metas=['struct'])
376863ef 870 if not base:
4148c298
MAL
871 raise QAPISemError(info, "Flat union '%s' must have a base"
872 % name)
14f00c6c 873 base_members = find_base_members(base)
48153745 874 assert base_members is not None
44bd1276 875
c9e0a798 876 # The value of member 'discriminator' must name a non-optional
fd41dd4e 877 # member of the base struct.
4148c298 878 check_name(info, "Discriminator of flat union '%s'" % name,
c9e0a798 879 discriminator)
87adbbff
MAL
880 discriminator_value = base_members.get(discriminator)
881 if not discriminator_value:
4148c298 882 raise QAPISemError(info,
887a2069
MA
883 "Discriminator '%s' is not a member of 'base'"
884 % discriminator)
ccadd6bc 885 if discriminator_value.get('if'):
9f5e6b08
MA
886 raise QAPISemError(
887 info,
887a2069
MA
888 "The discriminator '%s' for union %s must not be conditional"
889 % (discriminator, name))
87adbbff 890 enum_define = enum_types.get(discriminator_value['type'])
5223070c
WX
891 # Do not allow string discriminator
892 if not enum_define:
4148c298
MAL
893 raise QAPISemError(info,
894 "Discriminator '%s' must be of enumeration "
895 "type" % discriminator)
0ced9531
MA
896 enum_values = enum_get_names(enum_define)
897 allow_metas = ['struct']
898
899 if (len(enum_values) == 0):
900 raise QAPISemError(info, "Union '%s' has no branches" % name)
b86b05ed 901
b86b05ed 902 for (key, value) in members.items():
4148c298 903 check_name(info, "Member of union '%s'" % name, key)
c9e0a798 904
87adbbff 905 check_known_keys(info, "member '%s' of union '%s'" % (key, name),
a2724280 906 value, ['type'], ['if'])
dec0012e 907 check_if(value, info)
fe9c4dcf 908 normalize_if(value)
01cfbaa4 909 # Each value must name a known type
4148c298 910 check_type(info, "Member '%s' of union '%s'" % (key, name),
87adbbff
MAL
911 value['type'],
912 allow_array=not base, allow_metas=allow_metas)
dd883c6f 913
44bd1276 914 # If the discriminator names an enum type, then all members
61a94661 915 # of 'data' must also be members of the enum type.
0ced9531
MA
916 if discriminator is not None:
917 if key not in enum_values:
4148c298
MAL
918 raise QAPISemError(info,
919 "Discriminator value '%s' is not found in "
920 "enum '%s'"
eda43c68 921 % (key, enum_define['enum']))
44bd1276 922
437db254 923
4148c298 924def check_alternate(expr, info):
ab916fad 925 name = expr['alternate']
811d04fd 926 members = expr['data']
811d04fd
EB
927 types_seen = {}
928
f0325536 929 if len(members) == 0:
4148c298 930 raise QAPISemError(info,
f0325536 931 "Alternate '%s' cannot have empty 'data'" % name)
811d04fd 932 for (key, value) in members.items():
4148c298 933 check_name(info, "Member of alternate '%s'" % name, key)
87adbbff
MAL
934 check_known_keys(info,
935 "member '%s' of alternate '%s'" % (key, name),
3e270dca 936 value, ['type'], ['if'])
dec0012e 937 check_if(value, info)
fe9c4dcf 938 normalize_if(value)
87adbbff 939 typ = value['type']
c9e0a798 940
811d04fd 941 # Ensure alternates have no type conflicts.
87adbbff 942 check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ,
dd883c6f 943 allow_metas=['built-in', 'union', 'struct', 'enum'])
87adbbff 944 qtype = find_alternate_member_qtype(typ)
46534309 945 if not qtype:
4148c298 946 raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
87adbbff 947 "type '%s'" % (name, key, typ))
c0644771
MA
948 conflicting = set([qtype])
949 if qtype == 'QTYPE_QSTRING':
87adbbff 950 enum_expr = enum_types.get(typ)
c0644771 951 if enum_expr:
ea738b21 952 for v in enum_get_names(enum_expr):
c0644771
MA
953 if v in ['on', 'off']:
954 conflicting.add('QTYPE_QBOOL')
955 if re.match(r'[-+0-9.]', v): # lazy, could be tightened
01b2ffce 956 conflicting.add('QTYPE_QNUM')
c0644771 957 else:
01b2ffce 958 conflicting.add('QTYPE_QNUM')
c0644771 959 conflicting.add('QTYPE_QBOOL')
c0644771 960 for qt in conflicting:
fda72ab4
EH
961 if qt in types_seen:
962 raise QAPISemError(info, "Alternate '%s' member '%s' can't "
963 "be distinguished from member '%s'"
964 % (name, key, types_seen[qt]))
c0644771 965 types_seen[qt] = key
b86b05ed 966
437db254 967
4148c298 968def check_enum(expr, info):
cf393590 969 name = expr['enum']
ea738b21 970 members = expr['data']
351d36e4 971 prefix = expr.get('prefix')
cf393590
EB
972
973 if not isinstance(members, list):
4148c298
MAL
974 raise QAPISemError(info,
975 "Enum '%s' requires an array for 'data'" % name)
351d36e4 976 if prefix is not None and not isinstance(prefix, str):
4148c298
MAL
977 raise QAPISemError(info,
978 "Enum '%s' requires a string for 'prefix'" % name)
ea738b21 979
cf393590 980 for member in members:
8d40738d
MA
981 check_known_keys(info, "member of enum '%s'" % name, member,
982 ['name'], ['if'])
6cc32b0e 983 check_if(member, info)
fe9c4dcf 984 normalize_if(member)
ea738b21 985 check_name(info, "Member of enum '%s'" % name, member['name'],
c9e0a798 986 enum_member=True)
cf393590 987
437db254 988
4148c298 989def check_struct(expr, info):
fd41dd4e 990 name = expr['struct']
dd883c6f 991 members = expr['data']
6a8c0b51 992 features = expr.get('features')
dd883c6f 993
4148c298 994 check_type(info, "'data' for struct '%s'" % name, members,
dcca907b 995 allow_dict=True)
4148c298 996 check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
dd883c6f
EB
997 allow_metas=['struct'])
998
6a8c0b51
KW
999 if features:
1000 if not isinstance(features, list):
1001 raise QAPISemError(info,
1002 "Struct '%s' requires an array for 'features'" %
1003 name)
1004 for f in features:
1005 assert isinstance(f, dict)
1006 check_known_keys(info, "feature of struct %s" % name, f,
1007 ['name'], ['if'])
1008
1009 check_if(f, info)
fe9c4dcf 1010 normalize_if(f)
6a8c0b51
KW
1011 check_name(info, "Feature of struct %s" % name, f['name'])
1012
437db254 1013
563bd35d 1014def check_known_keys(info, source, keys, required, optional):
7e80d480
MAL
1015
1016 def pprint(elems):
1017 return ', '.join("'" + e + "'" for e in sorted(elems))
1018
1019 missing = set(required) - set(keys)
1020 if missing:
1021 raise QAPISemError(info, "Key%s %s %s missing from %s"
1022 % ('s' if len(missing) > 1 else '', pprint(missing),
1023 'are' if len(missing) > 1 else 'is', source))
1024 allowed = set(required + optional)
1025 unknown = set(keys) - allowed
1026 if unknown:
1027 raise QAPISemError(info, "Unknown key%s %s in %s\nValid keys are %s."
1028 % ('s' if len(unknown) > 1 else '', pprint(unknown),
1029 source, pprint(allowed)))
563bd35d
MAL
1030
1031
dc234189 1032def check_keys(expr, info, meta, required, optional=[]):
0545f6b8
EB
1033 name = expr[meta]
1034 if not isinstance(name, str):
4148c298 1035 raise QAPISemError(info, "'%s' key must have a string value" % meta)
437db254 1036 required = required + [meta]
563bd35d
MAL
1037 source = "%s '%s'" % (meta, name)
1038 check_known_keys(info, source, expr.keys(), required, optional)
0545f6b8 1039 for (key, value) in expr.items():
b736e25a 1040 if key in ['gen', 'success-response'] and value is not False:
4148c298
MAL
1041 raise QAPISemError(info,
1042 "'%s' of %s '%s' should only use false value"
1043 % (key, meta, name))
b736e25a
MA
1044 if (key in ['boxed', 'allow-oob', 'allow-preconfig']
1045 and value is not True):
4148c298
MAL
1046 raise QAPISemError(info,
1047 "'%s' of %s '%s' should only use true value"
1048 % (key, meta, name))
967c8851
MAL
1049 if key == 'if':
1050 check_if(expr, info)
0545f6b8 1051
437db254 1052
87adbbff
MAL
1053def normalize_enum(expr):
1054 if isinstance(expr['data'], list):
1055 expr['data'] = [m if isinstance(m, dict) else {'name': m}
1056 for m in expr['data']]
1057
1058
1059def normalize_members(members):
1060 if isinstance(members, OrderedDict):
1061 for key, arg in members.items():
1062 if isinstance(arg, dict):
1063 continue
1064 members[key] = {'type': arg}
1065
1066
6a8c0b51
KW
1067def normalize_features(features):
1068 if isinstance(features, list):
1069 features[:] = [f if isinstance(f, dict) else {'name': f}
1070 for f in features]
1071
1072
fe9c4dcf
MA
1073def normalize_if(expr):
1074 ifcond = expr.get('if')
1075 if isinstance(ifcond, str):
1076 expr['if'] = [ifcond]
1077
1078
4d076d67 1079def check_exprs(exprs):
4dc2e690 1080 global all_names
4dc2e690 1081
7947016d 1082 # Populate name table with names of built-in types
4d076d67
MA
1083 for builtin in builtin_types.keys():
1084 all_names[builtin] = 'built-in'
7947016d
MA
1085
1086 # Learn the types and check for valid expression keys
4d076d67
MA
1087 for expr_elem in exprs:
1088 expr = expr_elem['expr']
1089 info = expr_elem['info']
7947016d 1090 doc = expr_elem.get('doc')
3313b612 1091
97f02494
MA
1092 if 'include' in expr:
1093 continue
1094
7947016d 1095 if not doc and doc_required:
3313b612 1096 raise QAPISemError(info,
8d40738d 1097 "Definition missing documentation comment")
3313b612 1098
437db254 1099 if 'enum' in expr:
6f05345f 1100 meta = 'enum'
dc234189 1101 check_keys(expr, info, 'enum', ['data'], ['if', 'prefix'])
ea738b21 1102 normalize_enum(expr)
5f018446 1103 enum_types[expr[meta]] = expr
437db254 1104 elif 'union' in expr:
6f05345f 1105 meta = 'union'
dc234189 1106 check_keys(expr, info, 'union', ['data'],
967c8851 1107 ['base', 'discriminator', 'if'])
87adbbff
MAL
1108 normalize_members(expr.get('base'))
1109 normalize_members(expr['data'])
768562de 1110 union_types[expr[meta]] = expr
437db254 1111 elif 'alternate' in expr:
6f05345f 1112 meta = 'alternate'
dc234189 1113 check_keys(expr, info, 'alternate', ['data'], ['if'])
87adbbff 1114 normalize_members(expr['data'])
437db254 1115 elif 'struct' in expr:
6f05345f 1116 meta = 'struct'
dc234189 1117 check_keys(expr, info, 'struct', ['data'],
6a8c0b51 1118 ['base', 'if', 'features'])
87adbbff 1119 normalize_members(expr['data'])
6a8c0b51 1120 normalize_features(expr.get('features'))
ed285bf8 1121 struct_types[expr[meta]] = expr
437db254 1122 elif 'command' in expr:
6f05345f 1123 meta = 'command'
dc234189 1124 check_keys(expr, info, 'command', [],
876c6751 1125 ['data', 'returns', 'gen', 'success-response',
967c8851 1126 'boxed', 'allow-oob', 'allow-preconfig', 'if'])
87adbbff 1127 normalize_members(expr.get('data'))
437db254 1128 elif 'event' in expr:
6f05345f 1129 meta = 'event'
dc234189 1130 check_keys(expr, info, 'event', [], ['data', 'boxed', 'if'])
87adbbff 1131 normalize_members(expr.get('data'))
4d076d67 1132 else:
dc234189 1133 raise QAPISemError(info, "Expression is missing metatype")
fe9c4dcf 1134 normalize_if(expr)
6f05345f
MA
1135 name = expr[meta]
1136 add_name(name, info, meta)
7947016d
MA
1137 if doc and doc.symbol != name:
1138 raise QAPISemError(info, "Definition of '%s' follows documentation"
1139 " for '%s'" % (name, doc.symbol))
2caba36c 1140
4d076d67
MA
1141 # Try again for hidden UnionKind enum
1142 for expr_elem in exprs:
1143 expr = expr_elem['expr']
97f02494
MA
1144
1145 if 'include' in expr:
1146 continue
eda43c68
MA
1147 if 'union' in expr and not discriminator_find_enum_define(expr):
1148 name = '%sKind' % expr['union']
437db254 1149 elif 'alternate' in expr:
eda43c68
MA
1150 name = '%sKind' % expr['alternate']
1151 else:
1152 continue
5f018446 1153 enum_types[name] = {'enum': name}
6f05345f 1154 add_name(name, info, 'enum', implicit=True)
4d076d67
MA
1155
1156 # Validate that exprs make sense
1157 for expr_elem in exprs:
1158 expr = expr_elem['expr']
1159 info = expr_elem['info']
a9f396b0 1160 doc = expr_elem.get('doc')
268a1c5e 1161
97f02494
MA
1162 if 'include' in expr:
1163 continue
437db254 1164 if 'enum' in expr:
4d076d67 1165 check_enum(expr, info)
437db254 1166 elif 'union' in expr:
4d076d67 1167 check_union(expr, info)
437db254 1168 elif 'alternate' in expr:
4d076d67 1169 check_alternate(expr, info)
437db254 1170 elif 'struct' in expr:
4d076d67 1171 check_struct(expr, info)
437db254 1172 elif 'command' in expr:
4d076d67 1173 check_command(expr, info)
437db254 1174 elif 'event' in expr:
4d076d67
MA
1175 check_event(expr, info)
1176 else:
1177 assert False, 'unexpected meta type'
1178
a9f396b0
MA
1179 if doc:
1180 doc.check_expr(expr)
ac88219a 1181
a9f396b0 1182 return exprs
3313b612
MAL
1183
1184
ac88219a
MA
1185#
1186# Schema compiler frontend
1187#
1188
1189class QAPISchemaEntity(object):
2cbc9437 1190 def __init__(self, name, info, doc, ifcond=None):
cf40a0a5 1191 assert name is None or isinstance(name, str)
ac88219a 1192 self.name = name
cf40a0a5 1193 self.module = None
99df5289
EB
1194 # For explicitly defined entities, info points to the (explicit)
1195 # definition. For builtins (and their arrays), info is None.
1196 # For implicitly defined entities, info points to a place that
1197 # triggered the implicit definition (there may be more than one
1198 # such place).
ac88219a 1199 self.info = info
069fb5b2 1200 self.doc = doc
fe9c4dcf 1201 self._ifcond = ifcond or []
ac88219a 1202
f51d8c3d
MA
1203 def c_name(self):
1204 return c_name(self.name)
1205
ac88219a 1206 def check(self, schema):
4fca21c1
MAL
1207 if isinstance(self._ifcond, QAPISchemaType):
1208 # inherit the condition from a type
1209 typ = self._ifcond
1210 typ.check(schema)
1211 self.ifcond = typ.ifcond
1212 else:
fe9c4dcf 1213 self.ifcond = self._ifcond
56a46895
MA
1214 if self.info:
1215 self.module = os.path.relpath(self.info['file'],
1216 os.path.dirname(schema.fname))
ac88219a 1217
49823c4b
EB
1218 def is_implicit(self):
1219 return not self.info
1220
3f7dc21b
MA
1221 def visit(self, visitor):
1222 pass
1223
1224
1225class QAPISchemaVisitor(object):
1226 def visit_begin(self, schema):
1227 pass
1228
1229 def visit_end(self):
1230 pass
1231
cf40a0a5
MA
1232 def visit_module(self, fname):
1233 pass
1234
25a0d9c9
EB
1235 def visit_needed(self, entity):
1236 # Default to visiting everything
1237 return True
1238
cf40a0a5
MA
1239 def visit_include(self, fname, info):
1240 pass
1241
3f7dc21b
MA
1242 def visit_builtin_type(self, name, info, json_type):
1243 pass
1244
1962bd39 1245 def visit_enum_type(self, name, info, ifcond, members, prefix):
3f7dc21b
MA
1246 pass
1247
fbf09a2f 1248 def visit_array_type(self, name, info, ifcond, element_type):
3f7dc21b
MA
1249 pass
1250
6a8c0b51
KW
1251 def visit_object_type(self, name, info, ifcond, base, members, variants,
1252 features):
3f7dc21b
MA
1253 pass
1254
6a8c0b51
KW
1255 def visit_object_type_flat(self, name, info, ifcond, members, variants,
1256 features):
39a18158
MA
1257 pass
1258
fbf09a2f 1259 def visit_alternate_type(self, name, info, ifcond, variants):
3f7dc21b
MA
1260 pass
1261
fbf09a2f 1262 def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
d6fe3d02 1263 success_response, boxed, allow_oob, allow_preconfig):
3f7dc21b
MA
1264 pass
1265
fbf09a2f 1266 def visit_event(self, name, info, ifcond, arg_type, boxed):
3f7dc21b
MA
1267 pass
1268
ac88219a 1269
cf40a0a5
MA
1270class QAPISchemaInclude(QAPISchemaEntity):
1271
1272 def __init__(self, fname, info):
1273 QAPISchemaEntity.__init__(self, None, info, None)
1274 self.fname = fname
1275
1276 def visit(self, visitor):
1277 visitor.visit_include(self.fname, self.info)
1278
1279
ac88219a 1280class QAPISchemaType(QAPISchemaEntity):
4040d995
EB
1281 # Return the C type for common use.
1282 # For the types we commonly box, this is a pointer type.
1283 def c_type(self):
1284 pass
1285
1286 # Return the C type to be used in a parameter list.
1287 def c_param_type(self):
1288 return self.c_type()
1289
1290 # Return the C type to be used where we suppress boxing.
1291 def c_unboxed_type(self):
1292 return self.c_type()
f51d8c3d 1293
f51d8c3d
MA
1294 def json_type(self):
1295 pass
1296
1297 def alternate_qtype(self):
1298 json2qtype = {
4d2d5c41 1299 'null': 'QTYPE_QNULL',
f51d8c3d 1300 'string': 'QTYPE_QSTRING',
01b2ffce
MAL
1301 'number': 'QTYPE_QNUM',
1302 'int': 'QTYPE_QNUM',
f51d8c3d
MA
1303 'boolean': 'QTYPE_QBOOL',
1304 'object': 'QTYPE_QDICT'
1305 }
1306 return json2qtype.get(self.json_type())
ac88219a 1307
691e0313
MA
1308 def doc_type(self):
1309 if self.is_implicit():
1310 return None
1311 return self.name
1312
ac88219a
MA
1313
1314class QAPISchemaBuiltinType(QAPISchemaType):
861877a0 1315 def __init__(self, name, json_type, c_type):
069fb5b2 1316 QAPISchemaType.__init__(self, name, None, None)
f51d8c3d
MA
1317 assert not c_type or isinstance(c_type, str)
1318 assert json_type in ('string', 'number', 'int', 'boolean', 'null',
1319 'value')
1320 self._json_type_name = json_type
1321 self._c_type_name = c_type
f51d8c3d
MA
1322
1323 def c_name(self):
1324 return self.name
1325
4040d995
EB
1326 def c_type(self):
1327 return self._c_type_name
1328
1329 def c_param_type(self):
1330 if self.name == 'str':
f51d8c3d
MA
1331 return 'const ' + self._c_type_name
1332 return self._c_type_name
1333
f51d8c3d
MA
1334 def json_type(self):
1335 return self._json_type_name
ac88219a 1336
691e0313
MA
1337 def doc_type(self):
1338 return self.json_type()
1339
3f7dc21b
MA
1340 def visit(self, visitor):
1341 visitor.visit_builtin_type(self.name, self.info, self.json_type())
1342
ac88219a
MA
1343
1344class QAPISchemaEnumType(QAPISchemaType):
57516863 1345 def __init__(self, name, info, doc, ifcond, members, prefix):
2cbc9437 1346 QAPISchemaType.__init__(self, name, info, doc, ifcond)
57516863 1347 for m in members:
398969fe 1348 assert isinstance(m, QAPISchemaEnumMember)
57516863 1349 m.set_owner(name)
ac88219a 1350 assert prefix is None or isinstance(prefix, str)
57516863 1351 self.members = members
ac88219a
MA
1352 self.prefix = prefix
1353
1354 def check(self, schema):
4fca21c1 1355 QAPISchemaType.check(self, schema)
93bda4dd 1356 seen = {}
57516863
MAL
1357 for m in self.members:
1358 m.check_clash(self.info, seen)
069fb5b2 1359 if self.doc:
57516863 1360 self.doc.connect_member(m)
ac88219a 1361
99df5289 1362 def is_implicit(self):
4636211e
MA
1363 # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
1364 return self.name.endswith('Kind') or self.name == 'QType'
99df5289 1365
4040d995 1366 def c_type(self):
f51d8c3d
MA
1367 return c_name(self.name)
1368
93bda4dd 1369 def member_names(self):
57516863 1370 return [m.name for m in self.members]
93bda4dd 1371
f51d8c3d
MA
1372 def json_type(self):
1373 return 'string'
1374
3f7dc21b 1375 def visit(self, visitor):
fbf09a2f 1376 visitor.visit_enum_type(self.name, self.info, self.ifcond,
1962bd39 1377 self.members, self.prefix)
3f7dc21b 1378
ac88219a
MA
1379
1380class QAPISchemaArrayType(QAPISchemaType):
1381 def __init__(self, name, info, element_type):
2cbc9437 1382 QAPISchemaType.__init__(self, name, info, None, None)
ac88219a
MA
1383 assert isinstance(element_type, str)
1384 self._element_type_name = element_type
1385 self.element_type = None
1386
1387 def check(self, schema):
4fca21c1 1388 QAPISchemaType.check(self, schema)
ac88219a
MA
1389 self.element_type = schema.lookup_type(self._element_type_name)
1390 assert self.element_type
4fca21c1 1391 self.element_type.check(schema)
56a46895 1392 self.module = self.element_type.module
2cbc9437 1393 self.ifcond = self.element_type.ifcond
ac88219a 1394
99df5289
EB
1395 def is_implicit(self):
1396 return True
1397
4040d995
EB
1398 def c_type(self):
1399 return c_name(self.name) + pointer_suffix
1400
f51d8c3d
MA
1401 def json_type(self):
1402 return 'array'
1403
691e0313
MA
1404 def doc_type(self):
1405 elt_doc_type = self.element_type.doc_type()
1406 if not elt_doc_type:
1407 return None
1408 return 'array of ' + elt_doc_type
1409
3f7dc21b 1410 def visit(self, visitor):
fbf09a2f
MAL
1411 visitor.visit_array_type(self.name, self.info, self.ifcond,
1412 self.element_type)
3f7dc21b 1413
ac88219a
MA
1414
1415class QAPISchemaObjectType(QAPISchemaType):
2cbc9437 1416 def __init__(self, name, info, doc, ifcond,
6a8c0b51 1417 base, local_members, variants, features):
da34a9bd
EB
1418 # struct has local_members, optional base, and no variants
1419 # flat union has base, variants, and no local_members
1420 # simple union has local_members, variants, and no base
2cbc9437 1421 QAPISchemaType.__init__(self, name, info, doc, ifcond)
ac88219a
MA
1422 assert base is None or isinstance(base, str)
1423 for m in local_members:
1424 assert isinstance(m, QAPISchemaObjectTypeMember)
88d4ef8b
EB
1425 m.set_owner(name)
1426 if variants is not None:
1427 assert isinstance(variants, QAPISchemaObjectTypeVariants)
1428 variants.set_owner(name)
6a8c0b51
KW
1429 for f in features:
1430 assert isinstance(f, QAPISchemaFeature)
1431 f.set_owner(name)
ac88219a
MA
1432 self._base_name = base
1433 self.base = None
1434 self.local_members = local_members
1435 self.variants = variants
1436 self.members = None
6a8c0b51 1437 self.features = features
ac88219a
MA
1438
1439 def check(self, schema):
4fca21c1 1440 QAPISchemaType.check(self, schema)
bac5429c 1441 if self.members is False: # check for cycles
4148c298
MAL
1442 raise QAPISemError(self.info,
1443 "Object %s contains itself" % self.name)
ac88219a
MA
1444 if self.members:
1445 return
1446 self.members = False # mark as being checked
23a4b2c6 1447 seen = OrderedDict()
ac88219a
MA
1448 if self._base_name:
1449 self.base = schema.lookup_type(self._base_name)
1450 assert isinstance(self.base, QAPISchemaObjectType)
ac88219a 1451 self.base.check(schema)
6bbfb12d 1452 self.base.check_clash(self.info, seen)
ac88219a 1453 for m in self.local_members:
e564e2dd 1454 m.check(schema)
27b60ab9 1455 m.check_clash(self.info, seen)
069fb5b2
MA
1456 if self.doc:
1457 self.doc.connect_member(m)
14ff8461 1458 self.members = seen.values()
ac88219a 1459 if self.variants:
cdc5fa37 1460 self.variants.check(schema, seen)
14ff8461 1461 assert self.variants.tag_member in self.members
6bbfb12d 1462 self.variants.check_clash(self.info, seen)
6a8c0b51
KW
1463
1464 # Features are in a name space separate from members
1465 seen = {}
1466 for f in self.features:
1467 f.check_clash(self.info, seen)
1468
816a57cd
MA
1469 if self.doc:
1470 self.doc.check()
ac88219a 1471
14f00c6c 1472 # Check that the members of this type do not cause duplicate JSON members,
27b60ab9
EB
1473 # and update seen to track the members seen so far. Report any errors
1474 # on behalf of info, which is not necessarily self.info
6bbfb12d 1475 def check_clash(self, info, seen):
c2183d2e
EB
1476 assert not self.variants # not implemented
1477 for m in self.members:
27b60ab9 1478 m.check_clash(info, seen)
c2183d2e 1479
99df5289 1480 def is_implicit(self):
7599697c
EB
1481 # See QAPISchema._make_implicit_object_type(), as well as
1482 # _def_predefineds()
1483 return self.name.startswith('q_')
99df5289 1484
b6167706
EB
1485 def is_empty(self):
1486 assert self.members is not None
1487 return not self.members and not self.variants
1488
f51d8c3d 1489 def c_name(self):
cd50a256 1490 assert self.name != 'q_empty'
f51d8c3d
MA
1491 return QAPISchemaType.c_name(self)
1492
4040d995 1493 def c_type(self):
49823c4b 1494 assert not self.is_implicit()
becceedc 1495 return c_name(self.name) + pointer_suffix
f51d8c3d 1496
4040d995 1497 def c_unboxed_type(self):
4040d995
EB
1498 return c_name(self.name)
1499
f51d8c3d
MA
1500 def json_type(self):
1501 return 'object'
1502
3f7dc21b 1503 def visit(self, visitor):
fbf09a2f 1504 visitor.visit_object_type(self.name, self.info, self.ifcond,
6a8c0b51
KW
1505 self.base, self.local_members, self.variants,
1506 self.features)
fbf09a2f 1507 visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
6a8c0b51
KW
1508 self.members, self.variants,
1509 self.features)
3f7dc21b 1510
ac88219a 1511
d44f9ac8 1512class QAPISchemaMember(object):
6a8c0b51 1513 """ Represents object members, enum members and features """
88d4ef8b
EB
1514 role = 'member'
1515
6cc32b0e 1516 def __init__(self, name, ifcond=None):
ac88219a 1517 assert isinstance(name, str)
ac88219a 1518 self.name = name
fe9c4dcf 1519 self.ifcond = ifcond or []
88d4ef8b
EB
1520 self.owner = None
1521
1522 def set_owner(self, name):
1523 assert not self.owner
1524 self.owner = name
ac88219a 1525
27b60ab9
EB
1526 def check_clash(self, info, seen):
1527 cname = c_name(self.name)
2cfbae3c 1528 if cname.lower() != cname and self.owner not in name_case_whitelist:
4148c298
MAL
1529 raise QAPISemError(info,
1530 "%s should not use uppercase" % self.describe())
27b60ab9 1531 if cname in seen:
4148c298
MAL
1532 raise QAPISemError(info, "%s collides with %s" %
1533 (self.describe(), seen[cname].describe()))
27b60ab9 1534 seen[cname] = self
577de12d 1535
88d4ef8b
EB
1536 def _pretty_owner(self):
1537 owner = self.owner
7599697c 1538 if owner.startswith('q_obj_'):
88d4ef8b
EB
1539 # See QAPISchema._make_implicit_object_type() - reverse the
1540 # mapping there to create a nice human-readable description
7599697c 1541 owner = owner[6:]
88d4ef8b
EB
1542 if owner.endswith('-arg'):
1543 return '(parameter of %s)' % owner[:-4]
ac4338f8
EB
1544 elif owner.endswith('-base'):
1545 return '(base of %s)' % owner[:-5]
88d4ef8b
EB
1546 else:
1547 assert owner.endswith('-wrapper')
1548 # Unreachable and not implemented
1549 assert False
93bda4dd
EB
1550 if owner.endswith('Kind'):
1551 # See QAPISchema._make_implicit_enum_type()
1552 return '(branch of %s)' % owner[:-4]
88d4ef8b
EB
1553 return '(%s of %s)' % (self.role, owner)
1554
1555 def describe(self):
1556 return "'%s' %s" % (self.name, self._pretty_owner())
1557
ac88219a 1558
398969fe
MA
1559class QAPISchemaEnumMember(QAPISchemaMember):
1560 role = 'value'
1561
1562
6a8c0b51
KW
1563class QAPISchemaFeature(QAPISchemaMember):
1564 role = 'feature'
1565
1566
d44f9ac8 1567class QAPISchemaObjectTypeMember(QAPISchemaMember):
ccadd6bc
MAL
1568 def __init__(self, name, typ, optional, ifcond=None):
1569 QAPISchemaMember.__init__(self, name, ifcond)
d44f9ac8
EB
1570 assert isinstance(typ, str)
1571 assert isinstance(optional, bool)
1572 self._type_name = typ
1573 self.type = None
1574 self.optional = optional
1575
1576 def check(self, schema):
1577 assert self.owner
1578 self.type = schema.lookup_type(self._type_name)
1579 assert self.type
1580
1581
ac88219a 1582class QAPISchemaObjectTypeVariants(object):
46292ba7
EB
1583 def __init__(self, tag_name, tag_member, variants):
1584 # Flat unions pass tag_name but not tag_member.
1585 # Simple unions and alternates pass tag_member but not tag_name.
1586 # After check(), tag_member is always set, and tag_name remains
1587 # a reliable witness of being used by a flat union.
1588 assert bool(tag_member) != bool(tag_name)
1589 assert (isinstance(tag_name, str) or
1590 isinstance(tag_member, QAPISchemaObjectTypeMember))
ac88219a
MA
1591 for v in variants:
1592 assert isinstance(v, QAPISchemaObjectTypeVariant)
da9cb193 1593 self._tag_name = tag_name
46292ba7 1594 self.tag_member = tag_member
ac88219a
MA
1595 self.variants = variants
1596
88d4ef8b
EB
1597 def set_owner(self, name):
1598 for v in self.variants:
1599 v.set_owner(name)
1600
cdc5fa37 1601 def check(self, schema, seen):
14ff8461 1602 if not self.tag_member: # flat union
da9cb193
EB
1603 self.tag_member = seen[c_name(self._tag_name)]
1604 assert self._tag_name == self.tag_member.name
ac88219a 1605 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
800877bb
AN
1606 if self._tag_name: # flat union
1607 # branches that are not explicitly covered get an empty type
1608 cases = set([v.name for v in self.variants])
57516863
MAL
1609 for m in self.tag_member.type.members:
1610 if m.name not in cases:
ce1a1aec
MAL
1611 v = QAPISchemaObjectTypeVariant(m.name, 'q_empty',
1612 m.ifcond)
800877bb
AN
1613 v.set_owner(self.tag_member.owner)
1614 self.variants.append(v)
ac88219a 1615 for v in self.variants:
10565ca9 1616 v.check(schema)
0426d53c
EB
1617 # Union names must match enum values; alternate names are
1618 # checked separately. Use 'seen' to tell the two apart.
1619 if seen:
93bda4dd 1620 assert v.name in self.tag_member.type.member_names()
0426d53c 1621 assert isinstance(v.type, QAPISchemaObjectType)
b807a1e1
EB
1622 v.type.check(schema)
1623
6bbfb12d 1624 def check_clash(self, info, seen):
b807a1e1
EB
1625 for v in self.variants:
1626 # Reset seen map for each variant, since qapi names from one
1627 # branch do not affect another branch
b807a1e1 1628 assert isinstance(v.type, QAPISchemaObjectType)
6bbfb12d 1629 v.type.check_clash(info, dict(seen))
ac88219a 1630
437db254 1631
ac88219a 1632class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
88d4ef8b
EB
1633 role = 'branch'
1634
a2724280
MAL
1635 def __init__(self, name, typ, ifcond=None):
1636 QAPISchemaObjectTypeMember.__init__(self, name, typ, False, ifcond)
ac88219a 1637
ac88219a
MA
1638
1639class QAPISchemaAlternateType(QAPISchemaType):
2cbc9437
MAL
1640 def __init__(self, name, info, doc, ifcond, variants):
1641 QAPISchemaType.__init__(self, name, info, doc, ifcond)
ac88219a 1642 assert isinstance(variants, QAPISchemaObjectTypeVariants)
da9cb193 1643 assert variants.tag_member
88d4ef8b
EB
1644 variants.set_owner(name)
1645 variants.tag_member.set_owner(self.name)
ac88219a
MA
1646 self.variants = variants
1647
1648 def check(self, schema):
4fca21c1 1649 QAPISchemaType.check(self, schema)
e564e2dd 1650 self.variants.tag_member.check(schema)
b807a1e1
EB
1651 # Not calling self.variants.check_clash(), because there's nothing
1652 # to clash with
cdc5fa37 1653 self.variants.check(schema, {})
0426d53c
EB
1654 # Alternate branch names have no relation to the tag enum values;
1655 # so we have to check for potential name collisions ourselves.
1656 seen = {}
1657 for v in self.variants.variants:
1658 v.check_clash(self.info, seen)
069fb5b2
MA
1659 if self.doc:
1660 self.doc.connect_member(v)
816a57cd
MA
1661 if self.doc:
1662 self.doc.check()
ac88219a 1663
4040d995
EB
1664 def c_type(self):
1665 return c_name(self.name) + pointer_suffix
1666
f51d8c3d
MA
1667 def json_type(self):
1668 return 'value'
1669
3f7dc21b 1670 def visit(self, visitor):
fbf09a2f
MAL
1671 visitor.visit_alternate_type(self.name, self.info, self.ifcond,
1672 self.variants)
3f7dc21b 1673
ac88219a
MA
1674
1675class QAPISchemaCommand(QAPISchemaEntity):
2cbc9437 1676 def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
d6fe3d02 1677 gen, success_response, boxed, allow_oob, allow_preconfig):
2cbc9437 1678 QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
ac88219a
MA
1679 assert not arg_type or isinstance(arg_type, str)
1680 assert not ret_type or isinstance(ret_type, str)
1681 self._arg_type_name = arg_type
1682 self.arg_type = None
1683 self._ret_type_name = ret_type
1684 self.ret_type = None
1685 self.gen = gen
1686 self.success_response = success_response
48825ca4 1687 self.boxed = boxed
876c6751 1688 self.allow_oob = allow_oob
d6fe3d02 1689 self.allow_preconfig = allow_preconfig
ac88219a
MA
1690
1691 def check(self, schema):
4fca21c1 1692 QAPISchemaEntity.check(self, schema)
ac88219a
MA
1693 if self._arg_type_name:
1694 self.arg_type = schema.lookup_type(self._arg_type_name)
b22e8658 1695 assert isinstance(self.arg_type, QAPISchemaObjectType)
c818408e 1696 self.arg_type.check(schema)
675b214b 1697 assert not self.arg_type.variants or self.boxed
c818408e 1698 elif self.boxed:
4148c298 1699 raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
ac88219a
MA
1700 if self._ret_type_name:
1701 self.ret_type = schema.lookup_type(self._ret_type_name)
1702 assert isinstance(self.ret_type, QAPISchemaType)
1703
3f7dc21b 1704 def visit(self, visitor):
fbf09a2f 1705 visitor.visit_command(self.name, self.info, self.ifcond,
3f7dc21b 1706 self.arg_type, self.ret_type,
876c6751 1707 self.gen, self.success_response,
d6fe3d02
IM
1708 self.boxed, self.allow_oob,
1709 self.allow_preconfig)
3f7dc21b 1710
ac88219a
MA
1711
1712class QAPISchemaEvent(QAPISchemaEntity):
2cbc9437
MAL
1713 def __init__(self, name, info, doc, ifcond, arg_type, boxed):
1714 QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
ac88219a
MA
1715 assert not arg_type or isinstance(arg_type, str)
1716 self._arg_type_name = arg_type
1717 self.arg_type = None
48825ca4 1718 self.boxed = boxed
ac88219a
MA
1719
1720 def check(self, schema):
4fca21c1 1721 QAPISchemaEntity.check(self, schema)
ac88219a
MA
1722 if self._arg_type_name:
1723 self.arg_type = schema.lookup_type(self._arg_type_name)
b22e8658 1724 assert isinstance(self.arg_type, QAPISchemaObjectType)
c818408e 1725 self.arg_type.check(schema)
675b214b 1726 assert not self.arg_type.variants or self.boxed
c818408e 1727 elif self.boxed:
4148c298 1728 raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
ac88219a 1729
3f7dc21b 1730 def visit(self, visitor):
fbf09a2f
MAL
1731 visitor.visit_event(self.name, self.info, self.ifcond,
1732 self.arg_type, self.boxed)
3f7dc21b 1733
ac88219a
MA
1734
1735class QAPISchema(object):
1736 def __init__(self, fname):
56a46895 1737 self.fname = fname
de685ae5
MA
1738 if sys.version_info[0] >= 3:
1739 f = open(fname, 'r', encoding='utf-8')
1740 else:
1741 f = open(fname, 'r')
1742 parser = QAPISchemaParser(f)
181feaf3
MA
1743 exprs = check_exprs(parser.exprs)
1744 self.docs = parser.docs
8a84767c 1745 self._entity_list = []
181feaf3
MA
1746 self._entity_dict = {}
1747 self._predefining = True
1748 self._def_predefineds()
1749 self._predefining = False
1750 self._def_exprs(exprs)
1751 self.check()
ac88219a 1752
ac88219a 1753 def _def_entity(self, ent):
99df5289
EB
1754 # Only the predefined types are allowed to not have info
1755 assert ent.info or self._predefining
cf40a0a5 1756 assert ent.name is None or ent.name not in self._entity_dict
8a84767c 1757 self._entity_list.append(ent)
cf40a0a5
MA
1758 if ent.name is not None:
1759 self._entity_dict[ent.name] = ent
ac88219a
MA
1760
1761 def lookup_entity(self, name, typ=None):
1762 ent = self._entity_dict.get(name)
1763 if typ and not isinstance(ent, typ):
1764 return None
1765 return ent
1766
1767 def lookup_type(self, name):
1768 return self.lookup_entity(name, QAPISchemaType)
1769
cf40a0a5
MA
1770 def _def_include(self, expr, info, doc):
1771 include = expr['include']
1772 assert doc is None
1773 main_info = info
1774 while main_info['parent']:
1775 main_info = main_info['parent']
1776 fname = os.path.relpath(include, os.path.dirname(main_info['file']))
1777 self._def_entity(QAPISchemaInclude(fname, info))
1778
861877a0
EB
1779 def _def_builtin_type(self, name, json_type, c_type):
1780 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
cdb6610a
MA
1781 # Instantiating only the arrays that are actually used would
1782 # be nice, but we can't as long as their generated code
1783 # (qapi-builtin-types.[ch]) may be shared by some other
1784 # schema.
99df5289 1785 self._make_array_type(name, None)
ac88219a
MA
1786
1787 def _def_predefineds(self):
861877a0
EB
1788 for t in [('str', 'string', 'char' + pointer_suffix),
1789 ('number', 'number', 'double'),
1790 ('int', 'int', 'int64_t'),
1791 ('int8', 'int', 'int8_t'),
1792 ('int16', 'int', 'int16_t'),
1793 ('int32', 'int', 'int32_t'),
1794 ('int64', 'int', 'int64_t'),
1795 ('uint8', 'int', 'uint8_t'),
1796 ('uint16', 'int', 'uint16_t'),
1797 ('uint32', 'int', 'uint32_t'),
1798 ('uint64', 'int', 'uint64_t'),
1799 ('size', 'int', 'uint64_t'),
1800 ('bool', 'boolean', 'bool'),
4d2d5c41
MA
1801 ('any', 'value', 'QObject' + pointer_suffix),
1802 ('null', 'null', 'QNull' + pointer_suffix)]:
f51d8c3d 1803 self._def_builtin_type(*t)
069fb5b2 1804 self.the_empty_object_type = QAPISchemaObjectType(
6a8c0b51 1805 'q_empty', None, None, None, None, [], None, [])
39a18158 1806 self._def_entity(self.the_empty_object_type)
ea738b21
MAL
1807
1808 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1809 'qbool']
1810 qtype_values = self._make_enum_members([{'name': n} for n in qtypes])
1811
2cbc9437 1812 self._def_entity(QAPISchemaEnumType('QType', None, None, None,
069fb5b2 1813 qtype_values, 'QTYPE'))
ac88219a 1814
6a8c0b51
KW
1815 def _make_features(self, features):
1816 return [QAPISchemaFeature(f['name'], f.get('if')) for f in features]
1817
93bda4dd 1818 def _make_enum_members(self, values):
398969fe
MA
1819 return [QAPISchemaEnumMember(v['name'], v.get('if'))
1820 for v in values]
93bda4dd 1821
2cbc9437 1822 def _make_implicit_enum_type(self, name, info, ifcond, values):
93bda4dd 1823 # See also QAPISchemaObjectTypeMember._pretty_owner()
49823c4b 1824 name = name + 'Kind' # Use namespace reserved by add_name()
93bda4dd 1825 self._def_entity(QAPISchemaEnumType(
2cbc9437 1826 name, info, None, ifcond, self._make_enum_members(values), None))
ac88219a
MA
1827 return name
1828
99df5289 1829 def _make_array_type(self, element_type, info):
255960dd 1830 name = element_type + 'List' # Use namespace reserved by add_name()
ac88219a 1831 if not self.lookup_type(name):
99df5289 1832 self._def_entity(QAPISchemaArrayType(name, info, element_type))
ac88219a
MA
1833 return name
1834
2cbc9437
MAL
1835 def _make_implicit_object_type(self, name, info, doc, ifcond,
1836 role, members):
ac88219a
MA
1837 if not members:
1838 return None
88d4ef8b 1839 # See also QAPISchemaObjectTypeMember._pretty_owner()
7599697c 1840 name = 'q_obj_%s-%s' % (name, role)
2cbc9437
MAL
1841 typ = self.lookup_entity(name, QAPISchemaObjectType)
1842 if typ:
1843 # The implicit object type has multiple users. This can
1844 # happen only for simple unions' implicit wrapper types.
1845 # Its ifcond should be the disjunction of its user's
1846 # ifconds. Not implemented. Instead, we always pass the
1847 # wrapped type's ifcond, which is trivially the same for all
1848 # users. It's also necessary for the wrapper to compile.
1849 # But it's not tight: the disjunction need not imply it. We
1850 # may end up compiling useless wrapper types.
1851 # TODO kill simple unions or implement the disjunction
4fca21c1 1852 assert ifcond == typ._ifcond # pylint: disable=protected-access
2cbc9437
MAL
1853 else:
1854 self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
6a8c0b51 1855 None, members, None, []))
ac88219a
MA
1856 return name
1857
069fb5b2 1858 def _def_enum_type(self, expr, info, doc):
ac88219a
MA
1859 name = expr['enum']
1860 data = expr['data']
1861 prefix = expr.get('prefix')
2cbc9437 1862 ifcond = expr.get('if')
93bda4dd 1863 self._def_entity(QAPISchemaEnumType(
2cbc9437
MAL
1864 name, info, doc, ifcond,
1865 self._make_enum_members(data), prefix))
ac88219a 1866
ccadd6bc 1867 def _make_member(self, name, typ, ifcond, info):
ac88219a
MA
1868 optional = False
1869 if name.startswith('*'):
1870 name = name[1:]
1871 optional = True
1872 if isinstance(typ, list):
1873 assert len(typ) == 1
99df5289 1874 typ = self._make_array_type(typ[0], info)
ccadd6bc 1875 return QAPISchemaObjectTypeMember(name, typ, optional, ifcond)
ac88219a 1876
99df5289 1877 def _make_members(self, data, info):
ccadd6bc 1878 return [self._make_member(key, value['type'], value.get('if'), info)
2f848044 1879 for (key, value) in data.items()]
ac88219a 1880
069fb5b2 1881 def _def_struct_type(self, expr, info, doc):
ac88219a
MA
1882 name = expr['struct']
1883 base = expr.get('base')
1884 data = expr['data']
2cbc9437 1885 ifcond = expr.get('if')
6a8c0b51 1886 features = expr.get('features', [])
2cbc9437 1887 self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base,
99df5289 1888 self._make_members(data, info),
6a8c0b51
KW
1889 None,
1890 self._make_features(features)))
ac88219a 1891
3e270dca
MAL
1892 def _make_variant(self, case, typ, ifcond):
1893 return QAPISchemaObjectTypeVariant(case, typ, ifcond)
ac88219a 1894
a2724280 1895 def _make_simple_variant(self, case, typ, ifcond, info):
ac88219a
MA
1896 if isinstance(typ, list):
1897 assert len(typ) == 1
99df5289
EB
1898 typ = self._make_array_type(typ[0], info)
1899 typ = self._make_implicit_object_type(
4fca21c1 1900 typ, info, None, self.lookup_type(typ),
ccadd6bc 1901 'wrapper', [self._make_member('data', typ, None, info)])
a2724280 1902 return QAPISchemaObjectTypeVariant(case, typ, ifcond)
ac88219a 1903
069fb5b2 1904 def _def_union_type(self, expr, info, doc):
ac88219a
MA
1905 name = expr['union']
1906 data = expr['data']
1907 base = expr.get('base')
2cbc9437 1908 ifcond = expr.get('if')
ac88219a 1909 tag_name = expr.get('discriminator')
46292ba7 1910 tag_member = None
ac4338f8 1911 if isinstance(base, dict):
2cbc9437
MAL
1912 base = self._make_implicit_object_type(
1913 name, info, doc, ifcond,
1914 'base', self._make_members(base, info))
ac88219a 1915 if tag_name:
3e270dca 1916 variants = [self._make_variant(key, value['type'], value.get('if'))
2f848044 1917 for (key, value) in data.items()]
da34a9bd 1918 members = []
ac88219a 1919 else:
a2724280
MAL
1920 variants = [self._make_simple_variant(key, value['type'],
1921 value.get('if'), info)
2f848044 1922 for (key, value) in data.items()]
a2724280 1923 enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
ea738b21 1924 typ = self._make_implicit_enum_type(name, info, ifcond, enum)
9d3f3494 1925 tag_member = QAPISchemaObjectTypeMember('type', typ, False)
da34a9bd 1926 members = [tag_member]
ac88219a 1927 self._def_entity(
2cbc9437 1928 QAPISchemaObjectType(name, info, doc, ifcond, base, members,
ac88219a 1929 QAPISchemaObjectTypeVariants(tag_name,
46292ba7 1930 tag_member,
6a8c0b51 1931 variants), []))
ac88219a 1932
069fb5b2 1933 def _def_alternate_type(self, expr, info, doc):
ac88219a
MA
1934 name = expr['alternate']
1935 data = expr['data']
2cbc9437 1936 ifcond = expr.get('if')
3e270dca 1937 variants = [self._make_variant(key, value['type'], value.get('if'))
2f848044 1938 for (key, value) in data.items()]
0426d53c 1939 tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
ac88219a 1940 self._def_entity(
2cbc9437 1941 QAPISchemaAlternateType(name, info, doc, ifcond,
ac88219a 1942 QAPISchemaObjectTypeVariants(None,
46292ba7 1943 tag_member,
ac88219a 1944 variants)))
ac88219a 1945
069fb5b2 1946 def _def_command(self, expr, info, doc):
ac88219a
MA
1947 name = expr['command']
1948 data = expr.get('data')
1949 rets = expr.get('returns')
1950 gen = expr.get('gen', True)
1951 success_response = expr.get('success-response', True)
48825ca4 1952 boxed = expr.get('boxed', False)
876c6751 1953 allow_oob = expr.get('allow-oob', False)
d6fe3d02 1954 allow_preconfig = expr.get('allow-preconfig', False)
2cbc9437 1955 ifcond = expr.get('if')
ac88219a 1956 if isinstance(data, OrderedDict):
99df5289 1957 data = self._make_implicit_object_type(
2cbc9437 1958 name, info, doc, ifcond, 'arg', self._make_members(data, info))
ac88219a
MA
1959 if isinstance(rets, list):
1960 assert len(rets) == 1
99df5289 1961 rets = self._make_array_type(rets[0], info)
2cbc9437 1962 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
876c6751 1963 gen, success_response,
d6fe3d02 1964 boxed, allow_oob, allow_preconfig))
ac88219a 1965
069fb5b2 1966 def _def_event(self, expr, info, doc):
ac88219a
MA
1967 name = expr['event']
1968 data = expr.get('data')
48825ca4 1969 boxed = expr.get('boxed', False)
2cbc9437 1970 ifcond = expr.get('if')
ac88219a 1971 if isinstance(data, OrderedDict):
99df5289 1972 data = self._make_implicit_object_type(
2cbc9437
MAL
1973 name, info, doc, ifcond, 'arg', self._make_members(data, info))
1974 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
ac88219a 1975
71a7510b
MA
1976 def _def_exprs(self, exprs):
1977 for expr_elem in exprs:
ac88219a
MA
1978 expr = expr_elem['expr']
1979 info = expr_elem['info']
069fb5b2 1980 doc = expr_elem.get('doc')
ac88219a 1981 if 'enum' in expr:
069fb5b2 1982 self._def_enum_type(expr, info, doc)
ac88219a 1983 elif 'struct' in expr:
069fb5b2 1984 self._def_struct_type(expr, info, doc)
ac88219a 1985 elif 'union' in expr:
069fb5b2 1986 self._def_union_type(expr, info, doc)
ac88219a 1987 elif 'alternate' in expr:
069fb5b2 1988 self._def_alternate_type(expr, info, doc)
ac88219a 1989 elif 'command' in expr:
069fb5b2 1990 self._def_command(expr, info, doc)
ac88219a 1991 elif 'event' in expr:
069fb5b2 1992 self._def_event(expr, info, doc)
97f02494 1993 elif 'include' in expr:
cf40a0a5 1994 self._def_include(expr, info, doc)
ac88219a
MA
1995 else:
1996 assert False
1997
1998 def check(self):
8a84767c 1999 for ent in self._entity_list:
ac88219a 2000 ent.check(self)
4d076d67 2001
3f7dc21b 2002 def visit(self, visitor):
25a0d9c9 2003 visitor.visit_begin(self)
cf40a0a5 2004 module = None
dcac6471 2005 visitor.visit_module(module)
8a84767c 2006 for entity in self._entity_list:
25a0d9c9 2007 if visitor.visit_needed(entity):
cf40a0a5
MA
2008 if entity.module != module:
2009 module = entity.module
2010 visitor.visit_module(module)
25a0d9c9 2011 entity.visit(visitor)
3f7dc21b
MA
2012 visitor.visit_end()
2013
b86b05ed 2014
00e4b285
MA
2015#
2016# Code generation helpers
2017#
2018
0f923be2
MR
2019def camel_case(name):
2020 new_name = ''
2021 first = True
2022 for ch in name:
2023 if ch in ['_', '-']:
2024 first = True
2025 elif first:
2026 new_name += ch.upper()
2027 first = False
2028 else:
2029 new_name += ch.lower()
2030 return new_name
2031
437db254 2032
849bc538
MA
2033# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
2034# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
2035# ENUM24_Name -> ENUM24_NAME
2036def camel_to_upper(value):
2037 c_fun_str = c_name(value, False)
2038 if value.isupper():
2039 return c_fun_str
2040
2041 new_name = ''
b736e25a
MA
2042 length = len(c_fun_str)
2043 for i in range(length):
849bc538 2044 c = c_fun_str[i]
ef801a9b
MA
2045 # When c is upper and no '_' appears before, do more checks
2046 if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
b736e25a 2047 if i < length - 1 and c_fun_str[i + 1].islower():
437db254
EB
2048 new_name += '_'
2049 elif c_fun_str[i - 1].isdigit():
849bc538
MA
2050 new_name += '_'
2051 new_name += c
2052 return new_name.lstrip('_').upper()
2053
437db254 2054
351d36e4
DB
2055def c_enum_const(type_name, const_name, prefix=None):
2056 if prefix is not None:
2057 type_name = prefix
d20a580b 2058 return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
849bc538 2059
b736e25a 2060
52c4272c 2061if hasattr(str, 'maketrans'):
eb815e24 2062 c_name_trans = str.maketrans('.-', '__')
52c4272c 2063else:
eb815e24 2064 c_name_trans = string.maketrans('.-', '__')
47299262 2065
437db254 2066
c6405b54
EB
2067# Map @name to a valid C identifier.
2068# If @protect, avoid returning certain ticklish identifiers (like
ef801a9b 2069# C keywords) by prepending 'q_'.
c6405b54
EB
2070#
2071# Used for converting 'name' from a 'name':'type' qapi definition
2072# into a generated struct member, as well as converting type names
2073# into substrings of a generated C function name.
2074# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
2075# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
18df515e 2076def c_name(name, protect=True):
427a1a2c
BS
2077 # ANSI X3J11/88-090, 3.1.1
2078 c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
437db254
EB
2079 'default', 'do', 'double', 'else', 'enum', 'extern',
2080 'float', 'for', 'goto', 'if', 'int', 'long', 'register',
2081 'return', 'short', 'signed', 'sizeof', 'static',
2082 'struct', 'switch', 'typedef', 'union', 'unsigned',
2083 'void', 'volatile', 'while'])
427a1a2c
BS
2084 # ISO/IEC 9899:1999, 6.4.1
2085 c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
2086 # ISO/IEC 9899:2011, 6.4.1
437db254
EB
2087 c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
2088 '_Noreturn', '_Static_assert', '_Thread_local'])
427a1a2c
BS
2089 # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
2090 # excluding _.*
2091 gcc_words = set(['asm', 'typeof'])
6f88009e
TS
2092 # C++ ISO/IEC 14882:2003 2.11
2093 cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
2094 'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
2095 'namespace', 'new', 'operator', 'private', 'protected',
2096 'public', 'reinterpret_cast', 'static_cast', 'template',
2097 'this', 'throw', 'true', 'try', 'typeid', 'typename',
2098 'using', 'virtual', 'wchar_t',
2099 # alternative representations
2100 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
2101 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
1057725f 2102 # namespace pollution:
9a801c7d 2103 polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
c43567c1 2104 name = name.translate(c_name_trans)
437db254
EB
2105 if protect and (name in c89_words | c99_words | c11_words | gcc_words
2106 | cpp_words | polluted_words):
ef801a9b 2107 return 'q_' + name
c43567c1 2108 return name
0f923be2 2109
b736e25a 2110
05dfb26c 2111eatspace = '\033EATSPACE.'
d5573446 2112pointer_suffix = ' *' + eatspace
05dfb26c 2113
437db254 2114
0f923be2 2115def genindent(count):
ef801a9b 2116 ret = ''
437db254 2117 for _ in range(count):
ef801a9b 2118 ret += ' '
0f923be2
MR
2119 return ret
2120
b736e25a 2121
0f923be2
MR
2122indent_level = 0
2123
437db254 2124
0f923be2
MR
2125def push_indent(indent_amount=4):
2126 global indent_level
2127 indent_level += indent_amount
2128
437db254 2129
0f923be2
MR
2130def pop_indent(indent_amount=4):
2131 global indent_level
2132 indent_level -= indent_amount
2133
437db254 2134
77e703b8
MA
2135# Generate @code with @kwds interpolated.
2136# Obey indent_level, and strip eatspace.
0f923be2 2137def cgen(code, **kwds):
77e703b8
MA
2138 raw = code % kwds
2139 if indent_level:
2140 indent = genindent(indent_level)
2752e5be 2141 # re.subn() lacks flags support before Python 2.7, use re.compile()
485d948c
MAL
2142 raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
2143 indent, raw)
77e703b8 2144 raw = raw[0]
0fe675af 2145 return re.sub(re.escape(eatspace) + r' *', '', raw)
0f923be2 2146
437db254 2147
0f923be2 2148def mcgen(code, **kwds):
77e703b8
MA
2149 if code[0] == '\n':
2150 code = code[1:]
2151 return cgen(code, **kwds)
0f923be2 2152
0f923be2 2153
709395f8
MA
2154def c_fname(filename):
2155 return re.sub(r'[^A-Za-z0-9_]', '_', filename)
c0afa9c5 2156
437db254 2157
c0afa9c5
MR
2158def guardstart(name):
2159 return mcgen('''
c0afa9c5
MR
2160#ifndef %(name)s
2161#define %(name)s
2162
2163''',
709395f8 2164 name=c_fname(name).upper())
c0afa9c5 2165
437db254 2166
c0afa9c5
MR
2167def guardend(name):
2168 return mcgen('''
2169
2170#endif /* %(name)s */
c0afa9c5 2171''',
709395f8 2172 name=c_fname(name).upper())
2114f5a9 2173
437db254 2174
ded9fc28
MAL
2175def gen_if(ifcond):
2176 ret = ''
2177 for ifc in ifcond:
2178 ret += mcgen('''
2179#if %(cond)s
2180''', cond=ifc)
2181 return ret
2182
2183
2184def gen_endif(ifcond):
2185 ret = ''
2186 for ifc in reversed(ifcond):
2187 ret += mcgen('''
2188#endif /* %(cond)s */
2189''', cond=ifc)
2190 return ret
2191
2192
2193def _wrap_ifcond(ifcond, before, after):
2194 if before == after:
2195 return after # suppress empty #if ... #endif
2196
2197 assert after.startswith(before)
2198 out = before
2199 added = after[len(before):]
2200 if added[0] == '\n':
2201 out += '\n'
2202 added = added[1:]
2203 out += gen_if(ifcond)
2204 out += added
2205 out += gen_endif(ifcond)
2206 return out
2207
2208
1962bd39 2209def gen_enum_lookup(name, members, prefix=None):
efd2eaa6
MA
2210 ret = mcgen('''
2211
f7abe0ec
MAL
2212const QEnumLookup %(c_name)s_lookup = {
2213 .array = (const char *const[]) {
efd2eaa6 2214''',
e98859a9 2215 c_name=c_name(name))
1962bd39 2216 for m in members:
8ee06f61 2217 ret += gen_if(m.ifcond)
1962bd39 2218 index = c_enum_const(name, m.name, prefix)
efd2eaa6 2219 ret += mcgen('''
1962bd39 2220 [%(index)s] = "%(name)s",
efd2eaa6 2221''',
1962bd39 2222 index=index, name=m.name)
8ee06f61 2223 ret += gen_endif(m.ifcond)
efd2eaa6 2224
efd2eaa6 2225 ret += mcgen('''
f7abe0ec
MAL
2226 },
2227 .size = %(max_index)s
efd2eaa6
MA
2228};
2229''',
ebf677c8 2230 max_index=c_enum_const(name, '_MAX', prefix))
efd2eaa6
MA
2231 return ret
2232
437db254 2233
1962bd39 2234def gen_enum(name, members, prefix=None):
e98859a9 2235 # append automatically generated _MAX value
398969fe 2236 enum_members = members + [QAPISchemaEnumMember('_MAX')]
efd2eaa6 2237
e98859a9 2238 ret = mcgen('''
efd2eaa6 2239
e98859a9 2240typedef enum %(c_name)s {
efd2eaa6 2241''',
e98859a9 2242 c_name=c_name(name))
efd2eaa6 2243
1962bd39 2244 for m in enum_members:
8ee06f61 2245 ret += gen_if(m.ifcond)
e98859a9 2246 ret += mcgen('''
9c2f56e9 2247 %(c_enum)s,
efd2eaa6 2248''',
1962bd39 2249 c_enum=c_enum_const(name, m.name, prefix))
8ee06f61 2250 ret += gen_endif(m.ifcond)
efd2eaa6 2251
e98859a9
MA
2252 ret += mcgen('''
2253} %(c_name)s;
efd2eaa6 2254''',
e98859a9
MA
2255 c_name=c_name(name))
2256
2257 ret += mcgen('''
efd2eaa6 2258
5b5f825d 2259#define %(c_name)s_str(val) \\
f7abe0ec 2260 qapi_enum_lookup(&%(c_name)s_lookup, (val))
5b5f825d 2261
f7abe0ec 2262extern const QEnumLookup %(c_name)s_lookup;
e98859a9
MA
2263''',
2264 c_name=c_name(name))
2265 return ret
efd2eaa6 2266
437db254 2267
bdd2d42b 2268def build_params(arg_type, boxed, extra=None):
03b4367a
MA
2269 ret = ''
2270 sep = ''
48825ca4 2271 if boxed:
bdd2d42b 2272 assert arg_type
c818408e
EB
2273 ret += '%s arg' % arg_type.c_param_type()
2274 sep = ', '
bdd2d42b 2275 elif arg_type:
48825ca4
EB
2276 assert not arg_type.variants
2277 for memb in arg_type.members:
2278 ret += sep
2279 sep = ', '
2280 if memb.optional:
2281 ret += 'bool has_%s, ' % c_name(memb.name)
2282 ret += '%s %s' % (memb.type.c_param_type(),
2283 c_name(memb.name))
03b4367a
MA
2284 if extra:
2285 ret += sep + extra
bdd2d42b 2286 return ret if ret else 'void'
03b4367a 2287
1f353344 2288
00e4b285 2289#
47a6ea9a 2290# Accumulate and write output
00e4b285
MA
2291#
2292
47a6ea9a
MA
2293class QAPIGen(object):
2294
dddee4d7
MA
2295 def __init__(self, fname):
2296 self.fname = fname
47a6ea9a
MA
2297 self._preamble = ''
2298 self._body = ''
2299
2300 def preamble_add(self, text):
2301 self._preamble += text
2302
2303 def add(self, text):
2304 self._body += text
2305
dddee4d7
MA
2306 def get_content(self):
2307 return self._top() + self._preamble + self._body + self._bottom()
ded9fc28 2308
dddee4d7 2309 def _top(self):
47a6ea9a
MA
2310 return ''
2311
dddee4d7 2312 def _bottom(self):
47a6ea9a 2313 return ''
437db254 2314
dddee4d7
MA
2315 def write(self, output_dir):
2316 pathname = os.path.join(output_dir, self.fname)
cdb6610a
MA
2317 dir = os.path.dirname(pathname)
2318 if dir:
47a6ea9a 2319 try:
cdb6610a 2320 os.makedirs(dir)
47a6ea9a
MA
2321 except os.error as e:
2322 if e.errno != errno.EEXIST:
2323 raise
cdb6610a 2324 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
de685ae5
MA
2325 if sys.version_info[0] >= 3:
2326 f = open(fd, 'r+', encoding='utf-8')
2327 else:
2328 f = os.fdopen(fd, 'r+')
dddee4d7 2329 text = self.get_content()
907b8466
MA
2330 oldtext = f.read(len(text) + 1)
2331 if text != oldtext:
2332 f.seek(0)
2333 f.truncate(0)
2334 f.write(text)
47a6ea9a
MA
2335 f.close()
2336
2337
ded9fc28
MAL
2338@contextmanager
2339def ifcontext(ifcond, *args):
2340 """A 'with' statement context manager to wrap with start_if()/end_if()
47a6ea9a 2341
ded9fc28
MAL
2342 *args: any number of QAPIGenCCode
2343
2344 Example::
2345
2346 with ifcontext(ifcond, self._genh, self._genc):
2347 modify self._genh and self._genc ...
2348
2349 Is equivalent to calling::
2350
2351 self._genh.start_if(ifcond)
2352 self._genc.start_if(ifcond)
2353 modify self._genh and self._genc ...
2354 self._genh.end_if()
2355 self._genc.end_if()
2356 """
2357 for arg in args:
2358 arg.start_if(ifcond)
2359 yield
2360 for arg in args:
2361 arg.end_if()
2362
2363
2364class QAPIGenCCode(QAPIGen):
2365
dddee4d7
MA
2366 def __init__(self, fname):
2367 QAPIGen.__init__(self, fname)
ded9fc28
MAL
2368 self._start_if = None
2369
2370 def start_if(self, ifcond):
2371 assert self._start_if is None
2372 self._start_if = (ifcond, self._body, self._preamble)
2373
2374 def end_if(self):
2375 assert self._start_if
2376 self._wrap_ifcond()
2377 self._start_if = None
2378
2379 def _wrap_ifcond(self):
2380 self._body = _wrap_ifcond(self._start_if[0],
2381 self._start_if[1], self._body)
2382 self._preamble = _wrap_ifcond(self._start_if[0],
2383 self._start_if[2], self._preamble)
2384
dddee4d7 2385 def get_content(self):
ded9fc28 2386 assert self._start_if is None
dddee4d7 2387 return QAPIGen.get_content(self)
ded9fc28
MAL
2388
2389
2390class QAPIGenC(QAPIGenCCode):
2391
dddee4d7
MA
2392 def __init__(self, fname, blurb, pydoc):
2393 QAPIGenCCode.__init__(self, fname)
47a6ea9a
MA
2394 self._blurb = blurb
2395 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
2396 re.MULTILINE))
2397
dddee4d7 2398 def _top(self):
47a6ea9a
MA
2399 return mcgen('''
2400/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
c263de3f
MA
2401
2402/*
2403%(blurb)s
5ddeec83
MA
2404 *
2405 * %(copyright)s
c263de3f
MA
2406 *
2407 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
2408 * See the COPYING.LIB file in the top-level directory.
2409 */
2410
2411''',
47a6ea9a 2412 blurb=self._blurb, copyright=self._copyright)
12f8e1b9 2413
dddee4d7 2414 def _bottom(self):
252dc310 2415 return mcgen('''
5f1450f5 2416
252dc310 2417/* Dummy declaration to prevent empty .o file */
709395f8 2418char qapi_dummy_%(name)s;
252dc310 2419''',
709395f8 2420 name=c_fname(self.fname))
252dc310 2421
12f8e1b9 2422
47a6ea9a 2423class QAPIGenH(QAPIGenC):
12f8e1b9 2424
dddee4d7
MA
2425 def _top(self):
2426 return QAPIGenC._top(self) + guardstart(self.fname)
12f8e1b9 2427
dddee4d7
MA
2428 def _bottom(self):
2429 return guardend(self.fname)
12f8e1b9 2430
437db254 2431
47a6ea9a 2432class QAPIGenDoc(QAPIGen):
c263de3f 2433
dddee4d7
MA
2434 def _top(self):
2435 return (QAPIGen._top(self)
47a6ea9a 2436 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
71b3f045
MA
2437
2438
2439class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
2440
2441 def __init__(self, prefix, what, blurb, pydoc):
2442 self._prefix = prefix
2443 self._what = what
dddee4d7
MA
2444 self._genc = QAPIGenC(self._prefix + self._what + '.c',
2445 blurb, pydoc)
2446 self._genh = QAPIGenH(self._prefix + self._what + '.h',
2447 blurb, pydoc)
71b3f045
MA
2448
2449 def write(self, output_dir):
dddee4d7
MA
2450 self._genc.write(output_dir)
2451 self._genh.write(output_dir)
cdb6610a
MA
2452
2453
2454class QAPISchemaModularCVisitor(QAPISchemaVisitor):
2455
2456 def __init__(self, prefix, what, blurb, pydoc):
2457 self._prefix = prefix
2458 self._what = what
2459 self._blurb = blurb
2460 self._pydoc = pydoc
dcac6471
MA
2461 self._genc = None
2462 self._genh = None
cdb6610a 2463 self._module = {}
252dc310 2464 self._main_module = None
cdb6610a 2465
c2e196a9
MA
2466 @staticmethod
2467 def _is_user_module(name):
2468 return name and not name.startswith('./')
2469
dcac6471
MA
2470 @staticmethod
2471 def _is_builtin_module(name):
2472 return not name
2473
709395f8
MA
2474 def _module_dirname(self, what, name):
2475 if self._is_user_module(name):
2476 return os.path.dirname(name)
2477 return ''
2478
cdb6610a 2479 def _module_basename(self, what, name):
c2e196a9
MA
2480 ret = '' if self._is_builtin_module(name) else self._prefix
2481 if self._is_user_module(name):
709395f8 2482 basename = os.path.basename(name)
c2e196a9
MA
2483 ret += what
2484 if name != self._main_module:
2485 ret += '-' + os.path.splitext(basename)[0]
c2e196a9
MA
2486 else:
2487 name = name[2:] if name else 'builtin'
2488 ret += re.sub(r'-', '-' + name + '-', what)
2489 return ret
cdb6610a 2490
709395f8
MA
2491 def _module_filename(self, what, name):
2492 return os.path.join(self._module_dirname(what, name),
2493 self._module_basename(what, name))
2494
cdb6610a 2495 def _add_module(self, name, blurb):
709395f8 2496 basename = self._module_filename(self._what, name)
dddee4d7
MA
2497 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
2498 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
cdb6610a
MA
2499 self._module[name] = (genc, genh)
2500 self._set_module(name)
2501
c2e196a9
MA
2502 def _add_user_module(self, name, blurb):
2503 assert self._is_user_module(name)
2504 if self._main_module is None:
2505 self._main_module = name
2506 self._add_module(name, blurb)
2507
2508 def _add_system_module(self, name, blurb):
2509 self._add_module(name and './' + name, blurb)
2510
cdb6610a
MA
2511 def _set_module(self, name):
2512 self._genc, self._genh = self._module[name]
2513
252dc310 2514 def write(self, output_dir, opt_builtins=False):
cdb6610a 2515 for name in self._module:
dcac6471 2516 if self._is_builtin_module(name) and not opt_builtins:
cdb6610a 2517 continue
cdb6610a 2518 (genc, genh) = self._module[name]
dddee4d7
MA
2519 genc.write(output_dir)
2520 genh.write(output_dir)
cdb6610a 2521
dcac6471 2522 def _begin_user_module(self, name):
cdb6610a
MA
2523 pass
2524
2525 def visit_module(self, name):
252dc310
MA
2526 if name in self._module:
2527 self._set_module(name)
dcac6471
MA
2528 elif self._is_builtin_module(name):
2529 # The built-in module has not been created. No code may
2530 # be generated.
2531 self._genc = None
2532 self._genh = None
2533 else:
c2e196a9 2534 self._add_user_module(name, self._blurb)
dcac6471 2535 self._begin_user_module(name)
252dc310
MA
2536
2537 def visit_include(self, name, info):
709395f8
MA
2538 relname = os.path.relpath(self._module_filename(self._what, name),
2539 os.path.dirname(self._genh.fname))
252dc310 2540 self._genh.preamble_add(mcgen('''
709395f8 2541#include "%(relname)s.h"
252dc310 2542''',
709395f8 2543 relname=relname))