qapi: Move context-free checking to the proper place
[qemu/ar7.git] / scripts / qapi / common.py
blob2e1d8158d67db8b2fb3d0e66c93f7a0533bfde99
2 # QAPI helper library
4 # Copyright IBM, Corp. 2011
5 # Copyright (c) 2013-2018 Red Hat Inc.
7 # Authors:
8 # Anthony Liguori <aliguori@us.ibm.com>
9 # Markus Armbruster <armbru@redhat.com>
11 # This work is licensed under the terms of the GNU GPL, version 2.
12 # See the COPYING file in the top-level directory.
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
24 # Are documentation comments required?
25 doc_required = False
27 # Whitelist of commands allowed to return a non-dictionary
28 returns_whitelist = []
30 # Whitelist of entities allowed to violate case conventions
31 name_case_whitelist = []
35 # Parsing the schema into expressions
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
46 def set_defn(self, meta, name):
47 self.defn_meta = meta
48 self.defn_name = name
50 def next_line(self):
51 info = copy.copy(self)
52 info.line += 1
53 return info
55 def loc(self):
56 return '%s:%d' % (self.fname, self.line)
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 ''
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
72 def __str__(self):
73 return self.include_path() + self.in_defn() + self.loc()
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
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
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)
102 class QAPISemError(QAPIError):
103 def __init__(self, info, msg):
104 QAPIError.__init__(self, info, None, msg)
107 class QAPIDoc(object):
109 A documentation comment block, either definition or free-form
111 Definition documentation blocks consist of
113 * a body section: one line naming the definition, followed by an
114 overview (any number of lines)
116 * argument sections: a description of each argument (for commands
117 and events) or member (for structs, unions and alternates)
119 * features sections: a description of each feature flag
121 * additional (non-argument) sections, possibly tagged
123 Free-form documentation blocks consist only of a body section.
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 = ''
133 def append(self, line):
134 self.text += line.rstrip() + '\n'
136 class ArgSection(Section):
137 def __init__(self, name):
138 QAPIDoc.Section.__init__(self, name)
139 self.member = None
141 def connect(self, member):
142 self.member = member
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
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
169 def append(self, line):
171 Parse a comment line and add it to the documentation.
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
180 line = line[1:]
181 if not line:
182 self._append_freeform(line)
183 return
185 if line[0] != ' ':
186 raise QAPIParseError(self._parser, "missing space after #")
187 line = line[1:]
188 self._append_line(line)
190 def end_comment(self):
191 self._end_section()
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:')
201 def _append_body_line(self, line):
203 Process a line of documentation text in the body section.
205 If this a symbol line and it is the section's first line, this
206 is a definition documentation block for that symbol.
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.
213 Else, append the line to the current section.
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())
241 def _append_args_line(self, line):
243 Process a line of documentation text in an argument section.
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
250 Else, append the line to the current section.
253 name = line.split(' ', 1)[0]
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
272 self._append_freeform(line.strip())
274 def _append_features_line(self, line):
275 name = line.split(' ', 1)[0]
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
291 self._append_freeform(line.strip())
293 def _append_various_line(self, line):
295 Process a line of documentation text in an additional section.
297 A symbol line is an error.
299 A section tag begins an additional section. Start that
300 section and append the line to it.
302 Else, append the line to the current section.
304 name = line.split(' ', 1)[0]
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])
314 if (not self._section.name or
315 not self._section.name.startswith('Example')):
316 line = line.strip()
318 self._append_freeform(line)
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
332 def _start_args_section(self, name):
333 self._start_symbol_section(self.args, name)
335 def _start_features_section(self, name):
336 self._start_symbol_section(self.features, name)
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)
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
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)
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)
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")
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))
384 class QAPISchemaParser(object):
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
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
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)
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)
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
465 # skip multiple include of the same file
466 if incl_abs_fname in previously_included:
467 return None
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)
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)
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
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))
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")
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()
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
625 def get_doc(self, info):
626 if self.val != '##':
627 raise QAPIParseError(
628 self, "junk after '##' at start of documentation comment")
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)
646 raise QAPIParseError(self, "documentation comment must end with '##'")
650 # Check (context-free) schema expression structure
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_-]*$')
660 def check_name(name, info, source,
661 allow_optional=False, enum_member=False, permit_upper=False):
662 check_name_is_str(name, info, source)
663 check_name_str(name, info, source,
664 allow_optional, enum_member, permit_upper)
667 def check_name_is_str(name, info, source):
668 if not isinstance(name, str):
669 raise QAPISemError(info, "%s requires a string name" % source)
672 def check_name_str(name, info, source,
673 allow_optional=False, enum_member=False,
674 permit_upper=False):
675 global valid_name
676 membername = name
678 if allow_optional and name.startswith('*'):
679 membername = name[1:]
680 # Enum members can start with a digit, because the generated C
681 # code always prefixes it with the enum name
682 if enum_member and membername[0].isdigit():
683 membername = 'D' + membername
684 # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
685 # and 'q_obj_*' implicit type names.
686 if not valid_name.match(membername) or \
687 c_name(membername, False).startswith('q_'):
688 raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
689 if not permit_upper and name.lower() != name:
690 raise QAPISemError(
691 info, "%s uses uppercase in name '%s'" % (source, name))
692 assert not membername.startswith('*')
695 def check_defn_name_str(name, info, meta):
696 check_name_str(name, info, meta, permit_upper=True)
697 if name.endswith('Kind') or name.endswith('List'):
698 raise QAPISemError(
699 info, "%s '%s' should not end in '%s'" % (meta, name, name[-4:]))
702 def check_if(expr, info):
704 def check_if_str(ifcond, info):
705 if not isinstance(ifcond, str):
706 raise QAPISemError(
707 info, "'if' condition must be a string or a list of strings")
708 if ifcond.strip() == '':
709 raise QAPISemError(info, "'if' condition '%s' makes no sense"
710 % ifcond)
712 ifcond = expr.get('if')
713 if ifcond is None:
714 return
715 if isinstance(ifcond, list):
716 if ifcond == []:
717 raise QAPISemError(info, "'if' condition [] is useless")
718 for elt in ifcond:
719 check_if_str(elt, info)
720 else:
721 check_if_str(ifcond, info)
724 def check_type(value, info, source,
725 allow_array=False, allow_dict=False):
726 if value is None:
727 return
729 # Array type
730 if isinstance(value, list):
731 if not allow_array:
732 raise QAPISemError(info, "%s cannot be an array" % source)
733 if len(value) != 1 or not isinstance(value[0], str):
734 raise QAPISemError(info,
735 "%s: array type must contain single type name" %
736 source)
737 return
739 # Type name
740 if isinstance(value, str):
741 return
743 # Anonymous type
745 if not allow_dict:
746 raise QAPISemError(info, "%s should be a type name" % source)
748 if not isinstance(value, OrderedDict):
749 raise QAPISemError(info,
750 "%s should be an object or type name" % source)
752 permit_upper = allow_dict in name_case_whitelist
754 # value is a dictionary, check that each member is okay
755 for (key, arg) in value.items():
756 check_name_str(key, info, "member of %s" % source,
757 allow_optional=True, permit_upper=permit_upper)
758 if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
759 raise QAPISemError(
760 info, "member of %s uses reserved name '%s'" % (source, key))
761 check_known_keys(arg, info, "member '%s' of %s" % (key, source),
762 ['type'], ['if'])
763 check_if(arg, info)
764 normalize_if(arg)
765 check_type(arg['type'], info, "member '%s' of %s" % (key, source),
766 allow_array=True)
769 def check_command(expr, info):
770 name = expr['command']
771 args = expr.get('data')
772 boxed = expr.get('boxed', False)
774 if boxed and args is None:
775 raise QAPISemError(info, "'boxed': true requires 'data'")
776 check_type(args, info, "'data' for command '%s'" % name,
777 allow_dict=not boxed)
778 check_type(expr.get('returns'), info,
779 "'returns' for command '%s'" % name,
780 allow_array=True)
783 def check_event(expr, info):
784 name = expr['event']
785 args = expr.get('data')
786 boxed = expr.get('boxed', False)
788 if boxed and args is None:
789 raise QAPISemError(info, "'boxed': true requires 'data'")
790 check_type(args, info, "'data' for event '%s'" % name,
791 allow_dict=not boxed)
794 def check_union(expr, info):
795 name = expr['union']
796 base = expr.get('base')
797 discriminator = expr.get('discriminator')
798 members = expr['data']
800 if discriminator is None: # simple union
801 if base is not None:
802 raise QAPISemError(
803 info, "simple union '%s' must not have a base" % name)
804 else: # flat union
805 check_type(base, info, "'base' for union '%s'" % name,
806 allow_dict=name)
807 if not base:
808 raise QAPISemError(
809 info, "flat union '%s' must have a base" % name)
810 check_name_is_str(discriminator, info,
811 "discriminator of flat union '%s'" % name)
813 for (key, value) in members.items():
814 check_name_str(key, info, "member of union '%s'" % name)
815 check_known_keys(value, info,
816 "member '%s' of union '%s'" % (key, name),
817 ['type'], ['if'])
818 check_if(value, info)
819 normalize_if(value)
820 check_type(value['type'], info,
821 "member '%s' of union '%s'" % (key, name),
822 allow_array=not base)
825 def check_alternate(expr, info):
826 name = expr['alternate']
827 members = expr['data']
829 if len(members) == 0:
830 raise QAPISemError(info,
831 "alternate '%s' cannot have empty 'data'" % name)
832 for (key, value) in members.items():
833 check_name_str(key, info, "member of alternate '%s'" % name)
834 check_known_keys(value, info,
835 "member '%s' of alternate '%s'" % (key, name),
836 ['type'], ['if'])
837 check_if(value, info)
838 normalize_if(value)
839 check_type(value['type'], info,
840 "member '%s' of alternate '%s'" % (key, name))
843 def check_enum(expr, info):
844 name = expr['enum']
845 members = expr['data']
846 prefix = expr.get('prefix')
848 if not isinstance(members, list):
849 raise QAPISemError(info,
850 "enum '%s' requires an array for 'data'" % name)
851 if prefix is not None and not isinstance(prefix, str):
852 raise QAPISemError(info,
853 "enum '%s' requires a string for 'prefix'" % name)
855 permit_upper = name in name_case_whitelist
857 for member in members:
858 check_known_keys(member, info, "member of enum '%s'" % name,
859 ['name'], ['if'])
860 check_if(member, info)
861 normalize_if(member)
862 check_name(member['name'], info, "member of enum '%s'" % name,
863 enum_member=True, permit_upper=permit_upper)
866 def check_struct(expr, info):
867 name = expr['struct']
868 members = expr['data']
869 features = expr.get('features')
871 check_type(members, info, "'data' for struct '%s'" % name,
872 allow_dict=name)
873 check_type(expr.get('base'), info, "'base' for struct '%s'" % name)
875 if features:
876 if not isinstance(features, list):
877 raise QAPISemError(
878 info, "struct '%s' requires an array for 'features'" % name)
879 for f in features:
880 assert isinstance(f, dict)
881 check_known_keys(f, info, "feature of struct %s" % name,
882 ['name'], ['if'])
884 check_if(f, info)
885 normalize_if(f)
886 check_name(f['name'], info, "feature of struct %s" % name)
889 def check_known_keys(value, info, source, required, optional):
891 def pprint(elems):
892 return ', '.join("'" + e + "'" for e in sorted(elems))
894 missing = set(required) - set(value)
895 if missing:
896 raise QAPISemError(
897 info,
898 "key%s %s %s missing from %s"
899 % ('s' if len(missing) > 1 else '', pprint(missing),
900 'are' if len(missing) > 1 else 'is', source))
901 allowed = set(required + optional)
902 unknown = set(value) - allowed
903 if unknown:
904 raise QAPISemError(
905 info,
906 "unknown key%s %s in %s\nValid keys are %s."
907 % ('s' if len(unknown) > 1 else '', pprint(unknown),
908 source, pprint(allowed)))
911 def check_keys(expr, info, meta, required, optional=[]):
912 name = expr[meta]
913 if not isinstance(name, str):
914 raise QAPISemError(info, "'%s' key must have a string value" % meta)
915 required = required + [meta]
916 source = "%s '%s'" % (meta, name)
917 check_known_keys(expr, info, source, required, optional)
918 for (key, value) in expr.items():
919 if key in ['gen', 'success-response'] and value is not False:
920 raise QAPISemError(info,
921 "'%s' of %s '%s' should only use false value"
922 % (key, meta, name))
923 if (key in ['boxed', 'allow-oob', 'allow-preconfig']
924 and value is not True):
925 raise QAPISemError(info,
926 "'%s' of %s '%s' should only use true value"
927 % (key, meta, name))
928 if key == 'if':
929 check_if(expr, info)
932 def normalize_enum(expr):
933 if isinstance(expr['data'], list):
934 expr['data'] = [m if isinstance(m, dict) else {'name': m}
935 for m in expr['data']]
938 def normalize_members(members):
939 if isinstance(members, OrderedDict):
940 for key, arg in members.items():
941 if isinstance(arg, dict):
942 continue
943 members[key] = {'type': arg}
946 def normalize_features(features):
947 if isinstance(features, list):
948 features[:] = [f if isinstance(f, dict) else {'name': f}
949 for f in features]
952 def normalize_if(expr):
953 ifcond = expr.get('if')
954 if isinstance(ifcond, str):
955 expr['if'] = [ifcond]
958 def check_exprs(exprs):
959 for expr_elem in exprs:
960 expr = expr_elem['expr']
961 info = expr_elem['info']
962 doc = expr_elem.get('doc')
964 if 'include' in expr:
965 continue
967 if not doc and doc_required:
968 raise QAPISemError(info,
969 "definition missing documentation comment")
971 if 'enum' in expr:
972 meta = 'enum'
973 check_keys(expr, info, 'enum', ['data'], ['if', 'prefix'])
974 normalize_enum(expr)
975 elif 'union' in expr:
976 meta = 'union'
977 check_keys(expr, info, 'union', ['data'],
978 ['base', 'discriminator', 'if'])
979 normalize_members(expr.get('base'))
980 normalize_members(expr['data'])
981 elif 'alternate' in expr:
982 meta = 'alternate'
983 check_keys(expr, info, 'alternate', ['data'], ['if'])
984 normalize_members(expr['data'])
985 elif 'struct' in expr:
986 meta = 'struct'
987 check_keys(expr, info, 'struct', ['data'],
988 ['base', 'if', 'features'])
989 normalize_members(expr['data'])
990 normalize_features(expr.get('features'))
991 elif 'command' in expr:
992 meta = 'command'
993 check_keys(expr, info, 'command', [],
994 ['data', 'returns', 'gen', 'success-response',
995 'boxed', 'allow-oob', 'allow-preconfig', 'if'])
996 normalize_members(expr.get('data'))
997 elif 'event' in expr:
998 meta = 'event'
999 check_keys(expr, info, 'event', [], ['data', 'boxed', 'if'])
1000 normalize_members(expr.get('data'))
1001 else:
1002 raise QAPISemError(info, "expression is missing metatype")
1003 normalize_if(expr)
1005 name = expr[meta]
1006 check_name_is_str(name, info, "'%s'" % meta)
1007 info.set_defn(meta, name)
1008 check_defn_name_str(name, info, meta)
1010 if doc and doc.symbol != name:
1011 raise QAPISemError(
1012 info,
1013 "definition of '%s' follows documentation for '%s'"
1014 % (name, doc.symbol))
1016 if meta == 'enum':
1017 check_enum(expr, info)
1018 elif meta == 'union':
1019 check_union(expr, info)
1020 elif meta == 'alternate':
1021 check_alternate(expr, info)
1022 elif meta == 'struct':
1023 check_struct(expr, info)
1024 elif meta == 'command':
1025 check_command(expr, info)
1026 elif meta == 'event':
1027 check_event(expr, info)
1028 else:
1029 assert False, 'unexpected meta type'
1031 if doc:
1032 doc.check_expr(expr)
1034 return exprs
1038 # Schema compiler frontend
1039 # TODO catching name collisions in generated code would be nice
1042 class QAPISchemaEntity(object):
1043 meta = None
1045 def __init__(self, name, info, doc, ifcond=None):
1046 assert name is None or isinstance(name, str)
1047 self.name = name
1048 self._module = None
1049 # For explicitly defined entities, info points to the (explicit)
1050 # definition. For builtins (and their arrays), info is None.
1051 # For implicitly defined entities, info points to a place that
1052 # triggered the implicit definition (there may be more than one
1053 # such place).
1054 self.info = info
1055 self.doc = doc
1056 self._ifcond = ifcond or []
1057 self._checked = False
1059 def c_name(self):
1060 return c_name(self.name)
1062 def check(self, schema):
1063 assert not self._checked
1064 if self.info:
1065 self._module = os.path.relpath(self.info.fname,
1066 os.path.dirname(schema.fname))
1067 self._checked = True
1069 @property
1070 def ifcond(self):
1071 assert self._checked
1072 return self._ifcond
1074 @property
1075 def module(self):
1076 assert self._checked
1077 return self._module
1079 def is_implicit(self):
1080 return not self.info
1082 def visit(self, visitor):
1083 assert self._checked
1085 def describe(self):
1086 assert self.meta
1087 return "%s '%s'" % (self.meta, self.name)
1090 class QAPISchemaVisitor(object):
1091 def visit_begin(self, schema):
1092 pass
1094 def visit_end(self):
1095 pass
1097 def visit_module(self, fname):
1098 pass
1100 def visit_needed(self, entity):
1101 # Default to visiting everything
1102 return True
1104 def visit_include(self, fname, info):
1105 pass
1107 def visit_builtin_type(self, name, info, json_type):
1108 pass
1110 def visit_enum_type(self, name, info, ifcond, members, prefix):
1111 pass
1113 def visit_array_type(self, name, info, ifcond, element_type):
1114 pass
1116 def visit_object_type(self, name, info, ifcond, base, members, variants,
1117 features):
1118 pass
1120 def visit_object_type_flat(self, name, info, ifcond, members, variants,
1121 features):
1122 pass
1124 def visit_alternate_type(self, name, info, ifcond, variants):
1125 pass
1127 def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
1128 success_response, boxed, allow_oob, allow_preconfig):
1129 pass
1131 def visit_event(self, name, info, ifcond, arg_type, boxed):
1132 pass
1135 class QAPISchemaInclude(QAPISchemaEntity):
1137 def __init__(self, fname, info):
1138 QAPISchemaEntity.__init__(self, None, info, None)
1139 self.fname = fname
1141 def visit(self, visitor):
1142 QAPISchemaEntity.visit(self, visitor)
1143 visitor.visit_include(self.fname, self.info)
1146 class QAPISchemaType(QAPISchemaEntity):
1147 # Return the C type for common use.
1148 # For the types we commonly box, this is a pointer type.
1149 def c_type(self):
1150 pass
1152 # Return the C type to be used in a parameter list.
1153 def c_param_type(self):
1154 return self.c_type()
1156 # Return the C type to be used where we suppress boxing.
1157 def c_unboxed_type(self):
1158 return self.c_type()
1160 def json_type(self):
1161 pass
1163 def alternate_qtype(self):
1164 json2qtype = {
1165 'null': 'QTYPE_QNULL',
1166 'string': 'QTYPE_QSTRING',
1167 'number': 'QTYPE_QNUM',
1168 'int': 'QTYPE_QNUM',
1169 'boolean': 'QTYPE_QBOOL',
1170 'object': 'QTYPE_QDICT'
1172 return json2qtype.get(self.json_type())
1174 def doc_type(self):
1175 if self.is_implicit():
1176 return None
1177 return self.name
1179 def describe(self):
1180 assert self.meta
1181 return "%s type '%s'" % (self.meta, self.name)
1184 class QAPISchemaBuiltinType(QAPISchemaType):
1185 meta = 'built-in'
1187 def __init__(self, name, json_type, c_type):
1188 QAPISchemaType.__init__(self, name, None, None)
1189 assert not c_type or isinstance(c_type, str)
1190 assert json_type in ('string', 'number', 'int', 'boolean', 'null',
1191 'value')
1192 self._json_type_name = json_type
1193 self._c_type_name = c_type
1195 def c_name(self):
1196 return self.name
1198 def c_type(self):
1199 return self._c_type_name
1201 def c_param_type(self):
1202 if self.name == 'str':
1203 return 'const ' + self._c_type_name
1204 return self._c_type_name
1206 def json_type(self):
1207 return self._json_type_name
1209 def doc_type(self):
1210 return self.json_type()
1212 def visit(self, visitor):
1213 QAPISchemaType.visit(self, visitor)
1214 visitor.visit_builtin_type(self.name, self.info, self.json_type())
1217 class QAPISchemaEnumType(QAPISchemaType):
1218 meta = 'enum'
1220 def __init__(self, name, info, doc, ifcond, members, prefix):
1221 QAPISchemaType.__init__(self, name, info, doc, ifcond)
1222 for m in members:
1223 assert isinstance(m, QAPISchemaEnumMember)
1224 m.set_defined_in(name)
1225 assert prefix is None or isinstance(prefix, str)
1226 self.members = members
1227 self.prefix = prefix
1229 def check(self, schema):
1230 QAPISchemaType.check(self, schema)
1231 seen = {}
1232 for m in self.members:
1233 m.check_clash(self.info, seen)
1234 if self.doc:
1235 self.doc.connect_member(m)
1237 def is_implicit(self):
1238 # See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
1239 return self.name.endswith('Kind') or self.name == 'QType'
1241 def c_type(self):
1242 return c_name(self.name)
1244 def member_names(self):
1245 return [m.name for m in self.members]
1247 def json_type(self):
1248 return 'string'
1250 def visit(self, visitor):
1251 QAPISchemaType.visit(self, visitor)
1252 visitor.visit_enum_type(self.name, self.info, self.ifcond,
1253 self.members, self.prefix)
1256 class QAPISchemaArrayType(QAPISchemaType):
1257 meta = 'array'
1259 def __init__(self, name, info, element_type):
1260 QAPISchemaType.__init__(self, name, info, None, None)
1261 assert isinstance(element_type, str)
1262 self._element_type_name = element_type
1263 self.element_type = None
1265 def check(self, schema):
1266 QAPISchemaType.check(self, schema)
1267 self.element_type = schema.resolve_type(
1268 self._element_type_name, self.info,
1269 self.info and self.info.defn_meta)
1270 assert not isinstance(self.element_type, QAPISchemaArrayType)
1272 @property
1273 def ifcond(self):
1274 assert self._checked
1275 return self.element_type.ifcond
1277 @property
1278 def module(self):
1279 assert self._checked
1280 return self.element_type.module
1282 def is_implicit(self):
1283 return True
1285 def c_type(self):
1286 return c_name(self.name) + pointer_suffix
1288 def json_type(self):
1289 return 'array'
1291 def doc_type(self):
1292 elt_doc_type = self.element_type.doc_type()
1293 if not elt_doc_type:
1294 return None
1295 return 'array of ' + elt_doc_type
1297 def visit(self, visitor):
1298 QAPISchemaType.visit(self, visitor)
1299 visitor.visit_array_type(self.name, self.info, self.ifcond,
1300 self.element_type)
1302 def describe(self):
1303 assert self.meta
1304 return "%s type ['%s']" % (self.meta, self._element_type_name)
1307 class QAPISchemaObjectType(QAPISchemaType):
1308 def __init__(self, name, info, doc, ifcond,
1309 base, local_members, variants, features):
1310 # struct has local_members, optional base, and no variants
1311 # flat union has base, variants, and no local_members
1312 # simple union has local_members, variants, and no base
1313 QAPISchemaType.__init__(self, name, info, doc, ifcond)
1314 self.meta = 'union' if variants else 'struct'
1315 assert base is None or isinstance(base, str)
1316 for m in local_members:
1317 assert isinstance(m, QAPISchemaObjectTypeMember)
1318 m.set_defined_in(name)
1319 if variants is not None:
1320 assert isinstance(variants, QAPISchemaObjectTypeVariants)
1321 variants.set_defined_in(name)
1322 for f in features:
1323 assert isinstance(f, QAPISchemaFeature)
1324 f.set_defined_in(name)
1325 self._base_name = base
1326 self.base = None
1327 self.local_members = local_members
1328 self.variants = variants
1329 self.members = None
1330 self.features = features
1332 def check(self, schema):
1333 # This calls another type T's .check() exactly when the C
1334 # struct emitted by gen_object() contains that T's C struct
1335 # (pointers don't count).
1336 if self.members is not None:
1337 # A previous .check() completed: nothing to do
1338 return
1339 if self._checked:
1340 # Recursed: C struct contains itself
1341 raise QAPISemError(self.info,
1342 "object %s contains itself" % self.name)
1344 QAPISchemaType.check(self, schema)
1345 assert self._checked and self.members is None
1347 seen = OrderedDict()
1348 if self._base_name:
1349 self.base = schema.resolve_type(self._base_name, self.info,
1350 "'base'")
1351 if (not isinstance(self.base, QAPISchemaObjectType)
1352 or self.base.variants):
1353 raise QAPISemError(
1354 self.info,
1355 "'base' requires a struct type, %s isn't"
1356 % self.base.describe())
1357 self.base.check(schema)
1358 self.base.check_clash(self.info, seen)
1359 for m in self.local_members:
1360 m.check(schema)
1361 m.check_clash(self.info, seen)
1362 if self.doc:
1363 self.doc.connect_member(m)
1364 members = seen.values()
1366 if self.variants:
1367 self.variants.check(schema, seen)
1368 self.variants.check_clash(self.info, seen)
1370 # Features are in a name space separate from members
1371 seen = {}
1372 for f in self.features:
1373 f.check_clash(self.info, seen)
1375 if self.doc:
1376 self.doc.check()
1378 self.members = members # mark completed
1380 # Check that the members of this type do not cause duplicate JSON members,
1381 # and update seen to track the members seen so far. Report any errors
1382 # on behalf of info, which is not necessarily self.info
1383 def check_clash(self, info, seen):
1384 assert self._checked
1385 assert not self.variants # not implemented
1386 for m in self.members:
1387 m.check_clash(info, seen)
1389 @property
1390 def ifcond(self):
1391 assert self._checked
1392 if isinstance(self._ifcond, QAPISchemaType):
1393 # Simple union wrapper type inherits from wrapped type;
1394 # see _make_implicit_object_type()
1395 return self._ifcond.ifcond
1396 return self._ifcond
1398 def is_implicit(self):
1399 # See QAPISchema._make_implicit_object_type(), as well as
1400 # _def_predefineds()
1401 return self.name.startswith('q_')
1403 def is_empty(self):
1404 assert self.members is not None
1405 return not self.members and not self.variants
1407 def c_name(self):
1408 assert self.name != 'q_empty'
1409 return QAPISchemaType.c_name(self)
1411 def c_type(self):
1412 assert not self.is_implicit()
1413 return c_name(self.name) + pointer_suffix
1415 def c_unboxed_type(self):
1416 return c_name(self.name)
1418 def json_type(self):
1419 return 'object'
1421 def visit(self, visitor):
1422 QAPISchemaType.visit(self, visitor)
1423 visitor.visit_object_type(self.name, self.info, self.ifcond,
1424 self.base, self.local_members, self.variants,
1425 self.features)
1426 visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
1427 self.members, self.variants,
1428 self.features)
1431 class QAPISchemaMember(object):
1432 """ Represents object members, enum members and features """
1433 role = 'member'
1435 def __init__(self, name, info, ifcond=None):
1436 assert isinstance(name, str)
1437 self.name = name
1438 self.info = info
1439 self.ifcond = ifcond or []
1440 self.defined_in = None
1442 def set_defined_in(self, name):
1443 assert not self.defined_in
1444 self.defined_in = name
1446 def check_clash(self, info, seen):
1447 cname = c_name(self.name)
1448 if cname in seen:
1449 raise QAPISemError(
1450 info,
1451 "%s collides with %s"
1452 % (self.describe(info), seen[cname].describe(info)))
1453 seen[cname] = self
1455 def describe(self, info):
1456 role = self.role
1457 defined_in = self.defined_in
1458 assert defined_in
1460 if defined_in.startswith('q_obj_'):
1461 # See QAPISchema._make_implicit_object_type() - reverse the
1462 # mapping there to create a nice human-readable description
1463 defined_in = defined_in[6:]
1464 if defined_in.endswith('-arg'):
1465 # Implicit type created for a command's dict 'data'
1466 assert role == 'member'
1467 role = 'parameter'
1468 elif defined_in.endswith('-base'):
1469 # Implicit type created for a flat union's dict 'base'
1470 role = 'base ' + role
1471 else:
1472 # Implicit type created for a simple union's branch
1473 assert defined_in.endswith('-wrapper')
1474 # Unreachable and not implemented
1475 assert False
1476 elif defined_in.endswith('Kind'):
1477 # See QAPISchema._make_implicit_enum_type()
1478 # Implicit enum created for simple union's branches
1479 assert role == 'value'
1480 role = 'branch'
1481 elif defined_in != info.defn_name:
1482 return "%s '%s' of type '%s'" % (role, self.name, defined_in)
1483 return "%s '%s'" % (role, self.name)
1486 class QAPISchemaEnumMember(QAPISchemaMember):
1487 role = 'value'
1490 class QAPISchemaFeature(QAPISchemaMember):
1491 role = 'feature'
1494 class QAPISchemaObjectTypeMember(QAPISchemaMember):
1495 def __init__(self, name, info, typ, optional, ifcond=None):
1496 QAPISchemaMember.__init__(self, name, info, ifcond)
1497 assert isinstance(typ, str)
1498 assert isinstance(optional, bool)
1499 self._type_name = typ
1500 self.type = None
1501 self.optional = optional
1503 def check(self, schema):
1504 assert self.defined_in
1505 self.type = schema.resolve_type(self._type_name, self.info,
1506 self.describe)
1509 class QAPISchemaObjectTypeVariants(object):
1510 def __init__(self, tag_name, info, tag_member, variants):
1511 # Flat unions pass tag_name but not tag_member.
1512 # Simple unions and alternates pass tag_member but not tag_name.
1513 # After check(), tag_member is always set, and tag_name remains
1514 # a reliable witness of being used by a flat union.
1515 assert bool(tag_member) != bool(tag_name)
1516 assert (isinstance(tag_name, str) or
1517 isinstance(tag_member, QAPISchemaObjectTypeMember))
1518 for v in variants:
1519 assert isinstance(v, QAPISchemaObjectTypeVariant)
1520 self._tag_name = tag_name
1521 self.info = info
1522 self.tag_member = tag_member
1523 self.variants = variants
1525 def set_defined_in(self, name):
1526 for v in self.variants:
1527 v.set_defined_in(name)
1529 def check(self, schema, seen):
1530 if not self.tag_member: # flat union
1531 self.tag_member = seen.get(c_name(self._tag_name))
1532 base = "'base'"
1533 # Pointing to the base type when not implicit would be
1534 # nice, but we don't know it here
1535 if not self.tag_member or self._tag_name != self.tag_member.name:
1536 raise QAPISemError(
1537 self.info,
1538 "discriminator '%s' is not a member of %s"
1539 % (self._tag_name, base))
1540 # Here we do:
1541 base_type = schema.lookup_type(self.tag_member.defined_in)
1542 assert base_type
1543 if not base_type.is_implicit():
1544 base = "base type '%s'" % self.tag_member.defined_in
1545 if not isinstance(self.tag_member.type, QAPISchemaEnumType):
1546 raise QAPISemError(
1547 self.info,
1548 "discriminator member '%s' of %s must be of enum type"
1549 % (self._tag_name, base))
1550 if self.tag_member.optional:
1551 raise QAPISemError(
1552 self.info,
1553 "discriminator member '%s' of %s must not be optional"
1554 % (self._tag_name, base))
1555 if self.tag_member.ifcond:
1556 raise QAPISemError(
1557 self.info,
1558 "discriminator member '%s' of %s must not be conditional"
1559 % (self._tag_name, base))
1560 else: # simple union
1561 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
1562 assert not self.tag_member.optional
1563 assert self.tag_member.ifcond == []
1564 if self._tag_name: # flat union
1565 # branches that are not explicitly covered get an empty type
1566 cases = set([v.name for v in self.variants])
1567 for m in self.tag_member.type.members:
1568 if m.name not in cases:
1569 v = QAPISchemaObjectTypeVariant(m.name, self.info,
1570 'q_empty', m.ifcond)
1571 v.set_defined_in(self.tag_member.defined_in)
1572 self.variants.append(v)
1573 if not self.variants:
1574 raise QAPISemError(self.info, "union has no branches")
1575 for v in self.variants:
1576 v.check(schema)
1577 # Union names must match enum values; alternate names are
1578 # checked separately. Use 'seen' to tell the two apart.
1579 if seen:
1580 if v.name not in self.tag_member.type.member_names():
1581 raise QAPISemError(
1582 self.info,
1583 "branch '%s' is not a value of %s"
1584 % (v.name, self.tag_member.type.describe()))
1585 if (not isinstance(v.type, QAPISchemaObjectType)
1586 or v.type.variants):
1587 raise QAPISemError(
1588 self.info,
1589 "%s cannot use %s"
1590 % (v.describe(self.info), v.type.describe()))
1591 v.type.check(schema)
1593 def check_clash(self, info, seen):
1594 for v in self.variants:
1595 # Reset seen map for each variant, since qapi names from one
1596 # branch do not affect another branch
1597 v.type.check_clash(info, dict(seen))
1600 class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
1601 role = 'branch'
1603 def __init__(self, name, info, typ, ifcond=None):
1604 QAPISchemaObjectTypeMember.__init__(self, name, info, typ,
1605 False, ifcond)
1608 class QAPISchemaAlternateType(QAPISchemaType):
1609 meta = 'alternate'
1611 def __init__(self, name, info, doc, ifcond, variants):
1612 QAPISchemaType.__init__(self, name, info, doc, ifcond)
1613 assert isinstance(variants, QAPISchemaObjectTypeVariants)
1614 assert variants.tag_member
1615 variants.set_defined_in(name)
1616 variants.tag_member.set_defined_in(self.name)
1617 self.variants = variants
1619 def check(self, schema):
1620 QAPISchemaType.check(self, schema)
1621 self.variants.tag_member.check(schema)
1622 # Not calling self.variants.check_clash(), because there's nothing
1623 # to clash with
1624 self.variants.check(schema, {})
1625 # Alternate branch names have no relation to the tag enum values;
1626 # so we have to check for potential name collisions ourselves.
1627 seen = {}
1628 types_seen = {}
1629 for v in self.variants.variants:
1630 v.check_clash(self.info, seen)
1631 qtype = v.type.alternate_qtype()
1632 if not qtype:
1633 raise QAPISemError(
1634 self.info,
1635 "%s cannot use %s"
1636 % (v.describe(self.info), v.type.describe()))
1637 conflicting = set([qtype])
1638 if qtype == 'QTYPE_QSTRING':
1639 if isinstance(v.type, QAPISchemaEnumType):
1640 for m in v.type.members:
1641 if m.name in ['on', 'off']:
1642 conflicting.add('QTYPE_QBOOL')
1643 if re.match(r'[-+0-9.]', m.name):
1644 # lazy, could be tightened
1645 conflicting.add('QTYPE_QNUM')
1646 else:
1647 conflicting.add('QTYPE_QNUM')
1648 conflicting.add('QTYPE_QBOOL')
1649 for qt in conflicting:
1650 if qt in types_seen:
1651 raise QAPISemError(
1652 self.info,
1653 "%s can't be distinguished from '%s'"
1654 % (v.describe(self.info), types_seen[qt]))
1655 types_seen[qt] = v.name
1656 if self.doc:
1657 self.doc.connect_member(v)
1658 if self.doc:
1659 self.doc.check()
1661 def c_type(self):
1662 return c_name(self.name) + pointer_suffix
1664 def json_type(self):
1665 return 'value'
1667 def visit(self, visitor):
1668 QAPISchemaType.visit(self, visitor)
1669 visitor.visit_alternate_type(self.name, self.info, self.ifcond,
1670 self.variants)
1673 class QAPISchemaCommand(QAPISchemaEntity):
1674 meta = 'command'
1676 def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
1677 gen, success_response, boxed, allow_oob, allow_preconfig):
1678 QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1679 assert not arg_type or isinstance(arg_type, str)
1680 assert not ret_type or isinstance(ret_type, str)
1681 self._arg_type_name = arg_type
1682 self.arg_type = None
1683 self._ret_type_name = ret_type
1684 self.ret_type = None
1685 self.gen = gen
1686 self.success_response = success_response
1687 self.boxed = boxed
1688 self.allow_oob = allow_oob
1689 self.allow_preconfig = allow_preconfig
1691 def check(self, schema):
1692 QAPISchemaEntity.check(self, schema)
1693 if self._arg_type_name:
1694 self.arg_type = schema.resolve_type(
1695 self._arg_type_name, self.info, "command's 'data'")
1696 if not isinstance(self.arg_type, QAPISchemaObjectType):
1697 raise QAPISemError(
1698 self.info,
1699 "command's 'data' cannot take %s"
1700 % self.arg_type.describe())
1701 if self.arg_type.variants and not self.boxed:
1702 raise QAPISemError(
1703 self.info,
1704 "command's 'data' can take %s only with 'boxed': true"
1705 % self.arg_type.describe())
1706 if self._ret_type_name:
1707 self.ret_type = schema.resolve_type(
1708 self._ret_type_name, self.info, "command's 'returns'")
1709 if self.name not in returns_whitelist:
1710 if not (isinstance(self.ret_type, QAPISchemaObjectType)
1711 or (isinstance(self.ret_type, QAPISchemaArrayType)
1712 and isinstance(self.ret_type.element_type,
1713 QAPISchemaObjectType))):
1714 raise QAPISemError(
1715 self.info,
1716 "command's 'returns' cannot take %s"
1717 % self.ret_type.describe())
1719 def visit(self, visitor):
1720 QAPISchemaEntity.visit(self, visitor)
1721 visitor.visit_command(self.name, self.info, self.ifcond,
1722 self.arg_type, self.ret_type,
1723 self.gen, self.success_response,
1724 self.boxed, self.allow_oob,
1725 self.allow_preconfig)
1728 class QAPISchemaEvent(QAPISchemaEntity):
1729 meta = 'event'
1731 def __init__(self, name, info, doc, ifcond, arg_type, boxed):
1732 QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
1733 assert not arg_type or isinstance(arg_type, str)
1734 self._arg_type_name = arg_type
1735 self.arg_type = None
1736 self.boxed = boxed
1738 def check(self, schema):
1739 QAPISchemaEntity.check(self, schema)
1740 if self._arg_type_name:
1741 self.arg_type = schema.resolve_type(
1742 self._arg_type_name, self.info, "event's 'data'")
1743 if not isinstance(self.arg_type, QAPISchemaObjectType):
1744 raise QAPISemError(
1745 self.info,
1746 "event's 'data' cannot take %s"
1747 % self.arg_type.describe())
1748 if self.arg_type.variants and not self.boxed:
1749 raise QAPISemError(
1750 self.info,
1751 "event's 'data' can take %s only with 'boxed': true"
1752 % self.arg_type.describe())
1754 def visit(self, visitor):
1755 QAPISchemaEntity.visit(self, visitor)
1756 visitor.visit_event(self.name, self.info, self.ifcond,
1757 self.arg_type, self.boxed)
1760 class QAPISchema(object):
1761 def __init__(self, fname):
1762 self.fname = fname
1763 if sys.version_info[0] >= 3:
1764 f = open(fname, 'r', encoding='utf-8')
1765 else:
1766 f = open(fname, 'r')
1767 parser = QAPISchemaParser(f)
1768 exprs = check_exprs(parser.exprs)
1769 self.docs = parser.docs
1770 self._entity_list = []
1771 self._entity_dict = {}
1772 self._predefining = True
1773 self._def_predefineds()
1774 self._predefining = False
1775 self._def_exprs(exprs)
1776 self.check()
1778 def _def_entity(self, ent):
1779 # Only the predefined types are allowed to not have info
1780 assert ent.info or self._predefining
1781 self._entity_list.append(ent)
1782 if ent.name is None:
1783 return
1784 # TODO reject names that differ only in '_' vs. '.' vs. '-',
1785 # because they're liable to clash in generated C.
1786 other_ent = self._entity_dict.get(ent.name)
1787 if other_ent:
1788 raise QAPISemError(
1789 ent.info, "%s is already defined" % other_ent.describe())
1790 self._entity_dict[ent.name] = ent
1792 def lookup_entity(self, name, typ=None):
1793 ent = self._entity_dict.get(name)
1794 if typ and not isinstance(ent, typ):
1795 return None
1796 return ent
1798 def lookup_type(self, name):
1799 return self.lookup_entity(name, QAPISchemaType)
1801 def resolve_type(self, name, info, what):
1802 typ = self.lookup_type(name)
1803 if not typ:
1804 if callable(what):
1805 what = what(info)
1806 raise QAPISemError(
1807 info, "%s uses unknown type '%s'" % (what, name))
1808 return typ
1810 def _def_include(self, expr, info, doc):
1811 include = expr['include']
1812 assert doc is None
1813 main_info = info
1814 while main_info.parent:
1815 main_info = main_info.parent
1816 fname = os.path.relpath(include, os.path.dirname(main_info.fname))
1817 self._def_entity(QAPISchemaInclude(fname, info))
1819 def _def_builtin_type(self, name, json_type, c_type):
1820 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
1821 # Instantiating only the arrays that are actually used would
1822 # be nice, but we can't as long as their generated code
1823 # (qapi-builtin-types.[ch]) may be shared by some other
1824 # schema.
1825 self._make_array_type(name, None)
1827 def _def_predefineds(self):
1828 for t in [('str', 'string', 'char' + pointer_suffix),
1829 ('number', 'number', 'double'),
1830 ('int', 'int', 'int64_t'),
1831 ('int8', 'int', 'int8_t'),
1832 ('int16', 'int', 'int16_t'),
1833 ('int32', 'int', 'int32_t'),
1834 ('int64', 'int', 'int64_t'),
1835 ('uint8', 'int', 'uint8_t'),
1836 ('uint16', 'int', 'uint16_t'),
1837 ('uint32', 'int', 'uint32_t'),
1838 ('uint64', 'int', 'uint64_t'),
1839 ('size', 'int', 'uint64_t'),
1840 ('bool', 'boolean', 'bool'),
1841 ('any', 'value', 'QObject' + pointer_suffix),
1842 ('null', 'null', 'QNull' + pointer_suffix)]:
1843 self._def_builtin_type(*t)
1844 self.the_empty_object_type = QAPISchemaObjectType(
1845 'q_empty', None, None, None, None, [], None, [])
1846 self._def_entity(self.the_empty_object_type)
1848 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1849 'qbool']
1850 qtype_values = self._make_enum_members(
1851 [{'name': n} for n in qtypes], None)
1853 self._def_entity(QAPISchemaEnumType('QType', None, None, None,
1854 qtype_values, 'QTYPE'))
1856 def _make_features(self, features, info):
1857 return [QAPISchemaFeature(f['name'], info, f.get('if'))
1858 for f in features]
1860 def _make_enum_members(self, values, info):
1861 return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
1862 for v in values]
1864 def _make_implicit_enum_type(self, name, info, ifcond, values):
1865 # See also QAPISchemaObjectTypeMember.describe()
1866 name = name + 'Kind' # reserved by check_defn_name_str()
1867 self._def_entity(QAPISchemaEnumType(
1868 name, info, None, ifcond, self._make_enum_members(values, info),
1869 None))
1870 return name
1872 def _make_array_type(self, element_type, info):
1873 name = element_type + 'List' # reserved by check_defn_name_str()
1874 if not self.lookup_type(name):
1875 self._def_entity(QAPISchemaArrayType(name, info, element_type))
1876 return name
1878 def _make_implicit_object_type(self, name, info, doc, ifcond,
1879 role, members):
1880 if not members:
1881 return None
1882 # See also QAPISchemaObjectTypeMember.describe()
1883 name = 'q_obj_%s-%s' % (name, role)
1884 typ = self.lookup_entity(name, QAPISchemaObjectType)
1885 if typ:
1886 # The implicit object type has multiple users. This can
1887 # happen only for simple unions' implicit wrapper types.
1888 # Its ifcond should be the disjunction of its user's
1889 # ifconds. Not implemented. Instead, we always pass the
1890 # wrapped type's ifcond, which is trivially the same for all
1891 # users. It's also necessary for the wrapper to compile.
1892 # But it's not tight: the disjunction need not imply it. We
1893 # may end up compiling useless wrapper types.
1894 # TODO kill simple unions or implement the disjunction
1895 assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
1896 else:
1897 self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
1898 None, members, None, []))
1899 return name
1901 def _def_enum_type(self, expr, info, doc):
1902 name = expr['enum']
1903 data = expr['data']
1904 prefix = expr.get('prefix')
1905 ifcond = expr.get('if')
1906 self._def_entity(QAPISchemaEnumType(
1907 name, info, doc, ifcond,
1908 self._make_enum_members(data, info), prefix))
1910 def _make_member(self, name, typ, ifcond, info):
1911 optional = False
1912 if name.startswith('*'):
1913 name = name[1:]
1914 optional = True
1915 if isinstance(typ, list):
1916 assert len(typ) == 1
1917 typ = self._make_array_type(typ[0], info)
1918 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond)
1920 def _make_members(self, data, info):
1921 return [self._make_member(key, value['type'], value.get('if'), info)
1922 for (key, value) in data.items()]
1924 def _def_struct_type(self, expr, info, doc):
1925 name = expr['struct']
1926 base = expr.get('base')
1927 data = expr['data']
1928 ifcond = expr.get('if')
1929 features = expr.get('features', [])
1930 self._def_entity(QAPISchemaObjectType(
1931 name, info, doc, ifcond, base,
1932 self._make_members(data, info),
1933 None,
1934 self._make_features(features, info)))
1936 def _make_variant(self, case, typ, ifcond, info):
1937 return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
1939 def _make_simple_variant(self, case, typ, ifcond, info):
1940 if isinstance(typ, list):
1941 assert len(typ) == 1
1942 typ = self._make_array_type(typ[0], info)
1943 typ = self._make_implicit_object_type(
1944 typ, info, None, self.lookup_type(typ),
1945 'wrapper', [self._make_member('data', typ, None, info)])
1946 return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
1948 def _def_union_type(self, expr, info, doc):
1949 name = expr['union']
1950 data = expr['data']
1951 base = expr.get('base')
1952 ifcond = expr.get('if')
1953 tag_name = expr.get('discriminator')
1954 tag_member = None
1955 if isinstance(base, dict):
1956 base = self._make_implicit_object_type(
1957 name, info, doc, ifcond,
1958 'base', self._make_members(base, info))
1959 if tag_name:
1960 variants = [self._make_variant(key, value['type'],
1961 value.get('if'), info)
1962 for (key, value) in data.items()]
1963 members = []
1964 else:
1965 variants = [self._make_simple_variant(key, value['type'],
1966 value.get('if'), info)
1967 for (key, value) in data.items()]
1968 enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
1969 typ = self._make_implicit_enum_type(name, info, ifcond, enum)
1970 tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
1971 members = [tag_member]
1972 self._def_entity(
1973 QAPISchemaObjectType(name, info, doc, ifcond, base, members,
1974 QAPISchemaObjectTypeVariants(
1975 tag_name, info, tag_member, variants),
1976 []))
1978 def _def_alternate_type(self, expr, info, doc):
1979 name = expr['alternate']
1980 data = expr['data']
1981 ifcond = expr.get('if')
1982 variants = [self._make_variant(key, value['type'], value.get('if'),
1983 info)
1984 for (key, value) in data.items()]
1985 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1986 self._def_entity(
1987 QAPISchemaAlternateType(name, info, doc, ifcond,
1988 QAPISchemaObjectTypeVariants(
1989 None, info, tag_member, variants)))
1991 def _def_command(self, expr, info, doc):
1992 name = expr['command']
1993 data = expr.get('data')
1994 rets = expr.get('returns')
1995 gen = expr.get('gen', True)
1996 success_response = expr.get('success-response', True)
1997 boxed = expr.get('boxed', False)
1998 allow_oob = expr.get('allow-oob', False)
1999 allow_preconfig = expr.get('allow-preconfig', 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 if isinstance(rets, list):
2005 assert len(rets) == 1
2006 rets = self._make_array_type(rets[0], info)
2007 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
2008 gen, success_response,
2009 boxed, allow_oob, allow_preconfig))
2011 def _def_event(self, expr, info, doc):
2012 name = expr['event']
2013 data = expr.get('data')
2014 boxed = expr.get('boxed', False)
2015 ifcond = expr.get('if')
2016 if isinstance(data, OrderedDict):
2017 data = self._make_implicit_object_type(
2018 name, info, doc, ifcond, 'arg', self._make_members(data, info))
2019 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
2021 def _def_exprs(self, exprs):
2022 for expr_elem in exprs:
2023 expr = expr_elem['expr']
2024 info = expr_elem['info']
2025 doc = expr_elem.get('doc')
2026 if 'enum' in expr:
2027 self._def_enum_type(expr, info, doc)
2028 elif 'struct' in expr:
2029 self._def_struct_type(expr, info, doc)
2030 elif 'union' in expr:
2031 self._def_union_type(expr, info, doc)
2032 elif 'alternate' in expr:
2033 self._def_alternate_type(expr, info, doc)
2034 elif 'command' in expr:
2035 self._def_command(expr, info, doc)
2036 elif 'event' in expr:
2037 self._def_event(expr, info, doc)
2038 elif 'include' in expr:
2039 self._def_include(expr, info, doc)
2040 else:
2041 assert False
2043 def check(self):
2044 for ent in self._entity_list:
2045 ent.check(self)
2047 def visit(self, visitor):
2048 visitor.visit_begin(self)
2049 module = None
2050 visitor.visit_module(module)
2051 for entity in self._entity_list:
2052 if visitor.visit_needed(entity):
2053 if entity.module != module:
2054 module = entity.module
2055 visitor.visit_module(module)
2056 entity.visit(visitor)
2057 visitor.visit_end()
2061 # Code generation helpers
2064 def camel_case(name):
2065 new_name = ''
2066 first = True
2067 for ch in name:
2068 if ch in ['_', '-']:
2069 first = True
2070 elif first:
2071 new_name += ch.upper()
2072 first = False
2073 else:
2074 new_name += ch.lower()
2075 return new_name
2078 # ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
2079 # ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
2080 # ENUM24_Name -> ENUM24_NAME
2081 def camel_to_upper(value):
2082 c_fun_str = c_name(value, False)
2083 if value.isupper():
2084 return c_fun_str
2086 new_name = ''
2087 length = len(c_fun_str)
2088 for i in range(length):
2089 c = c_fun_str[i]
2090 # When c is upper and no '_' appears before, do more checks
2091 if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
2092 if i < length - 1 and c_fun_str[i + 1].islower():
2093 new_name += '_'
2094 elif c_fun_str[i - 1].isdigit():
2095 new_name += '_'
2096 new_name += c
2097 return new_name.lstrip('_').upper()
2100 def c_enum_const(type_name, const_name, prefix=None):
2101 if prefix is not None:
2102 type_name = prefix
2103 return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
2106 if hasattr(str, 'maketrans'):
2107 c_name_trans = str.maketrans('.-', '__')
2108 else:
2109 c_name_trans = string.maketrans('.-', '__')
2112 # Map @name to a valid C identifier.
2113 # If @protect, avoid returning certain ticklish identifiers (like
2114 # C keywords) by prepending 'q_'.
2116 # Used for converting 'name' from a 'name':'type' qapi definition
2117 # into a generated struct member, as well as converting type names
2118 # into substrings of a generated C function name.
2119 # '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
2120 # protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
2121 def c_name(name, protect=True):
2122 # ANSI X3J11/88-090, 3.1.1
2123 c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
2124 'default', 'do', 'double', 'else', 'enum', 'extern',
2125 'float', 'for', 'goto', 'if', 'int', 'long', 'register',
2126 'return', 'short', 'signed', 'sizeof', 'static',
2127 'struct', 'switch', 'typedef', 'union', 'unsigned',
2128 'void', 'volatile', 'while'])
2129 # ISO/IEC 9899:1999, 6.4.1
2130 c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
2131 # ISO/IEC 9899:2011, 6.4.1
2132 c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
2133 '_Noreturn', '_Static_assert', '_Thread_local'])
2134 # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
2135 # excluding _.*
2136 gcc_words = set(['asm', 'typeof'])
2137 # C++ ISO/IEC 14882:2003 2.11
2138 cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
2139 'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
2140 'namespace', 'new', 'operator', 'private', 'protected',
2141 'public', 'reinterpret_cast', 'static_cast', 'template',
2142 'this', 'throw', 'true', 'try', 'typeid', 'typename',
2143 'using', 'virtual', 'wchar_t',
2144 # alternative representations
2145 'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
2146 'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
2147 # namespace pollution:
2148 polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
2149 name = name.translate(c_name_trans)
2150 if protect and (name in c89_words | c99_words | c11_words | gcc_words
2151 | cpp_words | polluted_words):
2152 return 'q_' + name
2153 return name
2156 eatspace = '\033EATSPACE.'
2157 pointer_suffix = ' *' + eatspace
2160 def genindent(count):
2161 ret = ''
2162 for _ in range(count):
2163 ret += ' '
2164 return ret
2167 indent_level = 0
2170 def push_indent(indent_amount=4):
2171 global indent_level
2172 indent_level += indent_amount
2175 def pop_indent(indent_amount=4):
2176 global indent_level
2177 indent_level -= indent_amount
2180 # Generate @code with @kwds interpolated.
2181 # Obey indent_level, and strip eatspace.
2182 def cgen(code, **kwds):
2183 raw = code % kwds
2184 if indent_level:
2185 indent = genindent(indent_level)
2186 # re.subn() lacks flags support before Python 2.7, use re.compile()
2187 raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
2188 indent, raw)
2189 raw = raw[0]
2190 return re.sub(re.escape(eatspace) + r' *', '', raw)
2193 def mcgen(code, **kwds):
2194 if code[0] == '\n':
2195 code = code[1:]
2196 return cgen(code, **kwds)
2199 def c_fname(filename):
2200 return re.sub(r'[^A-Za-z0-9_]', '_', filename)
2203 def guardstart(name):
2204 return mcgen('''
2205 #ifndef %(name)s
2206 #define %(name)s
2208 ''',
2209 name=c_fname(name).upper())
2212 def guardend(name):
2213 return mcgen('''
2215 #endif /* %(name)s */
2216 ''',
2217 name=c_fname(name).upper())
2220 def gen_if(ifcond):
2221 ret = ''
2222 for ifc in ifcond:
2223 ret += mcgen('''
2224 #if %(cond)s
2225 ''', cond=ifc)
2226 return ret
2229 def gen_endif(ifcond):
2230 ret = ''
2231 for ifc in reversed(ifcond):
2232 ret += mcgen('''
2233 #endif /* %(cond)s */
2234 ''', cond=ifc)
2235 return ret
2238 def _wrap_ifcond(ifcond, before, after):
2239 if before == after:
2240 return after # suppress empty #if ... #endif
2242 assert after.startswith(before)
2243 out = before
2244 added = after[len(before):]
2245 if added[0] == '\n':
2246 out += '\n'
2247 added = added[1:]
2248 out += gen_if(ifcond)
2249 out += added
2250 out += gen_endif(ifcond)
2251 return out
2254 def gen_enum_lookup(name, members, prefix=None):
2255 ret = mcgen('''
2257 const QEnumLookup %(c_name)s_lookup = {
2258 .array = (const char *const[]) {
2259 ''',
2260 c_name=c_name(name))
2261 for m in members:
2262 ret += gen_if(m.ifcond)
2263 index = c_enum_const(name, m.name, prefix)
2264 ret += mcgen('''
2265 [%(index)s] = "%(name)s",
2266 ''',
2267 index=index, name=m.name)
2268 ret += gen_endif(m.ifcond)
2270 ret += mcgen('''
2272 .size = %(max_index)s
2274 ''',
2275 max_index=c_enum_const(name, '_MAX', prefix))
2276 return ret
2279 def gen_enum(name, members, prefix=None):
2280 # append automatically generated _MAX value
2281 enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
2283 ret = mcgen('''
2285 typedef enum %(c_name)s {
2286 ''',
2287 c_name=c_name(name))
2289 for m in enum_members:
2290 ret += gen_if(m.ifcond)
2291 ret += mcgen('''
2292 %(c_enum)s,
2293 ''',
2294 c_enum=c_enum_const(name, m.name, prefix))
2295 ret += gen_endif(m.ifcond)
2297 ret += mcgen('''
2298 } %(c_name)s;
2299 ''',
2300 c_name=c_name(name))
2302 ret += mcgen('''
2304 #define %(c_name)s_str(val) \\
2305 qapi_enum_lookup(&%(c_name)s_lookup, (val))
2307 extern const QEnumLookup %(c_name)s_lookup;
2308 ''',
2309 c_name=c_name(name))
2310 return ret
2313 def build_params(arg_type, boxed, extra=None):
2314 ret = ''
2315 sep = ''
2316 if boxed:
2317 assert arg_type
2318 ret += '%s arg' % arg_type.c_param_type()
2319 sep = ', '
2320 elif arg_type:
2321 assert not arg_type.variants
2322 for memb in arg_type.members:
2323 ret += sep
2324 sep = ', '
2325 if memb.optional:
2326 ret += 'bool has_%s, ' % c_name(memb.name)
2327 ret += '%s %s' % (memb.type.c_param_type(),
2328 c_name(memb.name))
2329 if extra:
2330 ret += sep + extra
2331 return ret if ret else 'void'
2335 # Accumulate and write output
2338 class QAPIGen(object):
2340 def __init__(self, fname):
2341 self.fname = fname
2342 self._preamble = ''
2343 self._body = ''
2345 def preamble_add(self, text):
2346 self._preamble += text
2348 def add(self, text):
2349 self._body += text
2351 def get_content(self):
2352 return self._top() + self._preamble + self._body + self._bottom()
2354 def _top(self):
2355 return ''
2357 def _bottom(self):
2358 return ''
2360 def write(self, output_dir):
2361 pathname = os.path.join(output_dir, self.fname)
2362 dir = os.path.dirname(pathname)
2363 if dir:
2364 try:
2365 os.makedirs(dir)
2366 except os.error as e:
2367 if e.errno != errno.EEXIST:
2368 raise
2369 fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
2370 if sys.version_info[0] >= 3:
2371 f = open(fd, 'r+', encoding='utf-8')
2372 else:
2373 f = os.fdopen(fd, 'r+')
2374 text = self.get_content()
2375 oldtext = f.read(len(text) + 1)
2376 if text != oldtext:
2377 f.seek(0)
2378 f.truncate(0)
2379 f.write(text)
2380 f.close()
2383 @contextmanager
2384 def ifcontext(ifcond, *args):
2385 """A 'with' statement context manager to wrap with start_if()/end_if()
2387 *args: any number of QAPIGenCCode
2389 Example::
2391 with ifcontext(ifcond, self._genh, self._genc):
2392 modify self._genh and self._genc ...
2394 Is equivalent to calling::
2396 self._genh.start_if(ifcond)
2397 self._genc.start_if(ifcond)
2398 modify self._genh and self._genc ...
2399 self._genh.end_if()
2400 self._genc.end_if()
2402 for arg in args:
2403 arg.start_if(ifcond)
2404 yield
2405 for arg in args:
2406 arg.end_if()
2409 class QAPIGenCCode(QAPIGen):
2411 def __init__(self, fname):
2412 QAPIGen.__init__(self, fname)
2413 self._start_if = None
2415 def start_if(self, ifcond):
2416 assert self._start_if is None
2417 self._start_if = (ifcond, self._body, self._preamble)
2419 def end_if(self):
2420 assert self._start_if
2421 self._wrap_ifcond()
2422 self._start_if = None
2424 def _wrap_ifcond(self):
2425 self._body = _wrap_ifcond(self._start_if[0],
2426 self._start_if[1], self._body)
2427 self._preamble = _wrap_ifcond(self._start_if[0],
2428 self._start_if[2], self._preamble)
2430 def get_content(self):
2431 assert self._start_if is None
2432 return QAPIGen.get_content(self)
2435 class QAPIGenC(QAPIGenCCode):
2437 def __init__(self, fname, blurb, pydoc):
2438 QAPIGenCCode.__init__(self, fname)
2439 self._blurb = blurb
2440 self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
2441 re.MULTILINE))
2443 def _top(self):
2444 return mcgen('''
2445 /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
2448 %(blurb)s
2450 * %(copyright)s
2452 * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
2453 * See the COPYING.LIB file in the top-level directory.
2456 ''',
2457 blurb=self._blurb, copyright=self._copyright)
2459 def _bottom(self):
2460 return mcgen('''
2462 /* Dummy declaration to prevent empty .o file */
2463 char qapi_dummy_%(name)s;
2464 ''',
2465 name=c_fname(self.fname))
2468 class QAPIGenH(QAPIGenC):
2470 def _top(self):
2471 return QAPIGenC._top(self) + guardstart(self.fname)
2473 def _bottom(self):
2474 return guardend(self.fname)
2477 class QAPIGenDoc(QAPIGen):
2479 def _top(self):
2480 return (QAPIGen._top(self)
2481 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
2484 class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
2486 def __init__(self, prefix, what, blurb, pydoc):
2487 self._prefix = prefix
2488 self._what = what
2489 self._genc = QAPIGenC(self._prefix + self._what + '.c',
2490 blurb, pydoc)
2491 self._genh = QAPIGenH(self._prefix + self._what + '.h',
2492 blurb, pydoc)
2494 def write(self, output_dir):
2495 self._genc.write(output_dir)
2496 self._genh.write(output_dir)
2499 class QAPISchemaModularCVisitor(QAPISchemaVisitor):
2501 def __init__(self, prefix, what, blurb, pydoc):
2502 self._prefix = prefix
2503 self._what = what
2504 self._blurb = blurb
2505 self._pydoc = pydoc
2506 self._genc = None
2507 self._genh = None
2508 self._module = {}
2509 self._main_module = None
2511 @staticmethod
2512 def _is_user_module(name):
2513 return name and not name.startswith('./')
2515 @staticmethod
2516 def _is_builtin_module(name):
2517 return not name
2519 def _module_dirname(self, what, name):
2520 if self._is_user_module(name):
2521 return os.path.dirname(name)
2522 return ''
2524 def _module_basename(self, what, name):
2525 ret = '' if self._is_builtin_module(name) else self._prefix
2526 if self._is_user_module(name):
2527 basename = os.path.basename(name)
2528 ret += what
2529 if name != self._main_module:
2530 ret += '-' + os.path.splitext(basename)[0]
2531 else:
2532 name = name[2:] if name else 'builtin'
2533 ret += re.sub(r'-', '-' + name + '-', what)
2534 return ret
2536 def _module_filename(self, what, name):
2537 return os.path.join(self._module_dirname(what, name),
2538 self._module_basename(what, name))
2540 def _add_module(self, name, blurb):
2541 basename = self._module_filename(self._what, name)
2542 genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
2543 genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
2544 self._module[name] = (genc, genh)
2545 self._set_module(name)
2547 def _add_user_module(self, name, blurb):
2548 assert self._is_user_module(name)
2549 if self._main_module is None:
2550 self._main_module = name
2551 self._add_module(name, blurb)
2553 def _add_system_module(self, name, blurb):
2554 self._add_module(name and './' + name, blurb)
2556 def _set_module(self, name):
2557 self._genc, self._genh = self._module[name]
2559 def write(self, output_dir, opt_builtins=False):
2560 for name in self._module:
2561 if self._is_builtin_module(name) and not opt_builtins:
2562 continue
2563 (genc, genh) = self._module[name]
2564 genc.write(output_dir)
2565 genh.write(output_dir)
2567 def _begin_user_module(self, name):
2568 pass
2570 def visit_module(self, name):
2571 if name in self._module:
2572 self._set_module(name)
2573 elif self._is_builtin_module(name):
2574 # The built-in module has not been created. No code may
2575 # be generated.
2576 self._genc = None
2577 self._genh = None
2578 else:
2579 self._add_user_module(name, self._blurb)
2580 self._begin_user_module(name)
2582 def visit_include(self, name, info):
2583 relname = os.path.relpath(self._module_filename(self._what, name),
2584 os.path.dirname(self._genh.fname))
2585 self._genh.preamble_add(mcgen('''
2586 #include "%(relname)s.h"
2587 ''',
2588 relname=relname))