arraykit 1.2.0__cp314-cp314-win32.whl

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.
@@ -0,0 +1,2972 @@
1
+ # include "Python.h"
2
+ # include "stdbool.h"
3
+
4
+ # define NO_IMPORT_ARRAY
5
+ # define PY_ARRAY_UNIQUE_SYMBOL AK_ARRAY_API
6
+ # define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
7
+
8
+ # include "numpy/arrayobject.h"
9
+ # include "numpy/halffloat.h"
10
+
11
+ # include "delimited_to_arrays.h"
12
+ # include "utilities.h"
13
+
14
+ #define AK_is_digit(c) (((unsigned)(c) - '0') < 10u)
15
+ #define AK_is_space(c) (((c) == ' ') || (((unsigned)(c) - '\t') < 5))
16
+ #define AK_is_quote(c) (((c) == '"') || ((c) == '\''))
17
+ #define AK_is_sign(c) (((c) == '+') || ((c) == '-'))
18
+ #define AK_is_paren_open(c) ((c) == '(')
19
+ #define AK_is_paren_close(c) ((c) == ')')
20
+
21
+ #define AK_is_a(c) (((c) == 'a') || ((c) == 'A'))
22
+ #define AK_is_e(c) (((c) == 'e') || ((c) == 'E'))
23
+ #define AK_is_f(c) (((c) == 'f') || ((c) == 'F'))
24
+ #define AK_is_i(c) (((c) == 'i') || ((c) == 'I'))
25
+ #define AK_is_j(c) (((c) == 'j') || ((c) == 'J'))
26
+ #define AK_is_l(c) (((c) == 'l') || ((c) == 'L'))
27
+ #define AK_is_n(c) (((c) == 'n') || ((c) == 'N'))
28
+ #define AK_is_r(c) (((c) == 'r') || ((c) == 'R'))
29
+ #define AK_is_s(c) (((c) == 's') || ((c) == 'S'))
30
+ #define AK_is_t(c) (((c) == 't') || ((c) == 'T'))
31
+ #define AK_is_u(c) (((c) == 'u') || ((c) == 'U'))
32
+
33
+ //------------------------------------------------------------------------------
34
+ // Utility setters of C types from possibly NULL PyObject*; all return -1 on error.
35
+
36
+ static inline int
37
+ AK_set_bool(const char *name,
38
+ bool *target,
39
+ PyObject *src,
40
+ bool dflt)
41
+ {
42
+ if (src == NULL)
43
+ *target = dflt;
44
+ else {
45
+ int b = PyObject_IsTrue(src);
46
+ if (b < 0) return -1;
47
+ *target = (char)b;
48
+ }
49
+ return 0;
50
+ }
51
+
52
+ static inline int
53
+ AK_set_int(const char *name,
54
+ int *target,
55
+ PyObject *src,
56
+ int dflt)
57
+ {
58
+ if (src == NULL)
59
+ *target = dflt;
60
+ else {
61
+ if (!PyLong_CheckExact(src)) {
62
+ PyErr_Format(PyExc_TypeError, "\"%s\" must be an integer", name);
63
+ return -1;
64
+ }
65
+ long value = PyLong_AsLong(src);
66
+ if (value == -1 && PyErr_Occurred()) {
67
+ return -1;
68
+ }
69
+ if (value < INT_MIN || value > INT_MAX) {
70
+ PyErr_Format(PyExc_TypeError, "\"%s\" overflowed integer", name);
71
+ return -1;
72
+ }
73
+ *target = (int)value;
74
+ }
75
+ return 0;
76
+ }
77
+
78
+ // Set a character from `src` on `target`; if src is NULL use default. Returns -1 on error, else 0. If a None is given, a null char will be assigned.
79
+ static inline int
80
+ AK_set_char(const char *name,
81
+ Py_UCS4 *target,
82
+ PyObject *src,
83
+ Py_UCS4 dflt)
84
+ {
85
+ if (src == NULL)
86
+ *target = dflt;
87
+ else {
88
+ *target = '\0';
89
+ if (src != Py_None) {
90
+ Py_ssize_t len;
91
+ if (!PyUnicode_Check(src)) {
92
+ PyErr_Format(PyExc_TypeError,
93
+ "\"%s\" must be string, not %.200s",
94
+ name,
95
+ Py_TYPE(src)->tp_name);
96
+ return -1;
97
+ }
98
+ len = PyUnicode_GetLength(src);
99
+ if (len > 1) {
100
+ PyErr_Format(PyExc_TypeError,
101
+ "\"%s\" must be a 1-character string",
102
+ name);
103
+ return -1;
104
+ }
105
+ if (len > 0)
106
+ *target = PyUnicode_READ_CHAR(src, 0);
107
+ }
108
+ }
109
+ return 0;
110
+ }
111
+
112
+ // Given a dtype_specifier, which might be a dtype, NULL, or None, assign a fresh dtype object (or NULL) to dtype_returned. Returns 0 on success, -1 on failure. This will not interpret a None dtype_specified as a float dtype. This will never set dtype_returned to None (only NULL). Returns a new reference.
113
+ static inline int
114
+ AK_DTypeFromSpecifier(PyObject *dtype_specifier, PyArray_Descr **dtype_returned)
115
+ {
116
+ PyArray_Descr* dtype;
117
+ if (dtype_specifier == NULL) {
118
+ dtype = NULL; // propagate, cannot call into oncverter
119
+ }
120
+ else if (PyObject_TypeCheck(dtype_specifier, &PyArrayDescr_Type)) {
121
+ dtype = (PyArray_Descr* )dtype_specifier;
122
+ }
123
+ else { // converter2 sets NULL for None
124
+ PyArray_DescrConverter2(dtype_specifier, &dtype);
125
+ }
126
+ // if not NULL, make a copy as we will give ownership to array and might mutate
127
+ if (dtype) {
128
+ dtype = PyArray_DescrNew(dtype);
129
+ if (dtype == NULL) return -1;
130
+ }
131
+ *dtype_returned = dtype;
132
+ return 0;
133
+ }
134
+
135
+ //------------------------------------------------------------------------------
136
+ // TypeParser: Type, New, Destructor
137
+
138
+ // This defines the observed type of each field, or an entire line of fields. Note that TPS_STRING serves as the last resort, the type that needs no conversion (or cannot be converted).
139
+ typedef enum AK_TypeParserState {
140
+ TPS_UNKNOWN, // for initial state
141
+ TPS_BOOL,
142
+ TPS_INT,
143
+ TPS_FLOAT,
144
+ TPS_COMPLEX, // 4
145
+ TPS_STRING,
146
+ TPS_EMPTY // empty fields
147
+ } AK_TypeParserState;
148
+
149
+ // Given previous and new parser states, return a next parser state. Does not error.
150
+ static inline AK_TypeParserState
151
+ AK_TPS_Resolve(AK_TypeParserState previous, AK_TypeParserState new) {
152
+ // unlikely case
153
+ if (new == TPS_UNKNOWN) return TPS_STRING;
154
+
155
+ // propagate new if previous is unknown or empty
156
+ if ((previous == TPS_UNKNOWN) || (previous == TPS_EMPTY)) return new;
157
+
158
+ // if either are string, go to string
159
+ if (previous == TPS_STRING || new == TPS_STRING) return TPS_STRING;
160
+
161
+ // handle both new, previous bool directly
162
+ if (previous == TPS_BOOL) {
163
+ if (new == TPS_EMPTY || new == TPS_BOOL) return TPS_BOOL;
164
+ else {return TPS_STRING;} // bool found with anything except empty is string
165
+ }
166
+ if (new == TPS_BOOL) {
167
+ if (previous == TPS_EMPTY) return TPS_BOOL;
168
+ else return TPS_STRING; // bool found with anything except empty is string
169
+ }
170
+ // numerical promotion
171
+ if (previous == TPS_INT) {
172
+ if (new == TPS_EMPTY || new == TPS_INT) return TPS_INT;
173
+ if (new == TPS_FLOAT) return TPS_FLOAT;
174
+ if (new == TPS_COMPLEX) return TPS_COMPLEX;
175
+ }
176
+ if (previous == TPS_FLOAT) {
177
+ if (new == TPS_EMPTY || new == TPS_INT || new == TPS_FLOAT) return TPS_FLOAT;
178
+ if (new == TPS_COMPLEX) return TPS_COMPLEX;
179
+ }
180
+ // previous == TPS_COMPLEX, new is TPS_EMPTY, TPS_INT, TPS_FLOAT, or TPS_COMPLEX
181
+ return TPS_COMPLEX;
182
+ }
183
+
184
+ // Given a TypeParser state, return a dtype. Returns NULL on error.
185
+ static inline PyArray_Descr *
186
+ AK_TPS_ToDtype(AK_TypeParserState state) {
187
+ PyArray_Descr *dtype = NULL;
188
+
189
+ switch (state) {
190
+ case TPS_UNKNOWN:
191
+ dtype = PyArray_DescrNewFromType(NPY_UNICODE);
192
+ break;
193
+ case TPS_EMPTY: // all empty defaults to string
194
+ dtype = PyArray_DescrNewFromType(NPY_UNICODE);
195
+ break;
196
+ case TPS_STRING:
197
+ dtype = PyArray_DescrNewFromType(NPY_UNICODE);
198
+ break;
199
+ case TPS_BOOL:
200
+ dtype = PyArray_DescrNewFromType(NPY_BOOL);
201
+ break;
202
+ case TPS_INT:
203
+ dtype = PyArray_DescrNewFromType(NPY_INT64);
204
+ break;
205
+ case TPS_FLOAT:
206
+ dtype = PyArray_DescrNewFromType(NPY_FLOAT64);
207
+ break;
208
+ case TPS_COMPLEX:
209
+ dtype = PyArray_DescrNewFromType(NPY_COMPLEX128);
210
+ break;
211
+ }
212
+ if (dtype == NULL) return NULL; // assume error is set by PyArray_DescrFromType
213
+ return dtype;
214
+ }
215
+
216
+ // An AK_TypeParser accumulates the state in parsing a single code point line. It holds both "active" state in the progress of parsing each field as well as finalized state in parsed_line
217
+ typedef struct AK_TypeParser {
218
+ bool previous_numeric;
219
+ bool contiguous_numeric;
220
+ bool contiguous_leading_space;
221
+ // counting will always stop before 8 or less
222
+ npy_int8 count_bool;
223
+ npy_int8 count_sign;
224
+ npy_int8 count_e;
225
+ npy_int8 count_j;
226
+ npy_int8 count_decimal;
227
+ npy_int8 count_nan;
228
+ npy_int8 count_inf;
229
+ npy_int8 count_paren_open;
230
+ npy_int8 count_paren_close;
231
+ // bound by number of chars in field
232
+ Py_ssize_t last_sign_pos; // signed
233
+ Py_ssize_t count_leading_space;
234
+ Py_ssize_t count_digit;
235
+ Py_ssize_t count_not_space;
236
+
237
+ AK_TypeParserState parsed_field; // state of current field
238
+ AK_TypeParserState parsed_line; // state of current resolved line type
239
+
240
+ Py_UCS4 tsep;
241
+ Py_UCS4 decc;
242
+
243
+ } AK_TypeParser;
244
+
245
+ // Initialize all state. This returns no error. This is called once per field for each field in a code point line: this is why parsed_field is reset, but parsed_line is not.
246
+ static inline void
247
+ AK_TP_reset_field(AK_TypeParser* tp)
248
+ {
249
+ tp->previous_numeric = false;
250
+ tp->contiguous_numeric = false;
251
+ tp->contiguous_leading_space = false;
252
+
253
+ tp->count_bool = 0;
254
+ tp->count_sign = 0;
255
+ tp->count_e = 0;
256
+ tp->count_j = 0;
257
+ tp->count_decimal = 0;
258
+ tp->count_nan = 0;
259
+ tp->count_inf = 0;
260
+ tp->count_paren_open = 0;
261
+ tp->count_paren_close = 0;
262
+
263
+ tp->last_sign_pos = -1;
264
+ tp->count_leading_space = 0;
265
+ tp->count_digit = 0;
266
+ tp->count_not_space = 0;
267
+
268
+ tp->parsed_field = TPS_UNKNOWN;
269
+ // NOTE: do not reset parsed_line
270
+ }
271
+
272
+ static inline AK_TypeParser *
273
+ AK_TP_New(Py_UCS4 tsep, Py_UCS4 decc)
274
+ {
275
+ AK_TypeParser *tp = (AK_TypeParser*)PyMem_Malloc(sizeof(AK_TypeParser));
276
+ if (tp == NULL) return (AK_TypeParser*)PyErr_NoMemory();
277
+ AK_TP_reset_field(tp);
278
+ tp->parsed_line = TPS_UNKNOWN;
279
+ tp->tsep = tsep; // take tsep into context for auto eval?
280
+ tp->decc = decc;
281
+ return tp;
282
+ }
283
+
284
+ static inline void
285
+ AK_TP_Free(AK_TypeParser* tp)
286
+ {
287
+ PyMem_Free(tp);
288
+ }
289
+
290
+ //------------------------------------------------------------------------------
291
+
292
+ // Given a type parse, process a single character and update the type parser state in `parsed_field`. Return true when processing should continue, false when no further processing is necessary. `pos` is the raw position within the current field.
293
+ static inline bool
294
+ AK_TP_ProcessChar(AK_TypeParser* tp,
295
+ Py_UCS4 c,
296
+ Py_ssize_t pos)
297
+ {
298
+ if (tp->parsed_field != TPS_UNKNOWN) {
299
+ // if parsed_field is set to anything other than TPS_UNKNOWN, we should not be calling process_char anymore
300
+ Py_UNREACHABLE();
301
+ }
302
+
303
+ // evaluate space ..........................................................
304
+ bool space = false;
305
+
306
+ if (AK_is_space(c)) {
307
+ if (pos == 0) {
308
+ tp->contiguous_leading_space = true;
309
+ }
310
+ if (tp->contiguous_leading_space) {
311
+ ++tp->count_leading_space;
312
+ return true;
313
+ }
314
+ space = true;
315
+ }
316
+ else if (AK_is_quote(c)) {
317
+ // any quote, leading or otherwise, defines a string
318
+ tp->parsed_field = TPS_STRING;
319
+ return false;
320
+ }
321
+ else if (AK_is_paren_open(c)) {
322
+ ++tp->count_paren_open;
323
+ ++tp->count_leading_space;
324
+ space = true;
325
+ // open paren permitted only in first non-space position
326
+ if ((pos > 0 && !tp->contiguous_leading_space) || tp->count_paren_open > 1) {
327
+ tp->parsed_field = TPS_STRING;
328
+ return false;
329
+ }
330
+ }
331
+ else if (AK_is_paren_close(c)) {
332
+ ++tp->count_paren_close;
333
+ space = true;
334
+ // NOTE: might evaluate if previous is contiguous numeric
335
+ if (tp->count_paren_close > 1) {
336
+ tp->parsed_field = TPS_STRING;
337
+ return false;
338
+ }
339
+ }
340
+ else {
341
+ ++tp->count_not_space;
342
+ }
343
+ // no longer in contiguous leading space
344
+ tp->contiguous_leading_space = false;
345
+
346
+ // pos_field defines the position within the field less leading space
347
+ Py_ssize_t pos_field = pos - tp->count_leading_space;
348
+
349
+ // evaluate numeric, non-positional ........................................
350
+ bool numeric = false;
351
+ bool digit = false;
352
+
353
+ if (space) {}
354
+ else if (AK_is_digit(c)) {
355
+ ++tp->count_digit;
356
+ digit = true;
357
+ numeric = true;
358
+ }
359
+ else if (c == tp->decc) { // is decimal
360
+ ++tp->count_decimal;
361
+ if (tp->count_decimal > 2) { // complex can have 2
362
+ tp->parsed_field = TPS_STRING;
363
+ return false;
364
+ }
365
+ numeric = true;
366
+ }
367
+ else if (AK_is_sign(c)) {
368
+ ++tp->count_sign;
369
+ if (tp->count_sign > 4) { // complex can have 4
370
+ tp->parsed_field = TPS_STRING;
371
+ return false;
372
+ }
373
+ tp->last_sign_pos = pos_field;
374
+ numeric = true;
375
+ }
376
+ else if (AK_is_e(c)) {
377
+ ++tp->count_e;
378
+ if ((pos_field == 0) || (tp->count_e > 2)) {
379
+ // can never lead field; true or false have one E; complex can have 2
380
+ tp->parsed_field = TPS_STRING;
381
+ return false;
382
+ }
383
+ numeric = true;
384
+ }
385
+ else if (AK_is_j(c)) {
386
+ ++tp->count_j;
387
+ if ((pos_field == 0) || (tp->count_j > 1)) {
388
+ // can never lead field; complex can have 1
389
+ tp->parsed_field = TPS_STRING;
390
+ return false;
391
+ }
392
+ numeric = true;
393
+ }
394
+
395
+ // evaluate contiguous numeric .............................................
396
+ if (numeric) {
397
+ if (pos_field == 0) {
398
+ tp->contiguous_numeric = true;
399
+ tp->previous_numeric = true;
400
+ }
401
+ if (!tp->previous_numeric) {
402
+ tp->contiguous_numeric = false;
403
+ }
404
+ tp->previous_numeric = true; // this char is numeric for next eval
405
+ }
406
+ else { // not numeric
407
+ // only mark as not contiguous_numeric if non space as might be trailing space
408
+ if (tp->contiguous_numeric && !space) {
409
+ tp->contiguous_numeric = false;
410
+ }
411
+ tp->previous_numeric = false;
412
+ }
413
+
414
+ // evaluate character positions ............................................
415
+ if (space || digit) {
416
+ return true;
417
+ }
418
+
419
+ if (tp->last_sign_pos >= 0) { // initialized to -1
420
+ pos_field -= tp->last_sign_pos + 1;
421
+ }
422
+
423
+ // given relative positions `pos_field`, map to known character sequences
424
+ switch (pos_field) {
425
+ case 0:
426
+ if (AK_is_t(c)) {++tp->count_bool;}
427
+ else if (AK_is_f(c)) {--tp->count_bool;}
428
+ else if (AK_is_n(c)) {++tp->count_nan;}
429
+ else if (AK_is_i(c)) {++tp->count_inf;}
430
+ else if (!numeric) { // if not decimal, sign, e, j
431
+ tp->parsed_field = TPS_STRING;
432
+ return false;
433
+ }
434
+ break;
435
+ case 1:
436
+ if (AK_is_r(c)) {++tp->count_bool;} // true
437
+ else if (AK_is_a(c)) {
438
+ --tp->count_bool; // false
439
+ ++tp->count_nan;
440
+ }
441
+ else if (AK_is_n(c)) {++tp->count_inf;}
442
+ else if (!numeric) {
443
+ tp->parsed_field = TPS_STRING;
444
+ return false;
445
+ }
446
+ break;
447
+ case 2:
448
+ if (AK_is_u(c)) {++tp->count_bool;}
449
+ else if (AK_is_l(c)) {--tp->count_bool;}
450
+ else if (AK_is_n(c)) {++tp->count_nan;}
451
+ else if (AK_is_f(c)) {++tp->count_inf;}
452
+ else if (!numeric) {
453
+ tp->parsed_field = TPS_STRING;
454
+ return false;
455
+ }
456
+ break;
457
+ case 3:
458
+ if (AK_is_e(c)) {++tp->count_bool;} // true
459
+ else if (AK_is_s(c)) {--tp->count_bool;} // false
460
+ else if (!numeric) {
461
+ tp->parsed_field = TPS_STRING;
462
+ return false;
463
+ }
464
+ break;
465
+ case 4:
466
+ if (AK_is_e(c)) {--tp->count_bool;} // false
467
+ else if (!numeric) {
468
+ tp->parsed_field = TPS_STRING;
469
+ return false;
470
+ }
471
+ break;
472
+ default: // character positions > 4
473
+ if (!numeric) {
474
+ tp->parsed_field = TPS_STRING;
475
+ return false;
476
+ }
477
+ }
478
+ return true; // continue processing
479
+ }
480
+
481
+ // This private function is used by AK_TP_ResolveLineResetField to evaluate the state of the AK_TypeParser and determine the resolved AK_TypeParserState.
482
+ static inline AK_TypeParserState
483
+ AK_TP_resolve_field(AK_TypeParser* tp,
484
+ Py_ssize_t count)
485
+ {
486
+ if (count == 0) return TPS_EMPTY;
487
+
488
+ // if parsed_field is known, return it
489
+ if (tp->parsed_field != TPS_UNKNOWN) return tp->parsed_field;
490
+
491
+ if (tp->count_bool == 4 && tp->count_not_space == 4) {
492
+ return TPS_BOOL;
493
+ }
494
+ if (tp->count_bool == -5 && tp->count_not_space == 5) {
495
+ return TPS_BOOL;
496
+ }
497
+ if (tp->contiguous_numeric) {
498
+ if (tp->count_digit == 0) return TPS_STRING;
499
+ // int
500
+ if (tp->count_j == 0 &&
501
+ tp->count_sign <= 1 &&
502
+ tp->last_sign_pos <= 0 &&
503
+ tp->count_decimal == 0 &&
504
+ tp->count_e == 0 &&
505
+ tp->count_paren_close == 0 &&
506
+ tp->count_paren_open == 0 &&
507
+ tp->count_nan == 0 &&
508
+ tp->count_inf == 0) {
509
+ return TPS_INT;
510
+ }
511
+ // float
512
+ if (tp->count_j == 0 &&
513
+ tp->count_sign <= 2 &&
514
+ tp->count_paren_close == 0 &&
515
+ tp->count_paren_open == 0 &&
516
+ (tp->count_decimal == 1 || tp->count_e == 1)
517
+ ) {
518
+ if (tp->count_sign == 2 && tp->count_e == 0) {
519
+ return TPS_STRING;
520
+ }
521
+ return TPS_FLOAT;
522
+ }
523
+ // complex with j
524
+ if ((tp->count_j == 1) &&
525
+ ((tp->count_paren_close == 0 && tp->count_paren_open == 0) ||
526
+ (tp->count_paren_close == 1 && tp->count_paren_open == 1))
527
+ ) {
528
+ if (tp->count_sign > 2 + tp->count_e) {
529
+ return TPS_STRING;
530
+ }
531
+ return TPS_COMPLEX;
532
+ }
533
+ // complex with parens (no j)
534
+ if (tp->count_j == 0 &&
535
+ tp->count_paren_close == 1 &&
536
+ tp->count_paren_open == 1
537
+ ) {
538
+ if (tp->count_sign > 2 && tp->count_e > 1) {
539
+ return TPS_STRING;
540
+ }
541
+ return TPS_COMPLEX;
542
+ }
543
+ }
544
+ // non contiguous numeric cases
545
+ else if (tp->count_j == 0) {
546
+ if (tp->count_nan == 3 && tp->count_sign + tp->count_nan == tp->count_not_space) {
547
+ return TPS_FLOAT;
548
+ }
549
+ if (tp->count_inf == 3 && tp->count_sign + tp->count_inf == tp->count_not_space) {
550
+ return TPS_FLOAT;
551
+ }
552
+ }
553
+ else if (tp->count_j == 1) {
554
+ Py_ssize_t count_numeric = (tp->count_sign +
555
+ tp->count_decimal +
556
+ tp->count_e +
557
+ tp->count_j +
558
+ tp->count_digit);
559
+
560
+ // one inf and one nan
561
+ if (tp->count_nan == 3 && tp->count_inf == 3 &&
562
+ tp->count_sign + 7 == tp->count_not_space) {
563
+ return TPS_COMPLEX;
564
+ }
565
+ // one nan one number
566
+ if (tp->count_nan == 3 &&
567
+ tp->count_nan + count_numeric == tp->count_not_space) {
568
+ return TPS_COMPLEX;
569
+ }
570
+ // two nans
571
+ if (tp->count_nan == 6 &&
572
+ tp->count_sign + tp->count_nan + 1 == tp->count_not_space) {
573
+ return TPS_COMPLEX;
574
+ }
575
+ // one inf one number
576
+ if (tp->count_inf == 3 &&
577
+ tp->count_inf + count_numeric == tp->count_not_space) {
578
+ return TPS_COMPLEX;
579
+ }
580
+ // two infs
581
+ if (tp->count_inf == 6 &&
582
+ tp->count_sign + tp->count_inf + 1 == tp->count_not_space) {
583
+ return TPS_COMPLEX;
584
+ }
585
+ }
586
+ return TPS_STRING; // default
587
+ }
588
+
589
+ // After field is complete, call AK_TP_ResolveLineResetField to evaluate and set the current parsed_line. All TypeParse field attributes are reset after this is called. Returns true if the line still needs to be evaluated.
590
+ static inline bool
591
+ AK_TP_ResolveLineResetField(AK_TypeParser* tp,
592
+ Py_ssize_t count)
593
+ {
594
+ if (tp->parsed_line != TPS_STRING) {
595
+ // resolve with previous parsed_line (or unkown if just initialized)
596
+ tp->parsed_line = AK_TPS_Resolve(tp->parsed_line, AK_TP_resolve_field(tp, count));
597
+ }
598
+ AK_TP_reset_field(tp);
599
+ // if string, return false to stop further line processing
600
+ return tp->parsed_line != TPS_STRING;
601
+ }
602
+
603
+ //------------------------------------------------------------------------------
604
+ // UCS4 array processors
605
+
606
+ static char * TRUE_LOWER = "true";
607
+ static char * TRUE_UPPER = "TRUE";
608
+
609
+ #define AK_ERROR_NO_DIGITS 1
610
+ #define AK_ERROR_OVERFLOW 2
611
+ #define AK_ERROR_INVALID_CHARS 3
612
+
613
+ // Convert a Py_UCS4 array to a signed integer. Extended from pandas/_libs/src/parser/tokenizer.c. Sets `error` to values greater than 0 on error; never sets error on success.
614
+ static inline npy_int64
615
+ AK_UCS4_to_int64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
616
+ {
617
+ npy_int64 int_min = NPY_MIN_INT64;
618
+ npy_int64 int_max = NPY_MAX_INT64;
619
+ int isneg = 0;
620
+ npy_int64 number = 0;
621
+ int d;
622
+
623
+ Py_UCS4 *p = p_item;
624
+
625
+ while (AK_is_space(*p)) {
626
+ ++p;
627
+ if (p >= end) return number; // NOTE: this means that all space will return zero without error
628
+ }
629
+ if (*p == '-') {
630
+ isneg = 1;
631
+ ++p;
632
+ } else if (*p == '+') {
633
+ ++p;
634
+ }
635
+ if (p >= end) return number;
636
+
637
+ // Check that there is a first digit.
638
+ if (!AK_is_digit(*p)) {
639
+ *error = AK_ERROR_NO_DIGITS;
640
+ return 0;
641
+ }
642
+ if (isneg) {
643
+ // If number is greater than pre_min, at least one more digit can be processed without overflowing.
644
+ int dig_pre_min = -(int_min % 10);
645
+ npy_int64 pre_min = int_min / 10;
646
+ d = *p;
647
+ if (tsep != '\0') {
648
+ while (1) {
649
+ if (d == tsep) {
650
+ ++p;
651
+ if (p >= end) return number;
652
+ d = *p;
653
+ continue;
654
+ } else if (!AK_is_digit(d)) {
655
+ break;
656
+ }
657
+ if ((number > pre_min) ||
658
+ ((number == pre_min) && (d - '0' <= dig_pre_min))) {
659
+ number = number * 10 - (d - '0');
660
+ ++p;
661
+ if (p >= end) return number;
662
+ d = *p;
663
+ } else {
664
+ *error = AK_ERROR_OVERFLOW;
665
+ return 0;
666
+ }
667
+ }
668
+ } else {
669
+ while (AK_is_digit(d)) {
670
+ if ((number > pre_min) ||
671
+ ((number == pre_min) && (d - '0' <= dig_pre_min))) {
672
+ number = number * 10 - (d - '0');
673
+ ++p;
674
+ if (p >= end) return number;
675
+ d = *p;
676
+ } else {
677
+ *error = AK_ERROR_OVERFLOW;
678
+ return 0;
679
+ }
680
+ }
681
+ }
682
+ } else {
683
+ // If number is less than pre_max, at least one more digit can be processed without overflowing.
684
+ npy_int64 pre_max = int_max / 10;
685
+ int dig_pre_max = int_max % 10;
686
+ d = *p;
687
+ if (tsep != '\0') {
688
+ while (1) {
689
+ if (d == tsep) {
690
+ ++p;
691
+ if (p >= end) return number;
692
+ d = *p;
693
+ continue;
694
+ } else if (!AK_is_digit(d)) {
695
+ break;
696
+ }
697
+ if ((number < pre_max) ||
698
+ ((number == pre_max) && (d - '0' <= dig_pre_max))) {
699
+ number = number * 10 + (d - '0');
700
+ ++p;
701
+ if (p >= end) return number;
702
+ d = *p;
703
+ } else {
704
+ *error = AK_ERROR_OVERFLOW;
705
+ return 0;
706
+ }
707
+ }
708
+ } else {
709
+ while (AK_is_digit(d)) {
710
+ if ((number < pre_max) ||
711
+ ((number == pre_max) && (d - '0' <= dig_pre_max))) {
712
+ number = number * 10 + (d - '0');
713
+ ++p;
714
+ if (p >= end) return number;
715
+ d = *p;
716
+ } else {
717
+ *error = AK_ERROR_OVERFLOW;
718
+ return 0;
719
+ }
720
+ }
721
+ }
722
+ }
723
+ while (p < end) {
724
+ if (!AK_is_space(*p)) {
725
+ *error = AK_ERROR_INVALID_CHARS;
726
+ return 0;
727
+ }
728
+ p++;
729
+ }
730
+ return number;
731
+ }
732
+
733
+ // Convert a Py_UCS4 array to an unsigned integer. Extended from pandas/_libs/src/parser/tokenizer.c. Sets error to > 0 on error; never sets error on success.
734
+ static inline npy_uint64
735
+ AK_UCS4_to_uint64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep)
736
+ {
737
+ npy_uint64 pre_max = NPY_MAX_UINT64 / 10;
738
+ npy_uint64 number = 0;
739
+ int dig_pre_max = NPY_MAX_UINT64 % 10;
740
+ int d;
741
+
742
+ Py_UCS4 *p = p_item;
743
+ while (AK_is_space(*p)) {
744
+ ++p;
745
+ if (p >= end) return number;
746
+ }
747
+ if (*p == '-') {
748
+ *error = AK_ERROR_INVALID_CHARS;
749
+ return 0;
750
+ } else if (*p == '+') {
751
+ p++;
752
+ if (p >= end) return number;
753
+ }
754
+
755
+ // Check that there is a first digit.
756
+ if (!AK_is_digit(*p)) {
757
+ *error = AK_ERROR_NO_DIGITS;
758
+ return 0;
759
+ }
760
+ // If number is less than pre_max, at least one more digit can be processed without overflowing.
761
+ d = *p;
762
+ if (tsep != '\0') {
763
+ while (1) {
764
+ if (d == tsep) {
765
+ ++p;
766
+ if (p >= end) return number;
767
+ d = *p;
768
+ continue;
769
+ } else if (!AK_is_digit(d)) {
770
+ break;
771
+ }
772
+ if ((number < pre_max) ||
773
+ ((number == pre_max) && (d - '0' <= dig_pre_max))) {
774
+ number = number * 10 + (d - '0');
775
+ ++p;
776
+ if (p >= end) return number;
777
+ d = *p;
778
+ } else {
779
+ *error = AK_ERROR_OVERFLOW;
780
+ return 0;
781
+ }
782
+ }
783
+ } else {
784
+ while (AK_is_digit(d)) {
785
+ if ((number < pre_max) ||
786
+ ((number == pre_max) && (d - '0' <= dig_pre_max))) {
787
+ number = number * 10 + (d - '0');
788
+ ++p;
789
+ if (p >= end) return number;
790
+ d = *p;
791
+ } else {
792
+ *error = AK_ERROR_OVERFLOW;
793
+ return 0;
794
+ }
795
+ }
796
+ }
797
+ while (p < end) {
798
+ if (!AK_is_space(*p)) {
799
+ *error = AK_ERROR_INVALID_CHARS;
800
+ return 0;
801
+ }
802
+ p++;
803
+ }
804
+ return number;
805
+ }
806
+
807
+ // Based on precise_xstrtod from pandas/_libs/src/parser/tokenizer.c.
808
+ static inline npy_float64
809
+ AK_UCS4_to_float64(Py_UCS4 *p_item, Py_UCS4 *end, int *error, char tsep, char decc)
810
+ {
811
+ // Cache powers of 10 in memory.
812
+ npy_float64 e[] = {
813
+ 1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
814
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
815
+ 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
816
+ 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
817
+ 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
818
+ 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
819
+ 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
820
+ 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79,
821
+ 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89,
822
+ 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99,
823
+ 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
824
+ 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
825
+ 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
826
+ 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
827
+ 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
828
+ 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
829
+ 1e160, 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169,
830
+ 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, 1e179,
831
+ 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, 1e188, 1e189,
832
+ 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, 1e197, 1e198, 1e199,
833
+ 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, 1e206, 1e207, 1e208, 1e209,
834
+ 1e210, 1e211, 1e212, 1e213, 1e214, 1e215, 1e216, 1e217, 1e218, 1e219,
835
+ 1e220, 1e221, 1e222, 1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229,
836
+ 1e230, 1e231, 1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239,
837
+ 1e240, 1e241, 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249,
838
+ 1e250, 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259,
839
+ 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, 1e269,
840
+ 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, 1e278, 1e279,
841
+ 1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, 1e287, 1e288, 1e289,
842
+ 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, 1e296, 1e297, 1e298, 1e299,
843
+ 1e300, 1e301, 1e302, 1e303, 1e304, 1e305, 1e306, 1e307, 1e308};
844
+
845
+ npy_float64 number = 0.0;
846
+ int exponent = 0;
847
+ bool negative_base = false;
848
+ bool negative_e = false;
849
+
850
+ int num_digits = 0;
851
+ int max_digits = 17;
852
+ int n = 0;
853
+
854
+ Py_UCS4 *p = p_item;
855
+ while (AK_is_space(*p)) {
856
+ ++p;
857
+ if (p >= end) return number;
858
+ }
859
+ switch (*p) {
860
+ case '-':
861
+ negative_base = true;
862
+ // fall through
863
+ case '+':
864
+ p++;
865
+ if (p >= end) return number; // nothing to do with sign
866
+ }
867
+ // check for inf, nan
868
+ if (AK_is_i(*p)) {
869
+ p++;
870
+ if (p >= end) goto error;
871
+ if (AK_is_n(*p)) {
872
+ p++;
873
+ if (p >= end) goto error;
874
+ if (AK_is_f(*p)) {
875
+ p++;
876
+ while (p < end) {
877
+ if (!AK_is_space(*p++)) goto error;
878
+ }
879
+ if (negative_base) return -NPY_INFINITY;
880
+ return NPY_INFINITY;
881
+ }
882
+ }
883
+ goto error; // matched i but nothing else
884
+ }
885
+ else if (AK_is_n(*p)) {
886
+ p++;
887
+ if (p >= end) goto error;
888
+ if (AK_is_a(*p)) {
889
+ p++;
890
+ if (p >= end) goto error;
891
+ if (AK_is_n(*p)) {
892
+ p++;
893
+ while (p < end) {
894
+ if (!AK_is_space(*p++)) goto error;
895
+ }
896
+ return NPY_NAN;
897
+ }
898
+ }
899
+ goto error; // matched n but nothing else
900
+ }
901
+ while (AK_is_digit(*p)) {
902
+ if (num_digits < max_digits) {
903
+ number = number * 10. + (*p - '0');
904
+ num_digits++;
905
+ } else {
906
+ ++exponent;
907
+ }
908
+ p++;
909
+ if (p >= end) goto exit;
910
+ if (tsep != '\0' && *p == (Py_UCS4)tsep) {
911
+ ++p;
912
+ if (p >= end) goto exit;
913
+ }
914
+ }
915
+
916
+ if (*p == (Py_UCS4)decc) {
917
+ p++;
918
+ if (p >= end) goto exit;
919
+
920
+ while (num_digits < max_digits && AK_is_digit(*p)) {
921
+ number = number * 10. + (*p - '0');
922
+ num_digits++;
923
+ exponent--;
924
+ p++;
925
+ if (p >= end) goto exit;
926
+ }
927
+ if (num_digits >= max_digits) // Consume extra decimal digits.
928
+ while (AK_is_digit(*p)) {
929
+ ++p;
930
+ if (p >= end) goto exit;
931
+ }
932
+ }
933
+ if (num_digits == 0) {
934
+ *error = ERANGE;
935
+ return 0.0;
936
+ }
937
+ if (AK_is_e(*p)) {
938
+ ++p;
939
+ if (p >= end) goto exit;
940
+
941
+ switch (*p) {
942
+ case '-':
943
+ negative_e = true;
944
+ // fall through
945
+ case '+':
946
+ p++;
947
+ if (p >= end) goto exit; // sign is not used
948
+ }
949
+ num_digits = 0; // reset
950
+ while (num_digits < max_digits && AK_is_digit(*p)) {
951
+ n = n * 10 + (*p - '0');
952
+ num_digits++;
953
+ p++;
954
+ if (p >= end) goto exit;
955
+ }
956
+ }
957
+ // if we have anything but space trailing, error
958
+ while (p < end) {
959
+ if (!AK_is_space(*p++)) goto error;
960
+ }
961
+ exit:
962
+ if (negative_base) number = -number;
963
+ // n will be zero if no E found
964
+ if (negative_e) {
965
+ exponent -= n;
966
+ }
967
+ else {
968
+ exponent += n;
969
+ }
970
+ // AK_DEBUG_MSG_OBJ("at exit", PyLong_FromLong(exponent));
971
+ // done with p at this point
972
+ if (exponent > 308) {
973
+ *error = ERANGE;
974
+ return HUGE_VAL;
975
+ } else if (exponent > 0) {
976
+ number *= e[exponent];
977
+ } else if (exponent < -308) { // Subnormal
978
+ if (exponent < -616) { // Prevent invalid array access.
979
+ number = 0.;
980
+ } else {
981
+ number /= e[-308 - exponent];
982
+ number /= e[308];
983
+ }
984
+ } else {
985
+ number /= e[-exponent];
986
+ }
987
+
988
+ if (number == HUGE_VAL || number == -HUGE_VAL) *error = ERANGE;
989
+ return number;
990
+ error:
991
+ *error = 1;
992
+ return number;
993
+ }
994
+
995
+ //------------------------------------------------------------------------------
996
+ // CodePointLine
997
+
998
+ // An AK_CodePointLine stores a contiguous buffer of Py_UCS4 without null terminators between fields. Separately, we store an array of integers, where each integer is the size of each field. The total number of fields is given by offset_count.
999
+ typedef struct AK_CodePointLine{
1000
+ // NOTE: should these be unsigned int types, like Py_uintptr_t?
1001
+ Py_ssize_t buffer_count; // accumulated number of code points
1002
+ Py_ssize_t buffer_capacity; // max number of code points
1003
+ Py_UCS4 *buffer;
1004
+
1005
+ Py_ssize_t offsets_count; // accumulated number of elements, never reset
1006
+ Py_ssize_t offsets_capacity; // max number of elements
1007
+ Py_ssize_t *offsets;
1008
+ Py_ssize_t offset_max; // observe max offset found across all
1009
+
1010
+ // these can be reset
1011
+ Py_UCS4 *buffer_current_ptr;
1012
+ Py_ssize_t offsets_current_index;
1013
+
1014
+ AK_TypeParser *type_parser;
1015
+ bool type_parser_field_active;
1016
+ bool type_parser_line_active;
1017
+
1018
+ } AK_CodePointLine;
1019
+
1020
+ // Returns NULL on error.
1021
+ static inline AK_CodePointLine *
1022
+ AK_CPL_New(bool type_parse, Py_UCS4 tsep, Py_UCS4 decc)
1023
+ {
1024
+ AK_CodePointLine *cpl = (AK_CodePointLine*)PyMem_Malloc(sizeof(AK_CodePointLine));
1025
+ if (cpl == NULL) return (AK_CodePointLine*)PyErr_NoMemory();
1026
+
1027
+ cpl->buffer_count = 0;
1028
+ cpl->buffer_capacity = 16384; // 2048;
1029
+ cpl->buffer = (Py_UCS4*)PyMem_Malloc(UCS4_SIZE * cpl->buffer_capacity);
1030
+ if (cpl->buffer == NULL) {
1031
+ PyMem_Free(cpl);
1032
+ return (AK_CodePointLine*)PyErr_NoMemory();
1033
+ }
1034
+ cpl->offsets_count = 0;
1035
+ cpl->offsets_capacity = 2048; // 16384; // 2048;
1036
+ cpl->offsets = (Py_ssize_t*)PyMem_Malloc(sizeof(Py_ssize_t) * cpl->offsets_capacity);
1037
+ if (cpl->offsets == NULL) {
1038
+ PyMem_Free(cpl->buffer);
1039
+ PyMem_Free(cpl);
1040
+ return (AK_CodePointLine*)PyErr_NoMemory();
1041
+ }
1042
+ cpl->buffer_current_ptr = cpl->buffer;
1043
+ cpl->offsets_current_index = 0; // position in offsets
1044
+ cpl->offset_max = 0;
1045
+
1046
+ // optional, dynamic values
1047
+ if (type_parse) {
1048
+ cpl->type_parser = AK_TP_New(tsep, decc);
1049
+ if (cpl->type_parser == NULL) {
1050
+ PyMem_Free(cpl->offsets);
1051
+ PyMem_Free(cpl->buffer);
1052
+ PyMem_Free(cpl);
1053
+ return NULL; // exception already set
1054
+ }
1055
+ cpl->type_parser_field_active = true;
1056
+ cpl->type_parser_line_active = true;
1057
+ }
1058
+ else {
1059
+ cpl->type_parser = NULL;
1060
+ cpl->type_parser_field_active = false;
1061
+ cpl->type_parser_line_active = false;
1062
+ }
1063
+ return cpl;
1064
+ }
1065
+
1066
+ static inline void
1067
+ AK_CPL_Free(AK_CodePointLine* cpl)
1068
+ {
1069
+ PyMem_Free(cpl->buffer);
1070
+ PyMem_Free(cpl->offsets);
1071
+ if (cpl->type_parser) {
1072
+ AK_TP_Free(cpl->type_parser);
1073
+ }
1074
+ PyMem_Free(cpl);
1075
+ }
1076
+
1077
+ //------------------------------------------------------------------------------
1078
+ // CodePointLine: Mutation
1079
+
1080
+ // Returns 0 on success, -1 on failure.
1081
+ static inline int
1082
+ AK_CPL_resize_buffer(AK_CodePointLine* cpl, Py_ssize_t increment) {
1083
+ Py_ssize_t target = cpl->buffer_count + increment;
1084
+ if (AK_UNLIKELY(target >= cpl->buffer_capacity)) {
1085
+ while (cpl->buffer_capacity < target) {
1086
+ cpl->buffer_capacity <<= 1;
1087
+ }
1088
+ cpl->buffer = PyMem_Realloc(cpl->buffer,
1089
+ UCS4_SIZE * cpl->buffer_capacity);
1090
+ if (cpl->buffer == NULL) {
1091
+ return -1;
1092
+ }
1093
+ cpl->buffer_current_ptr = cpl->buffer + cpl->buffer_count;
1094
+ }
1095
+ return 0;
1096
+ }
1097
+
1098
+ // NOTE: we only add one offset at time, so this does not need to take an increment argument.
1099
+ static inline int
1100
+ AK_CPL_resize_offsets(AK_CodePointLine* cpl) {
1101
+ // increment by at most one, so only need to check if equal
1102
+ if (AK_UNLIKELY(cpl->offsets_count == cpl->offsets_capacity)) {
1103
+ // realloc
1104
+ cpl->offsets_capacity <<= 1;
1105
+ cpl->offsets = PyMem_Realloc(cpl->offsets,
1106
+ sizeof(Py_ssize_t) * cpl->offsets_capacity);
1107
+ if (cpl->offsets == NULL) {
1108
+ return -1;
1109
+ }
1110
+ }
1111
+ return 0;
1112
+ }
1113
+
1114
+ // Given a PyUnicode PyObject representing a complete field, load the string content into the CPL. Used for iterable_str_to_array_1d. Returns 0 on success, -1 on error.
1115
+ static inline int
1116
+ AK_CPL_AppendField(AK_CodePointLine* cpl, PyObject* field)
1117
+ {
1118
+ if (!PyUnicode_Check(field)) { // NOTE: this permits subclasses, consider
1119
+ PyErr_SetString(PyExc_TypeError, "elements must be strings");
1120
+ return -1;
1121
+ }
1122
+ Py_ssize_t element_length = PyUnicode_GET_LENGTH(field);
1123
+
1124
+ // if we cannot fit field length, resize
1125
+ if (AK_CPL_resize_buffer(cpl, element_length)) {
1126
+ return -1;
1127
+ }
1128
+
1129
+ // we write the field directly into the CPL buffer
1130
+ if(PyUnicode_AsUCS4(field,
1131
+ cpl->buffer_current_ptr,
1132
+ cpl->buffer + cpl->buffer_capacity - cpl->buffer_current_ptr,
1133
+ 0) == NULL) { // last zero means do not copy null
1134
+ return -1;
1135
+ }
1136
+ // if type parsing has been enabled, we must process each char
1137
+ if (cpl->type_parser && cpl->type_parser_line_active) {
1138
+ Py_UCS4* p = cpl->buffer_current_ptr;
1139
+ Py_UCS4 *end = p + element_length;
1140
+ Py_ssize_t pos = 0;
1141
+ for (; p < end; ++p) {
1142
+ cpl->type_parser_field_active = AK_TP_ProcessChar(
1143
+ cpl->type_parser,
1144
+ *p,
1145
+ pos);
1146
+ if (!cpl->type_parser_field_active) break;
1147
+ ++pos;
1148
+ }
1149
+ cpl->type_parser_line_active = AK_TP_ResolveLineResetField(cpl->type_parser, element_length);
1150
+ cpl->type_parser_field_active = true; // turn back on for next field
1151
+ }
1152
+
1153
+ // read offset_count, then increment
1154
+ if (AK_CPL_resize_offsets(cpl)) return -1;
1155
+ cpl->offsets[cpl->offsets_count++] = element_length;
1156
+ cpl->buffer_count += element_length;
1157
+ cpl->buffer_current_ptr += element_length; // add to pointer
1158
+
1159
+ if (element_length > cpl->offset_max) {
1160
+ cpl->offset_max = element_length;
1161
+ }
1162
+ return 0;
1163
+ }
1164
+
1165
+ // Add a single point (or character) to a line. This does not update offsets. This is valid when updating a character. Returns 0 on success, -1 on error.
1166
+ static inline int
1167
+ AK_CPL_AppendPoint(AK_CodePointLine* cpl,
1168
+ Py_UCS4 p,
1169
+ Py_ssize_t pos)
1170
+ {
1171
+ // based on buffer_count, resize if we cannot fit one more character
1172
+ if (AK_CPL_resize_buffer(cpl, 1)) return -1;
1173
+
1174
+ // type_parser might not be active if we already know the dtype
1175
+ if (cpl->type_parser
1176
+ && cpl->type_parser_line_active
1177
+ && cpl->type_parser_field_active) {
1178
+ cpl->type_parser_field_active = AK_TP_ProcessChar(
1179
+ cpl->type_parser,
1180
+ p,
1181
+ pos);
1182
+ }
1183
+ *cpl->buffer_current_ptr++ = p;
1184
+ ++cpl->buffer_count;
1185
+ return 0;
1186
+ }
1187
+
1188
+ // Append to offsets. This does not update buffer lines. This is called when closing a field. Return -1 on failure, 0 on success.
1189
+ static inline int
1190
+ AK_CPL_AppendOffset(AK_CodePointLine* cpl, Py_ssize_t offset)
1191
+ {
1192
+ // this will update cpl->offsets if necessary
1193
+ if (AK_CPL_resize_offsets(cpl)) return -1;
1194
+
1195
+ if (cpl->type_parser && cpl->type_parser_line_active) {
1196
+ // when we resolve the line, we might determine that no further line processing is necessary
1197
+ cpl->type_parser_line_active = AK_TP_ResolveLineResetField(
1198
+ cpl->type_parser,
1199
+ offset);
1200
+ // NOTE: always turn on for next field; we choose not to check type_parser_line_active
1201
+ cpl->type_parser_field_active = true;
1202
+ }
1203
+ // increment offset_count after assignment so we can grow if needed next time
1204
+ cpl->offsets[cpl->offsets_count++] = offset;
1205
+ if (offset > cpl->offset_max) {
1206
+ cpl->offset_max = offset;
1207
+ }
1208
+ return 0;
1209
+ }
1210
+
1211
+ //------------------------------------------------------------------------------
1212
+ // CodePointLine: Constructors
1213
+
1214
+ // Given an iterable of unicode objects, load them into a AK_CodePointLine. Used for iterable_str_to_array_1d. Return NULL on error.
1215
+ static inline AK_CodePointLine *
1216
+ AK_CPL_FromIterable(PyObject* iterable, bool type_parse, Py_UCS4 tsep, Py_UCS4 decc)
1217
+ {
1218
+ PyObject *iter = PyObject_GetIter(iterable);
1219
+ if (iter == NULL) return NULL;
1220
+
1221
+ AK_CodePointLine *cpl = AK_CPL_New(type_parse, tsep, decc);
1222
+ if (cpl == NULL) {
1223
+ Py_DECREF(iter);
1224
+ return NULL;
1225
+ }
1226
+
1227
+ PyObject *field;
1228
+ while ((field = PyIter_Next(iter))) {
1229
+ if (AK_CPL_AppendField(cpl, field)) {
1230
+ Py_DECREF(field);
1231
+ Py_DECREF(iter);
1232
+ return NULL;
1233
+ }
1234
+ Py_DECREF(field);
1235
+ }
1236
+ Py_DECREF(iter);
1237
+ if (PyErr_Occurred()) {
1238
+ return NULL;
1239
+ }
1240
+ return cpl;
1241
+ }
1242
+
1243
+ //------------------------------------------------------------------------------
1244
+ // CodePointLine: Navigation
1245
+
1246
+ // Cannot error.
1247
+ static inline void
1248
+ AK_CPL_CurrentReset(AK_CodePointLine* cpl)
1249
+ {
1250
+ cpl->buffer_current_ptr = cpl->buffer;
1251
+ cpl->offsets_current_index = 0;
1252
+ }
1253
+
1254
+ // Advance the current position by the current offset. Cannot error.
1255
+ static inline void
1256
+ AK_CPL_CurrentAdvance(AK_CodePointLine* cpl)
1257
+ {
1258
+ // use offsets_current_index, then increment
1259
+ cpl->buffer_current_ptr += cpl->offsets[cpl->offsets_current_index++];
1260
+ }
1261
+
1262
+ // This will take any case of "TRUE" as True, while marking everything else as False; this is the same approach taken with genfromtxt when the dtype is given as bool. This will not fail for invalid true or false strings.
1263
+ static inline npy_int8
1264
+ AK_CPL_current_to_bool(AK_CodePointLine* cpl) {
1265
+ // must have at least 4 characters
1266
+ if (cpl->offsets[cpl->offsets_current_index] < 4) {
1267
+ return 0;
1268
+ }
1269
+ Py_UCS4 *p = cpl->buffer_current_ptr;
1270
+ Py_UCS4 *end = p + 4; // we must have at least 4 characters for True
1271
+ int i = 0;
1272
+ char c;
1273
+
1274
+ while (AK_is_space(*p)) p++;
1275
+
1276
+ for (;p < end; ++p) {
1277
+ c = *p;
1278
+ if (c == TRUE_LOWER[i] || c == TRUE_UPPER[i]) {
1279
+ ++i;
1280
+ }
1281
+ else {
1282
+ return 0;
1283
+ }
1284
+ }
1285
+ return 1; //matched all characters
1286
+ }
1287
+
1288
+ // NOTE: using PyOS_strtol was an alternative, but needed to be passed a null-terminated char, which would require copying the data out of the CPL. This approach reads directly from the CPL without copying.
1289
+ static inline npy_int64
1290
+ AK_CPL_current_to_int64(AK_CodePointLine* cpl, int *error, char tsep)
1291
+ {
1292
+ Py_UCS4 *p = cpl->buffer_current_ptr;
1293
+ Py_UCS4 *end = p + cpl->offsets[cpl->offsets_current_index]; // size is either 4 or 5
1294
+ return AK_UCS4_to_int64(p, end, error, tsep);
1295
+ }
1296
+
1297
+ // Provide start and end buffer positions to provide a range of bytes to read and transform into an integer. Returns 0 on error; does not set exception.
1298
+ static inline npy_uint64
1299
+ AK_CPL_current_to_uint64(AK_CodePointLine* cpl, int *error, char tsep)
1300
+ {
1301
+ Py_UCS4 *p = cpl->buffer_current_ptr;
1302
+ Py_UCS4 *end = p + cpl->offsets[cpl->offsets_current_index];
1303
+ return AK_UCS4_to_uint64(p, end, error, tsep);
1304
+ }
1305
+
1306
+ static inline npy_float64
1307
+ AK_CPL_current_to_float64(AK_CodePointLine* cpl, int *error, char tsep, char decc)
1308
+ {
1309
+ // interpret an empty field as NaN
1310
+ if (cpl->offsets[cpl->offsets_current_index] == 0) {
1311
+ return NPY_NAN;
1312
+ }
1313
+ Py_UCS4 *p = cpl->buffer_current_ptr;
1314
+ Py_UCS4 *end = p + cpl->offsets[cpl->offsets_current_index];
1315
+ return AK_UCS4_to_float64(p, end, error, tsep, decc);
1316
+ }
1317
+
1318
+ //------------------------------------------------------------------------------
1319
+ // CodePointLine: Exporters
1320
+
1321
+ static inline PyObject *
1322
+ AK_CPL_to_array_bool(AK_CodePointLine* cpl, PyArray_Descr* dtype)
1323
+ {
1324
+ npy_intp dims[] = {cpl->offsets_count};
1325
+
1326
+ // initialize all values to False
1327
+ PyObject *array = PyArray_Zeros(1, dims, dtype, 0); // steals dtype ref
1328
+ if (array == NULL) {
1329
+ return NULL;
1330
+ }
1331
+
1332
+ npy_bool *array_buffer = (npy_bool*)PyArray_DATA((PyArrayObject*)array);
1333
+
1334
+ NPY_BEGIN_THREADS_DEF;
1335
+ NPY_BEGIN_THREADS;
1336
+
1337
+ AK_CPL_CurrentReset(cpl);
1338
+ for (Py_ssize_t i=0; i < cpl->offsets_count; ++i) {
1339
+ // this is forgiving in that invalid strings remain false
1340
+ if (AK_CPL_current_to_bool(cpl)) {
1341
+ array_buffer[i] = 1;
1342
+ }
1343
+ AK_CPL_CurrentAdvance(cpl);
1344
+ }
1345
+ NPY_END_THREADS;
1346
+
1347
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1348
+ return array;
1349
+ }
1350
+
1351
+ // Given a type of signed integer, return the corresponding array.
1352
+ static inline PyObject *
1353
+ AK_CPL_to_array_float(AK_CodePointLine* cpl, PyArray_Descr* dtype, char tsep, char decc)
1354
+ {
1355
+ Py_ssize_t count = cpl->offsets_count;
1356
+ npy_intp dims[] = {count};
1357
+
1358
+ // NOTE: empty preferred over zeros
1359
+ PyObject *array = PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1360
+ if (array == NULL) {
1361
+ return NULL;
1362
+ }
1363
+
1364
+ // initialize error code to 0; only update on error.
1365
+ int error = 0;
1366
+ bool matched_elsize = true;
1367
+ int elsize = PyDataType_ELSIZE(dtype);
1368
+
1369
+ NPY_BEGIN_THREADS_DEF;
1370
+ NPY_BEGIN_THREADS;
1371
+
1372
+ AK_CPL_CurrentReset(cpl);
1373
+
1374
+ if (elsize == 16) {
1375
+ # ifdef PyFloat128ArrType_Type
1376
+ npy_float128 *array_buffer = (npy_float128*)PyArray_DATA((PyArrayObject*)array);
1377
+ npy_float128 *end = array_buffer + count;
1378
+ while (array_buffer < end) {
1379
+ // NOTE: cannot cast to npy_float128 here
1380
+ *array_buffer++ = AK_CPL_current_to_float64(cpl, &error, tsep, decc);
1381
+ AK_CPL_CurrentAdvance(cpl);
1382
+ }
1383
+ # endif
1384
+ }
1385
+ else if (elsize == 8) {
1386
+ npy_float64 *array_buffer = (npy_float64*)PyArray_DATA((PyArrayObject*)array);
1387
+ npy_float64 *end = array_buffer + count;
1388
+ while (array_buffer < end) {
1389
+ *array_buffer++ = AK_CPL_current_to_float64(cpl, &error, tsep, decc);
1390
+ AK_CPL_CurrentAdvance(cpl);
1391
+ }
1392
+ }
1393
+ else if (elsize == 4) {
1394
+ npy_float32 *array_buffer = (npy_float32*)PyArray_DATA((PyArrayObject*)array);
1395
+ npy_float32 *end = array_buffer + count;
1396
+ while (array_buffer < end) {
1397
+ *array_buffer++ = (npy_float32)AK_CPL_current_to_float64(cpl, &error, tsep, decc);
1398
+ AK_CPL_CurrentAdvance(cpl);
1399
+ }
1400
+ }
1401
+ else if (elsize == 2) {
1402
+ npy_float16 *array_buffer = (npy_float16*)PyArray_DATA((PyArrayObject*)array);
1403
+ npy_float16 *end = array_buffer + count;
1404
+ while (array_buffer < end) {
1405
+ *array_buffer++ = npy_double_to_half(AK_CPL_current_to_float64(cpl, &error, tsep, decc));
1406
+ AK_CPL_CurrentAdvance(cpl);
1407
+ }
1408
+ }
1409
+ else {
1410
+ matched_elsize = false;
1411
+ }
1412
+
1413
+ NPY_END_THREADS;
1414
+
1415
+ if (!matched_elsize) {
1416
+ PyErr_SetString(PyExc_TypeError, "cannot create array from itemsize");
1417
+ Py_DECREF(array);
1418
+ return NULL;
1419
+ }
1420
+ if (error) {
1421
+ PyErr_SetString(PyExc_TypeError, "error parsing float");
1422
+ Py_DECREF(array);
1423
+ return NULL;
1424
+ }
1425
+
1426
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1427
+ return array;
1428
+ }
1429
+
1430
+ // Given a type of signed integer, return the corresponding array.
1431
+ static inline PyObject *
1432
+ AK_CPL_to_array_int(AK_CodePointLine* cpl, PyArray_Descr* dtype, char tsep)
1433
+ {
1434
+ Py_ssize_t count = cpl->offsets_count;
1435
+ npy_intp dims[] = {count};
1436
+
1437
+ // NOTE: empty prefered over zeros
1438
+ PyObject *array = PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1439
+ if (array == NULL) {
1440
+ return NULL;
1441
+ }
1442
+ // initialize error code to 0; only update on error.
1443
+ int error = 0;
1444
+ bool matched_elsize = true;
1445
+ int elsize = PyDataType_ELSIZE(dtype);
1446
+
1447
+ NPY_BEGIN_THREADS_DEF;
1448
+ NPY_BEGIN_THREADS;
1449
+
1450
+ AK_CPL_CurrentReset(cpl);
1451
+ if (elsize == 8) {
1452
+ npy_int64 *array_buffer = (npy_int64*)PyArray_DATA((PyArrayObject*)array);
1453
+ npy_int64 *end = array_buffer + count;
1454
+ while (array_buffer < end) {
1455
+ *array_buffer++ = AK_CPL_current_to_int64(cpl, &error, tsep);
1456
+ AK_CPL_CurrentAdvance(cpl);
1457
+ }
1458
+ }
1459
+ else if (elsize == 4) {
1460
+ npy_int32 *array_buffer = (npy_int32*)PyArray_DATA((PyArrayObject*)array);
1461
+ npy_int32 *end = array_buffer + count;
1462
+ while (array_buffer < end) {
1463
+ *array_buffer++ = (npy_int32)AK_CPL_current_to_int64(cpl, &error, tsep);
1464
+ AK_CPL_CurrentAdvance(cpl);
1465
+ }
1466
+ }
1467
+ else if (elsize == 2) {
1468
+ npy_int16 *array_buffer = (npy_int16*)PyArray_DATA((PyArrayObject*)array);
1469
+ npy_int16 *end = array_buffer + count;
1470
+ while (array_buffer < end) {
1471
+ *array_buffer++ = (npy_int16)AK_CPL_current_to_int64(cpl, &error, tsep);
1472
+ AK_CPL_CurrentAdvance(cpl);
1473
+ }
1474
+ }
1475
+ else if (elsize == 1) {
1476
+ npy_int8 *array_buffer = (npy_int8*)PyArray_DATA((PyArrayObject*)array);
1477
+ npy_int8 *end = array_buffer + count;
1478
+ while (array_buffer < end) {
1479
+ *array_buffer++ = (npy_int8)AK_CPL_current_to_int64(cpl, &error, tsep);
1480
+ AK_CPL_CurrentAdvance(cpl);
1481
+ }
1482
+ }
1483
+ else {
1484
+ matched_elsize = false;
1485
+ }
1486
+ NPY_END_THREADS;
1487
+
1488
+ if (!matched_elsize) {
1489
+ PyErr_SetString(PyExc_TypeError, "cannot create array from integer itemsize");
1490
+ Py_DECREF(array);
1491
+ return NULL;
1492
+ }
1493
+ if (error) {
1494
+ PyErr_SetString(PyExc_TypeError, "error parsing integer");
1495
+ Py_DECREF(array);
1496
+ return NULL;
1497
+ }
1498
+
1499
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1500
+ return array;
1501
+ }
1502
+
1503
+ // Given a type of signed integer, return the corresponding array. Return NULL on error.
1504
+ static inline PyObject *
1505
+ AK_CPL_to_array_uint(AK_CodePointLine* cpl, PyArray_Descr* dtype, char tsep)
1506
+ {
1507
+ Py_ssize_t count = cpl->offsets_count;
1508
+ npy_intp dims[] = {count};
1509
+
1510
+ // NOTE: empty prefered over zeros
1511
+ PyObject *array = PyArray_Empty(1, dims, dtype, 0); // steals dtype ref
1512
+ if (array == NULL) {
1513
+ return NULL;
1514
+ }
1515
+ // initialize error code to 0; only update on error.
1516
+ int error = 0;
1517
+ bool matched_elsize = true;
1518
+ int elsize = PyDataType_ELSIZE(dtype);
1519
+
1520
+ NPY_BEGIN_THREADS_DEF;
1521
+ NPY_BEGIN_THREADS;
1522
+
1523
+ AK_CPL_CurrentReset(cpl);
1524
+ if (elsize == 8) {
1525
+ npy_uint64 *array_buffer = (npy_uint64*)PyArray_DATA((PyArrayObject*)array);
1526
+ npy_uint64 *end = array_buffer + count;
1527
+ while (array_buffer < end) {
1528
+ *array_buffer++ = AK_CPL_current_to_uint64(cpl, &error, tsep);
1529
+ AK_CPL_CurrentAdvance(cpl);
1530
+ }
1531
+ }
1532
+ else if (elsize == 4) {
1533
+ npy_uint32 *array_buffer = (npy_uint32*)PyArray_DATA((PyArrayObject*)array);
1534
+ npy_uint32 *end = array_buffer + count;
1535
+ while (array_buffer < end) {
1536
+ *array_buffer++ = (npy_uint32)AK_CPL_current_to_uint64(cpl, &error, tsep);
1537
+ AK_CPL_CurrentAdvance(cpl);
1538
+ }
1539
+ }
1540
+ else if (elsize == 2) {
1541
+ npy_uint16 *array_buffer = (npy_uint16*)PyArray_DATA((PyArrayObject*)array);
1542
+ npy_uint16 *end = array_buffer + count;
1543
+ while (array_buffer < end) {
1544
+ *array_buffer++ = (npy_uint16)AK_CPL_current_to_uint64(cpl, &error, tsep);
1545
+ AK_CPL_CurrentAdvance(cpl);
1546
+ }
1547
+ }
1548
+ else if (elsize == 1) {
1549
+ npy_uint8 *array_buffer = (npy_uint8*)PyArray_DATA((PyArrayObject*)array);
1550
+ npy_uint8 *end = array_buffer + count;
1551
+ while (array_buffer < end) {
1552
+ *array_buffer++ = (npy_uint8)AK_CPL_current_to_uint64(cpl, &error, tsep);
1553
+ AK_CPL_CurrentAdvance(cpl);
1554
+ }
1555
+ }
1556
+ else {
1557
+ matched_elsize = false;
1558
+ }
1559
+ NPY_END_THREADS;
1560
+
1561
+ if (!matched_elsize) {
1562
+ PyErr_SetString(PyExc_TypeError, "cannot create array from unsigned integer itemsize");
1563
+ Py_DECREF(array);
1564
+ return NULL;
1565
+ }
1566
+ if (error != 0) {
1567
+ PyErr_SetString(PyExc_TypeError, "error parsing unisigned integer");
1568
+ Py_DECREF(array);
1569
+ return NULL;
1570
+ }
1571
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1572
+ return array;
1573
+ }
1574
+
1575
+ static inline PyObject *
1576
+ AK_CPL_to_array_unicode(AK_CodePointLine* cpl, PyArray_Descr* dtype)
1577
+ {
1578
+ Py_ssize_t count = cpl->offsets_count;
1579
+ npy_intp dims[] = {count};
1580
+
1581
+ Py_ssize_t field_points;
1582
+ // If `capped_points` is True, we have been given a dtype with specific elsize, and we will only load that many code points; if `capper_points` is False, we set the dtype elsize to the max observed code opints via the CPL offset.
1583
+ bool capped_points;
1584
+
1585
+ // mutate the passed dtype as it is new and will be stolen in array construction
1586
+ if (PyDataType_ELSIZE(dtype) == 0) {
1587
+ field_points = cpl->offset_max;
1588
+ // dtype->elsize = (int)(field_points * UCS4_SIZE);
1589
+ PyDataType_SET_ELSIZE(dtype, (npy_intp)(field_points * UCS4_SIZE));
1590
+ capped_points = false;
1591
+ }
1592
+ else {
1593
+ // assume that elsize is already given in units of 4
1594
+ field_points = PyDataType_ELSIZE(dtype) / UCS4_SIZE;
1595
+ capped_points = true;
1596
+ }
1597
+
1598
+ // NOTE: it is assumed (though not verified in some testing) that we need to get zeroed array here as we might copy to the array with less than the full item size width
1599
+ PyObject *array = PyArray_Zeros(1, dims, dtype, 0); // increfs dtype
1600
+ if (array == NULL) {
1601
+ // expected array to steal dtype reference
1602
+ return NULL;
1603
+ }
1604
+
1605
+ Py_UCS4 *array_buffer = (Py_UCS4*)PyArray_DATA((PyArrayObject*)array);
1606
+ Py_UCS4 *end = array_buffer + count * field_points;
1607
+
1608
+ NPY_BEGIN_THREADS_DEF;
1609
+ NPY_BEGIN_THREADS;
1610
+
1611
+ AK_CPL_CurrentReset(cpl);
1612
+ if (capped_points) {
1613
+ Py_ssize_t copy_bytes;
1614
+ while (array_buffer < end) {
1615
+ if (cpl->offsets[cpl->offsets_current_index] >= field_points) {
1616
+ copy_bytes = field_points * UCS4_SIZE;
1617
+ } else {
1618
+ copy_bytes = cpl->offsets[cpl->offsets_current_index] * UCS4_SIZE;
1619
+ }
1620
+ memcpy(array_buffer,
1621
+ cpl->buffer_current_ptr,
1622
+ copy_bytes);
1623
+ array_buffer += field_points;
1624
+ AK_CPL_CurrentAdvance(cpl);
1625
+ }
1626
+ }
1627
+ else { // faster if we always know the offset will fit
1628
+ while (array_buffer < end) {
1629
+ memcpy(array_buffer,
1630
+ cpl->buffer_current_ptr,
1631
+ cpl->offsets[cpl->offsets_current_index] * UCS4_SIZE);
1632
+ array_buffer += field_points;
1633
+ AK_CPL_CurrentAdvance(cpl);
1634
+ }
1635
+ }
1636
+ NPY_END_THREADS;
1637
+
1638
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1639
+ return array;
1640
+ }
1641
+
1642
+ // Return NULL on error
1643
+ static inline PyObject *
1644
+ AK_CPL_to_array_bytes(AK_CodePointLine* cpl, PyArray_Descr* dtype)
1645
+ {
1646
+ Py_ssize_t count = cpl->offsets_count;
1647
+ npy_intp dims[] = {count};
1648
+
1649
+ Py_ssize_t field_points;
1650
+ bool capped_points;
1651
+
1652
+ if (PyDataType_ELSIZE(dtype) == 0) {
1653
+ field_points = cpl->offset_max;
1654
+ // dtype->elsize = (int)field_points;
1655
+ PyDataType_SET_ELSIZE(dtype, (npy_intp)field_points);
1656
+ capped_points = false;
1657
+ }
1658
+ else {
1659
+ field_points = PyDataType_ELSIZE(dtype);
1660
+ capped_points = true;
1661
+ }
1662
+
1663
+ PyObject *array = PyArray_Zeros(1, dims, dtype, 0);
1664
+ if (array == NULL) {
1665
+ // expected array to steal dtype reference
1666
+ return NULL;
1667
+ }
1668
+
1669
+ char *array_buffer = (char*)PyArray_DATA((PyArrayObject*)array);
1670
+ char *end = array_buffer + count * field_points;
1671
+ char *field_end;
1672
+
1673
+ Py_ssize_t copy_points;
1674
+ Py_UCS4 *p;
1675
+ Py_UCS4 *p_end;
1676
+
1677
+ NPY_BEGIN_THREADS_DEF;
1678
+ NPY_BEGIN_THREADS;
1679
+
1680
+ AK_CPL_CurrentReset(cpl);
1681
+ while (array_buffer < end) {
1682
+ if (!capped_points || cpl->offsets[cpl->offsets_current_index] < field_points) {
1683
+ // if not capped, or capped and offset is less than field points, use offset
1684
+ copy_points = cpl->offsets[cpl->offsets_current_index];
1685
+ }
1686
+ else {
1687
+ // if capped and offset is greater than field points, use field points
1688
+ copy_points = field_points;
1689
+ }
1690
+ // NOTE: not using memcopy as we need to cast to char to fit each point
1691
+ p = cpl->buffer_current_ptr;
1692
+ p_end = p + copy_points;
1693
+ field_end = array_buffer + field_points;
1694
+
1695
+ while (p < p_end) {
1696
+ *array_buffer++ = (char)*p++; // truncate
1697
+ }
1698
+ array_buffer = field_end; // jump to end regardless of how many chars written
1699
+ AK_CPL_CurrentAdvance(cpl);
1700
+ }
1701
+ NPY_END_THREADS;
1702
+
1703
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1704
+ return array;
1705
+ }
1706
+
1707
+ // If we cannot directly convert bytes to values in a pre-loaded array, we can create a bytes or unicode array and then use PyArray_CastToType to use numpy to interpret it as a new a array and handle conversions. Note that we can use bytes for a smaller memory load if we are confident that the values are not unicode. This is a safe assumption for complex. For datetime64, we have to use Unicode to get errors on malformed inputs: using bytes causes a seg fault with these interfaces (the same is not observed with astyping a byte array in Python).
1708
+ static inline PyObject *
1709
+ AK_CPL_to_array_via_cast(AK_CodePointLine* cpl,
1710
+ PyArray_Descr* dtype,
1711
+ int type_inter)
1712
+ {
1713
+ PyArray_Descr* dtype_inter; // interchange array
1714
+ PyObject* array_inter = NULL;
1715
+
1716
+ dtype_inter = PyArray_DescrNewFromType(type_inter);
1717
+ if (dtype_inter == NULL) {
1718
+ Py_DECREF(dtype);
1719
+ return NULL;
1720
+ }
1721
+ if (type_inter == NPY_STRING) {
1722
+ array_inter = AK_CPL_to_array_bytes(cpl, dtype_inter);
1723
+ }
1724
+ else if (type_inter == NPY_UNICODE) {
1725
+ array_inter = AK_CPL_to_array_unicode(cpl, dtype_inter);
1726
+ }
1727
+ // else array_inter is NULL and we exit without an exception set
1728
+ if (array_inter == NULL) {
1729
+ Py_DECREF(dtype); // dtype_inter ref already stolen
1730
+ return NULL;
1731
+ }
1732
+
1733
+ PyObject *array = PyArray_CastToType((PyArrayObject*)array_inter, dtype, 0);
1734
+ Py_DECREF(array_inter);
1735
+ if (array == NULL) { // dtype ref already stolen
1736
+ return NULL;
1737
+ }
1738
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
1739
+ return array;
1740
+ }
1741
+
1742
+ // Generic handler for converting a CPL to an array. The dtype given here must already be a fresh instance as it might be mutated. If passed dtype is NULL, must get dtype from type_parser-> parsed_line Might return NULL if array creation fails; an exception should be set. Will return NULL on error.
1743
+ static inline PyObject *
1744
+ AK_CPL_ToArray(AK_CodePointLine* cpl,
1745
+ PyArray_Descr* dtype,
1746
+ char tsep,
1747
+ char decc)
1748
+ {
1749
+ if (!dtype) {
1750
+ // If we have a type_parser on the CPL, we can use that to get the dtype
1751
+ if (cpl->type_parser) {
1752
+ // will return a fresh instance
1753
+ dtype = AK_TPS_ToDtype(cpl->type_parser->parsed_line);
1754
+ if (dtype == NULL) return NULL;
1755
+ }
1756
+ else {
1757
+ AK_NOT_IMPLEMENTED("dtype not passed to AK_CPL_ToArray, and CodePointLine has no type_parser");
1758
+ }
1759
+ }
1760
+ switch (dtype->kind) {
1761
+ case 'i':
1762
+ return AK_CPL_to_array_int(cpl, dtype, tsep);
1763
+ case 'f':
1764
+ return AK_CPL_to_array_float(cpl, dtype, tsep, decc);
1765
+ case 'U':
1766
+ return AK_CPL_to_array_unicode(cpl, dtype);
1767
+ case 'M':
1768
+ return AK_CPL_to_array_via_cast(cpl, dtype, NPY_UNICODE);
1769
+ case 'b':
1770
+ return AK_CPL_to_array_bool(cpl, dtype);
1771
+ case 'S':
1772
+ return AK_CPL_to_array_bytes(cpl, dtype);
1773
+ case 'u':
1774
+ return AK_CPL_to_array_uint(cpl, dtype, tsep);
1775
+ case 'c': // cannot pass tsep, decc as using NumPy cast
1776
+ return AK_CPL_to_array_via_cast(cpl, dtype, NPY_STRING);
1777
+ }
1778
+ PyErr_Format(PyExc_NotImplementedError, "No handling for %R", dtype);
1779
+ // caller will decref the passed dtype on error
1780
+ return NULL;
1781
+ }
1782
+
1783
+ //------------------------------------------------------------------------------
1784
+ // utility function used by CPG and DR
1785
+
1786
+ static inline int
1787
+ AK_line_select_keep(
1788
+ PyObject *line_select,
1789
+ bool axis_target,
1790
+ Py_ssize_t lookup_number)
1791
+ {
1792
+ if (axis_target && (line_select != NULL)) {
1793
+ PyObject* number = PyLong_FromSsize_t(lookup_number);
1794
+ if (number == NULL) return -1;
1795
+
1796
+ PyObject* keep = PyObject_CallFunctionObjArgs(
1797
+ line_select,
1798
+ number,
1799
+ NULL
1800
+ );
1801
+ Py_DECREF(number);
1802
+ if (keep == NULL) {
1803
+ PyErr_Format(PyExc_RuntimeError,
1804
+ "line_select callable failed for input: %d",
1805
+ lookup_number
1806
+ );
1807
+ return -1;
1808
+ }
1809
+
1810
+ int t = PyObject_IsTrue(keep); // 1 if truthy
1811
+ Py_DECREF(keep);
1812
+ if (t < 0) {
1813
+ return -1; // error
1814
+ }
1815
+ return t; // 0 or 1
1816
+ }
1817
+ return 1;
1818
+ }
1819
+
1820
+ //------------------------------------------------------------------------------
1821
+ // CodePointGrid Type, New, Destructor
1822
+
1823
+ typedef struct AK_CodePointGrid {
1824
+ Py_ssize_t lines_count; // accumulated number of lines
1825
+ Py_ssize_t lines_capacity; // max number of lines
1826
+ AK_CodePointLine **lines; // array of pointers
1827
+ PyObject *dtypes; // a callable that returns None or a dtype initializer
1828
+ Py_UCS4 tsep;
1829
+ Py_UCS4 decc;
1830
+ } AK_CodePointGrid;
1831
+
1832
+ // Create a new Code Point Grid; returns NULL on error. Missing `dtypes` has been normalized as NULL.
1833
+ static inline AK_CodePointGrid *
1834
+ AK_CPG_New(PyObject *dtypes, Py_UCS4 tsep, Py_UCS4 decc)
1835
+ {
1836
+ // normalize dtypes to NULL or callable
1837
+ if ((dtypes == NULL) || (dtypes == Py_None)) {
1838
+ dtypes = NULL;
1839
+ }
1840
+ else if (!PyCallable_Check(dtypes)) {
1841
+ PyErr_SetString(PyExc_TypeError, "dtypes must be a callable or None");
1842
+ return NULL;
1843
+ }
1844
+
1845
+ AK_CodePointGrid *cpg = (AK_CodePointGrid*)PyMem_Malloc(sizeof(AK_CodePointGrid));
1846
+ if (cpg == NULL) return (AK_CodePointGrid*)PyErr_NoMemory();
1847
+
1848
+ cpg->tsep = tsep;
1849
+ cpg->decc = decc;
1850
+ cpg->lines_count = 0;
1851
+ cpg->lines_capacity = 1024;
1852
+ cpg->lines = (AK_CodePointLine**)PyMem_Malloc(
1853
+ sizeof(AK_CodePointLine*) * cpg->lines_capacity);
1854
+ if (cpg->lines == NULL) return (AK_CodePointGrid*)PyErr_NoMemory();
1855
+
1856
+ cpg->dtypes = dtypes;
1857
+ return cpg;
1858
+ }
1859
+
1860
+ static inline void
1861
+ AK_CPG_Free(AK_CodePointGrid* cpg)
1862
+ {
1863
+ for (Py_ssize_t i=0; i < cpg->lines_count; ++i) {
1864
+ AK_CPL_Free(cpg->lines[i]);
1865
+ }
1866
+ PyMem_Free(cpg->lines);
1867
+ PyMem_Free(cpg);
1868
+ }
1869
+
1870
+ //------------------------------------------------------------------------------
1871
+ // CodePointGrid: Mutation
1872
+
1873
+ // Determine if a new CPL needs to be created and add it if needed. Return 0 on succes, -1 on failure.
1874
+ static inline int
1875
+ AK_CPG_resize(AK_CodePointGrid* cpg, Py_ssize_t line)
1876
+ {
1877
+ Py_ssize_t lines_count = cpg->lines_count;
1878
+ if (line < lines_count) return 0; // most common scenario
1879
+
1880
+ if (AK_UNLIKELY(line >= cpg->lines_capacity)) {
1881
+ cpg->lines_capacity *= 2;
1882
+ // NOTE: we assume this only copies the pointers, not the data in the CPLs
1883
+ cpg->lines = PyMem_Realloc(cpg->lines,
1884
+ sizeof(AK_CodePointLine*) * cpg->lines_capacity);
1885
+ if (cpg->lines == NULL) return -1;
1886
+ }
1887
+ // Create the new CPL; first check if we need to set type_parse by calling into the dtypes function. For now we assume sequential growth, so should only check if equal
1888
+ if (AK_UNLIKELY(line >= lines_count)) {
1889
+ // determine if we need to parse types
1890
+ bool type_parse = false;
1891
+ if (cpg->dtypes == NULL) {
1892
+ type_parse = true;
1893
+ }
1894
+ else {
1895
+ PyObject* line_count = PyLong_FromSsize_t(line);
1896
+ if (line_count == NULL) return -1;
1897
+
1898
+ PyObject* dtype_specifier = PyObject_CallFunctionObjArgs(
1899
+ cpg->dtypes,
1900
+ line_count,
1901
+ NULL
1902
+ );
1903
+ Py_DECREF(line_count);
1904
+
1905
+ if (dtype_specifier == NULL) {
1906
+ // NOTE: not sure how to get the exception from the failed call...
1907
+ PyErr_Format(PyExc_RuntimeError,
1908
+ "dtypes callable failed for input: %d",
1909
+ line
1910
+ );
1911
+ return -1;
1912
+ }
1913
+ if (dtype_specifier == Py_None) {
1914
+ type_parse = true;
1915
+ }
1916
+ Py_DECREF(dtype_specifier);
1917
+ }
1918
+ // Always initialize a CPL in the new position
1919
+ AK_CodePointLine *cpl = AK_CPL_New(type_parse, cpg->tsep, cpg->decc);
1920
+ if (cpl == NULL) return -1; // memory error set
1921
+
1922
+ cpg->lines[line] = cpl;
1923
+ ++cpg->lines_count;
1924
+ }
1925
+ return 0;
1926
+ }
1927
+
1928
+ // Append a point on the line; called for each character in a field. Return 0 on success, -1 on failure.
1929
+ static inline int
1930
+ AK_CPG_AppendPointAtLine(
1931
+ AK_CodePointGrid* cpg,
1932
+ Py_ssize_t line, // number
1933
+ Py_ssize_t field_len,
1934
+ Py_UCS4 p
1935
+ )
1936
+ {
1937
+ if (AK_CPG_resize(cpg, line)) return -1;
1938
+ if (AK_CPL_AppendPoint(cpg->lines[line], p, field_len)) return -1;
1939
+ return 0;
1940
+ }
1941
+
1942
+ // Append an offset in a line. Returns 0 on success, -1 on failure.
1943
+ static inline int
1944
+ AK_CPG_AppendOffsetAtLine(
1945
+ AK_CodePointGrid* cpg,
1946
+ Py_ssize_t line,
1947
+ Py_ssize_t offset)
1948
+ {
1949
+ // only need to call this if AK_CPG_AppendPointAtLine has not yet been called
1950
+ if (AK_CPG_resize(cpg, line)) return -1;
1951
+ if (AK_CPL_AppendOffset(cpg->lines[line], offset)) return -1;
1952
+ return 0;
1953
+ }
1954
+
1955
+ // Given a fully-loaded CodePointGrid, process each CodePointLine into an array and return a new list of those arrays. Returns NULL on failure.
1956
+ static inline PyObject *
1957
+ AK_CPG_ToArrayList(AK_CodePointGrid* cpg,
1958
+ int axis,
1959
+ PyObject* line_select,
1960
+ char tsep,
1961
+ char decc)
1962
+ {
1963
+ bool ls_inactive = line_select == NULL;
1964
+ PyObject *list;
1965
+
1966
+ if (ls_inactive) {
1967
+ // if we know how many lines we will need, can pre-allocate
1968
+ list = PyList_New(cpg->lines_count);
1969
+ }
1970
+ else {
1971
+ list = PyList_New(0);
1972
+ }
1973
+ if (list == NULL) return NULL;
1974
+
1975
+ PyObject* dtypes = cpg->dtypes;
1976
+
1977
+ // Iterate over lines in the code point grid
1978
+ for (Py_ssize_t i = 0; i < cpg->lines_count; ++i) {
1979
+ // if axis is axis 1, apply keep
1980
+ switch (AK_line_select_keep(line_select, 1 == axis, i)) {
1981
+ case -1:
1982
+ Py_DECREF(list);
1983
+ return NULL;
1984
+ case 0:
1985
+ continue;
1986
+ }
1987
+ // If dtypes is not NULL, fetch the dtype_specifier and use it to set dtype; else, pass the dtype as NULL to CPL.
1988
+ PyArray_Descr* dtype = NULL;
1989
+
1990
+ if (dtypes != NULL) {
1991
+ // NOTE: we call this with i regardless of if we skipped a line
1992
+ PyObject* line_count = PyLong_FromSsize_t(i);
1993
+ if (line_count == NULL) {
1994
+ Py_DECREF(list);
1995
+ return NULL;
1996
+ }
1997
+ PyObject* dtype_specifier = PyObject_CallFunctionObjArgs(
1998
+ dtypes,
1999
+ line_count,
2000
+ NULL
2001
+ );
2002
+ Py_DECREF(line_count);
2003
+ if (dtype_specifier == NULL) {
2004
+ Py_DECREF(list);
2005
+ // NOTE: not sure how to get the exception from the failed call...
2006
+ PyErr_Format(PyExc_RuntimeError,
2007
+ "dtypes callable failed for input: %d",
2008
+ i
2009
+ );
2010
+ return NULL;
2011
+ }
2012
+ if (dtype_specifier != Py_None) {
2013
+ // Set dtype; this value can be NULL or a dtype (never Py_None); if dtype_specifier is Py_None, keep dtype set as NULL (above); this will be a new reference that if used will be stolen in array construction.
2014
+ if (AK_DTypeFromSpecifier(dtype_specifier, &dtype)) {
2015
+ Py_DECREF(dtype_specifier);
2016
+ Py_DECREF(list);
2017
+ return NULL;
2018
+ }
2019
+ }
2020
+ Py_DECREF(dtype_specifier);
2021
+ }
2022
+ // This function will observe if dtype is NULL and read dtype from the CPL's type_parser if necessary
2023
+ // NOTE: this might be multi-threadable for dtypes that permit C-only buffer transfers
2024
+ PyObject* array = AK_CPL_ToArray(cpg->lines[i], dtype, tsep, decc);
2025
+ if (array == NULL) {
2026
+ // if array creation has been aborted due to a bad character, we will already have decrefed the array
2027
+ Py_DECREF(list);
2028
+ return NULL;
2029
+ }
2030
+
2031
+ if (ls_inactive) {
2032
+ PyList_SET_ITEM(list, i, array); // steals reference
2033
+ }
2034
+ else {
2035
+ if (PyList_Append(list, array)) {
2036
+ Py_DECREF(array);
2037
+ Py_DECREF(list);
2038
+ return NULL;
2039
+ }
2040
+ Py_DECREF(array); // decref as list owns
2041
+ }
2042
+ }
2043
+ return list;
2044
+ }
2045
+
2046
+ //------------------------------------------------------------------------------
2047
+ // AK_Dialect, based on _csv.c from CPython
2048
+
2049
+ typedef enum AK_DialectQuoteStyle {
2050
+ QUOTE_MINIMAL,
2051
+ QUOTE_ALL,
2052
+ QUOTE_NONNUMERIC, // NOTE: kept to match csv module, but has no effect here
2053
+ QUOTE_NONE
2054
+ } AK_DialectQuoteStyle;
2055
+
2056
+ typedef struct AK_DialectStyleDesc{
2057
+ AK_DialectQuoteStyle style;
2058
+ const char *name;
2059
+ } AK_DialectStyleDesc;
2060
+
2061
+ static const AK_DialectStyleDesc AK_Dialect_quote_styles[] = {
2062
+ { QUOTE_MINIMAL, "QUOTE_MINIMAL" },
2063
+ { QUOTE_ALL, "QUOTE_ALL" },
2064
+ { QUOTE_NONNUMERIC, "QUOTE_NONNUMERIC" },
2065
+ { QUOTE_NONE, "QUOTE_NONE" },
2066
+ { 0 }
2067
+ };
2068
+
2069
+ static inline int
2070
+ AK_Dialect_check_quoting(int quoting)
2071
+ {
2072
+ const AK_DialectStyleDesc *qs;
2073
+ for (qs = AK_Dialect_quote_styles; qs->name; qs++) {
2074
+ if ((int)qs->style == quoting)
2075
+ return 0;
2076
+ }
2077
+ PyErr_Format(PyExc_TypeError, "bad \"quoting\" value");
2078
+ return -1;
2079
+ }
2080
+
2081
+ typedef struct AK_Dialect{
2082
+ bool doublequote; // is " represented by ""?
2083
+ bool skipinitialspace; // ignore spaces following delimiter?
2084
+ bool strict; // raise exception on bad CSV
2085
+ int quoting; // style of quoting to write
2086
+ Py_UCS4 delimiter; // field separator
2087
+ Py_UCS4 quotechar; // quote character
2088
+ Py_UCS4 escapechar; // escape character
2089
+ } AK_Dialect;
2090
+
2091
+ // check types and convert to C values
2092
+ #define AK_Dialect_CALL_SETTER(meth, name, target, src, default) \
2093
+ do { \
2094
+ if (meth(name, target, src, default)) \
2095
+ goto error; \
2096
+ } while (0) \
2097
+
2098
+ static inline AK_Dialect *
2099
+ AK_Dialect_New(PyObject *delimiter,
2100
+ PyObject *doublequote,
2101
+ PyObject *escapechar,
2102
+ PyObject *quotechar,
2103
+ PyObject *quoting,
2104
+ PyObject *skipinitialspace,
2105
+ PyObject *strict
2106
+ )
2107
+ {
2108
+ AK_Dialect *dialect = (AK_Dialect *) PyMem_Malloc(sizeof(AK_Dialect));
2109
+ if (dialect == NULL) return (AK_Dialect *)PyErr_NoMemory();
2110
+
2111
+ Py_XINCREF(delimiter);
2112
+ Py_XINCREF(doublequote);
2113
+ Py_XINCREF(escapechar);
2114
+ Py_XINCREF(quotechar);
2115
+ Py_XINCREF(quoting);
2116
+ Py_XINCREF(skipinitialspace);
2117
+ Py_XINCREF(strict);
2118
+
2119
+ // all goto error on error from function used in macro setting
2120
+ AK_Dialect_CALL_SETTER(AK_set_char,
2121
+ "delimiter",
2122
+ &dialect->delimiter,
2123
+ delimiter,
2124
+ ',');
2125
+ AK_Dialect_CALL_SETTER(AK_set_bool,
2126
+ "doublequote",
2127
+ &dialect->doublequote,
2128
+ doublequote,
2129
+ true);
2130
+ AK_Dialect_CALL_SETTER(AK_set_char,
2131
+ "escapechar",
2132
+ &dialect->escapechar,
2133
+ escapechar,
2134
+ 0);
2135
+ AK_Dialect_CALL_SETTER(AK_set_char,
2136
+ "quotechar",
2137
+ &dialect->quotechar,
2138
+ quotechar,
2139
+ '"');
2140
+ AK_Dialect_CALL_SETTER(AK_set_int,
2141
+ "quoting",
2142
+ &dialect->quoting,
2143
+ quoting,
2144
+ QUOTE_MINIMAL); // we set QUOTE_MINIMAL by default
2145
+ AK_Dialect_CALL_SETTER(AK_set_bool,
2146
+ "skipinitialspace",
2147
+ &dialect->skipinitialspace,
2148
+ skipinitialspace,
2149
+ false);
2150
+ AK_Dialect_CALL_SETTER(AK_set_bool,
2151
+ "strict",
2152
+ &dialect->strict,
2153
+ strict,
2154
+ false);
2155
+
2156
+ if (AK_Dialect_check_quoting(dialect->quoting))
2157
+ goto error;
2158
+
2159
+ if (dialect->delimiter == 0) {
2160
+ PyErr_SetString(PyExc_TypeError,
2161
+ "\"delimiter\" must be a 1-character string");
2162
+ goto error;
2163
+ }
2164
+ if (quotechar == Py_None && quoting == NULL)
2165
+ dialect->quoting = QUOTE_NONE;
2166
+
2167
+ if (dialect->quoting != QUOTE_NONE && dialect->quotechar == 0) {
2168
+ PyErr_SetString(PyExc_TypeError,
2169
+ "quotechar must be set if quoting enabled");
2170
+ goto error;
2171
+ }
2172
+ // done with all pyobjects
2173
+ Py_CLEAR(delimiter);
2174
+ Py_CLEAR(doublequote);
2175
+ Py_CLEAR(escapechar);
2176
+ Py_CLEAR(quotechar);
2177
+ Py_CLEAR(quoting);
2178
+ Py_CLEAR(skipinitialspace);
2179
+ Py_CLEAR(strict);
2180
+ return dialect;
2181
+ error:
2182
+ Py_CLEAR(delimiter);
2183
+ Py_CLEAR(doublequote);
2184
+ Py_CLEAR(escapechar);
2185
+ Py_CLEAR(quotechar);
2186
+ Py_CLEAR(quoting);
2187
+ Py_CLEAR(skipinitialspace);
2188
+ Py_CLEAR(strict);
2189
+ // We may have gone to error after allocating dialect but found an error in a parameter
2190
+ PyMem_Free(dialect);
2191
+ return NULL;
2192
+ }
2193
+
2194
+ static inline void
2195
+ AK_Dialect_Free(AK_Dialect* dialect)
2196
+ {
2197
+ PyMem_Free(dialect); // might alrady be NULL
2198
+ }
2199
+
2200
+ //------------------------------------------------------------------------------
2201
+ // AK_DelimitedReader, based on _csv.c from CPython
2202
+
2203
+ typedef enum AK_DelimitedReaderState {
2204
+ START_RECORD,
2205
+ START_FIELD,
2206
+ ESCAPED_CHAR,
2207
+ IN_FIELD,
2208
+ IN_QUOTED_FIELD,
2209
+ ESCAPE_IN_QUOTED_FIELD,
2210
+ QUOTE_IN_QUOTED_FIELD,
2211
+ EAT_CRNL,
2212
+ AFTER_ESCAPED_CRNL
2213
+ } AK_DelimitedReaderState;
2214
+
2215
+ typedef struct AK_DelimitedReader{
2216
+ PyObject *input_iter;
2217
+ PyObject *line_select;
2218
+ AK_Dialect *dialect;
2219
+ AK_DelimitedReaderState state;
2220
+ Py_ssize_t field_len;
2221
+ Py_ssize_t record_number; // total records loaded
2222
+ Py_ssize_t record_iter_number; // records iterated (counting exclusion)
2223
+ Py_ssize_t field_number; // field in current record, reset for each record
2224
+ int axis;
2225
+ Py_ssize_t *axis_pos; // points to either record_number or field_number
2226
+ } AK_DelimitedReader;
2227
+
2228
+ // Called once at the close of each field in a line. Returns 0 on success, -1 on failure
2229
+ static inline int
2230
+ AK_DR_close_field(AK_DelimitedReader *dr, AK_CodePointGrid *cpg)
2231
+ {
2232
+ if (AK_CPG_AppendOffsetAtLine(cpg,
2233
+ *(dr->axis_pos),
2234
+ dr->field_len)) return -1;
2235
+ dr->field_len = 0; // clear to close
2236
+ // AK_DEBUG_MSG_OBJ("closing field", PyLong_FromLong(dr->field_number));
2237
+ ++dr->field_number; // increment after adding each offset, reset in AK_DR_line_reset
2238
+ return 0;
2239
+ }
2240
+
2241
+ // Called once to add each character, appending that character to the CPL. Return 0 on success, -1 on failure.
2242
+ static inline int
2243
+ AK_DR_add_char(AK_DelimitedReader *dr, AK_CodePointGrid *cpg, Py_UCS4 c)
2244
+ {
2245
+ // NOTE: ideally we could use line_select here; however, we would need to cache the lookup in another container as this is called once per char and line_select is a Python function; further, we would need to increment the field_number separately from another counter, which is done in AK_DR_close_field
2246
+ if (AK_CPG_AppendPointAtLine(cpg,
2247
+ *(dr->axis_pos),
2248
+ dr->field_len,
2249
+ c)) return -1;
2250
+ ++dr->field_len; // reset in AK_DR_close_field
2251
+ return 0;
2252
+ }
2253
+
2254
+ // Process each char and update AK_DelimitedReader state. When appropriate, call AK_DR_add_char to accumulate field characters, AK_DR_close_field to end a field. Return -1 on failure, 0 on success.
2255
+ static inline int
2256
+ AK_DR_process_char(AK_DelimitedReader *dr, AK_CodePointGrid *cpg, Py_UCS4 c)
2257
+ {
2258
+ AK_Dialect *dialect = dr->dialect;
2259
+
2260
+ switch (dr->state) {
2261
+ case START_RECORD: // start of record
2262
+ if (c == '\0') // empty line
2263
+ break;
2264
+ else if (c == '\n' || c == '\r') {
2265
+ dr->state = EAT_CRNL;
2266
+ break;
2267
+ }
2268
+ dr->state = START_FIELD; // normal character
2269
+ // fallthru
2270
+ case START_FIELD: // expecting field
2271
+ if (c == '\n' || c == '\r' || c == '\0') { // save empty field
2272
+ if (AK_DR_close_field(dr, cpg)) return -1;
2273
+ dr->state = (c == '\0' ? START_RECORD : EAT_CRNL);
2274
+ }
2275
+ else if (c == dialect->quotechar && dialect->quoting != QUOTE_NONE) {
2276
+ dr->state = IN_QUOTED_FIELD;
2277
+ }
2278
+ else if (c == dialect->escapechar) {
2279
+ dr->state = ESCAPED_CHAR;
2280
+ }
2281
+ else if (c == ' ' && dialect->skipinitialspace);
2282
+ else if (c == dialect->delimiter) { // end of a field
2283
+ if (AK_DR_close_field(dr, cpg)) return -1;
2284
+ }
2285
+ else { // begin new unquoted field
2286
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2287
+ dr->state = IN_FIELD;
2288
+ }
2289
+ break;
2290
+ case ESCAPED_CHAR:
2291
+ if (c == '\n' || c=='\r') {
2292
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2293
+ dr->state = AFTER_ESCAPED_CRNL;
2294
+ break;
2295
+ }
2296
+ if (c == '\0')
2297
+ c = '\n';
2298
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2299
+ dr->state = IN_FIELD;
2300
+ break;
2301
+ case AFTER_ESCAPED_CRNL:
2302
+ if (c == '\0') break;
2303
+ // fallthru
2304
+ case IN_FIELD: // in unquoted field
2305
+ if (c == '\n' || c == '\r' || c == '\0') { // end of line
2306
+ if (AK_DR_close_field(dr, cpg)) return -1;
2307
+ dr->state = (c == '\0' ? START_RECORD : EAT_CRNL);
2308
+ }
2309
+ else if (c == dialect->escapechar) {
2310
+ dr->state = ESCAPED_CHAR;
2311
+ }
2312
+ else if (c == dialect->delimiter) { // save field - wait for new field
2313
+ if (AK_DR_close_field(dr, cpg)) return -1;
2314
+ dr->state = START_FIELD;
2315
+ }
2316
+ else { // normal character - save in field
2317
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2318
+ }
2319
+ break;
2320
+ case IN_QUOTED_FIELD: // in quoted field
2321
+ if (c == '\0');
2322
+ else if (c == dialect->escapechar) {
2323
+ dr->state = ESCAPE_IN_QUOTED_FIELD;
2324
+ }
2325
+ else if (c == dialect->quotechar && dialect->quoting != QUOTE_NONE) {
2326
+ dr->state = (dialect->doublequote ? QUOTE_IN_QUOTED_FIELD : IN_FIELD);
2327
+ }
2328
+ else { // normal character - save in field
2329
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2330
+ }
2331
+ break;
2332
+ case ESCAPE_IN_QUOTED_FIELD:
2333
+ if (c == '\0') {
2334
+ c = '\n';
2335
+ }
2336
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2337
+ dr->state = IN_QUOTED_FIELD;
2338
+ break;
2339
+ case QUOTE_IN_QUOTED_FIELD:
2340
+ // doublequote - seen a quote in a quoted field
2341
+ if (dialect->quoting != QUOTE_NONE && c == dialect->quotechar) {
2342
+ // save "" as "
2343
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2344
+ dr->state = IN_QUOTED_FIELD;
2345
+ }
2346
+ else if (c == dialect->delimiter) { // save field - wait for new field
2347
+ if (AK_DR_close_field(dr, cpg)) return -1;
2348
+ dr->state = START_FIELD;
2349
+ }
2350
+ else if (c == '\n' || c == '\r' || c == '\0') { // end of line
2351
+ if (AK_DR_close_field(dr, cpg)) return -1;
2352
+ dr->state = (c == '\0' ? START_RECORD : EAT_CRNL);
2353
+ }
2354
+ else if (!dialect->strict) {
2355
+ if (AK_DR_add_char(dr, cpg, c)) return -1;
2356
+ dr->state = IN_FIELD;
2357
+ }
2358
+ else { // illegal
2359
+ PyErr_Format(PyExc_RuntimeError, "'%c' expected after '%c'",
2360
+ dialect->delimiter, dialect->quotechar);
2361
+ return -1;
2362
+ }
2363
+ break;
2364
+ case EAT_CRNL:
2365
+ if (c == '\n' || c == '\r');
2366
+ else if (c == '\0')
2367
+ dr->state = START_RECORD;
2368
+ else {
2369
+ PyErr_Format(PyExc_RuntimeError,
2370
+ "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");
2371
+ return -1;
2372
+ }
2373
+ break;
2374
+ }
2375
+ return 0;
2376
+ }
2377
+
2378
+ // Called once at the start of processing each line in AK_DR_ProcessRecord. This cannot error.
2379
+ static inline void
2380
+ AK_DR_line_reset(AK_DelimitedReader *dr)
2381
+ {
2382
+ dr->field_len = 0;
2383
+ dr->state = START_RECORD;
2384
+ dr->field_number = 0;
2385
+ }
2386
+
2387
+ // Using AK_DelimitedReader's state, process one record (via next(input_iter)); call AK_DR_process_char on each char in that line, loading individual fields into AK_CodePointGrid. Returns 1 when there are more lines to process, 0 when there are no lines to process, and -1 for error.
2388
+ static inline int
2389
+ AK_DR_ProcessRecord(AK_DelimitedReader *dr,
2390
+ AK_CodePointGrid *cpg,
2391
+ PyObject *line_select
2392
+ )
2393
+ {
2394
+ Py_ssize_t linelen;
2395
+ unsigned int kind;
2396
+ const void *data;
2397
+ PyObject *record;
2398
+
2399
+ AK_DR_line_reset(dr);
2400
+ do {
2401
+ // get a string, representing one record, to parse
2402
+ record = PyIter_Next(dr->input_iter);
2403
+ if (record == NULL) {
2404
+ if (PyErr_Occurred()) return -1;
2405
+ // if parser is in an unexptected state
2406
+ if ((dr->field_len != 0) || (dr->state == IN_QUOTED_FIELD)) {
2407
+ if (dr->dialect->strict) {
2408
+ PyErr_SetString(PyExc_RuntimeError, "unexpected end of data");
2409
+ return -1;
2410
+ }
2411
+ // try to close the field, propagate error
2412
+ if (AK_DR_close_field(dr, cpg)) return -1;
2413
+ }
2414
+ return 0; // end of input, not an error
2415
+ }
2416
+ ++dr->record_iter_number;
2417
+
2418
+ if (!PyUnicode_Check(record)) {
2419
+ PyErr_Format(PyExc_RuntimeError,
2420
+ "iterator should return strings, not %.200s "
2421
+ "(the file should be opened in text mode)",
2422
+ Py_TYPE(record)->tp_name
2423
+ );
2424
+ Py_DECREF(record);
2425
+ return -1;
2426
+ }
2427
+ if (PyUnicode_READY(record) == -1) {
2428
+ Py_DECREF(record);
2429
+ return -1;
2430
+ }
2431
+
2432
+ switch (AK_line_select_keep(line_select,
2433
+ 0 == dr->axis,
2434
+ dr->record_iter_number)) {
2435
+ case -1 :
2436
+ Py_DECREF(record);
2437
+ return -1;
2438
+ case 0:
2439
+ Py_DECREF(record);
2440
+ return 1; // skip, process more records
2441
+ }
2442
+ // NOTE: record_number should reflect the processed line count, and exlude any skipped lines. The value is initialized to -1 such the first line is number 0
2443
+ ++dr->record_number;
2444
+ // AK_DEBUG_MSG_OBJ("processing line", PyLong_FromLong(dr->record_number));
2445
+
2446
+ kind = PyUnicode_KIND(record);
2447
+ data = PyUnicode_DATA(record);
2448
+ linelen = PyUnicode_GET_LENGTH(record);
2449
+
2450
+ // NOTE: we used to check that the read character was not \0; this seems rare enough to not be necessary to handle explicit, as AK_DR_process_char will treat it as an end of record
2451
+ switch (kind) {
2452
+ case PyUnicode_1BYTE_KIND: {
2453
+ Py_UCS1* uc = (Py_UCS1*)data;
2454
+ Py_UCS1* uc_end = uc + linelen;
2455
+ while (uc < uc_end) {
2456
+ if (AK_DR_process_char(dr, cpg, *uc++)) {
2457
+ Py_DECREF(record);
2458
+ return -1;
2459
+ }
2460
+ }
2461
+ break;
2462
+ }
2463
+ case PyUnicode_2BYTE_KIND: {
2464
+ Py_UCS2* uc = (Py_UCS2*)data;
2465
+ Py_UCS2* uc_end = uc + linelen;
2466
+ while (uc < uc_end) {
2467
+ if (AK_DR_process_char(dr, cpg, *uc++)) {
2468
+ Py_DECREF(record);
2469
+ return -1;
2470
+ }
2471
+ }
2472
+ break;
2473
+ }
2474
+ case PyUnicode_4BYTE_KIND: {
2475
+ Py_UCS4* uc = (Py_UCS4*)data;
2476
+ Py_UCS4* uc_end = uc + linelen;
2477
+ while (uc < uc_end) {
2478
+ if (AK_DR_process_char(dr, cpg, *uc++)) {
2479
+ Py_DECREF(record);
2480
+ return -1;
2481
+ }
2482
+ }
2483
+ break;
2484
+ }
2485
+ }
2486
+ Py_DECREF(record);
2487
+ // force signaling we are at the end of a line
2488
+ if (AK_DR_process_char(dr, cpg, '\0')) return -1;
2489
+
2490
+ } while (dr->state != START_RECORD);
2491
+ return 1; // more lines to process
2492
+ }
2493
+
2494
+ static inline void
2495
+ AK_DR_Free(AK_DelimitedReader *dr)
2496
+ {
2497
+ if (dr->dialect) {
2498
+ AK_Dialect_Free(dr->dialect);
2499
+ }
2500
+ Py_XDECREF(dr->input_iter); // might already be NULL
2501
+ PyMem_Free(dr);
2502
+ }
2503
+
2504
+ // The arguments to this constructor are validated before this function is valled. Returns NULL on error.
2505
+ static inline AK_DelimitedReader *
2506
+ AK_DR_New(PyObject *iterable,
2507
+ int axis,
2508
+ PyObject *delimiter,
2509
+ PyObject *doublequote,
2510
+ PyObject *escapechar,
2511
+ PyObject *quotechar,
2512
+ PyObject *quoting,
2513
+ PyObject *skipinitialspace,
2514
+ PyObject *strict
2515
+ )
2516
+ {
2517
+ AK_DelimitedReader *dr = (AK_DelimitedReader*)PyMem_Malloc(sizeof(AK_DelimitedReader));
2518
+ if (dr == NULL) return (AK_DelimitedReader*)PyErr_NoMemory();
2519
+
2520
+ dr->axis = axis;
2521
+
2522
+ // we configure axis_pos to be a pointer that points to either record_number or field_number to avoid doing this per char/ field.
2523
+ if (axis == 0) {
2524
+ dr->axis_pos = &(dr->record_number);
2525
+ }
2526
+ else {
2527
+ dr->axis_pos = &(dr->field_number);
2528
+ }
2529
+
2530
+ dr->record_number = -1;
2531
+ dr->record_iter_number = -1;
2532
+ dr->dialect = NULL; // init in case input_iter fails to init
2533
+
2534
+ dr->input_iter = PyObject_GetIter(iterable); // new ref, decref in free
2535
+ if (dr->input_iter == NULL) {
2536
+ AK_DR_Free(dr);
2537
+ return NULL;
2538
+ }
2539
+
2540
+ dr->dialect = AK_Dialect_New(
2541
+ delimiter,
2542
+ doublequote,
2543
+ escapechar,
2544
+ quotechar,
2545
+ quoting,
2546
+ skipinitialspace,
2547
+ strict);
2548
+ if (dr->dialect == NULL) {
2549
+ AK_DR_Free(dr);
2550
+ return NULL;
2551
+ }
2552
+ return dr;
2553
+ }
2554
+
2555
+ //------------------------------------------------------------------------------
2556
+
2557
+ // Convert an sequence of strings to a 1D array.
2558
+ static inline PyObject *
2559
+ AK_IterableStrToArray1D(
2560
+ PyObject *sequence,
2561
+ PyObject *dtype_specifier,
2562
+ Py_UCS4 tsep,
2563
+ Py_UCS4 decc)
2564
+ {
2565
+ PyArray_Descr* dtype = NULL;
2566
+ // will set dtype_specifier to NULL for None, and propagate NULLs
2567
+ if (AK_DTypeFromSpecifier(dtype_specifier, &dtype)) return NULL;
2568
+
2569
+ // dtype only NULL from here
2570
+ bool type_parse = dtype == NULL;
2571
+
2572
+ AK_CodePointLine* cpl = AK_CPL_FromIterable(sequence, type_parse, tsep, decc);
2573
+ if (cpl == NULL) return NULL;
2574
+
2575
+ PyObject* array = AK_CPL_ToArray(cpl, dtype, tsep, decc);
2576
+ AK_CPL_Free(cpl);
2577
+ return array; // might be NULL
2578
+ }
2579
+
2580
+ //------------------------------------------------------------------------------
2581
+ // AK module public methods
2582
+ //------------------------------------------------------------------------------
2583
+
2584
+ static char *delimited_to_ararys_kwarg_names[] = {
2585
+ "file_like",
2586
+ "axis",
2587
+ "dtypes",
2588
+ "line_select",
2589
+ "delimiter",
2590
+ "doublequote",
2591
+ "escapechar",
2592
+ "quotechar",
2593
+ "quoting",
2594
+ "skipinitialspace",
2595
+ "strict",
2596
+ "thousandschar",
2597
+ "decimalchar",
2598
+ NULL
2599
+ };
2600
+
2601
+ // NOTE: implement skip_header, skip_footer in client Python, not here.
2602
+ PyObject *
2603
+ delimited_to_arrays(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
2604
+ {
2605
+ PyObject *file_like;
2606
+ int axis = 0;
2607
+ PyObject *dtypes = NULL;
2608
+ PyObject *line_select = NULL;
2609
+ PyObject *delimiter = NULL;
2610
+ PyObject *doublequote = NULL;
2611
+ PyObject *escapechar = NULL;
2612
+ PyObject *quotechar = NULL;
2613
+ PyObject *quoting = NULL;
2614
+ PyObject *skipinitialspace = NULL;
2615
+ PyObject *strict = NULL;
2616
+ PyObject *thousandschar = NULL;
2617
+ PyObject *decimalchar = NULL;
2618
+
2619
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
2620
+ "O|$iOOOOOOOOOOO:delimited_to_arrays",
2621
+ delimited_to_ararys_kwarg_names,
2622
+ &file_like,
2623
+ // kwarg only
2624
+ &axis,
2625
+ &dtypes,
2626
+ &line_select,
2627
+ &delimiter,
2628
+ &doublequote,
2629
+ &escapechar,
2630
+ &quotechar,
2631
+ &quoting,
2632
+ &skipinitialspace,
2633
+ &strict,
2634
+ &thousandschar,
2635
+ &decimalchar))
2636
+ return NULL;
2637
+
2638
+ // normalize line_select to NULL or callable
2639
+ if ((line_select == NULL) || (line_select == Py_None)) {
2640
+ line_select = NULL;
2641
+ }
2642
+ else if (!PyCallable_Check(line_select)) {
2643
+ PyErr_SetString(PyExc_TypeError, "line_select must be a callable or None");
2644
+ return NULL;
2645
+ }
2646
+
2647
+ if ((axis < 0) || (axis > 1)) {
2648
+ PyErr_SetString(PyExc_ValueError, "Axis must be 0 or 1");
2649
+ return NULL;
2650
+ }
2651
+ AK_DelimitedReader *dr = AK_DR_New(file_like,
2652
+ axis,
2653
+ delimiter,
2654
+ doublequote,
2655
+ escapechar,
2656
+ quotechar,
2657
+ quoting,
2658
+ skipinitialspace,
2659
+ strict);
2660
+ if (dr == NULL) { // can happen due to validation of dialect parameters
2661
+ return NULL;
2662
+ }
2663
+
2664
+ Py_UCS4 tsep;
2665
+ if (AK_set_char(
2666
+ "thousandschar",
2667
+ &tsep,
2668
+ thousandschar,
2669
+ '\0')) {
2670
+ AK_DR_Free(dr);
2671
+ return NULL; // default is off (skips evaluation)
2672
+ }
2673
+ Py_UCS4 decc;
2674
+ if (AK_set_char(
2675
+ "decimalchar",
2676
+ &decc,
2677
+ decimalchar,
2678
+ '.')) {
2679
+ AK_DR_Free(dr);
2680
+ return NULL;
2681
+ }
2682
+
2683
+ // dtypes inc / dec ref bound within CPG life
2684
+ AK_CodePointGrid* cpg = AK_CPG_New(dtypes, tsep, decc);
2685
+ if (cpg == NULL) { // error will be set
2686
+ AK_DR_Free(dr);
2687
+ return NULL;
2688
+ }
2689
+ // Consume all lines from dr and load into cpg
2690
+ int status;
2691
+ while (true) {
2692
+ status = AK_DR_ProcessRecord(dr, cpg, line_select);
2693
+ if (status == 1) {
2694
+ continue; // more lines to process
2695
+ }
2696
+ else if (status == 0) {
2697
+ break;
2698
+ }
2699
+ else if (status == -1) {
2700
+ AK_DR_Free(dr);
2701
+ AK_CPG_Free(cpg);
2702
+ return NULL;
2703
+ }
2704
+ // NOTE: could use PyErr_CheckSignals() at some number of dr->record_number
2705
+ }
2706
+ AK_DR_Free(dr);
2707
+
2708
+ PyObject* arrays = AK_CPG_ToArrayList(cpg, axis, line_select, tsep, decc);
2709
+ // NOTE: do not need to check if arrays is NULL as we will return NULL anyway
2710
+ AK_CPG_Free(cpg); // will free reference to dtypes
2711
+ return arrays; // could be NULL
2712
+ }
2713
+
2714
+ static char *iterable_str_to_array_1d_kwarg_names[] = {
2715
+ "iterable",
2716
+ "dtype",
2717
+ "thousandschar",
2718
+ "decimalchar",
2719
+ NULL
2720
+ };
2721
+
2722
+ PyObject *
2723
+ iterable_str_to_array_1d(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
2724
+ {
2725
+ PyObject *iterable = NULL;
2726
+ PyObject *dtype_specifier = NULL;
2727
+ PyObject *thousandschar = NULL;
2728
+ PyObject *decimalchar = NULL;
2729
+
2730
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
2731
+ "O|OOO:iterable_str_to_array_1d",
2732
+ iterable_str_to_array_1d_kwarg_names,
2733
+ &iterable,
2734
+ // kwarg only
2735
+ &dtype_specifier,
2736
+ &thousandschar,
2737
+ &decimalchar))
2738
+ return NULL;
2739
+
2740
+ Py_UCS4 tsep;
2741
+ if (AK_set_char(
2742
+ "thousandschar",
2743
+ &tsep,
2744
+ thousandschar,
2745
+ '\0')) return NULL;
2746
+
2747
+ Py_UCS4 decc;
2748
+ if (AK_set_char(
2749
+ "decimalchar",
2750
+ &decc,
2751
+ decimalchar,
2752
+ '.')) return NULL;
2753
+
2754
+ return AK_IterableStrToArray1D(iterable, dtype_specifier, tsep, decc);
2755
+ }
2756
+
2757
+ static char *split_after_count_kwarg_names[] = {
2758
+ "string",
2759
+ "delimiter",
2760
+ "count",
2761
+ "doublequote",
2762
+ "escapechar",
2763
+ "quotechar",
2764
+ "quoting",
2765
+ "strict",
2766
+ NULL
2767
+ };
2768
+
2769
+ PyObject *
2770
+ split_after_count(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
2771
+ {
2772
+ PyObject *string = NULL;
2773
+ PyObject *delimiter = NULL;
2774
+ int count = 0;
2775
+ PyObject *doublequote = NULL;
2776
+ PyObject *escapechar = NULL;
2777
+ PyObject *quotechar = NULL;
2778
+ PyObject *quoting = NULL;
2779
+ PyObject *strict = NULL;
2780
+
2781
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
2782
+ "O|$OiOOOOO:split_after_count",
2783
+ split_after_count_kwarg_names,
2784
+ &string,
2785
+ // kwarg-only
2786
+ &delimiter,
2787
+ &count,
2788
+ &doublequote,
2789
+ &escapechar,
2790
+ &quotechar,
2791
+ &quoting,
2792
+ &strict
2793
+ )) {
2794
+ return NULL;
2795
+ }
2796
+
2797
+ if (!PyUnicode_Check(string)) {
2798
+ PyErr_Format(PyExc_ValueError,
2799
+ "a string is required, not %.200s",
2800
+ Py_TYPE(string)->tp_name
2801
+ );
2802
+ return NULL;
2803
+ }
2804
+ if (count <= 0) {
2805
+ PyErr_Format(PyExc_ValueError,
2806
+ "count must be greater than zero, not %i",
2807
+ count
2808
+ );
2809
+ return NULL;
2810
+ }
2811
+
2812
+ AK_Dialect dialect;
2813
+
2814
+ if (AK_set_char(
2815
+ "delimiter",
2816
+ &dialect.delimiter,
2817
+ delimiter,
2818
+ ',')) return NULL;
2819
+
2820
+ if (AK_set_bool(
2821
+ "doublequote",
2822
+ &dialect.doublequote,
2823
+ doublequote,
2824
+ true)) return NULL;
2825
+
2826
+ if (AK_set_char(
2827
+ "escapechar",
2828
+ &dialect.escapechar,
2829
+ escapechar,
2830
+ 0)) return NULL;
2831
+
2832
+ if (AK_set_char(
2833
+ "quotechar",
2834
+ &dialect.quotechar,
2835
+ quotechar,
2836
+ '"')) return NULL;
2837
+
2838
+ if (AK_set_int(
2839
+ "quoting",
2840
+ &dialect.quoting,
2841
+ quoting,
2842
+ QUOTE_MINIMAL)) return NULL;
2843
+
2844
+ if (AK_set_bool(
2845
+ "strict",
2846
+ &dialect.strict,
2847
+ strict,
2848
+ false)) return NULL;
2849
+
2850
+ unsigned int kind = PyUnicode_KIND(string);
2851
+ const void *data = PyUnicode_DATA(string);
2852
+ Py_ssize_t pos = 0;
2853
+ Py_ssize_t delim_count = 0;
2854
+ Py_ssize_t linelen = PyUnicode_GET_LENGTH(string);
2855
+ Py_UCS4 c;
2856
+ AK_DelimitedReaderState state = START_RECORD;
2857
+
2858
+ while (pos < linelen) {
2859
+ c = PyUnicode_READ(kind, data, pos);
2860
+
2861
+ switch (state) {
2862
+ case START_RECORD: // start of record
2863
+ if (c == '\0') // empty line
2864
+ break;
2865
+ else if (c == '\n' || c == '\r') {
2866
+ state = EAT_CRNL;
2867
+ break;
2868
+ }
2869
+ state = START_FIELD; // normal character
2870
+ // fallthru
2871
+ case START_FIELD: // expecting field
2872
+ if (c == '\n' || c == '\r' || c == '\0') {
2873
+ state = (c == '\0' ? START_RECORD : EAT_CRNL);
2874
+ }
2875
+ else if (c == dialect.quotechar && dialect.quoting != QUOTE_NONE) {
2876
+ state = IN_QUOTED_FIELD;
2877
+ }
2878
+ else if (c == dialect.escapechar) {
2879
+ state = ESCAPED_CHAR;
2880
+ }
2881
+ else if (c == dialect.delimiter) { // end of a field
2882
+ delim_count += 1;
2883
+ }
2884
+ else {
2885
+ state = IN_FIELD;
2886
+ }
2887
+ break;
2888
+ case ESCAPED_CHAR:
2889
+ if (c == '\n' || c=='\r') {
2890
+ state = AFTER_ESCAPED_CRNL;
2891
+ break;
2892
+ }
2893
+ if (c == '\0')
2894
+ c = '\n';
2895
+ state = IN_FIELD;
2896
+ break;
2897
+ case AFTER_ESCAPED_CRNL:
2898
+ if (c == '\0') break;
2899
+ // fallthru
2900
+ case IN_FIELD: // in unquoted field
2901
+ if (c == '\n' || c == '\r' || c == '\0') { // end of line
2902
+ state = (c == '\0' ? START_RECORD : EAT_CRNL);
2903
+ }
2904
+ else if (c == dialect.escapechar) {
2905
+ state = ESCAPED_CHAR;
2906
+ }
2907
+ else if (c == dialect.delimiter) {
2908
+ delim_count += 1;
2909
+ state = START_FIELD;
2910
+ }
2911
+ break;
2912
+ case IN_QUOTED_FIELD: // in quoted field
2913
+ if (c == '\0');
2914
+ else if (c == dialect.escapechar) {
2915
+ state = ESCAPE_IN_QUOTED_FIELD;
2916
+ }
2917
+ else if (c == dialect.quotechar && dialect.quoting != QUOTE_NONE) {
2918
+ state = (dialect.doublequote ? QUOTE_IN_QUOTED_FIELD : IN_FIELD);
2919
+ }
2920
+ break;
2921
+ case ESCAPE_IN_QUOTED_FIELD:
2922
+ if (c == '\0') {
2923
+ c = '\n';
2924
+ }
2925
+ state = IN_QUOTED_FIELD;
2926
+ break;
2927
+ case QUOTE_IN_QUOTED_FIELD:
2928
+ // doublequote - seen a quote in a quoted field
2929
+ if (dialect.quoting != QUOTE_NONE && c == dialect.quotechar) {
2930
+ state = IN_QUOTED_FIELD;
2931
+ }
2932
+ else if (c == dialect.delimiter) {
2933
+ delim_count += 1;
2934
+ state = START_FIELD;
2935
+ }
2936
+ else if (c == '\n' || c == '\r' || c == '\0') {
2937
+ state = (c == '\0' ? START_RECORD : EAT_CRNL);
2938
+ }
2939
+ else if (!dialect.strict) {
2940
+ state = IN_FIELD;
2941
+ }
2942
+ else { // illegal
2943
+ PyErr_Format(PyExc_RuntimeError, "'%c' expected after '%c'",
2944
+ dialect.delimiter, dialect.quotechar);
2945
+ return NULL;
2946
+ }
2947
+ break;
2948
+ case EAT_CRNL:
2949
+ if (c == '\n' || c == '\r');
2950
+ else if (c == '\0')
2951
+ state = START_RECORD;
2952
+ else {
2953
+ PyErr_Format(PyExc_RuntimeError,
2954
+ "new-line character seen in unquoted field - do you need to open the file in universal-newline mode?");
2955
+ return NULL;
2956
+ }
2957
+ break;
2958
+ }
2959
+ if (delim_count == count) {
2960
+ break; // to not include delim at transition
2961
+ }
2962
+ // NOTE: must break before the increment when finding match
2963
+ pos++;
2964
+ }
2965
+
2966
+ PyObject* left = PyUnicode_Substring(string, 0, pos);
2967
+ PyObject* right = PyUnicode_Substring(string, pos+1, linelen);
2968
+ PyObject *result = PyTuple_Pack(2, left, right);
2969
+ Py_DECREF(left);
2970
+ Py_DECREF(right);
2971
+ return result;
2972
+ }