diff --git a/parser/outparam.py b/parser/outparam.py index 76b1d81..9ae50d1 100644 --- a/parser/outparam.py +++ b/parser/outparam.py @@ -48,12 +48,27 @@ def doxygen_sources(meos_root: str | Path) -> list[str]: # here because, combined with this module's whole-doc-block `_FUNC` match, a DOTALL # `.*?` inside the skip run backtracks catastrophically (doxygroup runs a bounded # two-step search, so it can afford it); no vendored out-param carrier needs it. +# A guard may also sit between the return-type line and the name, choosing between two +# spellings of one signature (`basetype_in` in meos/src/temporal/type_in.c marks a +# parameter UNUSED in the build that does not read it): +# bool +# #if CBUFFER || NPOINT || POSE +# basetype_in(const char *str, MeosType type, bool end, Datum *result) +# #else +# basetype_in(const char *str, MeosType type, bool end UNUSED, Datum *result) +# #endif +# { +# `_TWIN` steps over the `#else`/`#elif` spellings and the closing `#endif`, so +# `params` is the first spelling and the match still ends at the body's `{`. _SKIP = r"(?:[^\S\n]*(?:\#[^\n]*|//[^\n]*)?[^\S\n]*\n)*" +_TWIN = (r"(?:[^\S\n]*\n[^\S\n]*\#[^\S\n]*(?:else|elif)[^\n]*\n[^;{]*?\))*" + r"(?:[^\S\n]*\n[^\S\n]*\#[^\S\n]*endif[^\n]*)?") _FUNC = re.compile( r'/\*\*(?P.*?)\*/[^\S\n]*\n' + _SKIP + r'(?:[A-Za-z_][\w\s\*]*?\n)?' # optional return-type line + + _SKIP + r'(?P[a-z][a-z0-9_]*)\s*\(' - r'(?P[^;{]*?)\)\s*\{', + r'(?P[^;{]*?)\)' + _TWIN + r'\s*\{', re.S) # One @param[out] entry: capture the (possibly comma-separated) parameter names. _POUT = re.compile(r'@param\[out\]\s+(?P\w+(?:\s*,\s*\w+)*)', re.S) diff --git a/tests/test_outparam.py b/tests/test_outparam.py index de2d6ff..feb4966 100644 --- a/tests/test_outparam.py +++ b/tests/test_outparam.py @@ -76,7 +76,81 @@ def _write(root, rel, text): """ +# A guard between the return-type line and the name choosing between two spellings +# of one signature, the shape of `basetype_in` in meos/src/temporal/type_in.c, then +# an undocumented neighbour that must not inherit the block. +TWIN_SIGNATURE = """\ +/** + * @brief doc + * @param[in] str String + * @param[in] end True when the string must end where the value ends + * @param[out] result Value read + */ +bool +#if CBUFFER || NPOINT || POSE +basetype_in(const char *str, bool end, Datum *result) +#else +basetype_in(const char *str, bool end UNUSED, Datum *result) +#endif +{ + return true; +} + +/** + * @brief doc + */ +static Datum +parse_coord(json_object *obj) +{ + return 0; +} +""" + +# A guard inside the parameter list, the shape of `skiplist_splice` in +# meos/src/temporal/skiplist.c, then an undocumented neighbour. +TWIN_TAIL = """\ +/** + * @brief doc + * @param[in] list Skiplist + * @param[in] sktype Type of the skiplist + */ +void +skiplist_splice(SkipList *list, +#if MEOS + SkipListType sktype) +#else + SkipListType sktype UNUSED) +#endif /* ! MEOS */ +{ + return; +} + +/** + * @brief doc + */ +void ** +skiplist_values(SkipList *list) +{ + return NULL; +} +""" + + class TestExtract(unittest.TestCase): + def test_guard_between_return_type_and_name(self): + with tempfile.TemporaryDirectory() as root: + _write(root, "meos/src/type_in.c", TWIN_SIGNATURE) + out = extract_outparams(Path(root) / "meos") + self.assertEqual(out.get("basetype_in"), ["result"]) + self.assertNotIn("parse_coord", out) + + def test_guard_inside_the_parameter_list(self): + with tempfile.TemporaryDirectory() as root: + _write(root, "meos/src/skiplist.c", TWIN_TAIL) + names = extract_param_names(Path(root) / "meos") + self.assertEqual(names.get("skiplist_splice"), {"list", "sktype"}) + self.assertNotIn("skiplist_values", names) + def test_plain_meos_src(self): with tempfile.TemporaryDirectory() as root: _write(root, "meos/src/temporal_tile_meos.c", PLAIN)