| 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 |