python¶
The Python target, python, generates bindings of the API through
ctypes, into <output>/python:
<prefix>.pyThe bindings, a module with no dependencies outside the standard library;
<prefix>_.pywhere the prefix names a module of the standard library, such aszlib, which the bindings would otherwise hide or be hidden by.<prefix>_check.pyThe 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>.hThe C API for Cython, see From Cython, and the header it declares, as the C target emits it.
<prefix>_check.pyxThe check of the
.pxd, not part of the API: it assigns each name the.pxddeclares to what the.pxdsays 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
#defineconstants, by their names.- Enumerations
Each enum is an
enum.IntEnum, with members by their C names, also given as constants of the module, assignalandsocketdo.Kind(value).nameis the name of a value.- Records
Each struct and union is a
ctypes.Structureorctypes.Union, a packed one with_pack_ = 1, and in the.pxda packed struct ispacked. Each that has fields has the pretty-printers:to_dict(), its fields as plain values,fpr(stream, obj, flags)andpr(obj, flags), printingobj, an instance, a pointer to one, orNone, as the C API’s<name>_fpr()and<name>_pr()do.str()of a record is its YAML, andrepr()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 asstatus.code_type, reading the part shifted down and writing it in its place, raisingValueErrorfor 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 astr, orbytes, and aconst char *returned is astr. A pointer returned isNonewhen NULL. Out-parameters are passed withctypes.byref(), as in C; a record passed where a pointer to it is expected is passed by reference. A function without aconst 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, andinspect.signature()still gives its parameters. It takes its arguments by position only, as C does. A name imported withfrom ... importbefore 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_handleisHandle. Its constructor is a function of the module,foo.open(), its destructor the methodclose(), raisingOSErrorwhere it has@errno, also called on leaving awith-block, or when collected, and the functions taking it first are its methods:handle.point_add(point)callsfoo_point_add(handle, point).- Errors
A function documented
@errnoraisesOSError, with the errno, when it returns a negative value. One returningintreturns 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
@lencount not given; sequences sharing one count must be as long, orValueErroris raised. One that is[in,out]is a list, written back. Avoid *with a@lenis bytes, abytearraywritten in place where[in,out]. A handle given is its class. A callback is given as any callable, without thevoid *context, which the layer gives C; pointers to records it receives are the records, and stringsstr. 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@nullableparameter defaults toNonewhere 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:
"""
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¶
"""
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
# 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>_.pywhere the prefix names a module of Python’s standard library, such aszlib<prefix>_check.py, the check program<prefix>.pxd, the C API for Cython, andlib<prefix>.h, the C API, as the C target emits it, which the.pxddeclares<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
- 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