1 /**
2    This module contains D code for the contract tests between Python
3    and its C API regarding scalars.  The functions below are meant to
4    document the behaviour of said API when values are passed through
5    the layer between the two languages, be it as function parameters
6    or return values.
7  */
8 module contract.scalars;
9 
10 import python;
11 
12 extern(C):
13 
14 
15 package PyObject* always_none(PyObject* self, PyObject *args) nothrow @nogc {
16     pyIncRef(pyNone);
17     return pyNone;
18 }
19 
20 
21 // Always returns 42. Takes no parameters.
22 // Tests PyLongFromUnsignedLong.
23 package PyObject* the_answer(PyObject* self, PyObject *args) nothrow @nogc {
24 
25     if(!PyArg_ParseTuple(args, ""))
26         return null;
27 
28     return PyLong_FromUnsignedLong(42UL);
29 }
30 
31 
32 // returns the boolean opposite of the bool passed in
33 package PyObject* one_bool_param_to_not(PyObject* self, PyObject *args) nothrow @nogc {
34     if(PyTuple_Size(args) != 1) return null;
35 
36     auto arg = PyTuple_GetItem(args, 0);
37     if(arg is null) return null;
38 
39     if(!pyBoolCheck(arg)) return null;
40     const dArg = arg == pyTrue;
41 
42     return PyBool_FromLong(!dArg);
43 }
44 
45 
46 // returns the number passed in multiplied by two
47 package PyObject* one_int_param_to_times_two(PyObject* self, PyObject *args) nothrow @nogc {
48 
49     long arg;
50 
51     if(!PyArg_ParseTuple(args, "l", &arg))
52         return null;
53 
54     return PyLong_FromLong(arg * 2);
55 }
56 
57 
58 // returns the number passed in multiplied by 3
59 package PyObject* one_double_param_to_times_three(PyObject* self, PyObject *args) nothrow @nogc {
60 
61     double arg;
62 
63     if(!PyArg_ParseTuple(args, "d", &arg))
64         return null;
65 
66     return PyFloat_FromDouble(arg * 3);
67 }
68 
69 
70 // returns the length of the single passed-in string
71 package PyObject* one_string_param_to_length(PyObject* self, PyObject *args) nothrow @nogc {
72     import core.stdc.string: cstrlen = strlen;
73 
74     const char* arg;
75 
76     if(!PyArg_ParseTuple(args, "s", &arg))
77         return null;
78 
79     const auto len = cstrlen(arg);
80     return PyLong_FromLong(len);
81 }
82 
83 
84 // appends "_suffix" to the string passed in
85 package PyObject* one_string_param_to_string(PyObject* self, PyObject *args) nothrow {
86     import std.string: fromStringz;
87 
88     if(PyTuple_Size(args) != 1) {
89         PyErr_SetString(PyExc_TypeError, &"Wrong number of arguments"[0]);
90         return null;
91     }
92 
93     auto arg = PyTuple_GetItem(args, 0);
94     if(arg is null) {
95         PyErr_SetString(PyExc_TypeError, &"Could not get first argument"[0]);
96         return null;
97     }
98 
99     if(!pyUnicodeCheck(arg)) arg = pyObjectUnicode(arg);
100 
101     if(!pyUnicodeCheck(arg)) {
102         PyErr_SetString(PyExc_TypeError, &"Argument not a unicode string"[0]);
103         return null;
104     }
105 
106     auto unicodeArg = pyUnicodeAsUtf8String(arg);
107     if(!unicodeArg) {
108         PyErr_SetString(PyExc_TypeError, &"Could not decode UTF8"[0]);
109         return null;
110     }
111 
112     const ptr = pyBytesAsString(unicodeArg);
113 
114     const ret = ptr.fromStringz ~ "_suffix";
115 
116     return pyUnicodeDecodeUTF8(&ret[0], ret.length, null /*errors*/);
117 }
118 
119 
120 // appends "_suffix" to the string passed in without using the GC
121 package PyObject* one_string_param_to_string_manual_mem(PyObject* self, PyObject *args) nothrow @nogc {
122     import std.string: fromStringz;
123     import core.stdc.stdlib: malloc;
124     import core.stdc.string: strlen;
125 
126     if(PyTuple_Size(args) != 1) {
127         PyErr_SetString(PyExc_TypeError, &"Wrong number of arguments"[0]);
128         return null;
129     }
130 
131     auto arg = PyTuple_GetItem(args, 0);
132     if(arg is null) {
133         PyErr_SetString(PyExc_TypeError, &"Could not get first argument"[0]);
134         return null;
135     }
136 
137     if(!pyUnicodeCheck(arg)) arg = pyObjectUnicode(arg);
138 
139     if(!pyUnicodeCheck(arg)) {
140         PyErr_SetString(PyExc_TypeError, &"Argument not a unicode string"[0]);
141         return null;
142     }
143 
144     auto unicodeArg = pyUnicodeAsUtf8String(arg);
145     if(!unicodeArg) {
146         PyErr_SetString(PyExc_TypeError, &"Could not decode UTF8"[0]);
147         return null;
148     }
149 
150     const ptr = pyBytesAsString(unicodeArg);
151 
152     enum suffix = "_suffix";
153     const ptrLen = strlen(ptr);
154     const retLength = strlen(ptr) + suffix.length;
155     auto ret = cast(char*) malloc(retLength);
156     scope(exit) free(cast(void*) ret);
157 
158     ret[0 .. ptrLen] = ptr[0 .. ptrLen];
159     ret[ptrLen .. retLength] = suffix[];
160 
161     return pyUnicodeDecodeUTF8(&ret[0], retLength, null /*errors*/);
162 }
163 
164 
165 // takes a list and returns its length + 1
166 package PyObject* one_list_param(PyObject* self, PyObject *args) nothrow {
167     if(PyTuple_Size(args) != 1) return null;
168 
169     auto arg = PyTuple_GetItem(args, 0);
170     if(arg is null) return null;
171 
172     if(!pyListCheck(arg)) return null;
173     // lists have iterators
174     if(PyObject_GetIter(arg) is null) return null;
175 
176     return PyLong_FromLong(PyList_Size(arg) + 1);
177 }
178 
179 
180 // takes a list and returns its length + 1
181 package PyObject* one_list_param_to_list(PyObject* self, PyObject *args) nothrow {
182     if(PyTuple_Size(args) != 1) return null;
183 
184     auto arg = PyTuple_GetItem(args, 0);
185     if(arg is null) return null;
186 
187     if(!pyListCheck(arg)) return null;
188 
189     auto ret = PyList_New(PyList_Size(arg));
190 
191     foreach(i; 0 .. PyList_Size(arg))
192         PyList_SetItem(ret, i, PyList_GetItem(arg, i));
193 
194     PyList_Append(ret, PyLong_FromLong(4));
195     PyList_Append(ret, PyLong_FromLong(5));
196 
197     return ret;
198 }
199 
200 
201 // takes a tuple and returns its length + 2
202 package PyObject* one_tuple_param(PyObject* self, PyObject *args) nothrow {
203     if(PyTuple_Size(args) != 1) return null;
204 
205     auto arg = PyTuple_GetItem(args, 0);
206     if(arg is null) return null;
207 
208     if(!pyTupleCheck(arg)) return null;
209     // tuples have iterators
210     if(PyObject_GetIter(arg) is null) return null;
211 
212     return PyLong_FromLong(PyTuple_Size(arg) + 2);
213 }
214 
215 
216 // takes a range and returns its length + 3
217 package PyObject* one_range_param(PyObject* self, PyObject *args) nothrow {
218     if(PyTuple_Size(args) != 1) return null;
219 
220     auto arg = PyTuple_GetItem(args, 0);
221     if(arg is null) return null;
222 
223     auto iter = PyObject_GetIter(arg);
224     if(iter is null) return null;
225 
226     int length;
227     while(PyIter_Next(iter)) ++length;
228     return PyLong_FromLong(length + 3);
229 }
230 
231 
232 // takes a range and returns its length + 3
233 package PyObject* one_dict_param(PyObject* self, PyObject *args) nothrow {
234     if(PyTuple_Size(args) != 1) return null;
235 
236     auto arg = PyTuple_GetItem(args, 0);
237     if(arg is null) return null;
238 
239     if(!pyDictCheck(arg)) return null;
240 
241     return PyLong_FromLong(PyDict_Size(arg));
242 }
243 
244 
245 // takes a dict and returns a copy with an extra `"oops": "noooo"` in it
246 package PyObject* one_dict_param_to_dict(PyObject* self, PyObject *args) nothrow {
247     if(PyTuple_Size(args) != 1) return null;
248 
249     auto arg = PyTuple_GetItem(args, 0);
250     if(arg is null) return null;
251 
252     if(!pyDictCheck(arg)) return null;
253 
254     auto ret = PyDict_New;
255 
256     auto keysIter = PyObject_GetIter(PyDict_Keys(arg));
257     for(auto key = PyIter_Next(keysIter);
258         key;
259         key = PyIter_Next(keysIter))
260     {
261         PyDict_SetItem(ret, key, PyDict_GetItem(arg, key));
262     }
263 
264     const oops = "oops";
265     const no = "noooo";
266 
267     auto pyOops = pyUnicodeDecodeUTF8(&oops[0], oops.length, null /*errors*/);
268     auto pyNo = pyUnicodeDecodeUTF8(&no[0], no.length, null /*errors*/);
269 
270     PyDict_SetItem(ret, pyOops, pyNo);
271 
272     return ret;
273 }
274 
275 
276 // Adds a certain number of days to the passed in object
277 package PyObject* add_days_to_date(PyObject* self, PyObject *args) {
278     import std.datetime: Date, days;
279 
280     if(PyTuple_Size(args) != 2) return null;
281 
282     auto dateArg = PyTuple_GetItem(args, 0);
283     if(dateArg is null) return null;
284 
285     auto deltaArg = PyTuple_GetItem(args, 1);
286     if(deltaArg is null) return null;
287 
288     const year = pyDateTimeYear(dateArg);
289     const month = pyDateTimeMonth(dateArg);
290     const day = pyDateTimeDay(dateArg);
291 
292     auto date = Date(year, month, day);
293     date += PyLong_AsLong(deltaArg).days;
294 
295     return pyDateFromDate(date.year, date.month, date.day);
296 }
297 
298 
299 // Adds a certain number of days to the passed in object
300 package PyObject* add_days_to_datetime(PyObject* self, PyObject *args) {
301     import std.datetime: DateTime, days;
302 
303     if(PyTuple_Size(args) != 2) return null;
304 
305     auto dateArg = PyTuple_GetItem(args, 0);
306     if(dateArg is null) return null;
307 
308     auto deltaArg = PyTuple_GetItem(args, 1);
309     if(deltaArg is null) return null;
310 
311     const year = pyDateTimeYear(dateArg);
312     const month = pyDateTimeMonth(dateArg);
313     const day = pyDateTimeDay(dateArg);
314     const hour = pyDateTimeHour(dateArg);
315     const minute = pyDateTimeMinute(dateArg);
316     const second = pyDateTimeSecond(dateArg);
317 
318     auto date = DateTime(year, month, day, hour, minute, second);
319     date += PyLong_AsLong(deltaArg).days;
320 
321     return pyDateTimeFromDateAndTime(date.year, date.month, date.day,
322                                      date.hour, date.minute, date.second);
323 }
324 
325 
326 
327 package PyObject* throws_runtime_error(PyObject* self, PyObject *args) @nogc nothrow {
328     enum str = "Generic runtime error";
329     PyErr_SetString(PyExc_RuntimeError, str.ptr);
330     return null;
331 }
332 
333 
334 // Returns the length of kwargs, which is a dict
335 package PyObject* kwargs_count(PyObject* self, PyObject *args, PyObject* kwargs) @nogc nothrow {
336     if(PyTuple_Size(args) != 2) {
337         PyErr_BadArgument();
338         return null;
339     }
340 
341     return PyLong_FromLong(PyObject_Length(kwargs));
342 }