qemu/scripts/qapi/common.py
Markus Armbruster 5fbc78dd36 qapi: Permit flat union members for any tag value
Flat union branch names match the tag enum's member names.  Omitted
branches default to "no members for this tag value".

Branch names starting with a digit get rejected like "'data' member
'0' has an invalid name".  However, omitting the branch works.

This is because flat union tag values get checked twice: as enum
member name, and as union branch name.  The former accepts leading
digits, the latter doesn't.

Branches whose names start with a digit therefore cannot have members.
Feels wrong.  Get rid of the restriction by skipping the latter check.

This can expose c_name() to input it can't handle: a name starting
with a digit.  Improve it to return a valid C identifier for any
input.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20210323094025.3569441-9-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[Commit message rewritten]
2021-03-23 22:30:08 +01:00

213 lines
6.4 KiB
Python

#
# QAPI helper library
#
# Copyright IBM, Corp. 2011
# Copyright (c) 2013-2018 Red Hat Inc.
#
# Authors:
# Anthony Liguori <aliguori@us.ibm.com>
# Markus Armbruster <armbru@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
import re
from typing import Optional, Sequence
#: Magic string that gets removed along with all space to its right.
EATSPACE = '\033EATSPACE.'
POINTER_SUFFIX = ' *' + EATSPACE
def camel_to_upper(value: str) -> str:
"""
Converts CamelCase to CAMEL_CASE.
Examples::
ENUMName -> ENUM_NAME
EnumName1 -> ENUM_NAME1
ENUM_NAME -> ENUM_NAME
ENUM_NAME1 -> ENUM_NAME1
ENUM_Name2 -> ENUM_NAME2
ENUM24_Name -> ENUM24_NAME
"""
c_fun_str = c_name(value, False)
if value.isupper():
return c_fun_str
new_name = ''
length = len(c_fun_str)
for i in range(length):
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] != '_':
if i < length - 1 and c_fun_str[i + 1].islower():
new_name += '_'
elif c_fun_str[i - 1].isdigit():
new_name += '_'
new_name += char
return new_name.lstrip('_').upper()
def c_enum_const(type_name: str,
const_name: str,
prefix: Optional[str] = None) -> str:
"""
Generate a C enumeration constant name.
:param type_name: The name of the enumeration.
:param const_name: The name of this constant.
:param prefix: Optional, prefix that overrides the type_name.
"""
if prefix is not None:
type_name = prefix
return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
def c_name(name: str, protect: bool = True) -> str:
"""
Map ``name`` to a valid C identifier.
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'
:param name: The name to map.
:param protect: If true, avoid returning certain ticklish identifiers
(like C keywords) by prepending ``q_``.
"""
# ANSI X3J11/88-090, 3.1.1
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
'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'])
# ISO/IEC 9899:1999, 6.4.1
c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
# ISO/IEC 9899:2011, 6.4.1
c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
'_Noreturn', '_Static_assert', '_Thread_local'])
# GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
# excluding _.*
gcc_words = set(['asm', 'typeof'])
# 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'])
# namespace pollution:
polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386'])
name = re.sub(r'[^A-Za-z0-9_]', '_', name)
if protect and (name in (c89_words | c99_words | c11_words | gcc_words
| cpp_words | polluted_words)
or name[0].isdigit()):
return 'q_' + name
return name
class Indentation:
"""
Indentation level management.
:param initial: Initial number of spaces, default 0.
"""
def __init__(self, initial: int = 0) -> None:
self._level = initial
def __int__(self) -> int:
return self._level
def __repr__(self) -> str:
return "{}({:d})".format(type(self).__name__, self._level)
def __str__(self) -> str:
"""Return the current indentation as a string of spaces."""
return ' ' * self._level
def __bool__(self) -> bool:
"""True when there is a non-zero indentation."""
return bool(self._level)
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
#: Global, current indent level for code generation.
indent = Indentation()
def cgen(code: str, **kwds: object) -> str:
"""
Generate ``code`` with ``kwds`` interpolated.
Obey `indent`, and strip `EATSPACE`.
"""
raw = code % kwds
if indent:
raw = re.sub(r'^(?!(#|$))', str(indent), raw, flags=re.MULTILINE)
return re.sub(re.escape(EATSPACE) + r' *', '', raw)
def mcgen(code: str, **kwds: object) -> str:
if code[0] == '\n':
code = code[1:]
return cgen(code, **kwds)
def c_fname(filename: str) -> str:
return re.sub(r'[^A-Za-z0-9_]', '_', filename)
def guardstart(name: str) -> str:
return mcgen('''
#ifndef %(name)s
#define %(name)s
''',
name=c_fname(name).upper())
def guardend(name: str) -> str:
return mcgen('''
#endif /* %(name)s */
''',
name=c_fname(name).upper())
def gen_if(ifcond: Sequence[str]) -> str:
ret = ''
for ifc in ifcond:
ret += mcgen('''
#if %(cond)s
''', cond=ifc)
return ret
def gen_endif(ifcond: Sequence[str]) -> str:
ret = ''
for ifc in reversed(ifcond):
ret += mcgen('''
#endif /* %(cond)s */
''', cond=ifc)
return ret