1 /* 2 Copyright (c) 2012 Ellery Newcomer 3 4 Permission is hereby granted, free of charge, to any person obtaining a copy of 5 this software and associated documentation files (the "Software"), to deal in 6 the Software without restriction, including without limitation the rights to 7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 of the Software, and to permit persons to whom the Software is furnished to do 9 so, subject to the following conditions: 10 11 The above copyright notice and this permission notice shall be included in all 12 copies or substantial portions of the Software. 13 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 SOFTWARE. 21 */ 22 23 /// Contains utilities for embedding python in D. 24 /// 25 /// Importing this module will call Py_Initialize. 26 module pyd.embedded; 27 28 /+ 29 + Things we want to do: 30 + * run python code in D (easily) [check] 31 + * run D code in python [check] 32 + * declare python functions and use them in D [check] 33 + * access and manipulate python globals in D [check] 34 + * wrap D classes/structs and use them in python or D [check] 35 + * use python class instances in D [check] 36 + * wrap D ranges and iterators or whatever and iterate through them in python [why?] 37 + * wrap python iterators as D input ranges [check] 38 + * do things with inheritance [why??!??] 39 + * do things with multithreading 40 +/ 41 42 import deimos.python.Python; 43 import pyd.pyd; 44 import pyd.util.conv; 45 import std.algorithm: findSplit; 46 import std.string: strip, outdent; 47 import std.traits; 48 49 /++ 50 + Fetch a python module object. 51 +/ 52 PydObject py_import(string name) { 53 debug assert(Py_IsInitialized(), "python not initialized"); 54 return new PydObject(PyImport_ImportModule(zcc(name))); 55 } 56 57 /** 58 Encapsulate a context within the Python interpreter. 59 60 This will preserve local variables and changes to the Python interpreter 61 made by 'from ___future__ import feature' across calls to this.py_eval and 62 this.py_stmts. 63 64 Otherwise, will not segregate global changes made to the Python interpreter. 65 */ 66 class InterpContext { 67 /// dict object: global variables in this context 68 PydObject globals; 69 /// dict object: local variables in this context 70 PydObject locals; 71 PyCompilerFlags flags; 72 PyFrameObject* frame; 73 74 /** 75 */ 76 this() { 77 debug assert(Py_IsInitialized(), "python not initialized"); 78 locals = new PydObject(PyDict_New()); 79 globals = py(["__builtins__": new PydObject(PyEval_GetBuiltins())]); 80 } 81 82 void pushDummyFrame() { 83 auto threadstate = PyThreadState_GET(); 84 if(threadstate.frame == null) { 85 PyCodeObject* code = PyCode_NewEmpty("<d>","<d>", 0); 86 frame = PyFrame_New(threadstate, code, 87 cast(PyObject*)(globals.ptr), 88 cast(PyObject*)(locals.ptr)); 89 threadstate.frame = frame; 90 } 91 } 92 93 void popDummyFrame() { 94 auto threadstate = PyThreadState_GET(); 95 if(threadstate.frame == frame) { 96 threadstate.frame = null; 97 } 98 } 99 100 /** 101 Evaluate a python expression once within this context and return the 102 result. 103 104 Params: 105 python = a python expression 106 file = 107 line = 108 Returns: 109 the result of expression 110 */ 111 T py_eval(T = PydObject)( 112 string python, 113 string file = __FILE__, 114 size_t line = __LINE__) { 115 116 auto pres = PyRun_StringFlags( 117 zcc(python), 118 Py_eval_input, 119 cast(PyObject*) globals.ptr, 120 cast(PyObject*) locals.ptr, 121 &flags); 122 if(pres) { 123 auto res = new PydObject(pres); 124 return res.to_d!T(); 125 }else{ 126 handle_exception(file,line); 127 assert(0); 128 } 129 } 130 /** 131 Evaluate one or more python statements once. 132 133 Params: 134 python = 135 python = python statements 136 file = 137 line = 138 */ 139 void py_stmts(string python, 140 string file = __FILE__, 141 size_t line = __LINE__) { 142 import std.string: outdent; 143 144 auto pres = PyRun_StringFlags( 145 zcc(outdent(python)), 146 Py_file_input, 147 cast(PyObject*) globals.ptr, 148 cast(PyObject*) locals.ptr, 149 &flags); 150 if(pres) { 151 Py_DECREF(pres); 152 }else{ 153 handle_exception(file,line); 154 } 155 } 156 157 @property PydObject opDispatch(string id)() { 158 return this.locals[id]; 159 } 160 161 @property void opDispatch(string id, T)(T t) { 162 static if(is(T == PydObject)) { 163 alias t s; 164 }else{ 165 PydObject s = py(t); 166 } 167 this.locals[id] = py(s); 168 } 169 } 170 171 /++ 172 Wraps a python function (specified as a string) as a D function roughly of 173 signature func_t 174 175 Template Parameters: 176 python = a python function 177 modl = context in which to run expression. must be a python module name. 178 func_t = type of d function 179 +/ 180 ReturnType!func_t py_def( string python, string modl, func_t) 181 (ParameterTypeTuple!func_t args, 182 string file = __FILE__, size_t line = __LINE__) { 183 //Note that type is really the only thing that need be static here, but hey. 184 alias ReturnType!func_t R; 185 alias ParameterTypeTuple!func_t Args; 186 enum afterdef = findSplit(python, "def")[2]; 187 enum ereparen = findSplit(afterdef, "(")[0]; 188 enum name = strip(ereparen) ~ "\0"; 189 static PydObject func; 190 static PythonException exc; 191 static string errmsg; 192 static bool once = true; 193 debug assert(Py_IsInitialized(), "python not initialized"); 194 if(once) { 195 once = false; 196 auto globals = py_import(modl).getdict(); 197 auto globals_ptr = Py_INCREF(globals.ptr); 198 scope(exit) Py_DECREF(globals_ptr); 199 auto locals = py((string[string]).init); 200 auto locals_ptr = Py_INCREF(locals.ptr); 201 scope(exit) Py_DECREF(locals_ptr); 202 if("__builtins__" !in globals) { 203 auto builtins = new PydObject(PyEval_GetBuiltins()); 204 globals["__builtins__"] = builtins; 205 } 206 auto pres = PyRun_String( 207 zcc(python), 208 Py_file_input, globals_ptr, locals_ptr); 209 if(pres) { 210 auto res = new PydObject(pres); 211 func = locals[name]; 212 }else{ 213 try{ 214 handle_exception(); 215 }catch(PythonException e) { 216 exc = e; 217 } 218 } 219 } 220 if(!func) { 221 throw exc; 222 } 223 return func(args).to_d!R(); 224 } 225 226 /++ 227 Evaluate a python expression once and return the result. 228 229 Params: 230 python = 231 python = a python expression 232 modl = context in which to run expression. either a python module name, or "". 233 file = 234 line = 235 +/ 236 T py_eval(T = PydObject)(string python, string modl = "", string file = __FILE__, size_t line = __LINE__) { 237 debug assert(Py_IsInitialized(), "python not initialized"); 238 PydObject locals = null; 239 PyObject* locals_ptr = null; 240 if(modl == "") { 241 locals = py((string[string]).init); 242 }else { 243 locals = py_import(modl).getdict(); 244 } 245 locals_ptr = Py_INCREF(locals.ptr); 246 if("__builtins__" !in locals) { 247 auto builtins = new PydObject(PyEval_GetBuiltins()); 248 locals["__builtins__"] = builtins; 249 } 250 auto pres = PyRun_String( 251 zcc(python), 252 Py_eval_input, locals_ptr, locals_ptr); 253 scope(exit) Py_XDECREF(locals_ptr); 254 if(pres) { 255 auto res = new PydObject(pres); 256 return res.to_d!T(); 257 }else{ 258 handle_exception(file,line); 259 assert(0); 260 } 261 } 262 263 /++ 264 Evaluate one or more python statements once. 265 266 Params: 267 python = 268 python = python statements 269 modl = context in which to run expression. either a python module name, or "". 270 file = 271 line = 272 +/ 273 void py_stmts(string python, string modl = "",string file = __FILE__, size_t line = __LINE__) { 274 debug assert(Py_IsInitialized(), "python not initialized"); 275 PydObject locals; 276 PyObject* locals_ptr; 277 if(modl == "") { 278 locals = py((string[string]).init); 279 }else { 280 locals = py_import(modl).getdict(); 281 } 282 locals_ptr = Py_INCREF(locals.ptr); 283 if("__builtins__" !in locals) { 284 auto builtins = new PydObject(PyEval_GetBuiltins()); 285 locals["__builtins__"] = builtins; 286 } 287 auto pres = PyRun_String( 288 zcc(outdent(python)), 289 Py_file_input, locals_ptr, locals_ptr); 290 scope(exit) Py_DECREF(locals_ptr); 291 if(pres) { 292 Py_DECREF(pres); 293 }else{ 294 handle_exception(file,line); 295 } 296 } 297