python

The Python target, python, generates bindings of the API through ctypes, into <output>/python:

<prefix>.py

The bindings, a module with no dependencies outside the standard library; <prefix>_.py where the prefix names a module of the standard library, such as zlib, which the bindings would otherwise hide or be hidden by.

<prefix>_check.py

The check, not part of the API: it asserts that every record has the size, alignment and field offsets recorded in the IR, then prints a zeroed instance of every record, as YAML or JSON.

<prefix>.pxd, lib<prefix>.h

The C API for Cython, see From Cython, and the header it declares, as the C target emits it.

<prefix>_check.pyx

The check of the .pxd, not part of the API: it assigns each name the .pxd declares to what the .pxd says its type is, or its address to a pointer to that.

The check is run with the Python running yace. It loads no library, so it needs none built. yace then parses what it printed, as YAML and as JSON. As for the C API, the layout is asserted only on the platform it was recorded on. Where that Python has Cython, yace also translates the check of the .pxd and compiles what Cython made of it with the header, so a declaration that disagrees with the header fails to compile. A bit-field is the exception: it has no address to take, so the integer type the .pxd gives it is not checked. Nothing runs it. Where that Python has no Cython, yace warns that the .pxd is not checked, and the check does not fail.

The bindings

Names are those of the C API, so its documentation applies, as with the modules of Python’s own library that bind C APIs. Only types are renamed, to CamelCase, as Python names classes: struct foo_point is FooPoint, as is typedef struct foo_point foo_point;, the callback type foo_visit_cb is FooVisitCb. Where two C names would get the same Python name, both keep their C names. A name that is a Python keyword gets a trailing underscore.

Constants

#define constants, by their names.

Enumerations

Each enum is an enum.IntEnum, with members by their C names, also given as constants of the module, as signal and socket do. Kind(value).name is the name of a value.

Records

Each struct and union is a ctypes.Structure or ctypes.Union, a packed one with _pack_ = 1, and in the .pxd a packed struct is packed. Each that has fields has the pretty-printers: to_dict(), its fields as plain values, fpr(stream, obj, flags) and pr(obj, flags), printing obj, an instance, a pointer to one, or None, as the C API’s <name>_fpr() and <name>_pr() do. str() of a record is its YAML, and repr() is as its constructor is called, FooPoint(x=3, y=4): arrays as lists, a field typed as an enum by the name of its member, every member of a union. An opaque record is an empty structure, used through pointers. Each part of a field of bits, see The C Subset, is a property, <field>_<part>, such as status.code_type, reading the part shifted down and writing it in its place, raising ValueError for a value that does not fit its bits; the field itself is there as before.

Callbacks

A function pointer type is a ctypes.CFUNCTYPE(). The caller keeps the callback alive as long as C may call it.

Functions

Each function is a Python function of the same name and parameters. A const char * parameter takes a str, or bytes, and a const char * returned is a str. A pointer returned is None when NULL. Out-parameters are passed with ctypes.byref(), as in C; a record passed where a pointer to it is expected is passed by reference. A function without a const char * parameter is the ctypes function itself, once the library is loaded, so a call through the module reaches ctypes directly. It keeps the docstring, and inspect.signature() still gives its parameters. It takes its arguments by position only, as C does. A name imported with from ... import before the library is loaded stays a Python wrapper, which is slower.

The idiomatic layer

Over the raw functions, the module has an idiomatic layer, from what the header says beyond C, see The C Subset. Its names lose the prefix of the API:

Handles

An opaque record is a class, named without the prefix: struct foo_handle is Handle. Its constructor is a function of the module, foo.open(), its destructor the method close(), raising OSError where it has @errno, also called on leaving a with-block, or when collected, and the functions taking it first are its methods: handle.point_add(point) calls foo_point_add(handle, point).

Errors

A function documented @errno raises OSError, with the errno, when it returns a negative value. One returning int returns nothing otherwise; one returning another integer, such as a count, returns it.

Results

What a function writes through an [out] pointer is returned rather than passed: foo.figure_fill(shape) returns the figure. An [out] array is returned as a list of as many elements as the function says it wrote.

Arguments

An array read through a pointer is given as a sequence, its @len count not given; sequences sharing one count must be as long, or ValueError is raised. One that is [in,out] is a list, written back. A void * with a @len is bytes, a bytearray written in place where [in,out]. A handle given is its class. A callback is given as any callable, without the void * context, which the layer gives C; pointers to records it receives are the records, and strings str. One of @lifetime once (see Callback lifetimes) is kept until called, or until the call fails; one of @lifetime handle, unless the call fails, until the handle is closed or collected, and for good where destroying it fails or nothing destroys it, as C may still hold it; a handle the layer only borrows keeps them, as nothing tells it when the handle is destroyed. A @nullable parameter defaults to None where those after it do too.

The idiomatic layer uses the raw functions and records, so the two mix: a record returned by one is given to the other. A name the idiomatic layer would take from the raw one, where there is no prefix to strip, or that two functions would share, keeps the C name. A module-level function named ctypes or enum, or beginning with _, and a constructor that keeps its C name, get the suffix _; so does a parameter named as what the function names, such as self, ret, a raw function or a class, or beginning or ending with _, so no two parameters share a name.

The library is loaded on the first call of a function. Its path is the environment variable <PREFIX>_LIBRARY, or the library is found by ctypes.util.find_library() from the name in the IR’s meta, without the lib prefix.

A variadic function is not bound: calling one through ctypes is not portable. Types declared outside the headers, such as FILE, are empty structures, usable through pointers. These are outside the C subset; the diagnostics of the header say so.

From Cython

The module is the one Python API. For Cython code calling C in its own hot loops, <prefix>.pxd declares the C API, its records, enums, defines and functions, from the same IR, in a cdef extern block of lib<prefix>.h, nogil, so the functions are called also without the GIL:

cimport foo as lib

def count(int times):
    cdef lib.foo_handle *handle = NULL
    if lib.foo_open(b"loop", &handle):
        raise OSError("foo_open() failed")
    ...

The .pxd is named as the module, and beside it, so cimport foo finds the declarations and import foo the ctypes module; Cython code may do both. Cimporting it as another name, such as lib, also works for a prefix that Cython reserves, such as api. Names are those of the C API, but for those Cython reserves, such as a function named include, and the tags of records and enums named as a function, define, enum member or typedef, which C keeps apart and Cython does not: these are declared with a trailing _ and their C name, int include_ "include"(int cdef_). A flexible array is left out, as Cython has none, and a comment says so.

A user’s extension is built with <output>/python on the include path of both Cython, for the .pxd, and the C compiler, for the header, and linked against the library; with setuptools:

from Cython.Build import cythonize
from setuptools import Extension, setup

extension = Extension(
    "loop", ["loop.pyx"], include_dirs=["output/python"], libraries=["foo"]
)
setup(ext_modules=cythonize([extension], include_path=["output/python"]))

examples/libfoo/checks/cython/check.pyx calls libfoo this way, and prints what it got back, its records from their C fields.

What it brings

Benchmark measures a call of libfoo from every target, against C. Through the .pxd, a Cython loop calls the C API at C’s cost, where ctypes, whose every call goes through the interpreter, costs two orders of magnitude more. That is what the .pxd is for: code with a hot loop over the library writes it in Cython, and the rest uses the ctypes module.

Example

yace output/foo.yaml --emit python --output output
output/python
├── __pycache__
│   └── foo.cpython-312.pyc
├── check.json
├── check.yaml
├── cython.log
├── foo.pxd
├── foo.py
├── foo_check.py
├── foo_check.pyx
├── libfoo.h
└── pxd_check.c

2 directories, 10 files

The fixture library is called through the raw layer by examples/libfoo/checks/python/check.py, and through the idiomatic layer by:

../../../../examples/libfoo/checks/python/idiomatic.py
"""
The check of the idiomatic Python layer of libfoo: as check.py, through handles,
exceptions and returned values rather than raw calls. An OSError is printed as
the negative errno the C function returned, so the documents printed are those
of 'expected.yaml', as for the raw layer.
"""

import sys

import foo
from check import document, name_of, record, statuses


def errno_of(call, *args):
    """What 'call' returns, 0 for nothing, or the negative errno it raises"""

    try:
        ret = call(*args)
    except OSError as exc:
        return -exc.errno
    return 0 if ret is None else ret


def main():
    as_json = len(sys.argv) > 1 and sys.argv[1] == "json"
    flags = foo.FOO_PR_JSON if as_json else foo.FOO_PR_YAML

    document(
        "defines",
        {
            name: getattr(foo, name)
            for name in [
                "FOO_NAME",
                "FOO_MAGIC",
                "FOO_VERSION_MAJOR",
                "FOO_VERSION_MINOR",
                "FOO_VERSION_PATCH",
                "FOO_POINTS_MAX",
                "FOO_DEFAULT_SHAPE_SIDES",
            ]
        },
        as_json,
    )
    document(
        "enums",
        {
            "foo_shape": [name_of(foo.FooShape, v) for v in (-1, 0, 1, 2, 3)],
            "foo_option": [name_of(foo.FooOption, v) for v in (1, 2, 3)],
        },
        as_json,
    )

    points = [foo.FooPoint(1, 2), foo.FooPoint(3, 4), foo.FooPoint(5, 6)]
    calls = {"version": foo.version()}
    with foo.open("points") as handle:
        calls["open"] = 0
        calls["open_without_name"] = errno_of(foo.open, None)
        calls["name"] = handle.name()
        watched = []
        calls["watch"] = errno_of(handle.point_watch, lambda p: watched.append(p.x))
        calls["watch_busy"] = errno_of(handle.point_watch, watched.append)
        calls["added"] = sum(errno_of(handle.point_add, p) == 0 for p in points)
        calls["count"] = handle.point_count()

        seen = []
        calls["visit"] = handle.point_visit(lambda point: seen.append(point.x) or 0)
        calls["visit_sum_x"] = sum(seen)
        ys = []
        handle.point_visit(lambda point: ys.append(point.y) or 0)
        calls["visit_sum_y"] = sum(ys)
        calls["visit_stopped"] = handle.point_visit(lambda point: point.x == 3)
        calls["visit_without_callback"] = errno_of(handle.point_visit, None)
        with_visitor = []
        callback = foo.FooVisitCb(lambda arg, p: with_visitor.append(p.contents.x) or 0)
        calls["visit_with"] = handle.point_visit_with(foo.FooVisitor(visit=callback))
        calls["visit_with_sum_x"] = sum(with_visitor)
        calls["figure_of_unknown_shape"] = errno_of(foo.figure_fill, 3)
        calls["add_many"] = handle.point_add_many(points)
        calls["count_after_add_many"] = handle.point_count()
        calls["watched"] = len(watched)
        calls["watched_sum_x"] = sum(watched)
        copied = handle.point_copy(4)
        calls["points_copied"] = len(copied)
        calls["points_sum_x"] = sum(point.x for point in copied)
        counted = []
        calls["submit"] = errno_of(handle.count_submit, counted.append)
        calls["submit_busy"] = errno_of(handle.count_submit, counted.append)
        calls["complete"] = handle.count_complete()
        calls["done_count"] = counted[0]
        calls["complete_again"] = handle.count_complete()
        document("calls", calls, as_json)

        record(foo.FooPoint, handle.point_get(1), flags)
        record(foo.FooPoint, handle.point_get(6), flags)

        while errno_of(handle.point_add, points[0]) == 0:
            pass
        document(
            "full",
            {
                "count": handle.point_count(),
                "add": errno_of(handle.point_add, points[0]),
                "add_many": errno_of(handle.point_add_many, points[:1]),
                "watched": len(watched),
            },
            as_json,
        )
        statuses(handle.status_get(), flags, as_json)
    handle.close()  # closing again does nothing

    record(foo.FooScalars, foo.scalars_fill(), flags)
    for shape in (foo.FOO_SHAPE_POINT, foo.FOO_SHAPE_LINE, foo.FOO_SHAPE_POLYGON):
        record(foo.FooFigure, foo.figure_fill(shape), flags)
    record(foo.FooValue, foo.value_set(-1), flags)
    record(foo.FooWire, foo.wire_fill(), flags)

    return 0


if __name__ == "__main__":
    sys.exit(main())

Bindings

../../../../output/python/foo.py
"""
libfoo: An example library described in the C subset of yace

Python bindings, through ctypes, generated by yace from foo.h;
do not edit. The shared library is loaded on the first call of a function: the
path in the environment variable FOO_LIBRARY, or else the library
'foo' found the way ctypes.util.find_library() finds it.

A function that converts none of its arguments is replaced in this module, on
that first call, by the ctypes function itself, which takes its arguments by
position only. Call it through the module, as module.function(), to reach
ctypes directly. A name imported with 'from ... import' before the first call
stays the wrapper, which is slower.

SPDX-License-Identifier: GPL-2.0-only
"""

import builtins as _builtins
import ctypes
import ctypes.util
import enum
import json as _json
import os as _os
import sys as _sys

_LIBRARY = None


def _api():
    """The shared library, loaded, with the signatures of its functions set"""

    global _LIBRARY

    if _LIBRARY is None:
        path = _os.environ.get("FOO_LIBRARY") or ctypes.util.find_library(
            "foo"
        )
        if not path:
            raise OSError(
                "no library 'foo' found; set FOO_LIBRARY to its path"
            )
        lib = ctypes.CDLL(path)
        for name, (restype, argtypes, errcheck) in _SIGNATURES.items():
            func = _builtins.getattr(lib, name, None)
            if func is not None:
                func.restype = restype
                func.argtypes = argtypes
                if errcheck is not None:
                    func.errcheck = errcheck
        _LIBRARY = lib
        scope = _builtins.globals()
        for name, pyname in _DIRECT:
            func = _builtins.getattr(lib, name, None)
            if func is not None:
                func.__doc__ = scope[pyname].__doc__
                func.__wrapped__ = scope[pyname]  # its signature, for inspect
                scope[pyname] = func

    return _LIBRARY


def _str_in(value):
    """A str argument, encoded, for a 'const char *' parameter"""

    return value.encode() if _builtins.isinstance(value, _builtins.str) else value


def _str_out(value):
    return None if value is None else value.decode(errors="replace")


def _str_check(value, func, args):
    """A string returned, decoded"""

    return _str_out(value)


def _ptr_check(value, func, args):
    """A NULL pointer returned, as None"""

    return value if value else None


def _plain(ctype, value):
    """'value', of the C type 'ctype', as what the pretty-printers print"""

    if ctype is ctypes.c_char:
        return value[0]
    if ctype is ctypes.c_char_p:
        return _str_out(value)
    if ctype is ctypes.c_void_p:
        return None if not value else f"0x{value:x}"
    if ctype in (ctypes.c_float, ctypes.c_double):
        return _builtins.float(_builtins.format(value, "g"))
    if _builtins.issubclass(ctype, ctypes.Array):
        if not ctype._length_:
            return None
        if ctype._type_ is ctypes.c_char:
            raw = value if _builtins.isinstance(value, _builtins.bytes) else _builtins.bytes(value)
            return _str_out(raw.split(b"\0", 1)[0])
        return [_plain(ctype._type_, item) for item in value]
    if _builtins.issubclass(ctype, _Record):
        return _Record.to_dict(value)
    if _builtins.issubclass(ctype, (ctypes._Pointer, ctypes._CFuncPtr)):
        addr = ctypes.cast(value, ctypes.c_void_p).value
        return None if not addr else f"0x{addr:x}"
    if _builtins.issubclass(ctype, (ctypes.Structure, ctypes.Union)):
        return None
    return value


class _Bits(_builtins.dict):
    """A field of bits, its value and its parts by name, printed on one line"""


def _yaml(data, indent):
    lines = []
    for key, value in data.items():
        if _builtins.isinstance(value, _Bits):
            parts = ", ".join(f"{name}: {part:#x}" for name, part in value.items())
            lines.append(f"{'':{indent}}{key}: " + "{" + parts + "}\n")
        elif _builtins.isinstance(value, _builtins.dict):
            lines.append(f"{'':{indent}}{key}:\n{_yaml(value, indent + 2)}")
        else:
            lines.append(f"{'':{indent}}{key}: {_json.dumps(value)}\n")
    return "".join(lines)


class FooPr(enum.IntFlag):
    """Output formats of the pretty-printers"""

    FOO_PR_DEF = 0x0  #: The default, YAML
    FOO_PR_YAML = 0x1  #: YAML
    FOO_PR_TERSE = 0x2  #: Terse; the generated printers print YAML
    FOO_PR_JSON = 0x4  #: JSON, on one line


FOO_PR_DEF = FooPr.FOO_PR_DEF
FOO_PR_YAML = FooPr.FOO_PR_YAML
FOO_PR_TERSE = FooPr.FOO_PR_TERSE
FOO_PR_JSON = FooPr.FOO_PR_JSON


class _Record(object):
    """The pretty-printers of a struct or union; called through _Record, as a
    field of the same name hides one"""

    _yace_name = ""
    _yace_masks = {}  # the parts of each field of bits: name, mask and shift
    _yace_enums = {}  # the enum of each field typed as one

    def to_dict(self):
        """The fields, by name, as plain values: pointers as hex strings, a
        field of bits as its value and its parts"""

        cls = _builtins.type(self)
        found = {}
        for name, ctype, *_ in cls._fields_:
            if not name:
                continue  # a padding bit-field
            value = _plain(ctype, _builtins.getattr(self, name))
            if name in cls._yace_masks:
                value = _Bits(
                    {"val": value},
                    **{part: (value & mask) >> lo for part, mask, lo in cls._yace_masks[name]},
                )
            found[name] = value
        return found

    @classmethod
    def fpr(cls, stream, obj, flags=FOO_PR_DEF):
        """Print 'obj', a record, a pointer to one, or None, to 'stream'

        Returns:
            The number of characters written
        """

        if _builtins.isinstance(obj, ctypes._Pointer):
            obj = obj.contents if obj else None
        if flags & FOO_PR_JSON:
            text = _json.dumps(None if obj is None else _Record.to_dict(obj)) + "\n"
        elif obj is None:
            text = f"{cls._yace_name}: ~\n"
        else:
            text = f"{cls._yace_name}:\n{_yaml(_Record.to_dict(obj), 2)}"
        stream.write(text)
        return _builtins.len(text)

    @classmethod
    def pr(cls, obj, flags=FOO_PR_DEF):
        """Print 'obj' to stdout, as fpr()"""

        return _Record.fpr.__func__(cls, _sys.stdout, obj, flags)

    def __str__(self):
        return f"{self._yace_name}:\n{_yaml(_Record.to_dict(self), 2)}"

    def __repr__(self):
        """As the constructor is called: each field by name, every one of a
        union, as which member is meant is not known"""

        cls = _builtins.type(self)
        fields = []
        for name, *_ in cls._fields_:
            value = _builtins.getattr(self, name)
            fields.append(f"{name}={_repr(value, cls._yace_enums.get(name))}")
        return f"{cls.__name__}({', '.join(fields)})"


def _repr(value, enum=None):
    """A field's value, as __repr__() shows it: an array as a list, and a value
    of 'enum' by the name of its member, if it is one"""

    if _builtins.isinstance(value, ctypes.Array) and value._type_ is ctypes.c_char:
        return _builtins.repr(value.value)
    if _builtins.isinstance(value, ctypes.Array):
        return "[" + ", ".join(_repr(item, enum) for item in value) + "]"
    if enum is not None:
        try:
            return f"{enum.__name__}.{enum(value).name}"
        except _builtins.ValueError:
            pass
    return _builtins.repr(value)


FOO_NAME = "foo"  #: Name of the library
FOO_MAGIC = 0xacdc  #: A hexadecimal constant
FOO_VERSION_MAJOR = 1  #: Major version
FOO_VERSION_MINOR = 2  #: Minor version
FOO_VERSION_PATCH = 3  #: Patch version
FOO_POINTS_MAX = 8  #: Points a handle holds at most
FOO_DEFAULT_SHAPE_SIDES = 8  #: A constant, by name
FOO_STATUS_CODE_PENDING = 0x1  #: A count is submitted and not yet completed; bit 0 of 'code'
FOO_STATUS_CODE_POINTS = 0xff0  #: Points held; bits 11:4 of 'code'
FOO_STATUS_CODE_FULL = 0x8000  #: The handle holds FOO_POINTS_MAX points; bit 15 of 'code'


class FooShape(enum.IntEnum):
    """Kinds of figure"""

    FOO_SHAPE_NONE = -1  #: No figure
    FOO_SHAPE_POINT = 0  #: A single point
    FOO_SHAPE_LINE = 1  #: Two points
    FOO_SHAPE_POLYGON = 2  #: Three points or more


FOO_SHAPE_NONE = FooShape.FOO_SHAPE_NONE
FOO_SHAPE_POINT = FooShape.FOO_SHAPE_POINT
FOO_SHAPE_LINE = FooShape.FOO_SHAPE_LINE
FOO_SHAPE_POLYGON = FooShape.FOO_SHAPE_POLYGON


class FooOption(enum.IntEnum):
    """Options of a figure, combined as bits"""

    FOO_OPTION_CLOSED = 1  #: The last corner connects to the first
    FOO_OPTION_FILLED = 2  #: The inside is filled


FOO_OPTION_CLOSED = FooOption.FOO_OPTION_CLOSED
FOO_OPTION_FILLED = FooOption.FOO_OPTION_FILLED


class FooHandle(ctypes.Structure):
    """A collection of points, opened with foo_open() and closed with foo_close()"""


class FooPoint(ctypes.Structure, _Record):
    """A point on the plane

    Attributes:
        x: Horizontal position
        y: Vertical position
    """

    _yace_name = "foo_point"


class FooVisitor(ctypes.Structure, _Record):
    """A callback and its argument

    Attributes:
        visit: Called for each point
        arg: Given to 'visit'
    """

    _yace_name = "foo_visitor"


class FooScalars(ctypes.Structure, _Record):
    """A value of every kind of scalar

    Attributes:
        flag: bool
        letter: char
        schar: signed char
        uchar: unsigned char
        s: short
        us: unsigned short
        i: int
        ui: unsigned int
        l: long
        ul: unsigned long
        ll: long long
        ull: unsigned long long
        f: float
        d: double
        i8: int8_t
        i16: int16_t
        i32: int32_t
        i64: int64_t
        u8: uint8_t
        u16: uint16_t
        u32: uint32_t
        u64: uint64_t
        iptr: intptr_t
        uptr: uintptr_t
        size: size_t
        ssize: ssize_t
        diff: ptrdiff_t
    """

    _yace_name = "foo_scalars"


class FooFigure(ctypes.Structure, _Record):
    """A figure: its kind, where it is, and its corners

    Attributes:
        shape: One of enum foo_shape
        options: Bits of enum foo_option
        origin: Where the figure is
        corners: The corners, relative to 'origin'
        ncorners: How many of 'corners' are used
        name: Name of the figure
        grid: A two-dimensional array
        label: A string, NULL when there is none
        scale: Scale of the figure
        area: Area of the figure
    """

    _yace_name = "foo_figure"


class FooValue(ctypes.Union, _Record):
    """A 32-bit value, read as signed, as unsigned, or as the 'x' of a point

    Attributes:
        i: Signed
        u: Unsigned
        point: A point, its 'x' sharing the bits of 'i'
    """

    _yace_name = "foo_value"


class FooWire(ctypes.Structure, _Record):
    """A record laid out without padding, as a wire format is: no field after the first is aligned

    Attributes:
        kind: One of enum foo_shape
        length: At offset 1
        at: A point, at offset 5
        pair: An array, at offset 13
    """

    # Packed, as in C: ctypes packs only as MSVC lays out, which is as GCC
    # does but for bit-fields
    _layout_ = "ms"
    _pack_ = 1

    _yace_name = "foo_wire"


class FooStatus(ctypes.Structure, _Record):
    """The status of a handle, its code a field of bits

    Attributes:
        code: Status code, see FOO_STATUS_CODE_*
        max: Points the handle holds at most
    """

    _yace_name = "foo_status"
    _yace_masks = {
        "code": (("pending", 0x1, 0), ("points", 0xff0, 4), ("full", 0x8000, 15), ),
    }

    @property
    def code_pending(self):
        """A count is submitted and not yet completed; bit 0 of 'code'"""

        return (self.code & 0x1) >> 0

    @code_pending.setter
    def code_pending(self, value):
        if not 0 <= value <= 0x1:
            raise ValueError(f"code_pending is 1 bit, not {value}")
        self.code = (self.code & ~0x1) | (value << 0)

    @property
    def code_points(self):
        """Points held; bits 11:4 of 'code'"""

        return (self.code & 0xff0) >> 4

    @code_points.setter
    def code_points(self, value):
        if not 0 <= value <= 0xff:
            raise ValueError(f"code_points is 8 bits, not {value}")
        self.code = (self.code & ~0xff0) | (value << 4)

    @property
    def code_full(self):
        """The handle holds FOO_POINTS_MAX points; bit 15 of 'code'"""

        return (self.code & 0x8000) >> 15

    @code_full.setter
    def code_full(self, value):
        if not 0 <= value <= 0x1:
            raise ValueError(f"code_full is 1 bit, not {value}")
        self.code = (self.code & ~0x8000) | (value << 15)


FooVisitCb = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(FooPoint))
"""Called back with each point visited

Args:
    arg: The argument given along with the callback
    point: The point visited

Returns:
    0 to go on, anything else to stop
"""


FooDoneCb = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_size_t)
"""Called back when a count submitted completes

Args:
    arg: The argument given along with the callback
    count: The number of points when it completed
"""


FooAddedCb = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(FooPoint))
"""Called back with each point added to a handle watched

Args:
    arg: The argument given along with the callback
    point: The point added
"""


FooPoint._fields_ = [
    ("x", ctypes.c_int32),
    ("y", ctypes.c_int32),
]

FooVisitor._fields_ = [
    ("visit", FooVisitCb),
    ("arg", ctypes.c_void_p),
]

FooScalars._fields_ = [
    ("flag", ctypes.c_bool),
    ("letter", ctypes.c_char),
    ("schar", ctypes.c_byte),
    ("uchar", ctypes.c_ubyte),
    ("s", ctypes.c_short),
    ("us", ctypes.c_ushort),
    ("i", ctypes.c_int),
    ("ui", ctypes.c_uint),
    ("l", ctypes.c_long),
    ("ul", ctypes.c_ulong),
    ("ll", ctypes.c_longlong),
    ("ull", ctypes.c_ulonglong),
    ("f", ctypes.c_float),
    ("d", ctypes.c_double),
    ("i8", ctypes.c_int8),
    ("i16", ctypes.c_int16),
    ("i32", ctypes.c_int32),
    ("i64", ctypes.c_int64),
    ("u8", ctypes.c_uint8),
    ("u16", ctypes.c_uint16),
    ("u32", ctypes.c_uint32),
    ("u64", ctypes.c_uint64),
    ("iptr", ctypes.c_ssize_t),
    ("uptr", ctypes.c_size_t),
    ("size", ctypes.c_size_t),
    ("ssize", ctypes.c_ssize_t),
    ("diff", ctypes.c_ssize_t),
]

FooFigure._fields_ = [
    ("shape", ctypes.c_uint32),
    ("options", ctypes.c_uint32),
    ("origin", FooPoint),
    ("corners", (FooPoint * 4)),
    ("ncorners", ctypes.c_uint8),
    ("name", (ctypes.c_char * 16)),
    ("grid", ((ctypes.c_int16 * 3) * 2)),
    ("label", ctypes.c_char_p),
    ("scale", ctypes.c_float),
    ("area", ctypes.c_double),
]

FooValue._fields_ = [
    ("i", ctypes.c_int32),
    ("u", ctypes.c_uint32),
    ("point", FooPoint),
]

FooWire._fields_ = [
    ("kind", ctypes.c_uint8),
    ("length", ctypes.c_uint32),
    ("at", FooPoint),
    ("pair", (ctypes.c_uint16 * 2)),
]

FooStatus._fields_ = [
    ("code", ctypes.c_uint16),
    ("max", ctypes.c_uint16),
]

_SIGNATURES = {
    "foo_version": (
        ctypes.c_char_p,
        [],
        _str_check,
    ),
    "foo_open": (
        ctypes.c_int,
        [ctypes.c_char_p, ctypes.POINTER(ctypes.POINTER(FooHandle))],
        None,
    ),
    "foo_close": (
        None,
        [ctypes.POINTER(FooHandle)],
        None,
    ),
    "foo_name": (
        ctypes.c_char_p,
        [ctypes.POINTER(FooHandle)],
        _str_check,
    ),
    "foo_point_add": (
        ctypes.c_int,
        [ctypes.POINTER(FooHandle), ctypes.POINTER(FooPoint)],
        None,
    ),
    "foo_point_add_many": (
        ctypes.c_ssize_t,
        [ctypes.POINTER(FooHandle), ctypes.POINTER(FooPoint), ctypes.c_size_t],
        None,
    ),
    "foo_point_count": (
        ctypes.c_size_t,
        [ctypes.POINTER(FooHandle)],
        None,
    ),
    "foo_point_get": (
        ctypes.POINTER(FooPoint),
        [ctypes.POINTER(FooHandle), ctypes.c_size_t],
        _ptr_check,
    ),
    "foo_point_copy": (
        ctypes.c_ssize_t,
        [ctypes.POINTER(FooHandle), ctypes.POINTER(FooPoint), ctypes.c_size_t],
        None,
    ),
    "foo_point_watch": (
        ctypes.c_int,
        [ctypes.POINTER(FooHandle), FooAddedCb, ctypes.c_void_p],
        None,
    ),
    "foo_point_visit": (
        ctypes.c_ssize_t,
        [ctypes.POINTER(FooHandle), FooVisitCb, ctypes.c_void_p],
        None,
    ),
    "foo_point_visit_with": (
        ctypes.c_ssize_t,
        [ctypes.POINTER(FooHandle), ctypes.POINTER(FooVisitor)],
        None,
    ),
    "foo_count_submit": (
        ctypes.c_int,
        [ctypes.POINTER(FooHandle), FooDoneCb, ctypes.c_void_p],
        None,
    ),
    "foo_count_complete": (
        ctypes.c_size_t,
        [ctypes.POINTER(FooHandle)],
        None,
    ),
    "foo_status_get": (
        None,
        [ctypes.POINTER(FooHandle), ctypes.POINTER(FooStatus)],
        None,
    ),
    "foo_scalars_fill": (
        None,
        [ctypes.POINTER(FooScalars)],
        None,
    ),
    "foo_figure_fill": (
        ctypes.c_int,
        [ctypes.POINTER(FooFigure), ctypes.c_uint32],
        None,
    ),
    "foo_value_set": (
        None,
        [ctypes.POINTER(FooValue), ctypes.c_int32],
        None,
    ),
    "foo_wire_fill": (
        None,
        [ctypes.POINTER(FooWire)],
        None,
    ),
}

# The functions the ctypes functions themselves replace, by C and Python name
_DIRECT = [
    ("foo_version", "foo_version"),
    ("foo_close", "foo_close"),
    ("foo_name", "foo_name"),
    ("foo_point_add", "foo_point_add"),
    ("foo_point_add_many", "foo_point_add_many"),
    ("foo_point_count", "foo_point_count"),
    ("foo_point_get", "foo_point_get"),
    ("foo_point_copy", "foo_point_copy"),
    ("foo_point_watch", "foo_point_watch"),
    ("foo_point_visit", "foo_point_visit"),
    ("foo_point_visit_with", "foo_point_visit_with"),
    ("foo_count_submit", "foo_count_submit"),
    ("foo_count_complete", "foo_count_complete"),
    ("foo_status_get", "foo_status_get"),
    ("foo_scalars_fill", "foo_scalars_fill"),
    ("foo_figure_fill", "foo_figure_fill"),
    ("foo_value_set", "foo_value_set"),
    ("foo_wire_fill", "foo_wire_fill"),
]


def foo_version():
    """The version of the library

    Returns:
        The version, as "MAJOR.MINOR.PATCH"
    """

    return _api().foo_version()


def foo_open(name, handle):
    """Open a handle holding no points

    Args:
        name: Name of the handle, copied
        handle: Set to the handle opened

    Returns:
        0 on success, -EINVAL when 'name' or 'handle' is NULL, -ENOMEM when memory ran out
    """

    return _api().foo_open(_str_in(name), handle)


def foo_close(handle, /):
    """Close a handle

    Args:
        handle: The handle to close, NULL is ignored
    """

    return _api().foo_close(handle)


def foo_name(handle, /):
    """The name the handle was opened with

    Args:
        handle: The handle

    Returns:
        The name, owned by the handle
    """

    return _api().foo_name(handle)


def foo_point_add(handle, point, /):
    """Add a point to the handle

    Args:
        handle: The handle
        point: The point to add, copied

    Returns:
        0 on success, -ENOSPC when the handle holds FOO_POINTS_MAX points
    """

    return _api().foo_point_add(handle, point)


def foo_point_add_many(handle, points, npoints, /):
    """Add points to the handle, as many as it has room for

    Args:
        handle: The handle
        points: The points to add, copied
        npoints: The number of points

    Returns:
        The number of points added, -ENOSPC when there was room for none
    """

    return _api().foo_point_add_many(handle, points, npoints)


def foo_point_count(handle, /):
    """The number of points held

    Args:
        handle: The handle

    Returns:
        The number of points
    """

    return _api().foo_point_count(handle)


def foo_point_get(handle, idx, /):
    """A point held by the handle

    Args:
        handle: The handle
        idx: Index of the point

    Returns:
        The point, NULL when 'idx' is past the last one
    """

    return _api().foo_point_get(handle, idx)


def foo_point_copy(handle, points, npoints, /):
    """Copy the points held, as many as fit

    Args:
        handle: The handle
        points: Where to copy the points to
        npoints: The number of points that fit

    Returns:
        The number of points copied
    """

    return _api().foo_point_copy(handle, points, npoints)


def foo_point_watch(handle, added, arg, /):
    """Watch the points added to the handle, from now until it is closed

    Args:
        handle: The handle
        added: Called with each point added, until the handle is closed
        arg: Given to 'added'

    Returns:
        0 on success, -EBUSY when the handle is watched already
    """

    return _api().foo_point_watch(handle, added, arg)


def foo_point_visit(handle, visit, arg, /):
    """Visit the points in the order they were added

    Args:
        handle: The handle
        visit: Called for each point, until it returns non-zero
        arg: Given to 'visit'

    Returns:
        The number of points visited, -EINVAL when 'visit' is NULL
    """

    return _api().foo_point_visit(handle, visit, arg)


def foo_point_visit_with(handle, visitor, /):
    """Visit the points with a visitor; as foo_point_visit()

    Args:
        handle: The handle
        visitor: The callback and its argument

    Returns:
        The number of points visited, -EINVAL when the callback is NULL
    """

    return _api().foo_point_visit_with(handle, visitor)


def foo_count_submit(handle, done, arg, /):
    """Submit a count of the points, completed by foo_count_complete()

    Args:
        handle: The handle
        done: Called once, by foo_count_complete(), with the number of points
        arg: Given to 'done'

    Returns:
        0, -EBUSY when a count is submitted and not yet completed
    """

    return _api().foo_count_submit(handle, done, arg)


def foo_count_complete(handle, /):
    """Complete the count submitted, if any, calling its callback

    Args:
        handle: The handle

    Returns:
        The number of counts completed, 0 or 1
    """

    return _api().foo_count_complete(handle)


def foo_status_get(handle, status, /):
    """The status of the handle

    Args:
        handle: The handle
        status: The status to fill in
    """

    return _api().foo_status_get(handle, status)


def foo_scalars_fill(scalars, /):
    """Fill in a value of every kind of scalar, the limits of the fixed-width ones

    Args:
        scalars: The values to fill in
    """

    return _api().foo_scalars_fill(scalars)


def foo_figure_fill(figure, shape, /):
    """Fill in a figure of the given shape

    Args:
        figure: The figure to fill in
        shape: One of enum foo_shape

    Returns:
        0 on success, -EINVAL when 'shape' is none of enum foo_shape
    """

    return _api().foo_figure_fill(figure, shape)


def foo_value_set(value, i, /):
    """Set a value, clearing the bits it does not use

    Args:
        value: The value to set
        i: What to set it to
    """

    return _api().foo_value_set(value, i)


def foo_wire_fill(wire, /):
    """Fill in a packed record

    Args:
        wire: The record to fill in
    """

    return _api().foo_wire_fill(wire)


def _check(ret):
    """Raise the negative errno returned as an OSError"""

    if ret < 0:
        raise OSError(-ret, _os.strerror(-ret))


_KEPT = {}
_KEYS = __import__('itertools').count(1)


def _kept0(a0, a1):
    return _KEPT[a0](a1.contents)


_kept0_c = FooAddedCb(_kept0)


def _kept1(a0, a1):
    return _KEPT.pop(a0)(a1)


_kept1_c = FooDoneCb(_kept1)


class Handle(object):
    """A collection of points, opened with foo_open() and closed with foo_close()

    Wraps a pointer to struct foo_handle, which it releases
    with close(), also on leaving a with-block or when
    collected,
    when it owns it.
    """

    def __init__(self, ptr, owned=True):
        self._handle = ptr
        self._owned = owned
        self._keys = []

    @property
    def _ptr(self):
        if not self._handle:
            raise ValueError("the handle is closed")
        return self._handle

    def _release(self):
        for key in self._keys:
            _KEPT.pop(key, None)
        self._keys = []

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        if self._owned and self._handle:
            self.close()

    def __del__(self):
        if self._owned and self._handle:
            ret = foo_close(self._handle)
            self._release()

    def name(self):
        """The name the handle was opened with

        Returns:
            The name, owned by the handle

        Calls foo_name().
        """

        _ptr = self._ptr
        ret = foo_name(_ptr)
        return ret

    def point_add(self, point):
        """Add a point to the handle

        Args:
            point: The point to add, copied

        Raises:
            OSError: foo_point_add() returns: 0 on success, -ENOSPC when the handle holds FOO_POINTS_MAX points

        Calls foo_point_add().
        """

        _ptr = self._ptr
        ret = foo_point_add(_ptr, point)
        _check(ret)

    def point_add_many(self, points):
        """Add points to the handle, as many as it has room for

        Args:
            points: The points to add, copied

        Raises:
            OSError: foo_point_add_many() returns: The number of points added, -ENOSPC when there was room for none

        Calls foo_point_add_many().
        """

        _ptr = self._ptr
        points_c = (FooPoint * _builtins.len(points))(*points)
        ret = foo_point_add_many(_ptr, points_c, _builtins.len(points))
        _check(ret)
        return ret

    def point_count(self):
        """The number of points held

        Returns:
            The number of points

        Calls foo_point_count().
        """

        _ptr = self._ptr
        ret = foo_point_count(_ptr)
        return ret

    def point_get(self, idx):
        """A point held by the handle

        Args:
            idx: Index of the point

        Returns:
            The point, NULL when 'idx' is past the last one

        Calls foo_point_get().
        """

        _ptr = self._ptr
        ret = foo_point_get(_ptr, idx)
        return None if not ret else _builtins.type(ret.contents).from_buffer_copy(ret.contents)

    def point_copy(self, npoints):
        """Copy the points held, as many as fit

        Args:
            npoints: The number of points that fit

        Raises:
            OSError: foo_point_copy() returns: The number of points copied

        Calls foo_point_copy().
        """

        _ptr = self._ptr
        points = (FooPoint * npoints)()
        ret = foo_point_copy(_ptr, points, npoints)
        _check(ret)
        return points[: ret]

    def point_watch(self, added):
        """Watch the points added to the handle, from now until it is closed

        Args:
            added: Called with each point added, until the handle is closed

        Raises:
            OSError: foo_point_watch() returns: 0 on success, -EBUSY when the handle is watched already

        Calls foo_point_watch().
        """

        _ptr = self._ptr
        added_key = None
        if added is not None:
            added_key = _builtins.next(_KEYS)
            _KEPT[added_key] = added
            self._keys.append(added_key)
        ret = foo_point_watch(_ptr, _kept0_c if added_key else FooAddedCb(), added_key)
        if ret < 0 and added_key:
            _KEPT.pop(added_key, None)
            self._keys.remove(added_key)
        _check(ret)

    def point_visit(self, visit):
        """Visit the points in the order they were added

        Args:
            visit: Called for each point, until it returns non-zero

        Raises:
            OSError: foo_point_visit() returns: The number of points visited, -EINVAL when 'visit' is NULL

        Calls foo_point_visit().
        """

        _ptr = self._ptr
        visit_c = FooVisitCb()
        if visit is not None:
            visit_c = FooVisitCb(
                lambda _a0, _a1: visit(_a1.contents)
            )
        ret = foo_point_visit(_ptr, visit_c, ctypes.cast(visit_c, ctypes.c_void_p))
        _check(ret)
        return ret

    def point_visit_with(self, visitor):
        """Visit the points with a visitor; as foo_point_visit()

        Args:
            visitor: The callback and its argument

        Raises:
            OSError: foo_point_visit_with() returns: The number of points visited, -EINVAL when the callback is NULL

        Calls foo_point_visit_with().
        """

        _ptr = self._ptr
        ret = foo_point_visit_with(_ptr, visitor)
        _check(ret)
        return ret

    def count_submit(self, done):
        """Submit a count of the points, completed by foo_count_complete()

        Args:
            done: Called once, by foo_count_complete(), with the number of points

        Raises:
            OSError: foo_count_submit() returns: 0, -EBUSY when a count is submitted and not yet completed

        Calls foo_count_submit().
        """

        _ptr = self._ptr
        done_key = None
        if done is not None:
            done_key = _builtins.next(_KEYS)
            _KEPT[done_key] = done
        ret = foo_count_submit(_ptr, _kept1_c if done_key else FooDoneCb(), done_key)
        if ret < 0 and done_key:
            _KEPT.pop(done_key, None)
        _check(ret)

    def count_complete(self):
        """Complete the count submitted, if any, calling its callback

        Returns:
            The number of counts completed, 0 or 1

        Calls foo_count_complete().
        """

        _ptr = self._ptr
        ret = foo_count_complete(_ptr)
        return ret

    def status_get(self):
        """The status of the handle

        Calls foo_status_get().
        """

        _ptr = self._ptr
        status = FooStatus()
        ret = foo_status_get(_ptr, status)
        return status

    def close(self):
        """Close a handle

        Calls foo_close().
        """

        if self._handle:
            ret = foo_close(self._handle)
            self._handle = None
            self._release()


def open(name):
    """Open a handle holding no points

    Args:
        name: Name of the handle, copied

    Raises:
        OSError: foo_open() returns: 0 on success, -EINVAL when 'name' or 'handle' is NULL, -ENOMEM when memory ran out

    Calls foo_open().
    """

    handle = ctypes.POINTER(FooHandle)()
    ret = foo_open(name, ctypes.byref(handle))
    _check(ret)
    return Handle(handle)


def version():
    """The version of the library

    Returns:
        The version, as "MAJOR.MINOR.PATCH"

    Calls foo_version().
    """

    ret = foo_version()
    return ret


def scalars_fill():
    """Fill in a value of every kind of scalar, the limits of the fixed-width ones

    Calls foo_scalars_fill().
    """

    scalars = FooScalars()
    ret = foo_scalars_fill(scalars)
    return scalars


def figure_fill(shape):
    """Fill in a figure of the given shape

    Args:
        shape: One of enum foo_shape

    Raises:
        OSError: foo_figure_fill() returns: 0 on success, -EINVAL when 'shape' is none of enum foo_shape

    Calls foo_figure_fill().
    """

    figure = FooFigure()
    ret = foo_figure_fill(figure, shape)
    _check(ret)
    return figure


def value_set(i):
    """Set a value, clearing the bits it does not use

    Args:
        i: What to set it to

    Calls foo_value_set().
    """

    value = FooValue()
    ret = foo_value_set(value, i)
    return value


def wire_fill():
    """Fill in a packed record

    Calls foo_wire_fill().
    """

    wire = FooWire()
    ret = foo_wire_fill(wire)
    return wire
../../../../output/python/foo.pxd
# libfoo: the C API for Cython, as libfoo.h, beside this file, declares
# it: 'cimport foo as lib', and its functions called as 'lib.<name>()', also
# without the GIL. The C compiler then needs this directory on its include path.
#
# Generated by yace from foo.h; do not edit.
#
# SPDX-License-Identifier: GPL-2.0-only

from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, intptr_t, uintptr_t
from libc.stddef cimport ptrdiff_t


cdef extern from "libfoo.h" nogil:
    ctypedef bint _Bool
    const char *FOO_NAME
    const long long FOO_MAGIC
    const long long FOO_VERSION_MAJOR
    const long long FOO_VERSION_MINOR
    const long long FOO_VERSION_PATCH
    const long long FOO_POINTS_MAX
    const long long FOO_DEFAULT_SHAPE_SIDES
    const long long FOO_STATUS_CODE_PENDING
    const long long FOO_STATUS_CODE_POINTS
    const long long FOO_STATUS_CODE_FULL
    enum foo_shape:
        FOO_SHAPE_NONE
        FOO_SHAPE_POINT
        FOO_SHAPE_LINE
        FOO_SHAPE_POLYGON
    enum foo_option:
        FOO_OPTION_CLOSED
        FOO_OPTION_FILLED
    struct foo_handle
    struct foo_point
    struct foo_visitor
    struct foo_scalars
    struct foo_figure
    union foo_value
    struct foo_wire
    struct foo_status
    ctypedef int (*foo_visit_cb)(void *, const foo_point *)
    ctypedef void (*foo_done_cb)(void *, size_t)
    ctypedef void (*foo_added_cb)(void *, const foo_point *)
    struct foo_point:
        int32_t x
        int32_t y
    struct foo_visitor:
        foo_visit_cb visit
        void *arg
    struct foo_scalars:
        _Bool flag
        char letter
        signed char schar
        unsigned char uchar
        short s
        unsigned short us
        int i
        unsigned int ui
        long l
        unsigned long ul
        long long ll
        unsigned long long ull
        float f
        double d
        int8_t i8
        int16_t i16
        int32_t i32
        int64_t i64
        uint8_t u8
        uint16_t u16
        uint32_t u32
        uint64_t u64
        intptr_t iptr
        uintptr_t uptr
        size_t size
        ssize_t ssize
        ptrdiff_t diff
    struct foo_figure:
        uint32_t shape
        uint32_t options
        foo_point origin
        foo_point corners[4]
        uint8_t ncorners
        char name[16]
        int16_t grid[2][3]
        const char *label
        float scale
        double area
    union foo_value:
        int32_t i
        uint32_t u
        foo_point point
    packed struct foo_wire:
        uint8_t kind
        uint32_t length
        foo_point at
        uint16_t pair[2]
    struct foo_status:
        uint16_t code
        uint16_t max
    const char *foo_version()
    int foo_open(const char *name, foo_handle **handle)
    void foo_close(foo_handle *handle)
    const char *foo_name(const foo_handle *handle)
    int foo_point_add(foo_handle *handle, const foo_point *point)
    ssize_t foo_point_add_many(foo_handle *handle, const foo_point *points, size_t npoints)
    size_t foo_point_count(const foo_handle *handle)
    const foo_point *foo_point_get(const foo_handle *handle, size_t idx)
    ssize_t foo_point_copy(const foo_handle *handle, foo_point *points, size_t npoints)
    int foo_point_watch(foo_handle *handle, foo_added_cb added, void *arg)
    ssize_t foo_point_visit(foo_handle *handle, foo_visit_cb visit, void *arg)
    ssize_t foo_point_visit_with(foo_handle *handle, const foo_visitor *visitor)
    int foo_count_submit(foo_handle *handle, foo_done_cb done, void *arg)
    size_t foo_count_complete(foo_handle *handle)
    void foo_status_get(const foo_handle *handle, foo_status *status)
    void foo_scalars_fill(foo_scalars *scalars)
    int foo_figure_fill(foo_figure *figure, uint32_t shape)
    void foo_value_set(foo_value *value, int32_t i)
    void foo_wire_fill(foo_wire *wire)

Implementation

The Python target: a module binding the API through ctypes, with the records as ctypes structures and unions that print themselves as YAML or JSON, the enums as IntEnum, and a check of what it generated

Emits, into <output>/python:

  • <prefix>.py, the bindings, or <prefix>_.py where the prefix names a module of Python’s standard library, such as zlib

  • <prefix>_check.py, the check program

  • <prefix>.pxd, the C API for Cython, and lib<prefix>.h, the C API, as the C target emits it, which the .pxd declares

  • <prefix>_check.pyx, the check of the .pxd

C names are kept, so the documentation of the C API applies: functions, constants and enum members by their C names, as Python’s own modules bind C APIs. Only types are renamed, to CamelCase, as Python names classes: struct foo_point is FooPoint.

class yace.targets.python.target.Python(output: Path, include: List[Path] | None = None, options: Dict[str, str] | None = None)
check(model: Model) → list

Run the check program, and, where there is Cython, compile the check of the .pxd; returns what failed, if anything

cython(model: Model) → list

Translate the check of the .pxd with Cython, and compile what it made of it with the header, so a declaration that disagrees with the header fails; where the Python running yace has no Cython, it warns that the .pxd is not checked, and fails nothing

emit(model: Model) → list

Emit the code for ‘model’; returns the paths written

yace.targets.python.target.module_name(model: Model) → str

The name of the module: the prefix, other than a module of Python’s standard library, which an import would find instead, or hide

Python code for the IR: names, and the ctypes type of each IR type. The Python target’s templates lay out the module; this produces what goes in it that depends on the types.

class yace.targets.python.pygen.Namer(model: Model)

The Python names of the IR’s types: CamelCase of the C name, or the C name itself where two would get the same one

bits_of(typ)

The bits of the integer ‘typ’, a bit-field’s unit; None if unknown

ctype(typ, param: bool = False) → str

The ctypes type of ‘typ’; a parameter’s arrays are pointers, as in C

enum_fields(record) → Dict[str, str]

The Python enum of each field of ‘record’ typed as an enum the IR defines, or as an array of one

fields(record) → List[str]

The entries of the record’s ‘_fields_’; of a blob, its bytes

function(typ: FunctionType) → str

The ctypes type of a pointer to a function of type ‘typ’

is_blob(record) → bool

Whether ctypes cannot lay out the record as C does: an unnamed bit-field sets no alignment in C, but does in ctypes, which refuses one of width 0; ctypes starts bit-fields of another type, and a field after one, in a unit of their own, where GCC packs them in what is left; and ctypes packs bit-fields only as MSVC does

yace.targets.python.pygen.accessor(field: Field, mask: Mask) → str

The name of the property reading and writing the part ‘mask’ of ‘field’

yace.targets.python.pygen.argument(param: Param, name: str) → str

The expression passing the wrapper’s argument ‘name’ on to C

yace.targets.python.pygen.attribute(name: str, value: str = '') → str

Getting the field ‘name’ of ‘self’, or setting it to ‘value’; a field named as a keyword is no attribute Python can spell

yace.targets.python.pygen.direct(function: Function) → bool

Whether the ctypes function itself can stand for ‘function’ in the module, as it converts none of its arguments; what it returns, its errcheck does

yace.targets.python.pygen.docstring(doc, indent: str = '', errors: str = '') → str

‘doc’ as the body of a docstring, or empty when there is none; ‘errors’ says when what is documented raises OSError

yace.targets.python.pygen.errcheck(function: Function, namer: Namer) → str

The errcheck of the ctypes function of ‘function’, converting what it returns, or None

yace.targets.python.pygen.ident(name: str) → str

‘name’, usable as a Python identifier

yace.targets.python.pygen.params(function: Function) → List[str]

The wrapper’s parameter names; C allows unnamed ones, Python does not

yace.targets.python.pygen.quoted(body: str, indent: str = '') → str

‘body’ as a docstring; one ending in a quote would end it early

The idiomatic Python layer of the bindings, over the raw functions: handles as classes that are context managers, errors as OSError, what is written through [out] pointers returned, sequences for arrays, callables for callbacks, and str for strings. See yace.targets.idiom for what is derived from where.

Cython code for the IR: the names of the C API in a .pxd, and the Cython spelling of each IR type. The .pxd declares the C API as the C target’s header does, so Cython code cimports it and calls C directly; the check, a .pyx cimporting every name, holds each declaration to the header.

class yace.targets.python.cygen.Check(pxd: Pxd)

The body of a function, never called, assigning each name the .pxd declares, cimported as ‘c’, to what Cython takes its type to be, or its address to a pointer to that; the C compiler, given the header, then fails where a declaration disagrees with it

local(typ, value: str = '', spec: str = 'noexcept nogil') → str

A local of ‘typ’ assigned ‘value’; a function type it holds has ‘spec’, as Cython gives what the .pxd declares of the C API, but for a field of a function pointer, which it does not declare nogil

class yace.targets.python.cygen.Names(model: Model)

The names of the C API in the .pxd: the C names, but for those Cython reserves, and the tags of records and enums that share a name with something else, as C keeps tags apart and Cython does not

cname(name: str, cname: str) → str

‘name’ as declared in the .pxd, with its C name where that differs

decl(typ, inner: str = '', qual: str = '', spec: str = '') → str

The Cython declaration of ‘inner’ as a ‘typ’; with ‘qual’, such as ‘c.’, the names of the API are those of the .pxd cimported as it, and ‘spec’, such as ‘noexcept’, follows each function type

of(name: str) → str

The .pxd name of the function, typedef, define or enum member ‘name’

tag(name: str) → str

The .pxd name of the record or enum ‘name’

typedef(name: str) → str

The .pxd name of the typedef ‘name’, or of the type it aliases

unique(name: str, own: bool = False) → str

‘name’, or with ‘_’ until it is neither reserved nor taken by another; an ‘own’ name is taken by itself

class yace.targets.python.cygen.Pxd(model: Model)

The declarations of the .pxd, in the order the C target’s header has them: enums, records declared, typedefs, records defined, functions

lines() → List[str]

The declarations, indented for the ‘cdef extern’ block

yace.targets.python.cygen.field_name(name: str) → str

The .pxd name of a field, in the namespace of its record

yace.targets.python.cygen.spelled_function(typ) → bool

Whether ‘typ’ spells out a function type, rather than by a typedef