emacspy.pyx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. # cython: language_level=3str
  2. cdef extern from "emacs-module.h":
  3. ctypedef int emacs_funcall_exit
  4. ctypedef bint bool
  5. ctypedef long long int64_t
  6. ctypedef unsigned long long uint64_t
  7. ctypedef int64_t intmax_t
  8. ctypedef uint64_t uintmax_t
  9. struct emacs_runtime:
  10. emacs_env* (*get_environment)(emacs_runtime* ert)
  11. ctypedef struct emacs_value_tag:
  12. pass
  13. ctypedef emacs_value_tag* emacs_value
  14. ctypedef struct emacs_env:
  15. emacs_value (*funcall) (emacs_env *env,
  16. emacs_value function,
  17. ptrdiff_t nargs,
  18. emacs_value* args) nogil
  19. emacs_value (*make_function) (emacs_env *env,
  20. ptrdiff_t min_arity,
  21. ptrdiff_t max_arity,
  22. emacs_value (*function) (emacs_env *env,
  23. ptrdiff_t nargs,
  24. emacs_value* args,
  25. void *),
  26. char *documentation,
  27. void *data)
  28. emacs_value (*intern) (emacs_env *env,
  29. const char *symbol_name)
  30. emacs_value (*type_of) (emacs_env *env,
  31. emacs_value value)
  32. bool (*is_not_nil) (emacs_env *env, emacs_value value)
  33. bool (*eq) (emacs_env *env, emacs_value a, emacs_value b)
  34. intmax_t (*extract_integer) (emacs_env *env, emacs_value value)
  35. emacs_value (*make_integer) (emacs_env *env, intmax_t value)
  36. double (*extract_float) (emacs_env *env, emacs_value value)
  37. emacs_value (*make_float) (emacs_env *env, double value)
  38. emacs_value (*make_string) (emacs_env *env,
  39. const char *contents, ptrdiff_t length)
  40. bool (*copy_string_contents) (emacs_env *env,
  41. emacs_value value,
  42. char *buffer,
  43. ptrdiff_t *size_inout)
  44. emacs_value (*make_user_ptr) (emacs_env *env,
  45. void (*fin) (void *),
  46. void *ptr)
  47. void *(*get_user_ptr) (emacs_env *env, emacs_value uptr)
  48. void (*set_user_ptr) (emacs_env *env, emacs_value uptr, void *ptr)
  49. void (*set_user_finalizer) (emacs_env *env,
  50. emacs_value uptr,
  51. void (*fin) (void *))
  52. emacs_value (*vec_get) (emacs_env *env, emacs_value vec, ptrdiff_t i)
  53. void (*vec_set) (emacs_env *env, emacs_value vec, ptrdiff_t i,
  54. emacs_value val)
  55. ptrdiff_t (*vec_size) (emacs_env *env, emacs_value vec)
  56. emacs_value (*make_global_ref) (emacs_env *env,
  57. emacs_value any_reference)
  58. void (*free_global_ref) (emacs_env *env,
  59. emacs_value global_reference)
  60. void (*non_local_exit_clear) (emacs_env *env)
  61. void (*non_local_exit_signal) (emacs_env *env,
  62. emacs_value non_local_exit_symbol,
  63. emacs_value non_local_exit_data)
  64. void (*non_local_exit_throw) (emacs_env *env,
  65. emacs_value tag,
  66. emacs_value value)
  67. emacs_funcall_exit (*non_local_exit_get) (emacs_env *env,
  68. emacs_value *non_local_exit_symbol_out,
  69. emacs_value *non_local_exit_data_out)
  70. from cython.view cimport array as cvarray
  71. import traceback
  72. cdef extern from "thread_local.c":
  73. emacs_env* current_env
  74. #cdef emacs_env* current_env = NULL
  75. _defined_functions = []
  76. _dealloc_queue = []
  77. cdef extern from "stdlib.h":
  78. void abort()
  79. void* malloc(size_t s)
  80. void free(void*)
  81. cdef emacs_env* get_env() except *:
  82. if current_env == NULL:
  83. raise Exception('not running in Emacs context, use emacspy_threads.run_in_main_thread')
  84. return current_env
  85. cdef class _ForDealloc:
  86. cdef emacs_value v
  87. cdef class EmacsValue:
  88. cdef emacs_value v
  89. @staticmethod
  90. cdef EmacsValue wrap(emacs_value v):
  91. if NULL == v:
  92. raise ValueError("NULL in emacs value")
  93. wrapper = EmacsValue()
  94. cdef emacs_env* env = get_env()
  95. wrapper.v = env.make_global_ref(env, v)
  96. return wrapper
  97. def __dealloc__(self):
  98. if current_env == NULL:
  99. for_dealloc = _ForDealloc()
  100. for_dealloc.v = self.v
  101. _dealloc_queue.append(for_dealloc)
  102. else:
  103. current_env.free_global_ref(current_env, self.v)
  104. self.v = NULL
  105. cpdef str(self):
  106. cdef emacs_env* env = get_env()
  107. cdef ptrdiff_t size = -1
  108. env.copy_string_contents(env, self.v, NULL, &size)
  109. cdef char* buf = <char*>malloc(size)
  110. if not env.copy_string_contents(env, self.v, buf, &size):
  111. raise TypeError('value is not a string')
  112. assert size > 0
  113. ret = buf[:size - 1].decode('utf8')
  114. free(buf)
  115. return ret
  116. cpdef int int(self) except *:
  117. cdef emacs_env* env = get_env()
  118. cdef intmax_t i = env.extract_integer(env, self.v)
  119. return i
  120. cpdef double float(self) except *:
  121. cdef emacs_env* env = get_env()
  122. cdef double i = env.extract_float(env, self.v)
  123. return i
  124. def sym_str(self):
  125. return _F().symbol_name(self).str()
  126. def type(self):
  127. cdef emacs_env* env = get_env()
  128. return EmacsValue.wrap(env.type_of(env, self.v)).sym_str()
  129. # i hate this joggling with pointers mixed with python syntax.
  130. # TODO: rewrite that in C with serious data validation?
  131. def as_list(self):
  132. cdef emacs_env* env = get_env()
  133. fcar = unwrap(sym("car"))
  134. fcdr = unwrap(sym("cdr"))
  135. is_false = unwrap(sym("null"))
  136. is_list = unwrap(sym("listp"))
  137. ret = list()
  138. cdef emacs_value cdr = self.v
  139. if not EmacsValue.wrap(env.funcall(env, is_list, 1, &cdr)).to_python_type():
  140. raise ValueError("Attepted list translation on non list")
  141. while True:
  142. car = env.funcall(env, fcar, 1, &cdr)
  143. cdr = env.funcall(env, fcdr, 1, &cdr)
  144. ret.append(EmacsValue.wrap(car).to_python_type())
  145. if EmacsValue.wrap(env.funcall(env, is_false, 1, &cdr)).to_python_type():
  146. break
  147. return ret
  148. def as_dict(self): # aka hash table
  149. cdef emacs_env* env = get_env()
  150. hash_table_to_lists = unwrap(sym("emacspy--hash-table-to-lists"))
  151. is_hash = unwrap(sym("hash-table-p"))
  152. cdef emacs_value hash_src = self.v
  153. if not EmacsValue.wrap(env.funcall(env, is_hash, 1, &hash_src)).to_python_type():
  154. raise ValueError("Attepted hash translation on non hash")
  155. keys, values = \
  156. EmacsValue.wrap(env.funcall(env, hash_table_to_lists, 1, &hash_src)).to_python_type()
  157. if keys and values:
  158. return dict(zip(keys, values))
  159. return dict()
  160. def to_python_type(self):
  161. my_type = self.type()
  162. if my_type == "string":
  163. return self.str()
  164. elif my_type == "integer":
  165. return self.int()
  166. elif my_type == "float":
  167. return self.float()
  168. elif my_type == "symbol":
  169. as_str = self.sym_str()
  170. if as_str == "nil":
  171. return False
  172. elif as_str == "t":
  173. return True
  174. elif my_type == "cons": # list
  175. return self.as_list()
  176. elif my_type == "hash-table":
  177. return self.as_dict()
  178. raise ValueError(f"Unable to export emacs value of type '{my_type} ({str(self)})'")
  179. def __str__(self):
  180. return _F().prin1_to_string(self).str()
  181. cdef emacs_value unwrap(obj) except *:
  182. if isinstance(obj, str):
  183. obj = string(obj)
  184. elif obj is True or obj is False: # test for bool must be before int
  185. obj = make_bool(obj)
  186. elif isinstance(obj, int):
  187. obj = make_int(obj)
  188. elif isinstance(obj, float):
  189. obj = make_float(obj)
  190. elif obj is None:
  191. obj = nil
  192. elif isinstance(obj, list):
  193. obj = make_list(obj)
  194. elif isinstance(obj, tuple):
  195. obj = make_list(list(obj))
  196. elif isinstance(obj, dict):
  197. obj = make_dict(obj)
  198. if isinstance(obj, EmacsValue):
  199. return (<EmacsValue>obj).v
  200. else:
  201. raise TypeError("cannot convert %s to emacs value" % type(obj))
  202. cpdef sym(str s):
  203. cdef emacs_env* env = get_env()
  204. return EmacsValue.wrap(env.intern(env, s.encode('utf8')))
  205. cdef emacs_value sym_ptr(str s):
  206. cdef emacs_env* env = get_env()
  207. return env.intern(env, s.encode('utf8'))
  208. cpdef string(str s):
  209. cdef emacs_env* env = get_env()
  210. s_utf8 = s.encode('utf8')
  211. return EmacsValue.wrap(env.make_string(env, s_utf8, len(s_utf8)))
  212. cpdef make_int(int i):
  213. cdef emacs_env* env = get_env()
  214. return EmacsValue.wrap(env.make_integer(env, i))
  215. cpdef make_float(double f):
  216. cdef emacs_env* env = get_env()
  217. return EmacsValue.wrap(env.make_float(env, f))
  218. cpdef make_bool(bool b):
  219. cdef emacs_env* env = get_env()
  220. if b:
  221. return EmacsValue.wrap(env.intern(env, "t".encode('utf8')))
  222. return nil
  223. cpdef make_list(list l):
  224. return funcall(sym("list"), l)
  225. cpdef make_dict(dict d):
  226. if not d:
  227. return funcall(sym("make-hash-table"), [])
  228. keys = []
  229. values = []
  230. for k, v in d.items():
  231. keys.append(k)
  232. values.append(v)
  233. return funcall(sym("emacspy--lists-to-hash-table"), [keys, values])
  234. cdef emacs_value string_ptr(str s):
  235. cdef emacs_env* env = get_env()
  236. s_utf8 = s.encode('utf8')
  237. return env.make_string(env, s_utf8, len(s_utf8))
  238. class EmacsError(Exception):
  239. def __init__(self, symbol, data):
  240. self.symbol = symbol
  241. self.data = data
  242. def __str__(self):
  243. return '%s: %s' % (self.symbol, self.data)
  244. cpdef EmacsValue funcall(f, args):
  245. args = list(args)
  246. cdef emacs_env* env = get_env()
  247. cdef cvarray arg_array = cvarray(shape=(max(1, len(args)), ), itemsize=sizeof(emacs_value), format="i")
  248. cdef emacs_value* arg_ptr = <emacs_value*>arg_array.data
  249. for i in range(len(args)):
  250. arg_ptr[i] = unwrap(args[i])
  251. cdef emacs_value f_val = unwrap(f)
  252. cdef int n = len(args)
  253. cdef emacs_value result
  254. with nogil:
  255. result = env.funcall(env, f_val, n, arg_ptr)
  256. cdef emacs_value exit_symbol
  257. cdef emacs_value exit_data
  258. cdef int has_err = env.non_local_exit_get(env, &exit_symbol, &exit_data)
  259. if has_err != 0:
  260. env.non_local_exit_clear(env)
  261. raise EmacsError(EmacsValue.wrap(exit_symbol).sym_str(), str(EmacsValue.wrap(exit_data)))
  262. return EmacsValue.wrap(result)
  263. cdef emacs_value call_python_object(emacs_env *env, ptrdiff_t nargs, emacs_value* args, void * data) noexcept with gil:
  264. global current_env
  265. cdef emacs_env* prev_env = current_env
  266. current_env = env
  267. obj = <object>(data)
  268. arg_list = []
  269. for i in range(nargs):
  270. arg_list.append(EmacsValue.wrap(args[i]))
  271. if _dealloc_queue:
  272. for item in _dealloc_queue:
  273. current_env.free_global_ref(current_env, (<_ForDealloc>item).v)
  274. _dealloc_queue[:] = []
  275. cdef emacs_value c_result
  276. try:
  277. result = obj(*arg_list)
  278. c_result = unwrap(result)
  279. except BaseException as exc:
  280. c_result = string_ptr("error")
  281. msg = type(exc).__name__ + ': ' + str(exc) + "\n" + traceback.format_exc()
  282. env.non_local_exit_signal(env, sym_ptr('python-exception'), string_ptr(msg))
  283. current_env = prev_env
  284. return c_result
  285. cpdef make_function(obj, str docstring=""):
  286. cdef emacs_env* env = get_env()
  287. _defined_functions.append(obj)
  288. return EmacsValue.wrap(env.make_function(env, 0, 99, call_python_object, docstring.encode('utf8'), <void*>obj))
  289. cdef public int plugin_is_GPL_compatible = 0
  290. eval_python_dict = {}
  291. def str_elisp2c(string):
  292. return str.encode(string.str())
  293. cdef extern from "subinterpreter.c":
  294. object make_interpreter(char*)
  295. object destroy_subinterpreter(char*)
  296. object list_subinterpreters()
  297. object run_string(char*, char*, object)
  298. object call_py(char*, object, object, object, object, object)
  299. object import_module(char*, object, object)
  300. object get_global_variable(char*, object)
  301. object get_object_attr(char*, object, object, object)
  302. object set_global(char*, object, object)
  303. def init():
  304. @defun('py-make-interpreter')
  305. def py_make_interpreter(interpreter_name):
  306. if not make_interpreter(str.encode(interpreter_name.str())):
  307. raise ValueError(f"Failed to create subinterpreter '{interpreter_name}'")
  308. return True
  309. @defun('py-destroy-interpreter')
  310. def py_destroy_interpreter(interpreter_name):
  311. ret = destroy_subinterpreter(str_elisp2c(interpreter_name))
  312. if isinstance(ret, BaseException):
  313. raise ret
  314. return ret
  315. @defun('py-list-interpreters')
  316. def py_list_interpreters():
  317. ret = list_subinterpreters()
  318. if isinstance(ret, BaseException):
  319. raise ret
  320. return ret
  321. @defun('py-run-string')
  322. def py_run_string(interpreter_name, run, target_name=''):
  323. if target_name and target_name.to_python_type():
  324. target_name = target_name.to_python_type()
  325. else:
  326. target_name = ''
  327. ret = run_string(str.encode(interpreter_name.str()), str.encode(run.str()), target_name)
  328. if isinstance(ret, BaseException):
  329. raise ret
  330. return ret
  331. @defun('exec-python')
  332. def exec_python(s):
  333. s = s.str()
  334. exec(s, eval_python_dict)
  335. @defun('eval-python')
  336. def eval_python(s):
  337. s = s.str()
  338. return eval(s, eval_python_dict)
  339. @defun('py-import')
  340. def py_import(interpreter_name, name, as_name=''):
  341. if not as_name:
  342. as_name = name
  343. ret = import_module(str_elisp2c(interpreter_name), name.to_python_type(), \
  344. as_name.to_python_type())
  345. if isinstance(ret, BaseException):
  346. raise ret
  347. return ret
  348. @defun('emacspy--call')
  349. def call_function_python(interpreter_name, object_name, method_name, target_name, args, kwargs):
  350. target_name_py: str = target_name.to_python_type() or ''
  351. args_py: tuple = tuple(args.to_python_type() or tuple())
  352. kwargs_py: dict = kwargs.to_python_type() or {}
  353. ret = call_py(str_elisp2c(interpreter_name), \
  354. object_name.to_python_type(), method_name.to_python_type(), \
  355. target_name_py, args_py, kwargs_py)
  356. if isinstance(ret, BaseException):
  357. raise ret
  358. return ret
  359. @defun('py-get-global-variable')
  360. def get_global_var(interpreter_name, var_name):
  361. ret = get_global_variable(str_elisp2c(interpreter_name), var_name.to_python_type())
  362. if isinstance(ret, BaseException):
  363. raise ret
  364. return ret
  365. @defun('py-get-object-attr')
  366. def get_obj_attr(interpreter_name, obj_name, attr_name, target_name=''):
  367. if target_name and target_name.to_python_type():
  368. target_name = target_name.to_python_type()
  369. else:
  370. target_name = ''
  371. ret = get_object_attr(str_elisp2c(interpreter_name), obj_name.to_python_type(), \
  372. attr_name.to_python_type(), target_name)
  373. if isinstance(ret, BaseException):
  374. raise ret
  375. return ret
  376. @defun('py-set-global')
  377. def py_set_global(interpreter_name, obj, target_name):
  378. ret = set_global(str_elisp2c(interpreter_name), obj.to_python_type(), \
  379. target_name.to_python_type())
  380. if isinstance(ret, BaseException):
  381. raise ret
  382. return ret
  383. _F().define_error(sym('python-exception'), "Python error")
  384. _F().provide(sym('emacspy-module'))
  385. cdef public int emacs_module_init_py(emacs_runtime* runtime):
  386. global current_env, nil
  387. cdef emacs_env* prev_env = current_env
  388. current_env = runtime.get_environment(runtime)
  389. nil = _V().nil
  390. init()
  391. current_env = prev_env
  392. return 0
  393. class _F:
  394. def __getattr__(self, name):
  395. name = name.replace('_', '-')
  396. def f(*args):
  397. return funcall(sym(name), args)
  398. return f
  399. def __getitem__(self, name):
  400. return getattr(self, name)
  401. # for calling Emacs functions
  402. f = _F()
  403. def defun(name, docstring=""):
  404. def wrapper(f):
  405. _F().fset(sym(name), make_function(f, docstring=docstring))
  406. return f
  407. return wrapper
  408. class _V:
  409. def __getattr__(self, name):
  410. name = name.replace('_', '-')
  411. return f.symbol_value(sym(name))
  412. def __setattr__(self, name, value):
  413. name = name.replace('_', '-')
  414. f.set(sym(name), value)
  415. def __getitem__(self, name):
  416. return getattr(self, name)
  417. def __setitem__(self, name, value):
  418. setattr(self, name, value)
  419. # for accessing Emacs variables
  420. v = _V()