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