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