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 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 Returns: 107 the result of expression 108 */ 109 T py_eval(T = PydObject)( 110 string python, 111 string file = __FILE__, 112 size_t line = __LINE__) { 113 114 auto pres = PyRun_StringFlags( 115 zcc(python), 116 Py_eval_input, 117 cast(PyObject*) globals.ptr, 118 cast(PyObject*) locals.ptr, 119 &flags); 120 if(pres) { 121 auto res = new PydObject(pres); 122 return res.to_d!T(); 123 }else{ 124 handle_exception(file,line); 125 assert(0); 126 } 127 } 128 /** 129 Evaluate one or more python statements once. 130 131 Params: 132 python = python statements 133 */ 134 void py_stmts(string python, 135 string file = __FILE__, 136 size_t line = __LINE__) { 137 138 auto pres = PyRun_StringFlags( 139 zcc(outdent(python)), 140 Py_file_input, 141 cast(PyObject*) globals.ptr, 142 cast(PyObject*) locals.ptr, 143 &flags); 144 if(pres) { 145 Py_DECREF(pres); 146 }else{ 147 handle_exception(file,line); 148 } 149 } 150 151 @property PydObject opDispatch(string id)() { 152 return this.locals[id]; 153 } 154 155 @property void opDispatch(string id, T)(T t) { 156 static if(is(T == PydObject)) { 157 alias t s; 158 }else{ 159 PydObject s = py(t); 160 } 161 this.locals[id] = py(s); 162 } 163 } 164 165 /++ 166 Wraps a python function (specified as a string) as a D function roughly of 167 signature func_t 168 169 Params: 170 python = a python function 171 modl = context in which to run expression. must be a python module name. 172 func_t = type of d function 173 +/ 174 ReturnType!func_t py_def( string python, string modl, func_t) 175 (ParameterTypeTuple!func_t args, 176 string file = __FILE__, size_t line = __LINE__) { 177 //Note that type is really the only thing that need be static here, but hey. 178 alias ReturnType!func_t R; 179 alias ParameterTypeTuple!func_t Args; 180 enum afterdef = findSplit(python, "def")[2]; 181 enum ereparen = findSplit(afterdef, "(")[0]; 182 enum name = strip(ereparen) ~ "\0"; 183 static PydObject func; 184 static PythonException exc; 185 static string errmsg; 186 static bool once = true; 187 debug assert(Py_IsInitialized(), "python not initialized"); 188 if(once) { 189 once = false; 190 auto globals = py_import(modl).getdict(); 191 auto globals_ptr = Py_INCREF(globals.ptr); 192 scope(exit) Py_DECREF(globals_ptr); 193 auto locals = py((string[string]).init); 194 auto locals_ptr = Py_INCREF(locals.ptr); 195 scope(exit) Py_DECREF(locals_ptr); 196 if("__builtins__" !in globals) { 197 auto builtins = new PydObject(PyEval_GetBuiltins()); 198 globals["__builtins__"] = builtins; 199 } 200 auto pres = PyRun_String( 201 zcc(python), 202 Py_file_input, globals_ptr, locals_ptr); 203 if(pres) { 204 auto res = new PydObject(pres); 205 func = locals[name]; 206 }else{ 207 try{ 208 handle_exception(); 209 }catch(PythonException e) { 210 exc = e; 211 } 212 } 213 } 214 if(!func) { 215 throw exc; 216 } 217 return func(args).to_d!R(); 218 } 219 220 /++ 221 Evaluate a python expression once and return the result. 222 223 Params: 224 python = a python expression 225 modl = context in which to run expression. either a python module name, or "". 226 +/ 227 T py_eval(T = PydObject)(string python, string modl = "", string file = __FILE__, size_t line = __LINE__) { 228 debug assert(Py_IsInitialized(), "python not initialized"); 229 PydObject locals = null; 230 PyObject* locals_ptr = null; 231 if(modl == "") { 232 locals = py((string[string]).init); 233 }else { 234 locals = py_import(modl).getdict(); 235 } 236 locals_ptr = Py_INCREF(locals.ptr); 237 if("__builtins__" !in locals) { 238 auto builtins = new PydObject(PyEval_GetBuiltins()); 239 locals["__builtins__"] = builtins; 240 } 241 auto pres = PyRun_String( 242 zcc(python), 243 Py_eval_input, locals_ptr, locals_ptr); 244 scope(exit) Py_XDECREF(locals_ptr); 245 if(pres) { 246 auto res = new PydObject(pres); 247 return res.to_d!T(); 248 }else{ 249 handle_exception(file,line); 250 assert(0); 251 } 252 } 253 254 /++ 255 Evaluate one or more python statements once. 256 257 Params: 258 python = python statements 259 modl = context in which to run expression. either a python module name, or "". 260 +/ 261 void py_stmts(string python, string modl = "",string file = __FILE__, size_t line = __LINE__) { 262 debug assert(Py_IsInitialized(), "python not initialized"); 263 PydObject locals; 264 PyObject* locals_ptr; 265 if(modl == "") { 266 locals = py((string[string]).init); 267 }else { 268 locals = py_import(modl).getdict(); 269 } 270 locals_ptr = Py_INCREF(locals.ptr); 271 if("__builtins__" !in locals) { 272 auto builtins = new PydObject(PyEval_GetBuiltins()); 273 locals["__builtins__"] = builtins; 274 } 275 auto pres = PyRun_String( 276 zcc(outdent(python)), 277 Py_file_input, locals_ptr, locals_ptr); 278 scope(exit) Py_DECREF(locals_ptr); 279 if(pres) { 280 Py_DECREF(pres); 281 }else{ 282 handle_exception(file,line); 283 } 284 } 285