1 |
johnpye |
862 |
/* ASCEND modelling environment |
2 |
|
|
Copyright (C) 2006 Carnegie Mellon University |
3 |
|
|
|
4 |
|
|
This program is free software; you can redistribute it and/or modify |
5 |
|
|
it under the terms of the GNU General Public License as published by |
6 |
|
|
the Free Software Foundation; either version 2, or (at your option) |
7 |
|
|
any later version. |
8 |
|
|
|
9 |
|
|
This program is distributed in the hope that it will be useful, |
10 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 |
|
|
GNU General Public License for more details. |
13 |
|
|
|
14 |
|
|
You should have received a copy of the GNU General Public License |
15 |
jpye |
2649 |
along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 |
johnpye |
869 |
*//** |
17 |
|
|
@file |
18 |
|
|
Import handler to provide external python script functionality for ASCEND. |
19 |
johnpye |
880 |
*//* |
20 |
|
|
by John Pye, Oct 2006 |
21 |
johnpye |
862 |
*/ |
22 |
|
|
|
23 |
johnpye |
982 |
#ifndef WITH_PYTHON |
24 |
|
|
# error "Can't build 'extpy' without WITH_PYTHON set" |
25 |
|
|
#else |
26 |
|
|
|
27 |
johnpye |
912 |
#include <Python.h> |
28 |
|
|
|
29 |
johnpye |
862 |
#include <stdio.h> |
30 |
|
|
#include <string.h> |
31 |
|
|
|
32 |
jpye |
2323 |
#include <ascend/general/platform.h> |
33 |
jpye |
2018 |
#include <ascend/utilities/error.h> |
34 |
|
|
#include <ascend/general/ospath.h> |
35 |
johnpye |
862 |
|
36 |
jpye |
2018 |
#include <ascend/compiler/importhandler.h> |
37 |
|
|
#include <ascend/compiler/extfunc.h> |
38 |
johnpye |
862 |
|
39 |
jpye |
3258 |
//#define EXTPY_DEBUG |
40 |
jpye |
2803 |
#ifdef EXTPY_DEBUG |
41 |
|
|
# define MSG CONSOLE_DEBUG |
42 |
|
|
#else |
43 |
|
|
# define MSG(ARGS...) ((void)0) |
44 |
|
|
#endif |
45 |
johnpye |
865 |
|
46 |
jpye |
1332 |
|
47 |
johnpye |
862 |
ImportHandlerCreateFilenameFn extpy_filename; |
48 |
|
|
ImportHandlerImportFn extpy_import; |
49 |
jpye |
2334 |
ImportHandlerDestroyFn extpy_handler_destroy; |
50 |
johnpye |
862 |
|
51 |
johnpye |
914 |
ExtMethodDestroyFn extpy_destroy; |
52 |
|
|
|
53 |
|
|
|
54 |
johnpye |
862 |
#ifndef ASC_EXPORT |
55 |
|
|
# error "Where is ASC_EXPORT?" |
56 |
|
|
#endif |
57 |
|
|
|
58 |
johnpye |
912 |
struct ExtPyData{ |
59 |
|
|
PyObject *fn; |
60 |
|
|
char *name; |
61 |
|
|
}; |
62 |
|
|
|
63 |
johnpye |
862 |
/** |
64 |
|
|
This is the function called from "IMPORT extpy" |
65 |
|
|
|
66 |
|
|
It sets up the functions in this external function library |
67 |
|
|
*/ |
68 |
johnpye |
1063 |
extern ASC_EXPORT int extpy_register(){ |
69 |
johnpye |
862 |
int result = 0; |
70 |
|
|
|
71 |
|
|
struct ImportHandler *handler; |
72 |
|
|
handler = ASC_NEW(struct ImportHandler); |
73 |
|
|
|
74 |
|
|
handler->name = "extpy"; |
75 |
johnpye |
912 |
handler->filenamefn = &extpy_filename; |
76 |
|
|
handler->importfn = &extpy_import; |
77 |
jpye |
2334 |
handler->destroyfn = &extpy_handler_destroy; |
78 |
johnpye |
862 |
|
79 |
|
|
result = importhandler_add(handler); |
80 |
|
|
|
81 |
|
|
if(result){ |
82 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to register import handler (error = %d)",result); |
83 |
|
|
} |
84 |
johnpye |
1059 |
|
85 |
jpye |
3258 |
ERROR_REPORTER_HERE(ASC_USER_WARNING,"'extpy' import handler is still EXPERIMENTAL."); |
86 |
jpye |
2649 |
|
87 |
johnpye |
862 |
return result; |
88 |
|
|
} |
89 |
|
|
|
90 |
jpye |
2334 |
int extpy_handler_destroy(struct ImportHandler *handler){ |
91 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_WARNING,"Not implemented"); |
92 |
|
|
return 0; |
93 |
|
|
} |
94 |
|
|
|
95 |
johnpye |
869 |
/*------------------------------------------------------------------------------ |
96 |
johnpye |
873 |
PYTHON METHOD INVOKER |
97 |
johnpye |
869 |
*/ |
98 |
|
|
|
99 |
johnpye |
873 |
ExtMethodRun extpy_invokemethod; |
100 |
|
|
|
101 |
|
|
/** Method invoker. extpy will supply a pointer to this function whenever it |
102 |
|
|
registers a python function as an external script method. This function will |
103 |
|
|
then dereference the user_data field into a python function, and execute that |
104 |
|
|
python function. |
105 |
|
|
|
106 |
|
|
One difficult aspect is the question of how to usefully pass the 'context' |
107 |
|
|
argument to Python? |
108 |
|
|
*/ |
109 |
|
|
int extpy_invokemethod(struct Instance *context, struct gl_list_t *args, void *user_data){ |
110 |
johnpye |
937 |
PyObject *arglist=NULL, *result=NULL, *pyinstance=NULL, *dict=NULL |
111 |
|
|
, *mainmodule=NULL, *errstring=NULL, *errtypestring=NULL; |
112 |
|
|
PyObject *perrtype=NULL, *perrvalue=NULL, *perrtrace=NULL; |
113 |
|
|
|
114 |
|
|
int ret; |
115 |
johnpye |
912 |
struct ExtPyData *extpydata; |
116 |
johnpye |
937 |
|
117 |
johnpye |
873 |
/* cast user data to PyObject pointer */ |
118 |
johnpye |
937 |
extpydata = (struct ExtPyData *)user_data; |
119 |
johnpye |
874 |
|
120 |
johnpye |
937 |
mainmodule = PyImport_AddModule("__main__"); |
121 |
|
|
if(mainmodule==NULL){ |
122 |
jpye |
2803 |
MSG("Unable to retrieve __main__ module"); |
123 |
johnpye |
937 |
ret = 1; |
124 |
|
|
goto cleanup_extpy_invokemethod; |
125 |
|
|
} |
126 |
johnpye |
874 |
|
127 |
johnpye |
937 |
dict = PyModule_GetDict(mainmodule); |
128 |
|
|
if(dict==NULL){ |
129 |
jpye |
2803 |
MSG("Unable to retrieve __main__ dict"); |
130 |
johnpye |
937 |
ret = 1; |
131 |
|
|
goto cleanup_extpy_invokemethod; |
132 |
|
|
} |
133 |
johnpye |
873 |
|
134 |
jpye |
2803 |
MSG("Running python method '%s'",extpydata->name); |
135 |
johnpye |
912 |
|
136 |
|
|
if(!PyCallable_Check(extpydata->fn)){ |
137 |
johnpye |
874 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"user_data is not a PyCallable"); |
138 |
johnpye |
937 |
ret = 1; |
139 |
|
|
goto cleanup_extpy_invokemethod; |
140 |
johnpye |
874 |
} |
141 |
|
|
|
142 |
johnpye |
875 |
/* |
143 |
|
|
We need to be able to convert C 'struct Instance' pointers to Python 'Instance' objects. |
144 |
|
|
This functionality is implemented in 'ascpy' but we're not going to link to that here, |
145 |
johnpye |
877 |
so we will use the importhandler 'setsharedpointer' trick to pass the object to the |
146 |
|
|
'registry' then write a routine in ascpy that will cast it into the appropriate |
147 |
|
|
Python object. |
148 |
johnpye |
875 |
*/ |
149 |
|
|
importhandler_setsharedpointer("context",(void *)context); |
150 |
|
|
|
151 |
johnpye |
937 |
PyErr_Clear(); |
152 |
|
|
pyinstance = PyRun_String("ascpy.Registry().getInstance('context')",Py_eval_input,dict,dict); |
153 |
|
|
if(PyErr_Occurred()){ |
154 |
jpye |
2803 |
MSG("Failed retrieving instance"); |
155 |
johnpye |
937 |
ret = 1; |
156 |
|
|
goto cleanup_extpy_invokemethod; |
157 |
|
|
} |
158 |
johnpye |
875 |
|
159 |
johnpye |
937 |
arglist = Py_BuildValue("(O)", pyinstance); |
160 |
|
|
|
161 |
jpye |
2649 |
|
162 |
johnpye |
937 |
PyErr_Clear(); |
163 |
johnpye |
912 |
result = PyEval_CallObject(extpydata->fn, arglist); |
164 |
johnpye |
875 |
|
165 |
johnpye |
874 |
if(PyErr_Occurred()){ |
166 |
jpye |
2803 |
MSG("Error occured in PyEval_CallObject"); |
167 |
johnpye |
937 |
|
168 |
|
|
/* get the content of the error message */ |
169 |
jpye |
2649 |
PyErr_Fetch(&perrtype, &perrvalue, &perrtrace); |
170 |
johnpye |
937 |
|
171 |
|
|
errtypestring = NULL; |
172 |
|
|
if(perrtype != NULL |
173 |
|
|
&& (errtypestring = PyObject_Str(perrtype)) != NULL |
174 |
|
|
&& PyString_Check(errtypestring) |
175 |
|
|
){ |
176 |
|
|
// nothing |
177 |
|
|
}else{ |
178 |
|
|
errtypestring = Py_BuildValue(""); |
179 |
|
|
} |
180 |
jpye |
3258 |
MSG("errtypestring = \"%s\"",PyString_AsString(errtypestring)); |
181 |
jpye |
2649 |
|
182 |
jpye |
3258 |
MSG("errtypestring = \"%s\"",PyString_AsString(errtypestring)); |
183 |
|
|
|
184 |
johnpye |
937 |
errstring = NULL; |
185 |
|
|
if(perrvalue != NULL |
186 |
|
|
&& (errstring = PyObject_Str(perrvalue)) != NULL |
187 |
|
|
&& PyString_Check(errstring) |
188 |
|
|
){ |
189 |
jpye |
3258 |
MSG("errstring = \"%s\"",PyString_AsString(errstring)); |
190 |
johnpye |
937 |
error_reporter(ASC_PROG_ERR |
191 |
|
|
,extpydata->name,0 |
192 |
|
|
,PyString_AsString(errtypestring) |
193 |
jpye |
3258 |
,"Error in extpy call: %s",PyString_AsString(errstring) |
194 |
johnpye |
937 |
); |
195 |
|
|
}else{ |
196 |
|
|
error_reporter(ASC_PROG_ERR,extpydata->name,0,extpydata->name,"(unknown python error)"); |
197 |
|
|
} |
198 |
johnpye |
875 |
PyErr_Print(); |
199 |
johnpye |
937 |
ret = 1; |
200 |
|
|
goto cleanup_extpy_invokemethod; |
201 |
johnpye |
874 |
} |
202 |
|
|
|
203 |
johnpye |
937 |
ret=0; |
204 |
|
|
|
205 |
|
|
cleanup_extpy_invokemethod: |
206 |
|
|
Py_XDECREF(dict); |
207 |
|
|
Py_XDECREF(arglist); |
208 |
|
|
Py_XDECREF(pyinstance); |
209 |
|
|
Py_XDECREF(errstring); |
210 |
|
|
Py_XDECREF(errtypestring); |
211 |
|
|
Py_XDECREF(perrtype); |
212 |
|
|
Py_XDECREF(perrvalue); |
213 |
|
|
Py_XDECREF(perrtrace); |
214 |
|
|
return ret; |
215 |
johnpye |
873 |
} |
216 |
|
|
|
217 |
johnpye |
912 |
/** |
218 |
|
|
Free memory associated with a registered script method. |
219 |
johnpye |
914 |
@return 0 on success |
220 |
johnpye |
912 |
*/ |
221 |
johnpye |
914 |
int extpy_destroy(void *user_data){ |
222 |
johnpye |
912 |
struct ExtPyData *extpydata; |
223 |
|
|
extpydata = (struct ExtPyData *)user_data; |
224 |
|
|
Py_DECREF(extpydata->fn); |
225 |
|
|
ASC_FREE(extpydata->name); |
226 |
|
|
ASC_FREE(extpydata); |
227 |
johnpye |
914 |
return 0; |
228 |
johnpye |
912 |
} |
229 |
|
|
|
230 |
johnpye |
873 |
/*------------------------------------------------------------------------------ |
231 |
|
|
'EXTPY' PYTHON STATIC MODULE |
232 |
|
|
*/ |
233 |
|
|
|
234 |
johnpye |
869 |
static PyObject *extpy_getbrowser(PyObject *self, PyObject *args){ |
235 |
|
|
PyObject *browser; |
236 |
|
|
if(args!=NULL){ |
237 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_ERR,"args is not NULL?!"); |
238 |
|
|
} |
239 |
|
|
browser = (PyObject *)importhandler_getsharedpointer("browser"); |
240 |
johnpye |
930 |
if(browser==NULL){ |
241 |
johnpye |
935 |
return Py_BuildValue(""); |
242 |
johnpye |
930 |
} |
243 |
johnpye |
1055 |
return browser; |
244 |
|
|
/* return Py_BuildValue("O",browser);*/ |
245 |
johnpye |
869 |
} |
246 |
|
|
|
247 |
johnpye |
873 |
static PyObject *extpy_registermethod(PyObject *self, PyObject *args){ |
248 |
|
|
PyObject *fn, *name, *docstring; |
249 |
|
|
const char *cname, *cdocstring; |
250 |
|
|
int res; |
251 |
|
|
int nargs = 1; |
252 |
johnpye |
912 |
struct ExtPyData *extpydata; |
253 |
johnpye |
873 |
|
254 |
|
|
PyArg_ParseTuple(args,"O:registermethod", &fn); |
255 |
|
|
if(!PyCallable_Check(fn)){ |
256 |
|
|
PyErr_SetString(PyExc_TypeError,"parameter must be callable"); |
257 |
|
|
return NULL; |
258 |
|
|
} |
259 |
|
|
|
260 |
jpye |
2803 |
/* MSG("FOUND FN=%p",fn); */ |
261 |
johnpye |
873 |
|
262 |
|
|
name = PyObject_GetAttr(fn,PyString_FromString("__name__")); |
263 |
|
|
if(name==NULL){ |
264 |
jpye |
2803 |
MSG("No __name__ attribute"); |
265 |
johnpye |
873 |
PyErr_SetString(PyExc_TypeError,"No __name__ attribute"); |
266 |
|
|
return NULL; |
267 |
|
|
} |
268 |
|
|
cname = PyString_AsString(name); |
269 |
|
|
|
270 |
jpye |
2803 |
/* MSG("REGISTERED METHOD '%s' HAS %d ARGS",cname,nargs); */ |
271 |
johnpye |
873 |
|
272 |
|
|
docstring = PyObject_GetAttr(fn,PyString_FromString("func_doc")); |
273 |
|
|
cdocstring = "(no help)"; |
274 |
|
|
if(name!=NULL){ |
275 |
|
|
cdocstring = PyString_AsString(docstring); |
276 |
jpye |
2803 |
//MSG("DOCSTRING: %s",cdocstring); |
277 |
johnpye |
873 |
} |
278 |
|
|
|
279 |
johnpye |
912 |
extpydata = ASC_NEW(struct ExtPyData); |
280 |
|
|
extpydata->name = ASC_NEW_ARRAY(char,strlen(cname)+1); |
281 |
|
|
extpydata->fn = fn; |
282 |
|
|
strcpy(extpydata->name, cname); |
283 |
|
|
|
284 |
|
|
res = CreateUserFunctionMethod(cname,&extpy_invokemethod,nargs,cdocstring,(void *)extpydata,&extpy_destroy); |
285 |
johnpye |
874 |
Py_INCREF(fn); |
286 |
johnpye |
873 |
|
287 |
jpye |
2803 |
/* MSG("EXTPY 'fn' IS AT %p",fn); */ |
288 |
johnpye |
873 |
|
289 |
jpye |
2803 |
/* MSG("EXTPY INVOKER IS AT %p",extpy_invokemethod); */ |
290 |
johnpye |
912 |
|
291 |
johnpye |
873 |
if(res){ |
292 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Problem registering external script method (%d)",res); |
293 |
|
|
PyErr_SetString(PyExc_Exception,"unable to register script method"); |
294 |
|
|
return NULL; |
295 |
|
|
} |
296 |
|
|
|
297 |
jpye |
3258 |
MSG("Registered python method '%s'",cname); |
298 |
johnpye |
873 |
|
299 |
|
|
/* nothing gets returned (but possibly an exception) */ |
300 |
johnpye |
935 |
return Py_BuildValue(""); |
301 |
johnpye |
873 |
} |
302 |
|
|
|
303 |
johnpye |
869 |
static PyMethodDef extpymethods[] = { |
304 |
|
|
{"getbrowser", extpy_getbrowser, METH_NOARGS,"Retrieve browser pointer"} |
305 |
johnpye |
873 |
,{"registermethod", extpy_registermethod, METH_VARARGS,"Register a python method as an ASCEND script method"} |
306 |
johnpye |
869 |
,{NULL,NULL,0,NULL} |
307 |
|
|
}; |
308 |
|
|
|
309 |
|
|
PyMODINIT_FUNC initextpy(void){ |
310 |
|
|
PyObject *obj; |
311 |
|
|
obj = Py_InitModule3("extpy", extpymethods,"Module for accessing shared ASCEND pointers from python"); |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
/*------------------------------------------------------------------------------ |
315 |
|
|
STANDARD IMPORT HANDLER ROUTINES |
316 |
|
|
*/ |
317 |
|
|
|
318 |
johnpye |
862 |
/** |
319 |
|
|
Create a filename base on a partial filename. In that case of python, this |
320 |
|
|
just means adding '.py' to the end. |
321 |
johnpye |
867 |
|
322 |
johnpye |
862 |
@param partialname the filename without suffix, as specified in the user's "IMPORT" command |
323 |
|
|
@return new filename, or NULL on failure |
324 |
|
|
*/ |
325 |
|
|
char *extpy_filename(const char *partialname){ |
326 |
|
|
char *name; |
327 |
|
|
int len; |
328 |
|
|
if(partialname==NULL){ |
329 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Partial name is NULL, can't work out filename"); |
330 |
|
|
return NULL; |
331 |
|
|
} |
332 |
johnpye |
867 |
|
333 |
johnpye |
862 |
len = strlen(partialname); |
334 |
|
|
name = ASC_NEW_ARRAY_CLEAR(char,len+4); |
335 |
|
|
strcpy(name,partialname); |
336 |
|
|
strcat(name,".py"); |
337 |
jpye |
2803 |
MSG("New filename is '%s'",name); |
338 |
johnpye |
862 |
return name; |
339 |
|
|
} |
340 |
|
|
|
341 |
|
|
/** |
342 |
|
|
Import a python script located at the path indicated. |
343 |
|
|
|
344 |
|
|
@return 0 on success, else error codes (TBD) |
345 |
|
|
*/ |
346 |
johnpye |
864 |
int extpy_import(const struct FilePath *fp, const char *initfunc, const char *partialpath){ |
347 |
johnpye |
862 |
char *name; |
348 |
|
|
name = ospath_str(fp); |
349 |
johnpye |
865 |
FILE *f; |
350 |
johnpye |
872 |
PyObject *pyfile; |
351 |
johnpye |
930 |
int iserr; |
352 |
johnpye |
865 |
|
353 |
jpye |
2803 |
MSG("Importing Python script %s",name); |
354 |
johnpye |
1058 |
|
355 |
johnpye |
865 |
if(Py_IsInitialized()){ |
356 |
jpye |
2803 |
MSG("Python was already initialised"); |
357 |
johnpye |
865 |
}else{ |
358 |
jpye |
2803 |
MSG("INITIALISING PYTHON"); |
359 |
johnpye |
865 |
Py_Initialize(); |
360 |
jpye |
2803 |
MSG("COMPLETED ATTEMPT TO INITIALISE PYTHON"); |
361 |
johnpye |
865 |
} |
362 |
|
|
|
363 |
johnpye |
867 |
if(!Py_IsInitialized()){ |
364 |
johnpye |
873 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to initialise Python"); |
365 |
jpye |
2803 |
MSG("UNABLE TO INITIALIZE PYTHON"); |
366 |
johnpye |
873 |
ASC_FREE(name); |
367 |
johnpye |
867 |
return 1; |
368 |
|
|
} |
369 |
|
|
|
370 |
johnpye |
869 |
initextpy(); |
371 |
|
|
|
372 |
johnpye |
875 |
if(PyRun_SimpleString("import ascpy")){ |
373 |
jpye |
2803 |
MSG("Failed importing 'ascpy'"); |
374 |
johnpye |
875 |
return 1; |
375 |
|
|
} |
376 |
|
|
|
377 |
johnpye |
872 |
pyfile = PyFile_FromString(name,"r"); |
378 |
|
|
if(pyfile==NULL){ |
379 |
jpye |
2803 |
MSG("Failed opening script"); |
380 |
johnpye |
873 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to open '%s' (%s)",partialpath,name); |
381 |
|
|
ASC_FREE(name); |
382 |
johnpye |
871 |
return 1; |
383 |
|
|
} |
384 |
johnpye |
880 |
|
385 |
|
|
f = PyFile_AsFile(pyfile); |
386 |
johnpye |
872 |
if(f==NULL){ |
387 |
johnpye |
873 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to cast PyObject to FILE*"); |
388 |
|
|
ASC_FREE(name); |
389 |
johnpye |
872 |
return 1; |
390 |
|
|
} |
391 |
johnpye |
873 |
|
392 |
johnpye |
930 |
PyErr_Clear(); |
393 |
|
|
|
394 |
|
|
iserr = PyRun_AnyFileEx(f,name,1); |
395 |
jpye |
2649 |
|
396 |
johnpye |
930 |
if(iserr){ |
397 |
|
|
/* according to the manual, there is no way of determining anything more about the error. */ |
398 |
|
|
ERROR_REPORTER_HERE(ASC_PROG_ERR,"An error occurring in importing the script '%s'",name); |
399 |
johnpye |
873 |
return 1; |
400 |
johnpye |
930 |
} |
401 |
johnpye |
871 |
|
402 |
jpye |
3258 |
MSG("Imported python script '%s'",partialpath); |
403 |
johnpye |
873 |
|
404 |
johnpye |
862 |
ASC_FREE(name); |
405 |
johnpye |
873 |
return 0; |
406 |
johnpye |
862 |
} |
407 |
|
|
|
408 |
johnpye |
982 |
#endif /* WITH_PYTHON */ |
409 |
|
|
|