omlish-cext 0.0.0.dev517__tar.gz → 0.0.0.dev519__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/PKG-INFO +2 -2
  2. omlish_cext-0.0.0.dev519/omlish/dispatch/_dispatch.cc +349 -0
  3. omlish_cext-0.0.0.dev519/omlish/dispatch/_methods.cc +404 -0
  4. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish_cext.egg-info/PKG-INFO +2 -2
  5. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish_cext.egg-info/SOURCES.txt +2 -0
  6. omlish_cext-0.0.0.dev519/omlish_cext.egg-info/requires.txt +1 -0
  7. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/pyproject.toml +2 -2
  8. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/setup.py +10 -0
  9. omlish_cext-0.0.0.dev517/omlish_cext.egg-info/requires.txt +0 -1
  10. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/LICENSE +0 -0
  11. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/README.md +0 -0
  12. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish/_check.cc +0 -0
  13. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish/collections/hamt/_hamt.c +0 -0
  14. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish/lang/_asyncs.cc +0 -0
  15. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish/lang/imports/_capture.cc +0 -0
  16. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish/typedvalues/_collection.cc +0 -0
  17. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish_cext.egg-info/dependency_links.txt +0 -0
  18. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/omlish_cext.egg-info/top_level.txt +0 -0
  19. {omlish_cext-0.0.0.dev517 → omlish_cext-0.0.0.dev519}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish-cext
3
- Version: 0.0.0.dev517
3
+ Version: 0.0.0.dev519
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omlish==0.0.0.dev517
17
+ Requires-Dist: omlish==0.0.0.dev519
18
18
  Dynamic: license-file
19
19
 
20
20
  # Overview
@@ -0,0 +1,349 @@
1
+ // @omlish-cext
2
+ #define PY_SSIZE_T_CLEAN
3
+ #include "Python.h"
4
+
5
+ //
6
+
7
+ #define _MODULE_NAME "_dispatch"
8
+ #define _PACKAGE_NAME "omlish.dispatch"
9
+ #define _MODULE_FULL_NAME _PACKAGE_NAME "." _MODULE_NAME
10
+
11
+ typedef struct {
12
+ PyTypeObject *StrongCacheType;
13
+ PyObject *abc_get_cache_token;
14
+ } dispatch_state;
15
+
16
+ static inline dispatch_state * get_dispatch_state(PyObject *module)
17
+ {
18
+ void *state = PyModule_GetState(module);
19
+ assert(state != NULL);
20
+ return (dispatch_state *)state;
21
+ }
22
+
23
+ //
24
+
25
+ typedef struct {
26
+ PyObject_HEAD
27
+ PyObject *token;
28
+ PyObject *dct;
29
+ PyObject *impls_by_arg_cls;
30
+ PyObject *find_impl;
31
+ PyObject *reset_cache_for_token;
32
+ PyObject *abc_get_cache_token;
33
+ } StrongCache;
34
+
35
+ static int StrongCache_traverse(StrongCache *self, visitproc visit, void *arg)
36
+ {
37
+ Py_VISIT(self->token);
38
+ Py_VISIT(self->dct);
39
+ Py_VISIT(self->impls_by_arg_cls);
40
+ Py_VISIT(self->find_impl);
41
+ Py_VISIT(self->reset_cache_for_token);
42
+ Py_VISIT(self->abc_get_cache_token);
43
+ return 0;
44
+ }
45
+
46
+ static int StrongCache_clear(StrongCache *self)
47
+ {
48
+ Py_CLEAR(self->token);
49
+ Py_CLEAR(self->dct);
50
+ Py_CLEAR(self->impls_by_arg_cls);
51
+ Py_CLEAR(self->find_impl);
52
+ Py_CLEAR(self->reset_cache_for_token);
53
+ Py_CLEAR(self->abc_get_cache_token);
54
+ return 0;
55
+ }
56
+
57
+ static void StrongCache_dealloc(StrongCache *self)
58
+ {
59
+ PyObject_GC_UnTrack(self);
60
+ StrongCache_clear(self);
61
+ Py_TYPE(self)->tp_free((PyObject *)self);
62
+ }
63
+
64
+ static PyObject * StrongCache_dispatch(StrongCache *self, PyObject *cls)
65
+ {
66
+ // if token is not None and abc.get_cache_token() != token:
67
+ // reset_cache_for_token(self)
68
+ // return find_impl(cls, impls_by_arg_cls)
69
+ if (self->token != Py_None) {
70
+ PyObject *current_token = PyObject_CallNoArgs(self->abc_get_cache_token);
71
+ if (current_token == nullptr) {
72
+ return nullptr;
73
+ }
74
+
75
+ int token_changed = PyObject_RichCompareBool(current_token, self->token, Py_NE);
76
+ Py_DECREF(current_token);
77
+
78
+ if (token_changed < 0) {
79
+ return nullptr;
80
+ }
81
+
82
+ if (token_changed) {
83
+ // Call reset_cache_for_token(self)
84
+ PyObject *reset_result = PyObject_CallOneArg(self->reset_cache_for_token, (PyObject *)self);
85
+ if (reset_result == nullptr) {
86
+ return nullptr;
87
+ }
88
+ Py_DECREF(reset_result);
89
+
90
+ // Call find_impl(cls, impls_by_arg_cls)
91
+ PyObject *impl = PyObject_CallFunctionObjArgs(
92
+ self->find_impl,
93
+ cls,
94
+ self->impls_by_arg_cls,
95
+ nullptr
96
+ );
97
+ return impl;
98
+ }
99
+ }
100
+
101
+ // try:
102
+ // return dct[cls]
103
+ // except KeyError:
104
+ // pass
105
+ PyObject *cached = PyDict_GetItemWithError(self->dct, cls);
106
+ if (cached != nullptr) {
107
+ Py_INCREF(cached);
108
+ return cached;
109
+ }
110
+ if (PyErr_Occurred()) {
111
+ return nullptr;
112
+ }
113
+
114
+ // try:
115
+ // impl = impls_by_arg_cls[cls]
116
+ // except KeyError:
117
+ // impl = find_impl(cls, impls_by_arg_cls)
118
+ PyObject *impl = PyDict_GetItemWithError(self->impls_by_arg_cls, cls);
119
+ if (impl != nullptr) {
120
+ Py_INCREF(impl);
121
+ } else if (PyErr_Occurred()) {
122
+ return nullptr;
123
+ } else {
124
+ // Call find_impl(cls, impls_by_arg_cls)
125
+ impl = PyObject_CallFunctionObjArgs(
126
+ self->find_impl,
127
+ cls,
128
+ self->impls_by_arg_cls,
129
+ nullptr
130
+ );
131
+ if (impl == nullptr) {
132
+ return nullptr;
133
+ }
134
+ }
135
+
136
+ // dct[cls] = impl
137
+ if (PyDict_SetItem(self->dct, cls, impl) < 0) {
138
+ Py_DECREF(impl);
139
+ return nullptr;
140
+ }
141
+
142
+ Py_DECREF(impl);
143
+ return impl;
144
+ }
145
+
146
+ static PyObject * StrongCache_get_token(StrongCache *self, void *closure)
147
+ {
148
+ Py_INCREF(self->token);
149
+ return self->token;
150
+ }
151
+
152
+ static PyObject * StrongCache_get_dct(StrongCache *self, void *closure)
153
+ {
154
+ Py_INCREF(self->dct);
155
+ return self->dct;
156
+ }
157
+
158
+ static PyMethodDef StrongCache_methods[] = {
159
+ {"dispatch", (PyCFunction)StrongCache_dispatch, METH_O, "Dispatch to implementation for given class"},
160
+ {nullptr}
161
+ };
162
+
163
+ static PyGetSetDef StrongCache_getsets[] = {
164
+ {"token", (getter)StrongCache_get_token, nullptr, nullptr, nullptr},
165
+ {"dct", (getter)StrongCache_get_dct, nullptr, nullptr, nullptr},
166
+ {nullptr}
167
+ };
168
+
169
+ static PyType_Slot StrongCache_slots[] = {
170
+ {Py_tp_dealloc, (void *)StrongCache_dealloc},
171
+ {Py_tp_traverse, (void *)StrongCache_traverse},
172
+ {Py_tp_clear, (void *)StrongCache_clear},
173
+ {Py_tp_methods, StrongCache_methods},
174
+ {Py_tp_getset, StrongCache_getsets},
175
+ {Py_tp_doc, (void *)"Fast strong cache for dispatcher"},
176
+ {0, nullptr}
177
+ };
178
+
179
+ static PyType_Spec StrongCache_spec = {
180
+ .name = _MODULE_FULL_NAME ".StrongCache",
181
+ .basicsize = sizeof(StrongCache),
182
+ .itemsize = 0,
183
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
184
+ .slots = StrongCache_slots,
185
+ };
186
+
187
+ //
188
+
189
+ PyDoc_STRVAR(build_strong_dispatch_cache_doc,
190
+ "build_strong_dispatch_cache(impls_by_arg_cls, find_impl, reset_cache_for_token, token)\n\
191
+ \n\
192
+ Create a fast strong cache for dispatcher.\n\
193
+ \n\
194
+ Args:\n\
195
+ impls_by_arg_cls: Dictionary mapping types to implementations\n\
196
+ find_impl: Function to find implementation for a type\n\
197
+ reset_cache_for_token: Function to reset cache when token changes\n\
198
+ token: Current ABC cache token or None\n\
199
+ \n\
200
+ Returns:\n\
201
+ A StrongCache instance");
202
+
203
+ static PyObject * build_strong_dispatch_cache(PyObject *module, PyObject *args)
204
+ {
205
+ PyObject *impls_by_arg_cls;
206
+ PyObject *find_impl;
207
+ PyObject *reset_cache_for_token;
208
+ PyObject *token;
209
+
210
+ if (!PyArg_ParseTuple(args, "OOOO", &impls_by_arg_cls, &find_impl, &reset_cache_for_token, &token)) {
211
+ return nullptr;
212
+ }
213
+
214
+ if (!PyDict_Check(impls_by_arg_cls)) {
215
+ PyErr_SetString(PyExc_TypeError, "impls_by_arg_cls must be a dictionary");
216
+ return nullptr;
217
+ }
218
+
219
+ if (!PyCallable_Check(find_impl)) {
220
+ PyErr_SetString(PyExc_TypeError, "find_impl must be callable");
221
+ return nullptr;
222
+ }
223
+
224
+ if (!PyCallable_Check(reset_cache_for_token)) {
225
+ PyErr_SetString(PyExc_TypeError, "reset_cache_for_token must be callable");
226
+ return nullptr;
227
+ }
228
+
229
+ dispatch_state *state = get_dispatch_state(module);
230
+ StrongCache *self = PyObject_GC_New(StrongCache, state->StrongCacheType);
231
+ if (self == nullptr) {
232
+ return nullptr;
233
+ }
234
+
235
+ self->token = nullptr;
236
+ self->dct = nullptr;
237
+ self->impls_by_arg_cls = nullptr;
238
+ self->find_impl = nullptr;
239
+ self->reset_cache_for_token = nullptr;
240
+ self->abc_get_cache_token = nullptr;
241
+
242
+ self->token = Py_NewRef(token);
243
+ self->dct = PyDict_New();
244
+ if (self->dct == nullptr) {
245
+ Py_DECREF(self);
246
+ return nullptr;
247
+ }
248
+ self->impls_by_arg_cls = Py_NewRef(impls_by_arg_cls);
249
+ self->find_impl = Py_NewRef(find_impl);
250
+ self->reset_cache_for_token = Py_NewRef(reset_cache_for_token);
251
+ self->abc_get_cache_token = Py_NewRef(state->abc_get_cache_token);
252
+
253
+ PyObject_GC_Track(self);
254
+ return (PyObject *)self;
255
+ }
256
+
257
+ //
258
+
259
+ PyDoc_STRVAR(dispatch_doc, "Native C++ implementations for omlish.dispatch");
260
+
261
+ static int dispatch_exec(PyObject *module)
262
+ {
263
+ dispatch_state *state = get_dispatch_state(module);
264
+
265
+ // Import abc.get_cache_token
266
+ PyObject *abc_module = PyImport_ImportModule("abc");
267
+ if (abc_module == nullptr) {
268
+ return -1;
269
+ }
270
+
271
+ state->abc_get_cache_token = PyObject_GetAttrString(abc_module, "get_cache_token");
272
+ Py_DECREF(abc_module);
273
+ if (state->abc_get_cache_token == nullptr) {
274
+ return -1;
275
+ }
276
+
277
+ // Create the type dynamically
278
+ state->StrongCacheType = (PyTypeObject *)PyType_FromModuleAndSpec(
279
+ module,
280
+ &StrongCache_spec,
281
+ nullptr
282
+ );
283
+ if (state->StrongCacheType == nullptr) {
284
+ return -1;
285
+ }
286
+ Py_INCREF(state->StrongCacheType);
287
+
288
+ // Add the type to the module
289
+ if (PyModule_AddType(module, state->StrongCacheType) < 0) {
290
+ Py_CLEAR(state->StrongCacheType);
291
+ return -1;
292
+ }
293
+
294
+ return 0;
295
+ }
296
+
297
+ static int dispatch_traverse(PyObject *module, visitproc visit, void *arg)
298
+ {
299
+ dispatch_state *state = get_dispatch_state(module);
300
+ Py_VISIT(state->StrongCacheType);
301
+ Py_VISIT(state->abc_get_cache_token);
302
+ return 0;
303
+ }
304
+
305
+ static int dispatch_clear(PyObject *module)
306
+ {
307
+ dispatch_state *state = get_dispatch_state(module);
308
+ Py_CLEAR(state->StrongCacheType);
309
+ Py_CLEAR(state->abc_get_cache_token);
310
+ return 0;
311
+ }
312
+
313
+ static void dispatch_free(void *module)
314
+ {
315
+ dispatch_clear((PyObject *)module);
316
+ }
317
+
318
+ static PyMethodDef dispatch_methods[] = {
319
+ {"build_strong_dispatch_cache", (PyCFunction)build_strong_dispatch_cache, METH_VARARGS, build_strong_dispatch_cache_doc},
320
+ {nullptr, nullptr, 0, nullptr}
321
+ };
322
+
323
+ static struct PyModuleDef_Slot dispatch_slots[] = {
324
+ {Py_mod_exec, (void *) dispatch_exec},
325
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
326
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
327
+ {0, nullptr}
328
+ };
329
+
330
+ static struct PyModuleDef dispatch_module = {
331
+ .m_base = PyModuleDef_HEAD_INIT,
332
+ .m_name = _MODULE_NAME,
333
+ .m_doc = dispatch_doc,
334
+ .m_size = sizeof(dispatch_state),
335
+ .m_methods = dispatch_methods,
336
+ .m_slots = dispatch_slots,
337
+ .m_traverse = dispatch_traverse,
338
+ .m_clear = dispatch_clear,
339
+ .m_free = dispatch_free,
340
+ };
341
+
342
+ extern "C" {
343
+
344
+ PyMODINIT_FUNC PyInit__dispatch(void)
345
+ {
346
+ return PyModuleDef_Init(&dispatch_module);
347
+ }
348
+
349
+ }
@@ -0,0 +1,404 @@
1
+ // @omlish-cext
2
+ #define PY_SSIZE_T_CLEAN
3
+ #include "Python.h"
4
+ #include "structmember.h"
5
+
6
+ //
7
+
8
+ #define _MODULE_NAME "_methods"
9
+ #define _PACKAGE_NAME "omlish.dispatch"
10
+ #define _MODULE_FULL_NAME _PACKAGE_NAME "." _MODULE_NAME
11
+
12
+ typedef struct {
13
+ PyTypeObject *MethodDispatchFuncType;
14
+ } methods_state;
15
+
16
+ static inline methods_state * get_methods_state(PyObject *module)
17
+ {
18
+ void *state = PyModule_GetState(module);
19
+ assert(state != NULL);
20
+ return (methods_state *)state;
21
+ }
22
+
23
+ //
24
+
25
+ typedef struct {
26
+ PyObject_HEAD
27
+ PyObject *dispatch_func;
28
+ PyObject *base_func;
29
+ PyObject *func_name;
30
+ PyObject *dict;
31
+ } MethodDispatchFunc;
32
+
33
+ static int MethodDispatchFunc_traverse(MethodDispatchFunc *self, visitproc visit, void *arg)
34
+ {
35
+ Py_VISIT(self->dispatch_func);
36
+ Py_VISIT(self->base_func);
37
+ Py_VISIT(self->func_name);
38
+ Py_VISIT(self->dict);
39
+ return 0;
40
+ }
41
+
42
+ static int MethodDispatchFunc_clear(MethodDispatchFunc *self)
43
+ {
44
+ Py_CLEAR(self->dispatch_func);
45
+ Py_CLEAR(self->base_func);
46
+ Py_CLEAR(self->func_name);
47
+ Py_CLEAR(self->dict);
48
+ return 0;
49
+ }
50
+
51
+ static void MethodDispatchFunc_dealloc(MethodDispatchFunc *self)
52
+ {
53
+ PyObject_GC_UnTrack(self);
54
+ MethodDispatchFunc_clear(self);
55
+ Py_TYPE(self)->tp_free((PyObject *)self);
56
+ }
57
+
58
+ static PyObject * MethodDispatchFunc_call(MethodDispatchFunc *self, PyObject *args, PyObject *kwargs)
59
+ {
60
+ // This is the unbound call - args[0] should be the instance (self)
61
+ // args[1:] are the actual method arguments
62
+
63
+ Py_ssize_t nargs = PyTuple_GET_SIZE(args);
64
+ if (nargs < 2) {
65
+ PyErr_Format(
66
+ PyExc_TypeError,
67
+ "%U requires at least 1 positional argument",
68
+ self->func_name
69
+ );
70
+ return nullptr;
71
+ }
72
+
73
+ // Get the instance (first positional arg - the 'self')
74
+ PyObject *instance = PyTuple_GET_ITEM(args, 0); // borrowed reference
75
+
76
+ // Get type of first method arg directly: Py_TYPE(args[1])
77
+ PyObject *first_method_arg = PyTuple_GET_ITEM(args, 1); // borrowed reference
78
+ PyTypeObject *first_arg_type = Py_TYPE(first_method_arg);
79
+
80
+ // Call dispatch(type_(method_args[0]))
81
+ PyObject *impl_att = PyObject_CallOneArg(self->dispatch_func, (PyObject *)first_arg_type);
82
+ if (impl_att == nullptr) {
83
+ return nullptr;
84
+ }
85
+
86
+ PyObject *result = nullptr;
87
+
88
+ // Check if impl_att is not None
89
+ if (impl_att != Py_None) {
90
+ // Use PyObject_GetAttr directly instead of calling getattr builtin
91
+ PyObject *impl_method = PyObject_GetAttr(instance, impl_att);
92
+ Py_DECREF(impl_att);
93
+
94
+ if (impl_method == nullptr) {
95
+ return nullptr;
96
+ }
97
+
98
+ // Optimize for common case of single argument with no kwargs
99
+ Py_ssize_t method_nargs = nargs - 1;
100
+ if (method_nargs == 1 && (kwargs == nullptr || PyDict_GET_SIZE(kwargs) == 0)) {
101
+ // Fast path for single argument, no kwargs
102
+ result = PyObject_CallOneArg(impl_method, PyTuple_GET_ITEM(args, 1));
103
+ Py_DECREF(impl_method);
104
+ } else {
105
+ // Build method_args tuple (args[1:])
106
+ PyObject *method_args = PyTuple_New(method_nargs);
107
+ if (method_args == nullptr) {
108
+ Py_DECREF(impl_method);
109
+ return nullptr;
110
+ }
111
+
112
+ for (Py_ssize_t i = 0; i < method_nargs; i++) {
113
+ PyObject *item = PyTuple_GET_ITEM(args, i + 1);
114
+ Py_INCREF(item);
115
+ PyTuple_SET_ITEM(method_args, i, item);
116
+ }
117
+
118
+ // Call impl_method(*method_args, **kwargs)
119
+ result = PyObject_Call(impl_method, method_args, kwargs);
120
+ Py_DECREF(impl_method);
121
+ Py_DECREF(method_args);
122
+ }
123
+
124
+ } else {
125
+ Py_DECREF(impl_att);
126
+
127
+ // base_func.__get__(instance)
128
+ PyObject *get = PyObject_GetAttrString(self->base_func, "__get__"); // new ref or NULL
129
+ if (get == nullptr) {
130
+ return nullptr;
131
+ }
132
+
133
+ PyObject *owner = (PyObject *)Py_TYPE(instance); // borrowed
134
+ PyObject *bound_func = PyObject_CallFunctionObjArgs(get, instance, owner, NULL);
135
+ Py_DECREF(get);
136
+
137
+ if (bound_func == nullptr) {
138
+ return nullptr;
139
+ }
140
+
141
+ // Optimize for common case of single argument with no kwargs
142
+ Py_ssize_t method_nargs = nargs - 1;
143
+ if (method_nargs == 1 && (kwargs == nullptr || PyDict_GET_SIZE(kwargs) == 0)) {
144
+ // Fast path for single argument, no kwargs
145
+ result = PyObject_CallOneArg(bound_func, PyTuple_GET_ITEM(args, 1));
146
+ Py_DECREF(bound_func);
147
+ } else {
148
+ // Build method_args tuple (args[1:])
149
+ PyObject *method_args = PyTuple_New(method_nargs);
150
+ if (method_args == nullptr) {
151
+ Py_DECREF(bound_func);
152
+ return nullptr;
153
+ }
154
+
155
+ for (Py_ssize_t i = 0; i < method_nargs; i++) {
156
+ PyObject *item = PyTuple_GET_ITEM(args, i + 1);
157
+ Py_INCREF(item);
158
+ PyTuple_SET_ITEM(method_args, i, item);
159
+ }
160
+
161
+ // Call bound_func(*method_args, **kwargs)
162
+ result = PyObject_Call(bound_func, method_args, kwargs);
163
+ Py_DECREF(bound_func);
164
+ Py_DECREF(method_args);
165
+ }
166
+ }
167
+
168
+ return result;
169
+ }
170
+
171
+ static PyObject * MethodDispatchFunc_get(MethodDispatchFunc *self, PyObject *instance, PyObject *owner)
172
+ {
173
+ // Implement descriptor protocol for binding
174
+ if (instance == Py_None || instance == nullptr) {
175
+ // Unbound access - return self
176
+ Py_INCREF(self);
177
+ return (PyObject *)self;
178
+ }
179
+
180
+ // Bound access - return a bound method
181
+ return PyMethod_New((PyObject *)self, instance);
182
+ }
183
+
184
+ static PyObject * MethodDispatchFunc_get_dict(MethodDispatchFunc *self, void *closure)
185
+ {
186
+ if (self->dict == nullptr) {
187
+ self->dict = PyDict_New();
188
+ if (self->dict == nullptr) {
189
+ return nullptr;
190
+ }
191
+ }
192
+ Py_INCREF(self->dict);
193
+ return self->dict;
194
+ }
195
+
196
+ static int MethodDispatchFunc_set_dict(MethodDispatchFunc *self, PyObject *value, void *closure)
197
+ {
198
+ if (value == nullptr) {
199
+ PyErr_SetString(PyExc_TypeError, "Cannot delete __dict__");
200
+ return -1;
201
+ }
202
+ if (!PyDict_Check(value)) {
203
+ PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary");
204
+ return -1;
205
+ }
206
+ Py_XSETREF(self->dict, Py_NewRef(value));
207
+ return 0;
208
+ }
209
+
210
+ static PyObject * MethodDispatchFunc_getattro(MethodDispatchFunc *self, PyObject *name)
211
+ {
212
+ // First check instance dict
213
+ if (self->dict != nullptr) {
214
+ PyObject *res = PyDict_GetItemWithError(self->dict, name);
215
+ if (res != nullptr) {
216
+ Py_INCREF(res);
217
+ return res;
218
+ }
219
+ if (PyErr_Occurred()) {
220
+ return nullptr;
221
+ }
222
+ }
223
+
224
+ // Fall back to type's getattro
225
+ return PyObject_GenericGetAttr((PyObject *)self, name);
226
+ }
227
+
228
+ static int MethodDispatchFunc_setattro(MethodDispatchFunc *self, PyObject *name, PyObject *value)
229
+ {
230
+ // Ensure dict exists
231
+ if (self->dict == nullptr) {
232
+ self->dict = PyDict_New();
233
+ if (self->dict == nullptr) {
234
+ return -1;
235
+ }
236
+ }
237
+
238
+ // Set in instance dict
239
+ if (value == nullptr) {
240
+ return PyDict_DelItem(self->dict, name);
241
+ } else {
242
+ return PyDict_SetItem(self->dict, name, value);
243
+ }
244
+ }
245
+
246
+ static PyGetSetDef MethodDispatchFunc_getsets[] = {
247
+ {"__dict__", (getter)MethodDispatchFunc_get_dict, (setter)MethodDispatchFunc_set_dict, nullptr, nullptr},
248
+ {nullptr}
249
+ };
250
+
251
+ static PyType_Slot MethodDispatchFunc_slots[] = {
252
+ {Py_tp_dealloc, (void *)MethodDispatchFunc_dealloc},
253
+ {Py_tp_call, (void *)MethodDispatchFunc_call},
254
+ {Py_tp_descr_get, (void *)MethodDispatchFunc_get},
255
+ {Py_tp_traverse, (void *)MethodDispatchFunc_traverse},
256
+ {Py_tp_clear, (void *)MethodDispatchFunc_clear},
257
+ {Py_tp_getattro, (void *)MethodDispatchFunc_getattro},
258
+ {Py_tp_setattro, (void *)MethodDispatchFunc_setattro},
259
+ {Py_tp_getset, MethodDispatchFunc_getsets},
260
+ {Py_tp_doc, (void *)"Fast dispatch function for Method instances"},
261
+ {0, nullptr}
262
+ };
263
+
264
+ static PyType_Spec MethodDispatchFunc_spec = {
265
+ .name = _MODULE_FULL_NAME ".MethodDispatchFunc",
266
+ .basicsize = sizeof(MethodDispatchFunc),
267
+ .itemsize = 0,
268
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
269
+ .slots = MethodDispatchFunc_slots,
270
+ };
271
+
272
+ //
273
+
274
+ PyDoc_STRVAR(build_method_dispatch_func_doc,
275
+ "build_method_dispatch_func(dispatch_func, base_func, func_name)\n\
276
+ \n\
277
+ Create a fast dispatch function for Method instances.\n\
278
+ \n\
279
+ Args:\n\
280
+ dispatch_func: The dispatcher's dispatch callable\n\
281
+ base_func: The base function to call if no implementation is found\n\
282
+ func_name: The name of the function (for error messages)\n\
283
+ \n\
284
+ Returns:\n\
285
+ A callable that performs method dispatch");
286
+
287
+ static PyObject * build_method_dispatch_func(PyObject *module, PyObject *args)
288
+ {
289
+ PyObject *dispatch_func;
290
+ PyObject *base_func;
291
+ PyObject *func_name;
292
+
293
+ if (!PyArg_ParseTuple(args, "OOO", &dispatch_func, &base_func, &func_name)) {
294
+ return nullptr;
295
+ }
296
+
297
+ if (!PyCallable_Check(dispatch_func)) {
298
+ PyErr_SetString(PyExc_TypeError, "dispatch_func must be callable");
299
+ return nullptr;
300
+ }
301
+
302
+ if (!PyCallable_Check(base_func) && !PyObject_HasAttrString(base_func, "__get__")) {
303
+ PyErr_SetString(PyExc_TypeError, "base_func must be callable or a descriptor");
304
+ return nullptr;
305
+ }
306
+
307
+ if (!PyUnicode_Check(func_name)) {
308
+ PyErr_SetString(PyExc_TypeError, "func_name must be a string");
309
+ return nullptr;
310
+ }
311
+
312
+ methods_state *state = get_methods_state(module);
313
+ MethodDispatchFunc *self = PyObject_GC_New(MethodDispatchFunc, state->MethodDispatchFuncType);
314
+ if (self == nullptr) {
315
+ return nullptr;
316
+ }
317
+
318
+ self->dispatch_func = Py_NewRef(dispatch_func);
319
+ self->base_func = Py_NewRef(base_func);
320
+ self->func_name = Py_NewRef(func_name);
321
+ self->dict = nullptr;
322
+
323
+ PyObject_GC_Track(self);
324
+ return (PyObject *)self;
325
+ }
326
+
327
+ //
328
+
329
+ PyDoc_STRVAR(methods_doc, "Native C++ implementations for omlish.dispatch.methods");
330
+
331
+ static int methods_exec(PyObject *module)
332
+ {
333
+ methods_state *state = get_methods_state(module);
334
+
335
+ // Create the type dynamically
336
+ state->MethodDispatchFuncType = (PyTypeObject *)PyType_FromModuleAndSpec(
337
+ module,
338
+ &MethodDispatchFunc_spec,
339
+ nullptr
340
+ );
341
+ if (state->MethodDispatchFuncType == nullptr) {
342
+ return -1;
343
+ }
344
+
345
+ // Add the type to the module
346
+ if (PyModule_AddType(module, state->MethodDispatchFuncType) < 0) {
347
+ Py_CLEAR(state->MethodDispatchFuncType);
348
+ return -1;
349
+ }
350
+
351
+ return 0;
352
+ }
353
+
354
+ static int methods_traverse(PyObject *module, visitproc visit, void *arg)
355
+ {
356
+ methods_state *state = get_methods_state(module);
357
+ Py_VISIT(state->MethodDispatchFuncType);
358
+ return 0;
359
+ }
360
+
361
+ static int methods_clear(PyObject *module)
362
+ {
363
+ methods_state *state = get_methods_state(module);
364
+ Py_CLEAR(state->MethodDispatchFuncType);
365
+ return 0;
366
+ }
367
+
368
+ static void methods_free(void *module)
369
+ {
370
+ methods_clear((PyObject *)module);
371
+ }
372
+
373
+ static PyMethodDef methods_methods[] = {
374
+ {"build_method_dispatch_func", (PyCFunction)build_method_dispatch_func, METH_VARARGS, build_method_dispatch_func_doc},
375
+ {NULL, NULL, 0, NULL}
376
+ };
377
+
378
+ static struct PyModuleDef_Slot methods_slots[] = {
379
+ {Py_mod_exec, (void *) methods_exec},
380
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
381
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
382
+ {0, NULL}
383
+ };
384
+
385
+ static struct PyModuleDef methods_module = {
386
+ .m_base = PyModuleDef_HEAD_INIT,
387
+ .m_name = _MODULE_NAME,
388
+ .m_doc = methods_doc,
389
+ .m_size = sizeof(methods_state),
390
+ .m_methods = methods_methods,
391
+ .m_slots = methods_slots,
392
+ .m_traverse = methods_traverse,
393
+ .m_clear = methods_clear,
394
+ .m_free = methods_free,
395
+ };
396
+
397
+ extern "C" {
398
+
399
+ PyMODINIT_FUNC PyInit__methods(void)
400
+ {
401
+ return PyModuleDef_Init(&methods_module);
402
+ }
403
+
404
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish-cext
3
- Version: 0.0.0.dev517
3
+ Version: 0.0.0.dev519
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omlish==0.0.0.dev517
17
+ Requires-Dist: omlish==0.0.0.dev519
18
18
  Dynamic: license-file
19
19
 
20
20
  # Overview
@@ -4,6 +4,8 @@ pyproject.toml
4
4
  setup.py
5
5
  omlish/_check.cc
6
6
  omlish/collections/hamt/_hamt.c
7
+ omlish/dispatch/_dispatch.cc
8
+ omlish/dispatch/_methods.cc
7
9
  omlish/lang/_asyncs.cc
8
10
  omlish/lang/imports/_capture.cc
9
11
  omlish/typedvalues/_collection.cc
@@ -0,0 +1 @@
1
+ omlish==0.0.0.dev519
@@ -13,7 +13,7 @@ urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = 'BSD-3-Clause'
14
14
  readme = 'README.md'
15
15
  requires-python = '>=3.13'
16
- version = '0.0.0.dev517'
16
+ version = '0.0.0.dev519'
17
17
  classifiers = [
18
18
  'Development Status :: 2 - Pre-Alpha',
19
19
  'Intended Audience :: Developers',
@@ -24,7 +24,7 @@ classifiers = [
24
24
  ]
25
25
  description = 'omlish'
26
26
  dependencies = [
27
- 'omlish == 0.0.0.dev517',
27
+ 'omlish == 0.0.0.dev519',
28
28
  ]
29
29
 
30
30
  [tool.setuptools]
@@ -13,6 +13,16 @@ st.setup(
13
13
  sources=['omlish/collections/hamt/_hamt.c'],
14
14
  extra_compile_args=['-std=c11'],
15
15
  ),
16
+ st.Extension(
17
+ name='omlish.dispatch._dispatch',
18
+ sources=['omlish/dispatch/_dispatch.cc'],
19
+ extra_compile_args=['-std=c++20'],
20
+ ),
21
+ st.Extension(
22
+ name='omlish.dispatch._methods',
23
+ sources=['omlish/dispatch/_methods.cc'],
24
+ extra_compile_args=['-std=c++20'],
25
+ ),
16
26
  st.Extension(
17
27
  name='omlish.lang._asyncs',
18
28
  sources=['omlish/lang/_asyncs.cc'],
@@ -1 +0,0 @@
1
- omlish==0.0.0.dev517