xref: /openbmc/qemu/scripts/qapi/common.py (revision ff788b70)
1#
2# QAPI helper library
3#
4# Copyright IBM, Corp. 2011
5# Copyright (c) 2013-2018 Red Hat Inc.
6#
7# Authors:
8#  Anthony Liguori <aliguori@us.ibm.com>
9#  Markus Armbruster <armbru@redhat.com>
10#
11# This work is licensed under the terms of the GNU GPL, version 2.
12# See the COPYING file in the top-level directory.
13
14import re
15from typing import (
16    Any,
17    Dict,
18    Match,
19    Optional,
20    Sequence,
21    Union,
22)
23
24
25#: Magic string that gets removed along with all space to its right.
26EATSPACE = '\033EATSPACE.'
27POINTER_SUFFIX = ' *' + EATSPACE
28
29
30def camel_to_upper(value: str) -> str:
31    """
32    Converts CamelCase to CAMEL_CASE.
33
34    Examples::
35
36        ENUMName -> ENUM_NAME
37        EnumName1 -> ENUM_NAME1
38        ENUM_NAME -> ENUM_NAME
39        ENUM_NAME1 -> ENUM_NAME1
40        ENUM_Name2 -> ENUM_NAME2
41        ENUM24_Name -> ENUM24_NAME
42    """
43    ret = value[0]
44    upc = value[0].isupper()
45
46    # Copy remainder of ``value`` to ``ret`` with '_' inserted
47    for ch in value[1:]:
48        if ch.isupper() == upc:
49            pass
50        elif upc:
51            # ``ret`` ends in upper case, next char isn't: insert '_'
52            # before the last upper case char unless there is one
53            # already, or it's at the beginning
54            if len(ret) > 2 and ret[-2].isalnum():
55                ret = ret[:-1] + '_' + ret[-1]
56        else:
57            # ``ret`` doesn't end in upper case, next char is: insert
58            # '_' before it
59            if ret[-1].isalnum():
60                ret += '_'
61        ret += ch
62        upc = ch.isupper()
63
64    return c_name(ret.upper()).lstrip('_')
65
66
67def c_enum_const(type_name: str,
68                 const_name: str,
69                 prefix: Optional[str] = None) -> str:
70    """
71    Generate a C enumeration constant name.
72
73    :param type_name: The name of the enumeration.
74    :param const_name: The name of this constant.
75    :param prefix: Optional, prefix that overrides the type_name.
76    """
77    if prefix is None:
78        prefix = camel_to_upper(type_name)
79    return prefix + '_' + c_name(const_name, False).upper()
80
81
82def c_name(name: str, protect: bool = True) -> str:
83    """
84    Map ``name`` to a valid C identifier.
85
86    Used for converting 'name' from a 'name':'type' qapi definition
87    into a generated struct member, as well as converting type names
88    into substrings of a generated C function name.
89
90    '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
91    protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
92
93    :param name: The name to map.
94    :param protect: If true, avoid returning certain ticklish identifiers
95                    (like C keywords) by prepending ``q_``.
96    """
97    # ANSI X3J11/88-090, 3.1.1
98    c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
99                     'default', 'do', 'double', 'else', 'enum', 'extern',
100                     'float', 'for', 'goto', 'if', 'int', 'long', 'register',
101                     'return', 'short', 'signed', 'sizeof', 'static',
102                     'struct', 'switch', 'typedef', 'union', 'unsigned',
103                     'void', 'volatile', 'while'])
104    # ISO/IEC 9899:1999, 6.4.1
105    c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
106    # ISO/IEC 9899:2011, 6.4.1
107    c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
108                     '_Noreturn', '_Static_assert', '_Thread_local'])
109    # GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
110    # excluding _.*
111    gcc_words = set(['asm', 'typeof'])
112    # C++ ISO/IEC 14882:2003 2.11
113    cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
114                     'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
115                     'namespace', 'new', 'operator', 'private', 'protected',
116                     'public', 'reinterpret_cast', 'static_cast', 'template',
117                     'this', 'throw', 'true', 'try', 'typeid', 'typename',
118                     'using', 'virtual', 'wchar_t',
119                     # alternative representations
120                     'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
121                     'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
122    # namespace pollution:
123    polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386', 'linux'])
124    name = re.sub(r'[^A-Za-z0-9_]', '_', name)
125    if protect and (name in (c89_words | c99_words | c11_words | gcc_words
126                             | cpp_words | polluted_words)
127                    or name[0].isdigit()):
128        return 'q_' + name
129    return name
130
131
132class Indentation:
133    """
134    Indentation level management.
135
136    :param initial: Initial number of spaces, default 0.
137    """
138    def __init__(self, initial: int = 0) -> None:
139        self._level = initial
140
141    def __repr__(self) -> str:
142        return "{}({:d})".format(type(self).__name__, self._level)
143
144    def __str__(self) -> str:
145        """Return the current indentation as a string of spaces."""
146        return ' ' * self._level
147
148    def increase(self, amount: int = 4) -> None:
149        """Increase the indentation level by ``amount``, default 4."""
150        self._level += amount
151
152    def decrease(self, amount: int = 4) -> None:
153        """Decrease the indentation level by ``amount``, default 4."""
154        assert amount <= self._level
155        self._level -= amount
156
157
158#: Global, current indent level for code generation.
159indent = Indentation()
160
161
162def cgen(code: str, **kwds: object) -> str:
163    """
164    Generate ``code`` with ``kwds`` interpolated.
165
166    Obey `indent`, and strip `EATSPACE`.
167    """
168    raw = code % kwds
169    pfx = str(indent)
170    if pfx:
171        raw = re.sub(r'^(?!(#|$))', pfx, raw, flags=re.MULTILINE)
172    return re.sub(re.escape(EATSPACE) + r' *', '', raw)
173
174
175def mcgen(code: str, **kwds: object) -> str:
176    if code[0] == '\n':
177        code = code[1:]
178    return cgen(code, **kwds)
179
180
181def c_fname(filename: str) -> str:
182    return re.sub(r'[^A-Za-z0-9_]', '_', filename)
183
184
185def guardstart(name: str) -> str:
186    return mcgen('''
187#ifndef %(name)s
188#define %(name)s
189
190''',
191                 name=c_fname(name).upper())
192
193
194def guardend(name: str) -> str:
195    return mcgen('''
196
197#endif /* %(name)s */
198''',
199                 name=c_fname(name).upper())
200
201
202def gen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]],
203               cond_fmt: str, not_fmt: str,
204               all_operator: str, any_operator: str) -> str:
205
206    def do_gen(ifcond: Union[str, Dict[str, Any]],
207               need_parens: bool) -> str:
208        if isinstance(ifcond, str):
209            return cond_fmt % ifcond
210        assert isinstance(ifcond, dict) and len(ifcond) == 1
211        if 'not' in ifcond:
212            return not_fmt % do_gen(ifcond['not'], True)
213        if 'all' in ifcond:
214            gen = gen_infix(all_operator, ifcond['all'])
215        else:
216            gen = gen_infix(any_operator, ifcond['any'])
217        if need_parens:
218            gen = '(' + gen + ')'
219        return gen
220
221    def gen_infix(operator: str, operands: Sequence[Any]) -> str:
222        return operator.join([do_gen(o, True) for o in operands])
223
224    if not ifcond:
225        return ''
226    return do_gen(ifcond, False)
227
228
229def cgen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]]) -> str:
230    return gen_ifcond(ifcond, 'defined(%s)', '!%s', ' && ', ' || ')
231
232
233def docgen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]]) -> str:
234    # TODO Doc generated for conditions needs polish
235    return gen_ifcond(ifcond, '%s', 'not %s', ' and ', ' or ')
236
237
238def gen_if(cond: str) -> str:
239    if not cond:
240        return ''
241    return mcgen('''
242#if %(cond)s
243''', cond=cond)
244
245
246def gen_endif(cond: str) -> str:
247    if not cond:
248        return ''
249    return mcgen('''
250#endif /* %(cond)s */
251''', cond=cond)
252
253
254def must_match(pattern: str, string: str) -> Match[str]:
255    match = re.match(pattern, string)
256    assert match is not None
257    return match
258