Apparcane

RobocraftPy

Sep 9th, 2024 (edited)
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.76 KB | Source Code | 0 0
  1. local _py = {
  2. cc_url = 'ws://pastebin.com/raw/KspT7QSZ/ws/',
  3. oc_host = '127.0.0.1',
  4. oc_port = 8001,
  5. proto_version = 5,
  6. event_sub = {},
  7. tasks = {},
  8. filters = {},
  9. coparams = {},
  10. modules = {},
  11. mcache = {},
  12. }
  13.  
  14. if type(getfenv) == 'function' then
  15. _py.genv = getfenv()
  16. elseif type(_ENV) == 'table' then
  17. _py.genv = _ENV
  18. elseif type(_G) == 'table' then
  19. _py.genv = _G -- TODO: necessary?
  20. else
  21. error('E001: Can\'t get environment')
  22. end
  23. -- TODO: rename temp to _pytemp
  24. _py.genv.temp = {}
  25. _py.genv._m = _py.modules
  26.  
  27. if type(loadstring) == 'function' then
  28. -- 5.1: prefer loadstring
  29. function _py.loadstring(source)
  30. local r, err = loadstring(source)
  31. if r ~= nil then setfenv(r, _py.genv) end
  32. return r, err
  33. end
  34. else
  35. -- 5.2+: load can deal with strings as well
  36. function _py.loadstring(source)
  37. return load(source, nil, nil, _py.genv)
  38. end
  39. end
  40.  
  41. if type(require) == 'function' then
  42. function _py.try_import(module, fn_name, save_as)
  43. local r, m = pcall(function() return require(module) end)
  44. if (not r
  45. or type(m) ~= 'table'
  46. or type(m[fn_name]) ~= 'function') then return false end
  47. if save_as == nil then
  48. _py._impfn = m[fn_name]
  49. else
  50. _py[save_as] = m[fn_name]
  51. end
  52. return true
  53. end
  54. else
  55. function _py.try_import() return false end
  56. end
  57.  
  58. function _py.loadmethod(code)
  59. -- 0..N R:module: -- require(module)
  60. -- 0..1 G:module: -- use builtin module
  61. -- choice:
  62. -- M:method$ -- take method of loaded module
  63. -- code$ -- arbitrary code
  64. if _py.mcache[code] ~= nil then return _py.mcache[code] end
  65.  
  66. local mod, modname
  67. while true do
  68. local _, _, rmod, mcode = string.find(code, '^R:(%a%w*):(.*)$')
  69. if rmod == nil then break end
  70. if _py.modules[rmod] == nil then
  71. local r, v = pcall(require, rmod)
  72. if not r then return nil, 'module not found' end
  73. _py.modules[rmod] = v
  74. end
  75. mod, code = _py.modules[rmod], mcode
  76. end
  77. do
  78. local _, _, rmod, mcode = string.find(code, '^G:(%a%w*):(.*)$')
  79. if rmod ~= nil then
  80. mod, code = _py.genv[rmod], mcode
  81. if mod == nil then return nil, 'module not found' end
  82. end
  83. end
  84.  
  85. local fn
  86. do
  87. local _, _, meth = string.find(code, '^M:(%a%w*)$')
  88. if meth ~= nil then
  89. fn = mod[meth]
  90. if fn == nil then return nil, 'method not found' end
  91. else
  92. local err
  93. fn, err = _py.loadstring(code)
  94. if not fn then return nil, err end
  95. end
  96. end
  97. _py.mcache[code] = fn
  98. return fn
  99. end
  100.  
  101. if type(os) == 'table' and type(os.pullEvent) == 'function' then
  102. _py.pullEvent = os.pullEvent -- computercraft
  103. elseif _py.try_import('event', 'pull') then
  104. _py.pullEvent = _py._impfn -- opencomputers
  105. else
  106. error('E002: Can\'t detect pullEvent method')
  107. end
  108.  
  109. if type(arg) == 'table' then
  110. _py.argv = arg -- TODO: remove?
  111. else
  112. _py.argv = {...}
  113. end
  114.  
  115. do
  116. local function s_rec(v, tracking)
  117. local t = type(v)
  118. if v == nil then
  119. return 'N'
  120. elseif v == false then
  121. return 'F'
  122. elseif v == true then
  123. return 'T'
  124. elseif t == 'number' then
  125. return '[' .. tostring(v) .. ']'
  126. elseif t == 'string' then
  127. return string.format('<%u>', #v) .. v
  128. elseif t == 'table' then
  129. if tracking[v] ~= nil then
  130. error('Cannot serialize table with recursive entries', 0)
  131. end
  132. tracking[v] = true
  133. local r = '{'
  134. for k, x in pairs(v) do
  135. r = r .. ':' .. s_rec(k, tracking) .. s_rec(x, tracking)
  136. end
  137. return r .. '}'
  138. else
  139. error('Cannot serialize type ' .. t, 0)
  140. end
  141. end
  142. _py.serialize = function(v) return s_rec(v, {}) end
  143. end
  144.  
  145. function _py.create_stream(s, idx)
  146. if idx == nil then idx = 1 end
  147. return {
  148. getidx=function() return idx end,
  149. isend=function() return idx > #s end,
  150. fixed=function(n)
  151. local r = s:sub(idx, idx + n - 1)
  152. if #r ~= n then error('Unexpected end of stream') end
  153. idx = idx + n
  154. return r
  155. end,
  156. tostop=function(sym)
  157. local newidx = s:find(sym, idx, true)
  158. if newidx == nil then error('Unexpected end of stream') end
  159. local r = s:sub(idx, newidx - 1)
  160. idx = newidx + 1
  161. return r
  162. end,
  163. }
  164. end
  165.  
  166. function _py.deserialize(stream)
  167. local tok = stream.fixed(1)
  168. if tok == 'N' then
  169. return nil
  170. elseif tok == 'F' then
  171. return false
  172. elseif tok == 'T' then
  173. return true
  174. elseif tok == '[' then
  175. return tonumber(stream.tostop(']'))
  176. elseif tok == '<' then
  177. local slen = tonumber(stream.tostop('>'))
  178. return stream.fixed(slen)
  179. elseif tok == 'E' then
  180. -- same as string (<), but intended for evaluation
  181. local slen = tonumber(stream.tostop('>'))
  182. local fn = assert(_py.loadstring(stream.fixed(slen)))
  183. return fn()
  184. elseif tok == '{' then
  185. local r = {}
  186. while true do
  187. tok = stream.fixed(1)
  188. if tok == ':' then
  189. local key = _py.deserialize(stream)
  190. r[key] = _py.deserialize(stream)
  191. else break end
  192. end
  193. return r
  194. else
  195. error('Unknown token ' .. tok)
  196. end
  197. end
  198.  
  199. function _py.drop_task(task_id)
  200. _py.tasks[task_id] = nil
  201. _py.filters[task_id] = nil
  202. _py.coparams[task_id] = nil
  203. end
  204.  
  205. -- nil-safe
  206. if type(table.maxn) == 'function' then
  207. function _py.safe_unpack(a)
  208. return table.unpack(a, 1, table.maxn(a))
  209. end
  210. else
  211. function _py.safe_unpack(a)
  212. local maxn = #a
  213. -- TODO: better solution?
  214. for k in pairs(a) do
  215. if type(k) == 'number' and k > maxn then maxn = k end
  216. end
  217. return table.unpack(a, 1, maxn)
  218. end
  219. end
  220.  
  221. if type(http) == 'table' and type(http.websocket) == 'function' then
  222. function _py.start_connection()
  223. local ws = http.websocket(_py.cc_url)
  224. if not ws then
  225. error('Unable to connect to server ' .. _py.cc_url)
  226. end
  227. _py.ws = {
  228. send = function(m) return ws.send(m, true) end,
  229. close = function() ws.close() end,
  230. }
  231. end
  232. elseif _py.try_import('internet', 'socket', 'oc_connect') then
  233. function _py.start_connection()
  234. local s = _py.oc_connect(_py.oc_host, _py.oc_port)
  235. if not s or s.socket.finishConnect() == nil then
  236. error('Unable to connect to server ' .. _py.oc_host .. ':' .. _py.oc_port)
  237. end
  238. local bit32 = require('bit32')
  239. local buf = ''
  240. _py.ws = {
  241. send = function(frame)
  242. local z = #frame
  243. s.socket.write(string.char(
  244. bit32.band(bit32.rshift(z, 16), 255),
  245. bit32.band(bit32.rshift(z, 8), 255),
  246. bit32.band(z, 255)
  247. ))
  248. s.socket.write(frame)
  249. end,
  250. close = function() s.socket.close() end,
  251. read_pending = function(frame_callback)
  252. local inc = s.socket.read()
  253. if inc == nil then
  254. return true, 'Connection with server has been closed'
  255. end
  256. buf = buf .. inc
  257. while #buf >= 3 do
  258. local frame_size = (
  259. bit32.lshift(string.byte(buf, 1), 16)
  260. + bit32.lshift(string.byte(buf, 2), 8)
  261. + string.byte(buf, 3))
  262. if #buf < frame_size + 3 then break end
  263. if frame_callback(string.sub(buf, 4, 3 + frame_size)) then
  264. return true, nil
  265. end
  266. buf = string.sub(buf, 4 + frame_size)
  267. end
  268. return false
  269. end,
  270. }
  271. end
  272. else
  273. error('E003: Can\'t detect connection method')
  274. end
  275.  
  276. function _py.ws_send(action, ...)
  277. local m = action
  278. for _, v in ipairs({...}) do
  279. m = m .. _py.serialize(v)
  280. end
  281. _py.ws.send(m)
  282. end
  283.  
  284. function _py.exec_python_directive(dstring)
  285. local msg = _py.create_stream(dstring)
  286. local action = msg.fixed(1)
  287.  
  288. if action == 'T' or action == 'I' then -- new task
  289. local task_id = _py.deserialize(msg)
  290. local code = _py.deserialize(msg)
  291. local params = _py.deserialize(msg)
  292.  
  293. local fn, err = _py.loadmethod(code)
  294. if fn == nil then
  295. -- couldn't compile
  296. _py.ws_send('T', task_id, _py.serialize{false, err})
  297. else
  298. if action == 'I' then
  299. _py.ws_send('T', task_id, _py.serialize{fn(_py.safe_unpack(params))})
  300. else
  301. _py.tasks[task_id] = coroutine.create(fn)
  302. _py.coparams[task_id] = params
  303. end
  304. end
  305. elseif action == 'D' then -- drop tasks
  306. while not msg.isend() do
  307. _py.drop_task(_py.deserialize(msg))
  308. end
  309. elseif action == 'S' or action == 'U' then -- (un)subscribe to event
  310. local event = _py.deserialize(msg)
  311. if action == 'S' then
  312. _py.event_sub[event] = true
  313. else
  314. _py.event_sub[event] = nil
  315. end
  316. elseif action == 'C' then -- close session
  317. local err = _py.deserialize(msg)
  318. if err ~= nil then
  319. io.stderr:write(err .. '\n')
  320. end
  321. return true
  322. end
  323. end
  324.  
  325. function _py.resume_coros(event, p1, p2, p3, p4, p5)
  326. local del_tasks = {}
  327. for task_id in pairs(_py.tasks) do
  328. if _py.filters[task_id] == nil or _py.filters[task_id] == event then
  329. local r
  330. if _py.coparams[task_id] ~= nil then
  331. r = {coroutine.resume(
  332. _py.tasks[task_id],
  333. _py.safe_unpack(_py.coparams[task_id]))}
  334. _py.coparams[task_id] = nil
  335. else
  336. r = {coroutine.resume(
  337. _py.tasks[task_id],
  338. event, p1, p2, p3, p4, p5)}
  339. end
  340. if coroutine.status(_py.tasks[task_id]) == 'dead' then
  341. _py.ws_send('T', task_id, _py.serialize(r))
  342. del_tasks[task_id] = true
  343. else
  344. if r[1] == true then
  345. _py.filters[task_id] = r[2]
  346. else
  347. _py.filters[task_id] = nil
  348. end
  349. end
  350. end
  351. end
  352. for task_id in pairs(del_tasks) do _py.drop_task(task_id) end
  353. end
  354.  
  355. if type(fs) == 'table' and type(fs.combine) == 'function' then
  356. function _py.start_program(name)
  357. local path = fs.combine(shell.dir(), name)
  358. if not fs.exists(path) then return nil end
  359. if fs.isDir(path) then return nil end
  360. local f = fs.open(path, 'r')
  361. local code = f.readAll()
  362. f.close()
  363. return path, code
  364. end
  365. else
  366. function _py.start_program(name)
  367. local filesystem = require('filesystem')
  368. local shell = require('shell')
  369. local path = filesystem.concat(shell.getWorkingDirectory(), name)
  370. if not filesystem.exists(path) then return nil end
  371. if filesystem.isDirectory(path) then return nil end
  372. local f = io.open(path, 'rb')
  373. local code = f:read('*a')
  374. f:close()
  375. return path, code
  376. end
  377. end
  378.  
  379. _py.start_connection()
  380. do
  381. local path, code = nil, nil
  382. if _py.argv[1] ~= nil then
  383. path, code = _py.start_program(_py.argv[1])
  384. if path == nil then error('Program not found') end
  385. end
  386. _py.ws_send('0', _py.proto_version, _py.argv, path, code)
  387. end
  388. while true do
  389. local event, p1, p2, p3, p4, p5 = _py.pullEvent()
  390. if event == 'websocket_message' then
  391. -- TODO: filter by address
  392. if _py.exec_python_directive(p2) then break end
  393. elseif event == 'websocket_closed' then
  394. -- TODO: filter by address
  395. error('Connection with server has been closed')
  396. elseif event == 'internet_ready' then
  397. -- TODO: filter by address
  398. local must_exit, err = _py.ws.read_pending(_py.exec_python_directive)
  399. if must_exit then
  400. if err == nil then break else error(err) end
  401. end
  402. elseif _py.event_sub[event] == true then
  403. _py.ws_send('E', event, {p1, p2, p3, p4, p5})
  404. end
  405. _py.resume_coros(event, p1, p2, p3, p4, p5)
  406. end
  407. _py.ws.close()
Add Comment
Please, Sign In to add comment