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