From 693febbd1440f6958880582c1a59bd1d6a83a2cc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 14 Jul 2019 12:01:38 +0200 Subject: [PATCH] documentation/python: properly handle nested pybind signature parse errors. I still feel uneasy throwing exceptions. But eh. --- documentation/python.py | 96 ++++++++++++------------ documentation/test_python/test_pybind.py | 10 +++ 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/documentation/python.py b/documentation/python.py index 39fb22ab..38b3532f 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -583,8 +583,11 @@ def parse_pybind_type(state: State, referrer_path: List[str], signature: str) -> type_link += inner_type_link if signature[0] == ']': break - assert signature.startswith(', ') + + # Expecting the next item now, if not there, we failed + if not signature.startswith(', '): raise SyntaxError() signature = signature[2:] + type += ', ' type_link += ', ' @@ -606,59 +609,58 @@ def parse_pybind_signature(state: State, referrer_path: List[str], signature: st assert signature[0] == '(' signature = signature[1:] - # Arguments - while signature[0] != ')': - # Name - arg_name = _pybind_arg_name_rx.match(signature).group(0) - assert arg_name - signature = signature[len(arg_name):] - - # Type (optional) - if signature.startswith(': '): - signature = signature[2:] - signature, arg_type, arg_type_link = parse_pybind_type(state, referrer_path, signature) - else: - arg_type = None - arg_type_link = None - - # Default (optional) -- for now take everything until the next comma - # TODO: ugh, do properly - # The equals has spaces around since 2.3.0, preserve 2.2 compatibility. - # https://github.com/pybind/pybind11/commit/0826b3c10607c8d96e1d89dc819c33af3799a7b8 - if signature.startswith(('=', ' = ')): - signature = signature[1 if signature[0] == '=' else 3:] - default = _pybind_default_value_rx.match(signature).group(0) - signature = signature[len(default):] - else: - default = None + # parse_pybind_type() can throw a SyntaxError in case it gets confused, + # provide graceful handling for that along with own parse errors + try: + # Arguments + while signature[0] != ')': + # Name + arg_name = _pybind_arg_name_rx.match(signature).group(0) + assert arg_name + signature = signature[len(arg_name):] + + # Type (optional) + if signature.startswith(': '): + signature = signature[2:] + signature, arg_type, arg_type_link = parse_pybind_type(state, referrer_path, signature) + else: + arg_type = None + arg_type_link = None + + # Default (optional) -- for now take everything until the next comma + # TODO: ugh, do properly + # The equals has spaces around since 2.3.0, preserve 2.2 compatibility. + # https://github.com/pybind/pybind11/commit/0826b3c10607c8d96e1d89dc819c33af3799a7b8 + if signature.startswith(('=', ' = ')): + signature = signature[1 if signature[0] == '=' else 3:] + default = _pybind_default_value_rx.match(signature).group(0) + signature = signature[len(default):] + else: + default = None - args += [(arg_name, arg_type, arg_type_link, default)] + args += [(arg_name, arg_type, arg_type_link, default)] - if signature[0] == ')': break + if signature[0] == ')': break - # Failed to parse, return an ellipsis and docs - if not signature.startswith(', '): - end = original_signature.find('\n') - logging.warning("cannot parse pybind11 function signature %s", original_signature[:end if end != -1 else None]) - if end != -1 and len(original_signature) > end + 1 and original_signature[end + 1] == '\n': - summary = extract_summary(state, {}, [], original_signature[end + 1:]) - else: - summary = '' - return (name, summary, [('…', None, None, None)], None) + # Expecting the next argument now, if not there, we failed + if not signature.startswith(', '): raise SyntaxError() + signature = signature[2:] - signature = signature[2:] + assert signature[0] == ')' + signature = signature[1:] - assert signature[0] == ')' - signature = signature[1:] + # Return type (optional) + if signature.startswith(' -> '): + signature = signature[4:] + signature, _, return_type_link = parse_pybind_type(state, referrer_path, signature) + else: + return_type_link = None - # Return type (optional) - if signature.startswith(' -> '): - signature = signature[4:] - signature, _, return_type_link = parse_pybind_type(state, referrer_path, signature) - else: - return_type_link = None + # Expecting end of the signature line now, if not there, we failed + if signature and signature[0] != '\n': raise SyntaxError() - if signature and signature[0] != '\n': + # Failed to parse, return an ellipsis and docs + except SyntaxError: end = original_signature.find('\n') logging.warning("cannot parse pybind11 function signature %s", original_signature[:end if end != -1 else None]) if end != -1 and len(original_signature) > end + 1 and original_signature[end + 1] == '\n': diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index 44bcfef8..bb7a4da6 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -129,6 +129,11 @@ class Signature(unittest.TestCase): 'foo(a: int, b: Math::Vector<4, UnsignedInt>)'), ('foo', '', [('…', None, None, None)], None)) + def test_crazy_stuff_nested(self): + self.assertEqual(parse_pybind_signature(State({}), [], + 'foo(a: int, b: List[Math::Vector<4, UnsignedInt>])'), + ('foo', '', [('…', None, None, None)], None)) + def test_crazy_stuff_docs(self): self.assertEqual(parse_pybind_signature(State({}), [], 'foo(a: int, b: Math::Vector<4, UnsignedInt>)\n\nThis is text!!'), @@ -139,6 +144,11 @@ class Signature(unittest.TestCase): 'foo(a: int) -> Math::Vector<4, UnsignedInt>'), ('foo', '', [('…', None, None, None)], None)) + def test_crazy_return_nested(self): + self.assertEqual(parse_pybind_signature(State({}), [], + 'foo(a: int) -> List[Math::Vector<4, UnsignedInt>]'), + ('foo', '', [('…', None, None, None)], None)) + def test_crazy_return_docs(self): self.assertEqual(parse_pybind_signature(State({}), [], 'foo(a: int) -> Math::Vector<4, UnsignedInt>\n\nThis returns!'), -- 2.30.2