1##### 2# 3# aspy.py 4# 5# Andy Hammerlindl 2011/09/03 6# 7# Uses ctypes to interface with the shared library version of Python. 8# Asymptote can run and its datatypes inspected via Python. 9# 10# 11# To use the module: 12# 1. make asymptote.so 13# 2. Ensure that asymptote.so is visable to python, say adding its directory 14# to LD_LIBRARY_PATH 15# 3. Run this module. (See runExample for an example.) 16# 17##### 18 19from ctypes import * 20 21asyInt = c_longlong 22handle_typ = c_void_p 23arguments_typ = c_void_p 24state_typ = c_void_p 25 26function_typ = CFUNCTYPE(None, state_typ, c_void_p) 27 28class string_typ(Structure): 29 _fields_ = [ 30 ("buf", c_char_p), # Should be NUL-terminated? Maybe replace with 31 # POINTER(c_char). 32 ("length", asyInt) 33 ] 34 35ErrorCallbackFUNC = CFUNCTYPE(None, string_typ) 36 37NORMAL_ARG = 45000 38REST_ARG = 45001 39 40class Policy(Structure): 41 _fields_ = [ 42 ("version", asyInt), 43 ("copyHandle", CFUNCTYPE(handle_typ, handle_typ)), 44 ("releaseHandle", CFUNCTYPE(None, handle_typ)), 45 ("handleFromInt", CFUNCTYPE(handle_typ, asyInt)), 46 ("handleFromBool", CFUNCTYPE(handle_typ, asyInt)), 47 ("handleFromDouble", CFUNCTYPE(handle_typ, c_double)), 48 ("handleFromString", CFUNCTYPE(handle_typ, string_typ)), 49 ("handleFromFunction", CFUNCTYPE(handle_typ, 50 c_char_p, 51 function_typ, 52 c_void_p)), 53 ("IntFromHandle", CFUNCTYPE(asyInt, handle_typ)), 54 ("boolFromHandle", CFUNCTYPE(asyInt, handle_typ)), 55 ("doubleFromHandle", CFUNCTYPE(c_double, handle_typ)), 56 ("stringFromHandle", CFUNCTYPE(string_typ, handle_typ)), 57 ("getField", CFUNCTYPE(handle_typ, 58 handle_typ, 59 c_char_p)), 60 ("getCell", CFUNCTYPE(handle_typ, 61 handle_typ, 62 asyInt)), 63 ("addField", CFUNCTYPE(None, 64 handle_typ, 65 c_char_p, 66 handle_typ)), 67 ("newArguments", CFUNCTYPE(arguments_typ)), 68 ("releaseArguments", CFUNCTYPE(None, arguments_typ)), 69 ("addArgument", CFUNCTYPE(None, 70 arguments_typ, 71 c_char_p, 72 handle_typ, 73 asyInt)), 74 ("call", CFUNCTYPE(handle_typ, 75 handle_typ, 76 arguments_typ)), 77 ("globals", CFUNCTYPE(handle_typ, state_typ)), 78 ("numParams", CFUNCTYPE(asyInt, state_typ)), 79 ("getParam", CFUNCTYPE(handle_typ, state_typ, asyInt)), 80 ("setReturnValue", CFUNCTYPE(None, state_typ, handle_typ)), 81 ("setErrorCallback", CFUNCTYPE(None, ErrorCallbackFUNC)), 82 ] 83 84policy = None 85baseState = None 86def initPolicyAndBaseState(): 87 global policy, baseState 88 lib = CDLL("asymptote.so") 89 90 getPolicy = lib._asy_getPolicy 91 getPolicy.restype = POINTER(Policy) 92 policy = getPolicy() 93 94 getState = lib._asy_getState 95 getState.restype = state_typ 96 baseState = getState() 97 98initPolicyAndBaseState() 99 100def pyStringFromAsyString(st): 101 #TODO: Handle strings with null-terminators. 102 return str(st.buf) 103 104def pyStringFromHandle(h): 105 #TODO: Handle strings with null-terminators. 106 st = policy.contents.stringFromHandle(h) 107 checkForErrors() 108 return pyStringFromAsyString(st) 109 110def handleFromPyString(s): 111 st = string_typ(s, len(s)) 112 h = policy.contents.handleFromString(st) 113 checkForErrors() 114 return h 115 116def ensureDatum(val): 117 return val if type(val) is Datum else Datum(val) 118 119# The error detection scheme. 120# policyError is set to a string when an error occurs. 121policyError = [] 122def pyErrorCallback(s): 123 global policyError 124 policyError.append(pyStringFromAsyString(s)) 125 126cErrorCallback = ErrorCallbackFUNC(pyErrorCallback) 127policy.contents.setErrorCallback(cErrorCallback) 128 129class AsyException(Exception): 130 def __init__(self, msg): 131 self.msg = msg 132 def __str__(self): 133 return self.msg 134 135def checkForErrors(): 136 """Raises an exception if an error occured.""" 137 global policyError 138 if policyError != []: 139 s = policyError[0] 140 if len(policyError) > 1: 141 s += ' (and other errors)' 142 policyError = [] 143 raise AsyException(s) 144 145class Datum(object): 146 147 def _setHandle(self, handle): 148 object.__setattr__(self, 'handle', handle) 149 150 def __init__(self, val): 151 self._setHandle(0) 152 153 if val is None: 154 return 155 156 if type(val) is int: 157 self._setHandle(policy.contents.handleFromInt(val)) 158 checkForErrors() 159 elif type(val) is bool: 160 self._setHandle(policy.contents.handleFromBool(1 if val else 0)) 161 checkForErrors() 162 elif type(val) is float: 163 self._setHandle(policy.contents.handleFromDouble(val)) 164 elif type(val) is str: 165 self._setHandle(handleFromPyString(val)) 166 checkForErrors() 167 elif type(val) is tuple: 168 # Could do this more efficiently, and avoid a copyHandle 169 ret = state.globals()["operator tuple"](*val) 170 self._setHandle(policy.contents.copyHandle(ret.handle)) 171 checkForErrors() 172 elif type(val) is Datum: 173 self._setHandle(policy.contents.copyHandle(val.handle)) 174 checkForErrors() 175 else: 176 # TODO: check if val has a toDatum field 177 raise TypeError("cannot initialize Datum from '%s'" % 178 type(val).__name__) 179 180 def __repr__(self): 181 # TODO: Add type-checking to policy. 182 return '<Datum with handle %s>' % hex(self.handle) 183 184 def __int__(self): 185 l = policy.contents.IntFromHandle(self.handle) 186 checkForErrors() 187 return int(l) 188 189 def __nonzero__(self): 190 # This will throw an exception for anything but an underlying bool 191 # type. Perhaps we should be more pythonic. 192 l = policy.contents.boolFromHandle(self.handle) 193 checkForErrors() 194 assert l in [0,1] 195 return l == 1 196 197 def __float__(self): 198 x = policy.contents.doubleFromHandle(self.handle) 199 checkForErrors() 200 return float(x) 201 202 def __str__(self): 203 return pyStringFromHandle(self.handle) 204 205 def __getattr__(self, name): 206 field = policy.contents.getField(self.handle, name) 207 checkForErrors() 208 return DatumFromHandle(field) 209 210 def __getitem__(self, name): 211 assert type(name) == str 212 return self.__getattr__(name) 213 #TODO: raise an IndexError when appropriate. 214 #TODO: implement array indices 215 216 def __setattr__(self, name, val): 217 # TODO: Resolve setting versus declaring. 218 # One idea: d.x = f or d["x"] = f sets and d["int x()"] = f declares 219 # anew. 220 policy.contents.addField(self.handle, 221 name, ensureDatum(val).handle) 222 checkForErrors() 223 224 def __setitem__(self, name, val): 225 assert type(name) == str 226 self.__setattr__(name, val) 227 #TODO: raise an IndexError when appropriate. 228 #TODO: implement array indices 229 230 def __call__(self, *args, **namedArgs): 231 alist = policy.contents.newArguments() 232 checkForErrors() 233 234 235 for arg in args: 236 d = ensureDatum(arg) 237 policy.contents.addArgument(alist, "", d.handle, NORMAL_ARG) 238 checkForErrors() 239 240 for name,arg in namedArgs.items(): 241 d = ensureDatum(arg) 242 policy.contents.addArgument(alist, name, d.handle, NORMAL_ARG) 243 checkForErrors() 244 245 ret = policy.contents.call(self.handle, alist) 246 checkForErrors() 247 248 policy.contents.releaseArguments(alist) 249 checkForErrors() 250 251 if ret != None: 252 return DatumFromHandle(ret) 253 254 def __add__(self, other): 255 return state.globals()["operator +"](self, other) 256 def __sub__(self, other): 257 return state.globals()["operator -"](self, other) 258 def __mul__(self, other): 259 return state.globals()["operator *"](self, other) 260 def __div__(self, other): 261 return state.globals()["operator /"](self, other) 262 def __truediv__(self, other): 263 return state.globals()["operator /"](self, other) 264 def __mod__(self, other): 265 return state.globals()["operator %"](self, other) 266 def __pow__(self, other): 267 return state.globals()["operator ^"](self, other) 268 def __and__(self, other): 269 return state.globals()["operator &"](self, other) 270 def __or__(self, other): 271 return state.globals()["operator |"](self, other) 272 def __neg__(self, other): 273 return state.globals()["operator -"](self) 274 275 def __lt__(self, other): 276 return state.globals()["operator <"](self, other) 277 def __le__(self, other): 278 return state.globals()["operator <="](self, other) 279 def __eq__(self, other): 280 return state.globals()["operator =="](self, other) 281 def __ne__(self, other): 282 return state.globals()["operator !="](self, other) 283 def __gt__(self, other): 284 return state.globals()["operator >"](self, other) 285 def __ge__(self, other): 286 return state.globals()["operator >="](self, other) 287 288def DatumFromHandle(handle): 289 """Initializes a Datum from a given low-level handle. Does not invoke 290 copyHandle.""" 291 d = Datum(None) 292 d._setHandle(handle) 293 return d 294 295class State(object): 296 def __init__(self, base): 297 self.base = base 298 299 def globals(self): 300 handle = policy.contents.globals(self.base) 301 checkForErrors() 302 return DatumFromHandle(handle) 303 304 def params(self): 305 p = [] 306 307 numParams = policy.contents.numParams(self.base) 308 checkForErrors() 309 310 for i in range(numParams): 311 h = policy.contents.getParam(self.base, i) 312 checkForErrors() 313 p.append(DatumFromHandle(h)) 314 315 assert len(p) == numParams 316 return p 317 318 def setReturnValue(self, val): 319 policy.contents.setReturnValue(self.base, ensureDatum(val).handle) 320 checkForErrors() 321 322# Keep a link to all of the callbacks created, so they aren't garbage 323# collected. TODO: See if this is neccessary. 324storedCallbacks = [] 325 326def DatumFromCallable(f): 327 def wrapped(s, d): 328 state = State(s) 329 params = state.params() 330 r = f(*params) 331 if r != None: 332 state.setReturnValue(r) 333 334 cf = function_typ(wrapped) 335 storedCallbacks.append(cf) 336 337 h = policy.contents.handleFromFunction(f.__name__, cf, None) 338 checkForErrors() 339 340 return DatumFromHandle(h) 341 342print "version", policy.contents.version 343 344state = State(baseState) 345 346# An example 347def runExample(): 348 g = state.globals() 349 350 g.eval("path p = (0,0) -- (100,100) -- (200,0)", embedded=True) 351 g.draw(g.p) 352 g.shipout("frompython") 353 354 g.draw(g.circle(100), g.red) 355 356