diff --git a/parser/doxygroup.py b/parser/doxygroup.py index 831db08..b730213 100644 --- a/parser/doxygroup.py +++ b/parser/doxygroup.py @@ -24,18 +24,22 @@ # After the doxygen close `*/`, the function definition can be separated from the # comment by preprocessor directives (`#if MEOS` … `#endif`) and/or ordinary C # comments — the vendored pgtypes files guard the MEOS-build twin of a symbol -# that way. Skip any run of such lines, then an optional return-type line (no -# parens/braces/;/=), then `name(`. DOTALL lets a multi-line `/* … */` comment -# be skipped as one unit. +# that way. Skip any run of such lines, then capture the signature head up to the +# first `(`; the function name is the LAST identifier in that head. This handles +# the return type on its OWN line (`Pcpatch *\npcpatch_copy(`) AND inline on the +# name's line (`bool pcpatch_eq(`) — the latter otherwise slips through and the +# `@ingroup` mis-binds to the next own-line definition. DOTALL lets a multi-line +# `/* … */` comment be skipped as one unit. # A skippable line is blank or holds only a preprocessor directive / comment # (the directive/comment part is optional so a blank line matches too). `[^\S\n]` # is horizontal whitespace only — it matches `\r` so CRLF sources work, without # letting a unit swallow the following return-type or name line. _SKIP = r"(?:[^\S\n]*(?:\#[^\n]*|//[^\n]*|/\*.*?\*/)?[^\S\n]*\n)*" -_FNDEF = re.compile( - r"\*/[^\S\n]*\n" + _SKIP + r"(?:[^\n(){};=]+\n)?(\w+)\s*\(", +_FNHEAD = re.compile( + r"\*/[^\S\n]*\n" + _SKIP + r"([^(){};=]*?)\(", re.DOTALL, ) +_WORD = re.compile(r"[A-Za-z_]\w*") def _name_to_group(*srcs): @@ -51,9 +55,11 @@ def _name_to_group(*srcs): text = cf.read_text(errors="ignore") for m in _INGROUP.finditer(text): grp = m.group(1) - fm = _FNDEF.search(text, m.end()) + fm = _FNHEAD.search(text, m.end()) if fm: - out.setdefault(fm.group(1), grp) + words = _WORD.findall(fm.group(1)) + if words: + out.setdefault(words[-1], grp) return out diff --git a/tests/test_doxygroup.py b/tests/test_doxygroup.py index bc5a318..5e9e56b 100644 --- a/tests/test_doxygroup.py +++ b/tests/test_doxygroup.py @@ -90,6 +90,31 @@ def _write(root, rel, text): "}\r\n" ) +# Inline return type: the type and the name share one line (terse comparison +# one-liners such as `bool pcpatch_eq(...)`). The name is the last identifier +# before `(`; without this the @ingroup mis-binds to the next own-line +# definition. This fixture also pins that correction: the `_comp` group labels +# the inline `pcpatch_eq`, and the following own-line `pcpatch_copy` keeps its +# own `_constructor` group rather than being swallowed by `_comp`. +INLINE = """\ +/** + * @ingroup meos_pointcloud_comp + * @brief doc + */ +bool pcpatch_eq(const Pcpatch *pa1, const Pcpatch *pa2) +{ return pcpatch_cmp(pa1, pa2) == 0; } + +/** + * @ingroup meos_pointcloud_constructor + * @brief doc + */ +Pcpatch * +pcpatch_copy(const Pcpatch *pa) +{ + return NULL; +} +""" + class TestFndefRobustness(unittest.TestCase): def setUp(self): @@ -121,6 +146,14 @@ def test_crlf(self): self.assertEqual( self._map().get("jsonbset_value_n"), "meos_json_set_accessor") + def test_inline_return_type(self): + _write(self.root, "meos/src/pointcloud/pcpatch.c", INLINE) + m = self._map() + # The inline `bool pcpatch_eq(...)` is captured under its own group ... + self.assertEqual(m.get("pcpatch_eq"), "meos_pointcloud_comp") + # ... and does not steal the following own-line definition's group. + self.assertEqual(m.get("pcpatch_copy"), "meos_pointcloud_constructor") + class TestMultiRootScan(unittest.TestCase): def test_scans_every_root(self):