Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions parser/doxygroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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


Expand Down
33 changes: 33 additions & 0 deletions tests/test_doxygroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
Loading