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
86 changes: 86 additions & 0 deletions parser/outparam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Extract per-parameter OUT direction from the MEOS C Doxygen ``@param[out]`` as the SoT.

A MEOS function parameter is an OUTPUT parameter iff its Doxygen line says
``@param[out] name ...``. This is the single source of truth the codegens consume to
FOLD an out-param — the wrapper allocates the buffer, passes it, and reads the value
back — instead of exposing it as a caller argument. It replaces the per-binding
guesswork (JMEOS's hardcoded ``result``/``size_out`` name whitelist, a type/position
heuristic) with one explicit signal grounded in the C code, keyed by parameter name.
It feeds ``shape.outParams`` in the IDL, the exact sibling of ``shape.nullable``.

The Doxygen tags are MANUALLY MAINTAINED, so the flag is emitted ONLY when the tag
AGREES with the C signature — the param must be a NON-CONST POINTER. A tag on a
by-value param (``@param[out] count int``) or a ``const`` pointer is a documentation
error: it is dropped and reported for cleanup, never trusted. This cross-check is what
keeps a stray annotation from corrupting every binding's signature.
"""
from __future__ import annotations

import glob
import re
from pathlib import Path

# Doxygen block immediately followed by a function definition (mirrors nullable.py).
_FUNC = re.compile(
r'/\*\*(?P<doc>.*?)\*/\s*\n'
r'(?:[A-Za-z_][\w\s\*]*?\n)?' # optional return-type line
r'(?P<name>[a-z][a-z0-9_]*)\s*\('
r'(?P<params>[^;{]*?)\)\s*\{',
re.S)
# One @param[out] entry: capture the (possibly comma-separated) parameter names.
_POUT = re.compile(r'@param\[out\]\s+(?P<names>\w+(?:\s*,\s*\w+)*)', re.S)


def extract_outparams(meos_root: str | Path) -> dict[str, list[str]]:
"""Return ``{function: [params tagged @param[out]]}`` from the MEOS C sources under
``meos_root`` (scans both ``src`` and ``include``)."""
root = Path(meos_root)
out: dict[str, list[str]] = {}
files = glob.glob(str(root / "src/**/*.c"), recursive=True)
files += glob.glob(str(root / "include/**/*.h"), recursive=True)
for f in files:
txt = Path(f).read_text(errors="ignore")
for m in _FUNC.finditer(txt):
name = m.group("name")
for pm in _POUT.finditer(m.group("doc")):
for p in (n.strip() for n in pm.group("names").split(",")):
if p:
out.setdefault(name, [])
if p not in out[name]:
out[name].append(p)
return out


def _nonconst_ptr(canon: str) -> bool:
return "*" in canon and "const" not in canon


def merge_outparams(idl: dict, meos_root: str | Path) -> tuple[dict, int, list]:
"""Fold the extracted out-params into each function's ``shape.outParams`` — but ONLY
the params that are BOTH tagged ``@param[out]`` AND a non-const pointer in the C
signature. Returns ``(idl, count, drift)`` where ``drift`` lists the
``(function, param, reason)`` manual-maintenance discrepancies to clean at the source:
a ``@param[out]`` on a by-value/const param (``not-a-non-const-pointer``), or a tag
whose name is absent from the signature (``name-not-in-signature`` — e.g. the header
declares ``size`` while the ``.c`` doc says ``size_out``)."""
tagged = extract_outparams(meos_root)
n = 0
drift: list[tuple[str, str, str]] = []
for func in idl["functions"]:
names = {p["name"]: p for p in func.get("params", [])}
keep = []
for pn in tagged.get(func["name"], []):
p = names.get(pn)
if p is None:
if names: # only a real function with a param list; skip decl-less noise
drift.append((func["name"], pn, "name-not-in-signature"))
continue
if _nonconst_ptr(p.get("canonical", "")):
keep.append(pn)
else:
drift.append((func["name"], pn, "not-a-non-const-pointer: "
+ p.get("canonical", "")))
if keep:
func.setdefault("shape", {})["outParams"] = keep
n += len(keep)
return idl, n, drift
8 changes: 8 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from parser.header_types import reconcile
from parser.shapeinfer import infer_shapes
from parser.nullable import merge_nullable
from parser.outparam import merge_outparams
from parser.enrich import enrich_idl
from parser.sqlfn import attach_sqlfn_map, lint_ea_sqlfn, lint_sqlfn_case_collisions
from parser.doxygroup import attach_groups
Expand Down Expand Up @@ -60,6 +61,13 @@ def main():
idl, nn = merge_nullable(idl, HEADERS_DIR.parent)
print(f" nullable params from Doxygen `may be NULL`: {nn}",
file=sys.stderr)
idl, no, out_drift = merge_outparams(idl, HEADERS_DIR.parent)
print(f" out params from Doxygen `@param[out]`: {no}", file=sys.stderr)
if out_drift:
print(f" ⚠ {len(out_drift)} @param[out] tag(s) disagree with the C signature "
f"(manual-maintenance drift — clean at the MEOS source):", file=sys.stderr)
for fn, pn, reason in out_drift:
print(f" {fn}({pn}) — {reason}", file=sys.stderr)

# 1e. Derive service-projection metadata (category / encodings / network).
# Runs before the merge so manual annotations override the heuristics.
Expand Down
Loading