]>
Commit | Line | Data |
---|---|---|
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 | ||
14 | import re | |
15 | from 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. | |
26 | EATSPACE = '\033EATSPACE.' | |
27 | POINTER_SUFFIX = ' *' + EATSPACE | |
28 | ||
29 | ||
30 | def 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 | ||
67 | def 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 | ||
82 | def 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 | ||
132 | class 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. | |
159 | indent = Indentation() | |
160 | ||
161 | ||
162 | def 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 | ||
175 | def mcgen(code: str, **kwds: object) -> str: | |
176 | if code[0] == '\n': | |
177 | code = code[1:] | |
178 | return cgen(code, **kwds) | |
179 | ||
180 | ||
181 | def c_fname(filename: str) -> str: | |
182 | return re.sub(r'[^A-Za-z0-9_]', '_', filename) | |
183 | ||
184 | ||
185 | def 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 | ||
194 | def guardend(name: str) -> str: | |
195 | return mcgen(''' | |
196 | ||
197 | #endif /* %(name)s */ | |
198 | ''', | |
199 | name=c_fname(name).upper()) | |
200 | ||
201 | ||
202 | def 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 | ||
229 | def cgen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]]) -> str: | |
230 | return gen_ifcond(ifcond, 'defined(%s)', '!%s', ' && ', ' || ') | |
231 | ||
232 | ||
233 | def 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 | ||
238 | def gen_if(cond: str) -> str: | |
239 | if not cond: | |
240 | return '' | |
241 | return mcgen(''' | |
242 | #if %(cond)s | |
243 | ''', cond=cond) | |
244 | ||
245 | ||
246 | def gen_endif(cond: str) -> str: | |
247 | if not cond: | |
248 | return '' | |
249 | return mcgen(''' | |
250 | #endif /* %(cond)s */ | |
251 | ''', cond=cond) | |
252 | ||
253 | ||
254 | def must_match(pattern: str, string: str) -> Match[str]: | |
255 | match = re.match(pattern, string) | |
256 | assert match is not None | |
257 | return match |