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