arraykit 1.2.0__cp314-cp314t-win_amd64.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.
- arraykit/__init__.py +40 -0
- arraykit/__init__.pyi +211 -0
- arraykit/_arraykit.c +160 -0
- arraykit/_arraykit.cp314t-win_amd64.pyd +0 -0
- arraykit/array_go.c +264 -0
- arraykit/array_go.h +8 -0
- arraykit/array_to_tuple.c +251 -0
- arraykit/array_to_tuple.h +14 -0
- arraykit/auto_map.c +2661 -0
- arraykit/auto_map.h +14 -0
- arraykit/block_index.c +1445 -0
- arraykit/block_index.h +15 -0
- arraykit/delimited_to_arrays.c +2972 -0
- arraykit/delimited_to_arrays.h +16 -0
- arraykit/methods.c +1135 -0
- arraykit/methods.h +81 -0
- arraykit/py.typed +0 -0
- arraykit/tri_map.c +1386 -0
- arraykit/tri_map.h +8 -0
- arraykit/utilities.h +392 -0
- arraykit-1.2.0.dist-info/METADATA +498 -0
- arraykit-1.2.0.dist-info/RECORD +25 -0
- arraykit-1.2.0.dist-info/WHEEL +5 -0
- arraykit-1.2.0.dist-info/licenses/LICENSE.txt +37 -0
- arraykit-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
"echar,
|
|
2631
|
+
"ing,
|
|
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
|
+
"echar,
|
|
2791
|
+
"ing,
|
|
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
|
+
}
|