/src/cpython/Objects/templateobject.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* t-string Template object implementation */ |
2 | | |
3 | | #include "Python.h" |
4 | | #include "pycore_interpolation.h" // _PyInterpolation_CheckExact() |
5 | | #include "pycore_runtime.h" // _Py_STR() |
6 | | #include "pycore_template.h" |
7 | | |
8 | | typedef struct { |
9 | | PyObject_HEAD |
10 | | PyObject *stringsiter; |
11 | | PyObject *interpolationsiter; |
12 | | int from_strings; |
13 | | } templateiterobject; |
14 | | |
15 | | #define templateiterobject_CAST(op) \ |
16 | 0 | (assert(_PyTemplateIter_CheckExact(op)), _Py_CAST(templateiterobject*, (op))) |
17 | | |
18 | | static PyObject * |
19 | | templateiter_next(PyObject *op) |
20 | 0 | { |
21 | 0 | templateiterobject *self = templateiterobject_CAST(op); |
22 | 0 | PyObject *item; |
23 | 0 | if (self->from_strings) { |
24 | 0 | item = PyIter_Next(self->stringsiter); |
25 | 0 | self->from_strings = 0; |
26 | 0 | if (item == NULL) { |
27 | 0 | return NULL; |
28 | 0 | } |
29 | 0 | if (PyUnicode_GET_LENGTH(item) == 0) { |
30 | 0 | Py_SETREF(item, PyIter_Next(self->interpolationsiter)); |
31 | 0 | self->from_strings = 1; |
32 | 0 | } |
33 | 0 | } |
34 | 0 | else { |
35 | 0 | item = PyIter_Next(self->interpolationsiter); |
36 | 0 | self->from_strings = 1; |
37 | 0 | } |
38 | 0 | return item; |
39 | 0 | } |
40 | | |
41 | | static void |
42 | | templateiter_dealloc(PyObject *op) |
43 | 0 | { |
44 | 0 | PyObject_GC_UnTrack(op); |
45 | 0 | Py_TYPE(op)->tp_clear(op); |
46 | 0 | Py_TYPE(op)->tp_free(op); |
47 | 0 | } |
48 | | |
49 | | static int |
50 | | templateiter_clear(PyObject *op) |
51 | 0 | { |
52 | 0 | templateiterobject *self = templateiterobject_CAST(op); |
53 | 0 | Py_CLEAR(self->stringsiter); |
54 | 0 | Py_CLEAR(self->interpolationsiter); |
55 | 0 | return 0; |
56 | 0 | } |
57 | | |
58 | | static int |
59 | | templateiter_traverse(PyObject *op, visitproc visit, void *arg) |
60 | 0 | { |
61 | 0 | templateiterobject *self = templateiterobject_CAST(op); |
62 | 0 | Py_VISIT(self->stringsiter); |
63 | 0 | Py_VISIT(self->interpolationsiter); |
64 | 0 | return 0; |
65 | 0 | } |
66 | | |
67 | | PyTypeObject _PyTemplateIter_Type = { |
68 | | PyVarObject_HEAD_INIT(NULL, 0) |
69 | | .tp_name = "string.templatelib.TemplateIter", |
70 | | .tp_doc = PyDoc_STR("Template iterator object"), |
71 | | .tp_basicsize = sizeof(templateiterobject), |
72 | | .tp_itemsize = 0, |
73 | | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, |
74 | | .tp_alloc = PyType_GenericAlloc, |
75 | | .tp_dealloc = templateiter_dealloc, |
76 | | .tp_clear = templateiter_clear, |
77 | | .tp_free = PyObject_GC_Del, |
78 | | .tp_traverse = templateiter_traverse, |
79 | | .tp_iter = PyObject_SelfIter, |
80 | | .tp_iternext = templateiter_next, |
81 | | }; |
82 | | |
83 | | typedef struct { |
84 | | PyObject_HEAD |
85 | | PyObject *strings; |
86 | | PyObject *interpolations; |
87 | | } templateobject; |
88 | | |
89 | | #define templateobject_CAST(op) \ |
90 | 2 | (assert(_PyTemplate_CheckExact(op)), _Py_CAST(templateobject*, (op))) |
91 | | |
92 | | static PyObject * |
93 | | template_new(PyTypeObject *type, PyObject *args, PyObject *kwds) |
94 | 0 | { |
95 | 0 | if (kwds != NULL) { |
96 | 0 | PyErr_SetString(PyExc_TypeError, "Template.__new__ only accepts *args arguments"); |
97 | 0 | return NULL; |
98 | 0 | } |
99 | | |
100 | 0 | Py_ssize_t argslen = PyTuple_GET_SIZE(args); |
101 | 0 | Py_ssize_t stringslen = 0; |
102 | 0 | Py_ssize_t interpolationslen = 0; |
103 | 0 | int last_was_str = 0; |
104 | |
|
105 | 0 | for (Py_ssize_t i = 0; i < argslen; i++) { |
106 | 0 | PyObject *item = PyTuple_GET_ITEM(args, i); |
107 | 0 | if (PyUnicode_Check(item)) { |
108 | 0 | if (!last_was_str) { |
109 | 0 | stringslen++; |
110 | 0 | } |
111 | 0 | last_was_str = 1; |
112 | 0 | } |
113 | 0 | else if (_PyInterpolation_CheckExact(item)) { |
114 | 0 | if (!last_was_str) { |
115 | 0 | stringslen++; |
116 | 0 | } |
117 | 0 | interpolationslen++; |
118 | 0 | last_was_str = 0; |
119 | 0 | } |
120 | 0 | else { |
121 | 0 | PyErr_Format( |
122 | 0 | PyExc_TypeError, |
123 | 0 | "Template.__new__ *args need to be of type 'str' or 'Interpolation', got %T", |
124 | 0 | item); |
125 | 0 | return NULL; |
126 | 0 | } |
127 | 0 | } |
128 | 0 | if (!last_was_str) { |
129 | 0 | stringslen++; |
130 | 0 | } |
131 | |
|
132 | 0 | PyObject *strings = PyTuple_New(stringslen); |
133 | 0 | if (!strings) { |
134 | 0 | return NULL; |
135 | 0 | } |
136 | | |
137 | 0 | PyObject *interpolations = PyTuple_New(interpolationslen); |
138 | 0 | if (!interpolations) { |
139 | 0 | Py_DECREF(strings); |
140 | 0 | return NULL; |
141 | 0 | } |
142 | | |
143 | 0 | last_was_str = 0; |
144 | 0 | Py_ssize_t stringsidx = 0, interpolationsidx = 0; |
145 | 0 | for (Py_ssize_t i = 0; i < argslen; i++) { |
146 | 0 | PyObject *item = PyTuple_GET_ITEM(args, i); |
147 | 0 | if (PyUnicode_Check(item)) { |
148 | 0 | if (last_was_str) { |
149 | 0 | PyObject *laststring = PyTuple_GET_ITEM(strings, stringsidx - 1); |
150 | 0 | PyObject *concat = PyUnicode_Concat(laststring, item); |
151 | 0 | Py_DECREF(laststring); |
152 | 0 | if (!concat) { |
153 | 0 | Py_DECREF(strings); |
154 | 0 | Py_DECREF(interpolations); |
155 | 0 | return NULL; |
156 | 0 | } |
157 | 0 | PyTuple_SET_ITEM(strings, stringsidx - 1, concat); |
158 | 0 | } |
159 | 0 | else { |
160 | 0 | PyTuple_SET_ITEM(strings, stringsidx++, Py_NewRef(item)); |
161 | 0 | } |
162 | 0 | last_was_str = 1; |
163 | 0 | } |
164 | 0 | else if (_PyInterpolation_CheckExact(item)) { |
165 | 0 | if (!last_was_str) { |
166 | 0 | _Py_DECLARE_STR(empty, ""); |
167 | 0 | PyTuple_SET_ITEM(strings, stringsidx++, &_Py_STR(empty)); |
168 | 0 | } |
169 | 0 | PyTuple_SET_ITEM(interpolations, interpolationsidx++, Py_NewRef(item)); |
170 | 0 | last_was_str = 0; |
171 | 0 | } |
172 | 0 | } |
173 | 0 | if (!last_was_str) { |
174 | 0 | _Py_DECLARE_STR(empty, ""); |
175 | 0 | PyTuple_SET_ITEM(strings, stringsidx++, &_Py_STR(empty)); |
176 | 0 | } |
177 | |
|
178 | 0 | PyObject *template = _PyTemplate_Build(strings, interpolations); |
179 | 0 | Py_DECREF(strings); |
180 | 0 | Py_DECREF(interpolations); |
181 | 0 | return template; |
182 | 0 | } |
183 | | |
184 | | static void |
185 | | template_dealloc(PyObject *op) |
186 | 2 | { |
187 | 2 | PyObject_GC_UnTrack(op); |
188 | 2 | Py_TYPE(op)->tp_clear(op); |
189 | 2 | Py_TYPE(op)->tp_free(op); |
190 | 2 | } |
191 | | |
192 | | static int |
193 | | template_clear(PyObject *op) |
194 | 2 | { |
195 | 2 | templateobject *self = templateobject_CAST(op); |
196 | 2 | Py_CLEAR(self->strings); |
197 | 2 | Py_CLEAR(self->interpolations); |
198 | 2 | return 0; |
199 | 2 | } |
200 | | |
201 | | static int |
202 | | template_traverse(PyObject *op, visitproc visit, void *arg) |
203 | 0 | { |
204 | 0 | templateobject *self = templateobject_CAST(op); |
205 | 0 | Py_VISIT(self->strings); |
206 | 0 | Py_VISIT(self->interpolations); |
207 | 0 | return 0; |
208 | 0 | } |
209 | | |
210 | | static PyObject * |
211 | | template_repr(PyObject *op) |
212 | 0 | { |
213 | 0 | templateobject *self = templateobject_CAST(op); |
214 | 0 | return PyUnicode_FromFormat("%s(strings=%R, interpolations=%R)", |
215 | 0 | _PyType_Name(Py_TYPE(self)), |
216 | 0 | self->strings, |
217 | 0 | self->interpolations); |
218 | 0 | } |
219 | | |
220 | | static PyObject * |
221 | | template_iter(PyObject *op) |
222 | 0 | { |
223 | 0 | templateobject *self = templateobject_CAST(op); |
224 | 0 | templateiterobject *iter = PyObject_GC_New(templateiterobject, &_PyTemplateIter_Type); |
225 | 0 | if (iter == NULL) { |
226 | 0 | return NULL; |
227 | 0 | } |
228 | | |
229 | 0 | PyObject *stringsiter = PyObject_GetIter(self->strings); |
230 | 0 | if (stringsiter == NULL) { |
231 | 0 | Py_DECREF(iter); |
232 | 0 | return NULL; |
233 | 0 | } |
234 | | |
235 | 0 | PyObject *interpolationsiter = PyObject_GetIter(self->interpolations); |
236 | 0 | if (interpolationsiter == NULL) { |
237 | 0 | Py_DECREF(iter); |
238 | 0 | Py_DECREF(stringsiter); |
239 | 0 | return NULL; |
240 | 0 | } |
241 | | |
242 | 0 | iter->stringsiter = stringsiter; |
243 | 0 | iter->interpolationsiter = interpolationsiter; |
244 | 0 | iter->from_strings = 1; |
245 | 0 | PyObject_GC_Track(iter); |
246 | 0 | return (PyObject *)iter; |
247 | 0 | } |
248 | | |
249 | | static PyObject * |
250 | | template_strings_concat(PyObject *left, PyObject *right) |
251 | 0 | { |
252 | 0 | Py_ssize_t left_stringslen = PyTuple_GET_SIZE(left); |
253 | 0 | PyObject *left_laststring = PyTuple_GET_ITEM(left, left_stringslen - 1); |
254 | 0 | Py_ssize_t right_stringslen = PyTuple_GET_SIZE(right); |
255 | 0 | PyObject *right_firststring = PyTuple_GET_ITEM(right, 0); |
256 | |
|
257 | 0 | PyObject *concat = PyUnicode_Concat(left_laststring, right_firststring); |
258 | 0 | if (concat == NULL) { |
259 | 0 | return NULL; |
260 | 0 | } |
261 | | |
262 | 0 | PyObject *newstrings = PyTuple_New(left_stringslen + right_stringslen - 1); |
263 | 0 | if (newstrings == NULL) { |
264 | 0 | Py_DECREF(concat); |
265 | 0 | return NULL; |
266 | 0 | } |
267 | | |
268 | 0 | Py_ssize_t index = 0; |
269 | 0 | for (Py_ssize_t i = 0; i < left_stringslen - 1; i++) { |
270 | 0 | PyTuple_SET_ITEM(newstrings, index++, Py_NewRef(PyTuple_GET_ITEM(left, i))); |
271 | 0 | } |
272 | 0 | PyTuple_SET_ITEM(newstrings, index++, concat); |
273 | 0 | for (Py_ssize_t i = 1; i < right_stringslen; i++) { |
274 | 0 | PyTuple_SET_ITEM(newstrings, index++, Py_NewRef(PyTuple_GET_ITEM(right, i))); |
275 | 0 | } |
276 | |
|
277 | 0 | return newstrings; |
278 | 0 | } |
279 | | |
280 | | static PyObject * |
281 | | template_concat_templates(templateobject *self, templateobject *other) |
282 | 0 | { |
283 | 0 | PyObject *newstrings = template_strings_concat(self->strings, other->strings); |
284 | 0 | if (newstrings == NULL) { |
285 | 0 | return NULL; |
286 | 0 | } |
287 | | |
288 | 0 | PyObject *newinterpolations = PySequence_Concat(self->interpolations, other->interpolations); |
289 | 0 | if (newinterpolations == NULL) { |
290 | 0 | Py_DECREF(newstrings); |
291 | 0 | return NULL; |
292 | 0 | } |
293 | | |
294 | 0 | PyObject *newtemplate = _PyTemplate_Build(newstrings, newinterpolations); |
295 | 0 | Py_DECREF(newstrings); |
296 | 0 | Py_DECREF(newinterpolations); |
297 | 0 | return newtemplate; |
298 | 0 | } |
299 | | |
300 | | PyObject * |
301 | | _PyTemplate_Concat(PyObject *self, PyObject *other) |
302 | 0 | { |
303 | 0 | if (_PyTemplate_CheckExact(self) && _PyTemplate_CheckExact(other)) { |
304 | 0 | return template_concat_templates((templateobject *) self, (templateobject *) other); |
305 | 0 | } |
306 | | |
307 | 0 | PyErr_Format(PyExc_TypeError, |
308 | 0 | "can only concatenate string.templatelib.Template (not \"%T\") to string.templatelib.Template", |
309 | 0 | other); |
310 | 0 | return NULL; |
311 | 0 | } |
312 | | |
313 | | static PyObject * |
314 | | template_values_get(PyObject *op, void *Py_UNUSED(data)) |
315 | 0 | { |
316 | 0 | templateobject *self = templateobject_CAST(op); |
317 | |
|
318 | 0 | Py_ssize_t len = PyTuple_GET_SIZE(self->interpolations); |
319 | 0 | PyObject *values = PyTuple_New(PyTuple_GET_SIZE(self->interpolations)); |
320 | 0 | if (values == NULL) { |
321 | 0 | return NULL; |
322 | 0 | } |
323 | | |
324 | 0 | for (Py_ssize_t i = 0; i < len; i++) { |
325 | 0 | PyObject *item = PyTuple_GET_ITEM(self->interpolations, i); |
326 | 0 | PyTuple_SET_ITEM(values, i, _PyInterpolation_GetValueRef(item)); |
327 | 0 | } |
328 | |
|
329 | 0 | return values; |
330 | 0 | } |
331 | | |
332 | | static PyMemberDef template_members[] = { |
333 | | {"strings", Py_T_OBJECT_EX, offsetof(templateobject, strings), Py_READONLY, "Strings"}, |
334 | | {"interpolations", Py_T_OBJECT_EX, offsetof(templateobject, interpolations), Py_READONLY, "Interpolations"}, |
335 | | {NULL}, |
336 | | }; |
337 | | |
338 | | static PyGetSetDef template_getset[] = { |
339 | | {"values", template_values_get, NULL, |
340 | | PyDoc_STR("Values of interpolations"), NULL}, |
341 | | {NULL}, |
342 | | }; |
343 | | |
344 | | static PySequenceMethods template_as_sequence = { |
345 | | .sq_concat = _PyTemplate_Concat, |
346 | | }; |
347 | | |
348 | | static PyObject* |
349 | | template_reduce(PyObject *op, PyObject *Py_UNUSED(dummy)) |
350 | 0 | { |
351 | 0 | PyObject *mod = PyImport_ImportModule("string.templatelib"); |
352 | 0 | if (mod == NULL) { |
353 | 0 | return NULL; |
354 | 0 | } |
355 | 0 | PyObject *func = PyObject_GetAttrString(mod, "_template_unpickle"); |
356 | 0 | Py_DECREF(mod); |
357 | 0 | if (func == NULL) { |
358 | 0 | return NULL; |
359 | 0 | } |
360 | | |
361 | 0 | templateobject *self = templateobject_CAST(op); |
362 | 0 | PyObject *result = Py_BuildValue("O(OO)", |
363 | 0 | func, |
364 | 0 | self->strings, |
365 | 0 | self->interpolations); |
366 | |
|
367 | 0 | Py_DECREF(func); |
368 | 0 | return result; |
369 | 0 | } |
370 | | |
371 | | static PyMethodDef template_methods[] = { |
372 | | {"__reduce__", template_reduce, METH_NOARGS, NULL}, |
373 | | {"__class_getitem__", Py_GenericAlias, |
374 | | METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, |
375 | | {NULL, NULL}, |
376 | | }; |
377 | | |
378 | | PyTypeObject _PyTemplate_Type = { |
379 | | PyVarObject_HEAD_INIT(NULL, 0) |
380 | | .tp_name = "string.templatelib.Template", |
381 | | .tp_doc = PyDoc_STR("Template object"), |
382 | | .tp_basicsize = sizeof(templateobject), |
383 | | .tp_itemsize = 0, |
384 | | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, |
385 | | .tp_as_sequence = &template_as_sequence, |
386 | | .tp_new = template_new, |
387 | | .tp_alloc = PyType_GenericAlloc, |
388 | | .tp_dealloc = template_dealloc, |
389 | | .tp_clear = template_clear, |
390 | | .tp_free = PyObject_GC_Del, |
391 | | .tp_repr = template_repr, |
392 | | .tp_members = template_members, |
393 | | .tp_methods = template_methods, |
394 | | .tp_getset = template_getset, |
395 | | .tp_iter = template_iter, |
396 | | .tp_traverse = template_traverse, |
397 | | }; |
398 | | |
399 | | PyObject * |
400 | | _PyTemplate_Build(PyObject *strings, PyObject *interpolations) |
401 | 2 | { |
402 | 2 | templateobject *template = PyObject_GC_New(templateobject, &_PyTemplate_Type); |
403 | 2 | if (template == NULL) { |
404 | 0 | return NULL; |
405 | 0 | } |
406 | | |
407 | 2 | template->strings = Py_NewRef(strings); |
408 | 2 | template->interpolations = Py_NewRef(interpolations); |
409 | 2 | PyObject_GC_Track(template); |
410 | 2 | return (PyObject *) template; |
411 | 2 | } |