2011-07-19 14:50:39 -05:00
|
|
|
#
|
|
|
|
# QAPI helper library
|
|
|
|
#
|
|
|
|
# Copyright IBM, Corp. 2011
|
2018-02-26 13:19:40 -06:00
|
|
|
# Copyright (c) 2013-2018 Red Hat Inc.
|
2011-07-19 14:50:39 -05:00
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
qapi.py: Restructure lexer and parser
The parser has a rather unorthodox structure:
Until EOF:
Read a section:
Generator function get_expr() yields one section after the
other, as a string. An unindented, non-empty line that
isn't a comment starts a new section.
Lexing:
Split section into a list of tokens (strings), with help
of generator function tokenize().
Parsing:
Parse the first expression from the list of tokens, with
parse(), throw away any remaining tokens.
In parse_schema(): record value of an enum, union or
struct key (if any) in the appropriate global table,
append expression to the list of expressions.
Return list of expressions.
Known issues:
(1) Indentation is significant, unlike in real JSON.
(2) Neither lexer nor parser have any idea of source positions. Error
reporting is hard, let's go shopping.
(3) The one error we bother to detect, we "report" via raise.
(4) The lexer silently ignores invalid characters.
(5) If everything in a section gets ignored, the parser crashes.
(6) The lexer treats a string containing a structural character exactly
like the structural character.
(7) Tokens trailing the first expression in a section are silently
ignored.
(8) The parser accepts any token in place of a colon.
(9) The parser treats comma as optional.
(10) parse() crashes on unexpected EOF.
(11) parse_schema() crashes when a section's expression isn't a JSON
object.
Replace this piece of original art by a thoroughly unoriginal design.
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
addressing the others. Generated source files remain unchanged.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2013-07-27 17:41:55 +02:00
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
2011-07-19 14:50:39 -05:00
|
|
|
#
|
2014-03-01 08:40:34 +01:00
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
|
|
# See the COPYING file in the top-level directory.
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2017-03-15 13:57:35 +01:00
|
|
|
import re
|
2021-08-04 12:30:59 +04:00
|
|
|
from typing import (
|
2021-08-04 12:31:01 +04:00
|
|
|
Any,
|
|
|
|
Dict,
|
2021-08-04 12:30:59 +04:00
|
|
|
Match,
|
|
|
|
Optional,
|
2021-08-31 14:38:03 +02:00
|
|
|
Sequence,
|
2021-08-04 12:30:59 +04:00
|
|
|
Union,
|
|
|
|
)
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:38 -04:00
|
|
|
#: Magic string that gets removed along with all space to its right.
|
2020-10-09 12:15:34 -04:00
|
|
|
EATSPACE = '\033EATSPACE.'
|
|
|
|
POINTER_SUFFIX = ' *' + EATSPACE
|
|
|
|
|
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def camel_to_upper(value: str) -> str:
|
2020-10-09 12:15:38 -04:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
"""
|
2024-09-04 13:18:18 +02:00
|
|
|
ret = value[0]
|
|
|
|
upc = value[0].isupper()
|
|
|
|
|
|
|
|
# Copy remainder of ``value`` to ``ret`` with '_' inserted
|
|
|
|
for ch in value[1:]:
|
|
|
|
if ch.isupper() == upc:
|
|
|
|
pass
|
|
|
|
elif upc:
|
|
|
|
# ``ret`` ends in upper case, next char isn't: insert '_'
|
|
|
|
# before the last upper case char unless there is one
|
|
|
|
# already, or it's at the beginning
|
|
|
|
if len(ret) > 2 and ret[-2].isalnum():
|
|
|
|
ret = ret[:-1] + '_' + ret[-1]
|
|
|
|
else:
|
|
|
|
# ``ret`` doesn't end in upper case, next char is: insert
|
|
|
|
# '_' before it
|
|
|
|
if ret[-1].isalnum():
|
|
|
|
ret += '_'
|
|
|
|
ret += ch
|
|
|
|
upc = ch.isupper()
|
|
|
|
|
|
|
|
return c_name(ret.upper()).lstrip('_')
|
2015-05-14 06:50:53 -06:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def c_enum_const(type_name: str,
|
|
|
|
const_name: str,
|
|
|
|
prefix: Optional[str] = None) -> str:
|
2020-10-09 12:15:38 -04:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2024-09-04 13:18:18 +02:00
|
|
|
if prefix is None:
|
|
|
|
prefix = camel_to_upper(type_name)
|
|
|
|
return prefix + '_' + c_name(const_name, False).upper()
|
2015-05-14 06:50:53 -06:00
|
|
|
|
2018-06-21 10:35:51 +02:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def c_name(name: str, protect: bool = True) -> str:
|
2020-10-09 12:15:38 -04:00
|
|
|
"""
|
|
|
|
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_``.
|
|
|
|
"""
|
2012-07-30 15:46:55 +00:00
|
|
|
# ANSI X3J11/88-090, 3.1.1
|
|
|
|
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
|
2015-09-29 16:21:02 -06:00
|
|
|
'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'])
|
2012-07-30 15:46:55 +00:00
|
|
|
# ISO/IEC 9899:1999, 6.4.1
|
|
|
|
c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
|
|
|
|
# ISO/IEC 9899:2011, 6.4.1
|
2015-09-29 16:21:02 -06:00
|
|
|
c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
|
|
|
|
'_Noreturn', '_Static_assert', '_Thread_local'])
|
2012-07-30 15:46:55 +00:00
|
|
|
# GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
|
|
|
|
# excluding _.*
|
|
|
|
gcc_words = set(['asm', 'typeof'])
|
2013-08-07 11:39:43 -04:00
|
|
|
# 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'])
|
2012-09-19 16:31:07 +02:00
|
|
|
# namespace pollution:
|
2022-07-07 08:56:01 +08:00
|
|
|
polluted_words = set(['unix', 'errno', 'mips', 'sparc', 'i386', 'linux'])
|
2021-03-23 10:40:05 +01:00
|
|
|
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()):
|
2017-03-15 13:57:08 +01:00
|
|
|
return 'q_' + name
|
2015-11-18 01:52:52 -07:00
|
|
|
return name
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2018-06-21 10:35:51 +02:00
|
|
|
|
2020-10-09 12:15:33 -04:00
|
|
|
class Indentation:
|
|
|
|
"""
|
|
|
|
Indentation level management.
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2020-10-09 12:15:33 -04:00
|
|
|
:param initial: Initial number of spaces, default 0.
|
|
|
|
"""
|
|
|
|
def __init__(self, initial: int = 0) -> None:
|
|
|
|
self._level = initial
|
2018-06-21 10:35:51 +02:00
|
|
|
|
2020-10-09 12:15:33 -04:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
return "{}({:d})".format(type(self).__name__, self._level)
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:33 -04:00
|
|
|
def __str__(self) -> str:
|
|
|
|
"""Return the current indentation as a string of spaces."""
|
|
|
|
return ' ' * self._level
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2020-10-09 12:15:33 -04:00
|
|
|
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."""
|
2021-09-08 06:54:26 +02:00
|
|
|
assert amount <= self._level
|
2020-10-09 12:15:33 -04:00
|
|
|
self._level -= amount
|
|
|
|
|
|
|
|
|
2020-10-09 12:15:38 -04:00
|
|
|
#: Global, current indent level for code generation.
|
2020-10-09 12:15:33 -04:00
|
|
|
indent = Indentation()
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def cgen(code: str, **kwds: object) -> str:
|
2020-10-09 12:15:38 -04:00
|
|
|
"""
|
|
|
|
Generate ``code`` with ``kwds`` interpolated.
|
|
|
|
|
|
|
|
Obey `indent`, and strip `EATSPACE`.
|
|
|
|
"""
|
2015-06-24 19:27:32 +02:00
|
|
|
raw = code % kwds
|
2021-09-08 06:54:25 +02:00
|
|
|
pfx = str(indent)
|
|
|
|
if pfx:
|
|
|
|
raw = re.sub(r'^(?!(#|$))', pfx, raw, flags=re.MULTILINE)
|
2020-10-09 12:15:34 -04:00
|
|
|
return re.sub(re.escape(EATSPACE) + r' *', '', raw)
|
2011-07-19 14:50:39 -05:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def mcgen(code: str, **kwds: object) -> str:
|
2015-06-24 19:27:32 +02:00
|
|
|
if code[0] == '\n':
|
|
|
|
code = code[1:]
|
|
|
|
return cgen(code, **kwds)
|
2011-07-19 14:50:39 -05:00
|
|
|
|
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def c_fname(filename: str) -> str:
|
2019-03-01 16:40:48 +01:00
|
|
|
return re.sub(r'[^A-Za-z0-9_]', '_', filename)
|
2013-05-10 17:46:00 -05:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def guardstart(name: str) -> str:
|
2013-05-10 17:46:00 -05:00
|
|
|
return mcgen('''
|
|
|
|
#ifndef %(name)s
|
|
|
|
#define %(name)s
|
|
|
|
|
|
|
|
''',
|
2019-03-01 16:40:48 +01:00
|
|
|
name=c_fname(name).upper())
|
2013-05-10 17:46:00 -05:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2020-10-09 12:15:37 -04:00
|
|
|
def guardend(name: str) -> str:
|
2013-05-10 17:46:00 -05:00
|
|
|
return mcgen('''
|
|
|
|
|
|
|
|
#endif /* %(name)s */
|
|
|
|
''',
|
2019-03-01 16:40:48 +01:00
|
|
|
name=c_fname(name).upper())
|
2015-04-02 13:12:21 +02:00
|
|
|
|
2015-09-29 16:21:02 -06:00
|
|
|
|
2021-08-31 14:38:03 +02:00
|
|
|
def gen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]],
|
|
|
|
cond_fmt: str, not_fmt: str,
|
|
|
|
all_operator: str, any_operator: str) -> str:
|
|
|
|
|
2021-09-08 06:54:24 +02:00
|
|
|
def do_gen(ifcond: Union[str, Dict[str, Any]],
|
|
|
|
need_parens: bool) -> str:
|
2021-08-31 14:38:03 +02:00
|
|
|
if isinstance(ifcond, str):
|
|
|
|
return cond_fmt % ifcond
|
|
|
|
assert isinstance(ifcond, dict) and len(ifcond) == 1
|
|
|
|
if 'not' in ifcond:
|
2021-08-31 14:38:04 +02:00
|
|
|
return not_fmt % do_gen(ifcond['not'], True)
|
2021-08-31 14:38:03 +02:00
|
|
|
if 'all' in ifcond:
|
|
|
|
gen = gen_infix(all_operator, ifcond['all'])
|
|
|
|
else:
|
|
|
|
gen = gen_infix(any_operator, ifcond['any'])
|
2021-08-31 14:38:04 +02:00
|
|
|
if need_parens:
|
|
|
|
gen = '(' + gen + ')'
|
2021-08-31 14:38:03 +02:00
|
|
|
return gen
|
|
|
|
|
|
|
|
def gen_infix(operator: str, operands: Sequence[Any]) -> str:
|
2021-08-31 14:38:04 +02:00
|
|
|
return operator.join([do_gen(o, True) for o in operands])
|
2021-08-31 14:38:03 +02:00
|
|
|
|
2021-08-04 12:30:59 +04:00
|
|
|
if not ifcond:
|
|
|
|
return ''
|
2021-08-31 14:38:04 +02:00
|
|
|
return do_gen(ifcond, False)
|
2021-08-04 12:30:59 +04:00
|
|
|
|
2021-08-31 14:38:03 +02:00
|
|
|
|
|
|
|
def cgen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]]) -> str:
|
|
|
|
return gen_ifcond(ifcond, 'defined(%s)', '!%s', ' && ', ' || ')
|
2021-08-04 12:30:59 +04:00
|
|
|
|
2021-08-04 12:31:01 +04:00
|
|
|
|
2021-08-31 14:37:59 +02:00
|
|
|
def docgen_ifcond(ifcond: Optional[Union[str, Dict[str, Any]]]) -> str:
|
2021-08-04 12:31:00 +04:00
|
|
|
# TODO Doc generated for conditions needs polish
|
2021-08-31 14:38:05 +02:00
|
|
|
return gen_ifcond(ifcond, '%s', 'not %s', ' and ', ' or ')
|
2021-08-04 12:31:00 +04:00
|
|
|
|
|
|
|
|
2021-08-04 12:30:59 +04:00
|
|
|
def gen_if(cond: str) -> str:
|
|
|
|
if not cond:
|
|
|
|
return ''
|
|
|
|
return mcgen('''
|
2018-07-03 17:56:40 +02:00
|
|
|
#if %(cond)s
|
2021-08-04 12:30:59 +04:00
|
|
|
''', cond=cond)
|
2018-07-03 17:56:40 +02:00
|
|
|
|
|
|
|
|
2021-08-04 12:30:59 +04:00
|
|
|
def gen_endif(cond: str) -> str:
|
|
|
|
if not cond:
|
|
|
|
return ''
|
|
|
|
return mcgen('''
|
2018-07-03 17:56:40 +02:00
|
|
|
#endif /* %(cond)s */
|
2021-08-04 12:30:59 +04:00
|
|
|
''', cond=cond)
|
2021-05-19 14:39:45 -04:00
|
|
|
|
|
|
|
|
|
|
|
def must_match(pattern: str, string: str) -> Match[str]:
|
|
|
|
match = re.match(pattern, string)
|
|
|
|
assert match is not None
|
|
|
|
return match
|