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 |
#include <stdio.h> |
26 |
#include <string.h> |
27 |
|
28 |
#include <utilities/ascConfig.h> |
29 |
#include <utilities/error.h> |
30 |
#include <general/ospath.h> |
31 |
|
32 |
#include <compiler/importhandler.h> |
33 |
#include <compiler/extfunc.h> |
34 |
|
35 |
#include <Python.h> |
36 |
|
37 |
ImportHandlerCreateFilenameFn extpy_filename; |
38 |
ImportHandlerImportFn extpy_import; |
39 |
|
40 |
#ifndef ASC_EXPORT |
41 |
# error "Where is ASC_EXPORT?" |
42 |
#endif |
43 |
|
44 |
/** |
45 |
This is the function called from "IMPORT extpy" |
46 |
|
47 |
It sets up the functions in this external function library |
48 |
*/ |
49 |
extern ASC_EXPORT(int) extpy_register(){ |
50 |
int result = 0; |
51 |
|
52 |
struct ImportHandler *handler; |
53 |
handler = ASC_NEW(struct ImportHandler); |
54 |
|
55 |
handler->name = "extpy"; |
56 |
handler->filenamefn = extpy_filename; |
57 |
handler->importfn = extpy_import; |
58 |
|
59 |
result = importhandler_add(handler); |
60 |
|
61 |
if(result){ |
62 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to register import handler (error = %d)",result); |
63 |
} |
64 |
return result; |
65 |
} |
66 |
|
67 |
/*------------------------------------------------------------------------------ |
68 |
PYTHON METHOD INVOKER |
69 |
*/ |
70 |
|
71 |
ExtMethodRun extpy_invokemethod; |
72 |
|
73 |
/** Method invoker. extpy will supply a pointer to this function whenever it |
74 |
registers a python function as an external script method. This function will |
75 |
then dereference the user_data field into a python function, and execute that |
76 |
python function. |
77 |
|
78 |
One difficult aspect is the question of how to usefully pass the 'context' |
79 |
argument to Python? |
80 |
*/ |
81 |
int extpy_invokemethod(struct Instance *context, struct gl_list_t *args, void *user_data){ |
82 |
PyObject *fn, *arglist, *result; |
83 |
PyObject *pycontext; |
84 |
/* cast user data to PyObject pointer */ |
85 |
|
86 |
CONSOLE_DEBUG("USER_DATA IS AT %p",user_data); |
87 |
|
88 |
fn = (PyObject *) user_data; |
89 |
|
90 |
ERROR_REPORTER_HERE(ASC_USER_NOTE,"RUNNING PYTHON METHOD"); |
91 |
CONSOLE_DEBUG("RUNNING PYTHON METHOD..."); |
92 |
|
93 |
PyErr_Clear(); |
94 |
|
95 |
if(!PyCallable_Check(fn)){ |
96 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"user_data is not a PyCallable"); |
97 |
return 1; |
98 |
} |
99 |
|
100 |
/* |
101 |
We need to be able to convert C 'struct Instance' pointers to Python 'Instance' objects. |
102 |
This functionality is implemented in 'ascpy' but we're not going to link to that here, |
103 |
so we will use the importhandler 'setsharedpointer' trick to pass the object to the |
104 |
'registry' then write a routine in ascpy that will cast it into the appropriate |
105 |
Python object. |
106 |
*/ |
107 |
importhandler_setsharedpointer("context",(void *)context); |
108 |
|
109 |
/* |
110 |
Eventually we'll work out how to pass the instance object directly into Python. For |
111 |
the moment, just have a placeholder variable instead: |
112 |
*/ |
113 |
arglist = Py_BuildValue("(i)", 666); |
114 |
|
115 |
CONSOLE_DEBUG("CALLING PYTHON"); |
116 |
result = PyEval_CallObject(fn, arglist); |
117 |
CONSOLE_DEBUG("DONE CALLING PYTHON"); |
118 |
Py_DECREF(arglist); |
119 |
|
120 |
if(PyErr_Occurred()){ |
121 |
/** @TODO we really want to capture these error messages and output them to the GUI, instead of outputting them to the console */ |
122 |
PyErr_Print(); |
123 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed running python method (see console)"); |
124 |
return 1; |
125 |
} |
126 |
|
127 |
return 0; |
128 |
} |
129 |
|
130 |
/*------------------------------------------------------------------------------ |
131 |
'EXTPY' PYTHON STATIC MODULE |
132 |
*/ |
133 |
|
134 |
static PyObject *extpy_getbrowser(PyObject *self, PyObject *args){ |
135 |
PyObject *browser; |
136 |
if(args!=NULL){ |
137 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"args is not NULL?!"); |
138 |
} |
139 |
browser = (PyObject *)importhandler_getsharedpointer("browser"); |
140 |
return Py_BuildValue("O",browser); |
141 |
} |
142 |
|
143 |
static PyObject *extpy_registermethod(PyObject *self, PyObject *args){ |
144 |
PyObject *fn, *name, *docstring; |
145 |
const char *cname, *cdocstring; |
146 |
int res; |
147 |
int nargs = 1; |
148 |
|
149 |
PyArg_ParseTuple(args,"O:registermethod", &fn); |
150 |
if(!PyCallable_Check(fn)){ |
151 |
PyErr_SetString(PyExc_TypeError,"parameter must be callable"); |
152 |
return NULL; |
153 |
} |
154 |
|
155 |
CONSOLE_DEBUG("FOUND FN=%p",fn); |
156 |
|
157 |
name = PyObject_GetAttr(fn,PyString_FromString("__name__")); |
158 |
if(name==NULL){ |
159 |
CONSOLE_DEBUG("No __name__ attribute"); |
160 |
PyErr_SetString(PyExc_TypeError,"No __name__ attribute"); |
161 |
return NULL; |
162 |
} |
163 |
cname = PyString_AsString(name); |
164 |
|
165 |
CONSOLE_DEBUG("REGISTERED METHOD '%s' HAS %d ARGS",cname,nargs); |
166 |
|
167 |
docstring = PyObject_GetAttr(fn,PyString_FromString("func_doc")); |
168 |
cdocstring = "(no help)"; |
169 |
if(name!=NULL){ |
170 |
cdocstring = PyString_AsString(docstring); |
171 |
CONSOLE_DEBUG("DOCSTRING: %s",cdocstring); |
172 |
} |
173 |
|
174 |
res = CreateUserFunctionMethod(cname,extpy_invokemethod,nargs,cdocstring,(void *)fn); |
175 |
CONSOLE_DEBUG("PYTHON FUNCTION IS AT %p",fn); |
176 |
Py_INCREF(fn); |
177 |
CONSOLE_DEBUG("PYTHON FUNCTION IS AT %p",fn); |
178 |
|
179 |
CONSOLE_DEBUG("EXTPY INVOKER IS AT %p",extpy_invokemethod); |
180 |
|
181 |
if(res){ |
182 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Problem registering external script method (%d)",res); |
183 |
PyErr_SetString(PyExc_Exception,"unable to register script method"); |
184 |
return NULL; |
185 |
} |
186 |
|
187 |
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"Registered python method '%s'",cname); |
188 |
|
189 |
/* nothing gets returned (but possibly an exception) */ |
190 |
Py_INCREF(Py_None); |
191 |
return Py_None; |
192 |
} |
193 |
|
194 |
static PyMethodDef extpymethods[] = { |
195 |
{"getbrowser", extpy_getbrowser, METH_NOARGS,"Retrieve browser pointer"} |
196 |
,{"registermethod", extpy_registermethod, METH_VARARGS,"Register a python method as an ASCEND script method"} |
197 |
,{NULL,NULL,0,NULL} |
198 |
}; |
199 |
|
200 |
PyMODINIT_FUNC initextpy(void){ |
201 |
PyObject *obj; |
202 |
obj = Py_InitModule3("extpy", extpymethods,"Module for accessing shared ASCEND pointers from python"); |
203 |
} |
204 |
|
205 |
/*------------------------------------------------------------------------------ |
206 |
STANDARD IMPORT HANDLER ROUTINES |
207 |
*/ |
208 |
|
209 |
/** |
210 |
Create a filename base on a partial filename. In that case of python, this |
211 |
just means adding '.py' to the end. |
212 |
|
213 |
@param partialname the filename without suffix, as specified in the user's "IMPORT" command |
214 |
@return new filename, or NULL on failure |
215 |
*/ |
216 |
char *extpy_filename(const char *partialname){ |
217 |
char *name; |
218 |
int len; |
219 |
if(partialname==NULL){ |
220 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Partial name is NULL, can't work out filename"); |
221 |
return NULL; |
222 |
} |
223 |
|
224 |
len = strlen(partialname); |
225 |
name = ASC_NEW_ARRAY_CLEAR(char,len+4); |
226 |
strcpy(name,partialname); |
227 |
strcat(name,".py"); |
228 |
CONSOLE_DEBUG("New filename is '%s'",name); |
229 |
return name; |
230 |
} |
231 |
|
232 |
/** |
233 |
Import a python script located at the path indicated. |
234 |
|
235 |
@return 0 on success, else error codes (TBD) |
236 |
*/ |
237 |
int extpy_import(const struct FilePath *fp, const char *initfunc, const char *partialpath){ |
238 |
char *name; |
239 |
name = ospath_str(fp); |
240 |
FILE *f; |
241 |
PyObject *pyfile; |
242 |
|
243 |
CONSOLE_DEBUG("Importing Python script %s",name); |
244 |
if(Py_IsInitialized()){ |
245 |
CONSOLE_DEBUG("Python was already initialised"); |
246 |
}else{ |
247 |
CONSOLE_DEBUG("INITIALISING PYTHON"); |
248 |
Py_Initialize(); |
249 |
CONSOLE_DEBUG("COMPLETED ATTEMPT TO INITIALISE PYTHON"); |
250 |
} |
251 |
|
252 |
if(!Py_IsInitialized()){ |
253 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to initialise Python"); |
254 |
CONSOLE_DEBUG("UNABLE TO INITIALIZE PYTHON"); |
255 |
ASC_FREE(name); |
256 |
return 1; |
257 |
} |
258 |
|
259 |
initextpy(); |
260 |
|
261 |
if(PyRun_SimpleString("import ascpy")){ |
262 |
CONSOLE_DEBUG("Failed importing 'ascpy'"); |
263 |
return 1; |
264 |
} |
265 |
|
266 |
pyfile = PyFile_FromString(name,"r"); |
267 |
if(pyfile==NULL){ |
268 |
CONSOLE_DEBUG("Failed opening script"); |
269 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to open '%s' (%s)",partialpath,name); |
270 |
ASC_FREE(name); |
271 |
return 1; |
272 |
} |
273 |
|
274 |
f = PyFile_AsFile(pyfile); |
275 |
if(f==NULL){ |
276 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to cast PyObject to FILE*"); |
277 |
ASC_FREE(name); |
278 |
return 1; |
279 |
} |
280 |
|
281 |
PyRun_AnyFileEx(f,name,1); |
282 |
/*if(PyErr_Occurred()){ |
283 |
ERROR_REPORTER_HERE(ASC_PROG_ERR,"An error occurred in the python script '%s'. Check the console for details"); |
284 |
PyErr_Print(); |
285 |
PyErr_Clear(); |
286 |
return 1; |
287 |
}*/ |
288 |
|
289 |
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"Imported python script '%s' (check console for errors)",partialpath); |
290 |
|
291 |
ASC_FREE(name); |
292 |
return 0; |
293 |
} |
294 |
|