@@ -28,6 +28,70 @@ static char *jsonnet_str(struct JsonnetVm *vm, const char *str)
28
28
return out ;
29
29
}
30
30
31
+ static const char * exc_to_str (void )
32
+ {
33
+ PyObject * ptype , * pvalue , * ptraceback ;
34
+ PyErr_Fetch (& ptype , & pvalue , & ptraceback );
35
+ PyObject * exc_str = PyObject_Str (pvalue );
36
+ return PyString_AsString (exc_str );
37
+ }
38
+
39
+ struct NativeCtx {
40
+ struct JsonnetVm * vm ;
41
+ PyObject * callback ;
42
+ size_t argc ;
43
+ };
44
+
45
+ /* This function is bound for every native callback, but with a different
46
+ * context.
47
+ */
48
+ static struct JsonnetJsonValue * cpython_native_callback (
49
+ void * ctx_ , const struct JsonnetJsonValue * const * argv , int * succ )
50
+ {
51
+ const struct NativeCtx * ctx = ctx_ ;
52
+ int i ;
53
+
54
+ PyObject * arglist ; // Will hold a tuple of strings.
55
+ PyObject * result ; // Will hold a string.
56
+
57
+ // Populate python function args.
58
+ arglist = PyTuple_New (ctx -> argc );
59
+ for (i = 0 ; i < ctx -> argc ; ++ i ) {
60
+ const char * param = jsonnet_json_extract_string (ctx -> vm , argv [i ]);
61
+ if (param == NULL ) {
62
+ Py_DECREF (arglist );
63
+ * succ = 0 ;
64
+ return jsonnet_json_make_string (ctx -> vm , "Non-string param." );
65
+ }
66
+ PyTuple_SetItem (arglist , i , PyString_FromString (param ));
67
+ }
68
+
69
+ // Call python function.
70
+ result = PyEval_CallObject (ctx -> callback , arglist );
71
+ Py_DECREF (arglist );
72
+
73
+ if (result == NULL ) {
74
+ // Get string from exception.
75
+ struct JsonnetJsonValue * r = jsonnet_json_make_string (ctx -> vm , exc_to_str ());
76
+ * succ = 0 ;
77
+ PyErr_Clear ();
78
+ return r ;
79
+ }
80
+
81
+ if (!PyString_Check (result )) {
82
+ struct JsonnetJsonValue * r =
83
+ jsonnet_json_make_string (ctx -> vm , "Python function did not return string" );
84
+ * succ = 0 ;
85
+ return r ;
86
+ }
87
+
88
+ struct JsonnetJsonValue * r =
89
+ jsonnet_json_make_string (ctx -> vm , PyString_AsString (result ));
90
+ * succ = 1 ;
91
+ return r ;
92
+ }
93
+
94
+
31
95
struct ImportCtx {
32
96
struct JsonnetVm * vm ;
33
97
PyObject * callback ;
@@ -46,13 +110,7 @@ static char *cpython_import_callback(void *ctx_, const char *base, const char *r
46
110
47
111
if (result == NULL ) {
48
112
// Get string from exception
49
- PyObject * ptype ;
50
- PyObject * pvalue ;
51
- PyObject * ptraceback ;
52
- PyErr_Fetch (& ptype , & pvalue , & ptraceback );
53
- PyObject * exc_str = PyObject_Str (pvalue );
54
- const char * exc_cstr = PyString_AsString (exc_str );
55
- char * out = jsonnet_str (ctx -> vm , exc_cstr );
113
+ char * out = jsonnet_str (ctx -> vm , exc_to_str ());
56
114
* success = 0 ;
57
115
PyErr_Clear ();
58
116
return out ;
@@ -136,6 +194,7 @@ int handle_import_callback(struct ImportCtx *ctx, PyObject *import_callback)
136
194
if (import_callback == NULL ) return 1 ;
137
195
138
196
if (!PyCallable_Check (import_callback )) {
197
+ jsonnet_destroy (ctx -> vm );
139
198
PyErr_SetString (PyExc_TypeError , "import_callback must be callable" );
140
199
return 0 ;
141
200
}
@@ -146,6 +205,104 @@ int handle_import_callback(struct ImportCtx *ctx, PyObject *import_callback)
146
205
}
147
206
148
207
208
+ /** Register native callbacks with Jsonnet VM.
209
+ *
210
+ * Example native_callbacks = { 'name': (('p1', 'p2', 'p3'), func) }
211
+ *
212
+ * May set *ctxs, in which case it should be free()'d by caller.
213
+ *
214
+ * \returns 1 on success, 0 with exception set upon failure.
215
+ */
216
+ static int handle_native_callbacks (struct JsonnetVm * vm , PyObject * native_callbacks ,
217
+ struct NativeCtx * * ctxs )
218
+ {
219
+ size_t num_natives = 0 ;
220
+ PyObject * key , * val ;
221
+ Py_ssize_t pos = 0 ;
222
+
223
+ if (native_callbacks == NULL ) return 1 ;
224
+
225
+ /* Verify the input before we allocate memory, throw all errors at this point.
226
+ * Also, count the callbacks to see how much memory we need.
227
+ */
228
+ while (PyDict_Next (native_callbacks , & pos , & key , & val )) {
229
+ Py_ssize_t i ;
230
+ Py_ssize_t num_params ;
231
+ PyObject * params ;
232
+ const char * key_ = PyString_AsString (key );
233
+ if (key_ == NULL ) {
234
+ PyErr_SetString (PyExc_TypeError , "native callback dict keys must be string" );
235
+ goto bad ;
236
+ }
237
+ if (!PyTuple_Check (val )) {
238
+ PyErr_SetString (PyExc_TypeError , "native callback dict values must be tuples" );
239
+ goto bad ;
240
+ } else if (PyTuple_Size (val ) != 2 ) {
241
+ PyErr_SetString (PyExc_TypeError , "native callback tuples must have size 2" );
242
+ goto bad ;
243
+ }
244
+ params = PyTuple_GetItem (val , 0 );
245
+ if (!PyTuple_Check (params )) {
246
+ PyErr_SetString (PyExc_TypeError , "native callback params must be a tuple" );
247
+ goto bad ;
248
+ }
249
+ /* Check the params are all strings */
250
+ num_params = PyTuple_Size (params );
251
+ for (i = 0 ; i < num_params ; ++ i ) {
252
+ PyObject * param = PyTuple_GetItem (params , 0 );
253
+ if (!PyString_Check (param )) {
254
+ PyErr_SetString (PyExc_TypeError , "native callback param must be string" );
255
+ goto bad ;
256
+ }
257
+ }
258
+ if (!PyCallable_Check (PyTuple_GetItem (val , 1 ))) {
259
+ PyErr_SetString (PyExc_TypeError , "native callback must be callable" );
260
+ goto bad ;
261
+ }
262
+
263
+ num_natives ++ ;
264
+ continue ;
265
+
266
+ bad :
267
+ jsonnet_destroy (vm );
268
+ return 0 ;
269
+ }
270
+
271
+ if (num_natives == 0 ) {
272
+ return 1 ;
273
+ }
274
+
275
+ * ctxs = malloc (sizeof (struct NativeCtx ) * num_natives );
276
+
277
+ /* Re-use num_natives but just as a counter this time. */
278
+ num_natives = 0 ;
279
+ pos = 0 ;
280
+ while (PyDict_Next (native_callbacks , & pos , & key , & val )) {
281
+ Py_ssize_t i ;
282
+ Py_ssize_t num_params ;
283
+ PyObject * params ;
284
+ const char * key_ = PyString_AsString (key );
285
+ params = PyTuple_GetItem (val , 0 );
286
+ num_params = PyTuple_Size (params );
287
+ /* Include space for terminating NULL. */
288
+ const char * * params_c = malloc (sizeof (const char * ) * (num_params + 1 ));
289
+ for (i = 0 ; i < num_params ; ++ i ) {
290
+ params_c [i ] = PyString_AsString (PyTuple_GetItem (params , i ));
291
+ }
292
+ params_c [num_params ] = NULL ;
293
+ (* ctxs )[num_natives ].vm = vm ;
294
+ (* ctxs )[num_natives ].callback = PyTuple_GetItem (val , 1 );
295
+ (* ctxs )[num_natives ].argc = num_params ;
296
+ jsonnet_native_callback (vm , key_ , cpython_native_callback , & (* ctxs )[num_natives ],
297
+ params_c );
298
+ free (params_c );
299
+ num_natives ++ ;
300
+ }
301
+
302
+ return 1 ;
303
+ }
304
+
305
+
149
306
static PyObject * evaluate_file (PyObject * self , PyObject * args , PyObject * keywds )
150
307
{
151
308
const char * filename ;
@@ -156,21 +313,24 @@ static PyObject* evaluate_file(PyObject* self, PyObject* args, PyObject *keywds)
156
313
PyObject * ext_vars = NULL , * ext_codes = NULL ;
157
314
PyObject * tla_vars = NULL , * tla_codes = NULL ;
158
315
PyObject * import_callback = NULL ;
316
+ PyObject * native_callbacks = NULL ;
159
317
struct JsonnetVm * vm ;
160
318
static char * kwlist [] = {
161
319
"filename" ,
162
320
"max_stack" , "gc_min_objects" , "gc_growth_trigger" , "ext_vars" ,
163
321
"ext_codes" , "tla_vars" , "tla_codes" , "max_trace" , "import_callback" ,
322
+ "native_callbacks" ,
164
323
NULL
165
324
};
166
325
167
326
(void ) self ;
168
327
169
328
if (!PyArg_ParseTupleAndKeywords (
170
- args , keywds , "s|IIdOOOOIO " , kwlist ,
329
+ args , keywds , "s|IIdOOOOIOO " , kwlist ,
171
330
& filename ,
172
331
& max_stack , & gc_min_objects , & gc_growth_trigger , & ext_vars ,
173
- & ext_codes , & tla_vars , & tla_codes , & max_trace , & import_callback )) {
332
+ & ext_codes , & tla_vars , & tla_codes , & max_trace , & import_callback ,
333
+ & native_callbacks )) {
174
334
return NULL ;
175
335
}
176
336
@@ -187,8 +347,13 @@ static PyObject* evaluate_file(PyObject* self, PyObject* args, PyObject *keywds)
187
347
if (!handle_import_callback (& ctx , import_callback )) {
188
348
return NULL ;
189
349
}
190
-
350
+ struct NativeCtx * ctxs = NULL ;
351
+ if (!handle_native_callbacks (vm , native_callbacks , & ctxs )) {
352
+ free (ctxs );
353
+ return NULL ;
354
+ }
191
355
out = jsonnet_evaluate_file (vm , filename , & error );
356
+ free (ctxs );
192
357
return handle_result (vm , out , error );
193
358
}
194
359
@@ -202,21 +367,24 @@ static PyObject* evaluate_snippet(PyObject* self, PyObject* args, PyObject *keyw
202
367
PyObject * ext_vars = NULL , * ext_codes = NULL ;
203
368
PyObject * tla_vars = NULL , * tla_codes = NULL ;
204
369
PyObject * import_callback = NULL ;
370
+ PyObject * native_callbacks = NULL ;
205
371
struct JsonnetVm * vm ;
206
372
static char * kwlist [] = {
207
373
"filename" , "src" ,
208
374
"max_stack" , "gc_min_objects" , "gc_growth_trigger" , "ext_vars" ,
209
375
"ext_codes" , "tla_vars" , "tla_codes" , "max_trace" , "import_callback" ,
376
+ "native_callbacks" ,
210
377
NULL
211
378
};
212
379
213
380
(void ) self ;
214
381
215
382
if (!PyArg_ParseTupleAndKeywords (
216
- args , keywds , "ss|IIdOOOOIO " , kwlist ,
383
+ args , keywds , "ss|IIdOOOOIOO " , kwlist ,
217
384
& filename , & src ,
218
385
& max_stack , & gc_min_objects , & gc_growth_trigger , & ext_vars ,
219
- & ext_codes , & tla_vars , & tla_codes , & max_trace , & import_callback )) {
386
+ & ext_codes , & tla_vars , & tla_codes , & max_trace , & import_callback ,
387
+ & native_callbacks )) {
220
388
return NULL ;
221
389
}
222
390
@@ -233,8 +401,13 @@ static PyObject* evaluate_snippet(PyObject* self, PyObject* args, PyObject *keyw
233
401
if (!handle_import_callback (& ctx , import_callback )) {
234
402
return NULL ;
235
403
}
236
-
404
+ struct NativeCtx * ctxs = NULL ;
405
+ if (!handle_native_callbacks (vm , native_callbacks , & ctxs )) {
406
+ free (ctxs );
407
+ return NULL ;
408
+ }
237
409
out = jsonnet_evaluate_snippet (vm , filename , src , & error );
410
+ free (ctxs );
238
411
return handle_result (vm , out , error );
239
412
}
240
413
0 commit comments