2011-07-19 23:50:39 +04:00
|
|
|
#
|
|
|
|
# QAPI helper library
|
|
|
|
#
|
|
|
|
# Copyright IBM, Corp. 2011
|
2018-02-26 22:19:40 +03:00
|
|
|
# Copyright (c) 2013-2018 Red Hat Inc.
|
2011-07-19 23:50:39 +04:00
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
2011-07-19 23:50:39 +04:00
|
|
|
#
|
2014-03-01 11:40:34 +04:00
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
|
|
# See the COPYING file in the top-level directory.
|
2011-07-19 23:50:39 +04:00
|
|
|
|
2018-01-16 16:42:04 +03:00
|
|
|
from __future__ import print_function
|
2018-07-03 18:56:40 +03:00
|
|
|
from contextlib import contextmanager
|
2015-04-02 15:46:39 +03:00
|
|
|
import errno
|
2014-05-02 17:52:35 +04:00
|
|
|
import os
|
2017-03-15 15:57:35 +03:00
|
|
|
import re
|
2015-05-14 15:50:47 +03:00
|
|
|
import string
|
2018-06-18 20:59:57 +03:00
|
|
|
import sys
|
2018-06-08 20:52:52 +03:00
|
|
|
from collections import OrderedDict
|
2011-07-19 23:50:39 +04:00
|
|
|
|
2015-05-04 18:05:00 +03:00
|
|
|
builtin_types = {
|
2017-06-26 20:25:14 +03:00
|
|
|
'null': 'QTYPE_QNULL',
|
2013-07-08 18:14:21 +04:00
|
|
|
'str': 'QTYPE_QSTRING',
|
2017-06-07 19:35:58 +03:00
|
|
|
'int': 'QTYPE_QNUM',
|
|
|
|
'number': 'QTYPE_QNUM',
|
2013-07-08 18:14:21 +04:00
|
|
|
'bool': 'QTYPE_QBOOL',
|
2017-06-07 19:35:58 +03:00
|
|
|
'int8': 'QTYPE_QNUM',
|
|
|
|
'int16': 'QTYPE_QNUM',
|
|
|
|
'int32': 'QTYPE_QNUM',
|
|
|
|
'int64': 'QTYPE_QNUM',
|
|
|
|
'uint8': 'QTYPE_QNUM',
|
|
|
|
'uint16': 'QTYPE_QNUM',
|
|
|
|
'uint32': 'QTYPE_QNUM',
|
|
|
|
'uint64': 'QTYPE_QNUM',
|
|
|
|
'size': 'QTYPE_QNUM',
|
2015-12-02 08:20:46 +03:00
|
|
|
'any': None, # any QType possible, actually
|
qapi: Convert QType into QAPI built-in enum type
What's more meta than using qapi to define qapi? :)
Convert QType into a full-fledged[*] builtin qapi enum type, so
that a subsequent patch can then use it as the discriminator
type of qapi alternate types. Fortunately, the judicious use of
'prefix' in the qapi definition avoids churn to the spelling of
the enum constants.
To avoid circular definitions, we have to flip the order of
inclusion between "qobject.h" vs. "qapi-types.h". Back in commit
28770e0, we had the latter include the former, so that we could
use 'QObject *' for our implementation of 'any'. But that usage
also works with only a forward declaration, whereas the
definition of QObject requires QType to be a complete type.
[*] The type has to be builtin, rather than declared in
qapi/common.json, because we want to use it for alternates even
when common.json is not included. But since it is the first
builtin enum type, we have to add special cases to qapi-types
and qapi-visit to only emit definitions once, even when two
qapi files are being compiled into the same binary (the way we
already handled builtin list types like 'intList'). We may
need to revisit how multiple qapi files share common types,
but that's a project for another day.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-4-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:47 +03:00
|
|
|
'QType': 'QTYPE_QSTRING',
|
2013-07-08 18:14:21 +04:00
|
|
|
}
|
|
|
|
|
2017-03-15 15:56:51 +03:00
|
|
|
# Are documentation comments required?
|
|
|
|
doc_required = False
|
|
|
|
|
2015-05-04 18:05:23 +03:00
|
|
|
# Whitelist of commands allowed to return a non-dictionary
|
2017-03-15 15:56:54 +03:00
|
|
|
returns_whitelist = []
|
2015-05-04 18:05:23 +03:00
|
|
|
|
qapi: Enforce (or whitelist) case conventions on qapi members
We document that members of enums and objects should be
'lower-case', although we were not enforcing it. We have to
whitelist a few pre-existing entities that violate the norms.
Add three new tests to expose the new error message, each of
which first uses the whitelisted name 'UuidInfo' to prove the
whitelist works, then triggers the failure (this is the same
pattern used in the existing returns-whitelist.json test).
Note that by adding this check, we have effectively forbidden
an entity with a case-insensitive clash of member names, for
any entity that is not on the whitelist (although there is
still the possibility to clash via '-' vs. '_').
Not done here: a future patch should also add naming convention
support and whitelist exceptions for command, event, and type
names.
The additions to QAPISchemaMember.check_clash() check whether
info['name'] is in the whitelist (the top-most entity name at
the point 'info' tracks), rather than self.owner (the type,
possibly implicit, that directly owns the member), because it
is easier to maintain the whitelist by the names actually in
the user's .json file, rather than worrying about the names
of implicit types.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-14-git-send-email-eblake@redhat.com>
[Simplified a bit as per discussion with Eric]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:57 +03:00
|
|
|
# Whitelist of entities allowed to violate case conventions
|
2017-03-15 15:56:55 +03:00
|
|
|
name_case_whitelist = []
|
qapi: Enforce (or whitelist) case conventions on qapi members
We document that members of enums and objects should be
'lower-case', although we were not enforcing it. We have to
whitelist a few pre-existing entities that violate the norms.
Add three new tests to expose the new error message, each of
which first uses the whitelisted name 'UuidInfo' to prove the
whitelist works, then triggers the failure (this is the same
pattern used in the existing returns-whitelist.json test).
Note that by adding this check, we have effectively forbidden
an entity with a case-insensitive clash of member names, for
any entity that is not on the whitelist (although there is
still the possibility to clash via '-' vs. '_').
Not done here: a future patch should also add naming convention
support and whitelist exceptions for command, event, and type
names.
The additions to QAPISchemaMember.check_clash() check whether
info['name'] is in the whitelist (the top-most entity name at
the point 'info' tracks), rather than self.owner (the type,
possibly implicit, that directly owns the member), because it
is easier to maintain the whitelist by the names actually in
the user's .json file, rather than worrying about the names
of implicit types.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-14-git-send-email-eblake@redhat.com>
[Simplified a bit as per discussion with Eric]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:57 +03:00
|
|
|
|
2017-03-15 15:57:31 +03:00
|
|
|
enum_types = {}
|
2017-03-15 15:57:32 +03:00
|
|
|
struct_types = {}
|
2017-03-15 15:57:33 +03:00
|
|
|
union_types = {}
|
2015-05-04 18:05:17 +03:00
|
|
|
all_names = {}
|
|
|
|
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
|
|
|
# Parsing the schema into expressions
|
|
|
|
#
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2014-05-07 22:46:15 +04:00
|
|
|
def error_path(parent):
|
2017-03-15 15:57:08 +03:00
|
|
|
res = ''
|
2014-05-07 22:46:15 +04:00
|
|
|
while parent:
|
2017-03-15 15:57:08 +03:00
|
|
|
res = ('In file included from %s:%d:\n' % (parent['file'],
|
2014-05-07 22:46:15 +04:00
|
|
|
parent['line'])) + res
|
|
|
|
parent = parent['parent']
|
|
|
|
return res
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
class QAPIError(Exception):
|
|
|
|
def __init__(self, fname, line, col, incl_info, msg):
|
2015-09-30 01:21:01 +03:00
|
|
|
Exception.__init__(self)
|
2017-01-13 17:41:25 +03:00
|
|
|
self.fname = fname
|
|
|
|
self.line = line
|
|
|
|
self.col = col
|
|
|
|
self.info = incl_info
|
2013-07-27 19:41:56 +04:00
|
|
|
self.msg = msg
|
|
|
|
|
|
|
|
def __str__(self):
|
2017-03-15 15:57:08 +03:00
|
|
|
loc = '%s:%d' % (self.fname, self.line)
|
2017-01-13 17:41:25 +03:00
|
|
|
if self.col is not None:
|
2017-03-15 15:57:08 +03:00
|
|
|
loc += ':%s' % self.col
|
|
|
|
return error_path(self.info) + '%s: %s' % (loc, self.msg)
|
2013-07-27 19:41:56 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
class QAPIParseError(QAPIError):
|
|
|
|
def __init__(self, parser, msg):
|
|
|
|
col = 1
|
|
|
|
for ch in parser.src[parser.line_pos:parser.pos]:
|
|
|
|
if ch == '\t':
|
|
|
|
col = (col + 7) % 8 + 1
|
|
|
|
else:
|
|
|
|
col += 1
|
|
|
|
QAPIError.__init__(self, parser.fname, parser.line, col,
|
|
|
|
parser.incl_info, msg)
|
2014-03-05 06:44:34 +04:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
|
|
|
|
class QAPISemError(QAPIError):
|
|
|
|
def __init__(self, info, msg):
|
|
|
|
QAPIError.__init__(self, info['file'], info['line'], None,
|
|
|
|
info['parent'], msg)
|
2014-03-05 06:44:34 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
class QAPIDoc(object):
|
2019-06-06 18:38:03 +03:00
|
|
|
"""
|
2019-09-13 23:13:49 +03:00
|
|
|
A documentation comment block, either definition or free-form
|
2019-06-06 18:38:03 +03:00
|
|
|
|
2019-09-13 23:13:49 +03:00
|
|
|
Definition documentation blocks consist of
|
2019-06-06 18:38:03 +03:00
|
|
|
|
2019-09-13 23:13:49 +03:00
|
|
|
* a body section: one line naming the definition, followed by an
|
2019-06-06 18:38:03 +03:00
|
|
|
overview (any number of lines)
|
|
|
|
|
|
|
|
* argument sections: a description of each argument (for commands
|
|
|
|
and events) or member (for structs, unions and alternates)
|
|
|
|
|
|
|
|
* features sections: a description of each feature flag
|
|
|
|
|
|
|
|
* additional (non-argument) sections, possibly tagged
|
|
|
|
|
|
|
|
Free-form documentation blocks consist only of a body section.
|
|
|
|
"""
|
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
class Section(object):
|
|
|
|
def __init__(self, name=None):
|
|
|
|
# optional section name (argument/member or section name)
|
|
|
|
self.name = name
|
|
|
|
# the list of lines for this section
|
2017-10-02 17:13:38 +03:00
|
|
|
self.text = ''
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
|
|
|
def append(self, line):
|
2017-10-02 17:13:38 +03:00
|
|
|
self.text += line.rstrip() + '\n'
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
|
|
|
class ArgSection(Section):
|
2017-03-15 15:57:03 +03:00
|
|
|
def __init__(self, name):
|
|
|
|
QAPIDoc.Section.__init__(self, name)
|
|
|
|
self.member = None
|
|
|
|
|
|
|
|
def connect(self, member):
|
|
|
|
self.member = member
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
|
|
|
def __init__(self, parser, info):
|
2017-10-02 17:13:40 +03:00
|
|
|
# self._parser is used to report errors with QAPIParseError. The
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
# resulting error position depends on the state of the parser.
|
|
|
|
# It happens to be the beginning of the comment. More or less
|
|
|
|
# servicable, but action at a distance.
|
2017-10-02 17:13:40 +03:00
|
|
|
self._parser = parser
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
self.info = info
|
|
|
|
self.symbol = None
|
|
|
|
self.body = QAPIDoc.Section()
|
|
|
|
# dict mapping parameter name to ArgSection
|
|
|
|
self.args = OrderedDict()
|
2019-06-06 18:38:01 +03:00
|
|
|
self.features = OrderedDict()
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
# a list of Section
|
|
|
|
self.sections = []
|
|
|
|
# the current section
|
2017-10-02 17:13:40 +03:00
|
|
|
self._section = self.body
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_body_line
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
|
|
|
def has_section(self, name):
|
|
|
|
"""Return True if we have a section with this name."""
|
|
|
|
for i in self.sections:
|
|
|
|
if i.name == name:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def append(self, line):
|
2019-06-06 18:38:00 +03:00
|
|
|
"""
|
|
|
|
Parse a comment line and add it to the documentation.
|
|
|
|
|
|
|
|
The way that the line is dealt with depends on which part of
|
|
|
|
the documentation we're parsing right now:
|
2019-06-06 18:38:03 +03:00
|
|
|
* The body section: ._append_line is ._append_body_line
|
|
|
|
* An argument section: ._append_line is ._append_args_line
|
|
|
|
* A features section: ._append_line is ._append_features_line
|
|
|
|
* An additional section: ._append_line is ._append_various_line
|
2019-06-06 18:38:00 +03:00
|
|
|
"""
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
line = line[1:]
|
|
|
|
if not line:
|
|
|
|
self._append_freeform(line)
|
|
|
|
return
|
|
|
|
|
|
|
|
if line[0] != ' ':
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser, "Missing space after #")
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
line = line[1:]
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line(line)
|
2019-06-06 18:38:00 +03:00
|
|
|
|
|
|
|
def end_comment(self):
|
|
|
|
self._end_section()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _is_section_tag(name):
|
|
|
|
return name in ('Returns:', 'Since:',
|
|
|
|
# those are often singular or plural
|
|
|
|
'Note:', 'Notes:',
|
|
|
|
'Example:', 'Examples:',
|
|
|
|
'TODO:')
|
|
|
|
|
|
|
|
def _append_body_line(self, line):
|
2019-06-06 18:38:03 +03:00
|
|
|
"""
|
|
|
|
Process a line of documentation text in the body section.
|
|
|
|
|
|
|
|
If this a symbol line and it is the section's first line, this
|
2019-09-13 23:13:49 +03:00
|
|
|
is a definition documentation block for that symbol.
|
2019-06-06 18:38:03 +03:00
|
|
|
|
2019-09-13 23:13:49 +03:00
|
|
|
If it's a definition documentation block, another symbol line
|
2019-06-06 18:38:03 +03:00
|
|
|
begins the argument section for the argument named by it, and
|
|
|
|
a section tag begins an additional section. Start that
|
|
|
|
section and append the line to it.
|
|
|
|
|
|
|
|
Else, append the line to the current section.
|
|
|
|
"""
|
2019-06-06 18:38:00 +03:00
|
|
|
name = line.split(' ', 1)[0]
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
# FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
|
|
|
|
# recognized, and get silently treated as ordinary text
|
2019-06-06 18:38:00 +03:00
|
|
|
if not self.symbol and not self.body.text and line.startswith('@'):
|
2017-03-15 15:57:08 +03:00
|
|
|
if not line.endswith(':'):
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self._parser, "Line should end with ':'")
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
self.symbol = line[1:-1]
|
|
|
|
# FIXME invalid names other than the empty string aren't flagged
|
|
|
|
if not self.symbol:
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser, "Invalid name")
|
2019-06-06 18:38:00 +03:00
|
|
|
elif self.symbol:
|
2019-09-13 23:13:49 +03:00
|
|
|
# This is a definition documentation block
|
2019-06-06 18:38:00 +03:00
|
|
|
if name.startswith('@') and name.endswith(':'):
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_args_line
|
2019-06-06 18:38:00 +03:00
|
|
|
self._append_args_line(line)
|
2019-06-06 18:38:01 +03:00
|
|
|
elif line == 'Features:':
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_features_line
|
2019-06-06 18:38:00 +03:00
|
|
|
elif self._is_section_tag(name):
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_various_line
|
2019-06-06 18:38:00 +03:00
|
|
|
self._append_various_line(line)
|
|
|
|
else:
|
|
|
|
self._append_freeform(line.strip())
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
else:
|
2019-06-06 18:38:03 +03:00
|
|
|
# This is a free-form documentation block
|
2019-06-06 18:38:00 +03:00
|
|
|
self._append_freeform(line.strip())
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2019-06-06 18:38:00 +03:00
|
|
|
def _append_args_line(self, line):
|
2019-06-06 18:38:03 +03:00
|
|
|
"""
|
|
|
|
Process a line of documentation text in an argument section.
|
|
|
|
|
|
|
|
A symbol line begins the next argument section, a section tag
|
|
|
|
section or a non-indented line after a blank line begins an
|
|
|
|
additional section. Start that section and append the line to
|
|
|
|
it.
|
|
|
|
|
|
|
|
Else, append the line to the current section.
|
|
|
|
|
|
|
|
"""
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
name = line.split(' ', 1)[0]
|
|
|
|
|
2017-03-15 15:57:08 +03:00
|
|
|
if name.startswith('@') and name.endswith(':'):
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
line = line[len(name)+1:]
|
|
|
|
self._start_args_section(name[1:-1])
|
2019-06-06 18:38:00 +03:00
|
|
|
elif self._is_section_tag(name):
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_various_line
|
2019-06-06 18:38:00 +03:00
|
|
|
self._append_various_line(line)
|
|
|
|
return
|
2019-06-06 18:38:01 +03:00
|
|
|
elif (self._section.text.endswith('\n\n')
|
|
|
|
and line and not line[0].isspace()):
|
|
|
|
if line == 'Features:':
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_features_line
|
2019-06-06 18:38:01 +03:00
|
|
|
else:
|
|
|
|
self._start_section()
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_various_line
|
2019-06-06 18:38:01 +03:00
|
|
|
self._append_various_line(line)
|
|
|
|
return
|
|
|
|
|
|
|
|
self._append_freeform(line.strip())
|
|
|
|
|
|
|
|
def _append_features_line(self, line):
|
|
|
|
name = line.split(' ', 1)[0]
|
|
|
|
|
|
|
|
if name.startswith('@') and name.endswith(':'):
|
|
|
|
line = line[len(name)+1:]
|
|
|
|
self._start_features_section(name[1:-1])
|
|
|
|
elif self._is_section_tag(name):
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_various_line
|
2019-06-06 18:38:01 +03:00
|
|
|
self._append_various_line(line)
|
|
|
|
return
|
2019-06-06 18:38:00 +03:00
|
|
|
elif (self._section.text.endswith('\n\n')
|
|
|
|
and line and not line[0].isspace()):
|
|
|
|
self._start_section()
|
2019-06-06 18:38:03 +03:00
|
|
|
self._append_line = self._append_various_line
|
2019-06-06 18:38:00 +03:00
|
|
|
self._append_various_line(line)
|
|
|
|
return
|
|
|
|
|
|
|
|
self._append_freeform(line.strip())
|
|
|
|
|
|
|
|
def _append_various_line(self, line):
|
2019-06-06 18:38:03 +03:00
|
|
|
"""
|
|
|
|
Process a line of documentation text in an additional section.
|
|
|
|
|
|
|
|
A symbol line is an error.
|
|
|
|
|
|
|
|
A section tag begins an additional section. Start that
|
|
|
|
section and append the line to it.
|
|
|
|
|
|
|
|
Else, append the line to the current section.
|
|
|
|
"""
|
2019-06-06 18:38:00 +03:00
|
|
|
name = line.split(' ', 1)[0]
|
|
|
|
|
|
|
|
if name.startswith('@') and name.endswith(':'):
|
|
|
|
raise QAPIParseError(self._parser,
|
|
|
|
"'%s' can't follow '%s' section"
|
|
|
|
% (name, self.sections[0].name))
|
|
|
|
elif self._is_section_tag(name):
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
line = line[len(name)+1:]
|
|
|
|
self._start_section(name[:-1])
|
|
|
|
|
2019-06-06 18:38:00 +03:00
|
|
|
if (not self._section.name or
|
|
|
|
not self._section.name.startswith('Example')):
|
|
|
|
line = line.strip()
|
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
self._append_freeform(line)
|
|
|
|
|
2019-06-06 18:38:01 +03:00
|
|
|
def _start_symbol_section(self, symbols_dict, name):
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
# FIXME invalid names other than the empty string aren't flagged
|
|
|
|
if not name:
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser, "Invalid parameter name")
|
2019-06-06 18:38:01 +03:00
|
|
|
if name in symbols_dict:
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser,
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
"'%s' parameter name duplicated" % name)
|
2019-06-06 18:38:00 +03:00
|
|
|
assert not self.sections
|
2017-03-15 15:57:23 +03:00
|
|
|
self._end_section()
|
2017-10-02 17:13:40 +03:00
|
|
|
self._section = QAPIDoc.ArgSection(name)
|
2019-06-06 18:38:01 +03:00
|
|
|
symbols_dict[name] = self._section
|
|
|
|
|
|
|
|
def _start_args_section(self, name):
|
|
|
|
self._start_symbol_section(self.args, name)
|
|
|
|
|
|
|
|
def _start_features_section(self, name):
|
|
|
|
self._start_symbol_section(self.features, name)
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2017-10-02 17:13:37 +03:00
|
|
|
def _start_section(self, name=None):
|
2017-03-15 15:57:08 +03:00
|
|
|
if name in ('Returns', 'Since') and self.has_section(name):
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser,
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
"Duplicated '%s' section" % name)
|
2017-03-15 15:57:23 +03:00
|
|
|
self._end_section()
|
2017-10-02 17:13:40 +03:00
|
|
|
self._section = QAPIDoc.Section(name)
|
|
|
|
self.sections.append(self._section)
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2017-03-15 15:57:23 +03:00
|
|
|
def _end_section(self):
|
2017-10-02 17:13:40 +03:00
|
|
|
if self._section:
|
|
|
|
text = self._section.text = self._section.text.strip()
|
|
|
|
if self._section.name and (not text or text.isspace()):
|
|
|
|
raise QAPIParseError(self._parser, "Empty doc section '%s'"
|
|
|
|
% self._section.name)
|
|
|
|
self._section = None
|
2017-03-15 15:57:23 +03:00
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
def _append_freeform(self, line):
|
2017-03-15 15:57:22 +03:00
|
|
|
match = re.match(r'(@\S+:)', line)
|
|
|
|
if match:
|
2017-10-02 17:13:40 +03:00
|
|
|
raise QAPIParseError(self._parser,
|
2017-03-15 15:57:22 +03:00
|
|
|
"'%s' not allowed in free-form documentation"
|
|
|
|
% match.group(1))
|
2017-10-02 17:13:40 +03:00
|
|
|
self._section.append(line)
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def connect_member(self, member):
|
|
|
|
if member.name not in self.args:
|
|
|
|
# Undocumented TODO outlaw
|
2017-03-15 15:57:04 +03:00
|
|
|
self.args[member.name] = QAPIDoc.ArgSection(member.name)
|
|
|
|
self.args[member.name].connect(member)
|
2017-03-15 15:57:03 +03:00
|
|
|
|
2017-03-15 15:57:27 +03:00
|
|
|
def check_expr(self, expr):
|
|
|
|
if self.has_section('Returns') and 'command' not in expr:
|
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"'Returns:' is only valid for commands")
|
|
|
|
|
2017-03-15 15:57:26 +03:00
|
|
|
def check(self):
|
2018-01-16 16:42:05 +03:00
|
|
|
bogus = [name for name, section in self.args.items()
|
2017-03-15 15:57:26 +03:00
|
|
|
if not section.member]
|
|
|
|
if bogus:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"The following documented members are not in "
|
|
|
|
"the declaration: %s" % ", ".join(bogus))
|
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2015-09-16 14:06:04 +03:00
|
|
|
class QAPISchemaParser(object):
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
def __init__(self, fp, previously_included=[], incl_info=None):
|
2017-10-02 17:13:33 +03:00
|
|
|
self.fname = fp.name
|
2018-02-11 12:35:49 +03:00
|
|
|
previously_included.append(os.path.abspath(fp.name))
|
2015-06-09 17:22:45 +03:00
|
|
|
self.incl_info = incl_info
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.src = fp.read()
|
|
|
|
if self.src == '' or self.src[-1] != '\n':
|
|
|
|
self.src += '\n'
|
|
|
|
self.cursor = 0
|
2014-03-05 06:44:33 +04:00
|
|
|
self.line = 1
|
|
|
|
self.line_pos = 0
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.exprs = []
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
self.docs = []
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.accept()
|
2017-10-02 17:13:34 +03:00
|
|
|
cur_doc = None
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
while self.tok is not None:
|
2017-10-02 17:13:33 +03:00
|
|
|
info = {'file': self.fname, 'line': self.line,
|
2017-01-13 17:41:25 +03:00
|
|
|
'parent': self.incl_info}
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
if self.tok == '#':
|
2017-10-02 17:13:34 +03:00
|
|
|
self.reject_expr_doc(cur_doc)
|
|
|
|
cur_doc = self.get_doc(info)
|
|
|
|
self.docs.append(cur_doc)
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
continue
|
|
|
|
|
2014-05-07 22:46:15 +04:00
|
|
|
expr = self.get_expr(False)
|
2017-03-15 15:56:50 +03:00
|
|
|
if 'include' in expr:
|
2017-10-02 17:13:34 +03:00
|
|
|
self.reject_expr_doc(cur_doc)
|
2014-05-07 22:46:15 +04:00
|
|
|
if len(expr) != 1:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "Invalid 'include' directive")
|
2017-03-15 15:57:08 +03:00
|
|
|
include = expr['include']
|
2014-05-07 22:46:15 +04:00
|
|
|
if not isinstance(include, str):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Value of 'include' must be a string")
|
2018-02-11 12:35:53 +03:00
|
|
|
incl_fname = os.path.join(os.path.dirname(self.fname),
|
|
|
|
include)
|
|
|
|
self.exprs.append({'expr': {'include': incl_fname},
|
|
|
|
'info': info})
|
|
|
|
exprs_include = self._include(include, info, incl_fname,
|
2018-02-11 12:35:52 +03:00
|
|
|
previously_included)
|
|
|
|
if exprs_include:
|
|
|
|
self.exprs.extend(exprs_include.exprs)
|
|
|
|
self.docs.extend(exprs_include.docs)
|
2017-03-15 15:56:51 +03:00
|
|
|
elif "pragma" in expr:
|
2017-10-02 17:13:34 +03:00
|
|
|
self.reject_expr_doc(cur_doc)
|
2017-03-15 15:56:51 +03:00
|
|
|
if len(expr) != 1:
|
|
|
|
raise QAPISemError(info, "Invalid 'pragma' directive")
|
|
|
|
pragma = expr['pragma']
|
|
|
|
if not isinstance(pragma, dict):
|
|
|
|
raise QAPISemError(
|
2019-09-13 23:13:49 +03:00
|
|
|
info, "Value of 'pragma' must be an object")
|
2018-01-16 16:42:05 +03:00
|
|
|
for name, value in pragma.items():
|
2017-03-15 15:56:51 +03:00
|
|
|
self._pragma(name, value, info)
|
2014-05-07 22:46:15 +04:00
|
|
|
else:
|
|
|
|
expr_elem = {'expr': expr,
|
2017-01-13 17:41:25 +03:00
|
|
|
'info': info}
|
2017-10-02 17:13:34 +03:00
|
|
|
if cur_doc:
|
|
|
|
if not cur_doc.symbol:
|
2017-03-15 15:57:20 +03:00
|
|
|
raise QAPISemError(
|
2019-09-13 23:13:49 +03:00
|
|
|
cur_doc.info, "Definition documentation required")
|
2017-10-02 17:13:34 +03:00
|
|
|
expr_elem['doc'] = cur_doc
|
2014-05-07 22:46:15 +04:00
|
|
|
self.exprs.append(expr_elem)
|
2017-10-02 17:13:34 +03:00
|
|
|
cur_doc = None
|
|
|
|
self.reject_expr_doc(cur_doc)
|
2017-03-15 15:57:20 +03:00
|
|
|
|
2017-10-02 17:13:34 +03:00
|
|
|
@staticmethod
|
|
|
|
def reject_expr_doc(doc):
|
|
|
|
if doc and doc.symbol:
|
2017-03-15 15:57:20 +03:00
|
|
|
raise QAPISemError(
|
2017-10-02 17:13:34 +03:00
|
|
|
doc.info,
|
2017-03-15 15:57:20 +03:00
|
|
|
"Documentation for '%s' is not followed by the definition"
|
2017-10-02 17:13:34 +03:00
|
|
|
% doc.symbol)
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
2018-02-11 12:35:53 +03:00
|
|
|
def _include(self, include, info, incl_fname, previously_included):
|
2018-02-11 12:35:49 +03:00
|
|
|
incl_abs_fname = os.path.abspath(incl_fname)
|
2017-03-15 15:56:50 +03:00
|
|
|
# catch inclusion cycle
|
|
|
|
inf = info
|
|
|
|
while inf:
|
|
|
|
if incl_abs_fname == os.path.abspath(inf['file']):
|
|
|
|
raise QAPISemError(info, "Inclusion loop for %s" % include)
|
|
|
|
inf = inf['parent']
|
|
|
|
|
|
|
|
# skip multiple include of the same file
|
|
|
|
if incl_abs_fname in previously_included:
|
2018-02-11 12:35:52 +03:00
|
|
|
return None
|
|
|
|
|
2017-03-15 15:56:50 +03:00
|
|
|
try:
|
2018-06-18 20:59:57 +03:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
fobj = open(incl_fname, 'r', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
fobj = open(incl_fname, 'r')
|
2017-03-15 15:56:50 +03:00
|
|
|
except IOError as e:
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPISemError(info, "%s: %s" % (e.strerror, incl_fname))
|
2018-02-11 12:35:52 +03:00
|
|
|
return QAPISchemaParser(fobj, previously_included, info)
|
2017-03-15 15:56:50 +03:00
|
|
|
|
2017-03-15 15:56:51 +03:00
|
|
|
def _pragma(self, name, value, info):
|
2017-03-15 15:56:55 +03:00
|
|
|
global doc_required, returns_whitelist, name_case_whitelist
|
2017-03-15 15:56:51 +03:00
|
|
|
if name == 'doc-required':
|
|
|
|
if not isinstance(value, bool):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma 'doc-required' must be boolean")
|
|
|
|
doc_required = value
|
2017-03-15 15:56:54 +03:00
|
|
|
elif name == 'returns-whitelist':
|
|
|
|
if (not isinstance(value, list)
|
|
|
|
or any([not isinstance(elt, str) for elt in value])):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma returns-whitelist must be"
|
|
|
|
" a list of strings")
|
|
|
|
returns_whitelist = value
|
2017-03-15 15:56:55 +03:00
|
|
|
elif name == 'name-case-whitelist':
|
|
|
|
if (not isinstance(value, list)
|
|
|
|
or any([not isinstance(elt, str) for elt in value])):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma name-case-whitelist must be"
|
|
|
|
" a list of strings")
|
|
|
|
name_case_whitelist = value
|
2017-03-15 15:56:51 +03:00
|
|
|
else:
|
|
|
|
raise QAPISemError(info, "Unknown pragma '%s'" % name)
|
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
def accept(self, skip_comment=True):
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
while True:
|
|
|
|
self.tok = self.src[self.cursor]
|
2013-07-27 19:41:56 +04:00
|
|
|
self.pos = self.cursor
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.cursor += 1
|
|
|
|
self.val = None
|
|
|
|
|
2013-07-27 19:42:01 +04:00
|
|
|
if self.tok == '#':
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
if self.src[self.cursor] == '#':
|
|
|
|
# Start of doc comment
|
|
|
|
skip_comment = False
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.cursor = self.src.find('\n', self.cursor)
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
if not skip_comment:
|
|
|
|
self.val = self.src[self.pos:self.cursor]
|
|
|
|
return
|
2017-03-15 15:57:08 +03:00
|
|
|
elif self.tok in '{}:,[]':
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
return
|
|
|
|
elif self.tok == "'":
|
2019-09-13 23:13:39 +03:00
|
|
|
# Note: we accept only printable ASCII
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
string = ''
|
|
|
|
esc = False
|
|
|
|
while True:
|
|
|
|
ch = self.src[self.cursor]
|
|
|
|
self.cursor += 1
|
|
|
|
if ch == '\n':
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Missing terminating \"'\"")
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
if esc:
|
2019-09-13 23:13:40 +03:00
|
|
|
# Note: we recognize only \\ because we have
|
|
|
|
# no use for funny characters in strings
|
|
|
|
if ch != '\\':
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPIParseError(self,
|
|
|
|
"Unknown escape \\%s" % ch)
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
esc = False
|
2017-03-15 15:57:08 +03:00
|
|
|
elif ch == '\\':
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
esc = True
|
2019-09-13 23:13:39 +03:00
|
|
|
continue
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
elif ch == "'":
|
|
|
|
self.val = string
|
|
|
|
return
|
2019-09-13 23:13:39 +03:00
|
|
|
if ord(ch) < 32 or ord(ch) >= 127:
|
|
|
|
raise QAPIParseError(
|
|
|
|
self, "Funny character in string")
|
|
|
|
string += ch
|
2017-03-15 15:57:08 +03:00
|
|
|
elif self.src.startswith('true', self.pos):
|
2015-06-10 09:24:58 +03:00
|
|
|
self.val = True
|
|
|
|
self.cursor += 3
|
|
|
|
return
|
2017-03-15 15:57:08 +03:00
|
|
|
elif self.src.startswith('false', self.pos):
|
2015-06-10 09:24:58 +03:00
|
|
|
self.val = False
|
|
|
|
self.cursor += 4
|
|
|
|
return
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
elif self.tok == '\n':
|
|
|
|
if self.cursor == len(self.src):
|
|
|
|
self.tok = None
|
|
|
|
return
|
2014-03-05 06:44:33 +04:00
|
|
|
self.line += 1
|
|
|
|
self.line_pos = self.cursor
|
2013-07-27 19:41:57 +04:00
|
|
|
elif not self.tok.isspace():
|
2019-09-14 18:34:55 +03:00
|
|
|
# Show up to next structural, whitespace or quote
|
|
|
|
# character
|
|
|
|
match = re.match('[^[\\]{}:,\\s\'"]+',
|
|
|
|
self.src[self.cursor-1:])
|
|
|
|
raise QAPIParseError(self, "Stray '%s'" % match.group(0))
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
|
|
|
def get_members(self):
|
|
|
|
expr = OrderedDict()
|
2013-07-27 19:41:58 +04:00
|
|
|
if self.tok == '}':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
|
|
|
if self.tok != "'":
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected string or '}'")
|
2013-07-27 19:41:58 +04:00
|
|
|
while True:
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
key = self.val
|
|
|
|
self.accept()
|
2013-07-27 19:41:58 +04:00
|
|
|
if self.tok != ':':
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected ':'")
|
2013-07-27 19:41:58 +04:00
|
|
|
self.accept()
|
2014-03-05 06:44:32 +04:00
|
|
|
if key in expr:
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Duplicate key '%s'" % key)
|
2013-07-27 19:41:59 +04:00
|
|
|
expr[key] = self.get_expr(True)
|
2013-07-27 19:41:58 +04:00
|
|
|
if self.tok == '}':
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.accept()
|
2013-07-27 19:41:58 +04:00
|
|
|
return expr
|
|
|
|
if self.tok != ',':
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected ',' or '}'")
|
2013-07-27 19:41:58 +04:00
|
|
|
self.accept()
|
|
|
|
if self.tok != "'":
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected string")
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
|
|
|
def get_values(self):
|
|
|
|
expr = []
|
2013-07-27 19:41:58 +04:00
|
|
|
if self.tok == ']':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
2015-09-30 01:21:02 +03:00
|
|
|
if self.tok not in "{['tfn":
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(
|
|
|
|
self, "Expected '{', '[', ']', string, boolean or 'null'")
|
2013-07-27 19:41:58 +04:00
|
|
|
while True:
|
2013-07-27 19:41:59 +04:00
|
|
|
expr.append(self.get_expr(True))
|
2013-07-27 19:41:58 +04:00
|
|
|
if self.tok == ']':
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
self.accept()
|
2013-07-27 19:41:58 +04:00
|
|
|
return expr
|
|
|
|
if self.tok != ',':
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected ',' or ']'")
|
2013-07-27 19:41:58 +04:00
|
|
|
self.accept()
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
|
2013-07-27 19:41:59 +04:00
|
|
|
def get_expr(self, nested):
|
|
|
|
if self.tok != '{' and not nested:
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(self, "Expected '{'")
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
if self.tok == '{':
|
|
|
|
self.accept()
|
|
|
|
expr = self.get_members()
|
|
|
|
elif self.tok == '[':
|
|
|
|
self.accept()
|
|
|
|
expr = self.get_values()
|
qapi: Allow true, false and null in schema json
In the near term, we will use it for a sensible-looking
'gen':false inside command declarations, instead of the
current ugly 'gen':'no'.
In the long term, it will allow conversion from shorthand
with defaults mentioned only in side-band documentation:
'data':{'*flag':'bool', '*string':'str'}
into an explicit default value documentation, as in:
'data':{'flag':{'type':'bool', 'optional':true, 'default':true},
'string':{'type':'str', 'optional':true, 'default':null}}
We still don't parse integer values (also necessary before
we can allow explicit defaults), but that can come in a later
series.
Update the testsuite to match an improved error message.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-05-04 18:05:18 +03:00
|
|
|
elif self.tok in "'tfn":
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
expr = self.val
|
|
|
|
self.accept()
|
2013-07-27 19:41:58 +04:00
|
|
|
else:
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPIParseError(
|
|
|
|
self, "Expected '{', '[', string, boolean or 'null'")
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 19:41:55 +04:00
|
|
|
return expr
|
2013-07-01 18:31:50 +04:00
|
|
|
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
def get_doc(self, info):
|
|
|
|
if self.val != '##':
|
|
|
|
raise QAPIParseError(self, "Junk after '##' at start of "
|
|
|
|
"documentation comment")
|
|
|
|
|
|
|
|
doc = QAPIDoc(self, info)
|
|
|
|
self.accept(False)
|
|
|
|
while self.tok == '#':
|
|
|
|
if self.val.startswith('##'):
|
|
|
|
# End of doc comment
|
|
|
|
if self.val != '##':
|
|
|
|
raise QAPIParseError(self, "Junk after '##' at end of "
|
|
|
|
"documentation comment")
|
2017-03-15 15:57:23 +03:00
|
|
|
doc.end_comment()
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
self.accept()
|
|
|
|
return doc
|
|
|
|
else:
|
|
|
|
doc.append(self.val)
|
|
|
|
self.accept(False)
|
|
|
|
|
|
|
|
raise QAPIParseError(self, "Documentation comment must end with '##'")
|
|
|
|
|
|
|
|
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
|
|
|
# Semantic analysis of schema expressions
|
2015-09-16 14:06:05 +03:00
|
|
|
# TODO fold into QAPISchema
|
|
|
|
# TODO catching name collisions in generated code would be nice
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2016-03-03 19:16:43 +03:00
|
|
|
def find_base_members(base):
|
2016-03-18 01:48:39 +03:00
|
|
|
if isinstance(base, dict):
|
|
|
|
return base
|
2017-03-15 15:57:32 +03:00
|
|
|
base_struct_define = struct_types.get(base)
|
2014-03-05 06:44:34 +04:00
|
|
|
if not base_struct_define:
|
|
|
|
return None
|
|
|
|
return base_struct_define['data']
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-05-04 18:05:10 +03:00
|
|
|
# Return the qtype of an alternate branch, or None on error.
|
|
|
|
def find_alternate_member_qtype(qapi_type):
|
2015-09-30 01:21:02 +03:00
|
|
|
if qapi_type in builtin_types:
|
2015-05-04 18:05:08 +03:00
|
|
|
return builtin_types[qapi_type]
|
2017-03-15 15:57:32 +03:00
|
|
|
elif qapi_type in struct_types:
|
2017-03-15 15:57:08 +03:00
|
|
|
return 'QTYPE_QDICT'
|
2017-03-15 15:57:31 +03:00
|
|
|
elif qapi_type in enum_types:
|
2017-03-15 15:57:08 +03:00
|
|
|
return 'QTYPE_QSTRING'
|
2017-03-15 15:57:33 +03:00
|
|
|
elif qapi_type in union_types:
|
2017-03-15 15:57:08 +03:00
|
|
|
return 'QTYPE_QDICT'
|
2015-05-04 18:05:08 +03:00
|
|
|
return None
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2014-03-07 05:08:56 +04:00
|
|
|
# Return the discriminator enum define if discriminator is specified as an
|
|
|
|
# enum type, otherwise return None.
|
|
|
|
def discriminator_find_enum_define(expr):
|
|
|
|
base = expr.get('base')
|
|
|
|
discriminator = expr.get('discriminator')
|
|
|
|
|
|
|
|
if not (discriminator and base):
|
|
|
|
return None
|
|
|
|
|
2016-03-03 19:16:43 +03:00
|
|
|
base_members = find_base_members(base)
|
|
|
|
if not base_members:
|
2014-03-07 05:08:56 +04:00
|
|
|
return None
|
|
|
|
|
2018-12-13 15:37:14 +03:00
|
|
|
discriminator_value = base_members.get(discriminator)
|
|
|
|
if not discriminator_value:
|
2014-03-07 05:08:56 +04:00
|
|
|
return None
|
|
|
|
|
2018-12-13 15:37:14 +03:00
|
|
|
return enum_types.get(discriminator_value['type'])
|
2014-03-07 05:08:56 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-11-18 11:52:56 +03:00
|
|
|
# Names must be letters, numbers, -, and _. They must start with letter,
|
|
|
|
# except for downstream extensions which must start with __RFQDN_.
|
|
|
|
# Dots are only valid in the downstream extension prefix.
|
2017-03-15 15:57:07 +03:00
|
|
|
valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
|
2015-11-18 11:52:56 +03:00
|
|
|
'[a-zA-Z][a-zA-Z0-9_-]*$')
|
2015-09-30 01:21:02 +03:00
|
|
|
|
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_name(info, source, name, allow_optional=False,
|
2015-09-30 01:21:02 +03:00
|
|
|
enum_member=False):
|
2015-05-04 18:05:22 +03:00
|
|
|
global valid_name
|
|
|
|
membername = name
|
|
|
|
|
|
|
|
if not isinstance(name, str):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s requires a string name" % source)
|
2015-05-04 18:05:22 +03:00
|
|
|
if name.startswith('*'):
|
|
|
|
membername = name[1:]
|
|
|
|
if not allow_optional:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s does not allow optional name '%s'"
|
|
|
|
% (source, name))
|
2015-05-04 18:05:22 +03:00
|
|
|
# Enum members can start with a digit, because the generated C
|
|
|
|
# code always prefixes it with the enum name
|
2015-11-18 11:52:56 +03:00
|
|
|
if enum_member and membername[0].isdigit():
|
|
|
|
membername = 'D' + membername
|
2016-03-18 01:48:29 +03:00
|
|
|
# Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
|
|
|
|
# and 'q_obj_*' implicit type names.
|
qapi: Reserve 'q_*' and 'has_*' member names
c_name() produces names starting with 'q_' when protecting a
dictionary member name that would fail to directly compile, but
in doing so can cause clashes with any member name already
beginning with 'q-' or 'q_'. Likewise, we create a C name 'has_'
for any optional member that can clash with any member name
beginning with 'has-' or 'has_'.
Technically, rather than blindly reserving the namespace,
we could try to complain about user names only when an actual
collision occurs, or even teach c_name() how to munge names
to avoid collisions. But it is not trivial, especially when
collisions can occur across multiple types (such as via
inheritance or flat unions). Besides, no existing .json
files are trying to use these names. So it's easier to just
outright forbid the potential for collision. We can always
relax things in the future if a real need arises for QMP to
express member names that have been forbidden here.
'has_' only has to be reserved for struct/union member names,
while 'q_' is reserved everywhere (matching the fact that
only members can be optional, while we use c_name() for munging
both members and entities). Note that we could relax 'q_'
restrictions on entities independently from member names; for
example, c_name('qmp_' + 'unix') would result in a different
function name than our current 'qmp_' + c_name('unix').
Update and add tests to cover the new error messages.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1445898903-12082-6-git-send-email-eblake@redhat.com>
[Consistently pass protect=False to c_name(); commit message tweaked
slightly]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-10-27 01:34:44 +03:00
|
|
|
if not valid_name.match(membername) or \
|
|
|
|
c_name(membername, False).startswith('q_'):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
|
2015-05-04 18:05:22 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
|
|
|
def add_name(name, info, meta, implicit=False):
|
2015-06-10 11:04:36 +03:00
|
|
|
global all_names
|
|
|
|
check_name(info, "'%s'" % meta, name)
|
2015-07-31 12:33:52 +03:00
|
|
|
# FIXME should reject names that differ only in '_' vs. '.'
|
|
|
|
# vs. '-', because they're liable to clash in generated C.
|
2015-06-10 11:04:36 +03:00
|
|
|
if name in all_names:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s '%s' is already defined"
|
|
|
|
% (all_names[name], name))
|
2015-10-27 01:34:43 +03:00
|
|
|
if not implicit and (name.endswith('Kind') or name.endswith('List')):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s '%s' should not end in '%s'"
|
|
|
|
% (meta, name, name[-4:]))
|
2015-06-10 11:04:36 +03:00
|
|
|
all_names[name] = meta
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-07-03 18:56:35 +03:00
|
|
|
def check_if(expr, info):
|
|
|
|
|
|
|
|
def check_if_str(ifcond, info):
|
|
|
|
if not isinstance(ifcond, str):
|
|
|
|
raise QAPISemError(
|
|
|
|
info, "'if' condition must be a string or a list of strings")
|
2019-09-14 18:34:58 +03:00
|
|
|
if ifcond.strip() == '':
|
|
|
|
raise QAPISemError(info, "'if' condition '%s' makes no sense"
|
|
|
|
% ifcond)
|
2018-07-03 18:56:35 +03:00
|
|
|
|
|
|
|
ifcond = expr.get('if')
|
|
|
|
if ifcond is None:
|
|
|
|
return
|
|
|
|
if isinstance(ifcond, list):
|
|
|
|
if ifcond == []:
|
|
|
|
raise QAPISemError(info, "'if' condition [] is useless")
|
|
|
|
for elt in ifcond:
|
|
|
|
check_if_str(elt, info)
|
|
|
|
else:
|
|
|
|
check_if_str(ifcond, info)
|
|
|
|
|
|
|
|
|
2019-09-13 23:13:35 +03:00
|
|
|
def check_type(info, source, value,
|
|
|
|
allow_array=False, allow_dict=False, allow_metas=[]):
|
2015-05-04 18:05:21 +03:00
|
|
|
global all_names
|
|
|
|
|
|
|
|
if value is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Check if array type for value is okay
|
|
|
|
if isinstance(value, list):
|
|
|
|
if not allow_array:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s cannot be an array" % source)
|
2015-05-04 18:05:21 +03:00
|
|
|
if len(value) != 1 or not isinstance(value[0], str):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"%s: array type must contain single type name" %
|
|
|
|
source)
|
2015-05-04 18:05:21 +03:00
|
|
|
value = value[0]
|
|
|
|
|
|
|
|
# Check if type name for value is okay
|
|
|
|
if isinstance(value, str):
|
2015-09-30 01:21:02 +03:00
|
|
|
if value not in all_names:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s uses unknown type '%s'"
|
|
|
|
% (source, value))
|
2015-05-04 18:05:21 +03:00
|
|
|
if not all_names[value] in allow_metas:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s cannot use %s type '%s'" %
|
|
|
|
(source, all_names[value], value))
|
2015-05-04 18:05:21 +03:00
|
|
|
return
|
|
|
|
|
|
|
|
if not allow_dict:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s should be a type name" % source)
|
2015-08-31 18:28:52 +03:00
|
|
|
|
|
|
|
if not isinstance(value, OrderedDict):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
2019-09-13 23:13:49 +03:00
|
|
|
"%s should be an object or type name" % source)
|
2015-08-31 18:28:52 +03:00
|
|
|
|
|
|
|
# value is a dictionary, check that each member is okay
|
2015-05-04 18:05:21 +03:00
|
|
|
for (key, arg) in value.items():
|
2017-01-13 17:41:25 +03:00
|
|
|
check_name(info, "Member of %s" % source, key,
|
2019-09-13 23:13:35 +03:00
|
|
|
allow_optional=True)
|
2015-10-27 01:35:02 +03:00
|
|
|
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "Member of %s uses reserved name '%s'"
|
|
|
|
% (source, key))
|
2015-05-04 18:05:33 +03:00
|
|
|
# Todo: allow dictionaries to represent default values of
|
|
|
|
# an optional argument.
|
2018-12-13 15:37:14 +03:00
|
|
|
check_known_keys(info, "member '%s' of %s" % (key, source),
|
2018-12-13 15:37:15 +03:00
|
|
|
arg, ['type'], ['if'])
|
2019-09-14 18:34:59 +03:00
|
|
|
check_if(arg, info)
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(arg)
|
2018-12-13 15:37:14 +03:00
|
|
|
check_type(info, "Member '%s' of %s" % (key, source),
|
|
|
|
arg['type'], allow_array=True,
|
2015-05-04 18:05:21 +03:00
|
|
|
allow_metas=['built-in', 'union', 'alternate', 'struct',
|
2015-05-04 18:05:33 +03:00
|
|
|
'enum'])
|
2015-05-04 18:05:21 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_command(expr, info):
|
2015-05-04 18:05:21 +03:00
|
|
|
name = expr['command']
|
2016-07-14 06:50:20 +03:00
|
|
|
boxed = expr.get('boxed', False)
|
2015-05-04 18:05:24 +03:00
|
|
|
|
2016-07-14 06:50:20 +03:00
|
|
|
args_meta = ['struct']
|
|
|
|
if boxed:
|
2019-09-13 23:13:36 +03:00
|
|
|
args_meta += ['union']
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'data' for command '%s'" % name,
|
2019-09-13 23:13:35 +03:00
|
|
|
expr.get('data'), allow_dict=not boxed,
|
2016-07-14 06:50:20 +03:00
|
|
|
allow_metas=args_meta)
|
2015-05-04 18:05:23 +03:00
|
|
|
returns_meta = ['union', 'struct']
|
|
|
|
if name in returns_whitelist:
|
|
|
|
returns_meta += ['built-in', 'alternate', 'enum']
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'returns' for command '%s'" % name,
|
qapi: Command returning anonymous type doesn't work, outlaw
Reproducer: with
{ 'command': 'user_def_cmd4', 'returns': { 'a': 'int' } }
added to qapi-schema-test.json, qapi-commands.py dies when it tries to
generate the command handler function
Traceback (most recent call last):
File "/work/armbru/qemu/scripts/qapi-commands.py", line 359, in <module>
ret = generate_command_decl(cmd['command'], arglist, ret_type) + "\n"
File "/work/armbru/qemu/scripts/qapi-commands.py", line 29, in generate_command_decl
ret_type=c_type(ret_type), name=c_name(name),
File "/work/armbru/qemu/scripts/qapi.py", line 927, in c_type
assert isinstance(value, str) and value != ""
AssertionError
because the return type doesn't exist.
Simply outlaw this usage, and drop or dumb down test cases accordingly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
2015-07-31 18:59:38 +03:00
|
|
|
expr.get('returns'), allow_array=True,
|
2019-09-13 23:13:35 +03:00
|
|
|
allow_metas=returns_meta)
|
2015-05-04 18:05:21 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_event(expr, info):
|
2015-05-04 18:05:17 +03:00
|
|
|
name = expr['event']
|
2016-07-14 06:50:20 +03:00
|
|
|
boxed = expr.get('boxed', False)
|
2015-05-04 18:05:17 +03:00
|
|
|
|
2016-07-14 06:50:20 +03:00
|
|
|
meta = ['struct']
|
|
|
|
if boxed:
|
2019-09-13 23:13:36 +03:00
|
|
|
meta += ['union']
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'data' for event '%s'" % name,
|
2019-09-13 23:13:35 +03:00
|
|
|
expr.get('data'), allow_dict=not boxed,
|
2016-07-14 06:50:20 +03:00
|
|
|
allow_metas=meta)
|
2014-06-18 10:43:28 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-12-13 15:37:08 +03:00
|
|
|
def enum_get_names(expr):
|
|
|
|
return [e['name'] for e in expr['data']]
|
|
|
|
|
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_union(expr, info):
|
2014-03-05 06:44:34 +04:00
|
|
|
name = expr['union']
|
|
|
|
base = expr.get('base')
|
|
|
|
discriminator = expr.get('discriminator')
|
|
|
|
members = expr['data']
|
|
|
|
|
2015-05-04 18:05:10 +03:00
|
|
|
# Two types of unions, determined by discriminator.
|
|
|
|
|
|
|
|
# With no discriminator it is a simple union.
|
|
|
|
if discriminator is None:
|
2019-09-13 23:13:43 +03:00
|
|
|
enum_values = members.keys()
|
2015-09-30 01:21:02 +03:00
|
|
|
allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
|
2015-05-04 18:05:08 +03:00
|
|
|
if base is not None:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "Simple union '%s' must not have a base" %
|
|
|
|
name)
|
2014-03-05 06:44:34 +04:00
|
|
|
|
|
|
|
# Else, it's a flat union.
|
|
|
|
else:
|
2016-03-18 01:48:39 +03:00
|
|
|
# The object must have a string or dictionary 'base'.
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'base' for union '%s'" % name,
|
2019-09-13 23:13:35 +03:00
|
|
|
base, allow_dict=True,
|
2016-03-18 01:48:39 +03:00
|
|
|
allow_metas=['struct'])
|
2015-09-30 01:21:07 +03:00
|
|
|
if not base:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "Flat union '%s' must have a base"
|
|
|
|
% name)
|
2016-03-03 19:16:43 +03:00
|
|
|
base_members = find_base_members(base)
|
2017-03-15 15:56:58 +03:00
|
|
|
assert base_members is not None
|
2015-05-04 18:05:08 +03:00
|
|
|
|
2015-05-04 18:05:22 +03:00
|
|
|
# The value of member 'discriminator' must name a non-optional
|
2015-05-04 18:05:25 +03:00
|
|
|
# member of the base struct.
|
2017-01-13 17:41:25 +03:00
|
|
|
check_name(info, "Discriminator of flat union '%s'" % name,
|
2015-05-04 18:05:22 +03:00
|
|
|
discriminator)
|
2018-12-13 15:37:14 +03:00
|
|
|
discriminator_value = base_members.get(discriminator)
|
|
|
|
if not discriminator_value:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
2019-09-14 18:34:57 +03:00
|
|
|
"Discriminator '%s' is not a member of 'base'"
|
|
|
|
% discriminator)
|
2018-12-13 15:37:15 +03:00
|
|
|
if discriminator_value.get('if'):
|
2019-09-14 18:34:54 +03:00
|
|
|
raise QAPISemError(
|
|
|
|
info,
|
2019-09-14 18:34:57 +03:00
|
|
|
"The discriminator '%s' for union %s must not be conditional"
|
|
|
|
% (discriminator, name))
|
2018-12-13 15:37:14 +03:00
|
|
|
enum_define = enum_types.get(discriminator_value['type'])
|
2014-03-05 06:44:39 +04:00
|
|
|
# Do not allow string discriminator
|
|
|
|
if not enum_define:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Discriminator '%s' must be of enumeration "
|
|
|
|
"type" % discriminator)
|
2019-09-13 23:13:43 +03:00
|
|
|
enum_values = enum_get_names(enum_define)
|
|
|
|
allow_metas = ['struct']
|
|
|
|
|
|
|
|
if (len(enum_values) == 0):
|
|
|
|
raise QAPISemError(info, "Union '%s' has no branches" % name)
|
2014-03-05 06:44:34 +04:00
|
|
|
|
|
|
|
for (key, value) in members.items():
|
2017-01-13 17:41:25 +03:00
|
|
|
check_name(info, "Member of union '%s'" % name, key)
|
2015-05-04 18:05:22 +03:00
|
|
|
|
2018-12-13 15:37:14 +03:00
|
|
|
check_known_keys(info, "member '%s' of union '%s'" % (key, name),
|
2018-12-13 15:37:17 +03:00
|
|
|
value, ['type'], ['if'])
|
2019-09-14 18:34:59 +03:00
|
|
|
check_if(value, info)
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(value)
|
2015-12-02 08:20:58 +03:00
|
|
|
# Each value must name a known type
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "Member '%s' of union '%s'" % (key, name),
|
2018-12-13 15:37:14 +03:00
|
|
|
value['type'],
|
|
|
|
allow_array=not base, allow_metas=allow_metas)
|
2015-05-04 18:05:21 +03:00
|
|
|
|
2015-05-04 18:05:08 +03:00
|
|
|
# If the discriminator names an enum type, then all members
|
2015-11-18 11:52:49 +03:00
|
|
|
# of 'data' must also be members of the enum type.
|
2019-09-13 23:13:43 +03:00
|
|
|
if discriminator is not None:
|
|
|
|
if key not in enum_values:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Discriminator value '%s' is not found in "
|
|
|
|
"enum '%s'"
|
2017-03-15 15:57:29 +03:00
|
|
|
% (key, enum_define['enum']))
|
2015-05-04 18:05:08 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_alternate(expr, info):
|
2015-05-04 18:05:13 +03:00
|
|
|
name = expr['alternate']
|
2015-05-04 18:05:10 +03:00
|
|
|
members = expr['data']
|
|
|
|
types_seen = {}
|
|
|
|
|
2019-09-13 23:13:42 +03:00
|
|
|
if len(members) == 0:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
2019-09-13 23:13:42 +03:00
|
|
|
"Alternate '%s' cannot have empty 'data'" % name)
|
2015-05-04 18:05:10 +03:00
|
|
|
for (key, value) in members.items():
|
2017-01-13 17:41:25 +03:00
|
|
|
check_name(info, "Member of alternate '%s'" % name, key)
|
2018-12-13 15:37:14 +03:00
|
|
|
check_known_keys(info,
|
|
|
|
"member '%s' of alternate '%s'" % (key, name),
|
2018-12-13 15:37:18 +03:00
|
|
|
value, ['type'], ['if'])
|
2019-09-14 18:34:59 +03:00
|
|
|
check_if(value, info)
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(value)
|
2018-12-13 15:37:14 +03:00
|
|
|
typ = value['type']
|
2015-05-04 18:05:22 +03:00
|
|
|
|
2015-05-04 18:05:10 +03:00
|
|
|
# Ensure alternates have no type conflicts.
|
2018-12-13 15:37:14 +03:00
|
|
|
check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ,
|
2015-05-04 18:05:21 +03:00
|
|
|
allow_metas=['built-in', 'union', 'struct', 'enum'])
|
2018-12-13 15:37:14 +03:00
|
|
|
qtype = find_alternate_member_qtype(typ)
|
2016-02-18 09:48:17 +03:00
|
|
|
if not qtype:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
|
2018-12-13 15:37:14 +03:00
|
|
|
"type '%s'" % (name, key, typ))
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
conflicting = set([qtype])
|
|
|
|
if qtype == 'QTYPE_QSTRING':
|
2018-12-13 15:37:14 +03:00
|
|
|
enum_expr = enum_types.get(typ)
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
if enum_expr:
|
2018-12-13 15:37:08 +03:00
|
|
|
for v in enum_get_names(enum_expr):
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
if v in ['on', 'off']:
|
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
if re.match(r'[-+0-9.]', v): # lazy, could be tightened
|
2017-06-07 19:35:58 +03:00
|
|
|
conflicting.add('QTYPE_QNUM')
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
else:
|
2017-06-07 19:35:58 +03:00
|
|
|
conflicting.add('QTYPE_QNUM')
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
for qt in conflicting:
|
qapi: Fix error handling code on alternate conflict
The conflict check added by commit c0644771 ("qapi: Reject
alternates that can't work with keyval_parse()") doesn't work
with the following declaration:
{ 'alternate': 'Alt',
'data': { 'one': 'bool',
'two': 'str' } }
It crashes with:
Traceback (most recent call last):
File "./scripts/qapi-types.py", line 295, in <module>
schema = QAPISchema(input_file)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 1468, in __init__
self.exprs = check_exprs(parser.exprs)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 958, in check_exprs
check_alternate(expr, info)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 830, in check_alternate
% (name, key, types_seen[qtype]))
KeyError: 'QTYPE_QSTRING'
This happens because the previously-seen conflicting member
('one') can't be found at types_seen[qtype], but at
types_seen['QTYPE_BOOL'].
Fix the bug by moving the error check to the same loop that adds
new items to types_seen, raising an exception if types_seen[qt]
is already set.
Add two additional test cases that can detect the bug.
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
Message-Id: <20170717180926.14924-1-ehabkost@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-07-17 21:09:26 +03:00
|
|
|
if qt in types_seen:
|
|
|
|
raise QAPISemError(info, "Alternate '%s' member '%s' can't "
|
|
|
|
"be distinguished from member '%s'"
|
|
|
|
% (name, key, types_seen[qt]))
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 19:42:15 +03:00
|
|
|
types_seen[qt] = key
|
2014-03-05 06:44:34 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_enum(expr, info):
|
2015-05-04 18:05:04 +03:00
|
|
|
name = expr['enum']
|
2018-12-13 15:37:08 +03:00
|
|
|
members = expr['data']
|
2015-08-26 16:21:20 +03:00
|
|
|
prefix = expr.get('prefix')
|
2015-05-04 18:05:04 +03:00
|
|
|
|
|
|
|
if not isinstance(members, list):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Enum '%s' requires an array for 'data'" % name)
|
2015-08-26 16:21:20 +03:00
|
|
|
if prefix is not None and not isinstance(prefix, str):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Enum '%s' requires a string for 'prefix'" % name)
|
2018-12-13 15:37:08 +03:00
|
|
|
|
2015-05-04 18:05:04 +03:00
|
|
|
for member in members:
|
2019-09-13 23:13:49 +03:00
|
|
|
check_known_keys(info, "member of enum '%s'" % name, member,
|
|
|
|
['name'], ['if'])
|
2018-12-13 15:37:11 +03:00
|
|
|
check_if(member, info)
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(member)
|
2018-12-13 15:37:08 +03:00
|
|
|
check_name(info, "Member of enum '%s'" % name, member['name'],
|
2015-05-04 18:05:22 +03:00
|
|
|
enum_member=True)
|
2015-05-04 18:05:04 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
def check_struct(expr, info):
|
2015-05-04 18:05:25 +03:00
|
|
|
name = expr['struct']
|
2015-05-04 18:05:21 +03:00
|
|
|
members = expr['data']
|
2019-06-06 18:37:57 +03:00
|
|
|
features = expr.get('features')
|
2015-05-04 18:05:21 +03:00
|
|
|
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'data' for struct '%s'" % name, members,
|
2019-09-13 23:13:35 +03:00
|
|
|
allow_dict=True)
|
2017-01-13 17:41:25 +03:00
|
|
|
check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
|
2015-05-04 18:05:21 +03:00
|
|
|
allow_metas=['struct'])
|
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
if features:
|
|
|
|
if not isinstance(features, list):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Struct '%s' requires an array for 'features'" %
|
|
|
|
name)
|
|
|
|
for f in features:
|
|
|
|
assert isinstance(f, dict)
|
|
|
|
check_known_keys(info, "feature of struct %s" % name, f,
|
|
|
|
['name'], ['if'])
|
|
|
|
|
|
|
|
check_if(f, info)
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(f)
|
2019-06-06 18:37:57 +03:00
|
|
|
check_name(info, "Feature of struct %s" % name, f['name'])
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-12-13 15:37:06 +03:00
|
|
|
def check_known_keys(info, source, keys, required, optional):
|
2018-12-13 15:37:07 +03:00
|
|
|
|
|
|
|
def pprint(elems):
|
|
|
|
return ', '.join("'" + e + "'" for e in sorted(elems))
|
|
|
|
|
|
|
|
missing = set(required) - set(keys)
|
|
|
|
if missing:
|
|
|
|
raise QAPISemError(info, "Key%s %s %s missing from %s"
|
|
|
|
% ('s' if len(missing) > 1 else '', pprint(missing),
|
|
|
|
'are' if len(missing) > 1 else 'is', source))
|
|
|
|
allowed = set(required + optional)
|
|
|
|
unknown = set(keys) - allowed
|
|
|
|
if unknown:
|
|
|
|
raise QAPISemError(info, "Unknown key%s %s in %s\nValid keys are %s."
|
|
|
|
% ('s' if len(unknown) > 1 else '', pprint(unknown),
|
|
|
|
source, pprint(allowed)))
|
2018-12-13 15:37:06 +03:00
|
|
|
|
|
|
|
|
2019-09-14 18:35:01 +03:00
|
|
|
def check_keys(expr, info, meta, required, optional=[]):
|
2015-05-04 18:05:15 +03:00
|
|
|
name = expr[meta]
|
|
|
|
if not isinstance(name, str):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "'%s' key must have a string value" % meta)
|
2015-09-30 01:21:02 +03:00
|
|
|
required = required + [meta]
|
2018-12-13 15:37:06 +03:00
|
|
|
source = "%s '%s'" % (meta, name)
|
|
|
|
check_known_keys(info, source, expr.keys(), required, optional)
|
2015-05-04 18:05:15 +03:00
|
|
|
for (key, value) in expr.items():
|
2018-06-21 11:35:51 +03:00
|
|
|
if key in ['gen', 'success-response'] and value is not False:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"'%s' of %s '%s' should only use false value"
|
|
|
|
% (key, meta, name))
|
2018-06-21 11:35:51 +03:00
|
|
|
if (key in ['boxed', 'allow-oob', 'allow-preconfig']
|
|
|
|
and value is not True):
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"'%s' of %s '%s' should only use true value"
|
|
|
|
% (key, meta, name))
|
2018-07-03 18:56:35 +03:00
|
|
|
if key == 'if':
|
|
|
|
check_if(expr, info)
|
2015-05-04 18:05:15 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-12-13 15:37:14 +03:00
|
|
|
def normalize_enum(expr):
|
|
|
|
if isinstance(expr['data'], list):
|
|
|
|
expr['data'] = [m if isinstance(m, dict) else {'name': m}
|
|
|
|
for m in expr['data']]
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_members(members):
|
|
|
|
if isinstance(members, OrderedDict):
|
|
|
|
for key, arg in members.items():
|
|
|
|
if isinstance(arg, dict):
|
|
|
|
continue
|
|
|
|
members[key] = {'type': arg}
|
|
|
|
|
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
def normalize_features(features):
|
|
|
|
if isinstance(features, list):
|
|
|
|
features[:] = [f if isinstance(f, dict) else {'name': f}
|
|
|
|
for f in features]
|
|
|
|
|
|
|
|
|
2019-09-14 18:35:00 +03:00
|
|
|
def normalize_if(expr):
|
|
|
|
ifcond = expr.get('if')
|
|
|
|
if isinstance(ifcond, str):
|
|
|
|
expr['if'] = [ifcond]
|
|
|
|
|
|
|
|
|
2015-06-10 09:55:21 +03:00
|
|
|
def check_exprs(exprs):
|
2015-05-04 18:05:17 +03:00
|
|
|
global all_names
|
|
|
|
|
2017-03-15 15:57:21 +03:00
|
|
|
# Populate name table with names of built-in types
|
2015-06-10 09:55:21 +03:00
|
|
|
for builtin in builtin_types.keys():
|
|
|
|
all_names[builtin] = 'built-in'
|
2017-03-15 15:57:21 +03:00
|
|
|
|
|
|
|
# Learn the types and check for valid expression keys
|
2015-06-10 09:55:21 +03:00
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2017-03-15 15:57:21 +03:00
|
|
|
doc = expr_elem.get('doc')
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2018-02-11 12:35:53 +03:00
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
|
|
|
|
2017-03-15 15:57:21 +03:00
|
|
|
if not doc and doc_required:
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
raise QAPISemError(info,
|
2019-09-13 23:13:49 +03:00
|
|
|
"Definition missing documentation comment")
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
if 'enum' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'enum'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'enum', ['data'], ['if', 'prefix'])
|
2018-12-13 15:37:08 +03:00
|
|
|
normalize_enum(expr)
|
2017-03-15 15:57:31 +03:00
|
|
|
enum_types[expr[meta]] = expr
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'union' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'union'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'union', ['data'],
|
2018-07-03 18:56:35 +03:00
|
|
|
['base', 'discriminator', 'if'])
|
2018-12-13 15:37:14 +03:00
|
|
|
normalize_members(expr.get('base'))
|
|
|
|
normalize_members(expr['data'])
|
2017-03-15 15:57:33 +03:00
|
|
|
union_types[expr[meta]] = expr
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'alternate' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'alternate'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'alternate', ['data'], ['if'])
|
2018-12-13 15:37:14 +03:00
|
|
|
normalize_members(expr['data'])
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'struct' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'struct'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'struct', ['data'],
|
2019-06-06 18:37:57 +03:00
|
|
|
['base', 'if', 'features'])
|
2018-12-13 15:37:14 +03:00
|
|
|
normalize_members(expr['data'])
|
2019-06-06 18:37:57 +03:00
|
|
|
normalize_features(expr.get('features'))
|
2017-03-15 15:57:32 +03:00
|
|
|
struct_types[expr[meta]] = expr
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'command' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'command'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'command', [],
|
2018-03-09 12:00:00 +03:00
|
|
|
['data', 'returns', 'gen', 'success-response',
|
2018-07-03 18:56:35 +03:00
|
|
|
'boxed', 'allow-oob', 'allow-preconfig', 'if'])
|
2018-12-13 15:37:14 +03:00
|
|
|
normalize_members(expr.get('data'))
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'event' in expr:
|
2017-03-15 15:57:30 +03:00
|
|
|
meta = 'event'
|
2019-09-14 18:35:01 +03:00
|
|
|
check_keys(expr, info, 'event', [], ['data', 'boxed', 'if'])
|
2018-12-13 15:37:14 +03:00
|
|
|
normalize_members(expr.get('data'))
|
2015-06-10 09:55:21 +03:00
|
|
|
else:
|
2019-09-14 18:35:01 +03:00
|
|
|
raise QAPISemError(info, "Expression is missing metatype")
|
2019-09-14 18:35:00 +03:00
|
|
|
normalize_if(expr)
|
2017-03-15 15:57:30 +03:00
|
|
|
name = expr[meta]
|
|
|
|
add_name(name, info, meta)
|
2017-03-15 15:57:21 +03:00
|
|
|
if doc and doc.symbol != name:
|
|
|
|
raise QAPISemError(info, "Definition of '%s' follows documentation"
|
|
|
|
" for '%s'" % (name, doc.symbol))
|
2013-07-27 19:41:56 +04:00
|
|
|
|
2015-06-10 09:55:21 +03:00
|
|
|
# Try again for hidden UnionKind enum
|
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
2018-02-11 12:35:53 +03:00
|
|
|
|
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
2017-03-15 15:57:29 +03:00
|
|
|
if 'union' in expr and not discriminator_find_enum_define(expr):
|
|
|
|
name = '%sKind' % expr['union']
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'alternate' in expr:
|
2017-03-15 15:57:29 +03:00
|
|
|
name = '%sKind' % expr['alternate']
|
|
|
|
else:
|
|
|
|
continue
|
2017-03-15 15:57:31 +03:00
|
|
|
enum_types[name] = {'enum': name}
|
2017-03-15 15:57:30 +03:00
|
|
|
add_name(name, info, 'enum', implicit=True)
|
2015-06-10 09:55:21 +03:00
|
|
|
|
|
|
|
# Validate that exprs make sense
|
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2017-03-15 15:57:27 +03:00
|
|
|
doc = expr_elem.get('doc')
|
2015-05-04 18:05:09 +03:00
|
|
|
|
2018-02-11 12:35:53 +03:00
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
2015-09-30 01:21:02 +03:00
|
|
|
if 'enum' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_enum(expr, info)
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'union' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_union(expr, info)
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'alternate' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_alternate(expr, info)
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'struct' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_struct(expr, info)
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'command' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_command(expr, info)
|
2015-09-30 01:21:02 +03:00
|
|
|
elif 'event' in expr:
|
2015-06-10 09:55:21 +03:00
|
|
|
check_event(expr, info)
|
|
|
|
else:
|
|
|
|
assert False, 'unexpected meta type'
|
|
|
|
|
2017-03-15 15:57:27 +03:00
|
|
|
if doc:
|
|
|
|
doc.check_expr(expr)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:27 +03:00
|
|
|
return exprs
|
qapi: add qapi2texi script
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol description:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @param1: the frob to frobnicate
# @param2: #optional how hard to frobnicate
#
# Returns: the frobnicated frob.
# If frob isn't frobnicatable, GenericError.
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ] "\n" freeform_comment
text = free text with markup
Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment. The actual parser
recognizes symbol_comment.
See docs/qapi-code-gen.txt for more details.
Deficiencies and limitations:
- the generated QMP documentation includes internal types
- union type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
to the beginning of the comment.
- a few minor issues, all marked TODO/FIXME in the code
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[test-qapi.py tweaked to avoid trailing empty lines in .out]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2017-01-13 17:41:29 +03:00
|
|
|
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
#
|
|
|
|
# Schema compiler frontend
|
|
|
|
#
|
|
|
|
|
|
|
|
class QAPISchemaEntity(object):
|
2018-07-03 18:56:36 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond=None):
|
2018-02-11 12:35:55 +03:00
|
|
|
assert name is None or isinstance(name, str)
|
2015-09-16 14:06:05 +03:00
|
|
|
self.name = name
|
2018-02-11 12:35:55 +03:00
|
|
|
self.module = None
|
2015-10-13 07:22:32 +03:00
|
|
|
# For explicitly defined entities, info points to the (explicit)
|
|
|
|
# definition. For builtins (and their arrays), info is None.
|
|
|
|
# For implicitly defined entities, info points to a place that
|
|
|
|
# triggered the implicit definition (there may be more than one
|
|
|
|
# such place).
|
2015-09-16 14:06:05 +03:00
|
|
|
self.info = info
|
2017-03-15 15:57:03 +03:00
|
|
|
self.doc = doc
|
2019-09-14 18:35:00 +03:00
|
|
|
self._ifcond = ifcond or []
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def c_name(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
if isinstance(self._ifcond, QAPISchemaType):
|
|
|
|
# inherit the condition from a type
|
|
|
|
typ = self._ifcond
|
|
|
|
typ.check(schema)
|
|
|
|
self.ifcond = typ.ifcond
|
|
|
|
else:
|
2019-09-14 18:35:00 +03:00
|
|
|
self.ifcond = self._ifcond
|
2019-03-01 18:40:51 +03:00
|
|
|
if self.info:
|
|
|
|
self.module = os.path.relpath(self.info['file'],
|
|
|
|
os.path.dirname(schema.fname))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-10-13 07:22:27 +03:00
|
|
|
def is_implicit(self):
|
|
|
|
return not self.info
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaVisitor(object):
|
|
|
|
def visit_begin(self, schema):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_end(self):
|
|
|
|
pass
|
|
|
|
|
2018-02-11 12:35:55 +03:00
|
|
|
def visit_module(self, fname):
|
|
|
|
pass
|
|
|
|
|
2015-10-13 07:22:21 +03:00
|
|
|
def visit_needed(self, entity):
|
|
|
|
# Default to visiting everything
|
|
|
|
return True
|
|
|
|
|
2018-02-11 12:35:55 +03:00
|
|
|
def visit_include(self, fname, info):
|
|
|
|
pass
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit_builtin_type(self, name, info, json_type):
|
|
|
|
pass
|
|
|
|
|
2018-12-13 15:37:04 +03:00
|
|
|
def visit_enum_type(self, name, info, ifcond, members, prefix):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2018-07-03 18:56:38 +03:00
|
|
|
def visit_array_type(self, name, info, ifcond, element_type):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
def visit_object_type(self, name, info, ifcond, base, members, variants,
|
|
|
|
features):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
def visit_object_type_flat(self, name, info, ifcond, members, variants,
|
|
|
|
features):
|
qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed
for QMP introspection, but should do for similar uses, such as QGA.
The introspection schema does not reflect all the rules and
restrictions that apply to QAPI schemata. A valid QAPI schema has an
introspection value conforming to the introspection schema, but the
converse is not true.
Introspection lowers away a number of schema details, and makes
implicit things explicit:
* The built-in types are declared with their JSON type.
All integer types are mapped to 'int', because how many bits we use
internally is an implementation detail. It could be pressed into
external interface service as very approximate range information,
but that's a bad idea. If we need range information, we better do
it properly.
* Implicit type definitions are made explicit, and given
auto-generated names:
- Array types, named by appending "List" to the name of their
element type, like in generated C.
- The enumeration types implicitly defined by simple union types,
named by appending "Kind" to the name of their simple union type,
like in generated C.
- Types that don't occur in generated C. Their names start with ':'
so they don't clash with the user's names.
* All type references are by name.
* The struct and union types are generalized into an object type.
* Base types are flattened.
* Commands take a single argument and return a single result.
Dictionary argument or list result is an implicit type definition.
The empty object type is used when a command takes no arguments or
produces no results.
The argument is always of object type, but the introspection schema
doesn't reflect that.
The 'gen': false directive is omitted as implementation detail.
The 'success-response' directive is omitted as well for now, even
though it's not an implementation detail, because it's not used by
QMP.
* Events carry a single data value.
Implicit type definition and empty object type use, just like for
commands.
The value is of object type, but the introspection schema doesn't
reflect that.
* Types not used by commands or events are omitted.
Indirect use counts as use.
* Optional members have a default, which can only be null right now
Instead of a mandatory "optional" flag, we have an optional default.
No default means mandatory, default null means optional without
default value. Non-null is available for optional with default
(possible future extension).
* Clients should *not* look up types by name, because type names are
not ABI. Look up the command or event you're interested in, then
follow the references.
TODO Should we hide the type names to eliminate the temptation?
New generator scripts/qapi-introspect.py computes an introspection
value for its input, and generates a C variable holding it.
It can generate awfully long lines. Marked TODO.
A new test-qmp-input-visitor test case feeds its result for both
tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
QmpInputVisitor to verify it actually conforms to the schema.
New QMP command query-qmp-schema takes its return value from that
variable. Its reply is some 85KiBytes for me right now.
If this turns out to be too much, we have a couple of options:
* We can use shorter names in the JSON. Not the QMP style.
* Optionally return the sub-schema for commands and events given as
arguments.
Right now qmp_query_schema() sends the string literal computed by
qmp-introspect.py. To compute sub-schema at run time, we'd have to
duplicate parts of qapi-introspect.py in C. Unattractive.
* Let clients cache the output of query-qmp-schema.
It changes only on QEMU upgrades, i.e. rarely. Provide a command
query-qmp-schema-hash. Clients can have a cache indexed by hash,
and re-query the schema only when they don't have it cached. Even
simpler: put the hash in the QMP greeting.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
2015-09-16 14:06:28 +03:00
|
|
|
pass
|
|
|
|
|
2018-07-03 18:56:38 +03:00
|
|
|
def visit_alternate_type(self, name, info, ifcond, variants):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2018-07-03 18:56:38 +03:00
|
|
|
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
2018-05-11 19:51:43 +03:00
|
|
|
success_response, boxed, allow_oob, allow_preconfig):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2018-07-03 18:56:38 +03:00
|
|
|
def visit_event(self, name, info, ifcond, arg_type, boxed):
|
2015-09-16 14:06:07 +03:00
|
|
|
pass
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2018-02-11 12:35:55 +03:00
|
|
|
class QAPISchemaInclude(QAPISchemaEntity):
|
|
|
|
|
|
|
|
def __init__(self, fname, info):
|
|
|
|
QAPISchemaEntity.__init__(self, None, info, None)
|
|
|
|
self.fname = fname
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_include(self.fname, self.info)
|
|
|
|
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
class QAPISchemaType(QAPISchemaEntity):
|
2016-03-18 01:48:28 +03:00
|
|
|
# Return the C type for common use.
|
|
|
|
# For the types we commonly box, this is a pointer type.
|
|
|
|
def c_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Return the C type to be used in a parameter list.
|
|
|
|
def c_param_type(self):
|
|
|
|
return self.c_type()
|
|
|
|
|
|
|
|
# Return the C type to be used where we suppress boxing.
|
|
|
|
def c_unboxed_type(self):
|
|
|
|
return self.c_type()
|
2015-09-16 14:06:06 +03:00
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def alternate_qtype(self):
|
|
|
|
json2qtype = {
|
2017-06-26 20:25:14 +03:00
|
|
|
'null': 'QTYPE_QNULL',
|
2015-09-16 14:06:06 +03:00
|
|
|
'string': 'QTYPE_QSTRING',
|
2017-06-07 19:35:58 +03:00
|
|
|
'number': 'QTYPE_QNUM',
|
|
|
|
'int': 'QTYPE_QNUM',
|
2015-09-16 14:06:06 +03:00
|
|
|
'boolean': 'QTYPE_QBOOL',
|
|
|
|
'object': 'QTYPE_QDICT'
|
|
|
|
}
|
|
|
|
return json2qtype.get(self.json_type())
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:14 +03:00
|
|
|
def doc_type(self):
|
|
|
|
if self.is_implicit():
|
|
|
|
return None
|
|
|
|
return self.name
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaBuiltinType(QAPISchemaType):
|
2016-03-18 01:48:36 +03:00
|
|
|
def __init__(self, name, json_type, c_type):
|
2017-03-15 15:57:03 +03:00
|
|
|
QAPISchemaType.__init__(self, name, None, None)
|
2015-09-16 14:06:06 +03:00
|
|
|
assert not c_type or isinstance(c_type, str)
|
|
|
|
assert json_type in ('string', 'number', 'int', 'boolean', 'null',
|
|
|
|
'value')
|
|
|
|
self._json_type_name = json_type
|
|
|
|
self._c_type_name = c_type
|
|
|
|
|
|
|
|
def c_name(self):
|
|
|
|
return self.name
|
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_type(self):
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def c_param_type(self):
|
|
|
|
if self.name == 'str':
|
2015-09-16 14:06:06 +03:00
|
|
|
return 'const ' + self._c_type_name
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return self._json_type_name
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:14 +03:00
|
|
|
def doc_type(self):
|
|
|
|
return self.json_type()
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_builtin_type(self.name, self.info, self.json_type())
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaEnumType(QAPISchemaType):
|
2018-12-08 14:15:42 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, members, prefix):
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2018-12-08 14:15:42 +03:00
|
|
|
for m in members:
|
2019-09-13 23:13:44 +03:00
|
|
|
assert isinstance(m, QAPISchemaEnumMember)
|
2018-12-08 14:15:42 +03:00
|
|
|
m.set_owner(name)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert prefix is None or isinstance(prefix, str)
|
2018-12-08 14:15:42 +03:00
|
|
|
self.members = members
|
2015-09-16 14:06:05 +03:00
|
|
|
self.prefix = prefix
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaType.check(self, schema)
|
2015-12-02 08:20:55 +03:00
|
|
|
seen = {}
|
2018-12-08 14:15:42 +03:00
|
|
|
for m in self.members:
|
|
|
|
m.check_clash(self.info, seen)
|
2017-03-15 15:57:03 +03:00
|
|
|
if self.doc:
|
2018-12-08 14:15:42 +03:00
|
|
|
self.doc.connect_member(m)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-10-13 07:22:32 +03:00
|
|
|
def is_implicit(self):
|
2017-03-15 15:57:02 +03:00
|
|
|
# See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
|
|
|
|
return self.name.endswith('Kind') or self.name == 'QType'
|
2015-10-13 07:22:32 +03:00
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_type(self):
|
2015-09-16 14:06:06 +03:00
|
|
|
return c_name(self.name)
|
|
|
|
|
2015-12-02 08:20:55 +03:00
|
|
|
def member_names(self):
|
2018-12-08 14:15:42 +03:00
|
|
|
return [m.name for m in self.members]
|
2015-12-02 08:20:55 +03:00
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def json_type(self):
|
|
|
|
return 'string'
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_enum_type(self.name, self.info, self.ifcond,
|
2018-12-13 15:37:04 +03:00
|
|
|
self.members, self.prefix)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaArrayType(QAPISchemaType):
|
|
|
|
def __init__(self, name, info, element_type):
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaType.__init__(self, name, info, None, None)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert isinstance(element_type, str)
|
|
|
|
self._element_type_name = element_type
|
|
|
|
self.element_type = None
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaType.check(self, schema)
|
2015-09-16 14:06:05 +03:00
|
|
|
self.element_type = schema.lookup_type(self._element_type_name)
|
|
|
|
assert self.element_type
|
2018-07-03 18:56:37 +03:00
|
|
|
self.element_type.check(schema)
|
2019-03-01 18:40:51 +03:00
|
|
|
self.module = self.element_type.module
|
2018-07-03 18:56:36 +03:00
|
|
|
self.ifcond = self.element_type.ifcond
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-10-13 07:22:32 +03:00
|
|
|
def is_implicit(self):
|
|
|
|
return True
|
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_type(self):
|
|
|
|
return c_name(self.name) + pointer_suffix
|
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def json_type(self):
|
|
|
|
return 'array'
|
|
|
|
|
2017-03-15 15:57:14 +03:00
|
|
|
def doc_type(self):
|
|
|
|
elt_doc_type = self.element_type.doc_type()
|
|
|
|
if not elt_doc_type:
|
|
|
|
return None
|
|
|
|
return 'array of ' + elt_doc_type
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_array_type(self.name, self.info, self.ifcond,
|
|
|
|
self.element_type)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaObjectType(QAPISchemaType):
|
2018-07-03 18:56:36 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond,
|
2019-06-06 18:37:57 +03:00
|
|
|
base, local_members, variants, features):
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-2-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:36 +03:00
|
|
|
# struct has local_members, optional base, and no variants
|
|
|
|
# flat union has base, variants, and no local_members
|
|
|
|
# simple union has local_members, variants, and no base
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert base is None or isinstance(base, str)
|
|
|
|
for m in local_members:
|
|
|
|
assert isinstance(m, QAPISchemaObjectTypeMember)
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
m.set_owner(name)
|
|
|
|
if variants is not None:
|
|
|
|
assert isinstance(variants, QAPISchemaObjectTypeVariants)
|
|
|
|
variants.set_owner(name)
|
2019-06-06 18:37:57 +03:00
|
|
|
for f in features:
|
|
|
|
assert isinstance(f, QAPISchemaFeature)
|
|
|
|
f.set_owner(name)
|
2015-09-16 14:06:05 +03:00
|
|
|
self._base_name = base
|
|
|
|
self.base = None
|
|
|
|
self.local_members = local_members
|
|
|
|
self.variants = variants
|
|
|
|
self.members = None
|
2019-06-06 18:37:57 +03:00
|
|
|
self.features = features
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaType.check(self, schema)
|
qapi: Detect base class loops
It should be fairly obvious that qapi base classes need to
form an acyclic graph, since QMP cannot specify the same
key more than once, while base classes are included as flat
members alongside other members added by the child. But the
old check_member_clash() parser function was not prepared to
check for this, and entered an infinite recursion (at least
until Python gives up, complaining about nesting too deep).
Now that check_member_clash() has been recently removed,
attempts at self-inheritance trigger an assertion failure
introduced by commit ac88219a. The obvious fix is to turn
the assertion into a conditional.
This patch includes both the tests (base-cycle-direct and
base-cycle-indirect) and the fix, since the .err file output
for the unfixed case is not useful (particularly when it was
warning about unbounded recursion, as that limit may be
platform-specific).
We don't need to worry about cycles in flat unions (neither
the base type nor the type of a variant can be a union) nor
in alternates (alternate branches cannot themselves be an
alternate). But if we later allow a union type as a variant,
we will still be okay, as QAPISchemaObjectTypeVariants.check()
triggers the same QAPISchemaObjectType.check() that will
detect any loops.
Likewise, we need not worry about the case of diamond
inheritance where the same class is used for a flat union base
class and one of its variants; either both uses will introduce
a collision in trying to insert the same member name twice, or
the shared type is empty and changes nothing.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-16-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:59 +03:00
|
|
|
if self.members is False: # check for cycles
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"Object %s contains itself" % self.name)
|
2015-09-16 14:06:05 +03:00
|
|
|
if self.members:
|
|
|
|
return
|
|
|
|
self.members = False # mark as being checked
|
2015-11-18 11:52:43 +03:00
|
|
|
seen = OrderedDict()
|
2015-09-16 14:06:05 +03:00
|
|
|
if self._base_name:
|
|
|
|
self.base = schema.lookup_type(self._base_name)
|
|
|
|
assert isinstance(self.base, QAPISchemaObjectType)
|
|
|
|
self.base.check(schema)
|
2017-03-15 15:57:34 +03:00
|
|
|
self.base.check_clash(self.info, seen)
|
2015-09-16 14:06:05 +03:00
|
|
|
for m in self.local_members:
|
2015-11-18 11:52:40 +03:00
|
|
|
m.check(schema)
|
2015-11-18 11:52:51 +03:00
|
|
|
m.check_clash(self.info, seen)
|
2017-03-15 15:57:03 +03:00
|
|
|
if self.doc:
|
|
|
|
self.doc.connect_member(m)
|
2015-11-18 11:52:45 +03:00
|
|
|
self.members = seen.values()
|
2015-09-16 14:06:05 +03:00
|
|
|
if self.variants:
|
2015-11-18 11:52:41 +03:00
|
|
|
self.variants.check(schema, seen)
|
2015-11-18 11:52:45 +03:00
|
|
|
assert self.variants.tag_member in self.members
|
2017-03-15 15:57:34 +03:00
|
|
|
self.variants.check_clash(self.info, seen)
|
2019-06-06 18:37:57 +03:00
|
|
|
|
|
|
|
# Features are in a name space separate from members
|
|
|
|
seen = {}
|
|
|
|
for f in self.features:
|
|
|
|
f.check_clash(self.info, seen)
|
|
|
|
|
2017-03-15 15:57:26 +03:00
|
|
|
if self.doc:
|
|
|
|
self.doc.check()
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2016-03-03 19:16:43 +03:00
|
|
|
# Check that the members of this type do not cause duplicate JSON members,
|
2015-11-18 11:52:51 +03:00
|
|
|
# and update seen to track the members seen so far. Report any errors
|
|
|
|
# on behalf of info, which is not necessarily self.info
|
2017-03-15 15:57:34 +03:00
|
|
|
def check_clash(self, info, seen):
|
2015-11-18 11:52:47 +03:00
|
|
|
assert not self.variants # not implemented
|
|
|
|
for m in self.members:
|
2015-11-18 11:52:51 +03:00
|
|
|
m.check_clash(info, seen)
|
2015-11-18 11:52:47 +03:00
|
|
|
|
2015-10-13 07:22:32 +03:00
|
|
|
def is_implicit(self):
|
2016-03-18 01:48:29 +03:00
|
|
|
# See QAPISchema._make_implicit_object_type(), as well as
|
|
|
|
# _def_predefineds()
|
|
|
|
return self.name.startswith('q_')
|
2015-10-13 07:22:32 +03:00
|
|
|
|
2016-07-14 06:50:16 +03:00
|
|
|
def is_empty(self):
|
|
|
|
assert self.members is not None
|
|
|
|
return not self.members and not self.variants
|
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def c_name(self):
|
2016-07-14 06:50:14 +03:00
|
|
|
assert self.name != 'q_empty'
|
2015-09-16 14:06:06 +03:00
|
|
|
return QAPISchemaType.c_name(self)
|
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_type(self):
|
2015-10-13 07:22:27 +03:00
|
|
|
assert not self.is_implicit()
|
qapi: Don't box struct branch of alternate
There's no reason to do two malloc's for an alternate type visiting
a QAPI struct; let's just inline the struct directly as the C union
branch of the struct.
Surprisingly, no clients were actually using the struct member prior
to this patch outside of the testsuite; an earlier patch in the series
added some testsuite coverage to make the effect of this patch more
obvious.
In qapi.py, c_type() gains a new is_unboxed flag to control when we
are emitting a C struct unboxed within the context of an outer
struct (different from our other two modes of usage with no flags
for normal local variable declarations, and with is_param for adding
'const' in a parameter list). I don't know if there is any more
pythonic way of collapsing the two flags into a single parameter,
as we never have a caller setting both flags at once.
Ultimately, we want to also unbox branches for QAPI unions, but as
that touches a lot more client code, it is better as separate
patches. But since unions and alternates share gen_variants(), I
had to hack in a way to test if we are visiting an alternate type
for setting the is_unboxed flag: look for a non-object branch.
This works because alternates have at least two branches, with at
most one object branch, while unions have only object branches.
The hack will go away in a later patch.
The generated code difference to qapi-types.h is relatively small:
| struct BlockdevRef {
| QType type;
| union { /* union tag is @type */
| void *data;
|- BlockdevOptions *definition;
|+ BlockdevOptions definition;
| char *reference;
| } u;
| };
The corresponding spot in qapi-visit.c calls visit_type_FOO(), which
first calls visit_start_struct() to allocate or deallocate the member
and handle a layer of {} from the JSON stream, then visits the
members. To peel off the indirection and the memory management that
comes with it, we inline this call, then suppress allocation /
deallocation by passing NULL to visit_start_struct(), and adjust the
member visit:
| switch ((*obj)->type) {
| case QTYPE_QDICT:
|- visit_type_BlockdevOptions(v, name, &(*obj)->u.definition, &err);
|+ visit_start_struct(v, name, NULL, 0, &err);
|+ if (err) {
|+ break;
|+ }
|+ visit_type_BlockdevOptions_fields(v, &(*obj)->u.definition, &err);
|+ error_propagate(errp, err);
|+ err = NULL;
|+ visit_end_struct(v, &err);
| break;
| case QTYPE_QSTRING:
| visit_type_str(v, name, &(*obj)->u.reference, &err);
The visit of non-object fields is unchanged.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1455778109-6278-13-git-send-email-eblake@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2016-02-18 09:48:26 +03:00
|
|
|
return c_name(self.name) + pointer_suffix
|
2015-09-16 14:06:06 +03:00
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_unboxed_type(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def json_type(self):
|
|
|
|
return 'object'
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_object_type(self.name, self.info, self.ifcond,
|
2019-06-06 18:37:57 +03:00
|
|
|
self.base, self.local_members, self.variants,
|
|
|
|
self.features)
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
|
2019-06-06 18:37:57 +03:00
|
|
|
self.members, self.variants,
|
|
|
|
self.features)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-12-02 08:20:54 +03:00
|
|
|
class QAPISchemaMember(object):
|
2019-06-06 18:37:57 +03:00
|
|
|
""" Represents object members, enum members and features """
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
role = 'member'
|
|
|
|
|
2018-12-13 15:37:11 +03:00
|
|
|
def __init__(self, name, ifcond=None):
|
2015-09-16 14:06:05 +03:00
|
|
|
assert isinstance(name, str)
|
|
|
|
self.name = name
|
2019-09-14 18:35:00 +03:00
|
|
|
self.ifcond = ifcond or []
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
self.owner = None
|
|
|
|
|
|
|
|
def set_owner(self, name):
|
|
|
|
assert not self.owner
|
|
|
|
self.owner = name
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-11-18 11:52:51 +03:00
|
|
|
def check_clash(self, info, seen):
|
|
|
|
cname = c_name(self.name)
|
2017-03-15 15:56:55 +03:00
|
|
|
if cname.lower() != cname and self.owner not in name_case_whitelist:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"%s should not use uppercase" % self.describe())
|
2015-11-18 11:52:51 +03:00
|
|
|
if cname in seen:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(info, "%s collides with %s" %
|
|
|
|
(self.describe(), seen[cname].describe()))
|
2015-11-18 11:52:51 +03:00
|
|
|
seen[cname] = self
|
2015-11-18 11:52:44 +03:00
|
|
|
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
def _pretty_owner(self):
|
|
|
|
owner = self.owner
|
2016-03-18 01:48:29 +03:00
|
|
|
if owner.startswith('q_obj_'):
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
# See QAPISchema._make_implicit_object_type() - reverse the
|
|
|
|
# mapping there to create a nice human-readable description
|
2016-03-18 01:48:29 +03:00
|
|
|
owner = owner[6:]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
if owner.endswith('-arg'):
|
|
|
|
return '(parameter of %s)' % owner[:-4]
|
2016-03-18 01:48:39 +03:00
|
|
|
elif owner.endswith('-base'):
|
|
|
|
return '(base of %s)' % owner[:-5]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
else:
|
|
|
|
assert owner.endswith('-wrapper')
|
|
|
|
# Unreachable and not implemented
|
|
|
|
assert False
|
2015-12-02 08:20:55 +03:00
|
|
|
if owner.endswith('Kind'):
|
|
|
|
# See QAPISchema._make_implicit_enum_type()
|
|
|
|
return '(branch of %s)' % owner[:-4]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
return '(%s of %s)' % (self.role, owner)
|
|
|
|
|
|
|
|
def describe(self):
|
|
|
|
return "'%s' %s" % (self.name, self._pretty_owner())
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2019-09-13 23:13:44 +03:00
|
|
|
class QAPISchemaEnumMember(QAPISchemaMember):
|
|
|
|
role = 'value'
|
|
|
|
|
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
class QAPISchemaFeature(QAPISchemaMember):
|
|
|
|
role = 'feature'
|
|
|
|
|
|
|
|
|
2015-12-02 08:20:54 +03:00
|
|
|
class QAPISchemaObjectTypeMember(QAPISchemaMember):
|
2018-12-13 15:37:15 +03:00
|
|
|
def __init__(self, name, typ, optional, ifcond=None):
|
|
|
|
QAPISchemaMember.__init__(self, name, ifcond)
|
2015-12-02 08:20:54 +03:00
|
|
|
assert isinstance(typ, str)
|
|
|
|
assert isinstance(optional, bool)
|
|
|
|
self._type_name = typ
|
|
|
|
self.type = None
|
|
|
|
self.optional = optional
|
|
|
|
|
|
|
|
def check(self, schema):
|
|
|
|
assert self.owner
|
|
|
|
self.type = schema.lookup_type(self._type_name)
|
|
|
|
assert self.type
|
|
|
|
|
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
class QAPISchemaObjectTypeVariants(object):
|
2015-10-13 07:22:29 +03:00
|
|
|
def __init__(self, tag_name, tag_member, variants):
|
|
|
|
# Flat unions pass tag_name but not tag_member.
|
|
|
|
# Simple unions and alternates pass tag_member but not tag_name.
|
|
|
|
# After check(), tag_member is always set, and tag_name remains
|
|
|
|
# a reliable witness of being used by a flat union.
|
|
|
|
assert bool(tag_member) != bool(tag_name)
|
|
|
|
assert (isinstance(tag_name, str) or
|
|
|
|
isinstance(tag_member, QAPISchemaObjectTypeMember))
|
2015-09-16 14:06:05 +03:00
|
|
|
for v in variants:
|
|
|
|
assert isinstance(v, QAPISchemaObjectTypeVariant)
|
2016-07-14 06:50:15 +03:00
|
|
|
self._tag_name = tag_name
|
2015-10-13 07:22:29 +03:00
|
|
|
self.tag_member = tag_member
|
2015-09-16 14:06:05 +03:00
|
|
|
self.variants = variants
|
|
|
|
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
def set_owner(self, name):
|
|
|
|
for v in self.variants:
|
|
|
|
v.set_owner(name)
|
|
|
|
|
2015-11-18 11:52:41 +03:00
|
|
|
def check(self, schema, seen):
|
2015-11-18 11:52:45 +03:00
|
|
|
if not self.tag_member: # flat union
|
2016-07-14 06:50:15 +03:00
|
|
|
self.tag_member = seen[c_name(self._tag_name)]
|
|
|
|
assert self._tag_name == self.tag_member.name
|
2015-09-16 14:06:05 +03:00
|
|
|
assert isinstance(self.tag_member.type, QAPISchemaEnumType)
|
2018-06-18 11:40:05 +03:00
|
|
|
if self._tag_name: # flat union
|
|
|
|
# branches that are not explicitly covered get an empty type
|
|
|
|
cases = set([v.name for v in self.variants])
|
2018-12-08 14:15:42 +03:00
|
|
|
for m in self.tag_member.type.members:
|
|
|
|
if m.name not in cases:
|
2018-12-17 23:40:46 +03:00
|
|
|
v = QAPISchemaObjectTypeVariant(m.name, 'q_empty',
|
|
|
|
m.ifcond)
|
2018-06-18 11:40:05 +03:00
|
|
|
v.set_owner(self.tag_member.owner)
|
|
|
|
self.variants.append(v)
|
2015-09-16 14:06:05 +03:00
|
|
|
for v in self.variants:
|
2015-11-18 11:52:48 +03:00
|
|
|
v.check(schema)
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:48 +03:00
|
|
|
# Union names must match enum values; alternate names are
|
|
|
|
# checked separately. Use 'seen' to tell the two apart.
|
|
|
|
if seen:
|
2015-12-02 08:20:55 +03:00
|
|
|
assert v.name in self.tag_member.type.member_names()
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:48 +03:00
|
|
|
assert isinstance(v.type, QAPISchemaObjectType)
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-12-git-send-email-eblake@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:46 +03:00
|
|
|
v.type.check(schema)
|
|
|
|
|
2017-03-15 15:57:34 +03:00
|
|
|
def check_clash(self, info, seen):
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-12-git-send-email-eblake@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:46 +03:00
|
|
|
for v in self.variants:
|
|
|
|
# Reset seen map for each variant, since qapi names from one
|
|
|
|
# branch do not affect another branch
|
|
|
|
assert isinstance(v.type, QAPISchemaObjectType)
|
2017-03-15 15:57:34 +03:00
|
|
|
v.type.check_clash(info, dict(seen))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
role = 'branch'
|
|
|
|
|
2018-12-13 15:37:17 +03:00
|
|
|
def __init__(self, name, typ, ifcond=None):
|
|
|
|
QAPISchemaObjectTypeMember.__init__(self, name, typ, False, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaAlternateType(QAPISchemaType):
|
2018-07-03 18:56:36 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, variants):
|
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert isinstance(variants, QAPISchemaObjectTypeVariants)
|
2016-07-14 06:50:15 +03:00
|
|
|
assert variants.tag_member
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
variants.set_owner(name)
|
|
|
|
variants.tag_member.set_owner(self.name)
|
2015-09-16 14:06:05 +03:00
|
|
|
self.variants = variants
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaType.check(self, schema)
|
2015-11-18 11:52:40 +03:00
|
|
|
self.variants.tag_member.check(schema)
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-12-git-send-email-eblake@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:46 +03:00
|
|
|
# Not calling self.variants.check_clash(), because there's nothing
|
|
|
|
# to clash with
|
2015-11-18 11:52:41 +03:00
|
|
|
self.variants.check(schema, {})
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:48 +03:00
|
|
|
# Alternate branch names have no relation to the tag enum values;
|
|
|
|
# so we have to check for potential name collisions ourselves.
|
|
|
|
seen = {}
|
|
|
|
for v in self.variants.variants:
|
|
|
|
v.check_clash(self.info, seen)
|
2017-03-15 15:57:03 +03:00
|
|
|
if self.doc:
|
|
|
|
self.doc.connect_member(v)
|
2017-03-15 15:57:26 +03:00
|
|
|
if self.doc:
|
|
|
|
self.doc.check()
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2016-03-18 01:48:28 +03:00
|
|
|
def c_type(self):
|
|
|
|
return c_name(self.name) + pointer_suffix
|
|
|
|
|
2015-09-16 14:06:06 +03:00
|
|
|
def json_type(self):
|
|
|
|
return 'value'
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_alternate_type(self.name, self.info, self.ifcond,
|
|
|
|
self.variants)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaCommand(QAPISchemaEntity):
|
2018-07-03 18:56:36 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
|
2018-05-11 19:51:43 +03:00
|
|
|
gen, success_response, boxed, allow_oob, allow_preconfig):
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
assert not ret_type or isinstance(ret_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
|
|
|
self._ret_type_name = ret_type
|
|
|
|
self.ret_type = None
|
|
|
|
self.gen = gen
|
|
|
|
self.success_response = success_response
|
2016-07-14 06:50:19 +03:00
|
|
|
self.boxed = boxed
|
2018-03-09 12:00:00 +03:00
|
|
|
self.allow_oob = allow_oob
|
2018-05-11 19:51:43 +03:00
|
|
|
self.allow_preconfig = allow_preconfig
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaEntity.check(self, schema)
|
2015-09-16 14:06:05 +03:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.lookup_type(self._arg_type_name)
|
2019-09-13 23:13:36 +03:00
|
|
|
assert isinstance(self.arg_type, QAPISchemaObjectType)
|
2016-07-14 06:50:20 +03:00
|
|
|
self.arg_type.check(schema)
|
2019-09-13 23:13:41 +03:00
|
|
|
assert not self.arg_type.variants or self.boxed
|
2016-07-14 06:50:20 +03:00
|
|
|
elif self.boxed:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
2015-09-16 14:06:05 +03:00
|
|
|
if self._ret_type_name:
|
|
|
|
self.ret_type = schema.lookup_type(self._ret_type_name)
|
|
|
|
assert isinstance(self.ret_type, QAPISchemaType)
|
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_command(self.name, self.info, self.ifcond,
|
2015-09-16 14:06:07 +03:00
|
|
|
self.arg_type, self.ret_type,
|
2018-03-09 12:00:00 +03:00
|
|
|
self.gen, self.success_response,
|
2018-05-11 19:51:43 +03:00
|
|
|
self.boxed, self.allow_oob,
|
|
|
|
self.allow_preconfig)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchemaEvent(QAPISchemaEntity):
|
2018-07-03 18:56:36 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, arg_type, boxed):
|
|
|
|
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
2016-07-14 06:50:19 +03:00
|
|
|
self.boxed = boxed
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-03 18:56:37 +03:00
|
|
|
QAPISchemaEntity.check(self, schema)
|
2015-09-16 14:06:05 +03:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.lookup_type(self._arg_type_name)
|
2019-09-13 23:13:36 +03:00
|
|
|
assert isinstance(self.arg_type, QAPISchemaObjectType)
|
2016-07-14 06:50:20 +03:00
|
|
|
self.arg_type.check(schema)
|
2019-09-13 23:13:41 +03:00
|
|
|
assert not self.arg_type.variants or self.boxed
|
2016-07-14 06:50:20 +03:00
|
|
|
elif self.boxed:
|
2017-01-13 17:41:25 +03:00
|
|
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2018-07-03 18:56:38 +03:00
|
|
|
visitor.visit_event(self.name, self.info, self.ifcond,
|
|
|
|
self.arg_type, self.boxed)
|
2015-09-16 14:06:07 +03:00
|
|
|
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
class QAPISchema(object):
|
|
|
|
def __init__(self, fname):
|
2019-03-01 18:40:51 +03:00
|
|
|
self.fname = fname
|
2018-06-18 20:59:57 +03:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
f = open(fname, 'r', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
f = open(fname, 'r')
|
|
|
|
parser = QAPISchemaParser(f)
|
2018-02-11 12:35:51 +03:00
|
|
|
exprs = check_exprs(parser.exprs)
|
|
|
|
self.docs = parser.docs
|
2018-02-11 12:35:54 +03:00
|
|
|
self._entity_list = []
|
2018-02-11 12:35:51 +03:00
|
|
|
self._entity_dict = {}
|
|
|
|
self._predefining = True
|
|
|
|
self._def_predefineds()
|
|
|
|
self._predefining = False
|
|
|
|
self._def_exprs(exprs)
|
|
|
|
self.check()
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def _def_entity(self, ent):
|
2015-10-13 07:22:32 +03:00
|
|
|
# Only the predefined types are allowed to not have info
|
|
|
|
assert ent.info or self._predefining
|
2018-02-11 12:35:55 +03:00
|
|
|
assert ent.name is None or ent.name not in self._entity_dict
|
2018-02-11 12:35:54 +03:00
|
|
|
self._entity_list.append(ent)
|
2018-02-11 12:35:55 +03:00
|
|
|
if ent.name is not None:
|
|
|
|
self._entity_dict[ent.name] = ent
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def lookup_entity(self, name, typ=None):
|
|
|
|
ent = self._entity_dict.get(name)
|
|
|
|
if typ and not isinstance(ent, typ):
|
|
|
|
return None
|
|
|
|
return ent
|
|
|
|
|
|
|
|
def lookup_type(self, name):
|
|
|
|
return self.lookup_entity(name, QAPISchemaType)
|
|
|
|
|
2018-02-11 12:35:55 +03:00
|
|
|
def _def_include(self, expr, info, doc):
|
|
|
|
include = expr['include']
|
|
|
|
assert doc is None
|
|
|
|
main_info = info
|
|
|
|
while main_info['parent']:
|
|
|
|
main_info = main_info['parent']
|
|
|
|
fname = os.path.relpath(include, os.path.dirname(main_info['file']))
|
|
|
|
self._def_entity(QAPISchemaInclude(fname, info))
|
|
|
|
|
2016-03-18 01:48:36 +03:00
|
|
|
def _def_builtin_type(self, name, json_type, c_type):
|
|
|
|
self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
# Instantiating only the arrays that are actually used would
|
|
|
|
# be nice, but we can't as long as their generated code
|
|
|
|
# (qapi-builtin-types.[ch]) may be shared by some other
|
|
|
|
# schema.
|
2015-10-13 07:22:32 +03:00
|
|
|
self._make_array_type(name, None)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
|
|
|
def _def_predefineds(self):
|
2016-03-18 01:48:36 +03:00
|
|
|
for t in [('str', 'string', 'char' + pointer_suffix),
|
|
|
|
('number', 'number', 'double'),
|
|
|
|
('int', 'int', 'int64_t'),
|
|
|
|
('int8', 'int', 'int8_t'),
|
|
|
|
('int16', 'int', 'int16_t'),
|
|
|
|
('int32', 'int', 'int32_t'),
|
|
|
|
('int64', 'int', 'int64_t'),
|
|
|
|
('uint8', 'int', 'uint8_t'),
|
|
|
|
('uint16', 'int', 'uint16_t'),
|
|
|
|
('uint32', 'int', 'uint32_t'),
|
|
|
|
('uint64', 'int', 'uint64_t'),
|
|
|
|
('size', 'int', 'uint64_t'),
|
|
|
|
('bool', 'boolean', 'bool'),
|
2017-06-26 20:25:14 +03:00
|
|
|
('any', 'value', 'QObject' + pointer_suffix),
|
|
|
|
('null', 'null', 'QNull' + pointer_suffix)]:
|
2015-09-16 14:06:06 +03:00
|
|
|
self._def_builtin_type(*t)
|
2017-03-15 15:57:03 +03:00
|
|
|
self.the_empty_object_type = QAPISchemaObjectType(
|
2019-06-06 18:37:57 +03:00
|
|
|
'q_empty', None, None, None, None, [], None, [])
|
qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed
for QMP introspection, but should do for similar uses, such as QGA.
The introspection schema does not reflect all the rules and
restrictions that apply to QAPI schemata. A valid QAPI schema has an
introspection value conforming to the introspection schema, but the
converse is not true.
Introspection lowers away a number of schema details, and makes
implicit things explicit:
* The built-in types are declared with their JSON type.
All integer types are mapped to 'int', because how many bits we use
internally is an implementation detail. It could be pressed into
external interface service as very approximate range information,
but that's a bad idea. If we need range information, we better do
it properly.
* Implicit type definitions are made explicit, and given
auto-generated names:
- Array types, named by appending "List" to the name of their
element type, like in generated C.
- The enumeration types implicitly defined by simple union types,
named by appending "Kind" to the name of their simple union type,
like in generated C.
- Types that don't occur in generated C. Their names start with ':'
so they don't clash with the user's names.
* All type references are by name.
* The struct and union types are generalized into an object type.
* Base types are flattened.
* Commands take a single argument and return a single result.
Dictionary argument or list result is an implicit type definition.
The empty object type is used when a command takes no arguments or
produces no results.
The argument is always of object type, but the introspection schema
doesn't reflect that.
The 'gen': false directive is omitted as implementation detail.
The 'success-response' directive is omitted as well for now, even
though it's not an implementation detail, because it's not used by
QMP.
* Events carry a single data value.
Implicit type definition and empty object type use, just like for
commands.
The value is of object type, but the introspection schema doesn't
reflect that.
* Types not used by commands or events are omitted.
Indirect use counts as use.
* Optional members have a default, which can only be null right now
Instead of a mandatory "optional" flag, we have an optional default.
No default means mandatory, default null means optional without
default value. Non-null is available for optional with default
(possible future extension).
* Clients should *not* look up types by name, because type names are
not ABI. Look up the command or event you're interested in, then
follow the references.
TODO Should we hide the type names to eliminate the temptation?
New generator scripts/qapi-introspect.py computes an introspection
value for its input, and generates a C variable holding it.
It can generate awfully long lines. Marked TODO.
A new test-qmp-input-visitor test case feeds its result for both
tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
QmpInputVisitor to verify it actually conforms to the schema.
New QMP command query-qmp-schema takes its return value from that
variable. Its reply is some 85KiBytes for me right now.
If this turns out to be too much, we have a couple of options:
* We can use shorter names in the JSON. Not the QMP style.
* Optionally return the sub-schema for commands and events given as
arguments.
Right now qmp_query_schema() sends the string literal computed by
qmp-introspect.py. To compute sub-schema at run time, we'd have to
duplicate parts of qapi-introspect.py in C. Unattractive.
* Let clients cache the output of query-qmp-schema.
It changes only on QEMU upgrades, i.e. rarely. Provide a command
query-qmp-schema-hash. Clients can have a cache indexed by hash,
and re-query the schema only when they don't have it cached. Even
simpler: put the hash in the QMP greeting.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
2015-09-16 14:06:28 +03:00
|
|
|
self._def_entity(self.the_empty_object_type)
|
2018-12-13 15:37:08 +03:00
|
|
|
|
|
|
|
qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
|
|
|
|
'qbool']
|
|
|
|
qtype_values = self._make_enum_members([{'name': n} for n in qtypes])
|
|
|
|
|
2018-07-03 18:56:36 +03:00
|
|
|
self._def_entity(QAPISchemaEnumType('QType', None, None, None,
|
2017-03-15 15:57:03 +03:00
|
|
|
qtype_values, 'QTYPE'))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2019-06-06 18:37:57 +03:00
|
|
|
def _make_features(self, features):
|
|
|
|
return [QAPISchemaFeature(f['name'], f.get('if')) for f in features]
|
|
|
|
|
2015-12-02 08:20:55 +03:00
|
|
|
def _make_enum_members(self, values):
|
2019-09-13 23:13:44 +03:00
|
|
|
return [QAPISchemaEnumMember(v['name'], v.get('if'))
|
|
|
|
for v in values]
|
2015-12-02 08:20:55 +03:00
|
|
|
|
2018-07-03 18:56:36 +03:00
|
|
|
def _make_implicit_enum_type(self, name, info, ifcond, values):
|
2015-12-02 08:20:55 +03:00
|
|
|
# See also QAPISchemaObjectTypeMember._pretty_owner()
|
2015-10-13 07:22:27 +03:00
|
|
|
name = name + 'Kind' # Use namespace reserved by add_name()
|
2015-12-02 08:20:55 +03:00
|
|
|
self._def_entity(QAPISchemaEnumType(
|
2018-07-03 18:56:36 +03:00
|
|
|
name, info, None, ifcond, self._make_enum_members(values), None))
|
2015-09-16 14:06:05 +03:00
|
|
|
return name
|
|
|
|
|
2015-10-13 07:22:32 +03:00
|
|
|
def _make_array_type(self, element_type, info):
|
2015-10-27 01:34:43 +03:00
|
|
|
name = element_type + 'List' # Use namespace reserved by add_name()
|
2015-09-16 14:06:05 +03:00
|
|
|
if not self.lookup_type(name):
|
2015-10-13 07:22:32 +03:00
|
|
|
self._def_entity(QAPISchemaArrayType(name, info, element_type))
|
2015-09-16 14:06:05 +03:00
|
|
|
return name
|
|
|
|
|
2018-07-03 18:56:36 +03:00
|
|
|
def _make_implicit_object_type(self, name, info, doc, ifcond,
|
|
|
|
role, members):
|
2015-09-16 14:06:05 +03:00
|
|
|
if not members:
|
|
|
|
return None
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-16-git-send-email-eblake@redhat.com>
[Incorrect & unused -wrapper case in _pretty_owner() dropped]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:50 +03:00
|
|
|
# See also QAPISchemaObjectTypeMember._pretty_owner()
|
2016-03-18 01:48:29 +03:00
|
|
|
name = 'q_obj_%s-%s' % (name, role)
|
2018-07-03 18:56:36 +03:00
|
|
|
typ = self.lookup_entity(name, QAPISchemaObjectType)
|
|
|
|
if typ:
|
|
|
|
# The implicit object type has multiple users. This can
|
|
|
|
# happen only for simple unions' implicit wrapper types.
|
|
|
|
# Its ifcond should be the disjunction of its user's
|
|
|
|
# ifconds. Not implemented. Instead, we always pass the
|
|
|
|
# wrapped type's ifcond, which is trivially the same for all
|
|
|
|
# users. It's also necessary for the wrapper to compile.
|
|
|
|
# But it's not tight: the disjunction need not imply it. We
|
|
|
|
# may end up compiling useless wrapper types.
|
|
|
|
# TODO kill simple unions or implement the disjunction
|
2018-07-03 18:56:37 +03:00
|
|
|
assert ifcond == typ._ifcond # pylint: disable=protected-access
|
2018-07-03 18:56:36 +03:00
|
|
|
else:
|
|
|
|
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
|
2019-06-06 18:37:57 +03:00
|
|
|
None, members, None, []))
|
2015-09-16 14:06:05 +03:00
|
|
|
return name
|
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_enum_type(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['enum']
|
|
|
|
data = expr['data']
|
|
|
|
prefix = expr.get('prefix')
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2015-12-02 08:20:55 +03:00
|
|
|
self._def_entity(QAPISchemaEnumType(
|
2018-07-03 18:56:36 +03:00
|
|
|
name, info, doc, ifcond,
|
|
|
|
self._make_enum_members(data), prefix))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2018-12-13 15:37:15 +03:00
|
|
|
def _make_member(self, name, typ, ifcond, info):
|
2015-09-16 14:06:05 +03:00
|
|
|
optional = False
|
|
|
|
if name.startswith('*'):
|
|
|
|
name = name[1:]
|
|
|
|
optional = True
|
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
2015-10-13 07:22:32 +03:00
|
|
|
typ = self._make_array_type(typ[0], info)
|
2018-12-13 15:37:15 +03:00
|
|
|
return QAPISchemaObjectTypeMember(name, typ, optional, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2015-10-13 07:22:32 +03:00
|
|
|
def _make_members(self, data, info):
|
2018-12-13 15:37:15 +03:00
|
|
|
return [self._make_member(key, value['type'], value.get('if'), info)
|
2018-01-16 16:42:05 +03:00
|
|
|
for (key, value) in data.items()]
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_struct_type(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['struct']
|
|
|
|
base = expr.get('base')
|
|
|
|
data = expr['data']
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2019-06-06 18:37:57 +03:00
|
|
|
features = expr.get('features', [])
|
2018-07-03 18:56:36 +03:00
|
|
|
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base,
|
2015-10-13 07:22:32 +03:00
|
|
|
self._make_members(data, info),
|
2019-06-06 18:37:57 +03:00
|
|
|
None,
|
|
|
|
self._make_features(features)))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2018-12-13 15:37:18 +03:00
|
|
|
def _make_variant(self, case, typ, ifcond):
|
|
|
|
return QAPISchemaObjectTypeVariant(case, typ, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2018-12-13 15:37:17 +03:00
|
|
|
def _make_simple_variant(self, case, typ, ifcond, info):
|
2015-09-16 14:06:05 +03:00
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
2015-10-13 07:22:32 +03:00
|
|
|
typ = self._make_array_type(typ[0], info)
|
|
|
|
typ = self._make_implicit_object_type(
|
2018-07-03 18:56:37 +03:00
|
|
|
typ, info, None, self.lookup_type(typ),
|
2018-12-13 15:37:15 +03:00
|
|
|
'wrapper', [self._make_member('data', typ, None, info)])
|
2018-12-13 15:37:17 +03:00
|
|
|
return QAPISchemaObjectTypeVariant(case, typ, ifcond)
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_union_type(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['union']
|
|
|
|
data = expr['data']
|
|
|
|
base = expr.get('base')
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2015-09-16 14:06:05 +03:00
|
|
|
tag_name = expr.get('discriminator')
|
2015-10-13 07:22:29 +03:00
|
|
|
tag_member = None
|
2016-03-18 01:48:39 +03:00
|
|
|
if isinstance(base, dict):
|
2018-07-03 18:56:36 +03:00
|
|
|
base = self._make_implicit_object_type(
|
|
|
|
name, info, doc, ifcond,
|
|
|
|
'base', self._make_members(base, info))
|
2015-09-16 14:06:05 +03:00
|
|
|
if tag_name:
|
2018-12-13 15:37:18 +03:00
|
|
|
variants = [self._make_variant(key, value['type'], value.get('if'))
|
2018-01-16 16:42:05 +03:00
|
|
|
for (key, value) in data.items()]
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-2-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:36 +03:00
|
|
|
members = []
|
2015-09-16 14:06:05 +03:00
|
|
|
else:
|
2018-12-13 15:37:17 +03:00
|
|
|
variants = [self._make_simple_variant(key, value['type'],
|
|
|
|
value.get('if'), info)
|
2018-01-16 16:42:05 +03:00
|
|
|
for (key, value) in data.items()]
|
2018-12-13 15:37:17 +03:00
|
|
|
enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
|
2018-12-13 15:37:08 +03:00
|
|
|
typ = self._make_implicit_enum_type(name, info, ifcond, enum)
|
2015-12-02 08:20:50 +03:00
|
|
|
tag_member = QAPISchemaObjectTypeMember('type', typ, False)
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-2-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:52:36 +03:00
|
|
|
members = [tag_member]
|
2015-09-16 14:06:05 +03:00
|
|
|
self._def_entity(
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaObjectType(name, info, doc, ifcond, base, members,
|
2015-09-16 14:06:05 +03:00
|
|
|
QAPISchemaObjectTypeVariants(tag_name,
|
2015-10-13 07:22:29 +03:00
|
|
|
tag_member,
|
2019-06-06 18:37:57 +03:00
|
|
|
variants), []))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_alternate_type(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['alternate']
|
|
|
|
data = expr['data']
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2018-12-13 15:37:18 +03:00
|
|
|
variants = [self._make_variant(key, value['type'], value.get('if'))
|
2018-01-16 16:42:05 +03:00
|
|
|
for (key, value) in data.items()]
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1449033659-25497-5-git-send-email-eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-12-02 08:20:48 +03:00
|
|
|
tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
|
2015-09-16 14:06:05 +03:00
|
|
|
self._def_entity(
|
2018-07-03 18:56:36 +03:00
|
|
|
QAPISchemaAlternateType(name, info, doc, ifcond,
|
2015-09-16 14:06:05 +03:00
|
|
|
QAPISchemaObjectTypeVariants(None,
|
2015-10-13 07:22:29 +03:00
|
|
|
tag_member,
|
2015-09-16 14:06:05 +03:00
|
|
|
variants)))
|
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_command(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['command']
|
|
|
|
data = expr.get('data')
|
|
|
|
rets = expr.get('returns')
|
|
|
|
gen = expr.get('gen', True)
|
|
|
|
success_response = expr.get('success-response', True)
|
2016-07-14 06:50:19 +03:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-03-09 12:00:00 +03:00
|
|
|
allow_oob = expr.get('allow-oob', False)
|
2018-05-11 19:51:43 +03:00
|
|
|
allow_preconfig = expr.get('allow-preconfig', False)
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2015-09-16 14:06:05 +03:00
|
|
|
if isinstance(data, OrderedDict):
|
2015-10-13 07:22:32 +03:00
|
|
|
data = self._make_implicit_object_type(
|
2018-07-03 18:56:36 +03:00
|
|
|
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
2015-09-16 14:06:05 +03:00
|
|
|
if isinstance(rets, list):
|
|
|
|
assert len(rets) == 1
|
2015-10-13 07:22:32 +03:00
|
|
|
rets = self._make_array_type(rets[0], info)
|
2018-07-03 18:56:36 +03:00
|
|
|
self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
|
2018-03-09 12:00:00 +03:00
|
|
|
gen, success_response,
|
2018-05-11 19:51:43 +03:00
|
|
|
boxed, allow_oob, allow_preconfig))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2017-03-15 15:57:03 +03:00
|
|
|
def _def_event(self, expr, info, doc):
|
2015-09-16 14:06:05 +03:00
|
|
|
name = expr['event']
|
|
|
|
data = expr.get('data')
|
2016-07-14 06:50:19 +03:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-07-03 18:56:36 +03:00
|
|
|
ifcond = expr.get('if')
|
2015-09-16 14:06:05 +03:00
|
|
|
if isinstance(data, OrderedDict):
|
2015-10-13 07:22:32 +03:00
|
|
|
data = self._make_implicit_object_type(
|
2018-07-03 18:56:36 +03:00
|
|
|
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
|
|
|
self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
|
2015-09-16 14:06:05 +03:00
|
|
|
|
2018-02-11 12:35:50 +03:00
|
|
|
def _def_exprs(self, exprs):
|
|
|
|
for expr_elem in exprs:
|
2015-09-16 14:06:05 +03:00
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2017-03-15 15:57:03 +03:00
|
|
|
doc = expr_elem.get('doc')
|
2015-09-16 14:06:05 +03:00
|
|
|
if 'enum' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_enum_type(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
elif 'struct' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_struct_type(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
elif 'union' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_union_type(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
elif 'alternate' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_alternate_type(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
elif 'command' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_command(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
elif 'event' in expr:
|
2017-03-15 15:57:03 +03:00
|
|
|
self._def_event(expr, info, doc)
|
2018-02-11 12:35:53 +03:00
|
|
|
elif 'include' in expr:
|
2018-02-11 12:35:55 +03:00
|
|
|
self._def_include(expr, info, doc)
|
2015-09-16 14:06:05 +03:00
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
|
|
|
|
def check(self):
|
2018-02-11 12:35:54 +03:00
|
|
|
for ent in self._entity_list:
|
2015-09-16 14:06:05 +03:00
|
|
|
ent.check(self)
|
2015-06-10 09:55:21 +03:00
|
|
|
|
2015-09-16 14:06:07 +03:00
|
|
|
def visit(self, visitor):
|
2015-10-13 07:22:21 +03:00
|
|
|
visitor.visit_begin(self)
|
2018-02-11 12:35:55 +03:00
|
|
|
module = None
|
2019-02-14 18:22:36 +03:00
|
|
|
visitor.visit_module(module)
|
2018-02-11 12:35:54 +03:00
|
|
|
for entity in self._entity_list:
|
2015-10-13 07:22:21 +03:00
|
|
|
if visitor.visit_needed(entity):
|
2018-02-11 12:35:55 +03:00
|
|
|
if entity.module != module:
|
|
|
|
module = entity.module
|
|
|
|
visitor.visit_module(module)
|
2015-10-13 07:22:21 +03:00
|
|
|
entity.visit(visitor)
|
2015-09-16 14:06:07 +03:00
|
|
|
visitor.visit_end()
|
|
|
|
|
2014-03-05 06:44:34 +04:00
|
|
|
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
|
|
|
# Code generation helpers
|
|
|
|
#
|
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
def camel_case(name):
|
|
|
|
new_name = ''
|
|
|
|
first = True
|
|
|
|
for ch in name:
|
|
|
|
if ch in ['_', '-']:
|
|
|
|
first = True
|
|
|
|
elif first:
|
|
|
|
new_name += ch.upper()
|
|
|
|
first = False
|
|
|
|
else:
|
|
|
|
new_name += ch.lower()
|
|
|
|
return new_name
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-05-14 15:50:53 +03:00
|
|
|
# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
|
|
|
|
# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
|
|
|
|
# ENUM24_Name -> ENUM24_NAME
|
|
|
|
def camel_to_upper(value):
|
|
|
|
c_fun_str = c_name(value, False)
|
|
|
|
if value.isupper():
|
|
|
|
return c_fun_str
|
|
|
|
|
|
|
|
new_name = ''
|
2018-06-21 11:35:51 +03:00
|
|
|
length = len(c_fun_str)
|
|
|
|
for i in range(length):
|
2015-05-14 15:50:53 +03:00
|
|
|
c = c_fun_str[i]
|
2017-03-15 15:57:08 +03:00
|
|
|
# When c is upper and no '_' appears before, do more checks
|
|
|
|
if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
|
2018-06-21 11:35:51 +03:00
|
|
|
if i < length - 1 and c_fun_str[i + 1].islower():
|
2015-09-30 01:21:02 +03:00
|
|
|
new_name += '_'
|
|
|
|
elif c_fun_str[i - 1].isdigit():
|
2015-05-14 15:50:53 +03:00
|
|
|
new_name += '_'
|
|
|
|
new_name += c
|
|
|
|
return new_name.lstrip('_').upper()
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-08-26 16:21:20 +03:00
|
|
|
def c_enum_const(type_name, const_name, prefix=None):
|
|
|
|
if prefix is not None:
|
|
|
|
type_name = prefix
|
qapi: Change munging of CamelCase enum values
When munging enum values, the fact that we were passing the entire
prefix + value through camel_to_upper() meant that enum values
spelled with CamelCase could be turned into CAMEL_CASE. However,
this provides a potential collision (both OneTwo and One-Two would
munge into ONE_TWO) for enum types, when the same two names are
valid side-by-side as QAPI member names. By changing the generation
of enum constants to always be prefix + '_' + c_name(value,
False).upper(), and ensuring that there are no case collisions (in
the next patches), we no longer have to worry about names that
would be distinct as QAPI members but collide as variant tag names,
without having to think about what munging the heuristics in
camel_to_upper() will actually perform on an enum value.
Making the change will affect enums that did not follow coding
conventions, using 'CamelCase' rather than desired 'lower-case'.
Thankfully, there are only two culprits: InputButton and ErrorClass.
We already tweaked ErrorClass to make it an alias of QapiErrorClass,
where only the alias needs changing rather than the whole tree. So
the bulk of this change is modifying INPUT_BUTTON_WHEEL_UP to the
new INPUT_BUTTON_WHEELUP (and likewise for WHEELDOWN). That part
of this commit may later need reverting if we rename the enum
constants from 'WheelUp' to 'wheel-up' as part of moving
x-input-send-event to a stable interface; but at least we have
documentation bread crumbs in place to remind us (commit 513e7cd),
and it matches the fact that SDL constants are also spelled
SDL_BUTTON_WHEELUP.
Suggested by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1447836791-369-27-git-send-email-eblake@redhat.com>
[Commit message tweaked]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 11:53:01 +03:00
|
|
|
return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
|
2015-05-14 15:50:53 +03:00
|
|
|
|
2018-06-21 11:35:51 +03:00
|
|
|
|
2018-01-16 16:42:08 +03:00
|
|
|
if hasattr(str, 'maketrans'):
|
2018-02-11 12:36:05 +03:00
|
|
|
c_name_trans = str.maketrans('.-', '__')
|
2018-01-16 16:42:08 +03:00
|
|
|
else:
|
2018-02-11 12:36:05 +03:00
|
|
|
c_name_trans = string.maketrans('.-', '__')
|
2015-05-14 15:50:47 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
qapi: Make c_type() consistently convert qapi names
Continuing the string of cleanups for supporting downstream names
containing '.', this patch focuses on ensuring c_type() can
handle a downstream name. This patch alone does not fix the
places where generator output should be calling this function
but was open-coding things instead, but it gets us a step closer.
In particular, the changes to c_list_type() and type_name() mean
that type_name(FOO) now handles the case when FOO contains '.',
'-', or is a ticklish identifier other than a builtin (builtins
are exempted because ['int'] must remain mapped to 'intList' and
not 'q_intList'). Meanwhile, ['unix'] now maps to 'q_unixList'
rather than 'unixList', to match the fact that 'unix' is ticklish;
however, our naming conventions state that complex types should
start with a capital, so no type name following conventions will
ever have the 'q_' prepended.
Likewise, changes to c_type() mean that c_type(FOO) properly
handles an enum or complex type FOO with '.' or '-' in the
name, or is a ticklish identifier (again, a ticklish identifier
as a type name violates conventions).
Signed-off-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-05-14 15:50:55 +03:00
|
|
|
# Map @name to a valid C identifier.
|
|
|
|
# If @protect, avoid returning certain ticklish identifiers (like
|
2017-03-15 15:57:08 +03:00
|
|
|
# C keywords) by prepending 'q_'.
|
qapi: Make c_type() consistently convert qapi names
Continuing the string of cleanups for supporting downstream names
containing '.', this patch focuses on ensuring c_type() can
handle a downstream name. This patch alone does not fix the
places where generator output should be calling this function
but was open-coding things instead, but it gets us a step closer.
In particular, the changes to c_list_type() and type_name() mean
that type_name(FOO) now handles the case when FOO contains '.',
'-', or is a ticklish identifier other than a builtin (builtins
are exempted because ['int'] must remain mapped to 'intList' and
not 'q_intList'). Meanwhile, ['unix'] now maps to 'q_unixList'
rather than 'unixList', to match the fact that 'unix' is ticklish;
however, our naming conventions state that complex types should
start with a capital, so no type name following conventions will
ever have the 'q_' prepended.
Likewise, changes to c_type() mean that c_type(FOO) properly
handles an enum or complex type FOO with '.' or '-' in the
name, or is a ticklish identifier (again, a ticklish identifier
as a type name violates conventions).
Signed-off-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-05-14 15:50:55 +03:00
|
|
|
#
|
|
|
|
# Used for converting 'name' from a 'name':'type' qapi definition
|
|
|
|
# into a generated struct member, as well as converting type names
|
|
|
|
# into substrings of a generated C function name.
|
|
|
|
# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
|
|
|
|
# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
|
2015-05-14 15:50:48 +03:00
|
|
|
def c_name(name, protect=True):
|
2012-07-30 19:46:55 +04:00
|
|
|
# ANSI X3J11/88-090, 3.1.1
|
|
|
|
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
|
2015-09-30 01:21:02 +03:00
|
|
|
'default', 'do', 'double', 'else', 'enum', 'extern',
|
|
|
|
'float', 'for', 'goto', 'if', 'int', 'long', 'register',
|
|
|
|
'return', 'short', 'signed', 'sizeof', 'static',
|
|
|
|
'struct', 'switch', 'typedef', 'union', 'unsigned',
|
|
|
|
'void', 'volatile', 'while'])
|
2012-07-30 19:46:55 +04:00
|
|
|
# ISO/IEC 9899:1999, 6.4.1
|
|
|
|
c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
|
|
|
|
# ISO/IEC 9899:2011, 6.4.1
|
2015-09-30 01:21:02 +03:00
|
|
|
c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
|
|
|
|
'_Noreturn', '_Static_assert', '_Thread_local'])
|
2012-07-30 19:46:55 +04:00
|
|
|
# GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
|
|
|
|
# excluding _.*
|
|
|
|
gcc_words = set(['asm', 'typeof'])
|
2013-08-07 19:39:43 +04:00
|
|
|
# C++ ISO/IEC 14882:2003 2.11
|
|
|
|
cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
|
|
|
|
'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
|
|
|
|
'namespace', 'new', 'operator', 'private', 'protected',
|
|
|
|
'public', 'reinterpret_cast', 'static_cast', 'template',
|
|
|
|
'this', 'throw', 'true', 'try', 'typeid', 'typename',
|
|
|
|
'using', 'virtual', 'wchar_t',
|
|
|
|
# alternative representations
|
|
|
|
'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
|
|
|
|
'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
|
2012-09-19 18:31:07 +04:00
|
|
|
# namespace pollution:
|
2018-04-27 22:28:49 +03:00
|
|
|
polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
|
2015-11-18 11:52:52 +03:00
|
|
|
name = name.translate(c_name_trans)
|
2015-09-30 01:21:02 +03:00
|
|
|
if protect and (name in c89_words | c99_words | c11_words | gcc_words
|
|
|
|
| cpp_words | polluted_words):
|
2017-03-15 15:57:08 +03:00
|
|
|
return 'q_' + name
|
2015-11-18 11:52:52 +03:00
|
|
|
return name
|
2011-07-19 23:50:39 +04:00
|
|
|
|
2018-06-21 11:35:51 +03:00
|
|
|
|
2014-06-10 15:25:53 +04:00
|
|
|
eatspace = '\033EATSPACE.'
|
2015-05-14 15:50:54 +03:00
|
|
|
pointer_suffix = ' *' + eatspace
|
2014-06-10 15:25:53 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
def genindent(count):
|
2017-03-15 15:57:08 +03:00
|
|
|
ret = ''
|
2015-09-30 01:21:02 +03:00
|
|
|
for _ in range(count):
|
2017-03-15 15:57:08 +03:00
|
|
|
ret += ' '
|
2011-07-19 23:50:39 +04:00
|
|
|
return ret
|
|
|
|
|
2018-06-21 11:35:51 +03:00
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
indent_level = 0
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
def push_indent(indent_amount=4):
|
|
|
|
global indent_level
|
|
|
|
indent_level += indent_amount
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
def pop_indent(indent_amount=4):
|
|
|
|
global indent_level
|
|
|
|
indent_level -= indent_amount
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2015-06-24 20:27:32 +03:00
|
|
|
# Generate @code with @kwds interpolated.
|
|
|
|
# Obey indent_level, and strip eatspace.
|
2011-07-19 23:50:39 +04:00
|
|
|
def cgen(code, **kwds):
|
2015-06-24 20:27:32 +03:00
|
|
|
raw = code % kwds
|
|
|
|
if indent_level:
|
|
|
|
indent = genindent(indent_level)
|
2015-09-07 18:45:55 +03:00
|
|
|
# re.subn() lacks flags support before Python 2.7, use re.compile()
|
2018-07-03 18:56:39 +03:00
|
|
|
raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
|
|
|
|
indent, raw)
|
2015-06-24 20:27:32 +03:00
|
|
|
raw = raw[0]
|
2017-03-15 15:57:07 +03:00
|
|
|
return re.sub(re.escape(eatspace) + r' *', '', raw)
|
2011-07-19 23:50:39 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2011-07-19 23:50:39 +04:00
|
|
|
def mcgen(code, **kwds):
|
2015-06-24 20:27:32 +03:00
|
|
|
if code[0] == '\n':
|
|
|
|
code = code[1:]
|
|
|
|
return cgen(code, **kwds)
|
2011-07-19 23:50:39 +04:00
|
|
|
|
|
|
|
|
2019-03-01 18:40:48 +03:00
|
|
|
def c_fname(filename):
|
|
|
|
return re.sub(r'[^A-Za-z0-9_]', '_', filename)
|
2013-05-11 02:46:00 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2013-05-11 02:46:00 +04:00
|
|
|
def guardstart(name):
|
|
|
|
return mcgen('''
|
|
|
|
#ifndef %(name)s
|
|
|
|
#define %(name)s
|
|
|
|
|
|
|
|
''',
|
2019-03-01 18:40:48 +03:00
|
|
|
name=c_fname(name).upper())
|
2013-05-11 02:46:00 +04:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2013-05-11 02:46:00 +04:00
|
|
|
def guardend(name):
|
|
|
|
return mcgen('''
|
|
|
|
|
|
|
|
#endif /* %(name)s */
|
|
|
|
''',
|
2019-03-01 18:40:48 +03:00
|
|
|
name=c_fname(name).upper())
|
2015-04-02 14:12:21 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-07-03 18:56:40 +03:00
|
|
|
def gen_if(ifcond):
|
|
|
|
ret = ''
|
|
|
|
for ifc in ifcond:
|
|
|
|
ret += mcgen('''
|
|
|
|
#if %(cond)s
|
|
|
|
''', cond=ifc)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def gen_endif(ifcond):
|
|
|
|
ret = ''
|
|
|
|
for ifc in reversed(ifcond):
|
|
|
|
ret += mcgen('''
|
|
|
|
#endif /* %(cond)s */
|
|
|
|
''', cond=ifc)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def _wrap_ifcond(ifcond, before, after):
|
|
|
|
if before == after:
|
|
|
|
return after # suppress empty #if ... #endif
|
|
|
|
|
|
|
|
assert after.startswith(before)
|
|
|
|
out = before
|
|
|
|
added = after[len(before):]
|
|
|
|
if added[0] == '\n':
|
|
|
|
out += '\n'
|
|
|
|
added = added[1:]
|
|
|
|
out += gen_if(ifcond)
|
|
|
|
out += added
|
|
|
|
out += gen_endif(ifcond)
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
2018-12-13 15:37:04 +03:00
|
|
|
def gen_enum_lookup(name, members, prefix=None):
|
2015-09-16 14:06:12 +03:00
|
|
|
ret = mcgen('''
|
|
|
|
|
2017-08-24 11:46:10 +03:00
|
|
|
const QEnumLookup %(c_name)s_lookup = {
|
|
|
|
.array = (const char *const[]) {
|
2015-09-16 14:06:12 +03:00
|
|
|
''',
|
2015-09-16 14:06:16 +03:00
|
|
|
c_name=c_name(name))
|
2018-12-13 15:37:04 +03:00
|
|
|
for m in members:
|
2018-12-13 15:37:19 +03:00
|
|
|
ret += gen_if(m.ifcond)
|
2018-12-13 15:37:04 +03:00
|
|
|
index = c_enum_const(name, m.name, prefix)
|
2015-09-16 14:06:12 +03:00
|
|
|
ret += mcgen('''
|
2018-12-13 15:37:04 +03:00
|
|
|
[%(index)s] = "%(name)s",
|
2015-09-16 14:06:12 +03:00
|
|
|
''',
|
2018-12-13 15:37:04 +03:00
|
|
|
index=index, name=m.name)
|
2018-12-13 15:37:19 +03:00
|
|
|
ret += gen_endif(m.ifcond)
|
2015-09-16 14:06:12 +03:00
|
|
|
|
|
|
|
ret += mcgen('''
|
2017-08-24 11:46:10 +03:00
|
|
|
},
|
|
|
|
.size = %(max_index)s
|
2015-09-16 14:06:12 +03:00
|
|
|
};
|
|
|
|
''',
|
2017-08-24 11:46:11 +03:00
|
|
|
max_index=c_enum_const(name, '_MAX', prefix))
|
2015-09-16 14:06:12 +03:00
|
|
|
return ret
|
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-12-13 15:37:04 +03:00
|
|
|
def gen_enum(name, members, prefix=None):
|
2015-09-16 14:06:16 +03:00
|
|
|
# append automatically generated _MAX value
|
2019-09-13 23:13:44 +03:00
|
|
|
enum_members = members + [QAPISchemaEnumMember('_MAX')]
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2015-09-16 14:06:16 +03:00
|
|
|
ret = mcgen('''
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2015-09-16 14:06:16 +03:00
|
|
|
typedef enum %(c_name)s {
|
2015-09-16 14:06:12 +03:00
|
|
|
''',
|
2015-09-16 14:06:16 +03:00
|
|
|
c_name=c_name(name))
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2018-12-13 15:37:04 +03:00
|
|
|
for m in enum_members:
|
2018-12-13 15:37:19 +03:00
|
|
|
ret += gen_if(m.ifcond)
|
2015-09-16 14:06:16 +03:00
|
|
|
ret += mcgen('''
|
2018-12-13 15:37:03 +03:00
|
|
|
%(c_enum)s,
|
2015-09-16 14:06:12 +03:00
|
|
|
''',
|
2018-12-13 15:37:04 +03:00
|
|
|
c_enum=c_enum_const(name, m.name, prefix))
|
2018-12-13 15:37:19 +03:00
|
|
|
ret += gen_endif(m.ifcond)
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2015-09-16 14:06:16 +03:00
|
|
|
ret += mcgen('''
|
|
|
|
} %(c_name)s;
|
2015-09-16 14:06:12 +03:00
|
|
|
''',
|
2015-09-16 14:06:16 +03:00
|
|
|
c_name=c_name(name))
|
|
|
|
|
|
|
|
ret += mcgen('''
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2017-08-24 11:46:07 +03:00
|
|
|
#define %(c_name)s_str(val) \\
|
2017-08-24 11:46:10 +03:00
|
|
|
qapi_enum_lookup(&%(c_name)s_lookup, (val))
|
2017-08-24 11:46:07 +03:00
|
|
|
|
2017-08-24 11:46:10 +03:00
|
|
|
extern const QEnumLookup %(c_name)s_lookup;
|
2015-09-16 14:06:16 +03:00
|
|
|
''',
|
|
|
|
c_name=c_name(name))
|
|
|
|
return ret
|
2015-09-16 14:06:12 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-08-15 16:37:36 +03:00
|
|
|
def build_params(arg_type, boxed, extra=None):
|
2015-09-16 14:06:20 +03:00
|
|
|
ret = ''
|
|
|
|
sep = ''
|
2016-07-14 06:50:19 +03:00
|
|
|
if boxed:
|
2018-08-15 16:37:36 +03:00
|
|
|
assert arg_type
|
2016-07-14 06:50:20 +03:00
|
|
|
ret += '%s arg' % arg_type.c_param_type()
|
|
|
|
sep = ', '
|
2018-08-15 16:37:36 +03:00
|
|
|
elif arg_type:
|
2016-07-14 06:50:19 +03:00
|
|
|
assert not arg_type.variants
|
|
|
|
for memb in arg_type.members:
|
|
|
|
ret += sep
|
|
|
|
sep = ', '
|
|
|
|
if memb.optional:
|
|
|
|
ret += 'bool has_%s, ' % c_name(memb.name)
|
|
|
|
ret += '%s %s' % (memb.type.c_param_type(),
|
|
|
|
c_name(memb.name))
|
2015-09-16 14:06:20 +03:00
|
|
|
if extra:
|
|
|
|
ret += sep + extra
|
2018-08-15 16:37:36 +03:00
|
|
|
return ret if ret else 'void'
|
2015-09-16 14:06:20 +03:00
|
|
|
|
2015-09-30 01:21:13 +03:00
|
|
|
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
2018-02-26 22:19:40 +03:00
|
|
|
# Accumulate and write output
|
2015-06-10 11:04:36 +03:00
|
|
|
#
|
|
|
|
|
2018-02-26 22:19:40 +03:00
|
|
|
class QAPIGen(object):
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def __init__(self, fname):
|
|
|
|
self.fname = fname
|
2018-02-26 22:19:40 +03:00
|
|
|
self._preamble = ''
|
|
|
|
self._body = ''
|
|
|
|
|
|
|
|
def preamble_add(self, text):
|
|
|
|
self._preamble += text
|
|
|
|
|
|
|
|
def add(self, text):
|
|
|
|
self._body += text
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def get_content(self):
|
|
|
|
return self._top() + self._preamble + self._body + self._bottom()
|
2018-07-03 18:56:40 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _top(self):
|
2018-02-26 22:19:40 +03:00
|
|
|
return ''
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _bottom(self):
|
2018-02-26 22:19:40 +03:00
|
|
|
return ''
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def write(self, output_dir):
|
|
|
|
pathname = os.path.join(output_dir, self.fname)
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
dir = os.path.dirname(pathname)
|
|
|
|
if dir:
|
2018-02-26 22:19:40 +03:00
|
|
|
try:
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
os.makedirs(dir)
|
2018-02-26 22:19:40 +03:00
|
|
|
except os.error as e:
|
|
|
|
if e.errno != errno.EEXIST:
|
|
|
|
raise
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
|
2018-06-18 20:59:57 +03:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
f = open(fd, 'r+', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
f = os.fdopen(fd, 'r+')
|
2019-03-01 18:40:47 +03:00
|
|
|
text = self.get_content()
|
2018-02-11 12:35:48 +03:00
|
|
|
oldtext = f.read(len(text) + 1)
|
|
|
|
if text != oldtext:
|
|
|
|
f.seek(0)
|
|
|
|
f.truncate(0)
|
|
|
|
f.write(text)
|
2018-02-26 22:19:40 +03:00
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
2018-07-03 18:56:40 +03:00
|
|
|
@contextmanager
|
|
|
|
def ifcontext(ifcond, *args):
|
|
|
|
"""A 'with' statement context manager to wrap with start_if()/end_if()
|
2018-02-26 22:19:40 +03:00
|
|
|
|
2018-07-03 18:56:40 +03:00
|
|
|
*args: any number of QAPIGenCCode
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
with ifcontext(ifcond, self._genh, self._genc):
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
|
|
|
|
Is equivalent to calling::
|
|
|
|
|
|
|
|
self._genh.start_if(ifcond)
|
|
|
|
self._genc.start_if(ifcond)
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
self._genh.end_if()
|
|
|
|
self._genc.end_if()
|
|
|
|
"""
|
|
|
|
for arg in args:
|
|
|
|
arg.start_if(ifcond)
|
|
|
|
yield
|
|
|
|
for arg in args:
|
|
|
|
arg.end_if()
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenCCode(QAPIGen):
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def __init__(self, fname):
|
|
|
|
QAPIGen.__init__(self, fname)
|
2018-07-03 18:56:40 +03:00
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def start_if(self, ifcond):
|
|
|
|
assert self._start_if is None
|
|
|
|
self._start_if = (ifcond, self._body, self._preamble)
|
|
|
|
|
|
|
|
def end_if(self):
|
|
|
|
assert self._start_if
|
|
|
|
self._wrap_ifcond()
|
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def _wrap_ifcond(self):
|
|
|
|
self._body = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[1], self._body)
|
|
|
|
self._preamble = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[2], self._preamble)
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def get_content(self):
|
2018-07-03 18:56:40 +03:00
|
|
|
assert self._start_if is None
|
2019-03-01 18:40:47 +03:00
|
|
|
return QAPIGen.get_content(self)
|
2018-07-03 18:56:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenC(QAPIGenCCode):
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def __init__(self, fname, blurb, pydoc):
|
|
|
|
QAPIGenCCode.__init__(self, fname)
|
2018-02-26 22:19:40 +03:00
|
|
|
self._blurb = blurb
|
|
|
|
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
|
|
|
|
re.MULTILINE))
|
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _top(self):
|
2018-02-26 22:19:40 +03:00
|
|
|
return mcgen('''
|
|
|
|
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
|
2018-02-11 12:35:40 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
%(blurb)s
|
2018-02-11 12:35:41 +03:00
|
|
|
*
|
|
|
|
* %(copyright)s
|
2018-02-11 12:35:40 +03:00
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
''',
|
2018-02-26 22:19:40 +03:00
|
|
|
blurb=self._blurb, copyright=self._copyright)
|
2015-04-02 15:46:39 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _bottom(self):
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
return mcgen('''
|
2018-08-28 15:07:35 +03:00
|
|
|
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
/* Dummy declaration to prevent empty .o file */
|
2019-03-01 18:40:48 +03:00
|
|
|
char qapi_dummy_%(name)s;
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
''',
|
2019-03-01 18:40:48 +03:00
|
|
|
name=c_fname(self.fname))
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
|
2015-04-02 15:46:39 +03:00
|
|
|
|
2018-02-26 22:19:40 +03:00
|
|
|
class QAPIGenH(QAPIGenC):
|
2015-04-02 15:46:39 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _top(self):
|
|
|
|
return QAPIGenC._top(self) + guardstart(self.fname)
|
2015-04-02 15:46:39 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _bottom(self):
|
|
|
|
return guardend(self.fname)
|
2015-04-02 15:46:39 +03:00
|
|
|
|
2015-09-30 01:21:02 +03:00
|
|
|
|
2018-02-26 22:19:40 +03:00
|
|
|
class QAPIGenDoc(QAPIGen):
|
2018-02-11 12:35:40 +03:00
|
|
|
|
2019-03-01 18:40:47 +03:00
|
|
|
def _top(self):
|
|
|
|
return (QAPIGen._top(self)
|
2018-02-26 22:19:40 +03:00
|
|
|
+ '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
|
2018-02-26 22:50:08 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
|
|
|
|
|
|
|
|
def __init__(self, prefix, what, blurb, pydoc):
|
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
2019-03-01 18:40:47 +03:00
|
|
|
self._genc = QAPIGenC(self._prefix + self._what + '.c',
|
|
|
|
blurb, pydoc)
|
|
|
|
self._genh = QAPIGenH(self._prefix + self._what + '.h',
|
|
|
|
blurb, pydoc)
|
2018-02-26 22:50:08 +03:00
|
|
|
|
|
|
|
def write(self, output_dir):
|
2019-03-01 18:40:47 +03:00
|
|
|
self._genc.write(output_dir)
|
|
|
|
self._genh.write(output_dir)
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaModularCVisitor(QAPISchemaVisitor):
|
|
|
|
|
|
|
|
def __init__(self, prefix, what, blurb, pydoc):
|
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
|
|
|
self._blurb = blurb
|
|
|
|
self._pydoc = pydoc
|
2019-02-14 18:22:36 +03:00
|
|
|
self._genc = None
|
|
|
|
self._genh = None
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
self._module = {}
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
self._main_module = None
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
|
2019-02-14 18:22:37 +03:00
|
|
|
@staticmethod
|
|
|
|
def _is_user_module(name):
|
|
|
|
return name and not name.startswith('./')
|
|
|
|
|
2019-02-14 18:22:36 +03:00
|
|
|
@staticmethod
|
|
|
|
def _is_builtin_module(name):
|
|
|
|
return not name
|
|
|
|
|
2019-03-01 18:40:48 +03:00
|
|
|
def _module_dirname(self, what, name):
|
|
|
|
if self._is_user_module(name):
|
|
|
|
return os.path.dirname(name)
|
|
|
|
return ''
|
|
|
|
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
def _module_basename(self, what, name):
|
2019-02-14 18:22:37 +03:00
|
|
|
ret = '' if self._is_builtin_module(name) else self._prefix
|
|
|
|
if self._is_user_module(name):
|
2019-03-01 18:40:48 +03:00
|
|
|
basename = os.path.basename(name)
|
2019-02-14 18:22:37 +03:00
|
|
|
ret += what
|
|
|
|
if name != self._main_module:
|
|
|
|
ret += '-' + os.path.splitext(basename)[0]
|
|
|
|
else:
|
|
|
|
name = name[2:] if name else 'builtin'
|
|
|
|
ret += re.sub(r'-', '-' + name + '-', what)
|
|
|
|
return ret
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
|
2019-03-01 18:40:48 +03:00
|
|
|
def _module_filename(self, what, name):
|
|
|
|
return os.path.join(self._module_dirname(what, name),
|
|
|
|
self._module_basename(what, name))
|
|
|
|
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
def _add_module(self, name, blurb):
|
2019-03-01 18:40:48 +03:00
|
|
|
basename = self._module_filename(self._what, name)
|
2019-03-01 18:40:47 +03:00
|
|
|
genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
|
|
|
|
genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
self._module[name] = (genc, genh)
|
|
|
|
self._set_module(name)
|
|
|
|
|
2019-02-14 18:22:37 +03:00
|
|
|
def _add_user_module(self, name, blurb):
|
|
|
|
assert self._is_user_module(name)
|
|
|
|
if self._main_module is None:
|
|
|
|
self._main_module = name
|
|
|
|
self._add_module(name, blurb)
|
|
|
|
|
|
|
|
def _add_system_module(self, name, blurb):
|
|
|
|
self._add_module(name and './' + name, blurb)
|
|
|
|
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
def _set_module(self, name):
|
|
|
|
self._genc, self._genh = self._module[name]
|
|
|
|
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
def write(self, output_dir, opt_builtins=False):
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
for name in self._module:
|
2019-02-14 18:22:36 +03:00
|
|
|
if self._is_builtin_module(name) and not opt_builtins:
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
continue
|
|
|
|
(genc, genh) = self._module[name]
|
2019-03-01 18:40:47 +03:00
|
|
|
genc.write(output_dir)
|
|
|
|
genh.write(output_dir)
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
|
2019-02-14 18:22:36 +03:00
|
|
|
def _begin_user_module(self, name):
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-21-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
[eblake: fix octal constant for python 3]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-27 01:29:21 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_module(self, name):
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
if name in self._module:
|
|
|
|
self._set_module(name)
|
2019-02-14 18:22:36 +03:00
|
|
|
elif self._is_builtin_module(name):
|
|
|
|
# The built-in module has not been created. No code may
|
|
|
|
# be generated.
|
|
|
|
self._genc = None
|
|
|
|
self._genh = None
|
|
|
|
else:
|
2019-02-14 18:22:37 +03:00
|
|
|
self._add_user_module(name, self._blurb)
|
2019-02-14 18:22:36 +03:00
|
|
|
self._begin_user_module(name)
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
|
|
|
|
def visit_include(self, name, info):
|
2019-03-01 18:40:48 +03:00
|
|
|
relname = os.path.relpath(self._module_filename(self._what, name),
|
|
|
|
os.path.dirname(self._genh.fname))
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
self._genh.preamble_add(mcgen('''
|
2019-03-01 18:40:48 +03:00
|
|
|
#include "%(relname)s.h"
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20180211093607.27351-23-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: declare a dummy variable in each .c file, to shut up OSX
toolchain warnings about empty .o files, including hacking c_name()]
Signed-off-by: Eric Blake <eblake@redhat.com>
2018-02-11 12:36:00 +03:00
|
|
|
''',
|
2019-03-01 18:40:48 +03:00
|
|
|
relname=relname))
|