2011-07-19 21:50:39 +02:00
|
|
|
#
|
|
|
|
# QAPI helper library
|
|
|
|
#
|
|
|
|
# Copyright IBM, Corp. 2011
|
2018-02-26 20:19:40 +01:00
|
|
|
# Copyright (c) 2013-2018 Red Hat Inc.
|
2011-07-19 21:50:39 +02: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 17:41:55 +02:00
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
2011-07-19 21:50:39 +02:00
|
|
|
#
|
2014-03-01 08:40:34 +01: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 21:50:39 +02:00
|
|
|
|
2017-03-15 13:57:35 +01:00
|
|
|
import re
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2020-10-09 18:15:34 +02:00
|
|
|
EATSPACE = '\033EATSPACE.'
|
|
|
|
POINTER_SUFFIX = ' *' + EATSPACE
|
|
|
|
_C_NAME_TRANS = str.maketrans('.-', '__')
|
|
|
|
|
|
|
|
|
2015-05-14 14:50:53 +02: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 10:35:51 +02:00
|
|
|
length = len(c_fun_str)
|
|
|
|
for i in range(length):
|
2020-10-09 18:15:35 +02:00
|
|
|
char = c_fun_str[i]
|
|
|
|
# When char is upper case and no '_' appears before, do more checks
|
|
|
|
if char.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
|
2018-06-21 10:35:51 +02:00
|
|
|
if i < length - 1 and c_fun_str[i + 1].islower():
|
2015-09-30 00:21:02 +02:00
|
|
|
new_name += '_'
|
|
|
|
elif c_fun_str[i - 1].isdigit():
|
2015-05-14 14:50:53 +02:00
|
|
|
new_name += '_'
|
2020-10-09 18:15:35 +02:00
|
|
|
new_name += char
|
2015-05-14 14:50:53 +02:00
|
|
|
return new_name.lstrip('_').upper()
|
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2015-08-26 15:21:20 +02: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 09:53:01 +01:00
|
|
|
return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
|
2015-05-14 14:50:53 +02:00
|
|
|
|
2018-06-21 10:35:51 +02: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 14:50:55 +02:00
|
|
|
# Map @name to a valid C identifier.
|
|
|
|
# If @protect, avoid returning certain ticklish identifiers (like
|
2017-03-15 13:57:08 +01: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 14:50:55 +02: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 14:50:48 +02:00
|
|
|
def c_name(name, protect=True):
|
2012-07-30 17:46:55 +02:00
|
|
|
# ANSI X3J11/88-090, 3.1.1
|
|
|
|
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
|
2015-09-30 00:21:02 +02: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 17:46:55 +02: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 00:21:02 +02:00
|
|
|
c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
|
|
|
|
'_Noreturn', '_Static_assert', '_Thread_local'])
|
2012-07-30 17:46:55 +02:00
|
|
|
# GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
|
|
|
|
# excluding _.*
|
|
|
|
gcc_words = set(['asm', 'typeof'])
|
2013-08-07 17:39:43 +02: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 16:31:07 +02:00
|
|
|
# namespace pollution:
|
2018-04-27 21:28:49 +02:00
|
|
|
polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
|
2020-10-09 18:15:34 +02:00
|
|
|
name = name.translate(_C_NAME_TRANS)
|
2015-09-30 00:21:02 +02:00
|
|
|
if protect and (name in c89_words | c99_words | c11_words | gcc_words
|
|
|
|
| cpp_words | polluted_words):
|
2017-03-15 13:57:08 +01:00
|
|
|
return 'q_' + name
|
2015-11-18 09:52:52 +01:00
|
|
|
return name
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2018-06-21 10:35:51 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
class Indentation:
|
|
|
|
"""
|
|
|
|
Indentation level management.
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
:param initial: Initial number of spaces, default 0.
|
|
|
|
"""
|
|
|
|
def __init__(self, initial: int = 0) -> None:
|
|
|
|
self._level = initial
|
2018-06-21 10:35:51 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
def __int__(self) -> int:
|
|
|
|
return self._level
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
return "{}({:d})".format(type(self).__name__, self._level)
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
def __str__(self) -> str:
|
|
|
|
"""Return the current indentation as a string of spaces."""
|
|
|
|
return ' ' * self._level
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
def __bool__(self) -> bool:
|
|
|
|
"""True when there is a non-zero indentation."""
|
|
|
|
return bool(self._level)
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2020-10-09 18:15:33 +02:00
|
|
|
def increase(self, amount: int = 4) -> None:
|
|
|
|
"""Increase the indentation level by ``amount``, default 4."""
|
|
|
|
self._level += amount
|
|
|
|
|
|
|
|
def decrease(self, amount: int = 4) -> None:
|
|
|
|
"""Decrease the indentation level by ``amount``, default 4."""
|
|
|
|
if self._level < amount:
|
|
|
|
raise ArithmeticError(
|
|
|
|
f"Can't remove {amount:d} spaces from {self!r}")
|
|
|
|
self._level -= amount
|
|
|
|
|
|
|
|
|
|
|
|
indent = Indentation()
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2015-06-24 19:27:32 +02:00
|
|
|
# Generate @code with @kwds interpolated.
|
2020-10-09 18:15:34 +02:00
|
|
|
# Obey indent, and strip EATSPACE.
|
2011-07-19 21:50:39 +02:00
|
|
|
def cgen(code, **kwds):
|
2015-06-24 19:27:32 +02:00
|
|
|
raw = code % kwds
|
2020-10-09 18:15:33 +02:00
|
|
|
if indent:
|
|
|
|
raw = re.sub(r'^(?!(#|$))', str(indent), raw, flags=re.MULTILINE)
|
2020-10-09 18:15:34 +02:00
|
|
|
return re.sub(re.escape(EATSPACE) + r' *', '', raw)
|
2011-07-19 21:50:39 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2011-07-19 21:50:39 +02:00
|
|
|
def mcgen(code, **kwds):
|
2015-06-24 19:27:32 +02:00
|
|
|
if code[0] == '\n':
|
|
|
|
code = code[1:]
|
|
|
|
return cgen(code, **kwds)
|
2011-07-19 21:50:39 +02:00
|
|
|
|
|
|
|
|
2019-03-01 16:40:48 +01:00
|
|
|
def c_fname(filename):
|
|
|
|
return re.sub(r'[^A-Za-z0-9_]', '_', filename)
|
2013-05-11 00:46:00 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2013-05-11 00:46:00 +02:00
|
|
|
def guardstart(name):
|
|
|
|
return mcgen('''
|
|
|
|
#ifndef %(name)s
|
|
|
|
#define %(name)s
|
|
|
|
|
|
|
|
''',
|
2019-03-01 16:40:48 +01:00
|
|
|
name=c_fname(name).upper())
|
2013-05-11 00:46:00 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2013-05-11 00:46:00 +02:00
|
|
|
def guardend(name):
|
|
|
|
return mcgen('''
|
|
|
|
|
|
|
|
#endif /* %(name)s */
|
|
|
|
''',
|
2019-03-01 16:40:48 +01:00
|
|
|
name=c_fname(name).upper())
|
2015-04-02 13:12:21 +02:00
|
|
|
|
2015-09-30 00:21:02 +02:00
|
|
|
|
2018-07-03 17:56:40 +02: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
|
|
|
|
|
|
|
|
|
2018-08-15 15:37:36 +02:00
|
|
|
def build_params(arg_type, boxed, extra=None):
|
2015-09-16 13:06:20 +02:00
|
|
|
ret = ''
|
|
|
|
sep = ''
|
2016-07-14 05:50:19 +02:00
|
|
|
if boxed:
|
2018-08-15 15:37:36 +02:00
|
|
|
assert arg_type
|
2016-07-14 05:50:20 +02:00
|
|
|
ret += '%s arg' % arg_type.c_param_type()
|
|
|
|
sep = ', '
|
2018-08-15 15:37:36 +02:00
|
|
|
elif arg_type:
|
2016-07-14 05:50:19 +02: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 13:06:20 +02:00
|
|
|
if extra:
|
|
|
|
ret += sep + extra
|
2018-08-15 15:37:36 +02:00
|
|
|
return ret if ret else 'void'
|