chiark / gitweb /
documentation/python: properly handle nested pybind signature parse errors.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 10:01:38 +0000 (12:01 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Jul 2019 17:11:08 +0000 (19:11 +0200)
I still feel uneasy throwing exceptions. But eh.

documentation/python.py
documentation/test_python/test_pybind.py

index 39fb22ab9c69057800b9b923fe00047ff6e42ad3..38b3532fdde33c6a3c1c9b8aca1049cc534846ac 100755 (executable)
@@ -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':
index 44bcfef8d6583e28e17b941030b3de82bdae2b66..bb7a4da6a48949aa2ce3a50e20bbbe7756ca86f3 100644 (file)
@@ -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!'),