omlish-cext 0.0.0.dev503__tar.gz → 0.0.0.dev505__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish-cext
3
- Version: 0.0.0.dev503
3
+ Version: 0.0.0.dev505
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.dev503
17
+ Requires-Dist: omlish==0.0.0.dev505
18
18
  Dynamic: license-file
19
19
 
20
20
  # Overview
@@ -0,0 +1,545 @@
1
+ // @omlish-cext
2
+ // @omlish-llm-author "claude-sonnet-4-5-20250929"
3
+ #define PY_SSIZE_T_CLEAN
4
+ #include "Python.h"
5
+ #include "structmember.h"
6
+
7
+ #include <vector>
8
+ #include <unordered_map>
9
+
10
+ //
11
+
12
+ #define _MODULE_NAME "_collection"
13
+ #define _PACKAGE_NAME "omlish.typedvalues"
14
+ #define _MODULE_FULL_NAME _PACKAGE_NAME "." _MODULE_NAME
15
+
16
+ typedef struct collection_state {
17
+ PyObject *typed_value_type;
18
+ PyObject *unique_typed_value_type;
19
+ PyObject *duplicate_error_type;
20
+ } collection_state;
21
+
22
+ static inline collection_state * get_collection_state(PyObject *module)
23
+ {
24
+ void *state = PyModule_GetState(module);
25
+ assert(state != NULL);
26
+ return (collection_state *)state;
27
+ }
28
+
29
+ //
30
+
31
+ // Helper struct to track unique typed values during processing
32
+ struct UniqueInfo {
33
+ PyObject *unique_tv_cls; // The unique class
34
+ PyObject *tv; // The typed value
35
+ std::vector<PyObject*> *unique_lst; // Pointer to the list in unique_dct
36
+ size_t idx; // Index in unique_lst when added
37
+ };
38
+
39
+ // Temporary list item - either a TypedValue or a UniqueInfo
40
+ struct TmpItem {
41
+ bool is_unique;
42
+ union {
43
+ PyObject *tv; // Non-unique typed value
44
+ UniqueInfo *unique_info; // Unique typed value info
45
+ };
46
+
47
+ TmpItem() : is_unique(false), tv(nullptr) {}
48
+ ~TmpItem() {
49
+ if (is_unique && unique_info != nullptr) {
50
+ delete unique_info;
51
+ }
52
+ }
53
+
54
+ // Disable copy, enable move
55
+ TmpItem(const TmpItem&) = delete;
56
+ TmpItem& operator=(const TmpItem&) = delete;
57
+
58
+ TmpItem(TmpItem&& other) noexcept : is_unique(other.is_unique) {
59
+ if (is_unique) {
60
+ unique_info = other.unique_info;
61
+ other.unique_info = nullptr;
62
+ } else {
63
+ tv = other.tv;
64
+ }
65
+ }
66
+
67
+ TmpItem& operator=(TmpItem&& other) noexcept {
68
+ if (this != &other) {
69
+ if (is_unique && unique_info != nullptr) {
70
+ delete unique_info;
71
+ }
72
+ is_unique = other.is_unique;
73
+ if (is_unique) {
74
+ unique_info = other.unique_info;
75
+ other.unique_info = nullptr;
76
+ } else {
77
+ tv = other.tv;
78
+ }
79
+ }
80
+ return *this;
81
+ }
82
+ };
83
+
84
+ PyDoc_STRVAR(init_typed_values_collection_doc,
85
+ "init_typed_values_collection(*tvs, override=False, check_type=None)\n"
86
+ "\n"
87
+ "Initialize a typed values collection.\n"
88
+ "\n"
89
+ "Returns a tuple of (tuple, dict, dict2) where:\n"
90
+ "- tuple: ordered sequence of typed values\n"
91
+ "- dict: mapping of type -> typed value or tuple of typed values\n"
92
+ "- dict2: extended dict including instance types for unique values");
93
+
94
+ static PyObject * init_typed_values_collection(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
95
+ {
96
+ collection_state *state = get_collection_state(module);
97
+
98
+ // Parse arguments
99
+ bool override = false;
100
+ PyObject *check_type = nullptr;
101
+
102
+ // Handle keyword arguments
103
+ Py_ssize_t nkwargs = kwnames ? PyTuple_GET_SIZE(kwnames) : 0;
104
+
105
+ for (Py_ssize_t i = 0; i < nkwargs; i++) {
106
+ PyObject *key = PyTuple_GET_ITEM(kwnames, i);
107
+ PyObject *value = args[nargs + i];
108
+ const char *key_str = PyUnicode_AsUTF8(key);
109
+
110
+ if (key_str == nullptr) {
111
+ return nullptr;
112
+ }
113
+
114
+ if (strcmp(key_str, "override") == 0) {
115
+ int override_result = PyObject_IsTrue(value);
116
+ if (override_result == -1) {
117
+ return nullptr;
118
+ }
119
+ override = override_result;
120
+ } else if (strcmp(key_str, "check_type") == 0) {
121
+ check_type = value;
122
+ } else {
123
+ PyErr_Format(PyExc_TypeError, "unexpected keyword argument: %s", key_str);
124
+ return nullptr;
125
+ }
126
+ }
127
+
128
+ // If no positional arguments, return empty tuple and dicts
129
+ if (nargs == 0) {
130
+ PyObject *empty_tuple = PyTuple_New(0);
131
+ if (empty_tuple == nullptr) {
132
+ return nullptr;
133
+ }
134
+
135
+ PyObject *empty_dict = PyDict_New();
136
+ if (empty_dict == nullptr) {
137
+ Py_DECREF(empty_tuple);
138
+ return nullptr;
139
+ }
140
+
141
+ PyObject *empty_dict2 = PyDict_New();
142
+ if (empty_dict2 == nullptr) {
143
+ Py_DECREF(empty_tuple);
144
+ Py_DECREF(empty_dict);
145
+ return nullptr;
146
+ }
147
+
148
+ PyObject *result = PyTuple_Pack(3, empty_tuple, empty_dict, empty_dict2);
149
+ Py_DECREF(empty_tuple);
150
+ Py_DECREF(empty_dict);
151
+ Py_DECREF(empty_dict2);
152
+ return result;
153
+ }
154
+
155
+ // Temporary storage
156
+ std::vector<TmpItem> tmp_lst;
157
+ tmp_lst.reserve(nargs);
158
+
159
+ // Map from unique type to list of typed values
160
+ std::unordered_map<PyObject*, std::vector<PyObject*>> unique_dct;
161
+
162
+ struct UniqueKeysCleaner {
163
+ std::unordered_map<PyObject*, std::vector<PyObject*>> &map;
164
+ ~UniqueKeysCleaner() { for (auto &pair : map) Py_DECREF(pair.first); }
165
+ } keys_cleaner{unique_dct};
166
+
167
+ // Process each typed value
168
+ for (Py_ssize_t i = 0; i < nargs; i++) {
169
+ PyObject *tv = args[i];
170
+
171
+ // Check type if requested
172
+ if (check_type != nullptr && check_type != Py_None) {
173
+ int result = PyObject_IsInstance(tv, check_type);
174
+ if (result == -1) {
175
+ return nullptr;
176
+ }
177
+ if (!result) {
178
+ PyErr_SetObject(PyExc_TypeError, tv);
179
+ return nullptr;
180
+ }
181
+ }
182
+
183
+ // Check if it's a UniqueTypedValue
184
+ int is_unique = PyObject_IsInstance(tv, state->unique_typed_value_type);
185
+ if (is_unique == -1) {
186
+ return nullptr;
187
+ }
188
+
189
+ if (is_unique) {
190
+ // Get the _unique_typed_value_cls attribute
191
+ PyObject *unique_tv_cls = PyObject_GetAttrString(tv, "_unique_typed_value_cls");
192
+ if (unique_tv_cls == nullptr) {
193
+ return nullptr;
194
+ }
195
+
196
+ // Check for duplicates if not override
197
+ if (!override) {
198
+ auto it = unique_dct.find(unique_tv_cls);
199
+ if (it != unique_dct.end() && !it->second.empty()) {
200
+ // Duplicate found - raise error
201
+ PyObject *old_tv = it->second[0];
202
+
203
+ // Create DuplicateUniqueTypedValueError
204
+ PyObject *error = PyObject_CallFunction(
205
+ state->duplicate_error_type,
206
+ "OOO",
207
+ unique_tv_cls,
208
+ tv,
209
+ old_tv
210
+ );
211
+
212
+ Py_DECREF(unique_tv_cls);
213
+
214
+ if (error == nullptr) {
215
+ return nullptr;
216
+ }
217
+
218
+ PyErr_SetObject(state->duplicate_error_type, error);
219
+ Py_DECREF(error);
220
+ return nullptr;
221
+ }
222
+ }
223
+
224
+ // Add to unique_dct
225
+ // Use insert to avoid reference leak: if key already exists, we need to decref the new reference
226
+ auto insertion = unique_dct.insert({unique_tv_cls, std::vector<PyObject*>()});
227
+ if (!insertion.second) {
228
+ // Key already existed, decref the newly acquired reference
229
+ Py_DECREF(unique_tv_cls);
230
+ }
231
+ std::vector<PyObject*> &unique_lst = insertion.first->second;
232
+ unique_lst.push_back(tv);
233
+
234
+ // Create UniqueInfo
235
+ UniqueInfo *info = new UniqueInfo{
236
+ unique_tv_cls, // Borrowed reference (will be decreffed at end)
237
+ tv,
238
+ &unique_lst,
239
+ unique_lst.size()
240
+ };
241
+
242
+ // Add to tmp_lst
243
+ TmpItem item;
244
+ item.is_unique = true;
245
+ item.unique_info = info;
246
+ tmp_lst.push_back(std::move(item));
247
+
248
+ } else {
249
+ // Check if it's a TypedValue
250
+ int is_typed_value = PyObject_IsInstance(tv, state->typed_value_type);
251
+ if (is_typed_value == -1) {
252
+ return nullptr;
253
+ }
254
+
255
+ if (!is_typed_value) {
256
+ PyErr_SetObject(PyExc_TypeError, tv);
257
+ return nullptr;
258
+ }
259
+
260
+ // Non-unique typed value
261
+ TmpItem item;
262
+ item.is_unique = false;
263
+ item.tv = tv;
264
+ tmp_lst.push_back(std::move(item));
265
+ }
266
+ }
267
+
268
+ // Build output structures
269
+ PyObject *lst = PyList_New(0);
270
+ if (lst == nullptr) {
271
+ return nullptr;
272
+ }
273
+
274
+ PyObject *tmp_dct = PyDict_New();
275
+ if (tmp_dct == nullptr) {
276
+ Py_DECREF(lst);
277
+ return nullptr;
278
+ }
279
+
280
+ // Process tmp_lst to build output list and tmp_dct
281
+ for (auto &item : tmp_lst) {
282
+ if (item.is_unique) {
283
+ UniqueInfo *info = item.unique_info;
284
+
285
+ // Last-in-wins: only include if this is the last one
286
+ if (info->idx == info->unique_lst->size()) {
287
+ // Add to output list
288
+ if (PyList_Append(lst, info->tv) == -1) {
289
+ Py_DECREF(lst);
290
+ Py_DECREF(tmp_dct);
291
+ return nullptr;
292
+ }
293
+
294
+ // Add to tmp_dct (scalar value for unique types)
295
+ if (PyDict_SetItem(tmp_dct, info->unique_tv_cls, info->tv) == -1) {
296
+ Py_DECREF(lst);
297
+ Py_DECREF(tmp_dct);
298
+ return nullptr;
299
+ }
300
+ }
301
+ } else {
302
+ PyObject *tv = item.tv;
303
+
304
+ // Add to output list
305
+ if (PyList_Append(lst, tv) == -1) {
306
+ Py_DECREF(lst);
307
+ Py_DECREF(tmp_dct);
308
+ return nullptr;
309
+ }
310
+
311
+ // Add to tmp_dct (accumulating list for non-unique types)
312
+ PyObject *tv_type = (PyObject *)Py_TYPE(tv);
313
+ PyObject *existing = PyDict_GetItem(tmp_dct, tv_type); // Borrowed reference
314
+
315
+ if (existing == nullptr) {
316
+ // Check if an error occurred during lookup
317
+ if (PyErr_Occurred()) {
318
+ Py_DECREF(lst);
319
+ Py_DECREF(tmp_dct);
320
+ return nullptr;
321
+ }
322
+
323
+ // Create new list
324
+ PyObject *new_list = PyList_New(0);
325
+ if (new_list == nullptr) {
326
+ Py_DECREF(lst);
327
+ Py_DECREF(tmp_dct);
328
+ return nullptr;
329
+ }
330
+
331
+ if (PyList_Append(new_list, tv) == -1) {
332
+ Py_DECREF(new_list);
333
+ Py_DECREF(lst);
334
+ Py_DECREF(tmp_dct);
335
+ return nullptr;
336
+ }
337
+
338
+ if (PyDict_SetItem(tmp_dct, tv_type, new_list) == -1) {
339
+ Py_DECREF(new_list);
340
+ Py_DECREF(lst);
341
+ Py_DECREF(tmp_dct);
342
+ return nullptr;
343
+ }
344
+
345
+ Py_DECREF(new_list);
346
+ } else {
347
+ // Append to existing list
348
+ if (PyList_Append(existing, tv) == -1) {
349
+ Py_DECREF(lst);
350
+ Py_DECREF(tmp_dct);
351
+ return nullptr;
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ // Convert list to tuple
358
+ PyObject *result_tuple = PyList_AsTuple(lst);
359
+ Py_DECREF(lst);
360
+ if (result_tuple == nullptr) {
361
+ Py_DECREF(tmp_dct);
362
+ return nullptr;
363
+ }
364
+
365
+ // Build dct: convert lists to tuples
366
+ PyObject *dct = PyDict_New();
367
+ if (dct == nullptr) {
368
+ Py_DECREF(result_tuple);
369
+ Py_DECREF(tmp_dct);
370
+ return nullptr;
371
+ }
372
+
373
+ PyObject *key, *value;
374
+ Py_ssize_t pos = 0;
375
+
376
+ while (PyDict_Next(tmp_dct, &pos, &key, &value)) {
377
+ if (PyList_Check(value)) {
378
+ PyObject *tuple_value = PyList_AsTuple(value);
379
+ if (tuple_value == nullptr) {
380
+ Py_DECREF(result_tuple);
381
+ Py_DECREF(tmp_dct);
382
+ Py_DECREF(dct);
383
+ return nullptr;
384
+ }
385
+
386
+ if (PyDict_SetItem(dct, key, tuple_value) == -1) {
387
+ Py_DECREF(tuple_value);
388
+ Py_DECREF(result_tuple);
389
+ Py_DECREF(tmp_dct);
390
+ Py_DECREF(dct);
391
+ return nullptr;
392
+ }
393
+
394
+ Py_DECREF(tuple_value);
395
+ } else {
396
+ // Scalar value (unique type)
397
+ if (PyDict_SetItem(dct, key, value) == -1) {
398
+ Py_DECREF(result_tuple);
399
+ Py_DECREF(tmp_dct);
400
+ Py_DECREF(dct);
401
+ return nullptr;
402
+ }
403
+ }
404
+ }
405
+
406
+ Py_DECREF(tmp_dct);
407
+
408
+ // Build dct2: dct + unique values keyed by instance type
409
+ PyObject *dct2 = PyDict_Copy(dct);
410
+ if (dct2 == nullptr) {
411
+ Py_DECREF(result_tuple);
412
+ Py_DECREF(dct);
413
+ return nullptr;
414
+ }
415
+
416
+ // Add unique values by their instance type
417
+ pos = 0;
418
+ while (PyDict_Next(dct, &pos, &key, &value)) {
419
+ // Check if value is a UniqueTypedValue (not a tuple)
420
+ if (!PyTuple_Check(value)) {
421
+ int is_unique = PyObject_IsInstance(value, state->unique_typed_value_type);
422
+ if (is_unique == -1) {
423
+ Py_DECREF(result_tuple);
424
+ Py_DECREF(dct);
425
+ Py_DECREF(dct2);
426
+ return nullptr;
427
+ }
428
+
429
+ if (is_unique) {
430
+ PyObject *value_type = (PyObject *)Py_TYPE(value);
431
+ if (PyDict_SetItem(dct2, value_type, value) == -1) {
432
+ Py_DECREF(result_tuple);
433
+ Py_DECREF(dct);
434
+ Py_DECREF(dct2);
435
+ return nullptr;
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ // Build final result
442
+ PyObject *result = PyTuple_Pack(3, result_tuple, dct, dct2);
443
+ Py_DECREF(result_tuple);
444
+ Py_DECREF(dct);
445
+ Py_DECREF(dct2);
446
+
447
+ return result;
448
+ }
449
+
450
+ //
451
+
452
+ PyDoc_STRVAR(collection_doc, "Native C++ implementations for omlish.typedvalues.collection");
453
+
454
+ static int collection_exec(PyObject *module)
455
+ {
456
+ collection_state *state = get_collection_state(module);
457
+
458
+ // Import TypedValue
459
+ PyObject *values_module = PyImport_ImportModule("omlish.typedvalues.values");
460
+ if (values_module == nullptr) {
461
+ return -1;
462
+ }
463
+
464
+ state->typed_value_type = PyObject_GetAttrString(values_module, "TypedValue");
465
+ if (state->typed_value_type == nullptr) {
466
+ Py_DECREF(values_module);
467
+ return -1;
468
+ }
469
+
470
+ state->unique_typed_value_type = PyObject_GetAttrString(values_module, "UniqueTypedValue");
471
+ Py_DECREF(values_module);
472
+ if (state->unique_typed_value_type == nullptr) {
473
+ return -1;
474
+ }
475
+
476
+ // Import DuplicateUniqueTypedValueError
477
+ PyObject *collection_module = PyImport_ImportModule("omlish.typedvalues.collection");
478
+ if (collection_module == nullptr) {
479
+ return -1;
480
+ }
481
+
482
+ state->duplicate_error_type = PyObject_GetAttrString(collection_module, "DuplicateUniqueTypedValueError");
483
+ Py_DECREF(collection_module);
484
+ if (state->duplicate_error_type == nullptr) {
485
+ return -1;
486
+ }
487
+
488
+ return 0;
489
+ }
490
+
491
+ static int collection_traverse(PyObject *module, visitproc visit, void *arg)
492
+ {
493
+ collection_state *state = get_collection_state(module);
494
+ Py_VISIT(state->typed_value_type);
495
+ Py_VISIT(state->unique_typed_value_type);
496
+ Py_VISIT(state->duplicate_error_type);
497
+ return 0;
498
+ }
499
+
500
+ static int collection_clear(PyObject *module)
501
+ {
502
+ collection_state *state = get_collection_state(module);
503
+ Py_CLEAR(state->typed_value_type);
504
+ Py_CLEAR(state->unique_typed_value_type);
505
+ Py_CLEAR(state->duplicate_error_type);
506
+ return 0;
507
+ }
508
+
509
+ static void collection_free(void *module)
510
+ {
511
+ collection_clear((PyObject *)module);
512
+ }
513
+
514
+ static PyMethodDef collection_methods[] = {
515
+ {"init_typed_values_collection", (PyCFunction)(void(*)(void))init_typed_values_collection, METH_FASTCALL | METH_KEYWORDS, init_typed_values_collection_doc},
516
+ {NULL, NULL, 0, NULL}
517
+ };
518
+
519
+ static struct PyModuleDef_Slot collection_slots[] = {
520
+ {Py_mod_exec, (void *) collection_exec},
521
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
522
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
523
+ {0, NULL}
524
+ };
525
+
526
+ static struct PyModuleDef collection_module = {
527
+ .m_base = PyModuleDef_HEAD_INIT,
528
+ .m_name = _MODULE_NAME,
529
+ .m_doc = collection_doc,
530
+ .m_size = sizeof(collection_state),
531
+ .m_methods = collection_methods,
532
+ .m_slots = collection_slots,
533
+ .m_traverse = collection_traverse,
534
+ .m_clear = collection_clear,
535
+ .m_free = collection_free,
536
+ };
537
+
538
+ extern "C" {
539
+
540
+ PyMODINIT_FUNC PyInit__collection(void)
541
+ {
542
+ return PyModuleDef_Init(&collection_module);
543
+ }
544
+
545
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish-cext
3
- Version: 0.0.0.dev503
3
+ Version: 0.0.0.dev505
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.dev503
17
+ Requires-Dist: omlish==0.0.0.dev505
18
18
  Dynamic: license-file
19
19
 
20
20
  # Overview
@@ -5,6 +5,7 @@ setup.py
5
5
  omlish/_check.cc
6
6
  omlish/lang/_asyncs.cc
7
7
  omlish/lang/imports/_capture.cc
8
+ omlish/typedvalues/_collection.cc
8
9
  omlish_cext.egg-info/PKG-INFO
9
10
  omlish_cext.egg-info/SOURCES.txt
10
11
  omlish_cext.egg-info/dependency_links.txt
@@ -0,0 +1 @@
1
+ omlish==0.0.0.dev505
@@ -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.dev503'
16
+ version = '0.0.0.dev505'
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.dev503',
27
+ 'omlish == 0.0.0.dev505',
28
28
  ]
29
29
 
30
30
  [tool.setuptools]
@@ -18,5 +18,10 @@ st.setup(
18
18
  sources=['omlish/lang/imports/_capture.cc'],
19
19
  extra_compile_args=['-std=c++20'],
20
20
  ),
21
+ st.Extension(
22
+ name='omlish.typedvalues._collection',
23
+ sources=['omlish/typedvalues/_collection.cc'],
24
+ extra_compile_args=['-std=c++20'],
25
+ ),
21
26
  ],
22
27
  )
@@ -1 +0,0 @@
1
- omlish==0.0.0.dev503