quickjs_py交互.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # File : quickjs_py交互.py
  4. # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
  5. # Date : 2022/10/12
  6. from quickjs import Context, Object as QuickJSObject
  7. import json
  8. from pprint import pp
  9. from uuid import UUID
  10. from datetime import date, datetime
  11. # https://github.com/PetterS/quickjs/pull/82 py交互扩展
  12. # print(QuickJSObject)
  13. # QuickJSObject.set('a',1)
  14. # print(Context.get_global())
  15. # exit()
  16. class JS:
  17. interp = None
  18. # Store global variables here. Reference from javascript by path
  19. _globals = None
  20. # Used for generating unique ids in Context namespace
  21. _incr = 0
  22. # I cache the values passed from python to js. Otherwise, we create new representation
  23. # objects each time a value is referenced.
  24. _cache = None
  25. def __init__(self):
  26. self.interp = Context()
  27. self._globals = {}
  28. self._cache = {}
  29. # Install js proxy logic
  30. self.interp.add_callable("proxy_get", self.proxy_get)
  31. self.interp.add_callable("proxy_set", self.proxy_set)
  32. self.interp.eval("""
  33. var handler = {
  34. get(target, property) {
  35. rv = proxy_get(target.path, property)
  36. if (typeof rv == 'string' && rv.substr(0, 5) == 'eval:') {
  37. eval(rv.substr(5));
  38. return eval(rv.substr(5));
  39. }
  40. return rv
  41. },
  42. set(target, property, value) {
  43. return proxy_set(target.path, property, value)
  44. }
  45. }
  46. var mk_proxy = function(path) {
  47. return new Proxy({path: path}, handler);
  48. }
  49. """)
  50. def set(self, **kwargs):
  51. for (k, v) in kwargs.items():
  52. self.interp.set(k, v)
  53. def __call__(self, s):
  54. return self.interp.eval(s)
  55. # -----------------------------------------------------------------
  56. def to_non_proxied(self, v):
  57. # returns True/False and a value if the value can be represented
  58. # by a Javascript type (not proxied)
  59. if v in [None, True, False]:
  60. return True, v
  61. if type(v) in [QuickJSObject, str, int, float]:
  62. return True, v
  63. if type(v) in [UUID]:
  64. return True, str(v)
  65. return False, None
  66. def to_eval_str(self, v, path=None):
  67. # The value will be produced via eval if it is a string starting with eval:
  68. # Cache results
  69. if id(v) and id(v) in self._cache:
  70. return self._cache[id(v)]
  71. # If the value is a list, create a list of return values. Problem is
  72. # that these have no path in the self._globals dict. They will have to
  73. # be duplicated if they are objects.
  74. # BUG here - every reference to the list, create another copy - need to cache
  75. if type(v) == list:
  76. rv = []
  77. for v1 in v:
  78. can_non_proxy, non_proxied = self.to_non_proxied(v1)
  79. if can_non_proxy:
  80. self._incr += 1
  81. self.interp.set("_lv%s" % self._incr, v1)
  82. rv.append("_lv%s" % self._incr)
  83. else:
  84. rv.append(self.to_eval_str(v1))
  85. rv = "[" + ",".join(rv) + "]"
  86. self._cache[id(v)] = rv
  87. return rv
  88. if type(v) == date:
  89. rv = "new Date(%s, %s, %s)" % (v.year, v.month - 1, v.day)
  90. self._cache[id(v)] = rv
  91. return rv
  92. if type(v) == datetime:
  93. rv = "new Date('%s')" % v.isoformat()
  94. self._cache[id(v)] = rv
  95. return rv
  96. # this creates a function, which can never be garbage collected
  97. if callable(v):
  98. self._incr += 1
  99. gname = "_fn%s" % self._incr
  100. self.interp.add_callable(gname, v)
  101. rv = "%s" % gname
  102. self._cache[id(v)] = rv
  103. return rv
  104. # Anonymous variables are created by values inside lists
  105. if path is None:
  106. self._incr += 1
  107. path = "_anon%s" % self._incr
  108. self._globals[path] = v
  109. # I need to do this for objects and try getattr
  110. if type(v) == dict:
  111. rv = "mk_proxy('%s')" % path
  112. self._cache[id(v)] = rv
  113. return rv
  114. # Should be a user defined object to get here. Proxy it.
  115. rv = "mk_proxy('%s')" % path
  116. self._cache[id(v)] = rv
  117. return rv
  118. # -----------------------------------------------------------------
  119. # Proxy Callback Points
  120. def proxy_variable(self, **kwargs):
  121. for (k, v) in kwargs.items():
  122. self._globals[k] = v
  123. self.interp.set(k, None)
  124. js("""%s = mk_proxy("%s");""" % (k, k))
  125. def eval_path(self, path):
  126. parts = path.split(".")
  127. root = self._globals
  128. for part in parts:
  129. root = root[part]
  130. return root
  131. def proxy_get(self, path, property):
  132. # print(path, property)
  133. root = self.eval_path(path)
  134. try:
  135. rv = root.get(property, None)
  136. except:
  137. # Object
  138. rv = getattr(root, property)
  139. # print(path, property, rv)
  140. can_non_proxy, non_proxied = self.to_non_proxied(rv)
  141. if can_non_proxy:
  142. return rv
  143. new_path = path + "." + property
  144. estr = self.to_eval_str(rv, path=new_path)
  145. # print("eval:" + estr)
  146. return "eval:" + estr
  147. def proxy_set(self, path, property, value):
  148. # print(path, property, value)
  149. root = self.eval_path(path)
  150. root[property] = value
  151. if __name__ == '__main__':
  152. # Example access class attributes
  153. class example:
  154. a = "I am a"
  155. a1 = 111
  156. def fn(self, a='not set'):
  157. print("fn() called, a = ", a)
  158. # Example access dict
  159. l = {
  160. "a": 1,
  161. "fn": lambda: "XXXX",
  162. "p1": None,
  163. "p2": {
  164. "p3": "PPP333"
  165. },
  166. "p4": ["A", 4, None, example()],
  167. "p5": example()
  168. }
  169. js = JS()
  170. # Standard Variables
  171. js.set(v1="Set via python")
  172. print("v1 = ", js("v1"))
  173. assert (js("v1") == "Set via python")
  174. js.set(v2=None)
  175. print("v2 = ", js("v2"))
  176. assert (js("v2") is None)
  177. js.proxy_variable(l=l)
  178. # null
  179. print("p1 = ", js("l.p1"))
  180. assert (l['p1'] == js("l.p1"))
  181. # Access dict values
  182. print("l.a = ", js("l.a"))
  183. assert (l['a'] == js("l.a"))
  184. js("l.b = 4")
  185. print("l.b = ", js("l.b"))
  186. assert (l['b'] == 4)
  187. print("fn() = ", js("l.fn()"))
  188. # Undefined attribute
  189. print("l.undef = ", js("l.undef"))
  190. # Nested dict
  191. print("l.p2.p3 = ", js("l.p2.p3"))
  192. assert (l['p2']['p3'] == js("l.p2.p3"))
  193. # Dict assigned from JS - Need to use .json() to unwrap in Python
  194. js("l.c = {d: 4}")
  195. print("l.c = ", js("l.c"))
  196. print("l.c.d = ", js("l.c.d"))
  197. print("l.c = ", l['c'].json())
  198. # List
  199. print("l.p4[1] =", js("l.p4[1]"))
  200. assert (js("l.p4[1]") == l['p4'][1])
  201. print("calling l.p4[3].fn('called')")
  202. js("l.p4[3].fn('called')")
  203. # THIS FAILS - p4 was copied and the original variable is never referenced.
  204. js("l.p4.push('added')")
  205. print("l.p4 = ", l['p4'])
  206. # Python Object accesss
  207. print("l.p5 =", js("l.p5"))
  208. print("l.p5.a1 =", js("l.p5.a1"))
  209. assert (l['p5'].a1 == js("l.p5.a1"))
  210. print("calling l.p5.fn(444)")
  211. js("l.p5.fn(444)")
  212. # Print the global variables - will see anonymous variables
  213. pp(js._globals)