_golang.pxd 8.95 KB
Newer Older
1
# cython: language_level=2
2
# Copyright (C) 2019-2023  Nexedi SA and Contributors.
3
#                          Kirill Smelkov <kirr@nexedi.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Package golang.pyx provides Go-like features for Cython/nogil and runtime for golang.py.

Cython/nogil API
----------------

Kirill Smelkov's avatar
Kirill Smelkov committed
25
- `go` spawns lightweight thread.
26
- `chan[T]`, `makechan[T]` and `select` provide C-level channels with Go semantic.
27
- `error` is the interface that represents errors.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
- `panic` stops normal execution of current goroutine by throwing a C-level exception.

Everything in Cython/nogil API do not depend on Python runtime and in
particular can be used in nogil code.

See README for thorough overview.
See libgolang.h for API details.
See also package golang.py which provides similar functionality for Python.


Golang.py runtime
-----------------

In addition to Cython/nogil API, golang.pyx provides runtime for golang.py:

43
- Python-level channels are represented by pychan + pyselect.
44
- Python-level error is represented by pyerror.
45
- Python-level panic is represented by pypanic.
46
- Python-level strings are represented by pybstr and pyustr.
47 48 49
"""


50
from libcpp cimport nullptr_t as Nil, nullptr as nil # golang::nil = nullptr
51
from libcpp.utility cimport pair
52
from libc.stdint cimport uint64_t
53 54 55
cdef extern from *:
    ctypedef bint cbool "bool"

56 57 58 59 60 61 62 63 64 65
# nogil pyx-level golang API.
#
# NOTE even though many functions may panic (= throw C++ exception) nothing is
# annotated with `except +`. Reason: `f() except +` tells Cython to wrap every
# call to f with try/catch and convert C++ exception into Python one. And once
# you have a Python-level exception you are in Python world. However we want
# nogil golang.pyx API to be usable without Python at all.
#
# -> golang.pyx users need to add `except +topyexc` to their functions that are
# on the edge of Python/nogil world.
66
from libcpp.string cimport string  # golang::string = std::string
67
cdef extern from "golang/libgolang.h" namespace "golang" nogil:
68 69 70
    ctypedef unsigned char  byte
    ctypedef signed int     rune  # = int32

71 72 73
    void panic(const char *)
    const char *recover()

Kirill Smelkov's avatar
Kirill Smelkov committed
74 75
    void go(...)    # typechecking is done by C

76 77 78 79 80
    struct _chan
    cppclass chan[T]:
        chan();

        # send/recv/close
81 82 83 84
        void send(const T&)                 const
        T recv()                            const
        pair[T, cbool] recv_()              const
        void close()                        const
85 86

        # send/recv in select
87 88 89 90
        _selcase sends(const T *ptx)        const
        _selcase recvs()                    const
        _selcase recvs(T* prx)              const
        _selcase recvs(T* prx, cbool *pok)  const
91 92

        # length/capacity
93 94
        unsigned len()                      const
        unsigned cap()                      const
95 96

        # compare wrt nil; =nil
97 98 99
        cbool operator==(Nil)               const
        cbool operator!=(Nil)               const
        void operator=(Nil)
100 101

        # for tests
102
        _chan *_rawchan()   const
103 104 105 106 107 108 109 110 111 112 113

    chan[T] makechan[T]()
    chan[T] makechan[T](unsigned size)

    struct structZ:
        pass

    enum _chanop:
        _CHANSEND
        _CHANRECV
        _DEFAULT
114 115
    enum _selflags:
        _INPLACE_DATA
116
    cppclass _selcase:
117 118 119
        _chan     *ch
        _chanop   op
        unsigned  flags
120
        unsigned  user
121 122 123
        void      *ptxrx
        uint64_t  itxrx
        cbool     *rxok
124 125 126 127

        const void *ptx() const
        void *prx() const

128 129 130 131
    const _selcase default "golang::_default"

    int select(_selcase casev[])

132

133 134 135
    # memory management of C++ nogil classes
    cppclass refptr[T]:
        # compare wrt nil; =nil
136 137 138
        cbool operator== (Nil)          const
        cbool operator!= (Nil)          const
        void  operator=  (Nil)          const
139

140 141 142 143 144 145 146 147 148
        # compare wrt refptr; =refptr
        # XXX workaround for https://github.com/cython/cython/issues/1357:
        #     compare by .eq() instead of ==
        #cbool operator== (refptr)       const
        #cbool operator!= (refptr)       const
        #cbool operator=  (refptr)       const
        cbool eq "operator==" (refptr)  const
        cbool ne "operator!=" (refptr)  const

149 150 151
        # get raw pointer
        T* _ptr()                       const

152 153 154 155 156 157 158 159
    refptr[T] adoptref[T](T *_obj)
    refptr[T] newref  [T](T *_obj)


    cppclass gobject "golang::object":
        cbool __decref()    # protected
        void  incref()
        int   refcnt() const
160

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

    # empty interface ~ interface{}
    cppclass _interface:
        void incref()
        void decref()

    cppclass interface (refptr[_interface]):
        # interface.X = interface->X in C++
        void incref "_ptr()->incref" ()
        void decref "_ptr()->decref" ()


    # error interface
    cppclass _error (_interface):
        string Error()

    cppclass error (refptr[_error]):
        # error.X = error->X in C++
        string Error    "_ptr()->Error" ()


182 183 184 185 186 187 188 189 190
    # error wrapper interface
    cppclass _errorWrapper (_error):
        error Unwrap()

    cppclass errorWrapper (refptr[_errorWrapper]):
        # errorWrapper.X = errorWrapper->X in C++
        error Unwrap    "_ptr()->Unwrap" ()


191 192 193 194
# ---- python bits ----

cdef void topyexc() except *
cpdef pypanic(arg)
195

196
# pychan is python wrapper over chan<object> or chan<structZ|bool|int|double|...>
197
from cython cimport final
Kirill Smelkov's avatar
Kirill Smelkov committed
198
from golang cimport os
199

200 201 202 203 204 205 206 207
# DType describes type of channel elements.
# TODO consider supporting NumPy dtypes too.
cdef enum DType:
    DTYPE_PYOBJECT   = 0    # chan[object]
    DTYPE_STRUCTZ    = 1    # chan[structZ]
    DTYPE_BOOL       = 2    # chan[bool]
    DTYPE_INT        = 3    # chan[int]
    DTYPE_DOUBLE     = 4    # chan[double]
Kirill Smelkov's avatar
Kirill Smelkov committed
208 209
    DTYPE_OS_SIGNAL  = 5    # chan[os::Signal]  TODO register dynamically
    DTYPE_NTYPES     = 6
210 211 212 213 214 215 216 217

# pychan wraps a channel into python object.
#
# Type of channel can be either channel of python objects, or channel of
# C-level objects. If channel elements are C-level objects, the channel - even
# via pychan wrapper - can be used to interact with nogil world.
#
# There can be multiple pychan(s) wrapping a particular raw channel.
218 219
@final
cdef class pychan:
220
    cdef _chan  *_ch
221 222 223 224 225 226 227 228 229 230 231 232 233 234
    cdef DType  dtype # type of channel elements

    # pychan.nil(X) creates new nil pychan with element type X.
    @staticmethod                  # XXX needs to be `cpdef nil()` but cython:
    cdef pychan _nil(object dtype) #  "static cpdef methods not yet supported"

    # chan_X returns ._ch wrapped into typesafe pyx/nogil-level chan[X].
    # chan_X panics if channel type != X.
    # X can be any C-level type, but not PyObject.
    cdef nogil:
        chan[structZ]   chan_structZ    (pychan pych)
        chan[cbool]     chan_bool       (pychan pych)
        chan[int]       chan_int        (pychan pych)
        chan[double]    chan_double     (pychan pych)
Kirill Smelkov's avatar
Kirill Smelkov committed
235 236
        # TODO move vvv out of pychan after dtypes are registered dynamically
        chan[os.Signal] _chan_osSignal  (pychan pych)
237 238 239 240 241 242 243 244 245 246 247

    # pychan.from_chan_X returns pychan wrapping pyx/nogil-level chan[X].
    # X can be any C-level type, but not PyObject.
    @staticmethod
    cdef pychan from_chan_structZ   (chan[structZ] ch)
    @staticmethod
    cdef pychan from_chan_bool      (chan[cbool] ch)
    @staticmethod
    cdef pychan from_chan_int       (chan[int] ch)
    @staticmethod
    cdef pychan from_chan_double    (chan[double] ch)
Kirill Smelkov's avatar
Kirill Smelkov committed
248 249 250
    # TODO move vvv out of pychan after dtypes are registered dynamically
    @staticmethod
    cdef pychan _from_chan_osSignal (chan[os.Signal] ch)
251 252 253 254 255 256


# pyerror wraps an error into python object.
#
# There can be multiple pyerror(s) wrapping a particular raw error object.
# Nil C-level error corresponds to None at Python-level.
257 258 259 260 261
#
# Pyerror can be also used as base class for Python-level exception types:
#
#  - objects with type being exact pyerror are treated as wrappers around C-level error.
#  - objects with other types inherited from pyerror are treated as Python-level error.
262
cdef class pyerror(Exception):
263 264
    cdef error    err   # raw error object; nil for Python-level case
    cdef readonly args  # .args for Python-level case
265 266 267 268 269

    # pyerror.from_error returns pyerror wrapping pyx/nogil-level error.
    # from_error(nil) -> returns None.
    @staticmethod
    cdef object from_error (error err) # -> pyerror | None
270 271 272


cdef __pystr(object obj)