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