tclobj.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. # Tcl_Obj, conversions with Python objects
  2. from .tklib_cffi import ffi as tkffi, lib as tklib
  3. import binascii
  4. class TypeCache(object):
  5. def __init__(self):
  6. self.OldBooleanType = tklib.Tcl_GetObjType(b"boolean")
  7. self.BooleanType = None
  8. self.ByteArrayType = tklib.Tcl_GetObjType(b"bytearray")
  9. self.DoubleType = tklib.Tcl_GetObjType(b"double")
  10. self.IntType = tklib.Tcl_GetObjType(b"int")
  11. self.WideIntType = tklib.Tcl_GetObjType(b"wideInt")
  12. self.BigNumType = None
  13. self.ListType = tklib.Tcl_GetObjType(b"list")
  14. self.ProcBodyType = tklib.Tcl_GetObjType(b"procbody")
  15. self.StringType = tklib.Tcl_GetObjType(b"string")
  16. def add_extra_types(self, app):
  17. # Some types are not registered in Tcl.
  18. result = app.call('expr', 'true')
  19. typePtr = AsObj(result).typePtr
  20. if tkffi.string(typePtr.name) == b"booleanString":
  21. self.BooleanType = typePtr
  22. result = app.call('expr', '2**63')
  23. typePtr = AsObj(result).typePtr
  24. if tkffi.string(typePtr.name) == b"bignum":
  25. self.BigNumType = typePtr
  26. def FromTclString(s):
  27. try:
  28. return s.decode('utf-8')
  29. except UnicodeDecodeError:
  30. # Tcl encodes null character as \xc0\x80
  31. try:
  32. return s.replace(b'\xc0\x80', b'\x00').decode('utf-8')
  33. except UnicodeDecodeError:
  34. pass
  35. return s
  36. # Only when tklib.HAVE_WIDE_INT_TYPE.
  37. def FromWideIntObj(app, value):
  38. wide = tkffi.new("Tcl_WideInt*")
  39. if tklib.Tcl_GetWideIntFromObj(app.interp, value, wide) != tklib.TCL_OK:
  40. app.raiseTclError()
  41. return wide[0]
  42. # Only when tklib.HAVE_LIBTOMMATH!
  43. def FromBignumObj(app, value):
  44. bigValue = tkffi.new("mp_int*")
  45. if tklib.Tcl_GetBignumFromObj(app.interp, value, bigValue) != tklib.TCL_OK:
  46. app.raiseTclError()
  47. try:
  48. numBytes = tklib.mp_unsigned_bin_size(bigValue)
  49. buf = tkffi.new("unsigned char[]", numBytes)
  50. bufSize_ptr = tkffi.new("unsigned long*", numBytes)
  51. if tklib.mp_to_unsigned_bin_n(
  52. bigValue, buf, bufSize_ptr) != tklib.MP_OKAY:
  53. raise MemoryError
  54. if bufSize_ptr[0] == 0:
  55. return 0
  56. bytes = tkffi.buffer(buf)[0:bufSize_ptr[0]]
  57. sign = -1 if bigValue.sign == tklib.MP_NEG else 1
  58. return int(sign * int(binascii.hexlify(bytes), 16))
  59. finally:
  60. tklib.mp_clear(bigValue)
  61. def AsBignumObj(value):
  62. sign = -1 if value < 0 else 1
  63. hexstr = b'%x' % abs(value)
  64. bigValue = tkffi.new("mp_int*")
  65. tklib.mp_init(bigValue)
  66. try:
  67. if tklib.mp_read_radix(bigValue, hexstr, 16) != tklib.MP_OKAY:
  68. raise MemoryError
  69. bigValue.sign = tklib.MP_NEG if value < 0 else tklib.MP_ZPOS
  70. return tklib.Tcl_NewBignumObj(bigValue)
  71. finally:
  72. tklib.mp_clear(bigValue)
  73. def FromObj(app, value):
  74. """Convert a TclObj pointer into a Python object."""
  75. typeCache = app._typeCache
  76. if not value.typePtr:
  77. buf = tkffi.buffer(value.bytes, value.length)
  78. return FromTclString(buf[:])
  79. if value.typePtr in (typeCache.BooleanType, typeCache.OldBooleanType):
  80. value_ptr = tkffi.new("int*")
  81. if tklib.Tcl_GetBooleanFromObj(
  82. app.interp, value, value_ptr) == tklib.TCL_ERROR:
  83. app.raiseTclError()
  84. return bool(value_ptr[0])
  85. if value.typePtr == typeCache.ByteArrayType:
  86. size = tkffi.new('int*')
  87. data = tklib.Tcl_GetByteArrayFromObj(value, size)
  88. return tkffi.buffer(data, size[0])[:]
  89. if value.typePtr == typeCache.DoubleType:
  90. return value.internalRep.doubleValue
  91. if value.typePtr == typeCache.IntType:
  92. return value.internalRep.longValue
  93. if value.typePtr == typeCache.WideIntType:
  94. return FromWideIntObj(app, value)
  95. if value.typePtr == typeCache.BigNumType and tklib.HAVE_LIBTOMMATH:
  96. return FromBignumObj(app, value)
  97. if value.typePtr == typeCache.ListType:
  98. size = tkffi.new('int*')
  99. status = tklib.Tcl_ListObjLength(app.interp, value, size)
  100. if status == tklib.TCL_ERROR:
  101. app.raiseTclError()
  102. result = []
  103. tcl_elem = tkffi.new("Tcl_Obj**")
  104. for i in range(size[0]):
  105. status = tklib.Tcl_ListObjIndex(app.interp,
  106. value, i, tcl_elem)
  107. if status == tklib.TCL_ERROR:
  108. app.raiseTclError()
  109. result.append(FromObj(app, tcl_elem[0]))
  110. return tuple(result)
  111. if value.typePtr == typeCache.ProcBodyType:
  112. pass # fall through and return tcl object.
  113. if value.typePtr == typeCache.StringType:
  114. buf = tklib.Tcl_GetUnicode(value)
  115. length = tklib.Tcl_GetCharLength(value)
  116. buf = tkffi.buffer(tkffi.cast("char*", buf), length*2)[:]
  117. return buf.decode('utf-16')
  118. return Tcl_Obj(value)
  119. def AsObj(value):
  120. if isinstance(value, bytes):
  121. return tklib.Tcl_NewByteArrayObj(value, len(value))
  122. if isinstance(value, bool):
  123. return tklib.Tcl_NewBooleanObj(value)
  124. if isinstance(value, int):
  125. try:
  126. return tklib.Tcl_NewLongObj(value)
  127. except OverflowError:
  128. if tklib.HAVE_WIDE_INT_TYPE:
  129. try:
  130. tkffi.new("Tcl_WideInt[]", [value])
  131. except OverflowError:
  132. pass
  133. else:
  134. return tklib.Tcl_NewWideIntObj(value)
  135. if tklib.HAVE_LIBTOMMATH:
  136. return AsBignumObj(value)
  137. if isinstance(value, float):
  138. return tklib.Tcl_NewDoubleObj(value)
  139. if isinstance(value, (tuple, list)):
  140. argv = tkffi.new("Tcl_Obj*[]", len(value))
  141. for i in range(len(value)):
  142. argv[i] = AsObj(value[i])
  143. return tklib.Tcl_NewListObj(len(value), argv)
  144. if isinstance(value, str):
  145. encoded = value.encode('utf-16')[2:]
  146. buf = tkffi.new("char[]", encoded)
  147. inbuf = tkffi.cast("Tcl_UniChar*", buf)
  148. return tklib.Tcl_NewUnicodeObj(inbuf, len(encoded)//2)
  149. if isinstance(value, Tcl_Obj):
  150. tklib.Tcl_IncrRefCount(value._value)
  151. return value._value
  152. return AsObj(str(value))
  153. class Tcl_Obj(object):
  154. def __new__(cls, value):
  155. self = object.__new__(cls)
  156. tklib.Tcl_IncrRefCount(value)
  157. self._value = value
  158. self._string = None
  159. return self
  160. def __del__(self):
  161. tklib.Tcl_DecrRefCount(self._value)
  162. def __str__(self):
  163. if self._string and isinstance(self._string, str):
  164. return self._string
  165. return tkffi.string(tklib.Tcl_GetString(self._value)).decode('utf-8')
  166. def __repr__(self):
  167. return "<%s object at 0x%x>" % (
  168. self.typename, int(tkffi.cast("intptr_t", self._value)))
  169. def __eq__(self, other):
  170. if not isinstance(other, Tcl_Obj):
  171. return NotImplemented
  172. return self._value == other._value
  173. @property
  174. def typename(self):
  175. return FromTclString(tkffi.string(self._value.typePtr.name))
  176. @property
  177. def string(self):
  178. "the string representation of this object, either as str or bytes"
  179. if self._string is None:
  180. length = tkffi.new("int*")
  181. s = tklib.Tcl_GetStringFromObj(self._value, length)
  182. value = tkffi.buffer(s, length[0])[:]
  183. value = value.decode('utf-8')
  184. self._string = value
  185. return self._string