nandi/frqpublic Fork 0
7751d4ffae3c665d7bccb4e9ba0896939d2fafee
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Bind libmoq_ffi from the object's own metadata, not from its header 9f081a1 · on 7751d4ffae3c665d7bccb4e9ba0896939d2fafee · nandi · 9d ago
py2jolt.py · 106 lines · 4.0 KBPython Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
#!/usr/bin/env python3
"""Turn uniffi-bindgen's Python output into jolt `defcfn` declarations.

The Python backend emits, for every entry point in the library, a pair of
lines that together are an exact ABI description:

    _UniffiLib.uniffi_moq_ffi_fn_method_moqclient_connect.argtypes = (
        ctypes.c_uint64, _UniffiRustBuffer, ctypes.POINTER(_UniffiRustCallStatus),
    )
    _UniffiLib.uniffi_moq_ffi_fn_method_moqclient_connect.restype = ctypes.c_uint64

That is generated from the metadata embedded in the .so itself, so it matches
the object being bound rather than a header shipped alongside it -- which for
this release is a different build (the Apple artifact carries audio/ video, the
Linux and Android ones do not).
"""
import re, sys, collections

CTYPE = {
    "ctypes.c_int8": ":int8",     "ctypes.c_uint8": ":uint8",
    "ctypes.c_int16": ":int16",   "ctypes.c_uint16": ":uint16",
    "ctypes.c_int32": ":int32",   "ctypes.c_uint32": ":uint32",
    "ctypes.c_int64": ":int64",   "ctypes.c_uint64": ":uint64",
    "ctypes.c_float": ":float",   "ctypes.c_double": ":double",
    "ctypes.c_void_p": ":pointer", "ctypes.c_size_t": ":uint64",
    "None": ":void",
}
# A RustBuffer crosses by value; a status is always a pointer out-param.
RB = "[:by-value [:struct [[:capacity :uint64] [:len :uint64] [:data :pointer]]]]"
STATUS = ":pointer"

def conv(t):
    t = t.strip().rstrip(",").strip()
    if not t:
        return None
    if t in CTYPE:
        return CTYPE[t]
    if "RustCallStatus" in t:
        return STATUS
    if "RustBuffer" in t:
        return RB
    if "ForeignBytes" in t:
        return "[:by-value [:struct [[:len :int32] [:data :pointer]]]]"
    if t.startswith("ctypes.POINTER"):
        return ":pointer"
    # A function-pointer typedef (the future continuation) -- jolt lowers an
    # ffi/callback to a plain pointer, so that is what the slot takes.
    if "callback" in t.lower() or "struct" in t.lower():
        return ":pointer"
    return None  # unknown -- reported, never guessed

def split_args(s):
    out, depth, cur = [], 0, ""
    for ch in s:
        if ch == "(":
            depth += 1
        elif ch == ")":
            depth -= 1
        if ch == "," and depth == 0:
            out.append(cur); cur = ""
        else:
            cur += ch
    if cur.strip():
        out.append(cur)
    return out

def main(path):
    src = open(path).read()
    args = dict(re.findall(r"_UniffiLib\.(\w+)\.argtypes\s*=\s*\(([^)]*(?:\([^)]*\)[^)]*)*)\)", src))
    rets = dict(re.findall(r"_UniffiLib\.(\w+)\.restype\s*=\s*(.+)", src))
    names = sorted(set(args) | set(rets))
    unknown = collections.Counter()
    rows = []
    for n in names:
        a = [conv(x) for x in split_args(args.get(n, ""))]
        r = conv(rets.get(n, "None"))
        if None in a or r is None:
            for x in split_args(args.get(n, "")) + [rets.get(n, "None")]:
                if conv(x) is None:
                    unknown[x.strip()] += 1
            continue
        rows.append((n, a, r))
    return rows, unknown, len(names)

if __name__ == "__main__":
    rows, unknown, total = main(sys.argv[1])
    print(f";; {len(rows)} of {total} entry points bound", file=sys.stderr)
    if unknown:
        print(";; UNMAPPED (left unbound rather than guessed):", file=sys.stderr)
        for t, c in unknown.most_common():
            print(f";;   {t}  x{c}", file=sys.stderr)
    for n, a, r in rows:
        # Anchored, longest-first: "uniffi_moq_ffi_checksum_" must not be
        # eaten by the "ffi_moq_ffi_" that also matches inside it.
        for pre in ("uniffi_moq_ffi_fn_", "uniffi_moq_ffi_checksum_",
                    "ffi_moq_ffi_", "uniffi_moq_ffi_"):
            if n.startswith(pre):
                stem = n[len(pre):]
                if pre.endswith("checksum_"):
                    stem = "checksum-" + stem
                break
        else:
            stem = n
        jolt_name = stem.replace("_", "-")
        argv = " ".join(a) if a else ""
        print(f'(ffi/defcfn {jolt_name} "{n}" [{argv}] {r})')